Initial curated Unraid-AppOps-Gateway source from 3bd6f6fee310125bfbb4d4650389a817f498724d
This commit is contained in:
commit
1dfbca08a1
528 files changed
+131054
No files matched your search
@@ -0,0 +1,17 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
const source = readFileSync(new URL("./appopsClient.ts", import.meta.url), "utf8");
|
||||
const liveClientSource = readFileSync(new URL("./liveClient.ts", import.meta.url), "utf8");
|
||||
|
||||
test("appops client is live-only and never instantiates mock data", () => {
|
||||
assert.match(source, /createLiveClient/);
|
||||
assert.match(source, /mode\(\)[\s\S]+live/);
|
||||
assert.doesNotMatch(source, /createMockClient|mockClient|mockData|aiMockData|VITE_APPOPS_API_MODE|API_MODE|liveOnly|withFallback/);
|
||||
});
|
||||
|
||||
test("browser client never embeds the Home Assistant voice token", () => {
|
||||
assert.doesNotMatch(liveClientSource, /VITE_APPOPS_VOICE_TOKEN|voiceToken|\/api\/voice\//);
|
||||
assert.match(liveClientSource, /\/api\/operator\/voice\//);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { createLiveClient } from './liveClient';
|
||||
|
||||
const liveClient = createLiveClient();
|
||||
|
||||
export const appopsClient = {
|
||||
...liveClient,
|
||||
get mode() {
|
||||
return 'live' as const;
|
||||
},
|
||||
};
|
||||
|
||||
export type AppopsClient = typeof appopsClient;
|
||||
@@ -0,0 +1,818 @@
|
||||
import { ActionID, ActionPlan, ActionRegistryDetail, ActionRegistryResponse, ActionResult, ApprovalRequest, AiMemoryItem, AiRecommendation, App, AuditDetail, AuditEvent, Connector, ConnectorBinding, ConnectorCandidate, ConnectorSettings, ConnectorSettingsPayload, DeepHealth, HealthCheck, HomeAssistantExport, HomeAssistantVoiceReadiness, Incident, JobRun, NetworkExposure, PreflightPreview, RuntimeInfo, SetupCompleteResult, SetupConnectionTestResult, SetupReconcileResult, SetupStatus, Stack, StorageSummary, Container, ContainerLogsResponse, StatisticsSummary, SystemSummary, UnraidSummary, VoiceCommand, SourceType, PolicyRule, Playbook, PolicyDecision, RiskLevel, AiToolPermission, Status, SonarrAddSeriesPayload, SonarrHealth, SonarrQueueSummary, SonarrSearchResponse, SonarrWantedSummary, RadarrAddMoviePayload, RadarrHealth, RadarrQueueSummary, RadarrSearchResponse, RadarrWantedSummary, InventoryDiscoveryResult, InventoryDriftItem, InventoryMapExistingPayload, InventoryReviewUpdatePayload, InventoryRunSummary, DiscoveredContainer, AppContainerMapping, WidefrogActivity, WidefrogDownloadStatus, WidefrogFailedJobs, WidefrogHealth, WidefrogQueueSummary, SecretsResponse, SqliteBackupCreateResult, SqliteBackupsResponse, SqliteBackupVerifyResult, SqliteCheckpointResult, SqliteMaintenanceStatus, ProwlarrApplicationStatus, ProwlarrHealth, ProwlarrIndexerHealth, ProwlarrIndexerStatus, ProwlarrSearchTest, ProwlarrSyncStatus, DelugeDownloadSummary, DelugeHealth, DelugeSessionStatus, DelugeTorrentStatus, DelugeSpeedStatus, PlexHealth, PlexLibraryStatus, PlexRecentMedia, PlexServerStatus, PlexSessionStatus, PlexStreamSummary, TautulliActivity, TautulliHealth, TautulliHistory, TautulliLibraries, TautulliPlexInfo, TautulliRecentlyAdded, TautulliStatus, TautulliStreamSummary, TautulliUsers, TautulliWatchStats, NpmAccessLists, NpmCertificates, NpmExposureSummary, NpmHealth, NpmHostReachability, NpmProxyHosts, NpmSecurityWarnings, AuthentikApplication, AuthentikAuthCoverage, AuthentikCollection, AuthentikEvent, AuthentikFlowsSummary, AuthentikHealth, AuthentikOAuth2Provider, AuthentikOutpost, AuthentikOutpostHealth, AuthentikPoliciesSummary, AuthentikProvider, AuthentikProxyProvider, AuthentikSecurityWarnings, AuthentikSystemStatus, MediaPipelineActivity, MediaPipelineCheckResult, MediaPipelineDiagnosis, MediaPipelineDownloads, MediaPipelineFlow, MediaPipelineIssues, MediaPipelineLibrary, MediaPipelineRecommendations, MediaPipelineSummary, VoiceIntentTestResult } from '../types/appops';
|
||||
import { AISummary, AIRecommendation, AIChatMessage, AIAutopilotRule, AITool } from '../types/ai';
|
||||
import { toIsoString } from '@/lib/backendDateTime';
|
||||
import type { AiSecurityFinding, AiSecurityRecommendation, AiSecurityReviewResult, AiSecurityScope, AiSecuritySummary, NotificationChannel, NotificationEvaluationResult, NotificationEvent, NotificationRule, NotificationSummary, RemediationApprovalProposal, RemediationDryRunResult, RemediationPlan, RemediationPlanCollection, SafetyDashboard, SafetyFinding, SafetyRecommendation, SafetyScoreBreakdown, SafetySection, SafetySummary, UnraidDisksResponse, UnraidDockerResponse, UnraidSharesResponse, UnraidVmCapabilitiesResponse, UnraidVmsResponse } from '../types/appops';
|
||||
import type { GiteaActionsStatus, IntegrationStatus, OperatorAuthPolicy, OperatorSessionStatus, RagQueryResult, RagStatus, RunnerFleetStatus } from '../types/appops';
|
||||
import type { DeliveryStatus, DeploymentJob, Enrollment, EnrollmentCapabilities, WorkMeshPromotion } from '../types/appops';
|
||||
|
||||
type HealthStatus = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
type NotificationEventPageOptions = {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
includeDismissed?: boolean;
|
||||
};
|
||||
|
||||
const notificationEventPath = (path: string, options: NotificationEventPageOptions = {}): string => {
|
||||
const params = new URLSearchParams();
|
||||
if (options.limit !== undefined) params.set('limit', String(options.limit));
|
||||
if (options.offset !== undefined) params.set('offset', String(options.offset));
|
||||
if (options.includeDismissed !== undefined) params.set('include_dismissed', String(options.includeDismissed));
|
||||
const query = params.toString();
|
||||
return query ? `${path}?${query}` : path;
|
||||
};
|
||||
|
||||
export interface LiveClientConfig {
|
||||
baseUrl: string;
|
||||
requestTimeoutMs: number;
|
||||
}
|
||||
|
||||
const defaultConfig: LiveClientConfig = {
|
||||
baseUrl: (import.meta.env.VITE_APPOPS_API_BASE_URL || '').replace(/\/$/, ''),
|
||||
requestTimeoutMs: 12000,
|
||||
};
|
||||
|
||||
const safetyConfig: LiveClientConfig = {
|
||||
...defaultConfig,
|
||||
requestTimeoutMs: 30000,
|
||||
};
|
||||
|
||||
const containerListConfig: LiveClientConfig = {
|
||||
...defaultConfig,
|
||||
requestTimeoutMs: 30000,
|
||||
};
|
||||
|
||||
const containerStatsConfig: LiveClientConfig = {
|
||||
...defaultConfig,
|
||||
requestTimeoutMs: 45000,
|
||||
};
|
||||
|
||||
const inventoryConfig: LiveClientConfig = {
|
||||
...defaultConfig,
|
||||
requestTimeoutMs: 30000,
|
||||
};
|
||||
|
||||
const mediaPipelineConfig: LiveClientConfig = {
|
||||
...defaultConfig,
|
||||
requestTimeoutMs: 45000,
|
||||
};
|
||||
|
||||
const REDACT_PATTERNS: RegExp[] = [
|
||||
/Bearer\s+\S+/gi,
|
||||
/\b(?:token|password|api[_-]?key)\s*[:=]\s*[^\s"'`]+/gi,
|
||||
];
|
||||
|
||||
const redactText = (input: string): string => {
|
||||
let value = input;
|
||||
for (const pattern of REDACT_PATTERNS) {
|
||||
value = value.replace(pattern, (match) => `${match.split(':')[0].split('=')[0]} [REDACTED]`);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const friendlyBackendError = (status: number, message: string): string => {
|
||||
if (status === 401) {
|
||||
return "Operatorautorisatie ontbreekt of is verlopen. Meld opnieuw aan via de accountproxy of ontgrendel dit tabblad via Instellingen → Beveiliging.";
|
||||
}
|
||||
if (status === 403) {
|
||||
return "Backend autorisatie is geweigerd.";
|
||||
}
|
||||
return `Backend ${status}: ${redactText(message || "Request failed")}`;
|
||||
};
|
||||
|
||||
const request = async <T>(
|
||||
path: string,
|
||||
config: LiveClientConfig,
|
||||
options: RequestInit = {},
|
||||
): Promise<T> => {
|
||||
const url = `${config.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), config.requestTimeoutMs);
|
||||
|
||||
const headers = new Headers(options.headers || {});
|
||||
headers.set('Content-Type', 'application/json');
|
||||
const method = (options.method || 'GET').toUpperCase();
|
||||
if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) {
|
||||
headers.set('X-AppOps-Session-CSRF', '1');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include',
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
throw new Error(friendlyBackendError(response.status, message || response.statusText));
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return null as T;
|
||||
}
|
||||
|
||||
return (await response.json()) as T;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(redactText(error.message));
|
||||
}
|
||||
throw new Error('Onbekende netwerkfout in backend aanvraag.');
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
};
|
||||
|
||||
const toRisk = (risk?: unknown): RiskLevel => (
|
||||
risk === 'low' || risk === 'medium' || risk === 'high' || risk === 'critical'
|
||||
? risk
|
||||
: 'low'
|
||||
) as RiskLevel;
|
||||
|
||||
const toPolicyDecision = (decision?: unknown): PolicyDecision => (
|
||||
decision === 'allowed' || decision === 'approval_required' || decision === 'blocked'
|
||||
? decision
|
||||
: 'blocked'
|
||||
) as PolicyDecision;
|
||||
|
||||
const toStringArray = (value: unknown): string[] => (
|
||||
Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []
|
||||
);
|
||||
|
||||
const toAuditStatus = (value: unknown): Status => {
|
||||
if (value === 'queued') {
|
||||
return 'running';
|
||||
}
|
||||
|
||||
if (value === 'succeeded') {
|
||||
return 'success';
|
||||
}
|
||||
|
||||
if (value === 'running' || value === 'failed' || value === 'success' || value === 'missing_config'
|
||||
|| value === 'blocked' || value === 'acknowledged' || value === 'muted' || value === 'resolved'
|
||||
|| value === 'warning' || value === 'healthy' || value === 'critical' || value === 'starting'
|
||||
|| value === 'stopped' || value === 'unhealthy') {
|
||||
return value;
|
||||
}
|
||||
|
||||
return 'failed';
|
||||
};
|
||||
|
||||
const normalizePolicyRule = (raw: Record<string, unknown>): PolicyRule => ({
|
||||
id: String(raw.id ?? `${raw.action}_${raw.target}`),
|
||||
action: String(raw.action ?? ''),
|
||||
target: String(raw.target ?? ''),
|
||||
voice: toPolicyDecision(raw.voice),
|
||||
manual: toPolicyDecision(raw.manual),
|
||||
confirmation: Boolean(raw.confirmation),
|
||||
risk: toRisk(raw.risk),
|
||||
reason: typeof raw.reason === 'string' ? raw.reason : undefined,
|
||||
});
|
||||
|
||||
const normalizeConnector = (raw: Record<string, unknown>): Connector => ({
|
||||
id: String(raw.id ?? ''),
|
||||
name: String(raw.name ?? ''),
|
||||
status: (raw.status === 'connected' || raw.status === 'disconnected' || raw.status === 'error' || raw.status === 'missing_config')
|
||||
? raw.status
|
||||
: 'disconnected',
|
||||
lastTest: typeof raw.last_test === 'string'
|
||||
? raw.last_test
|
||||
: typeof raw.lastTest === 'string'
|
||||
? raw.lastTest
|
||||
: 'never',
|
||||
capabilities: toStringArray(raw.capabilities),
|
||||
type: typeof raw.type === 'string' ? raw.type : 'Infrastructure',
|
||||
endpoint: typeof raw.endpoint === 'string' ? raw.endpoint : undefined,
|
||||
secretRef: typeof raw.secret_ref === 'string' ? raw.secret_ref : (
|
||||
typeof raw.secretRef === 'string' ? raw.secretRef : undefined
|
||||
),
|
||||
});
|
||||
|
||||
const normalizeApproval = (raw: Record<string, unknown>): ApprovalRequest => {
|
||||
const target = String(raw.target ?? '');
|
||||
const action = String(raw.action_id ?? raw.action ?? '');
|
||||
const source = raw.source === 'voice' || raw.source === 'manual' || raw.source === 'automation' || raw.source === 'system'
|
||||
? raw.source
|
||||
: 'manual';
|
||||
const createdAt = raw.created_at ?? raw.timestamp;
|
||||
const expiresAt = raw.expires_at ?? raw.expiresAt;
|
||||
const reason = typeof raw.reason === 'string' ? raw.reason : '';
|
||||
return {
|
||||
id: String(raw.id ?? ''),
|
||||
action,
|
||||
target,
|
||||
requester: typeof raw.requester === 'string' ? raw.requester : 'system',
|
||||
source,
|
||||
risk: toRisk(raw.risk),
|
||||
timestamp: toIsoString(createdAt),
|
||||
expiresAt: toIsoString(expiresAt),
|
||||
context: reason || `${action} op ${target}`,
|
||||
affectedApps: toStringArray(raw.affected_apps ?? raw.affectedApps).length
|
||||
? toStringArray(raw.affected_apps ?? raw.affectedApps)
|
||||
: [target].filter(Boolean),
|
||||
reason,
|
||||
plan: toStringArray(raw.plan ?? raw.execution_plan),
|
||||
status: raw.status === 'approved' || raw.status === 'rejected' || raw.status === 'expired' || raw.status === 'executed'
|
||||
? raw.status
|
||||
: 'pending',
|
||||
auditId: typeof raw.audit_event_id === 'string' ? raw.audit_event_id : (
|
||||
typeof raw.audit_id === 'string' ? raw.audit_id : (
|
||||
typeof raw.auditId === 'string' ? raw.auditId : undefined
|
||||
)
|
||||
),
|
||||
jobId: typeof raw.job_id === 'string' ? raw.job_id : (
|
||||
typeof raw.jobId === 'string' ? raw.jobId : undefined
|
||||
),
|
||||
executionSupported: raw.executionSupported === true || raw.execution_supported === true,
|
||||
executionReason: typeof raw.executionReason === 'string' ? raw.executionReason : (
|
||||
typeof raw.execution_reason === 'string' ? raw.execution_reason : undefined
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeAudit = (raw: Record<string, unknown>): AuditEvent => {
|
||||
const details: AuditDetail = {
|
||||
reason: typeof raw.reason === 'string' ? raw.reason : undefined,
|
||||
beforeState: raw.before_state ? JSON.stringify(raw.before_state) : undefined,
|
||||
afterState: raw.after_state ? JSON.stringify(raw.after_state) : undefined,
|
||||
metadata: raw.redacted_metadata && typeof raw.redacted_metadata === 'object'
|
||||
? raw.redacted_metadata as Record<string, unknown>
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const executionPlan = raw.execution_plan;
|
||||
if (executionPlan) {
|
||||
if (Array.isArray(executionPlan)) {
|
||||
details.plan = executionPlan.filter((step): step is string => typeof step === 'string');
|
||||
} else if (typeof executionPlan === 'object' && executionPlan !== null && Array.isArray((executionPlan as Record<string, unknown>).plan)) {
|
||||
const nestedExecutionPlan = (executionPlan as Record<string, unknown>).plan;
|
||||
details.plan = Array.isArray(nestedExecutionPlan)
|
||||
? nestedExecutionPlan.filter((step): step is string => typeof step === 'string')
|
||||
: [];
|
||||
} else if (typeof executionPlan === 'string') {
|
||||
details.plan = [executionPlan];
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(raw.post_checks)) {
|
||||
details.postChecks = raw.post_checks.filter((item): item is string => typeof item === 'string');
|
||||
} else if (Array.isArray(raw.postChecks)) {
|
||||
details.postChecks = raw.postChecks.filter((item): item is string => typeof item === 'string');
|
||||
}
|
||||
|
||||
return {
|
||||
id: String(raw.id ?? ''),
|
||||
time: toIsoString(raw.timestamp),
|
||||
source: raw.source === 'manual' || raw.source === 'voice' || raw.source === 'automation' || raw.source === 'system'
|
||||
? raw.source
|
||||
: 'manual',
|
||||
user: typeof raw.identity === 'string' ? raw.identity : 'system',
|
||||
action: String(raw.action_id ?? raw.action ?? ''),
|
||||
target: String(raw.target ?? ''),
|
||||
connector: typeof raw.target_type === 'string' ? raw.target_type : 'system',
|
||||
risk: toRisk(raw.risk),
|
||||
policy: toPolicyDecision(raw.policy_decision),
|
||||
result: toAuditStatus(raw.result),
|
||||
duration: typeof raw.duration_ms === 'number' ? `${raw.duration_ms}ms` : undefined,
|
||||
details,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeJob = (raw: Record<string, unknown>): JobRun => {
|
||||
const durationMs = typeof raw.duration_ms === 'number' ? raw.duration_ms : undefined;
|
||||
return {
|
||||
id: String(raw.id ?? ''),
|
||||
type: typeof raw.type === 'string' ? raw.type : 'job',
|
||||
status: (raw.status === 'queued' || raw.status === 'running' || raw.status === 'succeeded' || raw.status === 'failed' || raw.status === 'cancelled')
|
||||
? raw.status
|
||||
: 'queued',
|
||||
source: raw.source === 'manual' || raw.source === 'voice' || raw.source === 'automation' || raw.source === 'system'
|
||||
? raw.source
|
||||
: 'manual',
|
||||
actionId: String(raw.action_id ?? raw.actionId ?? ''),
|
||||
target: String(raw.target ?? ''),
|
||||
risk: toRisk(raw.risk),
|
||||
policyDecision: toPolicyDecision(raw.policy_decision),
|
||||
stepsJson: typeof raw.steps_json === 'string' ? raw.steps_json : JSON.stringify(raw.steps_json ?? []),
|
||||
progress: typeof raw.progress === 'number' ? raw.progress : 0,
|
||||
startedAt: typeof raw.started_at === 'string' || typeof raw.started_at === 'number'
|
||||
? toIsoString(raw.started_at)
|
||||
: toIsoString(raw.startedAt),
|
||||
finishedAt: typeof raw.finished_at === 'string' || typeof raw.finished_at === 'number'
|
||||
? toIsoString(raw.finished_at)
|
||||
: typeof raw.finishedAt === 'string' || typeof raw.finishedAt === 'number'
|
||||
? toIsoString(raw.finishedAt)
|
||||
: undefined,
|
||||
durationMs,
|
||||
auditEventId: typeof raw.audit_event_id === 'string' ? raw.audit_event_id : (
|
||||
typeof raw.auditEventId === 'string' ? raw.auditEventId : undefined
|
||||
),
|
||||
resultSummary: typeof raw.result_summary === 'string' ? raw.result_summary : (
|
||||
typeof raw.resultSummary === 'string' ? raw.resultSummary : undefined
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const post = <T>(path: string, body: unknown, config: LiveClientConfig) =>
|
||||
request<T>(path, config, { method: 'POST', body: JSON.stringify(body) });
|
||||
|
||||
const patch = <T>(path: string, body: unknown, config: LiveClientConfig) =>
|
||||
request<T>(path, config, { method: 'PATCH', body: JSON.stringify(body) });
|
||||
|
||||
const get = <T>(path: string, config: LiveClientConfig) =>
|
||||
request<T>(path, config, { method: 'GET' });
|
||||
|
||||
export const createLiveClient = () => ({
|
||||
async getApps(includeDiscovered = false): Promise<App[]> { return get<App[]>(`/api/apps${includeDiscovered ? '?include_discovered=true' : ''}`, defaultConfig); },
|
||||
async getApp(id: string): Promise<App | undefined> { return get<App>(`/api/apps/${id}`, defaultConfig); },
|
||||
async updateAppMetadata(id: string, payload: Pick<App, "name" | "category" | "criticality">): Promise<ActionResult & { app?: App }> {
|
||||
return request<ActionResult & { app?: App }>(`/api/apps/${encodeURIComponent(id)}/metadata`, defaultConfig, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
},
|
||||
async getContainers(includeStats = false, statsLimit = 250): Promise<Container[]> {
|
||||
const query = includeStats ? `?include_stats=true&stats_limit=${Math.max(1, Math.min(statsLimit, 500))}` : '';
|
||||
return get<Container[]>(`/api/containers${query}`, includeStats ? containerStatsConfig : containerListConfig);
|
||||
},
|
||||
async getContainerLogs(id: string): Promise<ContainerLogsResponse> {
|
||||
return get<ContainerLogsResponse>(`/api/containers/${encodeURIComponent(id)}/logs`, defaultConfig);
|
||||
},
|
||||
async getStacks(): Promise<Stack[]> { return get<Stack[]>('/api/stacks', defaultConfig); },
|
||||
async getDeploymentJobs(): Promise<{ items: DeploymentJob[] }> { return get<{ items: DeploymentJob[] }>('/api/deployments', defaultConfig); },
|
||||
async getWorkMeshPromotions(limit = 20): Promise<{ items: WorkMeshPromotion[] }> {
|
||||
return get<{ items: WorkMeshPromotion[] }>(`/api/deployments/workmesh/promotions?limit=${Math.max(1, Math.min(limit, 50))}`, defaultConfig);
|
||||
},
|
||||
async getWorkMeshPromotion(id: string): Promise<WorkMeshPromotion> {
|
||||
return get<WorkMeshPromotion>(`/api/deployments/workmesh/promotions/${encodeURIComponent(id)}`, defaultConfig);
|
||||
},
|
||||
async requestWorkMeshPromotionPreflight(revision: string, idempotencyKey: string): Promise<WorkMeshPromotion> {
|
||||
return request<WorkMeshPromotion>('/api/deployments/workmesh/promotions/preflight', defaultConfig, {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': idempotencyKey },
|
||||
body: JSON.stringify({ revision }),
|
||||
});
|
||||
},
|
||||
async getDeliveryStatus(): Promise<DeliveryStatus> { return get<DeliveryStatus>('/api/deployments/delivery-status', defaultConfig); },
|
||||
async getEnrollmentCapabilities(): Promise<EnrollmentCapabilities> { return get<EnrollmentCapabilities>('/api/enrollments/capabilities', defaultConfig); },
|
||||
async getEnrollments(): Promise<{ items: Enrollment[] }> { return get<{ items: Enrollment[] }>('/api/enrollments', defaultConfig); },
|
||||
async getSystemSummary(): Promise<SystemSummary> { return get<SystemSummary>('/api/system/summary', defaultConfig); },
|
||||
async getHealth(): Promise<HealthStatus> { return get<HealthStatus>('/api/health', defaultConfig); },
|
||||
async getRuntimeInfo(): Promise<RuntimeInfo> { return get<RuntimeInfo>('/api/runtime/info', defaultConfig); },
|
||||
async getOperatorSession(): Promise<OperatorSessionStatus> { return get<OperatorSessionStatus>('/api/operator/session', defaultConfig); },
|
||||
async getOperatorAuthPolicy(): Promise<OperatorAuthPolicy> { return get<OperatorAuthPolicy>('/api/runtime/operator-auth', defaultConfig); },
|
||||
async updateOperatorAuthPolicy(required: boolean): Promise<OperatorAuthPolicy> {
|
||||
return request<OperatorAuthPolicy>('/api/runtime/operator-auth', defaultConfig, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ required }),
|
||||
});
|
||||
},
|
||||
async unlockOperatorSession(token: string): Promise<OperatorSessionStatus> {
|
||||
return request<OperatorSessionStatus>('/api/operator/session', defaultConfig, {
|
||||
method: 'POST',
|
||||
headers: { 'X-AppOps-Operator-Token': token },
|
||||
body: '{}',
|
||||
});
|
||||
},
|
||||
async lockOperatorSession(): Promise<OperatorSessionStatus> {
|
||||
return request<OperatorSessionStatus>('/api/operator/session', defaultConfig, { method: 'DELETE' });
|
||||
},
|
||||
async getDeepHealth(): Promise<DeepHealth> { return get<DeepHealth>('/api/health/deep', defaultConfig); },
|
||||
async exportAdminData(kind: 'config' | 'policies' | 'inventory' | 'audit-summary'): Promise<Record<string, unknown>> {
|
||||
return get<Record<string, unknown>>(`/api/admin/export/${kind}`, defaultConfig);
|
||||
},
|
||||
async listSqliteBackups(): Promise<SqliteBackupsResponse> { return get<SqliteBackupsResponse>('/api/admin/backups/sqlite', defaultConfig); },
|
||||
async getSqliteMaintenanceStatus(): Promise<SqliteMaintenanceStatus> { return get<SqliteMaintenanceStatus>('/api/admin/database/status', defaultConfig); },
|
||||
async checkpointSqlite(): Promise<SqliteCheckpointResult> { return post<SqliteCheckpointResult>('/api/admin/database/checkpoint', {}, defaultConfig); },
|
||||
async createSqliteBackup(): Promise<SqliteBackupCreateResult> { return post<SqliteBackupCreateResult>('/api/admin/backups/sqlite', {}, defaultConfig); },
|
||||
async verifySqliteBackup(name: string): Promise<SqliteBackupVerifyResult> {
|
||||
return post<SqliteBackupVerifyResult>(`/api/admin/backups/sqlite/${encodeURIComponent(name)}/verify`, {}, defaultConfig);
|
||||
},
|
||||
async getUnraidSummary(): Promise<UnraidSummary> { return get<UnraidSummary>('/api/unraid/summary', defaultConfig); },
|
||||
async getIncidents(): Promise<Incident[]> { return get<Incident[]>('/api/incidents', defaultConfig); },
|
||||
async getAuditLogs(limit = 200, offset = 0): Promise<AuditEvent[]> {
|
||||
const response = await get<Array<Record<string, unknown>>>(`/api/audit?limit=${Math.max(1, Math.min(limit, 500))}&offset=${Math.max(0, offset)}`, defaultConfig);
|
||||
return response.map(normalizeAudit);
|
||||
},
|
||||
async getPolicies(): Promise<PolicyRule[]> {
|
||||
const response = await get<Array<Record<string, unknown>>>('/api/policies', defaultConfig);
|
||||
return response.map(normalizePolicyRule);
|
||||
},
|
||||
async updatePolicy(id: string, payload: Pick<PolicyRule, 'voice' | 'manual' | 'risk'> & { reason: string }): Promise<PolicyRule> {
|
||||
const response = await request<Record<string, unknown>>(`/api/policies/${encodeURIComponent(id)}`, defaultConfig, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
return normalizePolicyRule(response);
|
||||
},
|
||||
async getConnectors(): Promise<Connector[]> {
|
||||
const response = await get<Array<Record<string, unknown>>>('/api/connectors?refresh=true', defaultConfig);
|
||||
return response.map(normalizeConnector);
|
||||
},
|
||||
async getSetupStatus(): Promise<SetupStatus> { return get<SetupStatus>('/api/setup/status', defaultConfig); },
|
||||
async completeSetup(): Promise<SetupCompleteResult> {
|
||||
return post<SetupCompleteResult>('/api/setup/complete', {}, defaultConfig);
|
||||
},
|
||||
async reconcileSetup(): Promise<SetupReconcileResult> {
|
||||
return post<SetupReconcileResult>('/api/setup/reconcile', {}, defaultConfig);
|
||||
},
|
||||
async markSetupComplete(): Promise<SetupCompleteResult> {
|
||||
return post<SetupCompleteResult>('/api/setup/mark-complete', { confirm: 'MARK_SETUP_COMPLETE' }, defaultConfig);
|
||||
},
|
||||
async markVoiceConfigured(): Promise<SetupCompleteResult> {
|
||||
return post<SetupCompleteResult>('/api/setup/voice/mark-configured', {}, defaultConfig);
|
||||
},
|
||||
async getConnectorSettings(): Promise<ConnectorSettings[]> { return get<ConnectorSettings[]>('/api/settings/connectors', defaultConfig); },
|
||||
async getSecrets(): Promise<SecretsResponse> { return get<SecretsResponse>('/api/settings/secrets', defaultConfig); },
|
||||
async updateConnectorSettings(id: string, payload: ConnectorSettingsPayload): Promise<ConnectorSettings> {
|
||||
return request<ConnectorSettings>(`/api/settings/connectors/${encodeURIComponent(id)}`, defaultConfig, { method: 'PATCH', body: JSON.stringify(payload) });
|
||||
},
|
||||
async testSetupConnector(id: string): Promise<SetupConnectionTestResult> {
|
||||
if (id === 'portainer' || id === 'unraid') {
|
||||
return post<SetupConnectionTestResult>(`/api/setup/test/${id}`, {}, defaultConfig);
|
||||
}
|
||||
return post<SetupConnectionTestResult>(`/api/setup/test/connector/${encodeURIComponent(id)}`, {}, defaultConfig);
|
||||
},
|
||||
async getConnectorCandidates(id: string): Promise<ConnectorCandidate[]> {
|
||||
return get<ConnectorCandidate[]>(`/api/connectors/${encodeURIComponent(id)}/candidates`, defaultConfig);
|
||||
},
|
||||
async getConnectorBindings(id: string): Promise<ConnectorBinding[]> {
|
||||
return get<ConnectorBinding[]>(`/api/connectors/${encodeURIComponent(id)}/bindings`, defaultConfig);
|
||||
},
|
||||
async bindConnector(id: string, discoveredContainerId: string): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/connectors/${encodeURIComponent(id)}/bind`, { discoveredContainerId }, defaultConfig);
|
||||
},
|
||||
async unbindConnector(id: string, appId: string): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/connectors/${encodeURIComponent(id)}/unbind`, { appId }, defaultConfig);
|
||||
},
|
||||
async getPlaybooks(): Promise<Playbook[]> { return get<Playbook[]>('/api/playbooks', defaultConfig); },
|
||||
async getApprovals(): Promise<ApprovalRequest[]> {
|
||||
const response = await get<Array<Record<string, unknown>>>('/api/approvals', defaultConfig);
|
||||
return response.map(normalizeApproval);
|
||||
},
|
||||
async cleanupExpiredApprovals(): Promise<ActionResult & { dismissedCount?: number }> {
|
||||
return post<ActionResult & { dismissedCount?: number }>('/api/approvals/cleanup', {}, defaultConfig);
|
||||
},
|
||||
async getVoiceCommands(): Promise<VoiceCommand[]> { return get<VoiceCommand[]>('/api/operator/voice/commands', defaultConfig); },
|
||||
async getHomeAssistantExport(): Promise<HomeAssistantExport> { return get<HomeAssistantExport>('/api/operator/voice/home-assistant/export', defaultConfig); },
|
||||
async getHomeAssistantVoiceReadiness(): Promise<HomeAssistantVoiceReadiness> {
|
||||
return get<HomeAssistantVoiceReadiness>('/api/operator/voice/home-assistant/readiness', defaultConfig);
|
||||
},
|
||||
async testVoiceIntent(input: string): Promise<VoiceIntentTestResult> {
|
||||
return post<VoiceIntentTestResult>('/api/operator/voice/test-intent', { input }, defaultConfig);
|
||||
},
|
||||
async dryRunVoiceCommand(id: string): Promise<VoiceIntentTestResult> {
|
||||
return post<VoiceIntentTestResult>(`/api/operator/voice/commands/${encodeURIComponent(id)}/dry-run`, {}, defaultConfig);
|
||||
},
|
||||
async getActionRegistry(): Promise<ActionRegistryResponse> {
|
||||
return get<ActionRegistryResponse>('/api/actions/registry', defaultConfig);
|
||||
},
|
||||
async getActionRegistryDetail(id: string): Promise<ActionRegistryDetail> {
|
||||
return get<ActionRegistryDetail>(`/api/actions/registry/${encodeURIComponent(id)}`, defaultConfig);
|
||||
},
|
||||
async getActionPlan(input: string, source: SourceType): Promise<ActionPlan | null> {
|
||||
const response = await post<{ intent?: string; target?: string; source?: SourceType; risk?: string; policy?: string; reason?: string; plan?: string[]; postChecks?: string[] }>(
|
||||
'/api/actions/plan',
|
||||
{ input, source },
|
||||
defaultConfig
|
||||
);
|
||||
if (!response.intent || !response.target) {
|
||||
return null;
|
||||
}
|
||||
const risk = response.risk;
|
||||
const policy = response.policy;
|
||||
|
||||
const mappedRisk = (risk === 'low' || risk === 'medium' || risk === 'high' || risk === 'critical') ? (risk as RiskLevel) : 'low';
|
||||
const mappedPolicy = (policy === 'allowed' || policy === 'approval_required' || policy === 'blocked') ? (policy as PolicyDecision) : 'blocked';
|
||||
|
||||
return {
|
||||
intent: response.intent as ActionID,
|
||||
target: response.target,
|
||||
source: response.source || source,
|
||||
risk: mappedRisk,
|
||||
policy: mappedPolicy,
|
||||
reason: response.reason,
|
||||
plan: response.plan || [],
|
||||
postChecks: response.postChecks || [],
|
||||
};
|
||||
},
|
||||
async evaluatePolicy(action: string, target: string, source: string): Promise<{ decision: string; reason: string }> {
|
||||
return post<{ decision: string; reason: string }>('/api/policies/evaluate', { action, target, source }, defaultConfig);
|
||||
},
|
||||
async executeAction(plan: ActionPlan): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/actions/execute', { plan }, defaultConfig);
|
||||
},
|
||||
async restartContainer(appId: string): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/actions/container/${encodeURIComponent(appId)}/restart`, {}, defaultConfig);
|
||||
},
|
||||
async requestApproval(plan: ActionPlan): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/approvals', { action: plan.intent, target: plan.target, source: plan.source, risk: plan.risk, reason: plan.reason, plan: plan.plan }, defaultConfig);
|
||||
},
|
||||
async updateApprovalStatus(id: string, status: 'approved' | 'rejected'): Promise<ActionResult> {
|
||||
return patch<ActionResult>(`/api/approvals/${encodeURIComponent(id)}`, { status }, defaultConfig);
|
||||
},
|
||||
async executeApproval(id: string): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/approvals/${encodeURIComponent(id)}/execute`, {}, defaultConfig);
|
||||
},
|
||||
async updateIncidentStatus(id: string, status: string): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/incidents/${id}/status`, { status }, defaultConfig);
|
||||
},
|
||||
async exportAudit(format: string): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/audit/export', { format }, defaultConfig);
|
||||
},
|
||||
async testConnector(id: string): Promise<ActionResult> { return post<ActionResult>(`/api/connectors/${id}/test`, {}, defaultConfig); },
|
||||
async getAISummary(): Promise<AISummary> { return get<AISummary>('/api/ai/status', defaultConfig); },
|
||||
async getAIRecommendations(): Promise<AIRecommendation[]> { return get<AIRecommendation[]>('/api/ai/recommendations', defaultConfig); },
|
||||
async getAIChatHistory(): Promise<AIChatMessage[]> { return get<AIChatMessage[]>('/api/ai/history', defaultConfig); },
|
||||
async getAIAutopilotRules(): Promise<AIAutopilotRule[]> { return get<AIAutopilotRule[]>('/api/ai/tools', defaultConfig); },
|
||||
async getAIMemory(): Promise<AiMemoryItem[]> { return get<AiMemoryItem[]>('/api/ai/memory', defaultConfig); },
|
||||
async getAITools(): Promise<AITool[]> { return get<AITool[]>('/api/ai/tools', defaultConfig); },
|
||||
async sendAIChatMessage(content: string): Promise<AIChatMessage> {
|
||||
return post<AIChatMessage>('/api/ai/chat', { content }, defaultConfig);
|
||||
},
|
||||
async getIntegrationStatus(): Promise<IntegrationStatus> {
|
||||
return get<IntegrationStatus>('/api/integrations/status', defaultConfig);
|
||||
},
|
||||
async getRagStatus(): Promise<RagStatus> {
|
||||
return get<RagStatus>('/api/ai/rag/status', defaultConfig);
|
||||
},
|
||||
async rebuildRagIndex(): Promise<RagStatus> {
|
||||
return post<RagStatus>('/api/ai/rag/reindex', {}, defaultConfig);
|
||||
},
|
||||
async queryRag(question: string, limit = 6): Promise<RagQueryResult> {
|
||||
return post<RagQueryResult>('/api/ai/rag/query', { question, limit }, defaultConfig);
|
||||
},
|
||||
async getAiSecurityStatus(): Promise<AiSecuritySummary> { return get<AiSecuritySummary>('/api/ai/security/status', defaultConfig); },
|
||||
async getAiSecuritySummary(): Promise<AiSecuritySummary> { return get<AiSecuritySummary>('/api/ai/security/summary', defaultConfig); },
|
||||
async getAiSecurityFindings(scope: AiSecurityScope = 'all'): Promise<AiSecurityFinding[]> {
|
||||
return get<AiSecurityFinding[]>(`/api/ai/security/findings?scope=${encodeURIComponent(scope)}`, defaultConfig);
|
||||
},
|
||||
async getAiSecurityFinding(id: string): Promise<AiSecurityFinding> {
|
||||
return get<AiSecurityFinding>(`/api/ai/security/findings/${encodeURIComponent(id)}`, defaultConfig);
|
||||
},
|
||||
async getAiSecurityRecommendations(): Promise<AiSecurityRecommendation[]> {
|
||||
return get<AiSecurityRecommendation[]>('/api/ai/security/recommendations', defaultConfig);
|
||||
},
|
||||
async runAiSecurityReview(scope: AiSecurityScope = 'all'): Promise<AiSecurityReviewResult> {
|
||||
return post<AiSecurityReviewResult>(`/api/ai/security/review/scope/${encodeURIComponent(scope)}`, {}, defaultConfig);
|
||||
},
|
||||
async acknowledgeAiSecurityFinding(id: string): Promise<{ success: boolean; status: string; finding?: AiSecurityFinding | null }> {
|
||||
return post<{ success: boolean; status: string; finding?: AiSecurityFinding | null }>(`/api/ai/security/findings/${encodeURIComponent(id)}/acknowledge`, {}, defaultConfig);
|
||||
},
|
||||
async dismissAiSecurityFinding(id: string): Promise<{ success: boolean; status: string; finding?: AiSecurityFinding | null }> {
|
||||
return post<{ success: boolean; status: string; finding?: AiSecurityFinding | null }>(`/api/ai/security/findings/${encodeURIComponent(id)}/dismiss`, {}, defaultConfig);
|
||||
},
|
||||
async snoozeAiSecurityRecommendation(id: string): Promise<{ success: boolean; status: string; applied: boolean; recommendation?: AiSecurityRecommendation | null }> {
|
||||
return post<{ success: boolean; status: string; applied: boolean; recommendation?: AiSecurityRecommendation | null }>(`/api/ai/security/recommendations/${encodeURIComponent(id)}/snooze`, {}, defaultConfig);
|
||||
},
|
||||
async dismissAiSecurityRecommendation(id: string): Promise<{ success: boolean; status: string; applied: boolean; recommendation?: AiSecurityRecommendation | null }> {
|
||||
return post<{ success: boolean; status: string; applied: boolean; recommendation?: AiSecurityRecommendation | null }>(`/api/ai/security/recommendations/${encodeURIComponent(id)}/dismiss`, {}, defaultConfig);
|
||||
},
|
||||
async acceptAiSecurityRecommendation(id: string): Promise<{ success: boolean; status: string; applied: boolean; recommendation?: AiSecurityRecommendation | null }> {
|
||||
return post<{ success: boolean; status: string; applied: boolean; recommendation?: AiSecurityRecommendation | null }>(`/api/ai/security/recommendations/${encodeURIComponent(id)}/accept`, {}, defaultConfig);
|
||||
},
|
||||
async getRemediationStatus(): Promise<Record<string, unknown>> { return get<Record<string, unknown>>('/api/ai/remediation/status', defaultConfig); },
|
||||
async getRemediationPlans(): Promise<RemediationPlanCollection> { return get<RemediationPlanCollection>('/api/ai/remediation/plans', defaultConfig); },
|
||||
async getRemediationPlan(id: string): Promise<RemediationPlan> { return get<RemediationPlan>(`/api/ai/remediation/plans/${encodeURIComponent(id)}`, defaultConfig); },
|
||||
async generateRemediationPlans(): Promise<RemediationPlanCollection> { return post<RemediationPlanCollection>('/api/ai/remediation/plans/generate', {}, defaultConfig); },
|
||||
async generateRemediationFromFinding(id: string): Promise<RemediationPlan> {
|
||||
return post<RemediationPlan>(`/api/ai/remediation/plans/from-finding/${encodeURIComponent(id)}`, {}, defaultConfig);
|
||||
},
|
||||
async generateRemediationFromRecommendation(id: string): Promise<RemediationPlan> {
|
||||
return post<RemediationPlan>(`/api/ai/remediation/plans/from-recommendation/${encodeURIComponent(id)}`, {}, defaultConfig);
|
||||
},
|
||||
async reviewRemediationPlan(id: string): Promise<{ success: boolean; status: string; plan?: RemediationPlan | null }> {
|
||||
return post<{ success: boolean; status: string; plan?: RemediationPlan | null }>(`/api/ai/remediation/plans/${encodeURIComponent(id)}/review`, {}, defaultConfig);
|
||||
},
|
||||
async dismissRemediationPlan(id: string): Promise<{ success: boolean; status: string; plan?: RemediationPlan | null }> {
|
||||
return post<{ success: boolean; status: string; plan?: RemediationPlan | null }>(`/api/ai/remediation/plans/${encodeURIComponent(id)}/dismiss`, {}, defaultConfig);
|
||||
},
|
||||
async createRemediationApprovalProposal(id: string): Promise<RemediationApprovalProposal> {
|
||||
return post<RemediationApprovalProposal>(`/api/ai/remediation/plans/${encodeURIComponent(id)}/create-approval-proposal`, {}, defaultConfig);
|
||||
},
|
||||
async dryRunRemediationPlan(id: string): Promise<RemediationDryRunResult> {
|
||||
return post<RemediationDryRunResult>(`/api/ai/remediation/plans/${encodeURIComponent(id)}/dry-run`, {}, defaultConfig);
|
||||
},
|
||||
async getStatisticsSummary(): Promise<StatisticsSummary> { return get<StatisticsSummary>('/api/statistics/summary', defaultConfig); },
|
||||
async getStorageSummary(): Promise<StorageSummary> { return get<StorageSummary>('/api/storage/summary', defaultConfig); },
|
||||
async getUnraidDisks(): Promise<UnraidDisksResponse> { return get<UnraidDisksResponse>('/api/unraid/disks', defaultConfig); },
|
||||
async getUnraidVms(): Promise<UnraidVmsResponse> { return get<UnraidVmsResponse>('/api/unraid/vms', defaultConfig); },
|
||||
async getUnraidVmCapabilities(): Promise<UnraidVmCapabilitiesResponse> { return get<UnraidVmCapabilitiesResponse>('/api/unraid/vms/capabilities', defaultConfig); },
|
||||
async runUnraidVmAction(vmId: string, action: string): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/unraid/vms/${encodeURIComponent(vmId)}/actions/${encodeURIComponent(action)}`, { source: 'manual', confirm: true }, defaultConfig);
|
||||
},
|
||||
async getUnraidShares(): Promise<UnraidSharesResponse> { return get<UnraidSharesResponse>('/api/unraid/shares', defaultConfig); },
|
||||
async getUnraidDocker(): Promise<UnraidDockerResponse> { return get<UnraidDockerResponse>('/api/unraid/docker', defaultConfig); },
|
||||
async getNetworkExposure(refresh = false): Promise<NetworkExposure[]> { return get<NetworkExposure[]>(`/api/network/exposure${refresh ? '?refresh=true' : ''}`, defaultConfig); },
|
||||
async getSafetyDashboard(refresh = false): Promise<SafetyDashboard> {
|
||||
return get<SafetyDashboard>(`/api/safety/dashboard${refresh ? '?refresh=true' : ''}`, safetyConfig);
|
||||
},
|
||||
async getSafetySummary(): Promise<SafetySummary> { return get<SafetySummary>('/api/safety/summary', safetyConfig); },
|
||||
async getSafetySections(): Promise<SafetySection[]> { return get<SafetySection[]>('/api/safety/sections', safetyConfig); },
|
||||
async getSafetyFindings(): Promise<SafetyFinding[]> { return get<SafetyFinding[]>('/api/safety/findings', safetyConfig); },
|
||||
async getSafetyRecommendations(): Promise<SafetyRecommendation[]> { return get<SafetyRecommendation[]>('/api/safety/recommendations', safetyConfig); },
|
||||
async getSafetyScoreBreakdown(): Promise<SafetyScoreBreakdown> { return get<SafetyScoreBreakdown>('/api/safety/score-breakdown', safetyConfig); },
|
||||
async refreshSafety(): Promise<SafetyDashboard> { return post<SafetyDashboard>('/api/safety/refresh', {}, safetyConfig); },
|
||||
async getNotificationSummary(): Promise<NotificationSummary> { return get<NotificationSummary>('/api/notifications/summary', defaultConfig); },
|
||||
async getNotificationRules(): Promise<NotificationRule[]> { return get<NotificationRule[]>('/api/notifications/rules', defaultConfig); },
|
||||
async createNotificationRule(payload: Partial<NotificationRule>): Promise<NotificationRule> { return post<NotificationRule>('/api/notifications/rules', payload, defaultConfig); },
|
||||
async updateNotificationRule(id: string, payload: Partial<NotificationRule>): Promise<NotificationRule> { return request<NotificationRule>(`/api/notifications/rules/${encodeURIComponent(id)}`, defaultConfig, { method: 'PATCH', body: JSON.stringify(payload) }); },
|
||||
async deleteNotificationRule(id: string): Promise<{ success: boolean; status: string }> { return request<{ success: boolean; status: string }>(`/api/notifications/rules/${encodeURIComponent(id)}`, defaultConfig, { method: 'DELETE' }); },
|
||||
async getNotificationEvents(options: NotificationEventPageOptions = {}): Promise<NotificationEvent[]> {
|
||||
return get<NotificationEvent[]>(notificationEventPath('/api/notifications/events', options), defaultConfig);
|
||||
},
|
||||
async getUnreadNotificationEvents(options: Omit<NotificationEventPageOptions, 'includeDismissed'> = {}): Promise<NotificationEvent[]> {
|
||||
return get<NotificationEvent[]>(notificationEventPath('/api/notifications/events/unread', options), defaultConfig);
|
||||
},
|
||||
async dismissNotificationEvent(id: string): Promise<NotificationEvent & { success: boolean }> { return post<NotificationEvent & { success: boolean }>(`/api/notifications/events/${encodeURIComponent(id)}/dismiss`, {}, defaultConfig); },
|
||||
async markNotificationRead(id: string): Promise<NotificationEvent & { success: boolean }> { return post<NotificationEvent & { success: boolean }>(`/api/notifications/events/${encodeURIComponent(id)}/mark-read`, {}, defaultConfig); },
|
||||
async markAllNotificationsRead(): Promise<{ success: boolean; updated: number }> { return post<{ success: boolean; updated: number }>('/api/notifications/events/mark-all-read', {}, defaultConfig); },
|
||||
async getNotificationChannels(): Promise<NotificationChannel[]> { return get<NotificationChannel[]>('/api/notifications/channels', defaultConfig); },
|
||||
async createNotificationChannel(payload: Partial<NotificationChannel>): Promise<NotificationChannel> { return post<NotificationChannel>('/api/notifications/channels', payload, defaultConfig); },
|
||||
async updateNotificationChannel(id: string, payload: Partial<NotificationChannel>): Promise<NotificationChannel> { return request<NotificationChannel>(`/api/notifications/channels/${encodeURIComponent(id)}`, defaultConfig, { method: 'PATCH', body: JSON.stringify(payload) }); },
|
||||
async testNotificationChannel(id: string): Promise<{ success: boolean; status: string; channel?: NotificationChannel }> { return post<{ success: boolean; status: string; channel?: NotificationChannel }>(`/api/notifications/channels/${encodeURIComponent(id)}/test`, {}, defaultConfig); },
|
||||
async testNotificationChannelConnection(id: string): Promise<{ success: boolean; status: string; channel?: NotificationChannel }> { return post<{ success: boolean; status: string; channel?: NotificationChannel }>(`/api/notifications/channels/${encodeURIComponent(id)}/test-connection`, {}, defaultConfig); },
|
||||
async evaluateNotifications(source: 'all' | 'safety' | 'media' | 'connectors' = 'all'): Promise<NotificationEvaluationResult> {
|
||||
return post<NotificationEvaluationResult>(source === 'all' ? '/api/notifications/evaluate' : `/api/notifications/evaluate/${source}`, {}, defaultConfig);
|
||||
},
|
||||
async acknowledgeSafetyFinding(id: string): Promise<{ success: boolean; status: string }> {
|
||||
return post<{ success: boolean; status: string }>(`/api/safety/findings/${encodeURIComponent(id)}/acknowledge`, {}, defaultConfig);
|
||||
},
|
||||
async dismissSafetyFinding(id: string): Promise<{ success: boolean; status: string }> {
|
||||
return post<{ success: boolean; status: string }>(`/api/safety/findings/${encodeURIComponent(id)}/dismiss`, {}, defaultConfig);
|
||||
},
|
||||
async dismissSafetyRecommendation(id: string): Promise<{ success: boolean; status: string }> {
|
||||
return post<{ success: boolean; status: string }>(`/api/safety/recommendations/${encodeURIComponent(id)}/dismiss`, {}, defaultConfig);
|
||||
},
|
||||
async createSafetyRemediationPlan(id: string): Promise<{ success: boolean; status: string; href: string; executable: boolean; planId?: string | null }> {
|
||||
return post<{ success: boolean; status: string; href: string; executable: boolean; planId?: string | null }>(`/api/safety/recommendations/${encodeURIComponent(id)}/create-remediation-plan`, {}, defaultConfig);
|
||||
},
|
||||
async getAiToolPermissions(): Promise<AiToolPermission[]> { return get<AiToolPermission[]>('/api/ai/tools', defaultConfig); },
|
||||
async getHealthChecks(): Promise<HealthCheck[]> { return get<HealthCheck[]>('/api/healthchecks', defaultConfig); },
|
||||
async getGiteaActionsStatus(): Promise<GiteaActionsStatus> { return get<GiteaActionsStatus>('/api/gitea/actions/status', defaultConfig); },
|
||||
async getRunnerFleet(): Promise<RunnerFleetStatus> { return get<RunnerFleetStatus>('/api/gitea/actions/fleet', defaultConfig); },
|
||||
async getJobs(): Promise<JobRun[]> {
|
||||
const response = await get<Array<Record<string, unknown>>>('/api/jobs', defaultConfig);
|
||||
return response.map(normalizeJob);
|
||||
},
|
||||
async cancelJob(id: string): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/jobs/${encodeURIComponent(id)}/cancel`, {}, defaultConfig);
|
||||
},
|
||||
async getOperationMap(): Promise<{ generatedAt?: string; sources?: Record<string, { status: string; count: number }>; nodes: string[]; edges: Array<{ from: string; to: string }>}>
|
||||
{ return get<{ generatedAt?: string; sources?: Record<string, { status: string; count: number }>; nodes: string[]; edges: Array<{ from: string; to: string }> }>('/api/operations-map', defaultConfig); },
|
||||
async previewAction(action: ActionID | string, target: string): Promise<PreflightPreview> {
|
||||
return post<PreflightPreview>('/api/actions/preflight', { action, target }, defaultConfig);
|
||||
},
|
||||
async getSonarrHealth(): Promise<SonarrHealth> { return get<SonarrHealth>('/api/apps/sonarr/health', defaultConfig); },
|
||||
async getSonarrQueue(): Promise<SonarrQueueSummary> { return get<SonarrQueueSummary>('/api/apps/sonarr/queue', defaultConfig); },
|
||||
async getSonarrWanted(): Promise<SonarrWantedSummary> { return get<SonarrWantedSummary>('/api/apps/sonarr/wanted', defaultConfig); },
|
||||
async searchSonarrSeries(query: string): Promise<SonarrSearchResponse> {
|
||||
return get<SonarrSearchResponse>(`/api/apps/sonarr/search?q=${encodeURIComponent(query)}`, defaultConfig);
|
||||
},
|
||||
async addSonarrSeries(payload: SonarrAddSeriesPayload): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/apps/sonarr/series', payload, defaultConfig);
|
||||
},
|
||||
async getRadarrHealth(): Promise<RadarrHealth> { return get<RadarrHealth>('/api/apps/radarr/health', defaultConfig); },
|
||||
async getRadarrQueue(): Promise<RadarrQueueSummary> { return get<RadarrQueueSummary>('/api/apps/radarr/queue', defaultConfig); },
|
||||
async getRadarrWanted(): Promise<RadarrWantedSummary> { return get<RadarrWantedSummary>('/api/apps/radarr/wanted', defaultConfig); },
|
||||
async searchRadarrMovie(query: string): Promise<RadarrSearchResponse> {
|
||||
return get<RadarrSearchResponse>(`/api/apps/radarr/search?q=${encodeURIComponent(query)}`, defaultConfig);
|
||||
},
|
||||
async addRadarrMovie(payload: RadarrAddMoviePayload): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/apps/radarr/movie', payload, defaultConfig);
|
||||
},
|
||||
async getProwlarrHealth(): Promise<ProwlarrHealth> { return get<ProwlarrHealth>('/api/apps/prowlarr/health', defaultConfig); },
|
||||
async getProwlarrIndexers(): Promise<ProwlarrIndexerStatus> { return get<ProwlarrIndexerStatus>('/api/apps/prowlarr/indexers', defaultConfig); },
|
||||
async getProwlarrApplications(): Promise<ProwlarrApplicationStatus> { return get<ProwlarrApplicationStatus>('/api/apps/prowlarr/applications', defaultConfig); },
|
||||
async getProwlarrIndexerHealth(): Promise<ProwlarrIndexerHealth> { return get<ProwlarrIndexerHealth>('/api/apps/prowlarr/indexer-health', defaultConfig); },
|
||||
async getProwlarrSyncStatus(): Promise<ProwlarrSyncStatus> { return get<ProwlarrSyncStatus>('/api/apps/prowlarr/sync-status', defaultConfig); },
|
||||
async searchProwlarr(query: string): Promise<ProwlarrSearchTest> {
|
||||
return get<ProwlarrSearchTest>(`/api/apps/prowlarr/search-test?q=${encodeURIComponent(query)}`, defaultConfig);
|
||||
},
|
||||
async getDelugeHealth(): Promise<DelugeHealth> { return get<DelugeHealth>('/api/apps/deluge/health', defaultConfig); },
|
||||
async getDelugeSession(): Promise<DelugeSessionStatus> { return get<DelugeSessionStatus>('/api/apps/deluge/session', defaultConfig); },
|
||||
async getDelugeTorrents(): Promise<DelugeTorrentStatus> { return get<DelugeTorrentStatus>('/api/apps/deluge/torrents', defaultConfig); },
|
||||
async getDelugeActive(): Promise<DelugeDownloadSummary> { return get<DelugeDownloadSummary>('/api/apps/deluge/active', defaultConfig); },
|
||||
async getDelugeStalled(): Promise<DelugeDownloadSummary> { return get<DelugeDownloadSummary>('/api/apps/deluge/stalled', defaultConfig); },
|
||||
async getDelugePaused(): Promise<DelugeDownloadSummary> { return get<DelugeDownloadSummary>('/api/apps/deluge/paused', defaultConfig); },
|
||||
async getDelugeSpeeds(): Promise<DelugeSpeedStatus> { return get<DelugeSpeedStatus>('/api/apps/deluge/speeds', defaultConfig); },
|
||||
async pauseDelugeTorrent(torrentId: string, payload: { source?: SourceType } = { source: 'manual' }): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/apps/deluge/torrents/${encodeURIComponent(torrentId)}/pause`, payload, defaultConfig);
|
||||
},
|
||||
async resumeDelugeTorrent(torrentId: string, payload: { source?: SourceType } = { source: 'manual' }): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/apps/deluge/torrents/${encodeURIComponent(torrentId)}/resume`, payload, defaultConfig);
|
||||
},
|
||||
async pauseAllDeluge(payload: { source?: SourceType } = { source: 'manual' }): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/apps/deluge/pause-all', payload, defaultConfig);
|
||||
},
|
||||
async resumeAllDeluge(payload: { source?: SourceType } = { source: 'manual' }): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/apps/deluge/resume-all', payload, defaultConfig);
|
||||
},
|
||||
async getPlexHealth(): Promise<PlexHealth> { return get<PlexHealth>('/api/apps/plex/health', defaultConfig); },
|
||||
async getPlexServer(): Promise<PlexServerStatus> { return get<PlexServerStatus>('/api/apps/plex/server', defaultConfig); },
|
||||
async getPlexLibraries(): Promise<PlexLibraryStatus> { return get<PlexLibraryStatus>('/api/apps/plex/libraries', defaultConfig); },
|
||||
async getPlexSessions(): Promise<PlexSessionStatus> { return get<PlexSessionStatus>('/api/apps/plex/sessions', defaultConfig); },
|
||||
async getPlexRecent(): Promise<PlexRecentMedia> { return get<PlexRecentMedia>('/api/apps/plex/recent', defaultConfig); },
|
||||
async getPlexStreams(): Promise<PlexStreamSummary> { return get<PlexStreamSummary>('/api/apps/plex/streams', defaultConfig); },
|
||||
async scanPlexLibrary(libraryId: string, payload: { source?: SourceType } = { source: 'manual' }): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/apps/plex/libraries/${encodeURIComponent(libraryId)}/scan`, payload, defaultConfig);
|
||||
},
|
||||
async scanAllPlex(payload: { source?: SourceType } = { source: 'manual' }): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/apps/plex/libraries/scan-all', payload, defaultConfig);
|
||||
},
|
||||
async getTautulliHealth(): Promise<TautulliHealth> { return get<TautulliHealth>('/api/apps/tautulli/health', defaultConfig); },
|
||||
async getTautulliStatus(): Promise<TautulliStatus> { return get<TautulliStatus>('/api/apps/tautulli/status', defaultConfig); },
|
||||
async getTautulliActivity(): Promise<TautulliActivity> { return get<TautulliActivity>('/api/apps/tautulli/activity', defaultConfig); },
|
||||
async getTautulliHistory(): Promise<TautulliHistory> { return get<TautulliHistory>('/api/apps/tautulli/history', defaultConfig); },
|
||||
async getTautulliUsers(): Promise<TautulliUsers> { return get<TautulliUsers>('/api/apps/tautulli/users', defaultConfig); },
|
||||
async getTautulliLibraries(): Promise<TautulliLibraries> { return get<TautulliLibraries>('/api/apps/tautulli/libraries', defaultConfig); },
|
||||
async getTautulliRecentlyAdded(): Promise<TautulliRecentlyAdded> { return get<TautulliRecentlyAdded>('/api/apps/tautulli/recently-added', defaultConfig); },
|
||||
async getTautulliWatchStats(): Promise<TautulliWatchStats> { return get<TautulliWatchStats>('/api/apps/tautulli/watch-stats', defaultConfig); },
|
||||
async getTautulliStreams(): Promise<TautulliStreamSummary> { return get<TautulliStreamSummary>('/api/apps/tautulli/streams', defaultConfig); },
|
||||
async getTautulliPlexInfo(): Promise<TautulliPlexInfo> { return get<TautulliPlexInfo>('/api/apps/tautulli/plex-info', defaultConfig); },
|
||||
async getNpmHealth(): Promise<NpmHealth> { return get<NpmHealth>('/api/apps/nginx-proxy-manager/health', defaultConfig); },
|
||||
async getNpmProxyHosts(): Promise<NpmProxyHosts> { return get<NpmProxyHosts>('/api/apps/nginx-proxy-manager/proxy-hosts', defaultConfig); },
|
||||
async getNpmCertificates(): Promise<NpmCertificates> { return get<NpmCertificates>('/api/apps/nginx-proxy-manager/certificates', defaultConfig); },
|
||||
async getNpmExpiringCertificates(days = 30): Promise<NpmCertificates> { return get<NpmCertificates>(`/api/apps/nginx-proxy-manager/certificates/expiring?days=${days}`, defaultConfig); },
|
||||
async getNpmAccessLists(): Promise<NpmAccessLists> { return get<NpmAccessLists>('/api/apps/nginx-proxy-manager/access-lists', defaultConfig); },
|
||||
async getNpmExposureSummary(): Promise<NpmExposureSummary> { return get<NpmExposureSummary>('/api/apps/nginx-proxy-manager/exposure-summary', defaultConfig); },
|
||||
async getNpmHostReachability(): Promise<NpmHostReachability> { return get<NpmHostReachability>('/api/apps/nginx-proxy-manager/host-reachability', defaultConfig); },
|
||||
async getNpmSecurityWarnings(): Promise<NpmSecurityWarnings> { return get<NpmSecurityWarnings>('/api/apps/nginx-proxy-manager/security-warnings', defaultConfig); },
|
||||
async getAuthentikHealth(): Promise<AuthentikHealth> { return get<AuthentikHealth>('/api/apps/authentik/health', defaultConfig); },
|
||||
async getAuthentikSystem(): Promise<AuthentikSystemStatus> { return get<AuthentikSystemStatus>('/api/apps/authentik/system', defaultConfig); },
|
||||
async getAuthentikApplications(): Promise<AuthentikCollection<AuthentikApplication>> { return get<AuthentikCollection<AuthentikApplication>>('/api/apps/authentik/applications', defaultConfig); },
|
||||
async getAuthentikProviders(): Promise<AuthentikCollection<AuthentikProvider>> { return get<AuthentikCollection<AuthentikProvider>>('/api/apps/authentik/providers', defaultConfig); },
|
||||
async getAuthentikProxyProviders(): Promise<AuthentikCollection<AuthentikProxyProvider>> { return get<AuthentikCollection<AuthentikProxyProvider>>('/api/apps/authentik/providers/proxy', defaultConfig); },
|
||||
async getAuthentikOAuth2Providers(): Promise<AuthentikCollection<AuthentikOAuth2Provider>> { return get<AuthentikCollection<AuthentikOAuth2Provider>>('/api/apps/authentik/providers/oauth2', defaultConfig); },
|
||||
async getAuthentikOutposts(): Promise<AuthentikCollection<AuthentikOutpost>> { return get<AuthentikCollection<AuthentikOutpost>>('/api/apps/authentik/outposts', defaultConfig); },
|
||||
async getAuthentikOutpostHealth(): Promise<AuthentikOutpostHealth> { return get<AuthentikOutpostHealth>('/api/apps/authentik/outposts/health', defaultConfig); },
|
||||
async getAuthentikFlows(): Promise<AuthentikFlowsSummary> { return get<AuthentikFlowsSummary>('/api/apps/authentik/flows', defaultConfig); },
|
||||
async getAuthentikPolicies(): Promise<AuthentikPoliciesSummary> { return get<AuthentikPoliciesSummary>('/api/apps/authentik/policies', defaultConfig); },
|
||||
async getAuthentikEvents(): Promise<AuthentikCollection<AuthentikEvent>> { return get<AuthentikCollection<AuthentikEvent>>('/api/apps/authentik/events', defaultConfig); },
|
||||
async getAuthentikAuthCoverage(): Promise<AuthentikAuthCoverage> { return get<AuthentikAuthCoverage>('/api/apps/authentik/auth-coverage', defaultConfig); },
|
||||
async getAuthentikSecurityWarnings(): Promise<AuthentikSecurityWarnings> { return get<AuthentikSecurityWarnings>('/api/apps/authentik/security-warnings', defaultConfig); },
|
||||
async getMediaPipelineSummary(refresh = false): Promise<MediaPipelineSummary> { return get<MediaPipelineSummary>(`/api/media-pipeline/summary${refresh ? '?refresh=true' : ''}`, mediaPipelineConfig); },
|
||||
async getMediaPipelineHealth(): Promise<MediaPipelineSummary> { return get<MediaPipelineSummary>('/api/media-pipeline/health', mediaPipelineConfig); },
|
||||
async getMediaPipelineFlow(): Promise<MediaPipelineFlow> { return get<MediaPipelineFlow>('/api/media-pipeline/flow', mediaPipelineConfig); },
|
||||
async getMediaPipelineIssues(): Promise<MediaPipelineIssues> { return get<MediaPipelineIssues>('/api/media-pipeline/issues', mediaPipelineConfig); },
|
||||
async getMediaPipelineDownloads(): Promise<MediaPipelineDownloads> { return get<MediaPipelineDownloads>('/api/media-pipeline/downloads', mediaPipelineConfig); },
|
||||
async getMediaPipelineLibrary(): Promise<MediaPipelineLibrary> { return get<MediaPipelineLibrary>('/api/media-pipeline/library', mediaPipelineConfig); },
|
||||
async getMediaPipelineActivity(): Promise<MediaPipelineActivity> { return get<MediaPipelineActivity>('/api/media-pipeline/activity', mediaPipelineConfig); },
|
||||
async getMediaPipelineRecommendations(): Promise<MediaPipelineRecommendations> { return get<MediaPipelineRecommendations>('/api/media-pipeline/recommendations', mediaPipelineConfig); },
|
||||
async checkMediaPipeline(): Promise<MediaPipelineCheckResult> { return post<MediaPipelineCheckResult>('/api/media-pipeline/check', {}, mediaPipelineConfig); },
|
||||
async diagnoseMediaPipeline(payload: { query: string; target?: string; scope?: string }): Promise<MediaPipelineDiagnosis> {
|
||||
return post<MediaPipelineDiagnosis>('/api/media-pipeline/diagnose', payload, mediaPipelineConfig);
|
||||
},
|
||||
async runInventoryDiscovery(): Promise<InventoryDiscoveryResult> {
|
||||
return post<InventoryDiscoveryResult>('/api/inventory/discover', {}, defaultConfig);
|
||||
},
|
||||
async getInventoryRuns(limit = 100, offset = 0): Promise<InventoryRunSummary[]> {
|
||||
return get<InventoryRunSummary[]>(`/api/inventory/runs?limit=${Math.max(1, Math.min(limit, 500))}&offset=${Math.max(0, offset)}`, inventoryConfig);
|
||||
},
|
||||
async getDiscoveredContainers(): Promise<DiscoveredContainer[]> { return get<DiscoveredContainer[]>('/api/inventory/discovered-containers', inventoryConfig); },
|
||||
async getInventoryContainers(): Promise<DiscoveredContainer[]> { return get<DiscoveredContainer[]>('/api/inventory/containers', defaultConfig); },
|
||||
async getInventoryApps(): Promise<App[]> { return get<App[]>('/api/inventory/apps?include_unmanaged=true', inventoryConfig); },
|
||||
async getInventoryUnmapped(): Promise<DiscoveredContainer[]> { return get<DiscoveredContainer[]>('/api/inventory/unmapped', defaultConfig); },
|
||||
async getInventoryUnknown(): Promise<DiscoveredContainer[]> { return get<DiscoveredContainer[]>('/api/inventory/unknown', defaultConfig); },
|
||||
async getInventorySuggestions(): Promise<DiscoveredContainer[]> { return get<DiscoveredContainer[]>('/api/inventory/suggestions', defaultConfig); },
|
||||
async getInventoryMappings(): Promise<AppContainerMapping[]> { return get<AppContainerMapping[]>('/api/inventory/mappings', inventoryConfig); },
|
||||
async getInventoryDrift(): Promise<InventoryDriftItem[]> { return get<InventoryDriftItem[]>('/api/inventory/drift', inventoryConfig); },
|
||||
async acknowledgeInventoryDrift(id: string): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/inventory/drift/${encodeURIComponent(id)}/acknowledge`, {}, defaultConfig);
|
||||
},
|
||||
async createInventoryDriftIncident(id: string): Promise<ActionResult> {
|
||||
return post<ActionResult>(`/api/inventory/drift/${encodeURIComponent(id)}/incident`, {}, defaultConfig);
|
||||
},
|
||||
async importDiscoveredContainer(id: string): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/inventory/import', { discoveredContainerId: id }, defaultConfig);
|
||||
},
|
||||
async ignoreDiscoveredContainer(id: string): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/inventory/ignore', { discoveredContainerId: id }, defaultConfig);
|
||||
},
|
||||
async updateDiscoveredReview(payload: InventoryReviewUpdatePayload): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/inventory/review/update', payload, defaultConfig);
|
||||
},
|
||||
async mapDiscoveredToExisting(payload: InventoryMapExistingPayload): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/inventory/map-existing', payload, defaultConfig);
|
||||
},
|
||||
async completeInventoryReview(): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/inventory/review/complete', {}, defaultConfig);
|
||||
},
|
||||
async updateInventoryBaseline(): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/inventory/baseline/update', {}, defaultConfig);
|
||||
},
|
||||
async getWidefrogHealth(): Promise<WidefrogHealth> { return get<WidefrogHealth>('/api/apps/widefrog/health', defaultConfig); },
|
||||
async getWidefrogQueue(): Promise<WidefrogQueueSummary> { return get<WidefrogQueueSummary>('/api/apps/widefrog/queue', defaultConfig); },
|
||||
async getWidefrogFailedJobs(): Promise<WidefrogFailedJobs> { return get<WidefrogFailedJobs>('/api/apps/widefrog/failed', defaultConfig); },
|
||||
async getWidefrogActivity(): Promise<WidefrogActivity> { return get<WidefrogActivity>('/api/apps/widefrog/activity', defaultConfig); },
|
||||
async getWidefrogDownloadStatus(): Promise<WidefrogDownloadStatus> { return get<WidefrogDownloadStatus>('/api/apps/widefrog/download-status', defaultConfig); },
|
||||
async retryWidefrogFailed(payload: Record<string, unknown> = {}): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/apps/widefrog/retry-failed', payload, defaultConfig);
|
||||
},
|
||||
async scanWidefrogLibrary(payload: Record<string, unknown> = {}): Promise<ActionResult> {
|
||||
return post<ActionResult>('/api/apps/widefrog/scan-library', payload, defaultConfig);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import {
|
||||
getOperatorSessionSnapshot,
|
||||
setOperatorSessionStatus,
|
||||
} from "./operatorSession";
|
||||
|
||||
describe("operator session", () => {
|
||||
it("tracks backend-owned account and opaque browser sessions without storing tokens", () => {
|
||||
setOperatorSessionStatus({ authorized: false, mode: "locked" });
|
||||
assert.deepEqual(getOperatorSessionSnapshot(), { unlocked: false, mode: "locked" });
|
||||
|
||||
setOperatorSessionStatus({ authorized: true, mode: "session" });
|
||||
assert.deepEqual(getOperatorSessionSnapshot(), { unlocked: true, mode: "session" });
|
||||
|
||||
setOperatorSessionStatus({ authorized: true, mode: "account", identity: "example-host" });
|
||||
assert.deepEqual(getOperatorSessionSnapshot(), {
|
||||
unlocked: true,
|
||||
mode: "account",
|
||||
identity: "example-host",
|
||||
});
|
||||
|
||||
setOperatorSessionStatus({ authorized: false, mode: "locked" });
|
||||
assert.deepEqual(getOperatorSessionSnapshot(), { unlocked: false, mode: "locked" });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { OperatorSessionStatus } from "@/types/appops";
|
||||
|
||||
export const OPERATOR_SESSION_EVENT = "appops:operator-session";
|
||||
|
||||
export type OperatorSessionMode = "locked" | "token" | "session" | "account" | "private";
|
||||
|
||||
export interface OperatorSessionSnapshot {
|
||||
unlocked: boolean;
|
||||
mode: OperatorSessionMode;
|
||||
identity?: string;
|
||||
}
|
||||
|
||||
let snapshot: OperatorSessionSnapshot = { unlocked: false, mode: "locked" };
|
||||
|
||||
const notify = (): void => {
|
||||
if (typeof document !== "undefined") document.dispatchEvent(new CustomEvent(OPERATOR_SESSION_EVENT));
|
||||
};
|
||||
|
||||
export const setOperatorSessionStatus = (status: OperatorSessionStatus): void => {
|
||||
const mode = status.authorized && status.mode !== "locked" ? status.mode : "locked";
|
||||
snapshot = mode === "account"
|
||||
? { unlocked: true, mode, identity: (status.identity || "account").trim() }
|
||||
: mode === "locked"
|
||||
? { unlocked: false, mode }
|
||||
: { unlocked: true, mode };
|
||||
notify();
|
||||
};
|
||||
|
||||
export const hasOperatorSession = (): boolean => snapshot.unlocked;
|
||||
|
||||
export const getOperatorSessionSnapshot = (): OperatorSessionSnapshot => snapshot;
|
||||
Reference in new issue
Block a user