@skyhook-io/k8s-ui 1.10.3 → 1.10.4
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
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import ELK from 'elkjs/lib/elk-api.js'
|
|
9
9
|
import elkWorkerAlgorithm from 'elkjs/lib/elk-worker.min.js?url'
|
|
10
|
+
import { assetUrl } from '../../utils/asset-url'
|
|
10
11
|
|
|
11
12
|
// Create ELK with explicit worker URL (runs the algorithm in our worker context)
|
|
12
13
|
const elk = new ELK({
|
|
13
|
-
workerUrl: elkWorkerAlgorithm,
|
|
14
|
+
workerUrl: assetUrl(elkWorkerAlgorithm),
|
|
14
15
|
})
|
|
15
16
|
|
|
16
17
|
// ELK options for laying out nodes within a single group
|
package/src/types/core.ts
CHANGED
|
@@ -92,6 +92,21 @@ export interface Capabilities {
|
|
|
92
92
|
resources?: ResourcePermissions // Per-resource-type permissions
|
|
93
93
|
authEnabled?: boolean // Auth is enabled on the backend
|
|
94
94
|
username?: string // Authenticated user's username (when auth enabled)
|
|
95
|
+
// Which Cloud-connect lane this deployment gets. Optional on the wire:
|
|
96
|
+
// older backends don't advertise it — consumers fall back to wizard links.
|
|
97
|
+
cloudConnect?: CloudConnectCapability
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// CloudConnectCapability picks the Cloud funnel's connect lane: 'driver'
|
|
101
|
+
// means the in-product connect flow can run on this server (local, no auth,
|
|
102
|
+
// no existing tunnel); 'wizard' routes to the Hub's connect wizard at appUrl.
|
|
103
|
+
export interface CloudConnectCapability {
|
|
104
|
+
lane: 'driver' | 'wizard'
|
|
105
|
+
appUrl: string
|
|
106
|
+
// Hub API origin the connect dialog reads its live copy from. Absent means
|
|
107
|
+
// the server decided this deployment must not fetch — consumers render their
|
|
108
|
+
// compiled-in copy instead.
|
|
109
|
+
apiUrl?: string
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
export interface FeatureCapabilities {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest'
|
|
2
|
+
import { assetUrl } from './asset-url'
|
|
3
|
+
|
|
4
|
+
type RuntimeGlobal = typeof globalThis & {
|
|
5
|
+
__RADAR_RUNTIME_CONFIG__?: { assetBase?: string }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function setAssetBase(assetBase: string) {
|
|
9
|
+
;(globalThis as RuntimeGlobal).__RADAR_RUNTIME_CONFIG__ = { assetBase }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Simulates a Worker scope, where `window` is absent and `location` is the
|
|
14
|
+
* worker script's own URL. jsdom defines `window`, so it has to be removed.
|
|
15
|
+
*/
|
|
16
|
+
function asWorkerAt(pathname: string, run: () => void) {
|
|
17
|
+
const realWindow = globalThis.window
|
|
18
|
+
// @ts-expect-error — deliberately emulating a scope without `window`
|
|
19
|
+
delete globalThis.window
|
|
20
|
+
const realLocation = globalThis.location
|
|
21
|
+
Object.defineProperty(globalThis, 'location', {
|
|
22
|
+
value: { pathname },
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
})
|
|
26
|
+
try {
|
|
27
|
+
run()
|
|
28
|
+
} finally {
|
|
29
|
+
Object.defineProperty(globalThis, 'location', {
|
|
30
|
+
value: realLocation,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
})
|
|
34
|
+
globalThis.window = realWindow
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
delete (globalThis as RuntimeGlobal).__RADAR_RUNTIME_CONFIG__
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('assetUrl at the root (no base path)', () => {
|
|
43
|
+
it('leaves absolute paths untouched', () => {
|
|
44
|
+
expect(assetUrl('/images/radar/radar-icon.svg')).toBe('/images/radar/radar-icon.svg')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('makes Vite-relative asset paths absolute', () => {
|
|
48
|
+
expect(assetUrl('./assets/index-abc.js')).toBe('/assets/index-abc.js')
|
|
49
|
+
expect(assetUrl('assets/index-abc.js')).toBe('/assets/index-abc.js')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('unwraps the webpack/Next StaticImageData shape', () => {
|
|
53
|
+
expect(assetUrl({ src: '/_next/static/x.png' })).toBe('/_next/static/x.png')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// An app route may contain an "/assets/" segment (e.g. a namespace named
|
|
57
|
+
// "assets"). That must not be mistaken for a base path.
|
|
58
|
+
it('ignores an /assets/ segment in the current app route', () => {
|
|
59
|
+
expect(assetUrl('/images/radar/radar-icon.svg')).toBe('/images/radar/radar-icon.svg')
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('assetUrl under a base path', () => {
|
|
64
|
+
it('prefixes absolute and relative asset paths', () => {
|
|
65
|
+
setAssetBase('/radar')
|
|
66
|
+
expect(assetUrl('/images/radar/radar-icon.svg')).toBe('/radar/images/radar/radar-icon.svg')
|
|
67
|
+
expect(assetUrl('./assets/index-abc.js')).toBe('/radar/assets/index-abc.js')
|
|
68
|
+
expect(assetUrl('assets/index-abc.js')).toBe('/radar/assets/index-abc.js')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('is idempotent for already-prefixed paths', () => {
|
|
72
|
+
setAssetBase('/radar')
|
|
73
|
+
expect(assetUrl('/radar/assets/index-abc.js')).toBe('/radar/assets/index-abc.js')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('leaves protocol-relative URLs alone', () => {
|
|
77
|
+
setAssetBase('/radar')
|
|
78
|
+
expect(assetUrl('//cdn.example.com/x.png')).toBe('//cdn.example.com/x.png')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('handles a nested base path', () => {
|
|
82
|
+
setAssetBase('/tools/radar')
|
|
83
|
+
expect(assetUrl('/favicon.svg')).toBe('/tools/radar/favicon.svg')
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('assetUrl inside a Worker', () => {
|
|
88
|
+
it('recovers the base path from the worker script URL', () => {
|
|
89
|
+
asWorkerAt('/radar/assets/layout.worker-abc.js', () => {
|
|
90
|
+
expect(assetUrl('/images/x.svg')).toBe('/radar/images/x.svg')
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('resolves to the root when the worker is served from the root', () => {
|
|
95
|
+
asWorkerAt('/assets/layout.worker-abc.js', () => {
|
|
96
|
+
expect(assetUrl('/images/x.svg')).toBe('/images/x.svg')
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// The base path may itself contain "/assets/" — split on the last one.
|
|
101
|
+
it('splits on the last /assets/ segment', () => {
|
|
102
|
+
asWorkerAt('/host/assets/radar/assets/layout.worker-abc.js', () => {
|
|
103
|
+
expect(assetUrl('/images/x.svg')).toBe('/host/assets/radar/images/x.svg')
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('prefers the injected config over the URL heuristic', () => {
|
|
108
|
+
setAssetBase('/radar')
|
|
109
|
+
asWorkerAt('/somewhere/assets/worker.js', () => {
|
|
110
|
+
expect(assetUrl('/images/x.svg')).toBe('/radar/images/x.svg')
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
})
|
package/src/utils/asset-url.ts
CHANGED
|
@@ -9,5 +9,37 @@
|
|
|
9
9
|
export type AssetImport = string | { src: string }
|
|
10
10
|
|
|
11
11
|
export function assetUrl(asset: AssetImport): string {
|
|
12
|
-
|
|
12
|
+
const url = typeof asset === 'string' ? asset : asset.src
|
|
13
|
+
const assetBase = getRuntimeAssetBase()
|
|
14
|
+
if (!assetBase || url.startsWith('//') || url === assetBase || url.startsWith(`${assetBase}/`)) {
|
|
15
|
+
if (url.startsWith('./assets/')) return `/assets/${url.slice('./assets/'.length)}`
|
|
16
|
+
if (url.startsWith('assets/')) return `/${url}`
|
|
17
|
+
return url
|
|
18
|
+
}
|
|
19
|
+
if (url.startsWith('./')) return `${assetBase}/${url.slice(2)}`
|
|
20
|
+
if (!url.startsWith('/') && url.startsWith('assets/')) return `${assetBase}/${url}`
|
|
21
|
+
if (!url.startsWith('/')) return url
|
|
22
|
+
return `${assetBase}${url}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getRuntimeAssetBase(): string {
|
|
26
|
+
if (typeof globalThis === 'undefined') return ''
|
|
27
|
+
const runtime = (globalThis as typeof globalThis & {
|
|
28
|
+
__RADAR_RUNTIME_CONFIG__?: { assetBase?: string }
|
|
29
|
+
}).__RADAR_RUNTIME_CONFIG__
|
|
30
|
+
const configured = runtime?.assetBase?.replace(/\/+$/, '')
|
|
31
|
+
if (configured) return configured
|
|
32
|
+
|
|
33
|
+
// A Worker has its own global scope, so it never sees the runtime config the
|
|
34
|
+
// server injects into index.html — but its own script URL sits at
|
|
35
|
+
// `<base>/assets/<chunk>.js`, so the base can be recovered from it. Restricted
|
|
36
|
+
// to worker scopes on purpose: on the main thread `pathname` is an app route,
|
|
37
|
+
// which may legitimately contain an "/assets/" segment (a namespace or
|
|
38
|
+
// resource named "assets") and would yield a bogus prefix for every asset.
|
|
39
|
+
if (typeof window !== 'undefined') return ''
|
|
40
|
+
const pathname = globalThis.location?.pathname ?? ''
|
|
41
|
+
// lastIndexOf, not indexOf: the base path itself may contain "/assets/".
|
|
42
|
+
const assetsIndex = pathname.lastIndexOf('/assets/')
|
|
43
|
+
if (assetsIndex > 0) return pathname.slice(0, assetsIndex)
|
|
44
|
+
return ''
|
|
13
45
|
}
|