chore(immich): drop the IMMICH_URL hook

A deployment no longer suggests an address for the first server: every
address is each account's own and is typed in the app, so the field opens
blank with a placeholder. The env var, its compose passthrough, the example
line and the `defaultUrl` field that carried it are all gone; the backend's
config route now answers with the saved list alone.
This commit is contained in:
2026-10-10 17:10:30 +07:00
parent 8901e0c378
commit c557108eee
8 changed files with 19 additions and 43 deletions
+5 -3
View File
@@ -133,7 +133,7 @@ await context.route('**/api/**', (route) => {
if (path === '/api/immich/config') {
if (req.method() === 'GET') {
return send(200, { ok: true, defaultUrl: 'https://photos.example.com', servers: saved.map(view) });
return send(200, { ok: true, servers: saved.map(view) });
}
if (req.method() === 'DELETE') {
const at = saved.findIndex((s) => s.id === url.searchParams.get('id'));
@@ -297,10 +297,12 @@ await page.waitForSelector('[data-key="lib-add-immich"]', { timeout: 60_000 });
await page.click('[data-key="lib-add-immich"]');
await page.waitForSelector('[data-key="immich-url"]');
step(
'the dialog opens with the address the deployment suggests already in it',
(await page.inputValue('[data-key="immich-url"]')) === 'https://photos.example.com',
'the dialog opens blank: the address is each reader’s to type, with a nudge in the field',
(await page.inputValue('[data-key="immich-url"]')) === '' &&
(await page.getAttribute('[data-key="immich-url"]', 'placeholder')) === 'https://photos.example.com',
await page.inputValue('[data-key="immich-url"]')
);
await page.fill('[data-key="immich-url"]', 'https://photos.example.com');
await page.fill('[data-key="immich-key"]', 'good-key');
await page.click('[data-key="immich-check"]');
await page.waitForSelector('[data-key="immich-status"]');
-8
View File
@@ -336,10 +336,6 @@ function Catalogue({ admin }: { admin: boolean }) {
// A server is not a folder on the disk — see `engine/immich.ts` — so it is the
// column's second source and the page holds no key of its own.
const [servers, setServers] = useState<ImmichServer[]>([]);
// The address this deployment suggests for a first server — `IMMICH_URL`, which
// the backend answers with. Offered as a filled-in field, not as a saved row:
// it is a suggestion until a key is typed against it.
const [immichUrl, setImmichUrl] = useState('');
const [addImmich, setAddImmich] = useState(false);
const [editAlbums, setEditAlbums] = useState<LibraryFolder | null>(null);
// The strip starts on the catalogue the last screen read: coming back from the
@@ -800,7 +796,6 @@ function Catalogue({ admin }: { admin: boolean }) {
const config = await immichConfig();
if (!alive) return;
setServers(config.servers);
setImmichUrl(config.defaultUrl);
// 5. Await photos reload if still running
await reloadPromise;
@@ -1106,7 +1101,6 @@ function Catalogue({ admin }: { admin: boolean }) {
// holds the key, and what it says is what the dialog will show next time.
const config = await immichConfig(true);
setServers(config.servers);
setImmichUrl(config.defaultUrl);
if (!isNew && edited) {
const next: LibraryFolder = { ...edited, albums: saved.albums };
await putFolder(next);
@@ -3123,7 +3117,6 @@ function Catalogue({ admin }: { admin: boolean }) {
it goes straight up to the backend, which probes it before it stores. */}
{addImmich ? (
<ImmichDialog
defaultUrl={immichUrl}
onSaved={(saved, isNew) => void serverSaved(saved, isNew)}
onClose={() => setAddImmich(false)}
/>
@@ -3131,7 +3124,6 @@ function Catalogue({ admin }: { admin: boolean }) {
{editAlbums ? (
<ImmichDialog
server={servers.find((s) => s.id === editAlbums.server) ?? null}
defaultUrl=""
onSaved={(saved, isNew) => void serverSaved(saved, isNew)}
onClose={() => setEditAlbums(null)}
/>
+8 -8
View File
@@ -87,16 +87,16 @@ async function get<T>(path: string): Promise<T | null> {
}
}
// The saved servers, plus the address this deployment suggests for the first one.
// One call per session: the tree has to draw on the first paint, and a server
// list that is asked for on every render is a round trip per render.
let configOnce: Promise<{ defaultUrl: string; servers: ImmichServer[] }> | null = null;
// The saved servers. One call per session: the tree has to draw on the first
// paint, and a server list that is asked for on every render is a round trip per
// render.
let configOnce: Promise<{ servers: ImmichServer[] }> | null = null;
export function immichConfig(fresh = false): Promise<{ defaultUrl: string; servers: ImmichServer[] }> {
export function immichConfig(fresh = false): Promise<{ servers: ImmichServer[] }> {
if (fresh || !configOnce) {
configOnce = get<{ defaultUrl?: string; servers?: ImmichServer[] }>('/api/immich/config').then((body) =>
body ? { defaultUrl: body.defaultUrl ?? '', servers: body.servers ?? [] } : { defaultUrl: '', servers: [] }
);
configOnce = get<{ servers?: ImmichServer[] }>('/api/immich/config').then((body) => ({
servers: body?.servers ?? [],
}));
}
return configOnce;
}
+1 -3
View File
@@ -32,19 +32,17 @@ const UUID = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
export function ImmichDialog({
server,
defaultUrl,
onSaved,
onClose,
}: {
server?: ImmichServer | null;
defaultUrl: string;
onSaved: (saved: ImmichServer, isNew: boolean) => void;
onClose: () => void;
}) {
const { t } = useI18n();
const editing = Boolean(server);
const [name, setName] = useState(server?.name ?? '');
const [url, setUrl] = useState(server?.url ?? defaultUrl);
const [url, setUrl] = useState(server?.url ?? '');
const [key, setKey] = useState('');
// The key a node is being given in place of the one it has. Blank means the
// question was not asked — the node keeps the key it already answered with — so