@64mb/freelens-power 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +3 -1
  2. package/out/main/common/dashboard-picker.js +4 -4
  3. package/out/main/common/dashboard-picker.js.map +1 -1
  4. package/out/main/common/dashboard-readiness.js +17 -47
  5. package/out/main/common/dashboard-readiness.js.map +1 -1
  6. package/out/main/common/dashboard-selector.js +40 -6
  7. package/out/main/common/dashboard-selector.js.map +1 -1
  8. package/out/main/common/store/monitoring-preferences-store.js +5 -0
  9. package/out/main/common/store/monitoring-preferences-store.js.map +1 -1
  10. package/out/main/dashboard-picker.js +1 -4
  11. package/out/main/dashboard-picker.js.map +1 -1
  12. package/out/main/dashboard-preload.js +110 -0
  13. package/out/main/dashboard-preload.js.map +1 -0
  14. package/out/main/dashboard-views.js +207 -62
  15. package/out/main/dashboard-views.js.map +1 -1
  16. package/out/main/dock-icon.js +25 -10
  17. package/out/main/dock-icon.js.map +1 -1
  18. package/out/main/index.js +2 -0
  19. package/out/main/index.js.map +1 -1
  20. package/out/main/splash-accent.js +84 -0
  21. package/out/main/splash-accent.js.map +1 -0
  22. package/out/renderer/ai-diagnostic-handoff.js +52 -0
  23. package/out/renderer/ai-diagnostic-handoff.js.map +1 -0
  24. package/out/renderer/ai-extras.js +40 -4
  25. package/out/renderer/ai-extras.js.map +1 -1
  26. package/out/renderer/ai.js +48 -56
  27. package/out/renderer/ai.js.map +1 -1
  28. package/out/renderer/appearance.js +6 -2
  29. package/out/renderer/appearance.js.map +1 -1
  30. package/out/renderer/dashboard.js +26 -6
  31. package/out/renderer/dashboard.js.map +1 -1
  32. package/out/renderer/details/hpa-diagnostics-details.js +46 -0
  33. package/out/renderer/details/hpa-diagnostics-details.js.map +1 -0
  34. package/out/renderer/details/namespace-health-details.js +91 -0
  35. package/out/renderer/details/namespace-health-details.js.map +1 -0
  36. package/out/renderer/details/resource-health-details.js +131 -0
  37. package/out/renderer/details/resource-health-details.js.map +1 -0
  38. package/out/renderer/details/service-diagnostics-details.js +163 -0
  39. package/out/renderer/details/service-diagnostics-details.js.map +1 -0
  40. package/out/renderer/diagnostic-explain-button.js +16 -0
  41. package/out/renderer/diagnostic-explain-button.js.map +1 -0
  42. package/out/renderer/diagnostics-state.js +51 -0
  43. package/out/renderer/diagnostics-state.js.map +1 -0
  44. package/out/renderer/diagnostics.css.js +9 -0
  45. package/out/renderer/diagnostics.css.js.map +1 -1
  46. package/out/renderer/diagnostics.js +369 -73
  47. package/out/renderer/diagnostics.js.map +1 -1
  48. package/out/renderer/incident-copy-button.js +50 -0
  49. package/out/renderer/incident-copy-button.js.map +1 -0
  50. package/out/renderer/index.js +91 -19
  51. package/out/renderer/index.js.map +1 -1
  52. package/out/renderer/monitoring-history-collection.js +1 -1
  53. package/out/renderer/preferences/dashboard-preferences.js +16 -16
  54. package/out/renderer/preferences/dashboard-preferences.js.map +1 -1
  55. package/out/renderer/preferences/monitoring-preferences.js +10 -0
  56. package/out/renderer/preferences/monitoring-preferences.js.map +1 -1
  57. package/out/renderer/preferences/preferences.js +88 -0
  58. package/out/renderer/preferences/preferences.js.map +1 -1
  59. package/out/renderer/preferences/settings-modal.js.map +1 -1
  60. package/out/renderer/resource-relationships.css.js +7 -0
  61. package/out/renderer/resource-relationships.css.js.map +1 -0
  62. package/out/renderer/resource-relationships.js +556 -0
  63. package/out/renderer/resource-relationships.js.map +1 -0
  64. package/out/renderer/resource-search-distance.js +24 -0
  65. package/out/renderer/resource-search-distance.js.map +1 -0
  66. package/out/renderer/resource-search-fields.js +55 -0
  67. package/out/renderer/resource-search-fields.js.map +1 -0
  68. package/out/renderer/resource-search-model.js +193 -0
  69. package/out/renderer/resource-search-model.js.map +1 -0
  70. package/out/renderer/resource-search.js +365 -0
  71. package/out/renderer/resource-search.js.map +1 -0
  72. package/out/renderer/src/common/dashboard-picker.js.map +1 -1
  73. package/out/renderer/src/common/dashboard-selector.js.map +1 -1
  74. package/out/renderer/src/common/hpa-diagnostics.js +24 -0
  75. package/out/renderer/src/common/hpa-diagnostics.js.map +1 -0
  76. package/out/renderer/src/common/incident-summary.js +73 -0
  77. package/out/renderer/src/common/incident-summary.js.map +1 -0
  78. package/out/renderer/src/common/resource-health.js +92 -0
  79. package/out/renderer/src/common/resource-health.js.map +1 -0
  80. package/out/renderer/src/common/resource-relationships.js +155 -0
  81. package/out/renderer/src/common/resource-relationships.js.map +1 -0
  82. package/out/renderer/src/common/service-diagnostics.js +50 -0
  83. package/out/renderer/src/common/service-diagnostics.js.map +1 -0
  84. package/out/renderer/src/common/store/monitoring-preferences-store.js +5 -0
  85. package/out/renderer/src/common/store/monitoring-preferences-store.js.map +1 -1
  86. package/out/renderer/src/common/timeline-groups.js +36 -0
  87. package/out/renderer/src/common/timeline-groups.js.map +1 -0
  88. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.js","names":[],"sources":["../../src/renderer/dashboard.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\n// SPDX-License-Identifier: Apache-2.0\nimport { ipcRenderer, shell } from \"electron\";\nimport { observer } from \"mobx-react\";\nimport { type CSSProperties, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { normalizeDashboardUrl } from \"../common/dashboard\";\nimport {\n DASHBOARD_VIEWS_CLEAR,\n DASHBOARD_VIEWS_LOADING,\n DASHBOARD_VIEWS_PIN,\n DASHBOARD_VIEWS_PINNED,\n DASHBOARD_VIEWS_RELOAD,\n DASHBOARD_VIEWS_SYNC,\n type DashboardViewLoading,\n} from \"../common/dashboard-views\";\nimport { DashboardPreferencesStore } from \"../common/store/dashboard-preferences-store\";\n\nconst styles = `\n .freelens-power-dashboard .power-dashboard-card-pinned .power-dashboard-embed { opacity: .65; }\n .freelens-power-dashboard .power-dashboard-pin-placeholder { display: flex; width: 100%; height: 100%; flex-direction: column; align-items: center; justify-content: center; gap: 12px; border: 0; background: var(--mainBackground); color: var(--textColorSecondary); cursor: pointer; font: inherit; }\n .freelens-power-dashboard .power-dashboard-pin-placeholder:hover { color: var(--textColorAccent); }\n\n .ClusterManager main.freelens-power-dashboard, .freelens-power-dashboard { display: block; position: relative; z-index: 2; flex: 1; background: var(--mainBackground); padding: 28px; color: var(--textColorPrimary); min-width: 0; height: 100%; overflow: auto; box-sizing: border-box; }\n .freelens-power-dashboard .power-dashboard-header {\n display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 24px;\n }\n .freelens-power-dashboard h1 { margin: 0; color: var(--textColorAccent); font-size: 24px; }\n .freelens-power-dashboard .power-dashboard-subtitle,\n .freelens-power-dashboard .power-dashboard-hint { color: var(--textColorSecondary); line-height: 1.5; }\n .freelens-power-dashboard .power-dashboard-subtitle { margin: 6px 0 0; }\n .freelens-power-dashboard button {\n padding: 9px 16px; border: 1px solid var(--borderFaintColor); border-radius: 8px;\n background: var(--contentColor); color: var(--textColorAccent); cursor: pointer;\n }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action { display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 6px 12px; font-size: 13px; background: transparent; color: var(--textColorSecondary); }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action { transition: background-color 140ms, border-color 140ms, color 140ms; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action:hover { background: var(--contentColor); border-color: var(--borderColor); color: var(--textColorAccent); }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action .Icon { color: inherit; transition: color 160ms ease; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-refresh { width: 32px; height: 32px; padding: 0; justify-content: center; border-color: transparent; border-radius: 8px; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-refresh:hover { border-color: transparent; background: color-mix(in srgb, var(--primary) 10%, transparent); color: var(--primary); }\n .power-dashboard-actions { display: flex; align-items: center; gap: 8px; }\n .power-dashboard-action:disabled { opacity: 0.5; cursor: wait; }\n .freelens-power-dashboard .power-dashboard-grid {\n display: grid; grid-template-columns: repeat(var(--power-columns, 4), minmax(0, 1fr)); grid-auto-rows: 220px; grid-auto-flow: row; gap: 20px;\n }\n .freelens-power-dashboard .power-dashboard-card {\n display: flex; flex-direction: column; min-width: 0; overflow: hidden;\n border: 1px solid var(--borderFaintColor); border-radius: 12px; background: var(--contentColor);\n }\n .freelens-power-dashboard.power-dashboard-expanded { display: flex !important; flex-direction: column; overflow: hidden; }\n .freelens-power-dashboard.power-dashboard-expanded .power-dashboard-grid { display: block; flex: 1; min-height: 0; }\n .freelens-power-dashboard.power-dashboard-expanded .power-dashboard-card:not([hidden]) { height: 100%; box-sizing: border-box; }\n .freelens-power-dashboard .power-dashboard-card[hidden] { display: none; }\n .freelens-power-dashboard .power-dashboard-card-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }\n .freelens-power-dashboard button.power-dashboard-expand { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 5px; background: transparent; color: var(--textColorSecondary); transition: color 160ms ease, background-color 160ms ease; }\n .freelens-power-dashboard button.power-dashboard-expand:hover { color: var(--textColorAccent); background: var(--mainBackground); }\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"],\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"] .Icon { color: var(--primary); }\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"]:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); }\n .freelens-power-dashboard button.power-dashboard-expand:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n .freelens-power-dashboard .power-dashboard-card-xs { grid-column: span 1; grid-row: span 1; }\n .freelens-power-dashboard .power-dashboard-card-s { grid-column: span var(--power-small-span, 2); grid-row: span 2; }\n .freelens-power-dashboard .power-dashboard-card-m { grid-column: span var(--power-medium-span, 4); grid-row: span 2; }\n .freelens-power-dashboard .power-dashboard-card-l { grid-column: span var(--power-large-span, 4); grid-row: span 3; }\n .freelens-power-dashboard .power-dashboard-card-xl { grid-column: span var(--power-columns, 4); grid-row: span 3; }\n .freelens-power-dashboard .power-dashboard-card-header {\n display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 12px;\n }\n .freelens-power-dashboard h2 { margin: 0; font-size: 14px; overflow-wrap: anywhere; }\n .freelens-power-dashboard .power-dashboard-card-title { display: flex; align-items: center; gap: 8px; min-width: 0; }\n .freelens-power-dashboard .power-dashboard-card-title h2 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-header { flex-wrap: wrap; row-gap: 4px; }\n .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-title { flex-basis: 100%; }\n .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-actions { margin-left: auto; }\n .freelens-power-dashboard .power-dashboard-site-icon { width: 16px; height: 16px; border-radius: 4px; object-fit: contain; flex-shrink: 0; }\n .freelens-power-dashboard .power-dashboard-card-header a {\n flex-shrink: 0; color: var(--textColorPrimary); font-size: 12px; transition: color 160ms ease;\n }\n .freelens-power-dashboard .power-dashboard-card-header a .Icon { color: inherit; transition: color 160ms ease; }\n .freelens-power-dashboard .power-dashboard-card-header a:hover { color: var(--textColorAccent); }\n .freelens-power-dashboard .power-dashboard-embed { width: 100%; flex: 1; min-height: 0; overflow: hidden; }\n .freelens-power-dashboard .power-dashboard-skeleton { height: 100%; box-sizing: border-box; padding: 20px; display: flex; flex-direction: column; gap: 12px; background: var(--contentColor); }\n .freelens-power-dashboard .power-dashboard-skeleton span { height: 12px; border-radius: 6px; background: color-mix(in srgb, var(--textColorSecondary) 12%, transparent); animation: power-dashboard-loading 1.8s ease-in-out infinite; }\n .freelens-power-dashboard .power-dashboard-skeleton span:first-child { width: 45%; height: 20px; margin-bottom: 8px; }\n .freelens-power-dashboard .power-dashboard-skeleton span:last-child { flex: 1; min-height: 30px; }\n @keyframes power-dashboard-loading { 50% { opacity: .45; } }\n @media (prefers-reduced-motion: reduce) { .freelens-power-dashboard .power-dashboard-skeleton span { animation: none; } }\n .freelens-power-dashboard .power-dashboard-hint { margin: 16px 0 0; font-size: 12px; }\n .power-dashboard-right-tooltip { position: fixed !important; left: calc(anchor(right) + 8px) !important; top: anchor(center) !important; right: auto !important; bottom: auto !important; translate: 0 -50%; white-space: nowrap !important; max-width: none !important; }\n .freelens-power-dashboard .power-dashboard-empty {\n padding: 40px 24px; border: 1px solid var(--borderFaintColor); border-radius: 12px;\n background: var(--contentColor); text-align: center;\n }\n .freelens-power-dashboard .power-dashboard-empty p { color: var(--textColorSecondary); margin: 12px 0 24px; }\n @media (max-width: 700px) {\n .ClusterManager main.freelens-power-dashboard, .freelens-power-dashboard { display: block; position: relative; z-index: 2; flex: 1; background: var(--mainBackground); padding: 18px; }\n .freelens-power-dashboard .power-dashboard-header { flex-wrap: wrap; }\n }\n`;\n\nfunction DashboardActionIcon({ material, tooltip }: { material: string; tooltip: string }) {\n const [anchor] = useState(() => `--power-dashboard-action-${crypto.randomUUID()}`);\n return (\n <Renderer.Component.Icon\n material={material}\n size={18}\n style={{ anchorName: anchor, \"--size\": \"18px\" } as CSSProperties}\n tooltip={{ children: tooltip, className: \"power-dashboard-right-tooltip\", style: { positionAnchor: anchor } }}\n />\n );\n}\n\nfunction WebsiteIcon({ url, favicon }: { url: string; favicon?: string }) {\n const [failed, setFailed] = useState(false);\n if (failed)\n return (\n <span className=\"power-dashboard-site-icon\" aria-hidden=\"true\">\n 🌐\n </span>\n );\n return (\n <img\n className=\"power-dashboard-site-icon\"\n src={favicon || new URL(\"/favicon.ico\", url).href}\n alt=\"\"\n loading=\"lazy\"\n referrerPolicy=\"no-referrer\"\n onError={() => setFailed(true)}\n />\n );\n}\n\nexport const DashboardPage = observer(({ onOpenPreferences }: { onOpenPreferences: () => void }) => {\n const preferences = DashboardPreferencesStore.getInstanceOrCreate<DashboardPreferencesStore>();\n const cards = preferences.cards.flatMap((card) => {\n const url = normalizeDashboardUrl(card.url);\n return url ? [{ ...card, url }] : [];\n });\n const root = useRef<HTMLElement>(null);\n const [expandedId, setExpandedId] = useState<string | null>(null);\n const expanded = cards.some((card) => card.id === expandedId) ? expandedId : null;\n const expandedRef = useRef<string | null>(null);\n const gridScrollTop = useRef<number | null>(null);\n expandedRef.current = expanded;\n useLayoutEffect(() => {\n const container = root.current;\n if (!container) return;\n if (expanded) container.scrollTop = 0;\n else if (gridScrollTop.current !== null) {\n container.scrollTop = gridScrollTop.current;\n gridScrollTop.current = null;\n }\n }, [expanded]);\n useEffect(() => {\n if (expandedId && !expanded) setExpandedId(null);\n }, [expandedId, expanded]);\n useEffect(() => {\n const restore = () => setExpandedId(null);\n const escape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && expandedRef.current) {\n event.preventDefault();\n event.stopPropagation();\n restore();\n }\n };\n document.addEventListener(\"keydown\", escape, true);\n ipcRenderer.on(\"freelens-power:dashboard-escape\", restore);\n return () => {\n document.removeEventListener(\"keydown\", escape, true);\n ipcRenderer.removeListener(\"freelens-power:dashboard-escape\", restore);\n };\n }, []);\n const [loadFailed, setLoadFailed] = useState(false);\n const [loadingCards, setLoadingCards] = useState<Record<string, DashboardViewLoading>>({});\n useEffect(() => {\n const update = (_event: unknown, value: DashboardViewLoading) => {\n if (\n !value ||\n typeof value.id !== \"string\" ||\n typeof value.url !== \"string\" ||\n typeof value.loading !== \"boolean\" ||\n typeof value.failed !== \"boolean\" ||\n !preferences.cards.some((card) => card.id === value.id && normalizeDashboardUrl(card.url) === value.url)\n )\n return;\n setLoadingCards((previous) => {\n const existing = previous[value.id];\n return existing?.url === value.url && existing.loading === value.loading && existing.failed === value.failed\n ? previous\n : { ...previous, [value.id]: value };\n });\n if (value.failed) setLoadFailed(true);\n };\n ipcRenderer.on(DASHBOARD_VIEWS_LOADING, update);\n return () => {\n ipcRenderer.removeListener(DASHBOARD_VIEWS_LOADING, update);\n };\n }, [preferences]);\n const [reloading, setReloading] = useState(false);\n const reloadCards = async () => {\n if (reloading) return;\n setReloading(true);\n try {\n const results = await Promise.all(cards.map(({ id }) => ipcRenderer.invoke(DASHBOARD_VIEWS_RELOAD, { id })));\n setLoadFailed(results.some((result) => result?.failed?.length));\n } catch {\n setLoadFailed(true);\n } finally {\n setReloading(false);\n }\n };\n const focusPinned = (id: string) => {\n void ipcRenderer.invoke(DASHBOARD_VIEWS_PIN, { id, focus: true }).catch(() => setLoadFailed(true));\n };\n const [pinnedCards, setPinnedCards] = useState<string[]>([]);\n useEffect(() => {\n const update = (_event: unknown, ids: unknown) => {\n if (Array.isArray(ids)) setPinnedCards(ids.filter((id): id is string => typeof id === \"string\"));\n };\n ipcRenderer.on(DASHBOARD_VIEWS_PINNED, update);\n return () => {\n ipcRenderer.removeListener(DASHBOARD_VIEWS_PINNED, update);\n };\n }, []);\n const togglePinned = async (id: string) => {\n try {\n const result = await ipcRenderer.invoke(DASHBOARD_VIEWS_PIN, { id, pinned: !pinnedCards.includes(id) });\n if (Array.isArray(result?.pinned))\n setPinnedCards(result.pinned.filter((value: unknown): value is string => typeof value === \"string\"));\n } catch {\n setLoadFailed(true);\n }\n };\n const [siteIcons, setSiteIcons] = useState<Record<string, string>>({});\n useEffect(() => {\n const update = (_event: unknown, icon: { id?: unknown; url?: unknown }) => {\n if (typeof icon?.id !== \"string\" || typeof icon.url !== \"string\") return;\n const imageData =\n icon.url.length <= 720_000 &&\n /^data:image\\/(?:png|jpeg|gif|webp|x-icon|vnd\\.microsoft\\.icon|svg\\+xml);base64,[A-Za-z0-9+/]+={0,2}$/.test(\n icon.url,\n );\n const url = imageData ? icon.url : normalizeDashboardUrl(icon.url);\n if (!url) return;\n const id = icon.id;\n setSiteIcons((previous) => (previous[id] === url ? previous : { ...previous, [id]: url }));\n };\n ipcRenderer.on(\"freelens-power:dashboard-site-icon\", update);\n return () => {\n ipcRenderer.removeListener(\"freelens-power:dashboard-site-icon\", update);\n };\n }, []);\n const cardIdentity = JSON.stringify(cards.map(({ id, url, size }) => ({ id, url, size })));\n useEffect(() => {\n setSiteIcons({});\n }, [cardIdentity]);\n useEffect(() => {\n const container = root.current;\n if (!container) return;\n let active = true;\n let frame = 0;\n const sync = () => {\n frame = 0;\n const containerStyle = window.getComputedStyle(container);\n const contentWidth =\n container.clientWidth -\n (Number.parseFloat(containerStyle.paddingLeft) || 0) -\n (Number.parseFloat(containerStyle.paddingRight) || 0);\n const columns = contentWidth >= 1200 ? 6 : contentWidth >= 800 ? 4 : contentWidth >= 520 ? 2 : 1;\n for (const [property, value] of Object.entries({\n \"--power-columns\": columns,\n \"--power-small-span\": Math.min(2, columns),\n \"--power-medium-span\": Math.min(4, columns),\n \"--power-large-span\": Math.min(4, columns),\n })) {\n if (container.style.getPropertyValue(property) !== String(value))\n container.style.setProperty(property, String(value));\n }\n const containerBounds = container.getBoundingClientRect();\n const overlayOpen = Array.from(\n document.querySelectorAll<HTMLElement>('.Dialog, [role=\"dialog\"], [role=\"menu\"], .Menu, .power-ai-panel'),\n ).some((element) => {\n const rect = element.getBoundingClientRect();\n const style = window.getComputedStyle(element);\n return rect.width > 0 && rect.height > 0 && style.display !== \"none\" && style.visibility !== \"hidden\";\n });\n const visible = overlayOpen\n ? []\n : Array.from(container.querySelectorAll<HTMLElement>(\".power-dashboard-embed\")).flatMap((element) => {\n if (expandedRef.current && element.dataset.cardId !== expandedRef.current) return [];\n const rect = element.getBoundingClientRect();\n const x = Math.ceil(Math.max(0, rect.left, containerBounds.left));\n const y = Math.ceil(Math.max(0, rect.top, containerBounds.top));\n const right = Math.floor(Math.min(window.innerWidth, rect.right, containerBounds.right));\n const bottom = Math.floor(Math.min(window.innerHeight, rect.bottom, containerBounds.bottom));\n const bounds = {\n x: Math.floor(rect.left),\n y: Math.floor(rect.top),\n width: Math.ceil(rect.right) - Math.floor(rect.left),\n height: Math.ceil(rect.bottom) - Math.floor(rect.top),\n };\n const clip = { x, y, width: right - x, height: bottom - y };\n if (\n ![...Object.values(bounds), ...Object.values(clip)].every(Number.isFinite) ||\n rect.width <= 0 ||\n rect.height <= 0 ||\n clip.width <= 0 ||\n clip.height <= 0\n )\n return [];\n return [{ id: element.dataset.cardId, bounds, clip }];\n });\n void ipcRenderer\n .invoke(DASHBOARD_VIEWS_SYNC, {\n cards: visible,\n ...(overlayOpen ? { hidden: true } : {}),\n })\n .then(\n (result) => {\n if (active) {\n setLoadFailed(Boolean(result?.failed?.length));\n if (Array.isArray(result?.pinned)) setPinnedCards(result.pinned);\n }\n },\n () => {\n if (active) setLoadFailed(true);\n },\n );\n };\n const schedule = () => {\n if (!frame) frame = window.requestAnimationFrame(sync);\n };\n const mutations = new MutationObserver(schedule);\n mutations.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"class\", \"style\", \"hidden\"],\n });\n const observer = new ResizeObserver(schedule);\n observer.observe(container);\n for (const element of container.querySelectorAll(\".power-dashboard-embed\")) observer.observe(element);\n container.addEventListener(\"scroll\", schedule);\n window.addEventListener(\"resize\", schedule);\n schedule();\n return () => {\n active = false;\n window.cancelAnimationFrame(frame);\n observer.disconnect();\n mutations.disconnect();\n container.removeEventListener(\"scroll\", schedule);\n window.removeEventListener(\"resize\", schedule);\n void ipcRenderer.invoke(DASHBOARD_VIEWS_CLEAR).catch(() => {});\n };\n }, [cardIdentity]);\n\n return (\n <main ref={root} className={`freelens-power-dashboard${expanded ? \" power-dashboard-expanded\" : \"\"}`}>\n <style>{styles}</style>\n <header className=\"power-dashboard-header\">\n <div>\n <h1>🔋 Power Dashboard</h1>\n </div>\n <div className=\"power-dashboard-actions\">\n <button\n type=\"button\"\n className=\"power-dashboard-action power-dashboard-refresh\"\n aria-label=\"Refresh\"\n aria-busy={reloading}\n disabled={reloading || cards.length === 0}\n onClick={() => {\n void reloadCards();\n }}\n >\n <Renderer.Component.Icon material=\"refresh\" size={18} tooltip=\"Refresh\" />\n </button>\n <button type=\"button\" className=\"power-dashboard-action\" onClick={onOpenPreferences}>\n <span aria-hidden=\"true\">\n <Renderer.Component.Icon material=\"settings\" size={16} />\n </span>{\" \"}\n Settings\n </button>\n </div>\n </header>\n {cards.length === 0 ? (\n <section className=\"power-dashboard-empty\">\n <h2>Build your dashboard</h2>\n <p>Add your favorite tools and pages in Settings.</p>\n <button type=\"button\" onClick={onOpenPreferences}>\n Add cards\n </button>\n </section>\n ) : (\n <>\n <div className=\"power-dashboard-grid\">\n {cards.map((card) => (\n <section\n key={card.id}\n hidden={Boolean(expanded && expanded !== card.id)}\n className={`power-dashboard-card power-dashboard-card-${card.size}${pinnedCards.includes(card.id) ? \" power-dashboard-card-pinned\" : \"\"}`}\n >\n <header className=\"power-dashboard-card-header\">\n <div className=\"power-dashboard-card-title\">\n <WebsiteIcon\n key={`${card.url}:${siteIcons[card.id] || \"\"}`}\n url={card.url}\n favicon={siteIcons[card.id]}\n />\n <h2>{card.title}</h2>\n </div>\n <div className=\"power-dashboard-card-actions\">\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`Auto-refresh ${card.title} every 10 seconds`}\n aria-pressed={card.refreshEvery10Seconds === true}\n onClick={() => {\n preferences.upsertCard({ ...card, refreshEvery10Seconds: !card.refreshEvery10Seconds });\n }}\n >\n <DashboardActionIcon\n material=\"autorenew\"\n tooltip={card.refreshEvery10Seconds ? \"Disable refresh\" : \"Enable refresh (10s)\"}\n />\n </button>\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`${pinnedCards.includes(card.id) ? \"Unpin\" : \"Pin\"} ${card.title}`}\n aria-pressed={pinnedCards.includes(card.id)}\n onClick={() => {\n void togglePinned(card.id);\n }}\n >\n <DashboardActionIcon\n material=\"push_pin\"\n tooltip={pinnedCards.includes(card.id) ? \"Unpin\" : \"Pin\"}\n />\n </button>\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`${expanded === card.id ? \"Restore\" : \"Expand\"} ${card.title}`}\n aria-pressed={expanded === card.id}\n onClick={() => {\n if (!expanded) gridScrollTop.current = root.current?.scrollTop ?? 0;\n setExpandedId(expanded === card.id ? null : card.id);\n }}\n >\n <DashboardActionIcon\n material={expanded === card.id ? \"fullscreen_exit\" : \"fullscreen\"}\n tooltip={expanded === card.id ? \"Restore\" : \"Expand\"}\n />\n </button>\n <a\n href={card.url}\n onClick={(event) => {\n event.preventDefault();\n void shell.openExternal(card.url);\n }}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`Open ${card.title} in browser`}\n >\n <DashboardActionIcon material=\"open_in_new\" tooltip=\"Open\" />\n </a>\n </div>\n </header>\n <div\n className=\"power-dashboard-embed\"\n data-card-id={card.id}\n role=\"region\"\n aria-label={card.title}\n aria-busy={loadingCards[card.id]?.url === card.url && loadingCards[card.id]?.loading}\n >\n {!pinnedCards.includes(card.id) &&\n loadingCards[card.id]?.url === card.url &&\n loadingCards[card.id]?.loading && (\n <div className=\"power-dashboard-skeleton\" role=\"status\" aria-label={`Loading ${card.title}`}>\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n </div>\n )}\n {pinnedCards.includes(card.id) && (\n <button\n type=\"button\"\n className=\"power-dashboard-pin-placeholder\"\n onClick={() => focusPinned(card.id)}\n aria-label={`Focus pinned ${card.title}`}\n >\n <Renderer.Component.Icon material=\"push_pin\" size={28} />\n <span>Pinned above other windows</span>\n <span>Focus window</span>\n </button>\n )}\n </div>\n </section>\n ))}\n </div>\n {loadFailed && (\n <p role=\"alert\" className=\"power-dashboard-hint\">\n Unable to load dashboard cards. Open them in your browser.\n </p>\n )}\n </>\n )}\n </main>\n );\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"dashboard.js","names":[],"sources":["../../src/renderer/dashboard.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\n// SPDX-License-Identifier: Apache-2.0\nimport { ipcRenderer, shell } from \"electron\";\nimport { observer } from \"mobx-react\";\nimport { type CSSProperties, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { normalizeDashboardUrl } from \"../common/dashboard\";\nimport {\n DASHBOARD_VIEWS_CLEAR,\n DASHBOARD_VIEWS_LOADING,\n DASHBOARD_VIEWS_PIN,\n DASHBOARD_VIEWS_PINNED,\n DASHBOARD_VIEWS_RELOAD,\n DASHBOARD_VIEWS_SYNC,\n type DashboardViewLoading,\n} from \"../common/dashboard-views\";\nimport { DashboardPreferencesStore } from \"../common/store/dashboard-preferences-store\";\n\nconst styles = `\n .freelens-power-dashboard .power-dashboard-card-pinned .power-dashboard-embed { opacity: .65; }\n .freelens-power-dashboard .power-dashboard-pin-placeholder { display: flex; width: 100%; height: 100%; flex-direction: column; align-items: center; justify-content: center; gap: 12px; border: 0; background: var(--mainBackground); color: var(--textColorSecondary); cursor: pointer; font: inherit; }\n .freelens-power-dashboard .power-dashboard-pin-placeholder:hover { color: var(--textColorAccent); }\n\n .ClusterManager main.freelens-power-dashboard, .freelens-power-dashboard { display: block; position: relative; z-index: 2; flex: 1; background: var(--mainBackground); padding: 28px; color: var(--textColorPrimary); min-width: 0; height: 100%; overflow: auto; box-sizing: border-box; }\n .freelens-power-dashboard .power-dashboard-header {\n display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 24px;\n }\n .freelens-power-dashboard h1 { margin: 0; color: var(--textColorAccent); font-size: 24px; }\n .freelens-power-dashboard .power-dashboard-subtitle,\n .freelens-power-dashboard .power-dashboard-hint { color: var(--textColorSecondary); line-height: 1.5; }\n .freelens-power-dashboard .power-dashboard-subtitle { margin: 6px 0 0; }\n .freelens-power-dashboard button {\n padding: 9px 16px; border: 1px solid var(--borderFaintColor); border-radius: 8px;\n background: var(--contentColor); color: var(--textColorAccent); cursor: pointer;\n }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action { display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 6px 12px; font-size: 13px; background: transparent; color: var(--textColorSecondary); }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action { transition: background-color 140ms, border-color 140ms, color 140ms; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action:hover { background: var(--contentColor); border-color: var(--borderColor); color: var(--textColorAccent); }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action .Icon { color: inherit; transition: color 160ms ease; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-refresh { width: 32px; height: 32px; padding: 0; justify-content: center; border-color: transparent; border-radius: 8px; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-refresh:hover { border-color: transparent; background: color-mix(in srgb, var(--primary) 10%, transparent); color: var(--primary); }\n .power-dashboard-actions { display: flex; align-items: center; gap: 8px; }\n .power-dashboard-action:disabled { opacity: 0.5; cursor: wait; }\n .freelens-power-dashboard .power-dashboard-grid {\n display: grid; grid-template-columns: repeat(var(--power-columns, 4), minmax(0, 1fr)); grid-auto-rows: 220px; grid-auto-flow: row; gap: 20px;\n }\n .freelens-power-dashboard .power-dashboard-card {\n display: flex; flex-direction: column; min-width: 0; overflow: hidden;\n border: 1px solid var(--borderFaintColor); border-radius: 12px; background: var(--contentColor);\n }\n .freelens-power-dashboard.power-dashboard-expanded { display: flex !important; flex-direction: column; overflow: hidden; }\n .freelens-power-dashboard.power-dashboard-expanded .power-dashboard-grid { display: block; flex: 1; min-height: 0; }\n .freelens-power-dashboard.power-dashboard-expanded .power-dashboard-card:not([hidden]) { height: 100%; box-sizing: border-box; }\n .freelens-power-dashboard .power-dashboard-card[hidden] { display: none; }\n .freelens-power-dashboard .power-dashboard-card-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }\n .freelens-power-dashboard button.power-dashboard-expand { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 5px; background: transparent; color: var(--textColorSecondary); transition: color 160ms ease, background-color 160ms ease; }\n .freelens-power-dashboard button.power-dashboard-expand:hover { color: var(--textColorAccent); background: var(--mainBackground); }\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"],\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"] .Icon { color: var(--primary); }\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"]:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); }\n .freelens-power-dashboard button.power-dashboard-expand:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n .freelens-power-dashboard .power-dashboard-card-xs { grid-column: span 1; grid-row: span 1; }\n .freelens-power-dashboard .power-dashboard-card-s { grid-column: span var(--power-small-span, 2); grid-row: span 2; }\n .freelens-power-dashboard .power-dashboard-card-m { grid-column: span var(--power-medium-span, 4); grid-row: span 2; }\n .freelens-power-dashboard .power-dashboard-card-l { grid-column: span var(--power-large-span, 4); grid-row: span 3; }\n .freelens-power-dashboard .power-dashboard-card-xl { grid-column: span var(--power-columns, 4); grid-row: span 3; }\n .freelens-power-dashboard .power-dashboard-card-header {\n display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 12px;\n }\n .freelens-power-dashboard h2 { margin: 0; font-size: 14px; overflow-wrap: anywhere; }\n .freelens-power-dashboard .power-dashboard-card-title { display: flex; align-items: center; gap: 8px; min-width: 0; }\n .freelens-power-dashboard .power-dashboard-card-title h2 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-header { flex-wrap: wrap; row-gap: 4px; }\n .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-title { flex-basis: 100%; }\n .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-actions { margin-left: auto; }\n .freelens-power-dashboard .power-dashboard-site-icon { width: 16px; height: 16px; border-radius: 4px; object-fit: contain; flex-shrink: 0; }\n .freelens-power-dashboard .power-dashboard-card-header a {\n flex-shrink: 0; color: var(--textColorPrimary); font-size: 12px; transition: color 160ms ease;\n }\n .freelens-power-dashboard .power-dashboard-card-header a .Icon { color: inherit; transition: color 160ms ease; }\n .freelens-power-dashboard .power-dashboard-card-header a:hover { color: var(--textColorAccent); }\n .freelens-power-dashboard .power-dashboard-embed { width: 100%; flex: 1; min-height: 0; overflow: hidden; }\n .freelens-power-dashboard .power-dashboard-skeleton { height: 100%; box-sizing: border-box; padding: 20px; display: flex; flex-direction: column; gap: 12px; background: var(--contentColor); }\n .freelens-power-dashboard .power-dashboard-skeleton span { height: 12px; border-radius: 6px; background: color-mix(in srgb, var(--textColorSecondary) 12%, transparent); animation: power-dashboard-loading 1.8s ease-in-out infinite; }\n .freelens-power-dashboard .power-dashboard-skeleton span:first-child { width: 45%; height: 20px; margin-bottom: 8px; }\n .freelens-power-dashboard .power-dashboard-skeleton span:last-child { flex: 1; min-height: 30px; }\n @keyframes power-dashboard-loading { 50% { opacity: .45; } }\n @media (prefers-reduced-motion: reduce) { .freelens-power-dashboard .power-dashboard-skeleton span { animation: none; } }\n .freelens-power-dashboard .power-dashboard-hint { margin: 16px 0 0; font-size: 12px; }\n .power-dashboard-right-tooltip { position: fixed !important; left: calc(anchor(right) + 8px) !important; top: anchor(center) !important; right: auto !important; bottom: auto !important; translate: 0 -50%; white-space: nowrap !important; max-width: none !important; }\n .freelens-power-dashboard .power-dashboard-empty {\n padding: 40px 24px; border: 1px solid var(--borderFaintColor); border-radius: 12px;\n background: var(--contentColor); text-align: center;\n }\n .freelens-power-dashboard .power-dashboard-empty p { color: var(--textColorSecondary); margin: 12px 0 24px; }\n @media (max-width: 700px) {\n .ClusterManager main.freelens-power-dashboard, .freelens-power-dashboard { display: block; position: relative; z-index: 2; flex: 1; background: var(--mainBackground); padding: 18px; }\n .freelens-power-dashboard .power-dashboard-header { flex-wrap: wrap; }\n }\n`;\n\nfunction DashboardActionIcon({ material, tooltip }: { material: string; tooltip: string }) {\n const [anchor] = useState(() => `--power-dashboard-action-${crypto.randomUUID()}`);\n return (\n <Renderer.Component.Icon\n material={material}\n size={18}\n style={{ anchorName: anchor, \"--size\": \"18px\" } as CSSProperties}\n tooltip={{ children: tooltip, className: \"power-dashboard-right-tooltip\", style: { positionAnchor: anchor } }}\n />\n );\n}\n\nfunction WebsiteIcon({ url, favicon }: { url: string; favicon?: string }) {\n const [failed, setFailed] = useState(false);\n if (failed)\n return (\n <span className=\"power-dashboard-site-icon\" aria-hidden=\"true\">\n 🌐\n </span>\n );\n return (\n <img\n className=\"power-dashboard-site-icon\"\n src={favicon || new URL(\"/favicon.ico\", url).href}\n alt=\"\"\n loading=\"lazy\"\n referrerPolicy=\"no-referrer\"\n onError={() => setFailed(true)}\n />\n );\n}\n\nexport const DashboardPage = observer(({ onOpenPreferences }: { onOpenPreferences: () => void }) => {\n const preferences = DashboardPreferencesStore.getInstanceOrCreate<DashboardPreferencesStore>();\n const cards = preferences.cards.flatMap((card) => {\n const url = normalizeDashboardUrl(card.url);\n return url ? [{ ...card, url }] : [];\n });\n const root = useRef<HTMLElement>(null);\n const [expandedId, setExpandedId] = useState<string | null>(null);\n const expanded = cards.some((card) => card.id === expandedId) ? expandedId : null;\n const expandedRef = useRef<string | null>(null);\n const gridScrollTop = useRef<number | null>(null);\n expandedRef.current = expanded;\n useLayoutEffect(() => {\n const container = root.current;\n if (!container) return;\n if (expanded) container.scrollTop = 0;\n else if (gridScrollTop.current !== null) {\n container.scrollTop = gridScrollTop.current;\n gridScrollTop.current = null;\n }\n }, [expanded]);\n useEffect(() => {\n if (expandedId && !expanded) setExpandedId(null);\n }, [expandedId, expanded]);\n useEffect(() => {\n const restore = () => setExpandedId(null);\n const escape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && expandedRef.current) {\n event.preventDefault();\n event.stopPropagation();\n restore();\n }\n };\n document.addEventListener(\"keydown\", escape, true);\n ipcRenderer.on(\"freelens-power:dashboard-escape\", restore);\n return () => {\n document.removeEventListener(\"keydown\", escape, true);\n ipcRenderer.removeListener(\"freelens-power:dashboard-escape\", restore);\n };\n }, []);\n const [loadFailed, setLoadFailed] = useState(false);\n const [loadingCards, setLoadingCards] = useState<Record<string, DashboardViewLoading>>({});\n useEffect(() => {\n const update = (_event: unknown, value: DashboardViewLoading) => {\n if (\n !value ||\n typeof value.id !== \"string\" ||\n typeof value.url !== \"string\" ||\n typeof value.loading !== \"boolean\" ||\n typeof value.failed !== \"boolean\" ||\n !preferences.cards.some((card) => card.id === value.id && normalizeDashboardUrl(card.url) === value.url)\n )\n return;\n setLoadingCards((previous) => {\n const existing = previous[value.id];\n return existing?.url === value.url && existing.loading === value.loading && existing.failed === value.failed\n ? previous\n : { ...previous, [value.id]: value };\n });\n if (value.failed) setLoadFailed(true);\n };\n ipcRenderer.on(DASHBOARD_VIEWS_LOADING, update);\n return () => {\n ipcRenderer.removeListener(DASHBOARD_VIEWS_LOADING, update);\n };\n }, [preferences]);\n const [reloading, setReloading] = useState(false);\n const [reloadBlocked, setReloadBlocked] = useState(false);\n const reloadRequest = useRef(0);\n const reloadCooldown = useRef<ReturnType<typeof setTimeout>>();\n useEffect(\n () => () => {\n ++reloadRequest.current;\n clearTimeout(reloadCooldown.current);\n },\n [],\n );\n const reloadCards = async () => {\n if (reloadCooldown.current) return;\n const request = ++reloadRequest.current;\n setReloading(true);\n setReloadBlocked(true);\n reloadCooldown.current = setTimeout(() => {\n reloadCooldown.current = undefined;\n setReloadBlocked(false);\n }, 1000);\n try {\n const results = await Promise.all(cards.map(({ id }) => ipcRenderer.invoke(DASHBOARD_VIEWS_RELOAD, { id })));\n if (reloadRequest.current === request) setLoadFailed(results.some((result) => result?.failed?.length));\n } catch {\n if (reloadRequest.current === request) setLoadFailed(true);\n } finally {\n if (reloadRequest.current === request) {\n clearTimeout(reloadCooldown.current);\n reloadCooldown.current = undefined;\n setReloadBlocked(false);\n setReloading(false);\n }\n }\n };\n const focusPinned = (id: string) => {\n void ipcRenderer.invoke(DASHBOARD_VIEWS_PIN, { id, focus: true }).catch(() => setLoadFailed(true));\n };\n const [pinnedCards, setPinnedCards] = useState<string[]>([]);\n useEffect(() => {\n const update = (_event: unknown, ids: unknown) => {\n if (Array.isArray(ids)) setPinnedCards(ids.filter((id): id is string => typeof id === \"string\"));\n };\n ipcRenderer.on(DASHBOARD_VIEWS_PINNED, update);\n return () => {\n ipcRenderer.removeListener(DASHBOARD_VIEWS_PINNED, update);\n };\n }, []);\n const togglePinned = async (id: string) => {\n try {\n const result = await ipcRenderer.invoke(DASHBOARD_VIEWS_PIN, { id, pinned: !pinnedCards.includes(id) });\n if (Array.isArray(result?.pinned))\n setPinnedCards(result.pinned.filter((value: unknown): value is string => typeof value === \"string\"));\n } catch {\n setLoadFailed(true);\n }\n };\n const [siteIcons, setSiteIcons] = useState<Record<string, string>>({});\n useEffect(() => {\n const update = (_event: unknown, icon: { id?: unknown; url?: unknown }) => {\n if (typeof icon?.id !== \"string\" || typeof icon.url !== \"string\") return;\n const imageData =\n icon.url.length <= 720_000 &&\n /^data:image\\/(?:png|jpeg|gif|webp|x-icon|vnd\\.microsoft\\.icon|svg\\+xml);base64,[A-Za-z0-9+/]+={0,2}$/.test(\n icon.url,\n );\n const url = imageData ? icon.url : normalizeDashboardUrl(icon.url);\n if (!url) return;\n const id = icon.id;\n setSiteIcons((previous) => (previous[id] === url ? previous : { ...previous, [id]: url }));\n };\n ipcRenderer.on(\"freelens-power:dashboard-site-icon\", update);\n return () => {\n ipcRenderer.removeListener(\"freelens-power:dashboard-site-icon\", update);\n };\n }, []);\n const cardIdentity = JSON.stringify(cards.map(({ id, url, size }) => ({ id, url, size })));\n useEffect(() => {\n setSiteIcons({});\n }, [cardIdentity]);\n useEffect(() => {\n const container = root.current;\n if (!container) return;\n let active = true;\n let frame = 0;\n const sync = () => {\n frame = 0;\n const containerStyle = window.getComputedStyle(container);\n const contentWidth =\n container.clientWidth -\n (Number.parseFloat(containerStyle.paddingLeft) || 0) -\n (Number.parseFloat(containerStyle.paddingRight) || 0);\n const columns = contentWidth >= 1200 ? 6 : contentWidth >= 800 ? 4 : contentWidth >= 520 ? 2 : 1;\n for (const [property, value] of Object.entries({\n \"--power-columns\": columns,\n \"--power-small-span\": Math.min(2, columns),\n \"--power-medium-span\": Math.min(4, columns),\n \"--power-large-span\": Math.min(4, columns),\n })) {\n if (container.style.getPropertyValue(property) !== String(value))\n container.style.setProperty(property, String(value));\n }\n const containerBounds = container.getBoundingClientRect();\n const overlayOpen = Array.from(\n document.querySelectorAll<HTMLElement>('.Dialog, [role=\"dialog\"], [role=\"menu\"], .Menu, .power-ai-panel'),\n ).some((element) => {\n const rect = element.getBoundingClientRect();\n const style = window.getComputedStyle(element);\n return rect.width > 0 && rect.height > 0 && style.display !== \"none\" && style.visibility !== \"hidden\";\n });\n const visible = overlayOpen\n ? []\n : Array.from(container.querySelectorAll<HTMLElement>(\".power-dashboard-embed\")).flatMap((element) => {\n if (expandedRef.current && element.dataset.cardId !== expandedRef.current) return [];\n const rect = element.getBoundingClientRect();\n const x = Math.ceil(Math.max(0, rect.left, containerBounds.left));\n const y = Math.ceil(Math.max(0, rect.top, containerBounds.top));\n const right = Math.floor(Math.min(window.innerWidth, rect.right, containerBounds.right));\n const bottom = Math.floor(Math.min(window.innerHeight, rect.bottom, containerBounds.bottom));\n const bounds = {\n x: Math.floor(rect.left),\n y: Math.floor(rect.top),\n width: Math.ceil(rect.right) - Math.floor(rect.left),\n height: Math.ceil(rect.bottom) - Math.floor(rect.top),\n };\n const clip = { x, y, width: right - x, height: bottom - y };\n if (\n ![...Object.values(bounds), ...Object.values(clip)].every(Number.isFinite) ||\n rect.width <= 0 ||\n rect.height <= 0 ||\n clip.width <= 0 ||\n clip.height <= 0\n )\n return [];\n return [\n {\n id: element.dataset.cardId,\n bounds,\n clip,\n ...(expandedRef.current === element.dataset.cardId ? { scrollbars: true } : {}),\n },\n ];\n });\n void ipcRenderer\n .invoke(DASHBOARD_VIEWS_SYNC, {\n cards: visible,\n ...(overlayOpen ? { hidden: true } : {}),\n })\n .then(\n (result) => {\n if (active) {\n setLoadFailed(Boolean(result?.failed?.length));\n if (Array.isArray(result?.pinned)) setPinnedCards(result.pinned);\n }\n },\n () => {\n if (active) setLoadFailed(true);\n },\n );\n };\n const schedule = () => {\n if (!frame) frame = window.requestAnimationFrame(sync);\n };\n const mutations = new MutationObserver(schedule);\n mutations.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"class\", \"style\", \"hidden\"],\n });\n const observer = new ResizeObserver(schedule);\n observer.observe(container);\n for (const element of container.querySelectorAll(\".power-dashboard-embed\")) observer.observe(element);\n container.addEventListener(\"scroll\", schedule);\n window.addEventListener(\"resize\", schedule);\n schedule();\n return () => {\n active = false;\n window.cancelAnimationFrame(frame);\n observer.disconnect();\n mutations.disconnect();\n container.removeEventListener(\"scroll\", schedule);\n window.removeEventListener(\"resize\", schedule);\n void ipcRenderer.invoke(DASHBOARD_VIEWS_CLEAR).catch(() => {});\n };\n }, [cardIdentity]);\n\n return (\n <main ref={root} className={`freelens-power-dashboard${expanded ? \" power-dashboard-expanded\" : \"\"}`}>\n <style>{styles}</style>\n <header className=\"power-dashboard-header\">\n <div>\n <h1>🔋 Power Dashboard</h1>\n </div>\n <div className=\"power-dashboard-actions\">\n <button\n type=\"button\"\n className=\"power-dashboard-action power-dashboard-refresh\"\n aria-label=\"Refresh\"\n aria-busy={reloading}\n disabled={reloadBlocked || cards.length === 0}\n onClick={() => {\n void reloadCards();\n }}\n >\n <Renderer.Component.Icon material=\"refresh\" size={18} tooltip=\"Refresh\" />\n </button>\n <button type=\"button\" className=\"power-dashboard-action\" onClick={onOpenPreferences}>\n <span aria-hidden=\"true\">\n <Renderer.Component.Icon material=\"settings\" size={16} />\n </span>{\" \"}\n Settings\n </button>\n </div>\n </header>\n {cards.length === 0 ? (\n <section className=\"power-dashboard-empty\">\n <h2>Build your dashboard</h2>\n <p>Add your favorite tools and pages in Settings.</p>\n <button type=\"button\" onClick={onOpenPreferences}>\n Add cards\n </button>\n </section>\n ) : (\n <>\n <div className=\"power-dashboard-grid\">\n {cards.map((card) => (\n <section\n key={card.id}\n hidden={Boolean(expanded && expanded !== card.id)}\n className={`power-dashboard-card power-dashboard-card-${card.size}${pinnedCards.includes(card.id) ? \" power-dashboard-card-pinned\" : \"\"}`}\n >\n <header className=\"power-dashboard-card-header\">\n <div className=\"power-dashboard-card-title\">\n <WebsiteIcon\n key={`${card.url}:${siteIcons[card.id] || \"\"}`}\n url={card.url}\n favicon={siteIcons[card.id]}\n />\n <h2>{card.title}</h2>\n </div>\n <div className=\"power-dashboard-card-actions\">\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`Auto-refresh ${card.title} every 10 seconds`}\n aria-pressed={card.refreshEvery10Seconds === true}\n onClick={() => {\n preferences.upsertCard({ ...card, refreshEvery10Seconds: !card.refreshEvery10Seconds });\n }}\n >\n <DashboardActionIcon\n material=\"autorenew\"\n tooltip={card.refreshEvery10Seconds ? \"Disable refresh\" : \"Enable refresh (10s)\"}\n />\n </button>\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`${pinnedCards.includes(card.id) ? \"Unpin\" : \"Pin\"} ${card.title}`}\n aria-pressed={pinnedCards.includes(card.id)}\n onClick={() => {\n void togglePinned(card.id);\n }}\n >\n <DashboardActionIcon\n material=\"push_pin\"\n tooltip={pinnedCards.includes(card.id) ? \"Unpin\" : \"Pin\"}\n />\n </button>\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`${expanded === card.id ? \"Restore\" : \"Expand\"} ${card.title}`}\n aria-pressed={expanded === card.id}\n onClick={() => {\n if (!expanded) gridScrollTop.current = root.current?.scrollTop ?? 0;\n setExpandedId(expanded === card.id ? null : card.id);\n }}\n >\n <DashboardActionIcon\n material={expanded === card.id ? \"fullscreen_exit\" : \"fullscreen\"}\n tooltip={expanded === card.id ? \"Restore\" : \"Expand\"}\n />\n </button>\n <a\n href={card.url}\n onClick={(event) => {\n event.preventDefault();\n void shell.openExternal(card.url);\n }}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`Open ${card.title} in browser`}\n >\n <DashboardActionIcon material=\"open_in_new\" tooltip=\"Open\" />\n </a>\n </div>\n </header>\n <div\n className=\"power-dashboard-embed\"\n data-card-id={card.id}\n role=\"region\"\n aria-label={card.title}\n aria-busy={loadingCards[card.id]?.url === card.url && loadingCards[card.id]?.loading}\n >\n {!pinnedCards.includes(card.id) &&\n loadingCards[card.id]?.url === card.url &&\n loadingCards[card.id]?.loading && (\n <div className=\"power-dashboard-skeleton\" role=\"status\" aria-label={`Loading ${card.title}`}>\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n </div>\n )}\n {pinnedCards.includes(card.id) && (\n <button\n type=\"button\"\n className=\"power-dashboard-pin-placeholder\"\n onClick={() => focusPinned(card.id)}\n aria-label={`Focus pinned ${card.title}`}\n >\n <Renderer.Component.Icon material=\"push_pin\" size={28} />\n <span>Pinned above other windows</span>\n <span>Focus window</span>\n </button>\n )}\n </div>\n </section>\n ))}\n </div>\n {loadFailed && (\n <p role=\"alert\" className=\"power-dashboard-hint\">\n Unable to load dashboard cards. Open them in your browser.\n </p>\n )}\n </>\n )}\n </main>\n );\n});\n"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ const require_extensions = require("../_virtual/_global-external_@freelensapp/extensions.js");
