@64mb/freelens-power 1.0.1 → 1.0.2

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.
Files changed (54) hide show
  1. package/README.md +4 -4
  2. package/THIRD_PARTY_LICENSES +41 -0
  3. package/out/main/ai.js +9 -6
  4. package/out/main/ai.js.map +1 -1
  5. package/out/main/common/ai-extras.js +17 -0
  6. package/out/main/common/ai-extras.js.map +1 -1
  7. package/out/main/common/dashboard-readiness.js +88 -0
  8. package/out/main/common/dashboard-readiness.js.map +1 -0
  9. package/out/main/common/dashboard-selector.js +9 -1
  10. package/out/main/common/dashboard-selector.js.map +1 -1
  11. package/out/main/common/dashboard-views.js +2 -0
  12. package/out/main/common/dashboard-views.js.map +1 -1
  13. package/out/main/dashboard-network.js +104 -0
  14. package/out/main/dashboard-network.js.map +1 -0
  15. package/out/main/dashboard-views.js +98 -21
  16. package/out/main/dashboard-views.js.map +1 -1
  17. package/out/renderer/ai-extras.js +41 -12
  18. package/out/renderer/ai-extras.js.map +1 -1
  19. package/out/renderer/ai-message.js +1 -0
  20. package/out/renderer/ai-message.js.map +1 -1
  21. package/out/renderer/ai-provider-icon.js +44 -0
  22. package/out/renderer/ai-provider-icon.js.map +1 -0
  23. package/out/renderer/ai-shortcuts.js +1 -1
  24. package/out/renderer/ai-shortcuts.js.map +1 -1
  25. package/out/renderer/ai.js +53 -10
  26. package/out/renderer/ai.js.map +1 -1
  27. package/out/renderer/appearance.js +2 -1
  28. package/out/renderer/appearance.js.map +1 -1
  29. package/out/renderer/assets/claude.js +6 -0
  30. package/out/renderer/assets/claude.js.map +1 -0
  31. package/out/renderer/dashboard.js +39 -4
  32. package/out/renderer/dashboard.js.map +1 -1
  33. package/out/renderer/index.js +2 -0
  34. package/out/renderer/index.js.map +1 -1
  35. package/out/renderer/menus/secure-node-shell-menu.js +1 -1
  36. package/out/renderer/menus/secure-node-shell-menu.js.map +1 -1
  37. package/out/renderer/monitoring.js +671 -91
  38. package/out/renderer/monitoring.js.map +1 -1
  39. package/out/renderer/pod-monitoring.js +214 -0
  40. package/out/renderer/pod-monitoring.js.map +1 -0
  41. package/out/renderer/preferences/ai-preferences.js +2 -2
  42. package/out/renderer/preferences/ai-preferences.js.map +1 -1
  43. package/out/renderer/preferences/dashboard-preferences.js +1 -8
  44. package/out/renderer/preferences/dashboard-preferences.js.map +1 -1
  45. package/out/renderer/preferences/preferences.js +1 -1
  46. package/out/renderer/preferences/preferences.js.map +1 -1
  47. package/out/renderer/preferences/settings-icon-action.js +3 -1
  48. package/out/renderer/preferences/settings-icon-action.js.map +1 -1
  49. package/out/renderer/src/common/ai-extras.js +17 -0
  50. package/out/renderer/src/common/ai-extras.js.map +1 -1
  51. package/out/renderer/src/common/dashboard-selector.js.map +1 -1
  52. package/out/renderer/src/common/dashboard-views.js +2 -0
  53. package/out/renderer/src/common/dashboard-views.js.map +1 -1
  54. package/package.json +1 -1
@@ -1,12 +1,59 @@
1
1
  const require_extensions = require("./_virtual/_global-external_@freelensapp/extensions.js");
2
2
  const require__global_external_mobx = require("./_virtual/_global-external_mobx.js");
3
3
  const require_monitoring_preferences_store = require("./src/common/store/monitoring-preferences-store.js");
4
+ const require__global_external_react = require("./_virtual/_global-external_react.js");
4
5
  const require__global_external_react_dom = require("./_virtual/_global-external_react-dom.js");
5
6
  const require_jsx_runtime = require("./_virtual/_global-external_react/jsx-runtime.js");
6
7
  //#region src/renderer/monitoring.tsx
8
+ function effectivePodResource(pod, metric, kind) {
9
+ const read = (resources) => {
10
+ const raw = resources?.[kind]?.[metric];
11
+ if (raw !== void 0) return parseQuantity(raw, metric === "memory");
12
+ if (kind === "requests" && resources?.limits?.[metric] !== void 0) return parseQuantity(resources.limits[metric], metric === "memory");
13
+ return kind === "requests" ? 0 : Infinity;
14
+ };
15
+ const spec = pod.spec;
16
+ if (!spec?.containers?.length) throw new Error("Missing pod containers");
17
+ let sidecars = 0;
18
+ let initPeak = 0;
19
+ for (const container of spec.initContainers ?? []) {
20
+ const value = read(container.resources);
21
+ if (container.restartPolicy === "Always") {
22
+ sidecars += value;
23
+ initPeak = Math.max(initPeak, sidecars);
24
+ } else initPeak = Math.max(initPeak, sidecars + value);
25
+ }
26
+ let effective = Math.max(initPeak, sidecars + spec.containers.reduce((sum, container) => sum + read(container.resources), 0));
27
+ if (spec.resources?.[kind]?.[metric] !== void 0) effective = read(spec.resources);
28
+ const overhead = spec.overhead?.[metric];
29
+ if (overhead !== void 0 && (kind === "requests" || effective > 0)) effective += parseQuantity(overhead, metric === "memory");
30
+ return effective;
31
+ }
32
+ function aggregateResources(nodes, pods) {
33
+ if (!nodes.length || nodes.some((node) => !node.metadata?.name)) throw new Error("Missing node inventory");
34
+ const names = new Set(nodes.map((node) => node.metadata.name));
35
+ const scheduled = pods?.filter((pod) => pod.spec?.nodeName && names.has(pod.spec.nodeName) && pod.status?.phase !== "Succeeded" && pod.status?.phase !== "Failed");
36
+ const sum = (metric) => {
37
+ const capacity = nodes.reduce((total, node) => total + parseQuantity(node.status?.capacity?.[metric] ?? "", metric === "memory"), 0);
38
+ const allocatable = nodes.reduce((total, node) => total + parseQuantity(node.status?.allocatable?.[metric] ?? "", metric === "memory"), 0);
39
+ const requests = scheduled?.reduce((total, pod) => total + effectivePodResource(pod, metric, "requests"), 0);
40
+ const limits = scheduled?.reduce((total, pod) => total + effectivePodResource(pod, metric, "limits"), 0);
41
+ return {
42
+ capacity,
43
+ allocatable,
44
+ requests,
45
+ limits: limits === Infinity ? void 0 : limits,
46
+ unbounded: limits === Infinity
47
+ };
48
+ };
49
+ return {
50
+ cpu: sum("cpu"),
51
+ memory: sum("memory")
52
+ };
53
+ }
7
54
  var INTERVAL = 3e4;
