@64mb/freelens-power 1.2.2 → 1.4.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 (63) hide show
  1. package/README.md +3 -1
  2. package/out/main/common/store/monitoring-preferences-store.js +10 -0
  3. package/out/main/common/store/monitoring-preferences-store.js.map +1 -1
  4. package/out/renderer/ai-diagnostic-handoff.js +52 -0
  5. package/out/renderer/ai-diagnostic-handoff.js.map +1 -0
  6. package/out/renderer/ai-extras.js +40 -4
  7. package/out/renderer/ai-extras.js.map +1 -1
  8. package/out/renderer/ai.js +48 -56
  9. package/out/renderer/ai.js.map +1 -1
  10. package/out/renderer/details/hpa-diagnostics-details.js +46 -0
  11. package/out/renderer/details/hpa-diagnostics-details.js.map +1 -0
  12. package/out/renderer/details/namespace-health-details.js +91 -0
  13. package/out/renderer/details/namespace-health-details.js.map +1 -0
  14. package/out/renderer/details/resource-health-details.js +135 -0
  15. package/out/renderer/details/resource-health-details.js.map +1 -0
  16. package/out/renderer/details/service-diagnostics-details.js +163 -0
  17. package/out/renderer/details/service-diagnostics-details.js.map +1 -0
  18. package/out/renderer/diagnostic-explain-button.js +16 -0
  19. package/out/renderer/diagnostic-explain-button.js.map +1 -0
  20. package/out/renderer/diagnostics-state.js +53 -0
  21. package/out/renderer/diagnostics-state.js.map +1 -0
  22. package/out/renderer/diagnostics.css.js +10 -1
  23. package/out/renderer/diagnostics.css.js.map +1 -1
  24. package/out/renderer/diagnostics.js +486 -114
  25. package/out/renderer/diagnostics.js.map +1 -1
  26. package/out/renderer/incident-copy-button.js +50 -0
  27. package/out/renderer/incident-copy-button.js.map +1 -0
  28. package/out/renderer/index.js +91 -19
  29. package/out/renderer/index.js.map +1 -1
  30. package/out/renderer/monitoring-history-collection.js +1 -1
  31. package/out/renderer/monitoring.js +15 -0
  32. package/out/renderer/monitoring.js.map +1 -1
  33. package/out/renderer/preferences/monitoring-preferences.js +10 -0
  34. package/out/renderer/preferences/monitoring-preferences.js.map +1 -1
  35. package/out/renderer/preferences/preferences.js +98 -0
  36. package/out/renderer/preferences/preferences.js.map +1 -1
  37. package/out/renderer/resource-relationships.css.js +7 -0
  38. package/out/renderer/resource-relationships.css.js.map +1 -0
  39. package/out/renderer/resource-relationships.js +571 -0
  40. package/out/renderer/resource-relationships.js.map +1 -0
  41. package/out/renderer/resource-search-distance.js +24 -0
  42. package/out/renderer/resource-search-distance.js.map +1 -0
  43. package/out/renderer/resource-search-fields.js +55 -0
  44. package/out/renderer/resource-search-fields.js.map +1 -0
  45. package/out/renderer/resource-search-model.js +193 -0
  46. package/out/renderer/resource-search-model.js.map +1 -0
  47. package/out/renderer/resource-search.js +365 -0
  48. package/out/renderer/resource-search.js.map +1 -0
  49. package/out/renderer/src/common/hpa-diagnostics.js +24 -0
  50. package/out/renderer/src/common/hpa-diagnostics.js.map +1 -0
  51. package/out/renderer/src/common/incident-summary.js +73 -0
  52. package/out/renderer/src/common/incident-summary.js.map +1 -0
  53. package/out/renderer/src/common/resource-health.js +92 -0
  54. package/out/renderer/src/common/resource-health.js.map +1 -0
  55. package/out/renderer/src/common/resource-relationships.js +155 -0
  56. package/out/renderer/src/common/resource-relationships.js.map +1 -0
  57. package/out/renderer/src/common/service-diagnostics.js +50 -0
  58. package/out/renderer/src/common/service-diagnostics.js.map +1 -0
  59. package/out/renderer/src/common/store/monitoring-preferences-store.js +10 -0
  60. package/out/renderer/src/common/store/monitoring-preferences-store.js.map +1 -1
  61. package/out/renderer/src/common/timeline-groups.js +36 -0
  62. package/out/renderer/src/common/timeline-groups.js.map +1 -0
  63. package/package.json +1 -1
