@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.
Files changed (88) hide show
  1. package/README.md +3 -1
  2. package/out/main/common/dashboard-picker.js +4 -4
  3. package/out/main/common/dashboard-picker.js.map +1 -1
  4. package/out/main/common/dashboard-readiness.js +17 -47
  5. package/out/main/common/dashboard-readiness.js.map +1 -1
  6. package/out/main/common/dashboard-selector.js +40 -6
  7. package/out/main/common/dashboard-selector.js.map +1 -1
  8. package/out/main/common/store/monitoring-preferences-store.js +5 -0
  9. package/out/main/common/store/monitoring-preferences-store.js.map +1 -1
  10. package/out/main/dashboard-picker.js +1 -4
  11. package/out/main/dashboard-picker.js.map +1 -1
  12. package/out/main/dashboard-preload.js +110 -0
  13. package/out/main/dashboard-preload.js.map +1 -0
  14. package/out/main/dashboard-views.js +207 -62
  15. package/out/main/dashboard-views.js.map +1 -1
  16. package/out/main/dock-icon.js +25 -10
  17. package/out/main/dock-icon.js.map +1 -1
  18. package/out/main/index.js +2 -0
  19. package/out/main/index.js.map +1 -1
  20. package/out/main/splash-accent.js +84 -0
  21. package/out/main/splash-accent.js.map +1 -0
  22. package/out/renderer/ai-diagnostic-handoff.js +52 -0
  23. package/out/renderer/ai-diagnostic-handoff.js.map +1 -0
  24. package/out/renderer/ai-extras.js +40 -4
  25. package/out/renderer/ai-extras.js.map +1 -1
  26. package/out/renderer/ai.js +48 -56
  27. package/out/renderer/ai.js.map +1 -1
  28. package/out/renderer/appearance.js +6 -2
  29. package/out/renderer/appearance.js.map +1 -1
  30. package/out/renderer/dashboard.js +26 -6
  31. package/out/renderer/dashboard.js.map +1 -1
  32. package/out/renderer/details/hpa-diagnostics-details.js +46 -0
  33. package/out/renderer/details/hpa-diagnostics-details.js.map +1 -0
  34. package/out/renderer/details/namespace-health-details.js +91 -0
  35. package/out/renderer/details/namespace-health-details.js.map +1 -0
  36. package/out/renderer/details/resource-health-details.js +131 -0
  37. package/out/renderer/details/resource-health-details.js.map +1 -0
  38. package/out/renderer/details/service-diagnostics-details.js +163 -0
  39. package/out/renderer/details/service-diagnostics-details.js.map +1 -0
  40. package/out/renderer/diagnostic-explain-button.js +16 -0
  41. package/out/renderer/diagnostic-explain-button.js.map +1 -0
  42. package/out/renderer/diagnostics-state.js +51 -0
  43. package/out/renderer/diagnostics-state.js.map +1 -0
  44. package/out/renderer/diagnostics.css.js +9 -0
  45. package/out/renderer/diagnostics.css.js.map +1 -1
  46. package/out/renderer/diagnostics.js +369 -73
  47. package/out/renderer/diagnostics.js.map +1 -1
  48. package/out/renderer/incident-copy-button.js +50 -0
  49. package/out/renderer/incident-copy-button.js.map +1 -0
  50. package/out/renderer/index.js +91 -19
  51. package/out/renderer/index.js.map +1 -1
  52. package/out/renderer/monitoring-history-collection.js +1 -1
  53. package/out/renderer/preferences/dashboard-preferences.js +16 -16
  54. package/out/renderer/preferences/dashboard-preferences.js.map +1 -1
  55. package/out/renderer/preferences/monitoring-preferences.js +10 -0
  56. package/out/renderer/preferences/monitoring-preferences.js.map +1 -1
  57. package/out/renderer/preferences/preferences.js +88 -0
  58. package/out/renderer/preferences/preferences.js.map +1 -1
  59. package/out/renderer/preferences/settings-modal.js.map +1 -1
  60. package/out/renderer/resource-relationships.css.js +7 -0
  61. package/out/renderer/resource-relationships.css.js.map +1 -0
  62. package/out/renderer/resource-relationships.js +556 -0
  63. package/out/renderer/resource-relationships.js.map +1 -0
  64. package/out/renderer/resource-search-distance.js +24 -0
  65. package/out/renderer/resource-search-distance.js.map +1 -0
  66. package/out/renderer/resource-search-fields.js +55 -0
  67. package/out/renderer/resource-search-fields.js.map +1 -0
  68. package/out/renderer/resource-search-model.js +193 -0
  69. package/out/renderer/resource-search-model.js.map +1 -0
  70. package/out/renderer/resource-search.js +365 -0
  71. package/out/renderer/resource-search.js.map +1 -0
  72. package/out/renderer/src/common/dashboard-picker.js.map +1 -1
  73. package/out/renderer/src/common/dashboard-selector.js.map +1 -1
  74. package/out/renderer/src/common/hpa-diagnostics.js +24 -0
  75. package/out/renderer/src/common/hpa-diagnostics.js.map +1 -0
  76. package/out/renderer/src/common/incident-summary.js +73 -0
  77. package/out/renderer/src/common/incident-summary.js.map +1 -0
  78. package/out/renderer/src/common/resource-health.js +92 -0
  79. package/out/renderer/src/common/resource-health.js.map +1 -0
  80. package/out/renderer/src/common/resource-relationships.js +155 -0
  81. package/out/renderer/src/common/resource-relationships.js.map +1 -0
  82. package/out/renderer/src/common/service-diagnostics.js +50 -0
  83. package/out/renderer/src/common/service-diagnostics.js.map +1 -0
  84. package/out/renderer/src/common/store/monitoring-preferences-store.js +5 -0
  85. package/out/renderer/src/common/store/monitoring-preferences-store.js.map +1 -1
  86. package/out/renderer/src/common/timeline-groups.js +36 -0
  87. package/out/renderer/src/common/timeline-groups.js.map +1 -0
  88. package/package.json +1 -1
