generated from coulomb/repo-seed
feat: add deployment zone overlays
This commit is contained in:
@@ -574,7 +574,10 @@ def graph_explorer_page() -> str:
|
||||
|
||||
const elementText = (data) => [
|
||||
data.id, data.stableKey, data.label, data.name, data.description,
|
||||
data.repo, data.domain, data.kind, data.layer, data.edgeType
|
||||
data.repo, data.domain, data.kind, data.layer, data.edgeType,
|
||||
data.deploymentEnvironment, data.deploymentScenario, data.routingAuthority,
|
||||
data.accessZone, data.policyAuthority, data.exposureClass,
|
||||
data.routeHost, data.routeHostname, data.routePort, data.routeProtocol, data.route
|
||||
].join(" ").toLowerCase();
|
||||
|
||||
const overrideKey = (element) => element.data("stableKey") || element.id();
|
||||
@@ -614,6 +617,37 @@ def graph_explorer_page() -> str:
|
||||
|
||||
const selectedEdgeTypes = () => checkedValues(edgeTypeFilter);
|
||||
|
||||
const hasValue = (value) => value !== undefined && value !== null && value !== "";
|
||||
|
||||
const overlayRuleAttributes = [
|
||||
"deploymentEnvironment",
|
||||
"deploymentScenario",
|
||||
"routingAuthority",
|
||||
"accessZone",
|
||||
"policyAuthority",
|
||||
"exposureClass",
|
||||
"routeHost",
|
||||
"routeHostname",
|
||||
"routePort",
|
||||
"routeProtocol",
|
||||
];
|
||||
|
||||
const zoneModeFields = {
|
||||
"by-fabric": ["domain", "repo", "kind"],
|
||||
"by-deployment-environment": ["deploymentEnvironment"],
|
||||
"by-deployment-scenario": ["deploymentScenario"],
|
||||
"by-routing-authority": ["routingAuthority"],
|
||||
"by-access-zone": ["accessZone"],
|
||||
};
|
||||
|
||||
const zoneModeLabels = {
|
||||
"by-fabric": "Fabric",
|
||||
"by-deployment-environment": "Deployment Environment",
|
||||
"by-deployment-scenario": "Deployment Scenario",
|
||||
"by-routing-authority": "Routing Authority",
|
||||
"by-access-zone": "Access Zone",
|
||||
};
|
||||
|
||||
const summarizeSelection = (selected, allValues, labels, noun) => {
|
||||
if (selected.size === 0) return `No ${noun}`;
|
||||
if (selected.size === allValues.length) return `All ${noun}`;
|
||||
@@ -657,11 +691,21 @@ def graph_explorer_page() -> str:
|
||||
strength: "Strength",
|
||||
sameRepo: "Same repo",
|
||||
layoutAffinity: "Layout affinity",
|
||||
deploymentEnvironment: "Deployment Environment",
|
||||
deploymentScenario: "Deployment Scenario",
|
||||
routingAuthority: "Routing Authority",
|
||||
accessZone: "Access Zone",
|
||||
policyAuthority: "Policy Authority",
|
||||
exposureClass: "Exposure Class",
|
||||
routeHost: "Route Host",
|
||||
routeHostname: "Route Hostname",
|
||||
routePort: "Route Port",
|
||||
routeProtocol: "Route Protocol",
|
||||
};
|
||||
|
||||
const ruleAttributeCandidates = {
|
||||
node: ["any", "repo", "kind", "reviewState", "unresolved", "lifecycle"],
|
||||
edge: ["any", "repo", "kind", "reviewState", "unresolved", "strength", "sameRepo", "layoutAffinity"],
|
||||
node: ["any", "repo", "kind", "reviewState", "unresolved", "lifecycle", ...overlayRuleAttributes],
|
||||
edge: ["any", "repo", "kind", "reviewState", "unresolved", "strength", "sameRepo", "layoutAffinity", ...overlayRuleAttributes],
|
||||
};
|
||||
|
||||
const renderOptions = (select, options, current = "") => {
|
||||
@@ -835,6 +879,79 @@ def graph_explorer_page() -> str:
|
||||
(!type || edge.data("edgeType") === type)
|
||||
) : [];
|
||||
|
||||
const routeLabel = (data) => {
|
||||
if (hasValue(data.route)) return data.route;
|
||||
const host = data.routeHost || data.routeHostname || "";
|
||||
const protocol = data.routeProtocol || "";
|
||||
const port = data.routePort || "";
|
||||
if (!hasValue(host) && !hasValue(port)) return "";
|
||||
const authority = hasValue(port) ? `${host}:${port}` : host;
|
||||
return [protocol, authority].filter(hasValue).join(" ");
|
||||
};
|
||||
|
||||
const hasRouteEvidence = (data) =>
|
||||
hasValue(data.route) ||
|
||||
hasValue(data.routeHost) ||
|
||||
hasValue(data.routeHostname) ||
|
||||
hasValue(data.routePort);
|
||||
|
||||
const zoneWarningsForData = (data) => {
|
||||
const warnings = [];
|
||||
if (hasRouteEvidence(data) && !hasValue(data.policyAuthority)) {
|
||||
warnings.push("route without policy authority");
|
||||
}
|
||||
return warnings;
|
||||
};
|
||||
|
||||
const groupCounts = (elements, field) => {
|
||||
const counts = new Map();
|
||||
elements.forEach((element) => {
|
||||
const value = element.data(field);
|
||||
if (hasValue(value)) counts.set(String(value), (counts.get(String(value)) || 0) + 1);
|
||||
});
|
||||
return Array.from(counts.entries())
|
||||
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
|
||||
};
|
||||
|
||||
const renderMapOverview = () => {
|
||||
const visibleElements = cy
|
||||
? cy.elements().filter((element) => element.style("display") !== "none").toArray()
|
||||
: [];
|
||||
const visibleNodes = visibleElements.filter((element) => element.isNode()).length;
|
||||
const visibleEdges = visibleElements.filter((element) => element.isEdge()).length;
|
||||
const fields = zoneModeFields[activeMode] || [];
|
||||
const title = zoneModeLabels[activeMode] || "Full";
|
||||
detailTitle.textContent = activeMode === "full" ? "Fabric Map" : `${title} View`;
|
||||
detailSummary.textContent = `${visibleNodes} nodes / ${visibleEdges} edges visible`;
|
||||
detailPills.innerHTML = activeMode === "full" ? "" : `<span class="pill">${escapeHtml(activeMode)}</span>`;
|
||||
|
||||
const rows = [{label: "visible", value: `${visibleNodes} nodes / ${visibleEdges} edges`}];
|
||||
fields.forEach((field) => {
|
||||
const counts = groupCounts(visibleElements, field);
|
||||
rows.push({
|
||||
label: ruleAttributeLabels[field] || humanize(field),
|
||||
value: counts.length
|
||||
? counts.slice(0, 8).map(([value, count]) => `${value} (${count})`).join(", ")
|
||||
: "no annotated visible entities",
|
||||
state: counts.length ? "" : "warning",
|
||||
});
|
||||
});
|
||||
|
||||
const warnings = visibleElements
|
||||
.flatMap((element) => zoneWarningsForData(element.data()).map((warning) =>
|
||||
`${elementLabel(element)}: ${warning}`
|
||||
));
|
||||
warnings.slice(0, 6).forEach((warning) => rows.push({label: "warning", value: warning, state: "warning"}));
|
||||
if (warnings.length > 6) {
|
||||
rows.push({label: "warning", value: `${warnings.length - 6} additional route warnings`, state: "warning"});
|
||||
}
|
||||
|
||||
detailList.innerHTML = rows
|
||||
.filter((row) => row.value)
|
||||
.map((row) => `<li class="${orientationStateClass(row.state)}"><strong>${escapeHtml(row.label)}</strong> ${escapeHtml(row.value)}</li>`)
|
||||
.join("");
|
||||
};
|
||||
|
||||
const collectionArray = (collection) => Array.from(collection || []);
|
||||
|
||||
const orientationStateClass = (state) =>
|
||||
@@ -1145,6 +1262,12 @@ def graph_explorer_page() -> str:
|
||||
if (activeMode === "unresolved") {
|
||||
return new Set(cy.elements().filter((element) => element.data("unresolved") === true).map((element) => element.id()));
|
||||
}
|
||||
const zoneFields = zoneModeFields[activeMode] || [];
|
||||
if (zoneFields.length && activeMode !== "by-fabric") {
|
||||
return new Set(cy.elements().filter((element) =>
|
||||
zoneFields.some((field) => hasValue(element.data(field)))
|
||||
).map((element) => element.id()));
|
||||
}
|
||||
if (!selected) return null;
|
||||
if (activeMode === "selected-path") {
|
||||
const collection = selected.union(selected.predecessors()).union(selected.successors());
|
||||
@@ -1204,6 +1327,7 @@ def graph_explorer_page() -> str:
|
||||
hiddenSummary.textContent = removed ? `Hidden ${hidden} / Removed ${removed}` : `Hidden ${hidden}`;
|
||||
updateLabelVisibility();
|
||||
updateSelectionAnchor();
|
||||
if (!selected) renderMapOverview();
|
||||
if (options.redrawOnRemove && previousRemoved !== ruleRemovalSignature()) runLayout();
|
||||
};
|
||||
|
||||
@@ -1211,10 +1335,7 @@ def graph_explorer_page() -> str:
|
||||
selected = element || null;
|
||||
if (!element) {
|
||||
orientationContext = null;
|
||||
detailTitle.textContent = "Fabric Map";
|
||||
detailSummary.textContent = "No selection";
|
||||
detailPills.innerHTML = "";
|
||||
detailList.innerHTML = "";
|
||||
renderMapOverview();
|
||||
orientationTitle.textContent = "Select a service, interface, dependency, or registered-only repo.";
|
||||
orientationList.innerHTML = "";
|
||||
orientationActions.innerHTML = "";
|
||||
@@ -1241,6 +1362,14 @@ def graph_explorer_page() -> str:
|
||||
["mapping", data.mappingFit],
|
||||
["display only", data.displayOnly === true ? "yes" : ""],
|
||||
["strength", data.strength],
|
||||
["deployment environment", data.deploymentEnvironment],
|
||||
["deployment scenario", data.deploymentScenario],
|
||||
["routing authority", data.routingAuthority],
|
||||
["access zone", data.accessZone],
|
||||
["policy authority", data.policyAuthority],
|
||||
["exposure", data.exposureClass],
|
||||
["route", routeLabel(data)],
|
||||
...zoneWarningsForData(data).map((warning) => ["warning", warning]),
|
||||
...Object.entries(links),
|
||||
...refs.map((ref) => [ref.label || ref.kind || "source", ref.path || ref.url || ref.ref || ""])
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user