diff --git a/.gitignore b/.gitignore
index e7e32ab..a580b95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,5 @@ htmlcov/
# Claude Code
.claude/
+__pycache__/
+test-data/
diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json
index dcfcbad..e0d12de 100644
--- a/frontend/src/lib/i18n/en.json
+++ b/frontend/src/lib/i18n/en.json
@@ -244,6 +244,10 @@
"dark": "Dark",
"system": "System",
"test": "Test",
- "create": "Create"
+ "create": "Create",
+ "changePassword": "Change Password",
+ "currentPassword": "Current password",
+ "newPassword": "New password",
+ "passwordChanged": "Password changed successfully"
}
}
diff --git a/frontend/src/lib/i18n/ru.json b/frontend/src/lib/i18n/ru.json
index 27880c2..295d048 100644
--- a/frontend/src/lib/i18n/ru.json
+++ b/frontend/src/lib/i18n/ru.json
@@ -244,6 +244,10 @@
"dark": "Тёмная",
"system": "Системная",
"test": "Тест",
- "create": "Создать"
+ "create": "Создать",
+ "changePassword": "Сменить пароль",
+ "currentPassword": "Текущий пароль",
+ "newPassword": "Новый пароль",
+ "passwordChanged": "Пароль успешно изменён"
}
}
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index dc4c162..d68efd7 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -3,6 +3,7 @@
import { page } from '$app/state';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
+ import { api } from '$lib/api';
import { getAuth, loadUser, logout } from '$lib/auth.svelte';
import { t, initLocale, getLocale, setLocale, type Locale } from '$lib/i18n';
import { getTheme, initTheme, setTheme, type Theme } from '$lib/theme.svelte';
@@ -11,6 +12,21 @@
const auth = getAuth();
const theme = getTheme();
+ let showPasswordForm = $state(false);
+ let pwdCurrent = $state('');
+ let pwdNew = $state('');
+ let pwdMsg = $state('');
+
+ async function changePassword(e: SubmitEvent) {
+ e.preventDefault(); pwdMsg = '';
+ try {
+ await api('/auth/password', { method: 'PUT', body: JSON.stringify({ current_password: pwdCurrent, new_password: pwdNew }) });
+ pwdMsg = t('common.passwordChanged');
+ pwdCurrent = ''; pwdNew = '';
+ setTimeout(() => { showPasswordForm = false; pwdMsg = ''; }, 2000);
+ } catch (err: any) { pwdMsg = err.message; }
+ }
+
// Reactive counter to force re-render on locale change
let localeVersion = $state(0);
let collapsed = $state(false);
@@ -146,17 +162,33 @@
⏻
{:else}
-
-
-
{auth.user.username}
-
{auth.user.role}
+
+
+
+
{auth.user.username}
+
{auth.user.role}
+
+
-
{/if}
diff --git a/frontend/src/routes/servers/+page.svelte b/frontend/src/routes/servers/+page.svelte
index 03b500f..9da4a94 100644
--- a/frontend/src/routes/servers/+page.svelte
+++ b/frontend/src/routes/servers/+page.svelte
@@ -14,8 +14,17 @@
let submitting = $state(false);
let loaded = $state(false);
+ let health = $state
>({});
+
onMount(load);
- async function load() { try { servers = await api('/servers'); } catch {} finally { loaded = true; } }
+ async function load() {
+ try { servers = await api('/servers'); } catch {} finally { loaded = true; }
+ // Ping all servers in background
+ for (const s of servers) {
+ health[s.id] = null; // loading
+ api(`/servers/${s.id}/ping`).then(r => health[s.id] = r.online).catch(() => health[s.id] = false);
+ }
+ }
function openNew() {
form = { name: 'Immich', url: '', api_key: '' };
@@ -88,9 +97,13 @@
{#each servers as server}
-
-
{server.name}
-
{server.url}
+
+
+
+
{server.name}
+
{server.url}
+
edit(server)} class="text-xs text-[var(--color-muted-foreground)] hover:underline">{t('common.edit')}
diff --git a/frontend/src/routes/trackers/+page.svelte b/frontend/src/routes/trackers/+page.svelte
index 2902720..65e1b8a 100644
--- a/frontend/src/routes/trackers/+page.svelte
+++ b/frontend/src/routes/trackers/+page.svelte
@@ -13,6 +13,7 @@
let albums = $state
([]);
let showForm = $state(false);
let editing = $state(null);
+ let albumFilter = $state('');
const defaultForm = () => ({
name: '', server_id: 0, album_ids: [] as string[],
target_ids: [] as number[], scan_interval: 60,
@@ -84,12 +85,19 @@
{#if albums.length > 0}
-
-
- {#each albums as album}
-