mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-18 14:33:19 +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:
@@ -21,6 +21,7 @@ import {
|
||||
type PruneScope,
|
||||
type PruneTarget,
|
||||
} from './prunePlan';
|
||||
import type { NetworkingNetworkBase } from './network/networkingTypes';
|
||||
import { isPathWithinBase } from '../utils/validation';
|
||||
import { isDebugEnabled } from '../utils/debug';
|
||||
import { sanitizeForLog } from '../utils/safeLog';
|
||||
@@ -220,6 +221,8 @@ export interface DependencyNetwork {
|
||||
driver: string;
|
||||
scope: string;
|
||||
isSystem: boolean;
|
||||
ingress?: boolean;
|
||||
enableIPv6?: boolean;
|
||||
/** Raw com.docker.compose.project label (may not map to a known stack). */
|
||||
composeProject: string | null;
|
||||
/** Resolved Sencho stack this network belongs to, or null. */
|
||||
@@ -1773,12 +1776,15 @@ class DockerController {
|
||||
|
||||
const networks: DependencyNetwork[] = networksRaw.map((net: any) => {
|
||||
const project = net.Labels?.['com.docker.compose.project'] ?? null;
|
||||
const ingress = net.Ingress === true;
|
||||
return {
|
||||
id: net.Id,
|
||||
name: net.Name,
|
||||
driver: net.Driver ?? 'bridge',
|
||||
scope: net.Scope ?? 'local',
|
||||
isSystem: DockerController.SYSTEM_NETWORKS.has(net.Name),
|
||||
isSystem: DockerController.SYSTEM_NETWORKS.has(net.Name) || ingress,
|
||||
ingress,
|
||||
...(typeof net.EnableIPv6 === 'boolean' ? { enableIPv6: net.EnableIPv6 } : {}),
|
||||
composeProject: project,
|
||||
stack: DockerController.resolveProjectLabel(project ?? undefined, knownSet, projectToStack),
|
||||
};
|
||||
@@ -1797,6 +1803,51 @@ class DockerController {
|
||||
return { containers, networks, volumes };
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies snapshot networks into base inventory rows (phase A). Uses only
|
||||
* the provided snapshot; no additional Docker API calls.
|
||||
*/
|
||||
public static classifySnapshotNetworks(
|
||||
snapshot: DependencySnapshot,
|
||||
_knownStackNames: string[],
|
||||
): NetworkingNetworkBase[] {
|
||||
const selfIdentity = SelfIdentityService.getInstance();
|
||||
const connectedByNetwork = new Map<string, number>();
|
||||
for (const c of snapshot.containers) {
|
||||
for (const attached of c.networks) {
|
||||
const key = attached.id || attached.name;
|
||||
connectedByNetwork.set(key, (connectedByNetwork.get(key) ?? 0) + 1);
|
||||
if (attached.name && attached.name !== key) {
|
||||
connectedByNetwork.set(attached.name, (connectedByNetwork.get(attached.name) ?? 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot.networks.map(net => ({
|
||||
id: net.id,
|
||||
name: net.name,
|
||||
driver: net.driver,
|
||||
scope: net.scope,
|
||||
isSystem: net.isSystem,
|
||||
ingress: net.ingress === true,
|
||||
enableIPv6: net.enableIPv6,
|
||||
composeProject: net.composeProject,
|
||||
stack: net.stack,
|
||||
connectedCount: connectedByNetwork.get(net.id) ?? connectedByNetwork.get(net.name) ?? 0,
|
||||
isSencho: selfIdentity.isOwnNetwork(net.id) || selfIdentity.isOwnNetwork(net.name),
|
||||
ownership: net.isSystem || net.ingress === true
|
||||
? 'system'
|
||||
: net.stack
|
||||
? 'sencho-managed'
|
||||
: net.composeProject
|
||||
? 'compose-managed'
|
||||
: 'unmanaged',
|
||||
declaredByStacks: [],
|
||||
declaredExternalByStacks: [],
|
||||
isExternalDependency: false,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Resolves a Docker Compose project label to a known Sencho stack name, or null. */
|
||||
private static resolveProjectLabel(
|
||||
project: string | undefined,
|
||||
|
||||
Reference in New Issue
Block a user