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:
Daniel Luiz Alves
2025-06-27 15:23:37 -03:00
parent 8f85874cbe
commit 503ab4055f
29 changed files with 3307 additions and 1762 deletions

View File

@@ -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": "جاري الحذف..."
}
}
}

View File

@@ -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..."
}
}
}

View File

@@ -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..."
}
}
}

View File

@@ -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..."
}
}
}

View File

@@ -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..."
}
}
}

View File

@@ -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": "हटा रहा है..."
}
}
}

View File

@@ -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..."
}
}
}

View File

@@ -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": "削除中..."
}
}
}

View File

@@ -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": "삭제 중..."
}
}
}

View File

@@ -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..."
}
}
}

View File

@@ -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..."
}
}
}

View File

@@ -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..."
}
}
}

View File

@@ -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": "Удаление..."
}
}
}

View File

@@ -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..."
}
}
}

View File

@@ -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())

View File

@@ -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": "删除中..."
}
}
}

View File

@@ -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>
);
}

View File

@@ -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>

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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;
}

View File

@@ -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"];

View 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,
};
}

View File

@@ -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;
}