@agent-native/dispatch 0.1.1 → 0.2.2
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/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +2 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/list-dispatch-usage-metrics.d.ts +3 -0
- package/dist/actions/list-dispatch-usage-metrics.d.ts.map +1 -0
- package/dist/actions/list-dispatch-usage-metrics.js +18 -0
- package/dist/actions/list-dispatch-usage-metrics.js.map +1 -0
- package/dist/actions/navigate.d.ts +1 -0
- package/dist/actions/navigate.d.ts.map +1 -1
- package/dist/actions/navigate.js +3 -17
- package/dist/actions/navigate.js.map +1 -1
- package/dist/actions/view-screen.d.ts.map +1 -1
- package/dist/actions/view-screen.js +19 -0
- package/dist/actions/view-screen.js.map +1 -1
- package/dist/components/agents-panel.js +3 -3
- package/dist/components/app-keys-popover.js +2 -2
- package/dist/components/create-app-popover.js +2 -2
- package/dist/components/dispatch-shell.js +2 -2
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/Header.js +4 -4
- package/dist/components/layout/Header.js.map +1 -1
- package/dist/components/layout/Layout.d.ts +28 -3
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +137 -26
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/messaging-setup-panel.js +4 -4
- package/dist/components/ui/accordion.js +1 -1
- package/dist/components/ui/alert-dialog.js +2 -2
- package/dist/components/ui/alert.js +1 -1
- package/dist/components/ui/avatar.js +1 -1
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/breadcrumb.js +1 -1
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/calendar.js +2 -2
- package/dist/components/ui/card.js +1 -1
- package/dist/components/ui/carousel.d.ts +2 -2
- package/dist/components/ui/carousel.js +2 -2
- package/dist/components/ui/chart.js +1 -1
- package/dist/components/ui/checkbox.js +1 -1
- package/dist/components/ui/command.js +2 -2
- package/dist/components/ui/context-menu.js +1 -1
- package/dist/components/ui/dialog.js +1 -1
- package/dist/components/ui/drawer.js +1 -1
- package/dist/components/ui/dropdown-menu.js +1 -1
- package/dist/components/ui/form.js +2 -2
- package/dist/components/ui/hover-card.js +1 -1
- package/dist/components/ui/input-otp.js +1 -1
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/label.js +1 -1
- package/dist/components/ui/menubar.js +1 -1
- package/dist/components/ui/navigation-menu.js +1 -1
- package/dist/components/ui/pagination.d.ts +1 -1
- package/dist/components/ui/pagination.js +2 -2
- package/dist/components/ui/popover.js +1 -1
- package/dist/components/ui/progress.js +1 -1
- package/dist/components/ui/radio-group.js +1 -1
- package/dist/components/ui/resizable.js +1 -1
- package/dist/components/ui/scroll-area.js +1 -1
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/separator.js +1 -1
- package/dist/components/ui/sheet.js +1 -1
- package/dist/components/ui/sidebar.d.ts +2 -2
- package/dist/components/ui/sidebar.js +8 -8
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/slider.js +1 -1
- package/dist/components/ui/sonner.js +1 -1
- package/dist/components/ui/spinner.js +1 -1
- package/dist/components/ui/switch.js +1 -1
- package/dist/components/ui/table.js +1 -1
- package/dist/components/ui/tabs.js +1 -1
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/toast.js +1 -1
- package/dist/components/ui/toaster.js +2 -2
- package/dist/components/ui/toggle-group.js +2 -2
- package/dist/components/ui/toggle.js +1 -1
- package/dist/components/ui/tooltip.js +1 -1
- package/dist/components/ui/use-toast.d.ts +1 -1
- package/dist/components/ui/use-toast.js +1 -1
- package/dist/hooks/use-navigation-state.d.ts +2 -1
- package/dist/hooks/use-navigation-state.d.ts.map +1 -1
- package/dist/hooks/use-navigation-state.js +36 -8
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/hooks/use-toast.d.ts +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +3 -2
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/_index.js +1 -1
- package/dist/routes/pages/agents.js +2 -2
- package/dist/routes/pages/approval.js +2 -2
- package/dist/routes/pages/approvals.js +4 -4
- package/dist/routes/pages/apps.$appId.js +3 -3
- package/dist/routes/pages/apps.js +5 -5
- package/dist/routes/pages/audit.js +1 -1
- package/dist/routes/pages/destinations.js +6 -6
- package/dist/routes/pages/extensions.$id.d.ts +2 -0
- package/dist/routes/pages/extensions.$id.d.ts.map +1 -0
- package/dist/routes/pages/extensions.$id.js +6 -0
- package/dist/routes/pages/extensions.$id.js.map +1 -0
- package/dist/routes/pages/extensions._index.d.ts +2 -0
- package/dist/routes/pages/extensions._index.d.ts.map +1 -0
- package/dist/routes/pages/extensions._index.js +6 -0
- package/dist/routes/pages/extensions._index.js.map +1 -0
- package/dist/routes/pages/identities.js +2 -2
- package/dist/routes/pages/integrations.js +4 -4
- package/dist/routes/pages/messaging.js +2 -2
- package/dist/routes/pages/metrics.d.ts +5 -0
- package/dist/routes/pages/metrics.d.ts.map +1 -0
- package/dist/routes/pages/metrics.js +135 -0
- package/dist/routes/pages/metrics.js.map +1 -0
- package/dist/routes/pages/new-app.js +1 -1
- package/dist/routes/pages/overview.d.ts.map +1 -1
- package/dist/routes/pages/overview.js +9 -17
- package/dist/routes/pages/overview.js.map +1 -1
- package/dist/routes/pages/team.js +1 -1
- package/dist/routes/pages/vault.js +10 -10
- package/dist/routes/pages/workspace.js +10 -10
- package/dist/server/lib/pre-auth-routing.d.ts.map +1 -1
- package/dist/server/lib/pre-auth-routing.js +9 -2
- package/dist/server/lib/pre-auth-routing.js.map +1 -1
- package/dist/server/lib/usage-metrics-store.d.ts +93 -0
- package/dist/server/lib/usage-metrics-store.d.ts.map +1 -0
- package/dist/server/lib/usage-metrics-store.js +386 -0
- package/dist/server/lib/usage-metrics-store.js.map +1 -0
- package/package.json +9 -5
- package/src/actions/index.ts +2 -0
- package/src/actions/list-dispatch-usage-metrics.ts +19 -0
- package/src/actions/navigate.ts +5 -17
- package/src/actions/view-screen.ts +18 -0
- package/src/components/index.ts +6 -0
- package/src/components/layout/Header.tsx +1 -1
- package/src/components/layout/Layout.tsx +194 -37
- package/src/hooks/use-navigation-state.ts +57 -8
- package/src/routes/index.ts +3 -2
- package/src/routes/pages/extensions.$id.tsx +5 -0
- package/src/routes/pages/extensions._index.tsx +5 -0
- package/src/routes/pages/metrics.tsx +667 -0
- package/src/routes/pages/overview.tsx +0 -10
- package/src/server/lib/pre-auth-routing.ts +10 -2
- package/src/server/lib/usage-metrics-store.ts +605 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, type ReactNode } from "react";
|
|
1
|
+
import { useState, type ComponentType, type ReactNode } from "react";
|
|
2
2
|
import { NavLink, useLocation } from "react-router";
|
|
3
3
|
import {
|
|
4
4
|
AgentSidebar,
|
|
@@ -11,6 +11,7 @@ import { ToolsSidebarSection } from "@agent-native/core/client/tools";
|
|
|
11
11
|
import {
|
|
12
12
|
IconArrowUpRight,
|
|
13
13
|
IconApps,
|
|
14
|
+
IconChartBar,
|
|
14
15
|
IconBrandTelegram,
|
|
15
16
|
IconKey,
|
|
16
17
|
IconChevronDown,
|
|
@@ -33,27 +34,131 @@ import {
|
|
|
33
34
|
import { Header } from "./Header";
|
|
34
35
|
import { HeaderActionsProvider } from "./HeaderActions";
|
|
35
36
|
|
|
37
|
+
export type DispatchNavSection = "primary" | "operations";
|
|
38
|
+
|
|
39
|
+
export type DispatchNavIcon = ComponentType<{
|
|
40
|
+
size?: number | string;
|
|
41
|
+
className?: string;
|
|
42
|
+
}>;
|
|
43
|
+
|
|
44
|
+
export interface DispatchNavItem {
|
|
45
|
+
/** Stable id used for keys and navigation.view. Avoid built-in ids. */
|
|
46
|
+
id: string;
|
|
47
|
+
/** React Router path for the tab, usually backed by an app/routes/*.tsx file. */
|
|
48
|
+
to: string;
|
|
49
|
+
label: string;
|
|
50
|
+
icon?: DispatchNavIcon;
|
|
51
|
+
/** Defaults to "operations", which is where local management tools usually fit. */
|
|
52
|
+
section?: DispatchNavSection;
|
|
53
|
+
/** Override active matching for nested or multi-route tools. */
|
|
54
|
+
match?: (pathname: string) => boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface DispatchExtensionConfig {
|
|
58
|
+
/** Extra sidebar tabs supplied by the generated workspace. */
|
|
59
|
+
navItems?: readonly DispatchNavItem[];
|
|
60
|
+
/** Extra React Query keys to invalidate when Dispatch receives DB sync events. */
|
|
61
|
+
queryKeys?: readonly string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
36
64
|
const PRIMARY_NAV_ITEMS = [
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
65
|
+
{
|
|
66
|
+
id: "overview",
|
|
67
|
+
to: "/overview",
|
|
68
|
+
label: "Overview",
|
|
69
|
+
icon: IconBroadcast,
|
|
70
|
+
section: "primary",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "apps",
|
|
74
|
+
to: "/apps",
|
|
75
|
+
label: "Apps",
|
|
76
|
+
icon: IconApps,
|
|
77
|
+
section: "primary",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "metrics",
|
|
81
|
+
to: "/metrics",
|
|
82
|
+
label: "Metrics",
|
|
83
|
+
icon: IconChartBar,
|
|
84
|
+
section: "primary",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "vault",
|
|
88
|
+
to: "/vault",
|
|
89
|
+
label: "Vault",
|
|
90
|
+
icon: IconKey,
|
|
91
|
+
section: "primary",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "integrations",
|
|
95
|
+
to: "/integrations",
|
|
96
|
+
label: "Integrations",
|
|
97
|
+
icon: IconPuzzle,
|
|
98
|
+
section: "primary",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "agents",
|
|
102
|
+
to: "/agents",
|
|
103
|
+
label: "Agents",
|
|
104
|
+
icon: IconPlugConnected,
|
|
105
|
+
section: "primary",
|
|
106
|
+
},
|
|
107
|
+
] as const satisfies readonly DispatchNavItem[];
|
|
43
108
|
|
|
44
109
|
const OPERATIONS_NAV_ITEMS = [
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
110
|
+
{
|
|
111
|
+
id: "workspace",
|
|
112
|
+
to: "/workspace",
|
|
113
|
+
label: "Resources",
|
|
114
|
+
icon: IconLayersSubtract,
|
|
115
|
+
section: "operations",
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "messaging",
|
|
119
|
+
to: "/messaging",
|
|
120
|
+
label: "Messaging",
|
|
121
|
+
icon: IconBrandTelegram,
|
|
122
|
+
section: "operations",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "destinations",
|
|
126
|
+
to: "/destinations",
|
|
127
|
+
label: "Destinations",
|
|
128
|
+
icon: IconArrowUpRight,
|
|
129
|
+
section: "operations",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: "identities",
|
|
133
|
+
to: "/identities",
|
|
134
|
+
label: "Identities",
|
|
135
|
+
icon: IconFingerprint,
|
|
136
|
+
section: "operations",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "approvals",
|
|
140
|
+
to: "/approvals",
|
|
141
|
+
label: "Approvals",
|
|
142
|
+
icon: IconShieldCheck,
|
|
143
|
+
section: "operations",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "audit",
|
|
147
|
+
to: "/audit",
|
|
148
|
+
label: "Audit",
|
|
149
|
+
icon: IconHistory,
|
|
150
|
+
section: "operations",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "team",
|
|
154
|
+
to: "/team",
|
|
155
|
+
label: "Team",
|
|
156
|
+
icon: IconUsersGroup,
|
|
157
|
+
section: "operations",
|
|
158
|
+
},
|
|
159
|
+
] as const satisfies readonly DispatchNavItem[];
|
|
160
|
+
|
|
161
|
+
const EMPTY_NAV_ITEMS: readonly DispatchNavItem[] = [];
|
|
57
162
|
|
|
58
163
|
const SIDEBAR_SUGGESTIONS = [
|
|
59
164
|
"Create a new app",
|
|
@@ -68,6 +173,8 @@ const CHROMELESS_PATHS = ["/approval"];
|
|
|
68
173
|
// there's no double-header.
|
|
69
174
|
function pageOwnsToolbar(pathname: string): boolean {
|
|
70
175
|
if (pathname === "/tools" || pathname.startsWith("/tools/")) return true;
|
|
176
|
+
if (pathname === "/extensions" || pathname.startsWith("/extensions/"))
|
|
177
|
+
return true;
|
|
71
178
|
return false;
|
|
72
179
|
}
|
|
73
180
|
|
|
@@ -77,7 +184,35 @@ interface WorkspaceInfo {
|
|
|
77
184
|
appCount: number;
|
|
78
185
|
}
|
|
79
186
|
|
|
80
|
-
|
|
187
|
+
function sectionFor(item: DispatchNavItem): DispatchNavSection {
|
|
188
|
+
return item.section ?? "operations";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function navItemMatchesPath(item: DispatchNavItem, pathname: string): boolean {
|
|
192
|
+
if (item.match) {
|
|
193
|
+
try {
|
|
194
|
+
if (item.match(pathname)) return true;
|
|
195
|
+
} catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return pathname === item.to || pathname.startsWith(`${item.to}/`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function navItemsForSection(
|
|
203
|
+
items: readonly DispatchNavItem[],
|
|
204
|
+
section: DispatchNavSection,
|
|
205
|
+
): DispatchNavItem[] {
|
|
206
|
+
return items.filter((item) => sectionFor(item) === section);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function NavContent({
|
|
210
|
+
onNavigate,
|
|
211
|
+
extensions,
|
|
212
|
+
}: {
|
|
213
|
+
onNavigate?: () => void;
|
|
214
|
+
extensions?: DispatchExtensionConfig;
|
|
215
|
+
}) {
|
|
81
216
|
const location = useLocation();
|
|
82
217
|
const { data: workspace } = useActionQuery(
|
|
83
218
|
"get-workspace-info",
|
|
@@ -86,29 +221,42 @@ export function NavContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
86
221
|
);
|
|
87
222
|
const ws = workspace as WorkspaceInfo | undefined;
|
|
88
223
|
const workspaceLabel = ws?.displayName ?? ws?.name ?? null;
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
224
|
+
const extensionNavItems = extensions?.navItems ?? EMPTY_NAV_ITEMS;
|
|
225
|
+
const primaryNavItems = [
|
|
226
|
+
...PRIMARY_NAV_ITEMS,
|
|
227
|
+
...navItemsForSection(extensionNavItems, "primary"),
|
|
228
|
+
];
|
|
229
|
+
const operationsNavItems = [
|
|
230
|
+
...OPERATIONS_NAV_ITEMS,
|
|
231
|
+
...navItemsForSection(extensionNavItems, "operations"),
|
|
232
|
+
];
|
|
233
|
+
const operationsOpen = operationsNavItems.some((item) =>
|
|
234
|
+
navItemMatchesPath(item, location.pathname),
|
|
93
235
|
);
|
|
94
236
|
|
|
95
|
-
const renderNavItem = (item:
|
|
237
|
+
const renderNavItem = (item: DispatchNavItem) => {
|
|
96
238
|
const Icon = item.icon;
|
|
97
239
|
return (
|
|
98
|
-
<li key={item.
|
|
240
|
+
<li key={item.id}>
|
|
99
241
|
<NavLink
|
|
100
242
|
to={item.to}
|
|
101
243
|
onClick={onNavigate}
|
|
102
|
-
className={({ isActive }) =>
|
|
103
|
-
|
|
244
|
+
className={({ isActive }) => {
|
|
245
|
+
const active =
|
|
246
|
+
isActive || navItemMatchesPath(item, location.pathname);
|
|
247
|
+
return cn(
|
|
104
248
|
"flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm",
|
|
105
|
-
|
|
249
|
+
active
|
|
106
250
|
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
|
|
107
251
|
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
108
|
-
)
|
|
109
|
-
}
|
|
252
|
+
);
|
|
253
|
+
}}
|
|
110
254
|
>
|
|
111
|
-
|
|
255
|
+
{Icon ? (
|
|
256
|
+
<Icon size={16} className="shrink-0" />
|
|
257
|
+
) : (
|
|
258
|
+
<span className="h-4 w-4 shrink-0" aria-hidden="true" />
|
|
259
|
+
)}
|
|
112
260
|
<span className="truncate">{item.label}</span>
|
|
113
261
|
</NavLink>
|
|
114
262
|
</li>
|
|
@@ -147,7 +295,7 @@ export function NavContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
147
295
|
</div>
|
|
148
296
|
|
|
149
297
|
<nav className="flex-1 overflow-y-auto px-2 py-3">
|
|
150
|
-
<ul className="space-y-0.5">{
|
|
298
|
+
<ul className="space-y-0.5">{primaryNavItems.map(renderNavItem)}</ul>
|
|
151
299
|
<details className="group mt-4" open={operationsOpen}>
|
|
152
300
|
<summary className="flex h-8 cursor-pointer list-none items-center justify-between rounded-md px-2 text-xs font-medium uppercase text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [&::-webkit-details-marker]:hidden">
|
|
153
301
|
<span>Operations</span>
|
|
@@ -157,7 +305,7 @@ export function NavContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
157
305
|
/>
|
|
158
306
|
</summary>
|
|
159
307
|
<ul className="mt-1 space-y-0.5">
|
|
160
|
-
{
|
|
308
|
+
{operationsNavItems.map(renderNavItem)}
|
|
161
309
|
</ul>
|
|
162
310
|
</details>
|
|
163
311
|
</nav>
|
|
@@ -170,7 +318,13 @@ export function NavContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
170
318
|
);
|
|
171
319
|
}
|
|
172
320
|
|
|
173
|
-
export function Layout({
|
|
321
|
+
export function Layout({
|
|
322
|
+
children,
|
|
323
|
+
extensions,
|
|
324
|
+
}: {
|
|
325
|
+
children: ReactNode;
|
|
326
|
+
extensions?: DispatchExtensionConfig;
|
|
327
|
+
}) {
|
|
174
328
|
const location = useLocation();
|
|
175
329
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
176
330
|
const hasEmbeddedAgentChat =
|
|
@@ -206,7 +360,7 @@ export function Layout({ children }: { children: ReactNode }) {
|
|
|
206
360
|
<HeaderActionsProvider>
|
|
207
361
|
<div className="flex h-screen w-full overflow-hidden bg-background">
|
|
208
362
|
<aside className="hidden 2xl:flex w-64 shrink-0 flex-col border-r bg-sidebar text-sidebar-foreground">
|
|
209
|
-
<NavContent />
|
|
363
|
+
<NavContent extensions={extensions} />
|
|
210
364
|
</aside>
|
|
211
365
|
|
|
212
366
|
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
|
|
@@ -219,7 +373,10 @@ export function Layout({ children }: { children: ReactNode }) {
|
|
|
219
373
|
Workspace navigation links
|
|
220
374
|
</SheetDescription>
|
|
221
375
|
<div className="flex h-full w-full flex-col">
|
|
222
|
-
<NavContent
|
|
376
|
+
<NavContent
|
|
377
|
+
extensions={extensions}
|
|
378
|
+
onNavigate={() => setMobileOpen(false)}
|
|
379
|
+
/>
|
|
223
380
|
</div>
|
|
224
381
|
</SheetContent>
|
|
225
382
|
</Sheet>
|
|
@@ -6,13 +6,17 @@ import {
|
|
|
6
6
|
appBasePath,
|
|
7
7
|
appPath,
|
|
8
8
|
} from "@agent-native/core/client";
|
|
9
|
+
import type {
|
|
10
|
+
DispatchExtensionConfig,
|
|
11
|
+
DispatchNavItem,
|
|
12
|
+
} from "../components/index.js";
|
|
9
13
|
|
|
10
14
|
export interface NavigationState {
|
|
11
15
|
view: string;
|
|
12
16
|
path?: string;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
export function useNavigationState() {
|
|
19
|
+
export function useNavigationState(extensions?: DispatchExtensionConfig) {
|
|
16
20
|
const location = useLocation();
|
|
17
21
|
const navigate = useNavigate();
|
|
18
22
|
const qc = useQueryClient();
|
|
@@ -20,7 +24,7 @@ export function useNavigationState() {
|
|
|
20
24
|
// Sync current route to application state
|
|
21
25
|
useEffect(() => {
|
|
22
26
|
const state: NavigationState = {
|
|
23
|
-
view: resolveView(location.pathname),
|
|
27
|
+
view: resolveView(location.pathname, extensions),
|
|
24
28
|
path: appPath(location.pathname),
|
|
25
29
|
};
|
|
26
30
|
|
|
@@ -30,7 +34,7 @@ export function useNavigationState() {
|
|
|
30
34
|
headers: { "Content-Type": "application/json" },
|
|
31
35
|
body: JSON.stringify(state),
|
|
32
36
|
}).catch(() => {});
|
|
33
|
-
}, [location.pathname]);
|
|
37
|
+
}, [extensions, location.pathname]);
|
|
34
38
|
|
|
35
39
|
// Listen for navigate commands from agent
|
|
36
40
|
const { data: navCommand } = useQuery({
|
|
@@ -62,10 +66,12 @@ export function useNavigationState() {
|
|
|
62
66
|
const cmd = navCommand as NavigationState;
|
|
63
67
|
|
|
64
68
|
// Navigate to a specific path or resolve view name to path
|
|
65
|
-
const path = routerPath(
|
|
69
|
+
const path = routerPath(
|
|
70
|
+
cmd.path || resolvePath(cmd.view, extensions) || "/overview",
|
|
71
|
+
);
|
|
66
72
|
navigate(path);
|
|
67
73
|
qc.setQueryData(["navigate-command"], null);
|
|
68
|
-
}, [navCommand, navigate, qc]);
|
|
74
|
+
}, [extensions, navCommand, navigate, qc]);
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
function routerPath(path: string): string {
|
|
@@ -78,8 +84,45 @@ function routerPath(path: string): string {
|
|
|
78
84
|
return path;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
function
|
|
87
|
+
function extensionItemMatchesPath(
|
|
88
|
+
item: DispatchNavItem,
|
|
89
|
+
pathname: string,
|
|
90
|
+
): boolean {
|
|
91
|
+
if (item.match) {
|
|
92
|
+
try {
|
|
93
|
+
if (item.match(pathname)) return true;
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return pathname === item.to || pathname.startsWith(`${item.to}/`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function resolveExtensionView(
|
|
102
|
+
pathname: string,
|
|
103
|
+
extensions?: DispatchExtensionConfig,
|
|
104
|
+
): string | undefined {
|
|
105
|
+
return extensions?.navItems?.find((item) =>
|
|
106
|
+
extensionItemMatchesPath(item, pathname),
|
|
107
|
+
)?.id;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function resolveExtensionPath(
|
|
111
|
+
view: string | undefined,
|
|
112
|
+
extensions?: DispatchExtensionConfig,
|
|
113
|
+
): string | undefined {
|
|
114
|
+
if (!view) return undefined;
|
|
115
|
+
return extensions?.navItems?.find((item) => item.id === view)?.to;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function resolveView(
|
|
119
|
+
pathname: string,
|
|
120
|
+
extensions?: DispatchExtensionConfig,
|
|
121
|
+
): string {
|
|
122
|
+
const extensionView = resolveExtensionView(pathname, extensions);
|
|
123
|
+
if (extensionView) return extensionView;
|
|
82
124
|
if (pathname.startsWith("/apps")) return "apps";
|
|
125
|
+
if (pathname.startsWith("/metrics")) return "metrics";
|
|
83
126
|
if (pathname.startsWith("/new-app")) return "new-app";
|
|
84
127
|
if (pathname.startsWith("/vault")) return "vault";
|
|
85
128
|
if (pathname.startsWith("/integrations")) return "integrations";
|
|
@@ -94,12 +137,18 @@ function resolveView(pathname: string): string {
|
|
|
94
137
|
return "overview";
|
|
95
138
|
}
|
|
96
139
|
|
|
97
|
-
function resolvePath(
|
|
140
|
+
function resolvePath(
|
|
141
|
+
view?: string,
|
|
142
|
+
extensions?: DispatchExtensionConfig,
|
|
143
|
+
): string | undefined {
|
|
98
144
|
switch (view) {
|
|
99
145
|
case "overview":
|
|
100
146
|
return "/overview";
|
|
101
147
|
case "apps":
|
|
102
148
|
return "/apps";
|
|
149
|
+
case "metrics":
|
|
150
|
+
case "usage":
|
|
151
|
+
return "/metrics";
|
|
103
152
|
case "new-app":
|
|
104
153
|
case "create-app":
|
|
105
154
|
return "/new-app";
|
|
@@ -127,6 +176,6 @@ function resolvePath(view?: string): string | undefined {
|
|
|
127
176
|
case "team":
|
|
128
177
|
return "/team";
|
|
129
178
|
default:
|
|
130
|
-
return
|
|
179
|
+
return resolveExtensionPath(view, extensions);
|
|
131
180
|
}
|
|
132
181
|
}
|
package/src/routes/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { type RouteConfig, route, index } from "@react-router/dev/routes";
|
|
|
32
32
|
export const dispatchRoutes: RouteConfig = [
|
|
33
33
|
index("./pages/_index.js"),
|
|
34
34
|
route("overview", "./pages/overview.js"),
|
|
35
|
+
route("metrics", "./pages/metrics.js"),
|
|
35
36
|
route("apps", "./pages/apps.js"),
|
|
36
37
|
route("apps/:appId", "./pages/apps.$appId.js"),
|
|
37
38
|
route("new-app", "./pages/new-app.js"),
|
|
@@ -46,6 +47,6 @@ export const dispatchRoutes: RouteConfig = [
|
|
|
46
47
|
route("approvals", "./pages/approvals.js"),
|
|
47
48
|
route("audit", "./pages/audit.js"),
|
|
48
49
|
route("team", "./pages/team.js"),
|
|
49
|
-
route("
|
|
50
|
-
route("
|
|
50
|
+
route("extensions", "./pages/extensions._index.js"),
|
|
51
|
+
route("extensions/:id", "./pages/extensions.$id.js"),
|
|
51
52
|
];
|