Publish curated Unraid-AppOps-Gateway source from 68878c3167dacf2b424c16780e60e0d60815e477
This commit is contained in:
1 parent
837c263cd8
commit
551de41e9f
18 files changed
+1183
-58
No files matched your search
@@ -4,7 +4,7 @@ import { AISummary, AIRecommendation, AIChatMessage, AIAutopilotRule, AITool } f
|
||||
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, GatewayUser, IdentityProvider, PermissionDefinition, UserAccessUpdate, UserAuthSession, RagQueryResult, RagStatus, RunnerFleetStatus } from '../types/appops';
|
||||
import type { DeliveryStatus, DeploymentJob, Enrollment, EnrollmentCapabilities, EnrollmentStorageRootSelection, EnrollmentTrustStatus, ForgeRouteStageResult, WorkMeshPromotion } from '../types/appops';
|
||||
import type { DeliveryStatus, DeploymentJob, Enrollment, EnrollmentCapabilities, EnrollmentStorageRootSelection, EnrollmentTrustStatus, EnrollmentOidcProfile, EnrollmentOidcProfileStatus, ForgeRouteStageResult, WorkMeshPromotion } from '../types/appops';
|
||||
import type { ExposurePreferences, ManagedAiConfig, ManagedAiConfigUpdate, ManagedAiStatus, ManagedAiInference } from '../types/appops';
|
||||
|
||||
type HealthStatus = {
|
||||
@@ -386,6 +386,10 @@ export const createLiveClient = () => ({
|
||||
},
|
||||
async getEnrollmentCapabilities(): Promise<EnrollmentCapabilities> { return get<EnrollmentCapabilities>('/api/enrollments/capabilities', defaultConfig); },
|
||||
async getEnrollmentTrustStatus(): Promise<EnrollmentTrustStatus> { return get<EnrollmentTrustStatus>('/api/enrollments/trust', defaultConfig); },
|
||||
async getEnrollmentOidcProfile(): Promise<EnrollmentOidcProfileStatus> { return get<EnrollmentOidcProfileStatus>('/api/enrollments/trust/oidc-profile', defaultConfig); },
|
||||
async updateEnrollmentOidcProfile(payload: EnrollmentOidcProfile): Promise<EnrollmentOidcProfileStatus> {
|
||||
return patch<EnrollmentOidcProfileStatus>('/api/enrollments/trust/oidc-profile', payload, defaultConfig);
|
||||
},
|
||||
async updateEnrollmentStorageRoot(payload: EnrollmentStorageRootSelection): Promise<EnrollmentTrustStatus> {
|
||||
return patch<EnrollmentTrustStatus>('/api/enrollments/trust/storage-root', payload, defaultConfig);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { KeyRound, LoaderCircle, RefreshCw } from 'lucide-react';
|
||||
import { appopsClient } from '@/api/appopsClient';
|
||||
import { useUserSession } from '@/hooks/useUserSession';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { formatOperatorError } from '@/lib/operatorError';
|
||||
import type { EnrollmentOidcProfile } from '@/types/appops';
|
||||
|
||||
const emptyProfile: EnrollmentOidcProfile = {
|
||||
issuer: '', authorization_flow: '', invalidation_flow: '', openid_mapping: '',
|
||||
email_mapping: '', profile_mapping: '', roles_mapping: '', signing_key: '',
|
||||
administrator_group: 'Administrator', support_agent_group: 'SupportAgent', read_only_group: 'ReadOnly',
|
||||
};
|
||||
const fields = [
|
||||
['issuer', 'Authentik HTTPS-origin'], ['authorization_flow', 'Authorization flow ID'],
|
||||
['invalidation_flow', 'Invalidation flow ID'], ['openid_mapping', 'OpenID mapping ID'],
|
||||
['email_mapping', 'Email mapping ID'], ['profile_mapping', 'Identity-only profile mapping ID'],
|
||||
['roles_mapping', 'Begrensde roles mapping ID'], ['signing_key', 'RSA signing key ID'],
|
||||
] as const;
|
||||
|
||||
export const EnrollmentOidcSettings = () => {
|
||||
const session = useUserSession();
|
||||
const canManage = session.ready && session.authenticated && session.user?.role === 'admin' && session.permissions.includes('settings.manage');
|
||||
const [draft, setDraft] = useState<EnrollmentOidcProfile>(emptyProfile);
|
||||
const [configured, setConfigured] = useState(false);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [notice, setNotice] = useState('');
|
||||
const sequence = useRef(0);
|
||||
const load = useCallback(async () => {
|
||||
if (!canManage) return;
|
||||
const request = ++sequence.current;
|
||||
setBusy(true); setError(''); setLoaded(false); setNotice('');
|
||||
try {
|
||||
const status = await appopsClient.getEnrollmentOidcProfile();
|
||||
if (request !== sequence.current) return;
|
||||
setDraft(status.profile ?? { ...emptyProfile, issuer: status.issuerOrigin ?? '' }); setConfigured(status.configured); setLoaded(true);
|
||||
} catch (failure) {
|
||||
if (request === sequence.current) setError(formatOperatorError(failure, 'OIDC-rootprofiel laden mislukt.'));
|
||||
} finally { if (request === sequence.current) setBusy(false); }
|
||||
}, [canManage]);
|
||||
const invalidate = useCallback(() => { sequence.current++; }, []);
|
||||
useEffect(() => { void load(); return invalidate; }, [load, invalidate]);
|
||||
const save = async () => {
|
||||
if (!canManage || !loaded || busy) return;
|
||||
const request = ++sequence.current;
|
||||
setBusy(true); setError(''); setNotice('');
|
||||
try {
|
||||
const status = await appopsClient.updateEnrollmentOidcProfile(draft);
|
||||
if (request !== sequence.current) return;
|
||||
setDraft(status.profile ?? emptyProfile); setConfigured(status.configured);
|
||||
setNotice('Bestaande Authentik-templates geverifieerd en het rootprofiel opgeslagen.');
|
||||
} catch (failure) {
|
||||
if (request === sequence.current) setError(formatOperatorError(failure, 'OIDC-rootprofiel opslaan mislukt.'));
|
||||
} finally { if (request === sequence.current) setBusy(false); }
|
||||
};
|
||||
if (!canManage) return null;
|
||||
return <Card className="mb-5 border-slate-700 bg-slate-900/50">
|
||||
<CardHeader><CardTitle className="flex items-center gap-2 text-sm"><KeyRound className="h-4 w-4" /> Authenticatie voor nieuwe deploymentdoelen</CardTitle></CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm text-slate-400">Koppel bestaande Authentik-templates voor native OIDC. Rollen: Administrator, SupportAgent en ReadOnly. Nieuwe applicaties krijgen bij enrollment een eigen client en secret.</p>
|
||||
<p className="text-sm">Rootprofiel: {configured ? 'gekoppeld' : 'niet gekoppeld'}. De actuele deployment-readiness controleert de tenant opnieuw.</p>
|
||||
{error && <p role="alert" className="text-sm text-red-300">{error}</p>}
|
||||
{notice && <p role="status" className="text-sm text-emerald-300">{notice}</p>}
|
||||
<div className="grid gap-4 md:grid-cols-2">{fields.map(([field, label]) => <div key={field} className="space-y-2">
|
||||
<Label htmlFor={`enrollment-oidc-${field}`}>{label}</Label>
|
||||
<Input id={`enrollment-oidc-${field}`} value={draft[field]} disabled={busy || !loaded}
|
||||
onChange={event => setDraft(current => ({ ...current, [field]: event.target.value.trim() }))} />
|
||||
</div>)}</div>
|
||||
<div className="flex gap-2">
|
||||
<Button type="button" variant="outline" disabled={busy} onClick={() => void load()}><RefreshCw className="mr-2 h-4 w-4" />Vernieuwen</Button>
|
||||
<Button type="button" disabled={busy || !loaded || fields.some(([field]) => !draft[field])} onClick={() => void save()}>
|
||||
{busy && <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}Verifiëren en opslaan
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>;
|
||||
};
|
||||
@@ -16,6 +16,7 @@ import { UserAccessSettings } from "@/components/UserAccessSettings";
|
||||
import { ExposureSettings } from "@/components/ExposureSettings";
|
||||
import { ManagedAiSettings } from "@/components/ManagedAiSettings";
|
||||
import { ManagedStorageSettings } from "@/components/ManagedStorageSettings";
|
||||
import { EnrollmentOidcSettings } from "@/components/EnrollmentOidcSettings";
|
||||
|
||||
const settingsTabs = ["general", "security", "ai", "notifications", "audit", "voice", "maintenance", "backups"] as const;
|
||||
type SettingsTab = typeof settingsTabs[number];
|
||||
@@ -196,6 +197,7 @@ const Settings = () => {
|
||||
|
||||
<TabsContent value="general">
|
||||
<ManagedStorageSettings />
|
||||
<EnrollmentOidcSettings />
|
||||
<Card className="signal-rail border-slate-700 bg-slate-900/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm font-bold uppercase tracking-wider text-slate-400">Runtime Profiel</CardTitle>
|
||||
|
||||
@@ -164,6 +164,25 @@ export interface EnrollmentStorageRootSelection {
|
||||
managed_appdata_backup_acknowledged: boolean;
|
||||
}
|
||||
|
||||
export interface EnrollmentOidcProfile {
|
||||
issuer: string;
|
||||
authorization_flow: string;
|
||||
invalidation_flow: string;
|
||||
openid_mapping: string;
|
||||
email_mapping: string;
|
||||
profile_mapping: string;
|
||||
roles_mapping: string;
|
||||
signing_key: string;
|
||||
administrator_group: 'Administrator';
|
||||
support_agent_group: 'SupportAgent';
|
||||
read_only_group: 'ReadOnly';
|
||||
}
|
||||
export interface EnrollmentOidcProfileStatus {
|
||||
configured: boolean;
|
||||
issuerOrigin: string | null;
|
||||
profile: EnrollmentOidcProfile | null;
|
||||
}
|
||||
|
||||
export interface Enrollment {
|
||||
id: string;
|
||||
applicationId: string;
|
||||
|
||||
Reference in new issue
Block a user