@skyhook-io/k8s-ui 1.0.0 → 1.1.1
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 +4 -1
- package/src/components/resources/ResourcesView.tsx +50 -43
- package/src/components/resources/renderers/HPARenderer.tsx +2 -1
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +2 -1
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +2 -1
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +2 -1
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +7 -6
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +2 -1
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +3 -2
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +3 -2
- package/src/components/resources/renderers/VPARenderer.tsx +2 -1
- package/src/components/resources/renderers/WorkloadRenderer.tsx +53 -55
- package/src/components/shared/ResourceActionsBar.tsx +141 -142
- package/src/components/shared/ResourceRendererDispatch.tsx +40 -4
- package/src/components/shared/index.ts +1 -1
- package/src/components/topology/TopologyGraph.tsx +282 -19
- package/src/components/ui/ConfirmDialog.tsx +85 -130
- package/src/components/ui/DialogPortal.tsx +82 -0
- package/src/components/ui/index.ts +1 -0
- package/src/components/workload/WorkloadView.tsx +21 -1
- package/src/types/core.ts +1 -2
- package/src/utils/navigation.test.ts +142 -0
- package/src/utils/navigation.ts +52 -47
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { createTwoFilesPatch } from 'diff'
|
|
18
18
|
import { clsx } from 'clsx'
|
|
19
19
|
import { ForceDeleteConfirmDialog } from '../ui/ForceDeleteConfirmDialog'
|
|
20
|
+
import { DialogPortal } from '../ui/DialogPortal'
|
|
20
21
|
import type { SelectedResource, WorkloadRevision } from '../../types'
|
|
21
22
|
import { formatKindName } from '../ui/drawer-components'
|
|
22
23
|
|
|
@@ -619,7 +620,7 @@ export function RevisionHistoryDialog({ kind, namespace, name, open, onClose, re
|
|
|
619
620
|
const [confirmRevision, setConfirmRevision] = useState<number | null>(null)
|
|
620
621
|
const [diffRevision, setDiffRevision] = useState<number | null>(null)
|
|
621
622
|
|
|
622
|
-
|
|
623
|
+
const handleClose = () => { setDiffRevision(null); onClose() }
|
|
623
624
|
|
|
624
625
|
const currentRevision = revisions?.find(r => r.isCurrent)
|
|
625
626
|
const selectedRevision = revisions?.find(r => r.number === diffRevision)
|
|
@@ -660,160 +661,158 @@ export function RevisionHistoryDialog({ kind, namespace, name, open, onClose, re
|
|
|
660
661
|
}
|
|
661
662
|
|
|
662
663
|
return (
|
|
663
|
-
<
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
<div className={clsx(
|
|
670
|
-
"relative bg-theme-surface border border-theme-border rounded-lg shadow-2xl mx-4 outline-none flex flex-col",
|
|
664
|
+
<DialogPortal
|
|
665
|
+
open={open}
|
|
666
|
+
onClose={handleClose}
|
|
667
|
+
closable={!isRollingBack}
|
|
668
|
+
className={clsx(
|
|
669
|
+
"flex flex-col",
|
|
671
670
|
diffRevision ? "max-w-5xl w-full max-h-[85vh]" : "max-w-lg w-full"
|
|
672
|
-
)}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
<button
|
|
685
|
-
onClick={() => { setDiffRevision(null); onClose() }}
|
|
686
|
-
disabled={isRollingBack}
|
|
687
|
-
className="p-1 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded disabled:opacity-50"
|
|
688
|
-
>
|
|
689
|
-
<X className="w-5 h-5" />
|
|
690
|
-
</button>
|
|
671
|
+
)}
|
|
672
|
+
>
|
|
673
|
+
<div className="flex items-center justify-between p-4 border-b border-theme-border shrink-0">
|
|
674
|
+
<div className="flex items-center gap-2">
|
|
675
|
+
<History className="w-5 h-5 text-amber-500" />
|
|
676
|
+
<h3 className="text-lg font-semibold text-theme-text-primary">Revision History</h3>
|
|
677
|
+
{diffRevision && currentRevision && (
|
|
678
|
+
<span className="flex items-center gap-1 ml-2 px-2 py-0.5 text-xs bg-blue-500/15 text-blue-400 rounded">
|
|
679
|
+
<GitCompare className="w-3 h-3" />
|
|
680
|
+
#{currentRevision.number} vs #{diffRevision}
|
|
681
|
+
</span>
|
|
682
|
+
)}
|
|
691
683
|
</div>
|
|
684
|
+
<button
|
|
685
|
+
onClick={handleClose}
|
|
686
|
+
disabled={isRollingBack}
|
|
687
|
+
className="p-1 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded disabled:opacity-50"
|
|
688
|
+
>
|
|
689
|
+
<X className="w-5 h-5" />
|
|
690
|
+
</button>
|
|
691
|
+
</div>
|
|
692
692
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
693
|
+
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
|
694
|
+
<div className={clsx("p-4 overflow-y-auto", diffRevision ? "max-h-48 shrink-0" : "max-h-80")}>
|
|
695
|
+
{isLoading && (
|
|
696
|
+
<div className="flex items-center justify-center py-8 text-theme-text-secondary text-sm">
|
|
697
|
+
Loading revisions...
|
|
698
|
+
</div>
|
|
699
|
+
)}
|
|
700
|
+
|
|
701
|
+
{error && (
|
|
702
|
+
<div className="flex items-center justify-center py-8 text-red-400 text-sm">
|
|
703
|
+
Failed to load revisions: {error instanceof Error ? error.message : 'Unknown error'}
|
|
704
|
+
</div>
|
|
705
|
+
)}
|
|
706
|
+
|
|
707
|
+
{revisions && revisions.length === 0 && (
|
|
708
|
+
<div className="flex items-center justify-center py-8 text-theme-text-secondary text-sm">
|
|
709
|
+
No revisions found
|
|
710
|
+
</div>
|
|
711
|
+
)}
|
|
712
|
+
|
|
713
|
+
{revisions && revisions.length > 0 && (
|
|
714
|
+
<table className="w-full text-sm">
|
|
715
|
+
<thead>
|
|
716
|
+
<tr className="text-theme-text-secondary text-left text-xs uppercase tracking-wider">
|
|
717
|
+
<th className="pb-2 pr-3 font-medium">Rev</th>
|
|
718
|
+
<th className="pb-2 pr-3 font-medium">Image</th>
|
|
719
|
+
<th className="pb-2 pr-3 font-medium">Age</th>
|
|
720
|
+
<th className="pb-2 font-medium text-right">Action</th>
|
|
721
|
+
</tr>
|
|
722
|
+
</thead>
|
|
723
|
+
<tbody>
|
|
724
|
+
{revisions.map((rev: WorkloadRevision) => (
|
|
725
|
+
<tr
|
|
726
|
+
key={rev.number}
|
|
727
|
+
className={clsx(
|
|
728
|
+
"border-t border-theme-border/50",
|
|
729
|
+
diffRevision === rev.number && "bg-blue-500/10"
|
|
730
|
+
)}
|
|
731
|
+
>
|
|
732
|
+
<td className="py-2 pr-3 text-theme-text-primary font-mono">
|
|
733
|
+
#{rev.number}
|
|
734
|
+
</td>
|
|
735
|
+
<td className="py-2 pr-3 text-theme-text-secondary font-mono truncate max-w-[180px]" title={rev.image}>
|
|
736
|
+
{getImageTag(rev.image)}
|
|
737
|
+
</td>
|
|
738
|
+
<td className="py-2 pr-3 text-theme-text-secondary whitespace-nowrap">
|
|
739
|
+
{formatTimeAgo(rev.createdAt)}
|
|
740
|
+
</td>
|
|
741
|
+
<td className="py-2 text-right">
|
|
742
|
+
<div className="flex items-center gap-1 justify-end">
|
|
743
|
+
{!rev.isCurrent && rev.template && currentRevision?.template && (
|
|
744
|
+
<button
|
|
745
|
+
onClick={() => setDiffRevision(diffRevision === rev.number ? null : rev.number)}
|
|
746
|
+
className={clsx(
|
|
747
|
+
"px-2 py-0.5 text-xs font-medium rounded transition-colors flex items-center gap-1",
|
|
748
|
+
diffRevision === rev.number
|
|
749
|
+
? "bg-blue-500/20 text-blue-400 border border-blue-400/50"
|
|
750
|
+
: "text-blue-400 hover:text-blue-300 hover:bg-blue-500/10 border border-transparent"
|
|
751
|
+
)}
|
|
752
|
+
title={`Compare with current revision`}
|
|
753
|
+
>
|
|
754
|
+
<GitCompare className="w-3 h-3" />
|
|
755
|
+
Diff
|
|
756
|
+
</button>
|
|
757
|
+
)}
|
|
758
|
+
{rev.isCurrent ? (
|
|
759
|
+
<span className="inline-flex items-center px-2 py-0.5 text-xs font-medium bg-green-500/20 text-green-400 rounded">
|
|
760
|
+
Current
|
|
761
|
+
</span>
|
|
762
|
+
) : confirmRevision === rev.number ? (
|
|
763
|
+
<>
|
|
744
764
|
<button
|
|
745
|
-
onClick={() =>
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
diffRevision === rev.number
|
|
749
|
-
? "bg-blue-500/20 text-blue-400 border border-blue-400/50"
|
|
750
|
-
: "text-blue-400 hover:text-blue-300 hover:bg-blue-500/10 border border-transparent"
|
|
751
|
-
)}
|
|
752
|
-
title={`Compare with current revision`}
|
|
765
|
+
onClick={() => handleRollback(rev.number)}
|
|
766
|
+
disabled={isRollingBack}
|
|
767
|
+
className="px-2 py-0.5 text-xs font-medium text-white bg-amber-600 hover:bg-amber-700 rounded transition-colors disabled:opacity-50"
|
|
753
768
|
>
|
|
754
|
-
|
|
755
|
-
Diff
|
|
769
|
+
{isRollingBack ? 'Rolling back...' : 'Confirm'}
|
|
756
770
|
</button>
|
|
757
|
-
)}
|
|
758
|
-
{rev.isCurrent ? (
|
|
759
|
-
<span className="inline-flex items-center px-2 py-0.5 text-xs font-medium bg-green-500/20 text-green-400 rounded">
|
|
760
|
-
Current
|
|
761
|
-
</span>
|
|
762
|
-
) : confirmRevision === rev.number ? (
|
|
763
|
-
<>
|
|
764
|
-
<button
|
|
765
|
-
onClick={() => handleRollback(rev.number)}
|
|
766
|
-
disabled={isRollingBack}
|
|
767
|
-
className="px-2 py-0.5 text-xs font-medium text-white bg-amber-600 hover:bg-amber-700 rounded transition-colors disabled:opacity-50"
|
|
768
|
-
>
|
|
769
|
-
{isRollingBack ? 'Rolling back...' : 'Confirm'}
|
|
770
|
-
</button>
|
|
771
|
-
<button
|
|
772
|
-
onClick={() => setConfirmRevision(null)}
|
|
773
|
-
disabled={isRollingBack}
|
|
774
|
-
className="px-2 py-0.5 text-xs font-medium text-theme-text-secondary hover:text-theme-text-primary rounded transition-colors disabled:opacity-50"
|
|
775
|
-
>
|
|
776
|
-
Cancel
|
|
777
|
-
</button>
|
|
778
|
-
</>
|
|
779
|
-
) : (
|
|
780
771
|
<button
|
|
781
|
-
onClick={() => setConfirmRevision(
|
|
782
|
-
|
|
772
|
+
onClick={() => setConfirmRevision(null)}
|
|
773
|
+
disabled={isRollingBack}
|
|
774
|
+
className="px-2 py-0.5 text-xs font-medium text-theme-text-secondary hover:text-theme-text-primary rounded transition-colors disabled:opacity-50"
|
|
783
775
|
>
|
|
784
|
-
|
|
776
|
+
Cancel
|
|
785
777
|
</button>
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
selectedRevision={diffRevision}
|
|
802
|
-
/>
|
|
778
|
+
</>
|
|
779
|
+
) : (
|
|
780
|
+
<button
|
|
781
|
+
onClick={() => setConfirmRevision(rev.number)}
|
|
782
|
+
className="px-2 py-0.5 text-xs font-medium text-amber-400 hover:text-white hover:bg-amber-600 border border-amber-400/50 hover:border-amber-600 rounded transition-colors"
|
|
783
|
+
>
|
|
784
|
+
Rollback
|
|
785
|
+
</button>
|
|
786
|
+
)}
|
|
787
|
+
</div>
|
|
788
|
+
</td>
|
|
789
|
+
</tr>
|
|
790
|
+
))}
|
|
791
|
+
</tbody>
|
|
792
|
+
</table>
|
|
803
793
|
)}
|
|
804
794
|
</div>
|
|
805
795
|
|
|
806
|
-
|
|
807
|
-
<
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
</div>
|
|
796
|
+
{diffRevision && hasDiffData && (
|
|
797
|
+
<RevisionDiffView
|
|
798
|
+
currentTemplate={currentRevision!.template!}
|
|
799
|
+
selectedTemplate={selectedRevision!.template!}
|
|
800
|
+
currentRevision={currentRevision!.number}
|
|
801
|
+
selectedRevision={diffRevision}
|
|
802
|
+
/>
|
|
803
|
+
)}
|
|
815
804
|
</div>
|
|
816
|
-
|
|
805
|
+
|
|
806
|
+
<div className="flex items-center justify-end p-4 border-t border-theme-border shrink-0">
|
|
807
|
+
<button
|
|
808
|
+
onClick={handleClose}
|
|
809
|
+
disabled={isRollingBack}
|
|
810
|
+
className="px-4 py-2 text-sm font-medium text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded-lg transition-colors disabled:opacity-50"
|
|
811
|
+
>
|
|
812
|
+
Close
|
|
813
|
+
</button>
|
|
814
|
+
</div>
|
|
815
|
+
</DialogPortal>
|
|
817
816
|
)
|
|
818
817
|
}
|
|
819
818
|
|
|
@@ -171,6 +171,33 @@ import {
|
|
|
171
171
|
TraefikIngressRouteRenderer,
|
|
172
172
|
} from '../resources/renderers'
|
|
173
173
|
import type { SelectedResource, Relationships, ResourceRef, SecretCertificateInfo } from '../../types'
|
|
174
|
+
import type { CopyHandler } from '../ui/drawer-components'
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Override map letting each platform consumer swap in its own renderer components.
|
|
178
|
+
* Each override receives only the props that ResourceRendererDispatch passes at its
|
|
179
|
+
* call site — a subset of the base renderer's full props. The override is responsible
|
|
180
|
+
* for wiring any additional behavior (metrics, exec, port-forward, scale, etc.) internally.
|
|
181
|
+
*
|
|
182
|
+
* When an override is not provided, the base (shared) renderer is used.
|
|
183
|
+
*/
|
|
184
|
+
export interface RendererOverrides {
|
|
185
|
+
PodRenderer?: React.ComponentType<{
|
|
186
|
+
data: any; onCopy: CopyHandler; copied: string | null
|
|
187
|
+
onNavigate?: (ref: ResourceRef) => void
|
|
188
|
+
onOpenLogs?: (podName: string, containerName: string) => void
|
|
189
|
+
}>
|
|
190
|
+
NodeRenderer?: React.ComponentType<{
|
|
191
|
+
data: any; relationships?: Relationships
|
|
192
|
+
}>
|
|
193
|
+
ServiceRenderer?: React.ComponentType<{
|
|
194
|
+
data: any; onCopy: CopyHandler; copied: string | null
|
|
195
|
+
}>
|
|
196
|
+
WorkloadRenderer?: React.ComponentType<{
|
|
197
|
+
kind: string; data: any
|
|
198
|
+
onNavigate?: (ref: ResourceRef) => void
|
|
199
|
+
}>
|
|
200
|
+
}
|
|
174
201
|
|
|
175
202
|
// Known resource types with specific renderers (module-level to avoid re-allocation)
|
|
176
203
|
const KNOWN_KINDS = new Set([
|
|
@@ -238,6 +265,8 @@ interface ResourceRendererDispatchProps {
|
|
|
238
265
|
eventsLoading?: boolean
|
|
239
266
|
/** Render prop for Prometheus metrics charts — injected by the platform wrapper */
|
|
240
267
|
renderMetrics?: (props: { kind: string; namespace: string; name: string }) => React.ReactNode
|
|
268
|
+
/** Platform-specific renderer overrides (e.g. with hooks for metrics, exec, port-forward) */
|
|
269
|
+
rendererOverrides?: RendererOverrides
|
|
241
270
|
}
|
|
242
271
|
|
|
243
272
|
export function ResourceRendererDispatch({
|
|
@@ -258,11 +287,18 @@ export function ResourceRendererDispatch({
|
|
|
258
287
|
events,
|
|
259
288
|
eventsLoading,
|
|
260
289
|
renderMetrics,
|
|
290
|
+
rendererOverrides,
|
|
261
291
|
}: ResourceRendererDispatchProps) {
|
|
262
292
|
const kind = resource.kind.toLowerCase()
|
|
263
293
|
|
|
264
294
|
const isKnownKind = KNOWN_KINDS.has(kind)
|
|
265
295
|
|
|
296
|
+
// Resolve renderer components — use platform override when provided, otherwise base
|
|
297
|
+
const PodComp = rendererOverrides?.PodRenderer ?? PodRenderer
|
|
298
|
+
const WorkloadComp = rendererOverrides?.WorkloadRenderer ?? WorkloadRenderer
|
|
299
|
+
const NodeComp = rendererOverrides?.NodeRenderer ?? NodeRenderer
|
|
300
|
+
const ServiceComp = rendererOverrides?.ServiceRenderer ?? ServiceRenderer
|
|
301
|
+
|
|
266
302
|
const sidebarContent = showCommonSections && (
|
|
267
303
|
<>
|
|
268
304
|
<RelatedResourcesSection relationships={relationships} onNavigate={onNavigate} />
|
|
@@ -277,17 +313,17 @@ export function ResourceRendererDispatch({
|
|
|
277
313
|
<div className={renderSidebar ? 'lg:flex' : ''}>
|
|
278
314
|
<div className={clsx('p-4 space-y-4', renderSidebar && 'lg:flex-1 lg:min-w-0')}>
|
|
279
315
|
{/* Kind-specific content - delegates to modular renderers */}
|
|
280
|
-
{kind === 'pods' && <
|
|
281
|
-
{['deployments', 'statefulsets', 'daemonsets'].includes(kind) && <
|
|
316
|
+
{kind === 'pods' && <PodComp data={data} onCopy={onCopy} copied={copied} onNavigate={onNavigate} onOpenLogs={onOpenLogs} />}
|
|
317
|
+
{['deployments', 'statefulsets', 'daemonsets'].includes(kind) && <WorkloadComp kind={kind} data={data} onNavigate={onNavigate} />}
|
|
282
318
|
{kind === 'replicasets' && <ReplicaSetRenderer data={data} />}
|
|
283
|
-
{kind === 'services' && !data?.apiVersion?.includes('serving.knative.dev') && <
|
|
319
|
+
{kind === 'services' && !data?.apiVersion?.includes('serving.knative.dev') && <ServiceComp data={data} onCopy={onCopy} copied={copied} />}
|
|
284
320
|
{kind === 'ingresses' && !data?.apiVersion?.includes('networking.internal.knative.dev') && <IngressRenderer data={data} onNavigate={onNavigate} />}
|
|
285
321
|
{kind === 'configmaps' && <ConfigMapRenderer data={data} />}
|
|
286
322
|
{kind === 'secrets' && <SecretRenderer data={data} certificateInfo={certificateInfo} resourceData={data} onSaveSecretValue={onSaveSecretValue} isSaving={isSavingSecret} />}
|
|
287
323
|
{kind === 'jobs' && <JobRenderer data={data} />}
|
|
288
324
|
{kind === 'cronjobs' && <CronJobRenderer data={data} onNavigate={onNavigate} />}
|
|
289
325
|
{(kind === 'hpas' || kind === 'horizontalpodautoscalers') && <HPARenderer data={data} onNavigate={onNavigate} />}
|
|
290
|
-
{kind === 'nodes' && <
|
|
326
|
+
{kind === 'nodes' && <NodeComp data={data} relationships={relationships} />}
|
|
291
327
|
{kind === 'persistentvolumeclaims' && <PVCRenderer data={data} onNavigate={onNavigate} />}
|
|
292
328
|
{kind === 'rollouts' && <RolloutRenderer data={data} />}
|
|
293
329
|
{kind === 'certificates' && !data?.apiVersion?.includes('networking.internal.knative.dev') && <CertificateRenderer data={data} />}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { ResourceRendererDispatch, getResourceStatus } from './ResourceRendererDispatch'
|
|
1
|
+
export { ResourceRendererDispatch, getResourceStatus, type RendererOverrides } from './ResourceRendererDispatch'
|
|
2
2
|
export { EditableYamlView, SaveSuccessAnimation } from './EditableYamlView'
|
|
3
3
|
export { ResourceActionsBar, RevisionHistoryDialog } from './ResourceActionsBar'
|