2
+ const require__global_external_mobx_react = require("../_virtual/_global-external_mobx-react.js");
3
+ const require_jsx_runtime = require("../_virtual/_global-external_react/jsx-runtime.js");
4
+ const require_hpa_diagnostics = require("../src/common/hpa-diagnostics.js");
5
+ //#region src/renderer/details/hpa-diagnostics-details.tsx
6
+ var { DrawerTitle, DrawerItem, Icon } = require_extensions.Renderer.Component;
7
+ var HpaDiagnosticsDetails = require__global_external_mobx_react.observer(({ object }) => {
8
+ const result = require_hpa_diagnostics.diagnoseHpa(object);
9
+ const healthy = result.state !== "warning" && result.min !== result.max && object.status?.conditions?.some((condition) => condition.type === "ScalingActive" && condition.status === "True") && object.status?.conditions?.some((condition) => condition.type === "AbleToScale" && condition.status === "True") && !object.status?.conditions?.some((condition) => condition.type === "ScalingLimited" && condition.status === "True");
10
+ return /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [
11
+ /* @__PURE__ */ require_jsx_runtime.jsx(DrawerTitle, { children: "Autoscaling diagnostics" }),
12
+ /* @__PURE__ */ require_jsx_runtime.jsx(DrawerItem, {
13
+ name: "Summary",
14
+ children: /* @__PURE__ */ require_jsx_runtime.jsxs("span", {
15
+ style: { color: result.state === "warning" ? "var(--colorWarning)" : healthy ? "var(--colorOk)" : "var(--textColorSecondary)" },
16
+ children: [
17
+ /* @__PURE__ */ require_jsx_runtime.jsx(Icon, { material: result.state === "warning" ? "warning" : healthy ? "check_circle" : "info" }),
18
+ " ",
19
+ result.summary
20
+ ]
21
+ })
22
+ }),
23
+ /* @__PURE__ */ require_jsx_runtime.jsxs(DrawerItem, {
24
+ name: "Replicas",
25
+ children: [
26
+ "Current ",
27
+ result.current ?? "unknown",
28
+ " · desired ",
29
+ result.desired ?? "unknown",
30
+ " · min ",
31
+ result.min,
32
+ " · max",
33
+ " ",
34
+ result.max ?? "unknown"
35
+ ]
36
+ }),
37
+ result.evidence.map((item) => /* @__PURE__ */ require_jsx_runtime.jsx(DrawerItem, {
38
+ name: "Evidence",
39
+ children: item
40
+ }, item))
41
+ ] });
42
+ });
43
+ //#endregion
44
+ exports.HpaDiagnosticsDetails = HpaDiagnosticsDetails;
45
+
46
+ //# sourceMappingURL=hpa-diagnostics-details.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hpa-diagnostics-details.js","names":[],"sources":["../../../src/renderer/details/hpa-diagnostics-details.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { observer } from \"mobx-react\";\nimport { diagnoseHpa } from \"../../common/hpa-diagnostics\";\n\nconst { DrawerTitle, DrawerItem, Icon } = Renderer.Component;\nexport const HpaDiagnosticsDetails = observer(\n ({ object }: Renderer.Component.KubeObjectDetailsProps<Renderer.K8sApi.HorizontalPodAutoscaler>) => {\n const result = diagnoseHpa(object);\n const healthy =\n result.state !== \"warning\" &&\n result.min !== result.max &&\n object.status?.conditions?.some(\n (condition) => condition.type === \"ScalingActive\" && condition.status === \"True\",\n ) &&\n object.status?.conditions?.some((condition) => condition.type === \"AbleToScale\" && condition.status === \"True\") &&\n !object.status?.conditions?.some(\n (condition) => condition.type === \"ScalingLimited\" && condition.status === \"True\",\n );\n return (\n <>\n <DrawerTitle>Autoscaling diagnostics</DrawerTitle>\n <DrawerItem name=\"Summary\">\n <span\n style={{\n color:\n result.state === \"warning\"\n ? \"var(--colorWarning)\"\n : healthy\n ? \"var(--colorOk)\"\n : \"var(--textColorSecondary)\",\n }}\n >\n <Icon material={result.state === \"warning\" ? \"warning\" : healthy ? \"check_circle\" : \"info\"} />{\" \"}\n {result.summary}\n </span>\n </DrawerItem>\n <DrawerItem name=\"Replicas\">\n Current {result.current ?? \"unknown\"} · desired {result.desired ?? \"unknown\"} · min {result.min} · max{\" \"}\n {result.max ?? \"unknown\"}\n </DrawerItem>\n {result.evidence.map((item) => (\n <DrawerItem key={item} name=\"Evidence\">\n {item}\n </DrawerItem>\n ))}\n </>\n );\n },\n);\n"],"mappings":";;;;;AAIA,IAAM,EAAE,aAAa,YAAY,SAAS,mBAAA,SAAS;AACnD,IAAa,wBAAwB,oCAAA,UAClC,EAAE,aAAiG;CAClG,MAAM,SAAS,wBAAA,YAAY,MAAM;CACjC,MAAM,UACJ,OAAO,UAAU,aACjB,OAAO,QAAQ,OAAO,OACtB,OAAO,QAAQ,YAAY,MACxB,cAAc,UAAU,SAAS,mBAAmB,UAAU,WAAW,MAC5E,KACA,OAAO,QAAQ,YAAY,MAAM,cAAc,UAAU,SAAS,iBAAiB,UAAU,WAAW,MAAM,KAC9G,CAAC,OAAO,QAAQ,YAAY,MACzB,cAAc,UAAU,SAAS,oBAAoB,UAAU,WAAW,MAC7E;CACF,OACE,oCAAA,KAAA,oBAAA,UAAA,EAAA,UAAA;EACE,oCAAA,IAAC,aAAD,EAAA,UAAa,0BAAoC,CAAA;EACjD,oCAAA,IAAC,YAAD;GAAY,MAAK;aACf,oCAAA,KAAC,QAAD;IACE,OAAO,EACL,OACE,OAAO,UAAU,YACb,wBACA,UACE,mBACA,4BACV;cARF;KAUE,oCAAA,IAAC,MAAD,EAAM,UAAU,OAAO,UAAU,YAAY,YAAY,UAAU,iBAAiB,OAAS,CAAA;KAAE;KAC9F,OAAO;IACJ;;EACI,CAAA;EACZ,oCAAA,KAAC,YAAD;GAAY,MAAK;aAAjB;IAA4B;IACjB,OAAO,WAAW;IAAU;IAAY,OAAO,WAAW;IAAU;IAAQ,OAAO;IAAI;IAAO;IACtG,OAAO,OAAO;GACL;;EACX,OAAO,SAAS,KAAK,SACpB,oCAAA,IAAC,YAAD;GAAuB,MAAK;aACzB;EACS,GAFK,IAEL,CACb;CACD,EAAA,CAAA;AAEN,CACF"}
@@ -0,0 +1,91 @@
1
+ const require_extensions = require("../_virtual/_global-external_@freelensapp/extensions.js");
2
+ const require__global_external_react = require("../_virtual/_global-external_react.js");
3
+ const require_jsx_runtime = require("../_virtual/_global-external_react/jsx-runtime.js");
4
+ const require_date_time = require("../src/common/date-time.js");
5
+ const require_monitoring_math = require("../src/common/monitoring-math.js");
6
+ const require_monitoring_collector = require("../monitoring-collector.js");
7
+ const require_resource_health_details = require("./resource-health-details.js");
8
+ //#region src/renderer/details/namespace-health-details.tsx
9
+ var { DrawerItem } = require_extensions.Renderer.Component;
10
+ function NamespaceHealthDetails(props) {
11
+ const namespace = props.object.getName();
12
+ const clusterId = require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid;
13
+ const [result, setResult] = require__global_external_react.useState();
14
+ require__global_external_react.useEffect(() => {
15
+ let active = true;
16
+ const release = require_monitoring_collector.retainMonitoringCollector();
17
+ const refresh = () => {
18
+ require_monitoring_collector.getMonitoringSnapshot().then((snapshot) => {
19
+ if (active) setResult({
20
+ namespace,
21
+ clusterId: snapshot.clusterId,
22
+ snapshot
23
+ });
24
+ }, () => {
25
+ if (active && clusterId) setResult({
26
+ namespace,
27
+ clusterId,
28
+ failed: true
29
+ });
30
+ });
31
+ };
32
+ refresh();
33
+ const timer = setInterval(refresh, 3e4);
34
+ return () => {
35
+ active = false;
36
+ clearInterval(timer);
37
+ release();
38
+ };
39
+ }, [namespace, clusterId]);
40
+ const snapshot = result?.namespace === namespace && result?.clusterId === clusterId ? result.snapshot : void 0;
41
+ const pods = snapshot?.pods?.filter((pod) => pod.getNs() === namespace);
42
+ const activePods = pods?.filter((pod) => pod.status?.phase !== "Succeeded" && pod.status?.phase !== "Failed");
43
+ let requests;
44
+ if (activePods) try {
45
+ const cpu = activePods.reduce((sum, pod) => sum + require_monitoring_math.effectivePodResource(pod, "cpu", "requests"), 0);
46
+ const memory = activePods.reduce((sum, pod) => sum + require_monitoring_math.effectivePodResource(pod, "memory", "requests"), 0);
47
+ requests = `${cpu.toFixed(2)} CPU cores · ${(memory / 1024 ** 2).toFixed(0)} MiB memory`;
48
+ } catch {
49
+ requests = "Unavailable: incomplete resource specifications";
50
+ }
51
+ const failed = result?.namespace === namespace && result?.clusterId === clusterId && result.failed;
52
+ return /* @__PURE__ */ require_jsx_runtime.jsxs(require_jsx_runtime.Fragment, { children: [
53
+ /* @__PURE__ */ require_jsx_runtime.jsx(require_resource_health_details.ResourceHealthDetails, {
54
+ ...props,
55
+ onOpenDiagnostics: props.onOpenDiagnostics ? () => props.onOpenDiagnostics?.(namespace) : void 0
56
+ }),
57
+ /* @__PURE__ */ require_jsx_runtime.jsxs(DrawerItem, {
58
+ name: "Inventory scope",
59
+ children: [
60
+ "Namespace ",
61
+ namespace,
62
+ ", from the API-visible pod inventory"
63
+ ]
64
+ }),
65
+ /* @__PURE__ */ require_jsx_runtime.jsx(DrawerItem, {
66
+ name: "Pod coverage",
67
+ children: pods ? `${pods.length} visible pods · ${activePods?.length} non-terminal` : failed || snapshot ? "Unavailable or access denied" : "Loading"
68
+ }),
69
+ activePods && /* @__PURE__ */ require_jsx_runtime.jsxs(DrawerItem, {
70
+ name: "Pod readiness",
71
+ children: [
72
+ activePods.filter((pod) => pod.status?.conditions?.some((condition) => condition.type === "Ready" && condition.status === "True")).length,
73
+ "/",
74
+ activePods.length,
75
+ " non-terminal pods ready"
76
+ ]
77
+ }),
78
+ requests && /* @__PURE__ */ require_jsx_runtime.jsx(DrawerItem, {
79
+ name: "Pod requests",
80
+ children: requests
81
+ }),
82
+ /* @__PURE__ */ require_jsx_runtime.jsxs(DrawerItem, {
83
+ name: "Coverage",
84
+ children: ["Filtered to this namespace · visibility may be limited by access permissions", snapshot ? ` · sampled ${require_date_time.formatDateTime(snapshot.time)}` : ""]
85
+ })
86
+ ] });
87
+ }
88
+ //#endregion
89
+ exports.NamespaceHealthDetails = NamespaceHealthDetails;
90
+
91
+ //# sourceMappingURL=namespace-health-details.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace-health-details.js","names":[],"sources":["../../../src/renderer/details/namespace-health-details.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { useEffect, useState } from \"react\";\nimport { formatDateTime } from \"../../common/date-time\";\nimport { effectivePodResource } from \"../../common/monitoring-math\";\nimport { getMonitoringSnapshot, type MonitoringSnapshot, retainMonitoringCollector } from \"../monitoring-collector\";\nimport { ResourceHealthDetails, type ResourceHealthDetailsProps } from \"./resource-health-details\";\n\nconst { DrawerItem } = Renderer.Component;\n\nexport function NamespaceHealthDetails(props: ResourceHealthDetailsProps) {\n const namespace = props.object.getName();\n const clusterId = Renderer.Catalog.activeCluster.get()?.metadata.uid;\n const [result, setResult] = useState<{\n namespace: string;\n clusterId: string;\n snapshot?: MonitoringSnapshot;\n failed?: boolean;\n }>();\n useEffect(() => {\n let active = true;\n const release = retainMonitoringCollector();\n const refresh = () => {\n void getMonitoringSnapshot().then(\n (snapshot) => {\n if (active) setResult({ namespace, clusterId: snapshot.clusterId, snapshot });\n },\n () => {\n if (active && clusterId) setResult({ namespace, clusterId, failed: true });\n },\n );\n };\n refresh();\n const timer = setInterval(refresh, 30_000);\n return () => {\n active = false;\n clearInterval(timer);\n release();\n };\n }, [namespace, clusterId]);\n const snapshot = result?.namespace === namespace && result?.clusterId === clusterId ? result.snapshot : undefined;\n const pods = snapshot?.pods?.filter((pod) => pod.getNs() === namespace);\n const activePods = pods?.filter((pod) => pod.status?.phase !== \"Succeeded\" && pod.status?.phase !== \"Failed\");\n let requests: string | undefined;\n if (activePods) {\n try {\n const cpu = activePods.reduce((sum, pod) => sum + effectivePodResource(pod, \"cpu\", \"requests\"), 0);\n const memory = activePods.reduce((sum, pod) => sum + effectivePodResource(pod, \"memory\", \"requests\"), 0);\n requests = `${cpu.toFixed(2)} CPU cores · ${(memory / 1024 ** 2).toFixed(0)} MiB memory`;\n } catch {\n requests = \"Unavailable: incomplete resource specifications\";\n }\n }\n const failed = result?.namespace === namespace && result?.clusterId === clusterId && result.failed;\n return (\n <>\n <ResourceHealthDetails\n {...props}\n onOpenDiagnostics={props.onOpenDiagnostics ? () => props.onOpenDiagnostics?.(namespace) : undefined}\n />\n <DrawerItem name=\"Inventory scope\">Namespace {namespace}, from the API-visible pod inventory</DrawerItem>\n <DrawerItem name=\"Pod coverage\">\n {pods\n ? `${pods.length} visible pods · ${activePods?.length} non-terminal`\n : failed || snapshot\n ? \"Unavailable or access denied\"\n : \"Loading\"}\n </DrawerItem>\n {activePods && (\n <DrawerItem name=\"Pod readiness\">\n {\n activePods.filter((pod) =>\n pod.status?.conditions?.some((condition) => condition.type === \"Ready\" && condition.status === \"True\"),\n ).length\n }\n /{activePods.length} non-terminal pods ready\n </DrawerItem>\n )}\n {requests && <DrawerItem name=\"Pod requests\">{requests}</DrawerItem>}\n <DrawerItem name=\"Coverage\">\n Filtered to this namespace · visibility may be limited by access permissions\n {snapshot ? ` · sampled ${formatDateTime(snapshot.time)}` : \"\"}\n </DrawerItem>\n </>\n );\n}\n"],"mappings":";;;;;;;;AAOA,IAAM,EAAE,eAAe,mBAAA,SAAS;AAEhC,SAAgB,uBAAuB,OAAmC;CACxE,MAAM,YAAY,MAAM,OAAO,QAAQ;CACvC,MAAM,YAAY,mBAAA,SAAS,QAAQ,cAAc,IAAI,CAAC,EAAE,SAAS;CACjE,MAAM,CAAC,QAAQ,aAAa,+BAAA,SAKzB;CACH,+BAAA,gBAAgB;EACd,IAAI,SAAS;EACb,MAAM,UAAU,6BAAA,0BAA0B;EAC1C,MAAM,gBAAgB;GACpB,6BAAK,sBAAsB,CAAC,CAAC,MAC1B,aAAa;IACZ,IAAI,QAAQ,UAAU;KAAE;KAAW,WAAW,SAAS;KAAW;IAAS,CAAC;GAC9E,SACM;IACJ,IAAI,UAAU,WAAW,UAAU;KAAE;KAAW;KAAW,QAAQ;IAAK,CAAC;GAC3E,CACF;EACF;EACA,QAAQ;EACR,MAAM,QAAQ,YAAY,SAAS,GAAM;EACzC,aAAa;GACX,SAAS;GACT,cAAc,KAAK;GACnB,QAAQ;EACV;CACF,GAAG,CAAC,WAAW,SAAS,CAAC;CACzB,MAAM,WAAW,QAAQ,cAAc,aAAa,QAAQ,cAAc,YAAY,OAAO,WAAW,KAAA;CACxG,MAAM,OAAO,UAAU,MAAM,QAAQ,QAAQ,IAAI,MAAM,MAAM,SAAS;CACtE,MAAM,aAAa,MAAM,QAAQ,QAAQ,IAAI,QAAQ,UAAU,eAAe,IAAI,QAAQ,UAAU,QAAQ;CAC5G,IAAI;CACJ,IAAI,YACF,IAAI;EACF,MAAM,MAAM,WAAW,QAAQ,KAAK,QAAQ,MAAM,wBAAA,qBAAqB,KAAK,OAAO,UAAU,GAAG,CAAC;EACjG,MAAM,SAAS,WAAW,QAAQ,KAAK,QAAQ,MAAM,wBAAA,qBAAqB,KAAK,UAAU,UAAU,GAAG,CAAC;EACvG,WAAW,GAAG,IAAI,QAAQ,CAAC,EAAE,gBAAgB,SAAS,QAAQ,EAAA,CAAG,QAAQ,CAAC,EAAE;CAC9E,QAAQ;EACN,WAAW;CACb;CAEF,MAAM,SAAS,QAAQ,cAAc,aAAa,QAAQ,cAAc,aAAa,OAAO;CAC5F,OACE,oCAAA,KAAA,oBAAA,UAAA,EAAA,UAAA;EACE,oCAAA,IAAC,gCAAA,uBAAD;GACE,GAAI;GACJ,mBAAmB,MAAM,0BAA0B,MAAM,oBAAoB,SAAS,IAAI,KAAA;EAC3F,CAAA;EACD,oCAAA,KAAC,YAAD;GAAY,MAAK;aAAjB;IAAmC;IAAW;IAAU;GAAgD;;EACxG,oCAAA,IAAC,YAAD;GAAY,MAAK;aACd,OACG,GAAG,KAAK,OAAO,kBAAkB,YAAY,OAAO,iBACpD,UAAU,WACR,iCACA;EACI,CAAA;EACX,cACC,oCAAA,KAAC,YAAD;GAAY,MAAK;aAAjB;IAEI,WAAW,QAAQ,QACjB,IAAI,QAAQ,YAAY,MAAM,cAAc,UAAU,SAAS,WAAW,UAAU,WAAW,MAAM,CACvG,CAAC,CAAC;IACH;IACC,WAAW;IAAO;GACV;;EAEb,YAAY,oCAAA,IAAC,YAAD;GAAY,MAAK;aAAgB;EAAqB,CAAA;EACnE,oCAAA,KAAC,YAAD;GAAY,MAAK;aAAjB,CAA4B,gFAEzB,WAAW,cAAc,kBAAA,eAAe,SAAS,IAAI,MAAM,EAClD;;CACZ,EAAA,CAAA;AAEN"}
@@ -0,0 +1,131 @@
1
+ const require_extensions = require("../_virtual/_global-external_@freelensapp/extensions.js");
2
+ const require__global_external_mobx_react = require("../_virtual/_global-external_mobx-react.js");
3
+ const require_ai_preferences_store = require("../src/common/store/ai-preferences-store.js");
4
+ const require_jsx_runtime = require("../_virtual/_global-external_react/jsx-runtime.js");
5
+ const require_ai_diagnostic_handoff = require("../ai-diagnostic-handoff.js");
6
+ const require_resource_health = require("../src/common/resource-health.js");
7
+ //#region src/renderer/details/resource-health-details.tsx
8
+ var { Button, DrawerItem, DrawerTitle, Icon } = require_extensions.Renderer.Component;
9
+ var actionStyle = {
10
+ display: "inline-flex",
11
+ alignItems: "center",
12
+ justifyContent: "center",
13
+ gap: 6,
14
+ minHeight: 28,
15
+ padding: "4px 8px",
16
+ border: "1px solid var(--borderFaintColor)",
17
+ background: "var(--buttonDefaultBackground)"
18
+ };
19
+ var ResourceHealthDetails = require__global_external_mobx_react.observer(({ object, onOpenDiagnostics }) => {
20
+ const health = require_resource_health.summarizeObjectHealth(object);
21
+ const namespace = object.getNs();
22
+ const clusterId = require_extensions.Renderer.Catalog.activeCluster.get()?.metadata.uid;
23
+ const aiEnabled = require_ai_preferences_store.AiPreferencesStore.getInstanceOrCreate().enabled;
24
+ const askAi = () => {
25
+ if (!clusterId || !require_ai_preferences_store.AiPreferencesStore.getInstanceOrCreate().enabled) return;
26
+ const name = object.getName();
27
+ require_ai_diagnostic_handoff.explainDiagnosticFinding({
28
+ clusterId,
29
+ title: `${object.kind} ${name}: operational summary`.slice(0, 160),
30
+ summary: [...health.rows.map((row) => `${row.name}: ${row.value}`), ...health.issue ? [`Key issue: ${health.issue}`] : []].join("\n").slice(0, 6e3),
31
+ resource: {
32
+ kind: object.kind,
33
+ name,
34
+ ...namespace ? { namespace } : {}
35
+ }
36
+ });
37
+ };
38
+ const node = object.spec?.nodeName;
39
+ const open = (path) => {
40
+ Promise.resolve(require_extensions.Renderer.Navigation.showDetails(path)).catch(() => {});
41
+ };
42
+ return /* @__PURE__ */ require_jsx_runtime.jsxs("section", {
43
+ className: "power-operational-summary",
44
+ style: { marginBottom: 16 },
45
+ children: [
46
+ /* @__PURE__ */ require_jsx_runtime.jsx("style", { children: `
47
+ .power-operational-summary > .DrawerTitle.power-operational-title { margin-bottom: 10px; }
48
+ .power-operational-summary .power-resource-action::before { content: none; }
49
+ .power-operational-summary .power-resource-action:active { filter: brightness(1.12); }
50
+ .power-operational-summary .power-resource-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
51
+ ` }),
52
+ /* @__PURE__ */ require_jsx_runtime.jsx(DrawerTitle, {
53
+ className: "power-operational-title",
54
+ children: "Operational summary"
55
+ }),
56
+ /* @__PURE__ */ require_jsx_runtime.jsx(DrawerItem, {
57
+ name: "Health",
58
+ children: /* @__PURE__ */ require_jsx_runtime.jsxs("span", {
59
+ style: {
60
+ color: health.state === "healthy" ? "var(--colorOk)" : health.state === "warning" ? "var(--colorWarning)" : "var(--textColorSecondary)",
61
+ display: "inline-flex",
62
+ alignItems: "center",
63
+ gap: 6
64
+ },
65
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(Icon, { material: health.state === "healthy" ? "check_circle" : health.state === "warning" ? "warning" : "help_outline" }), health.state === "healthy" ? "Healthy" : health.state === "warning" ? "Needs attention" : "Unknown"]
66
+ })
67
+ }),
68
+ health.rows.map((row) => /* @__PURE__ */ require_jsx_runtime.jsx(DrawerItem, {
69
+ name: row.name,
70
+ children: row.value
71
+ }, row.name)),
72
+ health.issue && /* @__PURE__ */ require_jsx_runtime.jsx(DrawerItem, {
73
+ name: "Key issue",
74
+ children: health.issue
75
+ }),
76
+ namespace && object.kind !== "Namespace" && /* @__PURE__ */ require_jsx_runtime.jsx(DrawerItem, {
77
+ name: "Namespace",
78
+ children: /* @__PURE__ */ require_jsx_runtime.jsx("button", {
79
+ type: "button",
80
+ className: "link",
81
+ onClick: () => open(require_extensions.Renderer.K8sApi.namespacesApi.getUrl({ name: namespace })),
82
+ children: namespace
83
+ })
84
+ }),
85
+ /* @__PURE__ */ require_jsx_runtime.jsx(DrawerItem, {
86
+ name: "Investigate",
87
+ children: /* @__PURE__ */ require_jsx_runtime.jsxs("span", {
88
+ style: {
89
+ display: "flex",
90
+ flexWrap: "wrap",
91
+ gap: 12
92
+ },
93
+ children: [onOpenDiagnostics && /* @__PURE__ */ require_jsx_runtime.jsxs(Button, {
94
+ className: "power-resource-action",
95
+ onClick: () => onOpenDiagnostics(namespace),
96
+ style: actionStyle,
97
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(Icon, {
98
+ material: "insights",
99
+ size: 14,
100
+ "aria-hidden": "true"
101
+ }), " Open Diagnostics"]
102
+ }), /* @__PURE__ */ require_jsx_runtime.jsxs(Button, {
103
+ className: "power-resource-action",
104
+ onClick: askAi,
105
+ disabled: !aiEnabled || !clusterId,
106
+ title: !aiEnabled ? "Enable the AI assistant in settings" : !clusterId ? "Select a connected cluster" : "Attach this summary to the AI chat",
107
+ style: actionStyle,
108
+ children: [/* @__PURE__ */ require_jsx_runtime.jsx(Icon, {
109
+ material: "auto_awesome",
110
+ size: 14,
111
+ "aria-hidden": "true"
112
+ }), " Ask AI"]
113
+ })]
114
+ })
115
+ }),
116
+ node && /* @__PURE__ */ require_jsx_runtime.jsx(DrawerItem, {
117
+ name: "Node",
118
+ children: /* @__PURE__ */ require_jsx_runtime.jsx("button", {
119
+ type: "button",
120
+ className: "link",
121
+ onClick: () => open(require_extensions.Renderer.K8sApi.nodesApi.getUrl({ name: node })),
122
+ children: node
123
+ })
124
+ })
125
+ ]
126
+ });
127
+ });
128
+ //#endregion
129
+ exports.ResourceHealthDetails = ResourceHealthDetails;
130
+
131
+ //# sourceMappingURL=resource-health-details.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-health-details.js","names":[],"sources":["../../../src/renderer/details/resource-health-details.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { observer } from \"mobx-react\";\nimport { type HealthObject, summarizeObjectHealth } from \"../../common/resource-health\";\nimport { AiPreferencesStore } from \"../../common/store/ai-preferences-store\";\nimport { explainDiagnosticFinding } from \"../ai-diagnostic-handoff\";\n\nconst { Button, DrawerItem, DrawerTitle, Icon } = Renderer.Component;\nconst actionStyle = {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 6,\n minHeight: 28,\n padding: \"4px 8px\",\n border: \"1px solid var(--borderFaintColor)\",\n background: \"var(--buttonDefaultBackground)\",\n} as const;\n\nexport type ResourceHealthDetailsProps = Renderer.Component.KubeObjectDetailsProps & {\n onOpenDiagnostics?: (namespace?: string) => void;\n};\n\nexport const ResourceHealthDetails = observer(({ object, onOpenDiagnostics }: ResourceHealthDetailsProps) => {\n const health = summarizeObjectHealth(object as HealthObject);\n const namespace = object.getNs();\n const clusterId = Renderer.Catalog.activeCluster.get()?.metadata.uid;\n const aiEnabled = AiPreferencesStore.getInstanceOrCreate<AiPreferencesStore>().enabled;\n const askAi = () => {\n if (!clusterId || !AiPreferencesStore.getInstanceOrCreate<AiPreferencesStore>().enabled) return;\n const name = object.getName();\n explainDiagnosticFinding({\n clusterId,\n title: `${object.kind} ${name}: operational summary`.slice(0, 160),\n summary: [\n ...health.rows.map((row) => `${row.name}: ${row.value}`),\n ...(health.issue ? [`Key issue: ${health.issue}`] : []),\n ]\n .join(\"\\n\")\n .slice(0, 6000),\n resource: { kind: object.kind, name, ...(namespace ? { namespace } : {}) },\n });\n };\n const node = (object as HealthObject).spec?.nodeName;\n const open = (path: string) => {\n void Promise.resolve(Renderer.Navigation.showDetails(path)).catch(() => {});\n };\n return (\n <section className=\"power-operational-summary\" style={{ marginBottom: 16 }}>\n <style>{`\n .power-operational-summary > .DrawerTitle.power-operational-title { margin-bottom: 10px; }\n .power-operational-summary .power-resource-action::before { content: none; }\n .power-operational-summary .power-resource-action:active { filter: brightness(1.12); }\n .power-operational-summary .power-resource-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n `}</style>\n <DrawerTitle className=\"power-operational-title\">Operational summary</DrawerTitle>\n <DrawerItem name=\"Health\">\n <span\n style={{\n color:\n health.state === \"healthy\"\n ? \"var(--colorOk)\"\n : health.state === \"warning\"\n ? \"var(--colorWarning)\"\n : \"var(--textColorSecondary)\",\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n }}\n >\n <Icon\n material={\n health.state === \"healthy\" ? \"check_circle\" : health.state === \"warning\" ? \"warning\" : \"help_outline\"\n }\n />\n {health.state === \"healthy\" ? \"Healthy\" : health.state === \"warning\" ? \"Needs attention\" : \"Unknown\"}\n </span>\n </DrawerItem>\n {health.rows.map((row) => (\n <DrawerItem key={row.name} name={row.name}>\n {row.value}\n </DrawerItem>\n ))}\n {health.issue && <DrawerItem name=\"Key issue\">{health.issue}</DrawerItem>}\n {namespace && object.kind !== \"Namespace\" && (\n <DrawerItem name=\"Namespace\">\n <button\n type=\"button\"\n className=\"link\"\n onClick={() => open(Renderer.K8sApi.namespacesApi.getUrl({ name: namespace }))}\n >\n {namespace}\n </button>\n </DrawerItem>\n )}\n <DrawerItem name=\"Investigate\">\n <span style={{ display: \"flex\", flexWrap: \"wrap\", gap: 12 }}>\n {onOpenDiagnostics && (\n <Button className=\"power-resource-action\" onClick={() => onOpenDiagnostics(namespace)} style={actionStyle}>\n <Icon material=\"insights\" size={14} aria-hidden=\"true\" /> Open Diagnostics\n </Button>\n )}\n <Button\n className=\"power-resource-action\"\n onClick={askAi}\n disabled={!aiEnabled || !clusterId}\n title={\n !aiEnabled\n ? \"Enable the AI assistant in settings\"\n : !clusterId\n ? \"Select a connected cluster\"\n : \"Attach this summary to the AI chat\"\n }\n style={actionStyle}\n >\n <Icon material=\"auto_awesome\" size={14} aria-hidden=\"true\" /> Ask AI\n </Button>\n </span>\n </DrawerItem>\n {node && (\n <DrawerItem name=\"Node\">\n <button type=\"button\" className=\"link\" onClick={() => open(Renderer.K8sApi.nodesApi.getUrl({ name: node }))}>\n {node}\n </button>\n </DrawerItem>\n )}\n </section>\n );\n});\n"],"mappings":"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"}