@skyhook-io/radar-app 1.8.9 → 1.8.10

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/radar-app",
3
- "version": "1.8.9",
3
+ "version": "1.8.10",
4
4
  "description": "Radar's full web UI as a reusable React component. Used by Radar's own binary and by external consumers like Radar Cloud.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,7 +35,7 @@
35
35
  "diff": "^9.0.0",
36
36
  "monaco-editor": "^0.55.1",
37
37
  "react-markdown": "^10.1.0",
38
- "react-virtuoso": "^4.18.10",
38
+ "react-virtuoso": "^4.18.11",
39
39
  "remark-gfm": "^4.0.1",
40
40
  "shiki": "^4.2.0",
41
41
  "yaml": "^2.9.0"
@@ -59,7 +59,7 @@
59
59
  "@tailwindcss/typography": "^0.5.20",
60
60
  "@tailwindcss/vite": "^4.3.1",
61
61
  "@tanstack/react-query": "^5.101.2",
62
- "@types/node": "^26.0.1",
62
+ "@types/node": "^26.1.1",
63
63
  "@types/react": "^19.2.17",
64
64
  "@types/react-dom": "^19.2.3",
65
65
  "@vitejs/plugin-react": "^6.0.2",
@@ -80,7 +80,7 @@
80
80
  "tailwindcss": "^4.3.1",
81
81
  "typescript": "^6.0.2",
82
82
  "typescript-eslint": "^8.62.0",
83
- "vite": "^8.0.12"
83
+ "vite": "^8.1.5"
84
84
  },
