@skyhook-io/radar-app 1.12.2 → 1.12.3

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 (41) hide show
  1. package/package.json +2 -2
  2. package/src/App.tsx +34 -15
  3. package/src/api/client.images.test.ts +63 -0
  4. package/src/api/client.ts +208 -33
  5. package/src/api/client.yaml.test.ts +3 -3
  6. package/src/api/version-check.test.ts +78 -0
  7. package/src/components/CloudConnectFlow.tsx +46 -26
  8. package/src/components/CloudFunnelButton.tsx +166 -129
  9. package/src/components/ConnectionErrorView.test.tsx +21 -1
  10. package/src/components/ConnectionErrorView.tsx +7 -8
  11. package/src/components/applications/ApplicationsView.tsx +10 -9
  12. package/src/components/audit/AuditView.tsx +6 -3
  13. package/src/components/audit/UpgradeReadinessView.test.ts +26 -2
  14. package/src/components/audit/UpgradeReadinessView.tsx +19 -9
  15. package/src/components/diagnose/DiagnoseSurface.tsx +14 -10
  16. package/src/components/gitops/GitOpsView.tsx +8 -3
  17. package/src/components/helm/HelmReleaseDrawer.tsx +4 -3
  18. package/src/components/helm/OwnedResources.tsx +10 -2
  19. package/src/components/home/ClusterHealthCard.test.ts +31 -0
  20. package/src/components/home/ClusterHealthCard.tsx +60 -1
  21. package/src/components/home/HomeView.tsx +36 -11
  22. package/src/components/home/MCPSetupDialog.tsx +5 -4
  23. package/src/components/home/RadarVersionLine.test.tsx +145 -0
  24. package/src/components/home/RadarVersionLine.tsx +137 -0
  25. package/src/components/resources/PodFilesystemModal.tsx +54 -2
  26. package/src/components/resources/ResourcesView.tsx +31 -8
  27. package/src/components/resources/renderers/WorkloadRenderer.tsx +13 -5
  28. package/src/components/settings/SettingsDialog.tsx +21 -4
  29. package/src/components/ui/ErrorBoundary.test.tsx +55 -0
  30. package/src/components/ui/ErrorBoundary.tsx +17 -2
  31. package/src/components/ui/UpdateNotification.test.tsx +49 -0
  32. package/src/components/ui/UpdateNotification.tsx +6 -2
  33. package/src/components/workload/WorkloadView.test.ts +60 -0
  34. package/src/components/workload/WorkloadView.tsx +270 -29
  35. package/src/contexts/CapabilitiesContext.test.tsx +29 -0
  36. package/src/contexts/CapabilitiesContext.tsx +7 -3
  37. package/src/utils/navigation.test.ts +45 -0
  38. package/src/utils/navigation.ts +5 -5
  39. package/src/utils/topology-selection.ts +3 -2
  40. package/src/utils/version.test.ts +37 -0
  41. package/src/utils/version.ts +56 -0
@@ -1,5 +1,5 @@
1
1
  import type { SelectedResource, Topology, TopologyNode } from '@skyhook-io/k8s-ui/types/core'
2
- import { apiVersionToGroup, kindToPlural } from '@skyhook-io/k8s-ui/utils/navigation'
2
+ import { apiVersionToGroup, kindToPluralWithGroup } from '@skyhook-io/k8s-ui/utils/navigation'
3
3
 
