@64mb/freelens-power 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/out/main/common/power-logs.js.map +1 -1
- package/out/main/power-logs-child.js +3 -3
- package/out/main/power-logs-child.js.map +1 -1
- package/out/main/power-logs-database.js +165 -7
- package/out/main/power-logs-database.js.map +1 -1
- package/out/main/power-logs-ipc.js +12 -3
- package/out/main/power-logs-ipc.js.map +1 -1
- package/out/main/power-logs-worker.js +29 -7
- package/out/main/power-logs-worker.js.map +1 -1
- package/out/renderer/ai-diagnostic-handoff.js +2 -2
- package/out/renderer/ai-diagnostic-handoff.js.map +1 -1
- package/out/renderer/ai-extras.js +1 -1
- package/out/renderer/ai-extras.js.map +1 -1
- package/out/renderer/ai.js +4 -1
- package/out/renderer/ai.js.map +1 -1
- package/out/renderer/diagnostics-screen-finding.js +51 -0
- package/out/renderer/diagnostics-screen-finding.js.map +1 -0
- package/out/renderer/diagnostics.css.js +1 -1
- package/out/renderer/diagnostics.css.js.map +1 -1
- package/out/renderer/diagnostics.js +12 -0
- package/out/renderer/diagnostics.js.map +1 -1
- package/out/renderer/index.js +2 -0
- package/out/renderer/index.js.map +1 -1
- package/out/renderer/pod-details-enhancements.js +106 -0
- package/out/renderer/pod-details-enhancements.js.map +1 -0
- package/out/renderer/power-log-json-cell.js +15 -14
- package/out/renderer/power-log-json-cell.js.map +1 -1
- package/out/renderer/power-log-json.js +14 -6
- package/out/renderer/power-log-json.js.map +1 -1
- package/out/renderer/power-log-sql-editor.js +31 -2
- package/out/renderer/power-log-sql-editor.js.map +1 -1
- package/out/renderer/power-logs-client.js +5 -0
- package/out/renderer/power-logs-client.js.map +1 -1
- package/out/renderer/power-logs-entrypoints.js +10 -36
- package/out/renderer/power-logs-entrypoints.js.map +1 -1
- package/out/renderer/power-logs.css.js +9 -2
- package/out/renderer/power-logs.css.js.map +1 -1
- package/out/renderer/power-logs.js +220 -91
- package/out/renderer/power-logs.js.map +1 -1
- package/out/renderer/preferences/logs-preferences.js +1 -1
- package/out/renderer/preferences/logs-preferences.js.map +1 -1
- package/out/renderer/relationship-map-finding.js +53 -0
- package/out/renderer/relationship-map-finding.js.map +1 -0
- package/out/renderer/resource-relationships.css.js +6 -0
- package/out/renderer/resource-relationships.css.js.map +1 -1
- package/out/renderer/resource-relationships.js +50 -5
- package/out/renderer/resource-relationships.js.map +1 -1
- package/out/renderer/resource-search.js +13 -3
- package/out/renderer/resource-search.js.map +1 -1
- package/out/renderer/src/common/gateway-relationships.js +8 -7
- package/out/renderer/src/common/gateway-relationships.js.map +1 -1
- package/out/renderer/src/common/power-log-json-filter-sql.js +12 -0
- package/out/renderer/src/common/power-log-json-filter-sql.js.map +1 -1
- package/out/renderer/src/common/power-log-json-preset.js +2 -2
- package/out/renderer/src/common/power-log-json-preset.js.map +1 -1
- package/out/renderer/src/common/power-logs.js.map +1 -1
- package/out/renderer/src/common/resource-relationships.js +10 -8
- package/out/renderer/src/common/resource-relationships.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-search.js","names":[],"sources":["../../src/renderer/resource-search.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { reaction } from \"mobx\";\nimport { observer } from \"mobx-react\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport ReactDOM from \"react-dom\";\nimport Select from \"react-select\";\nimport { resourceKindPriority } from \"../common/resource-search-kind-priority\";\nimport { MonitoringPreferencesStore } from \"../common/store/monitoring-preferences-store\";\nimport { explainDiagnosticFinding } from \"./ai-diagnostic-handoff\";\nimport { AiIcon } from \"./ai-icon\";\nimport { DiagnosticExplainButton } from \"./diagnostic-explain-button\";\nimport { NativeTooltip } from \"./native-tooltip\";\nimport { createResourceSearchClient, createResourceSearchTransport, searchContextId } from \"./resource-search-client\";\nimport { projectSearchFields } from \"./resource-search-fields\";\nimport {\n createDoubleShift,\n createResourceSearchIndex,\n queryTokens,\n type SearchResource,\n} from \"./resource-search-model\";\nimport { closeSearchResourceFallback, openSearchResource } from \"./resource-search-routing\";\nimport { createSearchSynchronizer, type SearchCoverage } from \"./resource-search-sync\";\n\nimport type { SearchDocument } from \"../common/resource-search\";\n\nconst OPEN = \"power-resource-search\";\nconst activeId = () => Renderer.Catalog.activeCluster.get()?.metadata.uid;\nconst inventory = () =>\n [\n [\"Pod\", Renderer.K8sApi.podsStore, \"view_in_ar\"],\n [\"Node\", Renderer.K8sApi.nodesStore, \"dns\"],\n [\"Deployment\", Renderer.K8sApi.deploymentStore, \"layers\"],\n [\"StatefulSet\", Renderer.K8sApi.statefulSetStore, \"layers\"],\n [\"DaemonSet\", Renderer.K8sApi.daemonSetStore, \"layers\"],\n [\"ReplicaSet\", Renderer.K8sApi.replicaSetStore, \"layers\"],\n [\"Job\", Renderer.K8sApi.jobStore, \"work\"],\n [\"CronJob\", Renderer.K8sApi.cronJobStore, \"schedule\"],\n [\"Service\", Renderer.K8sApi.serviceStore, \"settings_ethernet\"],\n [\"Ingress\", Renderer.K8sApi.ingressStore, \"alt_route\"],\n [\"Namespace\", Renderer.K8sApi.namespaceStore, \"folder\"],\n [\"HorizontalPodAutoscaler\", Renderer.K8sApi.hpaStore, \"swap_vert\"],\n ] as const;\nconst icons = new Map(inventory().map(([kind, , icon]) => [kind as string, icon]));\n\nfunction SearchFilterSelect({\n value,\n options,\n ...props\n}: {\n value: string;\n options: { value: string; label: string }[];\n \"aria-label\": string;\n onMenuOpen(): void;\n onMenuClose(): void;\n onChange(option: { value: string; label: string } | null): void;\n}) {\n return (\n <Select\n {...props}\n className={`Select theme-${Renderer.Theme?.activeTheme?.get()?.type ?? \"dark\"}`}\n classNamePrefix=\"Select\"\n value={options.find((option) => option.value === value) ?? null}\n options={options}\n isClearable={false}\n tabSelectsValue={false}\n menuPlacement=\"auto\"\n />\n );\n}\n\nexport const ResourceSearchLauncher = observer(() => {\n if (!MonitoringPreferencesStore.getInstanceOrCreate<MonitoringPreferencesStore>().resourceSearchEnabled) return null;\n return (\n <NativeTooltip title=\"Search resources (double Shift)\">\n <button type=\"button\" aria-label=\"Search resources\" onClick={() => window.dispatchEvent(new Event(OPEN))}>\n <AiIcon material=\"search\" />\n </button>\n </NativeTooltip>\n );\n});\n\nfunction SearchSnippet({ field, query }: { field?: { label: string; text: string }; query: string }) {\n if (!field) return null;\n const token = queryTokens(query).find((token) => field.text.toLocaleLowerCase().includes(token));\n const offset = token ? field.text.toLocaleLowerCase().indexOf(token) : -1;\n return (\n <small className=\"power-resource-search-snippet\">\n {field.label}:{\" \"}\n {offset < 0 || !token ? (\n field.text\n ) : (\n <>\n {field.text.slice(0, offset)}\n <mark>{field.text.slice(offset, offset + token.length)}</mark>\n {field.text.slice(offset + token.length)}\n </>\n )}\n </small>\n );\n}\n\nfunction SearchDialog({ close, clusterId }: { close: () => void; clusterId: string }) {\n const [query, setQuery] = useState(\"\");\n const [kindFilter, setKindFilter] = useState(\"\");\n const [namespaceFilter, setNamespaceFilter] = useState(\"\");\n const [resources, setResources] = useState<SearchResource[]>([]);\n const [loading, setLoading] = useState(true);\n const [failed, setFailed] = useState(0);\n const [cachedCount, setCachedCount] = useState<number>();\n const [selectedPath, setSelectedPath] = useState<string>();\n const [error, setError] = useState(\"\");\n const dialog = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLInputElement>(null);\n const openFilterMenus = useRef(new Set<string>());\n const escapeFromOpenMenu = useRef(false);\n const searchIndex = useMemo(() => createResourceSearchIndex(resources), [resources]);\n const matches = searchIndex.search(query);\n const selectedResource = matches.find((resource) => resource.path === selectedPath) ?? matches[0];\n const results = matches;\n if (selectedResource && !results.includes(selectedResource)) results[results.length - 1] = selectedResource;\n const selected = results.findIndex((resource) => resource.path === selectedResource?.path);\n useEffect(() => {\n if (selectedPath !== undefined && selectedPath !== selectedResource?.path) setSelectedPath(undefined);\n }, [selectedPath, selectedResource?.path]);\n const [coverage, setCoverage] = useState<SearchCoverage>(currentCoverage);\n const searchKey = JSON.stringify([query, kindFilter, namespaceFilter]);\n const [allFor, setAllFor] = useState<string>();\n const fetchAll = allFor === searchKey;\n const [allRequests, setAllRequests] = useState(0);\n const [progress, setProgress] = useState({ loaded: 0, total: 0 });\n const [hasMore, setHasMore] = useState(false);\n useEffect(() => {\n const previous = document.activeElement as HTMLElement | null;\n input.current?.focus();\n const update = () => setCoverage({ ...currentCoverage });\n window.addEventListener(\"power-search-coverage\", update);\n return () => {\n window.removeEventListener(\"power-search-coverage\", update);\n if (previous?.isConnected) previous.focus();\n };\n }, []);\n useEffect(() => {\n let live = true;\n setLoading(true);\n const client = createResourceSearchClient(clusterId, searchContextId());\n const timer = window.setTimeout(() => {\n void (async () => {\n const filters = {\n ...(kindFilter\n ? {\n kind:\n coverage.kinds?.find((kind) => kind.toLocaleLowerCase() === kindFilter.toLocaleLowerCase()) ??\n kindFilter,\n }\n : {}),\n ...(namespaceFilter ? { namespace: namespaceFilter === \"(cluster)\" ? \"\" : namespaceFilter } : {}),\n };\n const documents = new Map<string, SearchDocument>();\n let offset = 0;\n do {\n const result = await client.query(query, fetchAll ? 1000 : 100, {\n ...filters,\n ...(offset ? { offset } : {}),\n });\n if (!live || activeId() !== clusterId) return;\n const previousCount = documents.size;\n for (const document of result.documents) documents.set(document.path, document);\n setResources([...documents.values()]);\n setProgress({ loaded: documents.size, total: result.total });\n setHasMore(result.hasMore);\n offset += result.documents.length;\n if (!fetchAll || !result.hasMore) break;\n if (documents.size === previousCount) throw new Error(\"Search pagination made no progress\");\n } while (live);\n if (!live) return;\n setLoading(false);\n setFailed(0);\n void client\n .status?.()\n .then((status) => {\n if (live) setCachedCount(status.count);\n })\n .catch(() => {});\n })().catch(() => {\n if (live) {\n setFailed(1);\n setLoading(false);\n }\n });\n }, 100);\n return () => {\n live = false;\n window.clearTimeout(timer);\n };\n }, [clusterId, query, fetchAll, allRequests, kindFilter, namespaceFilter, coverage.scanned, coverage.indexing]);\n useEffect(() => {\n document.getElementById(`power-resource-result-${selected}`)?.scrollIntoView?.({ block: \"nearest\" });\n }, [selected, selectedResource?.path]);\n const clearCache = () => {\n void createResourceSearchClient(clusterId, searchContextId())\n .clear()\n .then(() => {\n setResources([]);\n restartSearchSync();\n })\n .catch(() => setError(\"Could not clear the local search cache. Try again.\"));\n };\n const open = (resource: SearchResource) => {\n if (activeId() !== clusterId) return;\n void Promise.resolve()\n .then(() => openSearchResource(resource as SearchDocument))\n .then(() => {\n if (activeId() === clusterId) close();\n })\n .catch(() => setError(\"Could not open this resource. Try again or refresh the inventory.\"));\n };\n return (\n <div\n className=\"power-resource-search-backdrop\"\n data-freelens-power-ai-overlay=\"\"\n onClick={(event) => event.stopPropagation()}\n onMouseDown={(event) => {\n event.stopPropagation();\n if (event.target === event.currentTarget) close();\n }}\n >\n <div\n ref={dialog}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"power-resource-search-title\"\n className=\"power-resource-search-dialog\"\n onKeyDownCapture={(event) => {\n if (event.key === \"Escape\") escapeFromOpenMenu.current = openFilterMenus.current.size > 0;\n }}\n onKeyDown={(event) => {\n if (event.key === \"Escape\" && !escapeFromOpenMenu.current) {\n event.stopPropagation();\n close();\n }\n if (event.key === \"Tab\") {\n const controls = Array.from(\n dialog.current?.querySelectorAll<HTMLElement>(\"input, button:not(:disabled), select\") ?? [],\n );\n const index = controls.indexOf(document.activeElement as HTMLElement);\n if (event.shiftKey && index <= 0) {\n event.preventDefault();\n controls.at(-1)?.focus();\n } else if (!event.shiftKey && index === controls.length - 1) {\n event.preventDefault();\n controls[0]?.focus();\n }\n }\n }}\n >\n <div className=\"power-resource-search-heading\">\n <strong id=\"power-resource-search-title\" style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n <AiIcon material=\"search\" size={18} />\n Search resources\n </strong>\n <div style={{ display: \"flex\", gap: 4 }}>\n <NativeTooltip title=\"Refresh resource inventory\">\n <button type=\"button\" aria-label=\"Refresh resource inventory\" onClick={() => restartSearchSync()}>\n <AiIcon material=\"refresh\" />\n </button>\n </NativeTooltip>\n <button type=\"button\" aria-label=\"Close resource search\" onClick={close}>\n <AiIcon material=\"close\" />\n </button>\n </div>\n </div>\n <input\n ref={input}\n aria-label=\"Resource name, namespace, kind, label or indexed text\"\n aria-controls=\"power-resource-search-results\"\n placeholder=\"Name, namespace, label, image, condition…\"\n maxLength={300}\n value={query}\n onChange={(event) => {\n setQuery(event.target.value);\n setSelectedPath(undefined);\n }}\n onKeyDown={(event) => {\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n const next = results.length\n ? (selected + (event.key === \"ArrowDown\" ? 1 : -1) + results.length) % results.length\n : 0;\n setSelectedPath(results[next]?.path);\n }\n if (event.key === \"Enter\" && !event.nativeEvent.isComposing && results[selected]) {\n event.preventDefault();\n open(results[selected]);\n }\n }}\n />\n <div className=\"power-resource-search-filters\">\n <label className=\"power-resource-search-filter\">\n <span>Kind</span>\n <SearchFilterSelect\n aria-label=\"Kind\"\n onMenuOpen={() => openFilterMenus.current.add(\"kind\")}\n onMenuClose={() => openFilterMenus.current.delete(\"kind\")}\n value={kindFilter}\n options={[\n { value: \"\", label: \"All types\" },\n ...[...(coverage.kinds ?? [])]\n .sort(\n (first, second) =>\n resourceKindPriority(first) - resourceKindPriority(second) || first.localeCompare(second, \"en\"),\n )\n .map((kind) => ({ value: kind, label: kind })),\n ]}\n onChange={(option) => {\n setKindFilter(option?.value ?? \"\");\n setSelectedPath(undefined);\n }}\n />\n </label>\n <label className=\"power-resource-search-filter\">\n <span>Namespace</span>\n <SearchFilterSelect\n aria-label=\"Namespace\"\n onMenuOpen={() => openFilterMenus.current.add(\"namespace\")}\n onMenuClose={() => openFilterMenus.current.delete(\"namespace\")}\n value={namespaceFilter}\n options={[\n { value: \"\", label: \"All namespaces\" },\n { value: \"(cluster)\", label: \"Cluster scoped\" },\n ...(coverage.namespaces ?? []).map((namespace) => ({ value: namespace, label: namespace })),\n ]}\n onChange={(option) => {\n setNamespaceFilter(option?.value ?? \"\");\n setSelectedPath(undefined);\n }}\n />\n </label>\n </div>\n <p role=\"status\">\n {coverage.indexing\n ? `Indexing ${coverage.scanned}/${coverage.discovered} types`\n : coverage.completedAt\n ? `Updated ${new Date(coverage.completedAt).toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" })}`\n : \"Cached inventory\"}\n {cachedCount !== undefined && ` · ${cachedCount.toLocaleString()} resources`}\n {coverage.denied > 0 && ` · ${coverage.denied} restricted scopes`}\n {coverage.unavailable + coverage.unsupported > 0 &&\n ` · ${coverage.unavailable + coverage.unsupported} APIs unavailable`}\n {failed > 0 && \" · Search cache unavailable\"}\n </p>\n {(coverage.message || coverage.denied > 0 || coverage.unavailable + coverage.unsupported > 0) && (\n <details>\n <summary>Inventory coverage</summary>\n <p>\n {coverage.message ?? \"Results include the resources your current permissions and available APIs allow.\"}\n </p>\n </details>\n )}\n {coverage.storageFull && (\n <div role=\"alert\">\n <p>Local search storage is full. Cached results are partial; indexing is paused.</p>\n </div>\n )}\n {error && <p role=\"alert\">{error}</p>}\n <div\n id=\"power-resource-search-results\"\n role=\"list\"\n aria-label=\"Matching resources\"\n className=\"power-resource-search-results\"\n >\n {results.map((resource, index) => (\n <div\n key={resource.path}\n id={`power-resource-result-${index}`}\n role=\"listitem\"\n className={`power-resource-search-row ${selected === index ? \"selected\" : \"\"}`}\n >\n <button className=\"power-resource-search-open\" type=\"button\" onClick={() => open(resource)}>\n <AiIcon material={icons.get(resource.kind) ?? \"view_in_ar\"} />\n <span>\n <strong>{resource.name}</strong>\n <small>\n {resource.kind} · {resource.namespace ?? \"cluster scoped\"}\n </small>\n {searchIndex.nameParts(resource) && <small>Name parts</small>}\n {searchIndex.similarName(resource) && <small>Similar name</small>}\n <SearchSnippet field={searchIndex.snippet(resource, query)} query={query} />\n </span>\n </button>\n <DiagnosticExplainButton\n className=\"power-resource-search-ai\"\n aria-label={`Ask AI about ${resource.kind} ${resource.name}`}\n title=\"Ask AI about this resource\"\n onClick={() => {\n if (activeId() !== clusterId) return;\n close();\n explainDiagnosticFinding({\n clusterId,\n title: `${resource.kind}: ${resource.name}`,\n summary: `Resource selected from search. API group: ${(resource as SearchDocument).group || \"core\"}; version: ${(resource as SearchDocument).version}; resource: ${(resource as SearchDocument).resource}; kind: ${resource.kind}; namespace: ${resource.namespace ?? \"cluster scoped\"}; name: ${resource.name}; UID: ${(resource as SearchDocument).uid ?? \"unknown\"}; path: ${resource.path}. No live diagnostic data was collected.`,\n resource: { kind: resource.kind, name: resource.name, namespace: resource.namespace },\n });\n }}\n >\n <AiIcon material=\"auto_awesome\" />\n </DiagnosticExplainButton>\n </div>\n ))}\n {!results.length && <p>{loading ? \"Searching…\" : \"No matching resources in the available inventory\"}</p>}\n </div>\n {(hasMore || (loading && fetchAll)) && (\n <button\n type=\"button\"\n disabled={loading}\n aria-busy={loading}\n onClick={() => {\n setLoading(true);\n setAllFor(searchKey);\n setAllRequests((value) => value + 1);\n }}\n >\n {loading ? `Loading matches… ${progress.loaded} / ${progress.total}` : \"Show all matches\"}\n </button>\n )}\n <div className=\"power-resource-search-footer\">\n <p className=\"power-resource-search-shortcuts\">↑↓ Navigate · Enter Open · Esc Close</p>\n <NativeTooltip title=\"Clear local search cache\">\n <button\n type=\"button\"\n className=\"power-resource-search-clear\"\n aria-label=\"Clear local search cache\"\n onClick={clearCache}\n >\n <AiIcon material=\"delete_sweep\" size={16} />\n </button>\n </NativeTooltip>\n </div>\n </div>\n </div>\n );\n}\n\nlet currentCoverage: SearchCoverage = {\n scanned: 0,\n discovered: 0,\n denied: 0,\n unavailable: 0,\n unsupported: 0,\n indexing: true,\n};\nlet restartSearchSync = () => {};\n\nexport function startResourceSearch(): () => void {\n const preferences = MonitoringPreferencesStore.getInstanceOrCreate<MonitoringPreferencesStore>();\n let stop = preferences.resourceSearchEnabled ? startEnabledResourceSearch() : undefined;\n const dispose = reaction(\n () => preferences.resourceSearchEnabled,\n (enabled) => {\n stop?.();\n stop = enabled ? startEnabledResourceSearch() : undefined;\n },\n );\n return () => {\n dispose();\n stop?.();\n stop = undefined;\n };\n}\n\nfunction startEnabledResourceSearch(): () => void {\n let syncAbort: AbortController | undefined;\n const sync = () => {\n syncAbort?.abort();\n closeSearchResourceFallback();\n const clusterId = activeId();\n if (!clusterId || window.top === window) return;\n const phase = Renderer.Catalog.activeCluster.get()?.status?.phase;\n if (phase && phase !== \"connected\") {\n currentCoverage = {\n ...currentCoverage,\n indexing: false,\n message: \"Cluster disconnected; cached results may be stale\",\n };\n window.dispatchEvent(new Event(\"power-search-coverage\"));\n return;\n }\n const controller = new AbortController();\n syncAbort = controller;\n const client = createResourceSearchClient(clusterId, searchContextId());\n const liveStores = new Map(inventory().map(([kind, store]) => [kind as string, store]));\n const liveObjects = new Map<string, Map<string, unknown>>();\n const sink = {\n ...client,\n append: (scope: string, scanId: string, documents: SearchDocument[]) =>\n client.append(\n scope,\n scanId,\n documents.map((item) => {\n const store = liveStores.get(item.kind);\n if (!store) return item;\n let objects = liveObjects.get(item.kind);\n if (!objects) {\n objects = new Map(store.items.map((object) => [object.selfLink, object] as const));\n liveObjects.set(item.kind, objects);\n }\n const object = objects.get(item.path);\n return object ? { ...item, fields: projectSearchFields(item.kind, object) } : item;\n }),\n ),\n };\n let wasIndexing = false;\n const task = createSearchSynchronizer({\n transport: createResourceSearchTransport(clusterId, controller.signal),\n sink,\n signal: controller.signal,\n namespaces: () => Renderer.Catalog.activeCluster.get()?.spec?.accessibleNamespaces ?? [],\n onStatus: (status) => {\n if (!controller.signal.aborted) {\n if (status.indexing && !wasIndexing) liveObjects.clear();\n wasIndexing = status.indexing;\n currentCoverage = status;\n window.dispatchEvent(new Event(\"power-search-coverage\"));\n }\n },\n });\n void task.run().catch(() => {\n if (!controller.signal.aborted) {\n currentCoverage = { ...currentCoverage, indexing: false, unavailable: currentCoverage.unavailable + 1 };\n window.dispatchEvent(new Event(\"power-search-coverage\"));\n }\n });\n };\n restartSearchSync = sync;\n sync();\n const container = document.createElement(\"div\");\n const style = document.createElement(\"style\");\n style.textContent = `.power-resource-search-backdrop{position:fixed;inset:0;z-index:10000;background:#0007;display:flex;align-items:flex-start;justify-content:center;padding:8vh 20px}.power-resource-search-dialog{width:680px;max-width:100%;max-height:75vh;display:flex;flex-direction:column;padding:20px;gap:12px;background:var(--mainBackground,#222);color:var(--textColorPrimary,#eee);border:1px solid var(--borderColor,#555);border-radius:8px;box-shadow:0 16px 60px #0008}.power-resource-search-heading{display:flex;align-items:center;justify-content:space-between}.power-resource-search-filters{display:flex;align-items:end;gap:8px;flex-wrap:wrap}.power-resource-search-filter{display:flex;flex-direction:column;gap:4px;flex:1;min-width:180px;font-size:12px;color:var(--textColorSecondary)}.power-resource-search-filter .Select{width:100%;min-width:0}.power-resource-search-filter .Select__control{min-height:30px;height:30px;font-size:12px}.power-resource-search-filter .Select__value-container{padding:0 8px;min-width:0}.power-resource-search-filter .Select__input-container{margin:0;padding:0}.power-resource-search-filter .Select__indicator{padding:4px 8px}.power-resource-search-filter .Select__single-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.power-resource-search-dialog > input,.power-resource-search-dialog details input{padding:12px;font-size:16px;background:var(--inputControlBackground,#333);color:inherit;border:1px solid var(--borderColor,#666);border-radius:4px}.power-resource-search-dialog p{margin:0;font-size:12px;color:var(--textColorSecondary,#bbb)}.power-resource-search-results{overflow:auto;min-height:180px;flex:1}.power-resource-search-row{display:flex;align-items:center;gap:12px;padding:4px;border-radius:4px}.power-resource-search-row{border-left:3px solid transparent}.power-resource-search-row.selected{background:color-mix(in srgb,var(--primary,#668fff) 16%,var(--mainBackground,#222));color:var(--tableSelectedRowColor,var(--textColorPrimary,#eee));border-left-color:var(--primary,#668fff)}.power-resource-search-row.selected .Icon{color:var(--primary,#8daaff)}.power-resource-search-row:hover{background:var(--tableBgcSelected,var(--contentColor,#292d32))}.power-resource-search-dialog button:hover:not(:disabled){background:#ffffff12;border-radius:4px}.power-resource-search-row>button:last-child{border:1px solid var(--borderColor,#555);border-radius:4px}.power-resource-search-footer{display:flex;align-items:center;justify-content:space-between;gap:8px;padding-top:8px;border-top:1px solid var(--borderColor,#555)}.power-resource-search-shortcuts{flex:1}.power-resource-search-footer .power-resource-search-clear{display:inline-flex;align-items:center;justify-content:center;min-width:28px;min-height:28px;width:28px;height:28px;padding:0}.power-resource-search-dialog button{min-width:32px;min-height:32px;padding:6px;color:inherit;background:transparent;border:0;cursor:pointer}.power-resource-search-dialog button:disabled{opacity:.45;cursor:default}.power-resource-search-open{display:flex;align-items:center;gap:12px;flex:1;text-align:left;min-width:0}.power-resource-search-open span{min-width:0}.power-resource-search-open strong,.power-resource-search-open small{display:block;overflow-wrap:anywhere}.power-resource-search-open small{opacity:.7}.power-resource-search-row>button.power-resource-search-ai{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:34px;height:34px;min-width:34px;padding:0;background:var(--contentColor,#292d32);border:1px solid var(--borderColor,#666)}.power-resource-search-row.selected>button.power-resource-search-ai{background:var(--mainBackground,#202326);border-color:color-mix(in srgb,var(--textColorPrimary,#ddd) 55%,var(--borderColor,#666));box-shadow:0 1px 3px #0002}.power-resource-search-row.selected>button.power-resource-search-ai .Icon{color:var(--textColorAccent,#eee)}.power-resource-search-row>button.power-resource-search-ai:hover:not(:disabled){background:var(--contentColor,#30343a);border-color:var(--primary,#8daaff);color:var(--primary,#8daaff)}.power-resource-search-row>button.power-resource-search-ai:hover:not(:disabled) .Icon{color:inherit}.power-resource-search-dialog button:focus-visible{outline:2px solid var(--primary,#668fff)}`;\n document.head.append(style);\n document.body.append(container);\n let visible = false;\n const close = () => {\n if (visible) {\n visible = false;\n ReactDOM.unmountComponentAtNode(container);\n }\n };\n const show = () => {\n const clusterId = activeId();\n if (\n window.top === window ||\n !clusterId ||\n visible ||\n document.querySelector('[aria-modal=\"true\"], .Dialog.is-opened')\n )\n return;\n visible = true;\n ReactDOM.render(<SearchDialog close={close} clusterId={clusterId} />, container);\n };\n let shift = createDoubleShift();\n const resetShift = () => {\n shift = createDoubleShift();\n };\n const key = (event: KeyboardEvent) => {\n if (event.isComposing || event.key === \"Process\") {\n resetShift();\n return;\n }\n if (shift(event, performance.now())) show();\n };\n window.addEventListener(\"blur\", resetShift);\n window.addEventListener(\"keydown\", key);\n window.addEventListener(\"keyup\", key);\n window.addEventListener(OPEN, show);\n const dispose = reaction(\n () => `${searchContextId()}/${Renderer.Catalog.activeCluster.get()?.status?.phase}`,\n () => {\n close();\n sync();\n },\n );\n return () => {\n close();\n syncAbort?.abort();\n closeSearchResourceFallback();\n restartSearchSync = () => {};\n dispose();\n window.removeEventListener(\"blur\", resetShift);\n window.removeEventListener(\"keydown\", key);\n window.removeEventListener(\"keyup\", key);\n window.removeEventListener(OPEN, show);\n container.remove();\n style.remove();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resource-search.js","names":[],"sources":["../../src/renderer/resource-search.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { reaction } from \"mobx\";\nimport { observer } from \"mobx-react\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport ReactDOM from \"react-dom\";\nimport Select from \"react-select\";\nimport { resourceKindPriority } from \"../common/resource-search-kind-priority\";\nimport { MonitoringPreferencesStore } from \"../common/store/monitoring-preferences-store\";\nimport { explainDiagnosticFinding } from \"./ai-diagnostic-handoff\";\nimport { AiIcon } from \"./ai-icon\";\nimport { DiagnosticExplainButton } from \"./diagnostic-explain-button\";\nimport { NativeTooltip } from \"./native-tooltip\";\nimport { createResourceSearchClient, createResourceSearchTransport, searchContextId } from \"./resource-search-client\";\nimport { projectSearchFields } from \"./resource-search-fields\";\nimport {\n createDoubleShift,\n createResourceSearchIndex,\n queryTokens,\n type SearchResource,\n} from \"./resource-search-model\";\nimport { closeSearchResourceFallback, openSearchResource } from \"./resource-search-routing\";\nimport { createSearchSynchronizer, type SearchCoverage } from \"./resource-search-sync\";\n\nimport type { SearchDocument } from \"../common/resource-search\";\n\nconst OPEN = \"power-resource-search\";\nconst activeId = () => Renderer.Catalog.activeCluster.get()?.metadata.uid;\nconst inventory = () =>\n [\n [\"Pod\", Renderer.K8sApi.podsStore, \"view_in_ar\"],\n [\"Node\", Renderer.K8sApi.nodesStore, \"dns\"],\n [\"Deployment\", Renderer.K8sApi.deploymentStore, \"layers\"],\n [\"StatefulSet\", Renderer.K8sApi.statefulSetStore, \"layers\"],\n [\"DaemonSet\", Renderer.K8sApi.daemonSetStore, \"layers\"],\n [\"ReplicaSet\", Renderer.K8sApi.replicaSetStore, \"layers\"],\n [\"Job\", Renderer.K8sApi.jobStore, \"work\"],\n [\"CronJob\", Renderer.K8sApi.cronJobStore, \"schedule\"],\n [\"Service\", Renderer.K8sApi.serviceStore, \"settings_ethernet\"],\n [\"Ingress\", Renderer.K8sApi.ingressStore, \"alt_route\"],\n [\"Namespace\", Renderer.K8sApi.namespaceStore, \"folder\"],\n [\"HorizontalPodAutoscaler\", Renderer.K8sApi.hpaStore, \"swap_vert\"],\n ] as const;\nconst icons = new Map(inventory().map(([kind, , icon]) => [kind as string, icon]));\n\nfunction SearchFilterSelect({\n value,\n options,\n ...props\n}: {\n value: string;\n options: { value: string; label: string }[];\n \"aria-label\": string;\n onMenuOpen(): void;\n onMenuClose(): void;\n onChange(option: { value: string; label: string } | null): void;\n}) {\n return (\n <Select\n {...props}\n className={`Select theme-${Renderer.Theme?.activeTheme?.get()?.type ?? \"dark\"}`}\n classNamePrefix=\"Select\"\n value={options.find((option) => option.value === value) ?? null}\n options={options}\n isClearable={false}\n tabSelectsValue={false}\n menuPlacement=\"auto\"\n />\n );\n}\n\nexport const ResourceSearchLauncher = observer(() => {\n if (!MonitoringPreferencesStore.getInstanceOrCreate<MonitoringPreferencesStore>().resourceSearchEnabled) return null;\n return (\n <NativeTooltip title=\"Search resources (double Shift)\">\n <button type=\"button\" aria-label=\"Search resources\" onClick={() => window.dispatchEvent(new Event(OPEN))}>\n <AiIcon material=\"search\" />\n </button>\n </NativeTooltip>\n );\n});\n\nfunction SearchSnippet({ field, query }: { field?: { label: string; text: string }; query: string }) {\n if (!field) return null;\n const token = queryTokens(query).find((token) => field.text.toLocaleLowerCase().includes(token));\n const offset = token ? field.text.toLocaleLowerCase().indexOf(token) : -1;\n return (\n <small className=\"power-resource-search-snippet\">\n {field.label}:{\" \"}\n {offset < 0 || !token ? (\n field.text\n ) : (\n <>\n {field.text.slice(0, offset)}\n <mark>{field.text.slice(offset, offset + token.length)}</mark>\n {field.text.slice(offset + token.length)}\n </>\n )}\n </small>\n );\n}\n\nfunction SearchDialog({ close, clusterId }: { close: () => void; clusterId: string }) {\n const [query, setQuery] = useState(\"\");\n const [kindFilter, setKindFilter] = useState(\"\");\n const [namespaceFilter, setNamespaceFilter] = useState(\"\");\n const [resources, setResources] = useState<SearchResource[]>([]);\n const [loading, setLoading] = useState(true);\n const [failed, setFailed] = useState(0);\n const [cachedCount, setCachedCount] = useState<number>();\n const [selectedPath, setSelectedPath] = useState<string>();\n const [error, setError] = useState(\"\");\n const dialog = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLInputElement>(null);\n const openFilterMenus = useRef(new Set<string>());\n const escapeFromOpenMenu = useRef(false);\n const searchIndex = useMemo(() => createResourceSearchIndex(resources), [resources]);\n const matches = searchIndex.search(query);\n const selectedResource = matches.find((resource) => resource.path === selectedPath) ?? matches[0];\n const results = matches;\n if (selectedResource && !results.includes(selectedResource)) results[results.length - 1] = selectedResource;\n const selected = results.findIndex((resource) => resource.path === selectedResource?.path);\n useEffect(() => {\n if (selectedPath !== undefined && selectedPath !== selectedResource?.path) setSelectedPath(undefined);\n }, [selectedPath, selectedResource?.path]);\n const [coverage, setCoverage] = useState<SearchCoverage>(currentCoverage);\n const searchKey = JSON.stringify([query, kindFilter, namespaceFilter]);\n const [allFor, setAllFor] = useState<string>();\n const fetchAll = allFor === searchKey;\n const [allRequests, setAllRequests] = useState(0);\n const inventoryRevision = `${coverage.scanned}/${coverage.indexing}/${coverage.completedAt ?? \"\"}`;\n const [allInventoryRevision, setAllInventoryRevision] = useState(inventoryRevision);\n useEffect(() => {\n // finish pagination before reconciling the completed inventory\n if (!fetchAll || (!loading && !coverage.indexing)) setAllInventoryRevision(inventoryRevision);\n }, [fetchAll, loading, coverage.indexing, inventoryRevision]);\n const queryInventoryRevision = fetchAll ? allInventoryRevision : inventoryRevision;\n const [progress, setProgress] = useState({ loaded: 0, total: 0 });\n const [hasMore, setHasMore] = useState(false);\n useEffect(() => {\n const previous = document.activeElement as HTMLElement | null;\n input.current?.focus();\n const update = () => setCoverage({ ...currentCoverage });\n window.addEventListener(\"power-search-coverage\", update);\n return () => {\n window.removeEventListener(\"power-search-coverage\", update);\n if (previous?.isConnected) previous.focus();\n };\n }, []);\n useEffect(() => {\n let live = true;\n setLoading(true);\n const client = createResourceSearchClient(clusterId, searchContextId());\n const timer = window.setTimeout(() => {\n void (async () => {\n const filters = {\n ...(kindFilter\n ? {\n kind:\n coverage.kinds?.find((kind) => kind.toLocaleLowerCase() === kindFilter.toLocaleLowerCase()) ??\n kindFilter,\n }\n : {}),\n ...(namespaceFilter ? { namespace: namespaceFilter === \"(cluster)\" ? \"\" : namespaceFilter } : {}),\n };\n const documents = new Map<string, SearchDocument>();\n let offset = 0;\n do {\n const result = await client.query(query, fetchAll ? 1000 : 100, {\n ...filters,\n ...(offset ? { offset } : {}),\n });\n if (!live || activeId() !== clusterId) return;\n const previousCount = documents.size;\n for (const document of result.documents) documents.set(document.path, document);\n if (!fetchAll || documents.size >= resources.length || !result.hasMore) {\n setResources([...documents.values()]);\n }\n setProgress({ loaded: documents.size, total: result.total });\n setHasMore(result.hasMore);\n offset += result.documents.length;\n if (!fetchAll || !result.hasMore) break;\n if (documents.size === previousCount) throw new Error(\"Search pagination made no progress\");\n } while (live);\n if (!live) return;\n setLoading(false);\n setFailed(0);\n void client\n .status?.()\n .then((status) => {\n if (live) setCachedCount(status.count);\n })\n .catch(() => {});\n })().catch(() => {\n if (live) {\n setFailed(1);\n setLoading(false);\n }\n });\n }, 100);\n return () => {\n live = false;\n window.clearTimeout(timer);\n };\n }, [clusterId, query, fetchAll, allRequests, kindFilter, namespaceFilter, queryInventoryRevision]);\n useEffect(() => {\n document.getElementById(`power-resource-result-${selected}`)?.scrollIntoView?.({ block: \"nearest\" });\n }, [selected, selectedResource?.path]);\n const clearCache = () => {\n void createResourceSearchClient(clusterId, searchContextId())\n .clear()\n .then(() => {\n setResources([]);\n restartSearchSync();\n })\n .catch(() => setError(\"Could not clear the local search cache. Try again.\"));\n };\n const open = (resource: SearchResource) => {\n if (activeId() !== clusterId) return;\n void Promise.resolve()\n .then(() => openSearchResource(resource as SearchDocument))\n .then(() => {\n if (activeId() === clusterId) close();\n })\n .catch(() => setError(\"Could not open this resource. Try again or refresh the inventory.\"));\n };\n return (\n <div\n className=\"power-resource-search-backdrop\"\n data-freelens-power-ai-overlay=\"\"\n onClick={(event) => event.stopPropagation()}\n onMouseDown={(event) => {\n event.stopPropagation();\n if (event.target === event.currentTarget) close();\n }}\n >\n <div\n ref={dialog}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"power-resource-search-title\"\n className=\"power-resource-search-dialog\"\n onKeyDownCapture={(event) => {\n if (event.key === \"Escape\") escapeFromOpenMenu.current = openFilterMenus.current.size > 0;\n }}\n onKeyDown={(event) => {\n if (event.key === \"Escape\" && !escapeFromOpenMenu.current) {\n event.stopPropagation();\n close();\n }\n if (event.key === \"Tab\") {\n const controls = Array.from(\n dialog.current?.querySelectorAll<HTMLElement>(\"input, button:not(:disabled), select\") ?? [],\n );\n const index = controls.indexOf(document.activeElement as HTMLElement);\n if (event.shiftKey && index <= 0) {\n event.preventDefault();\n controls.at(-1)?.focus();\n } else if (!event.shiftKey && index === controls.length - 1) {\n event.preventDefault();\n controls[0]?.focus();\n }\n }\n }}\n >\n <div className=\"power-resource-search-heading\">\n <strong id=\"power-resource-search-title\" style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n <AiIcon material=\"search\" size={18} />\n Search resources\n </strong>\n <div style={{ display: \"flex\", gap: 4 }}>\n <NativeTooltip title=\"Refresh resource inventory\">\n <button type=\"button\" aria-label=\"Refresh resource inventory\" onClick={() => restartSearchSync()}>\n <AiIcon material=\"refresh\" />\n </button>\n </NativeTooltip>\n <button type=\"button\" aria-label=\"Close resource search\" onClick={close}>\n <AiIcon material=\"close\" />\n </button>\n </div>\n </div>\n <input\n ref={input}\n aria-label=\"Resource name, namespace, kind, label or indexed text\"\n aria-controls=\"power-resource-search-results\"\n placeholder=\"Name, namespace, label, image, condition…\"\n maxLength={300}\n value={query}\n onChange={(event) => {\n setQuery(event.target.value);\n setSelectedPath(undefined);\n }}\n onKeyDown={(event) => {\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n const next = results.length\n ? (selected + (event.key === \"ArrowDown\" ? 1 : -1) + results.length) % results.length\n : 0;\n setSelectedPath(results[next]?.path);\n }\n if (event.key === \"Enter\" && !event.nativeEvent.isComposing && results[selected]) {\n event.preventDefault();\n open(results[selected]);\n }\n }}\n />\n <div className=\"power-resource-search-filters\">\n <label className=\"power-resource-search-filter\">\n <span>Kind</span>\n <SearchFilterSelect\n aria-label=\"Kind\"\n onMenuOpen={() => openFilterMenus.current.add(\"kind\")}\n onMenuClose={() => openFilterMenus.current.delete(\"kind\")}\n value={kindFilter}\n options={[\n { value: \"\", label: \"All types\" },\n ...[...(coverage.kinds ?? [])]\n .sort(\n (first, second) =>\n resourceKindPriority(first) - resourceKindPriority(second) || first.localeCompare(second, \"en\"),\n )\n .map((kind) => ({ value: kind, label: kind })),\n ]}\n onChange={(option) => {\n setKindFilter(option?.value ?? \"\");\n setSelectedPath(undefined);\n }}\n />\n </label>\n <label className=\"power-resource-search-filter\">\n <span>Namespace</span>\n <SearchFilterSelect\n aria-label=\"Namespace\"\n onMenuOpen={() => openFilterMenus.current.add(\"namespace\")}\n onMenuClose={() => openFilterMenus.current.delete(\"namespace\")}\n value={namespaceFilter}\n options={[\n { value: \"\", label: \"All namespaces\" },\n { value: \"(cluster)\", label: \"Cluster scoped\" },\n ...(coverage.namespaces ?? []).map((namespace) => ({ value: namespace, label: namespace })),\n ]}\n onChange={(option) => {\n setNamespaceFilter(option?.value ?? \"\");\n setSelectedPath(undefined);\n }}\n />\n </label>\n </div>\n <p role=\"status\">\n {coverage.indexing\n ? `Indexing ${coverage.scanned}/${coverage.discovered} types`\n : coverage.completedAt\n ? `Updated ${new Date(coverage.completedAt).toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" })}`\n : \"Cached inventory\"}\n {cachedCount !== undefined && ` · ${cachedCount.toLocaleString()} resources`}\n {coverage.denied > 0 && ` · ${coverage.denied} restricted scopes`}\n {coverage.unavailable + coverage.unsupported > 0 &&\n ` · ${coverage.unavailable + coverage.unsupported} APIs unavailable`}\n {failed > 0 && \" · Search cache unavailable\"}\n </p>\n {(coverage.message || coverage.denied > 0 || coverage.unavailable + coverage.unsupported > 0) && (\n <details>\n <summary>Inventory coverage</summary>\n <p>\n {coverage.message ?? \"Results include the resources your current permissions and available APIs allow.\"}\n </p>\n </details>\n )}\n {coverage.storageFull && (\n <div role=\"alert\">\n <p>Local search storage is full. Cached results are partial; indexing is paused.</p>\n </div>\n )}\n {error && <p role=\"alert\">{error}</p>}\n <div\n id=\"power-resource-search-results\"\n role=\"list\"\n aria-label=\"Matching resources\"\n className=\"power-resource-search-results\"\n >\n {results.map((resource, index) => (\n <div\n key={resource.path}\n id={`power-resource-result-${index}`}\n role=\"listitem\"\n className={`power-resource-search-row ${selected === index ? \"selected\" : \"\"}`}\n >\n <button className=\"power-resource-search-open\" type=\"button\" onClick={() => open(resource)}>\n <AiIcon material={icons.get(resource.kind) ?? \"view_in_ar\"} />\n <span>\n <strong>{resource.name}</strong>\n <small>\n {resource.kind} · {resource.namespace ?? \"cluster scoped\"}\n </small>\n {searchIndex.nameParts(resource) && <small>Name parts</small>}\n {searchIndex.similarName(resource) && <small>Similar name</small>}\n <SearchSnippet field={searchIndex.snippet(resource, query)} query={query} />\n </span>\n </button>\n <DiagnosticExplainButton\n className=\"power-resource-search-ai\"\n aria-label={`Ask AI about ${resource.kind} ${resource.name}`}\n title=\"Ask AI about this resource\"\n onClick={() => {\n if (activeId() !== clusterId) return;\n close();\n explainDiagnosticFinding({\n clusterId,\n title: `${resource.kind}: ${resource.name}`,\n summary: `Resource selected from search. API group: ${(resource as SearchDocument).group || \"core\"}; version: ${(resource as SearchDocument).version}; resource: ${(resource as SearchDocument).resource}; kind: ${resource.kind}; namespace: ${resource.namespace ?? \"cluster scoped\"}; name: ${resource.name}; UID: ${(resource as SearchDocument).uid ?? \"unknown\"}; path: ${resource.path}. No live diagnostic data was collected.`,\n resource: { kind: resource.kind, name: resource.name, namespace: resource.namespace },\n });\n }}\n >\n <AiIcon material=\"auto_awesome\" />\n </DiagnosticExplainButton>\n </div>\n ))}\n {!results.length && <p>{loading ? \"Searching…\" : \"No matching resources in the available inventory\"}</p>}\n </div>\n {(hasMore || (loading && fetchAll)) && (\n <button\n type=\"button\"\n disabled={loading}\n aria-busy={loading}\n onClick={() => {\n setLoading(true);\n setAllFor(searchKey);\n setAllRequests((value) => value + 1);\n }}\n >\n {loading ? `Loading matches… ${progress.loaded} / ${progress.total}` : \"Show all matches\"}\n </button>\n )}\n <div className=\"power-resource-search-footer\">\n <p className=\"power-resource-search-shortcuts\">↑↓ Navigate · Enter Open · Esc Close</p>\n <NativeTooltip title=\"Clear local search cache\">\n <button\n type=\"button\"\n className=\"power-resource-search-clear\"\n aria-label=\"Clear local search cache\"\n onClick={clearCache}\n >\n <AiIcon material=\"delete_sweep\" size={16} />\n </button>\n </NativeTooltip>\n </div>\n </div>\n </div>\n );\n}\n\nlet currentCoverage: SearchCoverage = {\n scanned: 0,\n discovered: 0,\n denied: 0,\n unavailable: 0,\n unsupported: 0,\n indexing: true,\n};\nlet restartSearchSync = () => {};\n\nexport function startResourceSearch(): () => void {\n const preferences = MonitoringPreferencesStore.getInstanceOrCreate<MonitoringPreferencesStore>();\n let stop = preferences.resourceSearchEnabled ? startEnabledResourceSearch() : undefined;\n const dispose = reaction(\n () => preferences.resourceSearchEnabled,\n (enabled) => {\n stop?.();\n stop = enabled ? startEnabledResourceSearch() : undefined;\n },\n );\n return () => {\n dispose();\n stop?.();\n stop = undefined;\n };\n}\n\nfunction startEnabledResourceSearch(): () => void {\n let syncAbort: AbortController | undefined;\n const sync = () => {\n syncAbort?.abort();\n closeSearchResourceFallback();\n const clusterId = activeId();\n if (!clusterId || window.top === window) return;\n const phase = Renderer.Catalog.activeCluster.get()?.status?.phase;\n if (phase && phase !== \"connected\") {\n currentCoverage = {\n ...currentCoverage,\n indexing: false,\n message: \"Cluster disconnected; cached results may be stale\",\n };\n window.dispatchEvent(new Event(\"power-search-coverage\"));\n return;\n }\n const controller = new AbortController();\n syncAbort = controller;\n const client = createResourceSearchClient(clusterId, searchContextId());\n const liveStores = new Map(inventory().map(([kind, store]) => [kind as string, store]));\n const liveObjects = new Map<string, Map<string, unknown>>();\n const sink = {\n ...client,\n append: (scope: string, scanId: string, documents: SearchDocument[]) =>\n client.append(\n scope,\n scanId,\n documents.map((item) => {\n const store = liveStores.get(item.kind);\n if (!store) return item;\n let objects = liveObjects.get(item.kind);\n if (!objects) {\n objects = new Map(store.items.map((object) => [object.selfLink, object] as const));\n liveObjects.set(item.kind, objects);\n }\n const object = objects.get(item.path);\n return object ? { ...item, fields: projectSearchFields(item.kind, object) } : item;\n }),\n ),\n };\n let wasIndexing = false;\n const task = createSearchSynchronizer({\n transport: createResourceSearchTransport(clusterId, controller.signal),\n sink,\n signal: controller.signal,\n namespaces: () => Renderer.Catalog.activeCluster.get()?.spec?.accessibleNamespaces ?? [],\n onStatus: (status) => {\n if (!controller.signal.aborted) {\n if (status.indexing && !wasIndexing) liveObjects.clear();\n wasIndexing = status.indexing;\n currentCoverage = status;\n window.dispatchEvent(new Event(\"power-search-coverage\"));\n }\n },\n });\n void task.run().catch(() => {\n if (!controller.signal.aborted) {\n currentCoverage = { ...currentCoverage, indexing: false, unavailable: currentCoverage.unavailable + 1 };\n window.dispatchEvent(new Event(\"power-search-coverage\"));\n }\n });\n };\n restartSearchSync = sync;\n sync();\n const container = document.createElement(\"div\");\n const style = document.createElement(\"style\");\n style.textContent = `.power-resource-search-backdrop{position:fixed;inset:0;z-index:10000;background:#0007;display:flex;align-items:flex-start;justify-content:center;padding:8vh 20px}.power-resource-search-dialog{width:680px;max-width:100%;max-height:75vh;display:flex;flex-direction:column;padding:20px;gap:12px;background:var(--mainBackground,#222);color:var(--textColorPrimary,#eee);border:1px solid var(--borderColor,#555);border-radius:8px;box-shadow:0 16px 60px #0008}.power-resource-search-heading{display:flex;align-items:center;justify-content:space-between}.power-resource-search-filters{display:flex;align-items:end;gap:8px;flex-wrap:wrap}.power-resource-search-filter{display:flex;flex-direction:column;gap:4px;flex:1;min-width:180px;font-size:12px;color:var(--textColorSecondary)}.power-resource-search-filter .Select{width:100%;min-width:0}.power-resource-search-filter .Select__control{min-height:30px;height:30px;font-size:12px}.power-resource-search-filter .Select__value-container{padding:0 8px;min-width:0}.power-resource-search-filter .Select__input-container{margin:0;padding:0}.power-resource-search-filter .Select__indicator{padding:4px 8px}.power-resource-search-filter .Select__single-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.power-resource-search-dialog > input,.power-resource-search-dialog details input{padding:12px;font-size:16px;background:var(--inputControlBackground,#333);color:inherit;border:1px solid var(--borderColor,#666);border-radius:4px}.power-resource-search-dialog p{margin:0;font-size:12px;color:var(--textColorSecondary,#bbb)}.power-resource-search-results{overflow:auto;min-height:180px;flex:1}.power-resource-search-row{display:flex;align-items:center;gap:12px;padding:4px;border-radius:4px}.power-resource-search-row{border-left:3px solid transparent}.power-resource-search-row.selected{background:color-mix(in srgb,var(--primary,#668fff) 16%,var(--mainBackground,#222));color:var(--tableSelectedRowColor,var(--textColorPrimary,#eee));border-left-color:var(--primary,#668fff)}.power-resource-search-row.selected .Icon{color:var(--primary,#8daaff)}.power-resource-search-row:hover{background:var(--tableBgcSelected,var(--contentColor,#292d32))}.power-resource-search-dialog button:hover:not(:disabled){background:#ffffff12;border-radius:4px}.power-resource-search-row>button:last-child{border:1px solid var(--borderColor,#555);border-radius:4px}.power-resource-search-footer{display:flex;align-items:center;justify-content:space-between;gap:8px;padding-top:8px;border-top:1px solid var(--borderColor,#555)}.power-resource-search-shortcuts{flex:1}.power-resource-search-footer .power-resource-search-clear{display:inline-flex;align-items:center;justify-content:center;min-width:28px;min-height:28px;width:28px;height:28px;padding:0}.power-resource-search-dialog button{min-width:32px;min-height:32px;padding:6px;color:inherit;background:transparent;border:0;cursor:pointer}.power-resource-search-dialog button:disabled{opacity:.45;cursor:default}.power-resource-search-open{display:flex;align-items:center;gap:12px;flex:1;text-align:left;min-width:0}.power-resource-search-open span{min-width:0}.power-resource-search-open strong,.power-resource-search-open small{display:block;overflow-wrap:anywhere}.power-resource-search-open small{opacity:.7}.power-resource-search-row>button.power-resource-search-ai{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:34px;height:34px;min-width:34px;padding:0;background:var(--contentColor,#292d32);border:1px solid var(--borderColor,#666)}.power-resource-search-row.selected>button.power-resource-search-ai{background:var(--mainBackground,#202326);border-color:color-mix(in srgb,var(--textColorPrimary,#ddd) 55%,var(--borderColor,#666));box-shadow:0 1px 3px #0002}.power-resource-search-row.selected>button.power-resource-search-ai .Icon{color:var(--textColorAccent,#eee)}.power-resource-search-row>button.power-resource-search-ai:hover:not(:disabled){background:var(--contentColor,#30343a);border-color:var(--primary,#8daaff);color:var(--primary,#8daaff)}.power-resource-search-row>button.power-resource-search-ai:hover:not(:disabled) .Icon{color:inherit}.power-resource-search-dialog button:focus-visible{outline:2px solid var(--primary,#668fff)}`;\n document.head.append(style);\n document.body.append(container);\n let visible = false;\n const close = () => {\n if (visible) {\n visible = false;\n ReactDOM.unmountComponentAtNode(container);\n }\n };\n const show = () => {\n const clusterId = activeId();\n if (\n window.top === window ||\n !clusterId ||\n visible ||\n document.querySelector('[aria-modal=\"true\"], .Dialog.is-opened')\n )\n return;\n visible = true;\n ReactDOM.render(<SearchDialog close={close} clusterId={clusterId} />, container);\n };\n let shift = createDoubleShift();\n const resetShift = () => {\n shift = createDoubleShift();\n };\n const key = (event: KeyboardEvent) => {\n if (event.isComposing || event.key === \"Process\") {\n resetShift();\n return;\n }\n if (shift(event, performance.now())) show();\n };\n window.addEventListener(\"blur\", resetShift);\n window.addEventListener(\"keydown\", key);\n window.addEventListener(\"keyup\", key);\n window.addEventListener(OPEN, show);\n const dispose = reaction(\n () => `${searchContextId()}/${Renderer.Catalog.activeCluster.get()?.status?.phase}`,\n () => {\n close();\n sync();\n },\n );\n return () => {\n close();\n syncAbort?.abort();\n closeSearchResourceFallback();\n restartSearchSync = () => {};\n dispose();\n window.removeEventListener(\"blur\", resetShift);\n window.removeEventListener(\"keydown\", key);\n window.removeEventListener(\"keyup\", key);\n window.removeEventListener(OPEN, show);\n container.remove();\n style.remove();\n };\n}\n"],"mappings":"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"}
|
|
@@ -40,10 +40,11 @@ function attachment(route, gateway, listener, inventory) {
|
|
|
40
40
|
return false;
|
|
41
41
|
}) ? "allowedRoutes namespace selector matches" : "allowedRoutes namespace selector rejects";
|
|
42
42
|
}
|
|
43
|
-
function gatewayRelationships(service, inventory) {
|
|
43
|
+
function gatewayRelationships(service, inventory, showAll = false) {
|
|
44
44
|
const nodes = [];
|
|
45
45
|
const edges = [];
|
|
46
46
|
let omitted = 0;
|
|
47
|
+
const limit = showAll ? Infinity : 5;
|
|
47
48
|
const add = (object, suffix = "", detail = "", evidence) => {
|
|
48
49
|
const id = `gateway-api/${object.kind}/${object.metadata.namespace ?? ""}/${object.metadata.name}${suffix}`;
|
|
49
50
|
if (!nodes.some((node) => node.id === id)) nodes.push({
|
|
@@ -65,7 +66,7 @@ function gatewayRelationships(service, inventory) {
|
|
|
65
66
|
"TLSRoute",
|
|
66
67
|
"UDPRoute"
|
|
67
68
|
].includes(object.kind) && object.spec?.rules?.some((rule) => rule.backendRefs?.some((backend) => (backend.group ?? "") === "" && (backend.kind ?? "Service") === "Service" && backend.name === service.metadata.name && (backend.namespace ?? object.metadata.namespace) === service.metadata.namespace)));
|
|
68
|
-
for (const route of routes.slice(0,
|
|
69
|
+
for (const route of routes.slice(0, limit)) {
|
|
69
70
|
const backends = route.spec?.rules?.flatMap((rule) => rule.backendRefs ?? []).filter((backend) => (backend.group ?? "") === "" && (backend.kind ?? "Service") === "Service" && backend.name === service.metadata.name && (backend.namespace ?? route.metadata.namespace) === service.metadata.namespace) ?? [];
|
|
70
71
|
const summaries = route.spec?.rules?.flatMap((rule) => rule.matches?.map((match) => match.path?.value ?? [match.method?.service, match.method?.method].filter(Boolean).join("/")) ?? []) ?? [];
|
|
71
72
|
const routeId = add(route, "", [
|
|
@@ -79,14 +80,14 @@ function gatewayRelationships(service, inventory) {
|
|
|
79
80
|
label: "backend reference (not reachability)"
|
|
80
81
|
});
|
|
81
82
|
const parents = (route.spec?.parentRefs ?? []).filter((parent) => (parent.group ?? GROUP) === GROUP && (parent.kind ?? "Gateway") === "Gateway");
|
|
82
|
-
omitted += Math.max(0, parents.length -
|
|
83
|
-
for (const parent of parents.slice(0,
|
|
83
|
+
omitted += Math.max(0, parents.length - limit);
|
|
84
|
+
for (const parent of parents.slice(0, limit)) {
|
|
84
85
|
const gateway = inventory.objects.find((object) => object.kind === "Gateway" && object.metadata.name === parent.name && object.metadata.namespace === (parent.namespace ?? route.metadata.namespace));
|
|
85
86
|
if (!gateway) continue;
|
|
86
87
|
const status = route.status?.parents?.filter((item) => (item.parentRef.group ?? GROUP) === GROUP && (item.parentRef.kind ?? "Gateway") === "Gateway" && item.parentRef.name === parent.name && (item.parentRef.namespace ?? route.metadata.namespace) === (parent.namespace ?? route.metadata.namespace) && item.parentRef.sectionName === parent.sectionName && item.parentRef.port === parent.port);
|
|
87
88
|
const listeners = (gateway.spec?.listeners ?? []).filter((item) => (!parent.sectionName || parent.sectionName === item.name) && (!parent.port || parent.port === item.port));
|
|
88
|
-
omitted += Math.max(0, listeners.length -
|
|
89
|
-
for (const listener of listeners.slice(0,
|
|
89
|
+
omitted += Math.max(0, listeners.length - limit);
|
|
90
|
+
for (const listener of listeners.slice(0, limit)) {
|
|
90
91
|
const listenerStatus = gateway.status?.listeners?.find((item) => item.name === listener.name);
|
|
91
92
|
const id = add(gateway, `/${listener.name}`, `${listener.protocol}:${listener.port}`, [`Listener ${conditions(listenerStatus?.conditions, gateway.metadata.generation, [
|
|
92
93
|
"Accepted",
|
|
@@ -113,7 +114,7 @@ function gatewayRelationships(service, inventory) {
|
|
|
113
114
|
return {
|
|
114
115
|
nodes,
|
|
115
116
|
edges,
|
|
116
|
-
omitted: omitted + Math.max(0, routes.length -
|
|
117
|
+
omitted: omitted + Math.max(0, routes.length - limit)
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
120
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-relationships.js","names":[],"sources":["../../../../src/common/gateway-relationships.ts"],"sourcesContent":["import type { RelationshipEdge, RelationshipNode } from \"./resource-relationships\";\n\nconst GROUP = \"gateway.networking.k8s.io\";\ninterface Condition {\n type: string;\n status: string;\n reason?: string;\n observedGeneration?: number;\n}\ninterface Reference {\n name: string;\n namespace?: string;\n group?: string;\n kind?: string;\n sectionName?: string;\n port?: number;\n weight?: number;\n}\ninterface Listener {\n name: string;\n port: number;\n protocol: string;\n allowedRoutes?: {\n kinds?: { group?: string; kind: string }[];\n namespaces?: {\n from?: string;\n selector?: {\n matchLabels?: Record<string, string>;\n matchExpressions?: { key: string; operator: string; values?: string[] }[];\n };\n };\n };\n}\nexport interface GatewayObject {\n apiVersion?: string;\n kind: string;\n selfLink?: string;\n metadata: { name?: string; namespace?: string; uid?: string; generation?: number; labels?: Record<string, string> };\n spec?: {\n gatewayClassName?: string;\n listeners?: Listener[];\n parentRefs?: Reference[];\n hostnames?: string[];\n rules?: {\n backendRefs?: Reference[];\n matches?: { path?: { value?: string }; method?: { service?: string; method?: string } }[];\n }[];\n from?: { group: string; kind: string; namespace: string }[];\n to?: { group: string; kind: string; name?: string }[];\n };\n status?: {\n conditions?: Condition[];\n listeners?: { name: string; conditions?: Condition[] }[];\n parents?: { parentRef: Reference; controllerName?: string; conditions?: Condition[] }[];\n };\n}\nexport interface GatewayInventory {\n objects: GatewayObject[];\n grants?: GatewayObject[];\n grantNamespaces?: string[];\n namespaces?: GatewayObject[];\n gaps: string[];\n}\nfunction conditions(items: Condition[] | undefined, generation?: number, types = [\"Accepted\", \"ResolvedRefs\"]) {\n return types\n .map((type) => {\n const condition = items?.find((item) => item.type === type);\n const stale = generation !== undefined && condition?.observedGeneration !== generation;\n return `${type}: ${condition ? `${condition.status}${condition.reason ? ` (${condition.reason})` : \"\"}${stale ? \" · stale/unknown generation\" : \"\"}` : \"unknown\"}`;\n })\n .join(\" · \");\n}\nfunction grantEvidence(route: GatewayObject, backend: Reference, inventory: GatewayInventory) {\n const namespace = backend.namespace ?? route.metadata.namespace;\n if (namespace === route.metadata.namespace) return \"same-namespace backend\";\n if (!inventory.grants || (inventory.grantNamespaces && !inventory.grantNamespaces.includes(namespace ?? \"\")))\n return \"cross-namespace ReferenceGrant unknown\";\n const granted = inventory.grants.some(\n (grant) =>\n grant.metadata.namespace === namespace &&\n grant.spec?.from?.some(\n (from) => from.group === GROUP && from.kind === route.kind && from.namespace === route.metadata.namespace,\n ) &&\n grant.spec?.to?.some((to) => to.group === \"\" && to.kind === \"Service\" && (!to.name || to.name === backend.name)),\n );\n return granted\n ? \"cross-namespace ReferenceGrant permits backend\"\n : \"cross-namespace backend not permitted by observed ReferenceGrants\";\n}\nfunction attachment(route: GatewayObject, gateway: GatewayObject, listener: Listener, inventory: GatewayInventory) {\n const kinds = listener.allowedRoutes?.kinds;\n if (kinds && !kinds.some((kind) => (kind.group ?? GROUP) === GROUP && kind.kind === route.kind))\n return \"listener rejects route kind\";\n if (!kinds) {\n const supported: Record<string, string[]> = {\n HTTP: [\"HTTPRoute\", \"GRPCRoute\"],\n HTTPS: [\"HTTPRoute\", \"GRPCRoute\"],\n TCP: [\"TCPRoute\"],\n TLS: [\"TLSRoute\"],\n UDP: [\"UDPRoute\"],\n };\n if (!supported[listener.protocol]?.includes(route.kind))\n return \"listener rejects route kind or protocol support unknown\";\n }\n const ns = listener.allowedRoutes?.namespaces;\n if (!ns || !ns.from || ns.from === \"Same\")\n return route.metadata.namespace === gateway.metadata.namespace\n ? \"allowedRoutes permits namespace\"\n : \"allowedRoutes rejects namespace\";\n if (ns.from === \"All\") return \"allowedRoutes permits all namespaces\";\n if (ns.from !== \"Selector\") return \"allowedRoutes namespace unknown\";\n const namespace = inventory.namespaces?.find((item) => item.metadata.name === route.metadata.namespace);\n if (!namespace) return \"allowedRoutes namespace selector unknown\";\n const labels = namespace.metadata.labels ?? {};\n const selected =\n Object.entries(ns.selector?.matchLabels ?? {}).every(([key, value]) => labels[key] === value) &&\n (ns.selector?.matchExpressions ?? []).every(({ key, operator, values = [] }) => {\n if (operator === \"In\") return labels[key] !== undefined && values.includes(labels[key]);\n if (operator === \"NotIn\") return labels[key] === undefined || !values.includes(labels[key]);\n if (operator === \"Exists\") return labels[key] !== undefined;\n if (operator === \"DoesNotExist\") return labels[key] === undefined;\n return false;\n });\n return selected ? \"allowedRoutes namespace selector matches\" : \"allowedRoutes namespace selector rejects\";\n}\nexport function gatewayRelationships(\n service: { metadata: { name?: string; namespace?: string } },\n inventory: GatewayInventory,\n) {\n const nodes: RelationshipNode[] = [];\n const edges: RelationshipEdge[] = [];\n let omitted = 0;\n const add = (object: GatewayObject, suffix = \"\", detail = \"\", evidence?: string[]) => {\n const id = `gateway-api/${object.kind}/${object.metadata.namespace ?? \"\"}/${object.metadata.name}${suffix}`;\n if (!nodes.some((node) => node.id === id))\n nodes.push({\n id,\n kind: object.kind,\n name: `${object.metadata.name}${suffix}`,\n role: \"upstream\",\n tone: \"neutral\",\n detail,\n evidence,\n selfLink: object.selfLink,\n });\n return id;\n };\n const routes = inventory.objects.filter(\n (object) =>\n [\"HTTPRoute\", \"GRPCRoute\", \"TCPRoute\", \"TLSRoute\", \"UDPRoute\"].includes(object.kind) &&\n object.spec?.rules?.some((rule) =>\n rule.backendRefs?.some(\n (backend) =>\n (backend.group ?? \"\") === \"\" &&\n (backend.kind ?? \"Service\") === \"Service\" &&\n backend.name === service.metadata.name &&\n (backend.namespace ?? object.metadata.namespace) === service.metadata.namespace,\n ),\n ),\n );\n for (const route of routes.slice(0, 5)) {\n const backends =\n route.spec?.rules\n ?.flatMap((rule) => rule.backendRefs ?? [])\n .filter(\n (backend) =>\n (backend.group ?? \"\") === \"\" &&\n (backend.kind ?? \"Service\") === \"Service\" &&\n backend.name === service.metadata.name &&\n (backend.namespace ?? route.metadata.namespace) === service.metadata.namespace,\n ) ?? [];\n const summaries =\n route.spec?.rules?.flatMap(\n (rule) =>\n rule.matches?.map(\n (match) => match.path?.value ?? [match.method?.service, match.method?.method].filter(Boolean).join(\"/\"),\n ) ?? [],\n ) ?? [];\n const routeId = add(\n route,\n \"\",\n [\n ...(route.spec?.hostnames ?? []),\n ...summaries,\n ...backends.map((backend) => `weight ${backend.weight ?? 1} · ${grantEvidence(route, backend, inventory)}`),\n ].join(\" · \"),\n );\n edges.push({ from: routeId, to: \"service\", label: \"backend reference (not reachability)\" });\n const parents = (route.spec?.parentRefs ?? []).filter(\n (parent) => (parent.group ?? GROUP) === GROUP && (parent.kind ?? \"Gateway\") === \"Gateway\",\n );\n omitted += Math.max(0, parents.length - 5);\n for (const parent of parents.slice(0, 5)) {\n const gateway = inventory.objects.find(\n (object) =>\n object.kind === \"Gateway\" &&\n object.metadata.name === parent.name &&\n object.metadata.namespace === (parent.namespace ?? route.metadata.namespace),\n );\n if (!gateway) continue;\n const status = route.status?.parents?.filter(\n (item) =>\n (item.parentRef.group ?? GROUP) === GROUP &&\n (item.parentRef.kind ?? \"Gateway\") === \"Gateway\" &&\n item.parentRef.name === parent.name &&\n (item.parentRef.namespace ?? route.metadata.namespace) === (parent.namespace ?? route.metadata.namespace) &&\n item.parentRef.sectionName === parent.sectionName &&\n item.parentRef.port === parent.port,\n );\n const listeners = (gateway.spec?.listeners ?? []).filter(\n (item) =>\n (!parent.sectionName || parent.sectionName === item.name) && (!parent.port || parent.port === item.port),\n );\n omitted += Math.max(0, listeners.length - 5);\n for (const listener of listeners.slice(0, 5)) {\n const listenerStatus = gateway.status?.listeners?.find((item) => item.name === listener.name);\n const id = add(gateway, `/${listener.name}`, `${listener.protocol}:${listener.port}`, [\n `Listener ${conditions(listenerStatus?.conditions, gateway.metadata.generation, [\"Accepted\", \"ResolvedRefs\", \"Programmed\"])}`,\n `Gateway ${conditions(gateway.status?.conditions, gateway.metadata.generation, [\"Accepted\", \"Programmed\"])}`,\n ]);\n edges.push({\n from: id,\n to: routeId,\n label: \"parent reference\",\n evidence: `${attachment(route, gateway, listener, inventory)} · ${status?.length ? status.map((item) => `${item.controllerName ?? \"unknown controller\"}: ${conditions(item.conditions, route.metadata.generation)}`).join(\" · \") : \"parent reference · acceptance unknown\"}`,\n });\n const gatewayClass = inventory.objects.find(\n (object) => object.kind === \"GatewayClass\" && object.metadata.name === gateway.spec?.gatewayClassName,\n );\n if (gatewayClass)\n edges.push({\n from: add(\n gatewayClass,\n \"\",\n conditions(gatewayClass.status?.conditions, gatewayClass.metadata.generation, [\"Accepted\"]),\n ),\n to: id,\n label: \"class reference\",\n });\n }\n }\n }\n const order = (kind: string) => (kind === \"GatewayClass\" ? 0 : kind === \"Gateway\" ? 1 : 2);\n nodes.sort((a, b) => order(a.kind) - order(b.kind));\n return { nodes, edges, omitted: omitted + Math.max(0, routes.length - 5) };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gateway-relationships.js","names":[],"sources":["../../../../src/common/gateway-relationships.ts"],"sourcesContent":["import type { RelationshipEdge, RelationshipNode } from \"./resource-relationships\";\n\nconst GROUP = \"gateway.networking.k8s.io\";\ninterface Condition {\n type: string;\n status: string;\n reason?: string;\n observedGeneration?: number;\n}\ninterface Reference {\n name: string;\n namespace?: string;\n group?: string;\n kind?: string;\n sectionName?: string;\n port?: number;\n weight?: number;\n}\ninterface Listener {\n name: string;\n port: number;\n protocol: string;\n allowedRoutes?: {\n kinds?: { group?: string; kind: string }[];\n namespaces?: {\n from?: string;\n selector?: {\n matchLabels?: Record<string, string>;\n matchExpressions?: { key: string; operator: string; values?: string[] }[];\n };\n };\n };\n}\nexport interface GatewayObject {\n apiVersion?: string;\n kind: string;\n selfLink?: string;\n metadata: { name?: string; namespace?: string; uid?: string; generation?: number; labels?: Record<string, string> };\n spec?: {\n gatewayClassName?: string;\n listeners?: Listener[];\n parentRefs?: Reference[];\n hostnames?: string[];\n rules?: {\n backendRefs?: Reference[];\n matches?: { path?: { value?: string }; method?: { service?: string; method?: string } }[];\n }[];\n from?: { group: string; kind: string; namespace: string }[];\n to?: { group: string; kind: string; name?: string }[];\n };\n status?: {\n conditions?: Condition[];\n listeners?: { name: string; conditions?: Condition[] }[];\n parents?: { parentRef: Reference; controllerName?: string; conditions?: Condition[] }[];\n };\n}\nexport interface GatewayInventory {\n objects: GatewayObject[];\n grants?: GatewayObject[];\n grantNamespaces?: string[];\n namespaces?: GatewayObject[];\n gaps: string[];\n}\nfunction conditions(items: Condition[] | undefined, generation?: number, types = [\"Accepted\", \"ResolvedRefs\"]) {\n return types\n .map((type) => {\n const condition = items?.find((item) => item.type === type);\n const stale = generation !== undefined && condition?.observedGeneration !== generation;\n return `${type}: ${condition ? `${condition.status}${condition.reason ? ` (${condition.reason})` : \"\"}${stale ? \" · stale/unknown generation\" : \"\"}` : \"unknown\"}`;\n })\n .join(\" · \");\n}\nfunction grantEvidence(route: GatewayObject, backend: Reference, inventory: GatewayInventory) {\n const namespace = backend.namespace ?? route.metadata.namespace;\n if (namespace === route.metadata.namespace) return \"same-namespace backend\";\n if (!inventory.grants || (inventory.grantNamespaces && !inventory.grantNamespaces.includes(namespace ?? \"\")))\n return \"cross-namespace ReferenceGrant unknown\";\n const granted = inventory.grants.some(\n (grant) =>\n grant.metadata.namespace === namespace &&\n grant.spec?.from?.some(\n (from) => from.group === GROUP && from.kind === route.kind && from.namespace === route.metadata.namespace,\n ) &&\n grant.spec?.to?.some((to) => to.group === \"\" && to.kind === \"Service\" && (!to.name || to.name === backend.name)),\n );\n return granted\n ? \"cross-namespace ReferenceGrant permits backend\"\n : \"cross-namespace backend not permitted by observed ReferenceGrants\";\n}\nfunction attachment(route: GatewayObject, gateway: GatewayObject, listener: Listener, inventory: GatewayInventory) {\n const kinds = listener.allowedRoutes?.kinds;\n if (kinds && !kinds.some((kind) => (kind.group ?? GROUP) === GROUP && kind.kind === route.kind))\n return \"listener rejects route kind\";\n if (!kinds) {\n const supported: Record<string, string[]> = {\n HTTP: [\"HTTPRoute\", \"GRPCRoute\"],\n HTTPS: [\"HTTPRoute\", \"GRPCRoute\"],\n TCP: [\"TCPRoute\"],\n TLS: [\"TLSRoute\"],\n UDP: [\"UDPRoute\"],\n };\n if (!supported[listener.protocol]?.includes(route.kind))\n return \"listener rejects route kind or protocol support unknown\";\n }\n const ns = listener.allowedRoutes?.namespaces;\n if (!ns || !ns.from || ns.from === \"Same\")\n return route.metadata.namespace === gateway.metadata.namespace\n ? \"allowedRoutes permits namespace\"\n : \"allowedRoutes rejects namespace\";\n if (ns.from === \"All\") return \"allowedRoutes permits all namespaces\";\n if (ns.from !== \"Selector\") return \"allowedRoutes namespace unknown\";\n const namespace = inventory.namespaces?.find((item) => item.metadata.name === route.metadata.namespace);\n if (!namespace) return \"allowedRoutes namespace selector unknown\";\n const labels = namespace.metadata.labels ?? {};\n const selected =\n Object.entries(ns.selector?.matchLabels ?? {}).every(([key, value]) => labels[key] === value) &&\n (ns.selector?.matchExpressions ?? []).every(({ key, operator, values = [] }) => {\n if (operator === \"In\") return labels[key] !== undefined && values.includes(labels[key]);\n if (operator === \"NotIn\") return labels[key] === undefined || !values.includes(labels[key]);\n if (operator === \"Exists\") return labels[key] !== undefined;\n if (operator === \"DoesNotExist\") return labels[key] === undefined;\n return false;\n });\n return selected ? \"allowedRoutes namespace selector matches\" : \"allowedRoutes namespace selector rejects\";\n}\nexport function gatewayRelationships(\n service: { metadata: { name?: string; namespace?: string } },\n inventory: GatewayInventory,\n showAll = false,\n) {\n const nodes: RelationshipNode[] = [];\n const edges: RelationshipEdge[] = [];\n let omitted = 0;\n const limit = showAll ? Infinity : 5;\n const add = (object: GatewayObject, suffix = \"\", detail = \"\", evidence?: string[]) => {\n const id = `gateway-api/${object.kind}/${object.metadata.namespace ?? \"\"}/${object.metadata.name}${suffix}`;\n if (!nodes.some((node) => node.id === id))\n nodes.push({\n id,\n kind: object.kind,\n name: `${object.metadata.name}${suffix}`,\n role: \"upstream\",\n tone: \"neutral\",\n detail,\n evidence,\n selfLink: object.selfLink,\n });\n return id;\n };\n const routes = inventory.objects.filter(\n (object) =>\n [\"HTTPRoute\", \"GRPCRoute\", \"TCPRoute\", \"TLSRoute\", \"UDPRoute\"].includes(object.kind) &&\n object.spec?.rules?.some((rule) =>\n rule.backendRefs?.some(\n (backend) =>\n (backend.group ?? \"\") === \"\" &&\n (backend.kind ?? \"Service\") === \"Service\" &&\n backend.name === service.metadata.name &&\n (backend.namespace ?? object.metadata.namespace) === service.metadata.namespace,\n ),\n ),\n );\n for (const route of routes.slice(0, limit)) {\n const backends =\n route.spec?.rules\n ?.flatMap((rule) => rule.backendRefs ?? [])\n .filter(\n (backend) =>\n (backend.group ?? \"\") === \"\" &&\n (backend.kind ?? \"Service\") === \"Service\" &&\n backend.name === service.metadata.name &&\n (backend.namespace ?? route.metadata.namespace) === service.metadata.namespace,\n ) ?? [];\n const summaries =\n route.spec?.rules?.flatMap(\n (rule) =>\n rule.matches?.map(\n (match) => match.path?.value ?? [match.method?.service, match.method?.method].filter(Boolean).join(\"/\"),\n ) ?? [],\n ) ?? [];\n const routeId = add(\n route,\n \"\",\n [\n ...(route.spec?.hostnames ?? []),\n ...summaries,\n ...backends.map((backend) => `weight ${backend.weight ?? 1} · ${grantEvidence(route, backend, inventory)}`),\n ].join(\" · \"),\n );\n edges.push({ from: routeId, to: \"service\", label: \"backend reference (not reachability)\" });\n const parents = (route.spec?.parentRefs ?? []).filter(\n (parent) => (parent.group ?? GROUP) === GROUP && (parent.kind ?? \"Gateway\") === \"Gateway\",\n );\n omitted += Math.max(0, parents.length - limit);\n for (const parent of parents.slice(0, limit)) {\n const gateway = inventory.objects.find(\n (object) =>\n object.kind === \"Gateway\" &&\n object.metadata.name === parent.name &&\n object.metadata.namespace === (parent.namespace ?? route.metadata.namespace),\n );\n if (!gateway) continue;\n const status = route.status?.parents?.filter(\n (item) =>\n (item.parentRef.group ?? GROUP) === GROUP &&\n (item.parentRef.kind ?? \"Gateway\") === \"Gateway\" &&\n item.parentRef.name === parent.name &&\n (item.parentRef.namespace ?? route.metadata.namespace) === (parent.namespace ?? route.metadata.namespace) &&\n item.parentRef.sectionName === parent.sectionName &&\n item.parentRef.port === parent.port,\n );\n const listeners = (gateway.spec?.listeners ?? []).filter(\n (item) =>\n (!parent.sectionName || parent.sectionName === item.name) && (!parent.port || parent.port === item.port),\n );\n omitted += Math.max(0, listeners.length - limit);\n for (const listener of listeners.slice(0, limit)) {\n const listenerStatus = gateway.status?.listeners?.find((item) => item.name === listener.name);\n const id = add(gateway, `/${listener.name}`, `${listener.protocol}:${listener.port}`, [\n `Listener ${conditions(listenerStatus?.conditions, gateway.metadata.generation, [\"Accepted\", \"ResolvedRefs\", \"Programmed\"])}`,\n `Gateway ${conditions(gateway.status?.conditions, gateway.metadata.generation, [\"Accepted\", \"Programmed\"])}`,\n ]);\n edges.push({\n from: id,\n to: routeId,\n label: \"parent reference\",\n evidence: `${attachment(route, gateway, listener, inventory)} · ${status?.length ? status.map((item) => `${item.controllerName ?? \"unknown controller\"}: ${conditions(item.conditions, route.metadata.generation)}`).join(\" · \") : \"parent reference · acceptance unknown\"}`,\n });\n const gatewayClass = inventory.objects.find(\n (object) => object.kind === \"GatewayClass\" && object.metadata.name === gateway.spec?.gatewayClassName,\n );\n if (gatewayClass)\n edges.push({\n from: add(\n gatewayClass,\n \"\",\n conditions(gatewayClass.status?.conditions, gatewayClass.metadata.generation, [\"Accepted\"]),\n ),\n to: id,\n label: \"class reference\",\n });\n }\n }\n }\n const order = (kind: string) => (kind === \"GatewayClass\" ? 0 : kind === \"Gateway\" ? 1 : 2);\n nodes.sort((a, b) => order(a.kind) - order(b.kind));\n return { nodes, edges, omitted: omitted + Math.max(0, routes.length - limit) };\n}\n"],"mappings":"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"}
|
|
@@ -16,6 +16,18 @@ var descendants = (name, root) => `${name}(path, value, type) AS (
|
|
|
16
16
|
)`;
|
|
17
17
|
function predicate(filter) {
|
|
18
18
|
if (!filter || typeof filter.column !== "string" || !filter.column || filter.column.includes("\0") || !Array.isArray(filter.path) || filter.path.length > 128 || filter.operator !== "eq" && filter.operator !== "neq" || !require_power_log_field_filters.jsonValuesEqual(filter.value, filter.value)) throw new Error("Invalid JSON field filter");
|
|
19
|
+
if (filter.path.every((part) => typeof part === "number" && Number.isSafeInteger(part) && part >= 0 || typeof part === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(part)) && (filter.value === null || typeof filter.value !== "object")) {
|
|
20
|
+
const source = `"__power_rows".${quoteIdentifier(filter.column)}`;
|
|
21
|
+
const path = literal("$" + filter.path.map((part) => typeof part === "number" ? `[${part}]` : `.${part}`).join(""));
|
|
22
|
+
const type = `json_type(${source}, ${path})`;
|
|
23
|
+
const value = `json_extract(${source}, ${path})`;
|
|
24
|
+
let equal;
|
|
25
|
+
if (filter.value === null) equal = `${type} IS 'null'`;
|
|
26
|
+
else if (typeof filter.value === "boolean") equal = `${type} IS '${filter.value}'`;
|
|
27
|
+
else if (typeof filter.value === "number") equal = `(${type} IN ('integer', 'real') AND CAST(${value} AS REAL) IS ${filter.value})`;
|
|
28
|
+
else equal = `(${type} IS 'text' AND ${value} IS ${literal(filter.value)})`;
|
|
29
|
+
return `${filter.operator === "neq" ? "NOT " : ""}(CASE WHEN json_valid(${source}) THEN ${equal} ELSE 0 END)`;
|
|
30
|
+
}
|
|
19
31
|
const steps = [];
|
|
20
32
|
for (const [depth, part] of filter.path.entries()) {
|
|
21
33
|
if (typeof part !== "string" && !(typeof part === "number" && Number.isSafeInteger(part) && part >= 0)) throw new Error("Invalid JSON field path");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-log-json-filter-sql.js","names":[],"sources":["../../../../src/common/power-log-json-filter-sql.ts"],"sourcesContent":["import { type JsonFieldFilter, jsonValuesEqual } from \"./power-log-field-filters\";\n\nexport interface SqlJsonFieldFilter extends JsonFieldFilter {\n column: string;\n}\n\nconst maxSqlLength = 65536;\nconst normalizedType = (type: string) => `CASE WHEN ${type} IN ('integer', 'real') THEN 'number' ELSE ${type} END`;\nconst quoteIdentifier = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\nconst literal = (value: string): string =>\n value\n .split(\"\\0\")\n .map((part) => `'${part.replace(/'/g, \"''\")}'`)\n .join(\" || char(0) || \");\nconst tree = (name: string) => `SELECT path, ${normalizedType(\"type\")}, CASE\n WHEN type IN ('object', 'array') THEN NULL\n WHEN type IN ('integer', 'real') THEN CAST(value AS REAL)\n ELSE value END FROM ${name}`;\nconst descendants = (name: string, root: string) => `${name}(path, value, type) AS (\n ${root}\n UNION ALL\n SELECT json_insert(parent.path, '$[#]', j.key), j.value, j.type\n FROM ${name} AS parent, json_each(CASE WHEN parent.type IN ('object', 'array') THEN parent.value ELSE NULL END) AS j\n )`;\n\nfunction predicate(filter: SqlJsonFieldFilter): string {\n if (\n !filter ||\n typeof filter.column !== \"string\" ||\n !filter.column ||\n filter.column.includes(\"\\0\") ||\n !Array.isArray(filter.path) ||\n filter.path.length > 128 ||\n (filter.operator !== \"eq\" && filter.operator !== \"neq\") ||\n !jsonValuesEqual(filter.value, filter.value)\n )\n throw new Error(\"Invalid JSON field filter\");\n const steps: string[] = [];\n for (const [depth, part] of filter.path.entries()) {\n if (typeof part !== \"string\" && !(typeof part === \"number\" && Number.isSafeInteger(part) && part >= 0)) {\n throw new Error(\"Invalid JSON field path\");\n }\n steps.push(\n `(walk.depth = ${depth} AND typeof(j.key) = '${typeof part === \"string\" ? \"text\" : \"integer\"}' AND j.key = ${typeof part === \"string\" ? literal(part) : part})`,\n );\n }\n let encoded: string;\n try {\n encoded = JSON.stringify(filter.value);\n } catch {\n throw new Error(\"JSON field filter value cannot be encoded\");\n }\n const source = `\"__power_rows\".${quoteIdentifier(filter.column)}`;\n const safe = `CASE WHEN json_valid(${source}) THEN ${source} ELSE NULL END`;\n const recursion = steps.length\n ? `\n UNION ALL\n SELECT walk.depth + 1, j.value, j.type\n FROM walk, json_each(CASE WHEN walk.type IN ('object', 'array') THEN walk.value ELSE NULL END) AS j\n WHERE ${steps.join(\" OR \")}`\n : \"\";\n const leftTree = tree(\"actual_tree\");\n const rightTree = tree(\"expected_tree\");\n const equal = `EXISTS (\n SELECT 1 FROM walk, wanted WHERE walk.depth = ${filter.path.length}\n AND CASE\n WHEN walk.type IS NULL OR ${normalizedType(\"walk.type\")} != ${normalizedType(\"wanted.type\")} THEN 0\n WHEN walk.type IN ('object', 'array') THEN\n NOT EXISTS (${leftTree} EXCEPT ${rightTree}) AND NOT EXISTS (${rightTree} EXCEPT ${leftTree})\n WHEN walk.type IN ('integer', 'real') THEN CAST(walk.value AS REAL) IS CAST(wanted.value AS REAL)\n ELSE walk.value IS wanted.value\n END\n )`;\n return `(WITH RECURSIVE walk(depth, value, type) AS (\n SELECT 0, json_extract(${safe}, '$'), json_type(${safe})${recursion}\n ), wanted(value, type) AS (\n SELECT json_extract(${literal(encoded)}, '$'), json_type(${literal(encoded)})\n ), ${descendants(\"actual_tree\", `SELECT json_array(), value, type FROM walk WHERE depth = ${filter.path.length}`)},\n ${descendants(\"expected_tree\", \"SELECT json_array(), value, type FROM wanted\")}\n SELECT ${filter.operator === \"neq\" ? \"NOT \" : \"\"}${equal})`;\n}\n\nfunction withoutFinalSemicolon(sql: string): string {\n let lastCode = -1;\n for (let index = 0; index < sql.length; ) {\n const character = sql[index];\n if (/\\s/.test(character)) {\n index++;\n continue;\n }\n if (sql.startsWith(\"--\", index)) {\n const newline = sql.indexOf(\"\\n\", index + 2);\n index = newline < 0 ? sql.length : newline + 1;\n continue;\n }\n if (sql.startsWith(\"/*\", index)) {\n const end = sql.indexOf(\"*/\", index + 2);\n index = end < 0 ? sql.length : end + 2;\n continue;\n }\n lastCode = index;\n if (character === \"'\" || character === '\"' || character === \"`\" || character === \"[\") {\n const closing = character === \"[\" ? \"]\" : character;\n for (index++; index < sql.length; index++) {\n if (sql[index] !== closing) continue;\n if (closing !== \"]\" && sql[index + 1] === closing) {\n index++;\n continue;\n }\n index++;\n break;\n }\n } else index++;\n }\n return sql[lastCode] === \";\" ? sql.slice(0, lastCode) + sql.slice(lastCode + 1) : sql;\n}\n\nexport function buildJsonFilterSql(baseSql: string, filters: SqlJsonFieldFilter[]): string {\n if (typeof baseSql !== \"string\" || !Array.isArray(filters)) throw new Error(\"Invalid JSON filter query\");\n if (!filters.length) return baseSql;\n const base = withoutFinalSemicolon(baseSql.trim());\n if (!base) throw new Error(\"JSON filter query must not be empty\");\n let sql = `SELECT * FROM (${base}\\n) AS \"__power_rows\" WHERE `;\n for (const [index, filter] of filters.entries()) {\n sql += `${index ? \" AND \" : \"\"}${predicate(filter)}`;\n if (sql.length > maxSqlLength) throw new Error(\"JSON filter query exceeds 65536 characters\");\n }\n return sql;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"power-log-json-filter-sql.js","names":[],"sources":["../../../../src/common/power-log-json-filter-sql.ts"],"sourcesContent":["import { type JsonFieldFilter, jsonValuesEqual } from \"./power-log-field-filters\";\n\nexport interface SqlJsonFieldFilter extends JsonFieldFilter {\n column: string;\n}\n\nconst maxSqlLength = 65536;\nconst normalizedType = (type: string) => `CASE WHEN ${type} IN ('integer', 'real') THEN 'number' ELSE ${type} END`;\nconst quoteIdentifier = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\nconst literal = (value: string): string =>\n value\n .split(\"\\0\")\n .map((part) => `'${part.replace(/'/g, \"''\")}'`)\n .join(\" || char(0) || \");\nconst tree = (name: string) => `SELECT path, ${normalizedType(\"type\")}, CASE\n WHEN type IN ('object', 'array') THEN NULL\n WHEN type IN ('integer', 'real') THEN CAST(value AS REAL)\n ELSE value END FROM ${name}`;\nconst descendants = (name: string, root: string) => `${name}(path, value, type) AS (\n ${root}\n UNION ALL\n SELECT json_insert(parent.path, '$[#]', j.key), j.value, j.type\n FROM ${name} AS parent, json_each(CASE WHEN parent.type IN ('object', 'array') THEN parent.value ELSE NULL END) AS j\n )`;\n\nfunction predicate(filter: SqlJsonFieldFilter): string {\n if (\n !filter ||\n typeof filter.column !== \"string\" ||\n !filter.column ||\n filter.column.includes(\"\\0\") ||\n !Array.isArray(filter.path) ||\n filter.path.length > 128 ||\n (filter.operator !== \"eq\" && filter.operator !== \"neq\") ||\n !jsonValuesEqual(filter.value, filter.value)\n )\n throw new Error(\"Invalid JSON field filter\");\n const simplePath = filter.path.every(\n (part) =>\n (typeof part === \"number\" && Number.isSafeInteger(part) && part >= 0) ||\n (typeof part === \"string\" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(part)),\n );\n if (simplePath && (filter.value === null || typeof filter.value !== \"object\")) {\n const source = `\"__power_rows\".${quoteIdentifier(filter.column)}`;\n const path = literal(\n \"$\" + filter.path.map((part) => (typeof part === \"number\" ? `[${part}]` : `.${part}`)).join(\"\"),\n );\n const type = `json_type(${source}, ${path})`;\n const value = `json_extract(${source}, ${path})`;\n let equal: string;\n if (filter.value === null) equal = `${type} IS 'null'`;\n else if (typeof filter.value === \"boolean\") equal = `${type} IS '${filter.value}'`;\n else if (typeof filter.value === \"number\")\n equal = `(${type} IN ('integer', 'real') AND CAST(${value} AS REAL) IS ${filter.value})`;\n else equal = `(${type} IS 'text' AND ${value} IS ${literal(filter.value)})`;\n return `${filter.operator === \"neq\" ? \"NOT \" : \"\"}(CASE WHEN json_valid(${source}) THEN ${equal} ELSE 0 END)`;\n }\n const steps: string[] = [];\n for (const [depth, part] of filter.path.entries()) {\n if (typeof part !== \"string\" && !(typeof part === \"number\" && Number.isSafeInteger(part) && part >= 0)) {\n throw new Error(\"Invalid JSON field path\");\n }\n steps.push(\n `(walk.depth = ${depth} AND typeof(j.key) = '${typeof part === \"string\" ? \"text\" : \"integer\"}' AND j.key = ${typeof part === \"string\" ? literal(part) : part})`,\n );\n }\n let encoded: string;\n try {\n encoded = JSON.stringify(filter.value);\n } catch {\n throw new Error(\"JSON field filter value cannot be encoded\");\n }\n const source = `\"__power_rows\".${quoteIdentifier(filter.column)}`;\n const safe = `CASE WHEN json_valid(${source}) THEN ${source} ELSE NULL END`;\n const recursion = steps.length\n ? `\n UNION ALL\n SELECT walk.depth + 1, j.value, j.type\n FROM walk, json_each(CASE WHEN walk.type IN ('object', 'array') THEN walk.value ELSE NULL END) AS j\n WHERE ${steps.join(\" OR \")}`\n : \"\";\n const leftTree = tree(\"actual_tree\");\n const rightTree = tree(\"expected_tree\");\n const equal = `EXISTS (\n SELECT 1 FROM walk, wanted WHERE walk.depth = ${filter.path.length}\n AND CASE\n WHEN walk.type IS NULL OR ${normalizedType(\"walk.type\")} != ${normalizedType(\"wanted.type\")} THEN 0\n WHEN walk.type IN ('object', 'array') THEN\n NOT EXISTS (${leftTree} EXCEPT ${rightTree}) AND NOT EXISTS (${rightTree} EXCEPT ${leftTree})\n WHEN walk.type IN ('integer', 'real') THEN CAST(walk.value AS REAL) IS CAST(wanted.value AS REAL)\n ELSE walk.value IS wanted.value\n END\n )`;\n return `(WITH RECURSIVE walk(depth, value, type) AS (\n SELECT 0, json_extract(${safe}, '$'), json_type(${safe})${recursion}\n ), wanted(value, type) AS (\n SELECT json_extract(${literal(encoded)}, '$'), json_type(${literal(encoded)})\n ), ${descendants(\"actual_tree\", `SELECT json_array(), value, type FROM walk WHERE depth = ${filter.path.length}`)},\n ${descendants(\"expected_tree\", \"SELECT json_array(), value, type FROM wanted\")}\n SELECT ${filter.operator === \"neq\" ? \"NOT \" : \"\"}${equal})`;\n}\n\nfunction withoutFinalSemicolon(sql: string): string {\n let lastCode = -1;\n for (let index = 0; index < sql.length; ) {\n const character = sql[index];\n if (/\\s/.test(character)) {\n index++;\n continue;\n }\n if (sql.startsWith(\"--\", index)) {\n const newline = sql.indexOf(\"\\n\", index + 2);\n index = newline < 0 ? sql.length : newline + 1;\n continue;\n }\n if (sql.startsWith(\"/*\", index)) {\n const end = sql.indexOf(\"*/\", index + 2);\n index = end < 0 ? sql.length : end + 2;\n continue;\n }\n lastCode = index;\n if (character === \"'\" || character === '\"' || character === \"`\" || character === \"[\") {\n const closing = character === \"[\" ? \"]\" : character;\n for (index++; index < sql.length; index++) {\n if (sql[index] !== closing) continue;\n if (closing !== \"]\" && sql[index + 1] === closing) {\n index++;\n continue;\n }\n index++;\n break;\n }\n } else index++;\n }\n return sql[lastCode] === \";\" ? sql.slice(0, lastCode) + sql.slice(lastCode + 1) : sql;\n}\n\nexport function buildJsonFilterSql(baseSql: string, filters: SqlJsonFieldFilter[]): string {\n if (typeof baseSql !== \"string\" || !Array.isArray(filters)) throw new Error(\"Invalid JSON filter query\");\n if (!filters.length) return baseSql;\n const base = withoutFinalSemicolon(baseSql.trim());\n if (!base) throw new Error(\"JSON filter query must not be empty\");\n let sql = `SELECT * FROM (${base}\\n) AS \"__power_rows\" WHERE `;\n for (const [index, filter] of filters.entries()) {\n sql += `${index ? \" AND \" : \"\"}${predicate(filter)}`;\n if (sql.length > maxSqlLength) throw new Error(\"JSON filter query exceeds 65536 characters\");\n }\n return sql;\n}\n"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ JOIN json_each(recent.fieldsJson) AS field
|
|
|
5
5
|
WHERE typeof(field.key) = 'text'
|
|
6
6
|
LIMIT 65`;
|
|
7
7
|
function buildJsonProjectionSql(keys, options = {}) {
|
|
8
|
-
if (options.recent) return "SELECT
|
|
8
|
+
if (options.recent) return "SELECT timestamp, level, message, container, pod FROM logs ORDER BY timestamp DESC NULLS LAST LIMIT 200";
|
|
9
9
|
const fallbackSql = "SELECT fieldsJson FROM logs WHERE fieldsJson IS NOT NULL LIMIT 100";
|
|
10
10
|
if (!keys.length || keys.length > 64) return fallbackSql;
|
|
11
11
|
const usable = (key) => typeof key === "string" && key.length <= 128 && !key.includes("\0");
|
|
@@ -23,7 +23,7 @@ function buildJsonProjectionSql(keys, options = {}) {
|
|
|
23
23
|
if (key === void 0 || !usable(key)) return fallbackSql;
|
|
24
24
|
const literal = `'${key.replace(/'/g, "''")}'`;
|
|
25
25
|
const identifier = `"${key.replace(/"/g, "\"\"")}"`;
|
|
26
|
-
return `SELECT\n ${/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) ? `json_extract(logs.fieldsJson, '$.${key}')` : `(SELECT value FROM json_each(logs.fieldsJson) WHERE key = ${literal} LIMIT 1)`} AS ${identifier}\nFROM logs\nWHERE fieldsJson IS NOT NULL
|
|
26
|
+
return `SELECT\n ${/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) ? `json_extract(logs.fieldsJson, '$.${key}')` : `(SELECT value FROM json_each(logs.fieldsJson) WHERE key = ${literal} LIMIT 1)`} AS ${identifier},\n logs.fieldsJson AS "${key.toLowerCase() === "json" ? "fieldsJson" : "json"}"\nFROM logs\nWHERE fieldsJson IS NOT NULL
|
|
27
27
|
ORDER BY logs.timestamp DESC NULLS LAST
|
|
28
28
|
LIMIT 100`;
|
|
29
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-log-json-preset.js","names":[],"sources":["../../../../src/common/power-log-json-preset.ts"],"sourcesContent":["export const JSON_KEY_DISCOVERY_SQL = `SELECT DISTINCT field.key\nFROM (SELECT fieldsJson FROM logs WHERE fieldsJson IS NOT NULL ORDER BY timestamp DESC NULLS LAST LIMIT 100) AS recent\nJOIN json_each(recent.fieldsJson) AS field\nWHERE typeof(field.key) = 'text'\nLIMIT 65`;\n\nexport function buildJsonProjectionSql(keys: string[], options: { recent?: boolean } = {}): string {\n if (options.recent)\n return \"SELECT
|
|
1
|
+
{"version":3,"file":"power-log-json-preset.js","names":[],"sources":["../../../../src/common/power-log-json-preset.ts"],"sourcesContent":["export const JSON_KEY_DISCOVERY_SQL = `SELECT DISTINCT field.key\nFROM (SELECT fieldsJson FROM logs WHERE fieldsJson IS NOT NULL ORDER BY timestamp DESC NULLS LAST LIMIT 100) AS recent\nJOIN json_each(recent.fieldsJson) AS field\nWHERE typeof(field.key) = 'text'\nLIMIT 65`;\n\nexport function buildJsonProjectionSql(keys: string[], options: { recent?: boolean } = {}): string {\n if (options.recent)\n return \"SELECT timestamp, level, message, container, pod FROM logs ORDER BY timestamp DESC NULLS LAST LIMIT 200\";\n const fallbackSql = \"SELECT fieldsJson FROM logs WHERE fieldsJson IS NOT NULL LIMIT 100\";\n if (!keys.length || keys.length > 64) return fallbackSql;\n const usable = (key: string) => typeof key === \"string\" && key.length <= 128 && !key.includes(\"\\0\");\n const key =\n [\"message\", \"msg\", \"text\", \"Message\", \"RenderedMessage\", \"@m\", \"@mt\", \"body\", \"Body\"].find((preferred) =>\n keys.includes(preferred),\n ) ?? keys.find(usable);\n if (key === undefined || !usable(key)) return fallbackSql;\n const literal = `'${key.replace(/'/g, \"''\")}'`;\n const identifier = `\"${key.replace(/\"/g, '\"\"')}\"`;\n const value = /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)\n ? `json_extract(logs.fieldsJson, '$.${key}')`\n : `(SELECT value FROM json_each(logs.fieldsJson) WHERE key = ${literal} LIMIT 1)`;\n const tail = \"WHERE fieldsJson IS NOT NULL\\nORDER BY logs.timestamp DESC NULLS LAST\\nLIMIT 100\";\n return `SELECT\\n ${value} AS ${identifier},\\n logs.fieldsJson AS \"${key.toLowerCase() === \"json\" ? \"fieldsJson\" : \"json\"}\"\\nFROM logs\\n${tail}`;\n}\n"],"mappings":";AAAA,IAAa,yBAAyB;;;;;AAMtC,SAAgB,uBAAuB,MAAgB,UAAgC,CAAC,GAAW;CACjG,IAAI,QAAQ,QACV,OAAO;CACT,MAAM,cAAc;CACpB,IAAI,CAAC,KAAK,UAAU,KAAK,SAAS,IAAI,OAAO;CAC7C,MAAM,UAAU,QAAgB,OAAO,QAAQ,YAAY,IAAI,UAAU,OAAO,CAAC,IAAI,SAAS,IAAI;CAClG,MAAM,MACJ;EAAC;EAAW;EAAO;EAAQ;EAAW;EAAmB;EAAM;EAAO;EAAQ;CAAM,CAAC,CAAC,MAAM,cAC1F,KAAK,SAAS,SAAS,CACzB,KAAK,KAAK,KAAK,MAAM;CACvB,IAAI,QAAQ,KAAA,KAAa,CAAC,OAAO,GAAG,GAAG,OAAO;CAC9C,MAAM,UAAU,IAAI,IAAI,QAAQ,MAAM,IAAI,EAAE;CAC5C,MAAM,aAAa,IAAI,IAAI,QAAQ,MAAM,MAAI,EAAE;CAK/C,OAAO,aAJO,2BAA2B,KAAK,GAAG,IAC7C,oCAAoC,IAAI,MACxC,6DAA6D,QAAQ,WAE/C,MAAM,WAAW,2BAA2B,IAAI,YAAY,MAAM,SAAS,eAAe,OAAO;;;AAC7H"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-logs.js","names":[],"sources":["../../../../src/common/power-logs.ts"],"sourcesContent":["export const POWER_LOGS_CHANNEL = \"freelens-power:logs\";\nexport const POWER_LOGS_DEFAULT_LIMITS = {\n liveRows: 10000,\n liveBytes: 16 * 1024 * 1024,\n batchRows: 1000,\n batchBytes: 2 * 1024 * 1024,\n recordBytes: 64 * 1024,\n captureBytes: 256 * 1024 * 1024,\n captureRows: 1000000,\n captures: 3,\n sources: 200,\n resultRows: 1000,\n resultBytes: 2 * 1024 * 1024,\n queryMs: 15000,\n ttlMs: 60 * 60 * 1000,\n historyLines: 1000,\n historyBytes: 1024 * 1024,\n captureSourceLines: 10000,\n captureSourceBytes: 8 * 1024 * 1024,\n requestsPerSecond: 2,\n bytesPerSecond: 1024 * 1024,\n historyConcurrency: 2,\n liveConcurrency: 4,\n} as const;\n\nexport type PowerLogsLimitKey = keyof typeof POWER_LOGS_DEFAULT_LIMITS;\nexport type PowerLogsLimits = { [Key in PowerLogsLimitKey]: number };\nexport const POWER_LOGS_LIMITS: PowerLogsLimits = { ...POWER_LOGS_DEFAULT_LIMITS };\n\nexport type LogLevel = \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\" | \"fatal\" | \"unknown\";\nexport type LogWorkloadKind = \"Pod\" | \"Deployment\" | \"DaemonSet\" | \"StatefulSet\" | \"ReplicaSet\" | \"Job\" | \"CronJob\";\nexport interface LogTarget {\n kind: LogWorkloadKind;\n namespace: string;\n name: string;\n uid: string;\n}\nexport interface LogSource {\n namespace: string;\n pod: string;\n podUid: string;\n container: string;\n previous?: boolean;\n}\nexport interface LogRecord {\n timestamp: number | null;\n pod: string;\n podUid: string;\n container: string;\n level: LogLevel;\n message: string;\n fieldsJson: string | null;\n previous?: boolean;\n}\nexport interface LogCapture {\n id: string;\n target: LogTarget;\n createdAt: number;\n expiresAt: number;\n state: \"collecting\" | \"ready\" | \"partial\";\n rows: number;\n bytes: number;\n notes: string[];\n lastSql?: string;\n}\nexport type LogCell = string | number | boolean | null;\nexport interface LogQueryResult {\n error?: string;\n columns: string[];\n rows: LogCell[][];\n truncated: boolean;\n elapsedMs: number;\n}\nexport interface LogPartition {\n clusterId: string;\n contextId: string;\n}\nexport type LogRequest = LogPartition &\n (\n | { operation: \"permit\"; lane: \"start\" | \"bytes\"; bytes?: number }\n | { operation: \"stream-open\"; streamId: string; follow: boolean }\n | { operation: \"stream-close\" | \"stream-renew\"; streamId: string }\n | { operation: \"create\"; target: LogTarget }\n | { operation: \"append\"; captureId: string; records: LogRecord[] }\n | { operation: \"finish\"; captureId: string; partial: boolean; notes: string[] }\n | { operation: \"list\" }\n | { operation: \"delete\"; captureId: string }\n | { operation: \"save-sql\"; captureId: string; sql: string }\n | { operation: \"query\"; captureId: string; queryId: string; sql: string }\n | { operation: \"cancel\"; queryId: string }\n | { operation: \"export\"; captureId: string }\n );\n"],"mappings":";AAAA,IAAa,qBAAqB;AAClC,IAAa,4BAA4B;CACvC,UAAU;CACV,WAAW,KAAK,OAAO;CACvB,WAAW;CACX,YAAY,IAAI,OAAO;CACvB,aAAa,KAAK;CAClB,cAAc,MAAM,OAAO;CAC3B,aAAa;CACb,UAAU;CACV,SAAS;CACT,YAAY;CACZ,aAAa,IAAI,OAAO;CACxB,SAAS;CACT,OAAO,OAAU;CACjB,cAAc;CACd,cAAc,OAAO;CACrB,oBAAoB;CACpB,oBAAoB,IAAI,OAAO;CAC/B,mBAAmB;CACnB,gBAAgB,OAAO;CACvB,oBAAoB;CACpB,iBAAiB;AACnB;AAIA,IAAa,oBAAqC,EAAE,GAAG,0BAA0B"}
|
|
1
|
+
{"version":3,"file":"power-logs.js","names":[],"sources":["../../../../src/common/power-logs.ts"],"sourcesContent":["export const POWER_LOGS_CHANNEL = \"freelens-power:logs\";\nexport const POWER_LOGS_DEFAULT_LIMITS = {\n liveRows: 10000,\n liveBytes: 16 * 1024 * 1024,\n batchRows: 1000,\n batchBytes: 2 * 1024 * 1024,\n recordBytes: 64 * 1024,\n captureBytes: 256 * 1024 * 1024,\n captureRows: 1000000,\n captures: 3,\n sources: 200,\n resultRows: 1000,\n resultBytes: 2 * 1024 * 1024,\n queryMs: 15000,\n ttlMs: 60 * 60 * 1000,\n historyLines: 1000,\n historyBytes: 1024 * 1024,\n captureSourceLines: 10000,\n captureSourceBytes: 8 * 1024 * 1024,\n requestsPerSecond: 2,\n bytesPerSecond: 1024 * 1024,\n historyConcurrency: 2,\n liveConcurrency: 4,\n} as const;\n\nexport type PowerLogsLimitKey = keyof typeof POWER_LOGS_DEFAULT_LIMITS;\nexport type PowerLogsLimits = { [Key in PowerLogsLimitKey]: number };\nexport const POWER_LOGS_LIMITS: PowerLogsLimits = { ...POWER_LOGS_DEFAULT_LIMITS };\n\nexport type LogLevel = \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\" | \"fatal\" | \"unknown\";\nexport type LogWorkloadKind = \"Pod\" | \"Deployment\" | \"DaemonSet\" | \"StatefulSet\" | \"ReplicaSet\" | \"Job\" | \"CronJob\";\nexport interface LogTarget {\n kind: LogWorkloadKind;\n namespace: string;\n name: string;\n uid: string;\n}\nexport interface LogSource {\n namespace: string;\n pod: string;\n podUid: string;\n container: string;\n previous?: boolean;\n}\nexport interface LogRecord {\n timestamp: number | null;\n pod: string;\n podUid: string;\n container: string;\n level: LogLevel;\n message: string;\n fieldsJson: string | null;\n previous?: boolean;\n}\nexport interface LogCapture {\n id: string;\n target: LogTarget;\n createdAt: number;\n expiresAt: number;\n state: \"collecting\" | \"ready\" | \"partial\";\n rows: number;\n bytes: number;\n notes: string[];\n lastSql?: string;\n persistent?: boolean;\n}\nexport type LogCell = string | number | boolean | null;\nexport interface LogQueryResult {\n error?: string;\n columns: string[];\n rows: LogCell[][];\n truncated: boolean;\n elapsedMs: number;\n}\nexport interface LogPartition {\n clusterId: string;\n contextId: string;\n}\nexport type LogRequest = LogPartition &\n (\n | { operation: \"permit\"; lane: \"start\" | \"bytes\"; bytes?: number }\n | { operation: \"stream-open\"; streamId: string; follow: boolean }\n | { operation: \"stream-close\" | \"stream-renew\"; streamId: string }\n | { operation: \"create\"; target: LogTarget }\n | { operation: \"append\"; captureId: string; records: LogRecord[] }\n | { operation: \"finish\"; captureId: string; partial: boolean; notes: string[] }\n | { operation: \"list\" }\n | { operation: \"clear-persistent\" }\n | { operation: \"delete\"; captureId: string }\n | { operation: \"set-persistent\"; captureId: string; persistent: boolean }\n | { operation: \"save-sql\"; captureId: string; sql: string }\n | { operation: \"query\"; captureId: string; queryId: string; sql: string }\n | { operation: \"cancel\"; queryId: string }\n | { operation: \"export\"; captureId: string }\n );\n"],"mappings":";AAAA,IAAa,qBAAqB;AAClC,IAAa,4BAA4B;CACvC,UAAU;CACV,WAAW,KAAK,OAAO;CACvB,WAAW;CACX,YAAY,IAAI,OAAO;CACvB,aAAa,KAAK;CAClB,cAAc,MAAM,OAAO;CAC3B,aAAa;CACb,UAAU;CACV,SAAS;CACT,YAAY;CACZ,aAAa,IAAI,OAAO;CACxB,SAAS;CACT,OAAO,OAAU;CACjB,cAAc;CACd,cAAc,OAAO;CACrB,oBAAoB;CACpB,oBAAoB,IAAI,OAAO;CAC/B,mBAAmB;CACnB,gBAAgB,OAAO;CACvB,oBAAoB;CACpB,iBAAiB;AACnB;AAIA,IAAa,oBAAqC,EAAE,GAAG,0BAA0B"}
|
|
@@ -9,13 +9,15 @@ function relationshipReferencePath(kind, name, namespace) {
|
|
|
9
9
|
namespace
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
|
-
function buildResourceRelationships(service, inventory, focusedPod) {
|
|
12
|
+
function buildResourceRelationships(service, inventory, focusedPod, showAll = false) {
|
|
13
13
|
const external = service.spec?.type === "ExternalName";
|
|
14
14
|
const diagnostics = require_service_diagnostics.diagnoseService(service, external ? [] : inventory.pods, external ? [] : inventory.slices);
|
|
15
15
|
const nodes = [];
|
|
16
16
|
const edges = [];
|
|
17
17
|
const gaps = [...inventory.gaps ?? []];
|
|
18
18
|
let omitted = 0;
|
|
19
|
+
const upstreamLimit = showAll ? Infinity : 5;
|
|
20
|
+
const podLimit = showAll ? Infinity : 8;
|
|
19
21
|
const namespace = service.metadata.namespace;
|
|
20
22
|
const add = (node) => {
|
|
21
23
|
if (!nodes.some((n) => n.id === node.id)) nodes.push(node);
|
|
@@ -35,8 +37,8 @@ function buildResourceRelationships(service, inventory, focusedPod) {
|
|
|
35
37
|
ingress.spec?.backend?.serviceName,
|
|
36
38
|
...(ingress.spec?.rules ?? []).flatMap((rule) => (rule.http?.paths ?? []).map((path) => path.backend?.service?.name ?? path.backend?.serviceName))
|
|
37
39
|
].includes(service.metadata.name)) ?? [];
|
|
38
|
-
omitted += Math.max(0, upstream.length -
|
|
39
|
-
for (const ingress of upstream.slice(0,
|
|
40
|
+
omitted += Math.max(0, upstream.length - upstreamLimit);
|
|
41
|
+
for (const ingress of upstream.slice(0, upstreamLimit)) {
|
|
40
42
|
const id = add({
|
|
41
43
|
id: `ingress/${ingress.metadata.uid ?? ingress.metadata.name}`,
|
|
42
44
|
kind: "Ingress",
|
|
@@ -53,15 +55,15 @@ function buildResourceRelationships(service, inventory, focusedPod) {
|
|
|
53
55
|
});
|
|
54
56
|
}
|
|
55
57
|
if (inventory.gateway) {
|
|
56
|
-
const gateway = require_gateway_relationships.gatewayRelationships(service, inventory.gateway);
|
|
58
|
+
const gateway = require_gateway_relationships.gatewayRelationships(service, inventory.gateway, showAll);
|
|
57
59
|
nodes.push(...gateway.nodes);
|
|
58
60
|
edges.push(...gateway.edges);
|
|
59
61
|
omitted += gateway.omitted;
|
|
60
62
|
gaps.push(...inventory.gateway.gaps);
|
|
61
63
|
}
|
|
62
64
|
const pods = diagnostics.matchingPods?.filter((pod) => !focusedPod || pod.metadata.name === focusedPod) ?? [];
|
|
63
|
-
omitted += Math.max(0, pods.length -
|
|
64
|
-
for (const pod of pods.slice(0,
|
|
65
|
+
omitted += Math.max(0, pods.length - podLimit);
|
|
66
|
+
for (const pod of pods.slice(0, podLimit)) {
|
|
65
67
|
const ready = pod.status?.conditions?.find((c) => c.type === "Ready");
|
|
66
68
|
const id = add({
|
|
67
69
|
id: `pod/${pod.metadata.uid ?? pod.metadata.name}`,
|
|
@@ -122,8 +124,8 @@ function buildResourceRelationships(service, inventory, focusedPod) {
|
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
const slices = diagnostics.matchingSlices ?? [];
|
|
125
|
-
omitted += Math.max(0, slices.length -
|
|
126
|
-
for (const slice of slices.slice(0,
|
|
127
|
+
omitted += Math.max(0, slices.length - upstreamLimit);
|
|
128
|
+
for (const slice of slices.slice(0, upstreamLimit)) {
|
|
127
129
|
const ready = slice.endpoints?.filter((e) => e.conditions?.ready !== false && !e.conditions?.terminating).length;
|
|
128
130
|
const id = add({
|
|
129
131
|
id: `slice/${slice.metadata.name}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-relationships.js","names":[],"sources":["../../../../src/common/resource-relationships.ts"],"sourcesContent":["import { type GatewayInventory, gatewayRelationships } from \"./gateway-relationships\";\nimport { 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 evidence?: 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 evidence?: string;\n}\nexport interface RelationshipInventory {\n pods?: RelationshipPod[];\n slices?: RelationshipSlice[];\n ingresses?: RelationshipIngress[];\n owners?: RelationshipObject[];\n gaps?: string[];\n gateway?: GatewayInventory;\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 if (inventory.gateway) {\n const gateway = gatewayRelationships(service, inventory.gateway);\n nodes.push(...gateway.nodes);\n edges.push(...gateway.edges);\n omitted += gateway.omitted;\n gaps.push(...inventory.gateway.gaps);\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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"}
|
|
1
|
+
{"version":3,"file":"resource-relationships.js","names":[],"sources":["../../../../src/common/resource-relationships.ts"],"sourcesContent":["import { type GatewayInventory, gatewayRelationships } from \"./gateway-relationships\";\nimport { 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 evidence?: 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 evidence?: string;\n}\nexport interface RelationshipInventory {\n pods?: RelationshipPod[];\n slices?: RelationshipSlice[];\n ingresses?: RelationshipIngress[];\n owners?: RelationshipObject[];\n gaps?: string[];\n gateway?: GatewayInventory;\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 showAll = false,\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 upstreamLimit = showAll ? Infinity : 5;\n const podLimit = showAll ? Infinity : 8;\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 - upstreamLimit);\n for (const ingress of upstream.slice(0, upstreamLimit)) {\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 if (inventory.gateway) {\n const gateway = gatewayRelationships(service, inventory.gateway, showAll);\n nodes.push(...gateway.nodes);\n edges.push(...gateway.edges);\n omitted += gateway.omitted;\n gaps.push(...inventory.gateway.gaps);\n }\n const pods = diagnostics.matchingPods?.filter((pod) => !focusedPod || pod.metadata.name === focusedPod) ?? [];\n omitted += Math.max(0, pods.length - podLimit);\n for (const pod of pods.slice(0, podLimit)) {\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 - upstreamLimit);\n for (const slice of slices.slice(0, upstreamLimit)) {\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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"}
|