@@ -3,14 +3,20 @@ const require__global_external_mobx = require("./_virtual/_global-external_mobx.
3
3
  const require__global_external_react = require("./_virtual/_global-external_react.js");
4
4
  const require_jsx_runtime = require("./_virtual/_global-external_react/jsx-runtime.js");
5
5
  const require_ai_icon = require("./ai-icon.js");
6
+ const require_ai_diagnostic_handoff = require("./ai-diagnostic-handoff.js");
6
7
  const require_date_time = require("./src/common/date-time.js");
8
+ const require_monitoring_collector = require("./monitoring-collector.js");
7
9
  const require_resource_diagnostics = require("./src/common/resource-diagnostics.js");
10
+ const require_timeline_groups = require("./src/common/timeline-groups.js");
11
+ const require_diagnostic_explain_button = require("./diagnostic-explain-button.js");
8
12
  const require_diagnostics_css = require("./diagnostics.css.js");
9
- const require_monitoring_collector = require("./monitoring-collector.js");
13
+ const require_diagnostics_state = require("./diagnostics-state.js");
14
+ const require_incident_copy_button = require("./incident-copy-button.js");
10
15
  //#region src/renderer/diagnostics.tsx
11
16
  var viewDescriptions = {
12
17
  Consumers: ["What is using cluster resources?", "Compare workload usage and requests, then expand a workload to inspect its pods."],
13
18
  Timeline: ["What changed in this workload?", "Follow related Kubernetes events and changes observed while this view is open."],
19
+ "Not ready": ["Why is this pod not ready?", "Inspect running pod readiness, conditions and retained events."],
14
20
  Pending: ["Why is this pod waiting?", "Inspect scheduling evidence and resource requests before opening the pod for investigation."]
15
21
  };
16
22
  function DiagnosticsSelect({ label, value, onChange, options }) {
@@ -86,6 +92,7 @@ function consumerStatus(pods) {
86
92
  "InvalidImageName"
87
93
  ]);
88
94
  for (const pod of pods) {
95
+ if (pod.status?.phase === "Succeeded") continue;
89
96
  const status = pod.status;
90
97
  const reason = [...status?.containerStatuses ?? [], ...status?.initContainerStatuses ?? []].map((status) => status.state?.waiting?.reason).find((value) => value && failureReasons.has(value));
91
98
  if (pod.status?.phase === "Failed" || reason) return {
@@ -100,14 +107,18 @@ function consumerStatus(pods) {
100
107
  label: `${pendingCount} Pending`,
101
108
  icon: "hourglass_empty"
102
109
  };
103
- if (pods.some((pod) => pod.status?.conditions?.some((condition) => condition.type === "Ready" && condition.status === "False"))) return {
110
+ if (pods.some((pod) => pod.status?.phase !== "Succeeded" && pod.status?.conditions?.some((condition) => condition.type === "Ready" && condition.status === "False"))) return {
104
111
  tone: "warning",
105
112
  label: "Not ready",
106
113
  icon: "warning_amber"
107
114
  };
108
- if (pods.length && pods.every((pod) => pod.status?.phase === "Running" && pod.status.conditions?.some((condition) => condition.type === "Ready" && condition.status === "True"))) return {
115
+ if (pods.length && pods.every((pod) => pod.status?.phase === "Succeeded" || pod.status?.phase === "Running" && pod.status.conditions?.some((condition) => condition.type === "Ready" && condition.status === "True"))) return pods.every((pod) => pod.status?.phase === "Succeeded") ? {
116
+ tone: "healthy",
117
+ label: "Completed",
118
+ icon: "check_circle"
119
+ } : {
109
120
  tone: "healthy",
110
- label: "Ready",
121
+ label: pods.some((pod) => pod.status?.phase === "Succeeded") ? "Ready / Completed" : "Ready",
111
122
  icon: "check_circle_outline"
112
123
  };
113
124
  return {
@@ -157,7 +168,12 @@ var plurals = {
157
168
  function openResource(object, clusterId, event) {
158
169
  event.stopPropagation();
159
170
  const kind = object.kind ?? "Pod";
160
- if (activeId() !== clusterId || !object.metadata.name || !object.metadata.namespace || !resources[kind]) return;
171
+ if (activeId() !== clusterId || !object.metadata.name) return;
172
+ if (kind === "Namespace") {
173
+ Promise.resolve(require_extensions.Renderer.Navigation.showDetails(`/api/v1/namespaces/${encodeURIComponent(object.metadata.name)}`)).catch(() => {});
174
+ return;
175
+ }
176
+ if (!object.metadata.namespace || !resources[kind]) return;
161
177
  const path = `${resources[kind]}/namespaces/${encodeURIComponent(object.metadata.namespace)}/${plurals[kind]}/${encodeURIComponent(object.metadata.name)}`;
162
178
  Promise.resolve(require_extensions.Renderer.Navigation.showDetails(path)).catch(() => {});
163
179
  }
@@ -209,20 +225,73 @@ function projection(data) {
209
225
  return value;
210
226
  }
211
227
  function DiagnosticsPage() {
212
- const [tab, setTab] = require__global_external_react.useState("Consumers");
228
+ const initial = require__global_external_react.useRef(require_diagnostics_state.readDiagnosticsState(activeId() ?? "")).current;
229
+ const scrollContainer = require__global_external_react.useRef(null);
230
+ const stateCluster = require__global_external_react.useRef(activeId() ?? "");
231
+ const [tab, setTab] = require__global_external_react.useState(initial.tab);
213
232
  const [snapshot, setSnapshot] = require__global_external_react.useState();
214
233
  const [error, setError] = require__global_external_react.useState("");
215
- const [filter, setFilter] = require__global_external_react.useState("");
216
- const [needsAttention, setNeedsAttention] = require__global_external_react.useState(true);
217
- const [groupMode, setGroupMode] = require__global_external_react.useState("workload");
218
- const [namespacesOpen, setNamespacesOpen] = require__global_external_react.useState([]);
234
+ const [filter, setFilter] = require__global_external_react.useState(initial.filter);
235
+ const [namespaceScope, setNamespaceScope] = require__global_external_react.useState(initial.namespaceScope);
236
+ const [resourceScope, setResourceScope] = require__global_external_react.useState(initial.resourceScope);
237
+ const pendingScroll = require__global_external_react.useRef({
238
+ clusterId: stateCluster.current,
239
+ top: initial.scrollTop
240
+ });
241
+ const [needsAttention, setNeedsAttention] = require__global_external_react.useState(initial.needsAttention);
242
+ const [groupMode, setGroupMode] = require__global_external_react.useState(initial.groupMode);
243
+ const [namespacesOpen, setNamespacesOpen] = require__global_external_react.useState(initial.namespacesOpen);
219
244
  const known = require__global_external_react.useRef(/* @__PURE__ */ new Map());
220
- const [sort, setSort] = require__global_external_react.useState("cpu");
221
- const [expanded, setExpanded] = require__global_external_react.useState([]);
222
- const [selected, setSelected] = require__global_external_react.useState("");
223
- const [pendingUid, setPendingUid] = require__global_external_react.useState("");
224
- const [reason, setReason] = require__global_external_react.useState("");
225
- const [warningOnly, setWarningOnly] = require__global_external_react.useState(true);
245
+ const [sort, setSort] = require__global_external_react.useState(initial.sort);
246
+ const [expanded, setExpanded] = require__global_external_react.useState(initial.expanded);
247
+ const [selected, setSelected] = require__global_external_react.useState(initial.selected);
248
+ const [pendingUid, setPendingUid] = require__global_external_react.useState(initial.pendingUid);
249
+ const [notReadyUid, setNotReadyUid] = require__global_external_react.useState(initial.notReadyUid ?? "");
250
+ const [reason, setReason] = require__global_external_react.useState(initial.reason);
251
+ const [warningOnly, setWarningOnly] = require__global_external_react.useState(initial.warningOnly);
252
+ const [groupedTimeline, setGroupedTimeline] = require__global_external_react.useState(initial.groupedTimeline);
253
+ const [openTimeline, setOpenTimeline] = require__global_external_react.useState(initial.openTimeline);
254
+ const viewState = require__global_external_react.useRef(initial);
255
+ viewState.current = {
256
+ tab,
257
+ filter,
258
+ namespaceScope,
259
+ resourceScope,
260
+ needsAttention,
261
+ groupMode,
262
+ namespacesOpen,
263
+ sort,
264
+ expanded,
265
+ selected,
266
+ pendingUid,
267
+ notReadyUid,
268
+ reason,
269
+ warningOnly,
270
+ groupedTimeline,
271
+ openTimeline,
272
+ scrollTop: viewState.current.scrollTop
273
+ };
274
+ require__global_external_react.useEffect(() => {
275
+ return () => require_diagnostics_state.saveDiagnosticsState(stateCluster.current, viewState.current);
276
+ }, [initial]);
277
+ require__global_external_react.useEffect(() => {
278
+ const focusNamespace = (event) => {
279
+ const detail = event.detail;
280
+ if (!detail || detail.clusterId !== stateCluster.current || detail.clusterId !== activeId() || !detail.namespace && !detail.resourceScope) return;
281
+ setTab("Consumers");
282
+ setNamespaceScope(detail.namespace);
283
+ setResourceScope(detail.resourceScope);
284
+ setFilter("");
285
+ setNeedsAttention(false);
286
+ setGroupMode(detail.namespace ? "namespace" : "workload");
287
+ setNamespacesOpen(detail.namespace ? [detail.namespace] : []);
288
+ viewState.current.scrollTop = 0;
289
+ pendingScroll.current = void 0;
290
+ if (scrollContainer.current) scrollContainer.current.scrollTop = 0;
291
+ };
292
+ window.addEventListener(require_diagnostics_state.DIAGNOSTICS_NAMESPACE_EVENT, focusNamespace);
293
+ return () => window.removeEventListener(require_diagnostics_state.DIAGNOSTICS_NAMESPACE_EVENT, focusNamespace);
294
+ }, []);
226
295
  const [observed, setObserved] = require__global_external_react.useState([]);
227
296
  const previous = require__global_external_react.useRef();
228
297
  require__global_external_react.useEffect(() => {
@@ -236,9 +305,30 @@ function DiagnosticsPage() {
236
305
  known.current.clear();
237
306
  setSnapshot(void 0);
238
307
  setObserved([]);
239
- setSelected("");
240
- setPendingUid("");
241
- setExpanded([]);
308
+ require_diagnostics_state.saveDiagnosticsState(stateCluster.current, viewState.current);
309
+ stateCluster.current = activeId() ?? "";
310
+ const restored = require_diagnostics_state.readDiagnosticsState(stateCluster.current);
311
+ setTab(restored.tab);
312
+ setFilter(restored.filter);
313
+ setNamespaceScope(restored.namespaceScope);
314
+ setResourceScope(restored.resourceScope);
315
+ setNeedsAttention(restored.needsAttention);
316
+ setGroupMode(restored.groupMode);
317
+ setNamespacesOpen(restored.namespacesOpen);
318
+ setSort(restored.sort);
319
+ setExpanded(restored.expanded);
320
+ setSelected(restored.selected);
321
+ setPendingUid(restored.pendingUid);
322
+ setNotReadyUid(restored.notReadyUid ?? "");
323
+ setReason(restored.reason);
324
+ setWarningOnly(restored.warningOnly);
325
+ setGroupedTimeline(restored.groupedTimeline);
326
+ setOpenTimeline(restored.openTimeline);
327
+ viewState.current = restored;
328
+ pendingScroll.current = {
329
+ clusterId: stateCluster.current,
330
+ top: restored.scrollTop
331
+ };
242
332
  setError("");
243
333
  };
244
334
  const load = async () => {
@@ -301,20 +391,60 @@ function DiagnosticsPage() {
301
391
  };
302
392
  }, []);
303
393
  const data = snapshot?.clusterId === activeId() ? snapshot : void 0;
394
+ require__global_external_react.useLayoutEffect(() => {
395
+ const pending = pendingScroll.current;
396
+ if (!data || !pending || pending.clusterId !== data.clusterId || !scrollContainer.current) return;
397
+ scrollContainer.current.scrollTop = pending.top;
398
+ const frame = requestAnimationFrame(() => {
399
+ if (pendingScroll.current !== pending || !scrollContainer.current) return;
400
+ scrollContainer.current.scrollTop = pending.top;
401
+ viewState.current.scrollTop = scrollContainer.current.scrollTop;
402
+ pendingScroll.current = void 0;
403
+ });
404
+ return () => cancelAnimationFrame(frame);
405
+ }, [data]);
304
406
  const projected = require__global_external_react.useMemo(() => data ? projection(data) : void 0, [data]);
305
- const groups = projected?.groups ?? [];
306
- const filtered = require__global_external_react.useMemo(() => groups.filter((group) => `${group.namespace} ${group.name} ${group.kind}`.toLowerCase().includes(filter.toLowerCase())).filter((group) => !needsAttention || ["error", "warning"].includes(consumerStatus(group.pods).tone)).sort((a, b) => sort === "name" ? a.namespace.localeCompare(b.namespace) || a.name.localeCompare(b.name) || a.kind.localeCompare(b.kind) || a.identity.localeCompare(b.identity) : sort === "requests" || sort === "memoryRequests" ? b[sort === "requests" ? "cpu" : "memory"].requests - a[sort === "requests" ? "cpu" : "memory"].requests : (b[sort === "memory" ? "memory" : "cpu"].usage ?? -1) - (a[sort === "memory" ? "memory" : "cpu"].usage ?? -1)), [
407
+ const groups = require__global_external_react.useMemo(() => {
408
+ if (!data || !projected) return [];
409
+ if (!resourceScope) return projected.groups;
410
+ if (resourceScope.kind === "Pod" || resourceScope.kind === "Node") {
411
+ const pods = (data.pods ?? []).filter((pod) => resourceScope.kind === "Pod" ? (resourceScope.uid ? pod.metadata.uid === resourceScope.uid : pod.metadata.name === resourceScope.name) && (!namespaceScope || pod.metadata.namespace === namespaceScope) : pod.spec?.nodeName === resourceScope.name);
412
+ if (resourceScope.kind === "Pod") {
413
+ const originalPods = /* @__PURE__ */ new Map();
414
+ return require_resource_diagnostics.buildConsumerGroups(pods.map((pod) => {
415
+ const focused = {
416
+ ...pod,
417
+ status: {
418
+ ...pod.status,
419
+ phase: void 0
420
+ }
421
+ };
422
+ originalPods.set(focused, pod);
423
+ return focused;
424
+ }), data.owners ?? [], projected.usage).map((group) => ({
425
+ ...group,
426
+ pods: group.pods.map((pod) => originalPods.get(pod))
427
+ }));
428
+ }
429
+ return require_resource_diagnostics.buildConsumerGroups(pods, data.owners ?? [], projected.usage);
430
+ }
431
+ return projected.groups.filter((group) => resourceScope.uid ? group.identity === resourceScope.uid : group.kind === resourceScope.kind && group.name === resourceScope.name);
432
+ }, [
433
+ data,
434
+ projected,
435
+ resourceScope,
436
+ namespaceScope
437
+ ]);
438
+ const filtered = require__global_external_react.useMemo(() => groups.filter((group) => !namespaceScope || group.namespace === namespaceScope).filter((group) => `${group.namespace} ${group.name} ${group.kind}`.toLowerCase().includes(filter.toLowerCase())).filter((group) => !needsAttention || ["error", "warning"].includes(consumerStatus(group.pods).tone)).sort((a, b) => sort === "name" ? a.namespace.localeCompare(b.namespace) || a.name.localeCompare(b.name) || a.kind.localeCompare(b.kind) || a.identity.localeCompare(b.identity) : sort === "requests" || sort === "memoryRequests" ? b[sort === "requests" ? "cpu" : "memory"].requests - a[sort === "requests" ? "cpu" : "memory"].requests : (b[sort === "memory" ? "memory" : "cpu"].usage ?? -1) - (a[sort === "memory" ? "memory" : "cpu"].usage ?? -1)), [
307
439
  groups,
308
440
  filter,
441
+ namespaceScope,
309
442
  sort,
310
443
  needsAttention
311
444
  ]);
312
445
  const warningUids = new Set((data?.events ?? []).filter((event) => event.type === "Warning").map((event) => event.involvedObject?.uid ?? event.regarding?.uid).filter((uid) => !!uid));
313
446
  const applications = [...known.current.values()].filter((item) => !warningOnly || [...item.uids].some((uid) => warningUids.has(uid))).map((item) => item.group);
314
447
  const selectionAvailable = applications.some((group) => groupKey(group) === selected);
315
- require__global_external_react.useEffect(() => {
316
- if (selected && !selectionAvailable) setSelected("");
317
- }, [selected, selectionAvailable]);
318
448
  const application = applications.find((group) => groupKey(group) === selected);
319
449
  const uids = new Set(known.current.get(selected)?.uids ?? []);
320
450
  const normalizedEvents = require_resource_diagnostics.normalizeEvents(data?.events ?? []);
@@ -324,11 +454,32 @@ function DiagnosticsPage() {
324
454
  return [entry.id, event?.involvedObject ?? event?.regarding];
325
455
  }));
326
456
  const timeline = [...normalizedEvents, ...observed].filter((entry) => (!application || !!entry.objectUid && uids.has(entry.objectUid)) && (!reason || `${entry.reason} ${entry.message}`.toLowerCase().includes(reason.toLowerCase())) && (!warningOnly || warningIds.has(entry.id))).sort((a, b) => (Date.parse(b.time ?? "") || 0) - (Date.parse(a.time ?? "") || 0));
457
+ const timelineResource = (entry) => {
458
+ const object = entry.objectUid ? projected?.objects.get(entry.objectUid) : void 0;
459
+ const ref = eventReferences.get(entry.id);
460
+ return `${object?.metadata.namespace ?? ref?.namespace ?? "Unknown namespace"} / ${object?.kind ?? ref?.kind ?? "Resource"} / ${object?.metadata.name ?? ref?.name ?? entry.objectUid ?? entry.id}`;
461
+ };
462
+ const timelineGroups = require_timeline_groups.groupTimelineEntries(timeline, (entry) => {
463
+ if (!warningIds.has(entry.id)) return entry.id;
464
+ const owner = [...known.current.entries()].find(([, item]) => !!entry.objectUid && item.uids.has(entry.objectUid));
465
+ return owner ? `${owner[1].group.namespace}/${owner[1].group.kind}/${owner[1].group.name}` : timelineResource(entry);
466
+ }, timelineResource);
467
+ const displayedTimeline = groupedTimeline ? timelineGroups.map((group) => group.latest) : timeline;
327
468
  const pending = (data?.pods ?? []).filter((pod) => pod.status?.phase === "Pending");
328
- const PendingCard = pending.length && data?.pods ? "button" : "div";
329
- const selectedPod = pending.find((pod) => pod.metadata.uid === pendingUid);
469
+ const attentionPods = new Set(groups.filter((group) => !namespaceScope || group.namespace === namespaceScope).flatMap((group) => group.pods).filter((pod) => ["warning", "error"].includes(consumerStatus([pod]).tone)).map((pod) => pod.metadata.uid ?? pod)).size;
470
+ const AttentionCard = data?.pods ? "button" : "div";
471
+ const notReady = (data?.pods ?? []).filter((pod) => pod.status?.phase === "Running" && ["warning", "error"].includes(consumerStatus([pod]).tone));
472
+ const readinessView = tab === "Not ready";
473
+ const triagePods = readinessView ? notReady : pending;
474
+ const triageUid = readinessView ? notReadyUid : pendingUid;
475
+ const triageLabel = readinessView ? "Not ready pod" : "Pending pod";
476
+ const selectedPod = triagePods.find((pod) => pod.metadata.uid === triageUid);
330
477
  const diagnosis = selectedPod ? require_resource_diagnostics.diagnosePendingPod(selectedPod, data?.nodes ?? [], data?.events ?? [], data?.pvcs) : void 0;
331
478
  return /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
479
+ ref: scrollContainer,
480
+ onScroll: (event) => {
481
+ if (!pendingScroll.current) viewState.current.scrollTop = event.currentTarget.scrollTop;
482
+ },
332
483
  className: "power-diagnostics",
333
484
  style: {
334
485
  padding: 24,
@@ -350,7 +501,8 @@ function DiagnosticsPage() {
350
501
  children: [
351
502
  "Consumers",
352
503
  "Timeline",
353
- "Pending"
504
+ "Pending",
505
+ "Not ready"
354
506
  ].map((name) => /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
355
507
  type: "button",
356
508
  title: `Show ${name.toLowerCase()}`,
@@ -359,7 +511,7 @@ function DiagnosticsPage() {
359
511
  children: [
360
512
  /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, { material: name === "Consumers" ? "account_tree" : name === "Timeline" ? "timeline" : "hourglass_empty" }),
361
513
  name,
362
- name === "Pending" && data?.pods ? ` (${pending.length})` : ""
514
+ name === "Pending" && data?.pods ? ` (${pending.length})` : name === "Not ready" && data?.pods ? ` (${notReady.length})` : ""
363
515
  ]
364
516
  }, name))
365
517
  }),
@@ -401,24 +553,24 @@ function DiagnosticsPage() {
401
553
  })
402
554
  }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", { children: [/* @__PURE__ */ require_jsx_runtime.jsx("strong", { children: data.pods?.length ?? "Unavailable" }), /* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "Pods in snapshot" })] })]
403
555
  }),
404
- /* @__PURE__ */ require_jsx_runtime.jsxs(PendingCard, {
405
- className: `summary-card pending${!data.pods ? " unavailable" : pending.length ? " attention" : ""}`,
406
- ...pending.length ? {
556
+ /* @__PURE__ */ require_jsx_runtime.jsxs(AttentionCard, {
557
+ className: `summary-card pending${!data.pods ? " unavailable" : attentionPods ? " attention" : ""}`,
558
+ ...data.pods ? {
407
559
  type: "button",
408
- title: "Inspect pending pods",
409
- "aria-label": "Inspect pending pods",
560
+ title: "Show consumers needing attention",
561
+ "aria-label": "Show consumers needing attention",
410
562
  onClick: () => {
411
- setPendingUid(pending[0]?.metadata.uid ?? "");
412
- setTab("Pending");
563
+ setNeedsAttention(true);
564
+ setFilter("");
413
565
  }
414
566
  } : {},
415
567
  children: [/* @__PURE__ */ require_jsx_runtime.jsx("span", {
416
568
  className: "summary-icon",
417
569
  children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
418
- material: "hourglass_empty",
570
+ material: "warning_amber",
419
571
  size: 22
420
572
  })
421
- }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", { children: [/* @__PURE__ */ require_jsx_runtime.jsx("strong", { children: data.pods ? pending.length : "Unavailable" }), /* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "Pending pods" })] })]
573
+ }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", { children: [/* @__PURE__ */ require_jsx_runtime.jsx("strong", { children: data.pods ? attentionPods : "Unavailable" }), /* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "Needs attention" })] })]
422
574
  })
423
575
  ]
424
576
  }),
@@ -428,6 +580,42 @@ function DiagnosticsPage() {
428
580
  children: "Loading resource diagnostics…"
429
581
  }),
430
582
  tab === "Consumers" && data && /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [
583
+ resourceScope && /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
584
+ className: "namespace-scope",
585
+ role: "status",
586
+ children: [/* @__PURE__ */ require_jsx_runtime.jsxs("span", {
587
+ className: "namespace-scope-label",
588
+ children: [
589
+ resourceScope.kind,
590
+ ": ",
591
+ resourceScope.name
592
+ ]
593
+ }), /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
594
+ type: "button",
595
+ "aria-label": "Clear resource scope",
596
+ onClick: () => setResourceScope(void 0),
597
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
598
+ material: "close",
599
+ size: 13
600
+ }), "Show all resources"]
601
+ })]
602
+ }),
603
+ namespaceScope && /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
604
+ className: "namespace-scope",
605
+ role: "status",
606
+ children: [/* @__PURE__ */ require_jsx_runtime.jsxs("span", {
607
+ className: "namespace-scope-label",
608
+ children: ["Namespace: ", namespaceScope]
609
+ }), /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
610
+ type: "button",
611
+ "aria-label": "Clear namespace scope",
612
+ onClick: () => setNamespaceScope(void 0),
613
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
614
+ material: "close",
615
+ size: 13
616
+ }), "Show all namespaces"]
617
+ })]
618
+ }),
431
619
  /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
