@skyhook-io/k8s-ui 1.16.2 → 1.17.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 +3 -3
- package/src/components/applications/ApplicationDetail.test.tsx +15 -0
- package/src/components/applications/ApplicationDetail.tsx +17 -15
- package/src/components/checks/types.ts +2 -13
- package/src/components/cnpg/CNPGBackupSummary.tsx +223 -0
- package/src/components/cnpg/CNPGClusterSummary.tsx +222 -0
- package/src/components/cnpg/CNPGDeclarativeSummary.tsx +264 -0
- package/src/components/cnpg/CNPGImageCatalogSummary.tsx +80 -0
- package/src/components/cnpg/CNPGObjectStoreSummary.tsx +176 -0
- package/src/components/cnpg/CNPGObjectSummary.test.tsx +236 -0
- package/src/components/cnpg/CNPGPoolerSummary.tsx +70 -0
- package/src/components/cnpg/CNPGSharedSummary.tsx +88 -0
- package/src/components/cnpg/index.ts +14 -0
- package/src/components/cnpg/primitives.tsx +134 -0
- package/src/components/cnpg/relations.test.ts +304 -0
- package/src/components/cnpg/relations.ts +417 -0
- package/src/components/cnpg/workspace.test.ts +285 -0
- package/src/components/cnpg/workspace.ts +700 -0
- package/src/components/gitops/GitOpsTableView.tsx +9 -24
- package/src/components/gitops/detail-helpers.ts +1 -1
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +5 -12
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +2 -5
- package/src/components/issues/IssuesView.tsx +10 -1
- package/src/components/issues/issue-timing.ts +2 -2
- package/src/components/issues/issues.test.ts +1 -1
- package/src/components/issues/types.ts +24 -0
- package/src/components/logs/WorkloadLogsViewer.notice.test.tsx +59 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +17 -3
- package/src/components/logs/useLogBuffer.test.ts +17 -0
- package/src/components/logs/useLogStream.ts +11 -0
- package/src/components/resources/ResourcesSidebar.test.tsx +76 -0
- package/src/components/resources/ResourcesSidebar.tsx +183 -11
- package/src/components/resources/ResourcesView.tsx +12 -9
- package/src/components/resources/index.ts +1 -1
- package/src/components/resources/renderers/AnalysisRunRenderer.tsx +3 -3
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +6 -5
- package/src/components/resources/renderers/CAPIClusterRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +2 -2
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +2 -1
- package/src/components/resources/renderers/CNPGObjectStoreRenderer.test.tsx +25 -0
- package/src/components/resources/renderers/CNPGObjectStoreRenderer.tsx +16 -7
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +2 -2
- package/src/components/resources/renderers/CNPGShared.tsx +2 -2
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/CronJobRenderer.tsx +6 -4
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +3 -2
- package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +24 -0
- package/src/components/resources/renderers/EndpointSliceRenderer.tsx +1 -1
- package/src/components/resources/renderers/EventRenderer.tsx +3 -3
- package/src/components/resources/renderers/ExperimentRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +2 -2
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +4 -4
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +11 -24
- package/src/components/resources/renderers/GatewayRouteReferences.test.tsx +63 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/HPARenderer.tsx +2 -2
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +12 -25
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/IngressClassReferences.test.tsx +52 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +10 -3
- package/src/components/resources/renderers/IngressRenderer.tsx +6 -2
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +2 -14
- package/src/components/resources/renderers/KedaCredentials.test.tsx +85 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +2 -2
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +6 -3
- package/src/components/resources/renderers/KustomizationRenderer.tsx +2 -2
- package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +3 -2
- package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +4 -3
- package/src/components/resources/renderers/LeaseRenderer.tsx +3 -15
- package/src/components/resources/renderers/NamespaceRenderer.test.ts +69 -2
- package/src/components/resources/renderers/NamespaceRenderer.tsx +6 -1
- package/src/components/resources/renderers/NodeRenderer.test.tsx +81 -1
- package/src/components/resources/renderers/NodeRenderer.tsx +56 -15
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/PodClassReferences.test.tsx +42 -0
- package/src/components/resources/renderers/PodNomination.test.tsx +57 -0
- package/src/components/resources/renderers/PodRenderer.tsx +21 -7
- package/src/components/resources/renderers/RolloutRenderer.test.tsx +1 -1
- package/src/components/resources/renderers/RolloutRenderer.tsx +3 -2
- package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/SecretRenderer.test.tsx +88 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +34 -5
- package/src/components/resources/renderers/ServiceRenderer.test.tsx +43 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +11 -3
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +8 -21
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +5 -7
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +3 -3
- package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +2 -2
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +3 -3
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +2 -2
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/WorkflowRenderer.tsx +4 -3
- package/src/components/resources/renderers/kyverno-modern-cells.tsx +1 -1
- package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +2 -2
- package/src/components/resources/resource-utils-cnpg-objectstore.test.ts +1 -1
- package/src/components/resources/resource-utils-cnpg.ts +3 -7
- package/src/components/resources/resource-utils-keda.ts +1 -7
- package/src/components/resources/resource-utils-kyverno.ts +1 -7
- package/src/components/resources/resource-utils-velero.ts +0 -12
- package/src/components/resources/resource-utils.ts +17 -33
- package/src/components/resources/status-overclaim.test.ts +1 -1
- package/src/components/shared/DrainPlanDialog.tsx +1 -0
- package/src/components/shared/GitOpsWriteWarning.test.tsx +86 -0
- package/src/components/shared/GitOpsWriteWarning.tsx +135 -0
- package/src/components/shared/ResourceActionsBar.tsx +9 -20
- package/src/components/shared/ResourceRendererDispatch.tsx +1 -1
- package/src/components/shared/SetImageDialog.test.ts +75 -6
- package/src/components/shared/SetImageDialog.tsx +92 -84
- package/src/components/shared/index.ts +2 -1
- package/src/components/timeline/TimelineList.tsx +187 -38
- package/src/components/timeline/TimelineListFold.test.tsx +56 -0
- package/src/components/timeline/TimelineSwimlanes.tsx +2 -10
- package/src/components/timeline/TimelineToolbarState.test.tsx +98 -0
- package/src/components/timeline/index.ts +1 -0
- package/src/components/timeline/timeline-filters.ts +7 -0
- package/src/components/topology/TopologyGraph.tsx +5 -3
- package/src/components/ui/ScheduleValue.test.tsx +5 -0
- package/src/components/ui/ScheduleValue.tsx +3 -2
- package/src/components/ui/drawer-components.test.tsx +66 -1
- package/src/components/ui/drawer-components.tsx +43 -29
- package/src/components/workload/WorkloadView.deleted.test.tsx +58 -0
- package/src/components/workload/WorkloadView.deletedDialog.test.tsx +45 -0
- package/src/components/workload/WorkloadView.secretSave.test.tsx +62 -0
- package/src/components/workload/WorkloadView.tsx +216 -48
- package/src/components/workload/index.ts +1 -0
- package/src/components/workload/workload-logs-availability.test.ts +6 -0
- package/src/index.ts +3 -0
- package/src/types/capacity.ts +10 -0
- package/src/types/core.ts +32 -1
- package/src/utils/format.test.ts +40 -2
- package/src/utils/format.ts +5 -1
- package/src/utils/gateway-references.test.ts +17 -0
- package/src/utils/gateway-references.ts +29 -0
- package/src/utils/gitops-write-guard.test.ts +401 -0
- package/src/utils/gitops-write-guard.ts +509 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/ingress-class-references.ts +22 -0
- package/src/utils/log-level.ts +14 -0
- package/src/utils/navigation.test.ts +13 -0
- package/src/utils/navigation.ts +5 -0
- package/src/utils/node-lifecycle.test.ts +36 -0
- package/src/utils/node-lifecycle.ts +77 -0
- package/src/utils/resource-hierarchy.test.ts +32 -0
- package/src/utils/resource-hierarchy.ts +28 -2
- package/src/utils/resource-refs.test.ts +23 -0
- package/src/utils/resource-refs.ts +30 -0
|
@@ -8,6 +8,7 @@ import { ReflectorSummary, ReflectorSection, isReflectorMirror, reflectorEditNot
|
|
|
8
8
|
import type { SecretCertificateInfo, CertificateInfo, Relationships, ResourceRef } from '../../../types'
|
|
9
9
|
import { pluralize } from '../../../utils/pluralize'
|
|
10
10
|
import { cleanResourceForYaml } from '../../../utils/yaml'
|
|
11
|
+
import { isFetchError } from '../../../types/fetch-error'
|
|
11
12
|
|
|
12
13
|
interface SecretRendererProps {
|
|
13
14
|
data: any
|
|
@@ -31,6 +32,7 @@ export function SecretRenderer({ data, relationships, onNavigate, certificateInf
|
|
|
31
32
|
const [editingKey, setEditingKey] = useState<string | null>(null)
|
|
32
33
|
const [editValue, setEditValue] = useState('')
|
|
33
34
|
const [showSaveConfirm, setShowSaveConfirm] = useState(false)
|
|
35
|
+
const [saveError, setSaveError] = useState<{ reason?: string } | null>(null)
|
|
34
36
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
|
35
37
|
const dataKeys = Object.keys(data.data || {})
|
|
36
38
|
const isImmutable = data.immutable === true
|
|
@@ -68,12 +70,14 @@ export function SecretRenderer({ data, relationships, onNavigate, certificateInf
|
|
|
68
70
|
function startEdit(key: string, decoded: string) {
|
|
69
71
|
setEditingKey(key)
|
|
70
72
|
setEditValue(decoded)
|
|
73
|
+
setSaveError(null)
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
function cancelEdit() {
|
|
74
77
|
setEditingKey(null)
|
|
75
78
|
setEditValue('')
|
|
76
79
|
setShowSaveConfirm(false)
|
|
80
|
+
setSaveError(null)
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
const handleSave = useCallback(async (key: string, newValue: string) => {
|
|
@@ -83,14 +87,16 @@ export function SecretRenderer({ data, relationships, onNavigate, certificateInf
|
|
|
83
87
|
// btoa is byte-only; round-trip through encodeURIComponent/unescape so non-ASCII secret values survive base64 encoding.
|
|
84
88
|
cleaned.data[key] = btoa(unescape(encodeURIComponent(newValue)))
|
|
85
89
|
const yaml = yamlStringify(cleaned, { lineWidth: 0, indent: 2 })
|
|
90
|
+
setSaveError(null)
|
|
86
91
|
try {
|
|
87
92
|
await onSaveSecretValue(yaml)
|
|
88
93
|
setEditingKey(null)
|
|
89
94
|
setEditValue('')
|
|
90
95
|
setShowSaveConfirm(false)
|
|
91
|
-
} catch {
|
|
92
|
-
//
|
|
96
|
+
} catch (error) {
|
|
97
|
+
// The edit stays open with the value as typed, so it can be saved again.
|
|
93
98
|
setShowSaveConfirm(false)
|
|
99
|
+
setSaveError(secretSaveErrorMessage(error))
|
|
94
100
|
}
|
|
95
101
|
}, [onSaveSecretValue, resourceData])
|
|
96
102
|
|
|
@@ -171,8 +177,10 @@ export function SecretRenderer({ data, relationships, onNavigate, certificateInf
|
|
|
171
177
|
{dataKeys.map((key) => {
|
|
172
178
|
const decoded = decodeBase64(data.data[key])
|
|
173
179
|
const isBinary = decoded === '[binary data]'
|
|
174
|
-
const isEditing = editingKey === key
|
|
175
180
|
const canEdit = onSaveSecretValue && !isImmutable && !isBinary
|
|
181
|
+
// Without a save handler (the Secret was deleted, say) an open
|
|
182
|
+
// editor would have a Save button that does nothing.
|
|
183
|
+
const isEditing = editingKey === key && Boolean(canEdit)
|
|
176
184
|
|
|
177
185
|
return (
|
|
178
186
|
<div key={key} className="card-inner">
|
|
@@ -221,13 +229,21 @@ export function SecretRenderer({ data, relationships, onNavigate, certificateInf
|
|
|
221
229
|
<textarea
|
|
222
230
|
ref={textareaRef}
|
|
223
231
|
value={editValue}
|
|
224
|
-
onChange={(e) =>
|
|
232
|
+
onChange={(e) => {
|
|
233
|
+
setEditValue(e.target.value)
|
|
234
|
+
setSaveError(null)
|
|
235
|
+
}}
|
|
225
236
|
autoCorrect="off"
|
|
226
237
|
autoCapitalize="off"
|
|
227
238
|
className="w-full bg-theme-base rounded p-2 text-xs text-theme-text-secondary font-mono border border-blue-500/50 focus:border-blue-500 focus:outline-none resize-none overflow-hidden whitespace-pre-wrap"
|
|
228
239
|
style={{ minHeight: '60px' }}
|
|
229
240
|
disabled={isSaving}
|
|
230
241
|
/>
|
|
242
|
+
{saveError && (
|
|
243
|
+
<div role="alert" className="mt-2">
|
|
244
|
+
<AlertBanner variant="error" title="Not saved" message={saveError.reason} />
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
231
247
|
<div className="flex items-center gap-2 mt-2">
|
|
232
248
|
<button
|
|
233
249
|
onClick={() => setShowSaveConfirm(true)}
|
|
@@ -267,7 +283,7 @@ export function SecretRenderer({ data, relationships, onNavigate, certificateInf
|
|
|
267
283
|
|
|
268
284
|
<ReflectorSection data={data} reflection={relationships?.reflection} onNavigate={onNavigate} />
|
|
269
285
|
|
|
270
|
-
{editingKey && (
|
|
286
|
+
{editingKey && onSaveSecretValue && (
|
|
271
287
|
<ConfirmDialog
|
|
272
288
|
open={showSaveConfirm}
|
|
273
289
|
onClose={() => setShowSaveConfirm(false)}
|
|
@@ -284,6 +300,19 @@ export function SecretRenderer({ data, relationships, onNavigate, certificateInf
|
|
|
284
300
|
)
|
|
285
301
|
}
|
|
286
302
|
|
|
303
|
+
function secretSaveErrorMessage(error: unknown): { reason?: string } {
|
|
304
|
+
if (isFetchError(error) && error.status === 404) {
|
|
305
|
+
return { reason: 'This Secret no longer exists in the cluster.' }
|
|
306
|
+
}
|
|
307
|
+
// The view reloads the Secret after a failed save, but that read can trail
|
|
308
|
+
// the cluster by a moment, so the message can't promise it already shows
|
|
309
|
+
// the change.
|
|
310
|
+
if (isFetchError(error) && error.status === 409) {
|
|
311
|
+
return { reason: 'This Secret changed after it loaded. Once the view reloads it, save again to apply your value to the latest version.' }
|
|
312
|
+
}
|
|
313
|
+
return { reason: error instanceof Error && error.message ? error.message : undefined }
|
|
314
|
+
}
|
|
315
|
+
|
|
287
316
|
function CertificateInfoSection({ cert, index, total }: { cert: CertificateInfo; index: number; total: number }) {
|
|
288
317
|
const expiryTextColor = cert.expired || cert.daysLeft <= 7
|
|
289
318
|
? 'text-red-400'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { expect, it } from 'vitest'
|
|
3
|
+
import { ServiceRenderer } from './ServiceRenderer'
|
|
4
|
+
|
|
5
|
+
const data = { metadata: { name: 'web', namespace: 'team' }, spec: { selector: { app: 'web' } } }
|
|
6
|
+
const props = { data, onCopy: () => {}, copied: null }
|
|
7
|
+
const slice = (name: string, ready = true) => ({
|
|
8
|
+
metadata: { name },
|
|
9
|
+
addressType: 'IPv4',
|
|
10
|
+
endpoints: [{ addresses: ['10.0.0.1'], conditions: { ready } }],
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('renders supplied slices for a selector Service when the host enables the inventory', () => {
|
|
14
|
+
const html = renderToStaticMarkup(<ServiceRenderer {...props} endpointSlicesEnabled endpointSlices={[slice('published', false)]} />)
|
|
15
|
+
expect(html).toContain('EndpointSlices')
|
|
16
|
+
expect(html).toContain('published')
|
|
17
|
+
expect(html).toContain('0/1')
|
|
18
|
+
expect(html).not.toContain('Showing the first')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('keeps the section hidden for selector Services unless the host enables it', () => {
|
|
22
|
+
expect(renderToStaticMarkup(<ServiceRenderer {...props} endpointSlices={[slice('published')]} />)).not.toContain('EndpointSlices')
|
|
23
|
+
const selectorless = { ...data, spec: {} }
|
|
24
|
+
expect(renderToStaticMarkup(<ServiceRenderer {...props} data={selectorless} endpointSlices={[slice('manual')]} />)).toContain('manual')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('separates unavailable, loading and ready-empty inventory', () => {
|
|
28
|
+
const error = renderToStaticMarkup(<ServiceRenderer {...props} endpointSlicesEnabled endpointSlices={[]} endpointSlicesError="Read denied" />)
|
|
29
|
+
expect(error).toContain('EndpointSlice inventory unavailable')
|
|
30
|
+
expect(error).toContain('Read denied')
|
|
31
|
+
expect(error).not.toContain('No EndpointSlices found')
|
|
32
|
+
expect(renderToStaticMarkup(<ServiceRenderer {...props} endpointSlicesEnabled endpointSlicesLoading />)).toContain('Loading EndpointSlices')
|
|
33
|
+
expect(renderToStaticMarkup(<ServiceRenderer {...props} endpointSlicesEnabled endpointSlices={[]} />)).toContain('No EndpointSlices found')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('says when the inventory was cut off', () => {
|
|
37
|
+
const html = renderToStaticMarkup(<ServiceRenderer {...props} endpointSlicesEnabled endpointSlicesTruncated endpointSlices={[slice('a'), slice('b')]} />)
|
|
38
|
+
expect(html).toContain('Showing the first 2 EndpointSlices.')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('does not show slice inventory for ExternalName even when supplied', () => {
|
|
42
|
+
expect(renderToStaticMarkup(<ServiceRenderer {...props} data={{ ...data, spec: { type: 'ExternalName', externalName: 'example.org' } }} endpointSlicesEnabled endpointSlices={[]} />)).not.toContain('EndpointSlices')
|
|
43
|
+
})
|
|
@@ -18,6 +18,9 @@ interface ServiceRendererProps {
|
|
|
18
18
|
copied: string | null
|
|
19
19
|
endpointSlices?: any[]
|
|
20
20
|
endpointSlicesLoading?: boolean
|
|
21
|
+
endpointSlicesEnabled?: boolean
|
|
22
|
+
endpointSlicesError?: string
|
|
23
|
+
endpointSlicesTruncated?: boolean
|
|
21
24
|
onNavigate?: (ref: ResourceRef) => void
|
|
22
25
|
renderPortAction?: (props: ServicePortRenderProps) => ReactNode
|
|
23
26
|
renderPortPanel?: (props: ServicePortRenderProps) => ReactNode
|
|
@@ -38,7 +41,7 @@ function endpointSliceReadyClass(ready: number, total: number): string {
|
|
|
38
41
|
return 'status-unhealthy'
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
export function ServiceRenderer({ data, onCopy, copied, endpointSlices, endpointSlicesLoading, onNavigate, renderPortAction, renderPortPanel }: ServiceRendererProps) {
|
|
44
|
+
export function ServiceRenderer({ data, onCopy, copied, endpointSlices, endpointSlicesLoading, endpointSlicesEnabled, endpointSlicesError, endpointSlicesTruncated, onNavigate, renderPortAction, renderPortPanel }: ServiceRendererProps) {
|
|
42
45
|
const spec = data.spec || {}
|
|
43
46
|
const ports = spec.ports || []
|
|
44
47
|
const lbIngress = data.status?.loadBalancer?.ingress || []
|
|
@@ -111,10 +114,12 @@ export function ServiceRenderer({ data, onCopy, copied, endpointSlices, endpoint
|
|
|
111
114
|
</Section>
|
|
112
115
|
)}
|
|
113
116
|
|
|
114
|
-
{
|
|
115
|
-
<Section title="EndpointSlices" icon={Radio}>
|
|
117
|
+
{!isExternalName && (hasNoSelector || endpointSlicesEnabled) && (
|
|
118
|
+
<Section title="EndpointSlices" icon={Radio} defaultExpanded>
|
|
116
119
|
{endpointSlicesLoading ? (
|
|
117
120
|
<div className="text-sm text-theme-text-tertiary">Loading EndpointSlices…</div>
|
|
121
|
+
) : endpointSlicesError ? (
|
|
122
|
+
<AlertBanner variant="warning" title="EndpointSlice inventory unavailable" message={endpointSlicesError} />
|
|
118
123
|
) : endpointSlices && endpointSlices.length > 0 ? (
|
|
119
124
|
<div className="space-y-2">
|
|
120
125
|
{endpointSlices.map((slice: any) => {
|
|
@@ -147,6 +152,9 @@ export function ServiceRenderer({ data, onCopy, copied, endpointSlices, endpoint
|
|
|
147
152
|
</button>
|
|
148
153
|
)
|
|
149
154
|
})}
|
|
155
|
+
{endpointSlicesTruncated && (
|
|
156
|
+
<div className="text-xs text-theme-text-tertiary">Showing the first {endpointSlices.length} EndpointSlices.</div>
|
|
157
|
+
)}
|
|
150
158
|
</div>
|
|
151
159
|
) : (
|
|
152
160
|
<div className="text-sm text-theme-text-tertiary">No EndpointSlices found for this Service.</div>
|
|
@@ -2,6 +2,7 @@ import { Globe, ArrowRight, Network } from 'lucide-react'
|
|
|
2
2
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceRefBadge, useOperationalIssuesShown } from '../../ui/drawer-components'
|
|
3
3
|
import { Badge } from '../../ui/Badge'
|
|
4
4
|
import type { ResourceRef } from '../../../types'
|
|
5
|
+
import { gatewayBackendResourceRef, gatewayParentResourceRef } from '../../../utils/gateway-references'
|
|
5
6
|
|
|
6
7
|
interface SimpleRouteRendererProps {
|
|
7
8
|
data: any
|
|
@@ -35,22 +36,8 @@ export function SimpleRouteRenderer({ data, kind, onNavigate }: SimpleRouteRende
|
|
|
35
36
|
? parentStatuses[0].conditions
|
|
36
37
|
: undefined
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
kind: 'Gateway',
|
|
41
|
-
namespace: ref.namespace || routeNs,
|
|
42
|
-
name: ref.name,
|
|
43
|
-
group: 'gateway.networking.k8s.io',
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function toServiceRef(backend: any): ResourceRef {
|
|
48
|
-
return {
|
|
49
|
-
kind: 'Service',
|
|
50
|
-
namespace: backend.namespace || routeNs,
|
|
51
|
-
name: backend.name,
|
|
52
|
-
}
|
|
53
|
-
}
|
|
39
|
+
const toParentRef = (ref: any) => gatewayParentResourceRef(ref, routeNs)
|
|
40
|
+
const toBackendRef = (ref: any) => gatewayBackendResourceRef(ref, routeNs)
|
|
54
41
|
|
|
55
42
|
return (
|
|
56
43
|
<>
|
|
@@ -62,8 +49,8 @@ export function SimpleRouteRenderer({ data, kind, onNavigate }: SimpleRouteRende
|
|
|
62
49
|
const cond = (p.conditions || []).find((c: any) => c.type === 'Accepted' && c.status === 'False')
|
|
63
50
|
const gwName = p.parentRef?.name || 'unknown'
|
|
64
51
|
return cond?.reason
|
|
65
|
-
? `
|
|
66
|
-
: `
|
|
52
|
+
? `Parent "${gwName}": ${cond.reason}${cond.message ? ' — ' + cond.message : ''}`
|
|
53
|
+
: `Parent "${gwName}" has not accepted this route.`
|
|
67
54
|
}).join('; ')}
|
|
68
55
|
/>
|
|
69
56
|
)}
|
|
@@ -95,12 +82,12 @@ export function SimpleRouteRenderer({ data, kind, onNavigate }: SimpleRouteRende
|
|
|
95
82
|
/>
|
|
96
83
|
)}
|
|
97
84
|
<Property
|
|
98
|
-
label="
|
|
85
|
+
label="Parents"
|
|
99
86
|
value={
|
|
100
87
|
parentRefs.length > 0 ? (
|
|
101
88
|
<div className="flex flex-wrap gap-1">
|
|
102
89
|
{parentRefs.map((ref: any, i: number) => (
|
|
103
|
-
<ResourceRefBadge key={`${ref.namespace || ''}-${ref.name}-${i}`} resourceRef={
|
|
90
|
+
<ResourceRefBadge key={`${ref.namespace || ''}-${ref.name}-${i}`} resourceRef={toParentRef(ref)} onClick={onNavigate} />
|
|
104
91
|
))}
|
|
105
92
|
</div>
|
|
106
93
|
) : 'None'
|
|
@@ -131,7 +118,7 @@ export function SimpleRouteRenderer({ data, kind, onNavigate }: SimpleRouteRende
|
|
|
131
118
|
const pct = hasWeights ? Math.round(((b.weight ?? 1) / totalWeight) * 100) : null
|
|
132
119
|
return (
|
|
133
120
|
<span key={bi} className="flex items-center gap-1">
|
|
134
|
-
<ResourceRefBadge resourceRef={
|
|
121
|
+
<ResourceRefBadge resourceRef={toBackendRef(b)} onClick={onNavigate} />
|
|
135
122
|
{b.port && <span className="text-theme-text-tertiary">:{b.port}</span>}
|
|
136
123
|
{pct !== null && (
|
|
137
124
|
<span className="text-theme-text-tertiary text-[10px]">{pct}%</span>
|
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
getBSLRegion,
|
|
10
10
|
getBSLDefault,
|
|
11
11
|
getBSLAccessMode,
|
|
12
|
-
getBSLLastValidation,
|
|
13
|
-
getBSLLastSynced,
|
|
14
12
|
getBSLMessage,
|
|
15
13
|
} from '../resource-utils-velero'
|
|
16
14
|
import { VeleroPhaseValue } from './velero-cells'
|
|
@@ -111,8 +109,8 @@ export function VeleroBSLRenderer({ data, storedBackups, storedTotal, restorable
|
|
|
111
109
|
<Property label="Phase" value={
|
|
112
110
|
<VeleroPhaseValue status={bslStatus} phase={status.phase || ''} />
|
|
113
111
|
} />
|
|
114
|
-
<Property label="Last Validation" value={
|
|
115
|
-
<Property label="Last Synced" value={
|
|
112
|
+
<Property label="Last Validation" value={<TimeValue timestamp={data.status?.lastValidationTime} />} />
|
|
113
|
+
<Property label="Last Synced" value={<TimeValue timestamp={data.status?.lastSyncedTime} />} />
|
|
116
114
|
{status.lastSyncedRevision && (
|
|
117
115
|
<Property label="Last Synced Revision" value={
|
|
118
116
|
<span className="text-sm font-mono text-theme-text-secondary break-all">{status.lastSyncedRevision}</span>
|
|
@@ -235,18 +233,18 @@ export function VeleroBSLRenderer({ data, storedBackups, storedTotal, restorable
|
|
|
235
233
|
recovers. */}
|
|
236
234
|
{bslStatus.level === 'healthy' ? (
|
|
237
235
|
<>
|
|
238
|
-
Most recent restorable point: <TimeValue timestamp={newest} />
|
|
236
|
+
Most recent restorable point: <TimeValue timestamp={newest} />
|
|
239
237
|
</>
|
|
240
238
|
) : bslStatus.level === 'unhealthy' ? (
|
|
241
239
|
<>
|
|
242
240
|
Most recent point held here, once this location is reachable again:{' '}
|
|
243
|
-
<TimeValue timestamp={newest} />
|
|
241
|
+
<TimeValue timestamp={newest} />
|
|
244
242
|
</>
|
|
245
243
|
) : (
|
|
246
244
|
// Unknown: do not imply it is unreachable, and do not call it
|
|
247
245
|
// restorable either. Say what the timestamp is.
|
|
248
246
|
<>
|
|
249
|
-
Most recent point held here: <TimeValue timestamp={newest} />
|
|
247
|
+
Most recent point held here: <TimeValue timestamp={newest} />
|
|
250
248
|
</>
|
|
251
249
|
)}
|
|
252
250
|
</div>
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from '../resource-utils-velero'
|
|
27
27
|
import { VeleroPhaseValue } from './velero-cells'
|
|
28
28
|
import { veleroPhaseLabel } from '../resource-utils-velero'
|
|
29
|
-
import {
|
|
29
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
30
30
|
|
|
31
31
|
interface VeleroBackupRendererProps {
|
|
32
32
|
data: any
|
|
@@ -140,10 +140,10 @@ export function VeleroBackupRenderer({ data, storageLocationPhase, storageLocati
|
|
|
140
140
|
<Property label="Queue Position" value={String(queuePosition)} />
|
|
141
141
|
)}
|
|
142
142
|
{status.startTimestamp && (
|
|
143
|
-
<Property label="Started" value={
|
|
143
|
+
<Property label="Started" value={<TimeValue timestamp={status.startTimestamp} />} />
|
|
144
144
|
)}
|
|
145
145
|
{status.completionTimestamp && (
|
|
146
|
-
<Property label="Completed" value={
|
|
146
|
+
<Property label="Completed" value={<TimeValue timestamp={status.completionTimestamp} />} />
|
|
147
147
|
)}
|
|
148
148
|
<Property label="Duration" value={getBackupDuration(data)} />
|
|
149
149
|
<Property label="Expiration" value={getBackupExpiry(data)} />
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
getBackupRepositoryVolumeNamespace,
|
|
7
7
|
getBackupRepositoryLocation,
|
|
8
8
|
getBackupRepositoryMaintenanceFrequency,
|
|
9
|
-
getBackupRepositoryLastMaintenance,
|
|
10
9
|
getBackupRepositoryMessage,
|
|
11
10
|
} from '../resource-utils-velero'
|
|
12
11
|
import { VeleroPhaseValue } from './velero-cells'
|
|
12
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
13
13
|
|
|
14
14
|
interface VeleroBackupRepositoryRendererProps {
|
|
15
15
|
data: any
|
|
@@ -52,7 +52,7 @@ export function VeleroBackupRepositoryRenderer({ data, onNavigate }: VeleroBacku
|
|
|
52
52
|
last run is far older than its frequency is the shape of a
|
|
53
53
|
controller that has stopped. The two are shown together so that
|
|
54
54
|
comparison does not need a second screen. */}
|
|
55
|
-
<Property label="Last Maintenance" value={
|
|
55
|
+
<Property label="Last Maintenance" value={<TimeValue timestamp={data.status?.lastMaintenanceTime} />} />
|
|
56
56
|
<Property label="Maintenance Frequency" value={getBackupRepositoryMaintenanceFrequency(data)} />
|
|
57
57
|
</PropertyList>
|
|
58
58
|
</Section>
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
} from '../resource-utils-velero'
|
|
23
23
|
import { VeleroPhaseValue } from './velero-cells'
|
|
24
24
|
import { veleroPhaseLabel } from '../resource-utils-velero'
|
|
25
|
-
import { formatAge } from '../resource-utils'
|
|
26
25
|
import { VeleroRunMessages, type VeleroRunMessagesFetch } from './VeleroRunMessages'
|
|
26
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
27
27
|
|
|
28
28
|
interface VeleroRestoreRendererProps {
|
|
29
29
|
data: any
|
|
@@ -125,10 +125,10 @@ export function VeleroRestoreRenderer({ data, messages, onNavigate }: VeleroRest
|
|
|
125
125
|
/>
|
|
126
126
|
} />
|
|
127
127
|
{status.startTimestamp && (
|
|
128
|
-
<Property label="Started" value={
|
|
128
|
+
<Property label="Started" value={<TimeValue timestamp={status.startTimestamp} />} />
|
|
129
129
|
)}
|
|
130
130
|
{status.completionTimestamp && (
|
|
131
|
-
<Property label="Completed" value={
|
|
131
|
+
<Property label="Completed" value={<TimeValue timestamp={status.completionTimestamp} />} />
|
|
132
132
|
)}
|
|
133
133
|
<Property label="Duration" value={getRestoreDuration(data)} />
|
|
134
134
|
<Property label="Errors" value={
|
|
@@ -3,13 +3,13 @@ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, Resour
|
|
|
3
3
|
import {
|
|
4
4
|
getScheduleStatus,
|
|
5
5
|
getScheduleCron,
|
|
6
|
-
getScheduleLastBackup,
|
|
7
6
|
getSchedulePaused,
|
|
8
7
|
getScheduleTemplate,
|
|
9
8
|
getScheduleUseOwnerReferences,
|
|
10
9
|
getScheduleValidationErrors,
|
|
11
10
|
} from '../resource-utils-velero'
|
|
12
11
|
import { VeleroPhaseValue } from './velero-cells'
|
|
12
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
13
13
|
|
|
14
14
|
interface VeleroScheduleRendererProps {
|
|
15
15
|
data: any
|
|
@@ -64,7 +64,7 @@ export function VeleroScheduleRenderer({ data, onNavigate }: VeleroScheduleRende
|
|
|
64
64
|
<Property label="Cron Schedule" value={
|
|
65
65
|
<span className="font-mono text-sm">{getScheduleCron(data)}</span>
|
|
66
66
|
} />
|
|
67
|
-
<Property label="Last Backup" value={
|
|
67
|
+
<Property label="Last Backup" value={<TimeValue timestamp={data.status?.lastBackup} fallback="Never" />} />
|
|
68
68
|
<Property label="Paused" value={isPaused ? 'Yes' : 'No'} />
|
|
69
69
|
<Property label="Use Owner References" value={getScheduleUseOwnerReferences(data) ? 'Yes' : 'No'} />
|
|
70
70
|
</PropertyList>
|
|
@@ -4,8 +4,8 @@ import { clsx } from 'clsx'
|
|
|
4
4
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
5
5
|
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
6
6
|
import { Input } from '../../ui/Input'
|
|
7
|
-
import { formatAge } from '../resource-utils'
|
|
8
7
|
import { SEVERITY_BADGE_COLORS, SEVERITY_BAR_COLORS, SEVERITY_TEXT_COLORS, SEVERITY_ORDER, TrivyAlertBanner, formatTrivyImage } from './trivy-shared'
|
|
8
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
9
9
|
|
|
10
10
|
interface VulnerabilityReportRendererProps {
|
|
11
11
|
data: any
|
|
@@ -161,7 +161,7 @@ export function VulnerabilityReportRenderer({ data }: VulnerabilityReportRendere
|
|
|
161
161
|
} />
|
|
162
162
|
)}
|
|
163
163
|
<Property label="Scanner" value={scanner.name ? `${scanner.name} ${scanner.version || ''}`.trim() : '-'} />
|
|
164
|
-
<Property label="Scanned" value={
|
|
164
|
+
<Property label="Scanned" value={<TimeValue timestamp={report.updateTimestamp} />} />
|
|
165
165
|
</PropertyList>
|
|
166
166
|
</Section>
|
|
167
167
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Play, Clock, CheckCircle, XCircle, Loader2, SkipForward, PauseCircle } from 'lucide-react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
3
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
4
|
-
import {
|
|
4
|
+
import { formatDuration } from '../resource-utils'
|
|
5
5
|
import { buildWorkflowExecutionModel, flattenWorkflowExecution, WorkflowExecutionModel, WorkflowExecutionNode, WorkflowTemplateReference } from '../../../utils/workflow-execution'
|
|
6
6
|
import { SEVERITY_TEXT } from '../../../utils/badge-colors'
|
|
7
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
7
8
|
|
|
8
9
|
interface WorkflowRendererProps {
|
|
9
10
|
data: any
|
|
@@ -145,8 +146,8 @@ export function WorkflowRenderer({ data, onNavigate }: WorkflowRendererProps) {
|
|
|
145
146
|
</span>
|
|
146
147
|
} />
|
|
147
148
|
{duration && <Property label="Duration" value={duration} />}
|
|
148
|
-
{status.startedAt && <Property label="Started" value={
|
|
149
|
-
<Property label="Finished" value={
|
|
149
|
+
{status.startedAt && <Property label="Started" value={<TimeValue timestamp={status.startedAt} />} />}
|
|
150
|
+
<Property label="Finished" value={<TimeValue timestamp={status.finishedAt} fallback="Running..." />} />
|
|
150
151
|
{workflowTemplateRef && <Property label="Definition" value={<TemplateRefLink refInfo={workflowTemplateRef} onNavigate={onNavigate} />} />}
|
|
151
152
|
{status.progress && (
|
|
152
153
|
<Property label="Progress" value={status.progress} />
|
|
@@ -71,7 +71,7 @@ export function KyvernoModernPolicyCell({ resource, column }: { resource: any; c
|
|
|
71
71
|
</span>
|
|
72
72
|
)
|
|
73
73
|
}
|
|
74
|
-
return <span className="text-sm text-theme-text-secondary">{formatAge(last)}
|
|
74
|
+
return <span className="text-sm text-theme-text-secondary">{formatAge(last)}</span>
|
|
75
75
|
}
|
|
76
76
|
case 'schedule': {
|
|
77
77
|
const schedule = getKyvernoSchedule(resource)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useState, useId } from 'react'
|
|
2
2
|
import { Box } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
|
-
import { formatAge } from '../../resource-utils'
|
|
5
4
|
import { Collapse, CollapseChevron, disclosurePanelId } from '../../../ui/Collapse'
|
|
6
5
|
import { revisionRoleBadges } from '../../../shared/ResourceActionsBar'
|
|
7
6
|
import type { WorkloadRevision, WorkloadPodInfo } from '../../../../types'
|
|
7
|
+
import { formatRelativeAgeTime } from '../../../../utils/format'
|
|
8
8
|
|
|
9
9
|
interface ReplicaSetProgressionProps {
|
|
10
10
|
revisions: WorkloadRevision[]
|
|
@@ -90,7 +90,7 @@ export function ReplicaSetProgression({ revisions, pods, namespace, onNavigate }
|
|
|
90
90
|
))}
|
|
91
91
|
</div>
|
|
92
92
|
<span className="shrink-0 text-xs tabular-nums text-theme-text-tertiary">{rev.replicas ?? 0} replicas</span>
|
|
93
|
-
<span className="shrink-0 text-xs text-theme-text-tertiary">{
|
|
93
|
+
<span className="shrink-0 text-xs text-theme-text-tertiary">{formatRelativeAgeTime(rev.createdAt)}</span>
|
|
94
94
|
</button>
|
|
95
95
|
<Collapse open={isOpen} mountLazily id={disclosurePanelId(panelBase, rev.number)}>
|
|
96
96
|
<div className="space-y-0.5 border-t border-theme-border/50 px-2 py-1.5 pl-8">
|
|
@@ -77,7 +77,7 @@ describe('getCNPGObjectStoreStatus', () => {
|
|
|
77
77
|
it('is unknown, not healthy, when no server has reported', () => {
|
|
78
78
|
const s = getCNPGObjectStoreStatus(store({}))
|
|
79
79
|
expect(s.level).toBe('unknown')
|
|
80
|
-
expect(s.text).toBe('No
|
|
80
|
+
expect(s.text).toBe('No backup recorded')
|
|
81
81
|
})
|
|
82
82
|
|
|
83
83
|
it('is unhealthy when any server is failing since its last success', () => {
|
|
@@ -600,7 +600,7 @@ export interface CNPGObjectStoreRecoveryWindow {
|
|
|
600
600
|
firstRecoverabilityPoint?: string
|
|
601
601
|
lastSuccessfulBackupTime?: string
|
|
602
602
|
lastFailedBackupTime?: string
|
|
603
|
-
/** A failure newer than the last success
|
|
603
|
+
/** A failure newer than the last success, or a failure and no success at all. */
|
|
604
604
|
failingSinceLastSuccess: boolean
|
|
605
605
|
}
|
|
606
606
|
|
|
@@ -641,7 +641,7 @@ export function getCNPGObjectStoreRecoveryWindows(resource: any): CNPGObjectStor
|
|
|
641
641
|
export function getCNPGObjectStoreStatus(resource: any): StatusBadge {
|
|
642
642
|
const windows = getCNPGObjectStoreRecoveryWindows(resource)
|
|
643
643
|
if (windows.length === 0) {
|
|
644
|
-
return { text: 'No
|
|
644
|
+
return { text: 'No backup recorded', color: healthColors.unknown, level: 'unknown' }
|
|
645
645
|
}
|
|
646
646
|
if (windows.some((w) => w.failingSinceLastSuccess)) {
|
|
647
647
|
return { text: 'Backups Failing', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
@@ -649,7 +649,7 @@ export function getCNPGObjectStoreStatus(resource: any): StatusBadge {
|
|
|
649
649
|
// Every timestamp on a recovery window is optional, so a server can be listed
|
|
650
650
|
// with no recovery point at all. The entry existing is not a backup existing.
|
|
651
651
|
if (!windows.some((w) => w.firstRecoverabilityPoint || w.lastSuccessfulBackupTime)) {
|
|
652
|
-
return { text: '
|
|
652
|
+
return { text: 'Recovery point not reported', color: healthColors.unknown, level: 'unknown' }
|
|
653
653
|
}
|
|
654
654
|
return { text: 'Recoverable', color: healthColors.healthy, level: 'healthy' }
|
|
655
655
|
}
|
|
@@ -865,10 +865,6 @@ export function getCNPGBackupStartedAt(resource: any): string {
|
|
|
865
865
|
return resource.status?.startedAt ? formatAge(resource.status.startedAt) : '-'
|
|
866
866
|
}
|
|
867
867
|
|
|
868
|
-
export function getCNPGBackupStoppedAt(resource: any): string {
|
|
869
|
-
return resource.status?.stoppedAt ? formatAge(resource.status.stoppedAt) : '-'
|
|
870
|
-
}
|
|
871
|
-
|
|
872
868
|
export function getCNPGBackupName(resource: any): string {
|
|
873
869
|
return resource.status?.backupName || '-'
|
|
874
870
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// KEDA CRD utility functions
|
|
2
2
|
|
|
3
3
|
import type { StatusBadge } from './resource-utils'
|
|
4
|
-
import { healthColors
|
|
4
|
+
import { healthColors } from './resource-utils'
|
|
5
5
|
|
|
6
6
|
// ============================================================================
|
|
7
7
|
// SHARED HELPERS
|
|
@@ -108,12 +108,6 @@ export function getScaledObjectHpaName(resource: any): string {
|
|
|
108
108
|
return resource.status?.hpaName || '-'
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
export function getScaledObjectLastActiveTime(resource: any): string {
|
|
112
|
-
const lastActive = resource.status?.lastActiveTime
|
|
113
|
-
if (!lastActive) return '-'
|
|
114
|
-
return formatAge(lastActive)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
111
|
export function getScaledObjectPollingInterval(resource: any): number {
|
|
118
112
|
return resource.spec?.pollingInterval ?? 30
|
|
119
113
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Kyverno / Policy Report CRD utility functions
|
|
2
2
|
|
|
3
3
|
import type { StatusBadge } from './resource-utils'
|
|
4
|
-
import { healthColors
|
|
4
|
+
import { healthColors } from './resource-utils'
|
|
5
5
|
|
|
6
6
|
// ============================================================================
|
|
7
7
|
// SHARED HELPERS
|
|
@@ -286,12 +286,6 @@ export function getKyvernoPolicyAutogenRules(resource: any): string[] {
|
|
|
286
286
|
return autogen.map((r: any) => r.name).filter(Boolean)
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
export function getKyvernoPolicyLastScheduleTime(resource: any): string {
|
|
290
|
-
const lastSchedule = resource.status?.lastScheduleTime
|
|
291
|
-
if (!lastSchedule) return '-'
|
|
292
|
-
return formatAge(lastSchedule)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
289
|
// ============================================================================
|
|
296
290
|
// KYVERNO CLUSTERPOLICY UTILITIES (kyverno.io/v1)
|
|
297
291
|
// ============================================================================
|
|
@@ -504,12 +504,6 @@ export function getBSLLastValidation(resource: any): string {
|
|
|
504
504
|
return formatAge(lastValidation)
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
-
export function getBSLLastSynced(resource: any): string {
|
|
508
|
-
const lastSynced = resource.status?.lastSyncedTime
|
|
509
|
-
if (!lastSynced) return '-'
|
|
510
|
-
return formatAge(lastSynced)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
507
|
// Why the location is unavailable, in the validation controller's own words.
|
|
514
508
|
// A BackupStorageLocation carries no conditions, so status.message is the only
|
|
515
509
|
// place the reason exists — without it the phase is the whole story a reader
|
|
@@ -571,12 +565,6 @@ export function getBackupRepositoryMaintenanceFrequency(resource: any): string {
|
|
|
571
565
|
return resource.spec?.maintenanceFrequency || '-'
|
|
572
566
|
}
|
|
573
567
|
|
|
574
|
-
export function getBackupRepositoryLastMaintenance(resource: any): string {
|
|
575
|
-
const at = resource.status?.lastMaintenanceTime
|
|
576
|
-
if (!at) return '-'
|
|
577
|
-
return formatAge(at)
|
|
578
|
-
}
|
|
579
|
-
|
|
580
568
|
// Why the repository is not ready, in the controller's own words. Velero writes
|
|
581
569
|
// it on the object and nowhere else, so without it the phase is the whole story
|
|
582
570
|
// a reader gets.
|