@agent-native/dispatch 0.1.1 → 0.2.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/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 +5 -5
- 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 +138 -28
- 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.d.ts.map +1 -1
- package/dist/components/ui/sidebar.js +9 -9
- package/dist/components/ui/sidebar.js.map +1 -1
- 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 +11 -6
- 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 +2 -2
- package/src/components/layout/Layout.tsx +197 -48
- package/src/components/ui/sidebar.tsx +22 -18
- 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
- package/src/styles/dispatch-css.spec.ts +55 -0
- package/src/styles/dispatch.css +9 -0
package/src/actions/navigate.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
7
|
* pnpm action navigate --view=overview
|
|
8
|
+
* pnpm action navigate --view=<custom-dispatch-extension-id>
|
|
8
9
|
* pnpm action navigate --path=/some/route
|
|
9
10
|
*
|
|
10
11
|
* Options:
|
|
@@ -21,24 +22,11 @@ export default defineAction({
|
|
|
21
22
|
"Navigate the UI to a specific view or path. Writes a navigate command to application state which the UI reads and auto-deletes.",
|
|
22
23
|
schema: z.object({
|
|
23
24
|
view: z
|
|
24
|
-
.
|
|
25
|
-
"overview",
|
|
26
|
-
"apps",
|
|
27
|
-
"new-app",
|
|
28
|
-
"vault",
|
|
29
|
-
"integrations",
|
|
30
|
-
"messaging",
|
|
31
|
-
"workspace",
|
|
32
|
-
"agents",
|
|
33
|
-
"destinations",
|
|
34
|
-
"routes",
|
|
35
|
-
"identities",
|
|
36
|
-
"approvals",
|
|
37
|
-
"audit",
|
|
38
|
-
"team",
|
|
39
|
-
])
|
|
25
|
+
.string()
|
|
40
26
|
.optional()
|
|
41
|
-
.describe(
|
|
27
|
+
.describe(
|
|
28
|
+
"Named dispatch view to navigate to. Built-in views include overview, apps, metrics, new-app, vault, integrations, messaging, workspace, agents, destinations, identities, approvals, audit, and team. Generated Dispatch extension tabs can also use their nav item id.",
|
|
29
|
+
),
|
|
42
30
|
path: z.string().optional().describe("URL path to navigate to"),
|
|
43
31
|
}),
|
|
44
32
|
http: false,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
listRequests,
|
|
19
19
|
} from "../server/lib/vault-store.js";
|
|
20
20
|
import { listWorkspaceApps } from "../server/lib/app-creation-store.js";
|
|
21
|
+
import { listDispatchUsageMetrics } from "../server/lib/usage-metrics-store.js";
|
|
21
22
|
|
|
22
23
|
export default defineAction({
|
|
23
24
|
description:
|
|
@@ -45,6 +46,7 @@ export default defineAction({
|
|
|
45
46
|
}
|
|
46
47
|
if (
|
|
47
48
|
navigation?.view === "overview" ||
|
|
49
|
+
navigation?.view === "metrics" ||
|
|
48
50
|
navigation?.view === "apps" ||
|
|
49
51
|
navigation?.view === "new-app"
|
|
50
52
|
) {
|
|
@@ -52,6 +54,22 @@ export default defineAction({
|
|
|
52
54
|
includeAgentCards: true,
|
|
53
55
|
});
|
|
54
56
|
}
|
|
57
|
+
if (navigation?.view === "metrics") {
|
|
58
|
+
try {
|
|
59
|
+
const metrics = await listDispatchUsageMetrics({ sinceDays: 30 });
|
|
60
|
+
screen.usageMetrics = {
|
|
61
|
+
totals: metrics.totals,
|
|
62
|
+
byApp: metrics.byApp.slice(0, 8),
|
|
63
|
+
byUser: metrics.byUser.slice(0, 8),
|
|
64
|
+
appAccess: metrics.appAccess
|
|
65
|
+
.filter((app) => !app.isDispatch)
|
|
66
|
+
.slice(0, 8),
|
|
67
|
+
};
|
|
68
|
+
} catch (error) {
|
|
69
|
+
screen.usageMetricsError =
|
|
70
|
+
error instanceof Error ? error.message : String(error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
55
73
|
if (navigation?.view === "vault" || navigation?.view === "new-app") {
|
|
56
74
|
const [secrets, grants, requests] = await Promise.all([
|
|
57
75
|
listSecrets(),
|
package/src/components/index.ts
CHANGED
|
@@ -7,5 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { DispatchShell } from "./dispatch-shell.js";
|
|
9
9
|
export { Layout, NavContent } from "./layout/Layout.js";
|
|
10
|
+
export type {
|
|
11
|
+
DispatchExtensionConfig,
|
|
12
|
+
DispatchNavIcon,
|
|
13
|
+
DispatchNavItem,
|
|
14
|
+
DispatchNavSection,
|
|
15
|
+
} from "./layout/Layout.js";
|
|
10
16
|
export { CreateAppPopover, CreateAppFlow } from "./create-app-popover.js";
|
|
11
17
|
export { AppKeysPopover } from "./app-keys-popover.js";
|
|
@@ -22,7 +22,7 @@ const pageTitles: Record<string, string> = {
|
|
|
22
22
|
function resolveTitle(pathname: string): string {
|
|
23
23
|
if (pageTitles[pathname]) return pageTitles[pathname];
|
|
24
24
|
|
|
25
|
-
if (pathname.startsWith("/
|
|
25
|
+
if (pathname.startsWith("/extensions")) return "Extensions";
|
|
26
26
|
|
|
27
27
|
return "Dispatch";
|
|
28
28
|
}
|
|
@@ -44,7 +44,7 @@ export function Header({
|
|
|
44
44
|
<Button
|
|
45
45
|
variant="ghost"
|
|
46
46
|
size="icon"
|
|
47
|
-
className="h-8 w-8
|
|
47
|
+
className="h-8 w-8 lg:hidden cursor-pointer"
|
|
48
48
|
onClick={onOpenMobile}
|
|
49
49
|
aria-label="Open navigation"
|
|
50
50
|
>
|
|
@@ -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,11 +318,15 @@ 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
|
-
const hasEmbeddedAgentChat =
|
|
177
|
-
location.pathname === "/" || location.pathname === "/overview";
|
|
178
330
|
|
|
179
331
|
if (CHROMELESS_PATHS.some((path) => location.pathname === path)) {
|
|
180
332
|
return <>{children}</>;
|
|
@@ -183,12 +335,7 @@ export function Layout({ children }: { children: ReactNode }) {
|
|
|
183
335
|
const showHeader = !pageOwnsToolbar(location.pathname);
|
|
184
336
|
const appContent = (
|
|
185
337
|
<div className="flex h-full flex-1 flex-col overflow-hidden">
|
|
186
|
-
{showHeader ? (
|
|
187
|
-
<Header
|
|
188
|
-
onOpenMobile={() => setMobileOpen(true)}
|
|
189
|
-
showAgentToggle={!hasEmbeddedAgentChat}
|
|
190
|
-
/>
|
|
191
|
-
) : null}
|
|
338
|
+
{showHeader ? <Header onOpenMobile={() => setMobileOpen(true)} /> : null}
|
|
192
339
|
<InvitationBanner />
|
|
193
340
|
<main className="flex-1 overflow-y-auto">
|
|
194
341
|
{showHeader ? (
|
|
@@ -205,8 +352,8 @@ export function Layout({ children }: { children: ReactNode }) {
|
|
|
205
352
|
return (
|
|
206
353
|
<HeaderActionsProvider>
|
|
207
354
|
<div className="flex h-screen w-full overflow-hidden bg-background">
|
|
208
|
-
<aside className="hidden
|
|
209
|
-
<NavContent />
|
|
355
|
+
<aside className="hidden lg:flex w-64 shrink-0 flex-col border-r bg-sidebar text-sidebar-foreground">
|
|
356
|
+
<NavContent extensions={extensions} />
|
|
210
357
|
</aside>
|
|
211
358
|
|
|
212
359
|
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
|
|
@@ -219,15 +366,17 @@ export function Layout({ children }: { children: ReactNode }) {
|
|
|
219
366
|
Workspace navigation links
|
|
220
367
|
</SheetDescription>
|
|
221
368
|
<div className="flex h-full w-full flex-col">
|
|
222
|
-
<NavContent
|
|
369
|
+
<NavContent
|
|
370
|
+
extensions={extensions}
|
|
371
|
+
onNavigate={() => setMobileOpen(false)}
|
|
372
|
+
/>
|
|
223
373
|
</div>
|
|
224
374
|
</SheetContent>
|
|
225
375
|
</Sheet>
|
|
226
376
|
|
|
227
377
|
{/*
|
|
228
378
|
* Always mount AgentSidebar so home composer's sendToAgentChat
|
|
229
|
-
* fallback can pop it via agent-panel:open.
|
|
230
|
-
* hidden on overview because the home composer is the primary input.
|
|
379
|
+
* fallback can pop it via agent-panel:open.
|
|
231
380
|
*/}
|
|
232
381
|
<AgentSidebar
|
|
233
382
|
position="right"
|
|
@@ -307,24 +307,28 @@ const SidebarRail = React.forwardRef<
|
|
|
307
307
|
const { toggleSidebar } = useSidebar();
|
|
308
308
|
|
|
309
309
|
return (
|
|
310
|
-
<
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
310
|
+
<Tooltip>
|
|
311
|
+
<TooltipTrigger asChild>
|
|
312
|
+
<button
|
|
313
|
+
ref={ref}
|
|
314
|
+
data-sidebar="rail"
|
|
315
|
+
aria-label="Toggle Sidebar"
|
|
316
|
+
tabIndex={-1}
|
|
317
|
+
onClick={toggleSidebar}
|
|
318
|
+
className={cn(
|
|
319
|
+
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
|
|
320
|
+
"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
|
|
321
|
+
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
|
322
|
+
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
|
|
323
|
+
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
|
324
|
+
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
|
325
|
+
className,
|
|
326
|
+
)}
|
|
327
|
+
{...props}
|
|
328
|
+
/>
|
|
329
|
+
</TooltipTrigger>
|
|
330
|
+
<TooltipContent>Toggle Sidebar</TooltipContent>
|
|
331
|
+
</Tooltip>
|
|
328
332
|
);
|
|
329
333
|
});
|
|
330
334
|
SidebarRail.displayName = "SidebarRail";
|
|
@@ -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
|
];
|