mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-10 18:56:53 +00:00
feat: node-scoped Networking operator page (#1603)
* feat: add node-scoped Networking operator page Adds a Networking view with overview, topology, inventory, and findings. Shared aggregate reads back the page; Resources keeps prune and redirects here. Includes fail-closed network delete guards, operator docs, and /nodes/:slug/networking routing. * fix: rename unused variable n to _n to satisfy no-unused-vars lint * fix: keep top bar search clickable when nav grows * feat: complete Compose-first Networking Phase 2 operator assistant * fix: move networking action visibility helper out of component module * feat(networking): complete Compose-first Networking operator page Finish the node-scoped Networking page (Overview, Networks, Topology, Findings) with design-system parity and correct finding semantics. - Rebuild detail sheets on SystemSheet/SheetSection; align the tab band, masthead, and mobile tone with Fleet and Security. - Encode the host-mode and exposure severity matrix; fix collision counts so intentional shared externals are not flagged; add one typed drift predicate shared by inventory, topology, badges, and overview counts. - Preserve per-container attachments and IPs on topology node clicks; drawer-only click with an explicit logs action; ownership and boolean filters; bound large graphs before layout. - Aggregate cached Compose Doctor findings into the Findings tab with honest source labels, structural merge and dedupe, staleness reconciliation, and a shared exposure-context helper both engines use. - Networks tab: privacy-safe service search, precise ownership counts, schema v3 with version-2 adapters on every endpoint, pre-confirm delete reasons, and the shared sortable table with an internal scroll region. - Interop: Fleet node-card networking signal with pending-intent navigation, stack-to-node backlink, and Dossier/Drift deep links. - Enrich sanitized inspect with an allowlisted connected-container list; fetch topology once and filter client-side. - Docs and tests across every new finding kind, adapter, and flow. * fix(networking): correct drift count, exposure fail-soft, and inspect crash paths Address code-review findings on the Networking page implementation: - Fix the Overview drift count to use the shared drift-kind predicate instead of a hardcoded list that omitted external-network-missing. - Gate Compose Doctor's unclassified-exposure and reverse-proxy-undocumented rules on exposure-context availability, so a DB read failure no longer fabricates findings (mirrors the live engine's existing fail-soft behavior). - Guard the per-stack exposure-intent read in topology aggregation so a transient DB failure degrades to unknown intent instead of failing the whole response. - Harden the network detail drawer against a partial inspect payload from an older remote node, and log the real error instead of a bare catch. - Remove now-duplicated severity-rank and drift-kind helpers in favor of the shared modules; drop dead backend-only exports; widen the frontend schema version type to a plain number instead of casting past a literal type. - Add coverage for the delete-guard precedence, the full host-mode severity matrix, the schema-2 compatibility adapter, and the sanitized connected- container allowlist; tighten two tests that were not exercising the behavior they claimed to. * fix: add missing onOpenNodeNetworking prop to FleetView experimental test The added required prop on FleetViewProps broke the merge-build when the test file (on main but not on this branch) was compiled against the updated FleetView interface.
This commit is contained in:
@@ -216,6 +216,7 @@ export interface EditorViewProps {
|
||||
// Mobile-only: notifications + more-menu cluster for the detail header right
|
||||
// slot (the global TopBar is dropped on the full-screen detail surface).
|
||||
headerActions?: React.ReactNode;
|
||||
requestedAnatomyTab?: 'networking' | 'doctor' | 'dossier' | 'drift';
|
||||
|
||||
stackMuteActions?: ReturnType<typeof useStackMuteActions>;
|
||||
}
|
||||
@@ -679,6 +680,7 @@ export function EditorView(props: EditorViewProps) {
|
||||
applying={loadingAction === 'update'}
|
||||
canEdit={can('stack:edit', 'stack', stackName)}
|
||||
notifications={notifications}
|
||||
requestedTab={props.requestedAnatomyTab}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,10 @@ const AuditLogView = lazy(() =>
|
||||
const ScheduledOperationsView = lazy(() => import('../ScheduledOperationsView'));
|
||||
const AutoUpdateReadinessView = lazy(() => import('../AutoUpdateReadinessView'));
|
||||
const SecurityView = lazy(() =>
|
||||
import('../SecurityView').then(m => ({ default: m.SecurityView })),
|
||||
import('../SecurityView').then(m => ({ default: m.SecurityView })),
|
||||
);
|
||||
const NetworkingView = lazy(() =>
|
||||
import('../networking/NetworkingView').then(m => ({ default: m.NetworkingView })),
|
||||
);
|
||||
|
||||
// Sized for the main workspace area (flex-1 with p-6 padding). Visible
|
||||
@@ -81,6 +84,7 @@ export interface ViewRouterProps {
|
||||
onTemplateDeploySuccess: (stackName: string) => void;
|
||||
onHostConsoleClose: () => void;
|
||||
onFleetNavigateToNode: (nodeId: number, stackName: string) => void;
|
||||
onOpenNodeNetworking: (nodeId: number) => void;
|
||||
filterNodeId: number | null;
|
||||
onClearScheduledOpsFilter: () => void;
|
||||
schedulePrefill: ScheduleTaskPrefill | null;
|
||||
@@ -116,6 +120,7 @@ export function ViewRouter({
|
||||
onTemplateDeploySuccess,
|
||||
onHostConsoleClose,
|
||||
onFleetNavigateToNode,
|
||||
onOpenNodeNetworking,
|
||||
filterNodeId,
|
||||
onClearScheduledOpsFilter,
|
||||
schedulePrefill,
|
||||
@@ -157,6 +162,13 @@ export function ViewRouter({
|
||||
if (activeView === 'resources') {
|
||||
return <ResourcesView />;
|
||||
}
|
||||
if (activeView === 'networking') {
|
||||
return (
|
||||
<LazyView>
|
||||
<NetworkingView />
|
||||
</LazyView>
|
||||
);
|
||||
}
|
||||
if (activeView === 'security') {
|
||||
// Node-scoped (not hub-only): scan/scanner data follows the active node
|
||||
// like Resources. The page itself is Community; per-tab gates handle
|
||||
@@ -211,6 +223,7 @@ export function ViewRouter({
|
||||
<LazyView>
|
||||
<FleetView
|
||||
onNavigateToNode={onFleetNavigateToNode}
|
||||
onOpenNodeNetworking={onOpenNodeNetworking}
|
||||
onOpenSettingsSection={onOpenSettingsSection}
|
||||
onOpenMuteRulesWithPrefill={onOpenMuteRulesWithPrefill}
|
||||
fleetUpdatesIntent={fleetUpdatesIntent}
|
||||
|
||||
@@ -228,6 +228,9 @@ describe('useViewNavigationState', () => {
|
||||
expect(values).toContain('fleet');
|
||||
expect(values).toContain('resources');
|
||||
expect(values).toContain('templates');
|
||||
// Networking is a base nav item; the global search palette's Pages group
|
||||
// derives from this list, so it needs no separate registration.
|
||||
expect(values).toContain('networking');
|
||||
// Logs is an admin-only operator view; a non-admin must not see the entry.
|
||||
expect(values).not.toContain('global-observability');
|
||||
expect(values).not.toContain('auto-updates');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
Terminal, CloudDownload, Home, HardDrive, ScrollText,
|
||||
Activity, Radar, RefreshCw, Clock, ShieldCheck,
|
||||
Activity, Radar, RefreshCw, Clock, ShieldCheck, Network,
|
||||
} from 'lucide-react';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
@@ -133,6 +133,7 @@ export function useViewNavigationState(options?: UseViewNavigationStateOptions)
|
||||
}
|
||||
items.push(
|
||||
{ value: 'resources', label: 'Resources', icon: HardDrive },
|
||||
{ value: 'networking', label: 'Networking', icon: Network },
|
||||
{ value: 'security', label: 'Security', icon: ShieldCheck },
|
||||
{ value: 'templates', label: 'App Store', icon: CloudDownload },
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('mobile treatments', () => {
|
||||
// components/mobile/, or a masthead-led mobile branch of the desktop view as
|
||||
// Security does).
|
||||
expect([...BESPOKE_MOBILE_VIEWS].sort()).toEqual(
|
||||
['audit-log', 'auto-updates', 'dashboard', 'fleet', 'global-observability', 'resources', 'scheduled-ops', 'security', 'settings', 'templates'],
|
||||
['audit-log', 'auto-updates', 'dashboard', 'fleet', 'global-observability', 'networking', 'resources', 'scheduled-ops', 'security', 'settings', 'templates'],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ export const MOBILE_TREATMENTS: Record<ActiveView, MobileTreatment> = {
|
||||
settings: 'bespoke',
|
||||
editor: 'detail',
|
||||
resources: 'bespoke',
|
||||
networking: 'bespoke',
|
||||
security: 'bespoke',
|
||||
templates: 'bespoke',
|
||||
'global-observability': 'bespoke',
|
||||
|
||||
Reference in New Issue
Block a user