4
4
  function topologyNodeGroup(node: TopologyNode): string | undefined {
5
5
  const apiVersionGroup = apiVersionToGroup(node.data.apiVersion as string | undefined)
@@ -18,10 +18,11 @@ export function findSelectedTopologyNode(
18
18
  selectedResource: SelectedResource,
19
19
  ): TopologyNode | undefined {
20
20
  const namespace = selectedResource.namespace || ''
21
+ const selectedKind = kindToPluralWithGroup(selectedResource.kind, selectedResource.group ?? '')
21
22
  const candidates = topology?.nodes.filter(node =>
22
23
  ((node.data.namespace as string) || '') === namespace &&
23
24
  node.name === selectedResource.name &&
24
- (kindToPlural(node.kind) === selectedResource.kind || node.kind === selectedResource.kind),
25
+ (kindToPluralWithGroup(node.kind, topologyNodeGroup(node) ?? '') === selectedKind || node.kind === selectedResource.kind),
25
26
  ) ?? []
26
27
 
27
28
  if (candidates.length === 0) return undefined
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ getVersionUpdateStatus,
4
+ IN_CLUSTER_UPGRADE_URL,
5
+ parseMajorMinor,
6
+ versionUpdateURL,
7
+ } from './version'
8
+
9
+ describe('Radar version helpers', () => {
10
+ it('parses major and minor versions with an optional v prefix', () => {
11
+ expect(parseMajorMinor('v1.12.3')).toEqual({ major: 1, minor: 12 })
12
+ expect(parseMajorMinor('dev')).toBeNull()
13
+ })
14
+
15
+ it('grades available updates by distance from the latest release', () => {
16
+ expect(getVersionUpdateStatus('1.12.0', '1.12.1')).toEqual({ tier: 'patch' })
17
+ expect(getVersionUpdateStatus('1.11.4', '1.12.1')).toEqual({ tier: 'minor', minorVersionsBehind: 1 })
18
+ expect(getVersionUpdateStatus('1.10.4', '1.12.1')).toEqual({ tier: 'minor', minorVersionsBehind: 2 })
19
+ expect(getVersionUpdateStatus('1.9.4', '1.12.1')).toEqual({ tier: 'stale', minorVersionsBehind: 3 })
20
+ expect(getVersionUpdateStatus('0.12.4', '1.12.1')).toEqual({ tier: 'stale', majorVersionBehind: true })
21
+ expect(getVersionUpdateStatus('1.12.1-rc.1', '1.12.1')).toEqual({ tier: 'patch' })
22
+ })
23
+
24
+ it('does not flag invalid, current, or newer local builds', () => {
25
+ expect(getVersionUpdateStatus('dev', '1.12.1')).toEqual({ tier: 'none' })
26
+ expect(getVersionUpdateStatus('1.12.1', '1.12.1')).toEqual({ tier: 'none' })
27
+ expect(getVersionUpdateStatus('1.13.0', '1.12.1')).toEqual({ tier: 'none' })
28
+ expect(getVersionUpdateStatus('2.0.0', '1.12.1')).toEqual({ tier: 'none' })
29
+ })
30
+
31
+ it('routes in-cluster upgrades to instructions instead of release notes', () => {
32
+ const releaseURL = 'https://github.com/skyhook-io/radar/releases/tag/v1.2.4'
33
+ expect(versionUpdateURL('in-cluster', releaseURL)).toBe(IN_CLUSTER_UPGRADE_URL)
34
+ expect(versionUpdateURL('local', releaseURL)).toBe(releaseURL)
35
+ expect(versionUpdateURL('cloud', releaseURL)).toBeUndefined()
36
+ })
37
+ })
@@ -0,0 +1,56 @@
1
+ import type { DeploymentMode } from '../types'
2
+
3
+ export interface MajorMinorVersion {
4
+ major: number
5
+ minor: number
6
+ }
7
+
8
+ export type VersionUpdateTier = 'none' | 'patch' | 'minor' | 'stale'
9
+
10
+ export interface VersionUpdateStatus {
11
+ tier: VersionUpdateTier
12
+ minorVersionsBehind?: number
13
+ majorVersionBehind?: boolean
14
+ }
15
+
16
+ export const IN_CLUSTER_UPGRADE_URL = 'https://radarhq.io/docs/configuration/in-cluster'
17
+
18
+ export function versionUpdateURL(deploymentMode: DeploymentMode, releaseURL?: string): string | undefined {
19
+ if (deploymentMode === 'cloud') return undefined
20
+ return deploymentMode === 'in-cluster' ? IN_CLUSTER_UPGRADE_URL : releaseURL
21
+ }
22
+
23
+ export function parseMajorMinor(version: string): MajorMinorVersion | null {
24
+ const match = /^v?(\d+)\.(\d+)/.exec(version.trim())
25
+ if (!match) return null
26
+ return { major: Number(match[1]), minor: Number(match[2]) }
27
+ }
28
+
29
+ function parseVersion(version: string): [major: number, minor: number, patch: number, prerelease: boolean] | null {
30
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(version.trim())
31
+ if (!match) return null
32
+ return [Number(match[1]), Number(match[2]), Number(match[3]), !!match[4]]
33
+ }
34
+
35
+ export function getVersionUpdateStatus(current: string, latest?: string): VersionUpdateStatus {
36
+ if (!latest) return { tier: 'none' }
37
+
38
+ const currentVersion = parseVersion(current)
39
+ const latestVersion = parseVersion(latest)
40
+ if (!currentVersion || !latestVersion) return { tier: 'none' }
41
+
42
+ const [currentMajor, currentMinor, currentPatch, currentPrerelease] = currentVersion
43
+ const [latestMajor, latestMinor, latestPatch, latestPrerelease] = latestVersion
44
+
45
+ if (latestMajor < currentMajor) return { tier: 'none' }
46
+ if (latestMajor > currentMajor) return { tier: 'stale', majorVersionBehind: true }
47
+
48
+ const minorVersionsBehind = latestMinor - currentMinor
49
+ if (minorVersionsBehind >= 3) return { tier: 'stale', minorVersionsBehind }
50
+ if (minorVersionsBehind > 0) return { tier: 'minor', minorVersionsBehind }
51
+ if (minorVersionsBehind < 0 || latestPatch < currentPatch) return { tier: 'none' }
52
+ if (latestPatch === currentPatch) {
53
+ return currentPrerelease && !latestPrerelease ? { tier: 'patch' } : { tier: 'none' }
54
+ }
55
+ return { tier: 'patch' }
56
+ }