85
85
  "sideEffects": [
86
86
  "*.css",
@@ -23,7 +23,7 @@ import {
23
23
  agentLabelFor,
24
24
  openDiagnoseSettings,
25
25
  } from "./DiagnoseContext";
26
- import { useDiagnoseConsentCopy } from "../../context/DiagnoseCustomization";
26
+ import { useDiagnoseCustomization } from "../../context/DiagnoseCustomization";
27
27
  import { InvestigationView } from "./InvestigationView";
28
28
  import { RecentList } from "./Home";
29
29
  import { ConsentCard } from "./parts";
@@ -137,7 +137,11 @@ function InvestigationMenu({ run }: { run: RunSummary }) {
137
137
  // Helm drawers, so it no longer floats viewport-fixed or DOM-measures the chrome.
138
138
  export function DiagnoseSurface({ topInset = 0 }: { topInset?: number }) {
139
139
  const d = useDiagnose();
140
- const consentCopy = useDiagnoseConsentCopy();
140
+ // Injected settings action: undefined = Radar's own Settings dialog;
141
+ // null = hide the gear + links.
142
+ const { consentCopy, onOpenSettings: hostOpenSettings } = useDiagnoseCustomization();
143
+ const openSettings =
144
+ hostOpenSettings === undefined ? openDiagnoseSettings : hostOpenSettings;
141
145
  const {
142
146
  maximized,
143
147
  setMaximized,
@@ -206,7 +210,7 @@ export function DiagnoseSurface({ topInset = 0 }: { topInset?: number }) {
206
210
  agent={d.selectedAgent}
207
211
  isolated={d.isolated}
208
212
  copy={consentCopy}
209
- onOpenSettings={openDiagnoseSettings}
213
+ onOpenSettings={openSettings ?? undefined}
210
214
  onApprove={d.approveConsent}
211
215
  onCancel={d.cancelConsent}
212
216
  />
@@ -306,15 +310,17 @@ export function DiagnoseSurface({ topInset = 0 }: { topInset?: number }) {
306
310
  </div>
307
311
  <div className="flex items-center gap-1 text-xs text-theme-text-tertiary">
308
312
  <span className="truncate">{configLine}</span>
309
- <Tooltip content="AI settings" position="bottom">
310
- <button
311
- onClick={openDiagnoseSettings}
312
- className="shrink-0 rounded p-0.5 text-theme-text-tertiary hover:text-theme-text-primary"
313
- aria-label="AI settings"
314
- >
315
- <Settings2 className="h-3 w-3" />
316
- </button>
317
- </Tooltip>
313
+ {openSettings && (
314
+ <Tooltip content="AI settings" position="bottom">
315
+ <button
316
+ onClick={openSettings}
317
+ className="shrink-0 rounded p-0.5 text-theme-text-tertiary hover:text-theme-text-primary"
318
+ aria-label="AI settings"
319
+ >
320
+ <Settings2 className="h-3 w-3" />
321
+ </button>
322
+ </Tooltip>
323
+ )}
318
324
  </div>
319
325
  </div>
320
326
  </div>
@@ -96,13 +96,13 @@ export const MCP_TOOL_CATALOG: MCPToolInfo[] = [
96
96
  },
97
97
  {
98
98
  name: 'get_pod_logs',
99
- desc: 'Filtered log lines from a pod, prioritizing errors and warnings, falling back to recent tail lines. Optional grep, since, and previous-container logs.',
99
+ desc: 'Pod logs with secret redaction. Without grep, prioritizes errors and warnings and falls back to recent tail lines; grep returns only regex matches instead.',
100
100
  params: [
101
101
  { arg: 'namespace', required: true, desc: 'pod namespace' },
102
102
  { arg: 'name', required: true, desc: 'pod name' },
103
103
  { arg: 'container', desc: 'container name (defaults to first)' },
104
104
  { arg: 'tail_lines', desc: 'lines from end (default 200)' },
105
- { arg: 'grep', desc: 'regex to filter lines, like kubectl logs | grep' },
105
+ { arg: 'grep', desc: 'regex matches to return instead of diagnostic auto-filtering' },
106
106
  { arg: 'since', desc: 'only logs newer than this duration (e.g. 30s, 10m, 1h)' },
107
107
  { arg: 'previous', desc: 'logs from the previous terminated container (CrashLoopBackOff)' },
108
108
  ],
@@ -194,11 +194,17 @@ export const MCP_TOOL_CATALOG: MCPToolInfo[] = [
194
194
  },
195
195
  {
196
196
  name: 'get_subject_permissions',
197
- desc: 'Effective RBAC for a ServiceAccount, User, or Group: the bindings that grant access, a flattened rule list, and (for SAs) the Pods running as it. Answers "what\'s the blast radius if compromised?".',
197
+ desc: 'Effective RBAC for a ServiceAccount, User, or Group. Returns the full bindings/rules dump by default, or an authoritative SubjectAccessReview answer when verb and resource are supplied for a ServiceAccount.',
198
198
  params: [
199
199
  { arg: 'kind', required: true, desc: 'ServiceAccount, User, or Group' },
200
200
  { arg: 'name', required: true, desc: 'subject name' },
201
201
  { arg: 'namespace', desc: 'required for ServiceAccount; omit for User/Group' },
202
+ { arg: 'verb', desc: 'access check: Kubernetes API verb; requires resource' },
203
+ { arg: 'resource', desc: 'access check: plural API resource; requires verb' },
204
+ { arg: 'group', desc: 'access check: API group; omit for core/v1' },
205
+ { arg: 'resource_namespace', desc: 'access check target; defaults to subject namespace' },
206
+ { arg: 'subresource', desc: 'access check subresource, e.g. log for pods/log' },
207
+ { arg: 'resource_name', desc: 'access check target resource name' },
202
208
  ],
203
209
  },
204
210
  {
@@ -237,14 +243,14 @@ export const MCP_TOOL_CATALOG: MCPToolInfo[] = [
237
243
  },
238
244
  {
239
245
  name: 'get_workload_logs',
240
- desc: 'Aggregated, filtered logs across all pods of a workload (Deployment, StatefulSet, DaemonSet, Job, or Argo Workflow) collected concurrently, filtered for errors/warnings, and deduplicated.',
246
+ desc: 'Aggregated logs across all pods of a workload. Without grep, filters for diagnostic relevance; grep returns only matching timestamp-prefixed lines instead.',
241
247
  params: [
242
248
  { arg: 'kind', desc: 'deployment (default), statefulset, daemonset, job, or workflow' },
243
249
  { arg: 'namespace', required: true, desc: 'workload namespace' },
244
250
  { arg: 'name', required: true, desc: 'workload name' },
245
251
  { arg: 'container', desc: 'specific container (defaults to all)' },
246
252
  { arg: 'tail_lines', desc: 'lines per pod (default 100)' },
247
- { arg: 'grep', desc: 'regex to filter lines, like kubectl logs | grep' },
253
+ { arg: 'grep', desc: 'regex matches to return instead of diagnostic auto-filtering' },
248
254
  { arg: 'since', desc: 'only logs newer than this duration' },
249
255
  { arg: 'previous', desc: 'logs from the previous terminated container' },
250
256
  ],
@@ -288,7 +288,7 @@ function useActionsBarProps(kind: string, namespace: string, name: string) {
288
288
  const uncordonMutation = useUncordonNode()
289
289
  const drainMutation = useDrainNode()
290
290
 
291
- const renderDiagnose = useDiagnoseCustomization()
291
+ const { renderAction: renderDiagnose } = useDiagnoseCustomization()
292
292
 
293
293
  return {
294
294
  canExec,
@@ -9,7 +9,7 @@
9
9
  //
10
10
  // Default (no provider): Radar renders no Diagnose button — OSS stays
11
11
  // agent-free.
12
- import { createContext, useContext } from 'react';
12
+ import { createContext, useContext, useMemo } from 'react';
13
13
  import type { ReactNode } from 'react';
14
14
 
15
15
  /** Render prop for the resource-level Diagnose action. */
@@ -46,31 +46,48 @@ export type DiagnoseConsentCopy = {
46
46
  approveLabel?: string;
47
47
  };
48
48
 
49
- const DiagnoseCustomizationContext = createContext<RenderDiagnoseAction | undefined>(undefined);
50
- const DiagnoseConsentCopyContext = createContext<DiagnoseConsentCopy | undefined>(undefined);
49
+ // One context for the whole customization seam — the values are host config
50
+ // set once at mount, so per-value re-render isolation buys nothing.
51
+ export interface DiagnoseCustomization {
52
+ renderAction: RenderDiagnoseAction | undefined;
53
+ consentCopy: DiagnoseConsentCopy | undefined;
54
+ // undefined = default (CustomEvent → Radar's own Settings dialog);
55
+ // null = hide the settings affordances.
56
+ onOpenSettings: (() => void) | null | undefined;
57
+ }
58
+
59
+ const DEFAULTS: DiagnoseCustomization = {
60
+ renderAction: undefined,
61
+ consentCopy: undefined,
62
+ onOpenSettings: undefined,
63
+ };
64
+
65
+ const DiagnoseCustomizationContext = createContext<DiagnoseCustomization>(DEFAULTS);
51
66
 
52
67
  export function DiagnoseCustomizationProvider({
53
68
  value,
54
69
  consentCopy,
70
+ onOpenSettings,
55
71
  children,
56
72
  }: {
57
73
  value: RenderDiagnoseAction | undefined;
58
74
  consentCopy?: DiagnoseConsentCopy;
75
+ /** Where "AI settings" affordances lead. Omit for Radar's own Settings
76
+ * dialog; pass `null` to hide them. */
77
+ onOpenSettings?: (() => void) | null;
59
78
  children: ReactNode;
60
79
  }) {
80
+ const ctx = useMemo(
81
+ () => ({ renderAction: value, consentCopy, onOpenSettings }),
82
+ [value, consentCopy, onOpenSettings],
83
+ );
61
84
  return (
62
- <DiagnoseCustomizationContext.Provider value={value}>
63
- <DiagnoseConsentCopyContext.Provider value={consentCopy}>
64
- {children}
65
- </DiagnoseConsentCopyContext.Provider>
85
+ <DiagnoseCustomizationContext.Provider value={ctx}>
86
+ {children}
66
87
  </DiagnoseCustomizationContext.Provider>
67
88
  );
68
89
  }
69
90
 
70
- export function useDiagnoseCustomization(): RenderDiagnoseAction | undefined {
91
+ export function useDiagnoseCustomization(): DiagnoseCustomization {
71
92
  return useContext(DiagnoseCustomizationContext);
72
93
  }
73
-
74
- export function useDiagnoseConsentCopy(): DiagnoseConsentCopy | undefined {
75
- return useContext(DiagnoseConsentCopyContext);
76
- }
package/src/index.ts CHANGED
@@ -30,6 +30,24 @@ export type {
30
30
  RenderDiagnoseAction,
31
31
  DiagnoseConsentCopy,
32
32
  } from './context/DiagnoseCustomization';
33
+
34
+ // Standalone AI-diagnose surface — mount the investigation panel outside a
35
+ // full <RadarApp>. No router dependency, no client-side cluster state: the
36
+ // backend set via setApiBase() picks the cluster, so hosts remount
37
+ // <DiagnoseProvider key={cluster}> to switch. Mount order: ThemeProvider >
38
+ // DiagnoseCustomizationProvider > DiagnoseProvider > DiagnoseSurface, under a
39
+ // @tanstack/react-query QueryClientProvider.
40
+ export {
41
+ DiagnoseProvider,
42
+ useDiagnose,
43
+ useDiagnoseLayout,
44
+ } from './components/diagnose/DiagnoseContext';
45
+ export type { Target as DiagnoseTarget } from './components/diagnose/DiagnoseContext';
46
+ export { DiagnoseSurface } from './components/diagnose/DiagnoseSurface';
47
+ export { DiagnoseCustomizationProvider } from './context/DiagnoseCustomization';
48
+ // The panel reads Radar's ThemeContext (throws unprovided). It follows
49
+ // localStorage['radar-theme'], so host and panel flip together.
50
+ export { ThemeProvider } from './context/ThemeContext';
33
51
  export { ShortcutHelpOverlay } from './components/ui/ShortcutHelpOverlay';
34
52
 
35
53
  // Shared cluster-switcher primitive — re-exported from @skyhook-io/k8s-ui so