Phase 2: Docker and OIDC auth
This commit is contained in:
+98
-18
@@ -2,14 +2,20 @@
|
||||
* Typed API client for the FastAPI backend.
|
||||
*/
|
||||
|
||||
import { getAccessToken } from "../auth";
|
||||
import type {
|
||||
MediaCounts,
|
||||
LibraryCount,
|
||||
UserDirectoryResponse,
|
||||
UserMessageResponse,
|
||||
UserMessageQueueStatus,
|
||||
SmtpTestResponse,
|
||||
NowPlayingSession,
|
||||
MonitoringStatus,
|
||||
MonitoringMetrics,
|
||||
DiskSpace,
|
||||
MediaIndexStatus,
|
||||
MediaIndexActionResponse,
|
||||
MediaQueryResponse,
|
||||
DirectoryListing,
|
||||
JobTemplate,
|
||||
@@ -17,36 +23,89 @@ import type {
|
||||
ResolvedPath,
|
||||
} from "../types";
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:8000";
|
||||
const BASE_URL = import.meta.env.VITE_API_URL || "/api";
|
||||
|
||||
function isAbsoluteUrl(value: string): boolean {
|
||||
return /^https?:\/\//i.test(value) || value.startsWith("//");
|
||||
}
|
||||
|
||||
function buildUrl(path: string, params?: Record<string, string>): string {
|
||||
if (!isAbsoluteUrl(BASE_URL)) {
|
||||
const url = new URL(path, window.location.origin);
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== "")
|
||||
url.searchParams.set(key, value);
|
||||
});
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
async function get<T>(
|
||||
path: string,
|
||||
params?: Record<string, string>,
|
||||
): Promise<T> {
|
||||
const url = new URL(path, BASE_URL);
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== "") url.searchParams.set(key, value);
|
||||
});
|
||||
}
|
||||
const response = await fetch(url.toString());
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
async function readErrorDetail(response: Response): Promise<string> {
|
||||
const text = await response.text();
|
||||
try {
|
||||
const parsed = JSON.parse(text) as { detail?: unknown; message?: unknown };
|
||||
const detail = parsed.detail ?? parsed.message;
|
||||
if (typeof detail === "string" && detail.trim()) {
|
||||
return detail;
|
||||
}
|
||||
} catch {
|
||||
// Fall back to the raw response body below.
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function buildHeaders(isJsonBody: boolean): Headers {
|
||||
const headers = new Headers();
|
||||
const token = getAccessToken();
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
if (isJsonBody) headers.set("Content-Type", "application/json");
|
||||
return headers;
|
||||
}
|
||||
|
||||
async function get<T>(
|
||||
path: string,
|
||||
params?: Record<string, string>,
|
||||
): Promise<T> {
|
||||
const response = await fetch(buildUrl(path, params), {
|
||||
headers: buildHeaders(false),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const detail = await response.text();
|
||||
throw new Error(`${response.status}: ${detail}`);
|
||||
throw new Error(`${response.status}: ${await readErrorDetail(response)}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function post<T>(path: string, body?: unknown): Promise<T> {
|
||||
const url = new URL(path, BASE_URL);
|
||||
const response = await fetch(url.toString(), {
|
||||
const response = await fetch(buildUrl(path), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: buildHeaders(true),
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
if (!response.ok) {
|
||||
const detail = await response.text();
|
||||
throw new Error(`${response.status}: ${detail}`);
|
||||
throw new Error(`${response.status}: ${await readErrorDetail(response)}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function postForm<T>(path: string, body: FormData): Promise<T> {
|
||||
const headers = buildHeaders(false);
|
||||
const response = await fetch(buildUrl(path), {
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status}: ${await readErrorDetail(response)}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
@@ -55,15 +114,23 @@ async function post<T>(path: string, body?: unknown): Promise<T> {
|
||||
export const fetchCounts = () => get<MediaCounts>("/api/dashboard/counts");
|
||||
export const fetchLibraries = () =>
|
||||
get<LibraryCount[]>("/api/dashboard/libraries");
|
||||
export const fetchNowPlaying = () =>
|
||||
get<NowPlayingSession[]>("/api/dashboard/now-playing");
|
||||
export const fetchActivity = () =>
|
||||
get<NowPlayingSession[]>("/api/dashboard/activity");
|
||||
export const fetchUsers = () => get<UserDirectoryResponse>("/api/users");
|
||||
|
||||
// Backward-compatible alias used by older hooks/components.
|
||||
export const fetchNowPlaying = fetchActivity;
|
||||
|
||||
// Monitoring
|
||||
export const fetchMonitoringStatus = () =>
|
||||
get<MonitoringStatus>("/api/monitoring/status");
|
||||
export const fetchMonitoringMetrics = (lastSeconds = 3600) =>
|
||||
export const fetchMonitoringMetrics = (
|
||||
lastSeconds?: number | null,
|
||||
maxLines = 70_000,
|
||||
) =>
|
||||
get<MonitoringMetrics>("/api/monitoring/metrics", {
|
||||
last_seconds: String(lastSeconds),
|
||||
...(lastSeconds == null ? {} : { last_seconds: String(lastSeconds) }),
|
||||
max_lines: String(maxLines),
|
||||
});
|
||||
export const fetchDiskSpace = () => get<DiskSpace>("/api/monitoring/disk");
|
||||
export const startCollector = () =>
|
||||
@@ -77,7 +144,11 @@ export const restartCollector = () =>
|
||||
export const fetchMediaStatus = () =>
|
||||
get<MediaIndexStatus>("/api/media/status");
|
||||
export const buildMediaIndex = () =>
|
||||
post<{ indexed_items: number }>("/api/media/build");
|
||||
post<MediaIndexActionResponse>("/api/media/build");
|
||||
export const stopMediaIndexBuild = () =>
|
||||
post<MediaIndexActionResponse>("/api/media/stop");
|
||||
export const forceStopMediaIndexBuild = () =>
|
||||
post<MediaIndexActionResponse>("/api/media/force-stop");
|
||||
export const queryMedia = (params: {
|
||||
libraries?: string;
|
||||
types?: string;
|
||||
@@ -114,3 +185,12 @@ export const fetchJobTemplates = () =>
|
||||
get<JobTemplate[]>("/api/jobs/templates");
|
||||
export const runJob = (jobKey: string, path: string) =>
|
||||
post<JobResult>("/api/jobs/run", { job_key: jobKey, path });
|
||||
|
||||
export const fetchUserMessageQueueStatus = () =>
|
||||
get<UserMessageQueueStatus>("/api/users/message/status");
|
||||
|
||||
export const testUserSmtpConnection = () =>
|
||||
post<SmtpTestResponse>("/api/users/message/test-smtp");
|
||||
|
||||
export const sendUserMessage = (formData: FormData) =>
|
||||
postForm<UserMessageResponse>("/api/users/message", formData);
|
||||
|
||||
Reference in New Issue
Block a user