@64mb/freelens-power 1.0.2 → 1.1.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.
Files changed (47) hide show
  1. package/README.md +5 -1
  2. package/out/main/common/monitoring-history.js +52 -0
  3. package/out/main/common/monitoring-history.js.map +1 -0
  4. package/out/main/common/store/monitoring-preferences-store.js +25 -2
  5. package/out/main/common/store/monitoring-preferences-store.js.map +1 -1
  6. package/out/main/dashboard-views.js +132 -28
  7. package/out/main/dashboard-views.js.map +1 -1
  8. package/out/main/index.js +2 -0
  9. package/out/main/index.js.map +1 -1
  10. package/out/main/monitoring-history-database.js +103 -0
  11. package/out/main/monitoring-history-database.js.map +1 -0
  12. package/out/main/monitoring-history-worker.js +61 -0
  13. package/out/main/monitoring-history-worker.js.map +1 -0
  14. package/out/main/monitoring-history.js +100 -0
  15. package/out/main/monitoring-history.js.map +1 -0
  16. package/out/renderer/_virtual/_global-external_mobx.js +2 -1
  17. package/out/renderer/diagnostics.js +647 -0
  18. package/out/renderer/diagnostics.js.map +1 -0
  19. package/out/renderer/index.js +28 -1
  20. package/out/renderer/index.js.map +1 -1
  21. package/out/renderer/monitoring-collector.js +202 -0
  22. package/out/renderer/monitoring-collector.js.map +1 -0
  23. package/out/renderer/monitoring-history-collection.js +184 -0
  24. package/out/renderer/monitoring-history-collection.js.map +1 -0
  25. package/out/renderer/monitoring-history-view.js +150 -0
  26. package/out/renderer/monitoring-history-view.js.map +1 -0
  27. package/out/renderer/monitoring-history.js +19 -0
  28. package/out/renderer/monitoring-history.js.map +1 -0
  29. package/out/renderer/monitoring.js +104 -130
  30. package/out/renderer/monitoring.js.map +1 -1
  31. package/out/renderer/node-monitoring.js +116 -0
  32. package/out/renderer/node-monitoring.js.map +1 -0
  33. package/out/renderer/pod-monitoring.js +107 -8
  34. package/out/renderer/pod-monitoring.js.map +1 -1
  35. package/out/renderer/preferences/monitoring-preferences.js +55 -10
  36. package/out/renderer/preferences/monitoring-preferences.js.map +1 -1
  37. package/out/renderer/src/common/monitoring-history.js +15 -0
  38. package/out/renderer/src/common/monitoring-history.js.map +1 -0
  39. package/out/renderer/src/common/monitoring-math.js +98 -0
  40. package/out/renderer/src/common/monitoring-math.js.map +1 -0
  41. package/out/renderer/src/common/resource-diagnostics.js +227 -0
  42. package/out/renderer/src/common/resource-diagnostics.js.map +1 -0
  43. package/out/renderer/src/common/store/monitoring-preferences-store.js +25 -2
  44. package/out/renderer/src/common/store/monitoring-preferences-store.js.map +1 -1
  45. package/out/renderer/welcome.js +3 -1
  46. package/out/renderer/welcome.js.map +1 -1
  47. package/package.json +1 -1
