@skyhook-io/radar-app 1.6.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -2
- package/src/App.tsx +110 -45
- package/src/api/client.ts +10 -6
- package/src/components/ConnectionErrorView.tsx +5 -2
- package/src/components/DebugOverlay.tsx +4 -2
- package/src/components/UserMenu.tsx +5 -2
- package/src/components/audit/AuditSettingsDialog.tsx +14 -10
- package/src/components/audit/AuditView.tsx +3 -1
- package/src/components/compare/useCompareLauncher.tsx +6 -3
- package/src/components/cost/CostView.tsx +13 -4
- package/src/components/helm/ChartBrowser.tsx +6 -4
- package/src/components/helm/HelmReleaseDrawer.tsx +25 -13
- package/src/components/helm/HelmView.tsx +7 -6
- package/src/components/helm/InstallWizard.tsx +3 -2
- package/src/components/helm/OwnedResources.tsx +24 -13
- package/src/components/helm/RevisionHistory.tsx +2 -1
- package/src/components/helm/ValuesViewer.tsx +4 -2
- package/src/components/home/ClusterHealthCard.tsx +21 -18
- package/src/components/home/HelmSummary.tsx +3 -1
- package/src/components/home/HomeView.tsx +9 -2
- package/src/components/home/MCPSetupDialog.tsx +10 -4
- package/src/components/nav/PrimaryNavRail.tsx +5 -2
- package/src/components/portforward/PortForwardButton.tsx +14 -8
- package/src/components/resource/PrometheusChartsGrid.tsx +9 -2
- package/src/components/resource/RestartChart.tsx +6 -5
- package/src/components/resource/RightsizingStrip.tsx +4 -1
- package/src/components/resource-drawer/ResourceDrawer.tsx +3 -1
- package/src/components/resources/ImageFilesystemModal.tsx +16 -8
- package/src/components/resources/PodFilesystemModal.tsx +4 -2
- package/src/components/settings/SettingsDialog.tsx +371 -141
- package/src/components/traffic/TrafficFilterSidebar.tsx +8 -43
- package/src/components/traffic/TrafficFlowList.tsx +13 -4
- package/src/components/traffic/TrafficGraph.tsx +8 -3
- package/src/components/traffic/TrafficView.tsx +4 -2
- package/src/components/ui/Omnibar.tsx +0 -1
- package/src/components/workload/WorkloadView.tsx +15 -10
- package/src/context/NavCustomization.tsx +48 -9
- package/src/index.ts +1 -1
|
@@ -3,6 +3,7 @@ import type { ReactNode } from 'react'
|
|
|
3
3
|
import { Home, Network, List, Clock, AlertTriangle, Package, GitBranch, Boxes, Activity, DollarSign, ShieldCheck, Settings, PanelLeftClose, PanelLeftOpen } from 'lucide-react'
|
|
4
4
|
import { clsx } from 'clsx'
|
|
5
5
|
import type { MainView } from '../../types'
|
|
6
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
6
7
|
|
|
7
8
|
// The views the rail can navigate to. Broader than k8s-ui's ExtendedMainView
|
|
8
9
|
// (which omits 'applications') — it mirrors the navigable subset of App.tsx's
|
|
@@ -131,11 +132,11 @@ export function PrimaryNavRail({ activeView, onNavigate, pinned, onTogglePinned,
|
|
|
131
132
|
forced slim (showPinToggle=false) — expanding there isn't available. */}
|
|
132
133
|
{showPinToggle && (
|
|
133
134
|
<div className="px-2 pb-2 pt-1 border-t border-theme-border/50">
|
|
135
|
+
<Tooltip content={pinned ? 'Collapse navigation' : 'Expand navigation'} position="right" wrapperClassName="!block w-full shrink-0">
|
|
134
136
|
<button
|
|
135
137
|
type="button"
|
|
136
138
|
onClick={onTogglePinned}
|
|
137
139
|
aria-label={pinned ? 'Collapse navigation' : 'Expand navigation'}
|
|
138
|
-
title={pinned ? 'Collapse navigation' : 'Expand navigation'}
|
|
139
140
|
className="group/pin relative flex h-9 w-full items-center rounded-md text-theme-text-tertiary hover:bg-theme-hover hover:text-theme-text-secondary transition-colors"
|
|
140
141
|
>
|
|
141
142
|
<span className="flex w-10 shrink-0 items-center justify-center">
|
|
@@ -146,6 +147,7 @@ export function PrimaryNavRail({ activeView, onNavigate, pinned, onTogglePinned,
|
|
|
146
147
|
would contradict the "Expand navigation" label. */}
|
|
147
148
|
<span className={clsx('text-[13px] font-medium', !pinned && 'hidden')}>Collapse</span>
|
|
148
149
|
</button>
|
|
150
|
+
</Tooltip>
|
|
149
151
|
</div>
|
|
150
152
|
)}
|
|
151
153
|
</aside>
|
|
@@ -156,11 +158,11 @@ function BrandRow({ pinned, onNavigate }: { pinned: boolean; onNavigate: (view:
|
|
|
156
158
|
// Clickable brand = secondary home affordance (logo→home convention). The
|
|
157
159
|
// Home nav item below still carries the active state; the brand just navigates.
|
|
158
160
|
return (
|
|
161
|
+
<Tooltip content="Home" position="right" wrapperClassName="!block w-full shrink-0">
|
|
159
162
|
<button
|
|
160
163
|
type="button"
|
|
161
164
|
onClick={() => onNavigate('home')}
|
|
162
165
|
aria-label="Radar — go to home"
|
|
163
|
-
title="Home"
|
|
164
166
|
// Height matches the top bar header (App.tsx — items-center + py-2 = 51px)
|
|
165
167
|
// so the rail's brand divider and the header's bottom border form one line.
|
|
166
168
|
className="flex h-[51px] w-full items-center border-b border-theme-border/50 shrink-0 transition-opacity hover:opacity-80"
|
|
@@ -181,6 +183,7 @@ function BrandRow({ pinned, onNavigate }: { pinned: boolean; onNavigate: (view:
|
|
|
181
183
|
<span className="text-[9px] mt-0.5 tracking-wide uppercase text-theme-text-tertiary">by Skyhook</span>
|
|
182
184
|
</span>
|
|
183
185
|
</button>
|
|
186
|
+
</Tooltip>
|
|
184
187
|
)
|
|
185
188
|
}
|
|
186
189
|
|
|
@@ -4,6 +4,7 @@ import { clsx } from 'clsx'
|
|
|
4
4
|
import { useAvailablePorts, useClusterInfo, AvailablePort } from '../../api/client'
|
|
5
5
|
import { useStartPortForward } from './PortForwardManager'
|
|
6
6
|
import { validatePort } from '@skyhook-io/k8s-ui/utils/validators'
|
|
7
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
7
8
|
|
|
8
9
|
interface PortForwardButtonProps {
|
|
9
10
|
type: 'pod' | 'service'
|
|
@@ -206,31 +207,32 @@ export function PortForwardButton({
|
|
|
206
207
|
// If no ports available, show disabled button
|
|
207
208
|
if (!isLoading && ports.length === 0) {
|
|
208
209
|
return (
|
|
210
|
+
<Tooltip content="No ports available">
|
|
209
211
|
<button
|
|
210
212
|
disabled
|
|
211
213
|
className={clsx(
|
|
212
|
-
'flex items-center gap-2 px-3 py-2 bg-theme-elevated text-theme-text-primary text-sm rounded-lg opacity-50 cursor-not-allowed',
|
|
214
|
+
'flex items-center gap-2 px-3 py-2 bg-theme-elevated text-theme-text-primary text-sm rounded-lg opacity-50 cursor-not-allowed disabled:pointer-events-none',
|
|
213
215
|
className
|
|
214
216
|
)}
|
|
215
|
-
title="No ports available"
|
|
216
217
|
>
|
|
217
218
|
<Plug className="w-4 h-4" />
|
|
218
219
|
No Ports
|
|
219
220
|
</button>
|
|
221
|
+
</Tooltip>
|
|
220
222
|
)
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
// If only one port, forward directly on click (most common case)
|
|
224
226
|
if (ports.length === 1) {
|
|
225
227
|
return (
|
|
228
|
+
<Tooltip content={`Port forward to ${ports[0].port}`}>
|
|
226
229
|
<button
|
|
227
230
|
onClick={() => handlePortSelect(ports[0])}
|
|
228
231
|
disabled={isPending}
|
|
229
232
|
className={clsx(
|
|
230
|
-
'flex items-center gap-2 px-3 py-2 bg-theme-elevated text-theme-text-primary text-sm rounded-lg hover:bg-theme-hover transition-colors disabled:opacity-50',
|
|
233
|
+
'flex items-center gap-2 px-3 py-2 bg-theme-elevated text-theme-text-primary text-sm rounded-lg hover:bg-theme-hover transition-colors disabled:opacity-50 disabled:pointer-events-none',
|
|
231
234
|
className
|
|
232
235
|
)}
|
|
233
|
-
title={`Port forward to ${ports[0].port}`}
|
|
234
236
|
>
|
|
235
237
|
{isPending ? (
|
|
236
238
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
@@ -239,6 +241,7 @@ export function PortForwardButton({
|
|
|
239
241
|
)}
|
|
240
242
|
Forward :{ports[0].port}
|
|
241
243
|
</button>
|
|
244
|
+
</Tooltip>
|
|
242
245
|
)
|
|
243
246
|
}
|
|
244
247
|
|
|
@@ -269,6 +272,7 @@ export function PortForwardButton({
|
|
|
269
272
|
<div className="px-3 py-2 border-b border-theme-border">
|
|
270
273
|
<div className="text-xs text-theme-text-disabled mb-2">Listen on</div>
|
|
271
274
|
<div className="flex gap-1">
|
|
275
|
+
<Tooltip content="Only accessible from this machine" wrapperClassName="flex-1">
|
|
272
276
|
<button
|
|
273
277
|
onClick={(e) => { e.stopPropagation(); setListenAddress('127.0.0.1') }}
|
|
274
278
|
className={clsx(
|
|
@@ -277,11 +281,12 @@ export function PortForwardButton({
|
|
|
277
281
|
? 'btn-brand-toggle'
|
|
278
282
|
: 'bg-theme-elevated text-theme-text-tertiary hover:text-theme-text-primary'
|
|
279
283
|
)}
|
|
280
|
-
title="Only accessible from this machine"
|
|
281
284
|
>
|
|
282
285
|
<Monitor className="w-3 h-3" />
|
|
283
286
|
localhost
|
|
284
287
|
</button>
|
|
288
|
+
</Tooltip>
|
|
289
|
+
<Tooltip content="Accessible from other machines on the network" wrapperClassName="flex-1">
|
|
285
290
|
<button
|
|
286
291
|
onClick={(e) => { e.stopPropagation(); setListenAddress('0.0.0.0') }}
|
|
287
292
|
className={clsx(
|
|
@@ -290,11 +295,11 @@ export function PortForwardButton({
|
|
|
290
295
|
? 'bg-amber-600 text-white'
|
|
291
296
|
: 'bg-theme-elevated text-theme-text-tertiary hover:text-theme-text-primary'
|
|
292
297
|
)}
|
|
293
|
-
title="Accessible from other machines on the network"
|
|
294
298
|
>
|
|
295
299
|
<Globe className="w-3 h-3" />
|
|
296
300
|
all interfaces
|
|
297
301
|
</button>
|
|
302
|
+
</Tooltip>
|
|
298
303
|
</div>
|
|
299
304
|
</div>
|
|
300
305
|
)}
|
|
@@ -375,11 +380,11 @@ export function PortForwardInlineButton({
|
|
|
375
380
|
|
|
376
381
|
return (
|
|
377
382
|
<>
|
|
383
|
+
<Tooltip content={`Port forward ${port}`}>
|
|
378
384
|
<button
|
|
379
385
|
onClick={handleClick}
|
|
380
386
|
disabled={disabled || isPending}
|
|
381
|
-
className="inline-flex items-center gap-1 px-1.5 py-0.5 bg-theme-elevated hover:bg-accent-muted rounded text-xs transition-colors disabled:opacity-50 disabled:hover:bg-theme-elevated"
|
|
382
|
-
title={`Port forward ${port}`}
|
|
387
|
+
className="inline-flex items-center gap-1 px-1.5 py-0.5 bg-theme-elevated hover:bg-accent-muted rounded text-xs transition-colors disabled:opacity-50 disabled:hover:bg-theme-elevated disabled:pointer-events-none"
|
|
383
388
|
>
|
|
384
389
|
{port}/{protocol}
|
|
385
390
|
{isPending ? (
|
|
@@ -388,6 +393,7 @@ export function PortForwardInlineButton({
|
|
|
388
393
|
<Plug className="w-3 h-3" />
|
|
389
394
|
)}
|
|
390
395
|
</button>
|
|
396
|
+
</Tooltip>
|
|
391
397
|
{dialogInfo && (
|
|
392
398
|
<KubectlCommandDialog info={dialogInfo} onClose={() => setDialogInfo(null)} />
|
|
393
399
|
)}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type CategoryDef,
|
|
27
27
|
} from './PrometheusCharts'
|
|
28
28
|
import { RestartEventLane } from './RestartChart'
|
|
29
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
29
30
|
|
|
30
31
|
// Used when MetricsTabContent is in expanded (full-screen) mode. Drawer mode
|
|
31
32
|
// uses the single-chart tabbed `PrometheusCharts` instead — drawer width
|
|
@@ -288,7 +289,11 @@ function WorkloadHealthBadge({ kind, namespace, name }: { kind: string; namespac
|
|
|
288
289
|
// silently render as fine while we have no signal to display.
|
|
289
290
|
if (error && !data) {
|
|
290
291
|
const msg = error instanceof Error ? error.message : String(error)
|
|
291
|
-
return
|
|
292
|
+
return (
|
|
293
|
+
<Tooltip content={`Health check failed: ${msg}`}>
|
|
294
|
+
<span className={`badge badge-sm ${SEVERITY_BADGE.neutral}`}>Health unknown</span>
|
|
295
|
+
</Tooltip>
|
|
296
|
+
)
|
|
292
297
|
}
|
|
293
298
|
if (!data?.sampleAvailable || data.rows.length === 0) return null
|
|
294
299
|
|
|
@@ -320,7 +325,9 @@ function PanelError({ message }: { message: string }) {
|
|
|
320
325
|
return (
|
|
321
326
|
<div className={`flex flex-col items-center justify-center h-full min-h-[160px] ${SEVERITY_TEXT.warning} text-xs px-3 text-center`}>
|
|
322
327
|
Query failed
|
|
323
|
-
<
|
|
328
|
+
<Tooltip content={message} wrapperClassName="!block w-full">
|
|
329
|
+
<span className="text-theme-text-quaternary mt-0.5 line-clamp-2">{message}</span>
|
|
330
|
+
</Tooltip>
|
|
324
331
|
</div>
|
|
325
332
|
)
|
|
326
333
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo } from 'react'
|
|
2
2
|
import { AlertCircle } from 'lucide-react'
|
|
3
3
|
import { usePrometheusResourceMetrics, usePrometheusStatus, type PrometheusSeries, type PrometheusTimeRange } from '../../api/client'
|
|
4
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* RestartEventLane — vertical markers at each restart event, on a dedicated
|
|
@@ -58,14 +59,14 @@ export function RestartEventLane({ kind, namespace, name, range = '1h' }: {
|
|
|
58
59
|
{restarts.map((r, i) => {
|
|
59
60
|
const left = `${Math.max(0, Math.min(100, ((r.timestamp - windowStart) / span) * 100))}%`
|
|
60
61
|
return (
|
|
61
|
-
<
|
|
62
|
+
<Tooltip
|
|
62
63
|
key={i}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
content={`${new Date(r.timestamp * 1000).toLocaleString()} · ${r.label}${r.value > 1 ? ` ×${r.value}` : ''}`}
|
|
65
|
+
wrapperClassName="absolute top-0 h-full w-px bg-amber-500/80"
|
|
66
|
+
wrapperStyle={{ left }}
|
|
66
67
|
>
|
|
67
68
|
<div className="absolute -top-0.5 left-1/2 -translate-x-1/2 w-1.5 h-1.5 rounded-full bg-amber-500" />
|
|
68
|
-
</
|
|
69
|
+
</Tooltip>
|
|
69
70
|
)
|
|
70
71
|
})}
|
|
71
72
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ArrowRight, Check, Info, AlertTriangle } from 'lucide-react'
|
|
2
2
|
import { SEVERITY_TEXT, SEVERITY_BADGE, type Severity } from '@skyhook-io/k8s-ui/utils/badge-colors'
|
|
3
3
|
import { usePrometheusRightsizing, usePrometheusStatus, type RightsizingTone, type RightsizingRow } from '../../api/client'
|
|
4
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
4
5
|
|
|
5
6
|
const RIGHTSIZING_KINDS = new Set(['Deployment', 'StatefulSet', 'DaemonSet'])
|
|
6
7
|
|
|
@@ -38,7 +39,9 @@ export function RightsizingStrip({ kind, namespace, name }: {
|
|
|
38
39
|
<header className="flex items-center justify-between mb-1">
|
|
39
40
|
<h3 className="text-sm font-medium text-theme-text-primary">Right-sizing</h3>
|
|
40
41
|
</header>
|
|
41
|
-
<
|
|
42
|
+
<Tooltip content={msg}>
|
|
43
|
+
<p className="text-xs text-theme-text-tertiary">Right-sizing unavailable — Prometheus query failed.</p>
|
|
44
|
+
</Tooltip>
|
|
42
45
|
</section>
|
|
43
46
|
)
|
|
44
47
|
}
|
|
@@ -4,6 +4,7 @@ import { clsx } from 'clsx'
|
|
|
4
4
|
import { useState, useCallback } from 'react'
|
|
5
5
|
import type { TopologyNode, NodeKind, HealthStatus } from '../../types'
|
|
6
6
|
import { getKindBadgeBordered, healthToSeverity, SEVERITY_BADGE_BORDERED } from '../../utils/badge-colors'
|
|
7
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
7
8
|
|
|
8
9
|
interface ResourceDrawerProps {
|
|
9
10
|
node: TopologyNode
|
|
@@ -146,10 +147,10 @@ export const ResourceDrawer = memo(function ResourceDrawer({
|
|
|
146
147
|
<h2 className="text-lg font-semibold text-theme-text-primary truncate">
|
|
147
148
|
{node.name}
|
|
148
149
|
</h2>
|
|
150
|
+
<Tooltip content="Copy name">
|
|
149
151
|
<button
|
|
150
152
|
onClick={copyName}
|
|
151
153
|
className="p-1 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
|
|
152
|
-
title="Copy name"
|
|
153
154
|
>
|
|
154
155
|
{copied ? (
|
|
155
156
|
<Check className="w-4 h-4 text-green-400" />
|
|
@@ -157,6 +158,7 @@ export const ResourceDrawer = memo(function ResourceDrawer({
|
|
|
157
158
|
<Copy className="w-4 h-4" />
|
|
158
159
|
)}
|
|
159
160
|
</button>
|
|
161
|
+
</Tooltip>
|
|
160
162
|
</div>
|
|
161
163
|
</div>
|
|
162
164
|
<button
|
|
@@ -7,6 +7,7 @@ import { useImageMetadata, ApiError } from '../../api/client'
|
|
|
7
7
|
import type { FileNode, ImageFilesystem } from '../../types'
|
|
8
8
|
import { formatBytes } from '../../utils/format'
|
|
9
9
|
import { downloadBlob, filterTree } from './file-browser-utils'
|
|
10
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
10
11
|
import { apiUrl, getAuthHeaders, getCredentialsMode } from '../../api/config'
|
|
11
12
|
|
|
12
13
|
// Manual fetch function for filesystem (not a hook - gives us full control)
|
|
@@ -141,9 +142,11 @@ export function ImageFilesystemModal({
|
|
|
141
142
|
<div className="flex items-center justify-between p-4 border-b border-theme-border shrink-0">
|
|
142
143
|
<div className="flex-1 min-w-0">
|
|
143
144
|
<h3 className="text-lg font-semibold text-theme-text-primary">Image Filesystem</h3>
|
|
144
|
-
<
|
|
145
|
+
<Tooltip content={image} wrapperClassName="!block w-full">
|
|
146
|
+
<p className="text-sm text-theme-text-secondary truncate mt-0.5">
|
|
145
147
|
{image}
|
|
146
148
|
</p>
|
|
149
|
+
</Tooltip>
|
|
147
150
|
{(displayFilesystem?.platform || metadata?.platform) && (
|
|
148
151
|
<p className="text-xs text-theme-text-tertiary mt-1">
|
|
149
152
|
Platform: {displayFilesystem?.platform || metadata?.platform}
|
|
@@ -245,9 +248,11 @@ export function ImageFilesystemModal({
|
|
|
245
248
|
<span>{formatBytes(displayFilesystem.totalSize)}</span>
|
|
246
249
|
{displayFilesystem.layers && <span>{displayFilesystem.layers.length} layers</span>}
|
|
247
250
|
{displayFilesystem.digest && (
|
|
248
|
-
<
|
|
251
|
+
<Tooltip content={displayFilesystem.digest} wrapperClassName="min-w-0">
|
|
252
|
+
<span className="truncate">
|
|
249
253
|
Digest: {displayFilesystem.digest.substring(0, 20)}...
|
|
250
254
|
</span>
|
|
255
|
+
</Tooltip>
|
|
251
256
|
)}
|
|
252
257
|
</>
|
|
253
258
|
)}
|
|
@@ -362,10 +367,10 @@ function DownloadConfirmation({ metadata, onConfirm, onCancel }: DownloadConfirm
|
|
|
362
367
|
<pre className="bg-theme-elevated rounded p-3 text-xs text-theme-text-primary overflow-x-auto font-mono">
|
|
363
368
|
{authCommand}
|
|
364
369
|
</pre>
|
|
370
|
+
<Tooltip content="Copy to clipboard" position="left" wrapperClassName="absolute top-2 right-2">
|
|
365
371
|
<button
|
|
366
372
|
onClick={handleCopy}
|
|
367
|
-
className="
|
|
368
|
-
title="Copy to clipboard"
|
|
373
|
+
className="p-1.5 text-theme-text-tertiary hover:text-theme-text-primary hover:bg-theme-base rounded transition-colors"
|
|
369
374
|
>
|
|
370
375
|
{copied ? (
|
|
371
376
|
<Check className="w-4 h-4 text-green-400" />
|
|
@@ -373,6 +378,7 @@ function DownloadConfirmation({ metadata, onConfirm, onCancel }: DownloadConfirm
|
|
|
373
378
|
<Copy className="w-4 h-4" />
|
|
374
379
|
)}
|
|
375
380
|
</button>
|
|
381
|
+
</Tooltip>
|
|
376
382
|
</div>
|
|
377
383
|
</div>
|
|
378
384
|
)}
|
|
@@ -461,10 +467,10 @@ function AuthenticationHelp({ image, registryType, onRetry }: AuthenticationHelp
|
|
|
461
467
|
<pre className="bg-theme-elevated rounded p-3 text-xs text-theme-text-primary overflow-x-auto font-mono">
|
|
462
468
|
{authCommand}
|
|
463
469
|
</pre>
|
|
470
|
+
<Tooltip content="Copy to clipboard" position="left" wrapperClassName="absolute top-2 right-2">
|
|
464
471
|
<button
|
|
465
472
|
onClick={handleCopy}
|
|
466
|
-
className="
|
|
467
|
-
title="Copy to clipboard"
|
|
473
|
+
className="p-1.5 text-theme-text-tertiary hover:text-theme-text-primary hover:bg-theme-base rounded transition-colors"
|
|
468
474
|
>
|
|
469
475
|
{copied ? (
|
|
470
476
|
<Check className="w-4 h-4 text-green-400" />
|
|
@@ -472,6 +478,7 @@ function AuthenticationHelp({ image, registryType, onRetry }: AuthenticationHelp
|
|
|
472
478
|
<Copy className="w-4 h-4" />
|
|
473
479
|
)}
|
|
474
480
|
</button>
|
|
481
|
+
</Tooltip>
|
|
475
482
|
</div>
|
|
476
483
|
</div>
|
|
477
484
|
)}
|
|
@@ -709,11 +716,11 @@ function FileTreeNode({ node, depth, defaultExpanded = true, image, namespace, p
|
|
|
709
716
|
)}
|
|
710
717
|
|
|
711
718
|
{isFile && (
|
|
719
|
+
<Tooltip content="Download file" wrapperClassName="ml-1">
|
|
712
720
|
<button
|
|
713
721
|
onClick={handleDownload}
|
|
714
722
|
disabled={downloading}
|
|
715
|
-
className="p-1 text-theme-text-tertiary hover:text-blue-400 hover:bg-theme-elevated rounded
|
|
716
|
-
title="Download file"
|
|
723
|
+
className="p-1 text-theme-text-tertiary hover:text-blue-400 hover:bg-theme-elevated rounded disabled:opacity-50 disabled:pointer-events-none"
|
|
717
724
|
>
|
|
718
725
|
{downloading ? (
|
|
719
726
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
@@ -721,6 +728,7 @@ function FileTreeNode({ node, depth, defaultExpanded = true, image, namespace, p
|
|
|
721
728
|
<Download className="w-3.5 h-3.5" />
|
|
722
729
|
)}
|
|
723
730
|
</button>
|
|
731
|
+
</Tooltip>
|
|
724
732
|
)}
|
|
725
733
|
</div>
|
|
726
734
|
|
|
@@ -7,6 +7,7 @@ import type { FileNode } from '../../types'
|
|
|
7
7
|
import { formatBytes } from '../../utils/format'
|
|
8
8
|
import { downloadBlob, filterTree } from './file-browser-utils'
|
|
9
9
|
import { apiUrl, getAuthHeaders, getCredentialsMode } from '../../api/config'
|
|
10
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
10
11
|
|
|
11
12
|
interface PodFilesystem {
|
|
12
13
|
root: FileNode
|
|
@@ -384,11 +385,11 @@ function PodFileTreeNode({ node, namespace, podName, container, onNavigate }: Po
|
|
|
384
385
|
)}
|
|
385
386
|
|
|
386
387
|
{isDownloadable && (
|
|
388
|
+
<Tooltip content="Download file" wrapperClassName="ml-1">
|
|
387
389
|
<button
|
|
388
390
|
onClick={handleDownload}
|
|
389
391
|
disabled={downloading}
|
|
390
|
-
className="p-1 text-theme-text-tertiary hover:text-blue-400 hover:bg-theme-elevated rounded
|
|
391
|
-
title="Download file"
|
|
392
|
+
className="p-1 text-theme-text-tertiary hover:text-blue-400 hover:bg-theme-elevated rounded disabled:opacity-50 disabled:pointer-events-none"
|
|
392
393
|
>
|
|
393
394
|
{downloading ? (
|
|
394
395
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
@@ -396,6 +397,7 @@ function PodFileTreeNode({ node, namespace, podName, container, onNavigate }: Po
|
|
|
396
397
|
<Download className="w-3.5 h-3.5" />
|
|
397
398
|
)}
|
|
398
399
|
</button>
|
|
400
|
+
</Tooltip>
|
|
399
401
|
)}
|
|
400
402
|
</div>
|
|
401
403
|
)
|