432
620
  className: "toolbar consumers-toolbar",
433
621
  children: [
@@ -530,6 +718,7 @@ function DiagnosticsPage() {
530
718
  /* @__PURE__ */ require_jsx_runtime.jsx("tbody", { children: (groupMode === "namespace" ? [...new Set(filtered.map((group) => group.namespace))] : [""]).map((namespace) => /* @__PURE__ */ require_jsx_runtime.jsx(NamespaceRows, {
531
719
  namespace,
532
720
  groups: namespace ? filtered.filter((group) => group.namespace === namespace) : filtered,
721
+ snapshotTime: data.time,
533
722
  clusterId: data?.clusterId ?? "",
534
723
  usage: projected?.usage ?? [],
535
724
  open: !namespace || namespacesOpen.includes(namespace),
@@ -612,6 +801,14 @@ function DiagnosticsPage() {
612
801
  className: "diagnostics-explainer",
613
802
  children: [/* @__PURE__ */ require_jsx_runtime.jsx("summary", { children: "Timeline coverage and timing" }), /* @__PURE__ */ require_jsx_runtime.jsx("p", { children: "Kubernetes events have limited retention. Observed changes begin with this open view, retain at most 300 entries, and have unknown transition times between snapshots. Related identities observed in this view are retained for at most 100 applications and 500 identities per application; older identity coverage can be incomplete." })]
614
803
  }),
804
+ /* @__PURE__ */ require_jsx_runtime.jsx("div", {
805
+ className: "warning-filter",
806
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(require_extensions.Renderer.Component.Checkbox, {
807
+ label: "Group recurring warnings",
808
+ value: groupedTimeline,
809
+ onChange: setGroupedTimeline
810
+ })
811
+ }),
615
812
  /* @__PURE__ */ require_jsx_runtime.jsx("div", {
616
813
  className: "timeline-legend",
617
814
  "aria-label": "Event types",
@@ -630,7 +827,8 @@ function DiagnosticsPage() {
630
827
  /* @__PURE__ */ require_jsx_runtime.jsx("ol", {
631
828
  className: "diagnostics-timeline",
632
829
  "aria-label": "Workload event timeline",
633
- children: timeline.map((entry) => {
830
+ children: displayedTimeline.map((entry) => {
831
+ const recurrence = groupedTimeline ? timelineGroups.find((group) => group.latest === entry) : void 0;
634
832
  const kind = eventCategory(entry.reason);
635
833
  const category = warningIds.has(entry.id) ? "warning" : kind === "event" && entry.certainty === "observed" ? "observed" : kind;
636
834
  const label = category === "warning" ? "Warning" : category === "rollout" ? "Rollout" : category === "restart" ? "Restart" : category === "readiness" ? "Readiness" : entry.certainty === "observed" ? "Observed change" : "Kubernetes event";
@@ -649,41 +847,120 @@ function DiagnosticsPage() {
649
847
  }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
650
848
  className: "timeline-content",
651
849
  children: [
652
- object && data && /* @__PURE__ */ require_jsx_runtime.jsx("button", {
653
- type: "button",
654
- className: "timeline-resource-action",
655
- "aria-label": "Open resource",
656
- title: `Open ${object.kind ?? "Pod"} ${object.metadata.namespace} / ${object.metadata.name}`,
657
- onClick: (event) => openResource(object, data.clusterId, event),
658
- children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, { material: "open_in_new" })
659
- }),
660
850
  /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
661
- className: "timeline-card-heading",
662
- children: [
663
- " ",
664
- /* @__PURE__ */ require_jsx_runtime.jsx("span", {
665
- className: `timeline-type ${category}`,
666
- children: label
667
- }),
668
- /* @__PURE__ */ require_jsx_runtime.jsx("h4", { children: entry.reason }),
669
- /* @__PURE__ */ require_jsx_runtime.jsxs("small", {
670
- className: "timeline-resource-identity",
671
- children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
672
- material: referenceKind === "Pod" ? "view_in_ar" : "account_tree",
673
- size: 12
674
- }), metadata?.name ? `${metadata.namespace ?? "Namespace unknown"} / ${referenceKind ?? "Resource"} / ${metadata.name}` : "Resource identity unavailable"]
675
- })
676
- ]
851
+ className: "timeline-card-top",
852
+ children: [/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
853
+ className: "timeline-card-heading",
854
+ children: [
855
+ " ",
856
+ /* @__PURE__ */ require_jsx_runtime.jsx("span", {
857
+ className: `timeline-type ${category}`,
858
+ children: label
859
+ }),
860
+ /* @__PURE__ */ require_jsx_runtime.jsx("h4", { children: entry.reason }),
861
+ /* @__PURE__ */ require_jsx_runtime.jsxs("small", {
862
+ className: "timeline-resource-identity",
863
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
864
+ material: referenceKind === "Pod" ? "view_in_ar" : "account_tree",
865
+ size: 12
866
+ }), metadata?.name ? `${metadata.namespace ?? "Namespace unknown"} / ${referenceKind ?? "Resource"} / ${metadata.name}` : "Resource identity unavailable"]
867
+ })
868
+ ]
869
+ }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
870
+ className: "timeline-card-actions",
871
+ role: "group",
872
+ "aria-label": "Event actions",
873
+ children: [
874
+ object && data && /* @__PURE__ */ require_jsx_runtime.jsx("button", {
875
+ type: "button",
876
+ className: "timeline-resource-action",
877
+ "aria-label": "Open resource",
878
+ title: `Open ${object.kind ?? "Pod"} ${object.metadata.namespace} / ${object.metadata.name}`,
879
+ onClick: (event) => openResource(object, data.clusterId, event),
880
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, { material: "open_in_new" })
881
+ }),
882
+ /* @__PURE__ */ require_jsx_runtime.jsx(require_incident_copy_button.IncidentCopyButton, {
883
+ clusterId: data.clusterId,
884
+ incident: {
885
+ problem: entry.reason,
886
+ facts: [
887
+ entry.message,
888
+ `Reported count ${recurrence?.count ?? entry.count}`,
889
+ ...(recurrence?.affected ?? []).length > 1 ? [`Affected resources: ${recurrence?.affected.join("; ")}`] : []
890
+ ],
891
+ time: entry.time ?? data.time,
892
+ resources: (recurrence?.entries ?? [entry]).slice(0, 12).flatMap((original) => {
893
+ const originalObject = original.objectUid ? projected?.objects.get(original.objectUid) : void 0;
894
+ const originalReference = eventReferences.get(original.id);
895
+ const originalMetadata = originalObject?.metadata ?? originalReference;
896
+ const originalKind = originalObject?.kind ?? originalReference?.kind;
897
+ return originalMetadata?.name && originalKind ? [{
898
+ kind: originalKind,
899
+ namespace: originalMetadata.namespace,
900
+ name: originalMetadata.name
901
+ }] : [];
902
+ })
903
+ }
904
+ }),
905
+ /* @__PURE__ */ require_jsx_runtime.jsxs(require_diagnostic_explain_button.DiagnosticExplainButton, {
906
+ type: "button",
907
+ className: "explain-finding",
908
+ "aria-label": "Explain with AI",
909
+ title: "Explain with AI",
910
+ onClick: () => require_ai_diagnostic_handoff.explainDiagnosticFinding({
911
+ clusterId: data.clusterId,
912
+ title: entry.reason,
913
+ summary: `${timelineResource(entry)}: ${entry.message}. Reported count ${recurrence?.count ?? entry.count}`,
914
+ resource: metadata?.name && referenceKind ? {
915
+ kind: referenceKind,
916
+ namespace: metadata.namespace,
917
+ name: metadata.name
918
+ } : void 0
919
+ }),
920
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
921
+ material: "auto_awesome",
922
+ size: 13
923
+ }), /* @__PURE__ */ require_jsx_runtime.jsx("span", {
924
+ className: "timeline-action-label",
925
+ children: "Explain with AI"
926
+ })]
927
+ })
928
+ ]
929
+ })]
677
930
  }),
