@agent-native/dispatch 0.19.2 → 0.20.1
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/provider-api-register.d.ts +5 -5
- package/dist/actions/start-workspace-app-creation.js +1 -1
- package/dist/actions/start-workspace-app-creation.js.map +1 -1
- package/dist/components/admin-navigation.d.ts +2 -1
- package/dist/components/admin-navigation.d.ts.map +1 -1
- package/dist/components/admin-navigation.js +10 -7
- package/dist/components/admin-navigation.js.map +1 -1
- package/dist/components/app-icon.d.ts +9 -0
- package/dist/components/app-icon.d.ts.map +1 -0
- package/dist/components/app-icon.js +85 -0
- package/dist/components/app-icon.js.map +1 -0
- package/dist/components/app-list-row.d.ts +12 -0
- package/dist/components/app-list-row.d.ts.map +1 -0
- package/dist/components/app-list-row.js +11 -0
- package/dist/components/app-list-row.js.map +1 -0
- package/dist/components/app-open-actions.d.ts +17 -0
- package/dist/components/app-open-actions.d.ts.map +1 -0
- package/dist/components/app-open-actions.js +20 -0
- package/dist/components/app-open-actions.js.map +1 -0
- package/dist/components/connected-app-card.d.ts +2 -1
- package/dist/components/connected-app-card.d.ts.map +1 -1
- package/dist/components/connected-app-card.js +8 -8
- package/dist/components/connected-app-card.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +21 -14
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +40 -19
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -1
- package/dist/components/layout/workspace-apps-rail.js +10 -10
- package/dist/components/layout/workspace-apps-rail.js.map +1 -1
- package/dist/components/other-apps-section.d.ts +31 -0
- package/dist/components/other-apps-section.d.ts.map +1 -0
- package/dist/components/other-apps-section.js +59 -0
- package/dist/components/other-apps-section.js.map +1 -0
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +38 -28
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts +8 -2
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +17 -13
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/lib/other-apps.d.ts.map +1 -1
- package/dist/lib/other-apps.js +5 -1
- package/dist/lib/other-apps.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +1 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +3 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +0 -2
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/admin._index.d.ts.map +1 -1
- package/dist/routes/pages/admin._index.js +32 -7
- package/dist/routes/pages/admin._index.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +62 -13
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +21 -30
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/server/plugins/agent-chat.js +6 -1
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/package.json +2 -2
- package/src/actions/start-workspace-app-creation.ts +1 -1
- package/src/components/admin-navigation.tsx +21 -16
- package/src/components/app-icon.tsx +149 -0
- package/src/components/app-list-row.tsx +40 -0
- package/src/components/app-open-actions.tsx +111 -0
- package/src/components/connected-app-card.tsx +30 -34
- package/src/components/dispatch-control-plane.spec.tsx +28 -1
- package/src/components/dispatch-control-plane.tsx +122 -48
- package/src/components/layout/Layout.spec.tsx +4 -4
- package/src/components/layout/Layout.tsx +86 -55
- package/src/components/layout/workspace-apps-rail.tsx +20 -21
- package/src/components/other-apps-section.tsx +195 -0
- package/src/components/workspace-app-card.spec.tsx +58 -32
- package/src/components/workspace-app-card.tsx +222 -158
- package/src/components/workspace-template-card.spec.tsx +45 -7
- package/src/components/workspace-template-card.tsx +74 -81
- package/src/lib/other-apps.spec.ts +31 -0
- package/src/lib/other-apps.ts +9 -1
- package/src/lib/workspace-apps.ts +4 -0
- package/src/routes/index.spec.ts +2 -1
- package/src/routes/index.ts +0 -2
- package/src/routes/pages/admin._index.tsx +85 -17
- package/src/routes/pages/apps.$appId.tsx +144 -57
- package/src/routes/pages/apps.tsx +76 -147
- package/src/server/plugins/agent-chat.spec.ts +5 -0
- package/src/server/plugins/agent-chat.ts +6 -1
|
@@ -41,7 +41,7 @@ describe("WorkspaceAppCard", () => {
|
|
|
41
41
|
vi.unstubAllGlobals();
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
it("uses one
|
|
44
|
+
it("uses one explicit open action and one settings control", async () => {
|
|
45
45
|
await act(async () => {
|
|
46
46
|
root.render(
|
|
47
47
|
<TooltipProvider>
|
|
@@ -59,34 +59,49 @@ describe("WorkspaceAppCard", () => {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
const appLink = container.querySelector<HTMLAnchorElement>(
|
|
62
|
-
'a[
|
|
62
|
+
'a[href="/analytics"]',
|
|
63
63
|
);
|
|
64
|
-
expect(appLink
|
|
64
|
+
expect(appLink).not.toBeNull();
|
|
65
|
+
expect(appLink?.textContent).toContain("Open app");
|
|
66
|
+
expect(appLink?.className).toContain("bg-accent");
|
|
65
67
|
expect(appLink?.getAttribute("target")).toBeNull();
|
|
66
68
|
expect(appLink?.getAttribute("rel")).toBeNull();
|
|
67
|
-
expect(appLink?.
|
|
69
|
+
expect(appLink?.querySelector("svg")).toBeNull();
|
|
70
|
+
expect(container.textContent).not.toContain("/analytics");
|
|
68
71
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
const openOptionsButton = container.querySelector<HTMLButtonElement>(
|
|
73
|
+
'button[aria-label="Open options for Analytics"]',
|
|
74
|
+
);
|
|
75
|
+
expect(openOptionsButton).not.toBeNull();
|
|
76
|
+
await act(async () => {
|
|
77
|
+
openOptionsButton?.dispatchEvent(
|
|
78
|
+
new MouseEvent("pointerdown", { bubbles: true, button: 0 }),
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
const openInlineLink = Array.from(
|
|
82
|
+
document.querySelectorAll<HTMLAnchorElement>('[role="menu"] a'),
|
|
83
|
+
).find((anchor) => anchor.textContent?.includes("Open inline"));
|
|
84
|
+
const openNewTabLink = Array.from(
|
|
85
|
+
document.querySelectorAll<HTMLAnchorElement>('[role="menu"] a'),
|
|
86
|
+
).find((anchor) => anchor.textContent?.includes("Open in new tab"));
|
|
87
|
+
expect(openInlineLink?.getAttribute("target")).toBeNull();
|
|
88
|
+
expect(openNewTabLink?.getAttribute("target")).toBe("_blank");
|
|
89
|
+
|
|
90
|
+
const settingsButton = container.querySelector<HTMLButtonElement>(
|
|
91
|
+
'button[aria-label="Settings for Analytics"]',
|
|
92
|
+
);
|
|
93
|
+
expect(settingsButton).not.toBeNull();
|
|
94
|
+
expect(settingsButton?.className).toContain("size-7");
|
|
95
|
+
expect(
|
|
96
|
+
container.querySelector(
|
|
77
97
|
'button[aria-label="More actions for Analytics"]',
|
|
78
98
|
),
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
expect(action?.className).toContain(
|
|
86
|
-
"transition-[background-color,color]",
|
|
87
|
-
);
|
|
88
|
-
expect(action?.className).not.toContain("opacity-0");
|
|
89
|
-
}
|
|
99
|
+
).toBeNull();
|
|
100
|
+
expect(
|
|
101
|
+
container.querySelector(
|
|
102
|
+
'button[aria-label="View agent resources for Analytics"]',
|
|
103
|
+
),
|
|
104
|
+
).toBeNull();
|
|
90
105
|
});
|
|
91
106
|
|
|
92
107
|
it("labels per-app context as agent resources while preserving workspace scope", async () => {
|
|
@@ -106,11 +121,21 @@ describe("WorkspaceAppCard", () => {
|
|
|
106
121
|
);
|
|
107
122
|
});
|
|
108
123
|
|
|
109
|
-
const
|
|
110
|
-
'button[aria-label="
|
|
124
|
+
const settingsButton = container.querySelector<HTMLButtonElement>(
|
|
125
|
+
'button[aria-label="Settings for Analytics"]',
|
|
111
126
|
);
|
|
112
127
|
|
|
113
|
-
await act(async () =>
|
|
128
|
+
await act(async () => {
|
|
129
|
+
settingsButton?.dispatchEvent(
|
|
130
|
+
new MouseEvent("pointerdown", { bubbles: true, button: 0 }),
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
const resourcesItem = Array.from(
|
|
134
|
+
document.querySelectorAll<HTMLElement>('[role="menuitem"]'),
|
|
135
|
+
).find((item) => item.textContent?.includes("Agent resources"));
|
|
136
|
+
expect(resourcesItem).not.toBeUndefined();
|
|
137
|
+
|
|
138
|
+
await act(async () => resourcesItem?.click());
|
|
114
139
|
|
|
115
140
|
expect(document.body.textContent).toContain("Analytics agent resources");
|
|
116
141
|
expect(document.body.textContent).toContain(
|
|
@@ -139,8 +164,9 @@ describe("WorkspaceAppCard", () => {
|
|
|
139
164
|
});
|
|
140
165
|
|
|
141
166
|
const appLink = container.querySelector<HTMLAnchorElement>(
|
|
142
|
-
'a[
|
|
167
|
+
'a[href="https://builder.example.com/projects/new-app"]',
|
|
143
168
|
);
|
|
169
|
+
expect(appLink).not.toBeNull();
|
|
144
170
|
expect(appLink?.getAttribute("href")).toBe(
|
|
145
171
|
"https://builder.example.com/projects/new-app",
|
|
146
172
|
);
|
|
@@ -148,7 +174,7 @@ describe("WorkspaceAppCard", () => {
|
|
|
148
174
|
expect(appLink?.getAttribute("rel")).toBe("noreferrer");
|
|
149
175
|
});
|
|
150
176
|
|
|
151
|
-
it("shows ownership metadata in the
|
|
177
|
+
it("shows ownership metadata in the settings menu", async () => {
|
|
152
178
|
await act(async () => {
|
|
153
179
|
root.render(
|
|
154
180
|
<TooltipProvider>
|
|
@@ -168,13 +194,13 @@ describe("WorkspaceAppCard", () => {
|
|
|
168
194
|
);
|
|
169
195
|
});
|
|
170
196
|
|
|
171
|
-
const
|
|
172
|
-
'button[aria-label="
|
|
197
|
+
const settingsButton = container.querySelector<HTMLButtonElement>(
|
|
198
|
+
'button[aria-label="Settings for Analytics"]',
|
|
173
199
|
);
|
|
174
|
-
expect(
|
|
200
|
+
expect(settingsButton).not.toBeNull();
|
|
175
201
|
|
|
176
202
|
await act(async () => {
|
|
177
|
-
|
|
203
|
+
settingsButton?.dispatchEvent(
|
|
178
204
|
new MouseEvent("pointerdown", { bubbles: true, button: 0 }),
|
|
179
205
|
);
|
|
180
206
|
});
|
|
@@ -8,15 +8,16 @@ import {
|
|
|
8
8
|
IconChevronDown,
|
|
9
9
|
IconChevronRight,
|
|
10
10
|
IconClockHour4,
|
|
11
|
-
IconDots,
|
|
12
11
|
IconEdit,
|
|
13
12
|
IconEye,
|
|
14
13
|
IconEyeOff,
|
|
15
14
|
IconFileText,
|
|
16
|
-
|
|
15
|
+
IconKey,
|
|
16
|
+
IconSettings,
|
|
17
17
|
IconTrash,
|
|
18
18
|
IconUser,
|
|
19
19
|
IconUsersGroup,
|
|
20
|
+
IconWorld,
|
|
20
21
|
} from "@tabler/icons-react";
|
|
21
22
|
import { useEffect, useState, type FormEvent } from "react";
|
|
22
23
|
import { toast } from "sonner";
|
|
@@ -28,7 +29,10 @@ import {
|
|
|
28
29
|
type WorkspaceAppSummary,
|
|
29
30
|
} from "../lib/workspace-apps";
|
|
30
31
|
import { ActionQueryError } from "./action-query-error";
|
|
31
|
-
import {
|
|
32
|
+
import { AppIcon } from "./app-icon";
|
|
33
|
+
import { AppKeysPanel } from "./app-keys-popover";
|
|
34
|
+
import { AppListRow } from "./app-list-row";
|
|
35
|
+
import { AppOpenActions } from "./app-open-actions";
|
|
32
36
|
import { Badge } from "./ui/badge";
|
|
33
37
|
import { Button } from "./ui/button";
|
|
34
38
|
import {
|
|
@@ -38,7 +42,6 @@ import {
|
|
|
38
42
|
DialogFooter,
|
|
39
43
|
DialogHeader,
|
|
40
44
|
DialogTitle,
|
|
41
|
-
DialogTrigger,
|
|
42
45
|
} from "./ui/dialog";
|
|
43
46
|
import {
|
|
44
47
|
DropdownMenu,
|
|
@@ -67,12 +70,13 @@ export function WorkspaceAppCard({
|
|
|
67
70
|
const t = useT();
|
|
68
71
|
const { formatDate } = useFormatters();
|
|
69
72
|
const href = workspaceAppHref(app);
|
|
70
|
-
const openInNewTab = isPendingBuilderHref(app);
|
|
71
73
|
const isPending = app.status === "pending";
|
|
72
74
|
const pendingLabel = app.statusLabel || "Builder branch";
|
|
73
75
|
const isArchived = !!app.archived;
|
|
74
76
|
const audience = app.audience ?? "internal";
|
|
75
77
|
const [editOpen, setEditOpen] = useState(false);
|
|
78
|
+
const [keysOpen, setKeysOpen] = useState(false);
|
|
79
|
+
const [resourcesOpen, setResourcesOpen] = useState(false);
|
|
76
80
|
const [draftName, setDraftName] = useState(app.name);
|
|
77
81
|
const [draftDescription, setDraftDescription] = useState(
|
|
78
82
|
app.description || "",
|
|
@@ -132,145 +136,71 @@ export function WorkspaceAppCard({
|
|
|
132
136
|
};
|
|
133
137
|
|
|
134
138
|
return (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<a
|
|
145
|
-
href={href}
|
|
146
|
-
target={openInNewTab ? "_blank" : undefined}
|
|
147
|
-
rel={openInNewTab ? "noreferrer" : undefined}
|
|
148
|
-
aria-label={`Open ${app.name}`}
|
|
149
|
-
className="absolute inset-0 z-0 rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
150
|
-
/>
|
|
151
|
-
) : null}
|
|
152
|
-
|
|
153
|
-
<div className="pointer-events-none relative z-10 flex h-full min-w-0 items-start justify-between gap-3">
|
|
139
|
+
<>
|
|
140
|
+
<AppListRow
|
|
141
|
+
className={cn(
|
|
142
|
+
!href && "opacity-60",
|
|
143
|
+
isArchived && "opacity-70",
|
|
144
|
+
className,
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
<AppIcon id={app.id} name={app.name} size="sm" />
|
|
154
148
|
<div className="min-w-0 flex-1">
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
149
|
+
<h3 className="truncate text-sm font-semibold text-foreground">
|
|
150
|
+
{app.name}
|
|
151
|
+
</h3>
|
|
152
|
+
{app.description || isPending ? (
|
|
153
|
+
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
|
154
|
+
{app.description || pendingLabel}
|
|
155
|
+
</p>
|
|
156
|
+
) : null}
|
|
157
|
+
<div className="mt-1.5 flex min-w-0 flex-wrap items-center gap-2 text-[11px] text-muted-foreground">
|
|
159
158
|
{isPending ? (
|
|
160
|
-
<
|
|
161
|
-
variant="outline"
|
|
162
|
-
className="shrink-0 gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"
|
|
163
|
-
>
|
|
159
|
+
<span className="inline-flex min-w-0 items-center gap-1 rounded-full bg-muted px-2 py-0.5">
|
|
164
160
|
<IconClockHour4 size={12} />
|
|
165
|
-
{pendingLabel}
|
|
166
|
-
</
|
|
161
|
+
<span className="truncate">{pendingLabel}</span>
|
|
162
|
+
</span>
|
|
167
163
|
) : null}
|
|
168
164
|
{isArchived ? (
|
|
169
|
-
<
|
|
165
|
+
<span className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5">
|
|
170
166
|
<IconEyeOff size={12} />
|
|
171
167
|
Hidden
|
|
172
|
-
</
|
|
168
|
+
</span>
|
|
173
169
|
) : null}
|
|
174
170
|
{audience === "public" ? (
|
|
175
|
-
<
|
|
171
|
+
<span className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5">
|
|
176
172
|
<IconWorld size={12} />
|
|
177
173
|
Public
|
|
178
|
-
</
|
|
174
|
+
</span>
|
|
175
|
+
) : null}
|
|
176
|
+
{isPending && app.branchName ? (
|
|
177
|
+
<span className="min-w-0 truncate">{app.branchName}</span>
|
|
179
178
|
) : null}
|
|
180
179
|
</div>
|
|
181
|
-
<p className="mt-1 truncate font-mono text-[11px] text-muted-foreground">
|
|
182
|
-
{app.path}
|
|
183
|
-
</p>
|
|
184
|
-
{isPending && app.branchName ? (
|
|
185
|
-
<p className="mt-1 truncate text-xs text-muted-foreground">
|
|
186
|
-
Builder branch: {app.branchName}
|
|
187
|
-
</p>
|
|
188
|
-
) : null}
|
|
189
|
-
{app.description ? (
|
|
190
|
-
<p className="mt-2 min-h-10 line-clamp-2 text-[13px] leading-5 text-muted-foreground">
|
|
191
|
-
{app.description}
|
|
192
|
-
</p>
|
|
193
|
-
) : null}
|
|
194
180
|
</div>
|
|
195
|
-
<div className="flex shrink-0 items-center gap-
|
|
196
|
-
{
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
"inline-flex cursor-pointer items-center justify-center",
|
|
217
|
-
)}
|
|
218
|
-
onClick={(e) => e.stopPropagation()}
|
|
219
|
-
>
|
|
220
|
-
<IconDots size={15} />
|
|
221
|
-
</button>
|
|
222
|
-
</DropdownMenuTrigger>
|
|
223
|
-
</TooltipTrigger>
|
|
224
|
-
<TooltipContent>More actions</TooltipContent>
|
|
225
|
-
</Tooltip>
|
|
226
|
-
<DropdownMenuContent align="end" className="w-72">
|
|
227
|
-
<DropdownMenuItem
|
|
228
|
-
onSelect={(event) => {
|
|
229
|
-
event.preventDefault();
|
|
230
|
-
setEditOpen(true);
|
|
231
|
-
}}
|
|
232
|
-
>
|
|
233
|
-
<IconEdit size={14} className="mr-2" />
|
|
234
|
-
Edit details
|
|
235
|
-
</DropdownMenuItem>
|
|
236
|
-
{isPending ? (
|
|
237
|
-
<DropdownMenuItem
|
|
238
|
-
onSelect={handleRemovePending}
|
|
239
|
-
className="text-red-600 focus:text-red-600 dark:text-red-400 dark:focus:text-red-400"
|
|
240
|
-
>
|
|
241
|
-
<IconTrash size={14} className="mr-2" />
|
|
242
|
-
Remove from list
|
|
243
|
-
</DropdownMenuItem>
|
|
244
|
-
) : isArchived ? (
|
|
245
|
-
<DropdownMenuItem onSelect={handleUnarchive}>
|
|
246
|
-
<IconEye size={14} className="mr-2" />
|
|
247
|
-
Restore to list
|
|
248
|
-
</DropdownMenuItem>
|
|
249
|
-
) : (
|
|
250
|
-
<DropdownMenuItem onSelect={handleArchive}>
|
|
251
|
-
<IconEyeOff size={14} className="mr-2" />
|
|
252
|
-
Hide from list
|
|
253
|
-
</DropdownMenuItem>
|
|
254
|
-
)}
|
|
255
|
-
<DropdownMenuSeparator />
|
|
256
|
-
<DropdownMenuLabel className="font-normal">
|
|
257
|
-
<WorkspaceAppMetadata
|
|
258
|
-
app={app}
|
|
259
|
-
formatDate={formatDate}
|
|
260
|
-
labels={{
|
|
261
|
-
created: t("agents.dashboardMetadataCreated"),
|
|
262
|
-
createdBy: t("agents.dashboardMetadataCreatedBy"),
|
|
263
|
-
owner: t("dispatch.pages.appMetadataOwner"),
|
|
264
|
-
teams: t("dispatch.pages.appMetadataTeams"),
|
|
265
|
-
notTracked: t("agents.notTracked"),
|
|
266
|
-
}}
|
|
267
|
-
/>
|
|
268
|
-
</DropdownMenuLabel>
|
|
269
|
-
</DropdownMenuContent>
|
|
270
|
-
</DropdownMenu>
|
|
271
|
-
</div>
|
|
181
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
182
|
+
<WorkspaceAppOpenActions app={app} href={href} />
|
|
183
|
+
<WorkspaceAppSettings
|
|
184
|
+
app={app}
|
|
185
|
+
formatDate={formatDate}
|
|
186
|
+
isArchived={isArchived}
|
|
187
|
+
isPending={isPending}
|
|
188
|
+
onEdit={() => setEditOpen(true)}
|
|
189
|
+
onKeys={() => setKeysOpen(true)}
|
|
190
|
+
onResources={() => setResourcesOpen(true)}
|
|
191
|
+
onArchive={handleArchive}
|
|
192
|
+
onUnarchive={handleUnarchive}
|
|
193
|
+
onRemovePending={handleRemovePending}
|
|
194
|
+
labels={{
|
|
195
|
+
created: t("agents.dashboardMetadataCreated"),
|
|
196
|
+
createdBy: t("agents.dashboardMetadataCreatedBy"),
|
|
197
|
+
owner: t("dispatch.pages.appMetadataOwner"),
|
|
198
|
+
teams: t("dispatch.pages.appMetadataTeams"),
|
|
199
|
+
notTracked: t("agents.notTracked"),
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
272
202
|
</div>
|
|
273
|
-
</
|
|
203
|
+
</AppListRow>
|
|
274
204
|
<Dialog open={editOpen} onOpenChange={setEditOpen}>
|
|
275
205
|
<DialogContent>
|
|
276
206
|
<DialogHeader>
|
|
@@ -311,7 +241,157 @@ export function WorkspaceAppCard({
|
|
|
311
241
|
</form>
|
|
312
242
|
</DialogContent>
|
|
313
243
|
</Dialog>
|
|
314
|
-
|
|
244
|
+
<Dialog open={keysOpen} onOpenChange={setKeysOpen}>
|
|
245
|
+
<DialogContent className="max-w-md">
|
|
246
|
+
<DialogHeader>
|
|
247
|
+
<DialogTitle>Manage keys for {app.name}</DialogTitle>
|
|
248
|
+
<DialogDescription>
|
|
249
|
+
Choose which workspace credentials this app can use.
|
|
250
|
+
</DialogDescription>
|
|
251
|
+
</DialogHeader>
|
|
252
|
+
<AppKeysPanel appId={app.id} appName={app.name} />
|
|
253
|
+
<DialogFooter>
|
|
254
|
+
<Button type="button" onClick={() => setKeysOpen(false)}>
|
|
255
|
+
Done
|
|
256
|
+
</Button>
|
|
257
|
+
</DialogFooter>
|
|
258
|
+
</DialogContent>
|
|
259
|
+
</Dialog>
|
|
260
|
+
<AppResourcesDialog
|
|
261
|
+
app={app}
|
|
262
|
+
open={resourcesOpen}
|
|
263
|
+
onOpenChange={setResourcesOpen}
|
|
264
|
+
/>
|
|
265
|
+
</>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function WorkspaceAppOpenActions({
|
|
270
|
+
app,
|
|
271
|
+
href,
|
|
272
|
+
}: {
|
|
273
|
+
app: WorkspaceAppSummary;
|
|
274
|
+
href: string | null;
|
|
275
|
+
}) {
|
|
276
|
+
const openInNewTab = isPendingBuilderHref(app);
|
|
277
|
+
|
|
278
|
+
if (!href) {
|
|
279
|
+
return (
|
|
280
|
+
<Button size="sm" variant="outline" disabled>
|
|
281
|
+
Open app
|
|
282
|
+
</Button>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<AppOpenActions
|
|
288
|
+
name={app.name}
|
|
289
|
+
href={href}
|
|
290
|
+
target={openInNewTab ? "_blank" : undefined}
|
|
291
|
+
rel={openInNewTab ? "noreferrer" : undefined}
|
|
292
|
+
showInlineOption
|
|
293
|
+
showNewTabOption
|
|
294
|
+
/>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
type WorkspaceAppMetadataLabels = {
|
|
299
|
+
created: string;
|
|
300
|
+
createdBy: string;
|
|
301
|
+
owner: string;
|
|
302
|
+
teams: string;
|
|
303
|
+
notTracked: string;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
function WorkspaceAppSettings({
|
|
307
|
+
app,
|
|
308
|
+
formatDate,
|
|
309
|
+
isArchived,
|
|
310
|
+
isPending,
|
|
311
|
+
onEdit,
|
|
312
|
+
onKeys,
|
|
313
|
+
onResources,
|
|
314
|
+
onArchive,
|
|
315
|
+
onUnarchive,
|
|
316
|
+
onRemovePending,
|
|
317
|
+
labels,
|
|
318
|
+
}: {
|
|
319
|
+
app: WorkspaceAppSummary;
|
|
320
|
+
formatDate: ReturnType<typeof useFormatters>["formatDate"];
|
|
321
|
+
isArchived: boolean;
|
|
322
|
+
isPending: boolean;
|
|
323
|
+
onEdit: () => void;
|
|
324
|
+
onKeys: () => void;
|
|
325
|
+
onResources: () => void;
|
|
326
|
+
onArchive: () => void;
|
|
327
|
+
onUnarchive: () => void;
|
|
328
|
+
onRemovePending: () => void;
|
|
329
|
+
labels: WorkspaceAppMetadataLabels;
|
|
330
|
+
}) {
|
|
331
|
+
return (
|
|
332
|
+
<DropdownMenu>
|
|
333
|
+
<Tooltip>
|
|
334
|
+
<TooltipTrigger asChild>
|
|
335
|
+
<DropdownMenuTrigger asChild>
|
|
336
|
+
<Button
|
|
337
|
+
type="button"
|
|
338
|
+
variant="ghost"
|
|
339
|
+
size="sm"
|
|
340
|
+
aria-label={`Settings for ${app.name}`}
|
|
341
|
+
className={APP_CARD_ACTION_CLASS}
|
|
342
|
+
>
|
|
343
|
+
<IconSettings size={15} />
|
|
344
|
+
</Button>
|
|
345
|
+
</DropdownMenuTrigger>
|
|
346
|
+
</TooltipTrigger>
|
|
347
|
+
<TooltipContent>App settings</TooltipContent>
|
|
348
|
+
</Tooltip>
|
|
349
|
+
<DropdownMenuContent align="end" className="w-72">
|
|
350
|
+
<DropdownMenuItem onSelect={onEdit}>
|
|
351
|
+
<IconEdit size={14} className="mr-2" />
|
|
352
|
+
Edit details
|
|
353
|
+
</DropdownMenuItem>
|
|
354
|
+
{!isPending && !isArchived ? (
|
|
355
|
+
<>
|
|
356
|
+
<DropdownMenuItem onSelect={onKeys}>
|
|
357
|
+
<IconKey size={14} className="mr-2" />
|
|
358
|
+
Manage keys
|
|
359
|
+
</DropdownMenuItem>
|
|
360
|
+
<DropdownMenuItem onSelect={onResources}>
|
|
361
|
+
<IconFileText size={14} className="mr-2" />
|
|
362
|
+
Agent resources
|
|
363
|
+
</DropdownMenuItem>
|
|
364
|
+
</>
|
|
365
|
+
) : null}
|
|
366
|
+
{isPending ? (
|
|
367
|
+
<DropdownMenuItem
|
|
368
|
+
onSelect={onRemovePending}
|
|
369
|
+
className="text-destructive focus:text-destructive"
|
|
370
|
+
>
|
|
371
|
+
<IconTrash size={14} className="mr-2" />
|
|
372
|
+
Remove from list
|
|
373
|
+
</DropdownMenuItem>
|
|
374
|
+
) : isArchived ? (
|
|
375
|
+
<DropdownMenuItem onSelect={onUnarchive}>
|
|
376
|
+
<IconEye size={14} className="mr-2" />
|
|
377
|
+
Restore to list
|
|
378
|
+
</DropdownMenuItem>
|
|
379
|
+
) : (
|
|
380
|
+
<DropdownMenuItem onSelect={onArchive}>
|
|
381
|
+
<IconEyeOff size={14} className="mr-2" />
|
|
382
|
+
Hide from list
|
|
383
|
+
</DropdownMenuItem>
|
|
384
|
+
)}
|
|
385
|
+
<DropdownMenuSeparator />
|
|
386
|
+
<DropdownMenuLabel className="font-normal">
|
|
387
|
+
<WorkspaceAppMetadata
|
|
388
|
+
app={app}
|
|
389
|
+
formatDate={formatDate}
|
|
390
|
+
labels={labels}
|
|
391
|
+
/>
|
|
392
|
+
</DropdownMenuLabel>
|
|
393
|
+
</DropdownMenuContent>
|
|
394
|
+
</DropdownMenu>
|
|
315
395
|
);
|
|
316
396
|
}
|
|
317
397
|
|
|
@@ -322,13 +402,7 @@ function WorkspaceAppMetadata({
|
|
|
322
402
|
}: {
|
|
323
403
|
app: WorkspaceAppSummary;
|
|
324
404
|
formatDate: ReturnType<typeof useFormatters>["formatDate"];
|
|
325
|
-
labels:
|
|
326
|
-
created: string;
|
|
327
|
-
createdBy: string;
|
|
328
|
-
owner: string;
|
|
329
|
-
teams: string;
|
|
330
|
-
notTracked: string;
|
|
331
|
-
};
|
|
405
|
+
labels: WorkspaceAppMetadataLabels;
|
|
332
406
|
}) {
|
|
333
407
|
function formatMetadataDate(value: string | null | undefined): string {
|
|
334
408
|
if (!value) return labels.notTracked;
|
|
@@ -389,8 +463,15 @@ function WorkspaceAppMetadataRow({
|
|
|
389
463
|
);
|
|
390
464
|
}
|
|
391
465
|
|
|
392
|
-
function AppResourcesDialog({
|
|
393
|
-
|
|
466
|
+
function AppResourcesDialog({
|
|
467
|
+
app,
|
|
468
|
+
open,
|
|
469
|
+
onOpenChange,
|
|
470
|
+
}: {
|
|
471
|
+
app: WorkspaceAppSummary;
|
|
472
|
+
open: boolean;
|
|
473
|
+
onOpenChange: (open: boolean) => void;
|
|
474
|
+
}) {
|
|
394
475
|
const [inspectedResourceId, setInspectedResourceId] = useState<string | null>(
|
|
395
476
|
null,
|
|
396
477
|
);
|
|
@@ -408,27 +489,10 @@ function AppResourcesDialog({ app }: { app: WorkspaceAppSummary }) {
|
|
|
408
489
|
<Dialog
|
|
409
490
|
open={open}
|
|
410
491
|
onOpenChange={(nextOpen) => {
|
|
411
|
-
|
|
492
|
+
onOpenChange(nextOpen);
|
|
412
493
|
if (!nextOpen) setInspectedResourceId(null);
|
|
413
494
|
}}
|
|
414
495
|
>
|
|
415
|
-
<Tooltip>
|
|
416
|
-
<TooltipTrigger asChild>
|
|
417
|
-
<DialogTrigger asChild>
|
|
418
|
-
<Button
|
|
419
|
-
type="button"
|
|
420
|
-
variant="ghost"
|
|
421
|
-
size="sm"
|
|
422
|
-
aria-label={`View agent resources for ${app.name}`}
|
|
423
|
-
className={APP_CARD_ACTION_CLASS}
|
|
424
|
-
onClick={(e) => e.stopPropagation()}
|
|
425
|
-
>
|
|
426
|
-
<IconFileText size={14} />
|
|
427
|
-
</Button>
|
|
428
|
-
</DialogTrigger>
|
|
429
|
-
</TooltipTrigger>
|
|
430
|
-
<TooltipContent>View agent resources</TooltipContent>
|
|
431
|
-
</Tooltip>
|
|
432
496
|
<DialogContent className="max-w-2xl">
|
|
433
497
|
<DialogHeader>
|
|
434
498
|
<DialogTitle>{app.name} agent resources</DialogTitle>
|
|
@@ -546,7 +610,7 @@ function AppResourcesDialog({ app }: { app: WorkspaceAppSummary }) {
|
|
|
546
610
|
)}
|
|
547
611
|
</div>
|
|
548
612
|
<DialogFooter>
|
|
549
|
-
<Button type="button" onClick={() =>
|
|
613
|
+
<Button type="button" onClick={() => onOpenChange(false)}>
|
|
550
614
|
Done
|
|
551
615
|
</Button>
|
|
552
616
|
</DialogFooter>
|