web: admin surfaces so the PhotoPrism UI is never needed
- Account tab in General settings — self-service password change. - UsersDialog (admin-only footer entry) — full /api/v1/users CRUD with admin-issued password reset. - People as a fifth tag category alongside Labels/Keywords/Colors/Ratings, backed by /api/v1/subjects and the `person:` DSL clause. - About tab in Library settings — version, library counts, feature chips, and a collapsible env-config help panel for the bits PP has no runtime API for (OIDC, TF, WebDAV). - Library tab expanded with Indexer-advanced, extra Downloads checksums, and a Features grid that only renders keys PhotoPrism actually returns. - Fix the SettingsDialog null-draft race the same way GeneralSettingsDialog already had: normalize on open, never null on close. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import { primaryFile } from '$lib/types/photoprism';
|
||||
import type {
|
||||
PpClientConfig,
|
||||
PpPhoto,
|
||||
PpRole,
|
||||
PpSessionResponse,
|
||||
PpUser
|
||||
} from '$lib/types/photoprism';
|
||||
@@ -525,6 +526,39 @@ export async function listLabels(): Promise<PpLabel[]> {
|
||||
return data;
|
||||
}
|
||||
|
||||
// ── Subjects (people / face recognition) ────────────────────────────────────
|
||||
//
|
||||
// PhotoPrism's face indexer clusters detected faces into Subjects, each with a
|
||||
// stable UID, a human-editable Name, and a slug. The DSL operator `person:<slug>`
|
||||
// filters photos to those carrying a marker assigned to that subject.
|
||||
|
||||
export interface PpSubject {
|
||||
UID: string;
|
||||
Slug: string;
|
||||
Name: string;
|
||||
Favorite?: boolean;
|
||||
Private?: boolean;
|
||||
Excluded?: boolean;
|
||||
PhotoCount?: number;
|
||||
Thumb?: string;
|
||||
}
|
||||
|
||||
export async function listSubjects(): Promise<PpSubject[]> {
|
||||
const { data } = await http.get<PpSubject[]>('/subjects', {
|
||||
params: { count: 1000, order: 'count' }
|
||||
});
|
||||
return data ?? [];
|
||||
}
|
||||
|
||||
export async function updateSubject(uid: string, patch: Partial<PpSubject>): Promise<PpSubject> {
|
||||
const { data } = await http.put<PpSubject>(`/subjects/${uid}`, patch);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteSubject(uid: string): Promise<void> {
|
||||
await http.delete(`/subjects/${uid}`);
|
||||
}
|
||||
|
||||
// ── Albums = Heaps ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface PpAlbum {
|
||||
@@ -831,7 +865,15 @@ export interface PpSettings {
|
||||
showCaptions?: boolean;
|
||||
};
|
||||
maps?: { animate?: number; style?: string };
|
||||
index?: { path?: string; convert?: boolean; rescan?: boolean; skipArchived?: boolean };
|
||||
index?: {
|
||||
path?: string;
|
||||
convert?: boolean;
|
||||
rescan?: boolean;
|
||||
skipArchived?: boolean;
|
||||
skipMeta?: boolean;
|
||||
skipRaw?: boolean;
|
||||
skipHidden?: boolean;
|
||||
};
|
||||
import?: { path?: string; move?: boolean; dest?: string };
|
||||
stack?: { uuid?: boolean; meta?: boolean; name?: boolean };
|
||||
download?: {
|
||||
@@ -840,6 +882,41 @@ export interface PpSettings {
|
||||
originals?: boolean;
|
||||
mediaRaw?: boolean;
|
||||
mediaSidecar?: boolean;
|
||||
crc32?: boolean;
|
||||
sha1?: boolean;
|
||||
};
|
||||
/**
|
||||
* PhotoPrism's feature-flag bag. Each key gates a UI surface (and the
|
||||
* matching API endpoints) inside PP's own SPA — disabling `share` for
|
||||
* example hides every share button. Optional because older PP versions
|
||||
* don't return the block; the Library tab only renders toggles for
|
||||
* keys it actually sees in the response.
|
||||
*/
|
||||
features?: {
|
||||
archive?: boolean;
|
||||
private?: boolean;
|
||||
review?: boolean;
|
||||
files?: boolean;
|
||||
folders?: boolean;
|
||||
moments?: boolean;
|
||||
calendar?: boolean;
|
||||
places?: boolean;
|
||||
edit?: boolean;
|
||||
share?: boolean;
|
||||
library?: boolean;
|
||||
import?: boolean;
|
||||
logs?: boolean;
|
||||
search?: boolean;
|
||||
account?: boolean;
|
||||
settings?: boolean;
|
||||
services?: boolean;
|
||||
people?: boolean;
|
||||
labels?: boolean;
|
||||
download?: boolean;
|
||||
upload?: boolean;
|
||||
delete?: boolean;
|
||||
ratings?: boolean;
|
||||
[k: string]: boolean | undefined;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
}
|
||||
@@ -907,6 +984,55 @@ export async function getErrors(opts: { limit?: number } = {}): Promise<PpLogEnt
|
||||
return data ?? [];
|
||||
}
|
||||
|
||||
// ── Users ────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// PhotoPrism's admin user endpoints. List/create/update/delete require an
|
||||
// admin session; the password endpoint accepts the user's own UID with their
|
||||
// current password as `old`.
|
||||
|
||||
export interface CreateUserBody {
|
||||
Name: string;
|
||||
DisplayName?: string;
|
||||
Email?: string;
|
||||
Role: PpRole;
|
||||
BasePath?: string;
|
||||
UploadPath?: string;
|
||||
WebDAV?: boolean;
|
||||
Password?: string;
|
||||
}
|
||||
|
||||
export type UpdateUserBody = Partial<CreateUserBody>;
|
||||
|
||||
export async function listUsers(): Promise<PpUser[]> {
|
||||
const { data } = await http.get<PpUser[] | { users?: PpUser[] }>('/users', {
|
||||
params: { count: 1000, order: 'name' }
|
||||
});
|
||||
if (Array.isArray(data)) return data;
|
||||
return data.users ?? [];
|
||||
}
|
||||
|
||||
export async function createUser(body: CreateUserBody): Promise<PpUser> {
|
||||
const { data } = await http.post<PpUser>('/users', body);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateUser(uid: string, patch: UpdateUserBody): Promise<PpUser> {
|
||||
const { data } = await http.put<PpUser>(`/users/${uid}`, patch);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteUser(uid: string): Promise<void> {
|
||||
await http.delete(`/users/${uid}`);
|
||||
}
|
||||
|
||||
export async function setUserPassword(
|
||||
uid: string,
|
||||
oldPassword: string,
|
||||
newPassword: string
|
||||
): Promise<void> {
|
||||
await http.put(`/users/${uid}/password`, { old: oldPassword, new: newPassword });
|
||||
}
|
||||
|
||||
// ── Re-exports ───────────────────────────────────────────────────────────────
|
||||
|
||||
export type { PpClientConfig, PpPhoto, PpSessionResponse, PpUser };
|
||||
|
||||
Reference in New Issue
Block a user