8
55
  function parseQuantity(raw, memory = false) {
9
- const match = /^(\d+(?:\.\d+)?)(n|u|m|Ki|Mi|Gi|Ti|Pi|Ei|k|K|M|G|T|P|E)?$/.exec(raw);
56
+ const match = /^(\d+(?:\.\d*)?|\.\d+)([eE][+-]?\d+|n|u|m|Ki|Mi|Gi|Ti|Pi|Ei|k|K|M|G|T|P|E)?$/.exec(raw);
10
57
  if (!match) throw new Error("Invalid metrics quantity");
11
58
  const suffix = match[2] ?? "";
12
59
  const value = Number(match[1]) * (suffix.endsWith("i") ? 1024 ** ([
@@ -16,7 +63,7 @@ function parseQuantity(raw, memory = false) {
16
63
  "Ti",
17
64
  "Pi",
18
65
  "Ei"
19
- ].indexOf(suffix) + 1) : {
66
+ ].indexOf(suffix) + 1) : /^[eE][+-]?\d+$/.test(suffix) ? 10 ** Number(suffix.slice(1)) : {
20
67
  n: 1e-9,
21
68
  u: 1e-6,
22
69
  m: .001,
@@ -47,100 +94,562 @@ function aggregateMetrics(nodes, now = Date.now()) {
47
94
  memory
48
95
  };
49
96
  }
50
- function MonitoringCharts({ samples, status }) {
51
- return /* @__PURE__ */ require_jsx_runtime.jsx("div", {
97
+ function formatMonitoringTime(time) {
98
+ return new Date(time).toLocaleTimeString([], {
99
+ hour: "2-digit",
100
+ minute: "2-digit",
101
+ second: "2-digit",
102
+ hourCycle: "h23"
103
+ });
104
+ }
105
+ function ResourceTitle({ metric, label }) {
106
+ return /* @__PURE__ */ require_jsx_runtime.jsxs("strong", {
107
+ style: {
108
+ display: "inline-flex",
109
+ alignItems: "center",
110
+ gap: 6
111
+ },
112
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", {
113
+ className: "Icon material",
114
+ "aria-hidden": "true",
115
+ style: {
116
+ "--size": "18px",
117
+ fontWeight: 400
118
+ },
119
+ children: /* @__PURE__ */ require_jsx_runtime.jsx("span", {
120
+ className: "icon",
121
+ children: metric === "cpu" ? "developer_board" : "memory"
122
+ })
123
+ }), label ?? (metric === "cpu" ? "CPU" : "Memory")]
124
+ });
125
+ }
126
+ function MetricChart({ metric, samples, status, scope = "node" }) {
127
+ const [selectedTime, setSelectedTime] = require__global_external_react.useState();
128
+ const [pinned, setPinned] = require__global_external_react.useState(false);
129
+ const previousMetric = require__global_external_react.useRef(metric);
130
+ require__global_external_react.useEffect(() => {
131
+ if (previousMetric.current !== metric || selectedTime !== void 0 && !samples.some((sample) => sample.time === selectedTime)) {
132
+ setPinned(false);
133
+ setSelectedTime(void 0);
134
+ }
135
+ previousMetric.current = metric;
136
+ }, [
137
+ metric,
138
+ samples,
139
+ selectedTime
140
+ ]);
141
+ require__global_external_react.useEffect(() => {
142
+ if (!pinned) return;
143
+ const escape = (event) => {
144
+ if (event.key === "Escape") {
145
+ event.preventDefault();
146
+ event.stopPropagation();
147
+ setPinned(false);
148
+ setSelectedTime(void 0);
149
+ }
150
+ };
151
+ document.addEventListener("keydown", escape, true);
152
+ return () => document.removeEventListener("keydown", escape, true);
153
+ }, [pinned]);
154
+ const chartRef = require__global_external_react.useRef(null);
155
+ const [width, setWidth] = require__global_external_react.useState(600);
156
+ require__global_external_react.useLayoutEffect(() => {
157
+ const chart = chartRef.current;
158
+ if (!chart) return;
159
+ const measure = () => {
160
+ const measured = chart.getBoundingClientRect().width;
161
+ if (measured > 0) setWidth(measured);
162
+ };
163
+ measure();
164
+ const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(measure);
165
+ observer?.observe(chart);
166
+ window.addEventListener("resize", measure);
167
+ return () => {
168
+ observer?.disconnect();
169
+ window.removeEventListener("resize", measure);
170
+ };
171
+ }, []);
172
+ const plotWidth = Math.max(1, width - 112);
173
+ const last = samples.at(-1);
174
+ const selected = samples.find((sample) => sample.time === selectedTime);
175
+ const label = metric === "cpu" ? "CPU" : "Memory";
176
+ const format = (value) => metric === "cpu" ? `${value.toFixed(2)} cores` : `${(value / 1024 ** 3).toFixed(2)} GiB`;
177
+ const series = [{
178
+ key: "usage",
179
+ label: "Usage",
180
+ color: "#c93dce",
181
+ value: (sample) => sample[metric]
182
+ }, ...[
183
+ "requests",
184
+ "allocatable",
185
+ "capacity"
186
+ ].map((key) => ({
187
+ key,
188
+ label: {
189
+ requests: "Requests",
190
+ allocatable: "Allocatable Capacity",
191
+ capacity: "Capacity"
192
+ }[key],
193
+ color: {
194
+ requests: "#4caf50",
195
+ allocatable: "#5486b5",
196
+ capacity: "var(--textColorSecondary)"
197
+ }[key],
198
+ value: (sample) => sample.resources?.[metric][key]
199
+ }))];
200
+ const max = Math.max(...samples.flatMap((sample) => series.map((line) => line.value(sample) ?? 0)), .001) * 1.1;
201
+ const end = last?.time ?? Date.now();
202
+ const start = samples[0]?.time ?? end;
203
+ const duration = Math.max(end - start, INTERVAL);
204
+ const x = (sample) => 88 + (sample.time - start) / duration * plotWidth;
205
+ const y = (sample) => 208 - sample[metric] / max * 184;
206
+ const selectNearest = (event) => {
207
+ const bounds = event.currentTarget.getBoundingClientRect();
208
+ if (!bounds.width || !samples.length) return;
209
+ const scale = Math.min(bounds.width / width, (bounds.height || 250) / 250);
210
+ const offset = (bounds.width - width * scale) / 2;
211
+ const time = start + ((event.clientX - bounds.left - offset) / scale - 88) / plotWidth * duration;
212
+ const nearest = samples.reduce((nearest, sample) => Math.abs(sample.time - time) < Math.abs(nearest.time - time) ? sample : nearest).time;
213
+ setSelectedTime(nearest);
214
+ return nearest;
215
+ };
216
+ return /* @__PURE__ */ require_jsx_runtime.jsxs("section", {
217
+ "aria-label": `${label} metrics-server usage`,
52
218
  style: {
53
- display: "grid",
54
- gridTemplateColumns: "repeat(2,minmax(0,1fr))",
55
- gap: 16,
56
- marginBottom: 16
219
+ padding: "var(--power-monitoring-trend-top, 16px) 16px 16px",
220
+ background: "var(--contentColor)",
221
+ minWidth: 0,
222
+ minHeight: 280,
223
+ position: "relative"
57
224
  },
58
- children: ["cpu", "memory"].map((metric) => {
59
- const last = samples.at(-1);
60
- const format = (value) => metric === "cpu" ? `${value.toFixed(2)} cores` : `${(value / 1024 ** 3).toFixed(2)} GiB`;
61
- const max = Math.max(...samples.map((sample) => sample[metric]), .001) * 1.1;
62
- const end = last?.time ?? Date.now();
63
- const start = samples[0]?.time ?? end;
64
- const points = samples.map((sample) => `${8 + (sample.time - start) / Math.max(end - start, INTERVAL) * 384},${112 - sample[metric] / max * 100}`).join(" ");
65
- return /* @__PURE__ */ require_jsx_runtime.jsxs("section", {
225
+ children: [
226
+ /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
66
227
  style: {
67
- padding: 16,
68
- border: "1px solid var(--borderFaintColor)",
69
- borderRadius: 8,
228
+ display: "flex",
229
+ alignItems: "center",
230
+ gap: 12
231
+ },
232
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(ResourceTitle, { metric }), /* @__PURE__ */ require_jsx_runtime.jsx("span", { children: status ? "Unavailable" : last ? format(last[metric]) : "Collecting…" })]
233
+ }),
234
+ /* @__PURE__ */ require_jsx_runtime.jsxs("svg", {
235
+ ref: chartRef,
236
+ "data-monitoring-trend": "",
237
+ viewBox: `0 0 ${width} 250`,
238
+ role: "img",
239
+ tabIndex: 0,
240
+ "aria-label": `${label} usage over collected samples. Use arrow keys to inspect samples. Click or press Enter to pin the tooltip, Escape to dismiss`,
241
+ onMouseMove: (event) => {
242
+ if (!pinned) selectNearest(event);
243
+ },
244
+ onClick: (event) => {
245
+ if (pinned) setPinned(false);
246
+ else if (samples.length) {
247
+ if (selectNearest(event) === void 0 && !selected) setSelectedTime(last?.time);
248
+ setPinned(true);
249
+ }
250
+ },
251
+ onMouseLeave: () => {
252
+ if (!pinned) setSelectedTime(void 0);
253
+ },
254
+ onFocus: () => {
255
+ if (!pinned) setSelectedTime(last?.time);
256
+ },
257
+ onBlur: () => {
258
+ if (!pinned) setSelectedTime(void 0);
259
+ },
260
+ onKeyDown: (event) => {
261
+ if (event.key === "Enter" || event.key === " ") {
262
+ event.preventDefault();
263
+ if (samples.length) {
264
+ if (!selected) setSelectedTime(last?.time);
265
+ setPinned(!pinned);
266
+ }
267
+ return;
268
+ }
269
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
270
+ event.preventDefault();
271
+ const index = samples.findIndex((sample) => sample.time === selectedTime);
272
+ setSelectedTime(samples[Math.max(0, Math.min(samples.length - 1, (index < 0 ? samples.length - 1 : index) + (event.key === "ArrowLeft" ? -1 : 1)))]?.time);
273
+ },
274
+ style: {
275
+ width: "100%",
276
+ height: 250,
277
+ color: "#c93dce",
278
+ display: "block"
279
+ },
280
+ children: [
281
+ /* @__PURE__ */ require_jsx_runtime.jsx("title", { children: "Click to pin tooltip · click again or Escape to unpin" }),
282
+ [
283
+ 0,
284
+ .5,
285
+ 1
286
+ ].map((fraction) => /* @__PURE__ */ require_jsx_runtime.jsxs("g", { children: [/* @__PURE__ */ require_jsx_runtime.jsx("line", {
287
+ x1: "88",
288
+ x2: width - 24,
289
+ y1: 208 - fraction * 184,
290
+ y2: 208 - fraction * 184,
291
+ stroke: "var(--borderFaintColor)"
292
+ }), /* @__PURE__ */ require_jsx_runtime.jsx("text", {
293
+ x: "80",
294
+ y: 212 - fraction * 184,
295
+ textAnchor: "end",
296
+ fill: "var(--textColorSecondary)",
297
+ fontSize: "11",
298
+ children: format(max * fraction)
299
+ })] }, fraction)),
300
+ series.map((line) => /* @__PURE__ */ require_jsx_runtime.jsx("path", {
301
+ "data-monitoring-series": line.key,
302
+ d: samples.reduce((path, sample, index) => {
303
+ const value = line.value(sample);
304
+ if (value === void 0) return path;
305
+ return `${path} ${(index ? line.value(samples[index - 1]) : void 0) === void 0 ? "M" : "L"}${x(sample)},${208 - value / max * 184}`;
306
+ }, ""),
307
+ fill: "none",
308
+ stroke: line.color,
309
+ strokeWidth: "2",
310
+ strokeDasharray: line.key === "capacity" || line.key === "allocatable" ? "5 3" : void 0
311
+ }, line.key)),
312
+ samples.map((sample) => /* @__PURE__ */ require_jsx_runtime.jsx("circle", {
313
+ cx: x(sample),
314
+ cy: y(sample),
315
+ r: "2",
316
+ fill: "currentColor",
317
+ children: /* @__PURE__ */ require_jsx_runtime.jsxs("title", { children: [
318
+ formatMonitoringTime(sample.time),
319
+ ": ",
320
+ format(sample[metric])
321
+ ] })
322
+ }, sample.time)),
323
+ samples.length > 0 && [
324
+ 0,
325
+ .5,
326
+ 1
327
+ ].map((fraction) => /* @__PURE__ */ require_jsx_runtime.jsx("text", {
328
+ x: 88 + fraction * plotWidth,
329
+ y: "235",
330
+ textAnchor: fraction === 0 ? "start" : fraction === 1 ? "end" : "middle",
331
+ fill: "var(--textColorSecondary)",
332
+ fontSize: "11",
333
+ children: formatMonitoringTime(start + fraction * duration)
334
+ }, fraction)),
335
+ selected && /* @__PURE__ */ require_jsx_runtime.jsx("line", {
336
+ "data-monitoring-crosshair": "",
337
+ x1: x(selected),
338
+ x2: x(selected),
339
+ y1: "24",
340
+ y2: "208",
341
+ stroke: "var(--textColorSecondary)",
342
+ strokeDasharray: "4 3"
343
+ })
344
+ ]
345
+ }),
346
+ /* @__PURE__ */ require_jsx_runtime.jsx("style", { children: `
347
+ @keyframes freelens-power-metric-tooltip-enter { from { opacity:0; transform:translateY(2px) } to { opacity:1; transform:translateY(0) } }
348
+ [data-monitoring-tooltip] { animation:freelens-power-metric-tooltip-enter 140ms ease-out }
349
+ @media (prefers-reduced-motion:reduce) { [data-monitoring-tooltip] { animation:none } }
350
+ ` }),
351
+ selected && /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
352
+ "data-monitoring-tooltip": "",
353
+ role: "tooltip",
354
+ style: {
355
+ position: "absolute",
356
+ top: 44,
357
+ right: 24,
358
+ padding: 8,
70
359
  background: "var(--contentColor)",
71
- minWidth: 0
360
+ border: "1px solid var(--borderFaintColor)",
361
+ pointerEvents: "none"
72
362
  },
73
- "aria-label": `${metric === "cpu" ? "CPU" : "Memory"} metrics-server usage`,
74
363
  children: [
75
- /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
76
- style: {
77
- display: "flex",
78
- justifyContent: "space-between",
79
- gap: 8
80
- },
81
- children: [/* @__PURE__ */ require_jsx_runtime.jsx("strong", { children: metric === "cpu" ? "CPU" : "Memory" }), /* @__PURE__ */ require_jsx_runtime.jsx("span", { children: status ? "Unavailable" : last ? format(last[metric]) : "Collecting…" })]
82
- }),
83
- /* @__PURE__ */ require_jsx_runtime.jsxs("svg", {
84
- viewBox: "0 0 400 128",
85
- role: "img",
86
- "aria-label": `${metric} usage over collected samples`,
87
- style: {
88
- width: "100%",
89
- height: 128,
90
- color: "var(--primary)"
91
- },
364
+ /* @__PURE__ */ require_jsx_runtime.jsxs("div", { children: [formatMonitoringTime(selected.time), pinned ? " · Pinned" : ""] }),
365
+ series.map((line) => /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
366
+ style: { color: line.color },
92
367
  children: [
93
- [
94
- 12,
95
- 62,
96
- 112
97
- ].map((y) => /* @__PURE__ */ require_jsx_runtime.jsx("line", {
98
- x1: "8",
99
- x2: "392",
100
- y1: y,
101
- y2: y,
102
- stroke: "var(--borderFaintColor)"
103
- }, y)),
104
- /* @__PURE__ */ require_jsx_runtime.jsx("polyline", {
105
- points,
106
- fill: "none",
107
- stroke: "currentColor",
108
- strokeWidth: "2"
109
- }),
110
- samples.map((sample) => /* @__PURE__ */ require_jsx_runtime.jsx("circle", {
111
- cx: 8 + (sample.time - start) / Math.max(end - start, INTERVAL) * 384,
112
- cy: 112 - sample[metric] / max * 100,
113
- r: "2",
114
- fill: "currentColor",
115
- children: /* @__PURE__ */ require_jsx_runtime.jsxs("title", { children: [
116
- new Date(sample.time).toLocaleTimeString(),
117
- ": ",
118
- format(sample[metric])
119
- ] })
120
- }, sample.time))
368
+ line.label,
369
+ ": ",
370
+ line.value(selected) === void 0 ? "Unavailable" : format(line.value(selected))
121
371
  ]
122
- }),
123
- /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
372
+ }, line.key)),
373
+ /* @__PURE__ */ require_jsx_runtime.jsx("div", {
124
374
  style: {
125
- display: "flex",
126
- justifyContent: "space-between",
127
375
  color: "var(--textColorSecondary)",
128
- fontSize: 11
376
+ marginTop: 4,
377
+ fontSize: 10
129
378
  },
130
- children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", { children: samples.length ? new Date(start).toLocaleTimeString() : "Waiting for samples" }), /* @__PURE__ */ require_jsx_runtime.jsx("span", { children: samples.length ? new Date(end).toLocaleTimeString() : "" })]
379
+ children: pinned ? "Click chart or Escape to unpin" : "Click or Enter to pin"
380
+ })
381
+ ]
382
+ }),
383
+ /* @__PURE__ */ require_jsx_runtime.jsx("div", {
384
+ "aria-label": `${label} chart legend`,
385
+ style: {
386
+ display: "flex",
387
+ flexWrap: "wrap",
388
+ gap: "6px 16px",
389
+ marginBottom: 8
390
+ },
391
+ children: series.map((line) => /* @__PURE__ */ require_jsx_runtime.jsxs("span", { children: [
392
+ /* @__PURE__ */ require_jsx_runtime.jsx("span", {
393
+ style: { color: line.color },
394
+ children: "●"
131
395
  }),
132
- /* @__PURE__ */ require_jsx_runtime.jsx("div", {
133
- style: {
134
- marginTop: 8,
135
- fontSize: 11,
136
- color: "var(--textColorSecondary)"
137
- },
138
- role: "status",
139
- children: status || "metrics-server · reported node usage · session samples"
396
+ " ",
397
+ line.label
398
+ ] }, line.key))
399
+ }),
400
+ last?.resourceStatus && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
401
+ role: "status",
402
+ style: {
403
+ color: "var(--textColorSecondary)",
404
+ marginBottom: 8
405
+ },
406
+ children: last.resourceStatus
407
+ }),
408
+ /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
409
+ style: {
410
+ fontSize: 11,
411
+ color: "var(--textColorSecondary)",
412
+ display: "flex",
413
+ alignItems: "center",
414
+ gap: 4
415
+ },
416
+ role: "status",
417
+ "aria-label": `${scope} metrics status`,
418
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx("sup", {
419
+ "aria-hidden": "true",
420
+ style: { fontSize: 12 },
421
+ children: "*"
422
+ }), /* @__PURE__ */ require_jsx_runtime.jsx("span", { children: status || (samples.length ? "metrics-server data" : "Waiting for metrics-server data") })]
423
+ })
424
+ ]
425
+ });
426
+ }
427
+ function ResourceDonut({ metric, sample, status }) {
428
+ const [activeRing, setActiveRing] = require__global_external_react.useState();
429
+ require__global_external_react.useEffect(() => {
430
+ if (activeRing === void 0) return;
431
+ const dismiss = (event) => {
432
+ if (event.key !== "Escape") return;
433
+ event.preventDefault();
434
+ event.stopPropagation();
435
+ setActiveRing(void 0);
436
+ };
437
+ document.addEventListener("keydown", dismiss, true);
438
+ return () => document.removeEventListener("keydown", dismiss, true);
439
+ }, [activeRing]);
440
+ const label = metric === "cpu" ? "CPU" : "Memory";
441
+ const totals = sample?.resources?.[metric];
442
+ const format = (value) => metric === "cpu" ? `${value.toFixed(2)} cores` : `${(value / 1024 ** 3).toFixed(2)} GiB`;
443
+ const available = (value) => value === void 0 ? "Unavailable" : format(value);
444
+ const denominator = totals?.allocatable;
445
+ const rings = [{
446
+ label: "Usage",
447
+ color: "#c93dce",
448
+ value: sample?.[metric]
449
+ }, {
450
+ label: "Requests",
451
+ color: "#4caf50",
452
+ value: totals?.requests
453
+ }];
454
+ const percentage = (value) => value !== void 0 && denominator !== void 0 && denominator > 0 ? value / denominator * 100 : void 0;
455
+ const usagePercent = percentage(sample?.[metric]);
456
+ const unused = sample?.completeUsage && denominator !== void 0 ? format(denominator - sample[metric]) : "Unavailable";
457
+ const unrequested = denominator !== void 0 && totals?.requests !== void 0 ? format(denominator - totals.requests) : "Unavailable";
458
+ const allocationDetail = `Allocatable: ${available(denominator)}. Schedulable capacity after system reservations, used as the denominator for both rings. Physical Capacity: ${available(totals?.capacity)}. Unused allocatable (usage): ${unused}. Unrequested: ${unrequested}`;
459
+ const hovered = activeRing === void 0 ? void 0 : rings[activeRing];
460
+ const tooltipId = `power-monitoring-${metric}-donut-tooltip`;
461
+ return /* @__PURE__ */ require_jsx_runtime.jsxs("section", {
462
+ "aria-label": `${label} resource summary`,
463
+ style: {
464
+ padding: "var(--power-monitoring-summary-top, 16px) 16px 16px",
465
+ minWidth: 0,
466
+ background: "var(--contentColor)",
467
+ position: "relative",
468
+ overflow: "visible"
469
+ },
470
+ children: [
471
+ /* @__PURE__ */ require_jsx_runtime.jsx(ResourceTitle, {
472
+ metric,
473
+ label: metric === "memory" ? "RAM" : "CPU"
474
+ }),
475
+ /* @__PURE__ */ require_jsx_runtime.jsxs("svg", {
476
+ "data-monitoring-donut": "",
477
+ viewBox: "0 0 200 200",
478
+ role: "img",
479
+ "aria-label": `${label} usage and requests relative to allocatable capacity`,
480
+ style: {
481
+ display: "block",
482
+ width: "100%",
483
+ height: 200
484
+ },
485
+ children: [
486
+ rings.map((ring, index) => {
487
+ const radius = 76 - index * 18;
488
+ const percent = percentage(ring.value);
489
+ return /* @__PURE__ */ require_jsx_runtime.jsxs("g", {
490
+ tabIndex: 0,
491
+ role: "img",
492
+ "aria-label": `${ring.label}: ${available(ring.value)}${percent === void 0 ? "" : ` (${percent.toFixed(1)}% of allocatable${percent > 100 ? ", over allocatable" : ""})`}`,
493
+ "aria-describedby": activeRing === index ? tooltipId : void 0,
494
+ onMouseEnter: () => setActiveRing(index),
495
+ onMouseMove: () => setActiveRing(index),
496
+ onMouseLeave: () => setActiveRing(void 0),
497
+ onFocus: () => setActiveRing(index),
498
+ onBlur: () => setActiveRing(void 0),
499
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx("circle", {
500
+ cx: "100",
501
+ cy: "100",
502
+ r: radius,
503
+ fill: "none",
504
+ stroke: "var(--borderFaintColor)",
505
+ strokeWidth: "12"
506
+ }), percent !== void 0 && /* @__PURE__ */ require_jsx_runtime.jsx("circle", {
507
+ "data-monitoring-ring": ring.label,
508
+ cx: "100",
509
+ cy: "100",
510
+ r: radius,
511
+ fill: "none",
512
+ stroke: ring.color,
513
+ strokeWidth: "12",
514
+ pathLength: "100",
515
+ strokeDasharray: `${Math.min(100, Math.max(0, percent))} 100`,
516
+ transform: "rotate(-90 100 100)"
517
+ })]
518
+ }, ring.label);
519
+ }),
520
+ /* @__PURE__ */ require_jsx_runtime.jsx("text", {
521
+ "data-monitoring-usage-percentage": "",
522
+ x: "100",
523
+ y: "100",
524
+ textAnchor: "middle",
525
+ fill: "var(--textColorPrimary)",
526
+ fontSize: "26",
527
+ fontWeight: "600",
528
+ children: usagePercent === void 0 ? "N/A" : `${usagePercent.toFixed(1)}%`
529
+ }),
530
+ /* @__PURE__ */ require_jsx_runtime.jsx("text", {
531
+ x: "100",
532
+ y: "120",
533
+ textAnchor: "middle",
534
+ fill: "var(--textColorPrimary)",
535
+ fontSize: "12",
536
+ children: "Usage"
140
537
  })
141
538
  ]
142
- }, metric);
143
- })
539
+ }),
540
+ hovered && /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
541
+ id: tooltipId,
542
+ "data-monitoring-tooltip": "",
543
+ role: "tooltip",
544
+ style: {
545
+ position: "absolute",
546
+ top: 48,
547
+ left: 8,
548
+ right: 8,
549
+ zIndex: 20,
550
+ padding: 8,
551
+ border: "1px solid var(--borderFaintColor)",
552
+ background: "var(--contentColor)",
553
+ pointerEvents: "none",
554
+ overflowWrap: "anywhere",
555
+ boxShadow: "0 3px 12px #0003"
556
+ },
557
+ children: [
558
+ /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
559
+ style: { color: hovered.color },
560
+ children: [
561
+ hovered.label,
562
+ ": ",
563
+ available(hovered.value)
564
+ ]
565
+ }),
566
+ /* @__PURE__ */ require_jsx_runtime.jsx("div", { children: percentage(hovered.value) === void 0 ? "Percentage unavailable" : `${percentage(hovered.value).toFixed(1)}% of allocatable${percentage(hovered.value) > 100 ? " (over allocatable)" : ""}` }),
567
+ /* @__PURE__ */ require_jsx_runtime.jsxs("div", { children: ["Allocatable: ", available(denominator)] })
568
+ ]
569
+ }),
570
+ /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
571
+ "aria-label": `${label} resource totals`,
572
+ style: {
573
+ display: "grid",
574
+ gap: 6,
575
+ fontSize: 12
576
+ },
577
+ children: [rings.map((ring) => /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
578
+ title: `${ring.label} relative to allocatable capacity. ${ring.label === "Usage" ? `Unused allocatable (usage): ${unused}` : `Unrequested: ${unrequested}`}`,
579
+ children: [
580
+ /* @__PURE__ */ require_jsx_runtime.jsx("span", {
581
+ style: { color: ring.color },
582
+ children: "●"
583
+ }),
584
+ " ",
585
+ ring.label,
586
+ ": ",
587
+ available(ring.value),
588
+ percentage(ring.value) !== void 0 && /* @__PURE__ */ require_jsx_runtime.jsxs("span", { children: [
589
+ " (",
590
+ percentage(ring.value).toFixed(1),
591
+ "%)"
592
+ ] })
593
+ ]
594
+ }, ring.label)), /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
595
+ title: allocationDetail,
596
+ children: [
597
+ /* @__PURE__ */ require_jsx_runtime.jsx("span", {
598
+ style: { color: "#5486b5" },
599
+ children: "●"
600
+ }),
601
+ " Allocatable: ",
602
+ available(denominator)
603
+ ]
604
+ })]
605
+ }),
606
+ (status || sample?.resourceStatus) && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
607
+ role: "status",
608
+ style: {
609
+ color: "var(--textColorSecondary)",
610
+ fontSize: 11,
611
+ marginTop: 8
612
+ },
613
+ children: status || sample?.resourceStatus
614
+ })
615
+ ]
616
+ });
617
+ }
618
+ function MonitoringCharts({ samples, status }) {
619
+ return /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
620
+ style: { containerType: "inline-size" },
621
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx("style", { children: `
622
+ [data-monitoring-layout] { display:grid; grid-template-columns:minmax(0,1.6fr) minmax(0,1.6fr) minmax(0,1fr) minmax(0,1fr); gap:16px; align-items:stretch }
623
+ [data-monitoring-trends], [data-monitoring-summaries] { display:contents }
624
+ @container (min-width: 961px) {
625
+ [data-monitoring-summaries] > section { --power-monitoring-summary-top:16px }
626
+ }
627
+ @container (max-width: 960px) {
628
+ [data-monitoring-layout] { grid-template-columns:repeat(2,minmax(0,1fr)) }
629
+ .power-ai-overview-actions + [data-testid="cluster-overview-page"] [data-monitoring-trends] > section { --power-monitoring-trend-top:56px }
630
+ }
631
+ @container (max-width: 580px) {
632
+ [data-monitoring-layout] { grid-template-columns:minmax(0,1fr) }
633
+ .power-ai-overview-actions + [data-testid="cluster-overview-page"] [data-monitoring-trends] > section:not(:first-child) { --power-monitoring-trend-top:16px }
634
+ }
635
+ ` }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
636
+ "data-monitoring-layout": "",
637
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx("div", {
638
+ "data-monitoring-trends": "",
639
+ children: ["cpu", "memory"].map((metric) => /* @__PURE__ */ require_jsx_runtime.jsx(MetricChart, {
640
+ metric,
641
+ samples,
642
+ status
643
+ }, metric))
644
+ }), /* @__PURE__ */ require_jsx_runtime.jsx("div", {
645
+ "data-monitoring-summaries": "",
646
+ children: ["cpu", "memory"].map((metric) => /* @__PURE__ */ require_jsx_runtime.jsx(ResourceDonut, {
647
+ metric,
648
+ sample: samples.at(-1),
649
+ status
650
+ }, metric))
651
+ })]
652
+ })]
144
653
  });
