@sentry/junior-dashboard 0.263.0 → 0.265.0
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/LICENSE +201 -0
- package/README.md +2 -5
- package/dist/app.js +119 -35
- package/dist/client.js +148 -135
- package/dist/index.d.ts +0 -1
- package/dist/index.js +119 -35
- package/dist/tailwind.css +1 -1
- package/package.json +9 -4
- package/src/api/schema.ts +0 -1
- package/src/app.ts +5 -20
- package/src/client/App.tsx +29 -31
- package/src/client/api.ts +7 -0
- package/src/client/components/BottomSheet.tsx +26 -18
- package/src/client/components/CopyButton.tsx +83 -0
- package/src/client/components/DetailsPanel.tsx +133 -0
- package/src/client/components/Drawer.tsx +34 -33
- package/src/client/components/FilterBar.tsx +16 -31
- package/src/client/components/Metric.tsx +33 -23
- package/src/client/components/PageContentSkeleton.tsx +1 -1
- package/src/client/components/Pagination.tsx +1 -1
- package/src/client/components/ParticipantAvatarStack.tsx +40 -36
- package/src/client/components/ProfileMenu.tsx +27 -93
- package/src/client/components/README.md +101 -64
- package/src/client/components/SegmentedNav.tsx +1 -1
- package/src/client/components/ShimmerText.tsx +2 -6
- package/src/client/components/ThemeSwitcher.tsx +19 -15
- package/src/client/components/charts/ActivityChart.tsx +173 -288
- package/src/client/components/charts/CacheWrites.tsx +63 -50
- package/src/client/components/charts/InputCacheChart.tsx +137 -131
- package/src/client/components/charts/README.md +4 -4
- package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
- package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
- package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
- package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
- package/src/client/components/layout/DashboardHeader.tsx +44 -90
- package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
- package/src/client/components/layout/VisualViewportShell.tsx +2 -2
- package/src/client/components/ui/accordion.tsx +77 -0
- package/src/client/components/ui/alert-dialog.tsx +89 -0
- package/src/client/components/ui/button.tsx +85 -0
- package/src/client/components/ui/chart.tsx +74 -0
- package/src/client/components/ui/checkbox.tsx +26 -0
- package/src/client/components/ui/collapsible.tsx +22 -0
- package/src/client/components/ui/field.tsx +49 -0
- package/src/client/components/ui/input.tsx +35 -0
- package/src/client/components/ui/native-select.tsx +57 -0
- package/src/client/components/ui/popover.tsx +67 -0
- package/src/client/components/ui/radio-group.tsx +32 -0
- package/src/client/components/ui/sheet.tsx +151 -0
- package/src/client/components/ui/skeleton.tsx +19 -0
- package/src/client/components/ui/table.tsx +80 -0
- package/src/client/components/ui/tabs.tsx +75 -0
- package/src/client/components/ui/textarea.tsx +17 -0
- package/src/client/components/ui/toast.tsx +215 -0
- package/src/client/components/ui/toggle-group.tsx +36 -0
- package/src/client/components/ui/toggle.tsx +37 -0
- package/src/client/components/ui/tooltip.tsx +168 -0
- package/src/client/components/ui/trace.tsx +96 -0
- package/src/client/components/ui/useSheet.ts +74 -0
- package/src/client/conversations/ChatLayout.tsx +7 -3
- package/src/client/conversations/ConversationBrief.tsx +17 -6
- package/src/client/conversations/ConversationChildAgents.tsx +87 -58
- package/src/client/conversations/ConversationComposer.tsx +26 -12
- package/src/client/conversations/ConversationDetails.tsx +198 -95
- package/src/client/conversations/ConversationEventDetails.tsx +141 -0
- package/src/client/conversations/ConversationEventLog.tsx +150 -129
- package/src/client/conversations/ConversationHeader.tsx +8 -3
- package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
- package/src/client/conversations/ConversationHomeList.tsx +1 -1
- package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
- package/src/client/conversations/ConversationMeta.tsx +111 -92
- package/src/client/conversations/ConversationPage.tsx +241 -110
- package/src/client/conversations/ConversationSidebar.tsx +48 -29
- package/src/client/conversations/ConversationUsageBreakdown.tsx +16 -3
- package/src/client/conversations/ConversationWatches.tsx +165 -0
- package/src/client/conversations/ConversationWorkspace.tsx +12 -22
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
- package/src/client/conversations/DetailsList.tsx +124 -0
- package/src/client/conversations/EventDetails.tsx +42 -8
- package/src/client/conversations/ForkConversationButton.tsx +27 -16
- package/src/client/conversations/ObjectCard.tsx +20 -8
- package/src/client/conversations/PendingMailboxStack.tsx +52 -33
- package/src/client/conversations/README.md +93 -19
- package/src/client/conversations/ShareConversationButton.tsx +28 -3
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
- package/src/client/conversations/ToolFrame.tsx +36 -52
- package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
- package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
- package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
- package/src/client/conversations/TranscriptLoading.tsx +1 -1
- package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
- package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
- package/src/client/conversations/TranscriptSubagentView.tsx +3 -3
- package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
- package/src/client/conversations/TranscriptToolView.tsx +1 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
- package/src/client/conversations/TranscriptView.tsx +63 -27
- package/src/client/conversations/eventLog.ts +57 -0
- package/src/client/conversations/eventTime.ts +60 -0
- package/src/client/conversations/queries.ts +29 -0
- package/src/client/conversations/transcriptBottomPinning.ts +34 -0
- package/src/client/format.ts +5 -0
- package/src/client/lib/utils.ts +1 -0
- package/src/client/pages/ConnectionsPage.tsx +2 -2
- package/src/client/pages/PersonalTokensPage.tsx +4 -4
- package/src/client/pages/SettingsPage.tsx +9 -10
- package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
- package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
- package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
- package/src/client/pages/automations/AutomationDetails.tsx +193 -0
- package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
- package/src/client/pages/automations/AutomationEditor.tsx +41 -29
- package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
- package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
- package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
- package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
- package/src/client/pages/automations/AutomationFilters.tsx +189 -74
- package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
- package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
- package/src/client/pages/automations/AutomationRow.tsx +12 -138
- package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
- package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
- package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
- package/src/client/pages/automations/AutomationStatus.tsx +201 -0
- package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
- package/src/client/pages/automations/AutomationsPage.tsx +80 -68
- package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
- package/src/client/pages/automations/README.md +57 -0
- package/src/client/pages/automations/TriggerFilters.tsx +22 -0
- package/src/client/pages/automations/automationFormat.ts +37 -23
- package/src/client/pages/code/CodeActivityChart.tsx +59 -101
- package/src/client/pages/code/CodePage.tsx +52 -55
- package/src/client/pages/code/RepositoryPage.tsx +6 -8
- package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
- package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
- package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
- package/src/client/pages/memory/MemoryDetails.tsx +218 -0
- package/src/client/pages/memory/MemoryPage.tsx +168 -112
- package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
- package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/people/ContributionGrid.tsx +23 -16
- package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
- package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
- package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
- package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
- package/src/client/pages/system/GuardianActivity.tsx +1 -2
- package/src/client/pages/system/PluginBarChart.tsx +106 -87
- package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
- package/src/client/pages/system/README.md +23 -0
- package/src/client/pages/system/SystemNavigation.tsx +5 -1
- package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
- package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
- package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
- package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
- package/src/client/pages/system/WorkspaceList.tsx +20 -30
- package/src/client/pages/system/WorkspacesPage.tsx +117 -119
- package/src/client/pages/system/sandboxSize.ts +8 -0
- package/src/client/pages/system/workspaceQueries.ts +53 -0
- package/src/client/pages/user/PluginUserPage.tsx +51 -2
- package/src/client/pages/user/pluginUserPageData.ts +1 -6
- package/src/client/styles.ts +4 -0
- package/src/client.tsx +7 -1
- package/src/mock-reporting/automation-collection.ts +4 -2
- package/src/mock-reporting/fixtures.ts +23 -7
- package/src/mock-reporting/routes.ts +12 -0
- package/src/mock-reporting/watches.ts +94 -0
- package/src/shell.ts +4 -9
- package/src/tailwind.css +77 -10
- package/src/client/components/Button.tsx +0 -118
- package/src/client/components/Field.tsx +0 -53
- package/src/client/components/SegmentedTabs.tsx +0 -119
- package/src/client/components/Select.tsx +0 -52
- package/src/client/components/Sheet.LICENSE +0 -21
- package/src/client/components/Sheet.tsx +0 -141
- package/src/client/components/Skeleton.tsx +0 -17
- package/src/client/components/TextInput.tsx +0 -58
- package/src/client/components/Tooltip.tsx +0 -216
- package/src/client/components/charts/useChartLayout.ts +0 -27
- package/src/client/conversations/ConversationDetailsFrame.tsx +0 -91
- package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
- package/src/client/pages/dev/ChartFixtures.tsx +0 -325
- package/src/client/pages/dev/ComponentsPage.tsx +0 -899
- package/src/client/pages/dev/ConversationFixture.tsx +0 -231
- package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
- package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
- package/src/client/pages/dev/SelectFixtures.tsx +0 -62
- package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
- package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
- package/src/client/pages/system/SnapshotSummary.tsx +0 -78
|
@@ -1,67 +1,182 @@
|
|
|
1
|
+
import { Pencil, Trash2 } from "lucide-react";
|
|
2
|
+
import { Link } from "react-router";
|
|
1
3
|
import type { StatsReport, WorkspaceReport } from "@sentry/junior/api/schema";
|
|
2
4
|
|
|
3
5
|
import { useStatsData } from "../../api";
|
|
6
|
+
import { buttonVariants, Button } from "../../components/ui/button";
|
|
7
|
+
import { Skeleton } from "../../components/ui/skeleton";
|
|
8
|
+
import {
|
|
9
|
+
Tooltip,
|
|
10
|
+
TooltipContent,
|
|
11
|
+
TooltipTrigger,
|
|
12
|
+
} from "../../components/ui/tooltip";
|
|
13
|
+
import { Detail, DetailList } from "../../components/DetailList";
|
|
14
|
+
import { DetailsPanel } from "../../components/DetailsPanel";
|
|
4
15
|
import { EmptyTelemetry } from "../../components/EmptyTelemetry";
|
|
16
|
+
import { PageContentSkeleton } from "../../components/PageContentSkeleton";
|
|
5
17
|
import { WorkspaceUsageChart } from "../../components/charts/WorkspaceUsageChart";
|
|
6
18
|
import { workspaceUsageDays } from "../../components/charts/workspaceUsage";
|
|
7
19
|
import type { TimeRangeDays } from "../../components/controls/TimeRangeSelector";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
20
|
+
import {
|
|
21
|
+
formatMs,
|
|
22
|
+
formatRelativeTime,
|
|
23
|
+
formatTime,
|
|
24
|
+
workspacePath,
|
|
25
|
+
} from "../../format";
|
|
26
|
+
import { formatSandboxSize } from "./sandboxSize";
|
|
27
|
+
import { useWorkspaceData } from "./workspaceQueries";
|
|
11
28
|
|
|
12
|
-
|
|
29
|
+
// Workspace switch counters are daily-only (`junior_stats.date`).
|
|
30
|
+
const USAGE_RANGE: TimeRangeDays = 30;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Show one Workspace beside the list with Edit and Delete in the header. The
|
|
34
|
+
* list item renders at once; the detail read adds the current snapshot.
|
|
35
|
+
*/
|
|
13
36
|
export function WorkspaceDetails(props: {
|
|
14
37
|
defaultVcpus: number | null;
|
|
15
|
-
|
|
16
|
-
|
|
38
|
+
listError: boolean;
|
|
39
|
+
onClose(): void;
|
|
40
|
+
onDelete(workspace: WorkspaceReport, source: HTMLButtonElement): void;
|
|
41
|
+
workspace: WorkspaceReport | undefined;
|
|
42
|
+
workspaceId: string;
|
|
17
43
|
}) {
|
|
44
|
+
const detail = useWorkspaceData(props.workspaceId);
|
|
18
45
|
const statsQuery = useStatsData();
|
|
46
|
+
const workspace = detail.data ?? props.workspace;
|
|
47
|
+
const missing = !workspace && (detail.isError || props.listError);
|
|
19
48
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
49
|
+
<DetailsPanel
|
|
50
|
+
actions={
|
|
51
|
+
workspace ? (
|
|
52
|
+
<>
|
|
53
|
+
<Tooltip>
|
|
54
|
+
<TooltipTrigger
|
|
55
|
+
render={
|
|
56
|
+
<Link
|
|
57
|
+
aria-label={`Edit ${workspace.name}`}
|
|
58
|
+
className={buttonVariants({ size: "icon" })}
|
|
59
|
+
to={`${workspacePath(workspace.id)}/edit`}
|
|
60
|
+
>
|
|
61
|
+
<Pencil aria-hidden="true" size={15} />
|
|
62
|
+
</Link>
|
|
63
|
+
}
|
|
64
|
+
/>
|
|
65
|
+
<TooltipContent size="compact">Edit Workspace</TooltipContent>
|
|
66
|
+
</Tooltip>
|
|
67
|
+
<Tooltip>
|
|
68
|
+
<TooltipTrigger
|
|
69
|
+
render={
|
|
70
|
+
<Button
|
|
71
|
+
aria-label={`Delete ${workspace.name}`}
|
|
72
|
+
onClick={(event) =>
|
|
73
|
+
props.onDelete(workspace, event.currentTarget)
|
|
74
|
+
}
|
|
75
|
+
size="icon"
|
|
76
|
+
>
|
|
77
|
+
<Trash2 aria-hidden="true" size={15} />
|
|
78
|
+
</Button>
|
|
79
|
+
}
|
|
80
|
+
/>
|
|
81
|
+
<TooltipContent size="compact">Delete Workspace</TooltipContent>
|
|
82
|
+
</Tooltip>
|
|
83
|
+
</>
|
|
84
|
+
) : undefined
|
|
85
|
+
}
|
|
86
|
+
label="Workspace details"
|
|
87
|
+
onClose={props.onClose}
|
|
88
|
+
sheetTitle={workspace?.name}
|
|
89
|
+
title={
|
|
90
|
+
<h2 className="m-0 break-words font-display text-base font-medium text-dashboard-text">
|
|
91
|
+
{workspace?.name ?? "Workspace details"}
|
|
92
|
+
</h2>
|
|
93
|
+
}
|
|
94
|
+
>
|
|
95
|
+
<div className="p-4 [overflow-wrap:anywhere]">
|
|
96
|
+
{workspace ? (
|
|
97
|
+
<WorkspaceDetailsContent
|
|
98
|
+
defaultVcpus={props.defaultVcpus}
|
|
99
|
+
error={Boolean(statsQuery.error)}
|
|
100
|
+
loading={statsQuery.isPending}
|
|
101
|
+
snapshot={
|
|
102
|
+
detail.data ? "ready" : detail.isError ? "error" : "loading"
|
|
103
|
+
}
|
|
104
|
+
stats={statsQuery.data}
|
|
105
|
+
workspace={workspace}
|
|
106
|
+
/>
|
|
107
|
+
) : missing ? (
|
|
108
|
+
<EmptyTelemetry>
|
|
109
|
+
Workspace not found. It may have been deleted.
|
|
110
|
+
</EmptyTelemetry>
|
|
111
|
+
) : (
|
|
112
|
+
<PageContentSkeleton
|
|
113
|
+
label="Loading Workspace details"
|
|
114
|
+
variant="panel"
|
|
115
|
+
/>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
</DetailsPanel>
|
|
28
119
|
);
|
|
29
120
|
}
|
|
30
121
|
|
|
31
|
-
/** Present
|
|
122
|
+
/** Present repositories, Sandbox size, snapshot, setup, and switch volume for one Workspace. */
|
|
32
123
|
export function WorkspaceDetailsContent(props: {
|
|
33
124
|
defaultVcpus: number | null;
|
|
34
125
|
error: boolean;
|
|
35
126
|
loading: boolean;
|
|
36
|
-
|
|
127
|
+
snapshot: "error" | "loading" | "ready";
|
|
37
128
|
stats: StatsReport | undefined;
|
|
38
129
|
workspace: WorkspaceReport;
|
|
39
130
|
}) {
|
|
131
|
+
const { workspace } = props;
|
|
40
132
|
return (
|
|
41
133
|
<div className="grid min-w-0 gap-4">
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
134
|
+
<DetailList>
|
|
135
|
+
<Detail label="Repositories">
|
|
136
|
+
{workspace.repos.length === 0 ? (
|
|
137
|
+
<span className="text-dashboard-text-muted">None</span>
|
|
138
|
+
) : (
|
|
139
|
+
<ul className="m-0 grid list-none gap-1.5 p-0">
|
|
140
|
+
{workspace.repos.map((repo) => (
|
|
141
|
+
<li className="min-w-0" key={`${repo.provider}:${repo.repo}`}>
|
|
142
|
+
<span className="block font-mono text-xs text-dashboard-text">
|
|
143
|
+
{repo.provider}:{repo.repo}
|
|
144
|
+
</span>
|
|
145
|
+
<span className="block font-mono text-xs text-dashboard-text-muted">
|
|
146
|
+
{repo.checkoutPath}
|
|
147
|
+
</span>
|
|
148
|
+
</li>
|
|
149
|
+
))}
|
|
150
|
+
</ul>
|
|
151
|
+
)}
|
|
152
|
+
</Detail>
|
|
153
|
+
<Detail label="Sandbox size">
|
|
154
|
+
{formatSandboxSize(workspace.vcpus ?? props.defaultVcpus)}
|
|
155
|
+
{workspace.vcpus === null ? (
|
|
156
|
+
<span className="text-dashboard-text-muted"> · default</span>
|
|
157
|
+
) : null}
|
|
158
|
+
</Detail>
|
|
159
|
+
<Detail label="Snapshot">
|
|
160
|
+
<WorkspaceSnapshot state={props.snapshot} workspace={workspace} />
|
|
161
|
+
</Detail>
|
|
162
|
+
<Detail label="Setup script">
|
|
163
|
+
{workspace.setupScript.trim() ? (
|
|
164
|
+
<pre className="m-0 max-h-48 overflow-auto whitespace-pre-wrap rounded-md bg-dashboard-fill-soft p-2 font-mono text-xs leading-relaxed text-dashboard-text">
|
|
165
|
+
{workspace.setupScript}
|
|
166
|
+
</pre>
|
|
167
|
+
) : (
|
|
168
|
+
<span className="text-dashboard-text-muted">None</span>
|
|
169
|
+
)}
|
|
170
|
+
</Detail>
|
|
171
|
+
</DetailList>
|
|
55
172
|
{!props.stats ? (
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
</EmptyTelemetry>
|
|
64
|
-
</Card>
|
|
173
|
+
<EmptyTelemetry>
|
|
174
|
+
{props.error
|
|
175
|
+
? "Workspace usage failed to load."
|
|
176
|
+
: props.loading
|
|
177
|
+
? "Loading Workspace usage."
|
|
178
|
+
: "No usage in this period."}
|
|
179
|
+
</EmptyTelemetry>
|
|
65
180
|
) : (
|
|
66
181
|
<div className="grid min-w-0 gap-3">
|
|
67
182
|
{props.error ? (
|
|
@@ -71,16 +186,58 @@ export function WorkspaceDetailsContent(props: {
|
|
|
71
186
|
) : null}
|
|
72
187
|
<WorkspaceUsageChart
|
|
73
188
|
days={workspaceUsageDays({
|
|
74
|
-
workspaceId:
|
|
75
|
-
|
|
76
|
-
range: props.range,
|
|
189
|
+
workspaceId: workspace.id,
|
|
190
|
+
range: USAGE_RANGE,
|
|
77
191
|
stats: props.stats.stats,
|
|
78
192
|
})}
|
|
79
|
-
range={
|
|
80
|
-
workspaceName={
|
|
193
|
+
range={USAGE_RANGE}
|
|
194
|
+
workspaceName={workspace.name}
|
|
81
195
|
/>
|
|
82
196
|
</div>
|
|
83
197
|
)}
|
|
84
198
|
</div>
|
|
85
199
|
);
|
|
86
200
|
}
|
|
201
|
+
|
|
202
|
+
function WorkspaceSnapshot(props: {
|
|
203
|
+
state: "error" | "loading" | "ready";
|
|
204
|
+
workspace: WorkspaceReport;
|
|
205
|
+
}) {
|
|
206
|
+
if (props.state === "loading") {
|
|
207
|
+
return (
|
|
208
|
+
<span className="grid gap-1.5" role="status">
|
|
209
|
+
<span className="sr-only">Loading snapshot</span>
|
|
210
|
+
<Skeleton className="h-4 w-36" />
|
|
211
|
+
<Skeleton className="h-3.5 w-48" />
|
|
212
|
+
</span>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (props.state === "error") {
|
|
216
|
+
return (
|
|
217
|
+
<span className="text-dashboard-text-muted">
|
|
218
|
+
Snapshot status could not be loaded.
|
|
219
|
+
</span>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
const snapshot = props.workspace.snapshot;
|
|
223
|
+
if (!snapshot) {
|
|
224
|
+
return (
|
|
225
|
+
<span className="text-dashboard-text-muted">
|
|
226
|
+
Not built yet. The first successful prepare builds it.
|
|
227
|
+
</span>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return (
|
|
231
|
+
<>
|
|
232
|
+
<span className="block tabular-nums">
|
|
233
|
+
<span title={formatTime(snapshot.generatedAt)}>
|
|
234
|
+
{formatRelativeTime(snapshot.generatedAt)}
|
|
235
|
+
</span>{" "}
|
|
236
|
+
· built in {formatMs(snapshot.buildDurationMs)}
|
|
237
|
+
</span>
|
|
238
|
+
<span className="block break-all font-mono text-xs text-dashboard-text-muted">
|
|
239
|
+
{snapshot.id}
|
|
240
|
+
</span>
|
|
241
|
+
</>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Plus, Trash2 } from "lucide-react";
|
|
2
2
|
import type { FormEvent } from "react";
|
|
3
|
+
import { Link } from "react-router";
|
|
3
4
|
|
|
4
|
-
import { Button } from "../../components/
|
|
5
|
-
import {
|
|
5
|
+
import { Button, buttonVariants } from "../../components/ui/button";
|
|
6
|
+
import { Input } from "../../components/ui/input";
|
|
7
|
+
import { Textarea } from "../../components/ui/textarea";
|
|
8
|
+
import { Field, FieldDescription, FieldLabel } from "../../components/ui/field";
|
|
6
9
|
import { InlineError } from "../../components/InlineError";
|
|
7
|
-
import { TextArea, TextInput } from "../../components/TextInput";
|
|
8
10
|
import { Card } from "../../components/layout/Card";
|
|
9
|
-
import { formatSandboxSize } from "./
|
|
11
|
+
import { formatSandboxSize } from "./sandboxSize";
|
|
10
12
|
import {
|
|
11
13
|
createRepoDraft,
|
|
12
14
|
type RepoDraft,
|
|
@@ -16,6 +18,7 @@ import {
|
|
|
16
18
|
type WorkspaceEditorProps = {
|
|
17
19
|
busy: boolean;
|
|
18
20
|
canSave: boolean;
|
|
21
|
+
cancelPath: string;
|
|
19
22
|
defaultVcpus: number | null;
|
|
20
23
|
draft: WorkspaceDraft;
|
|
21
24
|
error?: string;
|
|
@@ -51,24 +54,9 @@ export function WorkspaceEditor(props: WorkspaceEditorProps) {
|
|
|
51
54
|
return (
|
|
52
55
|
<Card className="mb-0" padding="md" variant="section">
|
|
53
56
|
<form className="grid gap-6" onSubmit={submit}>
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
Configuration
|
|
58
|
-
</h2>
|
|
59
|
-
<p className="m-0 text-sm leading-relaxed text-dashboard-text-muted">
|
|
60
|
-
Change the recipe Junior uses the next time this Workspace is
|
|
61
|
-
prepared.
|
|
62
|
-
</p>
|
|
63
|
-
</div>
|
|
64
|
-
) : null}
|
|
65
|
-
|
|
66
|
-
<Field
|
|
67
|
-
help="Lowercase name used when agents switch into this Workspace."
|
|
68
|
-
htmlFor="workspace-name"
|
|
69
|
-
label="Name"
|
|
70
|
-
>
|
|
71
|
-
<TextInput
|
|
57
|
+
<Field>
|
|
58
|
+
<FieldLabel htmlFor="workspace-name">Name</FieldLabel>
|
|
59
|
+
<Input
|
|
72
60
|
autoComplete="off"
|
|
73
61
|
id="workspace-name"
|
|
74
62
|
maxLength={64}
|
|
@@ -78,6 +66,9 @@ export function WorkspaceEditor(props: WorkspaceEditorProps) {
|
|
|
78
66
|
placeholder="sentry"
|
|
79
67
|
value={props.draft.name}
|
|
80
68
|
/>
|
|
69
|
+
<FieldDescription id="workspace-name-help">
|
|
70
|
+
Lowercase name used when agents switch into this Workspace.
|
|
71
|
+
</FieldDescription>
|
|
81
72
|
</Field>
|
|
82
73
|
|
|
83
74
|
<section className="grid gap-3" aria-labelledby="workspace-repos-title">
|
|
@@ -120,12 +111,14 @@ export function WorkspaceEditor(props: WorkspaceEditorProps) {
|
|
|
120
111
|
key={repo.key}
|
|
121
112
|
>
|
|
122
113
|
<div className="grid gap-3 sm:grid-cols-[minmax(0,8.5rem)_minmax(0,1fr)_auto] sm:items-end">
|
|
123
|
-
<Field
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
<Field className="gap-1.5">
|
|
115
|
+
<FieldLabel
|
|
116
|
+
htmlFor={`workspace-repo-provider-${repo.key}`}
|
|
117
|
+
className="font-sans text-xs font-medium text-dashboard-text-muted"
|
|
118
|
+
>
|
|
119
|
+
Provider
|
|
120
|
+
</FieldLabel>
|
|
121
|
+
<Input
|
|
129
122
|
aria-label={`Provider ${index + 1}`}
|
|
130
123
|
id={`workspace-repo-provider-${repo.key}`}
|
|
131
124
|
onChange={(event) =>
|
|
@@ -135,12 +128,14 @@ export function WorkspaceEditor(props: WorkspaceEditorProps) {
|
|
|
135
128
|
value={repo.provider}
|
|
136
129
|
/>
|
|
137
130
|
</Field>
|
|
138
|
-
<Field
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
131
|
+
<Field className="gap-1.5">
|
|
132
|
+
<FieldLabel
|
|
133
|
+
htmlFor={`workspace-repo-name-${repo.key}`}
|
|
134
|
+
className="font-sans text-xs font-medium text-dashboard-text-muted"
|
|
135
|
+
>
|
|
136
|
+
Repository
|
|
137
|
+
</FieldLabel>
|
|
138
|
+
<Input
|
|
144
139
|
aria-label={`Repository ${index + 1}`}
|
|
145
140
|
id={`workspace-repo-name-${repo.key}`}
|
|
146
141
|
onChange={(event) =>
|
|
@@ -154,7 +149,7 @@ export function WorkspaceEditor(props: WorkspaceEditorProps) {
|
|
|
154
149
|
aria-label={`Remove repository ${index + 1}`}
|
|
155
150
|
className="w-full sm:w-auto"
|
|
156
151
|
onClick={() => removeRepo(repo.key)}
|
|
157
|
-
|
|
152
|
+
variant="danger"
|
|
158
153
|
type="button"
|
|
159
154
|
>
|
|
160
155
|
<Trash2 aria-hidden="true" size={14} />
|
|
@@ -166,8 +161,23 @@ export function WorkspaceEditor(props: WorkspaceEditorProps) {
|
|
|
166
161
|
</div>
|
|
167
162
|
</section>
|
|
168
163
|
|
|
169
|
-
<Field
|
|
170
|
-
|
|
164
|
+
<Field>
|
|
165
|
+
<FieldLabel htmlFor="workspace-setup">Setup script</FieldLabel>
|
|
166
|
+
<Textarea
|
|
167
|
+
className="font-mono"
|
|
168
|
+
id="workspace-setup"
|
|
169
|
+
onChange={(event) =>
|
|
170
|
+
props.onChange({
|
|
171
|
+
...props.draft,
|
|
172
|
+
setupScript: event.target.value,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
placeholder={
|
|
176
|
+
'pnpm install --dir "$JUNIOR_REPOS_ROOT/sentry"\n# cwd is $JUNIOR_WORKSPACE_ROOT'
|
|
177
|
+
}
|
|
178
|
+
value={props.draft.setupScript}
|
|
179
|
+
/>
|
|
180
|
+
<FieldDescription id="workspace-setup-help">
|
|
171
181
|
<>
|
|
172
182
|
Runs once while Junior builds the reusable snapshot. Current
|
|
173
183
|
working directory is{" "}
|
|
@@ -183,31 +193,12 @@ export function WorkspaceEditor(props: WorkspaceEditorProps) {
|
|
|
183
193
|
</code>
|
|
184
194
|
). Use those variables instead of hard-coded absolute paths.
|
|
185
195
|
</>
|
|
186
|
-
|
|
187
|
-
htmlFor="workspace-setup"
|
|
188
|
-
label="Setup script"
|
|
189
|
-
>
|
|
190
|
-
<TextArea
|
|
191
|
-
id="workspace-setup"
|
|
192
|
-
onChange={(event) =>
|
|
193
|
-
props.onChange({
|
|
194
|
-
...props.draft,
|
|
195
|
-
setupScript: event.target.value,
|
|
196
|
-
})
|
|
197
|
-
}
|
|
198
|
-
placeholder={
|
|
199
|
-
'pnpm install --dir "$JUNIOR_REPOS_ROOT/sentry"\n# cwd is $JUNIOR_WORKSPACE_ROOT'
|
|
200
|
-
}
|
|
201
|
-
value={props.draft.setupScript}
|
|
202
|
-
/>
|
|
196
|
+
</FieldDescription>
|
|
203
197
|
</Field>
|
|
204
198
|
|
|
205
|
-
<Field
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
label="vCPUs"
|
|
209
|
-
>
|
|
210
|
-
<TextInput
|
|
199
|
+
<Field>
|
|
200
|
+
<FieldLabel htmlFor="workspace-vcpus">vCPUs</FieldLabel>
|
|
201
|
+
<Input
|
|
211
202
|
autoComplete="off"
|
|
212
203
|
id="workspace-vcpus"
|
|
213
204
|
inputMode="numeric"
|
|
@@ -217,18 +208,25 @@ export function WorkspaceEditor(props: WorkspaceEditorProps) {
|
|
|
217
208
|
placeholder={formatSandboxSize(props.defaultVcpus)}
|
|
218
209
|
value={props.draft.vcpus}
|
|
219
210
|
/>
|
|
211
|
+
<FieldDescription id="workspace-vcpus-help">
|
|
212
|
+
Sandbox vCPUs for this Workspace. Each vCPU adds 2 GB of memory.
|
|
213
|
+
Leave empty to use the app default.
|
|
214
|
+
</FieldDescription>
|
|
220
215
|
</Field>
|
|
221
216
|
|
|
222
217
|
{props.error ? <InlineError>{props.error}</InlineError> : null}
|
|
223
218
|
|
|
224
219
|
<div className="flex flex-wrap items-center gap-3 border-t border-dashboard-border-subtle pt-5">
|
|
225
|
-
<Button disabled={!props.canSave} type="submit">
|
|
220
|
+
<Button disabled={!props.canSave} type="submit" variant="primary">
|
|
226
221
|
{props.busy
|
|
227
222
|
? "Saving…"
|
|
228
223
|
: props.editing
|
|
229
224
|
? "Save changes"
|
|
230
225
|
: "Create Workspace"}
|
|
231
226
|
</Button>
|
|
227
|
+
<Link className={buttonVariants()} to={props.cancelPath}>
|
|
228
|
+
Cancel
|
|
229
|
+
</Link>
|
|
232
230
|
<p className="m-0 text-xs text-dashboard-text-muted">
|
|
233
231
|
{props.editing
|
|
234
232
|
? "Saved changes apply the next time this Workspace is prepared."
|
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import { useMutation,
|
|
1
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { useNavigate, useParams } from "react-router";
|
|
4
4
|
import {
|
|
5
|
-
workspaceListSchema,
|
|
6
5
|
workspaceSchema,
|
|
7
6
|
type WorkspaceListReport,
|
|
8
7
|
} from "@sentry/junior/api/schema";
|
|
9
8
|
|
|
10
9
|
import { EmptyTelemetry } from "../../components/EmptyTelemetry";
|
|
11
|
-
import type { TimeRangeDays } from "../../components/controls/TimeRangeSelector";
|
|
12
10
|
import { LoadingView } from "../../components/LoadingView";
|
|
13
11
|
import { Card } from "../../components/layout/Card";
|
|
14
12
|
import { PageHeader } from "../../components/layout/PageHeader";
|
|
15
|
-
import {
|
|
13
|
+
import { workspacePath } from "../../format";
|
|
14
|
+
import { post, put } from "../../http";
|
|
16
15
|
import { SystemPageLayout } from "./SystemPageLayout";
|
|
17
|
-
import { WorkspaceDetails } from "./WorkspaceDetails";
|
|
18
16
|
import { WorkspaceEditor } from "./WorkspaceEditor";
|
|
19
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
readWorkspaceApiError,
|
|
19
|
+
useWorkspaceData,
|
|
20
|
+
useWorkspacesData,
|
|
21
|
+
workspaceQueryKey,
|
|
22
|
+
workspacesQueryKey,
|
|
23
|
+
} from "./workspaceQueries";
|
|
20
24
|
import {
|
|
21
25
|
canSaveWorkspaceDraft,
|
|
22
26
|
createWorkspaceDraft,
|
|
@@ -25,41 +29,21 @@ import {
|
|
|
25
29
|
workspaceDraftBody,
|
|
26
30
|
} from "./workspaceDraft";
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
/** Workspace switch stats are daily counters only. */
|
|
30
|
-
const WORKSPACE_RANGE_OPTIONS: TimeRangeDays[] = [7, 30, 90];
|
|
31
|
-
|
|
32
|
-
/** Create or edit one Workspace at a stable dashboard route. */
|
|
32
|
+
/** Create or edit one Workspace recipe on a full page. */
|
|
33
33
|
export function WorkspaceFormPage() {
|
|
34
34
|
const { workspaceId } = useParams();
|
|
35
35
|
const editing = workspaceId !== undefined;
|
|
36
36
|
const navigate = useNavigate();
|
|
37
37
|
const queryClient = useQueryClient();
|
|
38
|
-
const [range, setRange] = useState<TimeRangeDays>(30);
|
|
39
38
|
const [newDraft, setNewDraft] = useState(createWorkspaceDraft);
|
|
40
39
|
const [editedDraft, setEditedDraft] = useState<{
|
|
41
40
|
draft: WorkspaceDraft;
|
|
42
41
|
workspaceId: string;
|
|
43
42
|
}>();
|
|
44
43
|
|
|
45
|
-
const workspaceQuery =
|
|
46
|
-
enabled: editing,
|
|
47
|
-
queryKey: ["dashboard", "workspace", workspaceId],
|
|
48
|
-
queryFn: ({ signal }) =>
|
|
49
|
-
fetchDashboardJson(
|
|
50
|
-
workspaceSchema,
|
|
51
|
-
`/api/workspaces/${encodeURIComponent(workspaceId!)}`,
|
|
52
|
-
signal,
|
|
53
|
-
),
|
|
54
|
-
retry: false,
|
|
55
|
-
});
|
|
44
|
+
const workspaceQuery = useWorkspaceData(workspaceId);
|
|
56
45
|
// Shares the list cache so the default size is usually loaded already.
|
|
57
|
-
const workspacesQuery =
|
|
58
|
-
queryKey: workspacesQueryKey,
|
|
59
|
-
queryFn: ({ signal }) =>
|
|
60
|
-
fetchDashboardJson(workspaceListSchema, "/api/workspaces", signal),
|
|
61
|
-
retry: false,
|
|
62
|
-
});
|
|
46
|
+
const workspacesQuery = useWorkspacesData();
|
|
63
47
|
const defaultVcpus = workspacesQuery.data?.defaultVcpus ?? null;
|
|
64
48
|
|
|
65
49
|
// Seed the edit draft once per Workspace id. editWorkspaceDraft assigns fresh
|
|
@@ -103,7 +87,11 @@ export function WorkspaceFormPage() {
|
|
|
103
87
|
].sort((left, right) => left.name.localeCompare(right.name)),
|
|
104
88
|
},
|
|
105
89
|
);
|
|
106
|
-
|
|
90
|
+
// A recipe change can retire the current snapshot; read it again.
|
|
91
|
+
await queryClient.invalidateQueries({
|
|
92
|
+
queryKey: workspaceQueryKey(workspace.id),
|
|
93
|
+
});
|
|
94
|
+
navigate(workspacePath(workspace.id));
|
|
107
95
|
},
|
|
108
96
|
});
|
|
109
97
|
|
|
@@ -121,16 +109,9 @@ export function WorkspaceFormPage() {
|
|
|
121
109
|
<PageHeader
|
|
122
110
|
description={
|
|
123
111
|
editing
|
|
124
|
-
? "
|
|
112
|
+
? "Change the repositories, setup script, or Sandbox size."
|
|
125
113
|
: "Name the recipe, choose repositories, and optionally add a one-time setup script."
|
|
126
114
|
}
|
|
127
|
-
{...(editing
|
|
128
|
-
? {
|
|
129
|
-
onRangeChange: setRange,
|
|
130
|
-
range,
|
|
131
|
-
rangeOptions: WORKSPACE_RANGE_OPTIONS,
|
|
132
|
-
}
|
|
133
|
-
: {})}
|
|
134
115
|
title={
|
|
135
116
|
editing
|
|
136
117
|
? (workspaceQuery.data?.name ?? "Workspace")
|
|
@@ -138,14 +119,6 @@ export function WorkspaceFormPage() {
|
|
|
138
119
|
}
|
|
139
120
|
/>
|
|
140
121
|
|
|
141
|
-
{editing && workspaceQuery.data ? (
|
|
142
|
-
<WorkspaceDetails
|
|
143
|
-
defaultVcpus={defaultVcpus}
|
|
144
|
-
range={range}
|
|
145
|
-
workspace={workspaceQuery.data}
|
|
146
|
-
/>
|
|
147
|
-
) : null}
|
|
148
|
-
|
|
149
122
|
{workspaceQuery.error ? (
|
|
150
123
|
<Card padding="sm">
|
|
151
124
|
<EmptyTelemetry>
|
|
@@ -158,8 +131,11 @@ export function WorkspaceFormPage() {
|
|
|
158
131
|
) : draft ? (
|
|
159
132
|
<WorkspaceEditor
|
|
160
133
|
busy={saveMutation.isPending}
|
|
161
|
-
defaultVcpus={defaultVcpus}
|
|
162
134
|
canSave={canSaveWorkspaceDraft(draft, saveMutation.isPending)}
|
|
135
|
+
cancelPath={
|
|
136
|
+
workspaceId ? workspacePath(workspaceId) : "/system/workspaces"
|
|
137
|
+
}
|
|
138
|
+
defaultVcpus={defaultVcpus}
|
|
163
139
|
draft={draft}
|
|
164
140
|
editing={editing}
|
|
165
141
|
error={
|