@skyhook-io/k8s-ui 1.8.1 → 1.8.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyhook-io/k8s-ui",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/skyhook-io/radar",
@@ -1,4 +1,4 @@
1
- import { provenanceSource, HEALTH_META, type AppWorkload } from '../../utils/applications'
1
+ import { provenanceSource, HEALTH_META, appSourceLabel, isDeclaredAppSource, APP_IDENTITY_ANNOTATION, type AppWorkload } from '../../utils/applications'
2
2
  import { midTruncate } from '../../utils/format'
3
3
 
4
4
  // Structured tooltip content for the application chips. Both render a short
@@ -105,26 +105,53 @@ export function CategoryTooltip({
105
105
  )
106
106
  }
107
107
 
108
- // AppIdentityTooltip — why these instances are grouped. One line per distinct
109
- // evidence in the ProvenanceTooltip idiom (signals as inline-code, long repo
110
- // refs mid-truncated), plus a small flag when the grouping is heuristic-only.
111
- // No member list the env pills next to this chip already show the instances.
108
+ // AppIdentityTooltip — why these instances are grouped, and (in the fleet view)
109
+ // whether the grouping folds across clusters + how to make it. A clear
110
+ // source-driven headline, the per-evidence detail lines, and when `fleet` and
111
+ // the identity isn't a declared cross-cluster origin the canonical "how to
112
+ // fold" answer, so the question and its answer share one surface.
113
+ //
114
+ // `source`/`portable` come from the row's identity; absent (an older host that
115
+ // doesn't pass them) the headline degrades to the evidence lines. `fleet` gates
116
+ // the cross-cluster "how to fold" line — single-cluster (OSS) has no clusters to
117
+ // fold across, so it shows only the grouping evidence.
112
118
  export function AppIdentityTooltip({
113
119
  members,
120
+ source,
121
+ portable,
122
+ fleet,
114
123
  }: {
115
124
  identityKey?: string
116
125
  members: { name: string; env: string; confidence: string; evidence: string }[]
126
+ source?: string
127
+ portable?: boolean
128
+ fleet?: boolean
117
129
  }) {
118
130
  const evidences = Array.from(new Set(members.map((m) => m.evidence).filter(Boolean)))
119
131
  const heuristicOnly = members.every((m) => m.confidence !== 'high')
132
+ // The wire `portable` flag is authoritative; fall back to deriving it from the
133
+ // source when an older host doesn't pass it.
134
+ const folds = portable ?? isDeclaredAppSource(source)
120
135
  return (
121
- <div className="max-w-xs space-y-1">
136
+ <div className="max-w-xs space-y-1.5">
137
+ {source && <div className="text-xs font-medium text-theme-text-primary">Grouped by {appSourceLabel(source)}.</div>}
122
138
  {evidences.map((e, i) => (
123
- <div key={i} className="text-xs text-theme-text-primary">
139
+ <div key={i} className="text-xs text-theme-text-secondary">
124
140
  <EvidenceLine evidence={e} />
125
141
  </div>
126
142
  ))}
127
- {heuristicOnly && (
143
+ {fleet && folds && (
144
+ <div className="text-[10px] uppercase tracking-wide text-theme-text-tertiary">folds across clusters</div>
145
+ )}
146
+ {fleet && source !== undefined && !folds && (
147
+ <div className="space-y-1 border-t border-theme-border pt-1.5">
148
+ <div className="text-[11px] leading-snug text-theme-text-secondary">
149
+ Shown per-cluster — a name can't prove two clusters run the same app. To fold it across clusters, set the same{' '}
150
+ <code className="inline-code">{APP_IDENTITY_ANNOTATION}</code> on its workloads in each cluster, or deploy it via Argo CD / Flux with the environment in the source path.
151
+ </div>
152
+ </div>
153
+ )}
154
+ {!fleet && heuristicOnly && !source && (
128
155
  <div className={`text-[10px] uppercase tracking-wide ${HEALTH_META.degraded.text}`}>heuristic · medium confidence</div>
129
156
  )}
130
157
  </div>
@@ -93,6 +93,13 @@ export type ApplicationDetailProps = {
93
93
  /** Env tokens the cluster proved (the list derives the same set) — keeps a
94
94
  * ungrouped app's Environment fact consistent between list and detail. */
95
95
  discoveredEnvs?: ReadonlySet<string>
96
+ /** Which instance in `identityInstances` is active, by its `appKey`. Defaults
97
+ * to `app.key` — correct for the OSS case, where each instance IS a distinct
98
+ * app row keyed the same way. A host that keys instances on something other
99
+ * than the app key (e.g. the Cloud fleet keys per cluster, while `app.key`
100
+ * must stay the logical app key for provenance) passes the active instance's
101
+ * key here so the switcher marks it. */
102
+ activeInstanceKey?: string
96
103
  } & SelectionProps
97
104
 
98
105
  function ContextFact({ label, children }: { label: string; children: ReactNode }) {
@@ -132,7 +139,7 @@ function compareDefinedVersions(a: string | undefined, b: string | undefined): n
132
139
  return compareVersions(a, b) ?? 0
133
140
  }
134
141
 
135
- export function ApplicationDetail({ app, onBack, renderWorkload, topology, topologyLoading, onNavigateToResource, identityInstances, onSwitchInstance, discoveredEnvs, selectedWorkloadKey, onSelectWorkload }: ApplicationDetailProps) {
142
+ export function ApplicationDetail({ app, onBack, renderWorkload, topology, topologyLoading, onNavigateToResource, identityInstances, onSwitchInstance, discoveredEnvs, activeInstanceKey, selectedWorkloadKey, onSelectWorkload }: ApplicationDetailProps) {
136
143
  // Stable order regardless of API ordering: rail rows and the per-workload
137
144
  // color assignment both follow this array, so an order flap between
138
145
  // refetches must not reshuffle rows or reassign a workload's hue.
@@ -286,7 +293,7 @@ export function ApplicationDetail({ app, onBack, renderWorkload, topology, topol
286
293
  // pills for a handful; a picker beyond that (scales to ~any count).
287
294
  <div className="flex min-w-0 items-center gap-1.5">
288
295
  <span className="text-[10px] uppercase tracking-wide text-theme-text-tertiary">Environment</span>
289
- <EnvSwitcher identityKey={app.identity?.key ?? ''} instances={identityInstances} activeKey={app.key} onSwitch={onSwitchInstance} />
296
+ <EnvSwitcher identityKey={app.identity?.key ?? ''} instances={identityInstances} activeKey={activeInstanceKey ?? app.key} onSwitch={onSwitchInstance} />
290
297
  </div>
291
298
  ) : env ? (
292
299
  <ContextFact label="Environment">