@skyhook-io/k8s-ui 1.14.3 → 1.14.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/components/charts/AreaChart.test.tsx +160 -0
- package/src/components/charts/AreaChart.tsx +191 -39
- package/src/components/charts/PrometheusChartsView.test.tsx +36 -0
- package/src/components/charts/PrometheusChartsView.tsx +193 -76
- package/src/components/charts/SeriesLegend.tsx +8 -3
- package/src/components/charts/annotations.ts +59 -0
- package/src/components/charts/axis.test.ts +32 -0
- package/src/components/charts/axis.ts +60 -0
- package/src/components/charts/colors.test.ts +27 -0
- package/src/components/charts/colors.ts +23 -0
- package/src/components/charts/format.test.ts +19 -0
- package/src/components/charts/format.ts +17 -1
- package/src/components/charts/index.ts +3 -1
- package/src/components/charts/types.ts +12 -0
- package/src/components/logs/LogCore.tsx +182 -16
- package/src/components/logs/LogsViewer.tsx +4 -21
- package/src/components/logs/WorkloadLogsViewer.tsx +5 -23
- package/src/components/resources/HPADiagnosisSummary.test.tsx +178 -0
- package/src/components/resources/HPADiagnosisSummary.tsx +218 -0
- package/src/components/resources/ResourcesView.tsx +17 -3
- package/src/components/resources/index.ts +7 -0
- package/src/components/resources/renderers/AWSManagedControlPlaneRenderer.tsx +2 -2
- package/src/components/resources/renderers/AlertRenderer.tsx +3 -3
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +5 -5
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +1 -1
- package/src/components/resources/renderers/CertificateRenderer.tsx +3 -3
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +5 -5
- package/src/components/resources/renderers/ChallengeRenderer.tsx +8 -8
- package/src/components/resources/renderers/CiliumNetworkPolicyRenderer.tsx +3 -3
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +2 -2
- package/src/components/resources/renderers/ClusterNetworkPolicyRenderer.tsx +3 -3
- package/src/components/resources/renderers/GatewayRenderer.tsx +2 -2
- package/src/components/resources/renderers/GenericRenderer.tsx +1 -1
- package/src/components/resources/renderers/HPARenderer.tsx +4 -57
- package/src/components/resources/renderers/IngressClassRenderer.tsx +1 -1
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +3 -3
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +2 -2
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +3 -3
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +2 -2
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -1
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +23 -20
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +2 -2
- package/src/components/resources/renderers/NodeRenderer.tsx +3 -3
- package/src/components/resources/renderers/OrderRenderer.tsx +6 -6
- package/src/components/resources/renderers/RoleRenderer.tsx +3 -3
- package/src/components/resources/renderers/RolloutRenderer.tsx +2 -2
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +3 -3
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +8 -8
- package/src/components/resources/renderers/VPARenderer.tsx +1 -1
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +1 -1
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +9 -9
- package/src/components/resources/renderers/WorkloadRenderer.tsx +4 -20
- package/src/components/resources/renderers/badge-no-handrolled.test.tsx +6 -12
- package/src/components/resources/renderers/cnpg-cells.tsx +1 -1
- package/src/components/resources/renderers/eso-cells.tsx +17 -1
- package/src/components/resources/renderers/knative-cells.tsx +1 -1
- package/src/components/resources/renderers/kyverno-cells.tsx +7 -3
- package/src/components/resources/resource-utils-certmanager.ts +2 -2
- package/src/components/resources/resource-utils-eso.test.ts +26 -1
- package/src/components/resources/resource-utils-eso.ts +13 -0
- package/src/components/resources/resource-utils-knative.ts +3 -3
- package/src/components/topology/K8sResourceNode.test.ts +9 -0
- package/src/components/topology/K8sResourceNode.tsx +56 -5
- package/src/components/topology/TopologyGraph.tsx +18 -1
- package/src/components/topology/pod-phase-fallback.test.ts +24 -0
- package/src/components/ui/Badge.tsx +5 -1
- package/src/components/ui/index.ts +5 -0
- package/src/components/ui/monacoRuntime.ts +14 -0
- package/src/monaco-deep.d.ts +10 -0
- package/src/theme/components.css +29 -0
- package/src/types/core.ts +23 -10
- package/src/utils/badge-colors.ts +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/log-export.test.ts +225 -0
- package/src/utils/log-export.ts +149 -0
- package/src/utils/navigation.test.ts +10 -0
- package/src/utils/navigation.ts +4 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Badge, type BadgeSeverity } from "../ui/Badge";
|
|
3
|
+
import { StatusDot } from "../ui/status-tone";
|
|
4
|
+
import { hpaStateLabel, hpaStateLevel } from "./resource-utils-hpa";
|
|
5
|
+
import type {
|
|
6
|
+
HPADiagnosisState,
|
|
7
|
+
HPADiagnosisView,
|
|
8
|
+
HPAReasonSummary,
|
|
9
|
+
} from "../../types";
|
|
10
|
+
|
|
11
|
+
export function hpaBadgeSeverity(state: HPADiagnosisState): BadgeSeverity {
|
|
12
|
+
switch (hpaStateLevel(state)) {
|
|
13
|
+
case "healthy":
|
|
14
|
+
return "success";
|
|
15
|
+
case "unhealthy":
|
|
16
|
+
return "error";
|
|
17
|
+
case "degraded":
|
|
18
|
+
return "warning";
|
|
19
|
+
case "alert":
|
|
20
|
+
return "alert";
|
|
21
|
+
case "neutral":
|
|
22
|
+
return "info";
|
|
23
|
+
default:
|
|
24
|
+
return "neutral";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A reason that named the state, or repeats the summary verbatim, says the
|
|
30
|
+
* headline over again — only the controller's own sentence behind it (the
|
|
31
|
+
* reason's detail) adds anything a reader does not already have.
|
|
32
|
+
*/
|
|
33
|
+
export function isHPAReasonRedundant(
|
|
34
|
+
diagnosis: HPADiagnosisView,
|
|
35
|
+
reason: HPAReasonSummary,
|
|
36
|
+
): boolean {
|
|
37
|
+
return reason.id === diagnosis.state || reason.message === diagnosis.summary;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface HPAReasonGroups {
|
|
41
|
+
/** Reasons whose message restates the headline; render their detail, not their message. */
|
|
42
|
+
redundant: HPAReasonSummary[];
|
|
43
|
+
/** Reasons that carry something the headline does not. */
|
|
44
|
+
additional: HPAReasonSummary[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function hpaReasonGroups(diagnosis: HPADiagnosisView): HPAReasonGroups {
|
|
48
|
+
const reasons = diagnosis.reasons ?? [];
|
|
49
|
+
const redundant = reasons.filter((reason) =>
|
|
50
|
+
isHPAReasonRedundant(diagnosis, reason),
|
|
51
|
+
);
|
|
52
|
+
return {
|
|
53
|
+
redundant,
|
|
54
|
+
additional: reasons.filter((reason) => !redundant.includes(reason)),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function formatReasonID(id: string): string {
|
|
59
|
+
return id.replace(/_/g, " ");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface HPADiagnosisSummaryProps {
|
|
63
|
+
diagnosis: HPADiagnosisView;
|
|
64
|
+
/**
|
|
65
|
+
* `detail` — the HPA detail page: a boxed Evidence block per reason.
|
|
66
|
+
* `inline` — an evidence card on the workload the HPA scales: the headline
|
|
67
|
+
* once, then the controller's condition quoted and attributed.
|
|
68
|
+
*/
|
|
69
|
+
variant: "detail" | "inline";
|
|
70
|
+
/**
|
|
71
|
+
* Host-rendered identity for the `inline` variant (the scaler's name, owner
|
|
72
|
+
* links) placed before the state indicator. Navigation stays with the host.
|
|
73
|
+
*/
|
|
74
|
+
header?: ReactNode;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function HPADiagnosisSummary({
|
|
78
|
+
diagnosis,
|
|
79
|
+
variant,
|
|
80
|
+
header,
|
|
81
|
+
}: HPADiagnosisSummaryProps) {
|
|
82
|
+
const { redundant, additional } = hpaReasonGroups(diagnosis);
|
|
83
|
+
const bounds = diagnosis.bounds;
|
|
84
|
+
|
|
85
|
+
if (variant === "inline") {
|
|
86
|
+
return (
|
|
87
|
+
<div className="space-y-1 text-xs">
|
|
88
|
+
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
|
89
|
+
{header}
|
|
90
|
+
<span className="inline-flex items-center gap-1.5 text-theme-text-secondary">
|
|
91
|
+
<StatusDot
|
|
92
|
+
tone={hpaStateLevel(diagnosis.state)}
|
|
93
|
+
className="shrink-0"
|
|
94
|
+
/>
|
|
95
|
+
{hpaStateLabel(diagnosis.state)}
|
|
96
|
+
</span>
|
|
97
|
+
{bounds ? (
|
|
98
|
+
<span className="font-mono tabular-nums text-theme-text-tertiary">
|
|
99
|
+
{bounds.current}/{bounds.desired} replicas · bounds {bounds.min}-
|
|
100
|
+
{bounds.max}
|
|
101
|
+
</span>
|
|
102
|
+
) : null}
|
|
103
|
+
</div>
|
|
104
|
+
<p className="text-theme-text-secondary">{diagnosis.summary}</p>
|
|
105
|
+
{redundant.map((reason) =>
|
|
106
|
+
reason.detail ? (
|
|
107
|
+
<p
|
|
108
|
+
key={`${reason.id}-${reason.detail}`}
|
|
109
|
+
className="text-theme-text-tertiary"
|
|
110
|
+
>
|
|
111
|
+
{/* Same rule as the detail variant: only a condition-built
|
|
112
|
+
reason carries the controller's own sentence. */}
|
|
113
|
+
{reason.conditionType ? (
|
|
114
|
+
<>
|
|
115
|
+
Kubernetes {reason.conditionType}
|
|
116
|
+
{reason.conditionReason ? ` · ${reason.conditionReason}` : ""}
|
|
117
|
+
: “{reason.detail}”
|
|
118
|
+
</>
|
|
119
|
+
) : (
|
|
120
|
+
reason.detail
|
|
121
|
+
)}
|
|
122
|
+
</p>
|
|
123
|
+
) : null,
|
|
124
|
+
)}
|
|
125
|
+
{additional.length > 0 ? (
|
|
126
|
+
<ul className="list-disc space-y-0.5 pl-4 text-theme-text-secondary marker:text-theme-text-tertiary">
|
|
127
|
+
{additional.map((reason) => (
|
|
128
|
+
<li key={`${reason.id}-${reason.message}`}>
|
|
129
|
+
{reason.message}
|
|
130
|
+
{reason.detail ? (
|
|
131
|
+
<span className="text-theme-text-tertiary">
|
|
132
|
+
{" "}
|
|
133
|
+
· {reason.detail}
|
|
134
|
+
</span>
|
|
135
|
+
) : null}
|
|
136
|
+
</li>
|
|
137
|
+
))}
|
|
138
|
+
</ul>
|
|
139
|
+
) : null}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const reasons = diagnosis.reasons ?? [];
|
|
145
|
+
return (
|
|
146
|
+
<div className="card-inner space-y-3">
|
|
147
|
+
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
148
|
+
<div className="min-w-0">
|
|
149
|
+
<div className="text-sm font-medium text-theme-text-primary">
|
|
150
|
+
{diagnosis.summary}
|
|
151
|
+
</div>
|
|
152
|
+
{bounds ? (
|
|
153
|
+
<div className="mt-1 text-xs text-theme-text-secondary">
|
|
154
|
+
{bounds.current}/{bounds.desired} replicas, bounds {bounds.min}-
|
|
155
|
+
{bounds.max}
|
|
156
|
+
</div>
|
|
157
|
+
) : null}
|
|
158
|
+
</div>
|
|
159
|
+
<Badge severity={hpaBadgeSeverity(diagnosis.state)}>
|
|
160
|
+
{hpaStateLabel(diagnosis.state)}
|
|
161
|
+
</Badge>
|
|
162
|
+
</div>
|
|
163
|
+
{reasons.length > 0 && (
|
|
164
|
+
<div className="space-y-2">
|
|
165
|
+
{reasons.map((reason) => (
|
|
166
|
+
<div
|
|
167
|
+
key={`${reason.id}-${reason.message}`}
|
|
168
|
+
className="rounded border border-theme-border bg-theme-surface p-2"
|
|
169
|
+
>
|
|
170
|
+
<div className="flex flex-wrap items-center gap-2 text-xs">
|
|
171
|
+
<span className="font-medium text-theme-text-primary">
|
|
172
|
+
Evidence
|
|
173
|
+
</span>
|
|
174
|
+
<span className="text-theme-text-tertiary">
|
|
175
|
+
{formatReasonID(reason.id)}
|
|
176
|
+
</span>
|
|
177
|
+
{reason.conditionType && (
|
|
178
|
+
<span className="text-theme-text-tertiary">
|
|
179
|
+
{reason.conditionType}
|
|
180
|
+
</span>
|
|
181
|
+
)}
|
|
182
|
+
{reason.conditionReason && (
|
|
183
|
+
<span className="text-theme-text-tertiary">
|
|
184
|
+
{reason.conditionReason}
|
|
185
|
+
</span>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
{/* Radar's reading leads, as the reason is built to; the
|
|
189
|
+
controller's own sentence follows it, quoted and attributed
|
|
190
|
+
the same way the inline variant attributes it. Unlabelled
|
|
191
|
+
beside the condition chips, it read as Radar's words. */}
|
|
192
|
+
{!isHPAReasonRedundant(diagnosis, reason) && (
|
|
193
|
+
<div className="mt-1 text-xs text-theme-text-secondary">
|
|
194
|
+
{reason.message}
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
{reason.detail &&
|
|
198
|
+
// `detail` is the controller's own sentence only on a reason
|
|
199
|
+
// built from a condition, which is the only kind that carries
|
|
200
|
+
// a condition type. Elsewhere it is Radar's — a list of metric
|
|
201
|
+
// names, a note about an unobserved generation — and quoting
|
|
202
|
+
// that as Kubernetes would put words in its mouth.
|
|
203
|
+
(reason.conditionType ? (
|
|
204
|
+
<p className="mt-1 text-xs text-theme-text-tertiary">
|
|
205
|
+
Kubernetes: “{reason.detail}”
|
|
206
|
+
</p>
|
|
207
|
+
) : (
|
|
208
|
+
<p className="mt-1 text-xs text-theme-text-tertiary">
|
|
209
|
+
{reason.detail}
|
|
210
|
+
</p>
|
|
211
|
+
))}
|
|
212
|
+
</div>
|
|
213
|
+
))}
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
@@ -1839,6 +1839,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1839
1839
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1840
1840
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1841
1841
|
{ key: 'store', label: 'Store', width: 'w-36' },
|
|
1842
|
+
{ key: 'provider', label: 'Provider', width: 'w-48', tooltip: 'Backend the referenced store reads from. A dash means the store could not be read — it may not exist, or may be outside your access; an ellipsis means the stores are still loading.' },
|
|
1842
1843
|
{ key: 'refreshInterval', label: 'Refresh', width: 'w-24', tooltip: 'How often the value is re-pulled from the provider. A zero interval means it is fetched once and not updated afterward.' },
|
|
1843
1844
|
{ key: 'lastSync', label: 'Last Sync', width: 'w-28' },
|
|
1844
1845
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -3270,6 +3271,10 @@ interface ResourcesViewData {
|
|
|
3270
3271
|
onNavigate?: (path: string, options?: { replace?: boolean }) => void
|
|
3271
3272
|
certExpiry?: Record<string, { expired?: boolean; daysLeft: number }>
|
|
3272
3273
|
certExpiryError?: boolean
|
|
3274
|
+
// Provider type per SecretStore, keyed 'ns/name' and 'name' for the
|
|
3275
|
+
// cluster-scoped kind. An ExternalSecret names a store; only the store
|
|
3276
|
+
// knows which backend it reads from.
|
|
3277
|
+
storeProviders?: Record<string, string>
|
|
3273
3278
|
/** Cluster Audit findings keyed by "namespace/name". Raw compatibility
|
|
3274
3279
|
* counts render danger as High and warning as Medium. */
|
|
3275
3280
|
auditBadges?: Record<string, { danger: number; warning: number; messages?: AuditBadgeMessage[] }>
|
|
@@ -3330,6 +3335,10 @@ interface ResourcesViewProps {
|
|
|
3330
3335
|
topNodeMetrics?: TopNodeMetrics[]
|
|
3331
3336
|
certExpiry?: Record<string, { expired?: boolean; daysLeft: number }>
|
|
3332
3337
|
certExpiryError?: boolean
|
|
3338
|
+
// Provider type per SecretStore, keyed 'ns/name' and 'name' for the
|
|
3339
|
+
// cluster-scoped kind. An ExternalSecret names a store; only the store
|
|
3340
|
+
// knows which backend it reads from.
|
|
3341
|
+
storeProviders?: Record<string, string>
|
|
3333
3342
|
/** Cluster Audit findings keyed by "namespace/name". Raw compatibility
|
|
3334
3343
|
* counts render danger as High and warning as Medium. */
|
|
3335
3344
|
auditBadges?: Record<string, { danger: number; warning: number; messages?: AuditBadgeMessage[] }>
|
|
@@ -3579,6 +3588,7 @@ export function ResourcesView({
|
|
|
3579
3588
|
topNodeMetrics,
|
|
3580
3589
|
certExpiry,
|
|
3581
3590
|
certExpiryError,
|
|
3591
|
+
storeProviders,
|
|
3582
3592
|
auditBadges,
|
|
3583
3593
|
pinned = [],
|
|
3584
3594
|
togglePin = () => {},
|
|
@@ -6621,6 +6631,7 @@ export function ResourcesView({
|
|
|
6621
6631
|
isChecked={checkedResources.has(resourceKey)}
|
|
6622
6632
|
showCheckbox={isCheckboxMode}
|
|
6623
6633
|
majorityNodeMinorVersion={majorityNodeMinorVersion}
|
|
6634
|
+
storeProviders={storeProviders}
|
|
6624
6635
|
onRowClick={handleRowClick}
|
|
6625
6636
|
onRowMouseEnter={handleRowMouseEnter}
|
|
6626
6637
|
compareMode={compareMode}
|
|
@@ -6776,6 +6787,7 @@ interface ResourceRowCellsProps {
|
|
|
6776
6787
|
isChecked?: boolean
|
|
6777
6788
|
showCheckbox?: boolean
|
|
6778
6789
|
majorityNodeMinorVersion?: string
|
|
6790
|
+
storeProviders?: Record<string, string>
|
|
6779
6791
|
// Row callbacks receive the resource so the parent can pass referentially
|
|
6780
6792
|
// stable handlers — per-row closures would defeat React.memo and re-render
|
|
6781
6793
|
// every visible row on each SSE-driven refetch.
|
|
@@ -6810,7 +6822,7 @@ function rowHighlightClass(
|
|
|
6810
6822
|
return 'group-hover/row:bg-theme-surface/50'
|
|
6811
6823
|
}
|
|
6812
6824
|
|
|
6813
|
-
const ResourceRowCells = React.memo(function ResourceRowCells({ resource, kind, group, columns, extraColumnsByKey, hasSpacerColumn, isSelected, isHighlighted, isChecked, showCheckbox, majorityNodeMinorVersion, onRowClick, onRowMouseEnter, compareMode, comparePickIndex = -1, rowHref, onRowCheckToggle }: ResourceRowCellsProps) {
|
|
6825
|
+
const ResourceRowCells = React.memo(function ResourceRowCells({ resource, kind, group, columns, extraColumnsByKey, hasSpacerColumn, isSelected, isHighlighted, isChecked, showCheckbox, majorityNodeMinorVersion, storeProviders, onRowClick, onRowMouseEnter, compareMode, comparePickIndex = -1, rowHref, onRowCheckToggle }: ResourceRowCellsProps) {
|
|
6814
6826
|
const rowHighlight = rowHighlightClass(compareMode, comparePickIndex, isSelected, isHighlighted, isChecked)
|
|
6815
6827
|
const pickedSide = comparePickIndex === 0 ? 'a' : comparePickIndex === 1 ? 'b' : null
|
|
6816
6828
|
const onClick = onRowClick ? () => onRowClick(resource, !!isSelected) : undefined
|
|
@@ -6877,6 +6889,7 @@ const ResourceRowCells = React.memo(function ResourceRowCells({ resource, kind,
|
|
|
6877
6889
|
group={group}
|
|
6878
6890
|
column={col.key}
|
|
6879
6891
|
majorityNodeMinorVersion={majorityNodeMinorVersion}
|
|
6892
|
+
storeProviders={storeProviders}
|
|
6880
6893
|
extraColumn={extraColumnsByKey?.get(col.key)}
|
|
6881
6894
|
nameHref={col.key === 'name' ? rowHref : undefined}
|
|
6882
6895
|
/>
|
|
@@ -6920,6 +6933,7 @@ interface CellContentProps {
|
|
|
6920
6933
|
column: string
|
|
6921
6934
|
group?: string
|
|
6922
6935
|
majorityNodeMinorVersion?: string
|
|
6936
|
+
storeProviders?: Record<string, string>
|
|
6923
6937
|
/** When provided, the parent has injected an ExtraColumn for this
|
|
6924
6938
|
* column key. Render via the extra's render() and short-circuit
|
|
6925
6939
|
* the built-in cell logic. */
|
|
@@ -6929,7 +6943,7 @@ interface CellContentProps {
|
|
|
6929
6943
|
nameHref?: string
|
|
6930
6944
|
}
|
|
6931
6945
|
|
|
6932
|
-
function CellContent({ resource, kind, column, group, majorityNodeMinorVersion, extraColumn, nameHref }: CellContentProps) {
|
|
6946
|
+
function CellContent({ resource, kind, column, group, majorityNodeMinorVersion, storeProviders, extraColumn, nameHref }: CellContentProps) {
|
|
6933
6947
|
const { auditBadges } = useContext(ResourcesViewDataContext)
|
|
6934
6948
|
// Parent-injected extra columns short-circuit the built-in switch.
|
|
6935
6949
|
// Used by hosts that inject leading columns (e.g. a multi-cluster Cluster column).
|
|
@@ -7317,7 +7331,7 @@ function CellContent({ resource, kind, column, group, majorityNodeMinorVersion,
|
|
|
7317
7331
|
return <SbomReportCell resource={resource} column={column} />
|
|
7318
7332
|
// External Secrets Operator
|
|
7319
7333
|
case 'externalsecrets':
|
|
7320
|
-
return <ExternalSecretCell resource={resource} column={column} />
|
|
7334
|
+
return <ExternalSecretCell resource={resource} column={column} storeProviders={storeProviders} />
|
|
7321
7335
|
case 'clusterexternalsecrets':
|
|
7322
7336
|
return <ClusterExternalSecretCell resource={resource} column={column} />
|
|
7323
7337
|
case 'secretstores':
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export * from './resource-utils'
|
|
2
2
|
export * from './resource-utils-hpa'
|
|
3
|
+
export {
|
|
4
|
+
HPADiagnosisSummary,
|
|
5
|
+
hpaBadgeSeverity,
|
|
6
|
+
hpaReasonGroups,
|
|
7
|
+
isHPAReasonRedundant,
|
|
8
|
+
} from './HPADiagnosisSummary'
|
|
9
|
+
export type { HPADiagnosisSummaryProps, HPAReasonGroups } from './HPADiagnosisSummary'
|
|
3
10
|
export * from './resource-utils-argo'
|
|
4
11
|
export * from './resource-utils-certmanager'
|
|
5
12
|
export * from './resource-utils-cnpg'
|
|
@@ -145,9 +145,9 @@ export function AWSManagedControlPlaneRenderer({ data }: Props) {
|
|
|
145
145
|
<td className="py-1 text-theme-text-secondary font-mono text-[10px]">{a.statusVersion !== '-' ? a.statusVersion : a.specVersion}</td>
|
|
146
146
|
<td className="py-1">
|
|
147
147
|
<span className={`badge badge-sm ${a.status === 'ACTIVE'
|
|
148
|
-
? 'status-
|
|
148
|
+
? 'status-green'
|
|
149
149
|
: a.status === 'DEGRADED'
|
|
150
|
-
? 'status-
|
|
150
|
+
? 'status-red'
|
|
151
151
|
: 'status-neutral'
|
|
152
152
|
}`}>{a.status}</span>
|
|
153
153
|
</td>
|
|
@@ -92,9 +92,9 @@ export function AlertRenderer({ data }: AlertRendererProps) {
|
|
|
92
92
|
value={
|
|
93
93
|
<span className={clsx(
|
|
94
94
|
'badge',
|
|
95
|
-
eventSeverity === 'error' ? '
|
|
96
|
-
eventSeverity === 'warning' ? '
|
|
97
|
-
'
|
|
95
|
+
eventSeverity === 'error' ? 'status-red' :
|
|
96
|
+
eventSeverity === 'warning' ? 'status-amber' :
|
|
97
|
+
'status-blue'
|
|
98
98
|
)}>
|
|
99
99
|
{eventSeverity}
|
|
100
100
|
</span>
|
|
@@ -178,7 +178,7 @@ export function ArgoApplicationRenderer({ data, onTerminate, isTerminating }: Ar
|
|
|
178
178
|
<button
|
|
179
179
|
onClick={() => onTerminate({ namespace, name })}
|
|
180
180
|
disabled={isTerminating}
|
|
181
|
-
className="flex items-center gap-1.5 px-2 py-1 rounded text-xs
|
|
181
|
+
className="flex items-center gap-1.5 px-2 py-1 rounded text-xs status-red hover:opacity-80 transition-colors disabled:opacity-50"
|
|
182
182
|
title="Terminate sync"
|
|
183
183
|
>
|
|
184
184
|
<XCircle className="w-3.5 h-3.5" />
|
|
@@ -237,7 +237,7 @@ export function ArgoApplicationRenderer({ data, onTerminate, isTerminating }: Ar
|
|
|
237
237
|
<span
|
|
238
238
|
className={clsx(
|
|
239
239
|
'badge',
|
|
240
|
-
syncPolicy.automated ? '
|
|
240
|
+
syncPolicy.automated ? 'status-green' : BADGE_INACTIVE
|
|
241
241
|
)}
|
|
242
242
|
>
|
|
243
243
|
{syncPolicy.automated ? 'Enabled' : 'Disabled'}
|
|
@@ -281,11 +281,11 @@ export function ArgoApplicationRenderer({ data, onTerminate, isTerminating }: Ar
|
|
|
281
281
|
className={clsx(
|
|
282
282
|
'badge',
|
|
283
283
|
operationState.phase === 'Succeeded'
|
|
284
|
-
? '
|
|
284
|
+
? 'status-green'
|
|
285
285
|
: operationState.phase === 'Running'
|
|
286
|
-
? '
|
|
286
|
+
? 'status-blue'
|
|
287
287
|
: operationState.phase === 'Failed' || operationState.phase === 'Error'
|
|
288
|
-
? '
|
|
288
|
+
? 'status-red'
|
|
289
289
|
: BADGE_INACTIVE
|
|
290
290
|
)}
|
|
291
291
|
>
|
|
@@ -287,7 +287,7 @@ export function CNPGClusterRenderer({ data, onNavigate, declared}: CNPGClusterRe
|
|
|
287
287
|
)}
|
|
288
288
|
{data.spec?.enableSuperuserAccess !== undefined && (
|
|
289
289
|
<Property label="Superuser Access" value={
|
|
290
|
-
<span className={`badge-sm ${data.spec.enableSuperuserAccess ? '
|
|
290
|
+
<span className={`badge-sm ${data.spec.enableSuperuserAccess ? 'status-green' : 'bg-theme-hover text-theme-text-secondary'}`}>
|
|
291
291
|
{data.spec.enableSuperuserAccess ? 'Enabled' : 'Disabled'}
|
|
292
292
|
</span>
|
|
293
293
|
} />
|
|
@@ -120,13 +120,13 @@ export function CertificateRenderer({ data }: CertificateRendererProps) {
|
|
|
120
120
|
<span className={clsx(
|
|
121
121
|
'badge',
|
|
122
122
|
isReady
|
|
123
|
-
? '
|
|
124
|
-
: '
|
|
123
|
+
? 'status-green'
|
|
124
|
+
: 'status-red'
|
|
125
125
|
)}>
|
|
126
126
|
{isReady ? 'Ready' : 'Not Ready'}
|
|
127
127
|
</span>
|
|
128
128
|
{spec.isCA && (
|
|
129
|
-
<span className="badge
|
|
129
|
+
<span className="badge status-purple">
|
|
130
130
|
CA Certificate
|
|
131
131
|
</span>
|
|
132
132
|
)}
|
|
@@ -54,8 +54,8 @@ export function CertificateRequestRenderer({ data }: CertificateRequestRendererP
|
|
|
54
54
|
<span className={clsx(
|
|
55
55
|
'badge',
|
|
56
56
|
isReady
|
|
57
|
-
? '
|
|
58
|
-
: '
|
|
57
|
+
? 'status-green'
|
|
58
|
+
: 'status-red'
|
|
59
59
|
)}>
|
|
60
60
|
{isReady ? 'Ready' : 'Not Ready'}
|
|
61
61
|
</span>
|
|
@@ -67,10 +67,10 @@ export function CertificateRequestRenderer({ data }: CertificateRequestRendererP
|
|
|
67
67
|
<span className={clsx(
|
|
68
68
|
'badge',
|
|
69
69
|
isApproved
|
|
70
|
-
? '
|
|
70
|
+
? 'status-green'
|
|
71
71
|
: isDenied
|
|
72
|
-
? '
|
|
73
|
-
: '
|
|
72
|
+
? 'status-red'
|
|
73
|
+
: 'status-amber'
|
|
74
74
|
)}>
|
|
75
75
|
{isApproved ? 'Yes' : isDenied ? 'No' : 'Pending'}
|
|
76
76
|
</span>
|
|
@@ -7,19 +7,19 @@ import { BADGE_INACTIVE } from '../../../utils/badge-colors'
|
|
|
7
7
|
function getChallengeStateBadge(state: string): { color: string; text: string } {
|
|
8
8
|
switch (state?.toLowerCase()) {
|
|
9
9
|
case 'valid':
|
|
10
|
-
return { text: 'Valid', color: '
|
|
10
|
+
return { text: 'Valid', color: 'status-green' }
|
|
11
11
|
case 'ready':
|
|
12
|
-
return { text: 'Ready', color: '
|
|
12
|
+
return { text: 'Ready', color: 'status-blue' }
|
|
13
13
|
case 'pending':
|
|
14
|
-
return { text: 'Pending', color: '
|
|
14
|
+
return { text: 'Pending', color: 'status-amber' }
|
|
15
15
|
case 'processing':
|
|
16
|
-
return { text: 'Processing', color: '
|
|
16
|
+
return { text: 'Processing', color: 'status-amber' }
|
|
17
17
|
case 'invalid':
|
|
18
|
-
return { text: 'Invalid', color: '
|
|
18
|
+
return { text: 'Invalid', color: 'status-red' }
|
|
19
19
|
case 'expired':
|
|
20
|
-
return { text: 'Expired', color: '
|
|
20
|
+
return { text: 'Expired', color: 'status-red' }
|
|
21
21
|
case 'errored':
|
|
22
|
-
return { text: 'Errored', color: '
|
|
22
|
+
return { text: 'Errored', color: 'status-red' }
|
|
23
23
|
default:
|
|
24
24
|
return { text: state || 'Unknown', color: BADGE_INACTIVE }
|
|
25
25
|
}
|
|
@@ -78,7 +78,7 @@ export function ChallengeRenderer({ data }: { data: any }) {
|
|
|
78
78
|
{challengeType}
|
|
79
79
|
</span>
|
|
80
80
|
{spec.wildcard && (
|
|
81
|
-
<span className="badge
|
|
81
|
+
<span className="badge status-purple">
|
|
82
82
|
Wildcard
|
|
83
83
|
</span>
|
|
84
84
|
)}
|
|
@@ -144,7 +144,7 @@ function CiliumRuleCard({ rule }: { rule: any }) {
|
|
|
144
144
|
<div className="text-xs text-theme-text-tertiary mb-1">From Entities</div>
|
|
145
145
|
<div className="flex flex-wrap gap-1">
|
|
146
146
|
{fromEntities.map((e, j) => (
|
|
147
|
-
<span key={j} className="badge
|
|
147
|
+
<span key={j} className="badge status-purple">{e}</span>
|
|
148
148
|
))}
|
|
149
149
|
</div>
|
|
150
150
|
</div>
|
|
@@ -215,7 +215,7 @@ function CiliumRuleCard({ rule }: { rule: any }) {
|
|
|
215
215
|
<div className="text-xs text-theme-text-tertiary mb-1">To Entities</div>
|
|
216
216
|
<div className="flex flex-wrap gap-1">
|
|
217
217
|
{toEntities.map((e, j) => (
|
|
218
|
-
<span key={j} className="badge
|
|
218
|
+
<span key={j} className="badge status-purple">{e}</span>
|
|
219
219
|
))}
|
|
220
220
|
</div>
|
|
221
221
|
</div>
|
|
@@ -365,7 +365,7 @@ function L7Rules({ rules, ports }: { rules: any; ports?: any[] }) {
|
|
|
365
365
|
{httpRules.map((r: any, i: number) => (
|
|
366
366
|
<div key={i} className="flex flex-wrap items-center gap-1">
|
|
367
367
|
{r.method && (
|
|
368
|
-
<span className="badge badge-sm
|
|
368
|
+
<span className="badge badge-sm status-blue">
|
|
369
369
|
{r.method}
|
|
370
370
|
</span>
|
|
371
371
|
)}
|
|
@@ -81,8 +81,8 @@ function IssuerRendererBase({ data, kind }: { data: any; kind: string }) {
|
|
|
81
81
|
<span className={clsx(
|
|
82
82
|
'badge',
|
|
83
83
|
isReady
|
|
84
|
-
? '
|
|
85
|
-
: '
|
|
84
|
+
? 'status-green'
|
|
85
|
+
: 'status-red'
|
|
86
86
|
)}>
|
|
87
87
|
{isReady ? 'Ready' : 'Not Ready'}
|
|
88
88
|
</span>
|
|
@@ -76,10 +76,10 @@ function AdminRuleCard({ rule, direction }: { rule: any; direction: 'from' | 'to
|
|
|
76
76
|
const ports: any[] = rule.ports || []
|
|
77
77
|
|
|
78
78
|
const actionColor = action === 'Deny'
|
|
79
|
-
? '
|
|
79
|
+
? 'status-red'
|
|
80
80
|
: action === 'Pass'
|
|
81
|
-
? '
|
|
82
|
-
: '
|
|
81
|
+
? 'status-amber'
|
|
82
|
+
: 'status-green'
|
|
83
83
|
|
|
84
84
|
return (
|
|
85
85
|
<div className="card-inner-lg">
|
|
@@ -131,9 +131,9 @@ export function GatewayRenderer({ data, onNavigate }: GatewayRendererProps) {
|
|
|
131
131
|
<span className={clsx(
|
|
132
132
|
'w-4 h-4 rounded-full flex items-center justify-center text-xs shrink-0',
|
|
133
133
|
isListenerAccepted
|
|
134
|
-
? '
|
|
134
|
+
? 'status-green'
|
|
135
135
|
: isListenerNotAccepted
|
|
136
|
-
? '
|
|
136
|
+
? 'status-red'
|
|
137
137
|
: BADGE_INACTIVE
|
|
138
138
|
)}>
|
|
139
139
|
{isListenerAccepted ? '\u2713' : isListenerNotAccepted ? '\u2717' : '?'}
|
|
@@ -364,7 +364,7 @@ function GenericConditionsSection({ conditions }: { conditions: any[] }) {
|
|
|
364
364
|
<span className="text-theme-text-primary font-medium">{cond.type}</span>
|
|
365
365
|
<span className={clsx(
|
|
366
366
|
'badge-sm',
|
|
367
|
-
isTrue ? 'bg-theme-elevated text-theme-text-secondary' : '
|
|
367
|
+
isTrue ? 'bg-theme-elevated text-theme-text-secondary' : 'status-red'
|
|
368
368
|
)}>
|
|
369
369
|
{cond.status}
|
|
370
370
|
</span>
|
|
@@ -2,11 +2,11 @@ import type { ReactNode } from 'react'
|
|
|
2
2
|
import { Activity, Cpu } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { Section, PropertyList, Property, ConditionsSection, ResourceLink, type ConditionTone } from '../../ui/drawer-components'
|
|
5
|
-
import { Badge
|
|
5
|
+
import { Badge } from '../../ui/Badge'
|
|
6
6
|
import { kindToPlural } from '../../../utils/navigation'
|
|
7
7
|
import { formatAge } from '../resource-utils'
|
|
8
|
-
import {
|
|
9
|
-
import type { HPADiagnosis
|
|
8
|
+
import { HPADiagnosisSummary } from '../HPADiagnosisSummary'
|
|
9
|
+
import type { HPADiagnosis } from '../../../types'
|
|
10
10
|
|
|
11
11
|
interface HPARendererProps {
|
|
12
12
|
data: any
|
|
@@ -16,23 +16,6 @@ interface HPARendererProps {
|
|
|
16
16
|
extraSections?: ReactNode
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function hpaBadgeSeverity(state: HPADiagnosisState): BadgeSeverity {
|
|
20
|
-
switch (hpaStateLevel(state)) {
|
|
21
|
-
case 'healthy':
|
|
22
|
-
return 'success'
|
|
23
|
-
case 'unhealthy':
|
|
24
|
-
return 'error'
|
|
25
|
-
case 'degraded':
|
|
26
|
-
return 'warning'
|
|
27
|
-
case 'alert':
|
|
28
|
-
return 'alert'
|
|
29
|
-
case 'neutral':
|
|
30
|
-
return 'info'
|
|
31
|
-
default:
|
|
32
|
-
return 'neutral'
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
19
|
function hpaConditionTone(condition: any): ConditionTone | undefined {
|
|
37
20
|
if (condition?.type === 'ScaledToZero') {
|
|
38
21
|
if (condition.status === 'False') return 'ok'
|
|
@@ -50,14 +33,6 @@ function hpaConditionTone(condition: any): ConditionTone | undefined {
|
|
|
50
33
|
return 'warning'
|
|
51
34
|
}
|
|
52
35
|
|
|
53
|
-
function formatReasonID(id: string): string {
|
|
54
|
-
return id.replace(/_/g, ' ')
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isReasonMessageRedundant(state: HPADiagnosisState, reason: NonNullable<HPADiagnosis['reasons']>[number]): boolean {
|
|
58
|
-
return state === 'limited_max' && reason.id === 'limited_max'
|
|
59
|
-
}
|
|
60
|
-
|
|
61
36
|
export function HPARenderer({ data, onNavigate, hpaDiagnosis, extraSections }: HPARendererProps) {
|
|
62
37
|
const status = data.status || {}
|
|
63
38
|
const spec = data.spec || {}
|
|
@@ -67,35 +42,7 @@ export function HPARenderer({ data, onNavigate, hpaDiagnosis, extraSections }: H
|
|
|
67
42
|
<>
|
|
68
43
|
{hpaDiagnosis && (
|
|
69
44
|
<Section title="Diagnosis" icon={Activity}>
|
|
70
|
-
<
|
|
71
|
-
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
72
|
-
<div className="min-w-0">
|
|
73
|
-
<div className="text-sm font-medium text-theme-text-primary">{hpaDiagnosis.summary}</div>
|
|
74
|
-
<div className="mt-1 text-xs text-theme-text-secondary">
|
|
75
|
-
{hpaDiagnosis.bounds.current}/{hpaDiagnosis.bounds.desired} replicas, bounds {hpaDiagnosis.bounds.min}-{hpaDiagnosis.bounds.max}
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
<Badge severity={hpaBadgeSeverity(hpaDiagnosis.state)}>{hpaStateLabel(hpaDiagnosis.state)}</Badge>
|
|
79
|
-
</div>
|
|
80
|
-
{hpaDiagnosis.reasons && hpaDiagnosis.reasons.length > 0 && (
|
|
81
|
-
<div className="space-y-2">
|
|
82
|
-
{hpaDiagnosis.reasons.map((reason) => (
|
|
83
|
-
<div key={`${reason.id}-${reason.message}`} className="rounded border border-theme-border bg-theme-surface p-2">
|
|
84
|
-
<div className="flex flex-wrap items-center gap-2 text-xs">
|
|
85
|
-
<span className="font-medium text-theme-text-primary">Evidence</span>
|
|
86
|
-
<span className="text-theme-text-tertiary">{formatReasonID(reason.id)}</span>
|
|
87
|
-
{reason.conditionType && <span className="text-theme-text-tertiary">{reason.conditionType}</span>}
|
|
88
|
-
{reason.conditionReason && <span className="text-theme-text-tertiary">{reason.conditionReason}</span>}
|
|
89
|
-
{reason.detail && <span className="text-theme-text-tertiary">{reason.detail}</span>}
|
|
90
|
-
</div>
|
|
91
|
-
{!isReasonMessageRedundant(hpaDiagnosis.state, reason) && (
|
|
92
|
-
<div className="mt-1 text-xs text-theme-text-secondary">{reason.message}</div>
|
|
93
|
-
)}
|
|
94
|
-
</div>
|
|
95
|
-
))}
|
|
96
|
-
</div>
|
|
97
|
-
)}
|
|
98
|
-
</div>
|
|
45
|
+
<HPADiagnosisSummary diagnosis={hpaDiagnosis} variant="detail" />
|
|
99
46
|
</Section>
|
|
100
47
|
)}
|
|
101
48
|
|