678
931
  /* @__PURE__ */ require_jsx_runtime.jsx("p", { children: entry.message }),
679
932
  /* @__PURE__ */ require_jsx_runtime.jsxs("small", { children: [
680
933
  entry.certainty === "reported" ? "Reported" : entry.certainty === "observed" ? "Observed" : "Unknown",
681
934
  " ",
682
935
  "· Count ",
683
- entry.count,
936
+ recurrence?.count ?? entry.count,
684
937
  entry.firstTime ? ` · First ${require_date_time.formatDateTime(entry.firstTime)}` : ""
685
938
  ] }),
686
- entry.gap && /* @__PURE__ */ require_jsx_runtime.jsx("small", { children: entry.gap })
939
+ entry.gap && /* @__PURE__ */ require_jsx_runtime.jsx("small", { children: entry.gap }),
940
+ recurrence && recurrence.entries.length > 1 && /* @__PURE__ */ require_jsx_runtime.jsxs("details", {
941
+ open: openTimeline.includes(recurrence.key),
942
+ onToggle: (event) => {
943
+ const open = event.currentTarget.open;
944
+ setOpenTimeline((items) => open ? [.../* @__PURE__ */ new Set([...items, recurrence.key])] : items.filter((item) => item !== recurrence.key));
945
+ },
946
+ children: [
947
+ /* @__PURE__ */ require_jsx_runtime.jsxs("summary", { children: [
948
+ recurrence.affected.length,
949
+ " affected resources · ",
950
+ recurrence.entries.length,
951
+ " original entries"
952
+ ] }),
953
+ /* @__PURE__ */ require_jsx_runtime.jsx("small", { children: recurrence.affected.join(" · ") }),
954
+ /* @__PURE__ */ require_jsx_runtime.jsx("ul", { children: recurrence.entries.map((original, index) => /* @__PURE__ */ require_jsx_runtime.jsxs("li", { children: [/* @__PURE__ */ require_jsx_runtime.jsxs("small", { children: [
955
+ original.time ? require_date_time.formatDateTime(original.time) : "Time unknown",
956
+ " ·",
957
+ " ",
958
+ timelineResource(original),
959
+ " · Count ",
960
+ original.count
961
+ ] }), original.message] }, `${original.id}/${index}`)) })
962
+ ]
963
+ })
687
964
  ]
