Files
DECNET/decnet_web/src/components/Config.tsx
anti 171e20e427 refactor(decnet_web/Config): wire hook + bump coverage floor
Final integration. The page shell is now a thin composition of
useConfig + the previously-extracted children:

- Config.tsx: 989 -> 131 LOC. Page owns only the activeTab state
  (and the "drop the users tab if the server didn't send users"
  effect). Every form lives inside its tab; toast wiring lives
  in AppearanceTab; window.alert calls live inside UsersTab.
- Tabs receive their `onSave* / onAddUser / ...` callbacks
  directly from the hook — no intermediate wrapper handlers.

Coverage floor bumped after the split:

  lines       14 -> 17
  functions   13 -> 15
  branches    11 -> 13
  statements  13 -> 16

Phase 4 final scoreboard: 34 test files, 156 tests, all green.
2026-05-09 05:27:47 -04:00

132 lines
3.9 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import {
Settings, Users, Sliders, Shield, Palette, Activity,
} from '../icons';
import { useToast } from './Toasts/useToast';
import RuleStateControls from './RuleStateControls';
import './Dashboard.css';
import './Config.css';
import type { ConfigTab } from './Config/types';
import { useConfig } from './Config/useConfig';
import { WorkersPanel } from './Config/WorkersPanel';
import { LimitsTab } from './Config/tabs/LimitsTab';
import { UsersTab } from './Config/tabs/UsersTab';
import { GlobalsTab } from './Config/tabs/GlobalsTab';
import { AppearanceTab } from './Config/tabs/AppearanceTab';
const Config: React.FC = () => {
const {
config, loading, isAdmin,
setDeploymentLimit, setGlobalMutationInterval,
addUser, deleteUser, setUserRole, resetUserPassword,
reinit,
} = useConfig();
const { push: pushToast } = useToast();
const [activeTab, setActiveTab] = useState<ConfigTab>('limits');
// If server didn't send users, force tab away from users.
useEffect(() => {
if (config && !config.users && activeTab === 'users') {
setActiveTab('limits');
}
}, [config, activeTab]);
if (loading) {
return (
<div className="logs-section">
<div className="loader">LOADING CONFIGURATION...</div>
</div>
);
}
if (!config) {
return (
<div className="logs-section">
<div style={{ padding: '40px', textAlign: 'center', opacity: 0.5 }}>
<p>FAILED TO LOAD CONFIGURATION</p>
</div>
</div>
);
}
const tabs: { key: ConfigTab; label: string; icon: React.ReactNode }[] = [
{ key: 'limits', label: 'DEPLOYMENT LIMITS', icon: <Sliders size={14} /> },
...(config.users
? [{ key: 'users' as const, label: 'USER MANAGEMENT', icon: <Users size={14} /> }]
: []),
{ key: 'globals', label: 'GLOBAL VALUES', icon: <Settings size={14} /> },
{ key: 'appearance', label: 'APPEARANCE', icon: <Palette size={14} /> },
...(isAdmin
? [{ key: 'workers' as const, label: 'WORKERS', icon: <Activity size={14} /> }]
: []),
...(isAdmin
? [{ key: 'ttp' as const, label: 'TTP RULES', icon: <Shield size={14} /> }]
: []),
];
return (
<div className="config-page">
<div className="logs-section">
<div className="section-header">
<Shield size={20} />
<h2>SYSTEM CONFIGURATION</h2>
</div>
</div>
<div className="config-tabs">
{tabs.map((tab) => (
<button
key={tab.key}
className={`config-tab ${activeTab === tab.key ? 'active' : ''}`}
onClick={() => setActiveTab(tab.key)}
>
{tab.icon}
{tab.label}
</button>
))}
</div>
{activeTab === 'limits' && (
<LimitsTab
isAdmin={isAdmin}
initialValue={config.deployment_limit}
onSave={setDeploymentLimit}
/>
)}
{activeTab === 'users' && config.users && (
<UsersTab
users={config.users}
onDeleteUser={deleteUser}
onSetUserRole={setUserRole}
onResetUserPassword={resetUserPassword}
onAddUser={addUser}
/>
)}
{activeTab === 'globals' && (
<GlobalsTab
isAdmin={isAdmin}
developerMode={config.developer_mode === true}
initialInterval={config.global_mutation_interval}
onSaveInterval={setGlobalMutationInterval}
onReinit={reinit}
/>
)}
{activeTab === 'appearance' && <AppearanceTab />}
{activeTab === 'workers' && isAdmin && (
<WorkersPanel pushToast={pushToast} />
)}
{/* RuleStateControls also self-gates on /config?.role so a state
leak can't render it. */}
{activeTab === 'ttp' && isAdmin && <RuleStateControls />}
</div>
);
};
export default Config;