Files
DECNET/decnet_web/src/components/Config.tsx
anti f2b3393669 chore: relicense to AGPL-3.0-or-later and add SPDX headers
Replaces LICENSE (GPLv3 -> AGPLv3) and prepends
`SPDX-License-Identifier: AGPL-3.0-or-later` to every source file
across decnet/, decnet_web/, tests/, scripts/, and tools/.

Rationale: closes the GPLv3 ASP loophole so any party operating a
modified DECNET as a network service must offer their modified
source. Personal copyright (Samuel Paschuan) + inbound=outbound
contributions make a future unilateral relicense infeasible.

- LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt)
- COPYRIGHT: project copyright notice
- tools/add_spdx_headers.py: idempotent header injector
  (shebang- and PEP 263-aware)

Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh).
No behavior change; comments only.
2026-05-22 21:04:16 -04:00

141 lines
4.3 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import React, { useEffect, useState } from 'react';
import {
Settings, Users, Sliders, Shield, Palette, Activity, Cpu,
} from '../icons';
import { useToast } from './Toasts/useToast';
import RuleStateControls from './RuleStateControls';
import './DeckyFleet.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';
import { LLMTab } from './Config/tabs/LLMTab';
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} /> }]
: []),
...(isAdmin
? [{ key: 'llm' as const, label: 'LLM PROVIDER', icon: <Cpu size={14} /> }]
: []),
];
return (
<div className="fleet-root config-page">
<div className="page-header">
<div className="page-title-group">
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<Settings size={22} className="violet-accent" />
<h1>SYSTEM CONFIGURATION</h1>
</div>
</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 />}
{activeTab === 'llm' && isAdmin && <LLMTab isAdmin={isAdmin} />}
</div>
);
};
export default Config;