mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
feat(users): per-user library assignment + themed confirm modal (#222)
Adds Navidrome library assignment to the User Management settings
panel: GET /api/library + PUT /api/user/{id}/library wired through
new Rust commands. UserForm gets a checkbox picker (hidden for
admins, who auto-receive all libraries server-side) with inline
validation. User rows compacted to a single clickable line with
hover highlight; native confirm() replaced by a portal-based
ConfirmModal (theme-aware, ESC/Enter, vertically centered).
Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
6d3c50264a
commit
459c9f688d
@@ -1,11 +1,17 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
export interface NdLibrary {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface NdUser {
|
||||
id: string;
|
||||
userName: string;
|
||||
name: string;
|
||||
email: string;
|
||||
isAdmin: boolean;
|
||||
libraryIds: number[];
|
||||
lastLoginAt?: string | null;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
@@ -25,6 +31,18 @@ export async function ndLogin(
|
||||
return invoke<NdLoginResult>('navidrome_login', { serverUrl, username, password });
|
||||
}
|
||||
|
||||
function extractLibraryIds(o: Record<string, unknown>): number[] {
|
||||
const libs = o.libraries;
|
||||
if (!Array.isArray(libs)) return [];
|
||||
const ids: number[] = [];
|
||||
for (const l of libs) {
|
||||
const id = (l as Record<string, unknown>)?.id;
|
||||
if (typeof id === 'number') ids.push(id);
|
||||
else if (typeof id === 'string' && /^\d+$/.test(id)) ids.push(Number(id));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
export async function ndListUsers(serverUrl: string, token: string): Promise<NdUser[]> {
|
||||
const raw = await invoke<unknown>('nd_list_users', { serverUrl, token });
|
||||
if (!Array.isArray(raw)) return [];
|
||||
@@ -36,6 +54,7 @@ export async function ndListUsers(serverUrl: string, token: string): Promise<NdU
|
||||
name: String(o.name ?? ''),
|
||||
email: String(o.email ?? ''),
|
||||
isAdmin: !!o.isAdmin,
|
||||
libraryIds: extractLibraryIds(o),
|
||||
lastLoginAt: (o.lastLoginAt as string | null | undefined) ?? null,
|
||||
createdAt: o.createdAt as string | undefined,
|
||||
updatedAt: o.updatedAt as string | undefined,
|
||||
@@ -43,12 +62,35 @@ export async function ndListUsers(serverUrl: string, token: string): Promise<NdU
|
||||
});
|
||||
}
|
||||
|
||||
export async function ndListLibraries(serverUrl: string, token: string): Promise<NdLibrary[]> {
|
||||
const raw = await invoke<unknown>('nd_list_libraries', { serverUrl, token });
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw.map(l => {
|
||||
const o = l as Record<string, unknown>;
|
||||
const id = typeof o.id === 'number'
|
||||
? o.id
|
||||
: typeof o.id === 'string' && /^\d+$/.test(o.id) ? Number(o.id) : 0;
|
||||
return { id, name: String(o.name ?? '') };
|
||||
}).filter(l => l.id > 0);
|
||||
}
|
||||
|
||||
export async function ndSetUserLibraries(
|
||||
serverUrl: string,
|
||||
token: string,
|
||||
id: string,
|
||||
libraryIds: number[],
|
||||
): Promise<void> {
|
||||
await invoke('nd_set_user_libraries', { serverUrl, token, id, libraryIds });
|
||||
}
|
||||
|
||||
export async function ndCreateUser(
|
||||
serverUrl: string,
|
||||
token: string,
|
||||
data: { userName: string; name: string; email: string; password: string; isAdmin: boolean },
|
||||
): Promise<void> {
|
||||
await invoke('nd_create_user', { serverUrl, token, ...data });
|
||||
): Promise<{ id: string }> {
|
||||
const raw = await invoke<unknown>('nd_create_user', { serverUrl, token, ...data });
|
||||
const o = (raw as Record<string, unknown> | null) ?? {};
|
||||
return { id: String(o.id ?? '') };
|
||||
}
|
||||
|
||||
export async function ndUpdateUser(
|
||||
|
||||
Reference in New Issue
Block a user