@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.
- package/package.json +2 -2
- package/src/App.tsx +34 -15
- package/src/api/client.images.test.ts +63 -0
- package/src/api/client.ts +208 -33
- package/src/api/client.yaml.test.ts +3 -3
- package/src/api/version-check.test.ts +78 -0
- package/src/components/CloudConnectFlow.tsx +46 -26
- package/src/components/CloudFunnelButton.tsx +166 -129
- package/src/components/ConnectionErrorView.test.tsx +21 -1
- package/src/components/ConnectionErrorView.tsx +7 -8
- package/src/components/applications/ApplicationsView.tsx +10 -9
- package/src/components/audit/AuditView.tsx +6 -3
- package/src/components/audit/UpgradeReadinessView.test.ts +26 -2
- package/src/components/audit/UpgradeReadinessView.tsx +19 -9
- package/src/components/diagnose/DiagnoseSurface.tsx +14 -10
- package/src/components/gitops/GitOpsView.tsx +8 -3
- package/src/components/helm/HelmReleaseDrawer.tsx +4 -3
- package/src/components/helm/OwnedResources.tsx +10 -2
- package/src/components/home/ClusterHealthCard.test.ts +31 -0
- package/src/components/home/ClusterHealthCard.tsx +60 -1
- package/src/components/home/HomeView.tsx +36 -11
- package/src/components/home/MCPSetupDialog.tsx +5 -4
- package/src/components/home/RadarVersionLine.test.tsx +145 -0
- package/src/components/home/RadarVersionLine.tsx +137 -0
- package/src/components/resources/PodFilesystemModal.tsx +54 -2
- package/src/components/resources/ResourcesView.tsx +31 -8
- package/src/components/resources/renderers/WorkloadRenderer.tsx +13 -5
- package/src/components/settings/SettingsDialog.tsx +21 -4
- package/src/components/ui/ErrorBoundary.test.tsx +55 -0
- package/src/components/ui/ErrorBoundary.tsx +17 -2
- package/src/components/ui/UpdateNotification.test.tsx +49 -0
- package/src/components/ui/UpdateNotification.tsx +6 -2
- package/src/components/workload/WorkloadView.test.ts +60 -0
- package/src/components/workload/WorkloadView.tsx +270 -29
- package/src/contexts/CapabilitiesContext.test.tsx +29 -0
- package/src/contexts/CapabilitiesContext.tsx +7 -3
- package/src/utils/navigation.test.ts +45 -0
- package/src/utils/navigation.ts +5 -5
- package/src/utils/topology-selection.ts +3 -2
- package/src/utils/version.test.ts +37 -0
- package/src/utils/version.ts +56 -0
|
@@ -3,20 +3,35 @@ import { AlertTriangle, RefreshCw } from 'lucide-react'
|
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
children: ReactNode
|
|
6
|
+
/**
|
|
7
|
+
* Clears a caught error whenever this value changes - pass whatever selects
|
|
8
|
+
* the children, typically the route. A boundary latches: nothing resets
|
|
9
|
+
* hasError on its own, so without this a view that throws keeps the fallback
|
|
10
|
+
* on screen and navigating away cannot escape it. Preferred over remounting
|
|
11
|
+
* via `key`, which would also discard the children's state on every change
|
|
12
|
+
* while nothing is wrong.
|
|
13
|
+
*/
|
|
14
|
+
resetKey: string | number
|
|
6
15
|
}
|
|
7
16
|
|
|
8
17
|
interface State {
|
|
9
18
|
hasError: boolean
|
|
10
19
|
error: Error | null
|
|
20
|
+
resetKey: string | number
|
|
11
21
|
}
|
|
12
22
|
|
|
13
23
|
export class ErrorBoundary extends Component<Props, State> {
|
|
14
|
-
state: State = { hasError: false, error: null }
|
|
24
|
+
state: State = { hasError: false, error: null, resetKey: this.props.resetKey }
|
|
15
25
|
|
|
16
|
-
static getDerivedStateFromError(error: Error): State {
|
|
26
|
+
static getDerivedStateFromError(error: Error): Pick<State, 'hasError' | 'error'> {
|
|
17
27
|
return { hasError: true, error }
|
|
18
28
|
}
|
|
19
29
|
|
|
30
|
+
static getDerivedStateFromProps(props: Props, state: State): State | null {
|
|
31
|
+
if (Object.is(props.resetKey, state.resetKey)) return null
|
|
32
|
+
return { hasError: false, error: null, resetKey: props.resetKey }
|
|
33
|
+
}
|
|
34
|
+
|
|
20
35
|
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
|
21
36
|
console.error('[ErrorBoundary]', error, info.componentStack)
|
|
22
37
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
let deploymentMode = 'local'
|
|
6
|
+
|
|
7
|
+
vi.mock('../../api/client', () => ({
|
|
8
|
+
useCapabilities: () => ({ data: { deployment: { mode: deploymentMode } } }),
|
|
9
|
+
useVersionCheck: () => ({
|
|
10
|
+
data: {
|
|
11
|
+
currentVersion: '1.2.3',
|
|
12
|
+
latestVersion: '1.3.0',
|
|
13
|
+
updateAvailable: true,
|
|
14
|
+
installMethod: 'direct',
|
|
15
|
+
releaseUrl: 'https://github.com/skyhook-io/radar/releases/tag/v1.3.0',
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
useStartDesktopUpdate: () => ({ mutate: vi.fn(), isPending: false }),
|
|
19
|
+
useDesktopUpdateStatus: () => ({ data: undefined }),
|
|
20
|
+
useApplyDesktopUpdate: () => ({ mutate: vi.fn() }),
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
import { UpdateNotification } from './UpdateNotification'
|
|
24
|
+
|
|
25
|
+
function renderNotification() {
|
|
26
|
+
const client = new QueryClient()
|
|
27
|
+
return renderToString(
|
|
28
|
+
<QueryClientProvider client={client}>
|
|
29
|
+
<UpdateNotification />
|
|
30
|
+
</QueryClientProvider>,
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('UpdateNotification', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
deploymentMode = 'local'
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('keeps the update popup for local installations', () => {
|
|
40
|
+
const html = renderNotification()
|
|
41
|
+
expect(html).toContain('Update Available')
|
|
42
|
+
expect(html).toContain('1.3.0')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('suppresses the update popup for shared in-cluster viewers', () => {
|
|
46
|
+
deploymentMode = 'in-cluster'
|
|
47
|
+
expect(renderNotification()).toBe('')
|
|
48
|
+
})
|
|
49
|
+
})
|
|
@@ -3,6 +3,7 @@ import { Download, X, Copy, Check, RotateCw, ArrowDownToLine, Loader2 } from 'lu
|
|
|
3
3
|
import { useQueryClient } from '@tanstack/react-query'
|
|
4
4
|
import {
|
|
5
5
|
useVersionCheck,
|
|
6
|
+
useCapabilities,
|
|
6
7
|
useStartDesktopUpdate,
|
|
7
8
|
useDesktopUpdateStatus,
|
|
8
9
|
useApplyDesktopUpdate,
|
|
@@ -14,6 +15,8 @@ const DISMISSED_KEY = 'radar-update-dismissed'
|
|
|
14
15
|
|
|
15
16
|
export function UpdateNotification() {
|
|
16
17
|
const queryClient = useQueryClient()
|
|
18
|
+
const { data: capabilities } = useCapabilities()
|
|
19
|
+
const deploymentMode = capabilities ? (capabilities.deployment?.mode ?? 'local') : undefined
|
|
17
20
|
const { data: versionInfo } = useVersionCheck()
|
|
18
21
|
const [dismissed, setDismissed] = useState(false)
|
|
19
22
|
const [copied, setCopied] = useState(false)
|
|
@@ -98,8 +101,9 @@ export function UpdateNotification() {
|
|
|
98
101
|
})
|
|
99
102
|
}
|
|
100
103
|
|
|
101
|
-
//
|
|
102
|
-
|
|
104
|
+
// Shared in-cluster viewers get a persistent Home notice instead of a
|
|
105
|
+
// floating action prompt they may not be able to act on.
|
|
106
|
+
if (!versionInfo?.updateAvailable || dismissed || deploymentMode === undefined || deploymentMode === 'in-cluster' || deploymentMode === 'cloud') {
|
|
103
107
|
return null
|
|
104
108
|
}
|
|
105
109
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { gitOpsOwnerFromRelationships } from '@skyhook-io/k8s-ui'
|
|
4
|
+
import type { Relationships } from '../../types'
|
|
5
|
+
import { findInheritedGitOpsLookupRef } from './WorkloadView'
|
|
6
|
+
|
|
7
|
+
describe('findInheritedGitOpsLookupRef', () => {
|
|
8
|
+
it('follows a referenced ReplicaSet to its parent workload for inherited ownership', () => {
|
|
9
|
+
const relationships: Relationships = {
|
|
10
|
+
managedBy: [
|
|
11
|
+
{
|
|
12
|
+
kind: 'Deployment',
|
|
13
|
+
group: 'apps',
|
|
14
|
+
namespace: 'prod',
|
|
15
|
+
name: 'web',
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
expect(
|
|
21
|
+
findInheritedGitOpsLookupRef(relationships, null, {
|
|
22
|
+
kind: 'ReplicaSet',
|
|
23
|
+
group: 'apps',
|
|
24
|
+
namespace: 'prod',
|
|
25
|
+
name: 'web-7b8d9',
|
|
26
|
+
}),
|
|
27
|
+
).toEqual({
|
|
28
|
+
kind: 'Deployment',
|
|
29
|
+
group: 'apps',
|
|
30
|
+
namespace: 'prod',
|
|
31
|
+
name: 'web',
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('does not fetch a parent when the target has a direct GitOps owner', () => {
|
|
36
|
+
const relationships: Relationships = {
|
|
37
|
+
managedBy: [
|
|
38
|
+
{
|
|
39
|
+
kind: 'Application',
|
|
40
|
+
group: 'argoproj.io',
|
|
41
|
+
namespace: 'argocd',
|
|
42
|
+
name: 'web',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
expect(
|
|
48
|
+
findInheritedGitOpsLookupRef(
|
|
49
|
+
relationships,
|
|
50
|
+
gitOpsOwnerFromRelationships(relationships),
|
|
51
|
+
{
|
|
52
|
+
kind: 'Deployment',
|
|
53
|
+
group: 'apps',
|
|
54
|
+
namespace: 'prod',
|
|
55
|
+
name: 'web',
|
|
56
|
+
},
|
|
57
|
+
),
|
|
58
|
+
).toBeNull()
|
|
59
|
+
})
|
|
60
|
+
})
|