@@ -0,0 +1,92 @@
1
+ //#region src/common/resource-health.ts
2
+ function summarizeObjectHealth(object) {
3
+ const status = object.status;
4
+ const conditions = status?.conditions ?? [];
5
+ const rows = [];
6
+ let ready;
7
+ let issue;
8
+ if (object.kind === "Pod") {
9
+ rows.push({
10
+ name: "Phase",
11
+ value: status?.phase ?? "Unknown"
12
+ });
13
+ const condition = conditions.find((condition) => condition.type === "Ready");
14
+ ready = condition?.status === "True" ? true : condition?.status === "False" ? false : void 0;
15
+ const containers = status?.containerStatuses ?? [];
16
+ rows.push({
17
+ name: "Readiness",
18
+ value: status?.phase === "Succeeded" ? "Completed" : ready === void 0 ? "Readiness unknown" : ready ? "Ready" : "Not ready"
19
+ });
20
+ rows.push({
21
+ name: "Containers ready",
22
+ value: `${containers.filter((container) => container.ready).length}/${object.spec?.containers?.length ?? "?"}`
23
+ });
24
+ rows.push({
25
+ name: "Restarts",
26
+ value: String([...containers, ...status?.initContainerStatuses ?? []].reduce((sum, container) => sum + container.restartCount, 0))
27
+ });
28
+ if (status?.phase === "Succeeded") ready = true;
29
+ if (status?.phase === "Failed") {
30
+ ready = false;
31
+ issue = "Pod failed";
32
+ }
33
+ } else if (object.kind === "Node") {
34
+ const condition = conditions.find((condition) => condition.type === "Ready");
35
+ ready = condition?.status === "True" ? true : condition?.status === "False" ? false : void 0;
36
+ rows.push({
37
+ name: "Readiness",
38
+ value: ready === void 0 ? "Readiness unknown" : ready ? "Ready" : "Not ready"
39
+ });
40
+ rows.push({
41
+ name: "Scheduling",
42
+ value: object.spec?.unschedulable ? "Cordoned" : "Enabled"
43
+ });
44
+ const pressure = conditions.find((condition) => /Pressure$|NetworkUnavailable/.test(condition.type) && condition.status === "True");
45
+ if (pressure) {
46
+ issue = pressure.reason || pressure.type;
47
+ ready = false;
48
+ }
49
+ } else if (object.kind === "Namespace") {
50
+ rows.push({
51
+ name: "Phase",
52
+ value: status?.phase ?? "Unknown"
53
+ });
54
+ ready = status?.phase ? status.phase === "Active" : void 0;
55
+ } else {
56
+ const desired = object.kind === "DaemonSet" ? status?.desiredNumberScheduled : object.spec?.replicas ?? 1;
57
+ const count = object.kind === "DaemonSet" ? status?.numberReady : status?.readyReplicas;
58
+ const observed = status?.observedGeneration;
59
+ const stale = object.metadata.generation !== void 0 && (observed === void 0 || observed < object.metadata.generation);
60
+ ready = status && desired !== void 0 ? (count ?? 0) >= desired && !stale : void 0;
61
+ rows.push({
62
+ name: "Ready replicas",
63
+ value: `${status ? count ?? 0 : "?"}/${desired ?? "?"}`
64
+ });
65
+ const updated = object.kind === "DaemonSet" ? status?.updatedNumberScheduled : status?.updatedReplicas;
66
+ rows.push({
67
+ name: "Updated replicas",
68
+ value: status ? String(updated ?? 0) : "Unknown"
69
+ });
70
+ if (stale) issue = "Controller has not observed the latest generation";
71
+ }
72
+ const failure = conditions.find((condition) => condition.type === "ReplicaFailure" && condition.status === "True" || [
73
+ "Ready",
74
+ "Available",
75
+ "Progressing",
76
+ "PodScheduled"
77
+ ].includes(condition.type) && condition.status === "False");
78
+ if (!issue && failure && !(object.kind === "Pod" && status?.phase === "Succeeded")) {
79
+ issue = failure.reason || `${failure.type}: ${failure.status}`;
80
+ ready = false;
81
+ }
82
+ if (!issue && ready === false) issue = object.kind === "Namespace" ? "Namespace is not active" : "Readiness target is not met";
83
+ return {
84
+ state: ready === void 0 ? "unknown" : ready ? "healthy" : "warning",
85
+ rows,
86
+ issue
87
+ };
88
+ }
89
+ //#endregion
90
+ exports.summarizeObjectHealth = summarizeObjectHealth;
91
+
92
+ //# sourceMappingURL=resource-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-health.js","names":[],"sources":["../../../../src/common/resource-health.ts"],"sourcesContent":["import type { DiagnosticsObject } from \"./resource-diagnostics\";\n\nexport type HealthObject = DiagnosticsObject & {\n spec?: DiagnosticsObject[\"spec\"] & { replicas?: number; minReadySeconds?: number };\n status?: DiagnosticsObject[\"status\"] & {\n replicas?: number;\n availableReplicas?: number;\n desiredNumberScheduled?: number;\n numberReady?: number;\n updatedNumberScheduled?: number;\n initContainerStatuses?: { name: string; restartCount: number; ready?: boolean }[];\n };\n};\nexport interface ObjectHealth {\n state: \"healthy\" | \"warning\" | \"unknown\";\n rows: { name: string; value: string }[];\n issue?: string;\n}\nexport function summarizeObjectHealth(object: HealthObject): ObjectHealth {\n const status = object.status;\n const conditions = status?.conditions ?? [];\n const rows: ObjectHealth[\"rows\"] = [];\n let ready: boolean | undefined;\n let issue: string | undefined;\n if (object.kind === \"Pod\") {\n rows.push({ name: \"Phase\", value: status?.phase ?? \"Unknown\" });\n const condition = conditions.find((condition) => condition.type === \"Ready\");\n ready = condition?.status === \"True\" ? true : condition?.status === \"False\" ? false : undefined;\n const containers = status?.containerStatuses ?? [];\n rows.push({\n name: \"Readiness\",\n value:\n status?.phase === \"Succeeded\"\n ? \"Completed\"\n : ready === undefined\n ? \"Readiness unknown\"\n : ready\n ? \"Ready\"\n : \"Not ready\",\n });\n rows.push({\n name: \"Containers ready\",\n value: `${containers.filter((container) => container.ready).length}/${object.spec?.containers?.length ?? \"?\"}`,\n });\n rows.push({\n name: \"Restarts\",\n value: String(\n [...containers, ...(status?.initContainerStatuses ?? [])].reduce(\n (sum, container) => sum + container.restartCount,\n 0,\n ),\n ),\n });\n if (status?.phase === \"Succeeded\") ready = true;\n if (status?.phase === \"Failed\") {\n ready = false;\n issue = \"Pod failed\";\n }\n } else if (object.kind === \"Node\") {\n const condition = conditions.find((condition) => condition.type === \"Ready\");\n ready = condition?.status === \"True\" ? true : condition?.status === \"False\" ? false : undefined;\n rows.push({ name: \"Readiness\", value: ready === undefined ? \"Readiness unknown\" : ready ? \"Ready\" : \"Not ready\" });\n rows.push({ name: \"Scheduling\", value: object.spec?.unschedulable ? \"Cordoned\" : \"Enabled\" });\n const pressure = conditions.find(\n (condition) => /Pressure$|NetworkUnavailable/.test(condition.type) && condition.status === \"True\",\n );\n if (pressure) {\n issue = pressure.reason || pressure.type;\n ready = false;\n }\n } else if (object.kind === \"Namespace\") {\n rows.push({ name: \"Phase\", value: status?.phase ?? \"Unknown\" });\n ready = status?.phase ? status.phase === \"Active\" : undefined;\n } else {\n const desired = object.kind === \"DaemonSet\" ? status?.desiredNumberScheduled : (object.spec?.replicas ?? 1);\n const count = object.kind === \"DaemonSet\" ? status?.numberReady : status?.readyReplicas;\n const observed = status?.observedGeneration;\n const stale =\n object.metadata.generation !== undefined && (observed === undefined || observed < object.metadata.generation);\n ready = status && desired !== undefined ? (count ?? 0) >= desired && !stale : undefined;\n rows.push({ name: \"Ready replicas\", value: `${status ? (count ?? 0) : \"?\"}/${desired ?? \"?\"}` });\n const updated = object.kind === \"DaemonSet\" ? status?.updatedNumberScheduled : status?.updatedReplicas;\n rows.push({ name: \"Updated replicas\", value: status ? String(updated ?? 0) : \"Unknown\" });\n if (stale) issue = \"Controller has not observed the latest generation\";\n }\n const failure = conditions.find(\n (condition) =>\n (condition.type === \"ReplicaFailure\" && condition.status === \"True\") ||\n ([\"Ready\", \"Available\", \"Progressing\", \"PodScheduled\"].includes(condition.type) && condition.status === \"False\"),\n );\n if (!issue && failure && !(object.kind === \"Pod\" && status?.phase === \"Succeeded\")) {\n issue = failure.reason || `${failure.type}: ${failure.status}`;\n ready = false;\n }\n if (!issue && ready === false)\n issue = object.kind === \"Namespace\" ? \"Namespace is not active\" : \"Readiness target is not met\";\n return { state: ready === undefined ? \"unknown\" : ready ? \"healthy\" : \"warning\", rows, issue };\n}\n"],"mappings":"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"}
@@ -0,0 +1,155 @@
1
+ const require_service_diagnostics = require("./service-diagnostics.js");
2
+ const require_incident_summary = require("./incident-summary.js");
3
+ //#region src/common/resource-relationships.ts
4
+ function relationshipReferencePath(kind, name, namespace) {
5
+ return require_incident_summary.incidentResourcePath({
6
+ kind,
7
+ name,
8
+ namespace
9
+ });
10
+ }
11
+ function buildResourceRelationships(service, inventory, focusedPod) {
12
+ const external = service.spec?.type === "ExternalName";
13
+ const diagnostics = require_service_diagnostics.diagnoseService(service, external ? [] : inventory.pods, external ? [] : inventory.slices);
14
+ const nodes = [];
15
+ const edges = [];
16
+ const gaps = [...inventory.gaps ?? []];
17
+ let omitted = 0;
18
+ const namespace = service.metadata.namespace;
19
+ const add = (node) => {
20
+ if (!nodes.some((n) => n.id === node.id)) nodes.push(node);
21
+ return node.id;
22
+ };
23
+ const svc = add({
24
+ id: "service",
25
+ kind: "Service",
26
+ name: service.metadata.name ?? "Unnamed",
27
+ role: "service",
28
+ detail: diagnostics.summary,
29
+ tone: "neutral",
30
+ selfLink: service.selfLink
31
+ });
32
+ const upstream = inventory.ingresses?.filter((ingress) => ingress.metadata.namespace === namespace && [
33
+ ingress.spec?.defaultBackend?.service?.name,
34
+ ingress.spec?.backend?.serviceName,
35
+ ...(ingress.spec?.rules ?? []).flatMap((rule) => (rule.http?.paths ?? []).map((path) => path.backend?.service?.name ?? path.backend?.serviceName))
36
+ ].includes(service.metadata.name)) ?? [];
37
+ omitted += Math.max(0, upstream.length - 5);
38
+ for (const ingress of upstream.slice(0, 5)) {
39
+ const id = add({
40
+ id: `ingress/${ingress.metadata.uid ?? ingress.metadata.name}`,
41
+ kind: "Ingress",
42
+ name: ingress.metadata.name ?? "Unnamed",
43
+ role: "upstream",
44
+ detail: "Backend Service reference",
45
+ tone: "neutral",
46
+ selfLink: ingress.selfLink
47
+ });
48
+ edges.push({
49
+ from: id,
50
+ to: svc,
51
+ label: "references backend"
52
+ });
53
+ }
54
+ const pods = diagnostics.matchingPods?.filter((pod) => !focusedPod || pod.metadata.name === focusedPod) ?? [];
55
+ omitted += Math.max(0, pods.length - 8);
56
+ for (const pod of pods.slice(0, 8)) {
57
+ const ready = pod.status?.conditions?.find((c) => c.type === "Ready");
58
+ const id = add({
59
+ id: `pod/${pod.metadata.uid ?? pod.metadata.name}`,
60
+ kind: "Pod",
61
+ name: pod.metadata.name ?? "Unnamed",
62
+ role: "backend",
63
+ detail: pod.status?.phase === "Succeeded" ? "Completed" : pod.status?.phase === "Failed" ? "Failed" : pod.metadata.deletionTimestamp ? "Terminating" : pod.status?.phase === "Pending" ? "Pending" : ready ? `Ready: ${ready.status}` : "Readiness unknown",
64
+ tone: pod.status?.phase === "Succeeded" ? "healthy" : pod.status?.phase === "Failed" || pod.status?.phase === "Pending" || pod.metadata.deletionTimestamp || ready?.status === "False" ? "warning" : ready?.status === "True" ? "healthy" : "neutral",
65
+ selfLink: pod.selfLink
66
+ });
67
+ edges.push({
68
+ from: svc,
69
+ to: id,
70
+ label: "selector matches"
71
+ });
72
+ if (pod.spec?.nodeName) {
73
+ const node = add({
74
+ id: `node/${pod.spec.nodeName}`,
75
+ kind: "Node",
76
+ name: pod.spec.nodeName,
77
+ role: "placement",
78
+ detail: "Assignment reference; node health not queried",
79
+ tone: "neutral",
80
+ selfLink: relationshipReferencePath("Node", pod.spec.nodeName),
81
+ inferred: true
82
+ });
83
+ edges.push({
84
+ from: id,
85
+ to: node,
86
+ label: "assigned to"
87
+ });
88
+ }
89
+ let child = id;
90
+ let refs = pod.metadata.ownerReferences;
91
+ const visited = /* @__PURE__ */ new Set();
92
+ for (let depth = 0; depth < 4; depth++) {
93
+ const ref = refs?.find((r) => r.controller) ?? (refs?.length === 1 ? refs[0] : void 0);
94
+ if (!ref || visited.has(ref.uid)) break;
95
+ visited.add(ref.uid);
96
+ const owner = inventory.owners?.find((o) => o.metadata.uid === ref.uid && o.metadata.namespace === namespace);
97
+ const ownerId = add({
98
+ id: `owner/${ref.uid}`,
99
+ kind: ref.kind ?? "Owner",
100
+ name: ref.name ?? "Unnamed",
101
+ role: "placement",
102
+ detail: owner ? "Controller object observed" : "Owner reference only; object not observed",
103
+ tone: "neutral",
104
+ selfLink: owner?.selfLink ?? relationshipReferencePath(ref.kind ?? "", ref.name ?? "", namespace) ?? relationshipReferencePath(ref.kind ?? "", ref.name ?? ""),
105
+ inferred: !owner
106
+ });
107
+ edges.push({
108
+ from: child,
109
+ to: ownerId,
110
+ label: "owned by"
111
+ });
112
+ child = ownerId;
113
+ refs = owner?.metadata.ownerReferences;
114
+ }
115
+ }
116
+ const slices = diagnostics.matchingSlices ?? [];
117
+ omitted += Math.max(0, slices.length - 5);
118
+ for (const slice of slices.slice(0, 5)) {
119
+ const ready = slice.endpoints?.filter((e) => e.conditions?.ready !== false && !e.conditions?.terminating).length;
120
+ const id = add({
121
+ id: `slice/${slice.metadata.name}`,
122
+ kind: "EndpointSlice",
123
+ name: slice.metadata.name ?? "Unnamed",
124
+ role: "backend",
125
+ detail: `${slice.addressType} · ${ready ?? "unknown"} ready endpoint entries`,
126
+ tone: ready === 0 ? "warning" : "neutral",
127
+ selfLink: slice.selfLink
128
+ });
129
+ edges.push({
130
+ from: svc,
131
+ to: id,
132
+ label: "published in"
133
+ });
134
+ }
135
+ if (!external && !inventory.pods) gaps.push("Pod inventory unavailable; selector relationships are unknown");
136
+ if (!inventory.slices && !diagnostics.external) gaps.push("EndpointSlice inventory unavailable; published backends are unknown");
137
+ if (!inventory.ingresses) gaps.push("Ingress inventory unavailable; upstream references are unknown");
138
+ return {
139
+ nodes,
140
+ edges: [...new Map(edges.map((edge) => [JSON.stringify([
141
+ edge.from,
142
+ edge.to,
143
+ edge.label
144
+ ]), edge])).values()],
145
+ omitted,
146
+ gaps: [...new Set(gaps)],
147
+ diagnostics,
148
+ podOptions: diagnostics.matchingPods?.map((p) => p.metadata.name).filter((name) => !!name) ?? []
149
+ };
150
+ }
151
+ //#endregion
152
+ exports.buildResourceRelationships = buildResourceRelationships;
153
+ exports.relationshipReferencePath = relationshipReferencePath;
154
+
155
+ //# sourceMappingURL=resource-relationships.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-relationships.js","names":[],"sources":["../../../../src/common/resource-relationships.ts"],"sourcesContent":["import { incidentResourcePath } from \"./incident-summary\";\nimport {\n diagnoseService,\n type ServiceDiagnosticObject,\n type ServiceDiagnosticPod,\n type ServiceDiagnosticSlice,\n} from \"./service-diagnostics\";\n\nexport interface RelationshipMetadata {\n uid?: string;\n name?: string;\n namespace?: string;\n ownerReferences?: { uid: string; name?: string; kind?: string; controller?: boolean }[];\n}\nexport interface RelationshipObject {\n kind?: string;\n metadata: RelationshipMetadata;\n selfLink?: string;\n}\nexport interface RelationshipPod extends ServiceDiagnosticPod {\n metadata: ServiceDiagnosticPod[\"metadata\"] & RelationshipMetadata;\n spec?: ServiceDiagnosticPod[\"spec\"] & { nodeName?: string };\n status?: ServiceDiagnosticPod[\"status\"] & { phase?: string };\n selfLink?: string;\n}\nexport interface RelationshipSlice extends ServiceDiagnosticSlice {\n selfLink?: string;\n}\nexport interface RelationshipIngress extends RelationshipObject {\n spec?: {\n defaultBackend?: { service?: { name: string } };\n backend?: { serviceName?: string };\n rules?: { http?: { paths?: { backend?: { service?: { name: string }; serviceName?: string } }[] } }[];\n };\n}\nexport interface RelationshipNode {\n id: string;\n kind: string;\n name: string;\n role: \"upstream\" | \"service\" | \"backend\" | \"placement\";\n detail: string;\n tone: \"neutral\" | \"warning\" | \"healthy\";\n selfLink?: string;\n inferred?: boolean;\n}\nexport interface RelationshipEdge {\n from: string;\n to: string;\n label: string;\n}\nexport interface RelationshipInventory {\n pods?: RelationshipPod[];\n slices?: RelationshipSlice[];\n ingresses?: RelationshipIngress[];\n owners?: RelationshipObject[];\n gaps?: string[];\n}\nexport function relationshipReferencePath(kind: string, name: string, namespace?: string) {\n return incidentResourcePath({ kind, name, namespace });\n}\nexport function buildResourceRelationships(\n service: ServiceDiagnosticObject & { selfLink?: string },\n inventory: RelationshipInventory,\n focusedPod?: string,\n) {\n const external = service.spec?.type === \"ExternalName\";\n const diagnostics = diagnoseService(service, external ? [] : inventory.pods, external ? [] : inventory.slices);\n const nodes: RelationshipNode[] = [];\n const edges: RelationshipEdge[] = [];\n const gaps = [...(inventory.gaps ?? [])];\n let omitted = 0;\n const namespace = service.metadata.namespace;\n const add = (node: RelationshipNode) => {\n if (!nodes.some((n) => n.id === node.id)) nodes.push(node);\n return node.id;\n };\n const svc = add({\n id: \"service\",\n kind: \"Service\",\n name: service.metadata.name ?? \"Unnamed\",\n role: \"service\",\n detail: diagnostics.summary,\n tone: \"neutral\",\n selfLink: service.selfLink,\n });\n const upstream =\n inventory.ingresses?.filter(\n (ingress) =>\n ingress.metadata.namespace === namespace &&\n [\n ingress.spec?.defaultBackend?.service?.name,\n ingress.spec?.backend?.serviceName,\n ...(ingress.spec?.rules ?? []).flatMap((rule) =>\n (rule.http?.paths ?? []).map((path) => path.backend?.service?.name ?? path.backend?.serviceName),\n ),\n ].includes(service.metadata.name),\n ) ?? [];\n omitted += Math.max(0, upstream.length - 5);\n for (const ingress of upstream.slice(0, 5)) {\n const id = add({\n id: `ingress/${ingress.metadata.uid ?? ingress.metadata.name}`,\n kind: \"Ingress\",\n name: ingress.metadata.name ?? \"Unnamed\",\n role: \"upstream\",\n detail: \"Backend Service reference\",\n tone: \"neutral\",\n selfLink: ingress.selfLink,\n });\n edges.push({ from: id, to: svc, label: \"references backend\" });\n }\n const pods = diagnostics.matchingPods?.filter((pod) => !focusedPod || pod.metadata.name === focusedPod) ?? [];\n omitted += Math.max(0, pods.length - 8);\n for (const pod of pods.slice(0, 8)) {\n const ready = pod.status?.conditions?.find((c) => c.type === \"Ready\");\n const id = add({\n id: `pod/${pod.metadata.uid ?? pod.metadata.name}`,\n kind: \"Pod\",\n name: pod.metadata.name ?? \"Unnamed\",\n role: \"backend\",\n detail:\n pod.status?.phase === \"Succeeded\"\n ? \"Completed\"\n : pod.status?.phase === \"Failed\"\n ? \"Failed\"\n : pod.metadata.deletionTimestamp\n ? \"Terminating\"\n : pod.status?.phase === \"Pending\"\n ? \"Pending\"\n : ready\n ? `Ready: ${ready.status}`\n : \"Readiness unknown\",\n tone:\n pod.status?.phase === \"Succeeded\"\n ? \"healthy\"\n : pod.status?.phase === \"Failed\" ||\n pod.status?.phase === \"Pending\" ||\n pod.metadata.deletionTimestamp ||\n ready?.status === \"False\"\n ? \"warning\"\n : ready?.status === \"True\"\n ? \"healthy\"\n : \"neutral\",\n selfLink: pod.selfLink,\n });\n edges.push({ from: svc, to: id, label: \"selector matches\" });\n if (pod.spec?.nodeName) {\n const node = add({\n id: `node/${pod.spec.nodeName}`,\n kind: \"Node\",\n name: pod.spec.nodeName,\n role: \"placement\",\n detail: \"Assignment reference; node health not queried\",\n tone: \"neutral\",\n selfLink: relationshipReferencePath(\"Node\", pod.spec.nodeName),\n inferred: true,\n });\n edges.push({ from: id, to: node, label: \"assigned to\" });\n }\n let child = id;\n let refs = pod.metadata.ownerReferences;\n const visited = new Set<string>();\n for (let depth = 0; depth < 4; depth++) {\n const ref = refs?.find((r) => r.controller) ?? (refs?.length === 1 ? refs[0] : undefined);\n if (!ref || visited.has(ref.uid)) break;\n visited.add(ref.uid);\n const owner = inventory.owners?.find((o) => o.metadata.uid === ref.uid && o.metadata.namespace === namespace);\n const ownerId = add({\n id: `owner/${ref.uid}`,\n kind: ref.kind ?? \"Owner\",\n name: ref.name ?? \"Unnamed\",\n role: \"placement\",\n detail: owner ? \"Controller object observed\" : \"Owner reference only; object not observed\",\n tone: \"neutral\",\n selfLink:\n owner?.selfLink ??\n relationshipReferencePath(ref.kind ?? \"\", ref.name ?? \"\", namespace) ??\n relationshipReferencePath(ref.kind ?? \"\", ref.name ?? \"\"),\n inferred: !owner,\n });\n edges.push({ from: child, to: ownerId, label: \"owned by\" });\n child = ownerId;\n refs = owner?.metadata.ownerReferences;\n }\n }\n const slices = diagnostics.matchingSlices ?? [];\n omitted += Math.max(0, slices.length - 5);\n for (const slice of slices.slice(0, 5)) {\n const ready = slice.endpoints?.filter((e) => e.conditions?.ready !== false && !e.conditions?.terminating).length;\n const id = add({\n id: `slice/${slice.metadata.name}`,\n kind: \"EndpointSlice\",\n name: slice.metadata.name ?? \"Unnamed\",\n role: \"backend\",\n detail: `${slice.addressType} · ${ready ?? \"unknown\"} ready endpoint entries`,\n tone: ready === 0 ? \"warning\" : \"neutral\",\n selfLink: slice.selfLink,\n });\n edges.push({ from: svc, to: id, label: \"published in\" });\n }\n if (!external && !inventory.pods) gaps.push(\"Pod inventory unavailable; selector relationships are unknown\");\n if (!inventory.slices && !diagnostics.external)\n gaps.push(\"EndpointSlice inventory unavailable; published backends are unknown\");\n if (!inventory.ingresses) gaps.push(\"Ingress inventory unavailable; upstream references are unknown\");\n return {\n nodes,\n edges: [...new Map(edges.map((edge) => [JSON.stringify([edge.from, edge.to, edge.label]), edge])).values()],\n omitted,\n gaps: [...new Set(gaps)],\n diagnostics,\n podOptions: diagnostics.matchingPods?.map((p) => p.metadata.name).filter((name): name is string => !!name) ?? [],\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ //#region src/common/service-diagnostics.ts
2
+ function diagnoseService(service, pods, slices) {
3
+ const selector = Object.entries(service.spec?.selector ?? {});
4
+ const external = service.spec?.type === "ExternalName";
5
+ const matchingPods = selector.length ? pods?.filter((p) => p.metadata.namespace === service.metadata.namespace && selector.every(([k, v]) => p.metadata.labels?.[k] === v)) : void 0;
6
+ const readyPods = matchingPods?.filter((p) => !p.metadata.deletionTimestamp && p.status?.conditions?.some((c) => c.type === "Ready" && c.status === "True"));
7
+ const matchingSlices = slices?.filter((s) => s.metadata.namespace === service.metadata.namespace && s.metadata.labels?.["kubernetes.io/service-name"] === service.metadata.name);
8
+ const families = [...new Set(matchingSlices?.map((s) => s.addressType) ?? [])];
9
+ const readyEndpoints = matchingSlices?.reduce((sum, s) => sum + (s.endpoints?.filter((e) => e.conditions?.ready !== false && !e.conditions?.terminating && e.addresses.length).length ?? 0), 0);
10
+ const limitations = ["API configuration and readiness evidence only; network reachability is not tested"];
11
+ if (pods === void 0 && selector.length) limitations.push("Pod data unavailable; access may be restricted");
12
+ if (slices === void 0 && !external) limitations.push("EndpointSlice data unavailable; access may be restricted");
13
+ if (!selector.length && !external) limitations.push("Selectorless service: endpoints are managed separately; pod matching is not applicable");
14
+ if (service.spec?.publishNotReadyAddresses) limitations.push("Service publishes not-ready addresses; endpoint readiness does not establish pod readiness");
15
+ if (families.length > 1) limitations.push("Endpoint counts include separate address families and may count the same backend more than once");
16
+ for (const family of service.spec?.ipFamilies ?? []) if (matchingSlices && !families.includes(family)) limitations.push(`No EndpointSlice evidence for configured address family ${family}`);
17
+ const ports = (service.spec?.ports ?? []).map((port) => {
18
+ const target = port.targetPort ?? port.port;
19
+ const protocol = port.protocol ?? "TCP";
20
+ const unresolved = typeof target === "string" ? matchingPods?.filter((p) => ![...p.spec?.containers ?? [], ...(p.spec?.initContainers ?? []).filter((c) => c.restartPolicy === "Always")].some((c) => c.ports?.some((p) => p.name === target && (p.protocol ?? "TCP") === protocol))).length : void 0;
21
+ const slicePorts = matchingSlices?.flatMap((s) => (s.ports ?? []).filter((p) => (p.name ?? "") === (port.name ?? "") && (p.protocol ?? "TCP") === protocol).flatMap((p) => p.port == null ? [] : [p.port]));
22
+ return {
23
+ name: port.name ?? String(port.port),
24
+ target,
25
+ protocol,
26
+ unresolved,
27
+ endpointPorts: [...new Set(slicePorts ?? [])],
28
+ missingEndpointPort: matchingSlices !== void 0 && !slicePorts?.length,
29
+ numericMismatch: typeof target === "number" && !!slicePorts?.some((value) => value !== target)
30
+ };
31
+ });
32
+ const missingEndpointPorts = ports.some((port) => port.missingEndpointPort);
33
+ if (missingEndpointPorts) limitations.push("Matching EndpointSlice port and protocol evidence is missing for one or more Service ports");
34
+ return {
35
+ summary: external ? "ExternalName delegates service discovery to DNS" : !selector.length ? "Inspect manually managed EndpointSlices" : matchingPods === void 0 || matchingSlices === void 0 ? "Partial evidence; backend health cannot be established" : !matchingPods.length ? "Selector matches no pods" : !readyEndpoints ? "No ready EndpointSlice backends reported" : missingEndpointPorts ? "Partial evidence; matching EndpointSlice port and protocol are unavailable" : `${readyPods?.length ?? 0}/${matchingPods.length} selected pods ready; ${readyEndpoints} ready endpoint entries`,
36
+ external,
37
+ missingEndpointPorts,
38
+ matchingPods,
39
+ readyPods,
40
+ matchingSlices,
41
+ readyEndpoints,
42
+ families,
43
+ ports,
44
+ limitations
45
+ };
46
+ }
47
+ //#endregion
48
+ exports.diagnoseService = diagnoseService;
49
+
50
+ //# sourceMappingURL=service-diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-diagnostics.js","names":[],"sources":["../../../../src/common/service-diagnostics.ts"],"sourcesContent":["export interface ServiceDiagnosticObject {\n metadata: { name?: string; namespace?: string; labels?: Partial<Record<string, string>> };\n spec?: {\n type?: string;\n externalName?: string;\n selector?: Partial<Record<string, string>>;\n publishNotReadyAddresses?: boolean;\n ipFamilies?: string[];\n ports?: { name?: string; port: number; targetPort?: string | number; protocol?: string }[];\n };\n}\nexport interface ServiceDiagnosticPod {\n metadata: { name?: string; namespace?: string; labels?: Partial<Record<string, string>>; deletionTimestamp?: string };\n spec?: {\n containers?: { ports?: { name?: string; containerPort: number; protocol?: string }[] }[];\n initContainers?: {\n restartPolicy?: string;\n ports?: { name?: string; containerPort: number; protocol?: string }[];\n }[];\n };\n status?: { conditions?: { type: string; status: string }[] };\n}\nexport interface ServiceDiagnosticSlice {\n metadata: { name?: string; namespace?: string; labels?: Partial<Record<string, string>> };\n addressType: string;\n endpoints?: { addresses: string[]; conditions?: { ready?: boolean; terminating?: boolean } }[] | null;\n ports?: { name?: string; port?: number | null; protocol?: string }[] | null;\n}\nexport function diagnoseService<P extends ServiceDiagnosticPod, S extends ServiceDiagnosticSlice>(\n service: ServiceDiagnosticObject,\n pods: P[] | undefined,\n slices: S[] | undefined,\n) {\n const selector = Object.entries(service.spec?.selector ?? {});\n const external = service.spec?.type === \"ExternalName\";\n const matchingPods = selector.length\n ? pods?.filter(\n (p) =>\n p.metadata.namespace === service.metadata.namespace &&\n selector.every(([k, v]) => p.metadata.labels?.[k] === v),\n )\n : undefined;\n const readyPods = matchingPods?.filter(\n (p) =>\n !p.metadata.deletionTimestamp && p.status?.conditions?.some((c) => c.type === \"Ready\" && c.status === \"True\"),\n );\n const matchingSlices = slices?.filter(\n (s) =>\n s.metadata.namespace === service.metadata.namespace &&\n s.metadata.labels?.[\"kubernetes.io/service-name\"] === service.metadata.name,\n );\n const families = [...new Set(matchingSlices?.map((s) => s.addressType) ?? [])];\n const readyEndpoints = matchingSlices?.reduce(\n (sum, s) =>\n sum +\n (s.endpoints?.filter((e) => e.conditions?.ready !== false && !e.conditions?.terminating && e.addresses.length)\n .length ?? 0),\n 0,\n );\n const limitations = [\"API configuration and readiness evidence only; network reachability is not tested\"];\n if (pods === undefined && selector.length) limitations.push(\"Pod data unavailable; access may be restricted\");\n if (slices === undefined && !external) limitations.push(\"EndpointSlice data unavailable; access may be restricted\");\n if (!selector.length && !external)\n limitations.push(\"Selectorless service: endpoints are managed separately; pod matching is not applicable\");\n if (service.spec?.publishNotReadyAddresses)\n limitations.push(\"Service publishes not-ready addresses; endpoint readiness does not establish pod readiness\");\n if (families.length > 1)\n limitations.push(\"Endpoint counts include separate address families and may count the same backend more than once\");\n for (const family of service.spec?.ipFamilies ?? []) {\n if (matchingSlices && !families.includes(family))\n limitations.push(`No EndpointSlice evidence for configured address family ${family}`);\n }\n const ports = (service.spec?.ports ?? []).map((port) => {\n const target = port.targetPort ?? port.port;\n const protocol = port.protocol ?? \"TCP\";\n const unresolved =\n typeof target === \"string\"\n ? matchingPods?.filter(\n (p) =>\n ![\n ...(p.spec?.containers ?? []),\n ...(p.spec?.initContainers ?? []).filter((c) => c.restartPolicy === \"Always\"),\n ].some((c) => c.ports?.some((p) => p.name === target && (p.protocol ?? \"TCP\") === protocol)),\n ).length\n : undefined;\n const slicePorts = matchingSlices?.flatMap((s) =>\n (s.ports ?? [])\n .filter((p) => (p.name ?? \"\") === (port.name ?? \"\") && (p.protocol ?? \"TCP\") === protocol)\n .flatMap((p) => (p.port == null ? [] : [p.port])),\n );\n return {\n name: port.name ?? String(port.port),\n target,\n protocol,\n unresolved,\n endpointPorts: [...new Set(slicePorts ?? [])],\n missingEndpointPort: matchingSlices !== undefined && !slicePorts?.length,\n numericMismatch: typeof target === \"number\" && !!slicePorts?.some((value) => value !== target),\n };\n });\n const missingEndpointPorts = ports.some((port) => port.missingEndpointPort);\n if (missingEndpointPorts)\n limitations.push(\"Matching EndpointSlice port and protocol evidence is missing for one or more Service ports\");\n const summary = external\n ? \"ExternalName delegates service discovery to DNS\"\n : !selector.length\n ? \"Inspect manually managed EndpointSlices\"\n : matchingPods === undefined || matchingSlices === undefined\n ? \"Partial evidence; backend health cannot be established\"\n : !matchingPods.length\n ? \"Selector matches no pods\"\n : !readyEndpoints\n ? \"No ready EndpointSlice backends reported\"\n : missingEndpointPorts\n ? \"Partial evidence; matching EndpointSlice port and protocol are unavailable\"\n : `${readyPods?.length ?? 0}/${matchingPods.length} selected pods ready; ${readyEndpoints} ready endpoint entries`;\n return {\n summary,\n external,\n missingEndpointPorts,\n matchingPods,\n readyPods,\n matchingSlices,\n readyEndpoints,\n families,\n ports,\n limitations,\n };\n}\n"],"mappings":"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"}
@@ -5,6 +5,7 @@ const require_monitoring_history = require("../monitoring-history.js");
5
5
  var MonitoringPreferencesStore = class extends require_extensions.Common.Store.ExtensionStore {
6
6
  enabled = false;
7
7
  diagnosticsVisible = true;
8
+ relationshipsVisible = true;
8
9
  historyEnabled = false;
9
10
  trackedHistory = [];
10
11
  constructor() {
@@ -13,6 +14,7 @@ var MonitoringPreferencesStore = class extends require_extensions.Common.Store.E
13
14
  defaults: {
14
15
  enabled: false,
15
16
  diagnosticsVisible: true,
17
+ relationshipsVisible: true,
16
18
  historyEnabled: false,
17
19
  trackedHistory: []
18
20
  }
@@ -20,6 +22,7 @@ var MonitoringPreferencesStore = class extends require_extensions.Common.Store.E
20
22
  require__global_external_mobx.makeObservable(this, {
21
23
  enabled: require__global_external_mobx.observable,
22
24
  diagnosticsVisible: require__global_external_mobx.observable,
25
+ relationshipsVisible: require__global_external_mobx.observable,
23
26
  historyEnabled: require__global_external_mobx.observable,
24
27
  trackedHistory: require__global_external_mobx.observable,
25
28
  fromStore: require__global_external_mobx.action
@@ -28,6 +31,7 @@ var MonitoringPreferencesStore = class extends require_extensions.Common.Store.E
28
31
  fromStore(data) {
29
32
  this.enabled = data?.enabled === true;
30
33
  this.diagnosticsVisible = data?.diagnosticsVisible !== false;
34
+ this.relationshipsVisible = data?.relationshipsVisible !== false;
31
35
  this.historyEnabled = data?.historyEnabled === true;
32
36
  this.trackedHistory = Array.isArray(data?.trackedHistory) ? data.trackedHistory.filter(require_monitoring_history.validHistoryIdentity).slice(0, 100).map(({ clusterId, scope, uid }) => ({
33
37
  clusterId,
@@ -39,6 +43,7 @@ var MonitoringPreferencesStore = class extends require_extensions.Common.Store.E
39
43
  return {
40
44
  enabled: this.enabled,
41
45
  diagnosticsVisible: this.diagnosticsVisible,
46
+ relationshipsVisible: this.relationshipsVisible,
42
47
  historyEnabled: this.historyEnabled,
43
48
  trackedHistory: this.trackedHistory.map(({ clusterId, scope, uid }) => ({
44
49
  clusterId,
@@ -1 +1 @@
1
- {"version":3,"file":"monitoring-preferences-store.js","names":[],"sources":["../../../../../src/common/store/monitoring-preferences-store.ts"],"sourcesContent":["import { Common } from \"@freelensapp/extensions\";\nimport { action, makeObservable, observable } from \"mobx\";\nimport { type HistoryIdentity, validHistoryIdentity } from \"../monitoring-history\";\n\nexport class MonitoringPreferencesStore extends Common.Store.ExtensionStore<{\n enabled: boolean;\n diagnosticsVisible: boolean;\n historyEnabled: boolean;\n trackedHistory: HistoryIdentity[];\n}> {\n enabled = false;\n diagnosticsVisible = true;\n historyEnabled = false;\n trackedHistory: HistoryIdentity[] = [];\n\n constructor() {\n super({\n configName: \"monitoring-preferences\",\n defaults: { enabled: false, diagnosticsVisible: true, historyEnabled: false, trackedHistory: [] },\n });\n makeObservable(this, {\n enabled: observable,\n diagnosticsVisible: observable,\n historyEnabled: observable,\n trackedHistory: observable,\n fromStore: action,\n });\n }\n\n fromStore(data: {\n enabled?: boolean;\n diagnosticsVisible?: boolean;\n historyEnabled?: boolean;\n trackedHistory?: HistoryIdentity[];\n }): void {\n this.enabled = data?.enabled === true;\n this.diagnosticsVisible = data?.diagnosticsVisible !== false;\n this.historyEnabled = data?.historyEnabled === true;\n this.trackedHistory = Array.isArray(data?.trackedHistory)\n ? data.trackedHistory\n .filter(validHistoryIdentity)\n .slice(0, 100)\n .map(({ clusterId, scope, uid }) => ({ clusterId, scope, uid }))\n : [];\n }\n\n toJSON(): {\n enabled: boolean;\n diagnosticsVisible: boolean;\n historyEnabled: boolean;\n trackedHistory: HistoryIdentity[];\n } {\n return {\n enabled: this.enabled,\n diagnosticsVisible: this.diagnosticsVisible,\n historyEnabled: this.historyEnabled,\n trackedHistory: this.trackedHistory.map(({ clusterId, scope, uid }) => ({ clusterId, scope, uid })),\n };\n }\n}\n"],"mappings":";;;;AAIA,IAAa,6BAAb,cAAgD,mBAAA,OAAO,MAAM,eAK1D;CACD,UAAU;CACV,qBAAqB;CACrB,iBAAiB;CACjB,iBAAoC,CAAC;CAErC,cAAc;EACZ,MAAM;GACJ,YAAY;GACZ,UAAU;IAAE,SAAS;IAAO,oBAAoB;IAAM,gBAAgB;IAAO,gBAAgB,CAAC;GAAE;EAClG,CAAC;EACD,8BAAA,eAAe,MAAM;GACnB,SAAS,8BAAA;GACT,oBAAoB,8BAAA;GACpB,gBAAgB,8BAAA;GAChB,gBAAgB,8BAAA;GAChB,WAAW,8BAAA;EACb,CAAC;CACH;CAEA,UAAU,MAKD;EACP,KAAK,UAAU,MAAM,YAAY;EACjC,KAAK,qBAAqB,MAAM,uBAAuB;EACvD,KAAK,iBAAiB,MAAM,mBAAmB;EAC/C,KAAK,iBAAiB,MAAM,QAAQ,MAAM,cAAc,IACpD,KAAK,eACF,OAAO,2BAAA,oBAAoB,CAAC,CAC5B,MAAM,GAAG,GAAG,CAAC,CACb,KAAK,EAAE,WAAW,OAAO,WAAW;GAAE;GAAW;GAAO;EAAI,EAAE,IACjE,CAAC;CACP;CAEA,SAKE;EACA,OAAO;GACL,SAAS,KAAK;GACd,oBAAoB,KAAK;GACzB,gBAAgB,KAAK;GACrB,gBAAgB,KAAK,eAAe,KAAK,EAAE,WAAW,OAAO,WAAW;IAAE;IAAW;IAAO;GAAI,EAAE;EACpG;CACF;AACF"}
1
+ {"version":3,"file":"monitoring-preferences-store.js","names":[],"sources":["../../../../../src/common/store/monitoring-preferences-store.ts"],"sourcesContent":["import { Common } from \"@freelensapp/extensions\";\nimport { action, makeObservable, observable } from \"mobx\";\nimport { type HistoryIdentity, validHistoryIdentity } from \"../monitoring-history\";\n\nexport class MonitoringPreferencesStore extends Common.Store.ExtensionStore<{\n enabled: boolean;\n diagnosticsVisible: boolean;\n relationshipsVisible: boolean;\n historyEnabled: boolean;\n trackedHistory: HistoryIdentity[];\n}> {\n enabled = false;\n diagnosticsVisible = true;\n relationshipsVisible = true;\n historyEnabled = false;\n trackedHistory: HistoryIdentity[] = [];\n\n constructor() {\n super({\n configName: \"monitoring-preferences\",\n defaults: {\n enabled: false,\n diagnosticsVisible: true,\n relationshipsVisible: true,\n historyEnabled: false,\n trackedHistory: [],\n },\n });\n makeObservable(this, {\n enabled: observable,\n diagnosticsVisible: observable,\n relationshipsVisible: observable,\n historyEnabled: observable,\n trackedHistory: observable,\n fromStore: action,\n });\n }\n\n fromStore(data: {\n enabled?: boolean;\n diagnosticsVisible?: boolean;\n relationshipsVisible?: boolean;\n historyEnabled?: boolean;\n trackedHistory?: HistoryIdentity[];\n }): void {\n this.enabled = data?.enabled === true;\n this.diagnosticsVisible = data?.diagnosticsVisible !== false;\n this.relationshipsVisible = data?.relationshipsVisible !== false;\n this.historyEnabled = data?.historyEnabled === true;\n this.trackedHistory = Array.isArray(data?.trackedHistory)\n ? data.trackedHistory\n .filter(validHistoryIdentity)\n .slice(0, 100)\n .map(({ clusterId, scope, uid }) => ({ clusterId, scope, uid }))\n : [];\n }\n\n toJSON(): {\n enabled: boolean;\n diagnosticsVisible: boolean;\n relationshipsVisible: boolean;\n historyEnabled: boolean;\n trackedHistory: HistoryIdentity[];\n } {\n return {\n enabled: this.enabled,\n diagnosticsVisible: this.diagnosticsVisible,\n relationshipsVisible: this.relationshipsVisible,\n historyEnabled: this.historyEnabled,\n trackedHistory: this.trackedHistory.map(({ clusterId, scope, uid }) => ({ clusterId, scope, uid })),\n };\n }\n}\n"],"mappings":";;;;AAIA,IAAa,6BAAb,cAAgD,mBAAA,OAAO,MAAM,eAM1D;CACD,UAAU;CACV,qBAAqB;CACrB,uBAAuB;CACvB,iBAAiB;CACjB,iBAAoC,CAAC;CAErC,cAAc;EACZ,MAAM;GACJ,YAAY;GACZ,UAAU;IACR,SAAS;IACT,oBAAoB;IACpB,sBAAsB;IACtB,gBAAgB;IAChB,gBAAgB,CAAC;GACnB;EACF,CAAC;EACD,8BAAA,eAAe,MAAM;GACnB,SAAS,8BAAA;GACT,oBAAoB,8BAAA;GACpB,sBAAsB,8BAAA;GACtB,gBAAgB,8BAAA;GAChB,gBAAgB,8BAAA;GAChB,WAAW,8BAAA;EACb,CAAC;CACH;CAEA,UAAU,MAMD;EACP,KAAK,UAAU,MAAM,YAAY;EACjC,KAAK,qBAAqB,MAAM,uBAAuB;EACvD,KAAK,uBAAuB,MAAM,yBAAyB;EAC3D,KAAK,iBAAiB,MAAM,mBAAmB;EAC/C,KAAK,iBAAiB,MAAM,QAAQ,MAAM,cAAc,IACpD,KAAK,eACF,OAAO,2BAAA,oBAAoB,CAAC,CAC5B,MAAM,GAAG,GAAG,CAAC,CACb,KAAK,EAAE,WAAW,OAAO,WAAW;GAAE;GAAW;GAAO;EAAI,EAAE,IACjE,CAAC;CACP;CAEA,SAME;EACA,OAAO;GACL,SAAS,KAAK;GACd,oBAAoB,KAAK;GACzB,sBAAsB,KAAK;GAC3B,gBAAgB,KAAK;GACrB,gBAAgB,KAAK,eAAe,KAAK,EAAE,WAAW,OAAO,WAAW;IAAE;IAAW;IAAO;GAAI,EAAE;EACpG;CACF;AACF"}
@@ -0,0 +1,36 @@
1
+ //#region src/common/timeline-groups.ts
2
+ function groupTimelineEntries(entries, identity, resource) {
3
+ const groups = /* @__PURE__ */ new Map();
4
+ for (const entry of entries) {
5
+ const key = JSON.stringify([
6
+ entry.reason,
7
+ identity(entry),
8
+ entry.message.trim().replace(/\s+/g, " ")
9
+ ]);
10
+ let group = groups.get(key);
11
+ if (!group) {
12
+ group = {
13
+ key,
14
+ latest: entry,
15
+ entries: [],
16
+ affected: [],
17
+ count: 0
18
+ };
19
+ groups.set(key, group);
20
+ }
21
+ group.entries.push(entry);
22
+ const name = resource(entry);
23
+ if (!group.affected.includes(name)) group.affected.push(name);
24
+ if ((Date.parse(entry.time ?? "") || 0) > (Date.parse(group.latest.time ?? "") || 0)) group.latest = entry;
25
+ }
26
+ for (const group of groups.values()) {
27
+ const counts = /* @__PURE__ */ new Map();
28
+ for (const entry of group.entries) counts.set(entry.id, Math.max(counts.get(entry.id) ?? 0, entry.count));
29
+ group.count = [...counts.values()].reduce((total, count) => total + count, 0);
30
+ }
31
+ return [...groups.values()].sort((a, b) => (Date.parse(b.latest.time ?? "") || 0) - (Date.parse(a.latest.time ?? "") || 0));
32
+ }
33
+ //#endregion
34
+ exports.groupTimelineEntries = groupTimelineEntries;
35
+
36
+ //# sourceMappingURL=timeline-groups.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-groups.js","names":[],"sources":["../../../../src/common/timeline-groups.ts"],"sourcesContent":["import type { TimelineEntry } from \"./resource-diagnostics\";\n\nexport interface TimelineGroup {\n key: string;\n latest: TimelineEntry;\n entries: TimelineEntry[];\n affected: string[];\n count: number;\n}\n\nexport function groupTimelineEntries(\n entries: TimelineEntry[],\n identity: (entry: TimelineEntry) => string,\n resource: (entry: TimelineEntry) => string,\n): TimelineGroup[] {\n const groups = new Map<string, TimelineGroup>();\n for (const entry of entries) {\n const key = JSON.stringify([entry.reason, identity(entry), entry.message.trim().replace(/\\s+/g, \" \")]);\n let group = groups.get(key);\n if (!group) {\n group = { key, latest: entry, entries: [], affected: [], count: 0 };\n groups.set(key, group);\n }\n group.entries.push(entry);\n const name = resource(entry);\n if (!group.affected.includes(name)) group.affected.push(name);\n if ((Date.parse(entry.time ?? \"\") || 0) > (Date.parse(group.latest.time ?? \"\") || 0)) group.latest = entry;\n }\n for (const group of groups.values()) {\n // each event identity reports a cumulative count, so repeated snapshots use its maximum\n const counts = new Map<string, number>();\n for (const entry of group.entries) counts.set(entry.id, Math.max(counts.get(entry.id) ?? 0, entry.count));\n group.count = [...counts.values()].reduce((total, count) => total + count, 0);\n }\n return [...groups.values()].sort(\n (a, b) => (Date.parse(b.latest.time ?? \"\") || 0) - (Date.parse(a.latest.time ?? \"\") || 0),\n );\n}\n"],"mappings":";AAUA,SAAgB,qBACd,SACA,UACA,UACiB;CACjB,MAAM,yBAAS,IAAI,IAA2B;CAC9C,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,MAAM,KAAK,UAAU;GAAC,MAAM;GAAQ,SAAS,KAAK;GAAG,MAAM,QAAQ,KAAK,CAAC,CAAC,QAAQ,QAAQ,GAAG;EAAC,CAAC;EACrG,IAAI,QAAQ,OAAO,IAAI,GAAG;EAC1B,IAAI,CAAC,OAAO;GACV,QAAQ;IAAE;IAAK,QAAQ;IAAO,SAAS,CAAC;IAAG,UAAU,CAAC;IAAG,OAAO;GAAE;GAClE,OAAO,IAAI,KAAK,KAAK;EACvB;EACA,MAAM,QAAQ,KAAK,KAAK;EACxB,MAAM,OAAO,SAAS,KAAK;EAC3B,IAAI,CAAC,MAAM,SAAS,SAAS,IAAI,GAAG,MAAM,SAAS,KAAK,IAAI;EAC5D,KAAK,KAAK,MAAM,MAAM,QAAQ,EAAE,KAAK,MAAM,KAAK,MAAM,MAAM,OAAO,QAAQ,EAAE,KAAK,IAAI,MAAM,SAAS;CACvG;CACA,KAAK,MAAM,SAAS,OAAO,OAAO,GAAG;EAEnC,MAAM,yBAAS,IAAI,IAAoB;EACvC,KAAK,MAAM,SAAS,MAAM,SAAS,OAAO,IAAI,MAAM,IAAI,KAAK,IAAI,OAAO,IAAI,MAAM,EAAE,KAAK,GAAG,MAAM,KAAK,CAAC;EACxG,MAAM,QAAQ,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,QAAQ,OAAO,UAAU,QAAQ,OAAO,CAAC;CAC9E;CACA,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,MACzB,GAAG,OAAO,KAAK,MAAM,EAAE,OAAO,QAAQ,EAAE,KAAK,MAAM,KAAK,MAAM,EAAE,OAAO,QAAQ,EAAE,KAAK,EACzF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@64mb/freelens-power",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Power tools for Freelens Kubernetes IDE",
5
5
  "main": "out/main/index.js",
6
6
  "renderer": "out/renderer/index.js",