Release v0.1.5
This commit is contained in:
120
webui/src/features/admin/SystemSettingsPanel.tsx
Normal file
120
webui/src/features/admin/SystemSettingsPanel.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { ApiSettings } from "@govoplan/core-webui";
|
||||
import { Button } from "@govoplan/core-webui";
|
||||
import { Card } from "@govoplan/core-webui";
|
||||
import { FormField } from "@govoplan/core-webui";
|
||||
import { ToggleSwitch } from "@govoplan/core-webui";
|
||||
import { fetchSystemSettings, updateSystemSettings, type PrivacyRetentionLimitPermissions, type PrivacyRetentionPolicy, type SystemSettingsItem } from "../../api/admin";
|
||||
import { AdminPageLayout, adminErrorMessage } from "@govoplan/core-webui";
|
||||
|
||||
const defaultAllowLowerLevelLimits: PrivacyRetentionLimitPermissions = {
|
||||
store_raw_campaign_json: true,
|
||||
raw_campaign_json_retention_days: true,
|
||||
generated_eml_retention_days: true,
|
||||
stored_report_detail_retention_days: true,
|
||||
mock_mailbox_retention_days: true,
|
||||
audit_detail_retention_days: true,
|
||||
audit_detail_level: true
|
||||
};
|
||||
|
||||
const defaultPrivacyPolicy: PrivacyRetentionPolicy = {
|
||||
store_raw_campaign_json: true,
|
||||
raw_campaign_json_retention_days: null,
|
||||
generated_eml_retention_days: null,
|
||||
stored_report_detail_retention_days: null,
|
||||
mock_mailbox_retention_days: null,
|
||||
audit_detail_retention_days: null,
|
||||
audit_detail_level: "full",
|
||||
allow_lower_level_limits: defaultAllowLowerLevelLimits
|
||||
};
|
||||
|
||||
const fallback: SystemSettingsItem = {
|
||||
default_locale: "en",
|
||||
allow_tenant_custom_groups: true,
|
||||
allow_tenant_custom_roles: true,
|
||||
allow_tenant_api_keys: true,
|
||||
privacy_retention_policy: defaultPrivacyPolicy,
|
||||
maintenance_mode: { enabled: false, message: "" },
|
||||
settings: {}
|
||||
};
|
||||
|
||||
export default function SystemSettingsPanel({ settings, canWrite, canAccessMaintenance }: { settings: ApiSettings; canWrite: boolean; canAccessMaintenance: boolean }) {
|
||||
const [draft, setDraft] = useState<SystemSettingsItem>(fallback);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const loaded = await fetchSystemSettings(settings);
|
||||
setDraft(loaded);
|
||||
}
|
||||
catch (err) { setError(adminErrorMessage(err)); }
|
||||
finally { setLoading(false); }
|
||||
}
|
||||
|
||||
useEffect(() => { void load(); }, [settings.accessToken, settings.apiBaseUrl]);
|
||||
|
||||
async function save() {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
try {
|
||||
setDraft(await updateSystemSettings(settings, {
|
||||
default_locale: draft.default_locale,
|
||||
allow_tenant_custom_groups: draft.allow_tenant_custom_groups,
|
||||
allow_tenant_custom_roles: draft.allow_tenant_custom_roles,
|
||||
allow_tenant_api_keys: draft.allow_tenant_api_keys,
|
||||
maintenance_mode: draft.maintenance_mode
|
||||
}));
|
||||
setSuccess("System settings saved.");
|
||||
} catch (err) { setError(adminErrorMessage(err)); }
|
||||
finally { setBusy(false); }
|
||||
}
|
||||
|
||||
return (
|
||||
<AdminPageLayout
|
||||
title="System general settings"
|
||||
description="Instance-wide defaults and tenant governance capabilities. Retention policy management has its own system section."
|
||||
loading={loading}
|
||||
error={error}
|
||||
success={success}
|
||||
actions={<><Button onClick={() => void load()} disabled={loading}>Reload</Button><Button variant="primary" onClick={() => void save()} disabled={!canWrite || busy}>{busy ? "Saving…" : "Save settings"}</Button></>}
|
||||
>
|
||||
<div className="admin-settings-form">
|
||||
<Card title="Defaults for newly created tenants">
|
||||
<FormField label="Default locale"><input value={draft.default_locale} onChange={(event) => setDraft({ ...draft, default_locale: event.target.value })} /></FormField>
|
||||
</Card>
|
||||
<Card title="Tenant administration capabilities">
|
||||
<div className="settings-list">
|
||||
<ToggleSwitch checked={draft.allow_tenant_custom_groups} onChange={(checked) => setDraft({ ...draft, allow_tenant_custom_groups: checked })} label="Allow tenant-defined groups by default" />
|
||||
<ToggleSwitch checked={draft.allow_tenant_custom_roles} onChange={(checked) => setDraft({ ...draft, allow_tenant_custom_roles: checked })} label="Allow tenant-defined roles by default" />
|
||||
<ToggleSwitch checked={draft.allow_tenant_api_keys} onChange={(checked) => setDraft({ ...draft, allow_tenant_api_keys: checked })} label="Allow tenant API keys by default" />
|
||||
</div>
|
||||
<p className="muted small-note">These settings are enforced by the backend. Central groups and tenant roles remain available even when local creation is disabled.</p>
|
||||
</Card>
|
||||
<Card title="Maintenance mode">
|
||||
<div className="settings-list">
|
||||
<ToggleSwitch
|
||||
checked={draft.maintenance_mode.enabled}
|
||||
disabled={!canWrite || !canAccessMaintenance}
|
||||
onChange={(checked) => setDraft({ ...draft, maintenance_mode: { ...draft.maintenance_mode, enabled: checked } })}
|
||||
label="Restrict authenticated API access to maintenance operators"
|
||||
/>
|
||||
</div>
|
||||
<FormField label="Maintenance message">
|
||||
<textarea
|
||||
rows={3}
|
||||
value={draft.maintenance_mode.message ?? ""}
|
||||
disabled={!canWrite}
|
||||
onChange={(event) => setDraft({ ...draft, maintenance_mode: { ...draft.maintenance_mode, message: event.target.value } })}
|
||||
/>
|
||||
</FormField>
|
||||
<p className="muted small-note">Changing the maintenance-mode flag requires system:maintenance:access. Login remains available so an operator can sign in during maintenance.</p>
|
||||
</Card>
|
||||
</div>
|
||||
</AdminPageLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user