Repository navigation
Add terminal exec and port forwarding features - #2
Merged
Merged
Conversation
- Add internal/k8s/update.go with UpdateResource and DeleteResource functions using dynamic K8s client - Add YamlEditor component with Monaco editor, YAML validation, and inline highlighting for Pod editable fields
Adds two major interactive features to Explorer: 1. **Terminal Exec**: Open a shell session into any running container directly from the UI. Uses WebSocket to proxy kubectl exec with full terminal emulation via xterm.js. Supports multiple concurrent sessions with a tabbed dock interface that preserves state when switching tabs. 2. **Port Forwarding**: Forward container ports to localhost with one click. Includes a port forward manager in the UI header showing all active forwards with status indicators and easy cleanup. Backend changes: - exec.go: WebSocket handler for kubectl exec with PTY support - portforward.go: HTTP handlers for starting/stopping/listing port forwards - server.go: Register new routes and CORS for WebSocket Frontend changes: - New dock system (BottomDock, DockContext, TerminalTab, LogsTab) - Port forward components (PortForwardButton, PortForwardManager) - Updated PodRenderer with terminal/logs buttons per container - Updated ServiceRenderer with port forward buttons - xterm.js integration with FitAddon for responsive terminals Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
3 of 4 tasks
nadaverell
added a commit
that referenced
this pull request
May 2, 2026
…#570) * fix(ui): keep tooltips anchored to their trigger and dismiss on click Three reported tooltip bugs on app.radarhq.io that share a single root cause in the shared Tooltip primitive (SKY-818): 1. Hovering a nav button (Home, Topology, Resources, Timeline, Helm, Traffic, Audit) flashed the tooltip text at the viewport's top-left (0, 0) before snapping into place, overlapping the "Radar / By Skyhook" logo. On Timeline the logo literally rendered as "adar" because "Timeline" painted over the "R". 2. Clicking a pod row left the row's full-name tooltip pinned above the table header, obscuring the column headers after the cursor moved away. 3. Clicking a tab pill left the tab's tooltip stuck under the active pill instead of dismissing on click. Root causes: - The portal's initial render used `top: 0; left: 0` because position state was seeded with zeros and only updated one rAF later. On slower clients (or whenever the requestAnimationFrame callback was delayed by layout work) that single frame painted at the viewport origin was visible to the user. - Dismissal relied on `mouseleave` and on the trigger unmounting, but for nav buttons / tab pills the click triggers a route change without unmounting the trigger, and the cursor stays over it — so neither path fires and the tooltip lingers. Fix: - Extract the position math into a pure helper `computeTooltipPosition` with full unit coverage (`tooltip-position.ts` + `.test.ts`). It returns `null` when the trigger has no layout, so we never show coords we can't trust. - Track coords as `{ top, left } | null`. While null, render the portal with `visibility: hidden` and `aria-hidden`, so no (0, 0) frame is ever visible. - Two-pass measurement: rAF #1 captures the trigger rect with the unmeasured tooltip (size 0); rAF #2 re-runs with the now-known tooltip size for exact centering. Cleaned up via the existing rafRef so HMR / rapid re-renders can't leak frames. - Add `onPointerDown` on the wrapper that fires before any child onClick, guaranteeing the tooltip is hidden before nav/tab/route-change actions run. Fixes bugs 2 and 3. - Add window-level `popstate` and Escape listeners while a tooltip is visible, as belt-and-braces for in-app navigation that doesn't go through pointerdown on the trigger (e.g. keyboard shortcuts). No behavior change for the singleton coordinator, the disabled flow, or the public `Tooltip` / `WithTooltip` API. Linear: SKY-818 Made-with: Cursor * review: trim bug-narrative comments from tooltip primitive No behavior change — keeps the load-bearing WHYs (popstate dismissal mechanism, second-pass rAF, visibility:hidden guard) but drops the incident-history framing that would rot. --------- Co-authored-by: eliran-mic <eliran.mic@gmail.com> Co-authored-by: Nadav Erell <nadaverell@gmail.com>
nadaverell
added a commit
that referenced
this pull request
May 18, 2026
Pod/Workload drawer was placing the new "Permissions via ServiceAccount" section at position #2 — right under Status, before Containers and Resource Usage. That's incident/audit framing on a daily-browsing surface. Operators opening a Pod want to see what's running and how it's behaving; "what could an attacker do with this SA" comes after. Move it below the diagnostic block (Status → Containers → Resource Usage → Conditions). Combined with the earlier collapse-by-default change (auto-expands only when blast-radius detected), the section now stays out of the way unless it's actually load-bearing for the operator's task. Same move in PodRenderer and WorkloadRenderer.
nadaverell
added a commit
that referenced
this pull request
May 19, 2026
Two follow-ups from #655 on per-user RBAC visual test: 1. Topology preview card on Home: the SSE topology stream is cluster- wide for small/medium clusters; only the dashboard summary is namespace-filtered server-side. That left the kind legend showing cluster-wide totals while the "<X> resources · <Y> conn" header had already narrowed to the active namespace. Filter the topology client-side in HomeView before passing it to TopologyPreview (and ActivitySummary) so the legend matches the title. No-op on large clusters where forceNamespaceFilter already filters SSE server-side. 2. Cluster info card "0 namespaces" for restricted users: dashboard.go only populated resourceCounts.Namespaces when the user could list namespaces cluster-wide, leaving a misleading "0" for users with a restricted namespace view. Fall back to len(allowedNamespaces), mirroring the same pattern in resource_counts.go. Item #2 from the issue (sidebar count "1" vs page "No Namespace found" asymmetry) is left as-is — genuine product call, neither option is obviously better than the other.
nadaverell
added a commit
that referenced
this pull request
May 29, 2026
…aps, CRD noise #1 detector monotonicity (internal/k8s/health.go): classify crashloop from RestartCount + LastTerminationState, stable across Waiting->Running->Waiting so the category-hashed issue_id stops churning. #2 suppress parent workload_degraded/rollout_stalled when an equal-or-worse child symptom exists on the same subject — severity-gated so a critical rollup is never downgraded to a warning child. #3 PVC Lost + Job/CronJob failures classified (were discarded to unknown). #5 CRD-condition noise floor: transient-aware via shared packages.IsTransientConditionReason (one source of truth with the GitOps path). + table tests. Verified: go build/vet/test green (internal/issues, internal/k8s, pkg/packages).
nadaverell
added a commit
that referenced
this pull request
May 30, 2026
…aps, CRD noise #1 detector monotonicity (internal/k8s/health.go): classify crashloop from RestartCount + LastTerminationState, stable across Waiting->Running->Waiting so the category-hashed issue_id stops churning. #2 suppress parent workload_degraded/rollout_stalled when an equal-or-worse child symptom exists on the same subject — severity-gated so a critical rollup is never downgraded to a warning child. #3 PVC Lost + Job/CronJob failures classified (were discarded to unknown). #5 CRD-condition noise floor: transient-aware via shared packages.IsTransientConditionReason (one source of truth with the GitOps path). + table tests. Verified: go build/vet/test green (internal/issues, internal/k8s, pkg/packages).
nadaverell
added a commit
that referenced
this pull request
May 30, 2026
…aps, CRD noise #1 detector monotonicity (internal/k8s/health.go): classify crashloop from RestartCount + LastTerminationState, stable across Waiting->Running->Waiting so the category-hashed issue_id stops churning. #2 suppress parent workload_degraded/rollout_stalled when an equal-or-worse child symptom exists on the same subject — severity-gated so a critical rollup is never downgraded to a warning child. #3 PVC Lost + Job/CronJob failures classified (were discarded to unknown). #5 CRD-condition noise floor: transient-aware via shared packages.IsTransientConditionReason (one source of truth with the GitOps path). + table tests. Verified: go build/vet/test green (internal/issues, internal/k8s, pkg/packages).
nadaverell
added a commit
that referenced
this pull request
May 30, 2026
…aps, CRD noise #1 detector monotonicity (internal/k8s/health.go): classify crashloop from RestartCount + LastTerminationState, stable across Waiting->Running->Waiting so the category-hashed issue_id stops churning. #2 suppress parent workload_degraded/rollout_stalled when an equal-or-worse child symptom exists on the same subject — severity-gated so a critical rollup is never downgraded to a warning child. #3 PVC Lost + Job/CronJob failures classified (were discarded to unknown). #5 CRD-condition noise floor: transient-aware via shared packages.IsTransientConditionReason (one source of truth with the GitOps path). + table tests. Verified: go build/vet/test green (internal/issues, internal/k8s, pkg/packages).
nadaverell
added a commit
that referenced
this pull request
May 31, 2026
- #2 (Service): a Service that is BOTH no-ready-endpoints AND has an unresolved named targetPort now stays two issues (distinct fixes — the workload vs the Service port spec) via stable fingerprints, instead of collapsing under one service_no_endpoints row. - #5 (cluster): removed the 'cluster' CEL binding + the always-empty Issue.Cluster field + its activation projection. A single Radar is one cluster, so Issue.Cluster was always empty and a forwarded 'cluster == x' matched nothing — the advertised filter returned the wrong answer. Cross-cluster scoping is the hub's clusters=/target mechanism (applied at fan-out), not a per-issue predicate. MCP/filter docs updated to say so.
This was referenced Jun 23, 2026
shivram-experience
added a commit
to shivram-personal/kompass
that referenced
this pull request
Jul 7, 2026
The Phase-0 rebrand only covered index.html + the top-bar Logo; the standalone nav-rail brand, Settings dialog, and ~30 user-facing strings still read "Radar"/"by Skyhook" (CLAUDE.md rule skyhook-io#2 / SPEC §1.3). Rebrand: - nav rail: Kompass mark (reuse /images/kompass/kompass-icon.svg) + "Kompass" wordmark, drop the "by Skyhook" byline; Settings header now "Kompass v…". - 35 user-facing string replacements across web/src + packages/k8s-ui/src: error/help/tooltip copy, browser-tab titles (— Kompass), Helm/RBAC/GitOps/ timeline text, MCP dialog, permission-denied reasons, console prefixes. - delete the unreferenced upstream Skyhook logo pack (web/public/assets/skyhook). - harden test_no_upstream_branding from an index.html-only check into a source-tree gate: JSX text + aria-label/alt/title/placeholder/content, a hard "by Skyhook" ban, and a stray-asset check (caught a real leak: "Your Radar Cloud role …" in api/client.ts). Cluster onboarding UI gap: - add Connect (inject via seam skyhook-io#3, admin) + Select (activate) actions to the Cluster registry panel; previously register/delete only, forcing curl. Frontend-only: reuses existing /connect + /select endpoints, no new backend/data-model/security surface. Gate: 135 pytest passed, tsc clean, all 5 fatal scans ok, seam-drift ok, two-container smoke confirms engine reachable only via core. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nadaverell
added a commit
that referenced
this pull request
Jul 7, 2026
New `radar cloud install` (#2) — the recommended way to connect a cluster: installs Radar INTO the current-context cluster with Cloud mode enabled, using the operator's own kubeconfig (the only identity that can provision the impersonation RBAC). Ordering matters: SSAR preflight runs BEFORE the device-flow token mint, so a permission failure can't orphan a cluster + live token. On an existing release it hands off a guided `helm upgrade` command. Does NOT start a local dialer — the in-cluster agent it installs is what dials the tunnel. --dry-run runs preflight + prints the plan without minting or installing.
nadaverell
added a commit
that referenced
this pull request
Aug 8, 2026
Six of seven findings triaged as real; #2 rejected on evidence. An all-empty cluster now watches every readable report family, not just the first. On a cluster that has enabled openreports but not yet produced a violation, both families are legitimately empty; locking onto the first left the group Kyverno was about to write to unwatched, so reports would appear in the cluster and never in Radar while the status still claimed `ready`. Same class as the served-but-empty bug already fixed, in the case where nothing has data yet. Partial coverage is now nameable. Watching what we can read is right, but publishing it as unqualified coverage is not: a family we were denied is recorded in DeniedGroups and surfaced on the capability, so a consumer that renders "no violations" can tell it is speaking for part of the cluster. A frozen index no longer claims to be live. When change-handler registration fails the index is populated but never updates again until context switch, while `ready` promises live data; LiveUpdates carries that distinction. "Discovery unavailable" is no longer reported as absence. It was mapped to not_installed, which OmittedReason keeps silent — concluding there is no Kyverno from a lookup that never happened. Genuine, established absence stays silent; an unestablished one now says the index is cold. This narrows, rather than undoes, the spurious-omit fix: the permanent-undecided case that bug was about has discovery present and is still resolved by asking it. A MutatingPolicy with mutateExisting is no longer called Inactive. Verified against the upstream CRD: spec.evaluation.mutateExisting.enabled defaults to false, the opposite of admission/background, and rewrites resources that already exist independently of the admission path. Labelling that "Inactive — modifies nothing" told an operator nothing was changing while the policy actively rewrote their cluster. Ready status no longer races its own metadata. The index and the status live in separate atomics and publishReady stores the index first, so a reader sampling metadata before that store and the index after it reported `ready` with an empty ObservedCount and no WatchedGroups — a status describing no real cluster. Rejected: "a failed GVR list commits a partial Replace, erasing findings". listPolicyReportsAll is unchanged by this PR, and its premise does not hold — ListWatched reads local informer indexers with no network path, and for this caller (namespace "") indexerItems calls idx.List(), which has no error return at all. There is no transient failure mode to erase anything; the only error is the nil-cache guard, which fails every GVR alike.
nadaverell
added a commit
that referenced
this pull request
Aug 8, 2026
Six of seven findings triaged as real; #2 rejected on evidence. An all-empty cluster now watches every readable report family, not just the first. On a cluster that has enabled openreports but not yet produced a violation, both families are legitimately empty; locking onto the first left the group Kyverno was about to write to unwatched, so reports would appear in the cluster and never in Radar while the status still claimed `ready`. Same class as the served-but-empty bug already fixed, in the case where nothing has data yet. Partial coverage is now nameable. Watching what we can read is right, but publishing it as unqualified coverage is not: a family we were denied is recorded in DeniedGroups and surfaced on the capability, so a consumer that renders "no violations" can tell it is speaking for part of the cluster. A frozen index no longer claims to be live. When change-handler registration fails the index is populated but never updates again until context switch, while `ready` promises live data; LiveUpdates carries that distinction. "Discovery unavailable" is no longer reported as absence. It was mapped to not_installed, which OmittedReason keeps silent — concluding there is no Kyverno from a lookup that never happened. Genuine, established absence stays silent; an unestablished one now says the index is cold. This narrows, rather than undoes, the spurious-omit fix: the permanent-undecided case that bug was about has discovery present and is still resolved by asking it. A MutatingPolicy with mutateExisting is no longer called Inactive. Verified against the upstream CRD: spec.evaluation.mutateExisting.enabled defaults to false, the opposite of admission/background, and rewrites resources that already exist independently of the admission path. Labelling that "Inactive — modifies nothing" told an operator nothing was changing while the policy actively rewrote their cluster. Ready status no longer races its own metadata. The index and the status live in separate atomics and publishReady stores the index first, so a reader sampling metadata before that store and the index after it reported `ready` with an empty ObservedCount and no WatchedGroups — a status describing no real cluster. Rejected: "a failed GVR list commits a partial Replace, erasing findings". listPolicyReportsAll is unchanged by this PR, and its premise does not hold — ListWatched reads local informer indexers with no network path, and for this caller (namespace "") indexerItems calls idx.List(), which has no error return at all. There is no transient failure mode to erase anything; the only error is the nil-cache guard, which fails every GVR alike.
This was referenced Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two major interactive features to Explorer:
Terminal Exec: Open a shell session into any running container directly from the UI. Click the terminal icon next to any running container in the pod details drawer to open a fully interactive shell with xterm.js terminal emulation.
Port Forwarding: Forward container ports to localhost with one click. Click port numbers in pod/service views to start forwarding. Active forwards are shown in a manager dropdown in the header with status and easy cleanup.
Key Changes
Backend (Go)
exec.go- WebSocket handler proxying kubectl exec with PTY supportportforward.go- HTTP handlers for managing port forwards (start/stop/list)server.go- Route registration and WebSocket upgrade handlingFrontend (React)