diff --git a/frontend/js/api.js b/frontend/js/api.js index baa27fc..e0ec520 100644 --- a/frontend/js/api.js +++ b/frontend/js/api.js @@ -156,6 +156,11 @@ export const api = { // Device Groups - Playlist groupAssignPlaylist: (groupId, playlist_id) => request(`/groups/${groupId}/assign-playlist`, { method: 'POST', body: JSON.stringify({ playlist_id }) }), + // API Tokens (personal access tokens, workspace-scoped) + getTokens: () => request('/tokens'), + createToken: (data) => request('/tokens', { method: 'POST', body: JSON.stringify(data) }), + revokeToken: (id) => request('/tokens/' + id, { method: 'DELETE' }), + // Current user getMe: () => request('/auth/me'), updateMe: (data) => request('/auth/me', { method: 'PUT', body: JSON.stringify(data) }), diff --git a/frontend/js/i18n/de.js b/frontend/js/i18n/de.js index e40a9c2..d4f680a 100644 --- a/frontend/js/i18n/de.js +++ b/frontend/js/i18n/de.js @@ -350,6 +350,30 @@ export default { 'settings.title': 'Einstellungen', 'settings.subtitle': 'Serverkonfiguration und Setup-Informationen', 'settings.account': 'Konto', + // API-Tokens + 'apitoken.title': 'API-Tokens', + 'apitoken.desc': 'Persönliche Zugriffstokens für die öffentliche API, beschränkt auf diesen Arbeitsbereich. Behandeln Sie sie wie Passwörter – wer das Token hat, kann hier in Ihrem Namen handeln.', + 'apitoken.name_placeholder': 'z. B. Agentur-Integration', + 'apitoken.scope_read': 'Nur Lesen', + 'apitoken.scope_write': 'Lesen & Schreiben', + 'apitoken.scope_full': 'Voll (inkl. Gerätebefehle)', + 'apitoken.create': 'Token erstellen', + 'apitoken.none': 'Noch keine Tokens.', + 'apitoken.col_token': 'Token', + 'apitoken.col_name': 'Name', + 'apitoken.col_scope': 'Bereich', + 'apitoken.col_created': 'Erstellt', + 'apitoken.col_last_used': 'Zuletzt verwendet', + 'apitoken.never': 'Nie', + 'apitoken.revoke': 'Widerrufen', + 'apitoken.revoked': 'Widerrufen', + 'apitoken.secret_title': 'Kopieren Sie Ihr Token jetzt', + 'apitoken.secret_warning': 'Dies ist das einzige Mal, dass das vollständige Token angezeigt wird. Bewahren Sie es sicher auf – Sie können es nicht erneut einsehen.', + 'apitoken.copy': 'Kopieren', + 'apitoken.copied': 'In die Zwischenablage kopiert', + 'apitoken.created_toast': 'Token erstellt', + 'apitoken.revoked_toast': 'Token widerrufen', + 'apitoken.revoke_confirm': 'Dieses Token widerrufen? Jede Integration, die es verwendet, funktioniert sofort nicht mehr.', 'settings.save_profile': 'Profil speichern', 'settings.change_password': 'Passwort ändern', 'settings.password_min_8': 'Muss mindestens 8 Zeichen lang sein.', diff --git a/frontend/js/i18n/en.js b/frontend/js/i18n/en.js index 6dbae19..7efdaa0 100644 --- a/frontend/js/i18n/en.js +++ b/frontend/js/i18n/en.js @@ -386,6 +386,30 @@ export default { 'settings.title': 'Settings', 'settings.subtitle': 'Server configuration and setup information', 'settings.account': 'Account', + // API Tokens + 'apitoken.title': 'API Tokens', + 'apitoken.desc': 'Personal access tokens for the public API, scoped to this workspace. Treat them like passwords — anyone with the token can act as you here.', + 'apitoken.name_placeholder': 'e.g. Agency integration', + 'apitoken.scope_read': 'Read only', + 'apitoken.scope_write': 'Read & write', + 'apitoken.scope_full': 'Full (incl. device commands)', + 'apitoken.create': 'Create token', + 'apitoken.none': 'No tokens yet.', + 'apitoken.col_token': 'Token', + 'apitoken.col_name': 'Name', + 'apitoken.col_scope': 'Scope', + 'apitoken.col_created': 'Created', + 'apitoken.col_last_used': 'Last used', + 'apitoken.never': 'Never', + 'apitoken.revoke': 'Revoke', + 'apitoken.revoked': 'Revoked', + 'apitoken.secret_title': 'Copy your token now', + 'apitoken.secret_warning': "This is the only time the full token is shown. Store it somewhere safe — you won't be able to see it again.", + 'apitoken.copy': 'Copy', + 'apitoken.copied': 'Copied to clipboard', + 'apitoken.created_toast': 'Token created', + 'apitoken.revoked_toast': 'Token revoked', + 'apitoken.revoke_confirm': 'Revoke this token? Any integration using it stops working immediately.', 'settings.save_profile': 'Save Profile', 'settings.email_alerts': 'Email me when devices go offline', 'settings.change_password': 'Change Password', diff --git a/frontend/js/i18n/es.js b/frontend/js/i18n/es.js index 0661bd5..f722311 100644 --- a/frontend/js/i18n/es.js +++ b/frontend/js/i18n/es.js @@ -349,6 +349,30 @@ export default { 'settings.title': 'Configuración', 'settings.subtitle': 'Configuración del servidor e información de instalación', 'settings.account': 'Cuenta', + // Tokens de API + 'apitoken.title': 'Tokens de API', + 'apitoken.desc': 'Tokens de acceso personal para la API pública, limitados a este espacio de trabajo. Trátalos como contraseñas: cualquiera que tenga el token puede actuar como tú aquí.', + 'apitoken.name_placeholder': 'p. ej. Integración de agencia', + 'apitoken.scope_read': 'Solo lectura', + 'apitoken.scope_write': 'Lectura y escritura', + 'apitoken.scope_full': 'Completo (incl. comandos de dispositivo)', + 'apitoken.create': 'Crear token', + 'apitoken.none': 'Aún no hay tokens.', + 'apitoken.col_token': 'Token', + 'apitoken.col_name': 'Nombre', + 'apitoken.col_scope': 'Alcance', + 'apitoken.col_created': 'Creado', + 'apitoken.col_last_used': 'Último uso', + 'apitoken.never': 'Nunca', + 'apitoken.revoke': 'Revocar', + 'apitoken.revoked': 'Revocado', + 'apitoken.secret_title': 'Copia tu token ahora', + 'apitoken.secret_warning': 'Esta es la única vez que se muestra el token completo. Guárdalo en un lugar seguro: no podrás volver a verlo.', + 'apitoken.copy': 'Copiar', + 'apitoken.copied': 'Copiado al portapapeles', + 'apitoken.created_toast': 'Token creado', + 'apitoken.revoked_toast': 'Token revocado', + 'apitoken.revoke_confirm': '¿Revocar este token? Cualquier integración que lo use dejará de funcionar de inmediato.', 'settings.save_profile': 'Guardar perfil', 'settings.change_password': 'Cambiar contraseña', 'settings.password_min_8': 'Debe tener al menos 8 caracteres.', diff --git a/frontend/js/i18n/fr.js b/frontend/js/i18n/fr.js index 3f0cf57..e7e1c48 100644 --- a/frontend/js/i18n/fr.js +++ b/frontend/js/i18n/fr.js @@ -350,6 +350,30 @@ export default { 'settings.title': 'Paramètres', 'settings.subtitle': 'Configuration du serveur et informations d\'installation', 'settings.account': 'Compte', + // Jetons d'API + 'apitoken.title': "Jetons d'API", + 'apitoken.desc': "Jetons d'accès personnels pour l'API publique, limités à cet espace de travail. Traitez-les comme des mots de passe : toute personne disposant du jeton peut agir en votre nom ici.", + 'apitoken.name_placeholder': 'p. ex. Intégration agence', + 'apitoken.scope_read': 'Lecture seule', + 'apitoken.scope_write': 'Lecture et écriture', + 'apitoken.scope_full': 'Complet (cmd. appareils incluses)', + 'apitoken.create': 'Créer un jeton', + 'apitoken.none': 'Aucun jeton pour le moment.', + 'apitoken.col_token': 'Jeton', + 'apitoken.col_name': 'Nom', + 'apitoken.col_scope': 'Portée', + 'apitoken.col_created': 'Créé', + 'apitoken.col_last_used': 'Dernière utilisation', + 'apitoken.never': 'Jamais', + 'apitoken.revoke': 'Révoquer', + 'apitoken.revoked': 'Révoqué', + 'apitoken.secret_title': 'Copiez votre jeton maintenant', + 'apitoken.secret_warning': "C'est la seule fois où le jeton complet est affiché. Conservez-le en lieu sûr : vous ne pourrez plus le revoir.", + 'apitoken.copy': 'Copier', + 'apitoken.copied': 'Copié dans le presse-papiers', + 'apitoken.created_toast': 'Jeton créé', + 'apitoken.revoked_toast': 'Jeton révoqué', + 'apitoken.revoke_confirm': "Révoquer ce jeton ? Toute intégration qui l'utilise cessera de fonctionner immédiatement.", 'settings.save_profile': 'Enregistrer le profil', 'settings.change_password': 'Changer le mot de passe', 'settings.password_min_8': 'Doit contenir au moins 8 caractères.', diff --git a/frontend/js/i18n/pt.js b/frontend/js/i18n/pt.js index c08d463..da40f03 100644 --- a/frontend/js/i18n/pt.js +++ b/frontend/js/i18n/pt.js @@ -350,6 +350,30 @@ export default { 'settings.title': 'Configurações', 'settings.subtitle': 'Configuração do servidor e informações de instalação', 'settings.account': 'Conta', + // Tokens de API + 'apitoken.title': 'Tokens de API', + 'apitoken.desc': 'Tokens de acesso pessoal para a API pública, restritos a este espaço de trabalho. Trate-os como senhas — qualquer pessoa com o token pode agir como você aqui.', + 'apitoken.name_placeholder': 'ex.: Integração da agência', + 'apitoken.scope_read': 'Somente leitura', + 'apitoken.scope_write': 'Leitura e escrita', + 'apitoken.scope_full': 'Completo (incl. comandos de dispositivo)', + 'apitoken.create': 'Criar token', + 'apitoken.none': 'Ainda não há tokens.', + 'apitoken.col_token': 'Token', + 'apitoken.col_name': 'Nome', + 'apitoken.col_scope': 'Escopo', + 'apitoken.col_created': 'Criado', + 'apitoken.col_last_used': 'Último uso', + 'apitoken.never': 'Nunca', + 'apitoken.revoke': 'Revogar', + 'apitoken.revoked': 'Revogado', + 'apitoken.secret_title': 'Copie seu token agora', + 'apitoken.secret_warning': 'Esta é a única vez que o token completo é exibido. Guarde-o em um lugar seguro — você não poderá vê-lo novamente.', + 'apitoken.copy': 'Copiar', + 'apitoken.copied': 'Copiado para a área de transferência', + 'apitoken.created_toast': 'Token criado', + 'apitoken.revoked_toast': 'Token revogado', + 'apitoken.revoke_confirm': 'Revogar este token? Qualquer integração que o utilize para de funcionar imediatamente.', 'settings.save_profile': 'Salvar perfil', 'settings.change_password': 'Alterar senha', 'settings.password_min_8': 'Deve ter no mínimo 8 caracteres.', diff --git a/frontend/js/views/settings.js b/frontend/js/views/settings.js index 9a460f1..6e0fe02 100644 --- a/frontend/js/views/settings.js +++ b/frontend/js/views/settings.js @@ -60,6 +60,28 @@ export async function render(container) { `} +
${t('apitoken.desc')}
+${t('settings.loading_users')}
${t('apitoken.none')}
`; + return; + } + el.innerHTML = ` +| ${t('apitoken.col_token')} | +${t('apitoken.col_name')} | +${t('apitoken.col_scope')} | +${t('apitoken.col_created')} | +${t('apitoken.col_last_used')} | ++ |
|---|---|---|---|---|---|
| ${esc(tok.prefix)}… | +${esc(tok.name || '')} | +${esc(scopeLabel(tok.scope))} | +${esc(fmtTokenDate(tok.created_at))} | +${tok.last_used_at ? esc(fmtTokenDate(tok.last_used_at)) : t('apitoken.never')} | ++ ${tok.revoked_at + ? `${t('apitoken.revoked')}` + : ``} + | +
${t('apitoken.secret_warning')}
+