mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-04 14:45:41 +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:
@@ -0,0 +1,68 @@
|
||||
import type { NetworkingFinding, NetworkingFindingSeverity } from '@/types/networking';
|
||||
|
||||
export const NETWORKING_SEVERITY_RANK: Record<NetworkingFindingSeverity, number> = {
|
||||
info: 0,
|
||||
medium: 1,
|
||||
high: 2,
|
||||
critical: 3,
|
||||
};
|
||||
|
||||
/** Text color per severity, shared by the Overview attention list and the Findings tab. */
|
||||
export const SEVERITY_TEXT_CLASS: Record<NetworkingFindingSeverity, string> = {
|
||||
info: 'text-stat-subtitle',
|
||||
medium: 'text-warning',
|
||||
high: 'text-destructive',
|
||||
critical: 'text-destructive',
|
||||
};
|
||||
|
||||
/** Severity descending; at equal severity a live-sourced finding ranks ahead of a
|
||||
* Doctor-only finding, mirroring the backend ranking helper. */
|
||||
export function compareFindingsForRanking(a: NetworkingFinding, b: NetworkingFinding): number {
|
||||
const rankDiff = NETWORKING_SEVERITY_RANK[b.severity] - NETWORKING_SEVERITY_RANK[a.severity];
|
||||
if (rankDiff !== 0) return rankDiff;
|
||||
const aIsLive = a.sources.includes('live');
|
||||
const bIsLive = b.sources.includes('live');
|
||||
if (aIsLive !== bIsLive) return aIsLive ? -1 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function rankFindings(findings: NetworkingFinding[]): NetworkingFinding[] {
|
||||
return [...findings].sort(compareFindingsForRanking);
|
||||
}
|
||||
|
||||
export type NetworkingFindingGroup = 'needs-action' | 'review-recommended' | 'informational';
|
||||
|
||||
export const FINDING_GROUP_LABELS: Record<NetworkingFindingGroup, string> = {
|
||||
'needs-action': 'Needs action',
|
||||
'review-recommended': 'Review recommended',
|
||||
informational: 'Informational',
|
||||
};
|
||||
|
||||
export function groupForSeverity(severity: NetworkingFindingSeverity): NetworkingFindingGroup {
|
||||
if (severity === 'critical' || severity === 'high') return 'needs-action';
|
||||
if (severity === 'medium') return 'review-recommended';
|
||||
return 'informational';
|
||||
}
|
||||
|
||||
export function groupFindings(findings: NetworkingFinding[]): Record<NetworkingFindingGroup, NetworkingFinding[]> {
|
||||
const ranked = rankFindings(findings);
|
||||
const groups: Record<NetworkingFindingGroup, NetworkingFinding[]> = {
|
||||
'needs-action': [],
|
||||
'review-recommended': [],
|
||||
informational: [],
|
||||
};
|
||||
for (const f of ranked) groups[groupForSeverity(f.severity)].push(f);
|
||||
return groups;
|
||||
}
|
||||
|
||||
/** Label for a finding's source provenance, so cached Doctor findings never read as current runtime facts. */
|
||||
export function findingSourceLabel(finding: Pick<NetworkingFinding, 'sources' | 'doctorFindings'>): string | null {
|
||||
const isLive = finding.sources.includes('live');
|
||||
const isDoctor = finding.sources.includes('doctor');
|
||||
if (isLive && isDoctor) return 'Live · also found by Doctor';
|
||||
if (isDoctor && !isLive) {
|
||||
const ranAt = finding.doctorFindings[0]?.ranAt;
|
||||
return ranAt ? `Last Doctor run · ${new Date(ranAt).toLocaleString()}` : 'Last Doctor run';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user