145
654
  }
146
655
  function startMonitoring() {
@@ -156,10 +665,23 @@ function startMonitoring() {
156
665
  };
157
666
  }
158
667
  function mountMonitoring() {
668
+ const suppression = document.createElement("style");
669
+ suppression.setAttribute("data-freelens-power-native-metrics-suppression", "");
670
+ suppression.textContent = `
671
+ [data-testid="cluster-overview-page"] > [class*="ClusterMetrics"],
672
+ [data-testid="cluster-overview-page"] > :has([class*="ClusterMetrics"]),
673
+ [data-testid="cluster-overview-page"] > :is([class*="noMetrics"], [data-testid="no-metrics-message"]):not(:has(.PieChart)),
674
+ [data-testid="cluster-overview-page"] > :has([class*="noMetrics"], [data-testid="no-metrics-message"], [class*="cluster-pie-charts"][class*="empty"]):not(:has(.PieChart)) {
675
+ display:none !important;
676
+ }
677
+ `;
678
+ document.head.append(suppression);
159
679
  let host;
160
680
  let overview = null;
161
681
  let samples = [];
162
682
  let status = "";
683
+ let renderedSamples;
684
+ let renderedStatus;
163
685
  let cluster;
164
686
  let stopped = false;
165
687
  let pendingGeneration;
@@ -174,6 +696,8 @@ function mountMonitoring() {
174
696
  require__global_external_react_dom.default.unmountComponentAtNode(host);
175
697
  host.remove();
176
698
  host = void 0;
699
+ renderedSamples = void 0;
700
+ renderedStatus = void 0;
177
701
  }
178
702
  };
