mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-23 06:11:58 +00:00
feat: implement authentication provider management UI and functionality
- Added new UI components for managing authentication providers, including forms for adding, editing, and deleting providers. - Integrated drag-and-drop functionality for reordering providers in the settings interface. - Enhanced user experience with modals for confirming deletions and displaying callback URLs. - Updated translation files to include new keys for authentication provider management, ensuring localization support. - Refactored existing components to streamline the integration of new provider management features.
This commit is contained in:
@@ -1415,5 +1415,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "الملفات المستلمة"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "مزودي المصادقة",
|
||||
"description": "تكوين مزودي المصادقة الخارجيين لتسجيل الدخول الموحد",
|
||||
"enabledCount": "{count} مفعل",
|
||||
"loadingProviders": "جاري تحميل المزودين...",
|
||||
"providersConfigured": "{count} مزود تم تكوينه",
|
||||
"enabledOfTotal": "{enabled} مفعل من {total} مزود",
|
||||
"hideDisabledProviders": "إخفاء المزودين غير المفعلين",
|
||||
"addProvider": "إضافة مزود",
|
||||
"addProviderTitle": "إضافة مزود",
|
||||
"editProvider": "تحرير المزود",
|
||||
"deleteProvider": "حذف المزود",
|
||||
"enabled": "مفعل",
|
||||
"disabled": "معطل",
|
||||
"officialProvider": "مزود رسمي",
|
||||
"dragToReorder": "اسحب لإعادة الترتيب",
|
||||
"dragDisabledMessage": "السحب والإفلات معطل عند تصفية المزودين. اعرض جميع المزودين لإعادة ترتيبهم.",
|
||||
"dragEnabledMessage": "اسحب المزودين لإعادة ترتيبهم. سينعكس هذا الترتيب على صفحة تسجيل الدخول.",
|
||||
"noProvidersEnabled": "لا توجد مزودي مصادقة مفعلين",
|
||||
"noProvidersConfigured": "لم يتم تكوين أي مزودي مصادقة",
|
||||
"form": {
|
||||
"providerName": "اسم المزود",
|
||||
"providerNamePlaceholder": "مثال: شركتي",
|
||||
"displayName": "الاسم المعروض",
|
||||
"displayNamePlaceholder": "مثال: تسجيل دخول موحد لشركتي",
|
||||
"type": "النوع",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "الأيقونة",
|
||||
"iconPlaceholder": "اختر أيقونة",
|
||||
"clientId": "معرف العميل",
|
||||
"clientIdPlaceholder": "معرف عميل OAuth الخاص بك",
|
||||
"clientSecret": "سر العميل",
|
||||
"clientSecretPlaceholder": "سر عميل OAuth الخاص بك",
|
||||
"oauthScopes": "نطاقات OAuth",
|
||||
"scopesPlaceholder": "أدخل النطاقات (مثل: openid, profile, email)",
|
||||
"scopesHelpOidc": "النطاقات مقترحة تلقائياً بناءً على رابط المزود. نطاقات OIDC الشائعة: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "النطاقات مقترحة تلقائياً بناءً على رابط المزود. نطاقات OAuth2 الشائعة تعتمد على المزود",
|
||||
"providerUrl": "رابط المزود",
|
||||
"providerUrlPlaceholder": "https://auth.example.com",
|
||||
"providerUrlAutoPlaceholder": "https://your-provider.com (سيتم اكتشاف نقاط النهاية تلقائياً)",
|
||||
"providerUrlManualPlaceholder": "https://your-provider.com",
|
||||
"autoDiscoveryHelp": "سيكتشف النظام تلقائياً نقاط نهاية التفويض والرمز ومعلومات المستخدم",
|
||||
"manualConfigurationHelp": "الرابط الأساسي للمزود الخاص بك (ستكون نقاط النهاية نسبية لهذا)",
|
||||
"authorizationEndpoint": "نقطة نهاية التفويض",
|
||||
"authorizationEndpointPlaceholder": "https://auth.example.com/auth",
|
||||
"tokenEndpoint": "نقطة نهاية الرمز",
|
||||
"tokenEndpointPlaceholder": "https://auth.example.com/token",
|
||||
"userInfoEndpoint": "نقطة نهاية معلومات المستخدم",
|
||||
"userInfoEndpointPlaceholder": "https://auth.example.com/userinfo",
|
||||
"configurationMethod": "طريقة التكوين",
|
||||
"autoDiscovery": "اكتشاف تلقائي (موصى به)",
|
||||
"autoDiscoveryDescription": "اكتشاف نقاط النهاية تلقائياً من رابط المزود",
|
||||
"manualEndpoints": "نقاط نهاية يدوية",
|
||||
"manualEndpointsDescription": "تكوين نقاط نهاية التفويض والرمز ومعلومات المستخدم يدوياً",
|
||||
"callbackUrl": "رابط الاستجابة",
|
||||
"callbackUrlDescription": "استخدم هذا الرابط في تكوين مزود OAuth الخاص بك",
|
||||
"copyCallbackUrl": "نسخ رابط الاستجابة",
|
||||
"callbackUrlCopied": "تم نسخ رابط الاستجابة إلى الحافظة!",
|
||||
"adminEmailDomains": "نطاقات البريد الإلكتروني للمسؤولين",
|
||||
"adminEmailDomainsPlaceholder": "أدخل النطاقات (مثل: admin.company.com)",
|
||||
"adminEmailDomainsHelp": "سيتم منح المستخدمين الذين لديهم بريد إلكتروني من هذه النطاقات صلاحيات المسؤول",
|
||||
"autoRegister": "تسجيل المستخدمين الجدد تلقائياً",
|
||||
"officialProviderUrlPlaceholder": "استبدل العنصر النائب برابط {displayName} الخاص بك",
|
||||
"officialProviderHelp": "هذا مزود رسمي. نقاط النهاية مكونة مسبقاً. يمكنك تحرير هذا الرابط فقط.",
|
||||
"officialProviderIconHelp": "يمكنك تخصيص الأيقونة لهذا المزود الرسمي."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "إلغاء",
|
||||
"save": "حفظ",
|
||||
"saving": "جاري الحفظ...",
|
||||
"adding": "جاري الإضافة...",
|
||||
"updating": "جاري التحديث...",
|
||||
"saveProvider": "حفظ المزود",
|
||||
"delete": "حذف",
|
||||
"deleting": "جاري الحذف...",
|
||||
"edit": "تحرير",
|
||||
"enable": "تفعيل",
|
||||
"disable": "تعطيل"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "تمت إضافة المزود بنجاح",
|
||||
"providerUpdated": "تم تحديث المزود بنجاح",
|
||||
"providerDeleted": "تم حذف المزود بنجاح",
|
||||
"providerOrderUpdated": "تم تحديث ترتيب المزود بنجاح",
|
||||
"fillRequiredFields": "يرجى ملء جميع الحقول المطلوبة (الاسم، الاسم المعروض، معرف العميل، سر العميل)",
|
||||
"provideUrlOrEndpoints": "قم بتوفير رابط المزود للاكتشاف التلقائي أو جميع نقاط النهاية الثلاث المخصصة",
|
||||
"chooseDiscoveryOrManual": "اختر إما الاكتشاف التلقائي (رابط المزود) أو نقاط النهاية اليدوية، وليس كليهما",
|
||||
"loadFailed": "فشل تحميل المزودين",
|
||||
"addFailed": "فشل إضافة المزود",
|
||||
"updateFailed": "فشل تحديث المزود",
|
||||
"deleteFailed": "فشل حذف المزود",
|
||||
"orderUpdateFailed": "فشل تحديث ترتيب المزود"
|
||||
},
|
||||
"info": {
|
||||
"title": "معلومات",
|
||||
"officialProvidersRecommended": "للحصول على وظائف أفضل، نوصي باستخدام المزودين الرسميين. إذا واجهت مشاكل مع مزود مخصص، فكر في فتح مشكلة على",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "مزود رسمي",
|
||||
"officialProviderDescription": "هذا المزود محسن من قبل Palmr. يمكن تعديل بيانات الاعتماد والتكوين فقط.",
|
||||
"manualConfigTitle": "التكوين اليدوي",
|
||||
"manualConfigDescription": "أنت تقوم بتوفير جميع نقاط النهاية يدوياً. تأكد من صحتها لمزودك."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "حذف مزود المصادقة",
|
||||
"confirmMessage": "هل أنت متأكد أنك تريد حذف مزود \"{displayName}\"؟ لا يمكن التراجع عن هذا الإجراء.",
|
||||
"providerId": "معرف المزود: {name}",
|
||||
"cancel": "إلغاء",
|
||||
"delete": "حذف المزود",
|
||||
"deleting": "جاري الحذف..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "Empfangene Dateien"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "Authentifizierungsanbieter",
|
||||
"description": "Externe Authentifizierungsanbieter für SSO konfigurieren",
|
||||
"enabledCount": "{count} aktiviert",
|
||||
"loadingProviders": "Anbieter werden geladen...",
|
||||
"providersConfigured": "{count} Anbieter konfiguriert",
|
||||
"enabledOfTotal": "{enabled} von {total} Anbietern aktiviert",
|
||||
"hideDisabledProviders": "Deaktivierte Anbieter ausblenden",
|
||||
"addProvider": "Anbieter hinzufügen",
|
||||
"addProviderTitle": "Anbieter hinzufügen",
|
||||
"editProvider": "Anbieter bearbeiten",
|
||||
"deleteProvider": "Anbieter löschen",
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert",
|
||||
"officialProvider": "Offizieller Anbieter",
|
||||
"dragToReorder": "Zum Neuordnen ziehen",
|
||||
"dragDisabledMessage": "Drag & Drop ist bei gefilterten Anbietern deaktiviert. Zeigen Sie alle Anbieter an, um sie neu zu ordnen.",
|
||||
"dragEnabledMessage": "Ziehen Sie Anbieter, um sie neu zu ordnen. Diese Reihenfolge wird auf der Anmeldeseite angezeigt.",
|
||||
"noProvidersEnabled": "Keine Authentifizierungsanbieter aktiviert",
|
||||
"noProvidersConfigured": "Keine Authentifizierungsanbieter konfiguriert",
|
||||
"form": {
|
||||
"providerName": "Anbietername",
|
||||
"providerNamePlaceholder": "z.B. meinefirma",
|
||||
"displayName": "Anzeigename",
|
||||
"displayNamePlaceholder": "z.B. Meine Firma SSO",
|
||||
"type": "Typ",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "Symbol",
|
||||
"iconPlaceholder": "Symbol auswählen",
|
||||
"clientId": "Client-ID",
|
||||
"clientIdPlaceholder": "Ihre OAuth-Client-ID",
|
||||
"clientSecret": "Client-Secret",
|
||||
"clientSecretPlaceholder": "Ihr OAuth-Client-Secret",
|
||||
"oauthScopes": "OAuth-Scopes",
|
||||
"scopesPlaceholder": "Scopes eingeben (z.B. openid, profile, email)",
|
||||
"scopesHelpOidc": "Scopes werden basierend auf der Anbieter-URL automatisch vorgeschlagen. Übliche OIDC-Scopes: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "Scopes werden basierend auf der Anbieter-URL automatisch vorgeschlagen. Übliche OAuth2-Scopes hängen vom Anbieter ab",
|
||||
"providerUrl": "Anbieter-URL",
|
||||
"providerUrlPlaceholder": "https://auth.beispiel.de",
|
||||
"providerUrlAutoPlaceholder": "https://ihr-anbieter.de (Endpunkte werden automatisch erkannt)",
|
||||
"providerUrlManualPlaceholder": "https://ihr-anbieter.de",
|
||||
"autoDiscoveryHelp": "Das System erkennt automatisch die Autorisierungs-, Token- und Userinfo-Endpunkte",
|
||||
"manualConfigurationHelp": "Basis-URL Ihres Anbieters (Endpunkte werden relativ dazu angegeben)",
|
||||
"authorizationEndpoint": "Autorisierungs-Endpunkt",
|
||||
"authorizationEndpointPlaceholder": "https://auth.beispiel.de/auth",
|
||||
"tokenEndpoint": "Token-Endpunkt",
|
||||
"tokenEndpointPlaceholder": "https://auth.beispiel.de/token",
|
||||
"userInfoEndpoint": "Benutzerinfo-Endpunkt",
|
||||
"userInfoEndpointPlaceholder": "https://auth.beispiel.de/userinfo",
|
||||
"configurationMethod": "Konfigurationsmethode",
|
||||
"autoDiscovery": "Automatische Erkennung (Empfohlen)",
|
||||
"autoDiscoveryDescription": "Endpunkte automatisch von der Anbieter-URL erkennen",
|
||||
"manualEndpoints": "Manuelle Endpunkte",
|
||||
"manualEndpointsDescription": "Autorisierungs-, Token- und Benutzerinfo-Endpunkte manuell konfigurieren",
|
||||
"callbackUrl": "Callback-URL",
|
||||
"callbackUrlDescription": "Verwenden Sie diese URL in Ihrer OAuth-Anbieterkonfiguration",
|
||||
"copyCallbackUrl": "Callback-URL kopieren",
|
||||
"callbackUrlCopied": "Callback-URL in die Zwischenablage kopiert!",
|
||||
"adminEmailDomains": "Admin-E-Mail-Domains",
|
||||
"adminEmailDomainsPlaceholder": "Domains eingeben (z.B. admin.firma.de)",
|
||||
"adminEmailDomainsHelp": "Benutzer mit E-Mails von diesen Domains erhalten Administratorrechte",
|
||||
"autoRegister": "Neue Benutzer automatisch registrieren",
|
||||
"officialProviderUrlPlaceholder": "Ersetzen Sie den Platzhalter mit Ihrer {displayName}-URL",
|
||||
"officialProviderHelp": "Dies ist ein offizieller Anbieter. Die Endpunkte sind vorkonfiguriert. Sie können nur diese URL bearbeiten.",
|
||||
"officialProviderIconHelp": "Sie können das Symbol für diesen offiziellen Anbieter anpassen."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "Abbrechen",
|
||||
"save": "Speichern",
|
||||
"saving": "Wird gespeichert...",
|
||||
"adding": "Wird hinzugefügt...",
|
||||
"updating": "Wird aktualisiert...",
|
||||
"saveProvider": "Anbieter speichern",
|
||||
"delete": "Löschen",
|
||||
"deleting": "Wird gelöscht...",
|
||||
"edit": "Bearbeiten",
|
||||
"enable": "Aktivieren",
|
||||
"disable": "Deaktivieren"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "Anbieter erfolgreich hinzugefügt",
|
||||
"providerUpdated": "Anbieter erfolgreich aktualisiert",
|
||||
"providerDeleted": "Anbieter erfolgreich gelöscht",
|
||||
"providerOrderUpdated": "Anbieterreihenfolge erfolgreich aktualisiert",
|
||||
"fillRequiredFields": "Bitte füllen Sie alle erforderlichen Felder aus (Name, Anzeigename, Client-ID, Client-Secret)",
|
||||
"provideUrlOrEndpoints": "Geben Sie entweder eine Anbieter-URL für die automatische Erkennung ODER alle drei benutzerdefinierten Endpunkte an",
|
||||
"chooseDiscoveryOrManual": "Wählen Sie entweder die automatische Erkennung (Anbieter-URL) ODER manuelle Endpunkte, nicht beides",
|
||||
"loadFailed": "Fehler beim Laden der Anbieter",
|
||||
"addFailed": "Fehler beim Hinzufügen des Anbieters",
|
||||
"updateFailed": "Fehler beim Aktualisieren des Anbieters",
|
||||
"deleteFailed": "Fehler beim Löschen des Anbieters",
|
||||
"orderUpdateFailed": "Fehler beim Aktualisieren der Anbieterreihenfolge"
|
||||
},
|
||||
"info": {
|
||||
"title": "Information",
|
||||
"officialProvidersRecommended": "Für bessere Funktionalität empfehlen wir die Verwendung offizieller Anbieter. Bei Problemen mit einem benutzerdefinierten Anbieter können Sie ein Issue auf",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "Offizieller Anbieter",
|
||||
"officialProviderDescription": "Dieser Anbieter ist von Palmr optimiert. Nur Zugangsdaten und Konfiguration können geändert werden.",
|
||||
"manualConfigTitle": "Manuelle Konfiguration",
|
||||
"manualConfigDescription": "Sie geben alle Endpunkte manuell an. Stellen Sie sicher, dass sie für Ihren Anbieter korrekt sind."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Authentifizierungsanbieter löschen",
|
||||
"confirmMessage": "Sind Sie sicher, dass Sie den Anbieter \"{displayName}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"providerId": "Anbieter-ID: {name}",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Anbieter löschen",
|
||||
"deleting": "Wird gelöscht..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
"cancelEdit": "Cancel edit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "Authentication Providers",
|
||||
"description": "Configure external authentication providers for SSO",
|
||||
"enabledCount": "{count} enabled",
|
||||
"loadingProviders": "Loading providers...",
|
||||
"providersConfigured": "{count} providers configured",
|
||||
"enabledOfTotal": "{enabled} enabled of {total} providers",
|
||||
"hideDisabledProviders": "Hide disabled providers",
|
||||
"addProvider": "Add Provider",
|
||||
"addProviderTitle": "Add Provider",
|
||||
"editProvider": "Edit Provider",
|
||||
"deleteProvider": "Delete Provider",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"officialProvider": "Official Provider",
|
||||
"dragToReorder": "Drag to reorder",
|
||||
"dragDisabledMessage": "Drag and drop is disabled when filtering providers. Show all providers to reorder them.",
|
||||
"dragEnabledMessage": "Drag providers to reorder them. This order will be reflected on the login page.",
|
||||
"noProvidersEnabled": "No enabled authentication providers",
|
||||
"noProvidersConfigured": "No authentication providers configured",
|
||||
"form": {
|
||||
"providerName": "Provider Name",
|
||||
"providerNamePlaceholder": "e.g., mycompany",
|
||||
"displayName": "Display Name",
|
||||
"displayNamePlaceholder": "e.g., My Company SSO",
|
||||
"type": "Type",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "Icon",
|
||||
"iconPlaceholder": "Select an icon",
|
||||
"clientId": "Client ID",
|
||||
"clientIdPlaceholder": "Your OAuth client ID",
|
||||
"clientSecret": "Client Secret",
|
||||
"clientSecretPlaceholder": "Your OAuth client secret",
|
||||
"oauthScopes": "OAuth Scopes",
|
||||
"scopesPlaceholder": "Enter scopes (e.g., openid, profile, email)",
|
||||
"scopesHelpOidc": "Scopes auto-suggested based on Provider URL. Common OIDC scopes: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "Scopes auto-suggested based on Provider URL. Common OAuth2 scopes depend on the provider",
|
||||
"providerUrl": "Provider URL",
|
||||
"providerUrlPlaceholder": "https://auth.example.com",
|
||||
"providerUrlAutoPlaceholder": "https://your-provider.com (endpoints will be discovered automatically)",
|
||||
"providerUrlManualPlaceholder": "https://your-provider.com",
|
||||
"autoDiscoveryHelp": "The system will automatically discover authorization, token, and userinfo endpoints",
|
||||
"manualConfigurationHelp": "Base URL of your provider (endpoints will be relative to this)",
|
||||
"authorizationEndpoint": "Authorization Endpoint",
|
||||
"authorizationEndpointPlaceholder": "https://auth.example.com/auth",
|
||||
"tokenEndpoint": "Token Endpoint",
|
||||
"tokenEndpointPlaceholder": "https://auth.example.com/token",
|
||||
"userInfoEndpoint": "User Info Endpoint",
|
||||
"userInfoEndpointPlaceholder": "https://auth.example.com/userinfo",
|
||||
"configurationMethod": "Configuration Method",
|
||||
"autoDiscovery": "Auto-discovery (Recommended)",
|
||||
"autoDiscoveryDescription": "Automatically discover endpoints from Provider URL",
|
||||
"manualEndpoints": "Manual Endpoints",
|
||||
"manualEndpointsDescription": "Manually configure authorization, token, and user info endpoints",
|
||||
"callbackUrl": "Callback URL",
|
||||
"callbackUrlDescription": "Use this URL in your OAuth provider configuration",
|
||||
"copyCallbackUrl": "Copy Callback URL",
|
||||
"callbackUrlCopied": "Callback URL copied to clipboard!",
|
||||
"adminEmailDomains": "Admin Email Domains",
|
||||
"adminEmailDomainsPlaceholder": "Enter domains (e.g., admin.company.com)",
|
||||
"adminEmailDomainsHelp": "Users with emails from these domains will be granted admin privileges",
|
||||
"autoRegister": "Auto-register new users",
|
||||
"officialProviderUrlPlaceholder": "Replace placeholder with your {displayName} URL",
|
||||
"officialProviderHelp": "This is an official provider. Endpoints are pre-configured. You can edit just this URL.",
|
||||
"officialProviderIconHelp": "You can customize the icon for this official provider."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"adding": "Adding...",
|
||||
"updating": "Updating...",
|
||||
"saveProvider": "Save Provider",
|
||||
"delete": "Delete",
|
||||
"deleting": "Deleting...",
|
||||
"edit": "Edit",
|
||||
"enable": "Enable",
|
||||
"disable": "Disable"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "Provider added successfully",
|
||||
"providerUpdated": "Provider updated successfully",
|
||||
"providerDeleted": "Provider deleted successfully",
|
||||
"providerOrderUpdated": "Provider order updated successfully",
|
||||
"fillRequiredFields": "Please fill in all required fields (name, display name, client ID, client secret)",
|
||||
"provideUrlOrEndpoints": "Either provide a Provider URL for automatic discovery OR all three custom endpoints",
|
||||
"chooseDiscoveryOrManual": "Choose either automatic discovery (Provider URL) OR manual endpoints, not both",
|
||||
"loadFailed": "Failed to load providers",
|
||||
"addFailed": "Failed to add provider",
|
||||
"updateFailed": "Failed to update provider",
|
||||
"deleteFailed": "Failed to delete provider",
|
||||
"orderUpdateFailed": "Failed to update provider order"
|
||||
},
|
||||
"info": {
|
||||
"title": "Information",
|
||||
"officialProvidersRecommended": "For better functionality, consider using official providers. If you have issues with a custom provider, consider opening an issue on",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "Official Provider",
|
||||
"officialProviderDescription": "This provider is optimized by Palmr. Only credentials and configuration can be modified.",
|
||||
"manualConfigTitle": "Manual Configuration",
|
||||
"manualConfigDescription": "You're providing all endpoints manually. Make sure they're correct for your provider."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Delete Authentication Provider",
|
||||
"confirmMessage": "Are you sure you want to delete the \"{displayName}\" provider? This action cannot be undone.",
|
||||
"providerId": "Provider ID: {name}",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete Provider",
|
||||
"deleting": "Deleting..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "Archivos recibidos"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "Proveedores de Autenticación",
|
||||
"description": "Configurar proveedores de autenticación externos para SSO",
|
||||
"enabledCount": "{count} habilitados",
|
||||
"loadingProviders": "Cargando proveedores...",
|
||||
"providersConfigured": "{count} proveedores configurados",
|
||||
"enabledOfTotal": "{enabled} habilitados de {total} proveedores",
|
||||
"hideDisabledProviders": "Ocultar proveedores deshabilitados",
|
||||
"addProvider": "Agregar Proveedor",
|
||||
"addProviderTitle": "Agregar Proveedor",
|
||||
"editProvider": "Editar Proveedor",
|
||||
"deleteProvider": "Eliminar Proveedor",
|
||||
"enabled": "Habilitado",
|
||||
"disabled": "Deshabilitado",
|
||||
"officialProvider": "Proveedor Oficial",
|
||||
"dragToReorder": "Arrastrar para reordenar",
|
||||
"dragDisabledMessage": "El arrastrar y soltar está deshabilitado al filtrar proveedores. Muestra todos los proveedores para reordenarlos.",
|
||||
"dragEnabledMessage": "Arrastra los proveedores para reordenarlos. Este orden se reflejará en la página de inicio de sesión.",
|
||||
"noProvidersEnabled": "No hay proveedores de autenticación habilitados",
|
||||
"noProvidersConfigured": "No hay proveedores de autenticación configurados",
|
||||
"form": {
|
||||
"providerName": "Nombre del Proveedor",
|
||||
"providerNamePlaceholder": "ej., miempresa",
|
||||
"displayName": "Nombre para Mostrar",
|
||||
"displayNamePlaceholder": "ej., SSO de Mi Empresa",
|
||||
"type": "Tipo",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "Ícono",
|
||||
"iconPlaceholder": "Seleccionar un ícono",
|
||||
"clientId": "ID del Cliente",
|
||||
"clientIdPlaceholder": "Tu ID de cliente OAuth",
|
||||
"clientSecret": "Secreto del Cliente",
|
||||
"clientSecretPlaceholder": "Tu secreto de cliente OAuth",
|
||||
"oauthScopes": "Alcances OAuth",
|
||||
"scopesPlaceholder": "Ingresa alcances (ej., openid, profile, email)",
|
||||
"scopesHelpOidc": "Alcances sugeridos automáticamente según la URL del Proveedor. Alcances comunes de OIDC: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "Alcances sugeridos automáticamente según la URL del Proveedor. Los alcances comunes de OAuth2 dependen del proveedor",
|
||||
"providerUrl": "URL del Proveedor",
|
||||
"providerUrlPlaceholder": "https://auth.ejemplo.com",
|
||||
"providerUrlAutoPlaceholder": "https://tu-proveedor.com (los endpoints se descubrirán automáticamente)",
|
||||
"providerUrlManualPlaceholder": "https://tu-proveedor.com",
|
||||
"autoDiscoveryHelp": "El sistema descubrirá automáticamente los endpoints de autorización, token y userinfo",
|
||||
"manualConfigurationHelp": "URL base de tu proveedor (los endpoints serán relativos a esta)",
|
||||
"authorizationEndpoint": "Endpoint de Autorización",
|
||||
"authorizationEndpointPlaceholder": "https://auth.ejemplo.com/auth",
|
||||
"tokenEndpoint": "Endpoint de Token",
|
||||
"tokenEndpointPlaceholder": "https://auth.ejemplo.com/token",
|
||||
"userInfoEndpoint": "Endpoint de Información del Usuario",
|
||||
"userInfoEndpointPlaceholder": "https://auth.ejemplo.com/userinfo",
|
||||
"configurationMethod": "Método de Configuración",
|
||||
"autoDiscovery": "Descubrimiento Automático (Recomendado)",
|
||||
"autoDiscoveryDescription": "Descubrir endpoints automáticamente desde la URL del Proveedor",
|
||||
"manualEndpoints": "Endpoints Manuales",
|
||||
"manualEndpointsDescription": "Configurar manualmente los endpoints de autorización, token e información del usuario",
|
||||
"callbackUrl": "URL de Retorno",
|
||||
"callbackUrlDescription": "Usa esta URL en la configuración de tu proveedor OAuth",
|
||||
"copyCallbackUrl": "Copiar URL de Retorno",
|
||||
"callbackUrlCopied": "¡URL de retorno copiada al portapapeles!",
|
||||
"adminEmailDomains": "Dominios de Email de Administrador",
|
||||
"adminEmailDomainsPlaceholder": "Ingresa dominios (ej., admin.empresa.com)",
|
||||
"adminEmailDomainsHelp": "Los usuarios con emails de estos dominios recibirán privilegios de administrador",
|
||||
"autoRegister": "Auto-registrar nuevos usuarios",
|
||||
"officialProviderUrlPlaceholder": "Reemplaza el marcador de posición con tu URL de {displayName}",
|
||||
"officialProviderHelp": "Este es un proveedor oficial. Los endpoints están preconfigurados. Solo puedes editar esta URL.",
|
||||
"officialProviderIconHelp": "Puedes personalizar el ícono para este proveedor oficial."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "Cancelar",
|
||||
"save": "Guardar",
|
||||
"saving": "Guardando...",
|
||||
"adding": "Agregando...",
|
||||
"updating": "Actualizando...",
|
||||
"saveProvider": "Guardar Proveedor",
|
||||
"delete": "Eliminar",
|
||||
"deleting": "Eliminando...",
|
||||
"edit": "Editar",
|
||||
"enable": "Habilitar",
|
||||
"disable": "Deshabilitar"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "Proveedor agregado exitosamente",
|
||||
"providerUpdated": "Proveedor actualizado exitosamente",
|
||||
"providerDeleted": "Proveedor eliminado exitosamente",
|
||||
"providerOrderUpdated": "Orden de proveedores actualizado exitosamente",
|
||||
"fillRequiredFields": "Por favor completa todos los campos requeridos (nombre, nombre para mostrar, ID de cliente, secreto de cliente)",
|
||||
"provideUrlOrEndpoints": "Proporciona una URL del Proveedor para descubrimiento automático O los tres endpoints personalizados",
|
||||
"chooseDiscoveryOrManual": "Elige descubrimiento automático (URL del Proveedor) O endpoints manuales, no ambos",
|
||||
"loadFailed": "Error al cargar los proveedores",
|
||||
"addFailed": "Error al agregar el proveedor",
|
||||
"updateFailed": "Error al actualizar el proveedor",
|
||||
"deleteFailed": "Error al eliminar el proveedor",
|
||||
"orderUpdateFailed": "Error al actualizar el orden de los proveedores"
|
||||
},
|
||||
"info": {
|
||||
"title": "Información",
|
||||
"officialProvidersRecommended": "Para una mejor funcionalidad, considera usar proveedores oficiales. Si tienes problemas con un proveedor personalizado, considera abrir un issue en",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "Proveedor Oficial",
|
||||
"officialProviderDescription": "Este proveedor está optimizado por Palmr. Solo se pueden modificar las credenciales y la configuración.",
|
||||
"manualConfigTitle": "Configuración Manual",
|
||||
"manualConfigDescription": "Estás proporcionando todos los endpoints manualmente. Asegúrate de que sean correctos para tu proveedor."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Eliminar Proveedor de Autenticación",
|
||||
"confirmMessage": "¿Estás seguro de que deseas eliminar el proveedor \"{displayName}\"? Esta acción no se puede deshacer.",
|
||||
"providerId": "ID del Proveedor: {name}",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Eliminar Proveedor",
|
||||
"deleting": "Eliminando..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "Fichiers reçus"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "Fournisseurs d'authentification",
|
||||
"description": "Configurer les fournisseurs d'authentification externes pour le SSO",
|
||||
"enabledCount": "{count} activés",
|
||||
"loadingProviders": "Chargement des fournisseurs...",
|
||||
"providersConfigured": "{count} fournisseurs configurés",
|
||||
"enabledOfTotal": "{enabled} activés sur {total} fournisseurs",
|
||||
"hideDisabledProviders": "Masquer les fournisseurs désactivés",
|
||||
"addProvider": "Ajouter un fournisseur",
|
||||
"addProviderTitle": "Ajouter un fournisseur",
|
||||
"editProvider": "Modifier le fournisseur",
|
||||
"deleteProvider": "Supprimer le fournisseur",
|
||||
"enabled": "Activé",
|
||||
"disabled": "Désactivé",
|
||||
"officialProvider": "Fournisseur officiel",
|
||||
"dragToReorder": "Glisser pour réorganiser",
|
||||
"dragDisabledMessage": "Le glisser-déposer est désactivé lors du filtrage des fournisseurs. Affichez tous les fournisseurs pour les réorganiser.",
|
||||
"dragEnabledMessage": "Faites glisser les fournisseurs pour les réorganiser. Cet ordre sera reflété sur la page de connexion.",
|
||||
"noProvidersEnabled": "Aucun fournisseur d'authentification activé",
|
||||
"noProvidersConfigured": "Aucun fournisseur d'authentification configuré",
|
||||
"form": {
|
||||
"providerName": "Nom du fournisseur",
|
||||
"providerNamePlaceholder": "ex: masociete",
|
||||
"displayName": "Nom d'affichage",
|
||||
"displayNamePlaceholder": "ex: SSO de Ma Société",
|
||||
"type": "Type",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "Icône",
|
||||
"iconPlaceholder": "Sélectionner une icône",
|
||||
"clientId": "ID Client",
|
||||
"clientIdPlaceholder": "Votre ID client OAuth",
|
||||
"clientSecret": "Secret Client",
|
||||
"clientSecretPlaceholder": "Votre secret client OAuth",
|
||||
"oauthScopes": "Portées OAuth",
|
||||
"scopesPlaceholder": "Entrez les portées (ex: openid, profile, email)",
|
||||
"scopesHelpOidc": "Portées suggérées automatiquement selon l'URL du fournisseur. Portées OIDC courantes : openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "Portées suggérées automatiquement selon l'URL du fournisseur. Les portées OAuth2 courantes dépendent du fournisseur",
|
||||
"providerUrl": "URL du fournisseur",
|
||||
"providerUrlPlaceholder": "https://auth.exemple.com",
|
||||
"providerUrlAutoPlaceholder": "https://votre-fournisseur.com (les points de terminaison seront découverts automatiquement)",
|
||||
"providerUrlManualPlaceholder": "https://votre-fournisseur.com",
|
||||
"autoDiscoveryHelp": "Le système découvrira automatiquement les points de terminaison d'autorisation, de jeton et d'informations utilisateur",
|
||||
"manualConfigurationHelp": "URL de base de votre fournisseur (les points de terminaison seront relatifs à celle-ci)",
|
||||
"authorizationEndpoint": "Point de terminaison d'autorisation",
|
||||
"authorizationEndpointPlaceholder": "https://auth.exemple.com/auth",
|
||||
"tokenEndpoint": "Point de terminaison de jeton",
|
||||
"tokenEndpointPlaceholder": "https://auth.exemple.com/token",
|
||||
"userInfoEndpoint": "Point de terminaison d'informations utilisateur",
|
||||
"userInfoEndpointPlaceholder": "https://auth.exemple.com/userinfo",
|
||||
"configurationMethod": "Méthode de configuration",
|
||||
"autoDiscovery": "Découverte automatique (Recommandé)",
|
||||
"autoDiscoveryDescription": "Découvrir automatiquement les points de terminaison depuis l'URL du fournisseur",
|
||||
"manualEndpoints": "Points de terminaison manuels",
|
||||
"manualEndpointsDescription": "Configurer manuellement les points de terminaison d'autorisation, de jeton et d'informations utilisateur",
|
||||
"callbackUrl": "URL de rappel",
|
||||
"callbackUrlDescription": "Utilisez cette URL dans la configuration de votre fournisseur OAuth",
|
||||
"copyCallbackUrl": "Copier l'URL de rappel",
|
||||
"callbackUrlCopied": "URL de rappel copiée dans le presse-papiers !",
|
||||
"adminEmailDomains": "Domaines email administrateurs",
|
||||
"adminEmailDomainsPlaceholder": "Entrez les domaines (ex: admin.entreprise.com)",
|
||||
"adminEmailDomainsHelp": "Les utilisateurs avec des emails de ces domaines recevront des privilèges d'administrateur",
|
||||
"autoRegister": "Inscription automatique des nouveaux utilisateurs",
|
||||
"officialProviderUrlPlaceholder": "Remplacez l'espace réservé par votre URL {displayName}",
|
||||
"officialProviderHelp": "Ceci est un fournisseur officiel. Les points de terminaison sont préconfigurés. Vous pouvez modifier uniquement cette URL.",
|
||||
"officialProviderIconHelp": "Vous pouvez personnaliser l'icône de ce fournisseur officiel."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "Annuler",
|
||||
"save": "Enregistrer",
|
||||
"saving": "Enregistrement...",
|
||||
"adding": "Ajout...",
|
||||
"updating": "Mise à jour...",
|
||||
"saveProvider": "Enregistrer le fournisseur",
|
||||
"delete": "Supprimer",
|
||||
"deleting": "Suppression...",
|
||||
"edit": "Modifier",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactiver"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "Fournisseur ajouté avec succès",
|
||||
"providerUpdated": "Fournisseur mis à jour avec succès",
|
||||
"providerDeleted": "Fournisseur supprimé avec succès",
|
||||
"providerOrderUpdated": "Ordre des fournisseurs mis à jour avec succès",
|
||||
"fillRequiredFields": "Veuillez remplir tous les champs obligatoires (nom, nom d'affichage, ID client, secret client)",
|
||||
"provideUrlOrEndpoints": "Fournissez soit une URL de fournisseur pour la découverte automatique, soit les trois points de terminaison personnalisés",
|
||||
"chooseDiscoveryOrManual": "Choisissez soit la découverte automatique (URL du fournisseur) SOIT les points de terminaison manuels, pas les deux",
|
||||
"loadFailed": "Échec du chargement des fournisseurs",
|
||||
"addFailed": "Échec de l'ajout du fournisseur",
|
||||
"updateFailed": "Échec de la mise à jour du fournisseur",
|
||||
"deleteFailed": "Échec de la suppression du fournisseur",
|
||||
"orderUpdateFailed": "Échec de la mise à jour de l'ordre des fournisseurs"
|
||||
},
|
||||
"info": {
|
||||
"title": "Information",
|
||||
"officialProvidersRecommended": "Pour une meilleure fonctionnalité, considérez l'utilisation des fournisseurs officiels. Si vous rencontrez des problèmes avec un fournisseur personnalisé, envisagez d'ouvrir un ticket sur",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "Fournisseur officiel",
|
||||
"officialProviderDescription": "Ce fournisseur est optimisé par Palmr. Seuls les identifiants et la configuration peuvent être modifiés.",
|
||||
"manualConfigTitle": "Configuration manuelle",
|
||||
"manualConfigDescription": "Vous fournissez tous les points de terminaison manuellement. Assurez-vous qu'ils sont corrects pour votre fournisseur."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Supprimer le fournisseur d'authentification",
|
||||
"confirmMessage": "Êtes-vous sûr de vouloir supprimer le fournisseur \"{displayName}\" ? Cette action ne peut pas être annulée.",
|
||||
"providerId": "ID du fournisseur : {name}",
|
||||
"cancel": "Annuler",
|
||||
"delete": "Supprimer le fournisseur",
|
||||
"deleting": "Suppression..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "प्राप्त फ़ाइलें"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "प्रमाणीकरण प्रदाता",
|
||||
"description": "एसएसओ के लिए बाहरी प्रमाणीकरण प्रदाताओं को कॉन्फ़िगर करें",
|
||||
"enabledCount": "{count} सक्षम",
|
||||
"loadingProviders": "प्रदाताओं को लोड कर रहा है...",
|
||||
"providersConfigured": "{count} प्रदाता कॉन्फ़िगर किए गए",
|
||||
"enabledOfTotal": "{total} प्रदाताओं में से {enabled} सक्षम",
|
||||
"hideDisabledProviders": "अक्षम प्रदाताओं को छिपाएं",
|
||||
"addProvider": "प्रदाता जोड़ें",
|
||||
"addProviderTitle": "प्रदाता जोड़ें",
|
||||
"editProvider": "प्रदाता संपादित करें",
|
||||
"deleteProvider": "प्रदाता हटाएं",
|
||||
"enabled": "सक्षम",
|
||||
"disabled": "अक्षम",
|
||||
"officialProvider": "आधिकारिक प्रदाता",
|
||||
"dragToReorder": "पुनर्क्रमित करने के लिए खींचें",
|
||||
"dragDisabledMessage": "प्रदाताओं को फ़िल्टर करते समय ड्रैग और ड्रॉप अक्षम है। उन्हें पुनर्क्रमित करने के लिए सभी प्रदाताओं को दिखाएं।",
|
||||
"dragEnabledMessage": "प्रदाताओं को पुनर्क्रमित करने के लिए खींचें। यह क्रम लॉगिन पृष्ठ पर प्रतिबिंबित होगा।",
|
||||
"noProvidersEnabled": "कोई सक्षम प्रमाणीकरण प्रदाता नहीं",
|
||||
"noProvidersConfigured": "कोई प्रमाणीकरण प्रदाता कॉन्फ़िगर नहीं किया गया",
|
||||
"form": {
|
||||
"providerName": "प्रदाता का नाम",
|
||||
"providerNamePlaceholder": "उदा., मेरीकंपनी",
|
||||
"displayName": "प्रदर्शन नाम",
|
||||
"displayNamePlaceholder": "उदा., मेरी कंपनी एसएसओ",
|
||||
"type": "प्रकार",
|
||||
"typeOidc": "ओआईडीसी (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "आइकन",
|
||||
"iconPlaceholder": "एक आइकन चुनें",
|
||||
"clientId": "क्लाइंट आईडी",
|
||||
"clientIdPlaceholder": "आपकी OAuth क्लाइंट आईडी",
|
||||
"clientSecret": "क्लाइंट सीक्रेट",
|
||||
"clientSecretPlaceholder": "आपका OAuth क्लाइंट सीक्रेट",
|
||||
"oauthScopes": "OAuth स्कोप्स",
|
||||
"scopesPlaceholder": "स्कोप्स दर्ज करें (उदा., openid, profile, email)",
|
||||
"scopesHelpOidc": "प्रदाता URL के आधार पर स्कोप्स स्वतः सुझाए गए। सामान्य OIDC स्कोप्स: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "प्रदाता URL के आधार पर स्कोप्स स्वतः सुझाए गए। सामान्य OAuth2 स्कोप्स प्रदाता पर निर्भर करते हैं",
|
||||
"providerUrl": "प्रदाता URL",
|
||||
"providerUrlPlaceholder": "https://auth.example.com",
|
||||
"providerUrlAutoPlaceholder": "https://your-provider.com (एंडपॉइंट्स स्वचालित रूप से खोजे जाएंगे)",
|
||||
"providerUrlManualPlaceholder": "https://your-provider.com",
|
||||
"autoDiscoveryHelp": "सिस्टम स्वचालित रूप से प्राधिकरण, टोकन और यूजरइन्फो एंडपॉइंट्स की खोज करेगा",
|
||||
"manualConfigurationHelp": "आपके प्रदाता का बेस URL (एंडपॉइंट्स इससे सापेक्ष होंगे)",
|
||||
"authorizationEndpoint": "प्राधिकरण एंडपॉइंट",
|
||||
"authorizationEndpointPlaceholder": "https://auth.example.com/auth",
|
||||
"tokenEndpoint": "टोकन एंडपॉइंट",
|
||||
"tokenEndpointPlaceholder": "https://auth.example.com/token",
|
||||
"userInfoEndpoint": "यूजर इन्फो एंडपॉइंट",
|
||||
"userInfoEndpointPlaceholder": "https://auth.example.com/userinfo",
|
||||
"configurationMethod": "कॉन्फ़िगरेशन विधि",
|
||||
"autoDiscovery": "स्वचालित खोज (अनुशंसित)",
|
||||
"autoDiscoveryDescription": "प्रदाता URL से स्वचालित रूप से एंडपॉइंट्स खोजें",
|
||||
"manualEndpoints": "मैनुअल एंडपॉइंट्स",
|
||||
"manualEndpointsDescription": "प्राधिकरण, टोकन और यूजर इन्फो एंडपॉइंट्स को मैनुअल रूप से कॉन्फ़िगर करें",
|
||||
"callbackUrl": "कॉलबैक URL",
|
||||
"callbackUrlDescription": "अपने OAuth प्रदाता कॉन्फ़िगरेशन में इस URL का उपयोग करें",
|
||||
"copyCallbackUrl": "कॉलबैक URL कॉपी करें",
|
||||
"callbackUrlCopied": "कॉलबैक URL क्लिपबोर्ड पर कॉपी किया गया!",
|
||||
"adminEmailDomains": "व्यवस्थापक ईमेल डोमेन",
|
||||
"adminEmailDomainsPlaceholder": "डोमेन दर्ज करें (उदा., admin.company.com)",
|
||||
"adminEmailDomainsHelp": "इन डोमेन से ईमेल वाले उपयोगकर्ताओं को व्यवस्थापक अधिकार दिए जाएंगे",
|
||||
"autoRegister": "नए उपयोगकर्ताओं को स्वतः पंजीकृत करें",
|
||||
"officialProviderUrlPlaceholder": "प्लेसहोल्डर को अपने {displayName} URL से बदलें",
|
||||
"officialProviderHelp": "यह एक आधिकारिक प्रदाता है। एंडपॉइंट्स पूर्व-कॉन्फ़िगर किए गए हैं। आप केवल इस URL को संपादित कर सकते हैं।",
|
||||
"officialProviderIconHelp": "आप इस आधिकारिक प्रदाता के लिए आइकन को अनुकूलित कर सकते हैं।"
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "रद्द करें",
|
||||
"save": "सहेजें",
|
||||
"saving": "सहेज रहा है...",
|
||||
"adding": "जोड़ रहा है...",
|
||||
"updating": "अपडेट कर रहा है...",
|
||||
"saveProvider": "प्रदाता सहेजें",
|
||||
"delete": "हटाएं",
|
||||
"deleting": "हटा रहा है...",
|
||||
"edit": "संपादित करें",
|
||||
"enable": "सक्षम करें",
|
||||
"disable": "अक्षम करें"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "प्रदाता सफलतापूर्वक जोड़ा गया",
|
||||
"providerUpdated": "प्रदाता सफलतापूर्वक अपडेट किया गया",
|
||||
"providerDeleted": "प्रदाता सफलतापूर्वक हटाया गया",
|
||||
"providerOrderUpdated": "प्रदाता क्रम सफलतापूर्वक अपडेट किया गया",
|
||||
"fillRequiredFields": "कृपया सभी आवश्यक फ़ील्ड भरें (नाम, प्रदर्शन नाम, क्लाइंट आईडी, क्लाइंट सीक्रेट)",
|
||||
"provideUrlOrEndpoints": "स्वचालित खोज के लिए प्रदाता URL या सभी तीन कस्टम एंडपॉइंट्स प्रदान करें",
|
||||
"chooseDiscoveryOrManual": "या तो स्वचालित खोज (प्रदाता URL) या मैनुअल एंडपॉइंट्स चुनें, दोनों नहीं",
|
||||
"loadFailed": "प्रदाताओं को लोड करने में विफल",
|
||||
"addFailed": "प्रदाता जोड़ने में विफल",
|
||||
"updateFailed": "प्रदाता अपडेट करने में विफल",
|
||||
"deleteFailed": "प्रदाता हटाने में विफल",
|
||||
"orderUpdateFailed": "प्रदाता क्रम अपडेट करने में विफल"
|
||||
},
|
||||
"info": {
|
||||
"title": "जानकारी",
|
||||
"officialProvidersRecommended": "बेहतर कार्यक्षमता के लिए, आधिकारिक प्रदाताओं का उपयोग करने पर विचार करें। यदि आपको किसी कस्टम प्रदाता के साथ समस्या है, तो इस पर एक मुद्दा खोलने पर विचार करें",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "आधिकारिक प्रदाता",
|
||||
"officialProviderDescription": "यह प्रदाता Palmr द्वारा अनुकूलित है। केवल क्रेडेंशियल्स और कॉन्फ़िगरेशन को संशोधित किया जा सकता है।",
|
||||
"manualConfigTitle": "मैनुअल कॉन्फ़िगरेशन",
|
||||
"manualConfigDescription": "आप सभी एंडपॉइंट्स मैनुअल रूप से प्रदान कर रहे हैं। सुनिश्चित करें कि वे आपके प्रदाता के लिए सही हैं।"
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "प्रमाणीकरण प्रदाता हटाएं",
|
||||
"confirmMessage": "क्या आप वाकई \"{displayName}\" प्रदाता को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।",
|
||||
"providerId": "प्रदाता आईडी: {name}",
|
||||
"cancel": "रद्द करें",
|
||||
"delete": "प्रदाता हटाएं",
|
||||
"deleting": "हटा रहा है..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "File ricevuti"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "Provider di Autenticazione",
|
||||
"description": "Configura provider di autenticazione esterni per SSO",
|
||||
"enabledCount": "{count} abilitati",
|
||||
"loadingProviders": "Caricamento provider...",
|
||||
"providersConfigured": "{count} provider configurati",
|
||||
"enabledOfTotal": "{enabled} abilitati su {total} provider",
|
||||
"hideDisabledProviders": "Nascondi provider disabilitati",
|
||||
"addProvider": "Aggiungi Provider",
|
||||
"addProviderTitle": "Aggiungi Provider",
|
||||
"editProvider": "Modifica Provider",
|
||||
"deleteProvider": "Elimina Provider",
|
||||
"enabled": "Abilitato",
|
||||
"disabled": "Disabilitato",
|
||||
"officialProvider": "Provider Ufficiale",
|
||||
"dragToReorder": "Trascina per riordinare",
|
||||
"dragDisabledMessage": "Il trascinamento è disabilitato durante il filtraggio dei provider. Mostra tutti i provider per riordinarli.",
|
||||
"dragEnabledMessage": "Trascina i provider per riordinarli. Quest'ordine sarà riflesso nella pagina di accesso.",
|
||||
"noProvidersEnabled": "Nessun provider di autenticazione abilitato",
|
||||
"noProvidersConfigured": "Nessun provider di autenticazione configurato",
|
||||
"form": {
|
||||
"providerName": "Nome Provider",
|
||||
"providerNamePlaceholder": "es. miazienda",
|
||||
"displayName": "Nome Visualizzato",
|
||||
"displayNamePlaceholder": "es. SSO Mia Azienda",
|
||||
"type": "Tipo",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "Icona",
|
||||
"iconPlaceholder": "Seleziona un'icona",
|
||||
"clientId": "ID Client",
|
||||
"clientIdPlaceholder": "Il tuo ID client OAuth",
|
||||
"clientSecret": "Client Secret",
|
||||
"clientSecretPlaceholder": "Il tuo client secret OAuth",
|
||||
"oauthScopes": "Scope OAuth",
|
||||
"scopesPlaceholder": "Inserisci gli scope (es. openid, profile, email)",
|
||||
"scopesHelpOidc": "Scope suggeriti automaticamente in base all'URL del Provider. Scope OIDC comuni: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "Scope suggeriti automaticamente in base all'URL del Provider. Gli scope OAuth2 comuni dipendono dal provider",
|
||||
"providerUrl": "URL Provider",
|
||||
"providerUrlPlaceholder": "https://auth.esempio.com",
|
||||
"providerUrlAutoPlaceholder": "https://tuo-provider.com (gli endpoint saranno rilevati automaticamente)",
|
||||
"providerUrlManualPlaceholder": "https://tuo-provider.com",
|
||||
"autoDiscoveryHelp": "Il sistema rileverà automaticamente gli endpoint di autorizzazione, token e userinfo",
|
||||
"manualConfigurationHelp": "URL base del tuo provider (gli endpoint saranno relativi a questo)",
|
||||
"authorizationEndpoint": "Endpoint di Autorizzazione",
|
||||
"authorizationEndpointPlaceholder": "https://auth.esempio.com/auth",
|
||||
"tokenEndpoint": "Endpoint Token",
|
||||
"tokenEndpointPlaceholder": "https://auth.esempio.com/token",
|
||||
"userInfoEndpoint": "Endpoint Info Utente",
|
||||
"userInfoEndpointPlaceholder": "https://auth.esempio.com/userinfo",
|
||||
"configurationMethod": "Metodo di Configurazione",
|
||||
"autoDiscovery": "Rilevamento Automatico (Consigliato)",
|
||||
"autoDiscoveryDescription": "Rileva automaticamente gli endpoint dall'URL del Provider",
|
||||
"manualEndpoints": "Endpoint Manuali",
|
||||
"manualEndpointsDescription": "Configura manualmente gli endpoint di autorizzazione, token e info utente",
|
||||
"callbackUrl": "URL di Callback",
|
||||
"callbackUrlDescription": "Usa questo URL nella configurazione del tuo provider OAuth",
|
||||
"copyCallbackUrl": "Copia URL di Callback",
|
||||
"callbackUrlCopied": "URL di Callback copiato negli appunti!",
|
||||
"adminEmailDomains": "Domini Email Admin",
|
||||
"adminEmailDomainsPlaceholder": "Inserisci domini (es. admin.azienda.com)",
|
||||
"adminEmailDomainsHelp": "Gli utenti con email da questi domini riceveranno privilegi di amministratore",
|
||||
"autoRegister": "Registra automaticamente nuovi utenti",
|
||||
"officialProviderUrlPlaceholder": "Sostituisci il segnaposto con il tuo URL {displayName}",
|
||||
"officialProviderHelp": "Questo è un provider ufficiale. Gli endpoint sono pre-configurati. Puoi modificare solo questo URL.",
|
||||
"officialProviderIconHelp": "Puoi personalizzare l'icona per questo provider ufficiale."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "Annulla",
|
||||
"save": "Salva",
|
||||
"saving": "Salvataggio...",
|
||||
"adding": "Aggiunta...",
|
||||
"updating": "Aggiornamento...",
|
||||
"saveProvider": "Salva Provider",
|
||||
"delete": "Elimina",
|
||||
"deleting": "Eliminazione...",
|
||||
"edit": "Modifica",
|
||||
"enable": "Abilita",
|
||||
"disable": "Disabilita"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "Provider aggiunto con successo",
|
||||
"providerUpdated": "Provider aggiornato con successo",
|
||||
"providerDeleted": "Provider eliminato con successo",
|
||||
"providerOrderUpdated": "Ordine dei provider aggiornato con successo",
|
||||
"fillRequiredFields": "Compila tutti i campi obbligatori (nome, nome visualizzato, ID client, client secret)",
|
||||
"provideUrlOrEndpoints": "Fornisci un URL Provider per il rilevamento automatico OPPURE tutti e tre gli endpoint personalizzati",
|
||||
"chooseDiscoveryOrManual": "Scegli il rilevamento automatico (URL Provider) O gli endpoint manuali, non entrambi",
|
||||
"loadFailed": "Caricamento dei provider fallito",
|
||||
"addFailed": "Aggiunta del provider fallita",
|
||||
"updateFailed": "Aggiornamento del provider fallito",
|
||||
"deleteFailed": "Eliminazione del provider fallita",
|
||||
"orderUpdateFailed": "Aggiornamento dell'ordine dei provider fallito"
|
||||
},
|
||||
"info": {
|
||||
"title": "Informazioni",
|
||||
"officialProvidersRecommended": "Per una migliore funzionalità, considera l'uso di provider ufficiali. Se hai problemi con un provider personalizzato, considera di aprire una segnalazione su",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "Provider Ufficiale",
|
||||
"officialProviderDescription": "Questo provider è ottimizzato da Palmr. Possono essere modificate solo le credenziali e la configurazione.",
|
||||
"manualConfigTitle": "Configurazione Manuale",
|
||||
"manualConfigDescription": "Stai fornendo tutti gli endpoint manualmente. Assicurati che siano corretti per il tuo provider."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Elimina Provider di Autenticazione",
|
||||
"confirmMessage": "Sei sicuro di voler eliminare il provider \"{displayName}\"? Questa azione non può essere annullata.",
|
||||
"providerId": "ID Provider: {name}",
|
||||
"cancel": "Annulla",
|
||||
"delete": "Elimina Provider",
|
||||
"deleting": "Eliminazione..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "受信したファイル"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "認証プロバイダー",
|
||||
"description": "SSOの外部認証プロバイダーを設定する",
|
||||
"enabledCount": "{count}件有効",
|
||||
"loadingProviders": "プロバイダーを読み込み中...",
|
||||
"providersConfigured": "{count}件のプロバイダーが設定済み",
|
||||
"enabledOfTotal": "全{total}件中{enabled}件が有効",
|
||||
"hideDisabledProviders": "無効なプロバイダーを非表示",
|
||||
"addProvider": "プロバイダーを追加",
|
||||
"addProviderTitle": "プロバイダーを追加",
|
||||
"editProvider": "プロバイダーを編集",
|
||||
"deleteProvider": "プロバイダーを削除",
|
||||
"enabled": "有効",
|
||||
"disabled": "無効",
|
||||
"officialProvider": "公式プロバイダー",
|
||||
"dragToReorder": "ドラッグして並び替え",
|
||||
"dragDisabledMessage": "プロバイダーをフィルタリング中はドラッグ&ドロップが無効です。並び替えるには全てのプロバイダーを表示してください。",
|
||||
"dragEnabledMessage": "プロバイダーをドラッグして並び替えできます。この順序はログインページに反映されます。",
|
||||
"noProvidersEnabled": "有効な認証プロバイダーがありません",
|
||||
"noProvidersConfigured": "設定された認証プロバイダーがありません",
|
||||
"form": {
|
||||
"providerName": "プロバイダー名",
|
||||
"providerNamePlaceholder": "例:mycompany",
|
||||
"displayName": "表示名",
|
||||
"displayNamePlaceholder": "例:My Company SSO",
|
||||
"type": "タイプ",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "アイコン",
|
||||
"iconPlaceholder": "アイコンを選択",
|
||||
"clientId": "クライアントID",
|
||||
"clientIdPlaceholder": "OAuthクライアントID",
|
||||
"clientSecret": "クライアントシークレット",
|
||||
"clientSecretPlaceholder": "OAuthクライアントシークレット",
|
||||
"oauthScopes": "OAuthスコープ",
|
||||
"scopesPlaceholder": "スコープを入力(例:openid, profile, email)",
|
||||
"scopesHelpOidc": "プロバイダーURLに基づいて自動提案されるスコープ。一般的なOIDCスコープ:openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "プロバイダーURLに基づいて自動提案されるスコープ。一般的なOAuth2スコープはプロバイダーによって異なります",
|
||||
"providerUrl": "プロバイダーURL",
|
||||
"providerUrlPlaceholder": "https://auth.example.com",
|
||||
"providerUrlAutoPlaceholder": "https://your-provider.com(エンドポイントは自動的に検出されます)",
|
||||
"providerUrlManualPlaceholder": "https://your-provider.com",
|
||||
"autoDiscoveryHelp": "システムは認証、トークン、ユーザー情報のエンドポイントを自動的に検出します",
|
||||
"manualConfigurationHelp": "プロバイダーのベースURL(エンドポイントはこれを基準とします)",
|
||||
"authorizationEndpoint": "認証エンドポイント",
|
||||
"authorizationEndpointPlaceholder": "https://auth.example.com/auth",
|
||||
"tokenEndpoint": "トークンエンドポイント",
|
||||
"tokenEndpointPlaceholder": "https://auth.example.com/token",
|
||||
"userInfoEndpoint": "ユーザー情報エンドポイント",
|
||||
"userInfoEndpointPlaceholder": "https://auth.example.com/userinfo",
|
||||
"configurationMethod": "設定方法",
|
||||
"autoDiscovery": "自動検出(推奨)",
|
||||
"autoDiscoveryDescription": "プロバイダーURLからエンドポイントを自動的に検出",
|
||||
"manualEndpoints": "手動エンドポイント",
|
||||
"manualEndpointsDescription": "認証、トークン、ユーザー情報エンドポイントを手動で設定",
|
||||
"callbackUrl": "コールバックURL",
|
||||
"callbackUrlDescription": "OAuthプロバイダー設定でこのURLを使用してください",
|
||||
"copyCallbackUrl": "コールバックURLをコピー",
|
||||
"callbackUrlCopied": "コールバックURLをクリップボードにコピーしました!",
|
||||
"adminEmailDomains": "管理者メールドメイン",
|
||||
"adminEmailDomainsPlaceholder": "ドメインを入力(例:admin.company.com)",
|
||||
"adminEmailDomainsHelp": "これらのドメインのメールアドレスを持つユーザーに管理者権限が付与されます",
|
||||
"autoRegister": "新規ユーザーを自動登録",
|
||||
"officialProviderUrlPlaceholder": "プレースホルダーを{displayName}のURLに置き換えてください",
|
||||
"officialProviderHelp": "これは公式プロバイダーです。エンドポイントは事前設定されています。このURLのみ編集可能です。",
|
||||
"officialProviderIconHelp": "この公式プロバイダーのアイコンをカスタマイズできます。"
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "キャンセル",
|
||||
"save": "保存",
|
||||
"saving": "保存中...",
|
||||
"adding": "追加中...",
|
||||
"updating": "更新中...",
|
||||
"saveProvider": "プロバイダーを保存",
|
||||
"delete": "削除",
|
||||
"deleting": "削除中...",
|
||||
"edit": "編集",
|
||||
"enable": "有効化",
|
||||
"disable": "無効化"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "プロバイダーを追加しました",
|
||||
"providerUpdated": "プロバイダーを更新しました",
|
||||
"providerDeleted": "プロバイダーを削除しました",
|
||||
"providerOrderUpdated": "プロバイダーの順序を更新しました",
|
||||
"fillRequiredFields": "必須項目(名前、表示名、クライアントID、クライアントシークレット)を入力してください",
|
||||
"provideUrlOrEndpoints": "自動検出用のプロバイダーURLか、3つのカスタムエンドポイントのいずれかを提供してください",
|
||||
"chooseDiscoveryOrManual": "自動検出(プロバイダーURL)か手動エンドポイントのどちらかを選択してください。両方は選択できません",
|
||||
"loadFailed": "プロバイダーの読み込みに失敗しました",
|
||||
"addFailed": "プロバイダーの追加に失敗しました",
|
||||
"updateFailed": "プロバイダーの更新に失敗しました",
|
||||
"deleteFailed": "プロバイダーの削除に失敗しました",
|
||||
"orderUpdateFailed": "プロバイダーの順序の更新に失敗しました"
|
||||
},
|
||||
"info": {
|
||||
"title": "情報",
|
||||
"officialProvidersRecommended": "より良い機能のために、公式プロバイダーの使用を検討してください。カスタムプロバイダーで問題が発生した場合は、以下で問題を報告することを検討してください:",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "公式プロバイダー",
|
||||
"officialProviderDescription": "このプロバイダーはPalmrによって最適化されています。認証情報と設定のみ変更可能です。",
|
||||
"manualConfigTitle": "手動設定",
|
||||
"manualConfigDescription": "すべてのエンドポイントを手動で提供しています。プロバイダーに対して正しいことを確認してください。"
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "認証プロバイダーの削除",
|
||||
"confirmMessage": "本当に\"{displayName}\"プロバイダーを削除しますか?この操作は取り消せません。",
|
||||
"providerId": "プロバイダーID:{name}",
|
||||
"cancel": "キャンセル",
|
||||
"delete": "プロバイダーを削除",
|
||||
"deleting": "削除中..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "받은 파일"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "인증 제공자",
|
||||
"description": "SSO를 위한 외부 인증 제공자 구성",
|
||||
"enabledCount": "{count}개 활성화됨",
|
||||
"loadingProviders": "제공자 로딩 중...",
|
||||
"providersConfigured": "{count}개의 제공자가 구성됨",
|
||||
"enabledOfTotal": "전체 {total}개 중 {enabled}개 활성화됨",
|
||||
"hideDisabledProviders": "비활성화된 제공자 숨기기",
|
||||
"addProvider": "제공자 추가",
|
||||
"addProviderTitle": "제공자 추가",
|
||||
"editProvider": "제공자 편집",
|
||||
"deleteProvider": "제공자 삭제",
|
||||
"enabled": "활성화됨",
|
||||
"disabled": "비활성화됨",
|
||||
"officialProvider": "공식 제공자",
|
||||
"dragToReorder": "드래그하여 순서 변경",
|
||||
"dragDisabledMessage": "제공자 필터링 시 드래그 앤 드롭이 비활성화됩니다. 순서를 변경하려면 모든 제공자를 표시하세요.",
|
||||
"dragEnabledMessage": "제공자를 드래그하여 순서를 변경하세요. 이 순서는 로그인 페이지에 반영됩니다.",
|
||||
"noProvidersEnabled": "활성화된 인증 제공자가 없습니다",
|
||||
"noProvidersConfigured": "구성된 인증 제공자가 없습니다",
|
||||
"form": {
|
||||
"providerName": "제공자 이름",
|
||||
"providerNamePlaceholder": "예: mycompany",
|
||||
"displayName": "표시 이름",
|
||||
"displayNamePlaceholder": "예: 내 회사 SSO",
|
||||
"type": "유형",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "아이콘",
|
||||
"iconPlaceholder": "아이콘 선택",
|
||||
"clientId": "클라이언트 ID",
|
||||
"clientIdPlaceholder": "OAuth 클라이언트 ID",
|
||||
"clientSecret": "클라이언트 시크릿",
|
||||
"clientSecretPlaceholder": "OAuth 클라이언트 시크릿",
|
||||
"oauthScopes": "OAuth 스코프",
|
||||
"scopesPlaceholder": "스코프 입력 (예: openid, profile, email)",
|
||||
"scopesHelpOidc": "제공자 URL을 기반으로 자동 제안된 스코프. 일반적인 OIDC 스코프: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "제공자 URL을 기반으로 자동 제안된 스코프. 일반적인 OAuth2 스코프는 제공자에 따라 다릅니다",
|
||||
"providerUrl": "제공자 URL",
|
||||
"providerUrlPlaceholder": "https://auth.example.com",
|
||||
"providerUrlAutoPlaceholder": "https://your-provider.com (엔드포인트가 자동으로 발견됩니다)",
|
||||
"providerUrlManualPlaceholder": "https://your-provider.com",
|
||||
"autoDiscoveryHelp": "시스템이 자동으로 인증, 토큰 및 사용자 정보 엔드포인트를 발견합니다",
|
||||
"manualConfigurationHelp": "제공자의 기본 URL (엔드포인트는 이것을 기준으로 상대적)",
|
||||
"authorizationEndpoint": "인증 엔드포인트",
|
||||
"authorizationEndpointPlaceholder": "https://auth.example.com/auth",
|
||||
"tokenEndpoint": "토큰 엔드포인트",
|
||||
"tokenEndpointPlaceholder": "https://auth.example.com/token",
|
||||
"userInfoEndpoint": "사용자 정보 엔드포인트",
|
||||
"userInfoEndpointPlaceholder": "https://auth.example.com/userinfo",
|
||||
"configurationMethod": "구성 방법",
|
||||
"autoDiscovery": "자동 발견 (권장)",
|
||||
"autoDiscoveryDescription": "제공자 URL에서 엔드포인트 자동 발견",
|
||||
"manualEndpoints": "수동 엔드포인트",
|
||||
"manualEndpointsDescription": "인증, 토큰 및 사용자 정보 엔드포인트를 수동으로 구성",
|
||||
"callbackUrl": "콜백 URL",
|
||||
"callbackUrlDescription": "OAuth 제공자 구성에서 이 URL을 사용하세요",
|
||||
"copyCallbackUrl": "콜백 URL 복사",
|
||||
"callbackUrlCopied": "콜백 URL이 클립보드에 복사되었습니다!",
|
||||
"adminEmailDomains": "관리자 이메일 도메인",
|
||||
"adminEmailDomainsPlaceholder": "도메인 입력 (예: admin.company.com)",
|
||||
"adminEmailDomainsHelp": "이러한 도메인의 이메일을 가진 사용자에게 관리자 권한이 부여됩니다",
|
||||
"autoRegister": "새 사용자 자동 등록",
|
||||
"officialProviderUrlPlaceholder": "{displayName} URL로 자리표시자 교체",
|
||||
"officialProviderHelp": "이것은 공식 제공자입니다. 엔드포인트가 미리 구성되어 있습니다. 이 URL만 편집할 수 있습니다.",
|
||||
"officialProviderIconHelp": "이 공식 제공자의 아이콘을 사용자 지정할 수 있습니다."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "취소",
|
||||
"save": "저장",
|
||||
"saving": "저장 중...",
|
||||
"adding": "추가 중...",
|
||||
"updating": "업데이트 중...",
|
||||
"saveProvider": "제공자 저장",
|
||||
"delete": "삭제",
|
||||
"deleting": "삭제 중...",
|
||||
"edit": "편집",
|
||||
"enable": "활성화",
|
||||
"disable": "비활성화"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "제공자가 성공적으로 추가되었습니다",
|
||||
"providerUpdated": "제공자가 성공적으로 업데이트되었습니다",
|
||||
"providerDeleted": "제공자가 성공적으로 삭제되었습니다",
|
||||
"providerOrderUpdated": "제공자 순서가 성공적으로 업데이트되었습니다",
|
||||
"fillRequiredFields": "모든 필수 필드를 입력하세요 (이름, 표시 이름, 클라이언트 ID, 클라이언트 시크릿)",
|
||||
"provideUrlOrEndpoints": "자동 발견을 위한 제공자 URL 또는 세 가지 사용자 지정 엔드포인트를 모두 제공하세요",
|
||||
"chooseDiscoveryOrManual": "자동 발견(제공자 URL) 또는 수동 엔드포인트 중 하나를 선택하세요. 둘 다는 안 됩니다",
|
||||
"loadFailed": "제공자 로드에 실패했습니다",
|
||||
"addFailed": "제공자 추가에 실패했습니다",
|
||||
"updateFailed": "제공자 업데이트에 실패했습니다",
|
||||
"deleteFailed": "제공자 삭제에 실패했습니다",
|
||||
"orderUpdateFailed": "제공자 순서 업데이트에 실패했습니다"
|
||||
},
|
||||
"info": {
|
||||
"title": "정보",
|
||||
"officialProvidersRecommended": "더 나은 기능을 위해 공식 제공자 사용을 고려하세요. 사용자 지정 제공자에 문제가 있는 경우 다음에서 이슈를 열어주세요",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "공식 제공자",
|
||||
"officialProviderDescription": "이 제공자는 Palmr에 의해 최적화되었습니다. 자격 증명과 구성만 수정할 수 있습니다.",
|
||||
"manualConfigTitle": "수동 구성",
|
||||
"manualConfigDescription": "모든 엔드포인트를 수동으로 제공하고 있습니다. 제공자에 맞게 올바르게 설정되었는지 확인하세요."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "인증 제공자 삭제",
|
||||
"confirmMessage": "\"{displayName}\" 제공자를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
|
||||
"providerId": "제공자 ID: {name}",
|
||||
"cancel": "취소",
|
||||
"delete": "제공자 삭제",
|
||||
"deleting": "삭제 중..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "Ontvangen bestanden"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "Authenticatie Providers",
|
||||
"description": "Configureer externe authenticatie providers voor SSO",
|
||||
"enabledCount": "{count} ingeschakeld",
|
||||
"loadingProviders": "Providers laden...",
|
||||
"providersConfigured": "{count} providers geconfigureerd",
|
||||
"enabledOfTotal": "{enabled} ingeschakeld van {total} providers",
|
||||
"hideDisabledProviders": "Uitgeschakelde providers verbergen",
|
||||
"addProvider": "Provider Toevoegen",
|
||||
"addProviderTitle": "Provider Toevoegen",
|
||||
"editProvider": "Provider Bewerken",
|
||||
"deleteProvider": "Provider Verwijderen",
|
||||
"enabled": "Ingeschakeld",
|
||||
"disabled": "Uitgeschakeld",
|
||||
"officialProvider": "Officiële Provider",
|
||||
"dragToReorder": "Sleep om te herordenen",
|
||||
"dragDisabledMessage": "Slepen en neerzetten is uitgeschakeld bij het filteren van providers. Toon alle providers om ze opnieuw te ordenen.",
|
||||
"dragEnabledMessage": "Sleep providers om ze te herordenen. Deze volgorde wordt weergegeven op de inlogpagina.",
|
||||
"noProvidersEnabled": "Geen authenticatie providers ingeschakeld",
|
||||
"noProvidersConfigured": "Geen authenticatie providers geconfigureerd",
|
||||
"form": {
|
||||
"providerName": "Provider Naam",
|
||||
"providerNamePlaceholder": "bijv. mijnbedrijf",
|
||||
"displayName": "Weergavenaam",
|
||||
"displayNamePlaceholder": "bijv. Mijn Bedrijf SSO",
|
||||
"type": "Type",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "Icoon",
|
||||
"iconPlaceholder": "Selecteer een icoon",
|
||||
"clientId": "Client ID",
|
||||
"clientIdPlaceholder": "Uw OAuth client ID",
|
||||
"clientSecret": "Client Secret",
|
||||
"clientSecretPlaceholder": "Uw OAuth client secret",
|
||||
"oauthScopes": "OAuth Scopes",
|
||||
"scopesPlaceholder": "Voer scopes in (bijv. openid, profile, email)",
|
||||
"scopesHelpOidc": "Scopes automatisch voorgesteld op basis van Provider URL. Gebruikelijke OIDC scopes: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "Scopes automatisch voorgesteld op basis van Provider URL. Gebruikelijke OAuth2 scopes zijn afhankelijk van de provider",
|
||||
"providerUrl": "Provider URL",
|
||||
"providerUrlPlaceholder": "https://auth.voorbeeld.nl",
|
||||
"providerUrlAutoPlaceholder": "https://uw-provider.nl (eindpunten worden automatisch ontdekt)",
|
||||
"providerUrlManualPlaceholder": "https://uw-provider.nl",
|
||||
"autoDiscoveryHelp": "Het systeem zal automatisch autorisatie-, token- en gebruikersinformatie-eindpunten ontdekken",
|
||||
"manualConfigurationHelp": "Basis-URL van uw provider (eindpunten zijn relatief hieraan)",
|
||||
"authorizationEndpoint": "Autorisatie Eindpunt",
|
||||
"authorizationEndpointPlaceholder": "https://auth.voorbeeld.nl/auth",
|
||||
"tokenEndpoint": "Token Eindpunt",
|
||||
"tokenEndpointPlaceholder": "https://auth.voorbeeld.nl/token",
|
||||
"userInfoEndpoint": "Gebruikersinformatie Eindpunt",
|
||||
"userInfoEndpointPlaceholder": "https://auth.voorbeeld.nl/userinfo",
|
||||
"configurationMethod": "Configuratiemethode",
|
||||
"autoDiscovery": "Automatische Ontdekking (Aanbevolen)",
|
||||
"autoDiscoveryDescription": "Automatisch eindpunten ontdekken van Provider URL",
|
||||
"manualEndpoints": "Handmatige Eindpunten",
|
||||
"manualEndpointsDescription": "Handmatig configureren van autorisatie-, token- en gebruikersinformatie-eindpunten",
|
||||
"callbackUrl": "Callback URL",
|
||||
"callbackUrlDescription": "Gebruik deze URL in uw OAuth provider configuratie",
|
||||
"copyCallbackUrl": "Kopieer Callback URL",
|
||||
"callbackUrlCopied": "Callback URL gekopieerd naar klembord!",
|
||||
"adminEmailDomains": "Admin E-mail Domeinen",
|
||||
"adminEmailDomainsPlaceholder": "Voer domeinen in (bijv. admin.bedrijf.nl)",
|
||||
"adminEmailDomainsHelp": "Gebruikers met e-mails van deze domeinen krijgen beheerdersrechten",
|
||||
"autoRegister": "Automatisch nieuwe gebruikers registreren",
|
||||
"officialProviderUrlPlaceholder": "Vervang placeholder met uw {displayName} URL",
|
||||
"officialProviderHelp": "Dit is een officiële provider. Eindpunten zijn vooraf geconfigureerd. U kunt alleen deze URL bewerken.",
|
||||
"officialProviderIconHelp": "U kunt het icoon voor deze officiële provider aanpassen."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "Annuleren",
|
||||
"save": "Opslaan",
|
||||
"saving": "Opslaan...",
|
||||
"adding": "Toevoegen...",
|
||||
"updating": "Bijwerken...",
|
||||
"saveProvider": "Provider Opslaan",
|
||||
"delete": "Verwijderen",
|
||||
"deleting": "Verwijderen...",
|
||||
"edit": "Bewerken",
|
||||
"enable": "Inschakelen",
|
||||
"disable": "Uitschakelen"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "Provider succesvol toegevoegd",
|
||||
"providerUpdated": "Provider succesvol bijgewerkt",
|
||||
"providerDeleted": "Provider succesvol verwijderd",
|
||||
"providerOrderUpdated": "Provider volgorde succesvol bijgewerkt",
|
||||
"fillRequiredFields": "Vul alle verplichte velden in (naam, weergavenaam, client ID, client secret)",
|
||||
"provideUrlOrEndpoints": "Geef een Provider URL op voor automatische ontdekking OF alle drie de aangepaste eindpunten",
|
||||
"chooseDiscoveryOrManual": "Kies óf automatische ontdekking (Provider URL) óf handmatige eindpunten, niet beide",
|
||||
"loadFailed": "Laden van providers mislukt",
|
||||
"addFailed": "Toevoegen van provider mislukt",
|
||||
"updateFailed": "Bijwerken van provider mislukt",
|
||||
"deleteFailed": "Verwijderen van provider mislukt",
|
||||
"orderUpdateFailed": "Bijwerken van provider volgorde mislukt"
|
||||
},
|
||||
"info": {
|
||||
"title": "Informatie",
|
||||
"officialProvidersRecommended": "Voor betere functionaliteit raden we aan om officiële providers te gebruiken. Als u problemen heeft met een aangepaste provider, overweeg dan een issue te openen op",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "Officiële Provider",
|
||||
"officialProviderDescription": "Deze provider is geoptimaliseerd door Palmr. Alleen inloggegevens en configuratie kunnen worden aangepast.",
|
||||
"manualConfigTitle": "Handmatige Configuratie",
|
||||
"manualConfigDescription": "U voorziet alle eindpunten handmatig. Zorg ervoor dat ze correct zijn voor uw provider."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Authenticatie Provider Verwijderen",
|
||||
"confirmMessage": "Weet u zeker dat u de \"{displayName}\" provider wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.",
|
||||
"providerId": "Provider ID: {name}",
|
||||
"cancel": "Annuleren",
|
||||
"delete": "Provider Verwijderen",
|
||||
"deleting": "Verwijderen..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "Otrzymane pliki"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "Dostawcy uwierzytelniania",
|
||||
"description": "Skonfiguruj zewnętrznych dostawców uwierzytelniania dla SSO",
|
||||
"enabledCount": "{count} włączonych",
|
||||
"loadingProviders": "Ładowanie dostawców...",
|
||||
"providersConfigured": "Skonfigurowano {count} dostawców",
|
||||
"enabledOfTotal": "{enabled} włączonych z {total} dostawców",
|
||||
"hideDisabledProviders": "Ukryj wyłączonych dostawców",
|
||||
"addProvider": "Dodaj dostawcę",
|
||||
"addProviderTitle": "Dodaj dostawcę",
|
||||
"editProvider": "Edytuj dostawcę",
|
||||
"deleteProvider": "Usuń dostawcę",
|
||||
"enabled": "Włączony",
|
||||
"disabled": "Wyłączony",
|
||||
"officialProvider": "Oficjalny dostawca",
|
||||
"dragToReorder": "Przeciągnij, aby zmienić kolejność",
|
||||
"dragDisabledMessage": "Przeciąganie jest wyłączone podczas filtrowania dostawców. Pokaż wszystkich dostawców, aby zmienić ich kolejność.",
|
||||
"dragEnabledMessage": "Przeciągnij dostawców, aby zmienić ich kolejność. Ta kolejność będzie widoczna na stronie logowania.",
|
||||
"noProvidersEnabled": "Brak włączonych dostawców uwierzytelniania",
|
||||
"noProvidersConfigured": "Nie skonfigurowano żadnych dostawców uwierzytelniania",
|
||||
"form": {
|
||||
"providerName": "Nazwa dostawcy",
|
||||
"providerNamePlaceholder": "np. mojafirma",
|
||||
"displayName": "Nazwa wyświetlana",
|
||||
"displayNamePlaceholder": "np. SSO Mojej Firmy",
|
||||
"type": "Typ",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "Ikona",
|
||||
"iconPlaceholder": "Wybierz ikonę",
|
||||
"clientId": "ID klienta",
|
||||
"clientIdPlaceholder": "Twój ID klienta OAuth",
|
||||
"clientSecret": "Sekret klienta",
|
||||
"clientSecretPlaceholder": "Twój sekret klienta OAuth",
|
||||
"oauthScopes": "Zakresy OAuth",
|
||||
"scopesPlaceholder": "Wprowadź zakresy (np. openid, profile, email)",
|
||||
"scopesHelpOidc": "Zakresy sugerowane automatycznie na podstawie URL dostawcy. Typowe zakresy OIDC: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "Zakresy sugerowane automatycznie na podstawie URL dostawcy. Typowe zakresy OAuth2 zależą od dostawcy",
|
||||
"providerUrl": "URL dostawcy",
|
||||
"providerUrlPlaceholder": "https://auth.example.com",
|
||||
"providerUrlAutoPlaceholder": "https://twoj-dostawca.com (punkty końcowe zostaną wykryte automatycznie)",
|
||||
"providerUrlManualPlaceholder": "https://twoj-dostawca.com",
|
||||
"autoDiscoveryHelp": "System automatycznie wykryje punkty końcowe autoryzacji, tokena i informacji o użytkowniku",
|
||||
"manualConfigurationHelp": "Podstawowy URL twojego dostawcy (punkty końcowe będą względne do tego URL)",
|
||||
"authorizationEndpoint": "Punkt końcowy autoryzacji",
|
||||
"authorizationEndpointPlaceholder": "https://auth.example.com/auth",
|
||||
"tokenEndpoint": "Punkt końcowy tokena",
|
||||
"tokenEndpointPlaceholder": "https://auth.example.com/token",
|
||||
"userInfoEndpoint": "Punkt końcowy informacji o użytkowniku",
|
||||
"userInfoEndpointPlaceholder": "https://auth.example.com/userinfo",
|
||||
"configurationMethod": "Metoda konfiguracji",
|
||||
"autoDiscovery": "Automatyczne wykrywanie (Zalecane)",
|
||||
"autoDiscoveryDescription": "Automatycznie wykryj punkty końcowe z URL dostawcy",
|
||||
"manualEndpoints": "Ręczne punkty końcowe",
|
||||
"manualEndpointsDescription": "Ręcznie skonfiguruj punkty końcowe autoryzacji, tokena i informacji o użytkowniku",
|
||||
"callbackUrl": "URL wywołania zwrotnego",
|
||||
"callbackUrlDescription": "Użyj tego URL w konfiguracji twojego dostawcy OAuth",
|
||||
"copyCallbackUrl": "Kopiuj URL wywołania zwrotnego",
|
||||
"callbackUrlCopied": "URL wywołania zwrotnego skopiowany do schowka!",
|
||||
"adminEmailDomains": "Domeny email administratorów",
|
||||
"adminEmailDomainsPlaceholder": "Wprowadź domeny (np. admin.firma.com)",
|
||||
"adminEmailDomainsHelp": "Użytkownicy z adresami email z tych domen otrzymają uprawnienia administratora",
|
||||
"autoRegister": "Automatycznie rejestruj nowych użytkowników",
|
||||
"officialProviderUrlPlaceholder": "Zastąp placeholder swoim URL {displayName}",
|
||||
"officialProviderHelp": "To jest oficjalny dostawca. Punkty końcowe są wstępnie skonfigurowane. Możesz edytować tylko ten URL.",
|
||||
"officialProviderIconHelp": "Możesz dostosować ikonę dla tego oficjalnego dostawcy."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "Anuluj",
|
||||
"save": "Zapisz",
|
||||
"saving": "Zapisywanie...",
|
||||
"adding": "Dodawanie...",
|
||||
"updating": "Aktualizowanie...",
|
||||
"saveProvider": "Zapisz dostawcę",
|
||||
"delete": "Usuń",
|
||||
"deleting": "Usuwanie...",
|
||||
"edit": "Edytuj",
|
||||
"enable": "Włącz",
|
||||
"disable": "Wyłącz"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "Dostawca został pomyślnie dodany",
|
||||
"providerUpdated": "Dostawca został pomyślnie zaktualizowany",
|
||||
"providerDeleted": "Dostawca został pomyślnie usunięty",
|
||||
"providerOrderUpdated": "Kolejność dostawców została pomyślnie zaktualizowana",
|
||||
"fillRequiredFields": "Proszę wypełnić wszystkie wymagane pola (nazwa, nazwa wyświetlana, ID klienta, sekret klienta)",
|
||||
"provideUrlOrEndpoints": "Podaj albo URL dostawcy do automatycznego wykrywania ALBO wszystkie trzy niestandardowe punkty końcowe",
|
||||
"chooseDiscoveryOrManual": "Wybierz albo automatyczne wykrywanie (URL dostawcy) ALBO ręczne punkty końcowe, nie oba",
|
||||
"loadFailed": "Nie udało się załadować dostawców",
|
||||
"addFailed": "Nie udało się dodać dostawcy",
|
||||
"updateFailed": "Nie udało się zaktualizować dostawcy",
|
||||
"deleteFailed": "Nie udało się usunąć dostawcy",
|
||||
"orderUpdateFailed": "Nie udało się zaktualizować kolejności dostawców"
|
||||
},
|
||||
"info": {
|
||||
"title": "Informacja",
|
||||
"officialProvidersRecommended": "Dla lepszej funkcjonalności rozważ użycie oficjalnych dostawców. Jeśli masz problemy z niestandardowym dostawcą, rozważ zgłoszenie problemu na",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "Oficjalny dostawca",
|
||||
"officialProviderDescription": "Ten dostawca jest zoptymalizowany przez Palmr. Można modyfikować tylko poświadczenia i konfigurację.",
|
||||
"manualConfigTitle": "Konfiguracja ręczna",
|
||||
"manualConfigDescription": "Podajesz wszystkie punkty końcowe ręcznie. Upewnij się, że są poprawne dla twojego dostawcy."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Usuń dostawcę uwierzytelniania",
|
||||
"confirmMessage": "Czy na pewno chcesz usunąć dostawcę \"{displayName}\"? Tej akcji nie można cofnąć.",
|
||||
"providerId": "ID dostawcy: {name}",
|
||||
"cancel": "Anuluj",
|
||||
"delete": "Usuń dostawcę",
|
||||
"deleting": "Usuwanie..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "Arquivos recebidos"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "Provedores de autenticação",
|
||||
"description": "Configure provedores de autenticação externos para SSO",
|
||||
"enabledCount": "{count} ativados",
|
||||
"loadingProviders": "Carregando provedores...",
|
||||
"providersConfigured": "{count} provedores configurados",
|
||||
"enabledOfTotal": "{enabled} ativados de {total} provedores",
|
||||
"hideDisabledProviders": "Ocultar provedores desativados",
|
||||
"addProvider": "Adicionar provedor",
|
||||
"addProviderTitle": "Adicionar provedor",
|
||||
"editProvider": "Editar provedor",
|
||||
"deleteProvider": "Excluir provedor",
|
||||
"enabled": "Ativado",
|
||||
"disabled": "Desativado",
|
||||
"officialProvider": "Provedor oficial",
|
||||
"dragToReorder": "Arraste para reordenar",
|
||||
"dragDisabledMessage": "Arrastar e soltar está desativado ao filtrar provedores. Mostre todos os provedores para reordená-los.",
|
||||
"dragEnabledMessage": "Arraste os provedores para reordená-los. Esta ordem será refletida na página de login.",
|
||||
"noProvidersEnabled": "Nenhum provedor de autenticação ativado",
|
||||
"noProvidersConfigured": "Nenhum provedor de autenticação configurado",
|
||||
"form": {
|
||||
"providerName": "Nome do provedor",
|
||||
"providerNamePlaceholder": "ex: minhaempresa",
|
||||
"displayName": "Nome de exibição",
|
||||
"displayNamePlaceholder": "ex: SSO da Minha Empresa",
|
||||
"type": "Tipo",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "Ícone",
|
||||
"iconPlaceholder": "Selecione um ícone",
|
||||
"clientId": "ID do cliente",
|
||||
"clientIdPlaceholder": "Seu ID do cliente OAuth",
|
||||
"clientSecret": "Segredo do cliente",
|
||||
"clientSecretPlaceholder": "Seu segredo do cliente OAuth",
|
||||
"oauthScopes": "Escopos OAuth",
|
||||
"scopesPlaceholder": "Digite os escopos (ex: openid, profile, email)",
|
||||
"scopesHelpOidc": "Escopos sugeridos automaticamente com base na URL do Provedor. Escopos comuns do OIDC: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "Escopos sugeridos automaticamente com base na URL do Provedor. Escopos comuns do OAuth2 dependem do provedor",
|
||||
"providerUrl": "URL do provedor",
|
||||
"providerUrlPlaceholder": "https://auth.exemplo.com",
|
||||
"providerUrlAutoPlaceholder": "https://seu-provedor.com (endpoints serão descobertos automaticamente)",
|
||||
"providerUrlManualPlaceholder": "https://seu-provedor.com",
|
||||
"autoDiscoveryHelp": "O sistema descobrirá automaticamente os endpoints de autorização, token e informações do usuário",
|
||||
"manualConfigurationHelp": "URL base do seu provedor (endpoints serão relativos a esta)",
|
||||
"authorizationEndpoint": "Endpoint de Autorização",
|
||||
"authorizationEndpointPlaceholder": "https://auth.exemplo.com/auth",
|
||||
"tokenEndpoint": "Endpoint de Token",
|
||||
"tokenEndpointPlaceholder": "https://auth.exemplo.com/token",
|
||||
"userInfoEndpoint": "Endpoint de informações do usuário",
|
||||
"userInfoEndpointPlaceholder": "https://auth.exemplo.com/userinfo",
|
||||
"configurationMethod": "Método de configuração",
|
||||
"autoDiscovery": "Descoberta Automática (Recomendado)",
|
||||
"autoDiscoveryDescription": "Descobrir endpoints automaticamente a partir da URL do Provedor",
|
||||
"manualEndpoints": "Endpoints Manuais",
|
||||
"manualEndpointsDescription": "Configurar manualmente os endpoints de autorização, token e informações do usuário",
|
||||
"callbackUrl": "URL de Retorno",
|
||||
"callbackUrlDescription": "Use esta URL na configuração do seu provedor OAuth",
|
||||
"copyCallbackUrl": "Copiar URL de Retorno",
|
||||
"callbackUrlCopied": "URL de retorno copiada para a área de transferência!",
|
||||
"adminEmailDomains": "Domínios de Email de Administrador",
|
||||
"adminEmailDomainsPlaceholder": "Digite os domínios (ex: admin.empresa.com)",
|
||||
"adminEmailDomainsHelp": "Usuários com emails destes domínios receberão privilégios de administrador",
|
||||
"autoRegister": "Registrar automaticamente novos usuários",
|
||||
"officialProviderUrlPlaceholder": "Substitua o placeholder com sua URL do {displayName}",
|
||||
"officialProviderHelp": "Este é um provedor oficial. Os endpoints estão pré-configurados. Você pode editar apenas esta URL.",
|
||||
"officialProviderIconHelp": "Você pode personalizar o ícone para este provedor oficial."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "Cancelar",
|
||||
"save": "Salvar",
|
||||
"saving": "Salvando...",
|
||||
"adding": "Adicionando...",
|
||||
"updating": "Atualizando...",
|
||||
"saveProvider": "Salvar Provedor",
|
||||
"delete": "Excluir",
|
||||
"deleting": "Excluindo...",
|
||||
"edit": "Editar",
|
||||
"enable": "Ativar",
|
||||
"disable": "Desativar"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "Provedor adicionado com sucesso",
|
||||
"providerUpdated": "Provedor atualizado com sucesso",
|
||||
"providerDeleted": "Provedor excluído com sucesso",
|
||||
"providerOrderUpdated": "Ordem dos provedores atualizada com sucesso",
|
||||
"fillRequiredFields": "Por favor, preencha todos os campos obrigatórios (nome, nome de exibição, ID do cliente, segredo do cliente)",
|
||||
"provideUrlOrEndpoints": "Forneça uma URL do Provedor para descoberta automática OU todos os três endpoints personalizados",
|
||||
"chooseDiscoveryOrManual": "Escolha descoberta automática (URL do Provedor) OU endpoints manuais, não ambos",
|
||||
"loadFailed": "Falha ao carregar provedores",
|
||||
"addFailed": "Falha ao adicionar provedor",
|
||||
"updateFailed": "Falha ao atualizar provedor",
|
||||
"deleteFailed": "Falha ao excluir provedor",
|
||||
"orderUpdateFailed": "Falha ao atualizar ordem dos provedores"
|
||||
},
|
||||
"info": {
|
||||
"title": "Informações",
|
||||
"officialProvidersRecommended": "Para melhor funcionalidade, considere usar provedores oficiais. Se você tiver problemas com um provedor personalizado, considere abrir uma issue no",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "Provedor Oficial",
|
||||
"officialProviderDescription": "Este provedor é otimizado pelo Palmr. Apenas credenciais e configuração podem ser modificadas.",
|
||||
"manualConfigTitle": "Configuração Manual",
|
||||
"manualConfigDescription": "Você está fornecendo todos os endpoints manualmente. Certifique-se de que estejam corretos para seu provedor."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Excluir Provedor de Autenticação",
|
||||
"confirmMessage": "Tem certeza que deseja excluir o provedor \"{displayName}\"? Esta ação não pode ser desfeita.",
|
||||
"providerId": "ID do Provedor: {name}",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Excluir Provedor",
|
||||
"deleting": "Excluindo..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "Полученные файлы"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "Провайдеры аутентификации",
|
||||
"description": "Настройка внешних провайдеров аутентификации для SSO",
|
||||
"enabledCount": "{count} включено",
|
||||
"loadingProviders": "Загрузка провайдеров...",
|
||||
"providersConfigured": "Настроено {count} провайдеров",
|
||||
"enabledOfTotal": "Включено {enabled} из {total} провайдеров",
|
||||
"hideDisabledProviders": "Скрыть отключенные провайдеры",
|
||||
"addProvider": "Добавить провайдера",
|
||||
"addProviderTitle": "Добавить провайдера",
|
||||
"editProvider": "Редактировать провайдера",
|
||||
"deleteProvider": "Удалить провайдера",
|
||||
"enabled": "Включен",
|
||||
"disabled": "Отключен",
|
||||
"officialProvider": "Официальный провайдер",
|
||||
"dragToReorder": "Перетащите для изменения порядка",
|
||||
"dragDisabledMessage": "Перетаскивание отключено при фильтрации провайдеров. Покажите всех провайдеров для изменения порядка.",
|
||||
"dragEnabledMessage": "Перетащите провайдеров для изменения порядка. Этот порядок будет отображен на странице входа.",
|
||||
"noProvidersEnabled": "Нет включенных провайдеров аутентификации",
|
||||
"noProvidersConfigured": "Нет настроенных провайдеров аутентификации",
|
||||
"form": {
|
||||
"providerName": "Имя провайдера",
|
||||
"providerNamePlaceholder": "например, mycompany",
|
||||
"displayName": "Отображаемое имя",
|
||||
"displayNamePlaceholder": "например, SSO моей компании",
|
||||
"type": "Тип",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "Иконка",
|
||||
"iconPlaceholder": "Выберите иконку",
|
||||
"clientId": "ID клиента",
|
||||
"clientIdPlaceholder": "Ваш ID клиента OAuth",
|
||||
"clientSecret": "Секрет клиента",
|
||||
"clientSecretPlaceholder": "Ваш секрет клиента OAuth",
|
||||
"oauthScopes": "Области OAuth",
|
||||
"scopesPlaceholder": "Введите области (например, openid, profile, email)",
|
||||
"scopesHelpOidc": "Области автоматически предлагаются на основе URL провайдера. Общие области OIDC: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "Области автоматически предлагаются на основе URL провайдера. Общие области OAuth2 зависят от провайдера",
|
||||
"providerUrl": "URL провайдера",
|
||||
"providerUrlPlaceholder": "https://auth.example.com",
|
||||
"providerUrlAutoPlaceholder": "https://your-provider.com (конечные точки будут обнаружены автоматически)",
|
||||
"providerUrlManualPlaceholder": "https://your-provider.com",
|
||||
"autoDiscoveryHelp": "Система автоматически обнаружит конечные точки авторизации, токена и информации о пользователе",
|
||||
"manualConfigurationHelp": "Базовый URL вашего провайдера (конечные точки будут относительно этого URL)",
|
||||
"authorizationEndpoint": "Конечная точка авторизации",
|
||||
"authorizationEndpointPlaceholder": "https://auth.example.com/auth",
|
||||
"tokenEndpoint": "Конечная точка токена",
|
||||
"tokenEndpointPlaceholder": "https://auth.example.com/token",
|
||||
"userInfoEndpoint": "Конечная точка информации о пользователе",
|
||||
"userInfoEndpointPlaceholder": "https://auth.example.com/userinfo",
|
||||
"configurationMethod": "Метод конфигурации",
|
||||
"autoDiscovery": "Автоматическое обнаружение (Рекомендуется)",
|
||||
"autoDiscoveryDescription": "Автоматически обнаруживать конечные точки из URL провайдера",
|
||||
"manualEndpoints": "Ручные конечные точки",
|
||||
"manualEndpointsDescription": "Вручную настроить конечные точки авторизации, токена и информации о пользователе",
|
||||
"callbackUrl": "URL обратного вызова",
|
||||
"callbackUrlDescription": "Используйте этот URL в конфигурации вашего OAuth провайдера",
|
||||
"copyCallbackUrl": "Копировать URL обратного вызова",
|
||||
"callbackUrlCopied": "URL обратного вызова скопирован в буфер обмена!",
|
||||
"adminEmailDomains": "Домены электронной почты администраторов",
|
||||
"adminEmailDomainsPlaceholder": "Введите домены (например, admin.company.com)",
|
||||
"adminEmailDomainsHelp": "Пользователи с электронной почтой из этих доменов получат права администратора",
|
||||
"autoRegister": "Автоматически регистрировать новых пользователей",
|
||||
"officialProviderUrlPlaceholder": "Замените заполнитель вашим URL {displayName}",
|
||||
"officialProviderHelp": "Это официальный провайдер. Конечные точки предварительно настроены. Вы можете редактировать только этот URL.",
|
||||
"officialProviderIconHelp": "Вы можете настроить иконку для этого официального провайдера."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "Отмена",
|
||||
"save": "Сохранить",
|
||||
"saving": "Сохранение...",
|
||||
"adding": "Добавление...",
|
||||
"updating": "Обновление...",
|
||||
"saveProvider": "Сохранить провайдера",
|
||||
"delete": "Удалить",
|
||||
"deleting": "Удаление...",
|
||||
"edit": "Редактировать",
|
||||
"enable": "Включить",
|
||||
"disable": "Отключить"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "Провайдер успешно добавлен",
|
||||
"providerUpdated": "Провайдер успешно обновлен",
|
||||
"providerDeleted": "Провайдер успешно удален",
|
||||
"providerOrderUpdated": "Порядок провайдеров успешно обновлен",
|
||||
"fillRequiredFields": "Пожалуйста, заполните все обязательные поля (имя, отображаемое имя, ID клиента, секрет клиента)",
|
||||
"provideUrlOrEndpoints": "Укажите либо URL провайдера для автоматического обнаружения, ЛИБО все три пользовательские конечные точки",
|
||||
"chooseDiscoveryOrManual": "Выберите либо автоматическое обнаружение (URL провайдера), ЛИБО ручные конечные точки, но не оба варианта",
|
||||
"loadFailed": "Не удалось загрузить провайдеров",
|
||||
"addFailed": "Не удалось добавить провайдера",
|
||||
"updateFailed": "Не удалось обновить провайдера",
|
||||
"deleteFailed": "Не удалось удалить провайдера",
|
||||
"orderUpdateFailed": "Не удалось обновить порядок провайдеров"
|
||||
},
|
||||
"info": {
|
||||
"title": "Информация",
|
||||
"officialProvidersRecommended": "Для лучшей функциональности рекомендуется использовать официальных провайдеров. Если у вас возникли проблемы с пользовательским провайдером, рассмотрите возможность создания issue на",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "Официальный провайдер",
|
||||
"officialProviderDescription": "Этот провайдер оптимизирован Palmr. Можно изменять только учетные данные и конфигурацию.",
|
||||
"manualConfigTitle": "Ручная настройка",
|
||||
"manualConfigDescription": "Вы предоставляете все конечные точки вручную. Убедитесь, что они правильны для вашего провайдера."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Удалить провайдера аутентификации",
|
||||
"confirmMessage": "Вы уверены, что хотите удалить провайдера \"{displayName}\"? Это действие нельзя отменить.",
|
||||
"providerId": "ID провайдера: {name}",
|
||||
"cancel": "Отмена",
|
||||
"delete": "Удалить провайдера",
|
||||
"deleting": "Удаление..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "Alınan Dosyalar"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "Kimlik Doğrulama Sağlayıcıları",
|
||||
"description": "SSO için harici kimlik doğrulama sağlayıcılarını yapılandırın",
|
||||
"enabledCount": "{count} etkin",
|
||||
"loadingProviders": "Sağlayıcılar yükleniyor...",
|
||||
"providersConfigured": "{count} sağlayıcı yapılandırıldı",
|
||||
"enabledOfTotal": "{total} sağlayıcıdan {enabled} tanesi etkin",
|
||||
"hideDisabledProviders": "Devre dışı sağlayıcıları gizle",
|
||||
"addProvider": "Sağlayıcı Ekle",
|
||||
"addProviderTitle": "Sağlayıcı Ekle",
|
||||
"editProvider": "Sağlayıcıyı Düzenle",
|
||||
"deleteProvider": "Sağlayıcıyı Sil",
|
||||
"enabled": "Etkin",
|
||||
"disabled": "Devre Dışı",
|
||||
"officialProvider": "Resmi Sağlayıcı",
|
||||
"dragToReorder": "Yeniden sıralamak için sürükleyin",
|
||||
"dragDisabledMessage": "Sağlayıcıları filtrelerken sürükle ve bırak devre dışıdır. Yeniden sıralamak için tüm sağlayıcıları gösterin.",
|
||||
"dragEnabledMessage": "Sağlayıcıları yeniden sıralamak için sürükleyin. Bu sıralama giriş sayfasına yansıtılacaktır.",
|
||||
"noProvidersEnabled": "Etkin kimlik doğrulama sağlayıcısı yok",
|
||||
"noProvidersConfigured": "Yapılandırılmış kimlik doğrulama sağlayıcısı yok",
|
||||
"form": {
|
||||
"providerName": "Sağlayıcı Adı",
|
||||
"providerNamePlaceholder": "örn., sirketim",
|
||||
"displayName": "Görünen Ad",
|
||||
"displayNamePlaceholder": "örn., Şirketim SSO",
|
||||
"type": "Tür",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "Simge",
|
||||
"iconPlaceholder": "Bir simge seçin",
|
||||
"clientId": "İstemci Kimliği",
|
||||
"clientIdPlaceholder": "OAuth istemci kimliğiniz",
|
||||
"clientSecret": "İstemci Sırrı",
|
||||
"clientSecretPlaceholder": "OAuth istemci sırrınız",
|
||||
"oauthScopes": "OAuth Kapsamları",
|
||||
"scopesPlaceholder": "Kapsamları girin (örn., openid, profile, email)",
|
||||
"scopesHelpOidc": "Sağlayıcı URL'sine göre otomatik önerilen kapsamlar. Yaygın OIDC kapsamları: openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "Sağlayıcı URL'sine göre otomatik önerilen kapsamlar. Yaygın OAuth2 kapsamları sağlayıcıya bağlıdır",
|
||||
"providerUrl": "Sağlayıcı URL'si",
|
||||
"providerUrlPlaceholder": "https://auth.example.com",
|
||||
"providerUrlAutoPlaceholder": "https://your-provider.com (uç noktalar otomatik keşfedilecek)",
|
||||
"providerUrlManualPlaceholder": "https://your-provider.com",
|
||||
"autoDiscoveryHelp": "Sistem otomatik olarak yetkilendirme, token ve kullanıcı bilgisi uç noktalarını keşfedecek",
|
||||
"manualConfigurationHelp": "Sağlayıcınızın temel URL'si (uç noktalar buna göre belirlenecek)",
|
||||
"authorizationEndpoint": "Yetkilendirme Uç Noktası",
|
||||
"authorizationEndpointPlaceholder": "https://auth.example.com/auth",
|
||||
"tokenEndpoint": "Token Uç Noktası",
|
||||
"tokenEndpointPlaceholder": "https://auth.example.com/token",
|
||||
"userInfoEndpoint": "Kullanıcı Bilgisi Uç Noktası",
|
||||
"userInfoEndpointPlaceholder": "https://auth.example.com/userinfo",
|
||||
"configurationMethod": "Yapılandırma Yöntemi",
|
||||
"autoDiscovery": "Otomatik Keşif (Önerilen)",
|
||||
"autoDiscoveryDescription": "Sağlayıcı URL'sinden uç noktaları otomatik keşfet",
|
||||
"manualEndpoints": "Manuel Uç Noktalar",
|
||||
"manualEndpointsDescription": "Yetkilendirme, token ve kullanıcı bilgisi uç noktalarını manuel yapılandır",
|
||||
"callbackUrl": "Geri Dönüş URL'si",
|
||||
"callbackUrlDescription": "Bu URL'yi OAuth sağlayıcı yapılandırmanızda kullanın",
|
||||
"copyCallbackUrl": "Geri Dönüş URL'sini Kopyala",
|
||||
"callbackUrlCopied": "Geri dönüş URL'si panoya kopyalandı!",
|
||||
"adminEmailDomains": "Yönetici E-posta Etki Alanları",
|
||||
"adminEmailDomainsPlaceholder": "Etki alanlarını girin (örn., admin.sirket.com)",
|
||||
"adminEmailDomainsHelp": "Bu etki alanlarından e-postalara sahip kullanıcılara yönetici yetkileri verilecek",
|
||||
"autoRegister": "Yeni kullanıcıları otomatik kaydet",
|
||||
"officialProviderUrlPlaceholder": "{displayName} URL'nizi yer tutucuyla değiştirin",
|
||||
"officialProviderHelp": "Bu resmi bir sağlayıcıdır. Uç noktalar önceden yapılandırılmıştır. Sadece bu URL'yi düzenleyebilirsiniz.",
|
||||
"officialProviderIconHelp": "Bu resmi sağlayıcının simgesini özelleştirebilirsiniz."
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "İptal",
|
||||
"save": "Kaydet",
|
||||
"saving": "Kaydediliyor...",
|
||||
"adding": "Ekleniyor...",
|
||||
"updating": "Güncelleniyor...",
|
||||
"saveProvider": "Sağlayıcıyı Kaydet",
|
||||
"delete": "Sil",
|
||||
"deleting": "Siliniyor...",
|
||||
"edit": "Düzenle",
|
||||
"enable": "Etkinleştir",
|
||||
"disable": "Devre Dışı Bırak"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "Sağlayıcı başarıyla eklendi",
|
||||
"providerUpdated": "Sağlayıcı başarıyla güncellendi",
|
||||
"providerDeleted": "Sağlayıcı başarıyla silindi",
|
||||
"providerOrderUpdated": "Sağlayıcı sıralaması başarıyla güncellendi",
|
||||
"fillRequiredFields": "Lütfen tüm gerekli alanları doldurun (ad, görünen ad, istemci kimliği, istemci sırrı)",
|
||||
"provideUrlOrEndpoints": "Otomatik keşif için bir Sağlayıcı URL'si VEYA üç özel uç noktanın tümünü sağlayın",
|
||||
"chooseDiscoveryOrManual": "Otomatik keşif (Sağlayıcı URL'si) VEYA manuel uç noktalardan birini seçin, ikisini birden değil",
|
||||
"loadFailed": "Sağlayıcılar yüklenemedi",
|
||||
"addFailed": "Sağlayıcı eklenemedi",
|
||||
"updateFailed": "Sağlayıcı güncellenemedi",
|
||||
"deleteFailed": "Sağlayıcı silinemedi",
|
||||
"orderUpdateFailed": "Sağlayıcı sıralaması güncellenemedi"
|
||||
},
|
||||
"info": {
|
||||
"title": "Bilgi",
|
||||
"officialProvidersRecommended": "Daha iyi işlevsellik için resmi sağlayıcıları kullanmayı düşünün. Özel bir sağlayıcıyla ilgili sorun yaşarsanız,",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "Resmi Sağlayıcı",
|
||||
"officialProviderDescription": "Bu sağlayıcı Palmr tarafından optimize edilmiştir. Sadece kimlik bilgileri ve yapılandırma değiştirilebilir.",
|
||||
"manualConfigTitle": "Manuel Yapılandırma",
|
||||
"manualConfigDescription": "Tüm uç noktaları manuel olarak sağlıyorsunuz. Sağlayıcınız için doğru olduklarından emin olun."
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "Kimlik Doğrulama Sağlayıcısını Sil",
|
||||
"confirmMessage": "\"{displayName}\" sağlayıcısını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
|
||||
"providerId": "Sağlayıcı Kimliği: {name}",
|
||||
"cancel": "İptal",
|
||||
"delete": "Sağlayıcıyı Sil",
|
||||
"deleting": "Siliniyor..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,342 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script para traduzir automaticamente strings marcadas com [TO_TRANSLATE]
|
||||
usando Google Translate gratuito.
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List, Tuple, Optional
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
# Mapeamento de códigos de idioma dos arquivos para códigos do Google Translate
|
||||
LANGUAGE_MAPPING = {
|
||||
'pt-BR.json': 'pt', # Português (Brasil) -> Português
|
||||
'es-ES.json': 'es', # Espanhol (Espanha) -> Espanhol
|
||||
'fr-FR.json': 'fr', # Francês (França) -> Francês
|
||||
'de-DE.json': 'de', # Alemão -> Alemão
|
||||
'it-IT.json': 'it', # Italiano -> Italiano
|
||||
'ru-RU.json': 'ru', # Russo -> Russo
|
||||
'ja-JP.json': 'ja', # Japonês -> Japonês
|
||||
'ko-KR.json': 'ko', # Coreano -> Coreano
|
||||
'zh-CN.json': 'zh-cn', # Chinês (Simplificado) -> Chinês Simplificado
|
||||
'ar-SA.json': 'ar', # Árabe -> Árabe
|
||||
'hi-IN.json': 'hi', # Hindi -> Hindi
|
||||
'nl-NL.json': 'nl', # Holandês -> Holandês
|
||||
'tr-TR.json': 'tr', # Turco -> Turco
|
||||
'pl-PL.json': 'pl', # Polonês -> Polonês
|
||||
}
|
||||
|
||||
# Prefixo para identificar strings não traduzidas
|
||||
TO_TRANSLATE_PREFIX = '[TO_TRANSLATE] '
|
||||
|
||||
|
||||
def load_json_file(file_path: Path) -> Dict[str, Any]:
|
||||
"""Carrega um arquivo JSON."""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"❌ Erro ao carregar {file_path}: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
def save_json_file(file_path: Path, data: Dict[str, Any], indent: int = 2) -> bool:
|
||||
"""Salva um arquivo JSON com formatação consistente."""
|
||||
try:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=indent, separators=(',', ': '))
|
||||
f.write('\n') # Adiciona nova linha no final
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Erro ao salvar {file_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_nested_value(data: Dict[str, Any], key_path: str) -> Any:
|
||||
"""Obtém um valor aninhado usando uma chave com pontos como separador."""
|
||||
keys = key_path.split('.')
|
||||
current = data
|
||||
|
||||
for key in keys:
|
||||
if isinstance(current, dict) and key in current:
|
||||
current = current[key]
|
||||
else:
|
||||
return None
|
||||
|
||||
return current
|
||||
|
||||
|
||||
def set_nested_value(data: Dict[str, Any], key_path: str, value: Any) -> None:
|
||||
"""Define um valor aninhado usando uma chave com pontos como separador."""
|
||||
keys = key_path.split('.')
|
||||
current = data
|
||||
|
||||
# Navega até o penúltimo nível, criando dicionários conforme necessário
|
||||
for key in keys[:-1]:
|
||||
if key not in current:
|
||||
current[key] = {}
|
||||
elif not isinstance(current[key], dict):
|
||||
current[key] = {}
|
||||
current = current[key]
|
||||
|
||||
# Define o valor no último nível
|
||||
current[keys[-1]] = value
|
||||
|
||||
|
||||
def find_untranslated_strings(data: Dict[str, Any], prefix: str = '') -> List[Tuple[str, str]]:
|
||||
"""Encontra todas as strings marcadas com [TO_TRANSLATE] recursivamente."""
|
||||
untranslated = []
|
||||
|
||||
for key, value in data.items():
|
||||
current_key = f"{prefix}.{key}" if prefix else key
|
||||
|
||||
if isinstance(value, str) and value.startswith(TO_TRANSLATE_PREFIX):
|
||||
# Remove o prefixo para obter o texto original
|
||||
original_text = value[len(TO_TRANSLATE_PREFIX):].strip()
|
||||
untranslated.append((current_key, original_text))
|
||||
elif isinstance(value, dict):
|
||||
untranslated.extend(find_untranslated_strings(value, current_key))
|
||||
|
||||
return untranslated
|
||||
|
||||
|
||||
def install_googletrans():
|
||||
"""Instala a biblioteca googletrans se não estiver disponível."""
|
||||
try:
|
||||
import googletrans
|
||||
return True
|
||||
except ImportError:
|
||||
print("📦 Biblioteca 'googletrans' não encontrada. Tentando instalar...")
|
||||
import subprocess
|
||||
try:
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "googletrans==4.0.0rc1"])
|
||||
print("✅ googletrans instalada com sucesso!")
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
print("❌ Falha ao instalar googletrans. Instale manualmente com:")
|
||||
print("pip install googletrans==4.0.0rc1")
|
||||
return False
|
||||
|
||||
|
||||
def translate_text(text: str, target_language: str, max_retries: int = 3) -> Optional[str]:
|
||||
"""Traduz um texto usando Google Translate gratuito."""
|
||||
try:
|
||||
from googletrans import Translator
|
||||
|
||||
translator = Translator()
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
# Traduz do inglês para o idioma alvo
|
||||
result = translator.translate(text, src='en', dest=target_language)
|
||||
|
||||
if result and result.text:
|
||||
return result.text.strip()
|
||||
|
||||
except Exception as e:
|
||||
if attempt < max_retries - 1:
|
||||
print(f" ⚠️ Tentativa {attempt + 1} falhou: {str(e)[:50]}... Reentando em 2s...")
|
||||
time.sleep(2)
|
||||
else:
|
||||
print(f" ❌ Falha após {max_retries} tentativas: {str(e)[:50]}...")
|
||||
|
||||
return None
|
||||
|
||||
except ImportError:
|
||||
print("❌ Biblioteca googletrans não disponível")
|
||||
return None
|
||||
|
||||
|
||||
def translate_file(file_path: Path, target_language: str, dry_run: bool = False,
|
||||
delay_between_requests: float = 1.0) -> Tuple[int, int, int]:
|
||||
"""
|
||||
Traduz todas as strings [TO_TRANSLATE] em um arquivo.
|
||||
Retorna: (total_found, successful_translations, failed_translations)
|
||||
"""
|
||||
print(f"🔍 Processando: {file_path.name}")
|
||||
|
||||
# Carrega o arquivo
|
||||
data = load_json_file(file_path)
|
||||
if not data:
|
||||
return 0, 0, 0
|
||||
|
||||
# Encontra strings não traduzidas
|
||||
untranslated_strings = find_untranslated_strings(data)
|
||||
|
||||
if not untranslated_strings:
|
||||
print(f" ✅ Nenhuma string para traduzir")
|
||||
return 0, 0, 0
|
||||
|
||||
print(f" 📝 Encontradas {len(untranslated_strings)} strings para traduzir")
|
||||
|
||||
if dry_run:
|
||||
print(f" 🔍 [DRY RUN] Strings que seriam traduzidas:")
|
||||
for key, text in untranslated_strings[:3]:
|
||||
print(f" - {key}: \"{text[:50]}{'...' if len(text) > 50 else ''}\"")
|
||||
if len(untranslated_strings) > 3:
|
||||
print(f" ... e mais {len(untranslated_strings) - 3}")
|
||||
return len(untranslated_strings), 0, 0
|
||||
|
||||
# Traduz cada string
|
||||
successful = 0
|
||||
failed = 0
|
||||
updated_data = data.copy()
|
||||
|
||||
for i, (key_path, original_text) in enumerate(untranslated_strings, 1):
|
||||
print(f" 📍 ({i}/{len(untranslated_strings)}) Traduzindo: {key_path}")
|
||||
|
||||
# Traduz o texto
|
||||
translated_text = translate_text(original_text, target_language)
|
||||
|
||||
if translated_text and translated_text != original_text:
|
||||
# Atualiza no dicionário
|
||||
set_nested_value(updated_data, key_path, translated_text)
|
||||
successful += 1
|
||||
print(f" ✅ \"{original_text[:30]}...\" → \"{translated_text[:30]}...\"")
|
||||
else:
|
||||
failed += 1
|
||||
print(f" ❌ Falha na tradução")
|
||||
|
||||
# Delay entre requisições para evitar rate limiting
|
||||
if i < len(untranslated_strings): # Não espera após a última
|
||||
time.sleep(delay_between_requests)
|
||||
|
||||
# Salva o arquivo atualizado
|
||||
if successful > 0:
|
||||
if save_json_file(file_path, updated_data):
|
||||
print(f" 💾 Arquivo salvo com {successful} traduções")
|
||||
else:
|
||||
print(f" ❌ Erro ao salvar arquivo")
|
||||
failed += successful # Conta como falha se não conseguiu salvar
|
||||
successful = 0
|
||||
|
||||
return len(untranslated_strings), successful, failed
|
||||
|
||||
|
||||
def translate_all_files(messages_dir: Path, delay_between_requests: float = 1.0,
|
||||
dry_run: bool = False, skip_languages: List[str] = None) -> None:
|
||||
"""Traduz todos os arquivos de idioma que têm strings [TO_TRANSLATE]."""
|
||||
|
||||
if not install_googletrans():
|
||||
return
|
||||
|
||||
skip_languages = skip_languages or []
|
||||
|
||||
# Encontra arquivos JSON de idioma
|
||||
language_files = []
|
||||
for file_name, lang_code in LANGUAGE_MAPPING.items():
|
||||
file_path = messages_dir / file_name
|
||||
if file_path.exists() and file_name not in skip_languages:
|
||||
language_files.append((file_path, lang_code))
|
||||
|
||||
if not language_files:
|
||||
print("❌ Nenhum arquivo de idioma encontrado")
|
||||
return
|
||||
|
||||
print(f"🌍 Traduzindo {len(language_files)} idiomas...")
|
||||
print(f"⏱️ Delay entre requisições: {delay_between_requests}s")
|
||||
if dry_run:
|
||||
print("🔍 MODO DRY RUN - Nenhuma alteração será feita")
|
||||
print("-" * 60)
|
||||
|
||||
total_found = 0
|
||||
total_successful = 0
|
||||
total_failed = 0
|
||||
|
||||
for i, (file_path, lang_code) in enumerate(language_files, 1):
|
||||
print(f"\n[{i}/{len(language_files)}] 🌐 Idioma: {lang_code.upper()}")
|
||||
|
||||
found, successful, failed = translate_file(
|
||||
file_path, lang_code, dry_run, delay_between_requests
|
||||
)
|
||||
|
||||
total_found += found
|
||||
total_successful += successful
|
||||
total_failed += failed
|
||||
|
||||
# Pausa entre arquivos (exceto o último)
|
||||
if i < len(language_files) and not dry_run:
|
||||
print(f" ⏸️ Pausando {delay_between_requests * 2}s antes do próximo idioma...")
|
||||
time.sleep(delay_between_requests * 2)
|
||||
|
||||
# Sumário final
|
||||
print("\n" + "=" * 60)
|
||||
print("📊 SUMÁRIO FINAL")
|
||||
print("=" * 60)
|
||||
|
||||
if dry_run:
|
||||
print(f"🔍 MODO DRY RUN:")
|
||||
print(f" • {total_found} strings seriam traduzidas")
|
||||
else:
|
||||
print(f"✅ Traduções realizadas:")
|
||||
print(f" • {total_successful} sucessos")
|
||||
print(f" • {total_failed} falhas")
|
||||
print(f" • {total_found} total processadas")
|
||||
|
||||
if total_successful > 0:
|
||||
success_rate = (total_successful / total_found) * 100
|
||||
print(f" • Taxa de sucesso: {success_rate:.1f}%")
|
||||
|
||||
print("\n💡 DICAS:")
|
||||
print("• Execute 'python3 check_translations.py' para verificar o resultado")
|
||||
print("• Strings que falharam na tradução mantêm o prefixo [TO_TRANSLATE]")
|
||||
print("• Considere revisar as traduções automáticas para garantir qualidade")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Traduz automaticamente strings marcadas com [TO_TRANSLATE]'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--messages-dir',
|
||||
type=Path,
|
||||
default=Path(__file__).parent,
|
||||
help='Diretório contendo os arquivos de mensagem (padrão: diretório atual)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Apenas mostra o que seria traduzido, sem fazer alterações'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--delay',
|
||||
type=float,
|
||||
default=1.0,
|
||||
help='Delay em segundos entre requisições de tradução (padrão: 1.0)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skip-languages',
|
||||
nargs='*',
|
||||
default=[],
|
||||
help='Lista de idiomas para pular (ex: pt-BR.json fr-FR.json)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.messages_dir.exists():
|
||||
print(f"❌ Diretório não encontrado: {args.messages_dir}")
|
||||
return 1
|
||||
|
||||
print(f"📁 Diretório: {args.messages_dir}")
|
||||
print(f"🔍 Dry run: {args.dry_run}")
|
||||
print(f"⏱️ Delay: {args.delay}s")
|
||||
if args.skip_languages:
|
||||
print(f"⏭️ Ignorando: {', '.join(args.skip_languages)}")
|
||||
print("-" * 60)
|
||||
|
||||
translate_all_files(
|
||||
messages_dir=args.messages_dir,
|
||||
delay_between_requests=args.delay,
|
||||
dry_run=args.dry_run,
|
||||
skip_languages=args.skip_languages
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
@@ -1413,5 +1413,117 @@
|
||||
}
|
||||
},
|
||||
"defaultLinkName": "已接收文件"
|
||||
},
|
||||
"authProviders": {
|
||||
"title": "身份验证提供商",
|
||||
"description": "配置外部身份验证提供商以实现单点登录",
|
||||
"enabledCount": "{count} 个已启用",
|
||||
"loadingProviders": "正在加载提供商...",
|
||||
"providersConfigured": "已配置 {count} 个提供商",
|
||||
"enabledOfTotal": "共 {total} 个提供商中已启用 {enabled} 个",
|
||||
"hideDisabledProviders": "隐藏已禁用的提供商",
|
||||
"addProvider": "添加提供商",
|
||||
"addProviderTitle": "添加提供商",
|
||||
"editProvider": "编辑提供商",
|
||||
"deleteProvider": "删除提供商",
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用",
|
||||
"officialProvider": "官方提供商",
|
||||
"dragToReorder": "拖动以重新排序",
|
||||
"dragDisabledMessage": "筛选提供商时拖放功能已禁用。显示所有提供商以重新排序。",
|
||||
"dragEnabledMessage": "拖动提供商以重新排序。此顺序将反映在登录页面上。",
|
||||
"noProvidersEnabled": "没有启用的身份验证提供商",
|
||||
"noProvidersConfigured": "未配置身份验证提供商",
|
||||
"form": {
|
||||
"providerName": "提供商名称",
|
||||
"providerNamePlaceholder": "例如:mycompany",
|
||||
"displayName": "显示名称",
|
||||
"displayNamePlaceholder": "例如:我的公司 SSO",
|
||||
"type": "类型",
|
||||
"typeOidc": "OIDC (OpenID Connect)",
|
||||
"typeOauth2": "OAuth 2.0",
|
||||
"icon": "图标",
|
||||
"iconPlaceholder": "选择图标",
|
||||
"clientId": "客户端 ID",
|
||||
"clientIdPlaceholder": "您的 OAuth 客户端 ID",
|
||||
"clientSecret": "客户端密钥",
|
||||
"clientSecretPlaceholder": "您的 OAuth 客户端密钥",
|
||||
"oauthScopes": "OAuth 作用域",
|
||||
"scopesPlaceholder": "输入作用域(例如:openid, profile, email)",
|
||||
"scopesHelpOidc": "根据提供商 URL 自动建议作用域。常见 OIDC 作用域:openid, profile, email, groups",
|
||||
"scopesHelpOauth2": "根据提供商 URL 自动建议作用域。常见 OAuth2 作用域取决于提供商",
|
||||
"providerUrl": "提供商 URL",
|
||||
"providerUrlPlaceholder": "https://auth.example.com",
|
||||
"providerUrlAutoPlaceholder": "https://your-provider.com(将自动发现端点)",
|
||||
"providerUrlManualPlaceholder": "https://your-provider.com",
|
||||
"autoDiscoveryHelp": "系统将自动发现授权、令牌和用户信息端点",
|
||||
"manualConfigurationHelp": "提供商的基础 URL(端点将相对于此 URL)",
|
||||
"authorizationEndpoint": "授权端点",
|
||||
"authorizationEndpointPlaceholder": "https://auth.example.com/auth",
|
||||
"tokenEndpoint": "令牌端点",
|
||||
"tokenEndpointPlaceholder": "https://auth.example.com/token",
|
||||
"userInfoEndpoint": "用户信息端点",
|
||||
"userInfoEndpointPlaceholder": "https://auth.example.com/userinfo",
|
||||
"configurationMethod": "配置方法",
|
||||
"autoDiscovery": "自动发现(推荐)",
|
||||
"autoDiscoveryDescription": "从提供商 URL 自动发现端点",
|
||||
"manualEndpoints": "手动端点",
|
||||
"manualEndpointsDescription": "手动配置授权、令牌和用户信息端点",
|
||||
"callbackUrl": "回调 URL",
|
||||
"callbackUrlDescription": "在您的 OAuth 提供商配置中使用此 URL",
|
||||
"copyCallbackUrl": "复制回调 URL",
|
||||
"callbackUrlCopied": "回调 URL 已复制到剪贴板!",
|
||||
"adminEmailDomains": "管理员邮箱域名",
|
||||
"adminEmailDomainsPlaceholder": "输入域名(例如:admin.company.com)",
|
||||
"adminEmailDomainsHelp": "来自这些域名的用户将被授予管理员权限",
|
||||
"autoRegister": "自动注册新用户",
|
||||
"officialProviderUrlPlaceholder": "将占位符替换为您的 {displayName} URL",
|
||||
"officialProviderHelp": "这是官方提供商。端点已预配置。您只能编辑此 URL。",
|
||||
"officialProviderIconHelp": "您可以自定义此官方提供商的图标。"
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"saving": "保存中...",
|
||||
"adding": "添加中...",
|
||||
"updating": "更新中...",
|
||||
"saveProvider": "保存提供商",
|
||||
"delete": "删除",
|
||||
"deleting": "删除中...",
|
||||
"edit": "编辑",
|
||||
"enable": "启用",
|
||||
"disable": "禁用"
|
||||
},
|
||||
"messages": {
|
||||
"providerAdded": "提供商添加成功",
|
||||
"providerUpdated": "提供商更新成功",
|
||||
"providerDeleted": "提供商删除成功",
|
||||
"providerOrderUpdated": "提供商顺序更新成功",
|
||||
"fillRequiredFields": "请填写所有必填字段(名称、显示名称、客户端 ID、客户端密钥)",
|
||||
"provideUrlOrEndpoints": "请提供用于自动发现的提供商 URL 或所有三个自定义端点",
|
||||
"chooseDiscoveryOrManual": "请选择自动发现(提供商 URL)或手动端点,不能同时选择两者",
|
||||
"loadFailed": "加载提供商失败",
|
||||
"addFailed": "添加提供商失败",
|
||||
"updateFailed": "更新提供商失败",
|
||||
"deleteFailed": "删除提供商失败",
|
||||
"orderUpdateFailed": "更新提供商顺序失败"
|
||||
},
|
||||
"info": {
|
||||
"title": "信息",
|
||||
"officialProvidersRecommended": "为获得更好的功能,建议使用官方提供商。如果您在使用自定义提供商时遇到问题,请考虑在以下位置提出问题",
|
||||
"github": "GitHub",
|
||||
"officialProvider": "官方提供商",
|
||||
"officialProviderDescription": "此提供商由 Palmr 优化。只能修改凭据和配置。",
|
||||
"manualConfigTitle": "手动配置",
|
||||
"manualConfigDescription": "您正在手动提供所有端点。请确保它们对您的提供商是正确的。"
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "删除身份验证提供商",
|
||||
"confirmMessage": "您确定要删除'{displayName}'提供商吗?此操作无法撤消。",
|
||||
"providerId": "提供商 ID:{name}",
|
||||
"cancel": "取消",
|
||||
"delete": "删除提供商",
|
||||
"deleting": "删除中..."
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,289 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { IconInfoCircle, IconPlus } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { IconPicker } from "@/components/ui/icon-picker";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { TagsInput } from "@/components/ui/tags-input";
|
||||
import { CallbackUrlDisplay } from "./callback-url-display";
|
||||
import { ConfigurationMethodSelector } from "./configuration-method-selector";
|
||||
import type { NewProvider } from "./types";
|
||||
|
||||
interface AddProviderFormProps {
|
||||
showAddForm: boolean;
|
||||
onToggleForm: () => void;
|
||||
onAddProvider: (provider: NewProvider) => Promise<void>;
|
||||
saving: boolean;
|
||||
}
|
||||
|
||||
export function AddProviderForm({ showAddForm, onToggleForm, onAddProvider, saving }: AddProviderFormProps) {
|
||||
const t = useTranslations();
|
||||
const [newProvider, setNewProvider] = useState<NewProvider>({
|
||||
name: "",
|
||||
displayName: "",
|
||||
type: "oidc",
|
||||
icon: "",
|
||||
clientId: "",
|
||||
clientSecret: "",
|
||||
issuerUrl: "",
|
||||
scope: "openid profile email",
|
||||
authorizationEndpoint: "",
|
||||
tokenEndpoint: "",
|
||||
userInfoEndpoint: "",
|
||||
});
|
||||
|
||||
const detectProviderTypeAndSuggestScopes = (url: string): string[] => {
|
||||
if (!url) return [];
|
||||
|
||||
const urlLower = url.toLowerCase();
|
||||
|
||||
const providerPatterns = [
|
||||
{ pattern: "frontegg.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "discord.com", scopes: ["identify", "email"] },
|
||||
{ pattern: "github.com", scopes: ["read:user", "user:email"] },
|
||||
{ pattern: "gitlab.com", scopes: ["read_user", "read_api"] },
|
||||
{ pattern: "google.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "microsoft.com", scopes: ["openid", "profile", "email", "User.Read"] },
|
||||
{ pattern: "facebook.com", scopes: ["public_profile", "email"] },
|
||||
{ pattern: "twitter.com", scopes: ["tweet.read", "users.read"] },
|
||||
{ pattern: "linkedin.com", scopes: ["r_liteprofile", "r_emailaddress"] },
|
||||
{ pattern: "authentik", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "keycloak", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "auth0.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "okta.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "onelogin.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "pingidentity.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "azure.com", scopes: ["openid", "profile", "email", "User.Read"] },
|
||||
{ pattern: "aws.amazon.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "slack.com", scopes: ["identity.basic", "identity.email", "identity.avatar"] },
|
||||
{ pattern: "bitbucket.org", scopes: ["account", "repository"] },
|
||||
{ pattern: "atlassian.com", scopes: ["read:jira-user", "read:jira-work"] },
|
||||
{ pattern: "salesforce.com", scopes: ["api", "refresh_token"] },
|
||||
{ pattern: "zendesk.com", scopes: ["read"] },
|
||||
{ pattern: "shopify.com", scopes: ["read_products", "read_customers"] },
|
||||
{ pattern: "stripe.com", scopes: ["read"] },
|
||||
{ pattern: "twilio.com", scopes: ["read"] },
|
||||
{ pattern: "sendgrid.com", scopes: ["mail.send"] },
|
||||
{ pattern: "mailchimp.com", scopes: ["read"] },
|
||||
{ pattern: "hubspot.com", scopes: ["contacts", "crm.objects.contacts.read"] },
|
||||
{ pattern: "zoom.us", scopes: ["user:read:admin"] },
|
||||
{ pattern: "teams.microsoft.com", scopes: ["openid", "profile", "email", "User.Read"] },
|
||||
{ pattern: "notion.so", scopes: ["read"] },
|
||||
{ pattern: "figma.com", scopes: ["files:read"] },
|
||||
{ pattern: "dropbox.com", scopes: ["files.content.read"] },
|
||||
{ pattern: "box.com", scopes: ["root_readwrite"] },
|
||||
{ pattern: "trello.com", scopes: ["read"] },
|
||||
{ pattern: "asana.com", scopes: ["default"] },
|
||||
{ pattern: "monday.com", scopes: ["read"] },
|
||||
{ pattern: "clickup.com", scopes: ["read"] },
|
||||
{ pattern: "linear.app", scopes: ["read"] },
|
||||
{ pattern: "kinde.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "zitadel.com", scopes: ["openid", "profile", "email"] },
|
||||
];
|
||||
|
||||
for (const { pattern, scopes } of providerPatterns) {
|
||||
if (urlLower.includes(pattern)) {
|
||||
return scopes;
|
||||
}
|
||||
}
|
||||
|
||||
if (newProvider.type === "oidc") {
|
||||
return ["openid", "profile", "email"];
|
||||
} else {
|
||||
return ["profile", "email"];
|
||||
}
|
||||
};
|
||||
|
||||
const updateProviderUrl = (url: string) => {
|
||||
if (!url.trim()) return;
|
||||
|
||||
const suggestedScopes = detectProviderTypeAndSuggestScopes(url);
|
||||
|
||||
setNewProvider((prev) => {
|
||||
const shouldUpdateScopes = !prev.scope || prev.scope === "openid profile email" || prev.scope === "profile email";
|
||||
|
||||
return {
|
||||
...prev,
|
||||
scope: shouldUpdateScopes ? suggestedScopes.join(" ") : prev.scope,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!newProvider.name || !newProvider.displayName || !newProvider.clientId || !newProvider.clientSecret) {
|
||||
toast.error(t("authProviders.messages.fillRequiredFields"));
|
||||
return;
|
||||
}
|
||||
|
||||
const hasIssuerUrl = !!newProvider.issuerUrl;
|
||||
const hasAllCustomEndpoints = !!(
|
||||
newProvider.authorizationEndpoint &&
|
||||
newProvider.tokenEndpoint &&
|
||||
newProvider.userInfoEndpoint
|
||||
);
|
||||
|
||||
if (!hasIssuerUrl && !hasAllCustomEndpoints) {
|
||||
toast.error(t("authProviders.messages.provideUrlOrEndpoints"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasIssuerUrl && hasAllCustomEndpoints) {
|
||||
toast.error(t("authProviders.messages.chooseDiscoveryOrManual"));
|
||||
return;
|
||||
}
|
||||
|
||||
await onAddProvider(newProvider);
|
||||
|
||||
setNewProvider({
|
||||
name: "",
|
||||
displayName: "",
|
||||
type: "oidc",
|
||||
icon: "",
|
||||
clientId: "",
|
||||
clientSecret: "",
|
||||
issuerUrl: "",
|
||||
scope: "openid profile email",
|
||||
authorizationEndpoint: "",
|
||||
tokenEndpoint: "",
|
||||
userInfoEndpoint: "",
|
||||
});
|
||||
};
|
||||
|
||||
const updateProvider = (updates: Partial<NewProvider>) => {
|
||||
setNewProvider((prev) => ({ ...prev, ...updates }));
|
||||
};
|
||||
|
||||
if (!showAddForm) {
|
||||
return (
|
||||
<Button onClick={onToggleForm} variant="outline" size="sm">
|
||||
<IconPlus className="h-4 w-4 mr-2" />
|
||||
{t("authProviders.addProvider")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border border-dashed rounded-lg p-4 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-medium text-foreground dark:text-foreground">{t("authProviders.addProviderTitle")}</h3>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 dark:bg-blue-950/50 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
|
||||
<div className="flex items-center gap-2 text-blue-700 dark:text-blue-300">
|
||||
<span>
|
||||
<IconInfoCircle className="h-4 w-4" />
|
||||
</span>
|
||||
<span className="text-sm font-medium">{t("authProviders.info.title")}</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{t("authProviders.info.officialProvidersRecommended")}{" "}
|
||||
<a
|
||||
href="https://github.com/kyantech/Palmr/issues"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-semibold"
|
||||
>
|
||||
{t("authProviders.info.github")}.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.providerName")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.providerNamePlaceholder")}
|
||||
value={newProvider.name}
|
||||
onChange={(e) => updateProvider({ name: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.displayName")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.displayNamePlaceholder")}
|
||||
value={newProvider.displayName}
|
||||
onChange={(e) => updateProvider({ displayName: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.type")}</Label>
|
||||
<select
|
||||
className="w-full rounded-md border border-input bg-background dark:bg-background px-3 py-2 text-sm text-foreground dark:text-foreground"
|
||||
value={newProvider.type}
|
||||
onChange={(e) => updateProvider({ type: e.target.value as "oidc" | "oauth2" })}
|
||||
>
|
||||
<option value="oidc">{t("authProviders.form.typeOidc")}</option>
|
||||
<option value="oauth2">{t("authProviders.form.typeOauth2")}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.icon")}</Label>
|
||||
<IconPicker
|
||||
value={newProvider.icon}
|
||||
onChange={(icon) => updateProvider({ icon })}
|
||||
placeholder={t("authProviders.form.iconPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfigurationMethodSelector provider={newProvider} onUpdate={updateProvider} onUrlUpdate={updateProviderUrl} />
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.clientId")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.clientIdPlaceholder")}
|
||||
value={newProvider.clientId}
|
||||
onChange={(e) => updateProvider({ clientId: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.clientSecret")} *</Label>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={t("authProviders.form.clientSecretPlaceholder")}
|
||||
value={newProvider.clientSecret}
|
||||
onChange={(e) => updateProvider({ clientSecret: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.oauthScopes")}</Label>
|
||||
<TagsInput
|
||||
value={newProvider.scope ? newProvider.scope.split(/[,\s]+/).filter(Boolean) : []}
|
||||
onChange={(tags) => updateProvider({ scope: tags.join(" ") })}
|
||||
placeholder={t("authProviders.form.scopesPlaceholder")}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{newProvider.type === "oidc"
|
||||
? t("authProviders.form.scopesHelpOidc")
|
||||
: t("authProviders.form.scopesHelpOauth2")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{newProvider.name && (
|
||||
<div className="pt-2">
|
||||
<CallbackUrlDisplay providerName={newProvider.name} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button variant="outline" onClick={onToggleForm} size="sm">
|
||||
{t("authProviders.buttons.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} disabled={saving} size="sm">
|
||||
{saving ? t("authProviders.buttons.adding") : t("authProviders.addProvider")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -31,20 +31,22 @@ export function AuthProviderDeleteModal({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-destructive">
|
||||
<IconTrash size={20} />
|
||||
Delete Authentication Provider
|
||||
{t("authProviders.deleteModal.title")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Are you sure you want to delete the "{provider?.displayName}" provider? This action cannot be undone.
|
||||
{t("authProviders.deleteModal.confirmMessage", { displayName: provider?.displayName || "" })}
|
||||
</p>
|
||||
|
||||
{provider && (
|
||||
<div className="rounded-lg border p-4 bg-muted/30">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium">{provider.displayName}</h4>
|
||||
<p className="text-sm text-muted-foreground">Provider ID: {provider.name}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("authProviders.deleteModal.providerId", { name: provider.name })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -52,18 +54,18 @@ export function AuthProviderDeleteModal({
|
||||
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={onClose} disabled={isDeleting}>
|
||||
Cancel
|
||||
{t("authProviders.deleteModal.cancel")}
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={handleConfirm} disabled={isDeleting}>
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<IconTrash className="h-4 w-4 mr-2 animate-spin" />
|
||||
Deleting...
|
||||
{t("authProviders.deleteModal.deleting")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IconTrash className="h-4 w-4 mr-2" />
|
||||
Delete Provider
|
||||
{t("authProviders.deleteModal.delete")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
@@ -0,0 +1,158 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { IconChevronDown, IconChevronUp, IconSettings } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { renderIconByName } from "@/components/ui/icon-picker";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { useAuthProviders } from "../../hooks/use-auth-providers";
|
||||
import { AddProviderForm } from "./add-provider-form";
|
||||
import { AuthProviderDeleteModal } from "./auth-provider-delete-modal";
|
||||
import { ProviderList } from "./provider-list";
|
||||
|
||||
export function AuthProvidersSettings() {
|
||||
const t = useTranslations();
|
||||
|
||||
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||
const [showAddForm, setShowAddForm] = useState(false);
|
||||
|
||||
const {
|
||||
providers,
|
||||
loading,
|
||||
saving,
|
||||
editingProvider,
|
||||
editingFormData,
|
||||
hideDisabledProviders,
|
||||
providerToDelete,
|
||||
isDeleting,
|
||||
enabledCount,
|
||||
filteredProviders,
|
||||
loadProviders,
|
||||
updateProvider,
|
||||
addProvider,
|
||||
editProvider,
|
||||
deleteProvider,
|
||||
handleDragEnd,
|
||||
handleHideDisabledProvidersChange,
|
||||
handleEditProvider,
|
||||
handleDeleteProvider,
|
||||
handleCancelEdit,
|
||||
setEditingFormData,
|
||||
setProviderToDelete,
|
||||
} = useAuthProviders();
|
||||
|
||||
const getProviderIcon = (provider: any) => {
|
||||
const iconName = provider.icon || "FaCog";
|
||||
return renderIconByName(iconName, "w-5 h-5");
|
||||
};
|
||||
|
||||
const handleToggleAddForm = () => {
|
||||
setShowAddForm(!showAddForm);
|
||||
};
|
||||
|
||||
const handleConfirmDelete = async () => {
|
||||
if (providerToDelete) {
|
||||
await deleteProvider(providerToDelete.id);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-6 gap-0">
|
||||
<CardHeader
|
||||
className="flex flex-row items-center justify-between cursor-pointer p-0"
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
>
|
||||
<div className="flex flex-row items-center gap-8">
|
||||
<IconSettings className="text-xl text-muted-foreground" />
|
||||
<div className="flex flex-col gap-1">
|
||||
<h2 className="text-xl font-semibold">{t("authProviders.title")}</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("authProviders.description")}
|
||||
{enabledCount > 0 && (
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{t("authProviders.enabledCount", { count: enabledCount })}
|
||||
</Badge>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{isCollapsed ? (
|
||||
<IconChevronDown className="text-muted-foreground" />
|
||||
) : (
|
||||
<IconChevronUp className="text-muted-foreground" />
|
||||
)}
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className={`${isCollapsed ? "hidden" : "block"} px-0`}>
|
||||
<Separator className="my-6" />
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<IconSettings className="h-6 w-6 animate-spin mr-2" />
|
||||
{t("authProviders.loadingProviders")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className={showAddForm ? "space-y-4" : "flex justify-between items-center"}>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{hideDisabledProviders
|
||||
? t("authProviders.enabledOfTotal", { enabled: filteredProviders.length, total: providers.length })
|
||||
: t("authProviders.providersConfigured", { count: providers.length })}
|
||||
</div>
|
||||
|
||||
<AddProviderForm
|
||||
showAddForm={showAddForm}
|
||||
onToggleForm={handleToggleAddForm}
|
||||
onAddProvider={addProvider}
|
||||
saving={saving === "new"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{providers.length > 0 && (
|
||||
<div className="flex items-center space-x-2 py-2">
|
||||
<Checkbox
|
||||
id="hideDisabledProviders"
|
||||
checked={hideDisabledProviders}
|
||||
onCheckedChange={handleHideDisabledProvidersChange}
|
||||
/>
|
||||
<Label htmlFor="hideDisabledProviders" className="text-sm cursor-pointer">
|
||||
{t("authProviders.hideDisabledProviders")}
|
||||
</Label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ProviderList
|
||||
providers={providers}
|
||||
filteredProviders={filteredProviders}
|
||||
hideDisabledProviders={hideDisabledProviders}
|
||||
onDragEnd={handleDragEnd}
|
||||
onUpdateProvider={updateProvider}
|
||||
onEditProvider={handleEditProvider}
|
||||
onDeleteProvider={handleDeleteProvider}
|
||||
saving={saving}
|
||||
getIcon={getProviderIcon}
|
||||
editingProvider={editingProvider}
|
||||
editProvider={editProvider}
|
||||
onCancelEdit={handleCancelEdit}
|
||||
editingFormData={editingFormData}
|
||||
setEditingFormData={setEditingFormData}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AuthProviderDeleteModal
|
||||
provider={providerToDelete}
|
||||
isOpen={!!providerToDelete}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onClose={() => setProviderToDelete(null)}
|
||||
isDeleting={isDeleting}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { IconCheck, IconCopy } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
interface CallbackUrlDisplayProps {
|
||||
providerName: string;
|
||||
}
|
||||
|
||||
export function CallbackUrlDisplay({ providerName }: CallbackUrlDisplayProps) {
|
||||
const t = useTranslations();
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const callbackUrl =
|
||||
typeof window !== "undefined"
|
||||
? `${window.location.origin}/api/auth/providers/${providerName}/callback`
|
||||
: `/api/auth/providers/${providerName}/callback`;
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(callbackUrl);
|
||||
setCopied(true);
|
||||
toast.success(t("authProviders.form.callbackUrlCopied"));
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy text: ", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-foreground">{t("authProviders.form.callbackUrl")}</Label>
|
||||
<div className="flex items-center gap-2 mb-2 border py-2 px-3 mt-2 rounded-md w-fit ">
|
||||
<div className=" rounded-md font-mono text-sm break-all font-semibold px-2">{callbackUrl}</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={copyToClipboard}
|
||||
className="shrink-0"
|
||||
title={t("authProviders.form.copyCallbackUrl")}
|
||||
>
|
||||
{copied ? <IconCheck className="h-3 w-3" /> : <IconCopy className="h-3 w-3" />}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-2">{t("authProviders.form.callbackUrlDescription")}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -0,0 +1,134 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { IconInfoCircle } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import type { NewProvider } from "./types";
|
||||
|
||||
interface ConfigurationMethodSelectorProps {
|
||||
provider: NewProvider;
|
||||
onUpdate: (updates: Partial<NewProvider>) => void;
|
||||
onUrlUpdate: (url: string) => void;
|
||||
}
|
||||
|
||||
export function ConfigurationMethodSelector({ provider, onUpdate, onUrlUpdate }: ConfigurationMethodSelectorProps) {
|
||||
const t = useTranslations();
|
||||
const isManualMode = !!(provider.authorizationEndpoint || provider.tokenEndpoint || provider.userInfoEndpoint);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-800 rounded-lg p-4">
|
||||
<h4 className="text-sm font-medium mb-3">{t("authProviders.form.configurationMethod")}</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="radio"
|
||||
id="add-auto-discovery"
|
||||
name="addConfigMethod"
|
||||
checked={!isManualMode}
|
||||
onChange={() =>
|
||||
onUpdate({
|
||||
authorizationEndpoint: "",
|
||||
tokenEndpoint: "",
|
||||
userInfoEndpoint: "",
|
||||
})
|
||||
}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<label htmlFor="add-auto-discovery" className="text-sm">
|
||||
<span className="font-medium">{t("authProviders.form.autoDiscovery")}</span>
|
||||
<span className="text-muted-foreground ml-2">({t("authProviders.form.autoDiscoveryDescription")})</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="radio"
|
||||
id="add-manual-endpoints"
|
||||
name="addConfigMethod"
|
||||
checked={isManualMode}
|
||||
onChange={() => {
|
||||
if (!isManualMode) {
|
||||
onUpdate({
|
||||
authorizationEndpoint: "/oauth/authorize",
|
||||
tokenEndpoint: "/oauth/token",
|
||||
userInfoEndpoint: "/oauth/userinfo",
|
||||
issuerUrl: "",
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<label htmlFor="add-manual-endpoints" className="text-sm">
|
||||
<span className="font-medium">{t("authProviders.form.manualEndpoints")}</span>
|
||||
<span className="text-muted-foreground ml-2">({t("authProviders.form.manualEndpointsDescription")})</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isManualMode && (
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.providerUrl")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.providerUrlAutoPlaceholder")}
|
||||
value={provider.issuerUrl}
|
||||
onChange={(e) => onUpdate({ issuerUrl: e.target.value })}
|
||||
onBlur={(e) => onUrlUpdate(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">{t("authProviders.form.autoDiscoveryHelp")}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isManualMode && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.providerUrl")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.providerUrlManualPlaceholder")}
|
||||
value={provider.issuerUrl}
|
||||
onChange={(e) => onUpdate({ issuerUrl: e.target.value })}
|
||||
onBlur={(e) => onUrlUpdate(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">{t("authProviders.form.manualConfigurationHelp")}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.authorizationEndpoint")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.authorizationEndpointPlaceholder")}
|
||||
value={provider.authorizationEndpoint}
|
||||
onChange={(e) => onUpdate({ authorizationEndpoint: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.tokenEndpoint")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.tokenEndpointPlaceholder")}
|
||||
value={provider.tokenEndpoint}
|
||||
onChange={(e) => onUpdate({ tokenEndpoint: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.userInfoEndpoint")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.userInfoEndpointPlaceholder")}
|
||||
value={provider.userInfoEndpoint}
|
||||
onChange={(e) => onUpdate({ userInfoEndpoint: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-blue-50 dark:bg-blue-950/50 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
|
||||
<div className="flex items-start gap-2 text-blue-700 dark:text-blue-300">
|
||||
<IconInfoCircle className="h-4 w-4 mt-0.5 flex-shrink-0" />
|
||||
<div className="text-xs">
|
||||
<p className="font-medium">{t("authProviders.info.manualConfigTitle")}</p>
|
||||
<p className="mt-1">{t("authProviders.info.manualConfigDescription")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -0,0 +1,423 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { IconEye, IconEyeOff, IconInfoCircle } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { IconPicker } from "@/components/ui/icon-picker";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { TagsInput } from "@/components/ui/tags-input";
|
||||
import { CallbackUrlDisplay } from "./callback-url-display";
|
||||
|
||||
export interface AuthProvider {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
type: string;
|
||||
icon?: string;
|
||||
enabled: boolean;
|
||||
issuerUrl?: string;
|
||||
clientId?: string;
|
||||
clientSecret?: string;
|
||||
scope?: string;
|
||||
autoRegister: boolean;
|
||||
adminEmailDomains?: string;
|
||||
sortOrder: number;
|
||||
isOfficial?: boolean;
|
||||
authorizationEndpoint?: string;
|
||||
tokenEndpoint?: string;
|
||||
userInfoEndpoint?: string;
|
||||
}
|
||||
|
||||
interface EditProviderFormProps {
|
||||
provider: AuthProvider;
|
||||
onSave: (data: Partial<AuthProvider>) => void;
|
||||
onCancel: () => void;
|
||||
saving: boolean;
|
||||
editingFormData: Record<string, any>;
|
||||
setEditingFormData: (data: Record<string, any>) => void;
|
||||
}
|
||||
|
||||
export function EditProviderForm({
|
||||
provider,
|
||||
onSave,
|
||||
onCancel,
|
||||
saving,
|
||||
editingFormData,
|
||||
setEditingFormData,
|
||||
}: EditProviderFormProps) {
|
||||
const t = useTranslations();
|
||||
const savedData = editingFormData[provider.id] || {};
|
||||
const [formData, setFormData] = useState({
|
||||
name: savedData.name || provider.name || "",
|
||||
displayName: savedData.displayName || provider.displayName || "",
|
||||
type: (savedData.type || provider.type) as "oidc" | "oauth2",
|
||||
icon: savedData.icon || provider.icon || "FaCog",
|
||||
issuerUrl: savedData.issuerUrl || provider.issuerUrl || "",
|
||||
clientId: savedData.clientId || provider.clientId || "",
|
||||
clientSecret: savedData.clientSecret || provider.clientSecret || "",
|
||||
scope: savedData.scope || provider.scope || "",
|
||||
autoRegister: savedData.autoRegister !== undefined ? savedData.autoRegister : provider.autoRegister,
|
||||
adminEmailDomains: savedData.adminEmailDomains || provider.adminEmailDomains || "",
|
||||
authorizationEndpoint: savedData.authorizationEndpoint || provider.authorizationEndpoint || "",
|
||||
tokenEndpoint: savedData.tokenEndpoint || provider.tokenEndpoint || "",
|
||||
userInfoEndpoint: savedData.userInfoEndpoint || provider.userInfoEndpoint || "",
|
||||
});
|
||||
|
||||
const [showClientSecret, setShowClientSecret] = useState(false);
|
||||
const isOfficial = provider.isOfficial;
|
||||
|
||||
const detectProviderTypeAndSuggestScopesEdit = (url: string, currentType: string): string[] => {
|
||||
if (!url) return [];
|
||||
|
||||
const urlLower = url.toLowerCase();
|
||||
|
||||
const providerPatterns = [
|
||||
{ pattern: "frontegg.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "discord.com", scopes: ["identify", "email"] },
|
||||
{ pattern: "github.com", scopes: ["read:user", "user:email"] },
|
||||
{ pattern: "gitlab.com", scopes: ["read_user", "read_api"] },
|
||||
{ pattern: "google.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "microsoft.com", scopes: ["openid", "profile", "email", "User.Read"] },
|
||||
{ pattern: "facebook.com", scopes: ["public_profile", "email"] },
|
||||
{ pattern: "twitter.com", scopes: ["tweet.read", "users.read"] },
|
||||
{ pattern: "linkedin.com", scopes: ["r_liteprofile", "r_emailaddress"] },
|
||||
{ pattern: "auth0.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "okta.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "kinde.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "zitadel.com", scopes: ["openid", "profile", "email"] },
|
||||
];
|
||||
|
||||
for (const { pattern, scopes } of providerPatterns) {
|
||||
if (urlLower.includes(pattern)) {
|
||||
return scopes;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentType === "oidc") {
|
||||
return ["openid", "profile", "email"];
|
||||
} else {
|
||||
return ["profile", "email"];
|
||||
}
|
||||
};
|
||||
|
||||
const updateProviderUrlEdit = (url: string) => {
|
||||
if (!url.trim()) return;
|
||||
|
||||
if (isOfficial) {
|
||||
return;
|
||||
}
|
||||
|
||||
const suggestedScopes = detectProviderTypeAndSuggestScopesEdit(url, formData.type);
|
||||
const shouldUpdateScopes =
|
||||
!formData.scope || formData.scope === "openid profile email" || formData.scope === "profile email";
|
||||
|
||||
if (shouldUpdateScopes) {
|
||||
updateFormData({
|
||||
scope: suggestedScopes.join(" "),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updateFormData = (updates: Partial<typeof formData>) => {
|
||||
const newFormData = { ...formData, ...updates };
|
||||
setFormData(newFormData);
|
||||
|
||||
setEditingFormData({
|
||||
...editingFormData,
|
||||
[provider.id]: newFormData,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSave(formData);
|
||||
};
|
||||
|
||||
const isManualMode = !!(formData.authorizationEndpoint || formData.tokenEndpoint || formData.userInfoEndpoint);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{isOfficial && (
|
||||
<div className="bg-blue-50 dark:bg-blue-950/50 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
|
||||
<div className="flex items-center gap-2 text-blue-700 dark:text-blue-300">
|
||||
<span>
|
||||
<IconInfoCircle className="h-4 w-4" />
|
||||
</span>
|
||||
<span className="text-sm font-medium">{t("authProviders.info.officialProvider")}</span>
|
||||
</div>
|
||||
<p className="text-xs text-blue-600 dark:text-blue-400 mt-1">
|
||||
{t("authProviders.info.officialProviderDescription")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CallbackUrlDisplay providerName={formData.name || "provider"} />
|
||||
|
||||
{!isOfficial && (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.providerName")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.providerNamePlaceholder")}
|
||||
value={formData.name}
|
||||
onChange={(e) => updateFormData({ name: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.displayName")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.displayNamePlaceholder")}
|
||||
value={formData.displayName}
|
||||
onChange={(e) => updateFormData({ displayName: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isOfficial && (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.type")}</Label>
|
||||
<select
|
||||
className="w-full rounded-md border border-input bg-background dark:bg-background px-3 py-2 text-sm text-foreground dark:text-foreground"
|
||||
value={formData.type}
|
||||
onChange={(e) => updateFormData({ type: e.target.value as "oidc" | "oauth2" })}
|
||||
>
|
||||
<option value="oidc">{t("authProviders.form.typeOidc")}</option>
|
||||
<option value="oauth2">{t("authProviders.form.typeOauth2")}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.icon")}</Label>
|
||||
<IconPicker
|
||||
value={formData.icon}
|
||||
onChange={(icon) => updateFormData({ icon })}
|
||||
placeholder={t("authProviders.form.iconPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isOfficial && (
|
||||
<div className="space-y-4">
|
||||
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-800 rounded-lg p-4">
|
||||
<h4 className="text-sm font-medium mb-3">{t("authProviders.form.configurationMethod")}</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="radio"
|
||||
id="auto-discovery"
|
||||
name="configMethod"
|
||||
checked={!isManualMode}
|
||||
onChange={() =>
|
||||
updateFormData({
|
||||
authorizationEndpoint: "",
|
||||
tokenEndpoint: "",
|
||||
userInfoEndpoint: "",
|
||||
})
|
||||
}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<label htmlFor="auto-discovery" className="text-sm">
|
||||
<span className="font-medium">{t("authProviders.form.autoDiscovery")}</span>
|
||||
<span className="text-muted-foreground ml-2">
|
||||
({t("authProviders.form.autoDiscoveryDescription")})
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="radio"
|
||||
id="manual-endpoints"
|
||||
name="configMethod"
|
||||
checked={isManualMode}
|
||||
onChange={() => {
|
||||
if (!isManualMode) {
|
||||
updateFormData({
|
||||
authorizationEndpoint: "/oauth/authorize",
|
||||
tokenEndpoint: "/oauth/token",
|
||||
userInfoEndpoint: "/oauth/userinfo",
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<label htmlFor="manual-endpoints" className="text-sm">
|
||||
<span className="font-medium">{t("authProviders.form.manualEndpoints")}</span>
|
||||
<span className="text-muted-foreground ml-2">
|
||||
({t("authProviders.form.manualEndpointsDescription")})
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isManualMode && (
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.providerUrl")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.providerUrlAutoPlaceholder")}
|
||||
value={formData.issuerUrl}
|
||||
onChange={(e) => updateFormData({ issuerUrl: e.target.value })}
|
||||
onBlur={(e) => updateProviderUrlEdit(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">{t("authProviders.form.autoDiscoveryHelp")}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isManualMode && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.providerUrl")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.providerUrlManualPlaceholder")}
|
||||
value={formData.issuerUrl}
|
||||
onChange={(e) => updateFormData({ issuerUrl: e.target.value })}
|
||||
onBlur={(e) => updateProviderUrlEdit(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">{t("authProviders.form.manualConfigurationHelp")}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.authorizationEndpoint")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.authorizationEndpointPlaceholder")}
|
||||
value={formData.authorizationEndpoint}
|
||||
onChange={(e) => updateFormData({ authorizationEndpoint: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.tokenEndpoint")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.tokenEndpointPlaceholder")}
|
||||
value={formData.tokenEndpoint}
|
||||
onChange={(e) => updateFormData({ tokenEndpoint: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.userInfoEndpoint")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.userInfoEndpointPlaceholder")}
|
||||
value={formData.userInfoEndpoint}
|
||||
onChange={(e) => updateFormData({ userInfoEndpoint: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-blue-50 dark:bg-blue-950/50 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
|
||||
<div className="flex items-start gap-2 text-blue-700 dark:text-blue-300">
|
||||
<IconInfoCircle className="h-4 w-4 mt-0.5 flex-shrink-0" />
|
||||
<div className="text-xs">
|
||||
<p className="font-medium">{t("authProviders.info.manualConfigTitle")}</p>
|
||||
<p className="mt-1">{t("authProviders.info.manualConfigDescription")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isOfficial && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.providerUrl")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.officialProviderUrlPlaceholder", {
|
||||
displayName: provider.displayName,
|
||||
})}
|
||||
value={formData.issuerUrl}
|
||||
onChange={(e) => updateFormData({ issuerUrl: e.target.value })}
|
||||
onBlur={(e) => updateProviderUrlEdit(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">{t("authProviders.form.officialProviderHelp")}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.icon")}</Label>
|
||||
<IconPicker
|
||||
value={formData.icon}
|
||||
onChange={(icon) => updateFormData({ icon })}
|
||||
placeholder={t("authProviders.form.iconPlaceholder")}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">{t("authProviders.form.officialProviderIconHelp")}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.clientId")} *</Label>
|
||||
<Input
|
||||
placeholder={t("authProviders.form.clientIdPlaceholder")}
|
||||
value={formData.clientId}
|
||||
onChange={(e) => updateFormData({ clientId: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.clientSecret")} *</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showClientSecret ? "text" : "password"}
|
||||
placeholder={t("authProviders.form.clientSecretPlaceholder")}
|
||||
value={formData.clientSecret}
|
||||
onChange={(e) => updateFormData({ clientSecret: e.target.value })}
|
||||
className="pr-10"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
|
||||
onClick={() => setShowClientSecret(!showClientSecret)}
|
||||
>
|
||||
{showClientSecret ? (
|
||||
<IconEyeOff className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<IconEye className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.oauthScopes")}</Label>
|
||||
<TagsInput
|
||||
value={formData.scope ? formData.scope.split(/[,\s]+/).filter(Boolean) : []}
|
||||
onChange={(tags) => updateFormData({ scope: tags.join(" ") })}
|
||||
placeholder={t("authProviders.form.scopesPlaceholder")}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{formData.type === "oidc" ? t("authProviders.form.scopesHelpOidc") : t("authProviders.form.scopesHelpOauth2")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mb-2 block">{t("authProviders.form.adminEmailDomains")}</Label>
|
||||
<TagsInput
|
||||
value={formData.adminEmailDomains ? formData.adminEmailDomains.split(",").filter(Boolean) : []}
|
||||
onChange={(tags) => updateFormData({ adminEmailDomains: tags.join(",") })}
|
||||
placeholder={t("authProviders.form.adminEmailDomainsPlaceholder")}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">{t("authProviders.form.adminEmailDomainsHelp")}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={formData.autoRegister}
|
||||
onCheckedChange={(checked) => updateFormData({ autoRegister: checked })}
|
||||
/>
|
||||
<Label className="cursor-pointer">{t("authProviders.form.autoRegister")}</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end pt-4">
|
||||
<Button variant="outline" onClick={onCancel} size="sm">
|
||||
{t("authProviders.buttons.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} disabled={saving} size="sm">
|
||||
{saving ? t("authProviders.buttons.saving") : t("authProviders.buttons.saveProvider")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
|
||||
import { IconSettings } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { AuthProvider } from "./edit-provider-form";
|
||||
import { ProviderRow } from "./provider-row";
|
||||
|
||||
interface ProviderListProps {
|
||||
providers: AuthProvider[];
|
||||
filteredProviders: AuthProvider[];
|
||||
hideDisabledProviders: boolean;
|
||||
onDragEnd: (result: DropResult) => void;
|
||||
onUpdateProvider: (id: string, updates: Partial<AuthProvider>) => void;
|
||||
onEditProvider: (provider: AuthProvider) => void;
|
||||
onDeleteProvider: (provider: AuthProvider) => void;
|
||||
saving: string | null;
|
||||
getIcon: (provider: AuthProvider) => React.ReactNode;
|
||||
editingProvider: AuthProvider | null;
|
||||
editProvider: (data: Partial<AuthProvider>) => void;
|
||||
onCancelEdit: () => void;
|
||||
editingFormData: Record<string, any>;
|
||||
setEditingFormData: (data: Record<string, any>) => void;
|
||||
}
|
||||
|
||||
export function ProviderList({
|
||||
filteredProviders,
|
||||
hideDisabledProviders,
|
||||
onDragEnd,
|
||||
onUpdateProvider,
|
||||
onEditProvider,
|
||||
onDeleteProvider,
|
||||
saving,
|
||||
getIcon,
|
||||
editingProvider,
|
||||
editProvider,
|
||||
onCancelEdit,
|
||||
editingFormData,
|
||||
setEditingFormData,
|
||||
}: ProviderListProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
if (filteredProviders.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<IconSettings className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<p>
|
||||
{hideDisabledProviders ? t("authProviders.noProvidersEnabled") : t("authProviders.noProvidersConfigured")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{hideDisabledProviders ? t("authProviders.dragDisabledMessage") : t("authProviders.dragEnabledMessage")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!hideDisabledProviders ? (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="providers">
|
||||
{(provided) => (
|
||||
<div {...provided.droppableProps} ref={provided.innerRef} className="space-y-2">
|
||||
{filteredProviders.map((provider, index) => (
|
||||
<Draggable key={provider.id} draggableId={provider.id} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
className={`transition-all ${snapshot.isDragging ? "shadow-lg scale-105" : ""}`}
|
||||
>
|
||||
<ProviderRow
|
||||
provider={provider}
|
||||
onUpdate={(updates) => onUpdateProvider(provider.id, updates)}
|
||||
onEdit={() => onEditProvider(provider)}
|
||||
onDelete={() => onDeleteProvider(provider)}
|
||||
saving={saving === provider.id}
|
||||
getIcon={getIcon}
|
||||
editingProvider={editingProvider}
|
||||
editProvider={editProvider}
|
||||
onCancelEdit={onCancelEdit}
|
||||
editingFormData={editingFormData}
|
||||
setEditingFormData={setEditingFormData}
|
||||
dragHandleProps={provided.dragHandleProps}
|
||||
isDragging={snapshot.isDragging}
|
||||
isDragDisabled={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{filteredProviders.map((provider) => (
|
||||
<ProviderRow
|
||||
key={provider.id}
|
||||
provider={provider}
|
||||
onUpdate={(updates) => onUpdateProvider(provider.id, updates)}
|
||||
onEdit={() => onEditProvider(provider)}
|
||||
onDelete={() => onDeleteProvider(provider)}
|
||||
saving={saving === provider.id}
|
||||
getIcon={getIcon}
|
||||
editingProvider={editingProvider}
|
||||
editProvider={editProvider}
|
||||
onCancelEdit={onCancelEdit}
|
||||
editingFormData={editingFormData}
|
||||
setEditingFormData={setEditingFormData}
|
||||
dragHandleProps={null}
|
||||
isDragging={false}
|
||||
isDragDisabled={true}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -0,0 +1,118 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { IconEdit, IconGripVertical, IconTrash } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { AuthProvider, EditProviderForm } from "./edit-provider-form";
|
||||
|
||||
interface ProviderRowProps {
|
||||
provider: AuthProvider;
|
||||
onUpdate: (updates: Partial<AuthProvider>) => void;
|
||||
onEdit: () => void;
|
||||
onDelete: () => void;
|
||||
saving: boolean;
|
||||
getIcon: (provider: AuthProvider) => React.ReactNode;
|
||||
editingProvider: AuthProvider | null;
|
||||
editProvider: (data: Partial<AuthProvider>) => void;
|
||||
onCancelEdit: () => void;
|
||||
editingFormData: Record<string, any>;
|
||||
setEditingFormData: (data: Record<string, any>) => void;
|
||||
dragHandleProps: any;
|
||||
isDragging: boolean;
|
||||
isDragDisabled: boolean;
|
||||
}
|
||||
|
||||
export function ProviderRow({
|
||||
provider,
|
||||
onUpdate,
|
||||
onEdit,
|
||||
onDelete,
|
||||
saving,
|
||||
getIcon,
|
||||
editingProvider,
|
||||
editProvider,
|
||||
onCancelEdit,
|
||||
editingFormData,
|
||||
setEditingFormData,
|
||||
dragHandleProps,
|
||||
isDragging,
|
||||
isDragDisabled,
|
||||
}: ProviderRowProps) {
|
||||
const t = useTranslations();
|
||||
const isEditing = editingProvider?.id === provider.id;
|
||||
|
||||
return (
|
||||
<div className={`border rounded-lg ${isDragging ? "border-blue-300 bg-blue-50 dark:bg-blue-950/20" : ""}`}>
|
||||
<div className="flex items-center justify-between p-3">
|
||||
<div className="flex items-center gap-3">
|
||||
{!isDragDisabled ? (
|
||||
<div
|
||||
{...dragHandleProps}
|
||||
className="cursor-grab active:cursor-grabbing text-muted-foreground hover:text-foreground transition-colors"
|
||||
title={t("authProviders.dragToReorder")}
|
||||
>
|
||||
<IconGripVertical className="h-4 w-4" />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<span className="text-lg">{getIcon(provider)}</span>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${provider.enabled ? "bg-green-500" : "bg-gray-400"}`}
|
||||
title={provider.enabled ? t("authProviders.enabled") : t("authProviders.disabled")}
|
||||
/>
|
||||
<span className="font-medium text-sm">{provider.displayName}</span>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{provider.type.toUpperCase()} • {provider.name}
|
||||
{provider.isOfficial && (
|
||||
<span className="text-blue-600 dark:text-blue-400"> • {t("authProviders.officialProvider")}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Switch checked={provider.enabled} onCheckedChange={(enabled) => onUpdate({ enabled })} disabled={saving} />
|
||||
<Button variant="ghost" size="sm" onClick={onEdit} disabled={saving} title={t("authProviders.editProvider")}>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
{!provider.isOfficial && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onDelete}
|
||||
disabled={saving}
|
||||
className="text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
title={t("authProviders.deleteProvider")}
|
||||
>
|
||||
<IconTrash className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isEditing && (
|
||||
<div className="border-t border-border dark:border-border p-4 space-y-4 bg-muted/50 dark:bg-muted/20">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-medium text-foreground dark:text-foreground">
|
||||
Editar Provider: {provider.displayName}
|
||||
</h3>
|
||||
</div>
|
||||
<EditProviderForm
|
||||
key={provider.id}
|
||||
provider={provider}
|
||||
onSave={editProvider}
|
||||
onCancel={onCancelEdit}
|
||||
saving={saving}
|
||||
editingFormData={editingFormData}
|
||||
setEditingFormData={setEditingFormData}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
export interface AuthProvider {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
type: string;
|
||||
icon?: string;
|
||||
enabled: boolean;
|
||||
issuerUrl?: string;
|
||||
clientId?: string;
|
||||
clientSecret?: string;
|
||||
scope?: string;
|
||||
autoRegister: boolean;
|
||||
adminEmailDomains?: string;
|
||||
sortOrder: number;
|
||||
isOfficial?: boolean;
|
||||
authorizationEndpoint?: string;
|
||||
tokenEndpoint?: string;
|
||||
userInfoEndpoint?: string;
|
||||
}
|
||||
|
||||
export interface NewProvider {
|
||||
name: string;
|
||||
displayName: string;
|
||||
type: "oidc" | "oauth2";
|
||||
icon: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
issuerUrl: string;
|
||||
scope: string;
|
||||
authorizationEndpoint: string;
|
||||
tokenEndpoint: string;
|
||||
userInfoEndpoint: string;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import { SettingsFormProps, ValidGroup } from "../types";
|
||||
import { AuthProvidersSettings } from "./auth-providers-settings";
|
||||
import { AuthProvidersSettings } from "./auth-provider-form/auth-providers-settings";
|
||||
import { SettingsGroup } from "./settings-group";
|
||||
|
||||
const GROUP_ORDER: string[] = ["general", "email", "auth-providers", "security", "storage"];
|
||||
|
281
apps/web/src/app/settings/hooks/use-auth-providers.ts
Normal file
281
apps/web/src/app/settings/hooks/use-auth-providers.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { DropResult } from "@hello-pangea/dnd";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import type { AuthProvider, NewProvider } from "../components/auth-provider-form/types";
|
||||
|
||||
export function useAuthProviders() {
|
||||
const t = useTranslations();
|
||||
const [providers, setProviders] = useState<AuthProvider[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState<string | null>(null);
|
||||
const [editingProvider, setEditingProvider] = useState<AuthProvider | null>(null);
|
||||
const [editingFormData, setEditingFormData] = useState<Record<string, any>>({});
|
||||
const [hideDisabledProviders, setHideDisabledProviders] = useState<boolean>(false);
|
||||
const [providerToDelete, setProviderToDelete] = useState<{
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
} | null>(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
// Load initial state from localStorage
|
||||
useEffect(() => {
|
||||
const savedState = localStorage.getItem("hideDisabledProviders");
|
||||
if (savedState !== null) {
|
||||
setHideDisabledProviders(JSON.parse(savedState));
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Load providers on mount
|
||||
useEffect(() => {
|
||||
loadProviders();
|
||||
}, []);
|
||||
|
||||
const loadProviders = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch("/api/auth/providers/all");
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
setProviders(data.data.sort((a: AuthProvider, b: AuthProvider) => a.sortOrder - b.sortOrder));
|
||||
} else {
|
||||
toast.error(t("authProviders.messages.loadFailed"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading providers:", error);
|
||||
toast.error(t("authProviders.messages.loadFailed"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const updateProvider = async (id: string, updates: Partial<AuthProvider>) => {
|
||||
try {
|
||||
setSaving(id);
|
||||
const response = await fetch(`/api/auth/providers/manage/${id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(updates),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
setProviders((prev) => prev.map((p) => (p.id === id ? { ...p, ...updates } : p)));
|
||||
toast.success(t("authProviders.messages.providerUpdated"));
|
||||
} else {
|
||||
toast.error(t("authProviders.messages.updateFailed"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating provider:", error);
|
||||
toast.error(t("authProviders.messages.updateFailed"));
|
||||
} finally {
|
||||
setSaving(null);
|
||||
}
|
||||
};
|
||||
|
||||
const addProvider = async (newProvider: NewProvider) => {
|
||||
try {
|
||||
setSaving("new");
|
||||
const response = await fetch("/api/auth/providers", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name: newProvider.name.toLowerCase().replace(/\s+/g, "-"),
|
||||
displayName: newProvider.displayName,
|
||||
type: newProvider.type,
|
||||
icon: newProvider.icon,
|
||||
clientId: newProvider.clientId,
|
||||
clientSecret: newProvider.clientSecret,
|
||||
enabled: false,
|
||||
autoRegister: true,
|
||||
scope: newProvider.scope || (newProvider.type === "oidc" ? "openid profile email" : "user:email"),
|
||||
sortOrder: providers.length + 1,
|
||||
...(newProvider.issuerUrl ? { issuerUrl: newProvider.issuerUrl } : {}),
|
||||
...(newProvider.authorizationEndpoint ? { authorizationEndpoint: newProvider.authorizationEndpoint } : {}),
|
||||
...(newProvider.tokenEndpoint ? { tokenEndpoint: newProvider.tokenEndpoint } : {}),
|
||||
...(newProvider.userInfoEndpoint ? { userInfoEndpoint: newProvider.userInfoEndpoint } : {}),
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
await loadProviders();
|
||||
toast.success(t("authProviders.messages.providerAdded"));
|
||||
} else {
|
||||
toast.error(t("authProviders.messages.addFailed"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error adding provider:", error);
|
||||
toast.error(t("authProviders.messages.addFailed"));
|
||||
} finally {
|
||||
setSaving(null);
|
||||
}
|
||||
};
|
||||
|
||||
const editProvider = async (providerData: Partial<AuthProvider>) => {
|
||||
if (!editingProvider || !providerData.name || !providerData.displayName) {
|
||||
toast.error(t("authProviders.messages.fillRequiredFields"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setSaving(editingProvider.id);
|
||||
const response = await fetch(`/api/auth/providers/manage/${editingProvider.id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
...providerData,
|
||||
name: providerData.name?.toLowerCase().replace(/\s+/g, "-"),
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
await loadProviders();
|
||||
setEditingProvider(null);
|
||||
toast.success(t("authProviders.messages.providerUpdated"));
|
||||
} else {
|
||||
toast.error(t("authProviders.messages.updateFailed"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating provider:", error);
|
||||
toast.error(t("authProviders.messages.updateFailed"));
|
||||
} finally {
|
||||
setSaving(null);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteProvider = async (id: string) => {
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
const response = await fetch(`/api/auth/providers/manage/${id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
setProviders((prev) => prev.filter((p) => p.id !== id));
|
||||
toast.success(t("authProviders.messages.providerDeleted"));
|
||||
setProviderToDelete(null);
|
||||
} else {
|
||||
toast.error(t("authProviders.messages.deleteFailed"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting provider:", error);
|
||||
toast.error(t("authProviders.messages.deleteFailed"));
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragEnd = async (result: DropResult) => {
|
||||
if (!result.destination) return;
|
||||
|
||||
const items = Array.from(providers);
|
||||
const [reorderedItem] = items.splice(result.source.index, 1);
|
||||
items.splice(result.destination.index, 0, reorderedItem);
|
||||
|
||||
const updatedItems = items.map((provider, index) => ({
|
||||
...provider,
|
||||
sortOrder: index + 1,
|
||||
}));
|
||||
setProviders(updatedItems);
|
||||
|
||||
const updatedProviders = updatedItems.map((provider) => ({
|
||||
id: provider.id,
|
||||
sortOrder: provider.sortOrder,
|
||||
}));
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/providers/order", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ providers: updatedProviders }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
toast.success(t("authProviders.messages.providerOrderUpdated"));
|
||||
} else {
|
||||
toast.error(t("authProviders.messages.orderUpdateFailed"));
|
||||
await loadProviders();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating provider order:", error);
|
||||
toast.error(t("authProviders.messages.orderUpdateFailed"));
|
||||
await loadProviders();
|
||||
}
|
||||
};
|
||||
|
||||
const handleHideDisabledProvidersChange = (checked: boolean) => {
|
||||
setHideDisabledProviders(checked);
|
||||
localStorage.setItem("hideDisabledProviders", JSON.stringify(checked));
|
||||
};
|
||||
|
||||
const handleEditProvider = (provider: AuthProvider) => {
|
||||
if (editingProvider?.id === provider.id) {
|
||||
setEditingProvider(null);
|
||||
} else {
|
||||
setEditingProvider(provider);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteProvider = (provider: AuthProvider) => {
|
||||
setProviderToDelete({
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
displayName: provider.displayName,
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancelEdit = () => {
|
||||
setEditingProvider(null);
|
||||
setEditingFormData({});
|
||||
};
|
||||
|
||||
// Computed values
|
||||
const enabledCount = providers.filter((p) => p.enabled).length;
|
||||
const filteredProviders = hideDisabledProviders ? providers.filter((p) => p.enabled) : providers;
|
||||
|
||||
return {
|
||||
// State
|
||||
providers,
|
||||
loading,
|
||||
saving,
|
||||
editingProvider,
|
||||
editingFormData,
|
||||
hideDisabledProviders,
|
||||
providerToDelete,
|
||||
isDeleting,
|
||||
|
||||
// Computed
|
||||
enabledCount,
|
||||
filteredProviders,
|
||||
|
||||
// Actions
|
||||
loadProviders,
|
||||
updateProvider,
|
||||
addProvider,
|
||||
editProvider,
|
||||
deleteProvider,
|
||||
handleDragEnd,
|
||||
handleHideDisabledProvidersChange,
|
||||
handleEditProvider,
|
||||
handleDeleteProvider,
|
||||
handleCancelEdit,
|
||||
|
||||
// Setters
|
||||
setEditingFormData,
|
||||
setProviderToDelete,
|
||||
};
|
||||
}
|
@@ -112,7 +112,6 @@ export function useSettings() {
|
||||
setGroupedConfigs(grouped);
|
||||
|
||||
Object.entries(grouped).forEach(([groupName, groupConfigs]) => {
|
||||
// Skip auth-providers group as it has its own custom component
|
||||
if (groupName === "auth-providers") {
|
||||
return;
|
||||
}
|
||||
|
Reference in New Issue
Block a user