688
965
  })]
689
966
  }, entry.id);
@@ -698,17 +975,17 @@ function DiagnosticsPage() {
698
975
  })]
699
976
  })
700
977
  ] }),
701
- tab === "Pending" && data && /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [/* @__PURE__ */ require_jsx_runtime.jsxs("label", { children: [
702
- "Pending pod",
978
+ (tab === "Pending" || readinessView) && data && /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [/* @__PURE__ */ require_jsx_runtime.jsxs("label", { children: [
979
+ triageLabel,
703
980
  " ",
704
981
  /* @__PURE__ */ require_jsx_runtime.jsx(DiagnosticsSelect, {
705
- label: "Pending pod",
706
- value: pendingUid,
707
- onChange: setPendingUid,
982
+ label: triageLabel,
983
+ value: triageUid,
984
+ onChange: readinessView ? setNotReadyUid : setPendingUid,
708
985
  options: [{
709
986
  value: "",
710
987
  label: "Select a pod"
711
- }, ...pending.map((pod) => ({
988
+ }, ...triagePods.map((pod) => ({
712
989
  value: pod.metadata.uid ?? "",
713
990
  label: `${pod.metadata.namespace} / ${pod.metadata.name}`
714
991
  }))]
@@ -725,7 +1002,7 @@ function DiagnosticsPage() {
725
1002
  selectedPod.metadata.name
726
1003
  ] }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
727
1004
  className: "pending-state-row",
728
- children: [/* @__PURE__ */ require_jsx_runtime.jsx("h3", { children: diagnosis.state === "assigned-waiting" ? "Assigned and waiting" : "Unscheduled" }), consumerStatus([selectedPod]).tone === "error" && /* @__PURE__ */ require_jsx_runtime.jsx(ConsumerStatus, { pods: [selectedPod] })]
1005
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx("h3", { children: readinessView ? "Not ready" : diagnosis.state === "assigned-waiting" ? "Assigned and waiting" : "Unscheduled" }), consumerStatus([selectedPod]).tone === "error" && /* @__PURE__ */ require_jsx_runtime.jsx(ConsumerStatus, { pods: [selectedPod] })]
729
1006
  })] }), /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
730
1007
  className: "pending-open-action",
731
1008
  type: "button",
@@ -737,7 +1014,7 @@ function DiagnosticsPage() {
737
1014
  }), "Open pod"]
738
1015
  })]
