mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-27 10:46:51 +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:
@@ -77,8 +77,12 @@ export function assembleStackNetworkFacts(
|
||||
return { stack: stackName, renderable: true, renderError: null, runtime, networks, services, drift };
|
||||
}
|
||||
|
||||
/** Render the effective model and snapshot the node, then assemble the facts. */
|
||||
export async function buildStackNetworkFacts(nodeId: number, stackName: string): Promise<StackNetworkFacts> {
|
||||
/** Render the effective model and assemble facts, optionally reusing a snapshot. */
|
||||
export async function buildStackNetworkFacts(
|
||||
nodeId: number,
|
||||
stackName: string,
|
||||
sharedSnapshot?: DependencySnapshot | null,
|
||||
): Promise<StackNetworkFacts> {
|
||||
const fsSvc = FileSystemService.getInstance(nodeId);
|
||||
|
||||
let model: EffectiveModel | null = null;
|
||||
@@ -89,34 +93,33 @@ export async function buildStackNetworkFacts(nodeId: number, stackName: string):
|
||||
try {
|
||||
model = parseEffectiveModel(JSON.parse(result.rendered), stackName);
|
||||
} catch (parseErr) {
|
||||
// JSON.parse errors carry no file content, so the message is safe to log.
|
||||
console.warn('[NetworkInspector] Effective model parse failed for %s:',
|
||||
sanitizeForLog(stackName), sanitizeForLog(getErrorMessage(parseErr, 'unknown')));
|
||||
renderError = 'Sencho could not parse the rendered Compose model.';
|
||||
}
|
||||
} else {
|
||||
// Raw stderr can echo file content/secrets and is never surfaced; only the
|
||||
// names of any missing required variables, otherwise a generic nudge.
|
||||
const missing = parseMissingRequiredVars(result.stderr);
|
||||
renderError = missing.length
|
||||
? `Required variable${missing.length > 1 ? 's' : ''} ${missing.join(', ')} ${missing.length > 1 ? 'have' : 'has'} no value, so the effective model cannot be rendered.`
|
||||
: 'Sencho could not render the effective Compose model. Check the compose and env files for a YAML syntax error, an unresolved include or merge, or a required variable with no value.';
|
||||
}
|
||||
} catch (err) {
|
||||
// Spawn failure (docker unavailable). Redact defensively.
|
||||
renderError = redactSensitiveText(getErrorMessage(err, 'docker compose could not be started.')).slice(0, MAX_RENDER_ERROR).trim()
|
||||
|| 'Sencho could not run docker compose on this node.';
|
||||
}
|
||||
|
||||
// A null snapshot means the runtime is unavailable (drift is then left empty),
|
||||
// never confused with a real empty snapshot.
|
||||
let snapshot: DependencySnapshot | null = null;
|
||||
try {
|
||||
const knownStacks = await fsSvc.getStacks();
|
||||
snapshot = await DockerController.getInstance(nodeId).getDependencySnapshot(knownStacks);
|
||||
} catch (error) {
|
||||
console.warn('[NetworkInspector] Node snapshot unavailable for %s; runtime facts skipped:',
|
||||
sanitizeForLog(stackName), sanitizeForLog(getErrorMessage(error, 'unknown')));
|
||||
let snapshot: DependencySnapshot | null;
|
||||
if (sharedSnapshot !== undefined) {
|
||||
snapshot = sharedSnapshot;
|
||||
} else {
|
||||
try {
|
||||
const knownStacks = await fsSvc.getStacks();
|
||||
snapshot = await DockerController.getInstance(nodeId).getDependencySnapshot(knownStacks);
|
||||
} catch (error) {
|
||||
console.warn('[NetworkInspector] Node snapshot unavailable for %s; runtime facts skipped:',
|
||||
sanitizeForLog(stackName), sanitizeForLog(getErrorMessage(error, 'unknown')));
|
||||
snapshot = null;
|
||||
}
|
||||
}
|
||||
|
||||
return assembleStackNetworkFacts(stackName, model, renderError, snapshot);
|
||||
|
||||
Reference in New Issue
Block a user