@@ -0,0 +1,116 @@
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_monitoring_preferences_store = require("./src/common/store/monitoring-preferences-store.js");
4
+ const require__global_external_react = require("./_virtual/_global-external_react.js");
5
+ const require_jsx_runtime = require("./_virtual/_global-external_react/jsx-runtime.js");
6
+ const require_monitoring_collector = require("./monitoring-collector.js");
7
+ const require_monitoring_history_view = require("./monitoring-history-view.js");
8
+ const require_monitoring = require("./monitoring.js");
9
+ //#region src/renderer/node-monitoring.tsx
10
+ var NodeMonitoringCharts = require__global_external_mobx_react.observer(({ object }) => {
11
+ const clusterId = require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid;
12
+ const name = object.metadata.name;
13
+ const uid = object.metadata.uid;
14
+ const identity = JSON.stringify([
15
+ clusterId,
16
+ name,
17
+ uid
18
+ ]);
19
+ const [state, setState] = require__global_external_react.useState({
20
+ identity,
21
+ samples: [],
22
+ status: ""
23
+ });
24
+ require__global_external_react.useEffect(() => {
25
+ let stopped = false;
26
+ let pending = false;
27
+ const release = require_monitoring_collector.retainMonitoringCollector();
28
+ setState({
29
+ identity,
30
+ samples: [],
31
+ status: ""
32
+ });
33
+ const current = () => !stopped && require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid === clusterId && object.metadata.name === name && object.metadata.uid === uid;
34
+ const update = async () => {
35
+ if (pending || !clusterId || document.visibilityState === "hidden") return;
36
+ pending = true;
37
+ try {
38
+ const snapshot = await require_monitoring_collector.getMonitoringSnapshot();
39
+ if (!current() || snapshot.clusterId !== clusterId) return;
40
+ const node = snapshot.nodes?.find((candidate) => candidate.metadata.name === name);
41
+ const replaced = !!uid && !!node?.metadata.uid && node.metadata.uid !== uid;
42
+ const sample = replaced ? void 0 : require_monitoring_collector.getNodeSample(snapshot, name, uid);
43
+ setState((previous) => {
44
+ const retained = (previous.identity === identity && !replaced ? previous.samples : []).filter((entry) => entry.time >= Date.now() - 30 * 6e4);
45
+ return {
46
+ identity,
47
+ samples: sample ? [...retained.filter((entry) => entry.time !== sample.time), sample].sort((a, b) => a.time - b.time).slice(-60) : retained,
48
+ status: sample ? "" : "Node metrics unavailable, stale or access denied. Last collected samples remain visible."
49
+ };
50
+ });
51
+ } catch {
52
+ if (current()) setState((previous) => ({
53
+ identity,
54
+ samples: previous.identity === identity ? previous.samples.filter((entry) => entry.time >= Date.now() - 30 * 6e4) : [],
55
+ status: "Node metrics unavailable, stale or access denied. Last collected samples remain visible."
56
+ }));
57
+ } finally {
58
+ pending = false;
59
+ }
60
+ };
61
+ const timer = window.setInterval(() => {
62
+ update();
63
+ }, 1e3);
64
+ update();
65
+ return () => {
66
+ stopped = true;
67
+ window.clearInterval(timer);
68
+ release();
69
+ };
70
+ }, [
71
+ clusterId,
72
+ identity,
73
+ name,
74
+ object,
75
+ uid
76
+ ]);
77
+ const samples = state.identity === identity ? state.samples : [];
78
+ const status = state.identity === identity ? state.status : "";
79
+ const history = require_monitoring_history_view.useMonitoringHistory(clusterId && uid ? {
80
+ clusterId,
81
+ scope: "node",
82
+ uid
83
+ } : void 0, samples);
84
+ return /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
85
+ "data-power-node-monitoring": "",
86
+ children: [
87
+ /* @__PURE__ */ require_jsx_runtime.jsx("style", { children: `
88
+ .KubeObjectDetails:has([data-power-node-monitoring]) .ResourceMetrics,
89
+ .KubeObjectDetails:has([data-power-node-monitoring]) [data-time-range]:has(+ .ResourceMetrics) {
90
+ display: none !important;
91
+ }
92
+ ` }),
93
+ /* @__PURE__ */ require_jsx_runtime.jsx(require_monitoring_history_view.MonitoringHistoryRange, {
94
+ native: true,
95
+ range: history.range,
96
+ onChange: history.setRange
97
+ }),
98
+ history.status && /* @__PURE__ */ require_jsx_runtime.jsx("div", {
99
+ role: "status",
100
+ children: history.status
101
+ }),
102
+ ["cpu", "memory"].map((metric) => /* @__PURE__ */ require_jsx_runtime.jsx(require_monitoring.MetricChart, {
103
+ metric,
104
+ samples: history.samples,
105
+ status
106
+ }, `${identity}:${metric}`))
107
+ ]
108
+ });
109
+ });
110
+ var NodeMonitoringDetails = require__global_external_mobx_react.observer((props) => {
111
+ return require_monitoring_preferences_store.MonitoringPreferencesStore.getInstanceOrCreate().enabled ? /* @__PURE__ */ require_jsx_runtime.jsx(NodeMonitoringCharts, { ...props }) : null;
112
+ });
113
+ //#endregion
114
+ exports.NodeMonitoringDetails = NodeMonitoringDetails;
115
+
116
+ //# sourceMappingURL=node-monitoring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-monitoring.js","names":[],"sources":["../../src/renderer/node-monitoring.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { observer } from \"mobx-react\";\nimport { useEffect, useState } from \"react\";\nimport { MAX_SAMPLES, type MonitoringSample } from \"../common/monitoring-math\";\nimport { MonitoringPreferencesStore } from \"../common/store/monitoring-preferences-store\";\nimport { MetricChart } from \"./monitoring\";\nimport { getMonitoringSnapshot, getNodeSample, retainMonitoringCollector } from \"./monitoring-collector\";\nimport { MonitoringHistoryRange, useMonitoringHistory } from \"./monitoring-history-view\";\n\ntype NodeProps = Renderer.Component.KubeObjectDetailsProps<Renderer.K8sApi.Node>;\n\nconst NodeMonitoringCharts = observer(({ object }: NodeProps) => {\n const clusterId = Renderer.Catalog.activeCluster.get()?.metadata.uid;\n const name = object.metadata.name;\n const uid = object.metadata.uid;\n const identity = JSON.stringify([clusterId, name, uid]);\n const [state, setState] = useState<{ identity: string; samples: MonitoringSample[]; status: string }>({\n identity,\n samples: [],\n status: \"\",\n });\n useEffect(() => {\n let stopped = false;\n let pending = false;\n const release = retainMonitoringCollector();\n setState({ identity, samples: [], status: \"\" });\n const current = () =>\n !stopped &&\n Renderer.Catalog.activeCluster.get()?.metadata.uid === clusterId &&\n object.metadata.name === name &&\n object.metadata.uid === uid;\n const update = async () => {\n if (pending || !clusterId || document.visibilityState === \"hidden\") return;\n pending = true;\n try {\n const snapshot = await getMonitoringSnapshot();\n if (!current() || snapshot.clusterId !== clusterId) return;\n const node = snapshot.nodes?.find((candidate) => candidate.metadata.name === name);\n const replaced = !!uid && !!node?.metadata.uid && node.metadata.uid !== uid;\n const sample = replaced ? undefined : getNodeSample(snapshot, name, uid);\n setState((previous) => {\n const retained = (previous.identity === identity && !replaced ? previous.samples : []).filter(\n (entry) => entry.time >= Date.now() - 30 * 60_000,\n );\n return {\n identity,\n samples: sample\n ? [...retained.filter((entry) => entry.time !== sample.time), sample]\n .sort((a, b) => a.time - b.time)\n .slice(-MAX_SAMPLES)\n : retained,\n status: sample\n ? \"\"\n : \"Node metrics unavailable, stale or access denied. Last collected samples remain visible.\",\n };\n });\n } catch {\n if (current())\n setState((previous) => ({\n identity,\n samples:\n previous.identity === identity\n ? previous.samples.filter((entry) => entry.time >= Date.now() - 30 * 60_000)\n : [],\n status: \"Node metrics unavailable, stale or access denied. Last collected samples remain visible.\",\n }));\n } finally {\n pending = false;\n }\n };\n const timer = window.setInterval(() => {\n void update();\n }, 1000);\n void update();\n return () => {\n stopped = true;\n window.clearInterval(timer);\n release();\n };\n }, [clusterId, identity, name, object, uid]);\n const samples = state.identity === identity ? state.samples : [];\n const status = state.identity === identity ? state.status : \"\";\n const history = useMonitoringHistory(clusterId && uid ? { clusterId, scope: \"node\", uid } : undefined, samples);\n return (\n <div data-power-node-monitoring=\"\">\n <style>{`\n .KubeObjectDetails:has([data-power-node-monitoring]) .ResourceMetrics,\n .KubeObjectDetails:has([data-power-node-monitoring]) [data-time-range]:has(+ .ResourceMetrics) {\n display: none !important;\n }\n `}</style>\n <MonitoringHistoryRange native range={history.range} onChange={history.setRange} />\n {history.status && <div role=\"status\">{history.status}</div>}\n {([\"cpu\", \"memory\"] as const).map((metric) => (\n <MetricChart key={`${identity}:${metric}`} metric={metric} samples={history.samples} status={status} />\n ))}\n </div>\n );\n});\n\nexport const NodeMonitoringDetails = observer((props: NodeProps) => {\n const preferences = MonitoringPreferencesStore.getInstanceOrCreate<MonitoringPreferencesStore>();\n return preferences.enabled ? <NodeMonitoringCharts {...props} /> : null;\n});\n"],"mappings":"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"}
@@ -3,6 +3,9 @@ const require__global_external_mobx = require("./_virtual/_global-external_mobx.
3
3
  const require_monitoring_preferences_store = require("./src/common/store/monitoring-preferences-store.js");
4
4
  const require__global_external_react_dom = require("./_virtual/_global-external_react-dom.js");
5
5
  const require_jsx_runtime = require("./_virtual/_global-external_react/jsx-runtime.js");
6
+ const require_monitoring_math = require("./src/common/monitoring-math.js");
7
+ const require_monitoring_collector = require("./monitoring-collector.js");
8
+ const require_monitoring_history_view = require("./monitoring-history-view.js");
6
9
  const require_monitoring = require("./monitoring.js");
7
10
  //#region src/renderer/pod-monitoring.tsx
8
11
  var INTERVAL = 3e4;
@@ -17,8 +20,8 @@ function aggregatePodMetrics(metrics, identity, now = Date.now()) {
17
20
  for (const container of metrics.containers) {
18
21
  if (!container.name || names.has(container.name)) throw new Error("Invalid container metrics");
19
22
  names.add(container.name);
20
- cpu += require_monitoring.parseQuantity(container.usage.cpu);
21
- memory += require_monitoring.parseQuantity(container.usage.memory, true);
23
+ cpu += require_monitoring_math.parseQuantity(container.usage.cpu);
24
+ memory += require_monitoring_math.parseQuantity(container.usage.memory, true);
22
25
  }
23
26
  return {
24
27
  time,
@@ -26,6 +29,29 @@ function aggregatePodMetrics(metrics, identity, now = Date.now()) {
26
29
  memory
27
30
  };
28
31
  }
32
+ function PodMonitoringChart({ clusterId, uid, samples, status, metric }) {
33
+ const history = require_monitoring_history_view.useMonitoringHistory(uid ? {
34
+ clusterId,
35
+ scope: "pod",
36
+ uid
37
+ } : void 0, samples);
38
+ return /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [
39
+ /* @__PURE__ */ require_jsx_runtime.jsx(require_monitoring_history_view.MonitoringHistoryRange, {
40
+ range: history.range,
41
+ onChange: history.setRange
42
+ }),
43
+ history.status && /* @__PURE__ */ require_jsx_runtime.jsx("small", {
44
+ role: "status",
45
+ children: history.status
46
+ }),
47
+ /* @__PURE__ */ require_jsx_runtime.jsx(require_monitoring.MetricChart, {
48
+ metric,
49
+ samples: history.samples,
50
+ status,
51
+ scope: "pod"
52
+ })
53
+ ] });
54
+ }
29
55
  function selectedPod() {
30
56
  const match = new URLSearchParams(window.location.search).get("kube-details")?.match(/^\/api\/v1\/namespaces\/([a-z0-9][a-z0-9.-]*)\/pods\/([a-z0-9][a-z0-9.-]*)$/);
31
57
  if (match) return {
@@ -41,6 +67,7 @@ function visible(element) {
41
67
  return true;
42
68
  }
43
69
  function startPodMonitoring() {
70
+ const release = require_monitoring_collector.retainMonitoringCollector();
44
71
  const preferences = require_monitoring_preferences_store.MonitoringPreferencesStore.getInstanceOrCreate();
45
72
  let dispose;
46
73
  const stop = require__global_external_mobx.reaction(() => preferences.enabled, (enabled) => {
@@ -50,9 +77,22 @@ function startPodMonitoring() {
50
77
  return () => {
51
78
  stop();
52
79
  dispose?.();
80
+ release();
53
81
  };
54
82
  }
55
83
  function mountPodMonitoring() {
84
+ const nativeSuppression = document.createElement("style");
85
+ const controllers = ".DeploymentDetails, .ReplicaSetDetails, .DaemonSetDetails, .StatefulSetDetails, .JobDetails, .CronJobDetails, [class*=ReplicationControllerDetails]";
86
+ nativeSuppression.dataset.powerDetailMonitoring = "";
87
+ nativeSuppression.textContent = `
88
+ .KubeObjectDetails:has(:is(${controllers})) .ResourceMetrics,
89
+ .KubeObjectDetails:has(:is(${controllers})) [data-time-range]:has(+ .ResourceMetrics),
90
+ .KubeObjectDetails:has(.PodDetails) [data-time-range]:has(+ .ResourceMetrics > [data-power-pod-monitoring]),
91
+ .KubeObjectDetails .ResourceMetrics:has(> [data-power-pod-monitoring]) > .loader {
92
+ display: none !important;
93
+ }
94
+ `;
95
+ document.head.append(nativeSuppression);
56
96
  let stopped = false;
57
97
  let generation = 0;
58
98
  let session = "";
@@ -78,6 +118,58 @@ function mountPodMonitoring() {
78
118
  graph = void 0;
79
119
  renderedSamples = void 0;
80
120
  };
121
+ let controlledMetrics;
122
+ const tabAttributes = /* @__PURE__ */ new Map();
123
+ const saveAttributes = (element, names) => {
124
+ if (!tabAttributes.has(element)) tabAttributes.set(element, new Map(names.map((name) => [name, element.getAttribute(name)])));
125
+ };
126
+ const unsupportedTab = (target) => {
127
+ const radio = target instanceof Element ? target.closest(".Radio") : null;
128
+ return radio && ["Network", "Filesystem"].includes(radio.querySelector(".label")?.textContent ?? "");
129
+ };
130
+ const blockUnsupported = (event) => {
131
+ if (!unsupportedTab(event.target)) return;
132
+ event.preventDefault();
133
+ event.stopImmediatePropagation();
134
+ };
135
+ const tabEvents = [
136
+ "click",
137
+ "pointerdown",
138
+ "mousedown",
139
+ "keydown",
140
+ "change"
141
+ ];
142
+ const restoreTabs = () => {
143
+ for (const [element, attributes] of tabAttributes) for (const [name, value] of attributes) if (value === null) element.removeAttribute(name);
144
+ else element.setAttribute(name, value);
145
+ tabAttributes.clear();
146
+ for (const event of tabEvents) controlledMetrics?.removeEventListener(event, blockUnsupported, true);
147
+ controlledMetrics = void 0;
148
+ };
149
+ const restrictTabs = (metrics) => {
150
+ if (controlledMetrics !== metrics) {
151
+ restoreTabs();
152
+ controlledMetrics = metrics;
153
+ for (const event of tabEvents) metrics.addEventListener(event, blockUnsupported, true);
154
+ }
155
+ for (const radio of metrics.querySelectorAll(".Radio")) {
156
+ if (!unsupportedTab(radio)) continue;
157
+ saveAttributes(radio, [
158
+ "style",
159
+ "hidden",
160
+ "aria-hidden"
161
+ ]);
162
+ if (radio.style.display !== "none") radio.style.display = "none";
163
+ radio.hidden = true;
164
+ radio.setAttribute("aria-hidden", "true");
165
+ const input = radio.querySelector("input");
166
+ if (input) {
167
+ saveAttributes(input, ["disabled"]);
168
+ input.disabled = true;
169
+ }
170
+ }
171
+ if (unsupportedTab(metrics.querySelector(".Radio.checked"))) [...metrics.querySelectorAll(".Radio")].find((radio) => radio.querySelector(".label")?.textContent === "CPU")?.click();
172
+ };
81
173
  const reset = () => {
82
174
  generation++;
83
175
  session = "";
@@ -86,6 +178,7 @@ function mountPodMonitoring() {
86
178
  status = "";
87
179
  nextRead = 0;
88
180
  restore();
181
+ restoreTabs();
89
182
  };
90
183
  const update = async () => {
91
184
  if (stopped) return;
@@ -113,6 +206,8 @@ function mountPodMonitoring() {
113
206
  "Network",
114
207
  "Filesystem"
115
208
  ].every((label) => [...element.querySelectorAll(".Radio .label")].some((node) => node.textContent === label)));
209
+ if (metrics && visible(metrics)) restrictTabs(metrics);
210
+ else restoreTabs();
116
211
  const selected = metrics?.querySelector(".Radio.checked .label")?.textContent;
117
212
  const nativeGraph = metrics?.querySelector(":scope > .graph");
118
213
  if (!metrics || !visible(metrics) || !nativeGraph || selected !== "CPU" && selected !== "Memory") {
@@ -133,11 +228,12 @@ function mountPodMonitoring() {
133
228
  }
134
229
  const render = () => {
135
230
  if (host && (renderedSamples !== samples || renderedStatus !== status || renderedMetric !== metric)) {
136
- require__global_external_react_dom.default.render(/* @__PURE__ */ require_jsx_runtime.jsx(require_monitoring.MetricChart, {
231
+ require__global_external_react_dom.default.render(/* @__PURE__ */ require_jsx_runtime.jsx(PodMonitoringChart, {
232
+ clusterId: cluster,
233
+ uid: resourceUid,
137
234
  metric,
138
235
  samples,
139
- status,
140
- scope: "pod"
236
+ status
141
237
  }), host);
142
238
  renderedSamples = samples;
143
239
  renderedStatus = status;
@@ -151,7 +247,9 @@ function mountPodMonitoring() {
151
247
  nextRead = Date.now() + INTERVAL;
152
248
  const isCurrent = () => !stopped && current === generation && session === key && drawer.isConnected && visible(drawer) && document.visibilityState !== "hidden" && require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid === cluster && JSON.stringify(selectedPod()) === JSON.stringify(identity);
153
249
  try {
154
- const [usage, pod] = await Promise.allSettled([Promise.resolve().then(() => require_extensions.Renderer.K8sApi.podsMetricsApi.list({ namespace: identity.namespace }, { fieldSelector: `metadata.name=${identity.name}` })), Promise.resolve().then(() => require_extensions.Renderer.K8sApi.podsApi.get(identity))]);
250
+ const collected = await require_monitoring_collector.getMonitoringSnapshot();
251
+ if (!isCurrent()) return;
252
+ const [usage, pod] = await Promise.allSettled([collected.podMetrics !== void 0 ? Promise.resolve(collected.podMetrics) : Promise.resolve().then(() => require_extensions.Renderer.K8sApi.podsMetricsApi.list({ namespace: identity.namespace }, { fieldSelector: `metadata.name=${identity.name}` })), collected.pods !== void 0 ? Promise.resolve(collected.pods.find((item) => item.metadata.name === identity.name && item.metadata.namespace === identity.namespace)) : Promise.resolve().then(() => require_extensions.Renderer.K8sApi.podsApi.get(identity))]);
155
253
  if (!isCurrent()) return;
156
254
  const currentPod = pod.status === "fulfilled" && pod.value?.metadata.name === identity.name && pod.value.metadata.namespace === identity.namespace ? pod.value : void 0;
157
255
  if (currentPod) {
@@ -176,9 +274,9 @@ function mountPodMonitoring() {
176
274
  try {
177
275
  const spec = currentPod;
178
276
  sample.resources = Object.fromEntries(["cpu", "memory"].map((resource) => {
179
- const limits = require_monitoring.effectivePodResource(spec, resource, "limits");
277
+ const limits = require_monitoring_math.effectivePodResource(spec, resource, "limits");
180
278
  return [resource, {
181
- requests: require_monitoring.effectivePodResource(spec, resource, "requests"),
279
+ requests: require_monitoring_math.effectivePodResource(spec, resource, "requests"),
182
280
  limits: Number.isFinite(limits) ? limits : void 0,
183
281
  unbounded: limits === Infinity
184
282
  }];
@@ -205,6 +303,7 @@ function mountPodMonitoring() {
205
303
  stopped = true;
206
304
  reset();
207
305
  window.clearInterval(timer);
306
+ nativeSuppression.remove();
208
307
  };
209
308
  }
210
309
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"pod-monitoring.js","names":[],"sources":["../../src/renderer/pod-monitoring.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { reaction } from \"mobx\";\nimport ReactDOM from \"react-dom\";\nimport { MonitoringPreferencesStore } from \"../common/store/monitoring-preferences-store\";\nimport {\n effectivePodResource,\n MAX_SAMPLES,\n MetricChart,\n type MonitoringPod,\n type MonitoringSample,\n parseQuantity,\n} from \"./monitoring\";\n\ntype PodIdentity = { name: string; namespace: string };\ntype PodMetrics = {\n metadata: PodIdentity & { uid?: string };\n timestamp: string;\n containers: { name: string; usage: { cpu: string; memory: string } }[];\n};\nconst INTERVAL = 30_000;\n\nexport function aggregatePodMetrics(metrics: PodMetrics, identity: PodIdentity, now = Date.now()): MonitoringSample {\n if (metrics.metadata?.name !== identity.name || metrics.metadata?.namespace !== identity.namespace)\n throw new Error(\"Pod metrics identity changed\");\n const time = Date.parse(metrics.timestamp);\n if (!Number.isFinite(time) || now - time > 120_000 || time > now + 30_000) throw new Error(\"Pod metrics are stale\");\n if (!metrics.containers?.length) throw new Error(\"Missing container metrics\");\n const names = new Set<string>();\n let cpu = 0;\n let memory = 0;\n for (const container of metrics.containers) {\n if (!container.name || names.has(container.name)) throw new Error(\"Invalid container metrics\");\n names.add(container.name);\n cpu += parseQuantity(container.usage.cpu);\n memory += parseQuantity(container.usage.memory, true);\n }\n return { time, cpu, memory };\n}\n\nfunction selectedPod(): PodIdentity | undefined {\n const details = new URLSearchParams(window.location.search).get(\"kube-details\");\n const match = details?.match(/^\\/api\\/v1\\/namespaces\\/([a-z0-9][a-z0-9.-]*)\\/pods\\/([a-z0-9][a-z0-9.-]*)$/);\n if (match) return { namespace: match[1], name: match[2] };\n return;\n}\n\nfunction visible(element: HTMLElement): boolean {\n for (let node: HTMLElement | null = element; node; node = node.parentElement) {\n const style = getComputedStyle(node);\n if (\n node.hidden ||\n node.getAttribute(\"aria-hidden\") === \"true\" ||\n style.display === \"none\" ||\n style.visibility === \"hidden\"\n )\n return false;\n }\n return true;\n}\n\nexport function startPodMonitoring(): () => void {\n const preferences = MonitoringPreferencesStore.getInstanceOrCreate<MonitoringPreferencesStore>();\n let dispose: (() => void) | undefined;\n const stop = reaction(\n () => preferences.enabled,\n (enabled) => {\n dispose?.();\n dispose = enabled ? mountPodMonitoring() : undefined;\n },\n { fireImmediately: true },\n );\n return () => {\n stop();\n dispose?.();\n };\n}\n\nfunction mountPodMonitoring(): () => void {\n let stopped = false;\n let generation = 0;\n let session = \"\";\n let samples: MonitoringSample[] = [];\n let status = \"\";\n let resourceUid: string | undefined;\n let nextRead = 0;\n let pending: number | undefined;\n let host: HTMLElement | undefined;\n let graph: HTMLElement | undefined;\n let originalStyle: string | null = null;\n let renderedSamples: MonitoringSample[] | undefined;\n let renderedStatus: string | undefined;\n let renderedMetric: string | undefined;\n const restore = () => {\n if (host) {\n ReactDOM.unmountComponentAtNode(host);\n host.remove();\n }\n host = undefined;\n if (graph) {\n if (originalStyle === null) graph.removeAttribute(\"style\");\n else graph.setAttribute(\"style\", originalStyle);\n }\n graph = undefined;\n renderedSamples = undefined;\n };\n const reset = () => {\n generation++;\n session = \"\";\n samples = [];\n resourceUid = undefined;\n status = \"\";\n nextRead = 0;\n restore();\n };\n const update = async () => {\n if (stopped) return;\n const identity = selectedPod();\n const cluster = Renderer.Catalog.activeCluster.get()?.metadata.uid;\n const drawer = document.querySelector<HTMLElement>(\".KubeObjectDetails\");\n const title = drawer?.querySelector(\".drawer-title-text\");\n const titleText = title\n ? [...title.childNodes]\n .filter((node) => node.nodeType === Node.TEXT_NODE)\n .map((node) => node.textContent)\n .join(\"\")\n .trim()\n : \"\";\n if (\n !identity ||\n !cluster ||\n !drawer ||\n !visible(drawer) ||\n document.visibilityState === \"hidden\" ||\n titleText !== `Pod: ${identity.name}` ||\n !drawer.querySelector(\".PodDetails\")\n ) {\n if (session) reset();\n return;\n }\n const key = JSON.stringify([cluster, identity.namespace, identity.name]);\n if (session !== key) {\n reset();\n session = key;\n }\n const metrics = [...drawer.querySelectorAll<HTMLElement>(\".ResourceMetrics\")].find((element) =>\n [\"CPU\", \"Memory\", \"Network\", \"Filesystem\"].every((label) =>\n [...element.querySelectorAll(\".Radio .label\")].some((node) => node.textContent === label),\n ),\n );\n const selected = metrics?.querySelector(\".Radio.checked .label\")?.textContent;\n const nativeGraph = metrics?.querySelector<HTMLElement>(\":scope > .graph\");\n if (!metrics || !visible(metrics) || !nativeGraph || (selected !== \"CPU\" && selected !== \"Memory\")) {\n restore();\n return;\n }\n const retained = samples.filter((sample) => sample.time >= Date.now() - 30 * 60_000);\n if (retained.length !== samples.length) samples = retained;\n const metric = selected === \"CPU\" ? \"cpu\" : \"memory\";\n if (graph !== nativeGraph || !host?.isConnected) {\n restore();\n graph = nativeGraph;\n originalStyle = graph.getAttribute(\"style\");\n graph.style.display = \"none\";\n host = document.createElement(\"div\");\n host.dataset.powerPodMonitoring = \"\";\n graph.after(host);\n }\n const render = () => {\n if (host && (renderedSamples !== samples || renderedStatus !== status || renderedMetric !== metric)) {\n ReactDOM.render(<MetricChart metric={metric} samples={samples} status={status} scope=\"pod\" />, host);\n renderedSamples = samples;\n renderedStatus = status;\n renderedMetric = metric;\n }\n };\n render();\n if (pending === generation || Date.now() < nextRead) return;\n const current = generation;\n pending = current;\n nextRead = Date.now() + INTERVAL;\n const isCurrent = () =>\n !stopped &&\n current === generation &&\n session === key &&\n drawer.isConnected &&\n visible(drawer) &&\n document.visibilityState !== \"hidden\" &&\n Renderer.Catalog.activeCluster.get()?.metadata.uid === cluster &&\n JSON.stringify(selectedPod()) === JSON.stringify(identity);\n try {\n const [usage, pod] = await Promise.allSettled([\n Promise.resolve().then(() =>\n Renderer.K8sApi.podsMetricsApi.list(\n { namespace: identity.namespace },\n { fieldSelector: `metadata.name=${identity.name}` },\n ),\n ),\n Promise.resolve().then(() => Renderer.K8sApi.podsApi.get(identity)),\n ]);\n if (!isCurrent()) return;\n const currentPod =\n pod.status === \"fulfilled\" &&\n pod.value?.metadata.name === identity.name &&\n pod.value.metadata.namespace === identity.namespace\n ? pod.value\n : undefined;\n if (currentPod) {\n const nextUid = currentPod.metadata.uid;\n if (nextUid && resourceUid && nextUid !== resourceUid) samples = [];\n if (nextUid) resourceUid = nextUid;\n }\n if (usage.status !== \"fulfilled\" || !Array.isArray(usage.value)) throw new Error(\"Metrics unavailable\");\n const matches = usage.value.filter(\n (item) => item.metadata?.name === identity.name && item.metadata.namespace === identity.namespace,\n );\n if (matches.length !== 1) throw new Error(\"Metrics unavailable\");\n const podMetrics = matches[0];\n const sample = aggregatePodMetrics(podMetrics, identity);\n if (currentPod) {\n const created = currentPod.metadata.creationTimestamp;\n if (created !== undefined) {\n const creationTime = Date.parse(created);\n if (!Number.isFinite(creationTime)) throw new Error(\"Invalid pod creation time\");\n samples = samples.filter((entry) => entry.time >= creationTime);\n if (sample.time < creationTime) throw new Error(\"Pod metrics predate the current pod\");\n }\n if (podMetrics.metadata.uid && resourceUid && podMetrics.metadata.uid !== resourceUid)\n throw new Error(\"Pod metrics identity changed\");\n try {\n const spec: MonitoringPod = currentPod;\n sample.resources = Object.fromEntries(\n ([\"cpu\", \"memory\"] as const).map((resource) => {\n const limits = effectivePodResource(spec, resource, \"limits\");\n return [\n resource,\n {\n requests: effectivePodResource(spec, resource, \"requests\"),\n limits: Number.isFinite(limits) ? limits : undefined,\n unbounded: limits === Infinity,\n },\n ];\n }),\n ) as MonitoringSample[\"resources\"];\n } catch {\n sample.resourceStatus = \"Pod requests unavailable: incomplete resource data\";\n }\n } else sample.resourceStatus = \"Pod requests unavailable or access denied\";\n samples = [\n ...samples.filter((entry) => entry.time !== sample.time && entry.time >= Date.now() - 30 * 60_000),\n sample,\n ]\n .sort((a, b) => a.time - b.time)\n .slice(-MAX_SAMPLES);\n status = \"\";\n } catch {\n if (!isCurrent()) return;\n status = \"Pod metrics unavailable, stale or access denied. Last collected samples remain visible.\";\n } finally {\n if (pending === current) pending = undefined;\n }\n if (isCurrent()) void update();\n };\n const timer = window.setInterval(() => {\n void update();\n }, 1000);\n void update();\n return () => {\n stopped = true;\n reset();\n window.clearInterval(timer);\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pod-monitoring.js","names":[],"sources":["../../src/renderer/pod-monitoring.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { reaction } from \"mobx\";\nimport ReactDOM from \"react-dom\";\nimport { MonitoringPreferencesStore } from \"../common/store/monitoring-preferences-store\";\nimport {\n effectivePodResource,\n MAX_SAMPLES,\n MetricChart,\n type MonitoringPod,\n type MonitoringSample,\n parseQuantity,\n} from \"./monitoring\";\nimport { getMonitoringSnapshot, retainMonitoringCollector } from \"./monitoring-collector\";\nimport { MonitoringHistoryRange, useMonitoringHistory } from \"./monitoring-history-view\";\n\ntype PodIdentity = { name: string; namespace: string };\ntype PodMetrics = {\n metadata: PodIdentity & { uid?: string };\n timestamp: string;\n containers: { name: string; usage: { cpu: string; memory: string } }[];\n};\nconst INTERVAL = 30_000;\n\nexport function aggregatePodMetrics(metrics: PodMetrics, identity: PodIdentity, now = Date.now()): MonitoringSample {\n if (metrics.metadata?.name !== identity.name || metrics.metadata?.namespace !== identity.namespace)\n throw new Error(\"Pod metrics identity changed\");\n const time = Date.parse(metrics.timestamp);\n if (!Number.isFinite(time) || now - time > 120_000 || time > now + 30_000) throw new Error(\"Pod metrics are stale\");\n if (!metrics.containers?.length) throw new Error(\"Missing container metrics\");\n const names = new Set<string>();\n let cpu = 0;\n let memory = 0;\n for (const container of metrics.containers) {\n if (!container.name || names.has(container.name)) throw new Error(\"Invalid container metrics\");\n names.add(container.name);\n cpu += parseQuantity(container.usage.cpu);\n memory += parseQuantity(container.usage.memory, true);\n }\n return { time, cpu, memory };\n}\n\nfunction PodMonitoringChart({\n clusterId,\n uid,\n samples,\n status,\n metric,\n}: {\n clusterId: string;\n uid?: string;\n samples: MonitoringSample[];\n status: string;\n metric: \"cpu\" | \"memory\";\n}) {\n const history = useMonitoringHistory(uid ? { clusterId, scope: \"pod\", uid } : undefined, samples);\n return (\n <>\n <MonitoringHistoryRange range={history.range} onChange={history.setRange} />\n {history.status && <small role=\"status\">{history.status}</small>}\n <MetricChart metric={metric} samples={history.samples} status={status} scope=\"pod\" />\n </>\n );\n}\n\nfunction selectedPod(): PodIdentity | undefined {\n const details = new URLSearchParams(window.location.search).get(\"kube-details\");\n const match = details?.match(/^\\/api\\/v1\\/namespaces\\/([a-z0-9][a-z0-9.-]*)\\/pods\\/([a-z0-9][a-z0-9.-]*)$/);\n if (match) return { namespace: match[1], name: match[2] };\n return;\n}\n\nfunction visible(element: HTMLElement): boolean {\n for (let node: HTMLElement | null = element; node; node = node.parentElement) {\n const style = getComputedStyle(node);\n if (\n node.hidden ||\n node.getAttribute(\"aria-hidden\") === \"true\" ||\n style.display === \"none\" ||\n style.visibility === \"hidden\"\n )\n return false;\n }\n return true;\n}\n\nexport function startPodMonitoring(): () => void {\n const release = retainMonitoringCollector();\n const preferences = MonitoringPreferencesStore.getInstanceOrCreate<MonitoringPreferencesStore>();\n let dispose: (() => void) | undefined;\n const stop = reaction(\n () => preferences.enabled,\n (enabled) => {\n dispose?.();\n dispose = enabled ? mountPodMonitoring() : undefined;\n },\n { fireImmediately: true },\n );\n return () => {\n stop();\n dispose?.();\n release();\n };\n}\n\nfunction mountPodMonitoring(): () => void {\n const nativeSuppression = document.createElement(\"style\");\n const controllers =\n \".DeploymentDetails, .ReplicaSetDetails, .DaemonSetDetails, .StatefulSetDetails, .JobDetails, .CronJobDetails, [class*=ReplicationControllerDetails]\";\n nativeSuppression.dataset.powerDetailMonitoring = \"\";\n nativeSuppression.textContent = `\n .KubeObjectDetails:has(:is(${controllers})) .ResourceMetrics,\n .KubeObjectDetails:has(:is(${controllers})) [data-time-range]:has(+ .ResourceMetrics),\n .KubeObjectDetails:has(.PodDetails) [data-time-range]:has(+ .ResourceMetrics > [data-power-pod-monitoring]),\n .KubeObjectDetails .ResourceMetrics:has(> [data-power-pod-monitoring]) > .loader {\n display: none !important;\n }\n `;\n document.head.append(nativeSuppression);\n let stopped = false;\n let generation = 0;\n let session = \"\";\n let samples: MonitoringSample[] = [];\n let status = \"\";\n let resourceUid: string | undefined;\n let nextRead = 0;\n let pending: number | undefined;\n let host: HTMLElement | undefined;\n let graph: HTMLElement | undefined;\n let originalStyle: string | null = null;\n let renderedSamples: MonitoringSample[] | undefined;\n let renderedStatus: string | undefined;\n let renderedMetric: string | undefined;\n const restore = () => {\n if (host) {\n ReactDOM.unmountComponentAtNode(host);\n host.remove();\n }\n host = undefined;\n if (graph) {\n if (originalStyle === null) graph.removeAttribute(\"style\");\n else graph.setAttribute(\"style\", originalStyle);\n }\n graph = undefined;\n renderedSamples = undefined;\n };\n let controlledMetrics: HTMLElement | undefined;\n const tabAttributes = new Map<HTMLElement, Map<string, string | null>>();\n const saveAttributes = (element: HTMLElement, names: string[]) => {\n if (!tabAttributes.has(element))\n tabAttributes.set(element, new Map(names.map((name) => [name, element.getAttribute(name)])));\n };\n const unsupportedTab = (target: EventTarget | null) => {\n const radio = target instanceof Element ? target.closest(\".Radio\") : null;\n return radio && [\"Network\", \"Filesystem\"].includes(radio.querySelector(\".label\")?.textContent ?? \"\");\n };\n const blockUnsupported = (event: Event) => {\n if (!unsupportedTab(event.target)) return;\n event.preventDefault();\n event.stopImmediatePropagation();\n };\n const tabEvents = [\"click\", \"pointerdown\", \"mousedown\", \"keydown\", \"change\"];\n const restoreTabs = () => {\n for (const [element, attributes] of tabAttributes) {\n for (const [name, value] of attributes) {\n if (value === null) element.removeAttribute(name);\n else element.setAttribute(name, value);\n }\n }\n tabAttributes.clear();\n for (const event of tabEvents) controlledMetrics?.removeEventListener(event, blockUnsupported, true);\n controlledMetrics = undefined;\n };\n const restrictTabs = (metrics: HTMLElement) => {\n if (controlledMetrics !== metrics) {\n restoreTabs();\n controlledMetrics = metrics;\n for (const event of tabEvents) metrics.addEventListener(event, blockUnsupported, true);\n }\n for (const radio of metrics.querySelectorAll<HTMLElement>(\".Radio\")) {\n if (!unsupportedTab(radio)) continue;\n saveAttributes(radio, [\"style\", \"hidden\", \"aria-hidden\"]);\n if (radio.style.display !== \"none\") radio.style.display = \"none\";\n radio.hidden = true;\n radio.setAttribute(\"aria-hidden\", \"true\");\n const input = radio.querySelector<HTMLInputElement>(\"input\");\n if (input) {\n saveAttributes(input, [\"disabled\"]);\n input.disabled = true;\n }\n }\n if (unsupportedTab(metrics.querySelector(\".Radio.checked\"))) {\n const cpu = [...metrics.querySelectorAll<HTMLElement>(\".Radio\")].find(\n (radio) => radio.querySelector(\".label\")?.textContent === \"CPU\",\n );\n cpu?.click();\n }\n };\n const reset = () => {\n generation++;\n session = \"\";\n samples = [];\n resourceUid = undefined;\n status = \"\";\n nextRead = 0;\n restore();\n restoreTabs();\n };\n const update = async () => {\n if (stopped) return;\n const identity = selectedPod();\n const cluster = Renderer.Catalog.activeCluster.get()?.metadata.uid;\n const drawer = document.querySelector<HTMLElement>(\".KubeObjectDetails\");\n const title = drawer?.querySelector(\".drawer-title-text\");\n const titleText = title\n ? [...title.childNodes]\n .filter((node) => node.nodeType === Node.TEXT_NODE)\n .map((node) => node.textContent)\n .join(\"\")\n .trim()\n : \"\";\n if (\n !identity ||\n !cluster ||\n !drawer ||\n !visible(drawer) ||\n document.visibilityState === \"hidden\" ||\n titleText !== `Pod: ${identity.name}` ||\n !drawer.querySelector(\".PodDetails\")\n ) {\n if (session) reset();\n return;\n }\n const key = JSON.stringify([cluster, identity.namespace, identity.name]);\n if (session !== key) {\n reset();\n session = key;\n }\n const metrics = [...drawer.querySelectorAll<HTMLElement>(\".ResourceMetrics\")].find((element) =>\n [\"CPU\", \"Memory\", \"Network\", \"Filesystem\"].every((label) =>\n [...element.querySelectorAll(\".Radio .label\")].some((node) => node.textContent === label),\n ),\n );\n if (metrics && visible(metrics)) restrictTabs(metrics);\n else restoreTabs();\n const selected = metrics?.querySelector(\".Radio.checked .label\")?.textContent;\n const nativeGraph = metrics?.querySelector<HTMLElement>(\":scope > .graph\");\n if (!metrics || !visible(metrics) || !nativeGraph || (selected !== \"CPU\" && selected !== \"Memory\")) {\n restore();\n return;\n }\n const retained = samples.filter((sample) => sample.time >= Date.now() - 30 * 60_000);\n if (retained.length !== samples.length) samples = retained;\n const metric = selected === \"CPU\" ? \"cpu\" : \"memory\";\n if (graph !== nativeGraph || !host?.isConnected) {\n restore();\n graph = nativeGraph;\n originalStyle = graph.getAttribute(\"style\");\n graph.style.display = \"none\";\n host = document.createElement(\"div\");\n host.dataset.powerPodMonitoring = \"\";\n graph.after(host);\n }\n const render = () => {\n if (host && (renderedSamples !== samples || renderedStatus !== status || renderedMetric !== metric)) {\n ReactDOM.render(\n <PodMonitoringChart\n clusterId={cluster}\n uid={resourceUid}\n metric={metric}\n samples={samples}\n status={status}\n />,\n host,\n );\n renderedSamples = samples;\n renderedStatus = status;\n renderedMetric = metric;\n }\n };\n render();\n if (pending === generation || Date.now() < nextRead) return;\n const current = generation;\n pending = current;\n nextRead = Date.now() + INTERVAL;\n const isCurrent = () =>\n !stopped &&\n current === generation &&\n session === key &&\n drawer.isConnected &&\n visible(drawer) &&\n document.visibilityState !== \"hidden\" &&\n Renderer.Catalog.activeCluster.get()?.metadata.uid === cluster &&\n JSON.stringify(selectedPod()) === JSON.stringify(identity);\n try {\n const collected = await getMonitoringSnapshot();\n if (!isCurrent()) return;\n const [usage, pod] = await Promise.allSettled([\n collected.podMetrics !== undefined\n ? Promise.resolve(collected.podMetrics)\n : Promise.resolve().then(() =>\n Renderer.K8sApi.podsMetricsApi.list(\n { namespace: identity.namespace },\n { fieldSelector: `metadata.name=${identity.name}` },\n ),\n ),\n collected.pods !== undefined\n ? Promise.resolve(\n collected.pods.find(\n (item) => item.metadata.name === identity.name && item.metadata.namespace === identity.namespace,\n ),\n )\n : Promise.resolve().then(() => Renderer.K8sApi.podsApi.get(identity)),\n ]);\n if (!isCurrent()) return;\n const currentPod =\n pod.status === \"fulfilled\" &&\n pod.value?.metadata.name === identity.name &&\n pod.value.metadata.namespace === identity.namespace\n ? pod.value\n : undefined;\n if (currentPod) {\n const nextUid = currentPod.metadata.uid;\n if (nextUid && resourceUid && nextUid !== resourceUid) samples = [];\n if (nextUid) resourceUid = nextUid;\n }\n if (usage.status !== \"fulfilled\" || !Array.isArray(usage.value)) throw new Error(\"Metrics unavailable\");\n const matches = usage.value.filter(\n (item) => item.metadata?.name === identity.name && item.metadata.namespace === identity.namespace,\n );\n if (matches.length !== 1) throw new Error(\"Metrics unavailable\");\n const podMetrics = matches[0];\n const sample = aggregatePodMetrics(podMetrics, identity);\n if (currentPod) {\n const created = currentPod.metadata.creationTimestamp;\n if (created !== undefined) {\n const creationTime = Date.parse(created);\n if (!Number.isFinite(creationTime)) throw new Error(\"Invalid pod creation time\");\n samples = samples.filter((entry) => entry.time >= creationTime);\n if (sample.time < creationTime) throw new Error(\"Pod metrics predate the current pod\");\n }\n if (podMetrics.metadata.uid && resourceUid && podMetrics.metadata.uid !== resourceUid)\n throw new Error(\"Pod metrics identity changed\");\n try {\n const spec: MonitoringPod = currentPod;\n sample.resources = Object.fromEntries(\n ([\"cpu\", \"memory\"] as const).map((resource) => {\n const limits = effectivePodResource(spec, resource, \"limits\");\n return [\n resource,\n {\n requests: effectivePodResource(spec, resource, \"requests\"),\n limits: Number.isFinite(limits) ? limits : undefined,\n unbounded: limits === Infinity,\n },\n ];\n }),\n ) as MonitoringSample[\"resources\"];\n } catch {\n sample.resourceStatus = \"Pod requests unavailable: incomplete resource data\";\n }\n } else sample.resourceStatus = \"Pod requests unavailable or access denied\";\n samples = [\n ...samples.filter((entry) => entry.time !== sample.time && entry.time >= Date.now() - 30 * 60_000),\n sample,\n ]\n .sort((a, b) => a.time - b.time)\n .slice(-MAX_SAMPLES);\n status = \"\";\n } catch {\n if (!isCurrent()) return;\n status = \"Pod metrics unavailable, stale or access denied. Last collected samples remain visible.\";\n } finally {\n if (pending === current) pending = undefined;\n }\n if (isCurrent()) void update();\n };\n const timer = window.setInterval(() => {\n void update();\n }, 1000);\n void update();\n return () => {\n stopped = true;\n reset();\n window.clearInterval(timer);\n nativeSuppression.remove();\n };\n}\n"],"mappings":"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"}
@@ -1,20 +1,65 @@
1
1
  const require_extensions = require("../_virtual/_global-external_@freelensapp/extensions.js");
2
2
  const require__global_external_mobx_react = require("../_virtual/_global-external_mobx-react.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_jsx_runtime = require("../_virtual/_global-external_react/jsx-runtime.js");
6
+ const require_monitoring_history = require("../monitoring-history.js");
5
7
  //#region src/renderer/preferences/monitoring-preferences.tsx
6
8
  var MonitoringPreferences = require__global_external_mobx_react.observer(() => {
7
9
  const preferences = require_monitoring_preferences_store.MonitoringPreferencesStore.getInstanceOrCreate();
8
- return /* @__PURE__ */ require_jsx_runtime.jsxs("div", { children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_extensions.Renderer.Component.Switch, {
9
- checked: preferences.enabled,
10
- onChange: (value) => {
11
- preferences.enabled = value;
12
- },
13
- children: "Metrics-server overview"
14
- }), /* @__PURE__ */ require_jsx_runtime.jsx("div", {
15
- className: "power-settings-hint",
16
- children: "Show CPU and memory usage from metrics-server. Samples are kept in memory while this overview is visible, for up to 30 minutes. Requires permission to read node metrics. No historical data is available before collection."
17
- })] });
10
+ const [status, setStatus] = require__global_external_react.useState();
11
+ const refresh = () => {
12
+ require_monitoring_history.monitoringHistory.status().then(setStatus).catch(() => setStatus({
13
+ available: false,
14
+ enabled: preferences.historyEnabled,
15
+ bytes: 0,
16
+ samples: 0,
17
+ error: "History service unavailable"
18
+ }));
19
+ };
20
+ require__global_external_react.useEffect(refresh, [preferences.historyEnabled]);
21
+ return /* @__PURE__ */ require_jsx_runtime.jsxs("div", { children: [
22
+ /* @__PURE__ */ require_jsx_runtime.jsx(require_extensions.Renderer.Component.Switch, {
23
+ checked: preferences.enabled,
24
+ onChange: (value) => {
25
+ preferences.enabled = value;
26
+ },
27
+ children: "Metrics-server monitoring"
28
+ }),
29
+ /* @__PURE__ */ require_jsx_runtime.jsx("div", {
30
+ className: "power-settings-hint",
31
+ children: "Show cluster, node and pod CPU/memory charts from metrics-server. Live samples are kept for 30 minutes. Requires permission to read the corresponding resource metrics. Requests and capacity use Kubernetes resource data."
32
+ }),
33
+ /* @__PURE__ */ require_jsx_runtime.jsx(require_extensions.Renderer.Component.Switch, {
34
+ checked: preferences.historyEnabled,
35
+ onChange: (value) => {
36
+ preferences.historyEnabled = value;
37
+ },
38
+ children: "Keep local numeric monitoring history"
39
+ }),
40
+ /* @__PURE__ */ require_jsx_runtime.jsx("div", {
41
+ className: "power-settings-hint",
42
+ children: "Opt-in local SQLite history. Retained for up to 7 days within 100 MiB. Collection runs while monitoring is enabled and the cluster view is active. Track individual pods or workloads in Diagnostics. Resolution: 30 seconds for 2 hours, 5 minutes for 24 hours, 30 minutes for 7 days. No data is backfilled. Disabling collection preserves existing history until cleared."
43
+ }),
44
+ /* @__PURE__ */ require_jsx_runtime.jsx("div", {
45
+ role: "status",
46
+ children: status?.error ?? (status ? `${(status.bytes / 1024 / 1024).toFixed(2)} MiB, ${status.samples} stored metric buckets` : "Checking history storage…")
47
+ }),
48
+ /* @__PURE__ */ require_jsx_runtime.jsx("button", {
49
+ type: "button",
50
+ title: "Delete locally collected metric history without changing other settings",
51
+ onClick: () => {
52
+ require_monitoring_history.monitoringHistory.clear().then(refresh).catch(() => setStatus({
53
+ available: false,
54
+ enabled: preferences.historyEnabled,
55
+ bytes: 0,
56
+ samples: 0,
57
+ error: "Unable to clear local history"
58
+ }));
59
+ },
60
+ children: "Clear local history"
61
+ })
62
+ ] });
18
63
  });
19
64
  //#endregion
20
65
  exports.MonitoringPreferences = MonitoringPreferences;
@@ -1 +1 @@
1
- {"version":3,"file":"monitoring-preferences.js","names":[],"sources":["../../../src/renderer/preferences/monitoring-preferences.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { observer } from \"mobx-react\";\nimport { MonitoringPreferencesStore } from \"../../common/store/monitoring-preferences-store\";\n\nexport const MonitoringPreferences = observer(() => {\n const preferences = MonitoringPreferencesStore.getInstanceOrCreate<MonitoringPreferencesStore>();\n return (\n <div>\n <Renderer.Component.Switch\n checked={preferences.enabled}\n onChange={(value) => {\n preferences.enabled = value;\n }}\n >\n Metrics-server overview\n </Renderer.Component.Switch>\n <div className=\"power-settings-hint\">\n Show CPU and memory usage from metrics-server. Samples are kept in memory while this overview is visible, for up\n to 30 minutes. Requires permission to read node metrics. No historical data is available before collection.\n </div>\n </div>\n );\n});\n"],"mappings":";;;;;AAIA,IAAa,wBAAwB,oCAAA,eAAe;CAClD,MAAM,cAAc,qCAAA,2BAA2B,oBAAgD;CAC/F,OACE,oCAAA,KAAC,OAAD,EAAA,UAAA,CACE,oCAAA,IAAC,mBAAA,SAAS,UAAU,QAApB;EACE,SAAS,YAAY;EACrB,WAAW,UAAU;GACnB,YAAY,UAAU;EACxB;YACD;CAE0B,CAAA,GAC3B,oCAAA,IAAC,OAAD;EAAK,WAAU;YAAsB;CAGhC,CAAA,CACF,EAAA,CAAA;AAET,CAAC"}
1
+ {"version":3,"file":"monitoring-preferences.js","names":[],"sources":["../../../src/renderer/preferences/monitoring-preferences.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { observer } from \"mobx-react\";\nimport { useEffect, useState } from \"react\";\nimport { type HistoryStatus } from \"../../common/monitoring-history\";\nimport { MonitoringPreferencesStore } from \"../../common/store/monitoring-preferences-store\";\nimport { monitoringHistory } from \"../monitoring-history\";\n\nexport const MonitoringPreferences = observer(() => {\n const preferences = MonitoringPreferencesStore.getInstanceOrCreate<MonitoringPreferencesStore>();\n const [status, setStatus] = useState<HistoryStatus>();\n const refresh = () => {\n void monitoringHistory\n .status()\n .then(setStatus)\n .catch(() =>\n setStatus({\n available: false,\n enabled: preferences.historyEnabled,\n bytes: 0,\n samples: 0,\n error: \"History service unavailable\",\n }),\n );\n };\n useEffect(refresh, [preferences.historyEnabled]);\n return (\n <div>\n <Renderer.Component.Switch\n checked={preferences.enabled}\n onChange={(value) => {\n preferences.enabled = value;\n }}\n >\n Metrics-server monitoring\n </Renderer.Component.Switch>\n <div className=\"power-settings-hint\">\n Show cluster, node and pod CPU/memory charts from metrics-server. Live samples are kept for 30 minutes. Requires\n permission to read the corresponding resource metrics. Requests and capacity use Kubernetes resource data.\n </div>\n <Renderer.Component.Switch\n checked={preferences.historyEnabled}\n onChange={(value) => {\n preferences.historyEnabled = value;\n }}\n >\n Keep local numeric monitoring history\n </Renderer.Component.Switch>\n <div className=\"power-settings-hint\">\n Opt-in local SQLite history. Retained for up to 7 days within 100 MiB. Collection runs while monitoring is\n enabled and the cluster view is active. Track individual pods or workloads in Diagnostics. Resolution: 30\n seconds for 2 hours, 5 minutes for 24 hours, 30 minutes for 7 days. No data is backfilled. Disabling collection\n preserves existing history until cleared.\n </div>\n <div role=\"status\">\n {status?.error ??\n (status\n ? `${(status.bytes / 1024 / 1024).toFixed(2)} MiB, ${status.samples} stored metric buckets`\n : \"Checking history storage…\")}\n </div>\n <button\n type=\"button\"\n title=\"Delete locally collected metric history without changing other settings\"\n onClick={() => {\n void monitoringHistory\n .clear()\n .then(refresh)\n .catch(() =>\n setStatus({\n available: false,\n enabled: preferences.historyEnabled,\n bytes: 0,\n samples: 0,\n error: \"Unable to clear local history\",\n }),\n );\n }}\n >\n Clear local history\n </button>\n </div>\n );\n});\n"],"mappings":";;;;;;;AAOA,IAAa,wBAAwB,oCAAA,eAAe;CAClD,MAAM,cAAc,qCAAA,2BAA2B,oBAAgD;CAC/F,MAAM,CAAC,QAAQ,aAAa,+BAAA,SAAwB;CACpD,MAAM,gBAAgB;EACpB,2BAAK,kBACF,OAAO,CAAC,CACR,KAAK,SAAS,CAAC,CACf,YACC,UAAU;GACR,WAAW;GACX,SAAS,YAAY;GACrB,OAAO;GACP,SAAS;GACT,OAAO;EACT,CAAC,CACH;CACJ;CACA,+BAAA,UAAU,SAAS,CAAC,YAAY,cAAc,CAAC;CAC/C,OACE,oCAAA,KAAC,OAAD,EAAA,UAAA;EACE,oCAAA,IAAC,mBAAA,SAAS,UAAU,QAApB;GACE,SAAS,YAAY;GACrB,WAAW,UAAU;IACnB,YAAY,UAAU;GACxB;aACD;EAE0B,CAAA;EAC3B,oCAAA,IAAC,OAAD;GAAK,WAAU;aAAsB;EAGhC,CAAA;EACL,oCAAA,IAAC,mBAAA,SAAS,UAAU,QAApB;GACE,SAAS,YAAY;GACrB,WAAW,UAAU;IACnB,YAAY,iBAAiB;GAC/B;aACD;EAE0B,CAAA;EAC3B,oCAAA,IAAC,OAAD;GAAK,WAAU;aAAsB;EAKhC,CAAA;EACL,oCAAA,IAAC,OAAD;GAAK,MAAK;aACP,QAAQ,UACN,SACG,IAAI,OAAO,QAAQ,OAAO,KAAA,CAAM,QAAQ,CAAC,EAAE,QAAQ,OAAO,QAAQ,0BAClE;EACH,CAAA;EACL,oCAAA,IAAC,UAAD;GACE,MAAK;GACL,OAAM;GACN,eAAe;IACb,2BAAK,kBACF,MAAM,CAAC,CACP,KAAK,OAAO,CAAC,CACb,YACC,UAAU;KACR,WAAW;KACX,SAAS,YAAY;KACrB,OAAO;KACP,SAAS;KACT,OAAO;IACT,CAAC,CACH;GACJ;aACD;EAEO,CAAA;CACL,EAAA,CAAA;AAET,CAAC"}
@@ -0,0 +1,15 @@
1
+ //#region src/common/monitoring-history.ts
2
+ var MONITORING_HISTORY_CHANNEL = "freelens-power:monitoring-history";
3
+ function validHistoryIdentity(value) {
4
+ return !!value && [value.clusterId, value.uid].every((id) => typeof id === "string" && /^[a-zA-Z0-9_.:-]{1,128}$/.test(id)) && [
5
+ "cluster",
6
+ "node",
7
+ "pod",
8
+ "workload"
9
+ ].includes(value.scope);
10
+ }
11
+ //#endregion
12
+ exports.MONITORING_HISTORY_CHANNEL = MONITORING_HISTORY_CHANNEL;
13
+ exports.validHistoryIdentity = validHistoryIdentity;
14
+
15
+ //# sourceMappingURL=monitoring-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitoring-history.js","names":[],"sources":["../../../../src/common/monitoring-history.ts"],"sourcesContent":["export const MONITORING_HISTORY_CHANNEL = \"freelens-power:monitoring-history\";\nexport const HISTORY_RETENTION = 7 * 24 * 60 * 60 * 1000;\nexport const HISTORY_MAX_BYTES = 100 * 1024 * 1024;\nexport type HistoryIdentity = {\n clusterId: string;\n scope: \"cluster\" | \"node\" | \"pod\" | \"workload\";\n uid: string;\n};\nexport const HISTORY_METRICS = [\n \"cpu\",\n \"memory\",\n \"cpuRequests\",\n \"memoryRequests\",\n \"cpuLimits\",\n \"memoryLimits\",\n \"cpuCapacity\",\n \"memoryCapacity\",\n \"cpuAllocatable\",\n \"memoryAllocatable\",\n \"restarts\",\n] as const;\nexport type HistoryMetric = (typeof HISTORY_METRICS)[number];\nexport type HistorySample = HistoryIdentity & { time: number; values: Partial<Record<HistoryMetric, number>> };\nexport type HistoryQuery = HistoryIdentity & { from: number; to: number };\nexport type HistoryPoint = {\n time: number;\n resolution: number;\n metric: HistoryMetric;\n min: number;\n avg: number;\n max: number;\n count: number;\n gap: boolean;\n};\nexport type HistoryStatus = { available: boolean; enabled: boolean; bytes: number; samples: number; error?: string };\nexport type HistoryRequest =\n | { operation: \"status\" | \"clear\" }\n | { operation: \"append\"; samples: HistorySample[] }\n | { operation: \"query\"; query: HistoryQuery };\nexport function validHistoryIdentity(value: HistoryIdentity): boolean {\n return (\n !!value &&\n [value.clusterId, value.uid].every((id) => typeof id === \"string\" && /^[a-zA-Z0-9_.:-]{1,128}$/.test(id)) &&\n [\"cluster\", \"node\", \"pod\", \"workload\"].includes(value.scope)\n );\n}\nexport function projectHistorySamples(input: unknown, now = Date.now()): HistorySample[] {\n if (!Array.isArray(input) || input.length > 512) throw new Error(\"Invalid history samples\");\n return input.map((sample) => {\n if (\n !validHistoryIdentity(sample) ||\n !Number.isSafeInteger(sample.time) ||\n sample.time < now - HISTORY_RETENTION ||\n sample.time > now + 30_000 ||\n !sample.values ||\n typeof sample.values !== \"object\"\n )\n throw new Error(\"Invalid history sample\");\n const values: HistorySample[\"values\"] = {};\n for (const metric of HISTORY_METRICS) {\n const value = sample.values[metric];\n if (value === undefined) continue;\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0 || value > 1e30)\n throw new Error(\"Invalid history value\");\n values[metric] = value;\n }\n return { clusterId: sample.clusterId, scope: sample.scope, uid: sample.uid, time: sample.time, values };\n });\n}\n"],"mappings":";AAAA,IAAa,6BAA6B;AAuC1C,SAAgB,qBAAqB,OAAiC;CACpE,OACE,CAAC,CAAC,SACF,CAAC,MAAM,WAAW,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,OAAO,OAAO,YAAY,2BAA2B,KAAK,EAAE,CAAC,KACxG;EAAC;EAAW;EAAQ;EAAO;CAAU,CAAC,CAAC,SAAS,MAAM,KAAK;AAE/D"}