739
1016
  }),
740
- /* @__PURE__ */ require_jsx_runtime.jsx("p", { children: diagnosis.state === "assigned-waiting" ? "A node is assigned. Check pod conditions and events for startup or storage issues." : "No node is assigned. Start with scheduler events, then inspect constraints." }),
1017
+ /* @__PURE__ */ require_jsx_runtime.jsx("p", { children: readinessView ? "The pod is running but needs attention. Check readiness conditions, container status and events." : diagnosis.state === "assigned-waiting" ? "A node is assigned. Check pod conditions and events for startup or storage issues." : "No node is assigned. Start with scheduler events, then inspect constraints." }),
741
1018
  /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
742
1019
  className: "pending-request-badges",
743
1020
  children: [/* @__PURE__ */ require_jsx_runtime.jsxs("span", { children: [
@@ -780,7 +1057,42 @@ function DiagnosticsPage() {
780
1057
  /* @__PURE__ */ require_jsx_runtime.jsx("p", { children: selectedPod.status?.conditions?.map((condition) => `${condition.type}: ${condition.status}${condition.message ? ` (${condition.message})` : ""}`).join(" · ") })
781
1058
  ]
782
1059
  }),
783
- /* @__PURE__ */ require_jsx_runtime.jsx("h3", { children: "Scheduling evidence" }),
1060
+ /* @__PURE__ */ require_jsx_runtime.jsx(require_incident_copy_button.IncidentCopyButton, {
1061
+ clusterId: data.clusterId,
1062
+ incident: {
1063
+ problem: triageLabel,
1064
+ facts: [
1065
+ readinessView ? consumerStatus([selectedPod]).label : diagnosis.state,
1066
+ `CPU request ${podRequest(selectedPod, "cpu")}; memory request ${podRequest(selectedPod, "memory")}`,
1067
+ ...diagnosis.evidence.slice(0, 8).map((entry) => entry.message)
1068
+ ],
1069
+ time: data.time,
1070
+ resources: [{
1071
+ kind: "Pod",
1072
+ namespace: selectedPod.metadata.namespace,
1073
+ name: selectedPod.metadata.name
1074
+ }]
1075
+ }
1076
+ }),
1077
+ /* @__PURE__ */ require_jsx_runtime.jsxs(require_diagnostic_explain_button.DiagnosticExplainButton, {
1078
+ type: "button",
1079
+ className: "explain-finding",
1080
+ onClick: () => require_ai_diagnostic_handoff.explainDiagnosticFinding({
1081
+ clusterId: data.clusterId,
1082
+ title: triageLabel,
1083
+ summary: `${readinessView ? consumerStatus([selectedPod]).label : diagnosis.state}. CPU request ${podRequest(selectedPod, "cpu")}, memory request ${podRequest(selectedPod, "memory")}. ${diagnosis.evidence.slice(0, 8).map((entry) => entry.message).join("; ")}`,
1084
+ resource: {
1085
+ kind: "Pod",
1086
+ namespace: selectedPod.metadata.namespace,
1087
+ name: selectedPod.metadata.name
1088
+ }
1089
+ }),
1090
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
1091
+ material: "auto_awesome",
1092
+ size: 13
1093
+ }), "Explain with AI"]
1094
+ }),
1095
+ /* @__PURE__ */ require_jsx_runtime.jsx("h3", { children: readinessView ? "Readiness and retained evidence" : "Scheduling evidence" }),
784
1096
  /* @__PURE__ */ require_jsx_runtime.jsx("div", {
785
1097
  className: "diagnostics-table",
786
1098
  children: /* @__PURE__ */ require_jsx_runtime.jsxs("table", {
@@ -817,12 +1129,12 @@ function DiagnosticsPage() {
817
1129
  })
818
1130
  ] }) : /* @__PURE__ */ require_jsx_runtime.jsx("p", {
819
1131
  className: "diagnostics-empty",
820
- children: !data.pods ? "Pod inventory unavailable. Check cluster access before investigating pending pods." : pending.length ? "Select a pending pod to see scheduling evidence and requests" : "No pending pods in this snapshot"
1132
+ children: !data.pods ? `Pod inventory unavailable. Check cluster access before investigating ${readinessView ? "readiness" : "pending pods"}.` : triagePods.length ? `Select a ${readinessView ? "not ready" : "pending"} pod to see evidence and requests` : `No ${readinessView ? "not ready" : "pending"} pods in this snapshot`
821
1133
  })] })