179
703
  const update = async () => {
@@ -188,26 +712,50 @@ function mountMonitoring() {
188
712
  cluster = currentCluster;
189
713
  overview = currentOverview;
190
714
  }
191
- samples = samples.filter((sample) => sample.time >= Date.now() - 60 * INTERVAL);
715
+ const cutoff = Date.now() - 60 * INTERVAL;
716
+ if (samples.length && samples[0].time < cutoff) samples = samples.filter((sample) => sample.time >= cutoff);
192
717
  if (!overview || !cluster || document.visibilityState === "hidden") return;
193
718
  if (!host) {
194
719
  host = document.createElement("div");
195
720
  host.setAttribute("data-freelens-power-monitoring", "");
721
+ host.style.gridColumn = "1 / -1";
722
+ hidden.set(overview, overview.getAttribute("style"));
723
+ Object.assign(overview.style, {
724
+ gridTemplateAreas: "none",
725
+ gridTemplateColumns: "minmax(0, 1fr)",
726
+ gridTemplateRows: "none",
727
+ height: "auto"
728
+ });
196
729
  overview.prepend(host);
197
730
  }
198
731
  for (const title of overview.querySelectorAll(".PieChart > .chart-title")) {
199
732
  if (title.textContent !== "CPU" && title.textContent !== "Memory") continue;
200
733
  const panel = title.closest(".PieChart")?.parentElement;
201
- if (panel && !hidden.has(panel)) {
202
- hidden.set(panel, panel.getAttribute("style"));
734
+ if (panel && panel.style.display !== "none") {
735
+ if (!hidden.has(panel)) hidden.set(panel, panel.getAttribute("style"));
203
736
  panel.style.display = "none";
204
737
  }
205
738
  }
739
+ for (const child of overview.children) {
740
+ if (!(child instanceof HTMLElement) || child === host) continue;
741
+ const nativeMetrics = child.matches("[class*=\"ClusterMetrics\"]") || child.querySelector("[class*=\"ClusterMetrics\"]");
742
+ const unavailablePies = (child.matches("[class*=\"noMetrics\"], [data-testid=\"no-metrics-message\"]") || child.querySelector("[class*=\"noMetrics\"], [data-testid=\"no-metrics-message\"], [class*=\"cluster-pie-charts\"][class*=\"empty\"]")) && !child.querySelector(".PieChart");
743
+ if ((nativeMetrics || unavailablePies) && child.style.display !== "none") {
744
+ if (!hidden.has(child)) hidden.set(child, child.getAttribute("style"));
745
+ child.style.display = "none";
746
+ }
747
+ if (!hidden.has(child)) hidden.set(child, child.getAttribute("style"));
748
+ child.style.gridArea = "auto";
749
+ }
206
750
  const render = () => {
207
- if (host) require__global_external_react_dom.default.render(/* @__PURE__ */ require_jsx_runtime.jsx(MonitoringCharts, {
208
- samples,
209
- status
210
- }), host);
751
+ if (host && (renderedSamples !== samples || renderedStatus !== status)) {
752
+ require__global_external_react_dom.default.render(/* @__PURE__ */ require_jsx_runtime.jsx(MonitoringCharts, {
753
+ samples,
754
+ status
755
+ }), host);
756
+ renderedSamples = samples;
757
+ renderedStatus = status;
758
+ }
211
759
  };
212
760
  render();
213
761
  if (pendingGeneration === generation || Date.now() < nextSample) return;
@@ -216,9 +764,28 @@ function mountMonitoring() {
216
764
  const requestGeneration = generation;
217
765
  const isCurrent = () => !stopped && requestGeneration === generation && document.visibilityState !== "hidden" && overview?.isConnected === true && require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid === cluster;
218
766
  try {
219
- const nodes = await require_extensions.Renderer.K8sApi.nodesMetricsApi.list();
767
+ const [metrics, inventory, pods] = await Promise.allSettled([
768
+ require_extensions.Renderer.K8sApi.nodesMetricsApi.list(),
769
+ Promise.resolve().then(() => require_extensions.Renderer.K8sApi.nodesApi.list()),
770
+ Promise.resolve().then(() => require_extensions.Renderer.K8sApi.podsApi.list())
771
+ ]);
220
772
  if (!isCurrent()) return;
221
- samples = [...samples, aggregateMetrics(nodes ?? [])].slice(-60);
773
+ if (metrics.status !== "fulfilled") throw new Error("Metrics unavailable");
774
+ const sample = aggregateMetrics(metrics.value ?? []);
775
+ const warnings = [];
776
+ if (inventory.status === "fulfilled") try {
777
+ sample.resources = aggregateResources(inventory.value ?? [], pods.status === "fulfilled" ? pods.value ?? void 0 : void 0);
778
+ const expected = new Set((inventory.value ?? []).map((node) => node.metadata.name));
779
+ const observed = new Set((metrics.value ?? []).map((node) => node.metadata.name));
780
+ sample.completeUsage = expected.size > 0 && expected.size === observed.size && [...expected].every((name) => observed.has(name));
781
+ if (!sample.completeUsage) warnings.push("Partial node metrics coverage: usage is the reported subset; unused allocatable unavailable");
782
+ } catch {
783
+ warnings.push("Resource totals unavailable: incomplete or invalid node/pod data");
784
+ }
785
+ else warnings.push("Node inventory unavailable or access denied; usage coverage unverified, capacity unavailable");
786
+ if (pods.status !== "fulfilled") warnings.push("Cluster-wide Pods unavailable or access denied; requests unavailable");
787
+ sample.resourceStatus = warnings.join(". ");
788
+ samples = [...samples, sample].slice(-60);
222
789
  status = "";
223
790
  } catch {
224
791
  if (!isCurrent()) return;
@@ -228,6 +795,13 @@ function mountMonitoring() {
228
795
  }
229
796
  if (!stopped && requestGeneration === generation) render();
230
797
  };
798
+ const observer = new MutationObserver(() => {
799
+ update();
800
+ });
801
+ observer.observe(document.body, {
802
+ childList: true,
803
+ subtree: true
804
+ });
231
805
  const timer = window.setInterval(() => {
232
806
  update();
233
807
  }, 1e3);
@@ -236,12 +810,18 @@ function mountMonitoring() {
236
810
  stopped = true;
237
811
  generation++;
238
812
  window.clearInterval(timer);
813
+ observer.disconnect();
814
+ suppression.remove();
239
815
  restore();
240
816
  samples = [];
241
817
  };
242
818
  }
243
819
  //#endregion
820
+ exports.MetricChart = MetricChart;
244
821
  exports.aggregateMetrics = aggregateMetrics;
822
+ exports.aggregateResources = aggregateResources;
823
+ exports.effectivePodResource = effectivePodResource;
824
+ exports.formatMonitoringTime = formatMonitoringTime;
245
825
  exports.parseQuantity = parseQuantity;
246
826
  exports.startMonitoring = startMonitoring;
247
827