Adds a Default Roles section with Tag components for viewing/removing roles and an Input+Button for adding new ones. Replaces the plain delete button with a ConfirmDialog requiring typed confirmation. Introduces OidcConfigPage.module.css for CSS module layout classes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
111 lines
4.7 KiB
TypeScript
111 lines
4.7 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { Button, Input, Toggle, FormField, Card, Alert, SectionHeader, Tag, ConfirmDialog } from '@cameleer/design-system';
|
|
import { adminFetch } from '../../api/queries/admin/admin-api';
|
|
import styles from './OidcConfigPage.module.css';
|
|
|
|
interface OidcConfig {
|
|
enabled: boolean;
|
|
issuerUri: string;
|
|
clientId: string;
|
|
clientSecret: string;
|
|
rolesClaim: string;
|
|
defaultRoles: string[];
|
|
autoSignup: boolean;
|
|
displayNameClaim: string;
|
|
}
|
|
|
|
export default function OidcConfigPage() {
|
|
const [config, setConfig] = useState<OidcConfig | null>(null);
|
|
const [saving, setSaving] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [success, setSuccess] = useState(false);
|
|
const [newRole, setNewRole] = useState('');
|
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
adminFetch<OidcConfig>('/oidc')
|
|
.then(setConfig)
|
|
.catch(() => setConfig({ enabled: false, issuerUri: '', clientId: '', clientSecret: '', rolesClaim: 'roles', defaultRoles: ['VIEWER'], autoSignup: true, displayNameClaim: 'name' }));
|
|
}, []);
|
|
|
|
const handleSave = async () => {
|
|
if (!config) return;
|
|
setSaving(true);
|
|
setError(null);
|
|
try {
|
|
await adminFetch('/oidc', { method: 'PUT', body: JSON.stringify(config) });
|
|
setSuccess(true);
|
|
setTimeout(() => setSuccess(false), 3000);
|
|
} catch (e: any) {
|
|
setError(e.message);
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async () => {
|
|
try {
|
|
await adminFetch('/oidc', { method: 'DELETE' });
|
|
setConfig({ enabled: false, issuerUri: '', clientId: '', clientSecret: '', rolesClaim: 'roles', defaultRoles: ['VIEWER'], autoSignup: true, displayNameClaim: 'name' });
|
|
} catch (e: any) {
|
|
setError(e.message);
|
|
}
|
|
};
|
|
|
|
if (!config) return null;
|
|
|
|
return (
|
|
<div>
|
|
<h2 style={{ marginBottom: '1rem' }}>OIDC Configuration</h2>
|
|
<Card>
|
|
<div style={{ padding: '1.5rem', display: 'grid', gap: '1rem' }}>
|
|
<Toggle checked={config.enabled} onChange={(e) => setConfig({ ...config, enabled: e.target.checked })} label="Enable OIDC" />
|
|
<FormField label="Issuer URI"><Input value={config.issuerUri} onChange={(e) => setConfig({ ...config, issuerUri: e.target.value })} /></FormField>
|
|
<FormField label="Client ID"><Input value={config.clientId} onChange={(e) => setConfig({ ...config, clientId: e.target.value })} /></FormField>
|
|
<FormField label="Client Secret"><Input type="password" value={config.clientSecret} onChange={(e) => setConfig({ ...config, clientSecret: e.target.value })} /></FormField>
|
|
<FormField label="Roles Claim"><Input value={config.rolesClaim} onChange={(e) => setConfig({ ...config, rolesClaim: e.target.value })} /></FormField>
|
|
<FormField label="Display Name Claim"><Input value={config.displayNameClaim} onChange={(e) => setConfig({ ...config, displayNameClaim: e.target.value })} /></FormField>
|
|
<Toggle checked={config.autoSignup} onChange={(e) => setConfig({ ...config, autoSignup: e.target.checked })} label="Auto Signup" />
|
|
|
|
<div className={styles.section}>
|
|
<h3>Default Roles</h3>
|
|
<div className={styles.tagRow}>
|
|
{(config.defaultRoles || []).map(role => (
|
|
<Tag key={role} label={role} onRemove={() => {
|
|
setConfig(prev => ({ ...prev!, defaultRoles: prev!.defaultRoles.filter(r => r !== role) }));
|
|
}} />
|
|
))}
|
|
</div>
|
|
<div className={styles.addRow}>
|
|
<Input placeholder="Add role..." value={newRole} onChange={e => setNewRole(e.target.value)} />
|
|
<Button onClick={() => {
|
|
if (newRole.trim() && !config.defaultRoles?.includes(newRole.trim())) {
|
|
setConfig(prev => ({ ...prev!, defaultRoles: [...(prev!.defaultRoles || []), newRole.trim()] }));
|
|
setNewRole('');
|
|
}
|
|
}}>Add</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', gap: '0.75rem' }}>
|
|
<Button variant="primary" onClick={handleSave} disabled={saving}>{saving ? 'Saving...' : 'Save'}</Button>
|
|
<Button variant="danger" onClick={() => setDeleteOpen(true)}>Delete Configuration</Button>
|
|
</div>
|
|
|
|
{error && <Alert variant="error">{error}</Alert>}
|
|
{success && <Alert variant="success">Configuration saved</Alert>}
|
|
</div>
|
|
</Card>
|
|
|
|
<ConfirmDialog
|
|
open={deleteOpen}
|
|
onClose={() => setDeleteOpen(false)}
|
|
onConfirm={handleDelete}
|
|
title="Delete OIDC Configuration"
|
|
message="Delete OIDC configuration? All OIDC users will lose access."
|
|
confirmText="DELETE"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|