822
1134
  ]
823
1135
  });
824
1136
  }
825
- function ConsumerRows({ group, clusterId, expanded, toggle, usage, depth }) {
1137
+ function ConsumerRows({ group, clusterId, snapshotTime, expanded, toggle, usage, depth }) {
826
1138
  return /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [/* @__PURE__ */ require_jsx_runtime.jsxs("tr", {
827
1139
  className: `consumer-workload-row consumer-depth-${depth} consumer-tone-${consumerStatus(group.pods).tone}`,
828
1140
  "data-row-depth": depth,
@@ -833,7 +1145,7 @@ function ConsumerRows({ group, clusterId, expanded, toggle, usage, depth }) {
833
1145
  className: "consumer-identity-main",
834
1146
  children: [/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
835
1147
  className: "consumer-title-line",
836
- children: [group.ownerResolved && resources[group.kind] && group.name !== "Unknown" ? /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
1148
+ children: [group.ownerResolved && resources[group.kind] && group.name !== "Unknown" ? /* @__PURE__ */ require_jsx_runtime.jsx("button", {
837
1149
  className: "consumer-resource-link",
838
1150
  type: "button",
839
1151
  title: `Open ${group.kind} ${group.namespace} / ${group.name}`,
@@ -845,10 +1157,10 @@ function ConsumerRows({ group, clusterId, expanded, toggle, usage, depth }) {
845
1157
  name: group.name
846
1158
  }
847
1159
  }, clusterId, event),
848
- children: [group.name, /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
849
- material: "open_in_new",
850
- size: 13
851
- })]
1160
+ children: /* @__PURE__ */ require_jsx_runtime.jsx("span", {
1161
+ className: "consumer-link-name",
1162
+ children: group.name
1163
+ })
852
1164
  }) : /* @__PURE__ */ require_jsx_runtime.jsx("span", {
853
1165
  className: "consumer-identity-label",
854
1166
  children: group.name
@@ -873,20 +1185,59 @@ function ConsumerRows({ group, clusterId, expanded, toggle, usage, depth }) {
873
1185
  group.ownerResolved ? "" : " · Owner chain incomplete"
874
1186
  ]
875
1187
  })]
876
- }), /* @__PURE__ */ require_jsx_runtime.jsx("div", {
1188
+ }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
877
1189
  className: "consumer-row-actions",
878
- children: /* @__PURE__ */ require_jsx_runtime.jsx("span", {
879
- className: "consumer-expand-slot",
880
- children: /* @__PURE__ */ require_jsx_runtime.jsx("button", {
881
- className: "consumer-expand",
1190
+ children: [
1191
+ ["warning", "error"].includes(consumerStatus(group.pods).tone) && /* @__PURE__ */ require_jsx_runtime.jsx(require_incident_copy_button.IncidentCopyButton, {
1192
+ clusterId,
1193
+ incident: {
1194
+ problem: `${group.kind} ${group.name}: ${consumerStatus(group.pods).label}`,
1195
+ facts: [
1196
+ `${group.pods.length} pods`,
1197
+ `CPU ${resourceText(group.cpu.usage, "cpu")} used / ${resourceText(group.cpu.requests, "cpu")} requested`,
1198
+ `Memory ${resourceText(group.memory.usage, "memory")} used / ${resourceText(group.memory.requests, "memory")} requested`
1199
+ ],
1200
+ time: snapshotTime,
1201
+ resources: [{
1202
+ kind: group.kind,
1203
+ namespace: group.namespace,
1204
+ name: group.name
1205
+ }]
1206
+ }
1207
+ }),
1208
+ ["warning", "error"].includes(consumerStatus(group.pods).tone) && /* @__PURE__ */ require_jsx_runtime.jsx(require_diagnostic_explain_button.DiagnosticExplainButton, {
882
1209
  type: "button",
883
- title: expanded ? "Collapse pods" : "Expand pods",
884
- "aria-label": `${expanded ? "Collapse" : "Expand"} pods for ${group.name}`,
885
- "aria-expanded": expanded,
886
- onClick: toggle,
887
- children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, { material: expanded ? "expand_less" : "expand_more" })
1210
+ className: "explain-finding",
1211
+ title: "Explain with AI",
1212
+ "aria-label": `Explain finding for ${group.name}`,
1213
+ onClick: () => require_ai_diagnostic_handoff.explainDiagnosticFinding({
1214
+ clusterId,
1215
+ title: `${group.kind} ${group.name}`,
1216
+ summary: `${consumerStatus(group.pods).label}; ${group.pods.length} pods. CPU ${resourceText(group.cpu.usage, "cpu")} used / ${resourceText(group.cpu.requests, "cpu")} requested; memory ${resourceText(group.memory.usage, "memory")} used / ${resourceText(group.memory.requests, "memory")} requested`,
1217
+ resource: {
1218
+ kind: group.kind,
1219
+ namespace: group.namespace,
1220
+ name: group.name
1221
+ }
1222
+ }),
1223
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
1224
+ material: "auto_awesome",
1225
+ size: 13
1226
+ })
1227
+ }),
1228
+ /* @__PURE__ */ require_jsx_runtime.jsx("span", {
1229
+ className: "consumer-expand-slot",
1230
+ children: /* @__PURE__ */ require_jsx_runtime.jsx("button", {
1231
+ className: "consumer-expand",
1232
+ type: "button",
1233
+ title: expanded ? "Collapse pods" : "Expand pods",
1234
+ "aria-label": `${expanded ? "Collapse" : "Expand"} pods for ${group.name}`,
1235
+ "aria-expanded": expanded,
1236
+ onClick: toggle,
1237
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, { material: expanded ? "expand_less" : "expand_more" })
1238
+ })
888
1239
  })
889
- })
1240
+ ]
890
1241
  })]
