Skip to content

Commit fc3ff94

Browse files
authored
chore: review cleanup follow-up for #699 (#701)
1 parent 2bb099f commit fc3ff94

7 files changed

Lines changed: 29 additions & 32 deletions

File tree

internal/helm/client.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -734,20 +734,19 @@ func parseManifestResources(manifest, defaultNamespace string) []OwnedResource {
734734
manifests := releaseutil.SplitManifests(manifest)
735735

736736
for _, m := range manifests {
737-
// Simple parsing - look for kind, apiVersion, name, and namespace
738737
lines := strings.Split(m, "\n")
739738
var kind, apiVersion, name, namespace string
740739

740+
// Take the first occurrence of each top-level field; nested specs
741+
// (e.g. spec.template) can repeat the same keys with different values.
741742
for _, line := range lines {
742743
line = strings.TrimSpace(line)
743-
if after, ok := strings.CutPrefix(line, "kind:"); ok {
744-
kind = strings.TrimSpace(after)
745-
} else if after, ok := strings.CutPrefix(line, "apiVersion:"); ok {
744+
if after, ok := strings.CutPrefix(line, "kind:"); ok && kind == "" {
745+
kind = strings.Trim(strings.TrimSpace(after), `"'`)
746+
} else if after, ok := strings.CutPrefix(line, "apiVersion:"); ok && apiVersion == "" {
746747
apiVersion = strings.Trim(strings.TrimSpace(after), `"'`)
747748
} else if strings.HasPrefix(line, "name:") && name == "" {
748-
// Only take first name (metadata.name, not container names etc)
749749
name = strings.TrimSpace(strings.TrimPrefix(line, "name:"))
750-
// Remove quotes if present
751750
name = strings.Trim(name, `"'`)
752751
} else if strings.HasPrefix(line, "namespace:") && namespace == "" {
753752
namespace = strings.TrimSpace(strings.TrimPrefix(line, "namespace:"))

internal/timeline/sqlite_store.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,8 @@ func (s *SQLiteStore) initSchema() error {
135135
return err
136136
}
137137

138-
// Additive migration: api_version column added to disambiguate CRD kind collisions
139-
// on navigation. ALTER TABLE on an existing column errors with "duplicate column",
140-
// so detect via PRAGMA before adding.
138+
// SQLite's ALTER TABLE ADD COLUMN errors with "duplicate column" when the
139+
// schema is already current; PRAGMA-detect before adding.
141140
rows, err := s.db.Query("PRAGMA table_info(events)")
142141
if err != nil {
143142
return err

packages/k8s-ui/src/types/core.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ export interface TimelineEvent {
237237

238238
// Resource identity
239239
kind: string
240-
apiVersion?: string // e.g. "apps/v1", "cluster.x-k8s.io/v1beta1" — empty for older backends
240+
apiVersion?: string // e.g. "apps/v1", "cluster.x-k8s.io/v1beta1"
241241
namespace: string
242242
name: string
243243
uid?: string
@@ -508,7 +508,7 @@ export interface ChartDependency {
508508

509509
export interface HelmOwnedResource {
510510
kind: string
511-
apiVersion?: string // e.g. "apps/v1", "cluster.x-k8s.io/v1beta1" — empty for older backends
511+
apiVersion?: string // e.g. "apps/v1", "cluster.x-k8s.io/v1beta1"
512512
name: string
513513
namespace: string
514514
status?: string // Running, Pending, Failed, Active, etc.

packages/k8s-ui/src/utils/resource-hierarchy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ export function buildResourceHierarchy(options: HierarchyOptions): ResourceLane[
232232
childEventCount: 0,
233233
})
234234
} else {
235-
// Older SQLite-stored events may lack apiVersion (column added by migration);
236-
// pick up the group from any subsequent event that carries it.
235+
// Stored events may lack apiVersion; upgrade the lane's group from any
236+
// later event that carries it.
237237
if (!existing.group) {
238238
const fromEvent = apiVersionToGroup(event.apiVersion)
239239
if (fromEvent) existing.group = fromEvent

web/src/App.tsx

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -592,19 +592,11 @@ function AppInner() {
592592
}, forceNamespaceFilter, showPolicyEffect)
593593
const [reconnect, isReconnecting] = useRefreshAnimation(reconnectSSE)
594594

595-
// Engage SSE server-side filtering on large clusters and keep the filter
596-
// in lockstep with the user's pick. Two failure modes this covers:
597-
// * Header switcher: SSE was already filtered (large-cluster picker
598-
// engaged forceNamespaceFilter once); switching namespaces only updated
599-
// `namespaces`, so SSE kept streaming the previous pick → blank graph
600-
// with stale sidebar counts.
601-
// * URL bookmark on a large cluster: forceNamespaceFilter starts
602-
// undefined, SSE opens with no filter, server replies
603-
// requiresNamespaceFilter=true. The LargeClusterPicker fallback only
604-
// renders when `namespaces` is empty, so a deep link with a namespace
605-
// showed "No resources found".
606-
// Small clusters never see requiresNamespaceFilter and leave
607-
// forceNamespaceFilter === undefined — frontend filtering is unaffected.
595+
// On large clusters (where the server requires namespace filtering), keep
596+
// SSE's server-side filter in lockstep with the user's namespace pick.
597+
// Without this, header switches and deep-link loads can leave SSE filtered
598+
// to a stale namespace while sidebar/topology show a different one. Small
599+
// clusters never set forceNamespaceFilter and skip this path entirely.
608600
useEffect(() => {
609601
const isLarge = forceNamespaceFilter !== undefined || topology?.requiresNamespaceFilter === true
610602
if (!isLarge) return

web/src/components/workload/WorkloadView.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,16 @@ export function WorkloadViewRoute({ onNavigateToResource }: WorkloadViewRoutePro
5757
const navigate = useNavigate()
5858
const [searchParams] = useSearchParams()
5959

60-
// Parse /workload/:kind/:ns/:name from pathname
60+
// Parse /workload/:kind/:ns/:name from pathname. Segments are URL-encoded by
61+
// buildWorkloadPath; names can also contain literal slashes (e.g. some CRD names),
62+
// which survive encoding as %2F and reassemble correctly here.
6163
const parts = location.pathname.replace(/^\//, '').split('/')
62-
// parts[0] = 'workload', parts[1] = kind, parts[2] = ns, parts[3+] = name (may contain slashes)
63-
const kind = parts[1] || ''
64-
const namespace = parts[2] || ''
65-
const name = parts.slice(3).join('/') || ''
64+
const decode = (s: string): string => {
65+
try { return decodeURIComponent(s) } catch { return s }
66+
}
67+
const kind = decode(parts[1] ?? '')
68+
const namespace = decode(parts[2] ?? '')
69+
const name = parts.slice(3).map(decode).join('/')
6670
const group = searchParams.get('apiGroup') || ''
6771

6872
if (!kind || !namespace || !name) {

web/src/utils/navigation.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ export type { NavigateToResource } from '@skyhook-io/k8s-ui/utils/navigation'
1010
* query param so the WorkloadView can resolve CRDs with colliding kind names.
1111
*/
1212
export function buildWorkloadPath(resource: SelectedResource): string {
13-
const base = `/workload/${resource.kind}/${resource.namespace}/${resource.name}`
13+
const kind = encodeURIComponent(resource.kind)
14+
const namespace = encodeURIComponent(resource.namespace)
15+
const name = encodeURIComponent(resource.name)
16+
const base = `/workload/${kind}/${namespace}/${name}`
1417
return resource.group ? `${base}?apiGroup=${encodeURIComponent(resource.group)}` : base
1518
}
1619

0 commit comments

Comments
 (0)