@skyhook-io/k8s-ui 1.0.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 +63 -0
- package/src/components/dock/BottomDock.tsx +204 -0
- package/src/components/dock/DockContext.tsx +212 -0
- package/src/components/dock/TerminalTab.tsx +333 -0
- package/src/components/dock/index.ts +3 -0
- package/src/components/gitops/GitOpsActions.tsx +227 -0
- package/src/components/gitops/GitOpsStatusBadge.tsx +221 -0
- package/src/components/gitops/ManagedResourcesList.tsx +210 -0
- package/src/components/gitops/SyncCountdown.tsx +146 -0
- package/src/components/gitops/index.ts +4 -0
- package/src/components/logs/JsonLogLine.tsx +90 -0
- package/src/components/logs/LogCore.tsx +553 -0
- package/src/components/logs/LogToolbarSelects.tsx +74 -0
- package/src/components/logs/LogsViewer.tsx +146 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +263 -0
- package/src/components/logs/index.ts +13 -0
- package/src/components/logs/useLogBuffer.ts +138 -0
- package/src/components/logs/useLogSearch.ts +151 -0
- package/src/components/logs/useLogStream.ts +61 -0
- package/src/components/resources/ResourcesView.tsx +5494 -0
- package/src/components/resources/index.ts +17 -0
- package/src/components/resources/renderers/AlertRenderer.tsx +166 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +233 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +89 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +194 -0
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +79 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +68 -0
- package/src/components/resources/renderers/CertificateRenderer.tsx +209 -0
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +123 -0
- package/src/components/resources/renderers/ChallengeRenderer.tsx +111 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +164 -0
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +140 -0
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +153 -0
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +221 -0
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +124 -0
- package/src/components/resources/renderers/CronJobRenderer.tsx +86 -0
- package/src/components/resources/renderers/EventRenderer.tsx +175 -0
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +119 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +172 -0
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +242 -0
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +263 -0
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +68 -0
- package/src/components/resources/renderers/GatewayRenderer.tsx +218 -0
- package/src/components/resources/renderers/GenericRenderer.tsx +476 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +154 -0
- package/src/components/resources/renderers/HPARenderer.tsx +164 -0
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +303 -0
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +167 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +49 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +104 -0
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +175 -0
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +176 -0
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +125 -0
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +84 -0
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +112 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +288 -0
- package/src/components/resources/renderers/JobRenderer.tsx +103 -0
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +157 -0
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +188 -0
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +148 -0
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +90 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +136 -0
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +151 -0
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +70 -0
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +309 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +142 -0
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +232 -0
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +97 -0
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +74 -0
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +138 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +220 -0
- package/src/components/resources/renderers/KustomizationRenderer.tsx +151 -0
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +352 -0
- package/src/components/resources/renderers/LeaseRenderer.tsx +93 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +208 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +282 -0
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +153 -0
- package/src/components/resources/renderers/OrderRenderer.tsx +108 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +98 -0
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +138 -0
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +126 -0
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +63 -0
- package/src/components/resources/renderers/PodRenderer.tsx +677 -0
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +50 -0
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +62 -0
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +71 -0
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +97 -0
- package/src/components/resources/renderers/RoleRenderer.tsx +192 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +227 -0
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +75 -0
- package/src/components/resources/renderers/SbomReportRenderer.tsx +98 -0
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +121 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +321 -0
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +111 -0
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +81 -0
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +65 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +98 -0
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +195 -0
- package/src/components/resources/renderers/StorageClassRenderer.tsx +82 -0
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +257 -0
- package/src/components/resources/renderers/VPARenderer.tsx +193 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +73 -0
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +193 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +161 -0
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +134 -0
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +36 -0
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +347 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +156 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +202 -0
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +91 -0
- package/src/components/resources/renderers/WorkloadRenderer.tsx +311 -0
- package/src/components/resources/renderers/argo-cells.tsx +118 -0
- package/src/components/resources/renderers/certmanager-cells.tsx +164 -0
- package/src/components/resources/renderers/cnpg-cells.tsx +161 -0
- package/src/components/resources/renderers/eso-cells.tsx +115 -0
- package/src/components/resources/renderers/flux-cells.tsx +253 -0
- package/src/components/resources/renderers/index.ts +105 -0
- package/src/components/resources/renderers/istio-cells.tsx +210 -0
- package/src/components/resources/renderers/karpenter-cells.tsx +107 -0
- package/src/components/resources/renderers/keda-cells.tsx +94 -0
- package/src/components/resources/renderers/knative-cells.tsx +396 -0
- package/src/components/resources/renderers/kyverno-cells.tsx +85 -0
- package/src/components/resources/renderers/prometheus-cells.tsx +74 -0
- package/src/components/resources/renderers/traefik-cells.tsx +120 -0
- package/src/components/resources/renderers/trivy-cells.tsx +153 -0
- package/src/components/resources/renderers/trivy-shared.tsx +67 -0
- package/src/components/resources/renderers/velero-cells.tsx +164 -0
- package/src/components/resources/resource-utils-argo.ts +155 -0
- package/src/components/resources/resource-utils-certmanager.ts +198 -0
- package/src/components/resources/resource-utils-cnpg.ts +344 -0
- package/src/components/resources/resource-utils-eso.ts +385 -0
- package/src/components/resources/resource-utils-flux.ts +186 -0
- package/src/components/resources/resource-utils-istio.ts +372 -0
- package/src/components/resources/resource-utils-karpenter.ts +150 -0
- package/src/components/resources/resource-utils-keda.ts +203 -0
- package/src/components/resources/resource-utils-knative.ts +218 -0
- package/src/components/resources/resource-utils-kyverno.ts +224 -0
- package/src/components/resources/resource-utils-prometheus.ts +159 -0
- package/src/components/resources/resource-utils-traefik.ts +180 -0
- package/src/components/resources/resource-utils-trivy.ts +146 -0
- package/src/components/resources/resource-utils-velero.ts +293 -0
- package/src/components/resources/resource-utils.ts +1558 -0
- package/src/components/shared/EditableYamlView.tsx +302 -0
- package/src/components/shared/ResourceActionsBar.tsx +924 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +557 -0
- package/src/components/shared/index.ts +3 -0
- package/src/components/timeline/index.ts +1 -0
- package/src/components/timeline/shared.tsx +614 -0
- package/src/components/topology/GroupNode.tsx +198 -0
- package/src/components/topology/K8sResourceNode.tsx +440 -0
- package/src/components/topology/TopologyControls.tsx +62 -0
- package/src/components/topology/TopologyFilterSidebar.tsx +377 -0
- package/src/components/topology/TopologyGraph.tsx +748 -0
- package/src/components/topology/TopologySearch.tsx +304 -0
- package/src/components/topology/index.ts +7 -0
- package/src/components/topology/layout.ts +734 -0
- package/src/components/topology/layout.worker.ts +272 -0
- package/src/components/topology/topology.css +129 -0
- package/src/components/ui/CodeViewer.tsx +451 -0
- package/src/components/ui/ConfirmDialog.tsx +188 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +57 -0
- package/src/components/ui/HealthRing.tsx +72 -0
- package/src/components/ui/MetricsChart.tsx +259 -0
- package/src/components/ui/ResourceBar.tsx +68 -0
- package/src/components/ui/Toast.tsx +225 -0
- package/src/components/ui/Tooltip.tsx +165 -0
- package/src/components/ui/YamlEditor.tsx +280 -0
- package/src/components/ui/drawer-components.tsx +792 -0
- package/src/components/ui/index.ts +10 -0
- package/src/components/workload/ResourceDetailDrawer.tsx +158 -0
- package/src/components/workload/WorkloadView.tsx +1109 -0
- package/src/components/workload/index.ts +2 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useAnimatedUnmount.ts +30 -0
- package/src/hooks/useKeyboardShortcuts.tsx +336 -0
- package/src/hooks/useRefreshAnimation.ts +58 -0
- package/src/index.ts +35 -0
- package/src/types/core.ts +894 -0
- package/src/types/gitops.ts +458 -0
- package/src/types/index.ts +2 -0
- package/src/utils/animation.ts +40 -0
- package/src/utils/api-resources.ts +185 -0
- package/src/utils/badge-colors.ts +399 -0
- package/src/utils/context-name.ts +72 -0
- package/src/utils/download.ts +19 -0
- package/src/utils/format.ts +233 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/log-format.ts +300 -0
- package/src/utils/navigation.ts +119 -0
- package/src/utils/resource-hierarchy.ts +630 -0
- package/src/utils/resource-icons.ts +206 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// KEDA cell components for ResourcesView table
|
|
2
|
+
|
|
3
|
+
import { clsx } from 'clsx'
|
|
4
|
+
import {
|
|
5
|
+
getScaledObjectStatus,
|
|
6
|
+
getScaledObjectTarget,
|
|
7
|
+
getScaledObjectReplicas,
|
|
8
|
+
getScaledObjectTriggerTypes,
|
|
9
|
+
getScaledJobStatus,
|
|
10
|
+
getScaledJobTarget,
|
|
11
|
+
getScaledJobStrategy,
|
|
12
|
+
getScaledJobTriggerTypes,
|
|
13
|
+
getTriggerAuthSecretRefCount,
|
|
14
|
+
getTriggerAuthEnvCount,
|
|
15
|
+
getTriggerAuthHasVault,
|
|
16
|
+
} from '../resource-utils-keda'
|
|
17
|
+
|
|
18
|
+
export function ScaledObjectCell({ resource, column }: { resource: any; column: string }) {
|
|
19
|
+
switch (column) {
|
|
20
|
+
case 'status': {
|
|
21
|
+
const status = getScaledObjectStatus(resource)
|
|
22
|
+
return (
|
|
23
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', status.color)}>
|
|
24
|
+
{status.text}
|
|
25
|
+
</span>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
case 'target': {
|
|
29
|
+
const target = getScaledObjectTarget(resource)
|
|
30
|
+
return <span className="text-sm text-theme-text-secondary">{target}</span>
|
|
31
|
+
}
|
|
32
|
+
case 'replicas': {
|
|
33
|
+
const replicas = getScaledObjectReplicas(resource)
|
|
34
|
+
return <span className="text-sm text-theme-text-secondary">{replicas}</span>
|
|
35
|
+
}
|
|
36
|
+
case 'triggerTypes': {
|
|
37
|
+
const types = getScaledObjectTriggerTypes(resource)
|
|
38
|
+
return <span className="text-sm text-theme-text-secondary truncate block">{types}</span>
|
|
39
|
+
}
|
|
40
|
+
default:
|
|
41
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function ScaledJobCell({ resource, column }: { resource: any; column: string }) {
|
|
46
|
+
switch (column) {
|
|
47
|
+
case 'status': {
|
|
48
|
+
const status = getScaledJobStatus(resource)
|
|
49
|
+
return (
|
|
50
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', status.color)}>
|
|
51
|
+
{status.text}
|
|
52
|
+
</span>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
case 'target': {
|
|
56
|
+
const target = getScaledJobTarget(resource)
|
|
57
|
+
return <span className="text-sm text-theme-text-secondary">{target}</span>
|
|
58
|
+
}
|
|
59
|
+
case 'strategy': {
|
|
60
|
+
const strategy = getScaledJobStrategy(resource)
|
|
61
|
+
return <span className="text-sm text-theme-text-secondary">{strategy}</span>
|
|
62
|
+
}
|
|
63
|
+
case 'triggerTypes': {
|
|
64
|
+
const types = getScaledJobTriggerTypes(resource)
|
|
65
|
+
return <span className="text-sm text-theme-text-secondary truncate block">{types}</span>
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function TriggerAuthenticationCell({ resource, column }: { resource: any; column: string }) {
|
|
73
|
+
switch (column) {
|
|
74
|
+
case 'secretTargetRef': {
|
|
75
|
+
const count = getTriggerAuthSecretRefCount(resource)
|
|
76
|
+
return <span className="text-sm text-theme-text-secondary">{count > 0 ? count : '-'}</span>
|
|
77
|
+
}
|
|
78
|
+
case 'env': {
|
|
79
|
+
const count = getTriggerAuthEnvCount(resource)
|
|
80
|
+
return <span className="text-sm text-theme-text-secondary">{count > 0 ? count : '-'}</span>
|
|
81
|
+
}
|
|
82
|
+
case 'hashiCorpVault': {
|
|
83
|
+
const hasVault = getTriggerAuthHasVault(resource)
|
|
84
|
+
return <span className="text-sm text-theme-text-secondary">{hasVault ? 'Yes' : '-'}</span>
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function ClusterTriggerAuthenticationCell({ resource, column }: { resource: any; column: string }) {
|
|
92
|
+
// Same rendering logic as TriggerAuthentication
|
|
93
|
+
return <TriggerAuthenticationCell resource={resource} column={column} />
|
|
94
|
+
}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
// KNative cell components for ResourcesView table
|
|
2
|
+
|
|
3
|
+
import { clsx } from 'clsx'
|
|
4
|
+
import {
|
|
5
|
+
getKnativeConditionStatus,
|
|
6
|
+
getKnativeServiceUrl,
|
|
7
|
+
getKnativeServiceLatestRevision,
|
|
8
|
+
getKnativeServiceTraffic,
|
|
9
|
+
getRevisionStatus,
|
|
10
|
+
getRevisionImage,
|
|
11
|
+
getRevisionConcurrency,
|
|
12
|
+
getRouteUrl,
|
|
13
|
+
getRouteTraffic,
|
|
14
|
+
getConfigurationLatestCreated,
|
|
15
|
+
getConfigurationLatestReady,
|
|
16
|
+
getBrokerAddress,
|
|
17
|
+
getTriggerBroker,
|
|
18
|
+
getTriggerSubscriber,
|
|
19
|
+
getTriggerFilter,
|
|
20
|
+
getSourceSink,
|
|
21
|
+
getPingSourceSchedule,
|
|
22
|
+
getPingSourceData,
|
|
23
|
+
getChannelAddress,
|
|
24
|
+
getSubscriptionChannel,
|
|
25
|
+
getSubscriptionSubscriber,
|
|
26
|
+
getSequenceStepCount,
|
|
27
|
+
getParallelBranchCount,
|
|
28
|
+
getDomainMappingUrl,
|
|
29
|
+
getServerlessServiceMode,
|
|
30
|
+
} from '../resource-utils-knative'
|
|
31
|
+
|
|
32
|
+
function StatusCell({ resource, getStatus }: { resource: any; getStatus: (r: any) => { text: string; color: string } }) {
|
|
33
|
+
const status = getStatus(resource)
|
|
34
|
+
return (
|
|
35
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', status.color)}>
|
|
36
|
+
{status.text}
|
|
37
|
+
</span>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function TextCell({ value }: { value: string }) {
|
|
42
|
+
return <span className="text-sm text-theme-text-secondary truncate block">{value}</span>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function NumberCell({ value }: { value: number }) {
|
|
46
|
+
return <span className="text-sm text-theme-text-secondary">{value}</span>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function DefaultCell() {
|
|
50
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// KNATIVE SERVICE
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
export function KnativeServiceCell({ resource, column }: { resource: any; column: string }) {
|
|
58
|
+
switch (column) {
|
|
59
|
+
case 'status':
|
|
60
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
61
|
+
case 'url':
|
|
62
|
+
return <TextCell value={getKnativeServiceUrl(resource)} />
|
|
63
|
+
case 'latestRevision':
|
|
64
|
+
return <TextCell value={getKnativeServiceLatestRevision(resource)} />
|
|
65
|
+
case 'traffic':
|
|
66
|
+
return <TextCell value={getKnativeServiceTraffic(resource)} />
|
|
67
|
+
default:
|
|
68
|
+
return <DefaultCell />
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// REVISION
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
export function RevisionCell({ resource, column }: { resource: any; column: string }) {
|
|
77
|
+
switch (column) {
|
|
78
|
+
case 'status':
|
|
79
|
+
return <StatusCell resource={resource} getStatus={getRevisionStatus} />
|
|
80
|
+
case 'routing': {
|
|
81
|
+
const state = resource?.metadata?.labels?.['serving.knative.dev/routingState'] || ''
|
|
82
|
+
if (state === 'active') return <span className="text-green-400 text-sm">Active</span>
|
|
83
|
+
if (state === 'reserve') return <span className="text-theme-text-tertiary text-sm">Reserve</span>
|
|
84
|
+
return <TextCell value={state || '-'} />
|
|
85
|
+
}
|
|
86
|
+
case 'image':
|
|
87
|
+
return <TextCell value={getRevisionImage(resource)} />
|
|
88
|
+
case 'concurrency':
|
|
89
|
+
return <TextCell value={getRevisionConcurrency(resource)} />
|
|
90
|
+
default:
|
|
91
|
+
return <DefaultCell />
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// ROUTE
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
export function RouteCell({ resource, column }: { resource: any; column: string }) {
|
|
100
|
+
switch (column) {
|
|
101
|
+
case 'status':
|
|
102
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
103
|
+
case 'url':
|
|
104
|
+
return <TextCell value={getRouteUrl(resource)} />
|
|
105
|
+
case 'traffic':
|
|
106
|
+
return <TextCell value={getRouteTraffic(resource)} />
|
|
107
|
+
default:
|
|
108
|
+
return <DefaultCell />
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// CONFIGURATION
|
|
114
|
+
// ============================================================================
|
|
115
|
+
|
|
116
|
+
export function ConfigurationCell({ resource, column }: { resource: any; column: string }) {
|
|
117
|
+
switch (column) {
|
|
118
|
+
case 'status':
|
|
119
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
120
|
+
case 'latestCreated':
|
|
121
|
+
return <TextCell value={getConfigurationLatestCreated(resource)} />
|
|
122
|
+
case 'latestReady':
|
|
123
|
+
return <TextCell value={getConfigurationLatestReady(resource)} />
|
|
124
|
+
default:
|
|
125
|
+
return <DefaultCell />
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// BROKER
|
|
131
|
+
// ============================================================================
|
|
132
|
+
|
|
133
|
+
export function BrokerCell({ resource, column }: { resource: any; column: string }) {
|
|
134
|
+
switch (column) {
|
|
135
|
+
case 'status':
|
|
136
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
137
|
+
case 'address':
|
|
138
|
+
return <TextCell value={getBrokerAddress(resource)} />
|
|
139
|
+
default:
|
|
140
|
+
return <DefaultCell />
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// TRIGGER
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
export function TriggerCell({ resource, column }: { resource: any; column: string }) {
|
|
149
|
+
switch (column) {
|
|
150
|
+
case 'status':
|
|
151
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
152
|
+
case 'broker':
|
|
153
|
+
return <TextCell value={getTriggerBroker(resource)} />
|
|
154
|
+
case 'subscriber':
|
|
155
|
+
return <TextCell value={getTriggerSubscriber(resource)} />
|
|
156
|
+
case 'filter':
|
|
157
|
+
return <TextCell value={getTriggerFilter(resource)} />
|
|
158
|
+
default:
|
|
159
|
+
return <DefaultCell />
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// EVENTTYPE
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
export function EventTypeCell({ resource, column }: { resource: any; column: string }) {
|
|
168
|
+
switch (column) {
|
|
169
|
+
case 'type':
|
|
170
|
+
return <TextCell value={resource?.spec?.type || '-'} />
|
|
171
|
+
case 'source':
|
|
172
|
+
return <TextCell value={resource?.spec?.source || '-'} />
|
|
173
|
+
case 'reference': {
|
|
174
|
+
const ref = resource?.spec?.reference
|
|
175
|
+
if (ref) return <TextCell value={`${ref.kind || ''}/${ref.name || ''}`} />
|
|
176
|
+
const broker = resource?.spec?.broker
|
|
177
|
+
if (broker) return <TextCell value={`Broker/${broker}`} />
|
|
178
|
+
return <DefaultCell />
|
|
179
|
+
}
|
|
180
|
+
default:
|
|
181
|
+
return <DefaultCell />
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// PING SOURCE
|
|
187
|
+
// ============================================================================
|
|
188
|
+
|
|
189
|
+
export function PingSourceCell({ resource, column }: { resource: any; column: string }) {
|
|
190
|
+
switch (column) {
|
|
191
|
+
case 'status':
|
|
192
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
193
|
+
case 'schedule':
|
|
194
|
+
return <TextCell value={getPingSourceSchedule(resource)} />
|
|
195
|
+
case 'sink':
|
|
196
|
+
return <TextCell value={getSourceSink(resource)} />
|
|
197
|
+
case 'data':
|
|
198
|
+
return <TextCell value={getPingSourceData(resource)} />
|
|
199
|
+
default:
|
|
200
|
+
return <DefaultCell />
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// APISERVERSOURCE / CONTAINERSOURCE (identical columns: status + sink)
|
|
206
|
+
// ============================================================================
|
|
207
|
+
|
|
208
|
+
function SourceCell({ resource, column }: { resource: any; column: string }) {
|
|
209
|
+
switch (column) {
|
|
210
|
+
case 'status':
|
|
211
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
212
|
+
case 'sink':
|
|
213
|
+
return <TextCell value={getSourceSink(resource)} />
|
|
214
|
+
default:
|
|
215
|
+
return <DefaultCell />
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export { SourceCell as ApiServerSourceCell, SourceCell as ContainerSourceCell }
|
|
220
|
+
|
|
221
|
+
// ============================================================================
|
|
222
|
+
// SINKBINDING
|
|
223
|
+
// ============================================================================
|
|
224
|
+
|
|
225
|
+
export function SinkBindingCell({ resource, column }: { resource: any; column: string }) {
|
|
226
|
+
switch (column) {
|
|
227
|
+
case 'status':
|
|
228
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
229
|
+
case 'sink':
|
|
230
|
+
return <TextCell value={getSourceSink(resource)} />
|
|
231
|
+
case 'subject': {
|
|
232
|
+
const subject = resource?.spec?.subject
|
|
233
|
+
if (!subject) return <TextCell value="-" />
|
|
234
|
+
if (subject.name) return <TextCell value={`${subject.kind || ''}/${subject.name}`} />
|
|
235
|
+
if (subject.selector?.matchLabels) {
|
|
236
|
+
const labels = Object.entries(subject.selector.matchLabels).map(([k, v]) => `${k}=${v}`).join(', ')
|
|
237
|
+
return <TextCell value={`${subject.kind || ''} (${labels})`} />
|
|
238
|
+
}
|
|
239
|
+
return <TextCell value={subject.kind || '-'} />
|
|
240
|
+
}
|
|
241
|
+
default:
|
|
242
|
+
return <DefaultCell />
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ============================================================================
|
|
247
|
+
// CHANNEL / INMEMORYCHANNEL (identical columns: status + address)
|
|
248
|
+
// ============================================================================
|
|
249
|
+
|
|
250
|
+
function ChannelCellBase({ resource, column }: { resource: any; column: string }) {
|
|
251
|
+
switch (column) {
|
|
252
|
+
case 'status':
|
|
253
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
254
|
+
case 'address':
|
|
255
|
+
return <TextCell value={getChannelAddress(resource)} />
|
|
256
|
+
default:
|
|
257
|
+
return <DefaultCell />
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export { ChannelCellBase as ChannelCell, ChannelCellBase as InMemoryChannelCell }
|
|
262
|
+
|
|
263
|
+
// ============================================================================
|
|
264
|
+
// SUBSCRIPTION
|
|
265
|
+
// ============================================================================
|
|
266
|
+
|
|
267
|
+
export function SubscriptionCell({ resource, column }: { resource: any; column: string }) {
|
|
268
|
+
switch (column) {
|
|
269
|
+
case 'status':
|
|
270
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
271
|
+
case 'channel':
|
|
272
|
+
return <TextCell value={getSubscriptionChannel(resource)} />
|
|
273
|
+
case 'subscriber':
|
|
274
|
+
return <TextCell value={getSubscriptionSubscriber(resource)} />
|
|
275
|
+
default:
|
|
276
|
+
return <DefaultCell />
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// SEQUENCE
|
|
282
|
+
// ============================================================================
|
|
283
|
+
|
|
284
|
+
export function SequenceCell({ resource, column }: { resource: any; column: string }) {
|
|
285
|
+
switch (column) {
|
|
286
|
+
case 'status':
|
|
287
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
288
|
+
case 'steps':
|
|
289
|
+
return <NumberCell value={getSequenceStepCount(resource)} />
|
|
290
|
+
default:
|
|
291
|
+
return <DefaultCell />
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ============================================================================
|
|
296
|
+
// PARALLEL
|
|
297
|
+
// ============================================================================
|
|
298
|
+
|
|
299
|
+
export function ParallelCell({ resource, column }: { resource: any; column: string }) {
|
|
300
|
+
switch (column) {
|
|
301
|
+
case 'status':
|
|
302
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
303
|
+
case 'branches':
|
|
304
|
+
return <NumberCell value={getParallelBranchCount(resource)} />
|
|
305
|
+
default:
|
|
306
|
+
return <DefaultCell />
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ============================================================================
|
|
311
|
+
// DOMAINMAPPING
|
|
312
|
+
// ============================================================================
|
|
313
|
+
|
|
314
|
+
export function DomainMappingCell({ resource, column }: { resource: any; column: string }) {
|
|
315
|
+
switch (column) {
|
|
316
|
+
case 'status':
|
|
317
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
318
|
+
case 'url':
|
|
319
|
+
return <TextCell value={getDomainMappingUrl(resource)} />
|
|
320
|
+
default:
|
|
321
|
+
return <DefaultCell />
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// KNATIVE INGRESS
|
|
327
|
+
// ============================================================================
|
|
328
|
+
|
|
329
|
+
export function KnativeIngressCell({ resource, column }: { resource: any; column: string }) {
|
|
330
|
+
switch (column) {
|
|
331
|
+
case 'status':
|
|
332
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
333
|
+
case 'ingressClass': {
|
|
334
|
+
const cls = resource?.metadata?.annotations?.['networking.knative.dev/ingress.class'] || '-'
|
|
335
|
+
// Show just the short name (e.g., "kourier" from "kourier.ingress.networking.knative.dev")
|
|
336
|
+
const short = cls.includes('.') ? cls.split('.')[0] : cls
|
|
337
|
+
return <TextCell value={short} />
|
|
338
|
+
}
|
|
339
|
+
case 'hosts': {
|
|
340
|
+
const rules = resource?.spec?.rules || []
|
|
341
|
+
const allHosts = rules.flatMap((r: any) => r.hosts || [])
|
|
342
|
+
const unique = [...new Set(allHosts)]
|
|
343
|
+
return <TextCell value={unique.length > 0 ? unique.join(', ') : '-'} />
|
|
344
|
+
}
|
|
345
|
+
case 'visibility': {
|
|
346
|
+
const rules = resource?.spec?.rules || []
|
|
347
|
+
const vis = rules[0]?.visibility || '-'
|
|
348
|
+
return <TextCell value={vis} />
|
|
349
|
+
}
|
|
350
|
+
default:
|
|
351
|
+
return <DefaultCell />
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ============================================================================
|
|
356
|
+
// KNATIVE CERTIFICATE
|
|
357
|
+
// ============================================================================
|
|
358
|
+
|
|
359
|
+
export function KnativeCertificateCell({ resource, column }: { resource: any; column: string }) {
|
|
360
|
+
switch (column) {
|
|
361
|
+
case 'status':
|
|
362
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
363
|
+
case 'dnsNames': {
|
|
364
|
+
const names = resource?.spec?.dnsNames || []
|
|
365
|
+
return <TextCell value={names.length > 0 ? names.join(', ') : '-'} />
|
|
366
|
+
}
|
|
367
|
+
case 'secretName':
|
|
368
|
+
return <TextCell value={resource?.spec?.secretName || '-'} />
|
|
369
|
+
default:
|
|
370
|
+
return <DefaultCell />
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ============================================================================
|
|
375
|
+
// SERVERLESSSERVICE
|
|
376
|
+
// ============================================================================
|
|
377
|
+
|
|
378
|
+
export function ServerlessServiceCell({ resource, column }: { resource: any; column: string }) {
|
|
379
|
+
switch (column) {
|
|
380
|
+
case 'status':
|
|
381
|
+
return <StatusCell resource={resource} getStatus={getKnativeConditionStatus} />
|
|
382
|
+
case 'mode': {
|
|
383
|
+
const mode = getServerlessServiceMode(resource)
|
|
384
|
+
return (
|
|
385
|
+
<span className={clsx(
|
|
386
|
+
'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium',
|
|
387
|
+
mode === 'Proxy' ? 'bg-yellow-500/20 text-yellow-400' : 'bg-green-500/20 text-green-400'
|
|
388
|
+
)}>
|
|
389
|
+
{mode}
|
|
390
|
+
</span>
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
default:
|
|
394
|
+
return <DefaultCell />
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Kyverno / Policy Report cell components for ResourcesView table
|
|
2
|
+
|
|
3
|
+
import { clsx } from 'clsx'
|
|
4
|
+
import {
|
|
5
|
+
getPolicyReportStatus,
|
|
6
|
+
getPolicyReportSummary,
|
|
7
|
+
getKyvernoPolicyStatus,
|
|
8
|
+
getKyvernoPolicyAction,
|
|
9
|
+
getKyvernoPolicyRuleCount,
|
|
10
|
+
} from '../resource-utils-kyverno'
|
|
11
|
+
|
|
12
|
+
export function PolicyReportCell({ resource, column }: { resource: any; column: string }) {
|
|
13
|
+
switch (column) {
|
|
14
|
+
case 'status': {
|
|
15
|
+
const status = getPolicyReportStatus(resource)
|
|
16
|
+
return (
|
|
17
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', status.color)}>
|
|
18
|
+
{status.text}
|
|
19
|
+
</span>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
case 'pass': {
|
|
23
|
+
const summary = getPolicyReportSummary(resource)
|
|
24
|
+
return <span className={clsx('text-sm', summary.pass > 0 ? 'text-green-400' : 'text-theme-text-tertiary')}>{summary.pass}</span>
|
|
25
|
+
}
|
|
26
|
+
case 'fail': {
|
|
27
|
+
const summary = getPolicyReportSummary(resource)
|
|
28
|
+
return <span className={clsx('text-sm', summary.fail > 0 ? 'text-red-400 font-medium' : 'text-theme-text-tertiary')}>{summary.fail}</span>
|
|
29
|
+
}
|
|
30
|
+
case 'warn': {
|
|
31
|
+
const summary = getPolicyReportSummary(resource)
|
|
32
|
+
return <span className={clsx('text-sm', summary.warn > 0 ? 'text-yellow-400' : 'text-theme-text-tertiary')}>{summary.warn}</span>
|
|
33
|
+
}
|
|
34
|
+
case 'error': {
|
|
35
|
+
const summary = getPolicyReportSummary(resource)
|
|
36
|
+
return <span className={clsx('text-sm', summary.error > 0 ? 'text-red-400 font-medium' : 'text-theme-text-tertiary')}>{summary.error}</span>
|
|
37
|
+
}
|
|
38
|
+
case 'skip': {
|
|
39
|
+
const summary = getPolicyReportSummary(resource)
|
|
40
|
+
return <span className={clsx('text-sm', summary.skip > 0 ? 'text-blue-400' : 'text-theme-text-tertiary')}>{summary.skip}</span>
|
|
41
|
+
}
|
|
42
|
+
default:
|
|
43
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ClusterPolicyReportCell({ resource, column }: { resource: any; column: string }) {
|
|
48
|
+
// Same rendering logic as PolicyReport
|
|
49
|
+
return <PolicyReportCell resource={resource} column={column} />
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function KyvernoPolicyCell({ resource, column }: { resource: any; column: string }) {
|
|
53
|
+
switch (column) {
|
|
54
|
+
case 'status': {
|
|
55
|
+
const status = getKyvernoPolicyStatus(resource)
|
|
56
|
+
return (
|
|
57
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', status.color)}>
|
|
58
|
+
{status.text}
|
|
59
|
+
</span>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
case 'action': {
|
|
63
|
+
const action = getKyvernoPolicyAction(resource)
|
|
64
|
+
return (
|
|
65
|
+
<span className={clsx(
|
|
66
|
+
'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium',
|
|
67
|
+
action === 'Enforce' ? 'bg-red-500/20 text-red-400' : 'bg-yellow-500/20 text-yellow-400',
|
|
68
|
+
)}>
|
|
69
|
+
{action}
|
|
70
|
+
</span>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
case 'rules': {
|
|
74
|
+
const count = getKyvernoPolicyRuleCount(resource)
|
|
75
|
+
return <span className="text-sm text-theme-text-secondary">{count}</span>
|
|
76
|
+
}
|
|
77
|
+
default:
|
|
78
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function ClusterPolicyCell({ resource, column }: { resource: any; column: string }) {
|
|
83
|
+
// Same rendering logic as KyvernoPolicy
|
|
84
|
+
return <KyvernoPolicyCell resource={resource} column={column} />
|
|
85
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Prometheus Operator cell components for ResourcesView table
|
|
2
|
+
|
|
3
|
+
import { clsx } from 'clsx'
|
|
4
|
+
import {
|
|
5
|
+
getServiceMonitorStatus,
|
|
6
|
+
getServiceMonitorEndpointCount,
|
|
7
|
+
getServiceMonitorJobLabel,
|
|
8
|
+
getServiceMonitorSelector,
|
|
9
|
+
getPrometheusRuleStatus,
|
|
10
|
+
getPrometheusRuleGroupCount,
|
|
11
|
+
getPrometheusRuleTotalRules,
|
|
12
|
+
getPodMonitorStatus,
|
|
13
|
+
getPodMonitorEndpointCount,
|
|
14
|
+
getPodMonitorSelector,
|
|
15
|
+
} from '../resource-utils-prometheus'
|
|
16
|
+
|
|
17
|
+
export function ServiceMonitorCell({ resource, column }: { resource: any; column: string }) {
|
|
18
|
+
switch (column) {
|
|
19
|
+
case 'status': {
|
|
20
|
+
const status = getServiceMonitorStatus(resource)
|
|
21
|
+
return (
|
|
22
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', status.color)}>
|
|
23
|
+
{status.text}
|
|
24
|
+
</span>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
case 'endpoints':
|
|
28
|
+
return <span className="text-sm text-theme-text-secondary">{getServiceMonitorEndpointCount(resource)}</span>
|
|
29
|
+
case 'jobLabel':
|
|
30
|
+
return <span className="text-sm text-theme-text-secondary truncate block">{getServiceMonitorJobLabel(resource)}</span>
|
|
31
|
+
case 'selector':
|
|
32
|
+
return <span className="text-sm text-theme-text-secondary truncate block">{getServiceMonitorSelector(resource)}</span>
|
|
33
|
+
default:
|
|
34
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function PrometheusRuleCell({ resource, column }: { resource: any; column: string }) {
|
|
39
|
+
switch (column) {
|
|
40
|
+
case 'status': {
|
|
41
|
+
const status = getPrometheusRuleStatus(resource)
|
|
42
|
+
return (
|
|
43
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', status.color)}>
|
|
44
|
+
{status.text}
|
|
45
|
+
</span>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
case 'groups':
|
|
49
|
+
return <span className="text-sm text-theme-text-secondary">{getPrometheusRuleGroupCount(resource)}</span>
|
|
50
|
+
case 'rules':
|
|
51
|
+
return <span className="text-sm text-theme-text-secondary">{getPrometheusRuleTotalRules(resource)}</span>
|
|
52
|
+
default:
|
|
53
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function PodMonitorCell({ resource, column }: { resource: any; column: string }) {
|
|
58
|
+
switch (column) {
|
|
59
|
+
case 'status': {
|
|
60
|
+
const status = getPodMonitorStatus(resource)
|
|
61
|
+
return (
|
|
62
|
+
<span className={clsx('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', status.color)}>
|
|
63
|
+
{status.text}
|
|
64
|
+
</span>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
case 'endpoints':
|
|
68
|
+
return <span className="text-sm text-theme-text-secondary">{getPodMonitorEndpointCount(resource)}</span>
|
|
69
|
+
case 'selector':
|
|
70
|
+
return <span className="text-sm text-theme-text-secondary truncate block">{getPodMonitorSelector(resource)}</span>
|
|
71
|
+
default:
|
|
72
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
73
|
+
}
|
|
74
|
+
}
|