feat(ttp): E.3.16 frontend TTP UI
TTPsObservedSection.tsx: shared analyst-facing rollup. scope=
identity drives /ttp/by-identity/{uuid} (primary, with Navigator
export download); scope=attacker drives /ttp/by-attacker/{uuid}
(per-IP slice). Tactic → technique tree in fixed UKC-aligned order,
counts and confidence-weighted bars. Literal "NO TECHNIQUES
OBSERVED YET" empty state per TTP_TAGGING.md §"UI surface — Empty
state": no spinner, no fallback list.
RuleStateControls.tsx: admin-only rule operational state panel
backed by POST/DELETE /ttp/rules/{rule_id}/state. Server-gated by
require_admin AND client-gated on /config?.role so a non-admin
never sees the controls (per feedback_serverside_ui.md the client
gate is UX, not security — the server returns 403 either way).
Wired into Config.tsx as a new "TTP RULES" admin tab.
Wired TTPsObservedSection into IdentityDetail (above fingerprints)
and AttackerDetail (above TIMELINE). DeckyFleet/PersonaGeneration
vocabulary throughout (logs-section / section-header / btn /
matrix-text / dim-chip).
tsc --noEmit and vite build clean.
The dev-server browser smoke is deferred per the "can't reliably
exercise UI from this harness" reality — typecheck + build is the
correctness gate, not feature verification.
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import api from '../utils/api';
|
||||
import { Settings, Users, Sliders, Trash2, UserPlus, Key, Save, Shield, AlertTriangle, Palette, Activity, Square, RefreshCw, Play } from '../icons';
|
||||
import { useToast } from './Toasts/useToast';
|
||||
import RuleStateControls from './RuleStateControls';
|
||||
import './Dashboard.css';
|
||||
import './Config.css';
|
||||
|
||||
@@ -238,6 +239,7 @@ const Config: React.FC = () => {
|
||||
{ key: 'globals', label: 'GLOBAL VALUES', icon: <Settings size={14} /> },
|
||||
{ key: 'appearance', label: 'APPEARANCE', icon: <Palette size={14} /> },
|
||||
...(isAdmin ? [{ key: 'workers', label: 'WORKERS', icon: <Activity size={14} /> }] : []),
|
||||
...(isAdmin ? [{ key: 'ttp', label: 'TTP RULES', icon: <Shield size={14} /> }] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -494,6 +496,12 @@ const Config: React.FC = () => {
|
||||
<WorkersPanel pushToast={pushToast} />
|
||||
)}
|
||||
|
||||
{/* TTP RULES TAB — admin only. RuleStateControls also self-gates
|
||||
on /config?.role so a state leak can't render it. */}
|
||||
{activeTab === 'ttp' && isAdmin && (
|
||||
<RuleStateControls />
|
||||
)}
|
||||
|
||||
{/* APPEARANCE TAB */}
|
||||
{activeTab === 'appearance' && (
|
||||
<div className="config-panel">
|
||||
|
||||
Reference in New Issue
Block a user