891
1242
  }) }),
892
1243
  /* @__PURE__ */ require_jsx_runtime.jsx("td", {
@@ -926,17 +1277,13 @@ function ConsumerRows({ group, clusterId, expanded, toggle, usage, depth }) {
926
1277
  type: "button",
927
1278
  title: "Open pod details",
928
1279
  onClick: (event) => openResource(pod, clusterId, event),
929
- children: [
930
- /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
931
- material: "view_in_ar",
932
- size: 14
933
- }),
934
- pod.metadata.name,
935
- /* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
936
- material: "open_in_new",
937
- size: 12
938
- })
939
- ]
1280
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
1281
+ material: "view_in_ar",
1282
+ size: 14
1283
+ }), /* @__PURE__ */ require_jsx_runtime.jsx("span", {
1284
+ className: "consumer-link-name",
1285
+ children: pod.metadata.name
1286
+ })]
940
1287
  }), /* @__PURE__ */ require_jsx_runtime.jsx(ConsumerStatus, { pods: [pod] })]
941
1288
  }), /* @__PURE__ */ require_jsx_runtime.jsxs("small", {
942
1289
  className: "consumer-subtitle",
@@ -980,7 +1327,7 @@ function podRequest(pod, metric) {
980
1327
  return "Unavailable";
981
1328
  }
982
1329
  }
983
- function NamespaceRows({ namespace, groups, clusterId, usage, open, toggleNamespace, expanded, toggleGroup }) {
1330
+ function NamespaceRows({ namespace, groups, clusterId, snapshotTime, usage, open, toggleNamespace, expanded, toggleGroup }) {
984
1331
  const sum = (metric) => groups.reduce((total, group) => {
985
1332
  const next = group[metric];
986
1333
  return {
@@ -1010,22 +1357,42 @@ function NamespaceRows({ namespace, groups, clusterId, usage, open, toggleNamesp
1010
1357
  className: "consumer-identity-main",
1011
1358
  children: [/* @__PURE__ */ require_jsx_runtime.jsxs("div", {
1012
1359
  className: "consumer-title-line",
1013
- children: [/* @__PURE__ */ require_jsx_runtime.jsxs("span", {
1014
- className: "namespace-identity",
1015
- children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
1016
- material: "folder_open",
1017
- size: 17
1018
- }), /* @__PURE__ */ require_jsx_runtime.jsx("span", {
1019
- className: "consumer-identity-label",
1020
- children: namespace
1021
- })]
1022
- }), /* @__PURE__ */ require_jsx_runtime.jsx(ConsumerStatus, { pods: groups.flatMap((group) => group.pods) })]
1360
+ children: [
1361
+ /* @__PURE__ */ require_jsx_runtime.jsxs("button", {
1362
+ type: "button",
1363
+ className: "consumer-resource-link namespace-identity",
1364
+ onClick: (event) => openResource({
1365
+ kind: "Namespace",
1366
+ metadata: { name: namespace }
1367
+ }, clusterId, event),
1368
+ title: "Open namespace",
1369
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(require_ai_icon.AiIcon, {
1370
+ material: "folder_open",
1371
+ size: 17
1372
+ }), /* @__PURE__ */ require_jsx_runtime.jsx("span", {
1373
+ className: "consumer-link-name",
1374
+ children: namespace
1375
+ })]
1376
+ }),
1377
+ /* @__PURE__ */ require_jsx_runtime.jsx(ConsumerStatus, { pods: groups.flatMap((group) => group.pods) }),
1378
+ /* @__PURE__ */ require_jsx_runtime.jsxs("small", { children: [
1379
+ groups.filter((group) => consumerStatus(group.pods).tone === "healthy").length,
1380
+ " ready ·",
1381
+ " ",
1382
+ groups.filter((group) => ["warning", "error"].includes(consumerStatus(group.pods).tone)).length,
1383
+ " ",
1384
+ "need attention · ",
1385
+ groups.filter((group) => consumerStatus(group.pods).tone === "neutral").length,
1386
+ " ",
1387
+ "unknown"
1388
+ ] })
1389
+ ]
1023
1390
  }), /* @__PURE__ */ require_jsx_runtime.jsxs("small", {
1024
1391
  className: "consumer-subtitle",
1025
1392
  title: "Resource totals include only consumers shown by the current filters",
1026
1393
  children: [
1027
1394
  groups.length,
1028
- " shown consumers",
1395
+ " filtered consumers",
1029
1396
  /* @__PURE__ */ require_jsx_runtime.jsx("span", { children: "·" }),
1030
1397
  groups.reduce((total, group) => total + group.pods.length, 0),
1031
1398
  " pods"
@@ -1069,6 +1436,7 @@ function NamespaceRows({ namespace, groups, clusterId, usage, open, toggleNamesp
1069
1436
  }), open && groups.map((group) => /* @__PURE__ */ require_jsx_runtime.jsx(ConsumerRows, {
1070
1437
  group,
1071
1438
  depth: namespace ? 1 : 0,
1439
+ snapshotTime,
1072
1440
  clusterId,
1073
1441
  expanded: expanded.includes(groupKey(group)),
1074
1442
  toggle: () => toggleGroup(groupKey(group)),
@@ -1082,6 +1450,10 @@ function podResourceGroup(pod, usage) {
1082
1450
  metadata: {
1083
1451
  ...pod.metadata,
1084
1452
  ownerReferences: []
1453
+ },
1454
+ status: {
1455
+ ...pod.status,
1456
+ phase: void 0
1085
1457
  }
1086
1458
  }], [], usage)[0];
1087
1459
  }