@64mb/freelens-power 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/out/main/common/store/monitoring-preferences-store.js +5 -0
- package/out/main/common/store/monitoring-preferences-store.js.map +1 -1
- package/out/renderer/ai-diagnostic-handoff.js +52 -0
- package/out/renderer/ai-diagnostic-handoff.js.map +1 -0
- package/out/renderer/ai-extras.js +40 -4
- package/out/renderer/ai-extras.js.map +1 -1
- package/out/renderer/ai.js +48 -56
- package/out/renderer/ai.js.map +1 -1
- package/out/renderer/details/hpa-diagnostics-details.js +46 -0
- package/out/renderer/details/hpa-diagnostics-details.js.map +1 -0
- package/out/renderer/details/namespace-health-details.js +91 -0
- package/out/renderer/details/namespace-health-details.js.map +1 -0
- package/out/renderer/details/resource-health-details.js +131 -0
- package/out/renderer/details/resource-health-details.js.map +1 -0
- package/out/renderer/details/service-diagnostics-details.js +163 -0
- package/out/renderer/details/service-diagnostics-details.js.map +1 -0
- package/out/renderer/diagnostic-explain-button.js +16 -0
- package/out/renderer/diagnostic-explain-button.js.map +1 -0
- package/out/renderer/diagnostics-state.js +51 -0
- package/out/renderer/diagnostics-state.js.map +1 -0
- package/out/renderer/diagnostics.css.js +9 -0
- package/out/renderer/diagnostics.css.js.map +1 -1
- package/out/renderer/diagnostics.js +369 -73
- package/out/renderer/diagnostics.js.map +1 -1
- package/out/renderer/incident-copy-button.js +50 -0
- package/out/renderer/incident-copy-button.js.map +1 -0
- package/out/renderer/index.js +91 -19
- package/out/renderer/index.js.map +1 -1
- package/out/renderer/monitoring-history-collection.js +1 -1
- package/out/renderer/preferences/monitoring-preferences.js +10 -0
- package/out/renderer/preferences/monitoring-preferences.js.map +1 -1
- package/out/renderer/preferences/preferences.js +88 -0
- package/out/renderer/preferences/preferences.js.map +1 -1
- package/out/renderer/resource-relationships.css.js +7 -0
- package/out/renderer/resource-relationships.css.js.map +1 -0
- package/out/renderer/resource-relationships.js +556 -0
- package/out/renderer/resource-relationships.js.map +1 -0
- package/out/renderer/resource-search-distance.js +24 -0
- package/out/renderer/resource-search-distance.js.map +1 -0
- package/out/renderer/resource-search-fields.js +55 -0
- package/out/renderer/resource-search-fields.js.map +1 -0
- package/out/renderer/resource-search-model.js +193 -0
- package/out/renderer/resource-search-model.js.map +1 -0
- package/out/renderer/resource-search.js +365 -0
- package/out/renderer/resource-search.js.map +1 -0
- package/out/renderer/src/common/hpa-diagnostics.js +24 -0
- package/out/renderer/src/common/hpa-diagnostics.js.map +1 -0
- package/out/renderer/src/common/incident-summary.js +73 -0
- package/out/renderer/src/common/incident-summary.js.map +1 -0
- package/out/renderer/src/common/resource-health.js +92 -0
- package/out/renderer/src/common/resource-health.js.map +1 -0
- package/out/renderer/src/common/resource-relationships.js +155 -0
- package/out/renderer/src/common/resource-relationships.js.map +1 -0
- package/out/renderer/src/common/service-diagnostics.js +50 -0
- package/out/renderer/src/common/service-diagnostics.js.map +1 -0
- package/out/renderer/src/common/store/monitoring-preferences-store.js +5 -0
- package/out/renderer/src/common/store/monitoring-preferences-store.js.map +1 -1
- package/out/renderer/src/common/timeline-groups.js +36 -0
- package/out/renderer/src/common/timeline-groups.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
const require_extensions = require("./_virtual/_global-external_@freelensapp/extensions.js");
|
|
2
|
+
const require__global_external_mobx_react = require("./_virtual/_global-external_mobx-react.js");
|
|
3
|
+
const require__global_external_react = require("./_virtual/_global-external_react.js");
|
|
4
|
+
const require_jsx_runtime = require("./_virtual/_global-external_react/jsx-runtime.js");
|
|
5
|
+
const require_resource_relationships = require("./src/common/resource-relationships.js");
|
|
6
|
+
const require_resource_relationships_css = require("./resource-relationships.css.js");
|
|
7
|
+
//#region src/renderer/resource-relationships.tsx
|
|
8
|
+
var { Icon, Select } = require_extensions.Renderer.Component;
|
|
9
|
+
var LIMIT = 100;
|
|
10
|
+
var roles = [
|
|
11
|
+
{
|
|
12
|
+
id: "upstream",
|
|
13
|
+
name: "Upstream references",
|
|
14
|
+
relation: "references backend"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "service",
|
|
18
|
+
name: "Focused Service",
|
|
19
|
+
relation: "API configuration"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "backend",
|
|
23
|
+
name: "Selected & published backends",
|
|
24
|
+
relation: "selector matches / published in"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "placement",
|
|
28
|
+
name: "Ownership & placement",
|
|
29
|
+
relation: "owned by / assigned to"
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
var icons = {
|
|
33
|
+
Ingress: "login",
|
|
34
|
+
Service: "share",
|
|
35
|
+
Pod: "view_in_ar",
|
|
36
|
+
EndpointSlice: "hub",
|
|
37
|
+
Node: "dns",
|
|
38
|
+
ReplicaSet: "layers",
|
|
39
|
+
Deployment: "layers",
|
|
40
|
+
StatefulSet: "layers",
|
|
41
|
+
DaemonSet: "layers",
|
|
42
|
+
Job: "work",
|
|
43
|
+
CronJob: "schedule"
|
|
44
|
+
};
|
|
45
|
+
async function loadRelationshipInventory(namespace, service, signal) {
|
|
46
|
+
const gaps = [];
|
|
47
|
+
const list = async (name, request) => {
|
|
48
|
+
try {
|
|
49
|
+
const items = await request();
|
|
50
|
+
if (!items) {
|
|
51
|
+
gaps.push(`${name} inventory unavailable`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (items.length > LIMIT) gaps.push(`${name} inventory capped at ${LIMIT} objects; additional relationships may be omitted`);
|
|
55
|
+
return items.slice(0, LIMIT);
|
|
56
|
+
} catch {
|
|
57
|
+
if (!signal.aborted) gaps.push(`${name} inventory unavailable; access or API availability may be restricted`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const options = {
|
|
62
|
+
namespace,
|
|
63
|
+
reqInit: { signal }
|
|
64
|
+
};
|
|
65
|
+
const query = { limit: 101 };
|
|
66
|
+
const selector = Object.entries(service.spec.selector ?? {}).map(([key, value]) => `${key}=${value}`).join(",");
|
|
67
|
+
const external = service.spec.type === "ExternalName";
|
|
68
|
+
const ownerApis = external ? [] : [
|
|
69
|
+
require_extensions.Renderer.K8sApi.replicaSetApi,
|
|
70
|
+
require_extensions.Renderer.K8sApi.deploymentApi,
|
|
71
|
+
require_extensions.Renderer.K8sApi.statefulSetApi,
|
|
72
|
+
require_extensions.Renderer.K8sApi.daemonSetApi,
|
|
73
|
+
require_extensions.Renderer.K8sApi.jobApi,
|
|
74
|
+
require_extensions.Renderer.K8sApi.cronJobApi
|
|
75
|
+
];
|
|
76
|
+
const [pods, slices, ingresses, ...owners] = await Promise.all([
|
|
77
|
+
selector && !external ? list("Pod", () => require_extensions.Renderer.K8sApi.podsApi.list(options, {
|
|
78
|
+
...query,
|
|
79
|
+
labelSelector: selector
|
|
80
|
+
})) : Promise.resolve([]),
|
|
81
|
+
service.spec.type === "ExternalName" ? Promise.resolve([]) : list("EndpointSlice", () => require_extensions.Renderer.K8sApi.endpointSliceApi.list(options, {
|
|
82
|
+
...query,
|
|
83
|
+
labelSelector: `kubernetes.io/service-name=${service.getName()}`
|
|
84
|
+
})),
|
|
85
|
+
list("Ingress", () => require_extensions.Renderer.K8sApi.ingressApi.list(options, query)),
|
|
86
|
+
...ownerApis.map((api) => list("Controller", () => api.list(options, query)))
|
|
87
|
+
]);
|
|
88
|
+
return {
|
|
89
|
+
pods,
|
|
90
|
+
slices,
|
|
91
|
+
ingresses,
|
|
92
|
+
owners: owners.flatMap((items) => items ?? []),
|
|
93
|
+
gaps
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function ResourceCard({ node, incoming, onOpen, onHighlight }) {
|
|
97
|
+
return /* @__PURE__ */ require_jsx_runtime.jsxs("article", {
|
|
98
|
+
className: "card",
|
|
99
|
+
"data-node-id": node.id,
|
|
100
|
+
onMouseEnter: () => onHighlight(node.id),
|
|
101
|
+
onMouseLeave: () => onHighlight(),
|
|
102
|
+
onFocus: () => onHighlight(node.id),
|
|
103
|
+
onBlur: () => onHighlight(),
|
|
104
|
+
children: [
|
|
105
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
106
|
+
className: "relation-label",
|
|
107
|
+
children: incoming.join(" · ") || "Focus"
|
|
108
|
+
}),
|
|
109
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
110
|
+
className: "card-kind",
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ require_jsx_runtime.jsx(Icon, {
|
|
113
|
+
material: icons[node.kind] ?? "account_tree",
|
|
114
|
+
size: 16
|
|
115
|
+
}),
|
|
116
|
+
node.kind,
|
|
117
|
+
node.inferred && " · reference"
|
|
118
|
+
]
|
|
119
|
+
}),
|
|
120
|
+
node.selfLink ? /* @__PURE__ */ require_jsx_runtime.jsx("button", {
|
|
121
|
+
type: "button",
|
|
122
|
+
className: "resource-name",
|
|
123
|
+
onClick: (event) => {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
event.stopPropagation();
|
|
126
|
+
onOpen(node.selfLink);
|
|
127
|
+
},
|
|
128
|
+
title: `Open ${node.kind} ${node.name}`,
|
|
129
|
+
children: node.name
|
|
130
|
+
}) : /* @__PURE__ */ require_jsx_runtime.jsx("strong", { children: node.name }),
|
|
131
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("p", {
|
|
132
|
+
className: node.tone,
|
|
133
|
+
children: [
|
|
134
|
+
/* @__PURE__ */ require_jsx_runtime.jsx(Icon, {
|
|
135
|
+
material: node.tone === "warning" ? "warning" : node.tone === "healthy" ? "check_circle" : "info",
|
|
136
|
+
size: 14
|
|
137
|
+
}),
|
|
138
|
+
" ",
|
|
139
|
+
node.detail
|
|
140
|
+
]
|
|
141
|
+
})
|
|
142
|
+
]
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function roundedConnector(points) {
|
|
146
|
+
let path = `M ${points[0][0]} ${points[0][1]}`;
|
|
147
|
+
for (let index = 1; index < points.length - 1; index++) {
|
|
148
|
+
const [x, y] = points[index];
|
|
149
|
+
const [px, py] = points[index - 1];
|
|
150
|
+
const [nx, ny] = points[index + 1];
|
|
151
|
+
const before = Math.hypot(x - px, y - py);
|
|
152
|
+
const after = Math.hypot(nx - x, ny - y);
|
|
153
|
+
if (!before || !after) continue;
|
|
154
|
+
const radius = Math.min(6, before / 2, after / 2);
|
|
155
|
+
path += ` L ${x + (px - x) * radius / before} ${y + (py - y) * radius / before}`;
|
|
156
|
+
path += ` Q ${x} ${y} ${x + (nx - x) * radius / after} ${y + (ny - y) * radius / after}`;
|
|
157
|
+
}
|
|
158
|
+
const last = points[points.length - 1];
|
|
159
|
+
return `${path} L ${last[0]} ${last[1]}`;
|
|
160
|
+
}
|
|
161
|
+
function RelationshipGraph({ graph, onOpen, ingressAvailable, hideUpstreams = false }) {
|
|
162
|
+
const visibleRoles = roles.filter((role) => !hideUpstreams || role.id !== "upstream");
|
|
163
|
+
const element = require__global_external_react.useRef(null);
|
|
164
|
+
const [paths, setPaths] = require__global_external_react.useState([]);
|
|
165
|
+
const [highlight, setHighlight] = require__global_external_react.useState();
|
|
166
|
+
require__global_external_react.useLayoutEffect(() => {
|
|
167
|
+
const container = element.current;
|
|
168
|
+
if (!container) return;
|
|
169
|
+
const measure = () => {
|
|
170
|
+
const origin = container.getBoundingClientRect();
|
|
171
|
+
const cards = new Map(Array.from(container.querySelectorAll("[data-node-id]")).map((card) => [card.dataset.nodeId, card.getBoundingClientRect()]));
|
|
172
|
+
setPaths(graph.edges.flatMap((edge, index) => {
|
|
173
|
+
const from = cards.get(edge.from);
|
|
174
|
+
const to = cards.get(edge.to);
|
|
175
|
+
if (!from || !to) return [];
|
|
176
|
+
const sy = from.top + from.height / 2 - origin.top;
|
|
177
|
+
const ty = to.top + to.height / 2 - origin.top;
|
|
178
|
+
const sameColumn = Math.abs(from.left - to.left) < 2;
|
|
179
|
+
const sx = (sameColumn ? from.left : from.right) - origin.left;
|
|
180
|
+
const tx = to.left - origin.left;
|
|
181
|
+
const laneOffset = (index % 3 - 1) * 8;
|
|
182
|
+
const gutter = sameColumn ? sx - 28 + laneOffset : (sx + tx) / 2 + laneOffset;
|
|
183
|
+
const crossesColumn = Array.from(cards.values()).some((card) => card.left > from.left + 2 && card.right < to.left - 2);
|
|
184
|
+
let path = !sameColumn && sy === ty ? `M ${sx} ${sy} H ${tx}` : roundedConnector([
|
|
185
|
+
[sx, sy],
|
|
186
|
+
[gutter, sy],
|
|
187
|
+
[gutter, ty],
|
|
188
|
+
[tx, ty]
|
|
189
|
+
]);
|
|
190
|
+
if (crossesColumn) {
|
|
191
|
+
const bottom = Math.max(...Array.from(cards.values(), (card) => card.bottom)) - origin.top + 24 + index % 3 * 8;
|
|
192
|
+
const sourceGutter = sx + 32 + laneOffset;
|
|
193
|
+
const targetGutter = tx - 32 + laneOffset;
|
|
194
|
+
path = roundedConnector([
|
|
195
|
+
[sx, sy],
|
|
196
|
+
[sourceGutter, sy],
|
|
197
|
+
[sourceGutter, bottom],
|
|
198
|
+
[targetGutter, bottom],
|
|
199
|
+
[targetGutter, ty],
|
|
200
|
+
[tx, ty]
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
return [{
|
|
204
|
+
...edge,
|
|
205
|
+
path
|
|
206
|
+
}];
|
|
207
|
+
}));
|
|
208
|
+
};
|
|
209
|
+
measure();
|
|
210
|
+
const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(measure);
|
|
211
|
+
observer?.observe(container);
|
|
212
|
+
for (const card of container.querySelectorAll("[data-node-id]")) observer?.observe(card);
|
|
213
|
+
window.addEventListener("resize", measure);
|
|
214
|
+
return () => {
|
|
215
|
+
observer?.disconnect();
|
|
216
|
+
window.removeEventListener("resize", measure);
|
|
217
|
+
};
|
|
218
|
+
}, [graph, hideUpstreams]);
|
|
219
|
+
return /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
220
|
+
className: "graph-scroll",
|
|
221
|
+
children: /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
222
|
+
ref: element,
|
|
223
|
+
className: `graph${hideUpstreams ? " hide-upstreams" : ""}`,
|
|
224
|
+
style: { gridTemplateRows: `auto repeat(${Math.max(1, ...visibleRoles.map((role) => graph.nodes.filter((node) => node.role === role.id).length))}, auto)` },
|
|
225
|
+
"aria-label": "Service resource relationship graph",
|
|
226
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsxs("svg", {
|
|
227
|
+
className: "resource-connections",
|
|
228
|
+
width: "100%",
|
|
229
|
+
height: "100%",
|
|
230
|
+
"aria-hidden": "true",
|
|
231
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx("defs", { children: /* @__PURE__ */ require_jsx_runtime.jsx("marker", {
|
|
232
|
+
id: "relationship-arrow",
|
|
233
|
+
markerWidth: "5",
|
|
234
|
+
markerHeight: "5",
|
|
235
|
+
refX: "4",
|
|
236
|
+
refY: "2.5",
|
|
237
|
+
orient: "auto",
|
|
238
|
+
children: /* @__PURE__ */ require_jsx_runtime.jsx("path", {
|
|
239
|
+
d: "M0 0L5 2.5L0 5",
|
|
240
|
+
fill: "currentColor"
|
|
241
|
+
})
|
|
242
|
+
}) }), paths.map((edge) => /* @__PURE__ */ require_jsx_runtime.jsx("path", {
|
|
243
|
+
"data-edge-from": edge.from,
|
|
244
|
+
"data-edge-to": edge.to,
|
|
245
|
+
className: highlight ? edge.from === highlight || edge.to === highlight ? "connection highlighted" : "connection dimmed" : "connection",
|
|
246
|
+
d: edge.path,
|
|
247
|
+
markerEnd: "url(#relationship-arrow)",
|
|
248
|
+
children: /* @__PURE__ */ require_jsx_runtime.jsxs("title", { children: [
|
|
249
|
+
graph.nodes.find((node) => node.id === edge.from)?.name,
|
|
250
|
+
" → ",
|
|
251
|
+
edge.label,
|
|
252
|
+
" →",
|
|
253
|
+
" ",
|
|
254
|
+
graph.nodes.find((node) => node.id === edge.to)?.name
|
|
255
|
+
] })
|
|
256
|
+
}, `${edge.from}/${edge.to}/${edge.label}`))]
|
|
257
|
+
}), visibleRoles.map((role) => /* @__PURE__ */ require_jsx_runtime.jsxs("section", {
|
|
258
|
+
className: "column",
|
|
259
|
+
"aria-label": role.name,
|
|
260
|
+
children: [
|
|
261
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("h3", { children: role.name }),
|
|
262
|
+
graph.nodes.filter((node) => node.role === role.id).map((node) => /* @__PURE__ */ require_jsx_runtime.jsx(ResourceCard, {
|
|
263
|
+
node,
|
|
264
|
+
incoming: [...new Set(graph.edges.filter((edge) => edge.to === node.id).map((edge) => edge.label))],
|
|
265
|
+
onOpen,
|
|
266
|
+
onHighlight: setHighlight
|
|
267
|
+
}, node.id)),
|
|
268
|
+
!graph.nodes.some((node) => node.role === role.id) && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
269
|
+
className: "card neutral",
|
|
270
|
+
children: role.id === "upstream" ? ingressAvailable ? "No matching Ingress references observed" : "Ingress evidence unavailable" : "No relationships observed in available evidence"
|
|
271
|
+
})
|
|
272
|
+
]
|
|
273
|
+
}, role.id))]
|
|
274
|
+
})
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
var ResourceRelationshipsPage = require__global_external_mobx_react.observer(({ enabled = true }) => {
|
|
278
|
+
if (!enabled) return /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
279
|
+
className: "power-relationships",
|
|
280
|
+
children: /* @__PURE__ */ require_jsx_runtime.jsx("p", { children: "Resource relationships are disabled in Power settings" })
|
|
281
|
+
});
|
|
282
|
+
return /* @__PURE__ */ require_jsx_runtime.jsx(EnabledResourceRelationshipsPage, {}, require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid ?? "cluster");
|
|
283
|
+
});
|
|
284
|
+
function EnabledResourceRelationshipsPage() {
|
|
285
|
+
const mounted = require__global_external_react.useRef(true);
|
|
286
|
+
const clusterId = require__global_external_react.useRef(require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid);
|
|
287
|
+
require__global_external_react.useEffect(() => {
|
|
288
|
+
mounted.current = true;
|
|
289
|
+
return () => {
|
|
290
|
+
mounted.current = false;
|
|
291
|
+
};
|
|
292
|
+
}, []);
|
|
293
|
+
const [namespaces, setNamespaces] = require__global_external_react.useState([]);
|
|
294
|
+
const [namespace, setNamespace] = require__global_external_react.useState("");
|
|
295
|
+
const [services, setServices] = require__global_external_react.useState([]);
|
|
296
|
+
const [serviceName, setServiceName] = require__global_external_react.useState("");
|
|
297
|
+
const [focusedPod, setFocusedPod] = require__global_external_react.useState("");
|
|
298
|
+
const [hideUpstreams, setHideUpstreams] = require__global_external_react.useState(false);
|
|
299
|
+
const [inventory, setInventory] = require__global_external_react.useState();
|
|
300
|
+
const [refresh, setRefresh] = require__global_external_react.useState(0);
|
|
301
|
+
const [scopeRefresh, setScopeRefresh] = require__global_external_react.useState(0);
|
|
302
|
+
const [namespaceMessage, setNamespaceMessage] = require__global_external_react.useState("");
|
|
303
|
+
const [scopeLoading, setScopeLoading] = require__global_external_react.useState(true);
|
|
304
|
+
const [loading, setLoading] = require__global_external_react.useState(false);
|
|
305
|
+
const [scopeMessage, setScopeMessage] = require__global_external_react.useState("");
|
|
306
|
+
const [openError, setOpenError] = require__global_external_react.useState("");
|
|
307
|
+
require__global_external_react.useEffect(() => {
|
|
308
|
+
const controller = new AbortController();
|
|
309
|
+
setScopeLoading(true);
|
|
310
|
+
setNamespaceMessage("");
|
|
311
|
+
require_extensions.Renderer.K8sApi.namespacesApi.list({ reqInit: { signal: controller.signal } }, { limit: 101 }).then((items) => {
|
|
312
|
+
if (controller.signal.aborted) return;
|
|
313
|
+
const names = (items ?? []).slice(0, LIMIT).map((item) => item.getName()).sort();
|
|
314
|
+
setNamespaces(names);
|
|
315
|
+
setNamespace((current) => names.includes(current) ? current : names[0] ?? "");
|
|
316
|
+
if ((items?.length ?? 0) > LIMIT) setNamespaceMessage(`Namespace selector is capped at ${LIMIT} entries`);
|
|
317
|
+
else if (!names.length) setScopeMessage("No visible namespaces returned by the API");
|
|
318
|
+
}).catch(() => {
|
|
319
|
+
if (!controller.signal.aborted) setScopeMessage("Namespace inventory unavailable; check access to namespace listing");
|
|
320
|
+
}).finally(() => {
|
|
321
|
+
if (!controller.signal.aborted) setScopeLoading(false);
|
|
322
|
+
});
|
|
323
|
+
return () => controller.abort();
|
|
324
|
+
}, [scopeRefresh]);
|
|
325
|
+
require__global_external_react.useEffect(() => {
|
|
326
|
+
if (!namespace) return;
|
|
327
|
+
const controller = new AbortController();
|
|
328
|
+
setLoading(false);
|
|
329
|
+
setServices([]);
|
|
330
|
+
setServiceName("");
|
|
331
|
+
setInventory(void 0);
|
|
332
|
+
setScopeLoading(true);
|
|
333
|
+
setScopeMessage("");
|
|
334
|
+
require_extensions.Renderer.K8sApi.serviceApi.list({
|
|
335
|
+
namespace,
|
|
336
|
+
reqInit: { signal: controller.signal }
|
|
337
|
+
}, { limit: 101 }).then((items) => {
|
|
338
|
+
if (controller.signal.aborted) return;
|
|
339
|
+
const visible = (items ?? []).slice(0, LIMIT).sort((a, b) => a.getName().localeCompare(b.getName()));
|
|
340
|
+
setServices(visible);
|
|
341
|
+
setServiceName(visible[0]?.getName() ?? "");
|
|
342
|
+
if ((items?.length ?? 0) > LIMIT) setScopeMessage(`Service selector is capped at ${LIMIT} entries; additional Services are not shown`);
|
|
343
|
+
else if (!visible.length) setScopeMessage("No visible Services in this namespace");
|
|
344
|
+
}).catch(() => {
|
|
345
|
+
if (!controller.signal.aborted) setScopeMessage("Service inventory unavailable; check namespace access");
|
|
346
|
+
}).finally(() => {
|
|
347
|
+
if (!controller.signal.aborted) setScopeLoading(false);
|
|
348
|
+
});
|
|
349
|
+
return () => controller.abort();
|
|
350
|
+
}, [namespace, scopeRefresh]);
|
|
351
|
+
const service = services.find((item) => item.getName() === serviceName);
|
|
352
|
+
require__global_external_react.useEffect(() => {
|
|
353
|
+
if (!service || !namespace || service.metadata.namespace !== namespace) return;
|
|
354
|
+
const controller = new AbortController();
|
|
355
|
+
setLoading(true);
|
|
356
|
+
setInventory(void 0);
|
|
357
|
+
setFocusedPod("");
|
|
358
|
+
setOpenError("");
|
|
359
|
+
loadRelationshipInventory(namespace, service, controller.signal).then((data) => {
|
|
360
|
+
if (!controller.signal.aborted) setInventory({
|
|
361
|
+
namespace,
|
|
362
|
+
service: service.getName(),
|
|
363
|
+
data
|
|
364
|
+
});
|
|
365
|
+
}).finally(() => {
|
|
366
|
+
if (!controller.signal.aborted) setLoading(false);
|
|
367
|
+
});
|
|
368
|
+
return () => controller.abort();
|
|
369
|
+
}, [
|
|
370
|
+
namespace,
|
|
371
|
+
service,
|
|
372
|
+
refresh
|
|
373
|
+
]);
|
|
374
|
+
const current = inventory?.namespace === namespace && inventory.service === serviceName ? inventory : void 0;
|
|
375
|
+
const graph = current && service ? require_resource_relationships.buildResourceRelationships(service, current.data, focusedPod || void 0) : void 0;
|
|
376
|
+
const openResource = (path) => {
|
|
377
|
+
const currentCluster = () => mounted.current && require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid === clusterId.current;
|
|
378
|
+
setOpenError("");
|
|
379
|
+
Promise.resolve().then(() => {
|
|
380
|
+
if (currentCluster()) return require_extensions.Renderer.Navigation.showDetails(path);
|
|
381
|
+
}).catch(() => {
|
|
382
|
+
if (currentCluster()) setOpenError("Resource details could not be opened; the object may be unavailable");
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
return /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
386
|
+
className: "power-relationships",
|
|
387
|
+
children: [
|
|
388
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("style", { children: require_resource_relationships_css.resourceRelationshipsStyles }),
|
|
389
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
390
|
+
className: "page-heading",
|
|
391
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx("h2", { children: "Resource relationships" }), /* @__PURE__ */ require_jsx_runtime.jsxs("label", {
|
|
392
|
+
className: "graph-option",
|
|
393
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx("input", {
|
|
394
|
+
type: "checkbox",
|
|
395
|
+
checked: hideUpstreams,
|
|
396
|
+
onChange: (event) => setHideUpstreams(event.target.checked)
|
|
397
|
+
}), "Hide Upstream"]
|
|
398
|
+
})]
|
|
399
|
+
}),
|
|
400
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("p", {
|
|
401
|
+
className: "intro",
|
|
402
|
+
children: "Follow one Service from upstream Ingress references to selected pods, published EndpointSlices and controller ownership. Connections show Kubernetes API evidence; they do not prove network reachability."
|
|
403
|
+
}),
|
|
404
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
405
|
+
className: "controls",
|
|
406
|
+
children: [
|
|
407
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("label", {
|
|
408
|
+
className: "control",
|
|
409
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "Namespace" }), /* @__PURE__ */ require_jsx_runtime.jsx(Select, {
|
|
410
|
+
"aria-label": "Relationship namespace",
|
|
411
|
+
value: namespace,
|
|
412
|
+
options: namespaces.map((name) => ({
|
|
413
|
+
value: name,
|
|
414
|
+
label: name
|
|
415
|
+
})),
|
|
416
|
+
onChange: (option) => setNamespace(option?.value ?? ""),
|
|
417
|
+
isDisabled: !namespaces.length
|
|
418
|
+
})]
|
|
419
|
+
}),
|
|
420
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("label", {
|
|
421
|
+
className: "control",
|
|
422
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "Service" }), /* @__PURE__ */ require_jsx_runtime.jsx(Select, {
|
|
423
|
+
"aria-label": "Relationship Service",
|
|
424
|
+
value: serviceName,
|
|
425
|
+
options: services.map((item) => ({
|
|
426
|
+
value: item.getName(),
|
|
427
|
+
label: item.getName()
|
|
428
|
+
})),
|
|
429
|
+
onChange: (option) => setServiceName(option?.value ?? ""),
|
|
430
|
+
isDisabled: !services.length || scopeLoading
|
|
431
|
+
})]
|
|
432
|
+
}),
|
|
433
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("label", {
|
|
434
|
+
className: "control",
|
|
435
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "Pod focus" }), /* @__PURE__ */ require_jsx_runtime.jsx(Select, {
|
|
436
|
+
"aria-label": "Relationship pod focus",
|
|
437
|
+
value: focusedPod,
|
|
438
|
+
options: [{
|
|
439
|
+
value: "",
|
|
440
|
+
label: "All selected pods (up to 8)"
|
|
441
|
+
}, ...(graph?.podOptions ?? []).map((name) => ({
|
|
442
|
+
value: name,
|
|
443
|
+
label: name
|
|
444
|
+
}))],
|
|
445
|
+
onChange: (option) => setFocusedPod(option?.value ?? ""),
|
|
446
|
+
isDisabled: !graph?.podOptions.length
|
|
447
|
+
})]
|
|
448
|
+
}),
|
|
449
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("button", {
|
|
450
|
+
type: "button",
|
|
451
|
+
className: "refresh",
|
|
452
|
+
onClick: () => service ? setRefresh((value) => value + 1) : setScopeRefresh((value) => value + 1),
|
|
453
|
+
disabled: loading || scopeLoading,
|
|
454
|
+
title: "Refresh scoped API evidence",
|
|
455
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx(Icon, {
|
|
456
|
+
material: "refresh",
|
|
457
|
+
size: 18
|
|
458
|
+
}), "Refresh"]
|
|
459
|
+
})
|
|
460
|
+
]
|
|
461
|
+
}),
|
|
462
|
+
namespaceMessage && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
463
|
+
className: "notice",
|
|
464
|
+
role: "status",
|
|
465
|
+
children: namespaceMessage
|
|
466
|
+
}),
|
|
467
|
+
scopeMessage && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
468
|
+
className: "notice",
|
|
469
|
+
role: "status",
|
|
470
|
+
children: scopeMessage
|
|
471
|
+
}),
|
|
472
|
+
openError && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
473
|
+
className: "notice warning",
|
|
474
|
+
role: "alert",
|
|
475
|
+
children: openError
|
|
476
|
+
}),
|
|
477
|
+
(scopeLoading || loading) && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
478
|
+
className: "empty",
|
|
479
|
+
role: "status",
|
|
480
|
+
children: "Loading scoped relationship evidence…"
|
|
481
|
+
}),
|
|
482
|
+
!scopeLoading && !loading && !service && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
483
|
+
className: "empty",
|
|
484
|
+
children: "Select a namespace with a visible Service to inspect relationships"
|
|
485
|
+
}),
|
|
486
|
+
graph && !loading && /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [
|
|
487
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
488
|
+
className: "notice relationship-summary",
|
|
489
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx("strong", { children: graph.diagnostics.summary }), /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
490
|
+
className: "ports",
|
|
491
|
+
children: graph.diagnostics.ports.map((port) => /* @__PURE__ */ require_jsx_runtime.jsxs("span", { children: [
|
|
492
|
+
port.name,
|
|
493
|
+
" · ",
|
|
494
|
+
port.protocol,
|
|
495
|
+
" → ",
|
|
496
|
+
port.target,
|
|
497
|
+
" · published:",
|
|
498
|
+
" ",
|
|
499
|
+
port.endpointPorts.join(", ") || "unknown",
|
|
500
|
+
port.unresolved ? ` · ${port.unresolved} unresolved named targets` : ""
|
|
501
|
+
] }, port.name))
|
|
502
|
+
})]
|
|
503
|
+
}),
|
|
504
|
+
graph.gaps.length > 0 && /* @__PURE__ */ require_jsx_runtime.jsxs("details", {
|
|
505
|
+
open: true,
|
|
506
|
+
className: "notice",
|
|
507
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsxs("summary", { children: [
|
|
508
|
+
"Partial coverage (",
|
|
509
|
+
graph.gaps.length,
|
|
510
|
+
")"
|
|
511
|
+
] }), /* @__PURE__ */ require_jsx_runtime.jsx("ul", { children: graph.gaps.map((gap) => /* @__PURE__ */ require_jsx_runtime.jsx("li", { children: gap }, gap)) })]
|
|
512
|
+
}),
|
|
513
|
+
/* @__PURE__ */ require_jsx_runtime.jsx(RelationshipGraph, {
|
|
514
|
+
graph,
|
|
515
|
+
onOpen: openResource,
|
|
516
|
+
ingressAvailable: current?.data.ingresses !== void 0,
|
|
517
|
+
hideUpstreams
|
|
518
|
+
}),
|
|
519
|
+
graph.omitted > 0 && /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
520
|
+
className: "notice",
|
|
521
|
+
children: [graph.omitted, " additional visible resources omitted to keep the graph readable. Focus a pod to inspect its ownership and placement."]
|
|
522
|
+
}),
|
|
523
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("details", { children: [/* @__PURE__ */ require_jsx_runtime.jsxs("summary", { children: [
|
|
524
|
+
"Accessible connection list (",
|
|
525
|
+
graph.edges.length,
|
|
526
|
+
")"
|
|
527
|
+
] }), /* @__PURE__ */ require_jsx_runtime.jsx("ul", {
|
|
528
|
+
className: "edge-list",
|
|
529
|
+
children: graph.edges.map((edge) => /* @__PURE__ */ require_jsx_runtime.jsxs("li", { children: [
|
|
530
|
+
graph.nodes.find((node) => node.id === edge.from)?.name,
|
|
531
|
+
" → ",
|
|
532
|
+
edge.label,
|
|
533
|
+
" →",
|
|
534
|
+
" ",
|
|
535
|
+
graph.nodes.find((node) => node.id === edge.to)?.name
|
|
536
|
+
] }, `${edge.from}/${edge.to}/${edge.label}`))
|
|
537
|
+
})] }),
|
|
538
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("p", {
|
|
539
|
+
className: "scope",
|
|
540
|
+
children: [
|
|
541
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "Snapshot from namespace-scoped read-only API requests." }),
|
|
542
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "Owner and node references can point to objects that were not queried." }),
|
|
543
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "Endpoint counts can include separate address families." }),
|
|
544
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "No Secret or ConfigMap data is read." })
|
|
545
|
+
]
|
|
546
|
+
})
|
|
547
|
+
] })
|
|
548
|
+
]
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
//#endregion
|
|
552
|
+
exports.RelationshipGraph = RelationshipGraph;
|
|
553
|
+
exports.ResourceRelationshipsPage = ResourceRelationshipsPage;
|
|
554
|
+
exports.loadRelationshipInventory = loadRelationshipInventory;
|
|
555
|
+
|
|
556
|
+
//# sourceMappingURL=resource-relationships.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-relationships.js","names":[],"sources":["../../src/renderer/resource-relationships.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { observer } from \"mobx-react\";\nimport { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport {\n buildResourceRelationships,\n type RelationshipInventory,\n type RelationshipNode,\n type RelationshipObject,\n} from \"../common/resource-relationships\";\nimport { resourceRelationshipsStyles } from \"./resource-relationships.css\";\n\nconst { Icon, Select } = Renderer.Component;\nconst LIMIT = 100;\ntype Service = Renderer.K8sApi.Service;\ninterface InventoryState {\n namespace: string;\n service: string;\n data: RelationshipInventory;\n}\nconst roles = [\n { id: \"upstream\", name: \"Upstream references\", relation: \"references backend\" },\n { id: \"service\", name: \"Focused Service\", relation: \"API configuration\" },\n { id: \"backend\", name: \"Selected & published backends\", relation: \"selector matches / published in\" },\n { id: \"placement\", name: \"Ownership & placement\", relation: \"owned by / assigned to\" },\n] as const;\nconst icons: Record<string, string> = {\n Ingress: \"login\",\n Service: \"share\",\n Pod: \"view_in_ar\",\n EndpointSlice: \"hub\",\n Node: \"dns\",\n ReplicaSet: \"layers\",\n Deployment: \"layers\",\n StatefulSet: \"layers\",\n DaemonSet: \"layers\",\n Job: \"work\",\n CronJob: \"schedule\",\n};\nexport async function loadRelationshipInventory(\n namespace: string,\n service: Service,\n signal: AbortSignal,\n): Promise<RelationshipInventory> {\n const gaps: string[] = [];\n const list = async <T,>(name: string, request: () => Promise<T[] | null>) => {\n try {\n const items = await request();\n if (!items) {\n gaps.push(`${name} inventory unavailable`);\n return undefined;\n }\n if (items.length > LIMIT)\n gaps.push(`${name} inventory capped at ${LIMIT} objects; additional relationships may be omitted`);\n return items.slice(0, LIMIT);\n } catch {\n if (!signal.aborted) gaps.push(`${name} inventory unavailable; access or API availability may be restricted`);\n return undefined;\n }\n };\n const options = { namespace, reqInit: { signal } };\n const query = { limit: LIMIT + 1 };\n const selector = Object.entries(service.spec.selector ?? {})\n .map(([key, value]) => `${key}=${value}`)\n .join(\",\");\n const external = service.spec.type === \"ExternalName\";\n const ownerApis = external\n ? []\n : [\n Renderer.K8sApi.replicaSetApi,\n Renderer.K8sApi.deploymentApi,\n Renderer.K8sApi.statefulSetApi,\n Renderer.K8sApi.daemonSetApi,\n Renderer.K8sApi.jobApi,\n Renderer.K8sApi.cronJobApi,\n ];\n const [pods, slices, ingresses, ...owners] = await Promise.all([\n selector && !external\n ? list(\"Pod\", () => Renderer.K8sApi.podsApi.list(options, { ...query, labelSelector: selector }))\n : Promise.resolve([]),\n service.spec.type === \"ExternalName\"\n ? Promise.resolve([])\n : list(\"EndpointSlice\", () =>\n Renderer.K8sApi.endpointSliceApi.list(options, {\n ...query,\n labelSelector: `kubernetes.io/service-name=${service.getName()}`,\n }),\n ),\n list(\"Ingress\", () => Renderer.K8sApi.ingressApi.list(options, query)),\n ...ownerApis.map((api) => list<RelationshipObject>(\"Controller\", () => api.list(options, query))),\n ]);\n return { pods, slices, ingresses, owners: owners.flatMap((items) => items ?? []), gaps };\n}\nfunction ResourceCard({\n node,\n incoming,\n onOpen,\n onHighlight,\n}: {\n onHighlight: (id?: string) => void;\n node: RelationshipNode;\n incoming: string[];\n onOpen: (path: string) => void;\n}) {\n return (\n <article\n className=\"card\"\n data-node-id={node.id}\n onMouseEnter={() => onHighlight(node.id)}\n onMouseLeave={() => onHighlight()}\n onFocus={() => onHighlight(node.id)}\n onBlur={() => onHighlight()}\n >\n <div className=\"relation-label\">{incoming.join(\" · \") || \"Focus\"}</div>\n <div className=\"card-kind\">\n <Icon material={icons[node.kind] ?? \"account_tree\"} size={16} />\n {node.kind}\n {node.inferred && \" · reference\"}\n </div>\n {node.selfLink ? (\n <button\n type=\"button\"\n className=\"resource-name\"\n onClick={(event) => {\n event.preventDefault();\n event.stopPropagation();\n onOpen(node.selfLink as string);\n }}\n title={`Open ${node.kind} ${node.name}`}\n >\n {node.name}\n </button>\n ) : (\n <strong>{node.name}</strong>\n )}\n <p className={node.tone}>\n <Icon\n material={node.tone === \"warning\" ? \"warning\" : node.tone === \"healthy\" ? \"check_circle\" : \"info\"}\n size={14}\n />{\" \"}\n {node.detail}\n </p>\n </article>\n );\n}\nfunction roundedConnector(points: [number, number][]) {\n let path = `M ${points[0][0]} ${points[0][1]}`;\n for (let index = 1; index < points.length - 1; index++) {\n const [x, y] = points[index];\n const [px, py] = points[index - 1];\n const [nx, ny] = points[index + 1];\n const before = Math.hypot(x - px, y - py);\n const after = Math.hypot(nx - x, ny - y);\n if (!before || !after) continue;\n const radius = Math.min(6, before / 2, after / 2);\n path += ` L ${x + ((px - x) * radius) / before} ${y + ((py - y) * radius) / before}`;\n path += ` Q ${x} ${y} ${x + ((nx - x) * radius) / after} ${y + ((ny - y) * radius) / after}`;\n }\n const last = points[points.length - 1];\n return `${path} L ${last[0]} ${last[1]}`;\n}\nexport function RelationshipGraph({\n graph,\n onOpen,\n ingressAvailable,\n hideUpstreams = false,\n}: {\n graph: ReturnType<typeof buildResourceRelationships>;\n onOpen: (path: string) => void;\n ingressAvailable: boolean;\n hideUpstreams?: boolean;\n}) {\n const visibleRoles = roles.filter((role) => !hideUpstreams || role.id !== \"upstream\");\n const element = useRef<HTMLDivElement>(null);\n const [paths, setPaths] = useState<{ from: string; to: string; label: string; path: string }[]>([]);\n const [highlight, setHighlight] = useState<string>();\n useLayoutEffect(() => {\n const container = element.current;\n if (!container) return;\n const measure = () => {\n const origin = container.getBoundingClientRect();\n const cards = new Map(\n Array.from(container.querySelectorAll<HTMLElement>(\"[data-node-id]\")).map((card) => [\n card.dataset.nodeId,\n card.getBoundingClientRect(),\n ]),\n );\n setPaths(\n graph.edges.flatMap((edge, index) => {\n const from = cards.get(edge.from);\n const to = cards.get(edge.to);\n if (!from || !to) return [];\n const sy = from.top + from.height / 2 - origin.top;\n const ty = to.top + to.height / 2 - origin.top;\n const sameColumn = Math.abs(from.left - to.left) < 2;\n const sx = (sameColumn ? from.left : from.right) - origin.left;\n const tx = to.left - origin.left;\n const laneOffset = ((index % 3) - 1) * 8;\n const gutter = sameColumn ? sx - 28 + laneOffset : (sx + tx) / 2 + laneOffset;\n const crossesColumn = Array.from(cards.values()).some(\n (card) => card.left > from.left + 2 && card.right < to.left - 2,\n );\n let path =\n !sameColumn && sy === ty\n ? `M ${sx} ${sy} H ${tx}`\n : roundedConnector([\n [sx, sy],\n [gutter, sy],\n [gutter, ty],\n [tx, ty],\n ]);\n if (crossesColumn) {\n const bottom =\n Math.max(...Array.from(cards.values(), (card) => card.bottom)) - origin.top + 24 + (index % 3) * 8;\n const sourceGutter = sx + 32 + laneOffset;\n const targetGutter = tx - 32 + laneOffset;\n path = roundedConnector([\n [sx, sy],\n [sourceGutter, sy],\n [sourceGutter, bottom],\n [targetGutter, bottom],\n [targetGutter, ty],\n [tx, ty],\n ]);\n }\n return [{ ...edge, path }];\n }),\n );\n };\n measure();\n const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(measure);\n observer?.observe(container);\n for (const card of container.querySelectorAll(\"[data-node-id]\")) observer?.observe(card);\n window.addEventListener(\"resize\", measure);\n return () => {\n observer?.disconnect();\n window.removeEventListener(\"resize\", measure);\n };\n }, [graph, hideUpstreams]);\n return (\n <div className=\"graph-scroll\">\n <div\n ref={element}\n className={`graph${hideUpstreams ? \" hide-upstreams\" : \"\"}`}\n style={{\n gridTemplateRows: `auto repeat(${Math.max(1, ...visibleRoles.map((role) => graph.nodes.filter((node) => node.role === role.id).length))}, auto)`,\n }}\n aria-label=\"Service resource relationship graph\"\n >\n <svg className=\"resource-connections\" width=\"100%\" height=\"100%\" aria-hidden=\"true\">\n <defs>\n <marker id=\"relationship-arrow\" markerWidth=\"5\" markerHeight=\"5\" refX=\"4\" refY=\"2.5\" orient=\"auto\">\n <path d=\"M0 0L5 2.5L0 5\" fill=\"currentColor\" />\n </marker>\n </defs>\n {paths.map((edge) => (\n <path\n key={`${edge.from}/${edge.to}/${edge.label}`}\n data-edge-from={edge.from}\n data-edge-to={edge.to}\n className={\n highlight\n ? edge.from === highlight || edge.to === highlight\n ? \"connection highlighted\"\n : \"connection dimmed\"\n : \"connection\"\n }\n d={edge.path}\n markerEnd=\"url(#relationship-arrow)\"\n >\n <title>\n {graph.nodes.find((node) => node.id === edge.from)?.name} → {edge.label} →{\" \"}\n {graph.nodes.find((node) => node.id === edge.to)?.name}\n </title>\n </path>\n ))}\n </svg>\n {visibleRoles.map((role) => (\n <section className=\"column\" key={role.id} aria-label={role.name}>\n <h3>{role.name}</h3>\n {graph.nodes\n .filter((node) => node.role === role.id)\n .map((node) => (\n <ResourceCard\n key={node.id}\n node={node}\n incoming={[...new Set(graph.edges.filter((edge) => edge.to === node.id).map((edge) => edge.label))]}\n onOpen={onOpen}\n onHighlight={setHighlight}\n />\n ))}\n {!graph.nodes.some((node) => node.role === role.id) && (\n <div className=\"card neutral\">\n {role.id === \"upstream\"\n ? ingressAvailable\n ? \"No matching Ingress references observed\"\n : \"Ingress evidence unavailable\"\n : \"No relationships observed in available evidence\"}\n </div>\n )}\n </section>\n ))}\n </div>\n </div>\n );\n}\nexport const ResourceRelationshipsPage = observer(({ enabled = true }: { enabled?: boolean }) => {\n if (!enabled)\n return (\n <div className=\"power-relationships\">\n <p>Resource relationships are disabled in Power settings</p>\n </div>\n );\n return <EnabledResourceRelationshipsPage key={Renderer.Catalog.activeCluster.get()?.metadata.uid ?? \"cluster\"} />;\n});\nfunction EnabledResourceRelationshipsPage() {\n const mounted = useRef(true);\n const clusterId = useRef(Renderer.Catalog.activeCluster.get()?.metadata.uid);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n const [namespaces, setNamespaces] = useState<string[]>([]);\n const [namespace, setNamespace] = useState(\"\");\n const [services, setServices] = useState<Service[]>([]);\n const [serviceName, setServiceName] = useState(\"\");\n const [focusedPod, setFocusedPod] = useState(\"\");\n const [hideUpstreams, setHideUpstreams] = useState(false);\n const [inventory, setInventory] = useState<InventoryState>();\n const [refresh, setRefresh] = useState(0);\n const [scopeRefresh, setScopeRefresh] = useState(0);\n const [namespaceMessage, setNamespaceMessage] = useState(\"\");\n const [scopeLoading, setScopeLoading] = useState(true);\n const [loading, setLoading] = useState(false);\n const [scopeMessage, setScopeMessage] = useState(\"\");\n const [openError, setOpenError] = useState(\"\");\n useEffect(() => {\n const controller = new AbortController();\n setScopeLoading(true);\n setNamespaceMessage(\"\");\n void Renderer.K8sApi.namespacesApi\n .list({ reqInit: { signal: controller.signal } }, { limit: LIMIT + 1 })\n .then((items) => {\n if (controller.signal.aborted) return;\n const names = (items ?? [])\n .slice(0, LIMIT)\n .map((item) => item.getName())\n .sort();\n setNamespaces(names);\n setNamespace((current) => (names.includes(current) ? current : (names[0] ?? \"\")));\n if ((items?.length ?? 0) > LIMIT) setNamespaceMessage(`Namespace selector is capped at ${LIMIT} entries`);\n else if (!names.length) setScopeMessage(\"No visible namespaces returned by the API\");\n })\n .catch(() => {\n if (!controller.signal.aborted)\n setScopeMessage(\"Namespace inventory unavailable; check access to namespace listing\");\n })\n .finally(() => {\n if (!controller.signal.aborted) setScopeLoading(false);\n });\n return () => controller.abort();\n }, [scopeRefresh]);\n useEffect(() => {\n if (!namespace) return;\n const controller = new AbortController();\n setLoading(false);\n setServices([]);\n setServiceName(\"\");\n setInventory(undefined);\n setScopeLoading(true);\n setScopeMessage(\"\");\n void Renderer.K8sApi.serviceApi\n .list({ namespace, reqInit: { signal: controller.signal } }, { limit: LIMIT + 1 })\n .then((items) => {\n if (controller.signal.aborted) return;\n const visible = (items ?? []).slice(0, LIMIT).sort((a, b) => a.getName().localeCompare(b.getName()));\n setServices(visible);\n setServiceName(visible[0]?.getName() ?? \"\");\n if ((items?.length ?? 0) > LIMIT)\n setScopeMessage(`Service selector is capped at ${LIMIT} entries; additional Services are not shown`);\n else if (!visible.length) setScopeMessage(\"No visible Services in this namespace\");\n })\n .catch(() => {\n if (!controller.signal.aborted) setScopeMessage(\"Service inventory unavailable; check namespace access\");\n })\n .finally(() => {\n if (!controller.signal.aborted) setScopeLoading(false);\n });\n return () => controller.abort();\n }, [namespace, scopeRefresh]);\n const service = services.find((item) => item.getName() === serviceName);\n useEffect(() => {\n if (!service || !namespace || service.metadata.namespace !== namespace) return;\n const controller = new AbortController();\n setLoading(true);\n setInventory(undefined);\n setFocusedPod(\"\");\n setOpenError(\"\");\n void loadRelationshipInventory(namespace, service, controller.signal)\n .then((data) => {\n if (!controller.signal.aborted) setInventory({ namespace, service: service.getName(), data });\n })\n .finally(() => {\n if (!controller.signal.aborted) setLoading(false);\n });\n return () => controller.abort();\n }, [namespace, service, refresh]);\n const current = inventory?.namespace === namespace && inventory.service === serviceName ? inventory : undefined;\n const graph =\n current && service ? buildResourceRelationships(service, current.data, focusedPod || undefined) : undefined;\n const openResource = (path: string) => {\n const currentCluster = () =>\n mounted.current && Renderer.Catalog.activeCluster.get()?.metadata.uid === clusterId.current;\n setOpenError(\"\");\n void Promise.resolve()\n .then(() => {\n if (currentCluster()) return Renderer.Navigation.showDetails(path);\n })\n .catch(() => {\n if (currentCluster()) setOpenError(\"Resource details could not be opened; the object may be unavailable\");\n });\n };\n return (\n <div className=\"power-relationships\">\n <style>{resourceRelationshipsStyles}</style>\n <div className=\"page-heading\">\n <h2>Resource relationships</h2>\n <label className=\"graph-option\">\n <input type=\"checkbox\" checked={hideUpstreams} onChange={(event) => setHideUpstreams(event.target.checked)} />\n Hide Upstream\n </label>\n </div>\n <p className=\"intro\">\n Follow one Service from upstream Ingress references to selected pods, published EndpointSlices and controller\n ownership. Connections show Kubernetes API evidence; they do not prove network reachability.\n </p>\n <div className=\"controls\">\n <label className=\"control\">\n <span>Namespace</span>\n <Select\n aria-label=\"Relationship namespace\"\n value={namespace}\n options={namespaces.map((name) => ({ value: name, label: name }))}\n onChange={(option) => setNamespace(option?.value ?? \"\")}\n isDisabled={!namespaces.length}\n />\n </label>\n <label className=\"control\">\n <span>Service</span>\n <Select\n aria-label=\"Relationship Service\"\n value={serviceName}\n options={services.map((item) => ({ value: item.getName(), label: item.getName() }))}\n onChange={(option) => setServiceName(option?.value ?? \"\")}\n isDisabled={!services.length || scopeLoading}\n />\n </label>\n <label className=\"control\">\n <span>Pod focus</span>\n <Select\n aria-label=\"Relationship pod focus\"\n value={focusedPod}\n options={[\n { value: \"\", label: \"All selected pods (up to 8)\" },\n ...(graph?.podOptions ?? []).map((name) => ({ value: name, label: name })),\n ]}\n onChange={(option) => setFocusedPod(option?.value ?? \"\")}\n isDisabled={!graph?.podOptions.length}\n />\n </label>\n <button\n type=\"button\"\n className=\"refresh\"\n onClick={() => (service ? setRefresh((value) => value + 1) : setScopeRefresh((value) => value + 1))}\n disabled={loading || scopeLoading}\n title=\"Refresh scoped API evidence\"\n >\n <Icon material=\"refresh\" size={18} />\n Refresh\n </button>\n </div>\n {namespaceMessage && (\n <div className=\"notice\" role=\"status\">\n {namespaceMessage}\n </div>\n )}\n {scopeMessage && (\n <div className=\"notice\" role=\"status\">\n {scopeMessage}\n </div>\n )}\n {openError && (\n <div className=\"notice warning\" role=\"alert\">\n {openError}\n </div>\n )}\n {(scopeLoading || loading) && (\n <div className=\"empty\" role=\"status\">\n Loading scoped relationship evidence…\n </div>\n )}\n {!scopeLoading && !loading && !service && (\n <div className=\"empty\">Select a namespace with a visible Service to inspect relationships</div>\n )}\n {graph && !loading && (\n <>\n <div className=\"notice relationship-summary\">\n <strong>{graph.diagnostics.summary}</strong>\n <div className=\"ports\">\n {graph.diagnostics.ports.map((port) => (\n <span key={port.name}>\n {port.name} · {port.protocol} → {port.target} · published:{\" \"}\n {port.endpointPorts.join(\", \") || \"unknown\"}\n {port.unresolved ? ` · ${port.unresolved} unresolved named targets` : \"\"}\n </span>\n ))}\n </div>\n </div>\n {graph.gaps.length > 0 && (\n <details open className=\"notice\">\n <summary>Partial coverage ({graph.gaps.length})</summary>\n <ul>\n {graph.gaps.map((gap) => (\n <li key={gap}>{gap}</li>\n ))}\n </ul>\n </details>\n )}\n <RelationshipGraph\n graph={graph}\n onOpen={openResource}\n ingressAvailable={current?.data.ingresses !== undefined}\n hideUpstreams={hideUpstreams}\n />\n {graph.omitted > 0 && (\n <div className=\"notice\">\n {graph.omitted} additional visible resources omitted to keep the graph readable. Focus a pod to inspect\n its ownership and placement.\n </div>\n )}\n <details>\n <summary>Accessible connection list ({graph.edges.length})</summary>\n <ul className=\"edge-list\">\n {graph.edges.map((edge) => (\n <li key={`${edge.from}/${edge.to}/${edge.label}`}>\n {graph.nodes.find((node) => node.id === edge.from)?.name} → {edge.label} →{\" \"}\n {graph.nodes.find((node) => node.id === edge.to)?.name}\n </li>\n ))}\n </ul>\n </details>\n <p className=\"scope\">\n <span>Snapshot from namespace-scoped read-only API requests.</span>\n <span>Owner and node references can point to objects that were not queried.</span>\n <span>Endpoint counts can include separate address families.</span>\n <span>No Secret or ConfigMap data is read.</span>\n </p>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/renderer/resource-search-distance.ts
|
|
2
|
+
function boundedNameDistance(left, right, limit) {
|
|
3
|
+
if (left.length > 64 || right.length > 64 || Math.abs(left.length - right.length) > limit) return limit + 1;
|
|
4
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
5
|
+
let beforePrevious = previous;
|
|
6
|
+
for (let row = 1; row <= left.length; row++) {
|
|
7
|
+
const current = Array(right.length + 1).fill(limit + 1);
|
|
8
|
+
current[0] = row;
|
|
9
|
+
let minimum = limit + 1;
|
|
10
|
+
for (let column = Math.max(1, row - limit); column <= Math.min(right.length, row + limit); column++) {
|
|
11
|
+
current[column] = Math.min(previous[column] + 1, current[column - 1] + 1, previous[column - 1] + (left[row - 1] === right[column - 1] ? 0 : 1));
|
|
12
|
+
if (row > 1 && column > 1 && left[row - 1] === right[column - 2] && left[row - 2] === right[column - 1]) current[column] = Math.min(current[column], beforePrevious[column - 2] + 1);
|
|
13
|
+
minimum = Math.min(minimum, current[column]);
|
|
14
|
+
}
|
|
15
|
+
if (minimum > limit) return limit + 1;
|
|
16
|
+
beforePrevious = previous;
|
|
17
|
+
previous = current;
|
|
18
|
+
}
|
|
19
|
+
return previous[right.length];
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
exports.boundedNameDistance = boundedNameDistance;
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=resource-search-distance.js.map
|