fix(observability): router conflict, logout button, missing i18n

- Fix chi duplicate Route() panic by consolidating read/write routes
  into single Route blocks with nested admin Group
- Add logout button to sidebar with token cleanup
- Add missing settingsAuth.password i18n key
This commit is contained in:
2026-03-30 12:26:22 +03:00
parent e0a648fb0c
commit 71aeb615b3
6 changed files with 102 additions and 61 deletions
@@ -0,0 +1,9 @@
<script lang="ts">
interface Props { size?: number; class?: string; }
const { size = 24, class: className = '' }: Props = $props();
</script>
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={className}>
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16 17 21 12 16 7" />
<line x1="21" y1="12" x2="9" y2="12" />
</svg>
+1
View File
@@ -47,3 +47,4 @@ export { default as IconWifi } from './IconWifi.svelte';
export { default as IconRefresh } from './IconRefresh.svelte';
export { default as IconProxies } from './IconProxies.svelte';
export { default as IconEvents } from './IconEvents.svelte';
export { default as IconLogout } from './IconLogout.svelte';
+4 -2
View File
@@ -9,7 +9,8 @@
"deploy": "Deploy",
"proxies": "Proxies",
"events": "Events",
"settings": "Settings"
"settings": "Settings",
"logout": "Log out"
},
"dashboard": {
"title": "Dashboard",
@@ -311,7 +312,8 @@
"createFailed": "Failed to create user",
"deleteFailed": "Failed to delete user",
"deleteConfirm": "Are you sure you want to delete this user?",
"usernameRequired": "Username and password are required"
"usernameRequired": "Username and password are required",
"password": "Password"
},
"login": {
"title": "Docker Watcher",
+4 -2
View File
@@ -9,7 +9,8 @@
"deploy": "Деплой",
"proxies": "Прокси",
"events": "События",
"settings": "Настройки"
"settings": "Настройки",
"logout": "Выйти"
},
"dashboard": {
"title": "Панель управления",
@@ -311,7 +312,8 @@
"createFailed": "Не удалось создать пользователя",
"deleteFailed": "Не удалось удалить пользователя",
"deleteConfirm": "Вы уверены, что хотите удалить этого пользователя?",
"usernameRequired": "Имя пользователя и пароль обязательны"
"usernameRequired": "Имя пользователя и пароль обязательны",
"password": "Пароль"
},
"login": {
"title": "Docker Watcher",
+21 -2
View File
@@ -6,7 +6,7 @@
import Toast from '$lib/components/Toast.svelte';
import ThemeToggle from '$lib/components/ThemeToggle.svelte';
import LocaleSwitcher from '$lib/components/LocaleSwitcher.svelte';
import { IconDashboard, IconProjects, IconDeploy, IconProxies, IconEvents, IconSettings, IconMenu, IconX } from '$lib/components/icons';
import { IconDashboard, IconProjects, IconDeploy, IconProxies, IconEvents, IconSettings, IconMenu, IconX, IconLogout } from '$lib/components/icons';
import { connectGlobalEvents, type SSEConnection } from '$lib/sse';
import { instanceStatusStore } from '$lib/stores/instance-status';
import { resolvedTheme, applyTheme } from '$lib/stores/theme';
@@ -58,6 +58,15 @@
sidebarOpen = false;
});
function logout() {
if (typeof localStorage !== 'undefined') {
localStorage.removeItem('auth_token');
}
sseConnection?.close();
sseConnection = null;
window.location.href = '/login';
}
onMount(() => {
sseConnection = connectGlobalEvents({
onInstanceStatus(payload) {
@@ -151,7 +160,17 @@
<ThemeToggle />
<LocaleSwitcher />
</div>
<p class="text-xs text-[var(--text-tertiary)]">{$t('app.name')} {$t('app.version')}</p>
<div class="flex items-center justify-between">
<p class="text-xs text-[var(--text-tertiary)]">{$t('app.name')} {$t('app.version')}</p>
<button
onclick={logout}
class="inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-card-hover)] hover:text-[var(--color-danger)]"
title={$t('nav.logout')}
>
<IconLogout size={14} />
{$t('nav.logout')}
</button>
</div>
</div>
</aside>