Compare commits

...

19 Commits

Author SHA1 Message Date
Philip Okugbe 94461e90a3 New translations translation.json (Portuguese, Brazilian)
[ci skip]
2026-05-01 16:02:26 +01:00
Philip Okugbe 58aa02340e New translations translation.json (English)
[ci skip]
2026-05-01 16:02:24 +01:00
Philip Okugbe 592e6a39e8 New translations translation.json (Chinese Simplified)
[ci skip]
2026-05-01 16:02:23 +01:00
Philip Okugbe 56526c6c1c New translations translation.json (Ukrainian)
[ci skip]
2026-05-01 16:02:21 +01:00
Philip Okugbe 6f9387b8b4 New translations translation.json (Russian)
[ci skip]
2026-05-01 16:02:20 +01:00
Philip Okugbe aa2ca3ef91 New translations translation.json (Dutch)
[ci skip]
2026-05-01 16:02:18 +01:00
Philip Okugbe 21848b91bf New translations translation.json (Korean)
[ci skip]
2026-05-01 16:02:17 +01:00
Philip Okugbe 989231d818 New translations translation.json (Japanese)
[ci skip]
2026-05-01 16:02:15 +01:00
Philip Okugbe d50986453b New translations translation.json (Italian)
[ci skip]
2026-05-01 16:02:14 +01:00
Philip Okugbe 2c21af4e91 New translations translation.json (Spanish)
[ci skip]
2026-05-01 16:02:12 +01:00
Philip Okugbe 574f687335 New translations translation.json (French)
[ci skip]
2026-05-01 16:02:10 +01:00
Philip Okugbe 9956a98d1f New translations translation.json (German)
[ci skip]
2026-05-01 16:02:08 +01:00
Philip Okugbe 14fd3eb956 New translations translation.json (German)
[ci skip]
2026-05-01 00:49:11 +01:00
Philipinho dba8e315ab override 2026-04-14 17:59:59 +01:00
Philipinho 81ae7a17a6 confirm dialog 2026-04-14 17:56:36 +01:00
Philipinho 271f855761 v0.80.0 2026-04-14 17:08:44 +01:00
Philipinho 3e6d915227 sync 2026-04-14 16:34:44 +01:00
Philip Okugbe a6a7e4370a feat(ee): PDF export api (#2112)
* feat(ee): server side PDF export

* feat: pdf export queue

* sync

* sync
2026-04-14 16:26:54 +01:00
Philip Okugbe cc00e77dfb fix: space overview favorites (#2110) 2026-04-14 02:58:24 +01:00
41 changed files with 714 additions and 158 deletions
+3
View File
@@ -43,6 +43,9 @@ POSTMARK_TOKEN=
# for custom drawio server # for custom drawio server
DRAWIO_URL= DRAWIO_URL=
# Gotenberg URL for server-side PDF export
GOTENBERG_URL=
DISABLE_TELEMETRY=false DISABLE_TELEMETRY=false
# Enable debug logging in production (default: false) # Enable debug logging in production (default: false)
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "client", "name": "client",
"private": true, "private": true,
"version": "0.71.1", "version": "0.80.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
@@ -222,6 +222,8 @@
"Edit comment": "Kommentar bearbeiten", "Edit comment": "Kommentar bearbeiten",
"Delete comment": "Kommentar löschen", "Delete comment": "Kommentar löschen",
"Are you sure you want to delete this comment?": "Sind Sie sicher, dass Sie diesen Kommentar löschen möchten?", "Are you sure you want to delete this comment?": "Sind Sie sicher, dass Sie diesen Kommentar löschen möchten?",
"Delete chat": "Chat löschen",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Sind Sie sicher, dass Sie '{{title}}' löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"Comment created successfully": "Kommentar erfolgreich erstellt", "Comment created successfully": "Kommentar erfolgreich erstellt",
"Error creating comment": "Fehler beim Erstellen des Kommentars", "Error creating comment": "Fehler beim Erstellen des Kommentars",
"Comment updated successfully": "Kommentar erfolgreich aktualisiert", "Comment updated successfully": "Kommentar erfolgreich aktualisiert",
@@ -389,7 +391,7 @@
"Write anything. Enter \"/\" for commands": "Schreiben Sie etwas. Geben Sie \"/\" für Befehle ein", "Write anything. Enter \"/\" for commands": "Schreiben Sie etwas. Geben Sie \"/\" für Befehle ein",
"Write...": "\"Schreiben...\"", "Write...": "\"Schreiben...\"",
"Column count": "Spaltenanzahl", "Column count": "Spaltenanzahl",
"{{count}} Columns": "{count, plural, one {# Spalte} other {# Spalten}}", "{{count}} Columns": "{{count}} Spalten",
"Equal columns": "Gleich breite Spalten", "Equal columns": "Gleich breite Spalten",
"Left sidebar": "Linke Seitenleiste", "Left sidebar": "Linke Seitenleiste",
"Right sidebar": "Rechte Seitenleiste", "Right sidebar": "Rechte Seitenleiste",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "Bild überschreitet das Limit von 10 MB.", "Image exceeds 10MB limit.": "Bild überschreitet das Limit von 10 MB.",
"Image removed successfully": "Bild erfolgreich entfernt", "Image removed successfully": "Bild erfolgreich entfernt",
"API key": "API-Schlüssel", "API key": "API-Schlüssel",
"API key created successfully": "API-Schlüssel erfolgreich erstellt",
"API keys": "API-Schlüssel", "API keys": "API-Schlüssel",
"API management": "API-Verwaltung", "API management": "API-Verwaltung",
"Are you sure you want to revoke this API key": "Sind Sie sicher, dass Sie diesen API-Schlüssel widerrufen möchten?",
"Create API Key": "API-Schlüssel erstellen",
"Custom expiration date": "Benutzerdefiniertes Ablaufdatum", "Custom expiration date": "Benutzerdefiniertes Ablaufdatum",
"Enter a descriptive token name": "Geben Sie einen beschreibenden Token-Namen ein", "Enter a descriptive token name": "Geben Sie einen beschreibenden Token-Namen ein",
"Expiration": "Ablauf", "Expiration": "Ablauf",
"Expired": "Abgelaufen", "Expired": "Abgelaufen",
"Expires": "Läuft ab", "Expires": "Läuft ab",
"I've saved my API key": "Ich habe meinen API-Schlüssel gespeichert",
"Last use": "Zuletzt verwendet", "Last use": "Zuletzt verwendet",
"No API keys found": "Keine API-Schlüssel gefunden", "No API keys found": "Keine API-Schlüssel gefunden",
"No expiration": "Kein Ablauf", "No expiration": "Kein Ablauf",
"Revoke API key": "API-Schlüssel widerrufen",
"Revoked successfully": "Erfolgreich widerrufen", "Revoked successfully": "Erfolgreich widerrufen",
"Select expiration date": "Ablaufdatum wählen", "Select expiration date": "Ablaufdatum wählen",
"This action cannot be undone. Any applications using this API key will stop working.": "Diese Aktion kann nicht rückgängig gemacht werden. Alle Anwendungen, die diesen API-Schlüssel verwenden, werden nicht mehr funktionieren.", "This action cannot be undone. Any applications using this API key will stop working.": "Diese Aktion kann nicht rückgängig gemacht werden. Alle Anwendungen, die diesen API-Schlüssel verwenden, werden nicht mehr funktionieren.",
"Update API key": "API-Schlüssel aktualisieren", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "Verwalten Sie API-Schlüssel für alle Benutzer im Arbeitsbereich", "Manage API keys for all users in the workspace": "Verwalten Sie API-Schlüssel für alle Benutzer im Arbeitsbereich",
"Restrict API key creation to admins": "API-Schlüsselerstellung auf Administratoren beschränken", "Restrict API key creation to admins": "API-Schlüsselerstellung auf Administratoren beschränken",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Nur Administratoren und Eigentümer können neue API-Schlüssel erstellen. Bestehende Mitgliederschlüssel funktionieren weiterhin.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Nur Administratoren und Eigentümer können neue API-Schlüssel erstellen. Bestehende Mitgliederschlüssel funktionieren weiterhin.",
@@ -878,5 +876,29 @@
"Try a different search term.": "Versuchen Sie einen anderen Suchbegriff.", "Try a different search term.": "Versuchen Sie einen anderen Suchbegriff.",
"Try again": "Erneut versuchen", "Try again": "Erneut versuchen",
"Untitled chat": "Chat ohne Titel", "Untitled chat": "Chat ohne Titel",
"What can I help you with?": "Womit kann ich Ihnen helfen?" "What can I help you with?": "Womit kann ich Ihnen helfen?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "Edit comment", "Edit comment": "Edit comment",
"Delete comment": "Delete comment", "Delete comment": "Delete comment",
"Are you sure you want to delete this comment?": "Are you sure you want to delete this comment?", "Are you sure you want to delete this comment?": "Are you sure you want to delete this comment?",
"Delete chat": "Delete chat",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Are you sure you want to delete '{{title}}'? This action cannot be undone.",
"Comment created successfully": "Comment created successfully", "Comment created successfully": "Comment created successfully",
"Error creating comment": "Error creating comment", "Error creating comment": "Error creating comment",
"Comment updated successfully": "Comment updated successfully", "Comment updated successfully": "Comment updated successfully",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "Image exceeds 10MB limit.", "Image exceeds 10MB limit.": "Image exceeds 10MB limit.",
"Image removed successfully": "Image removed successfully", "Image removed successfully": "Image removed successfully",
"API key": "API key", "API key": "API key",
"API key created successfully": "API key created successfully",
"API keys": "API keys", "API keys": "API keys",
"API management": "API management", "API management": "API management",
"Are you sure you want to revoke this API key": "Are you sure you want to revoke this API key",
"Create API Key": "Create API Key",
"Custom expiration date": "Custom expiration date", "Custom expiration date": "Custom expiration date",
"Enter a descriptive token name": "Enter a descriptive token name", "Enter a descriptive token name": "Enter a descriptive token name",
"Expiration": "Expiration", "Expiration": "Expiration",
"Expired": "Expired", "Expired": "Expired",
"Expires": "Expires", "Expires": "Expires",
"I've saved my API key": "I've saved my API key",
"Last use": "Last Used", "Last use": "Last Used",
"No API keys found": "No API keys found", "No API keys found": "No API keys found",
"No expiration": "No expiration", "No expiration": "No expiration",
"Revoke API key": "Revoke API key",
"Revoked successfully": "Revoked successfully", "Revoked successfully": "Revoked successfully",
"Select expiration date": "Select expiration date", "Select expiration date": "Select expiration date",
"This action cannot be undone. Any applications using this API key will stop working.": "This action cannot be undone. Any applications using this API key will stop working.", "This action cannot be undone. Any applications using this API key will stop working.": "This action cannot be undone. Any applications using this API key will stop working.",
"Update API key": "Update API key", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "Manage API keys for all users in the workspace", "Manage API keys for all users in the workspace": "Manage API keys for all users in the workspace",
"Restrict API key creation to admins": "Restrict API key creation to admins", "Restrict API key creation to admins": "Restrict API key creation to admins",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Only admins and owners can create new API keys. Existing member keys will continue to work.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Only admins and owners can create new API keys. Existing member keys will continue to work.",
@@ -878,5 +876,29 @@
"Try a different search term.": "Try a different search term.", "Try a different search term.": "Try a different search term.",
"Try again": "Try again", "Try again": "Try again",
"Untitled chat": "Untitled chat", "Untitled chat": "Untitled chat",
"What can I help you with?": "What can I help you with?" "What can I help you with?": "What can I help you with?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "Editar comentario", "Edit comment": "Editar comentario",
"Delete comment": "Eliminar comentario", "Delete comment": "Eliminar comentario",
"Are you sure you want to delete this comment?": "¿Está seguro de que desea eliminar este comentario?", "Are you sure you want to delete this comment?": "¿Está seguro de que desea eliminar este comentario?",
"Delete chat": "Eliminar chat",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "¿Está seguro de que desea eliminar '{{title}}'? Esta acción no se puede deshacer.",
"Comment created successfully": "Comentario creado con éxito", "Comment created successfully": "Comentario creado con éxito",
"Error creating comment": "Error al crear comentario", "Error creating comment": "Error al crear comentario",
"Comment updated successfully": "Comentario actualizado con éxito", "Comment updated successfully": "Comentario actualizado con éxito",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "La imagen excede del límite de 10 MB", "Image exceeds 10MB limit.": "La imagen excede del límite de 10 MB",
"Image removed successfully": "Imagen eliminada correctamente", "Image removed successfully": "Imagen eliminada correctamente",
"API key": "Clave API", "API key": "Clave API",
"API key created successfully": "Clave API creada correctamente",
"API keys": "Claves API", "API keys": "Claves API",
"API management": "Gestión de API", "API management": "Gestión de API",
"Are you sure you want to revoke this API key": "¿Está seguro de que desea revocar esta clave API? ",
"Create API Key": "Crear clave API",
"Custom expiration date": "Fecha de vencimiento personalizada", "Custom expiration date": "Fecha de vencimiento personalizada",
"Enter a descriptive token name": "Introduce un nombre descriptivo del token", "Enter a descriptive token name": "Introduce un nombre descriptivo del token",
"Expiration": "Vencimiento", "Expiration": "Vencimiento",
"Expired": "Vencido", "Expired": "Vencido",
"Expires": "Vence", "Expires": "Vence",
"I've saved my API key": "He guardado mi clave API",
"Last use": "Último uso", "Last use": "Último uso",
"No API keys found": "No se han encontrado claves API", "No API keys found": "No se han encontrado claves API",
"No expiration": "Sin vencimiento", "No expiration": "Sin vencimiento",
"Revoke API key": "Revocar clave API",
"Revoked successfully": "Revocada correctamente", "Revoked successfully": "Revocada correctamente",
"Select expiration date": "Seleccionar fecha de vencimiento", "Select expiration date": "Seleccionar fecha de vencimiento",
"This action cannot be undone. Any applications using this API key will stop working.": "Esta acción no se puede deshacer. Las aplicaciones que utilicen esta clave API dejarán de funcionar.", "This action cannot be undone. Any applications using this API key will stop working.": "Esta acción no se puede deshacer. Las aplicaciones que utilicen esta clave API dejarán de funcionar.",
"Update API key": "Actualizar clave API", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "Gestionar claves API para todos los usuarios en el espacio de trabajo", "Manage API keys for all users in the workspace": "Gestionar claves API para todos los usuarios en el espacio de trabajo",
"Restrict API key creation to admins": "Restringir la creación de claves API a administradores", "Restrict API key creation to admins": "Restringir la creación de claves API a administradores",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Solo los administradores y propietarios pueden crear nuevas claves API. Las claves de miembros existentes seguirán funcionando.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Solo los administradores y propietarios pueden crear nuevas claves API. Las claves de miembros existentes seguirán funcionando.",
@@ -878,5 +876,29 @@
"Try a different search term.": "Prueba con otro término de búsqueda.", "Try a different search term.": "Prueba con otro término de búsqueda.",
"Try again": "Intentar de nuevo", "Try again": "Intentar de nuevo",
"Untitled chat": "Chat sin título", "Untitled chat": "Chat sin título",
"What can I help you with?": "¿En qué puedo ayudarte?" "What can I help you with?": "¿En qué puedo ayudarte?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "Modifier le commentaire", "Edit comment": "Modifier le commentaire",
"Delete comment": "Supprimer le commentaire", "Delete comment": "Supprimer le commentaire",
"Are you sure you want to delete this comment?": "Êtes-vous sûr de vouloir supprimer ce commentaire ?", "Are you sure you want to delete this comment?": "Êtes-vous sûr de vouloir supprimer ce commentaire ?",
"Delete chat": "Supprimer la conversation",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Êtes-vous sûr de vouloir supprimer '{{title}}' ? Cette action est irréversible.",
"Comment created successfully": "Commentaire créé avec succès", "Comment created successfully": "Commentaire créé avec succès",
"Error creating comment": "Erreur lors de la création du commentaire", "Error creating comment": "Erreur lors de la création du commentaire",
"Comment updated successfully": "Commentaire mis à jour avec succès", "Comment updated successfully": "Commentaire mis à jour avec succès",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "L'image dépasse la limite de 10 Mo.", "Image exceeds 10MB limit.": "L'image dépasse la limite de 10 Mo.",
"Image removed successfully": "Image supprimée avec succès", "Image removed successfully": "Image supprimée avec succès",
"API key": "Clé API", "API key": "Clé API",
"API key created successfully": "Clé API créée avec succès",
"API keys": "Clés API", "API keys": "Clés API",
"API management": "Gestion des API", "API management": "Gestion des API",
"Are you sure you want to revoke this API key": "Êtes-vous sûr de vouloir révoquer cette clé API",
"Create API Key": "Créer une clé API",
"Custom expiration date": "Date d'expiration personnalisée", "Custom expiration date": "Date d'expiration personnalisée",
"Enter a descriptive token name": "Entrez un nom descriptif pour le jeton", "Enter a descriptive token name": "Entrez un nom descriptif pour le jeton",
"Expiration": "Expiration", "Expiration": "Expiration",
"Expired": "Expiré(e)", "Expired": "Expiré(e)",
"Expires": "Expire", "Expires": "Expire",
"I've saved my API key": "J'ai enregistré ma clé API",
"Last use": "Dernière utilisation", "Last use": "Dernière utilisation",
"No API keys found": "Aucune clé API trouvée", "No API keys found": "Aucune clé API trouvée",
"No expiration": "Pas d'expiration", "No expiration": "Pas d'expiration",
"Revoke API key": "Révoquer la clé API",
"Revoked successfully": "Révoqué(e) avec succès", "Revoked successfully": "Révoqué(e) avec succès",
"Select expiration date": "Sélectionnez la date d'expiration", "Select expiration date": "Sélectionnez la date d'expiration",
"This action cannot be undone. Any applications using this API key will stop working.": "Cette action ne peut pas être annulée. Toutes les applications utilisant cette clé API cesseront de fonctionner.", "This action cannot be undone. Any applications using this API key will stop working.": "Cette action ne peut pas être annulée. Toutes les applications utilisant cette clé API cesseront de fonctionner.",
"Update API key": "Mettre à jour la clé API", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "Gérer les clés API pour tous les utilisateurs dans l'espace de travail", "Manage API keys for all users in the workspace": "Gérer les clés API pour tous les utilisateurs dans l'espace de travail",
"Restrict API key creation to admins": "Restreindre la création de clés API aux administrateurs", "Restrict API key creation to admins": "Restreindre la création de clés API aux administrateurs",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Seuls les administrateurs et les propriétaires peuvent créer de nouvelles clés API. Les clés des membres existants continueront de fonctionner.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Seuls les administrateurs et les propriétaires peuvent créer de nouvelles clés API. Les clés des membres existants continueront de fonctionner.",
@@ -878,5 +876,29 @@
"Try a different search term.": "Essayez un autre terme de recherche.", "Try a different search term.": "Essayez un autre terme de recherche.",
"Try again": "Réessayer", "Try again": "Réessayer",
"Untitled chat": "Discussion sans titre", "Untitled chat": "Discussion sans titre",
"What can I help you with?": "Que puis-je faire pour vous aider ?" "What can I help you with?": "Que puis-je faire pour vous aider ?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "Modifica commento", "Edit comment": "Modifica commento",
"Delete comment": "Elimina commento", "Delete comment": "Elimina commento",
"Are you sure you want to delete this comment?": "Sei sicuro di voler eliminare questo commento?", "Are you sure you want to delete this comment?": "Sei sicuro di voler eliminare questo commento?",
"Delete chat": "Elimina chat",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Sei sicuro di voler eliminare '{{title}}'? Questa azione non può essere annullata.",
"Comment created successfully": "Commento creato con successo", "Comment created successfully": "Commento creato con successo",
"Error creating comment": "Si è verificato un errore durante la creazione del commento", "Error creating comment": "Si è verificato un errore durante la creazione del commento",
"Comment updated successfully": "Commento aggiornato con successo", "Comment updated successfully": "Commento aggiornato con successo",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "L'immagine supera il limite di 10MB.", "Image exceeds 10MB limit.": "L'immagine supera il limite di 10MB.",
"Image removed successfully": "Immagine rimossa con successo", "Image removed successfully": "Immagine rimossa con successo",
"API key": "Chiave API", "API key": "Chiave API",
"API key created successfully": "Chiave API creata con successo",
"API keys": "Chiavi API", "API keys": "Chiavi API",
"API management": "Gestione API", "API management": "Gestione API",
"Are you sure you want to revoke this API key": "Sei sicuro di voler revocare questa chiave API",
"Create API Key": "Crea Chiave API",
"Custom expiration date": "Data di scadenza personalizzata", "Custom expiration date": "Data di scadenza personalizzata",
"Enter a descriptive token name": "Inserisci un nome descrittivo del token", "Enter a descriptive token name": "Inserisci un nome descrittivo del token",
"Expiration": "Scadenza", "Expiration": "Scadenza",
"Expired": "Scaduto", "Expired": "Scaduto",
"Expires": "Scade", "Expires": "Scade",
"I've saved my API key": "Ho salvato la mia chiave API",
"Last use": "Ultimo utilizzo", "Last use": "Ultimo utilizzo",
"No API keys found": "Nessuna chiave API trovata", "No API keys found": "Nessuna chiave API trovata",
"No expiration": "Nessuna scadenza", "No expiration": "Nessuna scadenza",
"Revoke API key": "Revoca chiave API",
"Revoked successfully": "Revocata con successo", "Revoked successfully": "Revocata con successo",
"Select expiration date": "Seleziona la data di scadenza", "Select expiration date": "Seleziona la data di scadenza",
"This action cannot be undone. Any applications using this API key will stop working.": "Questa azione non può essere annullata. Qualsiasi applicazione che utilizza questa chiave API smetterà di funzionare.", "This action cannot be undone. Any applications using this API key will stop working.": "Questa azione non può essere annullata. Qualsiasi applicazione che utilizza questa chiave API smetterà di funzionare.",
"Update API key": "Aggiorna chiave API", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "Gestisci le chiavi API per tutti gli utenti nell'area di lavoro", "Manage API keys for all users in the workspace": "Gestisci le chiavi API per tutti gli utenti nell'area di lavoro",
"Restrict API key creation to admins": "Limita la creazione delle chiavi API agli amministratori", "Restrict API key creation to admins": "Limita la creazione delle chiavi API agli amministratori",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Solo gli amministratori e i proprietari possono creare nuove chiavi API. Le chiavi dei membri esistenti continueranno a funzionare.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Solo gli amministratori e i proprietari possono creare nuove chiavi API. Le chiavi dei membri esistenti continueranno a funzionare.",
@@ -878,5 +876,29 @@
"Try a different search term.": "Prova un termine di ricerca diverso.", "Try a different search term.": "Prova un termine di ricerca diverso.",
"Try again": "Riprova", "Try again": "Riprova",
"Untitled chat": "Chat senza titolo", "Untitled chat": "Chat senza titolo",
"What can I help you with?": "Con cosa posso aiutarti?" "What can I help you with?": "Con cosa posso aiutarti?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "コメントを編集する", "Edit comment": "コメントを編集する",
"Delete comment": "コメントを削除する", "Delete comment": "コメントを削除する",
"Are you sure you want to delete this comment?": "このコメントを削除してもよろしいですか?", "Are you sure you want to delete this comment?": "このコメントを削除してもよろしいですか?",
"Delete chat": "チャットを削除",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "「{{title}}」を削除してもよろしいですか?この操作は元に戻せません。",
"Comment created successfully": "コメントを作成しました", "Comment created successfully": "コメントを作成しました",
"Error creating comment": "コメントの作成に失敗しました", "Error creating comment": "コメントの作成に失敗しました",
"Comment updated successfully": "コメントを更新しました", "Comment updated successfully": "コメントを更新しました",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "画像が10MBの制限を超えています", "Image exceeds 10MB limit.": "画像が10MBの制限を超えています",
"Image removed successfully": "画像を削除しました", "Image removed successfully": "画像を削除しました",
"API key": "APIキー", "API key": "APIキー",
"API key created successfully": "APIキーを作成しました",
"API keys": "APIキー", "API keys": "APIキー",
"API management": "API管理", "API management": "API管理",
"Are you sure you want to revoke this API key": "このAPIキーを無効にしてもよろしいですか",
"Create API Key": "APIキーを作成",
"Custom expiration date": "カスタム有効期限", "Custom expiration date": "カスタム有効期限",
"Enter a descriptive token name": "説明的なトークン名を入力してください", "Enter a descriptive token name": "説明的なトークン名を入力してください",
"Expiration": "有効期限", "Expiration": "有効期限",
"Expired": "期限切れ", "Expired": "期限切れ",
"Expires": "期限が切れます", "Expires": "期限が切れます",
"I've saved my API key": "APIキーを保存しました",
"Last use": "最終使用", "Last use": "最終使用",
"No API keys found": "APIキーが見つかりません", "No API keys found": "APIキーが見つかりません",
"No expiration": "期限なし", "No expiration": "期限なし",
"Revoke API key": "APIキーを無効にする",
"Revoked successfully": "無効にしました", "Revoked successfully": "無効にしました",
"Select expiration date": "有効期限を選択してください", "Select expiration date": "有効期限を選択してください",
"This action cannot be undone. Any applications using this API key will stop working.": "この操作は取り消せません。このAPIキーを使用しているアプリケーションは動作しなくなります", "This action cannot be undone. Any applications using this API key will stop working.": "この操作は取り消せません。このAPIキーを使用しているアプリケーションは動作しなくなります",
"Update API key": "APIキーを更新", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "ワークスペース内のすべてのユーザーのAPIキーを管理", "Manage API keys for all users in the workspace": "ワークスペース内のすべてのユーザーのAPIキーを管理",
"Restrict API key creation to admins": "APIキーの作成を管理者のみに制限する", "Restrict API key creation to admins": "APIキーの作成を管理者のみに制限する",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "新しいAPIキーを作成できるのは管理者とオーナーのみです。既存のメンバーキーは引き続き有効です。", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "新しいAPIキーを作成できるのは管理者とオーナーのみです。既存のメンバーキーは引き続き有効です。",
@@ -878,5 +876,29 @@
"Try a different search term.": "別の検索語を試してください。", "Try a different search term.": "別の検索語を試してください。",
"Try again": "再試行", "Try again": "再試行",
"Untitled chat": "無題のチャット", "Untitled chat": "無題のチャット",
"What can I help you with?": "何をお手伝いしましょうか?" "What can I help you with?": "何をお手伝いしましょうか?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "댓글 수정", "Edit comment": "댓글 수정",
"Delete comment": "댓글 삭제", "Delete comment": "댓글 삭제",
"Are you sure you want to delete this comment?": "이 댓글을 삭제하시겠습니까?", "Are you sure you want to delete this comment?": "이 댓글을 삭제하시겠습니까?",
"Delete chat": "채팅 삭제",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "'{{title}}'을(를) 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
"Comment created successfully": "댓글 생성 완료", "Comment created successfully": "댓글 생성 완료",
"Error creating comment": "댓글 생성 오류", "Error creating comment": "댓글 생성 오류",
"Comment updated successfully": "댓글 업데이트 완료", "Comment updated successfully": "댓글 업데이트 완료",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "이미지가 10MB 용량 제한을 초과합니다.", "Image exceeds 10MB limit.": "이미지가 10MB 용량 제한을 초과합니다.",
"Image removed successfully": "이미지가 성공적으로 제거되었습니다", "Image removed successfully": "이미지가 성공적으로 제거되었습니다",
"API key": "API 키", "API key": "API 키",
"API key created successfully": "API 키 생성 완료",
"API keys": "API 키", "API keys": "API 키",
"API management": "API 관리", "API management": "API 관리",
"Are you sure you want to revoke this API key": "이 API 키를 취소하시겠습니까?",
"Create API Key": "API 키 생성",
"Custom expiration date": "사용자 정의 만료일", "Custom expiration date": "사용자 정의 만료일",
"Enter a descriptive token name": "토큰 이름을 입력하세요", "Enter a descriptive token name": "토큰 이름을 입력하세요",
"Expiration": "만료", "Expiration": "만료",
"Expired": "만료됨", "Expired": "만료됨",
"Expires": "만료일", "Expires": "만료일",
"I've saved my API key": "API 키를 저장했습니다",
"Last use": "최근 사용", "Last use": "최근 사용",
"No API keys found": "API 키를 찾을 수 없습니다", "No API keys found": "API 키를 찾을 수 없습니다",
"No expiration": "유효기간 없음", "No expiration": "유효기간 없음",
"Revoke API key": "API 키 취소",
"Revoked successfully": "성공적으로 취소되었습니다", "Revoked successfully": "성공적으로 취소되었습니다",
"Select expiration date": "만료일 선택", "Select expiration date": "만료일 선택",
"This action cannot be undone. Any applications using this API key will stop working.": "이 작업은 되돌릴 수 없습니다. 이 API 키를 사용하는 모든 응용 프로그램이 작동을 멈출 것입니다.", "This action cannot be undone. Any applications using this API key will stop working.": "이 작업은 되돌릴 수 없습니다. 이 API 키를 사용하는 모든 응용 프로그램이 작동을 멈출 것입니다.",
"Update API key": "API 키 갱신", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "워크스페이스 내 모든 사용자의 API 키 관리", "Manage API keys for all users in the workspace": "워크스페이스 내 모든 사용자의 API 키 관리",
"Restrict API key creation to admins": "API 키 생성 권한을 관리자에게만 제한합니다", "Restrict API key creation to admins": "API 키 생성 권한을 관리자에게만 제한합니다",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "새로운 API 키는 관리자와 소유자만 생성할 수 있습니다. 기존 멤버 키는 계속 사용할 수 있습니다.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "새로운 API 키는 관리자와 소유자만 생성할 수 있습니다. 기존 멤버 키는 계속 사용할 수 있습니다.",
@@ -878,5 +876,29 @@
"Try a different search term.": "다른 검색어를 사용해 보세요.", "Try a different search term.": "다른 검색어를 사용해 보세요.",
"Try again": "다시 시도", "Try again": "다시 시도",
"Untitled chat": "제목 없는 채팅", "Untitled chat": "제목 없는 채팅",
"What can I help you with?": "무엇을 도와드릴까요?" "What can I help you with?": "무엇을 도와드릴까요?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "Bewerk reactie", "Edit comment": "Bewerk reactie",
"Delete comment": "Verwijder reactie", "Delete comment": "Verwijder reactie",
"Are you sure you want to delete this comment?": "Weet je zeker dat je deze reactie wilt verwijderen?", "Are you sure you want to delete this comment?": "Weet je zeker dat je deze reactie wilt verwijderen?",
"Delete chat": "Chat verwijderen",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Weet je zeker dat je '{{title}}' wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.",
"Comment created successfully": "Reactie succesvol aangemaakt", "Comment created successfully": "Reactie succesvol aangemaakt",
"Error creating comment": "Fout bij het aanmaken van reactie", "Error creating comment": "Fout bij het aanmaken van reactie",
"Comment updated successfully": "Opmerking succesvol bijgewerkt", "Comment updated successfully": "Opmerking succesvol bijgewerkt",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "Afbeelding overschrijdt de limiet van 10MB.", "Image exceeds 10MB limit.": "Afbeelding overschrijdt de limiet van 10MB.",
"Image removed successfully": "Afbeelding succesvol verwijderd", "Image removed successfully": "Afbeelding succesvol verwijderd",
"API key": "API-sleutel", "API key": "API-sleutel",
"API key created successfully": "API-sleutel succesvol aangemaakt",
"API keys": "API-sleutels", "API keys": "API-sleutels",
"API management": "API-beheer", "API management": "API-beheer",
"Are you sure you want to revoke this API key": "Weet u zeker dat u deze API-sleutel wilt intrekken",
"Create API Key": "API-sleutel aanmaken",
"Custom expiration date": "Aangepaste vervaldatum", "Custom expiration date": "Aangepaste vervaldatum",
"Enter a descriptive token name": "Voer een beschrijvende tokennaam in", "Enter a descriptive token name": "Voer een beschrijvende tokennaam in",
"Expiration": "Vervaldatum", "Expiration": "Vervaldatum",
"Expired": "Verlopen", "Expired": "Verlopen",
"Expires": "Verloopt", "Expires": "Verloopt",
"I've saved my API key": "Ik heb mijn API-sleutel opgeslagen",
"Last use": "Laatst gebruikt", "Last use": "Laatst gebruikt",
"No API keys found": "Geen API-sleutels gevonden", "No API keys found": "Geen API-sleutels gevonden",
"No expiration": "Geen vervaldatum", "No expiration": "Geen vervaldatum",
"Revoke API key": "API-sleutel intrekken",
"Revoked successfully": "Succesvol ingetrokken", "Revoked successfully": "Succesvol ingetrokken",
"Select expiration date": "Selecteer vervaldatum", "Select expiration date": "Selecteer vervaldatum",
"This action cannot be undone. Any applications using this API key will stop working.": "Deze actie kan niet ongedaan worden gemaakt. Alle toepassingen die deze API-sleutel gebruiken, zullen niet meer werken.", "This action cannot be undone. Any applications using this API key will stop working.": "Deze actie kan niet ongedaan worden gemaakt. Alle toepassingen die deze API-sleutel gebruiken, zullen niet meer werken.",
"Update API key": "API-sleutel bijwerken", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "Beheer API-sleutels voor alle gebruikers in de werkruimte", "Manage API keys for all users in the workspace": "Beheer API-sleutels voor alle gebruikers in de werkruimte",
"Restrict API key creation to admins": "Beperk het aanmaken van API-sleutels tot beheerders.", "Restrict API key creation to admins": "Beperk het aanmaken van API-sleutels tot beheerders.",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Alleen beheerders en eigenaren kunnen nieuwe API-sleutels aanmaken. Bestaande leden-sleutels blijven werken.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Alleen beheerders en eigenaren kunnen nieuwe API-sleutels aanmaken. Bestaande leden-sleutels blijven werken.",
@@ -878,5 +876,29 @@
"Try a different search term.": "Probeer een andere zoekterm.", "Try a different search term.": "Probeer een andere zoekterm.",
"Try again": "Probeer opnieuw", "Try again": "Probeer opnieuw",
"Untitled chat": "Chat zonder titel", "Untitled chat": "Chat zonder titel",
"What can I help you with?": "Waar kan ik je mee helpen?" "What can I help you with?": "Waar kan ik je mee helpen?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "Editar comentário", "Edit comment": "Editar comentário",
"Delete comment": "Excluir comentário", "Delete comment": "Excluir comentário",
"Are you sure you want to delete this comment?": "Você tem certeza de que deseja excluir este comentário?", "Are you sure you want to delete this comment?": "Você tem certeza de que deseja excluir este comentário?",
"Delete chat": "Excluir chat",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Tem certeza de que deseja excluir '{{title}}'? Esta ação não pode ser desfeita.",
"Comment created successfully": "Comentário criado com sucesso", "Comment created successfully": "Comentário criado com sucesso",
"Error creating comment": "Erro ao criar comentário", "Error creating comment": "Erro ao criar comentário",
"Comment updated successfully": "Comentário atualizado com sucesso", "Comment updated successfully": "Comentário atualizado com sucesso",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "A imagem excede o limite de 10MB.", "Image exceeds 10MB limit.": "A imagem excede o limite de 10MB.",
"Image removed successfully": "Imagem removida com sucesso", "Image removed successfully": "Imagem removida com sucesso",
"API key": "Chave API", "API key": "Chave API",
"API key created successfully": "Chave API criada com sucesso",
"API keys": "Chaves API", "API keys": "Chaves API",
"API management": "Gestão de API", "API management": "Gestão de API",
"Are you sure you want to revoke this API key": "Tem certeza de que deseja revogar esta chave API",
"Create API Key": "Criar Chave API",
"Custom expiration date": "Data de expiração personalizada", "Custom expiration date": "Data de expiração personalizada",
"Enter a descriptive token name": "Insira um nome descritivo para o token", "Enter a descriptive token name": "Insira um nome descritivo para o token",
"Expiration": "Expiração", "Expiration": "Expiração",
"Expired": "Expirado", "Expired": "Expirado",
"Expires": "Expira", "Expires": "Expira",
"I've saved my API key": "Salvei minha chave API",
"Last use": "Último uso", "Last use": "Último uso",
"No API keys found": "Nenhuma chave API encontrada", "No API keys found": "Nenhuma chave API encontrada",
"No expiration": "Sem expiração", "No expiration": "Sem expiração",
"Revoke API key": "Revogar chave API",
"Revoked successfully": "Revogada com sucesso", "Revoked successfully": "Revogada com sucesso",
"Select expiration date": "Selecionar data de expiração", "Select expiration date": "Selecionar data de expiração",
"This action cannot be undone. Any applications using this API key will stop working.": "Esta ação não pode ser desfeita. Qualquer aplicação usando esta chave API deixará de funcionar.", "This action cannot be undone. Any applications using this API key will stop working.": "Esta ação não pode ser desfeita. Qualquer aplicação usando esta chave API deixará de funcionar.",
"Update API key": "Atualizar chave API", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "Gerenciar chaves API para todos os usuários no espaço de trabalho", "Manage API keys for all users in the workspace": "Gerenciar chaves API para todos os usuários no espaço de trabalho",
"Restrict API key creation to admins": "Restringir a criação de chave de API aos administradores", "Restrict API key creation to admins": "Restringir a criação de chave de API aos administradores",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Somente administradores e proprietários podem criar novas chaves de API. As chaves de membros já existentes continuarão funcionando.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Somente administradores e proprietários podem criar novas chaves de API. As chaves de membros já existentes continuarão funcionando.",
@@ -878,5 +876,29 @@
"Try a different search term.": "Tente um termo de pesquisa diferente.", "Try a different search term.": "Tente um termo de pesquisa diferente.",
"Try again": "Tentar novamente", "Try again": "Tentar novamente",
"Untitled chat": "Chat sem título", "Untitled chat": "Chat sem título",
"What can I help you with?": "Com o que posso ajudar você?" "What can I help you with?": "Com o que posso ajudar você?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "Редактировать комментарий", "Edit comment": "Редактировать комментарий",
"Delete comment": "Удалить комментарий", "Delete comment": "Удалить комментарий",
"Are you sure you want to delete this comment?": "Вы уверены, что хотите удалить этот комментарий?", "Are you sure you want to delete this comment?": "Вы уверены, что хотите удалить этот комментарий?",
"Delete chat": "Удалить чат",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Вы уверены, что хотите удалить '{{title}}'? Это действие нельзя отменить.",
"Comment created successfully": "Комментарий успешно создан", "Comment created successfully": "Комментарий успешно создан",
"Error creating comment": "Ошибка при создании комментария", "Error creating comment": "Ошибка при создании комментария",
"Comment updated successfully": "Комментарий успешно обновлён", "Comment updated successfully": "Комментарий успешно обновлён",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "Изображение превышает предел 10MB.", "Image exceeds 10MB limit.": "Изображение превышает предел 10MB.",
"Image removed successfully": "Изображение успешно удалено", "Image removed successfully": "Изображение успешно удалено",
"API key": "API ключ", "API key": "API ключ",
"API key created successfully": "API ключ успешно создан",
"API keys": "API ключи", "API keys": "API ключи",
"API management": "Управление API", "API management": "Управление API",
"Are you sure you want to revoke this API key": "Вы уверены, что хотите отозвать этот API ключ",
"Create API Key": "Создать API ключ",
"Custom expiration date": "Пользовательская дата срока действия", "Custom expiration date": "Пользовательская дата срока действия",
"Enter a descriptive token name": "Введите понятное имя токена", "Enter a descriptive token name": "Введите понятное имя токена",
"Expiration": "Срок действия", "Expiration": "Срок действия",
"Expired": "Истек", "Expired": "Истек",
"Expires": "Истекает", "Expires": "Истекает",
"I've saved my API key": "Я сохранил мой API ключ",
"Last use": "Последнее использование", "Last use": "Последнее использование",
"No API keys found": "API ключи не найдены", "No API keys found": "API ключи не найдены",
"No expiration": "Не истекает", "No expiration": "Не истекает",
"Revoke API key": "Отозвать API ключ",
"Revoked successfully": "Отозван успешно", "Revoked successfully": "Отозван успешно",
"Select expiration date": "Выберете срок действия", "Select expiration date": "Выберете срок действия",
"This action cannot be undone. Any applications using this API key will stop working.": "Это действие необратимо. Любые приложения, использующие этот API ключ, перестанут работать.", "This action cannot be undone. Any applications using this API key will stop working.": "Это действие необратимо. Любые приложения, использующие этот API ключ, перестанут работать.",
"Update API key": "Обновить API ключ", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "Управлять API ключами для всех пользователей в рабочей области", "Manage API keys for all users in the workspace": "Управлять API ключами для всех пользователей в рабочей области",
"Restrict API key creation to admins": "Ограничить создание API-ключей только администраторами.", "Restrict API key creation to admins": "Ограничить создание API-ключей только администраторами.",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Только администраторы и владельцы могут создавать новые API-ключи. Существующие ключи участников продолжат работать.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Только администраторы и владельцы могут создавать новые API-ключи. Существующие ключи участников продолжат работать.",
@@ -878,5 +876,29 @@
"Try a different search term.": "Попробуйте другой поисковый запрос.", "Try a different search term.": "Попробуйте другой поисковый запрос.",
"Try again": "Попробовать снова", "Try again": "Попробовать снова",
"Untitled chat": "Чат без названия", "Untitled chat": "Чат без названия",
"What can I help you with?": "Чем я могу вам помочь?" "What can I help you with?": "Чем я могу вам помочь?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "Редагувати коментар", "Edit comment": "Редагувати коментар",
"Delete comment": "Видалити коментар", "Delete comment": "Видалити коментар",
"Are you sure you want to delete this comment?": "Ви впевнені, що хочете видалити цей коментар?", "Are you sure you want to delete this comment?": "Ви впевнені, що хочете видалити цей коментар?",
"Delete chat": "Видалити чат",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "Ви впевнені, що хочете видалити '{{title}}'? Цю дію неможливо скасувати.",
"Comment created successfully": "Коментар успішно створено", "Comment created successfully": "Коментар успішно створено",
"Error creating comment": "Помилка при створенні коментаря", "Error creating comment": "Помилка при створенні коментаря",
"Comment updated successfully": "Коментар успішно оновлено", "Comment updated successfully": "Коментар успішно оновлено",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "Зображення має займати менше, ніж 10 МБ.", "Image exceeds 10MB limit.": "Зображення має займати менше, ніж 10 МБ.",
"Image removed successfully": "Зображення видалено", "Image removed successfully": "Зображення видалено",
"API key": "Ключ API", "API key": "Ключ API",
"API key created successfully": "Ключ API успішно створено",
"API keys": "Ключі API", "API keys": "Ключі API",
"API management": "Управління API", "API management": "Управління API",
"Are you sure you want to revoke this API key": "Ви впевнені, що хочете відкликати цей ключ API",
"Create API Key": "Створити ключ API",
"Custom expiration date": "Користувацька дата закінчення", "Custom expiration date": "Користувацька дата закінчення",
"Enter a descriptive token name": "Введіть описову назву токена", "Enter a descriptive token name": "Введіть описову назву токена",
"Expiration": "Термін дії", "Expiration": "Термін дії",
"Expired": "Закінчився", "Expired": "Закінчився",
"Expires": "Закінчується", "Expires": "Закінчується",
"I've saved my API key": "Я зберіг свій ключ API",
"Last use": "Останнє використання", "Last use": "Останнє використання",
"No API keys found": "Ключі API не знайдено", "No API keys found": "Ключі API не знайдено",
"No expiration": "Без терміну дії", "No expiration": "Без терміну дії",
"Revoke API key": "Відкликати ключ API",
"Revoked successfully": "Успішно відкликано", "Revoked successfully": "Успішно відкликано",
"Select expiration date": "Виберіть дату закінчення", "Select expiration date": "Виберіть дату закінчення",
"This action cannot be undone. Any applications using this API key will stop working.": "Цю дію не можна скасувати. Будь-які додатки, що використовують цей ключ API, перестануть працювати.", "This action cannot be undone. Any applications using this API key will stop working.": "Цю дію не можна скасувати. Будь-які додатки, що використовують цей ключ API, перестануть працювати.",
"Update API key": "Оновити ключ API", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "Керувати ключами API для всіх користувачів у робочій області", "Manage API keys for all users in the workspace": "Керувати ключами API для всіх користувачів у робочій області",
"Restrict API key creation to admins": "Обмежити створення API-ключів лише для адміністраторів", "Restrict API key creation to admins": "Обмежити створення API-ключів лише для адміністраторів",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Тільки адміністратори та власники можуть створювати нові API-ключі. Існуючі ключі учасників і надалі працюватимуть.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Тільки адміністратори та власники можуть створювати нові API-ключі. Існуючі ключі учасників і надалі працюватимуть.",
@@ -878,5 +876,29 @@
"Try a different search term.": "Спробуйте інший пошуковий запит.", "Try a different search term.": "Спробуйте інший пошуковий запит.",
"Try again": "Спробувати ще раз", "Try again": "Спробувати ще раз",
"Untitled chat": "Чат без назви", "Untitled chat": "Чат без назви",
"What can I help you with?": "Чим я можу вам допомогти?" "What can I help you with?": "Чим я можу вам допомогти?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
@@ -222,6 +222,8 @@
"Edit comment": "编辑评论", "Edit comment": "编辑评论",
"Delete comment": "删除评论", "Delete comment": "删除评论",
"Are you sure you want to delete this comment?": "你确定要删除这条评论吗?", "Are you sure you want to delete this comment?": "你确定要删除这条评论吗?",
"Delete chat": "删除聊天",
"Are you sure you want to delete '{{title}}'? This action cannot be undone.": "您确定要删除「{{title}}」吗?此操作无法撤销。",
"Comment created successfully": "成功创建评论", "Comment created successfully": "成功创建评论",
"Error creating comment": "创建评论时出错", "Error creating comment": "创建评论时出错",
"Comment updated successfully": "评论更新成功", "Comment updated successfully": "评论更新成功",
@@ -606,25 +608,21 @@
"Image exceeds 10MB limit.": "图片超过10MB限制。", "Image exceeds 10MB limit.": "图片超过10MB限制。",
"Image removed successfully": "图片删除成功", "Image removed successfully": "图片删除成功",
"API key": "API密钥", "API key": "API密钥",
"API key created successfully": "API密钥创建成功",
"API keys": "API密钥", "API keys": "API密钥",
"API management": "API管理", "API management": "API管理",
"Are you sure you want to revoke this API key": "确定要撤销此API密钥吗",
"Create API Key": "创建API密钥",
"Custom expiration date": "自定义到期日期", "Custom expiration date": "自定义到期日期",
"Enter a descriptive token name": "输入描述性令牌名称", "Enter a descriptive token name": "输入描述性令牌名称",
"Expiration": "到期", "Expiration": "到期",
"Expired": "已过期", "Expired": "已过期",
"Expires": "到期", "Expires": "到期",
"I've saved my API key": "我已保存我的API密钥",
"Last use": "上次使用", "Last use": "上次使用",
"No API keys found": "找不到API密钥", "No API keys found": "找不到API密钥",
"No expiration": "无到期", "No expiration": "无到期",
"Revoke API key": "撤销API密钥",
"Revoked successfully": "撤销成功", "Revoked successfully": "撤销成功",
"Select expiration date": "选择到期日期", "Select expiration date": "选择到期日期",
"This action cannot be undone. Any applications using this API key will stop working.": "此操作无法撤销。使用此API密钥的任何应用程序将停止工作。", "This action cannot be undone. Any applications using this API key will stop working.": "此操作无法撤销。使用此API密钥的任何应用程序将停止工作。",
"Update API key": "更新API密钥", "Update": "Update",
"Update {{credential}}": "Update {{credential}}",
"Manage API keys for all users in the workspace": "管理工作空间中所有用户的API密钥", "Manage API keys for all users in the workspace": "管理工作空间中所有用户的API密钥",
"Restrict API key creation to admins": "仅限管理员创建 API 密钥。", "Restrict API key creation to admins": "仅限管理员创建 API 密钥。",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "只有管理员和所有者可以创建新的 API 密钥。现有成员密钥将继续有效。", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "只有管理员和所有者可以创建新的 API 密钥。现有成员密钥将继续有效。",
@@ -878,5 +876,29 @@
"Try a different search term.": "请尝试其他搜索词。", "Try a different search term.": "请尝试其他搜索词。",
"Try again": "重试", "Try again": "重试",
"Untitled chat": "未命名聊天", "Untitled chat": "未命名聊天",
"What can I help you with?": "我能帮您做什么?" "What can I help you with?": "我能帮您做什么?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Automatically provision users and groups from your identity provider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure your identity provider with this URL to provision users and groups.",
"Create {{credential}}": "Create {{credential}}",
"{{credential}} created": "{{credential}} created",
"{{credential}} created successfully": "{{credential}} created successfully",
"Created by": "Created by",
"Custom": "Custom",
"Enable SCIM": "Enable SCIM",
"Enter a descriptive name": "Enter a descriptive name",
"I've saved my {{credential}}": "I've saved my {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Make sure to copy your {{credential}} now. You won't be able to see it again!",
"Never": "Never",
"Revoke {{credential}}": "Revoke {{credential}}",
"SCIM endpoint URL": "SCIM endpoint URL",
"SCIM provisioning": "SCIM provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM takes precedence over SSO group sync while enabled.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.",
"SCIM token": "SCIM token",
"SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token"
} }
+2
View File
@@ -26,6 +26,7 @@ import Security from "@/ee/security/pages/security.tsx";
import License from "@/ee/licence/pages/license.tsx"; import License from "@/ee/licence/pages/license.tsx";
import { useRedirectToCloudSelect } from "@/ee/hooks/use-redirect-to-cloud-select.tsx"; import { useRedirectToCloudSelect } from "@/ee/hooks/use-redirect-to-cloud-select.tsx";
import SharedPage from "@/pages/share/shared-page.tsx"; import SharedPage from "@/pages/share/shared-page.tsx";
import PdfRenderPage from "@/ee/pdf-export/pdf-render-page.tsx";
import Shares from "@/pages/settings/shares/shares.tsx"; import Shares from "@/pages/settings/shares/shares.tsx";
import ShareLayout from "@/features/share/components/share-layout.tsx"; import ShareLayout from "@/features/share/components/share-layout.tsx";
import ShareRedirect from "@/pages/share/share-redirect.tsx"; import ShareRedirect from "@/pages/share/share-redirect.tsx";
@@ -81,6 +82,7 @@ export default function App() {
<Route path={"/share/p/:pageSlug"} element={<SharedPage />} /> <Route path={"/share/p/:pageSlug"} element={<SharedPage />} />
</Route> </Route>
<Route path={"/pdf-render/:pageId"} element={<PdfRenderPage />} />
<Route path={"/share/:shareId"} element={<ShareRedirect />} /> <Route path={"/share/:shareId"} element={<ShareRedirect />} />
<Route path={"/p/:pageSlug"} element={<PageRedirect />} /> <Route path={"/p/:pageSlug"} element={<PageRedirect />} />
@@ -9,7 +9,7 @@ import classes from "../styles/chat-sidebar.module.css";
type Props = { type Props = {
chat: AiChat; chat: AiChat;
isActive: boolean; isActive: boolean;
onDelete: (chatId: string) => void; onDelete: (chatId: string, title: string | null) => void;
onRename: (chatId: string, title: string) => void; onRename: (chatId: string, title: string) => void;
}; };
@@ -153,7 +153,7 @@ export default function AiChatSidebarItem({
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
onDelete(chat.id); onDelete(chat.id, chat.title);
}} }}
> >
{t("Delete")} {t("Delete")}
@@ -1,6 +1,14 @@
import { useState, useCallback, useEffect, useMemo, useRef } from "react"; import { useState, useCallback, useEffect, useMemo, useRef } from "react";
import { Link, useNavigate, useParams } from "react-router-dom"; import { Link, useNavigate, useParams } from "react-router-dom";
import { ActionIcon, Center, TextInput, Loader, Tooltip } from "@mantine/core"; import {
ActionIcon,
Center,
Text,
TextInput,
Loader,
Tooltip,
} from "@mantine/core";
import { modals } from "@mantine/modals";
import { useDebouncedValue } from "@mantine/hooks"; import { useDebouncedValue } from "@mantine/hooks";
import { IconPlus, IconSearch, IconMessageCircle2 } from "@tabler/icons-react"; import { IconPlus, IconSearch, IconMessageCircle2 } from "@tabler/icons-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -73,16 +81,31 @@ export default function AiChatSidebar() {
); );
const handleDelete = useCallback( const handleDelete = useCallback(
(id: string) => { (id: string, title: string | null) => {
deleteMutation.mutate(id, { modals.openConfirmModal({
onSuccess: () => { title: t("Delete chat"),
if (chatId === id) { centered: true,
navigate("/ai"); children: (
} <Text size="sm">
{t("Are you sure you want to delete '{{title}}'? This action cannot be undone.", {
title: title || t("Untitled"),
})}
</Text>
),
labels: { confirm: t("Delete"), cancel: t("Cancel") },
confirmProps: { color: "red" },
onConfirm: () => {
deleteMutation.mutate(id, {
onSuccess: () => {
if (chatId === id) {
navigate("/ai");
}
},
});
}, },
}); });
}, },
[deleteMutation, chatId, navigate], [deleteMutation, chatId, navigate, t],
); );
const handleRename = useCallback( const handleRename = useCallback(
@@ -0,0 +1,64 @@
import "@/features/editor/styles/index.css";
import { useEffect, useState } from "react";
import { useParams, useSearchParams } from "react-router-dom";
import ReadonlyPageEditor from "@/features/editor/readonly-page-editor";
import { Container } from "@mantine/core";
type PdfRenderData = {
pageId: string;
title: string;
content: any;
};
export default function PdfRenderPage() {
const { pageId } = useParams<{ pageId: string }>();
const [searchParams] = useSearchParams();
const token = searchParams.get("token");
const [data, setData] = useState<PdfRenderData | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!pageId || !token) {
setError("Missing page ID or token");
return;
}
fetch('/api/pdf-export/render', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pageId, token }),
})
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then((result) => setData(result.data))
.catch((err) => setError(err.message));
}, [pageId, token]);
useEffect(() => {
if (data?.title) {
document.title = data.title;
}
}, [data?.title]);
if (error) {
return <div>{error}</div>;
}
if (!data) {
return null;
}
return (
<Container size={900} p={0}>
<ReadonlyPageEditor
key={data.pageId}
title={data.title}
content={data.content}
pageId={data.pageId}
/>
</Container>
);
}
@@ -14,11 +14,11 @@ import {
} from "../services/favorite-service"; } from "../services/favorite-service";
import { FavoriteType } from "../types/favorite.types"; import { FavoriteType } from "../types/favorite.types";
export function useFavoritesQuery(type?: FavoriteType) { export function useFavoritesQuery(type?: FavoriteType, spaceId?: string) {
return useInfiniteQuery({ return useInfiniteQuery({
queryKey: ["favorites", type], queryKey: ["favorites", type, spaceId],
queryFn: ({ pageParam }) => queryFn: ({ pageParam }) =>
getFavorites({ type, cursor: pageParam, limit: 15 }), getFavorites({ type, spaceId, cursor: pageParam, limit: 15 }),
initialPageParam: undefined as string | undefined, initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => getNextPageParam: (lastPage) =>
lastPage.meta.hasNextPage ? lastPage.meta.nextCursor : undefined, lastPage.meta.hasNextPage ? lastPage.meta.nextCursor : undefined,
@@ -26,10 +26,10 @@ export function useFavoritesQuery(type?: FavoriteType) {
}); });
} }
export function useFavoriteIds(type: FavoriteType): Set<string> { export function useFavoriteIds(type: FavoriteType, spaceId?: string): Set<string> {
const { data } = useQuery({ const { data } = useQuery({
queryKey: ["favorite-ids", type], queryKey: ["favorite-ids", type, spaceId],
queryFn: () => getFavoriteIds(type), queryFn: () => getFavoriteIds(type, spaceId),
refetchOnMount: true, refetchOnMount: true,
}); });
@@ -52,9 +52,9 @@ export function useAddFavoriteMutation() {
onSuccess: (_result, variables) => { onSuccess: (_result, variables) => {
const entityId = getEntityId(variables); const entityId = getEntityId(variables);
if (entityId) { if (entityId) {
queryClient.setQueryData( queryClient.setQueriesData<{ items: string[]; meta: any }>(
["favorite-ids", variables.type], { queryKey: ["favorite-ids", variables.type] },
(old: { items: string[]; meta: any } | undefined) => { (old) => {
if (!old) return old; if (!old) return old;
if (old.items.includes(entityId)) return old; if (old.items.includes(entityId)) return old;
return { ...old, items: [...old.items, entityId] }; return { ...old, items: [...old.items, entityId] };
@@ -76,9 +76,9 @@ export function useRemoveFavoriteMutation() {
onSuccess: (_result, variables) => { onSuccess: (_result, variables) => {
const entityId = getEntityId(variables); const entityId = getEntityId(variables);
if (entityId) { if (entityId) {
queryClient.setQueryData( queryClient.setQueriesData<{ items: string[]; meta: any }>(
["favorite-ids", variables.type], { queryKey: ["favorite-ids", variables.type] },
(old: { items: string[]; meta: any } | undefined) => { (old) => {
if (!old) return old; if (!old) return old;
return { ...old, items: old.items.filter((id) => id !== entityId) }; return { ...old, items: old.items.filter((id) => id !== entityId) };
}, },
@@ -21,13 +21,14 @@ export async function removeFavorite(
await api.post("/favorites/remove", params); await api.post("/favorites/remove", params);
} }
export async function getFavoriteIds(type: FavoriteType): Promise<IPagination<string>> { export async function getFavoriteIds(type: FavoriteType, spaceId?: string): Promise<IPagination<string>> {
const req = await api.post<IPagination<string>>("/favorites/ids", { type }); const req = await api.post<IPagination<string>>("/favorites/ids", { type, spaceId });
return req.data; return req.data;
} }
export async function getFavorites(params?: { export async function getFavorites(params?: {
type?: FavoriteType; type?: FavoriteType;
spaceId?: string;
limit?: number; limit?: number;
cursor?: string; cursor?: string;
}): Promise<IPagination<IFavorite>> { }): Promise<IPagination<IFavorite>> {
@@ -18,7 +18,11 @@ import { getSpaceUrl } from "@/lib/config";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getInitialsColor } from "@/lib/get-initials-color"; import { getInitialsColor } from "@/lib/get-initials-color";
export default function FavoritesPages() { interface Props {
spaceId?: string;
}
export default function FavoritesPages({ spaceId }: Props) {
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
data, data,
@@ -27,7 +31,7 @@ export default function FavoritesPages() {
hasNextPage, hasNextPage,
fetchNextPage, fetchNextPage,
isFetchingNextPage, isFetchingNextPage,
} = useFavoritesQuery("page"); } = useFavoritesQuery("page", spaceId);
const favorites = data?.pages.flatMap((p) => p.items) ?? []; const favorites = data?.pages.flatMap((p) => p.items) ?? [];
@@ -72,19 +76,21 @@ export default function FavoritesPages() {
</Group> </Group>
</UnstyledButton> </UnstyledButton>
</Table.Td> </Table.Td>
<Table.Td> {!spaceId && (
{fav.space && ( <Table.Td>
<Badge {fav.space && (
color={getInitialsColor(fav.space.name)} <Badge
variant="light" color={getInitialsColor(fav.space.name)}
component={Link} variant="light"
to={getSpaceUrl(fav.space.slug)} component={Link}
style={{ cursor: "pointer" }} to={getSpaceUrl(fav.space.slug)}
> style={{ cursor: "pointer" }}
{fav.space.name} >
</Badge> {fav.space.name}
)} </Badge>
</Table.Td> )}
</Table.Td>
)}
<Table.Td> <Table.Td>
<Text <Text
c="dimmed" c="dimmed"
@@ -145,7 +145,7 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
] = useDisclosure(false); ] = useDisclosure(false);
const [pageEditor] = useAtom(pageEditorAtom); const [pageEditor] = useAtom(pageEditorAtom);
const pageUpdatedAt = useTimeAgo(page?.updatedAt); const pageUpdatedAt = useTimeAgo(page?.updatedAt);
const favoriteIds = useFavoriteIds("page"); const favoriteIds = useFavoriteIds("page", page?.spaceId);
const addFavoriteMutation = useAddFavoriteMutation(); const addFavoriteMutation = useAddFavoriteMutation();
const removeFavoriteMutation = useRemoveFavoriteMutation(); const removeFavoriteMutation = useRemoveFavoriteMutation();
const isFavorited = page?.id ? favoriteIds.has(page.id) : false; const isFavorited = page?.id ? favoriteIds.has(page.id) : false;
@@ -509,7 +509,7 @@ function NodeMenu({ node, treeApi, spaceId }: NodeMenuProps) {
copyPageModalOpened, copyPageModalOpened,
{ open: openCopyPageModal, close: closeCopySpaceModal }, { open: openCopyPageModal, close: closeCopySpaceModal },
] = useDisclosure(false); ] = useDisclosure(false);
const favoriteIds = useFavoriteIds("page"); const favoriteIds = useFavoriteIds("page", spaceId);
const addFavorite = useAddFavoriteMutation(); const addFavorite = useAddFavoriteMutation();
const removeFavorite = useRemoveFavoriteMutation(); const removeFavorite = useRemoveFavoriteMutation();
const isFavorited = favoriteIds.has(node.data.id); const isFavorited = favoriteIds.has(node.data.id);
@@ -47,7 +47,7 @@ export default function SpaceHomeTabs() {
{space?.id && <RecentChanges spaceId={space.id} />} {space?.id && <RecentChanges spaceId={space.id} />}
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="favorites"> <Tabs.Panel value="favorites">
<FavoritesPages /> {space?.id && <FavoritesPages spaceId={space.id} />}
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="created"> <Tabs.Panel value="created">
{space?.id && <CreatedByMe spaceId={space.id} />} {space?.id && <CreatedByMe spaceId={space.id} />}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "server", "name": "server",
"version": "0.71.1", "version": "0.80.0",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
+1
View File
@@ -18,6 +18,7 @@ export const Feature = {
SHARING_CONTROLS: 'sharing:controls', SHARING_CONTROLS: 'sharing:controls',
VIEWER_COMMENTS: 'comment:viewer', VIEWER_COMMENTS: 'comment:viewer',
TEMPLATES: 'templates', TEMPLATES: 'templates',
PDF_EXPORT: 'export:pdf',
} as const; } as const;
export type FeatureKey = (typeof Feature)[keyof typeof Feature]; export type FeatureKey = (typeof Feature)[keyof typeof Feature];
@@ -5,6 +5,8 @@ export enum JwtType {
ATTACHMENT = 'attachment', ATTACHMENT = 'attachment',
MFA_TOKEN = 'mfa_token', MFA_TOKEN = 'mfa_token',
API_KEY = 'api_key', API_KEY = 'api_key',
PDF_RENDER = 'pdf_render',
PDF_EXPORT_DOWNLOAD = 'pdf_export_download',
} }
export type JwtPayload = { export type JwtPayload = {
sub: string; sub: string;
@@ -45,3 +47,15 @@ export type JwtApiKeyPayload = {
apiKeyId: string; apiKeyId: string;
type: 'api_key'; type: 'api_key';
}; };
export type JwtPdfRenderPayload = {
pageId: string;
workspaceId: string;
type: 'pdf_render';
};
export type JwtPdfExportDownloadPayload = {
fileTaskId: string;
workspaceId: string;
type: 'pdf_export_download';
};
@@ -13,6 +13,8 @@ import {
JwtExchangePayload, JwtExchangePayload,
JwtMfaTokenPayload, JwtMfaTokenPayload,
JwtPayload, JwtPayload,
JwtPdfExportDownloadPayload,
JwtPdfRenderPayload,
JwtType, JwtType,
} from '../dto/jwt-payload'; } from '../dto/jwt-payload';
import { User } from '@docmost/db/types/entity.types'; import { User } from '@docmost/db/types/entity.types';
@@ -115,6 +117,30 @@ export class TokenService {
return this.jwtService.sign(payload, expiresIn ? { expiresIn } : {}); return this.jwtService.sign(payload, expiresIn ? { expiresIn } : {});
} }
async generatePdfRenderToken(
pageId: string,
workspaceId: string,
): Promise<string> {
const payload: JwtPdfRenderPayload = {
pageId,
workspaceId,
type: JwtType.PDF_RENDER,
};
return this.jwtService.sign(payload, { expiresIn: '60s' });
}
async generatePdfExportDownloadToken(
fileTaskId: string,
workspaceId: string,
): Promise<string> {
const payload: JwtPdfExportDownloadPayload = {
fileTaskId,
workspaceId,
type: JwtType.PDF_EXPORT_DOWNLOAD,
};
return this.jwtService.sign(payload, { expiresIn: '1h' });
}
async verifyJwt(token: string, tokenType: string) { async verifyJwt(token: string, tokenType: string) {
const payload = await this.jwtService.verifyAsync(token, { const payload = await this.jwtService.verifyAsync(token, {
secret: this.environmentService.getAppSecret(), secret: this.environmentService.getAppSecret(),
@@ -1,8 +1,12 @@
import { IsIn, IsNotEmpty, IsString } from 'class-validator'; import { IsIn, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
export class FavoriteIdsDto { export class FavoriteIdsDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@IsIn(['page', 'space', 'template']) @IsIn(['page', 'space', 'template'])
type: 'page' | 'space' | 'template'; type: 'page' | 'space' | 'template';
@IsOptional()
@IsUUID()
spaceId?: string;
} }
@@ -1,8 +1,12 @@
import { IsIn, IsOptional, IsString } from 'class-validator'; import { IsIn, IsOptional, IsString, IsUUID } from 'class-validator';
export class ListFavoritesDto { export class ListFavoritesDto {
@IsOptional() @IsOptional()
@IsString() @IsString()
@IsIn(['page', 'space', 'template']) @IsIn(['page', 'space', 'template'])
type?: 'page' | 'space' | 'template'; type?: 'page' | 'space' | 'template';
@IsOptional()
@IsUUID()
spaceId?: string;
} }
@@ -82,6 +82,7 @@ export class FavoriteController {
user.id, user.id,
workspace.id, workspace.id,
dto.type as FavoriteType, dto.type as FavoriteType,
dto.spaceId,
); );
} }
@@ -98,6 +99,7 @@ export class FavoriteController {
workspace.id, workspace.id,
pagination, pagination,
dto.type as FavoriteType | undefined, dto.type as FavoriteType | undefined,
dto.spaceId,
); );
} }
@@ -20,11 +20,13 @@ export class FavoriteService {
userId: string, userId: string,
workspaceId: string, workspaceId: string,
type: FavoriteType, type: FavoriteType,
spaceId?: string,
) { ) {
const result = await this.favoriteRepo.getFavoriteIds( const result = await this.favoriteRepo.getFavoriteIds(
userId, userId,
workspaceId, workspaceId,
type, type,
spaceId,
); );
if (result.items.length === 0) { if (result.items.length === 0) {
@@ -95,12 +97,14 @@ export class FavoriteService {
workspaceId: string, workspaceId: string,
pagination: PaginationOptions, pagination: PaginationOptions,
type?: FavoriteType, type?: FavoriteType,
spaceId?: string,
) { ) {
const result = await this.favoriteRepo.findUserFavorites( const result = await this.favoriteRepo.findUserFavorites(
userId, userId,
workspaceId, workspaceId,
pagination, pagination,
type, type,
spaceId,
); );
if (result.items.length === 0) { if (result.items.length === 0) {
@@ -0,0 +1,32 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.alterTable('file_tasks')
.addColumn('page_id', 'uuid', (col) =>
col.references('pages.id').onDelete('set null').ifNotExists(),
)
.execute();
await db.schema
.alterTable('file_tasks')
.addColumn('metadata', 'jsonb', (col) => col.ifNotExists())
.execute();
await db.schema
.createIndex('idx_file_tasks_page_export')
.ifNotExists()
.on('file_tasks')
.columns(['page_id', 'workspace_id'])
.where(sql.ref('type'), '=', 'export')
.where(sql.ref('deleted_at'), 'is', null)
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropIndex('idx_file_tasks_page_export').execute();
await db.schema.alterTable('file_tasks').dropColumn('page_id').execute();
await db.schema.alterTable('file_tasks').dropColumn('metadata').execute();
}
@@ -5,7 +5,7 @@ import { InsertableFavorite, Favorite } from '@docmost/db/types/entity.types';
import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination';
import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { ExpressionBuilder, sql } from 'kysely'; import { ExpressionBuilder, SelectQueryBuilder, sql } from 'kysely';
import { DB } from '@docmost/db/types/db'; import { DB } from '@docmost/db/types/db';
import { dbOrTx } from '@docmost/db/utils'; import { dbOrTx } from '@docmost/db/utils';
@@ -66,6 +66,7 @@ export class FavoriteRepo {
userId: string, userId: string,
workspaceId: string, workspaceId: string,
type: FavoriteType, type: FavoriteType,
spaceId?: string,
): Promise<{ items: string[]; meta: any }> { ): Promise<{ items: string[]; meta: any }> {
const idColumn = const idColumn =
type === FavoriteType.PAGE type === FavoriteType.PAGE
@@ -74,12 +75,16 @@ export class FavoriteRepo {
? 'spaceId' ? 'spaceId'
: 'templateId'; : 'templateId';
const query = this.db let query = this.db
.selectFrom('favorites') .selectFrom('favorites')
.select(['favorites.id', `favorites.${idColumn} as entityId`]) .select(['favorites.id', `favorites.${idColumn} as entityId`])
.where('userId', '=', userId) .where('favorites.userId', '=', userId)
.where('workspaceId', '=', workspaceId) .where('favorites.workspaceId', '=', workspaceId)
.where('type', '=', type); .where('favorites.type', '=', type);
if (spaceId) {
query = this.applySpaceFilter(query, type, spaceId);
}
const result = await executeWithCursorPagination(query, { const result = await executeWithCursorPagination(query, {
perPage: 250, perPage: 250,
@@ -100,6 +105,7 @@ export class FavoriteRepo {
workspaceId: string, workspaceId: string,
pagination: PaginationOptions, pagination: PaginationOptions,
type?: FavoriteType, type?: FavoriteType,
spaceId?: string,
) { ) {
let query = this.db let query = this.db
.selectFrom('favorites') .selectFrom('favorites')
@@ -111,6 +117,10 @@ export class FavoriteRepo {
query = query.where('favorites.type', '=', type); query = query.where('favorites.type', '=', type);
} }
if (spaceId) {
query = this.applySpaceFilter(query, type, spaceId);
}
if (type === FavoriteType.PAGE || !type) { if (type === FavoriteType.PAGE || !type) {
query = query.select((eb) => this.withPage(eb)); query = query.select((eb) => this.withPage(eb));
} }
@@ -184,6 +194,39 @@ export class FavoriteRepo {
.execute(); .execute();
} }
private applySpaceFilter<Q extends SelectQueryBuilder<any, any, any>>(
query: Q,
type: FavoriteType | undefined,
spaceId: string,
): Q {
if (type === FavoriteType.PAGE) {
return query.where((eb: any) =>
eb.exists(
eb
.selectFrom('pages')
.select(sql`1`.as('one'))
.whereRef('pages.id', '=', 'favorites.pageId')
.where('pages.spaceId', '=', spaceId),
),
) as Q;
}
if (type === FavoriteType.SPACE) {
return query.where('favorites.spaceId' as any, '=', spaceId) as Q;
}
if (type === FavoriteType.TEMPLATE) {
return query.where((eb: any) =>
eb.exists(
eb
.selectFrom('templates')
.select(sql`1`.as('one'))
.whereRef('templates.id', '=', 'favorites.templateId')
.where('templates.spaceId', '=', spaceId),
),
) as Q;
}
return query;
}
private withPage(eb: ExpressionBuilder<DB, 'favorites'>) { private withPage(eb: ExpressionBuilder<DB, 'favorites'>) {
return jsonObjectFrom( return jsonObjectFrom(
eb eb
+2
View File
@@ -196,6 +196,8 @@ export interface FileTasks {
filePath: string; filePath: string;
fileSize: Int8 | null; fileSize: Int8 | null;
id: Generated<string>; id: Generated<string>;
metadata: Json | null;
pageId: string | null;
source: string | null; source: string | null;
spaceId: string | null; spaceId: string | null;
status: string | null; status: string | null;
@@ -75,6 +75,10 @@ export class EnvironmentService {
return new Date(Date.now() + msUntilExpiry); return new Date(Date.now() + msUntilExpiry);
} }
getGotenbergUrl(): string | undefined {
return this.configService.get<string>('GOTENBERG_URL');
}
getStorageDriver(): string { getStorageDriver(): string {
return this.configService.get<string>('STORAGE_DRIVER', 'local'); return this.configService.get<string>('STORAGE_DRIVER', 'local');
} }
@@ -5,6 +5,9 @@ import { QueueJob, QueueName } from 'src/integrations/queue/constants';
import { FileImportTaskService } from '../services/file-import-task.service'; import { FileImportTaskService } from '../services/file-import-task.service';
import { FileTaskStatus } from '../utils/file.utils'; import { FileTaskStatus } from '../utils/file.utils';
import { StorageService } from '../../storage/storage.service'; import { StorageService } from '../../storage/storage.service';
import { ModuleRef } from '@nestjs/core';
import { InjectKysely } from 'nestjs-kysely';
import { KyselyDB } from '@docmost/db/types/kysely.types';
@Processor(QueueName.FILE_TASK_QUEUE) @Processor(QueueName.FILE_TASK_QUEUE)
export class FileTaskProcessor extends WorkerHost implements OnModuleDestroy { export class FileTaskProcessor extends WorkerHost implements OnModuleDestroy {
@@ -13,6 +16,8 @@ export class FileTaskProcessor extends WorkerHost implements OnModuleDestroy {
constructor( constructor(
private readonly fileTaskService: FileImportTaskService, private readonly fileTaskService: FileImportTaskService,
private readonly storageService: StorageService, private readonly storageService: StorageService,
private readonly moduleRef: ModuleRef,
@InjectKysely() private readonly db: KyselyDB,
) { ) {
super(); super();
} }
@@ -23,8 +28,11 @@ export class FileTaskProcessor extends WorkerHost implements OnModuleDestroy {
case QueueJob.IMPORT_TASK: case QueueJob.IMPORT_TASK:
await this.fileTaskService.processZIpImport(job.data.fileTaskId); await this.fileTaskService.processZIpImport(job.data.fileTaskId);
break; break;
case QueueJob.EXPORT_TASK: case QueueJob.PDF_EXPORT_TASK:
// TODO: export task await this.processExportTask(job.data.fileTaskId);
break;
case QueueJob.PDF_EXPORT_CLEANUP:
await this.processExportCleanup();
break; break;
} }
} catch (err) { } catch (err) {
@@ -33,6 +41,24 @@ export class FileTaskProcessor extends WorkerHost implements OnModuleDestroy {
} }
} }
private getPdfExportService() {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const PdfExportModule = require('./../../../ee/pdf-export/pdf-export.service');
return this.moduleRef.get(PdfExportModule.PdfExportService, {
strict: false,
});
}
private async processExportTask(fileTaskId: string): Promise<void> {
const pdfExportService = this.getPdfExportService();
await pdfExportService.generateAndStorePdf(fileTaskId);
}
private async processExportCleanup(): Promise<void> {
const pdfExportService = this.getPdfExportService();
await pdfExportService.cleanupExpiredExports();
}
@OnWorkerEvent('active') @OnWorkerEvent('active')
onActive(job: Job) { onActive(job: Job) {
this.logger.debug(`Processing ${job.name} job`); this.logger.debug(`Processing ${job.name} job`);
@@ -41,32 +67,39 @@ export class FileTaskProcessor extends WorkerHost implements OnModuleDestroy {
@OnWorkerEvent('failed') @OnWorkerEvent('failed')
async onFailed(job: Job) { async onFailed(job: Job) {
this.logger.error( this.logger.error(
`Error processing ${job.name} job. Import Task ID: ${job.data.fileTaskId}. Reason: ${job.failedReason}`, `Error processing ${job.name} job. File Task ID: ${job.data?.fileTaskId}. Reason: ${job.failedReason}`,
); );
await this.handleFailedJob(job); if (job.name === QueueJob.IMPORT_TASK) {
await this.handleFailedImportJob(job);
} else if (job.name === QueueJob.PDF_EXPORT_TASK) {
await this.handleFailedExportJob(job);
}
} }
@OnWorkerEvent('completed') @OnWorkerEvent('completed')
async onCompleted(job: Job) { async onCompleted(job: Job) {
this.logger.log( this.logger.log(
`Completed ${job.name} job for File task ID ${job.data.fileTaskId}`, `Completed ${job.name} job for File task ID ${job.data?.fileTaskId}`,
); );
try { if (job.name === QueueJob.IMPORT_TASK) {
const fileTask = await this.fileTaskService.getFileTask( try {
job.data.fileTaskId, const fileTask = await this.fileTaskService.getFileTask(
); job.data.fileTaskId,
if (fileTask) { );
await this.storageService.delete(fileTask.filePath); if (fileTask) {
this.logger.debug(`Deleted imported zip file: ${fileTask.filePath}`); await this.storageService.delete(fileTask.filePath);
this.logger.debug(`Deleted imported zip file: ${fileTask.filePath}`);
}
} catch (err) {
this.logger.error(`Failed to delete imported zip file:`, err);
} }
} catch (err) {
this.logger.error(`Failed to delete imported zip file:`, err);
} }
// Export tasks: do NOT delete the file on completion (kept for 24h cache)
} }
private async handleFailedJob(job: Job) { private async handleFailedImportJob(job: Job) {
try { try {
const fileTaskId = job.data.fileTaskId; const fileTaskId = job.data.fileTaskId;
const reason = job.failedReason || 'Unknown error'; const reason = job.failedReason || 'Unknown error';
@@ -86,6 +119,25 @@ export class FileTaskProcessor extends WorkerHost implements OnModuleDestroy {
} }
} }
private async handleFailedExportJob(job: Job) {
try {
const fileTaskId = job.data.fileTaskId;
const reason = job.failedReason || 'Unknown error';
await this.db
.updateTable('fileTasks')
.set({
status: FileTaskStatus.Failed,
errorMessage: reason,
updatedAt: new Date(),
})
.where('id', '=', fileTaskId)
.execute();
} catch (err) {
this.logger.error(err);
}
}
async onModuleDestroy(): Promise<void> { async onModuleDestroy(): Promise<void> {
if (this.worker) { if (this.worker) {
await this.worker.close(); await this.worker.close();
@@ -80,4 +80,7 @@ export enum QueueJob {
AUDIT_LOG = 'audit-log', AUDIT_LOG = 'audit-log',
AUDIT_CLEANUP = 'audit-cleanup', AUDIT_CLEANUP = 'audit-cleanup',
PDF_EXPORT_TASK = 'pdf-export-task',
PDF_EXPORT_CLEANUP = 'pdf-export-cleanup',
} }
+3 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "docmost", "name": "docmost",
"homepage": "https://docmost.com", "homepage": "https://docmost.com",
"version": "0.71.1", "version": "0.80.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "nx run-many -t build", "build": "nx run-many -t build",
@@ -131,7 +131,8 @@
"@xmldom/xmldom": "0.8.12", "@xmldom/xmldom": "0.8.12",
"handlebars": "4.7.9", "handlebars": "4.7.9",
"axios": "1.15.0", "axios": "1.15.0",
"langsmith": "0.5.18" "langsmith": "0.5.18",
"follow-redirects": "1.16.0"
}, },
"neverBuiltDependencies": [] "neverBuiltDependencies": []
} }
+5 -4
View File
@@ -39,6 +39,7 @@ overrides:
handlebars: 4.7.9 handlebars: 4.7.9
axios: 1.15.0 axios: 1.15.0
langsmith: 0.5.18 langsmith: 0.5.18
follow-redirects: 1.16.0
patchedDependencies: patchedDependencies:
react-arborist@3.4.0: react-arborist@3.4.0:
@@ -6996,8 +6997,8 @@ packages:
flatted@3.4.2: flatted@3.4.2:
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
follow-redirects@1.15.11: follow-redirects@1.16.0:
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
peerDependencies: peerDependencies:
debug: '*' debug: '*'
@@ -16273,7 +16274,7 @@ snapshots:
axios@1.15.0: axios@1.15.0:
dependencies: dependencies:
follow-redirects: 1.15.11 follow-redirects: 1.16.0
form-data: 4.0.5 form-data: 4.0.5
proxy-from-env: 2.1.0 proxy-from-env: 2.1.0
transitivePeerDependencies: transitivePeerDependencies:
@@ -17939,7 +17940,7 @@ snapshots:
flatted@3.4.2: {} flatted@3.4.2: {}
follow-redirects@1.15.11: {} follow-redirects@1.16.0: {}
for-each@0.3.3: for-each@0.3.3:
dependencies: dependencies: