mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-18 06:23:18 +00:00
8980910153
* 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.
65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { findingSourceLabel, groupFindings, rankFindings } from './networkingSeverity';
|
|
import type { NetworkingFinding } from '@/types/networking';
|
|
|
|
function finding(overrides: Partial<NetworkingFinding> = {}): NetworkingFinding {
|
|
return {
|
|
id: overrides.id ?? Math.random().toString(36),
|
|
kind: 'network-mode-host',
|
|
severity: 'medium',
|
|
title: 't',
|
|
message: 'm',
|
|
evidence: [],
|
|
recommendedActions: [],
|
|
sources: ['live'],
|
|
doctorFindings: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('groupFindings', () => {
|
|
it('groups critical and high into needs-action, medium into review, info into informational', () => {
|
|
const groups = groupFindings([
|
|
finding({ id: 'a', severity: 'critical' }),
|
|
finding({ id: 'b', severity: 'high' }),
|
|
finding({ id: 'c', severity: 'medium' }),
|
|
finding({ id: 'd', severity: 'info' }),
|
|
]);
|
|
expect(groups['needs-action'].map((f) => f.id)).toEqual(['a', 'b']);
|
|
expect(groups['review-recommended'].map((f) => f.id)).toEqual(['c']);
|
|
expect(groups.informational.map((f) => f.id)).toEqual(['d']);
|
|
});
|
|
});
|
|
|
|
describe('rankFindings', () => {
|
|
it('ranks live ahead of doctor-only at equal severity', () => {
|
|
const live = finding({ id: 'live', severity: 'high', sources: ['live'] });
|
|
const doctorOnly = finding({ id: 'doctor', severity: 'high', sources: ['doctor'] });
|
|
const ranked = rankFindings([doctorOnly, live]);
|
|
expect(ranked.map((f) => f.id)).toEqual(['live', 'doctor']);
|
|
});
|
|
|
|
it('ranks strictly by severity first', () => {
|
|
const low = finding({ id: 'low', severity: 'info' });
|
|
const high = finding({ id: 'high', severity: 'critical' });
|
|
expect(rankFindings([low, high]).map((f) => f.id)).toEqual(['high', 'low']);
|
|
});
|
|
});
|
|
|
|
describe('findingSourceLabel', () => {
|
|
it('labels a merged card as Live plus Doctor', () => {
|
|
const merged = finding({ sources: ['live', 'doctor'], doctorFindings: [{ ruleId: 'r', ranAt: new Date().toISOString(), title: 't', message: 'm', severity: 'high' }] });
|
|
expect(findingSourceLabel(merged)).toBe('Live · also found by Doctor');
|
|
});
|
|
|
|
it('labels a Doctor-only card with its last-run timestamp', () => {
|
|
const ranAt = new Date('2026-01-01T00:00:00Z').toISOString();
|
|
const doctorOnly = finding({ sources: ['doctor'], doctorFindings: [{ ruleId: 'r', ranAt, title: 't', message: 'm', severity: 'high' }] });
|
|
expect(findingSourceLabel(doctorOnly)).toContain('Last Doctor run');
|
|
});
|
|
|
|
it('returns null for a live-only finding', () => {
|
|
expect(findingSourceLabel(finding({ sources: ['live'] }))).toBeNull();
|
|
});
|
|
});
|