mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-16 13:38:33 +00:00
feat(settings): reorganize the settings hub into domain groups (#1321)
* refactor(settings): split System Limits and regroup the hub System Limits had grown into a grab-bag of host alert thresholds, Docker cleanup, and mesh data-plane controls under one mislabeled section. Split it into Host Alerts, Docker & Storage, and Fleet Mesh, and split Developer into Developer Diagnostics and Data Retention. Reorganize the sidebar into ten domain groups: Personal, Access, Infrastructure, Monitoring, Notifications, Automation, Organization, Security, Operations, Help. Each section now saves only its own keys, so a concurrent edit in one section no longer clobbers another. Data Retention sends the audit-log window only on a paid plan, matching the field's existing visibility, so a Community save no longer fails on a key the operator cannot set. NumberChip moves to a shared module and the toggle reuses the existing shared component. The /settings API is unchanged. * test(settings): cover registry structure and per-section save payloads Add structural invariants for the ten-group registry (every item maps to a real group, ids are unique, the System Limits and Developer splits land in the right groups with the right gates, renamed labels and the Registries paid gate hold) and per-section payload tests asserting each split section patches only its own keys, including the Community path where Data Retention omits the paid audit-log key. * docs(settings): document the regrouped settings hub Rewrite the settings reference for the ten-group layout, replace the System Limits page with Host Alerts, Docker & Storage, and Fleet Mesh, and document the prune-on-update, reclaimable-space banner, and mesh auto-recreate settings that were previously undocumented. Update the Settings navigation breadcrumbs across the feature docs and refresh the affected screenshots. * fix(settings): show Access sections as instance-global, not operator-scoped License, Users, SSO, and API Tokens are instance-global settings but the masthead scope label rendered them as operator-scoped because it keyed off the old Identity group. Only Personal sections (account, appearance) are operator/browser-scoped now; everything else reads as global. Also add a compile-time exhaustiveness guard to the section switch so a future SectionId added without a matching case fails the build instead of silently rendering a blank panel. * docs(settings): remap remaining settings breadcrumbs to the new groups Update the navigation breadcrumbs that still pointed at the removed Identity, Alerts, and Advanced groups: API Tokens and Users now sit under Access, Webhooks under Automation, Labels under Organization, App Store under Infrastructure, Appearance under Personal, and scan policies under Security > Vulnerability Scanning. Correct the settings reference scope note so Access reads as global. * docs(settings): remap renamed-section breadcrumbs across feature docs Sweep every feature, operations, getting-started, and reference page for navigation paths that still named the renamed settings sections, and point them at the current ones: Security becomes Security > Vulnerability Scanning, Notifications becomes Notifications > Channels, Routing becomes Notifications > Notification Routing, and Developer becomes Operations > Developer Diagnostics (with its retention windows under Operations > Data Retention). App Store moves under Infrastructure and the four-group overview in the getting-started intro is rewritten to the ten groups. Separators each page already used are preserved.
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { TogglePill } from '@/components/ui/toggle-pill';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useLicense } from '@/context/LicenseContext';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import { toast } from '@/components/ui/toast-store';
|
||||
@@ -25,25 +23,18 @@ function SectionSkeleton() {
|
||||
return (
|
||||
<div className="space-y-3 rounded-lg border border-glass-border bg-glass p-4">
|
||||
<Skeleton className="h-10 w-full" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type DeveloperFields = Pick<PatchableSettings, 'developer_mode' | 'metrics_retention_hours' | 'log_retention_days' | 'audit_retention_days' | 'scan_history_per_image_limit'>;
|
||||
type DeveloperFields = Pick<PatchableSettings, 'developer_mode'>;
|
||||
|
||||
const DEFAULT_DEVELOPER: DeveloperFields = {
|
||||
developer_mode: DEFAULT_SETTINGS.developer_mode,
|
||||
metrics_retention_hours: DEFAULT_SETTINGS.metrics_retention_hours,
|
||||
log_retention_days: DEFAULT_SETTINGS.log_retention_days,
|
||||
audit_retention_days: DEFAULT_SETTINGS.audit_retention_days,
|
||||
scan_history_per_image_limit: DEFAULT_SETTINGS.scan_history_per_image_limit,
|
||||
};
|
||||
|
||||
export function DeveloperSection({ onDirtyChange }: DeveloperSectionProps) {
|
||||
const { isAdmin } = useAuth();
|
||||
const { isPaid } = useLicense();
|
||||
const { activeNode } = useNodes();
|
||||
const readOnly = !isAdmin;
|
||||
const [settings, setSettings] = useState<DeveloperFields>({ ...DEFAULT_DEVELOPER });
|
||||
@@ -51,12 +42,7 @@ export function DeveloperSection({ onDirtyChange }: DeveloperSectionProps) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const hasChanges =
|
||||
settings.developer_mode !== serverSettingsRef.current.developer_mode ||
|
||||
settings.metrics_retention_hours !== serverSettingsRef.current.metrics_retention_hours ||
|
||||
settings.log_retention_days !== serverSettingsRef.current.log_retention_days ||
|
||||
settings.audit_retention_days !== serverSettingsRef.current.audit_retention_days ||
|
||||
settings.scan_history_per_image_limit !== serverSettingsRef.current.scan_history_per_image_limit;
|
||||
const hasChanges = settings.developer_mode !== serverSettingsRef.current.developer_mode;
|
||||
|
||||
useEffect(() => {
|
||||
onDirtyChange?.(hasChanges);
|
||||
@@ -82,10 +68,6 @@ export function DeveloperSection({ onDirtyChange }: DeveloperSectionProps) {
|
||||
const nodeData: Record<string, string> = nodeRes.ok ? await nodeRes.json() : {};
|
||||
const safe: DeveloperFields = {
|
||||
developer_mode: (nodeData.developer_mode as '0' | '1') ?? DEFAULT_SETTINGS.developer_mode,
|
||||
metrics_retention_hours: nodeData.metrics_retention_hours ?? DEFAULT_SETTINGS.metrics_retention_hours,
|
||||
log_retention_days: nodeData.log_retention_days ?? DEFAULT_SETTINGS.log_retention_days,
|
||||
audit_retention_days: nodeData.audit_retention_days ?? DEFAULT_SETTINGS.audit_retention_days,
|
||||
scan_history_per_image_limit: nodeData.scan_history_per_image_limit ?? DEFAULT_SETTINGS.scan_history_per_image_limit,
|
||||
};
|
||||
setSettings(safe);
|
||||
serverSettingsRef.current = { ...safe };
|
||||
@@ -106,10 +88,6 @@ export function DeveloperSection({ onDirtyChange }: DeveloperSectionProps) {
|
||||
const saveSettings = async () => {
|
||||
const payload = {
|
||||
developer_mode: settings.developer_mode,
|
||||
metrics_retention_hours: settings.metrics_retention_hours,
|
||||
log_retention_days: settings.log_retention_days,
|
||||
audit_retention_days: settings.audit_retention_days,
|
||||
scan_history_per_image_limit: settings.scan_history_per_image_limit,
|
||||
};
|
||||
setIsSaving(true);
|
||||
try {
|
||||
@@ -151,78 +129,6 @@ export function DeveloperSection({ onDirtyChange }: DeveloperSectionProps) {
|
||||
</SettingsField>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection title="Data retention">
|
||||
<SettingsField
|
||||
label="Container metrics"
|
||||
helper="How long to keep per-container CPU, RAM, and network history."
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
max={8760}
|
||||
value={settings.metrics_retention_hours}
|
||||
onChange={(e) => onSettingChange('metrics_retention_hours', e.target.value)}
|
||||
className="w-24"
|
||||
/>
|
||||
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">hrs</span>
|
||||
</div>
|
||||
</SettingsField>
|
||||
|
||||
<SettingsField
|
||||
label="Notification log"
|
||||
helper="How long to keep alert and notification history."
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
max={365}
|
||||
value={settings.log_retention_days}
|
||||
onChange={(e) => onSettingChange('log_retention_days', e.target.value)}
|
||||
className="w-24"
|
||||
/>
|
||||
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">days</span>
|
||||
</div>
|
||||
</SettingsField>
|
||||
|
||||
<SettingsField
|
||||
label="Scan history per image"
|
||||
helper="How many vulnerability scans to keep per image. Older scans beyond the cap are pruned."
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
min={5}
|
||||
max={1000}
|
||||
value={settings.scan_history_per_image_limit}
|
||||
onChange={(e) => onSettingChange('scan_history_per_image_limit', e.target.value)}
|
||||
className="w-24"
|
||||
/>
|
||||
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">scans</span>
|
||||
</div>
|
||||
</SettingsField>
|
||||
|
||||
{isPaid && (
|
||||
<SettingsField
|
||||
label="Audit log"
|
||||
helper="How long to keep audit trail entries."
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
max={365}
|
||||
value={settings.audit_retention_days}
|
||||
onChange={(e) => onSettingChange('audit_retention_days', e.target.value)}
|
||||
className="w-24"
|
||||
/>
|
||||
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">days</span>
|
||||
</div>
|
||||
</SettingsField>
|
||||
)}
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsActions hint={readOnly ? 'Read-only · admin access required to edit' : (hasChanges ? 'unsaved changes' : undefined)}>
|
||||
{!readOnly && (
|
||||
<SettingsPrimaryButton onClick={saveSettings} disabled={isSaving || !hasChanges}>
|
||||
|
||||
Reference in New Issue
Block a user