@64mb/freelens-power 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/out/main/common/dashboard-picker.js +4 -4
- package/out/main/common/dashboard-picker.js.map +1 -1
- package/out/main/common/dashboard-readiness.js +17 -47
- package/out/main/common/dashboard-readiness.js.map +1 -1
- package/out/main/common/dashboard-selector.js +40 -6
- package/out/main/common/dashboard-selector.js.map +1 -1
- package/out/main/common/store/monitoring-preferences-store.js +5 -0
- package/out/main/common/store/monitoring-preferences-store.js.map +1 -1
- package/out/main/dashboard-picker.js +1 -4
- package/out/main/dashboard-picker.js.map +1 -1
- package/out/main/dashboard-preload.js +110 -0
- package/out/main/dashboard-preload.js.map +1 -0
- package/out/main/dashboard-views.js +207 -62
- package/out/main/dashboard-views.js.map +1 -1
- package/out/main/dock-icon.js +25 -10
- package/out/main/dock-icon.js.map +1 -1
- package/out/main/index.js +2 -0
- package/out/main/index.js.map +1 -1
- package/out/main/splash-accent.js +84 -0
- package/out/main/splash-accent.js.map +1 -0
- package/out/renderer/ai-diagnostic-handoff.js +52 -0
- package/out/renderer/ai-diagnostic-handoff.js.map +1 -0
- package/out/renderer/ai-extras.js +40 -4
- package/out/renderer/ai-extras.js.map +1 -1
- package/out/renderer/ai.js +48 -56
- package/out/renderer/ai.js.map +1 -1
- package/out/renderer/appearance.js +6 -2
- package/out/renderer/appearance.js.map +1 -1
- package/out/renderer/dashboard.js +26 -6
- package/out/renderer/dashboard.js.map +1 -1
- package/out/renderer/details/hpa-diagnostics-details.js +46 -0
- package/out/renderer/details/hpa-diagnostics-details.js.map +1 -0
- package/out/renderer/details/namespace-health-details.js +91 -0
- package/out/renderer/details/namespace-health-details.js.map +1 -0
- package/out/renderer/details/resource-health-details.js +131 -0
- package/out/renderer/details/resource-health-details.js.map +1 -0
- package/out/renderer/details/service-diagnostics-details.js +163 -0
- package/out/renderer/details/service-diagnostics-details.js.map +1 -0
- package/out/renderer/diagnostic-explain-button.js +16 -0
- package/out/renderer/diagnostic-explain-button.js.map +1 -0
- package/out/renderer/diagnostics-state.js +51 -0
- package/out/renderer/diagnostics-state.js.map +1 -0
- package/out/renderer/diagnostics.css.js +9 -0
- package/out/renderer/diagnostics.css.js.map +1 -1
- package/out/renderer/diagnostics.js +369 -73
- package/out/renderer/diagnostics.js.map +1 -1
- package/out/renderer/incident-copy-button.js +50 -0
- package/out/renderer/incident-copy-button.js.map +1 -0
- package/out/renderer/index.js +91 -19
- package/out/renderer/index.js.map +1 -1
- package/out/renderer/monitoring-history-collection.js +1 -1
- package/out/renderer/preferences/dashboard-preferences.js +16 -16
- package/out/renderer/preferences/dashboard-preferences.js.map +1 -1
- package/out/renderer/preferences/monitoring-preferences.js +10 -0
- package/out/renderer/preferences/monitoring-preferences.js.map +1 -1
- package/out/renderer/preferences/preferences.js +88 -0
- package/out/renderer/preferences/preferences.js.map +1 -1
- package/out/renderer/preferences/settings-modal.js.map +1 -1
- package/out/renderer/resource-relationships.css.js +7 -0
- package/out/renderer/resource-relationships.css.js.map +1 -0
- package/out/renderer/resource-relationships.js +556 -0
- package/out/renderer/resource-relationships.js.map +1 -0
- package/out/renderer/resource-search-distance.js +24 -0
- package/out/renderer/resource-search-distance.js.map +1 -0
- package/out/renderer/resource-search-fields.js +55 -0
- package/out/renderer/resource-search-fields.js.map +1 -0
- package/out/renderer/resource-search-model.js +193 -0
- package/out/renderer/resource-search-model.js.map +1 -0
- package/out/renderer/resource-search.js +365 -0
- package/out/renderer/resource-search.js.map +1 -0
- package/out/renderer/src/common/dashboard-picker.js.map +1 -1
- package/out/renderer/src/common/dashboard-selector.js.map +1 -1
- package/out/renderer/src/common/hpa-diagnostics.js +24 -0
- package/out/renderer/src/common/hpa-diagnostics.js.map +1 -0
- package/out/renderer/src/common/incident-summary.js +73 -0
- package/out/renderer/src/common/incident-summary.js.map +1 -0
- package/out/renderer/src/common/resource-health.js +92 -0
- package/out/renderer/src/common/resource-health.js.map +1 -0
- package/out/renderer/src/common/resource-relationships.js +155 -0
- package/out/renderer/src/common/resource-relationships.js.map +1 -0
- package/out/renderer/src/common/service-diagnostics.js +50 -0
- package/out/renderer/src/common/service-diagnostics.js.map +1 -0
- package/out/renderer/src/common/store/monitoring-preferences-store.js +5 -0
- package/out/renderer/src/common/store/monitoring-preferences-store.js.map +1 -1
- package/out/renderer/src/common/timeline-groups.js +36 -0
- package/out/renderer/src/common/timeline-groups.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
const require_extensions = require("./_virtual/_global-external_@freelensapp/extensions.js");
|
|
2
|
+
const require__global_external_mobx = require("./_virtual/_global-external_mobx.js");
|
|
3
|
+
const require__global_external_react = require("./_virtual/_global-external_react.js");
|
|
4
|
+
const require__global_external_react_dom = require("./_virtual/_global-external_react-dom.js");
|
|
5
|
+
const require_jsx_runtime = require("./_virtual/_global-external_react/jsx-runtime.js");
|
|
6
|
+
const require_ai_icon = require("./ai-icon.js");
|
|
7
|
+
const require_ai_diagnostic_handoff = require("./ai-diagnostic-handoff.js");
|
|
8
|
+
const require_diagnostic_explain_button = require("./diagnostic-explain-button.js");
|
|
9
|
+
const require_resource_search_fields = require("./resource-search-fields.js");
|
|
10
|
+
const require_resource_search_model = require("./resource-search-model.js");
|
|
11
|
+
//#region src/renderer/resource-search.tsx
|
|
12
|
+
var OPEN = "power-resource-search";
|
|
13
|
+
var activeId = () => require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid;
|
|
14
|
+
var inventory = () => [
|
|
15
|
+
[
|
|
16
|
+
"Pod",
|
|
17
|
+
require_extensions.Renderer.K8sApi.podsStore,
|
|
18
|
+
"view_in_ar"
|
|
19
|
+
],
|
|
20
|
+
[
|
|
21
|
+
"Node",
|
|
22
|
+
require_extensions.Renderer.K8sApi.nodesStore,
|
|
23
|
+
"dns"
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
"Deployment",
|
|
27
|
+
require_extensions.Renderer.K8sApi.deploymentStore,
|
|
28
|
+
"layers"
|
|
29
|
+
],
|
|
30
|
+
[
|
|
31
|
+
"StatefulSet",
|
|
32
|
+
require_extensions.Renderer.K8sApi.statefulSetStore,
|
|
33
|
+
"layers"
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"DaemonSet",
|
|
37
|
+
require_extensions.Renderer.K8sApi.daemonSetStore,
|
|
38
|
+
"layers"
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"ReplicaSet",
|
|
42
|
+
require_extensions.Renderer.K8sApi.replicaSetStore,
|
|
43
|
+
"layers"
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
"Job",
|
|
47
|
+
require_extensions.Renderer.K8sApi.jobStore,
|
|
48
|
+
"work"
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
"CronJob",
|
|
52
|
+
require_extensions.Renderer.K8sApi.cronJobStore,
|
|
53
|
+
"schedule"
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
"Service",
|
|
57
|
+
require_extensions.Renderer.K8sApi.serviceStore,
|
|
58
|
+
"settings_ethernet"
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"Ingress",
|
|
62
|
+
require_extensions.Renderer.K8sApi.ingressStore,
|
|
63
|
+
"alt_route"
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"Namespace",
|
|
67
|
+
require_extensions.Renderer.K8sApi.namespaceStore,
|
|
68
|
+
"folder"
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
"HorizontalPodAutoscaler",
|
|
72
|
+
require_extensions.Renderer.K8sApi.hpaStore,
|
|
73
|
+
"swap_vert"
|
|
74
|
+
]
|
|
75
|
+
];
|
|
76
|
+
var icons = new Map(inventory().map(([kind, , icon]) => [kind, icon]));
|
|
77
|
+
function project(kind, objects) {
|
|
78
|
+
return objects.slice(0, 500).flatMap((object) => object.metadata.name ? [{
|
|
79
|
+
kind,
|
|
80
|
+
name: object.metadata.name,
|
|
81
|
+
namespace: object.metadata.namespace,
|
|
82
|
+
labels: Object.fromEntries(Object.entries(object.metadata.labels ?? {}).filter((entry) => typeof entry[1] === "string")),
|
|
83
|
+
path: object.selfLink,
|
|
84
|
+
fields: require_resource_search_fields.projectSearchFields(kind, object)
|
|
85
|
+
}] : []);
|
|
86
|
+
}
|
|
87
|
+
function SearchSnippet({ field, query }) {
|
|
88
|
+
if (!field) return null;
|
|
89
|
+
const token = require_resource_search_model.queryTokens(query).find((token) => field.text.toLocaleLowerCase().includes(token));
|
|
90
|
+
const offset = token ? field.text.toLocaleLowerCase().indexOf(token) : -1;
|
|
91
|
+
return /* @__PURE__ */ require_jsx_runtime.jsxs("small", {
|
|
92
|
+
className: "power-resource-search-snippet",
|
|
93
|
+
children: [
|
|
94
|
+
field.label,
|
|
95
|
+
":",
|
|
96
|
+
" ",
|
|
97
|
+
offset < 0 || !token ? field.text : /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [
|
|
98
|
+
field.text.slice(0, offset),
|
|
99
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("mark", { children: field.text.slice(offset, offset + token.length) }),
|
|
100
|
+
field.text.slice(offset + token.length)
|
|
101
|
+
] })
|
|
102
|
+
]
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function SearchDialog({ close, clusterId }) {
|
|
106
|
+
const [query, setQuery] = require__global_external_react.useState("");
|
|
107
|
+
const [resources, setResources] = require__global_external_react.useState([]);
|
|
108
|
+
const [loading, setLoading] = require__global_external_react.useState(true);
|
|
109
|
+
const [failed, setFailed] = require__global_external_react.useState(0);
|
|
110
|
+
const [selectedPath, setSelectedPath] = require__global_external_react.useState();
|
|
111
|
+
const [error, setError] = require__global_external_react.useState("");
|
|
112
|
+
const dialog = require__global_external_react.useRef(null);
|
|
113
|
+
const input = require__global_external_react.useRef(null);
|
|
114
|
+
const searchIndex = require__global_external_react.useMemo(() => require_resource_search_model.createResourceSearchIndex(resources), [resources]);
|
|
115
|
+
const matches = searchIndex.search(query);
|
|
116
|
+
const selectedResource = matches.find((resource) => resource.path === selectedPath) ?? matches[0];
|
|
117
|
+
const results = matches.slice(0, 100);
|
|
118
|
+
if (selectedResource && !results.includes(selectedResource)) results[results.length - 1] = selectedResource;
|
|
119
|
+
const selected = results.findIndex((resource) => resource.path === selectedResource?.path);
|
|
120
|
+
require__global_external_react.useEffect(() => {
|
|
121
|
+
if (selectedPath !== void 0 && selectedPath !== selectedResource?.path) setSelectedPath(void 0);
|
|
122
|
+
}, [selectedPath, selectedResource?.path]);
|
|
123
|
+
require__global_external_react.useEffect(() => {
|
|
124
|
+
const previous = document.activeElement;
|
|
125
|
+
input.current?.focus();
|
|
126
|
+
const controller = new AbortController();
|
|
127
|
+
const timeout = window.setTimeout(() => controller.abort(), 15e3);
|
|
128
|
+
let live = true;
|
|
129
|
+
const sources = inventory();
|
|
130
|
+
const parts = sources.map(([kind, store]) => project(kind, store.items));
|
|
131
|
+
setResources(parts.flat());
|
|
132
|
+
let next = 0;
|
|
133
|
+
const worker = async () => {
|
|
134
|
+
while (next < sources.length && live && activeId() === clusterId) {
|
|
135
|
+
const index = next++;
|
|
136
|
+
const [kind, store] = sources[index];
|
|
137
|
+
try {
|
|
138
|
+
const objects = await store.api.list({
|
|
139
|
+
namespace: "",
|
|
140
|
+
reqInit: { signal: controller.signal }
|
|
141
|
+
}, { limit: 500 });
|
|
142
|
+
if (!live || activeId() !== clusterId) return;
|
|
143
|
+
parts[index] = project(kind, objects ?? []);
|
|
144
|
+
setResources(parts.flat());
|
|
145
|
+
} catch {
|
|
146
|
+
if (live && activeId() === clusterId) setFailed((count) => count + 1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
Promise.all([worker(), worker()]).then(() => {
|
|
151
|
+
if (live) setLoading(false);
|
|
152
|
+
});
|
|
153
|
+
return () => {
|
|
154
|
+
live = false;
|
|
155
|
+
window.clearTimeout(timeout);
|
|
156
|
+
controller.abort();
|
|
157
|
+
if (previous?.isConnected) previous.focus();
|
|
158
|
+
};
|
|
159
|
+
}, [clusterId]);
|
|
160
|
+
require__global_external_react.useEffect(() => {
|
|
161
|
+
document.getElementById(`power-resource-result-${selected}`)?.scrollIntoView?.({ block: "nearest" });
|
|
162
|
+
}, [selected, selectedResource?.path]);
|
|
163
|
+
const open = (resource) => {
|
|
164
|
+
if (activeId() !== clusterId) return;
|
|
165
|
+
Promise.resolve().then(() => require_extensions.Renderer.Navigation.showDetails(resource.path)).then(() => {
|
|
166
|
+
if (activeId() === clusterId) close();
|
|
167
|
+
}).catch(() => setError("Could not open this resource. Try again or refresh the inventory."));
|
|
168
|
+
};
|
|
169
|
+
return /* @__PURE__ */ require_jsx_runtime.jsx("div", {
|
|
170
|
+
className: "power-resource-search-backdrop",
|
|
171
|
+
"data-freelens-power-ai-overlay": "",
|
|
172
|
+
onClick: (event) => event.stopPropagation(),
|
|
173
|
+
onMouseDown: (event) => {
|
|
174
|
+
event.stopPropagation();
|
|
175
|
+
if (event.target === event.currentTarget) close();
|
|
176
|
+
},
|
|
177
|
+
children: /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
178
|
+
ref: dialog,
|
|
179
|
+
role: "dialog",
|
|
180
|
+
"aria-modal": "true",
|
|
181
|
+
"aria-labelledby": "power-resource-search-title",
|
|
182
|
+
className: "power-resource-search-dialog",
|
|
183
|
+
onKeyDown: (event) => {
|
|
184
|
+
if (event.key === "Escape") {
|
|
185
|
+
event.stopPropagation();
|
|
186
|
+
close();
|
|
187
|
+
}
|
|
188
|
+
if (event.key === "Tab") {
|
|
189
|
+
const controls = Array.from(dialog.current?.querySelectorAll("input, button:not(:disabled)") ?? []);
|
|
190
|
+
const index = controls.indexOf(document.activeElement);
|
|
191
|
+
if (event.shiftKey && index <= 0) {
|
|
192
|
+
event.preventDefault();
|
|
193
|
+
controls.at(-1)?.focus();
|
|
194
|
+
} else if (!event.shiftKey && index === controls.length - 1) {
|
|
195
|
+
event.preventDefault();
|
|
196
|
+
controls[0]?.focus();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
children: [
|
|
201
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
202
|
+
className: "power-resource-search-heading",
|
|
203
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsxs("strong", {
|
|
204
|
+
id: "power-resource-search-title",
|
|
205
|
+
style: {
|
|
206
|
+
display: "flex",
|
|
207
|
+
alignItems: "center",
|
|
208
|
+
gap: 8
|
|
209
|
+
},
|
|
210
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
|
|
211
|
+
material: "search",
|
|
212
|
+
size: 18
|
|
213
|
+
}), "Search resources"]
|
|
214
|
+
}), /* @__PURE__ */ require_jsx_runtime.jsx("button", {
|
|
215
|
+
type: "button",
|
|
216
|
+
"aria-label": "Close resource search",
|
|
217
|
+
onClick: close,
|
|
218
|
+
children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, { material: "close" })
|
|
219
|
+
})]
|
|
220
|
+
}),
|
|
221
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("input", {
|
|
222
|
+
ref: input,
|
|
223
|
+
"aria-label": "Resource name, namespace, kind, label or indexed text",
|
|
224
|
+
"aria-controls": "power-resource-search-results",
|
|
225
|
+
placeholder: "Name, namespace, label, image, condition…",
|
|
226
|
+
maxLength: 300,
|
|
227
|
+
value: query,
|
|
228
|
+
onChange: (event) => {
|
|
229
|
+
setQuery(event.target.value);
|
|
230
|
+
setSelectedPath(void 0);
|
|
231
|
+
},
|
|
232
|
+
onKeyDown: (event) => {
|
|
233
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
setSelectedPath(results[results.length ? (selected + (event.key === "ArrowDown" ? 1 : -1) + results.length) % results.length : 0]?.path);
|
|
236
|
+
}
|
|
237
|
+
if (event.key === "Enter" && !event.nativeEvent.isComposing && results[selected]) {
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
open(results[selected]);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}),
|
|
243
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("p", {
|
|
244
|
+
role: "status",
|
|
245
|
+
children: [
|
|
246
|
+
loading ? "Loading supported kinds; cached results are available. " : "Supported kinds loaded. ",
|
|
247
|
+
"Partial inventory: up to 500 objects per kind, subject to permissions. Secrets, ConfigMaps and custom resources are excluded. Full-text index covers safe metadata, images, selectors and status only.",
|
|
248
|
+
failed > 0 && ` ${failed} kinds unavailable; cached results retained.`
|
|
249
|
+
]
|
|
250
|
+
}),
|
|
251
|
+
error && /* @__PURE__ */ require_jsx_runtime.jsx("p", {
|
|
252
|
+
role: "alert",
|
|
253
|
+
children: error
|
|
254
|
+
}),
|
|
255
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
256
|
+
id: "power-resource-search-results",
|
|
257
|
+
role: "list",
|
|
258
|
+
"aria-label": "Matching resources",
|
|
259
|
+
className: "power-resource-search-results",
|
|
260
|
+
children: [results.map((resource, index) => /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
|
|
261
|
+
id: `power-resource-result-${index}`,
|
|
262
|
+
role: "listitem",
|
|
263
|
+
className: `power-resource-search-row ${selected === index ? "selected" : ""}`,
|
|
264
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsxs("button", {
|
|
265
|
+
className: "power-resource-search-open",
|
|
266
|
+
type: "button",
|
|
267
|
+
onClick: () => open(resource),
|
|
268
|
+
children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, { material: icons.get(resource.kind) ?? "view_in_ar" }), /* @__PURE__ */ require_jsx_runtime.jsxs("span", { children: [
|
|
269
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("strong", { children: resource.name }),
|
|
270
|
+
/* @__PURE__ */ require_jsx_runtime.jsxs("small", { children: [
|
|
271
|
+
resource.kind,
|
|
272
|
+
" · ",
|
|
273
|
+
resource.namespace ?? "cluster scoped"
|
|
274
|
+
] }),
|
|
275
|
+
searchIndex.nameParts(resource) && /* @__PURE__ */ require_jsx_runtime.jsx("small", { children: "Name parts" }),
|
|
276
|
+
searchIndex.similarName(resource) && /* @__PURE__ */ require_jsx_runtime.jsx("small", { children: "Similar name" }),
|
|
277
|
+
/* @__PURE__ */ require_jsx_runtime.jsx(SearchSnippet, {
|
|
278
|
+
field: searchIndex.snippet(resource, query),
|
|
279
|
+
query
|
|
280
|
+
})
|
|
281
|
+
] })]
|
|
282
|
+
}), /* @__PURE__ */ require_jsx_runtime.jsx(require_diagnostic_explain_button.DiagnosticExplainButton, {
|
|
283
|
+
className: "power-resource-search-ai",
|
|
284
|
+
"aria-label": `Ask AI about ${resource.kind} ${resource.name}`,
|
|
285
|
+
title: "Ask AI about this resource",
|
|
286
|
+
onClick: () => {
|
|
287
|
+
if (activeId() !== clusterId) return;
|
|
288
|
+
close();
|
|
289
|
+
require_ai_diagnostic_handoff.explainDiagnosticFinding({
|
|
290
|
+
clusterId,
|
|
291
|
+
title: `${resource.kind}: ${resource.name}`,
|
|
292
|
+
summary: `Resource selected from search. Kind: ${resource.kind}; namespace: ${resource.namespace ?? "cluster scoped"}; name: ${resource.name}. No live diagnostic data was collected.`,
|
|
293
|
+
resource: {
|
|
294
|
+
kind: resource.kind,
|
|
295
|
+
name: resource.name,
|
|
296
|
+
namespace: resource.namespace
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, { material: "auto_awesome" })
|
|
301
|
+
})]
|
|
302
|
+
}, resource.path)), !results.length && /* @__PURE__ */ require_jsx_runtime.jsx("p", { children: loading ? "Searching…" : "No matching resources in the available inventory" })]
|
|
303
|
+
}),
|
|
304
|
+
matches.length > 100 && /* @__PURE__ */ require_jsx_runtime.jsx("p", { children: "Showing 100 matches. Refine your search." }),
|
|
305
|
+
/* @__PURE__ */ require_jsx_runtime.jsx("p", {
|
|
306
|
+
className: "power-resource-search-shortcuts",
|
|
307
|
+
children: "↑↓ Navigate · Enter Open · Esc Close"
|
|
308
|
+
})
|
|
309
|
+
]
|
|
310
|
+
})
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
function startResourceSearch() {
|
|
314
|
+
const container = document.createElement("div");
|
|
315
|
+
const style = document.createElement("style");
|
|
316
|
+
style.textContent = `.power-resource-search-backdrop{position:fixed;inset:0;z-index:10000;background:#0007;display:flex;align-items:flex-start;justify-content:center;padding:12vh 20px}.power-resource-search-dialog{width:680px;max-width:100%;max-height:75vh;display:flex;flex-direction:column;padding:20px;gap:12px;background:var(--mainBackground,#222);color:var(--textColorPrimary,#eee);border:1px solid var(--borderColor,#555);border-radius:8px;box-shadow:0 16px 60px #0008}.power-resource-search-heading{display:flex;align-items:center;justify-content:space-between}.power-resource-search-dialog input{padding:12px;font-size:16px;background:var(--inputControlBackground,#333);color:inherit;border:1px solid var(--borderColor,#666);border-radius:4px}.power-resource-search-dialog p{margin:0;font-size:12px;color:var(--textColorSecondary,#bbb)}.power-resource-search-results{overflow:auto;min-height:60px}.power-resource-search-row{display:flex;align-items:center;gap:12px;padding:4px;border-radius:4px}.power-resource-search-row{border-left:3px solid transparent}.power-resource-search-row.selected{background:var(--tableSelectedBackground,#394b66);border-left-color:var(--primary,#668fff)}.power-resource-search-row.selected .Icon{color:var(--primary,#8daaff)}.power-resource-search-row:hover{background:var(--tableHoveredBackground,#ffffff0b)}.power-resource-search-dialog button:hover:not(:disabled){background:#ffffff12;border-radius:4px}.power-resource-search-row>button:last-child{border:1px solid var(--borderColor,#555);border-radius:4px}.power-resource-search-shortcuts{padding-top:8px;border-top:1px solid var(--borderColor,#555)}.power-resource-search-dialog button{min-width:32px;min-height:32px;padding:6px;color:inherit;background:transparent;border:0;cursor:pointer}.power-resource-search-dialog button:disabled{opacity:.45;cursor:default}.power-resource-search-open{display:flex;align-items:center;gap:12px;flex:1;text-align:left;min-width:0}.power-resource-search-open span{min-width:0}.power-resource-search-open strong,.power-resource-search-open small{display:block;overflow-wrap:anywhere}.power-resource-search-open small{opacity:.7}.power-resource-search-row>button.power-resource-search-ai{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:34px;height:34px;min-width:34px;padding:0;background:var(--contentColor,#292d32);border:1px solid var(--borderColor,#666)}.power-resource-search-row.selected>button.power-resource-search-ai{background:var(--mainBackground,#202326);border-color:color-mix(in srgb,var(--textColorPrimary,#ddd) 55%,var(--borderColor,#666));box-shadow:0 1px 3px #0002}.power-resource-search-row.selected>button.power-resource-search-ai .Icon{color:var(--textColorAccent,#eee)}.power-resource-search-row>button.power-resource-search-ai:hover:not(:disabled){background:var(--contentColor,#30343a);border-color:var(--primary,#8daaff);color:var(--primary,#8daaff)}.power-resource-search-row>button.power-resource-search-ai:hover:not(:disabled) .Icon{color:inherit}.power-resource-search-dialog button:focus-visible{outline:2px solid var(--primary,#668fff)}`;
|
|
317
|
+
document.head.append(style);
|
|
318
|
+
document.body.append(container);
|
|
319
|
+
let visible = false;
|
|
320
|
+
const close = () => {
|
|
321
|
+
if (visible) {
|
|
322
|
+
visible = false;
|
|
323
|
+
require__global_external_react_dom.default.unmountComponentAtNode(container);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const show = () => {
|
|
327
|
+
const clusterId = activeId();
|
|
328
|
+
if (window.top === window || !clusterId || visible || document.querySelector("[aria-modal=\"true\"], .Dialog.is-opened")) return;
|
|
329
|
+
visible = true;
|
|
330
|
+
require__global_external_react_dom.default.render(/* @__PURE__ */ require_jsx_runtime.jsx(SearchDialog, {
|
|
331
|
+
close,
|
|
332
|
+
clusterId
|
|
333
|
+
}), container);
|
|
334
|
+
};
|
|
335
|
+
let shift = require_resource_search_model.createDoubleShift();
|
|
336
|
+
const resetShift = () => {
|
|
337
|
+
shift = require_resource_search_model.createDoubleShift();
|
|
338
|
+
};
|
|
339
|
+
const key = (event) => {
|
|
340
|
+
if (event.isComposing || event.key === "Process") {
|
|
341
|
+
resetShift();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (shift(event, performance.now())) show();
|
|
345
|
+
};
|
|
346
|
+
window.addEventListener("blur", resetShift);
|
|
347
|
+
window.addEventListener("keydown", key);
|
|
348
|
+
window.addEventListener("keyup", key);
|
|
349
|
+
window.addEventListener(OPEN, show);
|
|
350
|
+
const dispose = require__global_external_mobx.reaction(activeId, close);
|
|
351
|
+
return () => {
|
|
352
|
+
close();
|
|
353
|
+
dispose();
|
|
354
|
+
window.removeEventListener("blur", resetShift);
|
|
355
|
+
window.removeEventListener("keydown", key);
|
|
356
|
+
window.removeEventListener("keyup", key);
|
|
357
|
+
window.removeEventListener(OPEN, show);
|
|
358
|
+
container.remove();
|
|
359
|
+
style.remove();
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
//#endregion
|
|
363
|
+
exports.startResourceSearch = startResourceSearch;
|
|
364
|
+
|
|
365
|
+
//# sourceMappingURL=resource-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-search.js","names":[],"sources":["../../src/renderer/resource-search.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { reaction } from \"mobx\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport ReactDOM from \"react-dom\";\nimport { explainDiagnosticFinding } from \"./ai-diagnostic-handoff\";\nimport { AiIcon } from \"./ai-icon\";\nimport { DiagnosticExplainButton } from \"./diagnostic-explain-button\";\nimport { projectSearchFields } from \"./resource-search-fields\";\nimport {\n createDoubleShift,\n createResourceSearchIndex,\n queryTokens,\n type SearchResource,\n} from \"./resource-search-model\";\n\nconst OPEN = \"power-resource-search\";\nconst activeId = () => Renderer.Catalog.activeCluster.get()?.metadata.uid;\nconst inventory = () =>\n [\n [\"Pod\", Renderer.K8sApi.podsStore, \"view_in_ar\"],\n [\"Node\", Renderer.K8sApi.nodesStore, \"dns\"],\n [\"Deployment\", Renderer.K8sApi.deploymentStore, \"layers\"],\n [\"StatefulSet\", Renderer.K8sApi.statefulSetStore, \"layers\"],\n [\"DaemonSet\", Renderer.K8sApi.daemonSetStore, \"layers\"],\n [\"ReplicaSet\", Renderer.K8sApi.replicaSetStore, \"layers\"],\n [\"Job\", Renderer.K8sApi.jobStore, \"work\"],\n [\"CronJob\", Renderer.K8sApi.cronJobStore, \"schedule\"],\n [\"Service\", Renderer.K8sApi.serviceStore, \"settings_ethernet\"],\n [\"Ingress\", Renderer.K8sApi.ingressStore, \"alt_route\"],\n [\"Namespace\", Renderer.K8sApi.namespaceStore, \"folder\"],\n [\"HorizontalPodAutoscaler\", Renderer.K8sApi.hpaStore, \"swap_vert\"],\n ] as const;\nconst icons = new Map(inventory().map(([kind, , icon]) => [kind as string, icon]));\nfunction project(\n kind: string,\n objects: {\n metadata: { name?: string; namespace?: string; labels?: Partial<Record<string, string>> };\n selfLink: string;\n }[],\n): SearchResource[] {\n return objects.slice(0, 500).flatMap((object) =>\n object.metadata.name\n ? [\n {\n kind,\n name: object.metadata.name,\n namespace: object.metadata.namespace,\n labels: Object.fromEntries(\n Object.entries(object.metadata.labels ?? {}).filter(\n (entry): entry is [string, string] => typeof entry[1] === \"string\",\n ),\n ),\n path: object.selfLink,\n fields: projectSearchFields(kind, object),\n },\n ]\n : [],\n );\n}\n\nexport function ResourceSearchLauncher() {\n return (\n <button\n type=\"button\"\n title=\"Search resources (double Shift)\"\n aria-label=\"Search resources\"\n onClick={() => window.dispatchEvent(new Event(OPEN))}\n >\n <AiIcon material=\"search\" />\n </button>\n );\n}\n\nfunction SearchSnippet({ field, query }: { field?: { label: string; text: string }; query: string }) {\n if (!field) return null;\n const token = queryTokens(query).find((token) => field.text.toLocaleLowerCase().includes(token));\n const offset = token ? field.text.toLocaleLowerCase().indexOf(token) : -1;\n return (\n <small className=\"power-resource-search-snippet\">\n {field.label}:{\" \"}\n {offset < 0 || !token ? (\n field.text\n ) : (\n <>\n {field.text.slice(0, offset)}\n <mark>{field.text.slice(offset, offset + token.length)}</mark>\n {field.text.slice(offset + token.length)}\n </>\n )}\n </small>\n );\n}\n\nfunction SearchDialog({ close, clusterId }: { close: () => void; clusterId: string }) {\n const [query, setQuery] = useState(\"\");\n const [resources, setResources] = useState<SearchResource[]>([]);\n const [loading, setLoading] = useState(true);\n const [failed, setFailed] = useState(0);\n const [selectedPath, setSelectedPath] = useState<string>();\n const [error, setError] = useState(\"\");\n const dialog = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLInputElement>(null);\n const searchIndex = useMemo(() => createResourceSearchIndex(resources), [resources]);\n const matches = searchIndex.search(query);\n const selectedResource = matches.find((resource) => resource.path === selectedPath) ?? matches[0];\n const results = matches.slice(0, 100);\n if (selectedResource && !results.includes(selectedResource)) results[results.length - 1] = selectedResource;\n const selected = results.findIndex((resource) => resource.path === selectedResource?.path);\n useEffect(() => {\n if (selectedPath !== undefined && selectedPath !== selectedResource?.path) setSelectedPath(undefined);\n }, [selectedPath, selectedResource?.path]);\n useEffect(() => {\n const previous = document.activeElement as HTMLElement | null;\n input.current?.focus();\n const controller = new AbortController();\n const timeout = window.setTimeout(() => controller.abort(), 15000);\n let live = true;\n const sources = inventory();\n const parts = sources.map(([kind, store]) => project(kind, store.items));\n setResources(parts.flat());\n let next = 0;\n const worker = async () => {\n while (next < sources.length && live && activeId() === clusterId) {\n const index = next++;\n const [kind, store] = sources[index];\n try {\n const objects = await store.api.list(\n { namespace: \"\", reqInit: { signal: controller.signal } },\n { limit: 500 },\n );\n if (!live || activeId() !== clusterId) return;\n parts[index] = project(kind, objects ?? []);\n setResources(parts.flat());\n } catch {\n if (live && activeId() === clusterId) setFailed((count) => count + 1);\n }\n }\n };\n void Promise.all([worker(), worker()]).then(() => {\n if (live) setLoading(false);\n });\n return () => {\n live = false;\n window.clearTimeout(timeout);\n controller.abort();\n if (previous?.isConnected) previous.focus();\n };\n }, [clusterId]);\n useEffect(() => {\n document.getElementById(`power-resource-result-${selected}`)?.scrollIntoView?.({ block: \"nearest\" });\n }, [selected, selectedResource?.path]);\n const open = (resource: SearchResource) => {\n if (activeId() !== clusterId) return;\n void Promise.resolve()\n .then(() => Renderer.Navigation.showDetails(resource.path))\n .then(() => {\n if (activeId() === clusterId) close();\n })\n .catch(() => setError(\"Could not open this resource. Try again or refresh the inventory.\"));\n };\n return (\n <div\n className=\"power-resource-search-backdrop\"\n data-freelens-power-ai-overlay=\"\"\n onClick={(event) => event.stopPropagation()}\n onMouseDown={(event) => {\n event.stopPropagation();\n if (event.target === event.currentTarget) close();\n }}\n >\n <div\n ref={dialog}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"power-resource-search-title\"\n className=\"power-resource-search-dialog\"\n onKeyDown={(event) => {\n if (event.key === \"Escape\") {\n event.stopPropagation();\n close();\n }\n if (event.key === \"Tab\") {\n const controls = Array.from(\n dialog.current?.querySelectorAll<HTMLElement>(\"input, button:not(:disabled)\") ?? [],\n );\n const index = controls.indexOf(document.activeElement as HTMLElement);\n if (event.shiftKey && index <= 0) {\n event.preventDefault();\n controls.at(-1)?.focus();\n } else if (!event.shiftKey && index === controls.length - 1) {\n event.preventDefault();\n controls[0]?.focus();\n }\n }\n }}\n >\n <div className=\"power-resource-search-heading\">\n <strong id=\"power-resource-search-title\" style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n <AiIcon material=\"search\" size={18} />\n Search resources\n </strong>\n <button type=\"button\" aria-label=\"Close resource search\" onClick={close}>\n <AiIcon material=\"close\" />\n </button>\n </div>\n <input\n ref={input}\n aria-label=\"Resource name, namespace, kind, label or indexed text\"\n aria-controls=\"power-resource-search-results\"\n placeholder=\"Name, namespace, label, image, condition…\"\n maxLength={300}\n value={query}\n onChange={(event) => {\n setQuery(event.target.value);\n setSelectedPath(undefined);\n }}\n onKeyDown={(event) => {\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n const next = results.length\n ? (selected + (event.key === \"ArrowDown\" ? 1 : -1) + results.length) % results.length\n : 0;\n setSelectedPath(results[next]?.path);\n }\n if (event.key === \"Enter\" && !event.nativeEvent.isComposing && results[selected]) {\n event.preventDefault();\n open(results[selected]);\n }\n }}\n />\n <p role=\"status\">\n {loading ? \"Loading supported kinds; cached results are available. \" : \"Supported kinds loaded. \"}Partial\n inventory: up to 500 objects per kind, subject to permissions. Secrets, ConfigMaps and custom resources are\n excluded. Full-text index covers safe metadata, images, selectors and status only.\n {failed > 0 && ` ${failed} kinds unavailable; cached results retained.`}\n </p>\n {error && <p role=\"alert\">{error}</p>}\n <div\n id=\"power-resource-search-results\"\n role=\"list\"\n aria-label=\"Matching resources\"\n className=\"power-resource-search-results\"\n >\n {results.map((resource, index) => (\n <div\n key={resource.path}\n id={`power-resource-result-${index}`}\n role=\"listitem\"\n className={`power-resource-search-row ${selected === index ? \"selected\" : \"\"}`}\n >\n <button className=\"power-resource-search-open\" type=\"button\" onClick={() => open(resource)}>\n <AiIcon material={icons.get(resource.kind) ?? \"view_in_ar\"} />\n <span>\n <strong>{resource.name}</strong>\n <small>\n {resource.kind} · {resource.namespace ?? \"cluster scoped\"}\n </small>\n {searchIndex.nameParts(resource) && <small>Name parts</small>}\n {searchIndex.similarName(resource) && <small>Similar name</small>}\n <SearchSnippet field={searchIndex.snippet(resource, query)} query={query} />\n </span>\n </button>\n <DiagnosticExplainButton\n className=\"power-resource-search-ai\"\n aria-label={`Ask AI about ${resource.kind} ${resource.name}`}\n title=\"Ask AI about this resource\"\n onClick={() => {\n if (activeId() !== clusterId) return;\n close();\n explainDiagnosticFinding({\n clusterId,\n title: `${resource.kind}: ${resource.name}`,\n summary: `Resource selected from search. Kind: ${resource.kind}; namespace: ${resource.namespace ?? \"cluster scoped\"}; name: ${resource.name}. No live diagnostic data was collected.`,\n resource: { kind: resource.kind, name: resource.name, namespace: resource.namespace },\n });\n }}\n >\n <AiIcon material=\"auto_awesome\" />\n </DiagnosticExplainButton>\n </div>\n ))}\n {!results.length && <p>{loading ? \"Searching…\" : \"No matching resources in the available inventory\"}</p>}\n </div>\n {matches.length > 100 && <p>Showing 100 matches. Refine your search.</p>}\n <p className=\"power-resource-search-shortcuts\">↑↓ Navigate · Enter Open · Esc Close</p>\n </div>\n </div>\n );\n}\n\nexport function startResourceSearch(): () => void {\n const container = document.createElement(\"div\");\n const style = document.createElement(\"style\");\n style.textContent = `.power-resource-search-backdrop{position:fixed;inset:0;z-index:10000;background:#0007;display:flex;align-items:flex-start;justify-content:center;padding:12vh 20px}.power-resource-search-dialog{width:680px;max-width:100%;max-height:75vh;display:flex;flex-direction:column;padding:20px;gap:12px;background:var(--mainBackground,#222);color:var(--textColorPrimary,#eee);border:1px solid var(--borderColor,#555);border-radius:8px;box-shadow:0 16px 60px #0008}.power-resource-search-heading{display:flex;align-items:center;justify-content:space-between}.power-resource-search-dialog input{padding:12px;font-size:16px;background:var(--inputControlBackground,#333);color:inherit;border:1px solid var(--borderColor,#666);border-radius:4px}.power-resource-search-dialog p{margin:0;font-size:12px;color:var(--textColorSecondary,#bbb)}.power-resource-search-results{overflow:auto;min-height:60px}.power-resource-search-row{display:flex;align-items:center;gap:12px;padding:4px;border-radius:4px}.power-resource-search-row{border-left:3px solid transparent}.power-resource-search-row.selected{background:var(--tableSelectedBackground,#394b66);border-left-color:var(--primary,#668fff)}.power-resource-search-row.selected .Icon{color:var(--primary,#8daaff)}.power-resource-search-row:hover{background:var(--tableHoveredBackground,#ffffff0b)}.power-resource-search-dialog button:hover:not(:disabled){background:#ffffff12;border-radius:4px}.power-resource-search-row>button:last-child{border:1px solid var(--borderColor,#555);border-radius:4px}.power-resource-search-shortcuts{padding-top:8px;border-top:1px solid var(--borderColor,#555)}.power-resource-search-dialog button{min-width:32px;min-height:32px;padding:6px;color:inherit;background:transparent;border:0;cursor:pointer}.power-resource-search-dialog button:disabled{opacity:.45;cursor:default}.power-resource-search-open{display:flex;align-items:center;gap:12px;flex:1;text-align:left;min-width:0}.power-resource-search-open span{min-width:0}.power-resource-search-open strong,.power-resource-search-open small{display:block;overflow-wrap:anywhere}.power-resource-search-open small{opacity:.7}.power-resource-search-row>button.power-resource-search-ai{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:34px;height:34px;min-width:34px;padding:0;background:var(--contentColor,#292d32);border:1px solid var(--borderColor,#666)}.power-resource-search-row.selected>button.power-resource-search-ai{background:var(--mainBackground,#202326);border-color:color-mix(in srgb,var(--textColorPrimary,#ddd) 55%,var(--borderColor,#666));box-shadow:0 1px 3px #0002}.power-resource-search-row.selected>button.power-resource-search-ai .Icon{color:var(--textColorAccent,#eee)}.power-resource-search-row>button.power-resource-search-ai:hover:not(:disabled){background:var(--contentColor,#30343a);border-color:var(--primary,#8daaff);color:var(--primary,#8daaff)}.power-resource-search-row>button.power-resource-search-ai:hover:not(:disabled) .Icon{color:inherit}.power-resource-search-dialog button:focus-visible{outline:2px solid var(--primary,#668fff)}`;\n document.head.append(style);\n document.body.append(container);\n let visible = false;\n const close = () => {\n if (visible) {\n visible = false;\n ReactDOM.unmountComponentAtNode(container);\n }\n };\n const show = () => {\n const clusterId = activeId();\n if (\n window.top === window ||\n !clusterId ||\n visible ||\n document.querySelector('[aria-modal=\"true\"], .Dialog.is-opened')\n )\n return;\n visible = true;\n ReactDOM.render(<SearchDialog close={close} clusterId={clusterId} />, container);\n };\n let shift = createDoubleShift();\n const resetShift = () => {\n shift = createDoubleShift();\n };\n const key = (event: KeyboardEvent) => {\n if (event.isComposing || event.key === \"Process\") {\n resetShift();\n return;\n }\n if (shift(event, performance.now())) show();\n };\n window.addEventListener(\"blur\", resetShift);\n window.addEventListener(\"keydown\", key);\n window.addEventListener(\"keyup\", key);\n window.addEventListener(OPEN, show);\n const dispose = reaction(activeId, close);\n return () => {\n close();\n dispose();\n window.removeEventListener(\"blur\", resetShift);\n window.removeEventListener(\"keydown\", key);\n window.removeEventListener(\"keyup\", key);\n window.removeEventListener(OPEN, show);\n container.remove();\n style.remove();\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard-picker.js","names":[],"sources":["../../../../src/common/dashboard-picker.ts"],"sourcesContent":["export const DASHBOARD_PICK_ELEMENT = \"freelens-power:dashboard:pick-element\";\nexport type DashboardPickerResult = { status: \"selected\"; selector: string } | { status: \"canceled\" | \"error\" };\n\n// this function runs only in an isolated website world with no extension bridge\nexport function dashboardPickerAdapter(
|
|
1
|
+
{"version":3,"file":"dashboard-picker.js","names":[],"sources":["../../../../src/common/dashboard-picker.ts"],"sourcesContent":["export const DASHBOARD_PICK_ELEMENT = \"freelens-power:dashboard:pick-element\";\nexport type DashboardPickerResult = { status: \"selected\"; selector: string } | { status: \"canceled\" | \"error\" };\n\n// this function runs only in an isolated website world with no extension bridge\nexport function dashboardPickerAdapter(): Promise<string | null> {\n return new Promise((resolve) => {\n const overlay = document.createElement(\"div\");\n overlay.style.cssText =\n \"all:initial!important;position:fixed!important;inset:0!important;z-index:2147483647!important;pointer-events:auto!important\";\n const shadow = overlay.attachShadow({ mode: \"open\" });\n const outline = document.createElement(\"div\");\n outline.style.cssText =\n \"position:fixed;box-sizing:border-box;border:2px solid #00e5ff;border-radius:6px;box-shadow:0 0 0 1px #111,inset 0 0 0 1px #111;background:rgba(0,229,255,0.08);pointer-events:none;display:none\";\n const toolbar = document.createElement(\"div\");\n toolbar.style.cssText =\n \"position:fixed;bottom:20px;left:50%;transform:translateX(-50%);padding:12px 16px;background:#202124;color:#fff;border-radius:8px;font:13px system-ui;pointer-events:auto;display:flex;gap:16px;align-items:center\";\n const hint = document.createElement(\"span\");\n hint.textContent = \"Hover an element, then click to select · Esc to cancel\";\n const cancel = document.createElement(\"button\");\n cancel.textContent = \"Cancel\";\n cancel.style.cssText =\n \"font:inherit;color:inherit;background:transparent;border:1px solid #888;border-radius:4px;padding:4px 10px;cursor:pointer\";\n toolbar.append(hint, cancel);\n shadow.append(outline, toolbar);\n document.documentElement.append(overlay);\n let hovered: Element | undefined;\n const escape = (value: string) => CSS.escape(value);\n function selectorFor(target: Element): string | undefined {\n const matches = (selector: string) => {\n try {\n return (\n selector.length <= 512 &&\n document.querySelectorAll(selector).length === 1 &&\n document.querySelector(selector) === target\n );\n } catch {\n return false;\n }\n };\n if (target.id) {\n const id = `#${escape(target.id)}`;\n if (matches(id)) return id;\n }\n const path: string[] = [];\n let element: Element | null = target;\n for (let depth = 0; element && depth < 64; depth++, element = element.parentElement) {\n const tag = escape(element.localName);\n const siblings = element.parentElement ? Array.from(element.parentElement.children) : [element];\n path.unshift(`${tag}:nth-child(${siblings.indexOf(element) + 1})`);\n const selector = path.join(\" > \");\n if (selector.length > 512) return;\n if (matches(selector)) return selector;\n }\n return undefined;\n }\n function finish(value: string | null) {\n window.removeEventListener(\"pointermove\", move, true);\n window.removeEventListener(\"pointerdown\", block, true);\n window.removeEventListener(\"pointerup\", block, true);\n window.removeEventListener(\"click\", click, true);\n window.removeEventListener(\"auxclick\", block, true);\n window.removeEventListener(\"dblclick\", block, true);\n window.removeEventListener(\"keydown\", key, true);\n window.removeEventListener(\"pagehide\", cancelSelection, true);\n window.removeEventListener(\"scroll\", draw, true);\n window.removeEventListener(\"resize\", draw, true);\n overlay.remove();\n resolve(value);\n }\n function block(event: Event) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n function hit(event: Event): Element | undefined {\n if (event.composedPath().includes(toolbar)) return;\n let target = event.composedPath().find((node) => node instanceof Element);\n if (event.composedPath().includes(overlay) && event instanceof MouseEvent) {\n overlay.style.setProperty(\"pointer-events\", \"none\", \"important\");\n target = document.elementFromPoint(event.clientX, event.clientY) ?? undefined;\n overlay.style.setProperty(\"pointer-events\", \"auto\", \"important\");\n }\n return target instanceof Element && target.getRootNode() === document ? target : undefined;\n }\n function draw() {\n if (!hovered?.isConnected) {\n outline.style.display = \"none\";\n return;\n }\n const rect = hovered.getBoundingClientRect();\n Object.assign(outline.style, {\n display: \"block\",\n left: `${rect.left}px`,\n top: `${rect.top}px`,\n width: `${rect.width}px`,\n height: `${rect.height}px`,\n });\n }\n function move(event: PointerEvent) {\n hovered = hit(event);\n draw();\n }\n function click(event: MouseEvent) {\n const isCancel = event.composedPath().includes(cancel);\n block(event);\n if (isCancel) {\n finish(null);\n return;\n }\n const target = hit(event);\n if (!target) return;\n const selector = selectorFor(target);\n if (selector) finish(selector);\n else hint.textContent = \"This element cannot be selected. Choose a containing element or cancel.\";\n }\n function key(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n block(event);\n finish(null);\n } else block(event);\n }\n function cancelSelection() {\n finish(null);\n }\n window.addEventListener(\"pointermove\", move, true);\n window.addEventListener(\"pointerdown\", block, true);\n window.addEventListener(\"pointerup\", block, true);\n window.addEventListener(\"click\", click, true);\n window.addEventListener(\"auxclick\", block, true);\n window.addEventListener(\"dblclick\", block, true);\n window.addEventListener(\"keydown\", key, true);\n window.addEventListener(\"pagehide\", cancelSelection, true);\n window.addEventListener(\"scroll\", draw, true);\n window.addEventListener(\"resize\", draw, true);\n });\n}\n\nexport function dashboardPickerScript(): string {\n return `(${dashboardPickerAdapter.toString()})()`;\n}\n"],"mappings":";AAAA,IAAa,yBAAyB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard-selector.js","names":[],"sources":["../../../../src/common/dashboard-selector.ts"],"sourcesContent":["export const dashboardSelectorLimit = 512;\n\nexport function normalizeDashboardSelector(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const selector = value.trim();\n return selector && selector.length <= dashboardSelectorLimit && !/[\\u0000-\\u001f\\u007f]/.test(selector)\n ? selector\n : undefined;\n}\n\n// this function runs
|
|
1
|
+
{"version":3,"file":"dashboard-selector.js","names":[],"sources":["../../../../src/common/dashboard-selector.ts"],"sourcesContent":["export const dashboardSelectorLimit = 512;\n\nexport function normalizeDashboardSelector(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const selector = value.trim();\n return selector && selector.length <= dashboardSelectorLimit && !/[\\u0000-\\u001f\\u007f]/.test(selector)\n ? selector\n : undefined;\n}\n\n// this function runs inside the website document\nexport function dashboardSelectorAdapter(selector: string): void {\n const state = globalThis as typeof globalThis & {\n powerSelectorCleanup?: () => void;\n powerSelectorState?: { selector: string; target: Element | null; isApplied: () => boolean };\n };\n state.powerSelectorCleanup?.();\n if (!selector) {\n state.powerSelectorState = { selector, target: null, isApplied: () => true };\n state.powerSelectorCleanup = () => {\n delete state.powerSelectorState;\n delete state.powerSelectorCleanup;\n };\n return;\n }\n const hidden = \"data-power-selector-hidden\";\n const path = \"data-power-selector-path\";\n const inset = \"data-power-selector-inset\";\n let insetRoot: HTMLElement | undefined;\n let marked: Element[] = [];\n const style = document.createElement(\"style\");\n style.textContent = `[${hidden}] { display: none !important; } [${path}] { display: block !important; position: static !important; width: auto !important; height: auto !important; min-height: 0 !important; max-height: none !important; overflow: visible !important; margin: 0 !important; padding: 0 !important; transform: none !important; } body[${inset}] { padding: 12px !important; box-sizing: border-box !important; }`;\n const notice = document.createElement(\"div\");\n notice.textContent =\n \"Selected element unavailable. Showing the whole page. Clear CSS selector in card settings to reset.\";\n notice.style.cssText =\n \"position:fixed;top:0;left:0;right:0;z-index:2147483647;padding:8px;background:#292929;color:white;font:12px sans-serif\";\n const observer = new MutationObserver(() => {\n if (!projection.isApplied()) update();\n });\n function restore() {\n for (const element of marked) {\n element.removeAttribute(hidden);\n element.removeAttribute(path);\n }\n marked = [];\n insetRoot?.removeAttribute(inset);\n insetRoot = undefined;\n style.remove();\n notice.remove();\n }\n const projection = {\n selector,\n target: null as Element | null,\n isApplied: () => {\n const target = projection.target;\n if (!target || !style.isConnected) return false;\n try {\n if (document.querySelector(selector) !== target) return false;\n } catch {\n return false;\n }\n if (target === document.documentElement || target === document.body)\n return document.body?.hasAttribute(inset) ?? false;\n let child = target;\n let depth = 0;\n while (child.parentElement) {\n const parent = child.parentElement;\n if (!parent.hasAttribute(path) || ++depth >= 128) return false;\n for (const sibling of parent.children) {\n if (\n sibling !== child &&\n sibling !== style &&\n sibling.localName !== \"freelens-power-loading\" &&\n !sibling.hasAttribute(hidden)\n )\n return false;\n }\n child = parent;\n }\n return document.body?.hasAttribute(inset) ?? false;\n },\n };\n state.powerSelectorState = projection;\n function update() {\n observer.disconnect();\n restore();\n let target: Element | null = null;\n try {\n target = document.querySelector(selector);\n } catch {}\n const branches: Element[] = [];\n const ancestors: Element[] = [];\n if (target && target !== document.documentElement && target !== document.body) {\n let child: Element = target;\n while (child.parentElement && ancestors.length < 128 && branches.length < 2000) {\n const parent = child.parentElement;\n ancestors.push(parent);\n for (const sibling of parent.children) {\n if (sibling !== child && sibling.localName !== \"freelens-power-loading\") branches.push(sibling);\n if (branches.length >= 2000) break;\n }\n child = parent;\n }\n if (ancestors.length >= 128 || branches.length >= 2000) target = null;\n }\n if (target) {\n for (const element of branches) element.setAttribute(hidden, \"\");\n for (const element of ancestors) element.setAttribute(path, \"\");\n marked = [...branches, ...ancestors];\n if (document.body) {\n insetRoot = document.body;\n insetRoot.setAttribute(inset, \"\");\n }\n document.documentElement.append(style);\n } else {\n document.documentElement.append(notice);\n }\n projection.target = target;\n observer.observe(document.documentElement, { subtree: true, childList: true, attributes: true });\n }\n state.powerSelectorCleanup = () => {\n observer.disconnect();\n restore();\n delete state.powerSelectorCleanup;\n delete state.powerSelectorState;\n };\n update();\n}\n\nexport function dashboardSelectorScript(selector?: string): string {\n return `(${dashboardSelectorAdapter.toString()})(${JSON.stringify(normalizeDashboardSelector(selector) ?? \"\")})`;\n}\n"],"mappings":"AAEA,SAAgB,2BAA2B,OAAoC;CAC7E,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,WAAW,MAAM,KAAK;CAC5B,OAAO,YAAY,SAAS,UAAA,OAAoC,CAAC,wBAAwB,KAAK,QAAQ,IAClG,WACA,KAAA;AACN"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/common/hpa-diagnostics.ts
|
|
2
|
+
function diagnoseHpa(hpa) {
|
|
3
|
+
const min = hpa.spec?.minReplicas ?? 1;
|
|
4
|
+
const max = hpa.spec?.maxReplicas;
|
|
5
|
+
const conditions = hpa.status?.conditions ?? [];
|
|
6
|
+
const evidence = conditions.map((c) => `${c.type}=${c.status}${c.reason ? ` (${c.reason})` : ""}${c.message ? `: ${c.message}` : ""}`);
|
|
7
|
+
const disabled = hpa.status?.currentReplicas === 0 && conditions.some((c) => c.type === "ScalingActive" && c.status === "False" && c.reason === "ScalingDisabled");
|
|
8
|
+
const failed = conditions.some((c) => (c.type === "ScalingActive" || c.type === "AbleToScale") && c.status === "False" && !(disabled && c.type === "ScalingActive" && c.reason === "ScalingDisabled"));
|
|
9
|
+
const limited = conditions.find((c) => c.type === "ScalingLimited" && c.status === "True");
|
|
10
|
+
const maxBlocked = limited?.reason === "TooManyReplicas" && min !== max;
|
|
11
|
+
return {
|
|
12
|
+
state: failed || maxBlocked ? "warning" : "neutral",
|
|
13
|
+
summary: failed ? "Autoscaling reports a controller or metric failure" : disabled ? "Autoscaling is disabled while the target has zero replicas" : min === max ? "Replica count is fixed by equal minimum and maximum" : maxBlocked ? "Desired scale exceeds the configured maximum" : limited?.reason === "TooFewReplicas" ? "Minimum replicas constrain downscaling" : limited ? "Scaling is limited; inspect the controller reason" : conditions.length ? "No scaling blocker reported by available conditions" : "Autoscaling conditions are unavailable",
|
|
14
|
+
evidence,
|
|
15
|
+
current: hpa.status?.currentReplicas,
|
|
16
|
+
desired: hpa.status?.desiredReplicas,
|
|
17
|
+
min,
|
|
18
|
+
max
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
exports.diagnoseHpa = diagnoseHpa;
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=hpa-diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hpa-diagnostics.js","names":[],"sources":["../../../../src/common/hpa-diagnostics.ts"],"sourcesContent":["export interface HpaDiagnosticsInput {\n spec?: { minReplicas?: number; maxReplicas: number };\n status?: {\n currentReplicas?: number;\n desiredReplicas?: number;\n conditions?: { type: string; status: string; reason?: string; message?: string }[];\n };\n}\nexport function diagnoseHpa(hpa: HpaDiagnosticsInput) {\n const min = hpa.spec?.minReplicas ?? 1;\n const max = hpa.spec?.maxReplicas;\n const conditions = hpa.status?.conditions ?? [];\n const evidence = conditions.map(\n (c) => `${c.type}=${c.status}${c.reason ? ` (${c.reason})` : \"\"}${c.message ? `: ${c.message}` : \"\"}`,\n );\n const disabled =\n hpa.status?.currentReplicas === 0 &&\n conditions.some((c) => c.type === \"ScalingActive\" && c.status === \"False\" && c.reason === \"ScalingDisabled\");\n const failed = conditions.some(\n (c) =>\n (c.type === \"ScalingActive\" || c.type === \"AbleToScale\") &&\n c.status === \"False\" &&\n !(disabled && c.type === \"ScalingActive\" && c.reason === \"ScalingDisabled\"),\n );\n const limited = conditions.find((c) => c.type === \"ScalingLimited\" && c.status === \"True\");\n const maxBlocked = limited?.reason === \"TooManyReplicas\" && min !== max;\n const state = failed || maxBlocked ? \"warning\" : \"neutral\";\n const summary = failed\n ? \"Autoscaling reports a controller or metric failure\"\n : disabled\n ? \"Autoscaling is disabled while the target has zero replicas\"\n : min === max\n ? \"Replica count is fixed by equal minimum and maximum\"\n : maxBlocked\n ? \"Desired scale exceeds the configured maximum\"\n : limited?.reason === \"TooFewReplicas\"\n ? \"Minimum replicas constrain downscaling\"\n : limited\n ? \"Scaling is limited; inspect the controller reason\"\n : conditions.length\n ? \"No scaling blocker reported by available conditions\"\n : \"Autoscaling conditions are unavailable\";\n return {\n state,\n summary,\n evidence,\n current: hpa.status?.currentReplicas,\n desired: hpa.status?.desiredReplicas,\n min,\n max,\n };\n}\n"],"mappings":";AAQA,SAAgB,YAAY,KAA0B;CACpD,MAAM,MAAM,IAAI,MAAM,eAAe;CACrC,MAAM,MAAM,IAAI,MAAM;CACtB,MAAM,aAAa,IAAI,QAAQ,cAAc,CAAC;CAC9C,MAAM,WAAW,WAAW,KACzB,MAAM,GAAG,EAAE,KAAK,GAAG,EAAE,SAAS,EAAE,SAAS,KAAK,EAAE,OAAO,KAAK,KAAK,EAAE,UAAU,KAAK,EAAE,YAAY,IACnG;CACA,MAAM,WACJ,IAAI,QAAQ,oBAAoB,KAChC,WAAW,MAAM,MAAM,EAAE,SAAS,mBAAmB,EAAE,WAAW,WAAW,EAAE,WAAW,iBAAiB;CAC7G,MAAM,SAAS,WAAW,MACvB,OACE,EAAE,SAAS,mBAAmB,EAAE,SAAS,kBAC1C,EAAE,WAAW,WACb,EAAE,YAAY,EAAE,SAAS,mBAAmB,EAAE,WAAW,kBAC7D;CACA,MAAM,UAAU,WAAW,MAAM,MAAM,EAAE,SAAS,oBAAoB,EAAE,WAAW,MAAM;CACzF,MAAM,aAAa,SAAS,WAAW,qBAAqB,QAAQ;CAiBpE,OAAO;EACL,OAjBY,UAAU,aAAa,YAAY;EAkB/C,SAjBc,SACZ,uDACA,WACE,+DACA,QAAQ,MACN,wDACA,aACE,iDACA,SAAS,WAAW,mBAClB,2CACA,UACE,sDACA,WAAW,SACT,wDACA;EAId;EACA,SAAS,IAAI,QAAQ;EACrB,SAAS,IAAI,QAAQ;EACrB;EACA;CACF;AACF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const require_ai_terminal_context = require("./ai-terminal-context.js");
|
|
2
|
+
const require_date_time = require("./date-time.js");
|
|
3
|
+
//#region src/common/incident-summary.ts
|
|
4
|
+
var api = {
|
|
5
|
+
Pod: ["/api/v1", "pods"],
|
|
6
|
+
Node: [
|
|
7
|
+
"/api/v1",
|
|
8
|
+
"nodes",
|
|
9
|
+
"cluster"
|
|
10
|
+
],
|
|
11
|
+
Namespace: [
|
|
12
|
+
"/api/v1",
|
|
13
|
+
"namespaces",
|
|
14
|
+
"cluster"
|
|
15
|
+
],
|
|
16
|
+
PersistentVolume: [
|
|
17
|
+
"/api/v1",
|
|
18
|
+
"persistentvolumes",
|
|
19
|
+
"cluster"
|
|
20
|
+
],
|
|
21
|
+
PersistentVolumeClaim: ["/api/v1", "persistentvolumeclaims"],
|
|
22
|
+
Service: ["/api/v1", "services"],
|
|
23
|
+
HorizontalPodAutoscaler: ["/apis/autoscaling/v2", "horizontalpodautoscalers"],
|
|
24
|
+
Ingress: ["/apis/networking.k8s.io/v1", "ingresses"],
|
|
25
|
+
EndpointSlice: ["/apis/discovery.k8s.io/v1", "endpointslices"],
|
|
26
|
+
Deployment: ["/apis/apps/v1", "deployments"],
|
|
27
|
+
ReplicaSet: ["/apis/apps/v1", "replicasets"],
|
|
28
|
+
StatefulSet: ["/apis/apps/v1", "statefulsets"],
|
|
29
|
+
DaemonSet: ["/apis/apps/v1", "daemonsets"],
|
|
30
|
+
Job: ["/apis/batch/v1", "jobs"],
|
|
31
|
+
CronJob: ["/apis/batch/v1", "cronjobs"]
|
|
32
|
+
};
|
|
33
|
+
function incidentResourcePath(resource) {
|
|
34
|
+
if (!Object.hasOwn(api, resource.kind)) return;
|
|
35
|
+
const route = api[resource.kind];
|
|
36
|
+
const validName = (value) => typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,252}$/.test(value);
|
|
37
|
+
if (!validName(resource.name)) return;
|
|
38
|
+
if (route[2] === "cluster") {
|
|
39
|
+
if (resource.namespace !== void 0) return;
|
|
40
|
+
return `${route[0]}/${route[1]}/${encodeURIComponent(resource.name)}`;
|
|
41
|
+
}
|
|
42
|
+
if (!validName(resource.namespace)) return;
|
|
43
|
+
return `${route[0]}/namespaces/${encodeURIComponent(resource.namespace)}/${route[1]}/${encodeURIComponent(resource.name)}`;
|
|
44
|
+
}
|
|
45
|
+
var safe = (value, limit) => require_ai_terminal_context.redactAiTerminalText(value).slice(0, limit);
|
|
46
|
+
function formatIncidentSummary(incident) {
|
|
47
|
+
const date = incident.time === void 0 ? void 0 : new Date(incident.time);
|
|
48
|
+
const offset = date && Number.isFinite(date.getTime()) ? -date.getTimezoneOffset() : void 0;
|
|
49
|
+
const timezone = offset === void 0 ? "" : ` UTC${offset < 0 ? "-" : "+"}${String(Math.floor(Math.abs(offset) / 60)).padStart(2, "0")}:${String(Math.abs(offset) % 60).padStart(2, "0")}`;
|
|
50
|
+
const links = [...new Set((incident.resources ?? []).slice(0, 12).map(incidentResourcePath).filter((value) => !!value))];
|
|
51
|
+
const lines = [
|
|
52
|
+
`Problem: ${safe(incident.problem, 240)}`,
|
|
53
|
+
...incident.clusterName ? [`Cluster: ${safe(incident.clusterName, 160)}`] : [],
|
|
54
|
+
`Snapshot time: ${date ? require_date_time.formatDateTime(date) : "Unavailable"}${timezone}`,
|
|
55
|
+
"Facts:",
|
|
56
|
+
...incident.facts.slice(0, 12).map((fact) => `- ${safe(fact, 400)}`),
|
|
57
|
+
...links.length ? ["Resource API links (Freelens resource detail paths):", ...links.map((link) => `- ${link}`)] : []
|
|
58
|
+
];
|
|
59
|
+
const footer = "Read-only snapshot; evidence may be partial or stale";
|
|
60
|
+
const selected = [];
|
|
61
|
+
let length = 52;
|
|
62
|
+
for (const line of lines) {
|
|
63
|
+
if (length + line.length + 1 > 8e3) break;
|
|
64
|
+
selected.push(line);
|
|
65
|
+
length += line.length + 1;
|
|
66
|
+
}
|
|
67
|
+
return [...selected, footer].join("\n");
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
exports.formatIncidentSummary = formatIncidentSummary;
|
|
71
|
+
exports.incidentResourcePath = incidentResourcePath;
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=incident-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incident-summary.js","names":[],"sources":["../../../../src/common/incident-summary.ts"],"sourcesContent":["import { redactAiTerminalText } from \"./ai-terminal-context\";\nimport { formatDateTime } from \"./date-time\";\nexport interface IncidentResource {\n kind: string;\n namespace?: string;\n name: string;\n}\nexport interface IncidentSummary {\n clusterName?: string;\n problem: string;\n facts: string[];\n time?: string | number;\n resources?: IncidentResource[];\n}\nconst api: Record<string, [string, string, \"cluster\"?]> = {\n Pod: [\"/api/v1\", \"pods\"],\n Node: [\"/api/v1\", \"nodes\", \"cluster\"],\n Namespace: [\"/api/v1\", \"namespaces\", \"cluster\"],\n PersistentVolume: [\"/api/v1\", \"persistentvolumes\", \"cluster\"],\n PersistentVolumeClaim: [\"/api/v1\", \"persistentvolumeclaims\"],\n Service: [\"/api/v1\", \"services\"],\n HorizontalPodAutoscaler: [\"/apis/autoscaling/v2\", \"horizontalpodautoscalers\"],\n Ingress: [\"/apis/networking.k8s.io/v1\", \"ingresses\"],\n EndpointSlice: [\"/apis/discovery.k8s.io/v1\", \"endpointslices\"],\n Deployment: [\"/apis/apps/v1\", \"deployments\"],\n ReplicaSet: [\"/apis/apps/v1\", \"replicasets\"],\n StatefulSet: [\"/apis/apps/v1\", \"statefulsets\"],\n DaemonSet: [\"/apis/apps/v1\", \"daemonsets\"],\n Job: [\"/apis/batch/v1\", \"jobs\"],\n CronJob: [\"/apis/batch/v1\", \"cronjobs\"],\n};\nexport function incidentResourcePath(resource: IncidentResource): string | undefined {\n if (!Object.hasOwn(api, resource.kind)) return;\n const route = api[resource.kind];\n const validName = (value: string | undefined) =>\n typeof value === \"string\" && /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,252}$/.test(value);\n if (!validName(resource.name)) return;\n if (route[2] === \"cluster\") {\n if (resource.namespace !== undefined) return;\n return `${route[0]}/${route[1]}/${encodeURIComponent(resource.name)}`;\n }\n if (!validName(resource.namespace)) return;\n return `${route[0]}/namespaces/${encodeURIComponent(resource.namespace!)}/${route[1]}/${encodeURIComponent(resource.name)}`;\n}\nconst safe = (value: string, limit: number) => redactAiTerminalText(value).slice(0, limit);\nexport function formatIncidentSummary(incident: IncidentSummary): string {\n const date = incident.time === undefined ? undefined : new Date(incident.time);\n const offset = date && Number.isFinite(date.getTime()) ? -date.getTimezoneOffset() : undefined;\n const timezone =\n offset === undefined\n ? \"\"\n : ` UTC${offset < 0 ? \"-\" : \"+\"}${String(Math.floor(Math.abs(offset) / 60)).padStart(2, \"0\")}:${String(Math.abs(offset) % 60).padStart(2, \"0\")}`;\n const links = [\n ...new Set(\n (incident.resources ?? [])\n .slice(0, 12)\n .map(incidentResourcePath)\n .filter((value): value is string => !!value),\n ),\n ];\n const lines = [\n `Problem: ${safe(incident.problem, 240)}`,\n ...(incident.clusterName ? [`Cluster: ${safe(incident.clusterName, 160)}`] : []),\n `Snapshot time: ${date ? formatDateTime(date) : \"Unavailable\"}${timezone}`,\n \"Facts:\",\n ...incident.facts.slice(0, 12).map((fact) => `- ${safe(fact, 400)}`),\n ...(links.length\n ? [\"Resource API links (Freelens resource detail paths):\", ...links.map((link) => `- ${link}`)]\n : []),\n ];\n const footer = \"Read-only snapshot; evidence may be partial or stale\";\n const selected: string[] = [];\n let length = footer.length;\n for (const line of lines) {\n if (length + line.length + 1 > 8000) break;\n selected.push(line);\n length += line.length + 1;\n }\n return [...selected, footer].join(\"\\n\");\n}\n"],"mappings":"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"}
|