@skyhook-io/k8s-ui 1.14.4 → 1.14.6
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 +5 -5
- package/src/components/audit/AuditCard.tsx +18 -7
- package/src/components/audit/AuditCoveragePresentation.test.tsx +45 -0
- package/src/components/charts/AreaChart.test.tsx +173 -0
- package/src/components/charts/AreaChart.tsx +208 -53
- package/src/components/charts/PrometheusChartsView.test.tsx +36 -0
- package/src/components/charts/PrometheusChartsView.tsx +205 -76
- package/src/components/charts/SeriesLegend.tsx +8 -3
- package/src/components/charts/annotations.ts +59 -0
- package/src/components/charts/axis.test.ts +48 -0
- package/src/components/charts/axis.ts +62 -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 +21 -1
- package/src/components/charts/index.ts +3 -1
- package/src/components/charts/nearestSample.test.ts +31 -0
- package/src/components/charts/nearestSample.ts +15 -0
- package/src/components/charts/types.ts +12 -0
- package/src/components/checks/ChecksView.tsx +20 -2
- package/src/components/gitops/GitOpsDetailLayout.tsx +13 -0
- package/src/components/gitops/GitOpsHealthSourceNotice.tsx +141 -0
- package/src/components/gitops/health-provenance.test.ts +54 -0
- package/src/components/gitops/health-provenance.ts +24 -0
- package/src/components/gitops/index.ts +2 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +40 -21
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +59 -8
- package/src/components/gitops/tree/merge.test.ts +34 -0
- package/src/components/gitops/tree/merge.ts +7 -0
- package/src/components/resources/HPADiagnosisSummary.test.tsx +178 -0
- package/src/components/resources/HPADiagnosisSummary.tsx +218 -0
- package/src/components/resources/ResourcesView.tsx +67 -2
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/index.ts +7 -0
- package/src/components/resources/renderers/AnalysisTemplateRenderer.tsx +150 -0
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +8 -1
- package/src/components/resources/renderers/ExperimentRenderer.tsx +179 -0
- package/src/components/resources/renderers/HPARenderer.tsx +4 -57
- package/src/components/resources/renderers/LimitRangeRenderer.test.tsx +156 -0
- package/src/components/resources/renderers/LimitRangeRenderer.tsx +277 -0
- package/src/components/resources/renderers/NamespaceRenderer.tsx +24 -1
- package/src/components/resources/renderers/NetworkPolicyDiagram.tsx +9 -16
- package/src/components/resources/renderers/NetworkPolicyRenderer.test.tsx +75 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +50 -36
- package/src/components/resources/renderers/PodRenderer.tsx +11 -0
- package/src/components/resources/renderers/ReflectorSection.test.tsx +281 -0
- package/src/components/resources/renderers/ReflectorSection.tsx +296 -0
- package/src/components/resources/renderers/RolloutRenderer.test.tsx +117 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +219 -56
- package/src/components/resources/renderers/SecretRenderer.tsx +10 -3
- package/src/components/resources/renderers/WorkloadRenderer.tsx +15 -21
- package/src/components/resources/renderers/index.ts +3 -0
- package/src/components/resources/renderers/rollout/CanaryStepTimeline.tsx +199 -0
- package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +116 -0
- package/src/components/resources/resource-utils.ts +18 -9
- package/src/components/shared/CreateResourceDialog.tsx +231 -11
- package/src/components/shared/DrainPlanDialog.test.tsx +154 -0
- package/src/components/shared/DrainPlanDialog.tsx +297 -0
- package/src/components/shared/ResourceActionsBar.tsx +53 -26
- package/src/components/shared/ResourceRendererDispatch.test.tsx +45 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +27 -6
- package/src/components/shared/index.ts +4 -0
- package/src/components/topology/K8sResourceNode.test.ts +9 -0
- package/src/components/topology/K8sResourceNode.tsx +56 -5
- package/src/components/ui/Badge.tsx +5 -1
- package/src/components/ui/FetchResult.test.tsx +20 -2
- package/src/components/ui/FetchResult.tsx +33 -3
- package/src/components/ui/FreshnessControl.tsx +8 -2
- package/src/components/ui/index.ts +5 -0
- package/src/components/ui/monacoRuntime.ts +14 -0
- package/src/components/workload/WorkloadView.tsx +2 -2
- package/src/monaco-deep.d.ts +10 -0
- package/src/theme/components.css +16 -0
- package/src/types/core.ts +38 -11
- package/src/types/gitops-insights.ts +23 -0
- package/src/types/gitops-tree.ts +29 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/navigation.test.ts +20 -1
- package/src/utils/navigation.ts +25 -0
- package/src/utils/network-policy.test.ts +42 -0
- package/src/utils/network-policy.ts +59 -0
- package/src/utils/reflector.ts +139 -0
- package/src/utils/yaml-file-import.test.ts +189 -0
- package/src/utils/yaml-file-import.ts +95 -0
- package/src/components/logs/JsonLogLine.tsx +0 -93
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasReflectorDetails } from '../resources/renderers/ReflectorSection'
|
|
1
2
|
import { clsx } from 'clsx'
|
|
2
3
|
import { SEVERITY_BADGE, HEALTH_BADGE_COLORS } from '../../utils/badge-colors'
|
|
3
4
|
import { isArgoRolloutResource } from '../../utils/workload-rollout'
|
|
@@ -120,6 +121,8 @@ import {
|
|
|
120
121
|
PVCRenderer,
|
|
121
122
|
RolloutRenderer,
|
|
122
123
|
AnalysisRunRenderer,
|
|
124
|
+
AnalysisTemplateRenderer,
|
|
125
|
+
ExperimentRenderer,
|
|
123
126
|
CertificateRenderer,
|
|
124
127
|
WorkflowRenderer,
|
|
125
128
|
PersistentVolumeRenderer,
|
|
@@ -231,6 +234,7 @@ import {
|
|
|
231
234
|
PriorityClassRenderer,
|
|
232
235
|
RuntimeClassRenderer,
|
|
233
236
|
LeaseRenderer,
|
|
237
|
+
LimitRangeRenderer,
|
|
234
238
|
TraefikIngressRouteRenderer,
|
|
235
239
|
TraefikMiddlewareRenderer,
|
|
236
240
|
TraefikServiceRenderer,
|
|
@@ -426,7 +430,7 @@ const KNOWN_KINDS = new Set([
|
|
|
426
430
|
'pods', 'deployments', 'statefulsets', 'daemonsets', 'replicasets',
|
|
427
431
|
'services', 'endpointslices', 'ingresses', 'configmaps', 'secrets', 'jobs', 'cronjobs', 'cronworkflows',
|
|
428
432
|
'hpas', 'horizontalpodautoscalers', 'nodes', 'persistentvolumeclaims',
|
|
429
|
-
'rollouts', 'analysisruns', 'certificates', 'workflows', 'persistentvolumes',
|
|
433
|
+
'rollouts', 'analysisruns', 'analysistemplates', 'clusteranalysistemplates', 'experiments', 'certificates', 'workflows', 'persistentvolumes',
|
|
430
434
|
'storageclasses', 'certificaterequests', 'clusterissuers', 'issuers',
|
|
431
435
|
'orders', 'challenges',
|
|
432
436
|
'gateways', 'gatewayclasses', 'httproutes', 'grpcroutes', 'tcproutes', 'tlsroutes', 'sealedsecrets', 'workflowtemplates', 'clusterworkflowtemplates',
|
|
@@ -473,7 +477,7 @@ const KNOWN_KINDS = new Set([
|
|
|
473
477
|
'virtualservices', 'destinationrules', 'serviceentries',
|
|
474
478
|
'peerauthentications', 'authorizationpolicies',
|
|
475
479
|
'mutatingwebhookconfigurations', 'validatingwebhookconfigurations',
|
|
476
|
-
'ingressclasses', 'priorityclasses', 'runtimeclasses', 'leases',
|
|
480
|
+
'ingressclasses', 'priorityclasses', 'runtimeclasses', 'leases', 'limitranges',
|
|
477
481
|
'knativeservices', 'knativeconfigurations', 'knativerevisions', 'knativeroutes',
|
|
478
482
|
'brokers', 'triggers', 'eventtypes', 'pingsources', 'apiserversources', 'containersources', 'sinkbindings',
|
|
479
483
|
'channels', 'inmemorychannels', 'subscriptions', 'sequences', 'parallels',
|
|
@@ -678,7 +682,8 @@ export function ResourceRendererDispatch({
|
|
|
678
682
|
kind === 'clusters' || kind === 'backups' || kind === 'scheduledbackups' || kind === 'poolers'
|
|
679
683
|
|| kind === 'objectstores' || kind === 'databases' || kind === 'publications'
|
|
680
684
|
|| kind === 'subscriptions' || kind === 'imagecatalogs' || kind === 'clusterimagecatalogs'
|
|
681
|
-
|| kind === 'policies' || kind === 'rollouts' || kind === '
|
|
685
|
+
|| kind === 'policies' || kind === 'rollouts' || kind === 'experiments'
|
|
686
|
+
|| kind === 'machines' || kind === 'machinesets'
|
|
682
687
|
const isCNPGApiVersion = isApiGroup(data?.apiVersion, CNPG_GROUP)
|
|
683
688
|
const groupGatedMatched =
|
|
684
689
|
(kind === 'clusters' && (isCNPGApiVersion || isApiGroup(data?.apiVersion, 'cluster.x-k8s.io')))
|
|
@@ -691,6 +696,11 @@ export function ResourceRendererDispatch({
|
|
|
691
696
|
&& (isCNPGApiVersion || isApiGroup(data?.apiVersion, 'messaging.knative.dev')))
|
|
692
697
|
|| (kind === 'policies' && isApiGroup(data?.apiVersion, 'kyverno.io'))
|
|
693
698
|
|| (kind === 'rollouts' && isArgoRolloutResource(data))
|
|
699
|
+
// Katib (kubeflow.org) ships its own, unrelated Experiment CRD sharing
|
|
700
|
+
// this plural — without this gate it got the Argo Rollouts Experiment
|
|
701
|
+
// renderer's mostly-empty status view instead of its actual resource
|
|
702
|
+
// details, the same collision shape as every other check in this block.
|
|
703
|
+
|| (kind === 'experiments' && isApiGroup(data?.apiVersion, 'argoproj.io'))
|
|
694
704
|
|| ((kind === 'machines' || kind === 'machinesets')
|
|
695
705
|
&& isApiGroup(data?.apiVersion, 'cluster.x-k8s.io'))
|
|
696
706
|
const groupGatedFallthrough = isGroupGatedKind && !groupGatedMatched
|
|
@@ -741,11 +751,15 @@ export function ResourceRendererDispatch({
|
|
|
741
751
|
const HPAComp = rendererOverrides?.HPARenderer ?? HPARenderer
|
|
742
752
|
const PVCComp = rendererOverrides?.PVCRenderer ?? PVCRenderer
|
|
743
753
|
const RolloutComp = rendererOverrides?.RolloutRenderer ?? RolloutRenderer
|
|
754
|
+
const showsReflection = (kind === 'configmaps' || kind === 'secrets') && hasReflectorDetails(data, relationships?.reflection)
|
|
755
|
+
const reflectionRefs = showsReflection ? [relationships?.reflection?.source, ...(relationships?.reflection?.mirrors ?? [])].filter((ref): ref is ResourceRef => !!ref) : []
|
|
756
|
+
const withoutReflection = (refs: ResourceRef[] | undefined) => refs?.filter(ref => !reflectionRefs.some(mirror => mirror.kind === ref.kind && mirror.namespace === ref.namespace && mirror.name === ref.name && (mirror.group ?? '') === (ref.group ?? '')))
|
|
757
|
+
const sidebarRelationships = showsReflection && relationships ? { ...relationships, configRefs: withoutReflection(relationships.configRefs), consumers: withoutReflection(relationships.consumers) } : relationships
|
|
744
758
|
const scaleBlockedBy = replicaScalers(relationships?.scalers)
|
|
745
759
|
|
|
746
760
|
const sidebarContent = showCommonSections && (
|
|
747
761
|
<>
|
|
748
|
-
<RelatedResourcesSection relationships={
|
|
762
|
+
<RelatedResourcesSection relationships={sidebarRelationships} onNavigate={onNavigate} />
|
|
749
763
|
{kind !== 'events' && <EventsSection events={events || []} updates={updates || []} isLoading={eventsLoading ?? false} eventsError={eventsError ?? null} updatesError={updatesError ?? null} hint={eventsHint} />}
|
|
750
764
|
<LabelsSection data={data} />
|
|
751
765
|
<AnnotationsSection data={data} />
|
|
@@ -773,8 +787,8 @@ export function ResourceRendererDispatch({
|
|
|
773
787
|
{kind === 'services' && !data?.apiVersion?.includes('serving.knative.dev') && <ServiceComp data={data} onCopy={onCopy} copied={copied} onNavigate={onNavigate} />}
|
|
774
788
|
{kind === 'endpointslices' && <EndpointSliceRenderer data={data} onNavigate={onNavigate} />}
|
|
775
789
|
{kind === 'ingresses' && !data?.apiVersion?.includes('networking.internal.knative.dev') && <IngressRenderer data={data} onNavigate={onNavigate} />}
|
|
776
|
-
{kind === 'configmaps' && <ConfigMapRenderer data={data} />}
|
|
777
|
-
{kind === 'secrets' && <SecretRenderer data={data} certificateInfo={certificateInfo} resourceData={data} onSaveSecretValue={onSaveSecretValue} isSaving={isSavingSecret} />}
|
|
790
|
+
{kind === 'configmaps' && <ConfigMapRenderer data={data} relationships={relationships} onNavigate={onNavigate} />}
|
|
791
|
+
{kind === 'secrets' && <SecretRenderer data={data} relationships={relationships} onNavigate={onNavigate} certificateInfo={certificateInfo} resourceData={data} onSaveSecretValue={onSaveSecretValue} isSaving={isSavingSecret} />}
|
|
778
792
|
{kind === 'jobs' && !nonCoreJobFallthrough && <JobRenderer data={data} />}
|
|
779
793
|
{kind === 'cronjobs' && <CronJobRenderer data={data} onNavigate={onNavigate} />}
|
|
780
794
|
{kind === 'cronworkflows' && <CronWorkflowRenderer data={data} onNavigate={onNavigate} />}
|
|
@@ -783,6 +797,8 @@ export function ResourceRendererDispatch({
|
|
|
783
797
|
{kind === 'persistentvolumeclaims' && <PVCComp data={data} onNavigate={onNavigate} />}
|
|
784
798
|
{kind === 'rollouts' && isArgoRolloutResource(data) && <RolloutComp data={data} onNavigate={onNavigate} />}
|
|
785
799
|
{kind === 'analysisruns' && <AnalysisRunRenderer data={data} onNavigate={onNavigate} />}
|
|
800
|
+
{(kind === 'analysistemplates' || kind === 'clusteranalysistemplates') && <AnalysisTemplateRenderer data={data} />}
|
|
801
|
+
{kind === 'experiments' && isApiGroup(data?.apiVersion, 'argoproj.io') && <ExperimentRenderer data={data} onNavigate={onNavigate} />}
|
|
786
802
|
{kind === 'certificates' && !data?.apiVersion?.includes('networking.internal.knative.dev') && <CertificateRenderer data={data} />}
|
|
787
803
|
{kind === 'workflows' && <WorkflowRenderer data={data} onNavigate={onNavigate} />}
|
|
788
804
|
{kind === 'persistentvolumes' && <PersistentVolumeRenderer data={data} onNavigate={onNavigate} />}
|
|
@@ -920,6 +936,7 @@ export function ResourceRendererDispatch({
|
|
|
920
936
|
{kind === 'priorityclasses' && <PriorityClassRenderer data={data} />}
|
|
921
937
|
{kind === 'runtimeclasses' && <RuntimeClassRenderer data={data} />}
|
|
922
938
|
{kind === 'leases' && <LeaseRenderer data={data} />}
|
|
939
|
+
{kind === 'limitranges' && <LimitRangeRenderer data={data} />}
|
|
923
940
|
{/* Knative Serving */}
|
|
924
941
|
{(kind === 'services' && data?.apiVersion?.includes('serving.knative.dev')) && <KnativeServiceRenderer data={data} onNavigate={onNavigate} />}
|
|
925
942
|
{kind === 'knativeservices' && <KnativeServiceRenderer data={data} onNavigate={onNavigate} />}
|
|
@@ -1103,6 +1120,10 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
|
|
|
1103
1120
|
if (k === 'nodes') return getNodeStatus(data)
|
|
1104
1121
|
if (k === 'persistentvolumeclaims') return getPVCStatus(data)
|
|
1105
1122
|
if (k === 'analysisruns') return getAnalysisRunStatus(data)
|
|
1123
|
+
// Same collision guard as the render branch above — Katib's unrelated
|
|
1124
|
+
// Experiment CRD (kubeflow.org) shares this plural and doesn't report the
|
|
1125
|
+
// AnalysisPhase vocabulary getAnalysisRunStatus expects.
|
|
1126
|
+
if (k === 'experiments' && isApiGroup(data?.apiVersion, 'argoproj.io')) return getAnalysisRunStatus(data)
|
|
1106
1127
|
if (k === 'workflows') return getWorkflowStatus(data)
|
|
1107
1128
|
if (k === 'cronworkflows') return getCronWorkflowStatus(data)
|
|
1108
1129
|
if (k === 'certificates') {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export { ResourceRendererDispatch, getResourceStatus, type RendererOverrides } from './ResourceRendererDispatch'
|
|
2
2
|
export { EditableYamlView, SaveSuccessAnimation } from './EditableYamlView'
|
|
3
3
|
export { ResourceActionsBar, RevisionHistoryDialog, isRolloutKind } from './ResourceActionsBar'
|
|
4
|
+
export {
|
|
5
|
+
DrainPlanDialog, DrainPlanContent, canConfirmDrain, planMatches, emptyDirPodsAtRisk, DEFAULT_DRAIN_DIALOG_OPTIONS,
|
|
6
|
+
type DrainPlan, type DrainPlanPod, type DrainOutcome, type DrainDialogOptions,
|
|
7
|
+
} from './DrainPlanDialog'
|
|
4
8
|
export { SetImageDialog, type ManagedImageSource, type SetImageDialogProps } from './SetImageDialog'
|
|
5
9
|
export { CreateResourceDialog, type CreateResourceDialogProps, type ApplyResult } from './CreateResourceDialog'
|
|
6
10
|
export { HelmManagedByChip, ManagedByChip, type HelmOwnerRef } from './ManagedByChip'
|
|
@@ -9,6 +9,15 @@ describe('baseSubtitle', () => {
|
|
|
9
9
|
expect(baseSubtitle('ServiceMonitor', { endpointCount: 1 })).toBe('1 scrape endpoint')
|
|
10
10
|
})
|
|
11
11
|
|
|
12
|
+
it('describes a Service by its first port, capping additional ports to a count', () => {
|
|
13
|
+
expect(baseSubtitle('Service', { type: 'ClusterIP', ports: [] })).toBe('ClusterIP')
|
|
14
|
+
expect(baseSubtitle('Service', { type: 'ClusterIP', ports: [{ port: 80 }] })).toBe('ClusterIP :80')
|
|
15
|
+
expect(baseSubtitle('Service', {
|
|
16
|
+
type: 'ClusterIP',
|
|
17
|
+
ports: [{ port: 80 }, { port: 443 }, { port: 9153 }],
|
|
18
|
+
})).toBe('ClusterIP :80 +2 more')
|
|
19
|
+
})
|
|
20
|
+
|
|
12
21
|
it('describes a Calico staged deletion as a removal, not as selecting everything', () => {
|
|
13
22
|
// A staged deletion carries no selector, because the Calico API forbids one.
|
|
14
23
|
// Falling through to the empty-selector default would label the broadest
|
|
@@ -168,6 +168,34 @@ function getIssueTooltip(issue: string | undefined): React.ReactNode {
|
|
|
168
168
|
);
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
// Full port list for the Service subtitle's hover affordance — the subtitle
|
|
172
|
+
// itself only ever shows the first port + a count, so this is the only place
|
|
173
|
+
// a multi-port Service's other ports are visible without opening the detail
|
|
174
|
+
// page. Formatting (hide targetPort when it matches port) mirrors
|
|
175
|
+
// ServicePortCards in ServiceRenderer.tsx so a Service's ports read the same
|
|
176
|
+
// whether glanced at in the graph or opened in the full resource view.
|
|
177
|
+
function servicePortsTooltip(ports: ServicePortEntry[]): React.ReactNode | null {
|
|
178
|
+
if (ports.length < 2) return null;
|
|
179
|
+
return (
|
|
180
|
+
<div className="max-w-xs space-y-0.5">
|
|
181
|
+
{ports.map((p, i) => {
|
|
182
|
+
const target =
|
|
183
|
+
p.targetPort != null && p.targetPort !== String(p.port)
|
|
184
|
+
? ` → ${p.targetPort}`
|
|
185
|
+
: "";
|
|
186
|
+
const proto = p.appProtocol || p.protocol;
|
|
187
|
+
return (
|
|
188
|
+
<div key={i} className="text-[11px]">
|
|
189
|
+
{p.name && <span className="text-theme-text-tertiary">{p.name} </span>}
|
|
190
|
+
<span className="font-medium">{p.port}{target}</span>
|
|
191
|
+
{proto && <span className="text-theme-text-tertiary"> ({proto})</span>}
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
})}
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
171
199
|
// Default dimensions for unknown CRD kinds
|
|
172
200
|
export const DEFAULT_NODE_DIMENSIONS = { width: 260, height: 84 };
|
|
173
201
|
|
|
@@ -322,6 +350,16 @@ const SUMMARY_POD_KINDS = new Set<NodeKind>([
|
|
|
322
350
|
"Service",
|
|
323
351
|
]);
|
|
324
352
|
|
|
353
|
+
// Shape of one entry in a Service node's nodeData.ports (pkg/topology/builder.go's
|
|
354
|
+
// serviceTopologyPorts). name/appProtocol are omitted server-side when unset.
|
|
355
|
+
export interface ServicePortEntry {
|
|
356
|
+
name?: string;
|
|
357
|
+
port: number;
|
|
358
|
+
targetPort?: string;
|
|
359
|
+
protocol?: string;
|
|
360
|
+
appProtocol?: string;
|
|
361
|
+
}
|
|
362
|
+
|
|
325
363
|
export function baseSubtitle(kind: NodeKind, nodeData: Record<string, unknown>): string {
|
|
326
364
|
if (nodeData.deploymentMembership === 'source-only') {
|
|
327
365
|
return `Declared by ${nodeData.deploymentSourceLabel ?? 'deployment source'}`
|
|
@@ -369,8 +407,10 @@ export function baseSubtitle(kind: NodeKind, nodeData: Record<string, unknown>):
|
|
|
369
407
|
return (nodeData.phase as string) || "Unknown";
|
|
370
408
|
case "Service": {
|
|
371
409
|
const svcType = (nodeData.type as string) || "ClusterIP";
|
|
372
|
-
const
|
|
373
|
-
|
|
410
|
+
const ports = (nodeData.ports as ServicePortEntry[] | undefined) || [];
|
|
411
|
+
if (ports.length === 0) return svcType;
|
|
412
|
+
const more = ports.length > 1 ? ` +${ports.length - 1} more` : "";
|
|
413
|
+
return `${svcType} :${ports[0].port}${more}`;
|
|
374
414
|
}
|
|
375
415
|
case "CalicoNetworkPolicy":
|
|
376
416
|
case "CalicoGlobalNetworkPolicy":
|
|
@@ -513,6 +553,9 @@ export const K8sResourceNode = memo(function K8sResourceNode({
|
|
|
513
553
|
const { ownerColorIndex } = ownershipOf(nodeData)
|
|
514
554
|
const hue = ownerColorIndex !== null ? workloadHue(ownerColorIndex) : undefined
|
|
515
555
|
const subtitle = getSubtitle(kind, nodeData)
|
|
556
|
+
const portsTooltip = kind === 'Service'
|
|
557
|
+
? servicePortsTooltip((nodeData.ports as ServicePortEntry[] | undefined) || [])
|
|
558
|
+
: null
|
|
516
559
|
const isInternet = kind === 'Internet'
|
|
517
560
|
const isPodGroup = kind === 'PodGroup'
|
|
518
561
|
const isSmallNode = kind === 'ConfigMap' || kind === 'Secret' || kind === 'ServiceAccount' || kind === 'SealedSecret' || kind === 'ServiceMonitor' || kind === 'PodMonitor' || kind === 'HorizontalPodAutoscaler'
|
|
@@ -730,9 +773,17 @@ export const K8sResourceNode = memo(function K8sResourceNode({
|
|
|
730
773
|
|
|
731
774
|
{/* Subtitle */}
|
|
732
775
|
{subtitle && (
|
|
733
|
-
|
|
734
|
-
{
|
|
735
|
-
|
|
776
|
+
portsTooltip ? (
|
|
777
|
+
<Tooltip content={portsTooltip} position="bottom" wrapperClassName="max-w-full mt-0.5">
|
|
778
|
+
<div className="text-xs text-theme-text-secondary truncate cursor-help">
|
|
779
|
+
{subtitle}
|
|
780
|
+
</div>
|
|
781
|
+
</Tooltip>
|
|
782
|
+
) : (
|
|
783
|
+
<div className="text-xs text-theme-text-secondary truncate mt-0.5">
|
|
784
|
+
{subtitle}
|
|
785
|
+
</div>
|
|
786
|
+
)
|
|
736
787
|
)}
|
|
737
788
|
</div>
|
|
738
789
|
</div>
|
|
@@ -23,7 +23,10 @@ export type BadgeSize = 'sm' | 'default'
|
|
|
23
23
|
// "tell these sibling options apart". The only place sharing is intended.
|
|
24
24
|
// - structural: ports/paths/hosts/weights/names — neutral data fragments, not a
|
|
25
25
|
// status. Its own name so it can diverge from severity-neutral later.
|
|
26
|
-
|
|
26
|
+
// - agent: words an AI agent attached to a Radar fact (evidence roles such
|
|
27
|
+
// as "Cause" / "Rules out"). Muted brand accent on purpose: never a severity
|
|
28
|
+
// hue, so an agent's framing can never be mistaken for a Radar finding.
|
|
29
|
+
export type BadgeTone = 'note' | 'accent1' | 'accent2' | 'accent3' | 'structural' | 'agent'
|
|
27
30
|
|
|
28
31
|
interface BadgeProps {
|
|
29
32
|
/** Severity-based coloring (status badges) */
|
|
@@ -231,6 +234,7 @@ const TONE: Record<BadgeTone, string> = {
|
|
|
231
234
|
accent3: 'bg-teal-100 text-teal-800 border-teal-300 dark:bg-teal-950/50 dark:text-teal-400 dark:border-teal-700/40',
|
|
232
235
|
// neutral data fragment (ports/paths/hosts/names)
|
|
233
236
|
structural: 'bg-theme-elevated text-theme-text-secondary border-theme-border',
|
|
237
|
+
agent: 'bg-accent-muted text-accent-text border-accent/30',
|
|
234
238
|
}
|
|
235
239
|
|
|
236
240
|
// Structure classes
|
|
@@ -2,8 +2,8 @@ import { describe, expect, it } from 'vitest'
|
|
|
2
2
|
import { renderToString } from 'react-dom/server'
|
|
3
3
|
import { FetchResult } from './FetchResult'
|
|
4
4
|
|
|
5
|
-
function shaped(message: string, status: number) {
|
|
6
|
-
return Object.assign(new Error(message), { status })
|
|
5
|
+
function shaped(message: string, status: number, data?: Record<string, unknown>) {
|
|
6
|
+
return Object.assign(new Error(message), { status, data })
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
describe('FetchResult', () => {
|
|
@@ -54,6 +54,24 @@ describe('FetchResult', () => {
|
|
|
54
54
|
expect(html).toContain('Resource cache not available')
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
+
it('does not blame the cluster for a kind_sync_failed 503', () => {
|
|
58
|
+
const html = renderToString(
|
|
59
|
+
<FetchResult
|
|
60
|
+
loading={false}
|
|
61
|
+
error={shaped('pods failed to load within the sync deadline', 503, { error_code: 'kind_sync_failed' })}
|
|
62
|
+
/>,
|
|
63
|
+
)
|
|
64
|
+
expect(html).not.toContain('Cluster unavailable')
|
|
65
|
+
expect(html).toContain('Couldn')
|
|
66
|
+
expect(html).toContain('pods failed to load within the sync deadline')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('renders a Retry button only when onRetry is given', () => {
|
|
70
|
+
const err = shaped('internal server error', 500)
|
|
71
|
+
expect(renderToString(<FetchResult loading={false} error={err} onRetry={() => {}} />)).toContain('Retry')
|
|
72
|
+
expect(renderToString(<FetchResult loading={false} error={err} />)).not.toContain('Retry')
|
|
73
|
+
})
|
|
74
|
+
|
|
57
75
|
it('renders "Sign-in required" on 401', () => {
|
|
58
76
|
const html = renderToString(
|
|
59
77
|
<FetchResult loading={false} error={shaped('Unauthorized', 401)} />,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import { ShieldOff, AlertTriangle, ServerCrash, LogIn, Copy, Check, type LucideIcon } from 'lucide-react'
|
|
2
|
+
import { ShieldOff, AlertTriangle, ServerCrash, LogIn, Copy, Check, RefreshCw, type LucideIcon } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { PaneLoader } from './PaneLoader'
|
|
5
5
|
import { isFetchError } from '../../types/fetch-error'
|
|
@@ -14,6 +14,7 @@ import { isFetchError } from '../../types/fetch-error'
|
|
|
14
14
|
// loading → <PaneLoader/>
|
|
15
15
|
// error (403) → "Access denied" + error.message
|
|
16
16
|
// error (404) → notFoundMessage + error.message
|
|
17
|
+
// error (503 sync failed) → "Couldn't load this view" + error.message
|
|
17
18
|
// error (503) → "Cluster unavailable" + error.message
|
|
18
19
|
// error (401) → "Sign-in required" (apiFetch redirects; fallback)
|
|
19
20
|
// error (other / no shape) → "Couldn't load this view" + error.message
|
|
@@ -34,6 +35,8 @@ interface FetchResultProps {
|
|
|
34
35
|
notFoundMessage?: string
|
|
35
36
|
/** Pin to parent height. Existing call sites use "h-32" or "h-full". */
|
|
36
37
|
className?: string
|
|
38
|
+
/** Renders a Retry button on the error surface. */
|
|
39
|
+
onRetry?: () => void
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export function FetchResult({
|
|
@@ -41,6 +44,7 @@ export function FetchResult({
|
|
|
41
44
|
error,
|
|
42
45
|
notFoundMessage = 'Resource not found',
|
|
43
46
|
className = 'h-32',
|
|
47
|
+
onRetry,
|
|
44
48
|
}: FetchResultProps) {
|
|
45
49
|
if (loading) {
|
|
46
50
|
return <PaneLoader className={className} />
|
|
@@ -55,16 +59,17 @@ export function FetchResult({
|
|
|
55
59
|
</div>
|
|
56
60
|
)
|
|
57
61
|
}
|
|
58
|
-
return <ErrorSurface error={error} notFoundMessage={notFoundMessage} className={className} />
|
|
62
|
+
return <ErrorSurface error={error} notFoundMessage={notFoundMessage} className={className} onRetry={onRetry} />
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
interface ErrorSurfaceProps {
|
|
62
66
|
error: unknown
|
|
63
67
|
notFoundMessage: string
|
|
64
68
|
className: string
|
|
69
|
+
onRetry?: () => void
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
function ErrorSurface({ error, notFoundMessage, className }: ErrorSurfaceProps) {
|
|
72
|
+
function ErrorSurface({ error, notFoundMessage, className, onRetry }: ErrorSurfaceProps) {
|
|
68
73
|
const classified = classify(error, notFoundMessage)
|
|
69
74
|
const Icon = classified.icon
|
|
70
75
|
|
|
@@ -81,6 +86,16 @@ function ErrorSurface({ error, notFoundMessage, className }: ErrorSurfaceProps)
|
|
|
81
86
|
<CopyErrorButton text={classified.detail} />
|
|
82
87
|
</div>
|
|
83
88
|
)}
|
|
89
|
+
{onRetry && (
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
onClick={onRetry}
|
|
93
|
+
className="mt-1 flex items-center gap-1.5 rounded-md border border-theme-border bg-theme-elevated px-3 py-1.5 text-xs text-theme-text-secondary hover:bg-theme-hover hover:text-theme-text-primary"
|
|
94
|
+
>
|
|
95
|
+
<RefreshCw className="h-3.5 w-3.5" aria-hidden />
|
|
96
|
+
Retry
|
|
97
|
+
</button>
|
|
98
|
+
)}
|
|
84
99
|
</div>
|
|
85
100
|
)
|
|
86
101
|
}
|
|
@@ -101,6 +116,17 @@ function classify(error: unknown, notFoundMessage: string): Classified {
|
|
|
101
116
|
case 401:
|
|
102
117
|
return { headline: 'Sign-in required', detail: error.message, icon: LogIn }
|
|
103
118
|
case 503:
|
|
119
|
+
// The cluster is connected; one kind's informer missed its sync
|
|
120
|
+
// deadline, so "Cluster unavailable" would send the reader the wrong way.
|
|
121
|
+
if (errorCodeOf(error) === 'kind_sync_failed') {
|
|
122
|
+
return { headline: "Couldn't load this view", detail: error.message, icon: AlertTriangle }
|
|
123
|
+
}
|
|
124
|
+
// The cluster is healthy and still loading — the retry policies keep
|
|
125
|
+
// these surfaces polling, but a caller without one must not tell the
|
|
126
|
+
// user a connecting cluster is down.
|
|
127
|
+
if (errorCodeOf(error) === 'cluster_connecting' || errorCodeOf(error) === 'kind_sync_pending') {
|
|
128
|
+
return { headline: 'Cluster is still loading', detail: error.message, icon: AlertTriangle }
|
|
129
|
+
}
|
|
104
130
|
return { headline: 'Cluster unavailable', detail: error.message, icon: ServerCrash }
|
|
105
131
|
default:
|
|
106
132
|
return { headline: "Couldn't load this view", detail: error.message, icon: AlertTriangle }
|
|
@@ -110,6 +136,10 @@ function classify(error: unknown, notFoundMessage: string): Classified {
|
|
|
110
136
|
return { headline: "Couldn't load this view", detail: errorMessageOf(error), icon: AlertTriangle }
|
|
111
137
|
}
|
|
112
138
|
|
|
139
|
+
function errorCodeOf(error: unknown): unknown {
|
|
140
|
+
return (error as { data?: { error_code?: unknown } }).data?.error_code
|
|
141
|
+
}
|
|
142
|
+
|
|
113
143
|
function errorMessageOf(error: unknown): string | null {
|
|
114
144
|
if (error instanceof Error && error.message) return error.message
|
|
115
145
|
if (typeof error === 'string') return error
|
|
@@ -6,7 +6,10 @@ import { useRefreshAnimation } from '../../hooks/useRefreshAnimation'
|
|
|
6
6
|
import { formatUpdatedAgo, msToNextBucket } from '../../utils/format'
|
|
7
7
|
|
|
8
8
|
export type FreshnessMode = 'auto' | 'snapshot'
|
|
9
|
-
|
|
9
|
+
// 'syncing' is the progressive first-load shell: the app has a live link to
|
|
10
|
+
// the cluster and serves kinds as their initial sync completes — nothing was
|
|
11
|
+
// ever connected before, so 'Reconnecting…' would be untrue there.
|
|
12
|
+
export type FreshnessConnection = 'connected' | 'disconnected' | 'connecting' | 'syncing'
|
|
10
13
|
|
|
11
14
|
interface FreshnessControlProps {
|
|
12
15
|
// 'auto' — the view keeps itself current (polls or streams). Reads
|
|
@@ -85,7 +88,10 @@ export function FreshnessControl({
|
|
|
85
88
|
let label: string | null
|
|
86
89
|
let tooltip: string | null
|
|
87
90
|
let live = false
|
|
88
|
-
if (
|
|
91
|
+
if (connectionState === 'syncing') {
|
|
92
|
+
label = 'Loading cluster data…'
|
|
93
|
+
tooltip = 'Initial sync in progress — rows shown are live for the kinds that have finished loading.'
|
|
94
|
+
} else if (degraded) {
|
|
89
95
|
label = 'Reconnecting…'
|
|
90
96
|
tooltip = 'Not connected to the cluster — data may be stale until the connection is restored.'
|
|
91
97
|
} else if (mode === 'auto' && paused) {
|
|
@@ -33,6 +33,11 @@ export { InClusterConsentDialog } from './InClusterConsentDialog'
|
|
|
33
33
|
export { ToastProvider, useToast, showApiError, showApiSuccess } from './Toast'
|
|
34
34
|
export { CodeViewer } from './CodeViewer'
|
|
35
35
|
export { YamlEditor, YamlDiffEditor } from './YamlEditor'
|
|
36
|
+
// Boots the self-hosted Monaco runtime (bundled editor, local workers) without
|
|
37
|
+
// pulling monaco-editor into the main bundle. Anything that mounts a bare
|
|
38
|
+
// `Editor` must await this first, or @monaco-editor/react reaches for its CDN.
|
|
39
|
+
export const ensureMonacoRuntime = () =>
|
|
40
|
+
import('./monacoRuntime').then(({ ensureMonaco }) => ensureMonaco())
|
|
36
41
|
export type {
|
|
37
42
|
YamlDiagnostic,
|
|
38
43
|
YamlDocumentIdentity,
|
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
import { loader } from '@monaco-editor/react'
|
|
4
4
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
|
|
5
|
+
// Tokenizers only — the set the YAML editor and the pod file viewer hand to
|
|
6
|
+
// Monaco. JSON deliberately absent: its language lives in vs/language/json and
|
|
7
|
+
// spawns a validation worker this runtime does not ship; viewers highlight
|
|
8
|
+
// JSON with the YAML tokenizer, which covers it.
|
|
5
9
|
import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution'
|
|
10
|
+
import 'monaco-editor/esm/vs/basic-languages/xml/xml.contribution'
|
|
11
|
+
import 'monaco-editor/esm/vs/basic-languages/html/html.contribution'
|
|
12
|
+
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution'
|
|
13
|
+
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution'
|
|
14
|
+
import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution'
|
|
15
|
+
import 'monaco-editor/esm/vs/basic-languages/python/python.contribution'
|
|
16
|
+
import 'monaco-editor/esm/vs/basic-languages/shell/shell.contribution'
|
|
17
|
+
import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution'
|
|
18
|
+
import 'monaco-editor/esm/vs/basic-languages/ini/ini.contribution'
|
|
19
|
+
import 'monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution'
|
|
6
20
|
import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController.js'
|
|
7
21
|
import 'monaco-editor/esm/vs/editor/contrib/folding/browser/folding.js'
|
|
8
22
|
import 'monaco-editor/esm/vs/editor/contrib/format/browser/formatActions.js'
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
import type { TimelineEvent, ResourceRef, Relationships, SelectedResource, ResolvedEnvFrom, Topology, TopologyNode, HPADiagnosis, WorkloadPodInfo } from '../../types'
|
|
37
37
|
import type { GitOpsStatus } from '../../types/gitops'
|
|
38
38
|
import type { NavigateToResource } from '../../utils/navigation'
|
|
39
|
-
import { refToSelectedResource, pluralToKind, kindToPlural, kindToPluralWithGroup, apiVersionToGroup } from '../../utils/navigation'
|
|
39
|
+
import { refToSelectedResource, pluralToKind, knownKindForPluralWithGroup, kindToPlural, kindToPluralWithGroup, apiVersionToGroup } from '../../utils/navigation'
|
|
40
40
|
import { neighborhoodFor, seedNodeIds } from '../../utils/topology-neighborhood'
|
|
41
41
|
import { TopologyGraph } from '../topology/TopologyGraph'
|
|
42
42
|
import { gitOpsOwnerFromRelationships, type GitOpsOwnerRef } from '../../utils/gitops-owner'
|
|
@@ -731,7 +731,7 @@ export function WorkloadView({
|
|
|
731
731
|
| ((ctx: { kind: string; group?: string; namespace: string; name: string; health?: DiagnoseHealthHint }) => ReactNode)
|
|
732
732
|
| undefined
|
|
733
733
|
const diagnoseAction = renderDiagnose?.({
|
|
734
|
-
kind: apiKind,
|
|
734
|
+
kind: resource?.kind ?? knownKindForPluralWithGroup(apiKind, group ?? '') ?? apiKind,
|
|
735
735
|
group,
|
|
736
736
|
namespace,
|
|
737
737
|
name,
|
package/src/monaco-deep.d.ts
CHANGED
|
@@ -3,3 +3,13 @@ declare module 'monaco-editor/esm/vs/editor/editor.api' {
|
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
declare module 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution'
|
|
6
|
+
declare module 'monaco-editor/esm/vs/basic-languages/xml/xml.contribution'
|
|
7
|
+
declare module 'monaco-editor/esm/vs/basic-languages/html/html.contribution'
|
|
8
|
+
declare module 'monaco-editor/esm/vs/basic-languages/css/css.contribution'
|
|
9
|
+
declare module 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution'
|
|
10
|
+
declare module 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution'
|
|
11
|
+
declare module 'monaco-editor/esm/vs/basic-languages/python/python.contribution'
|
|
12
|
+
declare module 'monaco-editor/esm/vs/basic-languages/shell/shell.contribution'
|
|
13
|
+
declare module 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution'
|
|
14
|
+
declare module 'monaco-editor/esm/vs/basic-languages/ini/ini.contribution'
|
|
15
|
+
declare module 'monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution'
|
package/src/theme/components.css
CHANGED
|
@@ -95,6 +95,11 @@
|
|
|
95
95
|
border-style: solid;
|
|
96
96
|
border-color: transparent;
|
|
97
97
|
line-height: 1.25rem;
|
|
98
|
+
/* A badge sits in table cells the reader can drag narrower than the label.
|
|
99
|
+
Without a ceiling it keeps its intrinsic width and draws straight over
|
|
100
|
+
the next column, so two cells become unreadable instead of one. */
|
|
101
|
+
max-width: 100%;
|
|
102
|
+
overflow: hidden;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
.badge-sm {
|
|
@@ -108,6 +113,17 @@
|
|
|
108
113
|
border-style: solid;
|
|
109
114
|
border-color: transparent;
|
|
110
115
|
line-height: 1.25rem;
|
|
116
|
+
max-width: 100%;
|
|
117
|
+
overflow: hidden;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* The icon is the one part of a badge that must never be traded for text:
|
|
121
|
+
a 12px glyph squeezed to 5px reads as a rendering fault, and a severity
|
|
122
|
+
icon that shrinks is worse than one that is cropped away cleanly. Flex
|
|
123
|
+
would otherwise shrink it first, because it is the item with slack. */
|
|
124
|
+
.badge > svg,
|
|
125
|
+
.badge-sm > svg {
|
|
126
|
+
flex-shrink: 0;
|
|
111
127
|
}
|
|
112
128
|
|
|
113
129
|
/* ── INNER CARDS ── */
|
package/src/types/core.ts
CHANGED
|
@@ -516,6 +516,9 @@ export interface ContextInfo {
|
|
|
516
516
|
/** Source kubeconfig label (e.g. "kube-cluster-paris"). Set by the backend
|
|
517
517
|
* for contexts loaded through the isolated source registry. */
|
|
518
518
|
source?: string
|
|
519
|
+
/** AWS profile extracted from the exec plugin's --profile arg or AWS_PROFILE
|
|
520
|
+
* env var. Present only for EKS contexts that pin a profile. */
|
|
521
|
+
awsProfile?: string
|
|
519
522
|
}
|
|
520
523
|
|
|
521
524
|
// Namespace
|
|
@@ -628,6 +631,11 @@ export interface ResourceRef {
|
|
|
628
631
|
|
|
629
632
|
// Computed relationships for a resource
|
|
630
633
|
export interface Relationships {
|
|
634
|
+
reflection?: {
|
|
635
|
+
source?: ResourceRef
|
|
636
|
+
sourceResourceVersion?: string
|
|
637
|
+
mirrors?: ResourceRef[]
|
|
638
|
+
}
|
|
631
639
|
owner?: ResourceRef
|
|
632
640
|
deployment?: ResourceRef // Grandparent Deployment (for Pods owned by ReplicaSets)
|
|
633
641
|
managedBy?: ResourceRef[] // Topmost meaningful manager(s): GitOps controller (ArgoCD Application / Flux Kustomization / Flux HelmRelease), Helm release, or the topmost K8s owner. Synthesized server-side; replaces client-side detectGitOpsOwner.
|
|
@@ -683,24 +691,34 @@ export type HPADiagnosisState =
|
|
|
683
691
|
| 'stabilized'
|
|
684
692
|
| 'unknown'
|
|
685
693
|
|
|
686
|
-
export interface
|
|
694
|
+
export interface HPABounds {
|
|
695
|
+
min: number
|
|
696
|
+
max: number
|
|
697
|
+
current: number
|
|
698
|
+
desired: number
|
|
699
|
+
observedGeneration?: number
|
|
700
|
+
generation?: number
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// The subset of an HPA diagnosis that HPADiagnosisSummary presents. Producers
|
|
704
|
+
// that carry a richer or looser envelope (Radar's own HPADiagnosis, the
|
|
705
|
+
// investigation evidence projection) extend this so the presentation rules —
|
|
706
|
+
// state severity, state label, reason redundancy — cannot drift per surface.
|
|
707
|
+
export interface HPADiagnosisView {
|
|
687
708
|
state: HPADiagnosisState
|
|
688
709
|
summary: string
|
|
710
|
+
bounds?: HPABounds
|
|
711
|
+
reasons?: HPAReasonSummary[]
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
export interface HPADiagnosis extends HPADiagnosisView {
|
|
689
715
|
target: {
|
|
690
716
|
apiVersion?: string
|
|
691
717
|
kind?: string
|
|
692
718
|
name?: string
|
|
693
719
|
}
|
|
694
|
-
bounds:
|
|
695
|
-
min: number
|
|
696
|
-
max: number
|
|
697
|
-
current: number
|
|
698
|
-
desired: number
|
|
699
|
-
observedGeneration?: number
|
|
700
|
-
generation?: number
|
|
701
|
-
}
|
|
720
|
+
bounds: HPABounds
|
|
702
721
|
metrics?: HPAMetricSummary[]
|
|
703
|
-
reasons?: HPAReasonSummary[]
|
|
704
722
|
}
|
|
705
723
|
|
|
706
724
|
export interface HPAReasonSummary {
|
|
@@ -1238,7 +1256,7 @@ export interface MetricsDataPoint {
|
|
|
1238
1256
|
export interface TrafficEndpoint {
|
|
1239
1257
|
name: string
|
|
1240
1258
|
namespace: string
|
|
1241
|
-
kind: string // Pod, Service, External
|
|
1259
|
+
kind: string // Pod, Service, External, Host, Unknown
|
|
1242
1260
|
ip?: string
|
|
1243
1261
|
labels?: Record<string, string>
|
|
1244
1262
|
workload?: string
|
|
@@ -1279,6 +1297,15 @@ export interface TrafficFlow {
|
|
|
1279
1297
|
* status code for no single flow. */
|
|
1280
1298
|
errorRate?: number
|
|
1281
1299
|
verdict: string // forwarded, dropped, error
|
|
1300
|
+
/** The network plugin's own account of which policies decided this flow
|
|
1301
|
+
* (Hubble reports it). Absent when the plugin said nothing. */
|
|
1302
|
+
policyVerdict?: {
|
|
1303
|
+
allowedBy?: { kind: string; namespace?: string; name: string }[]
|
|
1304
|
+
deniedBy?: { kind: string; namespace?: string; name: string }[]
|
|
1305
|
+
/** Denying references removed before delivery because the viewer may not
|
|
1306
|
+
* read policies of that kind there; the plugin still named a policy. */
|
|
1307
|
+
withheld?: number
|
|
1308
|
+
}
|
|
1282
1309
|
lastSeen: string // ISO date string
|
|
1283
1310
|
}
|
|
1284
1311
|
|