@64mb/freelens-power 1.0.1 → 1.0.3

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