@opengeni/react 0.12.0 → 0.15.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/README.md +20 -14
- package/dist/chunk-TOJR776I.js +2280 -0
- package/dist/chunk-TOJR776I.js.map +1 -0
- package/dist/index.d.ts +1040 -363
- package/dist/index.js +10938 -6281
- package/dist/index.js.map +1 -1
- package/dist/{machines-BeZ3bD3t.d.ts → machines-BpdwuQcD.d.ts} +134 -14
- package/dist/machines.d.ts +1 -1
- package/dist/machines.js +23 -1
- package/package.json +20 -12
- package/src/approvals.ts +16 -4
- package/src/client.ts +41 -5
- package/src/commands/index.ts +1 -7
- package/src/commands/registry.ts +46 -12
- package/src/components/chat-composer.tsx +417 -69
- package/src/components/code-editor.tsx +28 -36
- package/src/components/command-palette.tsx +26 -5
- package/src/components/desktop-viewer.tsx +29 -16
- package/src/components/diff-view.tsx +27 -189
- package/src/components/enrollment-consent.tsx +28 -10
- package/src/components/enrollment-device-flow.tsx +8 -5
- package/src/components/file-browser.tsx +190 -56
- package/src/components/fleet-tile.tsx +13 -4
- package/src/components/machine-card.tsx +93 -15
- package/src/components/machine-dock-bar.tsx +20 -8
- package/src/components/machine-health-pill.tsx +68 -0
- package/src/components/machine-metrics.tsx +28 -19
- package/src/components/machine-status-pill.tsx +26 -5
- package/src/components/machines/health.ts +146 -0
- package/src/components/machines/machine-detail.tsx +220 -0
- package/src/components/machines/metric-history-chart.tsx +298 -0
- package/src/components/machines/metric-sparkline.tsx +76 -0
- package/src/components/machines/series.ts +113 -0
- package/src/components/machines-dashboard.tsx +21 -3
- package/src/components/markdown.tsx +39 -9
- package/src/components/message-timeline.tsx +633 -109
- package/src/components/pierre-diff.tsx +83 -15
- package/src/components/pierre-file.tsx +10 -9
- package/src/components/queue-surface.tsx +578 -0
- package/src/components/sandbox-files.tsx +193 -225
- package/src/components/sandbox-terminal.tsx +256 -88
- package/src/components/sandbox-workspace.tsx +957 -0
- package/src/components/session-status.tsx +22 -2
- package/src/components/workbench-changes.tsx +585 -0
- package/src/components/workspace-dock.tsx +213 -59
- package/src/hooks/internal.ts +5 -2
- package/src/hooks/use-available-models.ts +7 -2
- package/src/hooks/use-billing-usage.ts +4 -1
- package/src/hooks/use-codex-accounts.ts +74 -44
- package/src/hooks/use-composer.ts +414 -63
- package/src/hooks/use-environments.ts +92 -47
- package/src/hooks/use-file-attachments.ts +101 -55
- package/src/hooks/use-goal.ts +52 -16
- package/src/hooks/use-machine-chip.ts +134 -0
- package/src/hooks/use-machines.ts +34 -16
- package/src/hooks/use-packs.ts +24 -19
- package/src/hooks/use-relay-frame-stream.ts +5 -2
- package/src/hooks/use-rigs.ts +335 -0
- package/src/hooks/use-sandbox-files.ts +213 -39
- package/src/hooks/use-sandbox-git.ts +188 -11
- package/src/hooks/use-sandbox-terminal.ts +18 -14
- package/src/hooks/use-scheduled-tasks.ts +10 -2
- package/src/hooks/use-session-capabilities.ts +77 -20
- package/src/hooks/use-session-control.ts +50 -23
- package/src/hooks/use-session-events.ts +50 -21
- package/src/hooks/use-session-lineage.ts +128 -0
- package/src/hooks/use-session.ts +53 -30
- package/src/hooks/use-slash-commands.ts +6 -4
- package/src/hooks/use-turn-queue.ts +202 -147
- package/src/hooks/use-windowed-sections.ts +204 -0
- package/src/hooks/use-workspace-capture.ts +233 -0
- package/src/hooks/use-workspace-edit.ts +231 -0
- package/src/hooks/use-workspace-sessions.ts +48 -5
- package/src/hooks/use-workspaces.ts +18 -15
- package/src/index.ts +133 -23
- package/src/lib/format.ts +3 -3
- package/src/lib/xterm-renderer.ts +65 -0
- package/src/lib/xterm-theme.ts +224 -18
- package/src/machines.ts +29 -3
- package/src/provider.tsx +195 -6
- package/src/timeline/activity-rail.tsx +213 -54
- package/src/timeline/disclosure-context.tsx +12 -2
- package/src/timeline/index.ts +21 -2
- package/src/timeline/parsers.ts +87 -16
- package/src/timeline/projection.ts +412 -87
- package/src/timeline/shared.tsx +111 -24
- package/src/timeline/tool-diff.tsx +24 -20
- package/src/timeline/tool-renderers.tsx +98 -21
- package/src/timeline/turn-summary.tsx +76 -23
- package/src/timeline/types.ts +96 -12
- package/styles/index.css +22 -0
- package/styles/tokens.css +2 -2
- package/dist/chunk-5TIXRCSI.js +0 -1291
- package/dist/chunk-5TIXRCSI.js.map +0 -1
|
@@ -0,0 +1,2280 @@
|
|
|
1
|
+
// src/types/machines.ts
|
|
2
|
+
function connectionStatusForState(state) {
|
|
3
|
+
switch (state) {
|
|
4
|
+
case "online":
|
|
5
|
+
case "consent_required":
|
|
6
|
+
case "display_unavailable":
|
|
7
|
+
return "online";
|
|
8
|
+
case "reconnecting":
|
|
9
|
+
case "enrolling":
|
|
10
|
+
return "reconnecting";
|
|
11
|
+
case "offline":
|
|
12
|
+
return "offline";
|
|
13
|
+
default: {
|
|
14
|
+
const _never = state;
|
|
15
|
+
return _never;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/lib/cn.ts
|
|
21
|
+
import { clsx } from "clsx";
|
|
22
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
23
|
+
var twMerge = extendTailwindMerge({
|
|
24
|
+
extend: {
|
|
25
|
+
classGroups: {
|
|
26
|
+
"font-size": [{ text: ["og-xs", "og-sm", "og-base", "og-md"] }]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
function cn(...inputs) {
|
|
31
|
+
return twMerge(clsx(inputs));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/components/machine-status-pill.tsx
|
|
35
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
36
|
+
var CONNECTION_STATUS_META = {
|
|
37
|
+
online: {
|
|
38
|
+
label: "Online",
|
|
39
|
+
dotClassName: "bg-og-status-running",
|
|
40
|
+
badgeClassName: "text-og-status-running border-og-status-running/30 bg-og-status-running/10",
|
|
41
|
+
pulse: false
|
|
42
|
+
},
|
|
43
|
+
reconnecting: {
|
|
44
|
+
label: "Reconnecting",
|
|
45
|
+
dotClassName: "bg-og-status-waiting",
|
|
46
|
+
badgeClassName: "text-og-status-waiting border-og-status-waiting/35 bg-og-status-waiting/10",
|
|
47
|
+
pulse: true
|
|
48
|
+
},
|
|
49
|
+
offline: {
|
|
50
|
+
label: "Offline",
|
|
51
|
+
dotClassName: "bg-og-status-failed",
|
|
52
|
+
badgeClassName: "text-og-fg-subtle border-og-border bg-og-status-failed/10",
|
|
53
|
+
pulse: false
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var MACHINE_STATE_BADGE_META = {
|
|
57
|
+
consent_required: {
|
|
58
|
+
label: "Consent required",
|
|
59
|
+
badgeClassName: "text-og-status-waiting border-og-status-waiting/35 bg-og-status-waiting/10"
|
|
60
|
+
},
|
|
61
|
+
display_unavailable: {
|
|
62
|
+
label: "No display",
|
|
63
|
+
badgeClassName: "text-og-fg-muted border-og-border bg-og-surface-2"
|
|
64
|
+
},
|
|
65
|
+
enrolling: {
|
|
66
|
+
label: "Enrolling",
|
|
67
|
+
badgeClassName: "text-og-accent border-og-accent/30 bg-og-accent-soft"
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function ConnectionStatusPill({
|
|
71
|
+
status,
|
|
72
|
+
label,
|
|
73
|
+
size = "md",
|
|
74
|
+
className
|
|
75
|
+
}) {
|
|
76
|
+
const meta = CONNECTION_STATUS_META[status];
|
|
77
|
+
return /* @__PURE__ */ jsxs(
|
|
78
|
+
"span",
|
|
79
|
+
{
|
|
80
|
+
"data-connection-status": status,
|
|
81
|
+
className: cn(
|
|
82
|
+
"og-root inline-flex shrink-0 items-center rounded-full border font-medium",
|
|
83
|
+
size === "sm" ? "gap-1 px-1.5 py-px text-og-xs" : "gap-1.5 px-2 py-0.5 text-og-sm",
|
|
84
|
+
meta.badgeClassName,
|
|
85
|
+
className
|
|
86
|
+
),
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ jsx(ConnectionDot, { status, className: size === "sm" ? "size-1" : "size-1.5" }),
|
|
89
|
+
label ?? meta.label
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
function ConnectionDot({ status, className }) {
|
|
95
|
+
const meta = CONNECTION_STATUS_META[status];
|
|
96
|
+
return /* @__PURE__ */ jsx(
|
|
97
|
+
"span",
|
|
98
|
+
{
|
|
99
|
+
className: cn(
|
|
100
|
+
"relative inline-flex size-1.5 shrink-0 rounded-full",
|
|
101
|
+
meta.dotClassName,
|
|
102
|
+
className
|
|
103
|
+
),
|
|
104
|
+
children: meta.pulse ? /* @__PURE__ */ jsx("span", { className: cn("absolute inset-0 animate-og-pulse rounded-full", meta.dotClassName) }) : null
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
function MachineStatusPill({
|
|
109
|
+
state,
|
|
110
|
+
sharedSessionCount,
|
|
111
|
+
size = "md",
|
|
112
|
+
className
|
|
113
|
+
}) {
|
|
114
|
+
const stateBadge = MACHINE_STATE_BADGE_META[state];
|
|
115
|
+
const shared = (sharedSessionCount ?? 0) > 1;
|
|
116
|
+
return /* @__PURE__ */ jsxs(
|
|
117
|
+
"span",
|
|
118
|
+
{
|
|
119
|
+
className: cn("og-root inline-flex flex-wrap items-center gap-1", className),
|
|
120
|
+
"data-machine-state": state,
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ jsx(ConnectionStatusPill, { status: connectionStatusForState(state), size }),
|
|
123
|
+
stateBadge ? /* @__PURE__ */ jsx(
|
|
124
|
+
"span",
|
|
125
|
+
{
|
|
126
|
+
"data-state-badge": state,
|
|
127
|
+
className: cn(
|
|
128
|
+
"inline-flex shrink-0 items-center rounded-full border font-medium",
|
|
129
|
+
size === "sm" ? "px-1.5 py-px text-og-xs" : "px-2 py-0.5 text-og-sm",
|
|
130
|
+
stateBadge.badgeClassName
|
|
131
|
+
),
|
|
132
|
+
children: stateBadge.label
|
|
133
|
+
}
|
|
134
|
+
) : null,
|
|
135
|
+
shared ? /* @__PURE__ */ jsxs(
|
|
136
|
+
"span",
|
|
137
|
+
{
|
|
138
|
+
"data-shared-chip": true,
|
|
139
|
+
className: cn(
|
|
140
|
+
"inline-flex shrink-0 items-center gap-1 rounded-full border font-medium",
|
|
141
|
+
"text-og-accent border-og-accent/30 bg-og-accent-soft",
|
|
142
|
+
size === "sm" ? "px-1.5 py-px text-og-xs" : "px-2 py-0.5 text-og-sm"
|
|
143
|
+
),
|
|
144
|
+
title: `${sharedSessionCount} sessions are on this machine`,
|
|
145
|
+
children: [
|
|
146
|
+
"Shared \xB7 ",
|
|
147
|
+
sharedSessionCount
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
) : null
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/lib/format.ts
|
|
157
|
+
function formatRelativeTime(iso, now = /* @__PURE__ */ new Date()) {
|
|
158
|
+
const then = new Date(iso).getTime();
|
|
159
|
+
if (Number.isNaN(then)) {
|
|
160
|
+
return "";
|
|
161
|
+
}
|
|
162
|
+
const seconds = Math.max(0, Math.floor((now.getTime() - then) / 1e3));
|
|
163
|
+
if (seconds < 10) {
|
|
164
|
+
return "now";
|
|
165
|
+
}
|
|
166
|
+
if (seconds < 60) {
|
|
167
|
+
return `${seconds}s`;
|
|
168
|
+
}
|
|
169
|
+
const minutes = Math.floor(seconds / 60);
|
|
170
|
+
if (minutes < 60) {
|
|
171
|
+
return `${minutes}m`;
|
|
172
|
+
}
|
|
173
|
+
const hours = Math.floor(minutes / 60);
|
|
174
|
+
if (hours < 24) {
|
|
175
|
+
return `${hours}h`;
|
|
176
|
+
}
|
|
177
|
+
const days = Math.floor(hours / 24);
|
|
178
|
+
if (days < 14) {
|
|
179
|
+
return `${days}d`;
|
|
180
|
+
}
|
|
181
|
+
return new Date(iso).toLocaleDateString();
|
|
182
|
+
}
|
|
183
|
+
function formatBytes(bytes) {
|
|
184
|
+
if (bytes < 1024) {
|
|
185
|
+
return `${bytes} B`;
|
|
186
|
+
}
|
|
187
|
+
const units = ["KB", "MB", "GB"];
|
|
188
|
+
let value = bytes / 1024;
|
|
189
|
+
for (const unit of units) {
|
|
190
|
+
if (value < 1024 || unit === "GB") {
|
|
191
|
+
return `${value.toFixed(value < 10 ? 1 : 0)} ${unit}`;
|
|
192
|
+
}
|
|
193
|
+
value /= 1024;
|
|
194
|
+
}
|
|
195
|
+
return `${bytes} B`;
|
|
196
|
+
}
|
|
197
|
+
function truncate(text, maxLength) {
|
|
198
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
199
|
+
if (collapsed.length <= maxLength) {
|
|
200
|
+
return collapsed;
|
|
201
|
+
}
|
|
202
|
+
return `${collapsed.slice(0, Math.max(0, maxLength - 1)).trimEnd()}\u2026`;
|
|
203
|
+
}
|
|
204
|
+
function stringifyPayload(value) {
|
|
205
|
+
if (value === null || value === void 0) {
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
208
|
+
if (typeof value === "string") {
|
|
209
|
+
const parsed = tryParseJson(value);
|
|
210
|
+
if (parsed !== void 0 && typeof parsed === "object") {
|
|
211
|
+
return stringifyPayload(parsed);
|
|
212
|
+
}
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
return JSON.stringify(value, null, 2) ?? String(value);
|
|
217
|
+
} catch {
|
|
218
|
+
return String(value);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function tryParseJson(text) {
|
|
222
|
+
const trimmed = text.trim();
|
|
223
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[") && !trimmed.startsWith('"')) {
|
|
224
|
+
return void 0;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
return JSON.parse(trimmed);
|
|
228
|
+
} catch {
|
|
229
|
+
return void 0;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
var CREDIT_EXHAUSTION_MESSAGE = "Out of OpenGeni credits \u2014 this workspace's balance is empty. Add credits to continue; the conversation is preserved.";
|
|
233
|
+
function isCreditExhaustion(input) {
|
|
234
|
+
if (typeof input === "string") {
|
|
235
|
+
return input.toLowerCase().includes("insufficient opengeni credits");
|
|
236
|
+
}
|
|
237
|
+
if (input.segmentLimit === "budget_exhausted") {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
for (const text of [input.error, input.detail]) {
|
|
241
|
+
if (typeof text === "string" && text.toLowerCase().includes("insufficient opengeni credits")) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
function humanizeFailureReason(reason) {
|
|
248
|
+
if (!reason) {
|
|
249
|
+
return reason;
|
|
250
|
+
}
|
|
251
|
+
if (isCreditExhaustion(reason)) {
|
|
252
|
+
return CREDIT_EXHAUSTION_MESSAGE;
|
|
253
|
+
}
|
|
254
|
+
const normalized = reason.toLowerCase();
|
|
255
|
+
const authFailure = normalized.includes("incorrect api key") || normalized.includes("invalid api key") || normalized.includes("invalid_api_key") || normalized.includes("platform.openai.com/account/api-keys") || normalized.includes("401") && (normalized.includes("api key") || normalized.includes("unauthorized"));
|
|
256
|
+
if (authFailure) {
|
|
257
|
+
return "The model provider rejected this deployment's engine credentials. Sending messages won't help until the deployment's engine configuration is fixed.";
|
|
258
|
+
}
|
|
259
|
+
const quotaFailure = normalized.includes("insufficient_quota") || normalized.includes("exceeded your current quota");
|
|
260
|
+
if (quotaFailure) {
|
|
261
|
+
return "The model provider refused the request: this deployment's provider quota is exhausted.";
|
|
262
|
+
}
|
|
263
|
+
return reason;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/components/machine-metrics.tsx
|
|
267
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
268
|
+
function pct(value) {
|
|
269
|
+
if (!Number.isFinite(value)) return 0;
|
|
270
|
+
return Math.max(0, Math.min(100, value));
|
|
271
|
+
}
|
|
272
|
+
function Meter({
|
|
273
|
+
label,
|
|
274
|
+
value,
|
|
275
|
+
fillPct,
|
|
276
|
+
tone
|
|
277
|
+
}) {
|
|
278
|
+
const fillClass = tone === "hot" ? "bg-og-status-failed" : tone === "warn" ? "bg-og-status-running" : "bg-og-status-idle";
|
|
279
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-col gap-1", "data-metric": label.toLowerCase(), children: [
|
|
280
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-baseline justify-between gap-2", children: [
|
|
281
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: label }),
|
|
282
|
+
/* @__PURE__ */ jsx2("span", { className: "font-og-mono text-[11px] tabular-nums text-og-fg-muted", children: value })
|
|
283
|
+
] }),
|
|
284
|
+
/* @__PURE__ */ jsx2("div", { className: "h-1 w-full overflow-hidden rounded-full bg-og-surface-2", children: /* @__PURE__ */ jsx2(
|
|
285
|
+
"div",
|
|
286
|
+
{
|
|
287
|
+
className: cn("h-full rounded-full transition-[width] duration-500", fillClass),
|
|
288
|
+
style: { width: `${pct(fillPct)}%` }
|
|
289
|
+
}
|
|
290
|
+
) })
|
|
291
|
+
] });
|
|
292
|
+
}
|
|
293
|
+
function toneFor(p) {
|
|
294
|
+
if (p >= 90) return "hot";
|
|
295
|
+
if (p >= 70) return "warn";
|
|
296
|
+
return "ok";
|
|
297
|
+
}
|
|
298
|
+
function StatTriple({
|
|
299
|
+
load1,
|
|
300
|
+
load5,
|
|
301
|
+
load15,
|
|
302
|
+
runQueue
|
|
303
|
+
}) {
|
|
304
|
+
const stats = [
|
|
305
|
+
{ label: "1m", value: load1 },
|
|
306
|
+
{ label: "5m", value: load5 },
|
|
307
|
+
{ label: "15m", value: load15 }
|
|
308
|
+
];
|
|
309
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-3", "data-metric": "load", children: [
|
|
310
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-col gap-1", children: [
|
|
311
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "Load" }),
|
|
312
|
+
/* @__PURE__ */ jsx2("div", { className: "flex items-baseline gap-3", children: stats.map((s) => /* @__PURE__ */ jsxs2("div", { className: "flex items-baseline gap-1", children: [
|
|
313
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: s.label }),
|
|
314
|
+
/* @__PURE__ */ jsx2("span", { className: "font-og-mono text-[12px] tabular-nums text-og-fg", children: s.value.toFixed(2) })
|
|
315
|
+
] }, s.label)) })
|
|
316
|
+
] }),
|
|
317
|
+
runQueue > 0 ? /* @__PURE__ */ jsxs2(
|
|
318
|
+
"div",
|
|
319
|
+
{
|
|
320
|
+
className: "flex shrink-0 items-baseline gap-1.5 rounded-og-sm bg-og-surface-2 px-2 py-1 text-[11px] text-og-fg-subtle",
|
|
321
|
+
"data-metric": "runqueue",
|
|
322
|
+
children: [
|
|
323
|
+
/* @__PURE__ */ jsx2("span", { className: "font-medium uppercase tracking-wide", children: "Queue" }),
|
|
324
|
+
/* @__PURE__ */ jsx2("span", { className: "font-og-mono tabular-nums text-og-fg-muted", children: runQueue })
|
|
325
|
+
]
|
|
326
|
+
}
|
|
327
|
+
) : null
|
|
328
|
+
] });
|
|
329
|
+
}
|
|
330
|
+
function MachineMetrics({ metrics, density = "compact", className }) {
|
|
331
|
+
if (!metrics) {
|
|
332
|
+
return /* @__PURE__ */ jsx2("div", { className: cn("text-[11px] text-og-fg-subtle", className), "data-metrics-empty": true, children: "No metrics yet" });
|
|
333
|
+
}
|
|
334
|
+
const memPct2 = metrics.memTotalBytes > 0 ? metrics.memUsedBytes / metrics.memTotalBytes * 100 : 0;
|
|
335
|
+
const diskPct2 = metrics.diskTotalBytes > 0 ? metrics.diskUsedBytes / metrics.diskTotalBytes * 100 : 0;
|
|
336
|
+
const hasGpu = metrics.gpuUtilPct !== null;
|
|
337
|
+
const gpuMemLabel = metrics.gpuMemBytes !== null ? formatBytes(metrics.gpuMemBytes) : null;
|
|
338
|
+
const ratioGridClass = density === "full" ? "grid-cols-1" : "grid-cols-1 sm:grid-cols-2";
|
|
339
|
+
return /* @__PURE__ */ jsxs2("div", { className: cn("flex flex-col gap-3", className), "data-machine-metrics": true, children: [
|
|
340
|
+
/* @__PURE__ */ jsxs2("div", { className: cn("grid gap-x-4 gap-y-2.5", ratioGridClass), children: [
|
|
341
|
+
/* @__PURE__ */ jsx2(
|
|
342
|
+
Meter,
|
|
343
|
+
{
|
|
344
|
+
label: "Memory",
|
|
345
|
+
value: `${formatBytes(metrics.memUsedBytes)} / ${formatBytes(metrics.memTotalBytes)}`,
|
|
346
|
+
fillPct: memPct2,
|
|
347
|
+
tone: toneFor(memPct2)
|
|
348
|
+
}
|
|
349
|
+
),
|
|
350
|
+
/* @__PURE__ */ jsx2(
|
|
351
|
+
Meter,
|
|
352
|
+
{
|
|
353
|
+
label: "Disk",
|
|
354
|
+
value: `${formatBytes(metrics.diskUsedBytes)} / ${formatBytes(metrics.diskTotalBytes)}`,
|
|
355
|
+
fillPct: diskPct2,
|
|
356
|
+
tone: toneFor(diskPct2)
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
] }),
|
|
360
|
+
/* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-2 gap-x-4 gap-y-2.5", children: [
|
|
361
|
+
/* @__PURE__ */ jsx2(
|
|
362
|
+
Meter,
|
|
363
|
+
{
|
|
364
|
+
label: "CPU",
|
|
365
|
+
value: `${metrics.cpuPct.toFixed(0)}%`,
|
|
366
|
+
fillPct: metrics.cpuPct,
|
|
367
|
+
tone: toneFor(metrics.cpuPct)
|
|
368
|
+
}
|
|
369
|
+
),
|
|
370
|
+
hasGpu ? /* @__PURE__ */ jsx2(
|
|
371
|
+
Meter,
|
|
372
|
+
{
|
|
373
|
+
label: "GPU",
|
|
374
|
+
value: gpuMemLabel ? `${metrics.gpuUtilPct.toFixed(0)}% \xB7 ${gpuMemLabel}` : `${metrics.gpuUtilPct.toFixed(0)}%`,
|
|
375
|
+
fillPct: metrics.gpuUtilPct,
|
|
376
|
+
tone: toneFor(metrics.gpuUtilPct)
|
|
377
|
+
}
|
|
378
|
+
) : null
|
|
379
|
+
] }),
|
|
380
|
+
/* @__PURE__ */ jsx2(
|
|
381
|
+
StatTriple,
|
|
382
|
+
{
|
|
383
|
+
load1: metrics.load1,
|
|
384
|
+
load5: metrics.load5,
|
|
385
|
+
load15: metrics.load15,
|
|
386
|
+
runQueue: metrics.runQueue
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
] });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/components/machine-card.tsx
|
|
393
|
+
import {
|
|
394
|
+
ArrowRightIcon,
|
|
395
|
+
CpuIcon,
|
|
396
|
+
LaptopIcon,
|
|
397
|
+
MonitorIcon,
|
|
398
|
+
MonitorOffIcon,
|
|
399
|
+
ServerIcon,
|
|
400
|
+
UsersIcon
|
|
401
|
+
} from "lucide-react";
|
|
402
|
+
|
|
403
|
+
// src/components/machines/health.ts
|
|
404
|
+
var PRESSURE = {
|
|
405
|
+
cpu: { warn: 90, crit: 98 },
|
|
406
|
+
mem: { warn: 85, crit: 95 },
|
|
407
|
+
disk: { warn: 90, crit: 96 }
|
|
408
|
+
};
|
|
409
|
+
var STALE_AFTER_MS = 45e3;
|
|
410
|
+
function pct2(used, total) {
|
|
411
|
+
if (!Number.isFinite(used) || !Number.isFinite(total) || total <= 0) return 0;
|
|
412
|
+
return used / total * 100;
|
|
413
|
+
}
|
|
414
|
+
function deriveHealth(state, metrics, now = Date.now()) {
|
|
415
|
+
if (state === "offline") {
|
|
416
|
+
return { level: "offline", label: "Offline", reason: "Not reachable", stale: true };
|
|
417
|
+
}
|
|
418
|
+
if (state === "enrolling") {
|
|
419
|
+
return { level: "unknown", label: "Enrolling", reason: "Completing enrollment", stale: false };
|
|
420
|
+
}
|
|
421
|
+
const stale = metrics ? now - new Date(metrics.sampledAt).getTime() > STALE_AFTER_MS : true;
|
|
422
|
+
if (state === "reconnecting") {
|
|
423
|
+
return { level: "degraded", label: "Reconnecting", reason: "Re-establishing link", stale };
|
|
424
|
+
}
|
|
425
|
+
if (!metrics) {
|
|
426
|
+
return { level: "unknown", label: "Online", reason: "Awaiting first sample", stale };
|
|
427
|
+
}
|
|
428
|
+
if (stale) {
|
|
429
|
+
return { level: "degraded", label: "Telemetry stale", reason: "No recent sample", stale: true };
|
|
430
|
+
}
|
|
431
|
+
const memPct2 = pct2(metrics.memUsedBytes, metrics.memTotalBytes);
|
|
432
|
+
const diskPct2 = pct2(metrics.diskUsedBytes, metrics.diskTotalBytes);
|
|
433
|
+
const cpu = metrics.cpuPct;
|
|
434
|
+
const pressures = [
|
|
435
|
+
resolve("Disk", diskPct2, PRESSURE.disk),
|
|
436
|
+
resolve("Memory", memPct2, PRESSURE.mem),
|
|
437
|
+
resolve("CPU", cpu, PRESSURE.cpu)
|
|
438
|
+
];
|
|
439
|
+
const worst = pressures.reduce((a, b) => b.sev > a.sev ? b : a);
|
|
440
|
+
if (worst.sev === 2) return { level: "critical", label: "Critical", reason: worst.reason, stale };
|
|
441
|
+
if (worst.sev === 1)
|
|
442
|
+
return { level: "degraded", label: "Under load", reason: worst.reason, stale };
|
|
443
|
+
return { level: "healthy", label: "Healthy", reason: "All systems nominal", stale };
|
|
444
|
+
}
|
|
445
|
+
function resolve(name, value, t) {
|
|
446
|
+
const rounded = Math.round(value);
|
|
447
|
+
if (value >= t.crit) return { level: "critical", reason: `${name} ${rounded}%`, sev: 2 };
|
|
448
|
+
if (value >= t.warn) return { level: "degraded", reason: `${name} ${rounded}%`, sev: 1 };
|
|
449
|
+
return { level: "healthy", reason: `${name} ${rounded}%`, sev: 0 };
|
|
450
|
+
}
|
|
451
|
+
var HEALTH_TOKEN = {
|
|
452
|
+
healthy: {
|
|
453
|
+
text: "text-og-status-idle",
|
|
454
|
+
dot: "bg-og-status-idle",
|
|
455
|
+
soft: "bg-og-status-idle/10 border-og-status-idle/25"
|
|
456
|
+
},
|
|
457
|
+
degraded: {
|
|
458
|
+
text: "text-og-status-running",
|
|
459
|
+
dot: "bg-og-status-running",
|
|
460
|
+
soft: "bg-og-status-running/10 border-og-status-running/25"
|
|
461
|
+
},
|
|
462
|
+
critical: {
|
|
463
|
+
text: "text-og-status-failed",
|
|
464
|
+
dot: "bg-og-status-failed",
|
|
465
|
+
soft: "bg-og-status-failed/10 border-og-status-failed/30"
|
|
466
|
+
},
|
|
467
|
+
offline: {
|
|
468
|
+
text: "text-og-fg-subtle",
|
|
469
|
+
dot: "bg-og-fg-subtle",
|
|
470
|
+
soft: "bg-og-surface-2 border-og-border"
|
|
471
|
+
},
|
|
472
|
+
unknown: {
|
|
473
|
+
text: "text-og-fg-muted",
|
|
474
|
+
dot: "bg-og-fg-muted",
|
|
475
|
+
soft: "bg-og-surface-2 border-og-border"
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
function healthPulses(level) {
|
|
479
|
+
return level === "degraded" || level === "critical";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/components/machine-health-pill.tsx
|
|
483
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
484
|
+
function MachineHealthPill({
|
|
485
|
+
state,
|
|
486
|
+
metrics,
|
|
487
|
+
now,
|
|
488
|
+
size = "sm",
|
|
489
|
+
className
|
|
490
|
+
}) {
|
|
491
|
+
const health = deriveHealth(state, metrics, now ?? Date.now());
|
|
492
|
+
const tone = HEALTH_TOKEN[health.level];
|
|
493
|
+
const badge = MACHINE_STATE_BADGE_META[state];
|
|
494
|
+
const pad = size === "md" ? "px-2.5 py-1 text-og-sm" : "px-2 py-0.5 text-og-xs";
|
|
495
|
+
return /* @__PURE__ */ jsxs3("div", { className: cn("flex shrink-0 items-center gap-1.5", className), children: [
|
|
496
|
+
badge ? /* @__PURE__ */ jsx3(
|
|
497
|
+
"span",
|
|
498
|
+
{
|
|
499
|
+
className: cn(
|
|
500
|
+
"rounded-full border px-2 py-0.5 text-og-xs font-medium",
|
|
501
|
+
badge.badgeClassName
|
|
502
|
+
),
|
|
503
|
+
children: badge.label
|
|
504
|
+
}
|
|
505
|
+
) : null,
|
|
506
|
+
/* @__PURE__ */ jsxs3(
|
|
507
|
+
"span",
|
|
508
|
+
{
|
|
509
|
+
className: cn(
|
|
510
|
+
"inline-flex items-center gap-1.5 rounded-full border font-medium",
|
|
511
|
+
pad,
|
|
512
|
+
tone.soft,
|
|
513
|
+
tone.text
|
|
514
|
+
),
|
|
515
|
+
"data-health": health.level,
|
|
516
|
+
title: health.reason,
|
|
517
|
+
children: [
|
|
518
|
+
/* @__PURE__ */ jsxs3("span", { className: "relative flex size-2", children: [
|
|
519
|
+
healthPulses(health.level) && /* @__PURE__ */ jsx3(
|
|
520
|
+
"span",
|
|
521
|
+
{
|
|
522
|
+
className: cn(
|
|
523
|
+
"absolute inline-flex size-full animate-og-pulse rounded-full opacity-60",
|
|
524
|
+
tone.dot
|
|
525
|
+
)
|
|
526
|
+
}
|
|
527
|
+
),
|
|
528
|
+
/* @__PURE__ */ jsx3("span", { className: cn("relative inline-flex size-2 rounded-full", tone.dot) })
|
|
529
|
+
] }),
|
|
530
|
+
health.label
|
|
531
|
+
]
|
|
532
|
+
}
|
|
533
|
+
)
|
|
534
|
+
] });
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/components/machines/metric-sparkline.tsx
|
|
538
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
539
|
+
var VW = 120;
|
|
540
|
+
function MetricSparkline({
|
|
541
|
+
points,
|
|
542
|
+
color = "var(--og-color-accent)",
|
|
543
|
+
yMax = "auto",
|
|
544
|
+
height = 28,
|
|
545
|
+
className
|
|
546
|
+
}) {
|
|
547
|
+
const valued = points.filter(
|
|
548
|
+
(p) => p.v != null && Number.isFinite(p.v)
|
|
549
|
+
);
|
|
550
|
+
if (valued.length < 2) {
|
|
551
|
+
return /* @__PURE__ */ jsx4("div", { className, style: { height }, "aria-hidden": true });
|
|
552
|
+
}
|
|
553
|
+
const vh = height;
|
|
554
|
+
const top = yMax === "auto" ? Math.max(1, ...valued.map((p) => p.v)) * 1.1 : yMax;
|
|
555
|
+
const tMin = valued[0].t;
|
|
556
|
+
const tSpan = Math.max(1, valued[valued.length - 1].t - tMin);
|
|
557
|
+
const xy = valued.map((p) => ({
|
|
558
|
+
x: (p.t - tMin) / tSpan * VW,
|
|
559
|
+
y: vh - Math.min(top, Math.max(0, p.v)) / Math.max(1e-6, top) * (vh - 2) - 1
|
|
560
|
+
}));
|
|
561
|
+
let d = `M${xy[0].x},${xy[0].y}`;
|
|
562
|
+
for (let i = 0; i < xy.length - 1; i++) {
|
|
563
|
+
const p0 = xy[i - 1] ?? xy[i];
|
|
564
|
+
const p1 = xy[i];
|
|
565
|
+
const p2 = xy[i + 1];
|
|
566
|
+
const p3 = xy[i + 2] ?? p2;
|
|
567
|
+
d += `C${p1.x + (p2.x - p0.x) / 6},${p1.y + (p2.y - p0.y) / 6} ${p2.x - (p3.x - p1.x) / 6},${p2.y - (p3.y - p1.y) / 6} ${p2.x},${p2.y}`;
|
|
568
|
+
}
|
|
569
|
+
const last = xy[xy.length - 1];
|
|
570
|
+
const gid = `spark-${Math.round(top)}-${color.length}`;
|
|
571
|
+
return /* @__PURE__ */ jsxs4(
|
|
572
|
+
"svg",
|
|
573
|
+
{
|
|
574
|
+
viewBox: `0 0 ${VW} ${vh}`,
|
|
575
|
+
preserveAspectRatio: "none",
|
|
576
|
+
className,
|
|
577
|
+
style: { height, width: "100%" },
|
|
578
|
+
"aria-hidden": true,
|
|
579
|
+
children: [
|
|
580
|
+
/* @__PURE__ */ jsx4("defs", { children: /* @__PURE__ */ jsxs4("linearGradient", { id: gid, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
581
|
+
/* @__PURE__ */ jsx4("stop", { offset: "0%", stopColor: color, stopOpacity: "0.22" }),
|
|
582
|
+
/* @__PURE__ */ jsx4("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
|
|
583
|
+
] }) }),
|
|
584
|
+
/* @__PURE__ */ jsx4("path", { d: `${d} L${last.x},${vh} L${xy[0].x},${vh} Z`, fill: `url(#${gid})` }),
|
|
585
|
+
/* @__PURE__ */ jsx4(
|
|
586
|
+
"path",
|
|
587
|
+
{
|
|
588
|
+
d,
|
|
589
|
+
fill: "none",
|
|
590
|
+
stroke: color,
|
|
591
|
+
strokeWidth: 1.25,
|
|
592
|
+
strokeLinejoin: "round",
|
|
593
|
+
strokeLinecap: "round",
|
|
594
|
+
vectorEffect: "non-scaling-stroke"
|
|
595
|
+
}
|
|
596
|
+
),
|
|
597
|
+
/* @__PURE__ */ jsx4("circle", { cx: last.x, cy: last.y, r: 1.6, fill: color, vectorEffect: "non-scaling-stroke" })
|
|
598
|
+
]
|
|
599
|
+
}
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/components/machine-card.tsx
|
|
604
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
605
|
+
function KindIcon({ machine }) {
|
|
606
|
+
if (machine.isSessionGroup)
|
|
607
|
+
return /* @__PURE__ */ jsx5(ServerIcon, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
|
|
608
|
+
if (machine.kind === "modal") return /* @__PURE__ */ jsx5(CpuIcon, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
|
|
609
|
+
return /* @__PURE__ */ jsx5(LaptopIcon, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
|
|
610
|
+
}
|
|
611
|
+
function MachineCard({
|
|
612
|
+
machine,
|
|
613
|
+
onAttach,
|
|
614
|
+
attaching,
|
|
615
|
+
series,
|
|
616
|
+
onOpenDetail,
|
|
617
|
+
now,
|
|
618
|
+
className
|
|
619
|
+
}) {
|
|
620
|
+
const offline = machine.state === "offline";
|
|
621
|
+
const attachable = !machine.active && !offline && Boolean(onAttach);
|
|
622
|
+
const shared = machine.sharedSessionCount > 1;
|
|
623
|
+
const clockNow = now ?? Date.now();
|
|
624
|
+
const sampledAgo = machine.metrics ? formatRelativeTime(machine.metrics.sampledAt, new Date(clockNow)) : null;
|
|
625
|
+
const cpuPts = (series ?? []).map((s) => ({ t: new Date(s.sampledAt).getTime(), v: s.cpuPct }));
|
|
626
|
+
const openable = Boolean(onOpenDetail);
|
|
627
|
+
return /* @__PURE__ */ jsxs5(
|
|
628
|
+
"div",
|
|
629
|
+
{
|
|
630
|
+
"data-machine-card": machine.sandboxId,
|
|
631
|
+
"data-active": machine.active ? "true" : "false",
|
|
632
|
+
role: openable ? "button" : void 0,
|
|
633
|
+
tabIndex: openable ? 0 : void 0,
|
|
634
|
+
onClick: openable ? () => onOpenDetail?.(machine) : void 0,
|
|
635
|
+
onKeyDown: openable ? (e) => {
|
|
636
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
637
|
+
e.preventDefault();
|
|
638
|
+
onOpenDetail?.(machine);
|
|
639
|
+
}
|
|
640
|
+
} : void 0,
|
|
641
|
+
className: cn(
|
|
642
|
+
"og-root group relative flex flex-col gap-3 overflow-hidden rounded-og-lg border border-og-border",
|
|
643
|
+
"bg-og-surface-1 p-4 shadow-og-sm transition-colors",
|
|
644
|
+
openable && "cursor-pointer hover:border-og-border-strong focus-visible:border-og-accent/60 focus-visible:outline-none",
|
|
645
|
+
machine.active && "border-og-accent/40",
|
|
646
|
+
className
|
|
647
|
+
),
|
|
648
|
+
children: [
|
|
649
|
+
machine.active ? /* @__PURE__ */ jsx5("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 w-0.5 bg-og-accent" }) : null,
|
|
650
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-start justify-between gap-3", children: [
|
|
651
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-start gap-2.5", children: [
|
|
652
|
+
/* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", children: /* @__PURE__ */ jsx5(KindIcon, { machine }) }),
|
|
653
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
|
|
654
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
|
|
655
|
+
/* @__PURE__ */ jsx5("span", { className: "truncate text-og-base font-medium text-og-fg", children: machine.name }),
|
|
656
|
+
machine.active ? /* @__PURE__ */ jsx5(
|
|
657
|
+
"span",
|
|
658
|
+
{
|
|
659
|
+
"data-active-marker": true,
|
|
660
|
+
className: "shrink-0 rounded-full border border-og-accent/30 bg-og-accent-soft px-1.5 py-px text-og-xs font-medium text-og-accent",
|
|
661
|
+
children: "Active"
|
|
662
|
+
}
|
|
663
|
+
) : null
|
|
664
|
+
] }),
|
|
665
|
+
/* @__PURE__ */ jsxs5("div", { className: "mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-og-xs text-og-fg-subtle", children: [
|
|
666
|
+
/* @__PURE__ */ jsx5("span", { className: "capitalize", children: machine.isSessionGroup ? "session sandbox" : machine.kind }),
|
|
667
|
+
/* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: "\xB7" }),
|
|
668
|
+
/* @__PURE__ */ jsxs5("span", { className: "font-og-mono", children: [
|
|
669
|
+
machine.os,
|
|
670
|
+
"/",
|
|
671
|
+
machine.arch
|
|
672
|
+
] }),
|
|
673
|
+
/* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: "\xB7" }),
|
|
674
|
+
/* @__PURE__ */ jsxs5("span", { className: "inline-flex items-center gap-1", children: [
|
|
675
|
+
machine.hasDisplay ? /* @__PURE__ */ jsx5(MonitorIcon, { className: "size-3", "aria-hidden": true }) : /* @__PURE__ */ jsx5(MonitorOffIcon, { className: "size-3", "aria-hidden": true }),
|
|
676
|
+
machine.hasDisplay ? "display" : "headless"
|
|
677
|
+
] })
|
|
678
|
+
] })
|
|
679
|
+
] })
|
|
680
|
+
] }),
|
|
681
|
+
/* @__PURE__ */ jsx5(
|
|
682
|
+
MachineHealthPill,
|
|
683
|
+
{
|
|
684
|
+
state: machine.state,
|
|
685
|
+
metrics: machine.metrics,
|
|
686
|
+
now: clockNow,
|
|
687
|
+
size: "sm",
|
|
688
|
+
className: "shrink-0"
|
|
689
|
+
}
|
|
690
|
+
)
|
|
691
|
+
] }),
|
|
692
|
+
shared ? /* @__PURE__ */ jsxs5(
|
|
693
|
+
"p",
|
|
694
|
+
{
|
|
695
|
+
"data-shared-disclosure": true,
|
|
696
|
+
className: "flex items-center gap-1.5 rounded-og-md border border-og-accent/25 bg-og-accent-soft px-2.5 py-1.5 text-og-xs text-og-fg-muted",
|
|
697
|
+
children: [
|
|
698
|
+
/* @__PURE__ */ jsx5(UsersIcon, { className: "size-3.5 shrink-0 text-og-accent", "aria-hidden": true }),
|
|
699
|
+
"Shared \u2014 ",
|
|
700
|
+
machine.sharedSessionCount,
|
|
701
|
+
" sessions are on this machine."
|
|
702
|
+
]
|
|
703
|
+
}
|
|
704
|
+
) : null,
|
|
705
|
+
/* @__PURE__ */ jsx5(MachineMetrics, { metrics: machine.metrics }),
|
|
706
|
+
cpuPts.length > 1 ? /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1 rounded-og-md bg-og-surface-2/40 p-2.5", children: [
|
|
707
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: [
|
|
708
|
+
/* @__PURE__ */ jsx5("span", { children: "CPU \xB7 last 15m" }),
|
|
709
|
+
openable ? /* @__PURE__ */ jsxs5("span", { className: "flex items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100", children: [
|
|
710
|
+
"Telemetry ",
|
|
711
|
+
/* @__PURE__ */ jsx5(ArrowRightIcon, { className: "size-2.5", "aria-hidden": true })
|
|
712
|
+
] }) : null
|
|
713
|
+
] }),
|
|
714
|
+
/* @__PURE__ */ jsx5(MetricSparkline, { points: cpuPts, color: "var(--og-color-accent)", yMax: 100, height: 26 })
|
|
715
|
+
] }) : null,
|
|
716
|
+
/* @__PURE__ */ jsxs5("div", { className: "mt-1 flex items-center justify-between gap-3 text-og-xs text-og-fg-subtle", children: [
|
|
717
|
+
/* @__PURE__ */ jsx5("span", { className: "inline-flex items-center gap-1.5", children: sampledAgo ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
718
|
+
/* @__PURE__ */ jsx5(
|
|
719
|
+
"span",
|
|
720
|
+
{
|
|
721
|
+
className: cn(
|
|
722
|
+
"size-1.5 rounded-full",
|
|
723
|
+
sampledAgo === "now" ? "bg-og-status-idle" : "bg-og-fg-subtle"
|
|
724
|
+
),
|
|
725
|
+
"aria-hidden": true
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
sampledAgo === "now" ? "Live" : `Updated ${sampledAgo} ago`
|
|
729
|
+
] }) : machine.lastSeenAt ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
730
|
+
"Last seen ",
|
|
731
|
+
formatRelativeTime(machine.lastSeenAt, new Date(clockNow))
|
|
732
|
+
] }) : "Never connected" }),
|
|
733
|
+
machine.active ? /* @__PURE__ */ jsx5("span", { className: "text-og-accent", children: "Routing here" }) : attachable ? /* @__PURE__ */ jsx5(
|
|
734
|
+
"button",
|
|
735
|
+
{
|
|
736
|
+
type: "button",
|
|
737
|
+
"data-attach": true,
|
|
738
|
+
disabled: attaching,
|
|
739
|
+
onClick: (e) => {
|
|
740
|
+
e.stopPropagation();
|
|
741
|
+
onAttach?.(machine);
|
|
742
|
+
},
|
|
743
|
+
className: cn(
|
|
744
|
+
"rounded-og-sm border border-og-border px-2.5 py-1 text-og-xs font-medium text-og-fg-muted transition-colors pointer-coarse:min-h-10",
|
|
745
|
+
"hover:border-og-border-strong hover:text-og-fg disabled:cursor-not-allowed disabled:opacity-50"
|
|
746
|
+
),
|
|
747
|
+
children: attaching ? "Switching\u2026" : "Attach"
|
|
748
|
+
}
|
|
749
|
+
) : /* @__PURE__ */ jsx5("span", { className: "text-og-fg-subtle/70", children: offline ? "Unavailable" : "\u2014" })
|
|
750
|
+
] })
|
|
751
|
+
]
|
|
752
|
+
}
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/components/machines/metric-history-chart.tsx
|
|
757
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
758
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
759
|
+
var PAD = { top: 10, right: 12, bottom: 18, left: 40 };
|
|
760
|
+
function niceCeil(v) {
|
|
761
|
+
if (v <= 0) return 1;
|
|
762
|
+
const mag = 10 ** Math.floor(Math.log10(v));
|
|
763
|
+
const n = v / mag;
|
|
764
|
+
const step = n <= 1 ? 1 : n <= 2 ? 2 : n <= 5 ? 5 : 10;
|
|
765
|
+
return step * mag;
|
|
766
|
+
}
|
|
767
|
+
function smoothPath(pts) {
|
|
768
|
+
if (pts.length === 0) return "";
|
|
769
|
+
if (pts.length === 1) return `M${pts[0].x},${pts[0].y}`;
|
|
770
|
+
let d = `M${pts[0].x},${pts[0].y}`;
|
|
771
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
772
|
+
const p0 = pts[i - 1] ?? pts[i];
|
|
773
|
+
const p1 = pts[i];
|
|
774
|
+
const p2 = pts[i + 1];
|
|
775
|
+
const p3 = pts[i + 2] ?? p2;
|
|
776
|
+
const cp1x = p1.x + (p2.x - p0.x) / 6;
|
|
777
|
+
const cp1y = p1.y + (p2.y - p0.y) / 6;
|
|
778
|
+
const cp2x = p2.x - (p3.x - p1.x) / 6;
|
|
779
|
+
const cp2y = p2.y - (p3.y - p1.y) / 6;
|
|
780
|
+
d += `C${cp1x},${cp1y} ${cp2x},${cp2y} ${p2.x},${p2.y}`;
|
|
781
|
+
}
|
|
782
|
+
return d;
|
|
783
|
+
}
|
|
784
|
+
function fmtClock(t) {
|
|
785
|
+
const d = new Date(t);
|
|
786
|
+
return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}`;
|
|
787
|
+
}
|
|
788
|
+
function MetricHistoryChart({
|
|
789
|
+
points,
|
|
790
|
+
yMax = "auto",
|
|
791
|
+
yMin = 0,
|
|
792
|
+
format,
|
|
793
|
+
unit = "",
|
|
794
|
+
color = "var(--og-color-accent)",
|
|
795
|
+
thresholds,
|
|
796
|
+
height = 132,
|
|
797
|
+
rangeLabel = "in this range",
|
|
798
|
+
className
|
|
799
|
+
}) {
|
|
800
|
+
const ref = useRef(null);
|
|
801
|
+
const [w, setW] = useState(560);
|
|
802
|
+
const [hover, setHover] = useState(null);
|
|
803
|
+
useLayoutEffect(() => {
|
|
804
|
+
const el = ref.current;
|
|
805
|
+
if (!el) return;
|
|
806
|
+
const ro = new ResizeObserver((entries) => {
|
|
807
|
+
const cw = entries[0]?.contentRect.width;
|
|
808
|
+
if (cw && cw > 0) setW(cw);
|
|
809
|
+
});
|
|
810
|
+
ro.observe(el);
|
|
811
|
+
return () => ro.disconnect();
|
|
812
|
+
}, []);
|
|
813
|
+
const valued = points.filter(
|
|
814
|
+
(p) => p.v != null && Number.isFinite(p.v)
|
|
815
|
+
);
|
|
816
|
+
const fmt = format ?? ((v) => `${Math.round(v)}${unit}`);
|
|
817
|
+
const H = height;
|
|
818
|
+
const plotW = Math.max(1, w - PAD.left - PAD.right);
|
|
819
|
+
const plotH = Math.max(1, H - PAD.top - PAD.bottom);
|
|
820
|
+
const dataMax = valued.length ? Math.max(...valued.map((p) => p.v)) : 1;
|
|
821
|
+
const top = yMax === "auto" ? niceCeil(Math.max(dataMax * 1.15, thresholds?.warn ?? 0, 1)) : yMax;
|
|
822
|
+
const span = Math.max(1e-6, top - yMin);
|
|
823
|
+
const tMin = valued.length ? valued[0].t : 0;
|
|
824
|
+
const tMax = valued.length ? valued[valued.length - 1].t : 1;
|
|
825
|
+
const tSpan = Math.max(1, tMax - tMin);
|
|
826
|
+
const xOf = (t) => PAD.left + (t - tMin) / tSpan * plotW;
|
|
827
|
+
const yOf = (v) => PAD.top + (1 - (Math.min(top, Math.max(yMin, v)) - yMin) / span) * plotH;
|
|
828
|
+
const pxPts = valued.map((p) => ({ x: xOf(p.t), y: yOf(p.v), t: p.t, v: p.v }));
|
|
829
|
+
const sparse = pxPts.length > 0 && pxPts.length <= 4;
|
|
830
|
+
const line = smoothPath(pxPts);
|
|
831
|
+
const area = pxPts.length > 1 ? `${line} L${pxPts[pxPts.length - 1].x},${PAD.top + plotH} L${pxPts[0].x},${PAD.top + plotH} Z` : "";
|
|
832
|
+
const yTicks = [yMin, yMin + span / 2, top];
|
|
833
|
+
const gid = `og-mh-${Math.round(top)}-${Math.round(color.length)}`;
|
|
834
|
+
const hoverPt = hover != null ? pxPts[hover] : null;
|
|
835
|
+
return /* @__PURE__ */ jsxs6(
|
|
836
|
+
"div",
|
|
837
|
+
{
|
|
838
|
+
ref,
|
|
839
|
+
className: cn("relative w-full select-none", className),
|
|
840
|
+
style: { height: H },
|
|
841
|
+
"data-metric-chart": true,
|
|
842
|
+
children: [
|
|
843
|
+
valued.length === 0 ? /* @__PURE__ */ jsx6("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxs6("span", { className: "text-og-xs text-og-fg-subtle", children: [
|
|
844
|
+
"No samples ",
|
|
845
|
+
rangeLabel
|
|
846
|
+
] }) }) : /* @__PURE__ */ jsxs6(
|
|
847
|
+
"svg",
|
|
848
|
+
{
|
|
849
|
+
width: w,
|
|
850
|
+
height: H,
|
|
851
|
+
className: "overflow-visible",
|
|
852
|
+
role: "img",
|
|
853
|
+
onMouseLeave: () => setHover(null),
|
|
854
|
+
onMouseMove: (e) => {
|
|
855
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
856
|
+
const mx = e.clientX - rect.left;
|
|
857
|
+
let best = 0;
|
|
858
|
+
let bestD = Infinity;
|
|
859
|
+
for (let i = 0; i < pxPts.length; i++) {
|
|
860
|
+
const d = Math.abs(pxPts[i].x - mx);
|
|
861
|
+
if (d < bestD) {
|
|
862
|
+
bestD = d;
|
|
863
|
+
best = i;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
setHover(best);
|
|
867
|
+
},
|
|
868
|
+
children: [
|
|
869
|
+
/* @__PURE__ */ jsx6("defs", { children: /* @__PURE__ */ jsxs6("linearGradient", { id: gid, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
870
|
+
/* @__PURE__ */ jsx6("stop", { offset: "0%", stopColor: color, stopOpacity: "0.26" }),
|
|
871
|
+
/* @__PURE__ */ jsx6("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
|
|
872
|
+
] }) }),
|
|
873
|
+
yTicks.map((v, i) => {
|
|
874
|
+
const y = yOf(v);
|
|
875
|
+
return /* @__PURE__ */ jsxs6("g", { children: [
|
|
876
|
+
/* @__PURE__ */ jsx6(
|
|
877
|
+
"line",
|
|
878
|
+
{
|
|
879
|
+
x1: PAD.left,
|
|
880
|
+
x2: w - PAD.right,
|
|
881
|
+
y1: y,
|
|
882
|
+
y2: y,
|
|
883
|
+
stroke: "var(--og-color-border)",
|
|
884
|
+
strokeOpacity: i === 0 ? 0.9 : 0.4,
|
|
885
|
+
strokeWidth: 1
|
|
886
|
+
}
|
|
887
|
+
),
|
|
888
|
+
/* @__PURE__ */ jsx6(
|
|
889
|
+
"text",
|
|
890
|
+
{
|
|
891
|
+
x: PAD.left - 8,
|
|
892
|
+
y: y + 3,
|
|
893
|
+
textAnchor: "end",
|
|
894
|
+
className: "font-og-mono",
|
|
895
|
+
fontSize: 10,
|
|
896
|
+
fill: "var(--og-color-fg-subtle)",
|
|
897
|
+
children: fmt(v)
|
|
898
|
+
}
|
|
899
|
+
)
|
|
900
|
+
] }, `yt-${v}`);
|
|
901
|
+
}),
|
|
902
|
+
["warn", "crit"].map((k) => {
|
|
903
|
+
const tv = thresholds?.[k];
|
|
904
|
+
if (tv == null || tv > top) return null;
|
|
905
|
+
const y = yOf(tv);
|
|
906
|
+
const stroke = k === "crit" ? "var(--og-color-status-failed)" : "var(--og-color-status-waiting)";
|
|
907
|
+
return /* @__PURE__ */ jsx6(
|
|
908
|
+
"line",
|
|
909
|
+
{
|
|
910
|
+
x1: PAD.left,
|
|
911
|
+
x2: w - PAD.right,
|
|
912
|
+
y1: y,
|
|
913
|
+
y2: y,
|
|
914
|
+
stroke,
|
|
915
|
+
strokeOpacity: 0.5,
|
|
916
|
+
strokeWidth: 1,
|
|
917
|
+
strokeDasharray: "3 4"
|
|
918
|
+
},
|
|
919
|
+
k
|
|
920
|
+
);
|
|
921
|
+
}),
|
|
922
|
+
valued.length > 1 && [tMin, tMax].map((t, i) => /* @__PURE__ */ jsx6(
|
|
923
|
+
"text",
|
|
924
|
+
{
|
|
925
|
+
x: i === 0 ? PAD.left : w - PAD.right,
|
|
926
|
+
y: H - 4,
|
|
927
|
+
textAnchor: i === 0 ? "start" : "end",
|
|
928
|
+
className: "font-og-mono",
|
|
929
|
+
fontSize: 10,
|
|
930
|
+
fill: "var(--og-color-fg-subtle)",
|
|
931
|
+
children: fmtClock(t)
|
|
932
|
+
},
|
|
933
|
+
i === 0 ? "x-start" : "x-end"
|
|
934
|
+
)),
|
|
935
|
+
area && /* @__PURE__ */ jsx6("path", { d: area, fill: `url(#${gid})` }),
|
|
936
|
+
/* @__PURE__ */ jsx6(
|
|
937
|
+
"path",
|
|
938
|
+
{
|
|
939
|
+
d: line,
|
|
940
|
+
fill: "none",
|
|
941
|
+
stroke: color,
|
|
942
|
+
strokeWidth: 1.75,
|
|
943
|
+
strokeLinejoin: "round",
|
|
944
|
+
strokeLinecap: "round",
|
|
945
|
+
pathLength: 1,
|
|
946
|
+
className: "og-mh-draw"
|
|
947
|
+
}
|
|
948
|
+
),
|
|
949
|
+
sparse && pxPts.map((p) => /* @__PURE__ */ jsx6("circle", { cx: p.x, cy: p.y, r: 2.5, fill: color }, `m-${p.t}`)),
|
|
950
|
+
hoverPt && /* @__PURE__ */ jsxs6("g", { pointerEvents: "none", children: [
|
|
951
|
+
/* @__PURE__ */ jsx6(
|
|
952
|
+
"line",
|
|
953
|
+
{
|
|
954
|
+
x1: hoverPt.x,
|
|
955
|
+
x2: hoverPt.x,
|
|
956
|
+
y1: PAD.top,
|
|
957
|
+
y2: PAD.top + plotH,
|
|
958
|
+
stroke: "var(--og-color-border-strong)",
|
|
959
|
+
strokeWidth: 1
|
|
960
|
+
}
|
|
961
|
+
),
|
|
962
|
+
/* @__PURE__ */ jsx6(
|
|
963
|
+
"circle",
|
|
964
|
+
{
|
|
965
|
+
cx: hoverPt.x,
|
|
966
|
+
cy: hoverPt.y,
|
|
967
|
+
r: 3.5,
|
|
968
|
+
fill: color,
|
|
969
|
+
stroke: "var(--og-color-bg)",
|
|
970
|
+
strokeWidth: 1.5
|
|
971
|
+
}
|
|
972
|
+
)
|
|
973
|
+
] })
|
|
974
|
+
]
|
|
975
|
+
}
|
|
976
|
+
),
|
|
977
|
+
hoverPt && /* @__PURE__ */ jsxs6(
|
|
978
|
+
"div",
|
|
979
|
+
{
|
|
980
|
+
className: "pointer-events-none absolute z-10 flex flex-col rounded-og-sm border border-og-border-strong bg-og-surface-3/95 px-2 py-1 shadow-og-md backdrop-blur-sm",
|
|
981
|
+
style: {
|
|
982
|
+
left: Math.min(Math.max(hoverPt.x - 30, 0), w - 78),
|
|
983
|
+
top: Math.max(hoverPt.y - 42, 0)
|
|
984
|
+
},
|
|
985
|
+
children: [
|
|
986
|
+
/* @__PURE__ */ jsx6("span", { className: "font-og-mono text-og-sm font-medium tabular-nums text-og-fg", children: fmt(hoverPt.v) }),
|
|
987
|
+
/* @__PURE__ */ jsx6("span", { className: "font-og-mono text-[10px] tabular-nums text-og-fg-subtle", children: fmtClock(hoverPt.t) })
|
|
988
|
+
]
|
|
989
|
+
}
|
|
990
|
+
)
|
|
991
|
+
]
|
|
992
|
+
}
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// src/components/machines/machine-detail.tsx
|
|
997
|
+
import { ArrowLeftIcon, CpuIcon as CpuIcon2, LaptopIcon as LaptopIcon2, RadioIcon, ServerIcon as ServerIcon2 } from "lucide-react";
|
|
998
|
+
|
|
999
|
+
// src/components/machines/series.ts
|
|
1000
|
+
var METRIC_WINDOWS = ["15m", "1h", "6h", "24h"];
|
|
1001
|
+
var WINDOW_LABEL = {
|
|
1002
|
+
"15m": "in the last 15 min",
|
|
1003
|
+
"1h": "in the last hour",
|
|
1004
|
+
"6h": "in the last 6 hours",
|
|
1005
|
+
"24h": "in the last 24 hours"
|
|
1006
|
+
};
|
|
1007
|
+
function ptsFrom(samples, pick) {
|
|
1008
|
+
return samples.map((s) => ({ t: new Date(s.sampledAt).getTime(), v: pick(s) }));
|
|
1009
|
+
}
|
|
1010
|
+
var memPct = (s) => s.memTotalBytes > 0 ? s.memUsedBytes / s.memTotalBytes * 100 : null;
|
|
1011
|
+
var diskPct = (s) => s.diskTotalBytes > 0 ? s.diskUsedBytes / s.diskTotalBytes * 100 : null;
|
|
1012
|
+
var fmtBytes = (b) => {
|
|
1013
|
+
if (b < 1024) return `${b} B`;
|
|
1014
|
+
const u = ["KB", "MB", "GB", "TB"];
|
|
1015
|
+
let v = b / 1024;
|
|
1016
|
+
for (const unit of u) {
|
|
1017
|
+
if (v < 1024 || unit === "TB") return `${v.toFixed(v < 10 ? 1 : 0)} ${unit}`;
|
|
1018
|
+
v /= 1024;
|
|
1019
|
+
}
|
|
1020
|
+
return `${b} B`;
|
|
1021
|
+
};
|
|
1022
|
+
var METRICS = [
|
|
1023
|
+
{
|
|
1024
|
+
key: "cpu",
|
|
1025
|
+
title: "CPU",
|
|
1026
|
+
unit: "%",
|
|
1027
|
+
yMax: 100,
|
|
1028
|
+
color: "var(--og-color-accent)",
|
|
1029
|
+
thresholds: { warn: 90, crit: 98 },
|
|
1030
|
+
pick: (s) => s.cpuPct,
|
|
1031
|
+
current: (s) => `${Math.round(s.cpuPct)}%`,
|
|
1032
|
+
sub: (s) => `${s.runQueue} in run queue`
|
|
1033
|
+
},
|
|
1034
|
+
{
|
|
1035
|
+
key: "mem",
|
|
1036
|
+
title: "Memory",
|
|
1037
|
+
unit: "%",
|
|
1038
|
+
yMax: 100,
|
|
1039
|
+
color: "var(--og-color-status-idle)",
|
|
1040
|
+
thresholds: { warn: 85, crit: 95 },
|
|
1041
|
+
pick: memPct,
|
|
1042
|
+
current: (s) => `${Math.round(memPct(s) ?? 0)}%`,
|
|
1043
|
+
sub: (s) => `${fmtBytes(s.memUsedBytes)} / ${fmtBytes(s.memTotalBytes)}`
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
key: "disk",
|
|
1047
|
+
title: "Disk",
|
|
1048
|
+
unit: "%",
|
|
1049
|
+
yMax: 100,
|
|
1050
|
+
color: "var(--og-color-status-waiting)",
|
|
1051
|
+
thresholds: { warn: 90, crit: 96 },
|
|
1052
|
+
pick: diskPct,
|
|
1053
|
+
current: (s) => `${Math.round(diskPct(s) ?? 0)}%`,
|
|
1054
|
+
sub: (s) => `${fmtBytes(s.diskUsedBytes)} / ${fmtBytes(s.diskTotalBytes)}`
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
key: "load",
|
|
1058
|
+
title: "Load average",
|
|
1059
|
+
unit: "",
|
|
1060
|
+
yMax: "auto",
|
|
1061
|
+
color: "var(--og-color-accent-strong)",
|
|
1062
|
+
pick: (s) => s.load1,
|
|
1063
|
+
current: (s) => s.load1.toFixed(2),
|
|
1064
|
+
sub: (s) => `5m ${s.load5.toFixed(2)} \xB7 15m ${s.load15.toFixed(2)}`
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
key: "gpu",
|
|
1068
|
+
title: "GPU",
|
|
1069
|
+
unit: "%",
|
|
1070
|
+
yMax: 100,
|
|
1071
|
+
color: "var(--og-color-status-running)",
|
|
1072
|
+
thresholds: { warn: 90, crit: 98 },
|
|
1073
|
+
pick: (s) => s.gpuUtilPct,
|
|
1074
|
+
current: (s) => s.gpuUtilPct == null ? "\u2014" : `${Math.round(s.gpuUtilPct)}%`,
|
|
1075
|
+
sub: (s) => s.gpuMemBytes == null ? null : `${fmtBytes(s.gpuMemBytes)} used`
|
|
1076
|
+
}
|
|
1077
|
+
];
|
|
1078
|
+
function pointsFor(def, samples) {
|
|
1079
|
+
return ptsFrom(samples, def.pick);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// src/components/machines/machine-detail.tsx
|
|
1083
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1084
|
+
function KindIcon2({ kind, session }) {
|
|
1085
|
+
if (session) return /* @__PURE__ */ jsx7(ServerIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
|
|
1086
|
+
if (kind === "modal") return /* @__PURE__ */ jsx7(CpuIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
|
|
1087
|
+
return /* @__PURE__ */ jsx7(LaptopIcon2, { className: "size-4 text-og-fg-subtle", "aria-hidden": true });
|
|
1088
|
+
}
|
|
1089
|
+
function MachineDetail({
|
|
1090
|
+
machine,
|
|
1091
|
+
series,
|
|
1092
|
+
window: window2,
|
|
1093
|
+
onWindowChange,
|
|
1094
|
+
loadingSeries,
|
|
1095
|
+
onBack,
|
|
1096
|
+
now = Date.now(),
|
|
1097
|
+
className
|
|
1098
|
+
}) {
|
|
1099
|
+
const health = deriveHealth(machine.state, machine.metrics, now);
|
|
1100
|
+
const tone = HEALTH_TOKEN[health.level];
|
|
1101
|
+
const latest = machine.metrics;
|
|
1102
|
+
const sampledAgo = latest ? formatRelativeTime(latest.sampledAt, new Date(now)) : null;
|
|
1103
|
+
const visible = METRICS.filter((m) => !(m.key === "gpu" && latest?.gpuUtilPct == null));
|
|
1104
|
+
const tileCols = visible.length >= 5 ? "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5" : "grid-cols-2 sm:grid-cols-4";
|
|
1105
|
+
return /* @__PURE__ */ jsxs7("div", { className: cn("og-root mx-auto flex w-full max-w-5xl flex-col gap-5", className), children: [
|
|
1106
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1107
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 items-center gap-2.5", children: [
|
|
1108
|
+
onBack && /* @__PURE__ */ jsx7(
|
|
1109
|
+
"button",
|
|
1110
|
+
{
|
|
1111
|
+
type: "button",
|
|
1112
|
+
onClick: onBack,
|
|
1113
|
+
className: "flex size-7 shrink-0 items-center justify-center rounded-og-sm border border-og-border bg-og-surface-1 text-og-fg-muted transition-colors hover:bg-og-surface-2 hover:text-og-fg",
|
|
1114
|
+
"aria-label": "Back to machines",
|
|
1115
|
+
children: /* @__PURE__ */ jsx7(ArrowLeftIcon, { className: "size-3.5", "aria-hidden": true })
|
|
1116
|
+
}
|
|
1117
|
+
),
|
|
1118
|
+
/* @__PURE__ */ jsx7(KindIcon2, { kind: machine.kind, session: machine.isSessionGroup }),
|
|
1119
|
+
/* @__PURE__ */ jsx7("h1", { className: "truncate text-og-md font-semibold text-og-fg", children: machine.name }),
|
|
1120
|
+
/* @__PURE__ */ jsxs7("span", { className: "hidden shrink-0 rounded-og-sm border border-og-border bg-og-surface-1 px-1.5 py-0.5 font-og-mono text-og-xs text-og-fg-subtle sm:inline", children: [
|
|
1121
|
+
machine.os,
|
|
1122
|
+
" \xB7 ",
|
|
1123
|
+
machine.arch
|
|
1124
|
+
] })
|
|
1125
|
+
] }),
|
|
1126
|
+
/* @__PURE__ */ jsx7("div", { className: "flex shrink-0 items-center gap-0.5 rounded-og-md border border-og-border bg-og-surface-1 p-0.5", children: METRIC_WINDOWS.map((w) => /* @__PURE__ */ jsx7(
|
|
1127
|
+
"button",
|
|
1128
|
+
{
|
|
1129
|
+
type: "button",
|
|
1130
|
+
onClick: () => onWindowChange(w),
|
|
1131
|
+
"data-active": w === window2,
|
|
1132
|
+
className: cn(
|
|
1133
|
+
"rounded-og-sm px-2 py-1 font-og-mono text-og-xs transition-colors",
|
|
1134
|
+
w === window2 ? "bg-og-surface-3 text-og-fg shadow-og-sm" : "text-og-fg-subtle hover:text-og-fg-muted"
|
|
1135
|
+
),
|
|
1136
|
+
children: w
|
|
1137
|
+
},
|
|
1138
|
+
w
|
|
1139
|
+
)) })
|
|
1140
|
+
] }),
|
|
1141
|
+
/* @__PURE__ */ jsxs7(
|
|
1142
|
+
"div",
|
|
1143
|
+
{
|
|
1144
|
+
className: cn(
|
|
1145
|
+
"relative flex flex-col gap-4 overflow-hidden rounded-og-lg border p-5 shadow-og-sm",
|
|
1146
|
+
tone.soft
|
|
1147
|
+
),
|
|
1148
|
+
children: [
|
|
1149
|
+
/* @__PURE__ */ jsx7("span", { "aria-hidden": true, className: cn("absolute inset-y-0 left-0 w-1", tone.dot) }),
|
|
1150
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center justify-between gap-3 pl-1", children: [
|
|
1151
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3", children: [
|
|
1152
|
+
/* @__PURE__ */ jsxs7("span", { className: "relative flex size-3", children: [
|
|
1153
|
+
healthPulses(health.level) && /* @__PURE__ */ jsx7(
|
|
1154
|
+
"span",
|
|
1155
|
+
{
|
|
1156
|
+
className: cn(
|
|
1157
|
+
"absolute inline-flex size-full animate-og-pulse rounded-full opacity-60",
|
|
1158
|
+
tone.dot
|
|
1159
|
+
)
|
|
1160
|
+
}
|
|
1161
|
+
),
|
|
1162
|
+
/* @__PURE__ */ jsx7("span", { className: cn("relative inline-flex size-3 rounded-full", tone.dot) })
|
|
1163
|
+
] }),
|
|
1164
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col", children: [
|
|
1165
|
+
/* @__PURE__ */ jsx7("span", { className: cn("text-og-md font-semibold", tone.text), children: health.label }),
|
|
1166
|
+
/* @__PURE__ */ jsx7("span", { className: "text-og-sm text-og-fg-muted", children: health.reason })
|
|
1167
|
+
] })
|
|
1168
|
+
] }),
|
|
1169
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-start gap-5 pr-1", children: [
|
|
1170
|
+
sampledAgo && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-end gap-0.5", children: [
|
|
1171
|
+
/* @__PURE__ */ jsx7("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "Sampled" }),
|
|
1172
|
+
/* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-1 font-og-mono text-og-sm tabular-nums text-og-fg", children: [
|
|
1173
|
+
/* @__PURE__ */ jsx7(
|
|
1174
|
+
RadioIcon,
|
|
1175
|
+
{
|
|
1176
|
+
className: cn("size-3", health.stale ? "text-og-fg-subtle" : tone.text),
|
|
1177
|
+
"aria-hidden": true
|
|
1178
|
+
}
|
|
1179
|
+
),
|
|
1180
|
+
sampledAgo === "now" ? "live" : `${sampledAgo} ago`
|
|
1181
|
+
] })
|
|
1182
|
+
] }),
|
|
1183
|
+
machine.lastSeenAt && /* @__PURE__ */ jsxs7("div", { className: "hidden flex-col items-end gap-0.5 sm:flex", children: [
|
|
1184
|
+
/* @__PURE__ */ jsx7("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "Last seen" }),
|
|
1185
|
+
/* @__PURE__ */ jsx7("span", { className: "font-og-mono text-og-sm tabular-nums text-og-fg-muted", children: formatRelativeTime(machine.lastSeenAt, new Date(now)) })
|
|
1186
|
+
] })
|
|
1187
|
+
] })
|
|
1188
|
+
] }),
|
|
1189
|
+
latest && /* @__PURE__ */ jsx7(
|
|
1190
|
+
"div",
|
|
1191
|
+
{
|
|
1192
|
+
className: cn(
|
|
1193
|
+
"grid gap-px overflow-hidden rounded-og-md border border-og-border bg-og-border",
|
|
1194
|
+
tileCols
|
|
1195
|
+
),
|
|
1196
|
+
children: visible.map((m) => {
|
|
1197
|
+
const pts = pointsFor(m, series);
|
|
1198
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1.5 bg-og-surface-1 p-3", children: [
|
|
1199
|
+
/* @__PURE__ */ jsx7("span", { className: "text-[10px] font-medium uppercase tracking-wide text-og-fg-subtle", children: m.title }),
|
|
1200
|
+
/* @__PURE__ */ jsx7("span", { className: "font-og-mono text-og-md font-medium tabular-nums text-og-fg", children: m.current(latest) }),
|
|
1201
|
+
/* @__PURE__ */ jsx7(MetricSparkline, { points: pts, color: m.color, yMax: m.yMax, height: 22 }),
|
|
1202
|
+
m.sub && /* @__PURE__ */ jsx7("span", { className: "truncate font-og-mono text-[10px] text-og-fg-subtle", children: m.sub(latest) })
|
|
1203
|
+
] }, m.key);
|
|
1204
|
+
})
|
|
1205
|
+
}
|
|
1206
|
+
)
|
|
1207
|
+
]
|
|
1208
|
+
}
|
|
1209
|
+
),
|
|
1210
|
+
/* @__PURE__ */ jsx7("div", { className: "grid grid-cols-1 gap-3 lg:grid-cols-2", children: visible.map((m) => {
|
|
1211
|
+
const pts = pointsFor(m, series);
|
|
1212
|
+
return /* @__PURE__ */ jsxs7(
|
|
1213
|
+
"div",
|
|
1214
|
+
{
|
|
1215
|
+
className: "flex flex-col gap-3 rounded-og-lg border border-og-border bg-og-surface-1 p-4 shadow-og-sm",
|
|
1216
|
+
children: [
|
|
1217
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-baseline justify-between", children: [
|
|
1218
|
+
/* @__PURE__ */ jsx7("span", { className: "text-og-sm font-medium text-og-fg", children: m.title }),
|
|
1219
|
+
latest && /* @__PURE__ */ jsx7("span", { className: "font-og-mono text-og-sm tabular-nums text-og-fg-muted", children: m.current(latest) })
|
|
1220
|
+
] }),
|
|
1221
|
+
/* @__PURE__ */ jsx7(
|
|
1222
|
+
MetricHistoryChart,
|
|
1223
|
+
{
|
|
1224
|
+
points: pts,
|
|
1225
|
+
yMax: m.yMax,
|
|
1226
|
+
unit: m.unit,
|
|
1227
|
+
color: m.color,
|
|
1228
|
+
thresholds: m.thresholds,
|
|
1229
|
+
rangeLabel: WINDOW_LABEL[window2],
|
|
1230
|
+
height: 128,
|
|
1231
|
+
className: cn(loadingSeries && "opacity-50 transition-opacity")
|
|
1232
|
+
}
|
|
1233
|
+
)
|
|
1234
|
+
]
|
|
1235
|
+
},
|
|
1236
|
+
m.key
|
|
1237
|
+
);
|
|
1238
|
+
}) })
|
|
1239
|
+
] });
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// src/components/machines-dashboard.tsx
|
|
1243
|
+
import { LaptopIcon as LaptopIcon3, PlusIcon, RefreshCwIcon } from "lucide-react";
|
|
1244
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1245
|
+
function EmptyState({ onEnroll }) {
|
|
1246
|
+
return /* @__PURE__ */ jsxs8(
|
|
1247
|
+
"div",
|
|
1248
|
+
{
|
|
1249
|
+
"data-machines-empty": true,
|
|
1250
|
+
className: "flex flex-col items-center justify-center gap-3 rounded-og-lg border border-dashed border-og-border bg-og-surface-1 px-6 py-12 text-center",
|
|
1251
|
+
children: [
|
|
1252
|
+
/* @__PURE__ */ jsx8("span", { className: "flex size-10 items-center justify-center rounded-full bg-og-surface-2 text-og-fg-subtle", children: /* @__PURE__ */ jsx8(LaptopIcon3, { className: "size-5", "aria-hidden": true }) }),
|
|
1253
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
|
|
1254
|
+
/* @__PURE__ */ jsx8("p", { className: "text-og-base font-medium text-og-fg", children: "No machines yet" }),
|
|
1255
|
+
/* @__PURE__ */ jsx8("p", { className: "max-w-xs text-og-sm text-og-fg-muted", children: "Enroll your own computer to run the agent on it \u2014 your files, your terminal, your desktop." })
|
|
1256
|
+
] }),
|
|
1257
|
+
onEnroll ? /* @__PURE__ */ jsxs8(
|
|
1258
|
+
"button",
|
|
1259
|
+
{
|
|
1260
|
+
type: "button",
|
|
1261
|
+
"data-enroll-cta": true,
|
|
1262
|
+
onClick: onEnroll,
|
|
1263
|
+
className: "inline-flex items-center gap-1.5 rounded-og-sm bg-og-accent px-3 py-1.5 text-og-sm font-medium text-og-accent-fg transition-colors hover:bg-og-accent-strong pointer-coarse:min-h-10",
|
|
1264
|
+
children: [
|
|
1265
|
+
/* @__PURE__ */ jsx8(PlusIcon, { className: "size-3.5", "aria-hidden": true }),
|
|
1266
|
+
"Enroll a machine"
|
|
1267
|
+
]
|
|
1268
|
+
}
|
|
1269
|
+
) : null
|
|
1270
|
+
]
|
|
1271
|
+
}
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
function Header({
|
|
1275
|
+
count,
|
|
1276
|
+
onEnroll,
|
|
1277
|
+
onRefresh,
|
|
1278
|
+
loading
|
|
1279
|
+
}) {
|
|
1280
|
+
return /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1281
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-baseline gap-2", children: [
|
|
1282
|
+
/* @__PURE__ */ jsx8("h2", { className: "text-og-base font-semibold text-og-fg", children: "Machines" }),
|
|
1283
|
+
/* @__PURE__ */ jsx8("span", { className: "font-og-mono text-og-xs text-og-fg-subtle", children: count })
|
|
1284
|
+
] }),
|
|
1285
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1.5", children: [
|
|
1286
|
+
onRefresh ? /* @__PURE__ */ jsx8(
|
|
1287
|
+
"button",
|
|
1288
|
+
{
|
|
1289
|
+
type: "button",
|
|
1290
|
+
"data-refresh": true,
|
|
1291
|
+
onClick: onRefresh,
|
|
1292
|
+
title: "Refresh",
|
|
1293
|
+
className: "rounded-og-sm p-1.5 text-og-fg-subtle transition-colors hover:bg-og-surface-2 hover:text-og-fg",
|
|
1294
|
+
children: /* @__PURE__ */ jsx8(RefreshCwIcon, { className: cn("size-3.5", loading && "animate-og-spin"), "aria-hidden": true })
|
|
1295
|
+
}
|
|
1296
|
+
) : null,
|
|
1297
|
+
onEnroll ? /* @__PURE__ */ jsxs8(
|
|
1298
|
+
"button",
|
|
1299
|
+
{
|
|
1300
|
+
type: "button",
|
|
1301
|
+
"data-enroll-cta": true,
|
|
1302
|
+
onClick: onEnroll,
|
|
1303
|
+
className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2.5 py-1 text-og-sm font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg pointer-coarse:min-h-10",
|
|
1304
|
+
children: [
|
|
1305
|
+
/* @__PURE__ */ jsx8(PlusIcon, { className: "size-3.5", "aria-hidden": true }),
|
|
1306
|
+
"Enroll machine"
|
|
1307
|
+
]
|
|
1308
|
+
}
|
|
1309
|
+
) : null
|
|
1310
|
+
] })
|
|
1311
|
+
] });
|
|
1312
|
+
}
|
|
1313
|
+
function MachinesDashboard({
|
|
1314
|
+
machines,
|
|
1315
|
+
activeSandboxId,
|
|
1316
|
+
loading,
|
|
1317
|
+
error,
|
|
1318
|
+
onAttach,
|
|
1319
|
+
attachingSandboxId,
|
|
1320
|
+
seriesByMachine,
|
|
1321
|
+
onOpenDetail,
|
|
1322
|
+
now,
|
|
1323
|
+
onEnroll,
|
|
1324
|
+
onRefresh,
|
|
1325
|
+
className
|
|
1326
|
+
}) {
|
|
1327
|
+
const isEmpty = !loading && !error && machines.length === 0;
|
|
1328
|
+
return /* @__PURE__ */ jsxs8("section", { "data-machines-dashboard": true, className: cn("og-root flex flex-col gap-3", className), children: [
|
|
1329
|
+
/* @__PURE__ */ jsx8(Header, { count: machines.length, onEnroll, onRefresh, loading }),
|
|
1330
|
+
error ? /* @__PURE__ */ jsxs8(
|
|
1331
|
+
"div",
|
|
1332
|
+
{
|
|
1333
|
+
"data-machines-error": true,
|
|
1334
|
+
className: "flex flex-wrap items-center justify-between gap-2 rounded-og-md border border-og-status-failed/30 bg-og-status-failed/10 px-3 py-2 text-og-sm text-og-status-failed",
|
|
1335
|
+
children: [
|
|
1336
|
+
/* @__PURE__ */ jsxs8("span", { children: [
|
|
1337
|
+
"Could not load machines: ",
|
|
1338
|
+
error.message
|
|
1339
|
+
] }),
|
|
1340
|
+
onRefresh ? /* @__PURE__ */ jsxs8(
|
|
1341
|
+
"button",
|
|
1342
|
+
{
|
|
1343
|
+
type: "button",
|
|
1344
|
+
"data-machines-retry": true,
|
|
1345
|
+
onClick: onRefresh,
|
|
1346
|
+
disabled: loading,
|
|
1347
|
+
className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-status-failed/30 px-2 py-1 text-og-xs font-medium transition-colors hover:border-og-status-failed/50 disabled:cursor-not-allowed disabled:opacity-60 pointer-coarse:min-h-10",
|
|
1348
|
+
children: [
|
|
1349
|
+
/* @__PURE__ */ jsx8(RefreshCwIcon, { className: cn("size-3.5", loading && "animate-og-spin"), "aria-hidden": true }),
|
|
1350
|
+
loading ? "Retrying\u2026" : "Retry"
|
|
1351
|
+
]
|
|
1352
|
+
}
|
|
1353
|
+
) : null
|
|
1354
|
+
]
|
|
1355
|
+
}
|
|
1356
|
+
) : null,
|
|
1357
|
+
loading && machines.length === 0 ? /* @__PURE__ */ jsx8("div", { "data-machines-loading": true, className: "grid gap-3 sm:grid-cols-2", children: [0, 1].map((i) => /* @__PURE__ */ jsx8(
|
|
1358
|
+
"div",
|
|
1359
|
+
{
|
|
1360
|
+
className: "h-36 animate-og-pulse rounded-og-lg border border-og-border bg-og-surface-1"
|
|
1361
|
+
},
|
|
1362
|
+
i
|
|
1363
|
+
)) }) : isEmpty ? /* @__PURE__ */ jsx8(EmptyState, { onEnroll }) : /* @__PURE__ */ jsx8("div", { className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-3", "data-machines-grid": true, children: machines.map((machine) => /* @__PURE__ */ jsx8(
|
|
1364
|
+
MachineCard,
|
|
1365
|
+
{
|
|
1366
|
+
machine: {
|
|
1367
|
+
...machine,
|
|
1368
|
+
active: machine.active || machine.sandboxId === activeSandboxId
|
|
1369
|
+
},
|
|
1370
|
+
onAttach,
|
|
1371
|
+
attaching: attachingSandboxId === machine.sandboxId,
|
|
1372
|
+
series: seriesByMachine?.[machine.sandboxId],
|
|
1373
|
+
onOpenDetail,
|
|
1374
|
+
now
|
|
1375
|
+
},
|
|
1376
|
+
machine.sandboxId
|
|
1377
|
+
)) })
|
|
1378
|
+
] });
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/components/machine-dock-bar.tsx
|
|
1382
|
+
import { LaptopIcon as LaptopIcon4, CpuIcon as CpuIcon3, UsersIcon as UsersIcon2 } from "lucide-react";
|
|
1383
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1384
|
+
function MachineDockBar({ name, kind, state, className }) {
|
|
1385
|
+
const Icon = kind === "selfhosted" ? LaptopIcon4 : CpuIcon3;
|
|
1386
|
+
const stateBadge = MACHINE_STATE_BADGE_META[state];
|
|
1387
|
+
return /* @__PURE__ */ jsxs9(
|
|
1388
|
+
"div",
|
|
1389
|
+
{
|
|
1390
|
+
"data-machine-dock-bar": true,
|
|
1391
|
+
className: cn(
|
|
1392
|
+
"flex shrink-0 items-center justify-between gap-2 border-b border-og-border bg-og-surface-1 px-2.5 py-1",
|
|
1393
|
+
className
|
|
1394
|
+
),
|
|
1395
|
+
children: [
|
|
1396
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex min-w-0 items-center gap-1.5", children: [
|
|
1397
|
+
/* @__PURE__ */ jsx9(Icon, { className: "size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
|
|
1398
|
+
/* @__PURE__ */ jsx9("span", { className: "truncate text-[11px] font-medium text-og-fg", children: name }),
|
|
1399
|
+
stateBadge ? /* @__PURE__ */ jsx9(
|
|
1400
|
+
"span",
|
|
1401
|
+
{
|
|
1402
|
+
"data-state-badge": state,
|
|
1403
|
+
className: cn(
|
|
1404
|
+
"shrink-0 rounded-full border px-1.5 py-px text-[10px] font-medium",
|
|
1405
|
+
stateBadge.badgeClassName
|
|
1406
|
+
),
|
|
1407
|
+
children: stateBadge.label
|
|
1408
|
+
}
|
|
1409
|
+
) : null
|
|
1410
|
+
] }),
|
|
1411
|
+
/* @__PURE__ */ jsx9(
|
|
1412
|
+
ConnectionStatusPill,
|
|
1413
|
+
{
|
|
1414
|
+
status: connectionStatusForState(state),
|
|
1415
|
+
size: "sm",
|
|
1416
|
+
className: "shrink-0"
|
|
1417
|
+
}
|
|
1418
|
+
)
|
|
1419
|
+
]
|
|
1420
|
+
}
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
function SharedMachineDisclosure({
|
|
1424
|
+
sharedSessionCount,
|
|
1425
|
+
density = "compact",
|
|
1426
|
+
className
|
|
1427
|
+
}) {
|
|
1428
|
+
const others = Math.max(0, sharedSessionCount - 1);
|
|
1429
|
+
return /* @__PURE__ */ jsxs9(
|
|
1430
|
+
"div",
|
|
1431
|
+
{
|
|
1432
|
+
"data-shared-disclosure": true,
|
|
1433
|
+
className: cn(
|
|
1434
|
+
"flex items-center gap-1.5 border-og-accent/25 bg-og-accent-soft text-og-fg-muted",
|
|
1435
|
+
density === "full" ? "rounded-og-md border px-3 py-2 text-[12px]" : "border-b px-2.5 py-1 text-[11px]",
|
|
1436
|
+
className
|
|
1437
|
+
),
|
|
1438
|
+
children: [
|
|
1439
|
+
/* @__PURE__ */ jsx9(UsersIcon2, { className: "size-3.5 shrink-0 text-og-accent", "aria-hidden": true }),
|
|
1440
|
+
/* @__PURE__ */ jsxs9("span", { children: [
|
|
1441
|
+
"Shared \u2014 ",
|
|
1442
|
+
others === 1 ? "another session is" : `${others} other sessions are`,
|
|
1443
|
+
" on this machine. They see the same terminal, files, and desktop."
|
|
1444
|
+
] })
|
|
1445
|
+
]
|
|
1446
|
+
}
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// src/components/enrollment-device-flow.tsx
|
|
1451
|
+
import { CopyIcon, ExternalLinkIcon, LaptopIcon as LaptopIcon5, TerminalIcon } from "lucide-react";
|
|
1452
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1453
|
+
var PHASE_TO_STATE = {
|
|
1454
|
+
pending: "enrolling",
|
|
1455
|
+
authorized: "online",
|
|
1456
|
+
denied: "offline",
|
|
1457
|
+
expired: "offline",
|
|
1458
|
+
disabled: "offline"
|
|
1459
|
+
};
|
|
1460
|
+
function PhaseLabel({ phase }) {
|
|
1461
|
+
const text = {
|
|
1462
|
+
pending: "Waiting for approval",
|
|
1463
|
+
authorized: "Connected",
|
|
1464
|
+
denied: "Denied",
|
|
1465
|
+
expired: "Code expired",
|
|
1466
|
+
disabled: "Enrollment disabled"
|
|
1467
|
+
};
|
|
1468
|
+
return /* @__PURE__ */ jsx10("span", { children: text[phase] });
|
|
1469
|
+
}
|
|
1470
|
+
function EnrollmentDeviceFlow({
|
|
1471
|
+
userCode,
|
|
1472
|
+
verificationUri,
|
|
1473
|
+
verificationUriComplete,
|
|
1474
|
+
installCommand,
|
|
1475
|
+
phase = "pending",
|
|
1476
|
+
expiresInSeconds,
|
|
1477
|
+
onCopyCode,
|
|
1478
|
+
onCopyInstall,
|
|
1479
|
+
onOpenVerification,
|
|
1480
|
+
className
|
|
1481
|
+
}) {
|
|
1482
|
+
const href = verificationUriComplete ?? verificationUri;
|
|
1483
|
+
const codeIsPlaceholder = !/[A-Za-z0-9]/.test(userCode);
|
|
1484
|
+
const awaitingCode = phase === "pending" && codeIsPlaceholder;
|
|
1485
|
+
return /* @__PURE__ */ jsxs10(
|
|
1486
|
+
"div",
|
|
1487
|
+
{
|
|
1488
|
+
"data-enrollment-device-flow": true,
|
|
1489
|
+
className: cn(
|
|
1490
|
+
"og-root flex w-full max-w-md flex-col gap-4 rounded-og-lg border border-og-border bg-og-surface-1 p-5 shadow-og-sm",
|
|
1491
|
+
className
|
|
1492
|
+
),
|
|
1493
|
+
children: [
|
|
1494
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1495
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
1496
|
+
/* @__PURE__ */ jsx10("span", { className: "flex size-7 items-center justify-center rounded-full bg-og-surface-2 text-og-fg-muted", children: /* @__PURE__ */ jsx10(LaptopIcon5, { className: "size-4", "aria-hidden": true }) }),
|
|
1497
|
+
/* @__PURE__ */ jsx10("h2", { className: "text-sm font-semibold text-og-fg", children: "Connect a machine" })
|
|
1498
|
+
] }),
|
|
1499
|
+
/* @__PURE__ */ jsx10(
|
|
1500
|
+
ConnectionStatusPill,
|
|
1501
|
+
{
|
|
1502
|
+
status: PHASE_TO_STATE[phase] === "online" ? "online" : PHASE_TO_STATE[phase] === "offline" ? "offline" : "reconnecting",
|
|
1503
|
+
label: /* @__PURE__ */ jsx10(PhaseLabel, { phase }),
|
|
1504
|
+
size: "sm"
|
|
1505
|
+
}
|
|
1506
|
+
)
|
|
1507
|
+
] }),
|
|
1508
|
+
installCommand ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-1.5", children: [
|
|
1509
|
+
/* @__PURE__ */ jsx10("span", { className: "text-[11px] font-medium uppercase tracking-wide text-og-fg-subtle", children: "1 \xB7 Run on the machine" }),
|
|
1510
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between gap-2 overflow-hidden rounded-og-md border border-og-border bg-og-bg px-2.5 py-1.5", children: [
|
|
1511
|
+
/* @__PURE__ */ jsxs10("code", { className: "flex min-w-0 items-center gap-2 overflow-x-auto font-og-mono text-[12px] text-og-fg", children: [
|
|
1512
|
+
/* @__PURE__ */ jsx10(TerminalIcon, { className: "size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
|
|
1513
|
+
installCommand
|
|
1514
|
+
] }),
|
|
1515
|
+
/* @__PURE__ */ jsx10(
|
|
1516
|
+
"button",
|
|
1517
|
+
{
|
|
1518
|
+
type: "button",
|
|
1519
|
+
"data-copy-install": true,
|
|
1520
|
+
onClick: onCopyInstall,
|
|
1521
|
+
title: "Copy command",
|
|
1522
|
+
className: "shrink-0 rounded-og-sm p-1.5 text-og-fg-subtle transition-colors hover:bg-og-surface-2 hover:text-og-fg",
|
|
1523
|
+
children: /* @__PURE__ */ jsx10(CopyIcon, { className: "size-4", "aria-hidden": true })
|
|
1524
|
+
}
|
|
1525
|
+
)
|
|
1526
|
+
] })
|
|
1527
|
+
] }) : null,
|
|
1528
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-1.5", children: [
|
|
1529
|
+
/* @__PURE__ */ jsx10("span", { className: "text-[11px] font-medium uppercase tracking-wide text-og-fg-subtle", children: installCommand ? "2 \xB7 Approve the machine" : "Approve the machine" }),
|
|
1530
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between gap-3 rounded-og-md border border-og-border bg-og-bg px-3 py-2.5", children: [
|
|
1531
|
+
awaitingCode ? /* @__PURE__ */ jsx10("span", { "data-user-code": true, className: "font-og-mono text-[13px] text-og-fg-subtle", children: "Run the command above \u2014 your code appears here" }) : /* @__PURE__ */ jsx10(
|
|
1532
|
+
"span",
|
|
1533
|
+
{
|
|
1534
|
+
"data-user-code": true,
|
|
1535
|
+
className: "select-all font-og-mono text-2xl font-semibold tracking-[0.25em] text-og-fg",
|
|
1536
|
+
children: userCode
|
|
1537
|
+
}
|
|
1538
|
+
),
|
|
1539
|
+
/* @__PURE__ */ jsx10(
|
|
1540
|
+
"button",
|
|
1541
|
+
{
|
|
1542
|
+
type: "button",
|
|
1543
|
+
"data-copy-code": true,
|
|
1544
|
+
onClick: onCopyCode,
|
|
1545
|
+
disabled: awaitingCode,
|
|
1546
|
+
title: "Copy code",
|
|
1547
|
+
className: "shrink-0 rounded-og-sm p-1.5 text-og-fg-subtle transition-colors hover:bg-og-surface-2 hover:text-og-fg disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent disabled:hover:text-og-fg-subtle",
|
|
1548
|
+
children: /* @__PURE__ */ jsx10(CopyIcon, { className: "size-4", "aria-hidden": true })
|
|
1549
|
+
}
|
|
1550
|
+
)
|
|
1551
|
+
] })
|
|
1552
|
+
] }),
|
|
1553
|
+
/* @__PURE__ */ jsxs10(
|
|
1554
|
+
"a",
|
|
1555
|
+
{
|
|
1556
|
+
href,
|
|
1557
|
+
"data-open-verification": true,
|
|
1558
|
+
target: "_blank",
|
|
1559
|
+
rel: "noreferrer",
|
|
1560
|
+
onClick: onOpenVerification,
|
|
1561
|
+
className: "inline-flex items-center justify-center gap-1.5 rounded-og-sm bg-og-accent px-3 py-2 text-sm font-medium text-og-accent-fg transition-colors hover:bg-og-accent-strong",
|
|
1562
|
+
children: [
|
|
1563
|
+
"Open approval page",
|
|
1564
|
+
/* @__PURE__ */ jsx10(ExternalLinkIcon, { className: "size-3.5", "aria-hidden": true })
|
|
1565
|
+
]
|
|
1566
|
+
}
|
|
1567
|
+
),
|
|
1568
|
+
/* @__PURE__ */ jsx10("p", { className: "text-center text-[11px] text-og-fg-subtle", children: awaitingCode ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
|
|
1569
|
+
"The one-liner prints a short code. Open",
|
|
1570
|
+
" ",
|
|
1571
|
+
/* @__PURE__ */ jsx10("span", { className: "font-og-mono text-og-fg-muted", children: verificationUri }),
|
|
1572
|
+
" and confirm the code shown there matches."
|
|
1573
|
+
] }) : /* @__PURE__ */ jsxs10(Fragment2, { children: [
|
|
1574
|
+
"Confirm code ",
|
|
1575
|
+
/* @__PURE__ */ jsx10("span", { className: "font-og-mono text-og-fg-muted", children: userCode }),
|
|
1576
|
+
" \u2014 the one printed by the one-liner \u2014 at",
|
|
1577
|
+
" ",
|
|
1578
|
+
/* @__PURE__ */ jsx10("span", { className: "font-og-mono text-og-fg-muted", children: verificationUri }),
|
|
1579
|
+
".",
|
|
1580
|
+
typeof expiresInSeconds === "number" ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
|
|
1581
|
+
" Expires in ",
|
|
1582
|
+
Math.max(0, Math.round(expiresInSeconds / 60)),
|
|
1583
|
+
" min."
|
|
1584
|
+
] }) : null
|
|
1585
|
+
] }) })
|
|
1586
|
+
]
|
|
1587
|
+
}
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// src/components/enrollment-consent.tsx
|
|
1592
|
+
import { useState as useState2 } from "react";
|
|
1593
|
+
import {
|
|
1594
|
+
CircleAlertIcon,
|
|
1595
|
+
LaptopIcon as LaptopIcon6,
|
|
1596
|
+
MonitorIcon as MonitorIcon2,
|
|
1597
|
+
ScreenShareIcon,
|
|
1598
|
+
ShieldAlertIcon,
|
|
1599
|
+
TerminalIcon as TerminalIcon2,
|
|
1600
|
+
UsersIcon as UsersIcon3
|
|
1601
|
+
} from "lucide-react";
|
|
1602
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1603
|
+
function Capability({ icon, title, body }) {
|
|
1604
|
+
return /* @__PURE__ */ jsxs11("li", { className: "flex items-start gap-2.5", children: [
|
|
1605
|
+
/* @__PURE__ */ jsx11("span", { className: "mt-0.5 shrink-0 text-og-status-failed", children: icon }),
|
|
1606
|
+
/* @__PURE__ */ jsxs11("span", { className: "min-w-0", children: [
|
|
1607
|
+
/* @__PURE__ */ jsx11("span", { className: "block text-og-base font-medium text-og-fg", children: title }),
|
|
1608
|
+
/* @__PURE__ */ jsx11("span", { className: "block text-og-sm text-og-fg-muted", children: body })
|
|
1609
|
+
] })
|
|
1610
|
+
] });
|
|
1611
|
+
}
|
|
1612
|
+
function EnrollmentConsent({
|
|
1613
|
+
userCode,
|
|
1614
|
+
machine,
|
|
1615
|
+
phase = "review",
|
|
1616
|
+
onApprove,
|
|
1617
|
+
onDeny,
|
|
1618
|
+
errorMessage,
|
|
1619
|
+
className
|
|
1620
|
+
}) {
|
|
1621
|
+
const [allowScreenControl, setAllowScreenControl] = useState2(machine.requestsScreenControl);
|
|
1622
|
+
const busy = phase === "approving";
|
|
1623
|
+
if (phase === "approved") {
|
|
1624
|
+
return /* @__PURE__ */ jsx11(
|
|
1625
|
+
ConsentResult,
|
|
1626
|
+
{
|
|
1627
|
+
className,
|
|
1628
|
+
tone: "ok",
|
|
1629
|
+
title: "Machine connected",
|
|
1630
|
+
body: `${machine.machineName} is now enrolled in this workspace. You can close this page \u2014 it will appear in your Machines dashboard.`
|
|
1631
|
+
}
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
if (phase === "denied") {
|
|
1635
|
+
return /* @__PURE__ */ jsx11(
|
|
1636
|
+
ConsentResult,
|
|
1637
|
+
{
|
|
1638
|
+
className,
|
|
1639
|
+
tone: "muted",
|
|
1640
|
+
title: "Enrollment denied",
|
|
1641
|
+
body: `You declined to connect ${machine.machineName}. The agent has no access to this machine. You can re-run the install one-liner to try again.`
|
|
1642
|
+
}
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
if (phase === "error") {
|
|
1646
|
+
return /* @__PURE__ */ jsx11(
|
|
1647
|
+
ConsentResult,
|
|
1648
|
+
{
|
|
1649
|
+
className,
|
|
1650
|
+
tone: "danger",
|
|
1651
|
+
title: "Could not complete enrollment",
|
|
1652
|
+
body: errorMessage ?? "The code may have expired. Re-run the install one-liner on the machine for a fresh code."
|
|
1653
|
+
}
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
return /* @__PURE__ */ jsxs11(
|
|
1657
|
+
"div",
|
|
1658
|
+
{
|
|
1659
|
+
"data-enrollment-consent": true,
|
|
1660
|
+
className: cn(
|
|
1661
|
+
"og-root mx-auto flex w-full max-w-md flex-col gap-5 rounded-og-lg border border-og-status-failed/30 bg-og-surface-1 p-6 shadow-og-md",
|
|
1662
|
+
className
|
|
1663
|
+
),
|
|
1664
|
+
children: [
|
|
1665
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-start gap-3", children: [
|
|
1666
|
+
/* @__PURE__ */ jsx11("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-full bg-og-status-failed/15 text-og-status-failed", children: /* @__PURE__ */ jsx11(ShieldAlertIcon, { className: "size-5", "aria-hidden": true }) }),
|
|
1667
|
+
/* @__PURE__ */ jsxs11("div", { className: "min-w-0", children: [
|
|
1668
|
+
/* @__PURE__ */ jsx11("h1", { className: "text-og-md font-semibold text-og-fg", children: "Give the agent your whole machine?" }),
|
|
1669
|
+
/* @__PURE__ */ jsxs11("p", { className: "mt-1 text-og-base text-og-fg-muted", children: [
|
|
1670
|
+
"Approving lets the OpenGeni agent run on",
|
|
1671
|
+
" ",
|
|
1672
|
+
/* @__PURE__ */ jsx11("span", { className: "font-medium text-og-fg", children: machine.machineName }),
|
|
1673
|
+
" with full access. This is your real computer \u2014 not a sandbox."
|
|
1674
|
+
] })
|
|
1675
|
+
] })
|
|
1676
|
+
] }),
|
|
1677
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center gap-2 rounded-og-md border border-og-border bg-og-surface-2 px-3 py-2 text-og-sm", children: [
|
|
1678
|
+
/* @__PURE__ */ jsx11(LaptopIcon6, { className: "size-4 text-og-fg-subtle", "aria-hidden": true }),
|
|
1679
|
+
/* @__PURE__ */ jsx11("span", { className: "font-medium text-og-fg", children: machine.machineName }),
|
|
1680
|
+
/* @__PURE__ */ jsxs11("span", { className: "font-og-mono text-og-fg-subtle", children: [
|
|
1681
|
+
machine.os,
|
|
1682
|
+
"/",
|
|
1683
|
+
machine.arch
|
|
1684
|
+
] }),
|
|
1685
|
+
/* @__PURE__ */ jsx11("span", { "aria-hidden": true, className: "text-og-fg-subtle", children: "\xB7" }),
|
|
1686
|
+
/* @__PURE__ */ jsxs11("span", { className: "text-og-fg-subtle", children: [
|
|
1687
|
+
"code ",
|
|
1688
|
+
/* @__PURE__ */ jsx11("span", { className: "font-og-mono font-medium text-og-fg", children: userCode })
|
|
1689
|
+
] })
|
|
1690
|
+
] }),
|
|
1691
|
+
/* @__PURE__ */ jsxs11("ul", { className: "flex flex-col gap-3", children: [
|
|
1692
|
+
/* @__PURE__ */ jsx11(
|
|
1693
|
+
Capability,
|
|
1694
|
+
{
|
|
1695
|
+
icon: /* @__PURE__ */ jsx11(TerminalIcon2, { className: "size-4", "aria-hidden": true }),
|
|
1696
|
+
title: "Read, write & run anything",
|
|
1697
|
+
body: "The agent can read and modify any file, run any command, and use your git credentials \u2014 as if it were you at the keyboard."
|
|
1698
|
+
}
|
|
1699
|
+
),
|
|
1700
|
+
machine.canOfferDisplay ? /* @__PURE__ */ jsx11(
|
|
1701
|
+
Capability,
|
|
1702
|
+
{
|
|
1703
|
+
icon: /* @__PURE__ */ jsx11(MonitorIcon2, { className: "size-4", "aria-hidden": true }),
|
|
1704
|
+
title: "See your screen",
|
|
1705
|
+
body: "The agent can capture this machine's display to watch what it is doing."
|
|
1706
|
+
}
|
|
1707
|
+
) : null,
|
|
1708
|
+
/* @__PURE__ */ jsx11(
|
|
1709
|
+
Capability,
|
|
1710
|
+
{
|
|
1711
|
+
icon: /* @__PURE__ */ jsx11(UsersIcon3, { className: "size-4", "aria-hidden": true }),
|
|
1712
|
+
title: "Shared while connected",
|
|
1713
|
+
body: "Any session in this workspace can use this machine while it is online. Disconnect the agent to revoke access instantly."
|
|
1714
|
+
}
|
|
1715
|
+
)
|
|
1716
|
+
] }),
|
|
1717
|
+
machine.canOfferDisplay ? /* @__PURE__ */ jsxs11(
|
|
1718
|
+
"label",
|
|
1719
|
+
{
|
|
1720
|
+
"data-screen-control-toggle": true,
|
|
1721
|
+
className: "flex cursor-pointer items-start gap-3 rounded-og-md border border-og-border bg-og-bg px-3 py-2.5",
|
|
1722
|
+
children: [
|
|
1723
|
+
/* @__PURE__ */ jsx11(
|
|
1724
|
+
"input",
|
|
1725
|
+
{
|
|
1726
|
+
type: "checkbox",
|
|
1727
|
+
checked: allowScreenControl,
|
|
1728
|
+
disabled: busy,
|
|
1729
|
+
onChange: (e) => setAllowScreenControl(e.target.checked),
|
|
1730
|
+
className: "mt-0.5 size-4 accent-og-accent"
|
|
1731
|
+
}
|
|
1732
|
+
),
|
|
1733
|
+
/* @__PURE__ */ jsxs11("span", { className: "min-w-0", children: [
|
|
1734
|
+
/* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-1.5 text-og-base font-medium text-og-fg", children: [
|
|
1735
|
+
/* @__PURE__ */ jsx11(ScreenShareIcon, { className: "size-3.5 text-og-fg-muted", "aria-hidden": true }),
|
|
1736
|
+
"Also let the agent control my mouse & keyboard"
|
|
1737
|
+
] }),
|
|
1738
|
+
/* @__PURE__ */ jsx11("span", { className: "mt-0.5 block text-og-sm text-og-fg-muted", children: "Optional. Enables computer-use \u2014 the agent can move the pointer, type, and click on this machine's desktop. Leave off to let it only watch." })
|
|
1739
|
+
] })
|
|
1740
|
+
]
|
|
1741
|
+
}
|
|
1742
|
+
) : /* @__PURE__ */ jsxs11("p", { className: "flex items-start gap-2 rounded-og-md border border-og-border bg-og-bg px-3 py-2 text-og-sm text-og-fg-muted", children: [
|
|
1743
|
+
/* @__PURE__ */ jsx11(CircleAlertIcon, { className: "mt-px size-3.5 shrink-0 text-og-fg-subtle", "aria-hidden": true }),
|
|
1744
|
+
"This machine has no display, so screen control isn't available \u2014 files, terminal, and git only."
|
|
1745
|
+
] }),
|
|
1746
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
|
|
1747
|
+
/* @__PURE__ */ jsx11(
|
|
1748
|
+
"button",
|
|
1749
|
+
{
|
|
1750
|
+
type: "button",
|
|
1751
|
+
"data-deny": true,
|
|
1752
|
+
disabled: busy,
|
|
1753
|
+
onClick: () => onDeny?.(),
|
|
1754
|
+
className: "flex-1 rounded-og-sm border border-og-border px-3 py-2 text-og-sm font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg disabled:opacity-50 pointer-coarse:min-h-10",
|
|
1755
|
+
children: "Cancel"
|
|
1756
|
+
}
|
|
1757
|
+
),
|
|
1758
|
+
/* @__PURE__ */ jsx11(
|
|
1759
|
+
"button",
|
|
1760
|
+
{
|
|
1761
|
+
type: "button",
|
|
1762
|
+
"data-approve": true,
|
|
1763
|
+
disabled: busy,
|
|
1764
|
+
onClick: () => onApprove?.(allowScreenControl),
|
|
1765
|
+
className: "flex-1 rounded-og-sm bg-og-status-failed px-3 py-2 text-og-sm font-semibold text-og-accent-fg transition-colors hover:opacity-90 disabled:opacity-50 pointer-coarse:min-h-10",
|
|
1766
|
+
children: busy ? "Connecting\u2026" : "Grant full access"
|
|
1767
|
+
}
|
|
1768
|
+
)
|
|
1769
|
+
] })
|
|
1770
|
+
]
|
|
1771
|
+
}
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
function ConsentResult({
|
|
1775
|
+
tone,
|
|
1776
|
+
title,
|
|
1777
|
+
body,
|
|
1778
|
+
className
|
|
1779
|
+
}) {
|
|
1780
|
+
const ring = tone === "ok" ? "border-og-status-running/30" : tone === "danger" ? "border-og-status-failed/30" : "border-og-border";
|
|
1781
|
+
const iconClass = tone === "ok" ? "text-og-status-running" : tone === "danger" ? "text-og-status-failed" : "text-og-fg-subtle";
|
|
1782
|
+
return /* @__PURE__ */ jsxs11(
|
|
1783
|
+
"div",
|
|
1784
|
+
{
|
|
1785
|
+
"data-enrollment-result": tone,
|
|
1786
|
+
className: cn(
|
|
1787
|
+
"og-root mx-auto flex w-full max-w-md flex-col items-center gap-3 rounded-og-lg border bg-og-surface-1 p-6 text-center shadow-og-md",
|
|
1788
|
+
ring,
|
|
1789
|
+
className
|
|
1790
|
+
),
|
|
1791
|
+
children: [
|
|
1792
|
+
/* @__PURE__ */ jsx11(
|
|
1793
|
+
"span",
|
|
1794
|
+
{
|
|
1795
|
+
className: cn(
|
|
1796
|
+
"flex size-10 items-center justify-center rounded-full bg-og-surface-2",
|
|
1797
|
+
iconClass
|
|
1798
|
+
),
|
|
1799
|
+
children: /* @__PURE__ */ jsx11(LaptopIcon6, { className: "size-5", "aria-hidden": true })
|
|
1800
|
+
}
|
|
1801
|
+
),
|
|
1802
|
+
/* @__PURE__ */ jsx11("h1", { className: "text-og-md font-semibold text-og-fg", children: title }),
|
|
1803
|
+
/* @__PURE__ */ jsx11("p", { className: "max-w-sm text-og-base text-og-fg-muted", children: body })
|
|
1804
|
+
]
|
|
1805
|
+
}
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// src/hooks/use-machines.ts
|
|
1810
|
+
import { useCallback as useCallback3, useState as useState5 } from "react";
|
|
1811
|
+
|
|
1812
|
+
// src/provider.tsx
|
|
1813
|
+
import {
|
|
1814
|
+
OpenGeniApiContractMismatchError,
|
|
1815
|
+
OPENGENI_API_CONTRACT_REVISION
|
|
1816
|
+
} from "@opengeni/sdk";
|
|
1817
|
+
import {
|
|
1818
|
+
createContext,
|
|
1819
|
+
useCallback,
|
|
1820
|
+
useContext,
|
|
1821
|
+
useEffect,
|
|
1822
|
+
useMemo,
|
|
1823
|
+
useRef as useRef2,
|
|
1824
|
+
useState as useState3
|
|
1825
|
+
} from "react";
|
|
1826
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1827
|
+
var OpenGeniContext = createContext(null);
|
|
1828
|
+
var NOOP_REGISTER_RECONCILER = () => () => void 0;
|
|
1829
|
+
var NOOP_RECONCILE_SESSION = async () => void 0;
|
|
1830
|
+
var CONTRACT_RELOAD_STORAGE_PREFIX = "opengeni.reloadForApiContract:";
|
|
1831
|
+
function OpenGeniProvider({
|
|
1832
|
+
client,
|
|
1833
|
+
workspaceId,
|
|
1834
|
+
onWorkspaceControlEvent,
|
|
1835
|
+
children
|
|
1836
|
+
}) {
|
|
1837
|
+
const [workspaceControlEvent, setWorkspaceControlEvent] = useState3(
|
|
1838
|
+
null
|
|
1839
|
+
);
|
|
1840
|
+
const [workspaceControlConnectionState, setWorkspaceControlConnectionState] = useState3("idle");
|
|
1841
|
+
const [contractMismatch, setContractMismatch] = useState3(
|
|
1842
|
+
null
|
|
1843
|
+
);
|
|
1844
|
+
const callbackRef = useRef2(onWorkspaceControlEvent);
|
|
1845
|
+
const reconcilersRef = useRef2(/* @__PURE__ */ new Map());
|
|
1846
|
+
callbackRef.current = onWorkspaceControlEvent;
|
|
1847
|
+
const verifyApiContract = useCallback(async () => {
|
|
1848
|
+
try {
|
|
1849
|
+
const config = await client.getClientConfig();
|
|
1850
|
+
if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
|
|
1851
|
+
throw new OpenGeniApiContractMismatchError(
|
|
1852
|
+
OPENGENI_API_CONTRACT_REVISION,
|
|
1853
|
+
String(config.apiContractRevision || "(missing)")
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
} catch (error) {
|
|
1857
|
+
if (error instanceof OpenGeniApiContractMismatchError) {
|
|
1858
|
+
setContractMismatch(error);
|
|
1859
|
+
reloadForContractMismatchOnce(error);
|
|
1860
|
+
}
|
|
1861
|
+
throw error;
|
|
1862
|
+
}
|
|
1863
|
+
}, [client]);
|
|
1864
|
+
const registerSessionReconciler = useMemo(
|
|
1865
|
+
() => (sessionId, key, reconcile) => {
|
|
1866
|
+
const sessionReconcilers = reconcilersRef.current.get(sessionId) ?? /* @__PURE__ */ new Map();
|
|
1867
|
+
sessionReconcilers.set(key, reconcile);
|
|
1868
|
+
reconcilersRef.current.set(sessionId, sessionReconcilers);
|
|
1869
|
+
return () => {
|
|
1870
|
+
const current = reconcilersRef.current.get(sessionId);
|
|
1871
|
+
current?.delete(key);
|
|
1872
|
+
if (current?.size === 0) reconcilersRef.current.delete(sessionId);
|
|
1873
|
+
};
|
|
1874
|
+
},
|
|
1875
|
+
[]
|
|
1876
|
+
);
|
|
1877
|
+
const reconcileSession = useMemo(
|
|
1878
|
+
() => async (sessionId) => {
|
|
1879
|
+
await verifyApiContract();
|
|
1880
|
+
const callbacks = [...reconcilersRef.current.get(sessionId)?.values() ?? []];
|
|
1881
|
+
await Promise.all(callbacks.map((reconcile) => reconcile()));
|
|
1882
|
+
},
|
|
1883
|
+
[verifyApiContract]
|
|
1884
|
+
);
|
|
1885
|
+
useEffect(() => {
|
|
1886
|
+
const controller = new AbortController();
|
|
1887
|
+
setWorkspaceControlEvent(null);
|
|
1888
|
+
setWorkspaceControlConnectionState("connecting");
|
|
1889
|
+
void (async () => {
|
|
1890
|
+
try {
|
|
1891
|
+
await verifyApiContract();
|
|
1892
|
+
const workspace = await client.getWorkspace(workspaceId);
|
|
1893
|
+
const stream = client.streamWorkspaceControlEvents(workspaceId, {
|
|
1894
|
+
after: workspace.inferenceControl.revision,
|
|
1895
|
+
signal: controller.signal,
|
|
1896
|
+
onStateChange: setWorkspaceControlConnectionState
|
|
1897
|
+
});
|
|
1898
|
+
for await (const event of stream) {
|
|
1899
|
+
if (controller.signal.aborted) return;
|
|
1900
|
+
setWorkspaceControlEvent(
|
|
1901
|
+
(current) => !current || event.sequence > current.sequence ? event : current
|
|
1902
|
+
);
|
|
1903
|
+
callbackRef.current?.(event);
|
|
1904
|
+
}
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
if (error instanceof OpenGeniApiContractMismatchError) {
|
|
1907
|
+
setContractMismatch(error);
|
|
1908
|
+
reloadForContractMismatchOnce(error);
|
|
1909
|
+
}
|
|
1910
|
+
if (!controller.signal.aborted) setWorkspaceControlConnectionState("error");
|
|
1911
|
+
}
|
|
1912
|
+
})();
|
|
1913
|
+
return () => controller.abort();
|
|
1914
|
+
}, [client, verifyApiContract, workspaceId]);
|
|
1915
|
+
const value = useMemo(
|
|
1916
|
+
() => ({
|
|
1917
|
+
client,
|
|
1918
|
+
workspaceId,
|
|
1919
|
+
workspaceControlEvent,
|
|
1920
|
+
workspaceControlConnectionState,
|
|
1921
|
+
registerSessionReconciler,
|
|
1922
|
+
reconcileSession
|
|
1923
|
+
}),
|
|
1924
|
+
[
|
|
1925
|
+
client,
|
|
1926
|
+
workspaceId,
|
|
1927
|
+
workspaceControlEvent,
|
|
1928
|
+
workspaceControlConnectionState,
|
|
1929
|
+
registerSessionReconciler,
|
|
1930
|
+
reconcileSession
|
|
1931
|
+
]
|
|
1932
|
+
);
|
|
1933
|
+
return /* @__PURE__ */ jsxs12(OpenGeniContext.Provider, { value, children: [
|
|
1934
|
+
children,
|
|
1935
|
+
contractMismatch ? /* @__PURE__ */ jsx12(ApiContractMismatchScreen, { mismatch: contractMismatch }) : null
|
|
1936
|
+
] });
|
|
1937
|
+
}
|
|
1938
|
+
function ApiContractMismatchScreen({ mismatch }) {
|
|
1939
|
+
return /* @__PURE__ */ jsx12(
|
|
1940
|
+
"div",
|
|
1941
|
+
{
|
|
1942
|
+
className: "og-root fixed inset-0 z-[2147483647] grid place-items-center bg-og-bg/95 p-6 backdrop-blur-sm",
|
|
1943
|
+
role: "alert",
|
|
1944
|
+
"aria-live": "assertive",
|
|
1945
|
+
"data-opengeni-api-contract-mismatch": true,
|
|
1946
|
+
children: /* @__PURE__ */ jsxs12("div", { className: "w-full max-w-md rounded-xl border border-og-border bg-og-surface p-6 shadow-2xl", children: [
|
|
1947
|
+
/* @__PURE__ */ jsx12("p", { className: "text-sm font-semibold text-og-fg", children: "OpenGeni updated" }),
|
|
1948
|
+
/* @__PURE__ */ jsx12("p", { className: "mt-2 text-sm leading-6 text-og-muted", children: "This tab cannot safely continue with the new server version. Reload it before sending or controlling work." }),
|
|
1949
|
+
/* @__PURE__ */ jsxs12("p", { className: "mt-3 font-mono text-xs text-og-subtle", children: [
|
|
1950
|
+
"Client ",
|
|
1951
|
+
mismatch.expected,
|
|
1952
|
+
" \xB7 API ",
|
|
1953
|
+
mismatch.actual
|
|
1954
|
+
] }),
|
|
1955
|
+
/* @__PURE__ */ jsx12(
|
|
1956
|
+
"button",
|
|
1957
|
+
{
|
|
1958
|
+
type: "button",
|
|
1959
|
+
className: "mt-5 inline-flex h-9 items-center rounded-md bg-og-fg px-3 text-sm font-medium text-og-bg",
|
|
1960
|
+
onClick: () => window.location.reload(),
|
|
1961
|
+
children: "Reload now"
|
|
1962
|
+
}
|
|
1963
|
+
)
|
|
1964
|
+
] })
|
|
1965
|
+
}
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
function reloadForContractMismatchOnce(mismatch) {
|
|
1969
|
+
if (typeof window === "undefined" || typeof sessionStorage === "undefined") return;
|
|
1970
|
+
const key = `${CONTRACT_RELOAD_STORAGE_PREFIX}${mismatch.actual}`;
|
|
1971
|
+
if (sessionStorage.getItem(key) === OPENGENI_API_CONTRACT_REVISION) return;
|
|
1972
|
+
sessionStorage.setItem(key, OPENGENI_API_CONTRACT_REVISION);
|
|
1973
|
+
window.setTimeout(() => window.location.reload(), 150);
|
|
1974
|
+
}
|
|
1975
|
+
function useOpenGeni(override = {}) {
|
|
1976
|
+
const context = useContext(OpenGeniContext);
|
|
1977
|
+
const client = override.client ?? context?.client;
|
|
1978
|
+
const workspaceId = override.workspaceId ?? context?.workspaceId;
|
|
1979
|
+
if (!client || !workspaceId) {
|
|
1980
|
+
throw new Error(
|
|
1981
|
+
"@opengeni/react: no OpenGeni client/workspace available. Wrap the tree in <OpenGeniProvider> or pass { client, workspaceId } to the hook."
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
return {
|
|
1985
|
+
client,
|
|
1986
|
+
workspaceId,
|
|
1987
|
+
workspaceControlEvent: context?.workspaceControlEvent ?? null,
|
|
1988
|
+
workspaceControlConnectionState: context?.workspaceControlConnectionState ?? "idle",
|
|
1989
|
+
registerSessionReconciler: context?.registerSessionReconciler ?? NOOP_REGISTER_RECONCILER,
|
|
1990
|
+
reconcileSession: context?.reconcileSession ?? NOOP_RECONCILE_SESSION
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
function useOpenGeniClient(override = {}) {
|
|
1994
|
+
const context = useContext(OpenGeniContext);
|
|
1995
|
+
const client = override.client ?? context?.client;
|
|
1996
|
+
if (!client) {
|
|
1997
|
+
throw new Error(
|
|
1998
|
+
"@opengeni/react: no OpenGeni client available. Wrap the tree in <OpenGeniProvider> or pass { client } to the hook."
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
return client;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
// src/hooks/internal.ts
|
|
2005
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
2006
|
+
function usePolledValue(load, options = {}) {
|
|
2007
|
+
const enabled = options.enabled ?? true;
|
|
2008
|
+
const pollIntervalMs = options.pollIntervalMs;
|
|
2009
|
+
const [data, setData] = useState4(null);
|
|
2010
|
+
const [loading, setLoading] = useState4(enabled);
|
|
2011
|
+
const [error, setError] = useState4(null);
|
|
2012
|
+
const generation = useRef3(0);
|
|
2013
|
+
const loadRef = useRef3(load);
|
|
2014
|
+
useEffect2(() => {
|
|
2015
|
+
if (loadRef.current !== load) {
|
|
2016
|
+
loadRef.current = load;
|
|
2017
|
+
setData(null);
|
|
2018
|
+
setError(null);
|
|
2019
|
+
}
|
|
2020
|
+
}, [load]);
|
|
2021
|
+
const run = useCallback2(async () => {
|
|
2022
|
+
const ticket = ++generation.current;
|
|
2023
|
+
try {
|
|
2024
|
+
const result = await load();
|
|
2025
|
+
if (ticket === generation.current) {
|
|
2026
|
+
setData(result);
|
|
2027
|
+
setError(null);
|
|
2028
|
+
setLoading(false);
|
|
2029
|
+
}
|
|
2030
|
+
} catch (cause) {
|
|
2031
|
+
if (ticket === generation.current) {
|
|
2032
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
2033
|
+
setLoading(false);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
}, [load]);
|
|
2037
|
+
useEffect2(() => {
|
|
2038
|
+
if (!enabled) {
|
|
2039
|
+
setLoading(false);
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
setLoading(true);
|
|
2043
|
+
void run();
|
|
2044
|
+
if (pollIntervalMs === void 0 || pollIntervalMs <= 0) {
|
|
2045
|
+
return () => {
|
|
2046
|
+
generation.current += 1;
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
const timer = setInterval(() => void run(), pollIntervalMs);
|
|
2050
|
+
return () => {
|
|
2051
|
+
clearInterval(timer);
|
|
2052
|
+
generation.current += 1;
|
|
2053
|
+
};
|
|
2054
|
+
}, [run, enabled, pollIntervalMs]);
|
|
2055
|
+
return { data, loading, error, refresh: run };
|
|
2056
|
+
}
|
|
2057
|
+
function useMutationRunner() {
|
|
2058
|
+
const [mutating, setMutating] = useState4(false);
|
|
2059
|
+
const [mutationError, setMutationError] = useState4(null);
|
|
2060
|
+
const inFlight = useRef3(0);
|
|
2061
|
+
const mounted = useRef3(true);
|
|
2062
|
+
useEffect2(() => {
|
|
2063
|
+
mounted.current = true;
|
|
2064
|
+
return () => {
|
|
2065
|
+
mounted.current = false;
|
|
2066
|
+
};
|
|
2067
|
+
}, []);
|
|
2068
|
+
const run = useCallback2(async (operation) => {
|
|
2069
|
+
inFlight.current += 1;
|
|
2070
|
+
if (mounted.current) {
|
|
2071
|
+
setMutating(true);
|
|
2072
|
+
setMutationError(null);
|
|
2073
|
+
}
|
|
2074
|
+
try {
|
|
2075
|
+
return await operation();
|
|
2076
|
+
} catch (cause) {
|
|
2077
|
+
if (mounted.current) {
|
|
2078
|
+
setMutationError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
2079
|
+
}
|
|
2080
|
+
return null;
|
|
2081
|
+
} finally {
|
|
2082
|
+
inFlight.current -= 1;
|
|
2083
|
+
if (mounted.current && inFlight.current === 0) {
|
|
2084
|
+
setMutating(false);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}, []);
|
|
2088
|
+
return {
|
|
2089
|
+
mutating,
|
|
2090
|
+
mutationError,
|
|
2091
|
+
clearMutationError: useCallback2(() => setMutationError(null), []),
|
|
2092
|
+
run
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
function useSessionEventTrigger(client, workspaceId, sessionId, match, onEvent, options = {}) {
|
|
2096
|
+
const enabled = options.enabled ?? true;
|
|
2097
|
+
const events = options.events;
|
|
2098
|
+
const sharedFeed = events !== void 0;
|
|
2099
|
+
const matchRef = useRef3(match);
|
|
2100
|
+
matchRef.current = match;
|
|
2101
|
+
const onEventRef = useRef3(onEvent);
|
|
2102
|
+
onEventRef.current = onEvent;
|
|
2103
|
+
const consumedRef = useRef3(0);
|
|
2104
|
+
const feedKeyRef = useRef3(null);
|
|
2105
|
+
useEffect2(() => {
|
|
2106
|
+
if (!sharedFeed || !enabled || !sessionId) {
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
const feedKey = `${workspaceId}\0${sessionId}`;
|
|
2110
|
+
const firstSequence = events[0]?.sequence ?? 0;
|
|
2111
|
+
if (feedKeyRef.current !== feedKey || firstSequence > consumedRef.current + 1) {
|
|
2112
|
+
feedKeyRef.current = feedKey;
|
|
2113
|
+
consumedRef.current = 0;
|
|
2114
|
+
}
|
|
2115
|
+
for (const event of events) {
|
|
2116
|
+
if (event.sequence <= consumedRef.current) {
|
|
2117
|
+
continue;
|
|
2118
|
+
}
|
|
2119
|
+
consumedRef.current = event.sequence;
|
|
2120
|
+
if (matchRef.current(event)) {
|
|
2121
|
+
onEventRef.current(event);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
}, [sharedFeed, enabled, events, workspaceId, sessionId]);
|
|
2125
|
+
useEffect2(() => {
|
|
2126
|
+
if (sharedFeed || !enabled || !sessionId) {
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
const controller = new AbortController();
|
|
2130
|
+
void (async () => {
|
|
2131
|
+
try {
|
|
2132
|
+
const session = await client.getSession(workspaceId, sessionId);
|
|
2133
|
+
if (controller.signal.aborted) {
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
const stream = client.streamEvents(workspaceId, sessionId, {
|
|
2137
|
+
after: session.lastSequence,
|
|
2138
|
+
signal: controller.signal
|
|
2139
|
+
});
|
|
2140
|
+
for await (const event of stream) {
|
|
2141
|
+
if (matchRef.current(event)) {
|
|
2142
|
+
onEventRef.current(event);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
} catch {
|
|
2146
|
+
}
|
|
2147
|
+
})();
|
|
2148
|
+
return () => {
|
|
2149
|
+
controller.abort();
|
|
2150
|
+
};
|
|
2151
|
+
}, [sharedFeed, enabled, client, workspaceId, sessionId]);
|
|
2152
|
+
}
|
|
2153
|
+
function useDebouncedCallback(callback, delayMs = 150) {
|
|
2154
|
+
const callbackRef = useRef3(callback);
|
|
2155
|
+
callbackRef.current = callback;
|
|
2156
|
+
const timerRef = useRef3(null);
|
|
2157
|
+
useEffect2(() => {
|
|
2158
|
+
return () => {
|
|
2159
|
+
if (timerRef.current !== null) {
|
|
2160
|
+
clearTimeout(timerRef.current);
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
}, []);
|
|
2164
|
+
return useCallback2(() => {
|
|
2165
|
+
if (timerRef.current !== null) {
|
|
2166
|
+
clearTimeout(timerRef.current);
|
|
2167
|
+
}
|
|
2168
|
+
timerRef.current = setTimeout(() => {
|
|
2169
|
+
timerRef.current = null;
|
|
2170
|
+
callbackRef.current();
|
|
2171
|
+
}, delayMs);
|
|
2172
|
+
}, [delayMs]);
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
// src/hooks/use-machines.ts
|
|
2176
|
+
var EMPTY = { activeSandboxId: null, activeEpoch: 0, machines: [] };
|
|
2177
|
+
function useMachines(options = {}) {
|
|
2178
|
+
const { client, workspaceId } = useOpenGeni(options);
|
|
2179
|
+
const machinesClient = options.machinesClient ?? client;
|
|
2180
|
+
const sessionId = options.sessionId;
|
|
2181
|
+
const load = useCallback3(async () => {
|
|
2182
|
+
return await machinesClient.listMachines(workspaceId, sessionId ? { sessionId } : void 0);
|
|
2183
|
+
}, [machinesClient, workspaceId, sessionId]);
|
|
2184
|
+
const {
|
|
2185
|
+
data: loadedData,
|
|
2186
|
+
loading,
|
|
2187
|
+
error,
|
|
2188
|
+
refresh
|
|
2189
|
+
} = usePolledValue(load, {
|
|
2190
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
2191
|
+
enabled: options.enabled
|
|
2192
|
+
});
|
|
2193
|
+
const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
|
|
2194
|
+
const [attachingSandboxId, setAttachingSandboxId] = useState5(null);
|
|
2195
|
+
const data = loadedData ?? EMPTY;
|
|
2196
|
+
const canAttach = sessionId !== void 0 && (typeof machinesClient.attachMachine === "function" || typeof machinesClient.swapActiveSandbox === "function");
|
|
2197
|
+
const attach = useCallback3(
|
|
2198
|
+
async (sandboxId) => {
|
|
2199
|
+
if (sessionId === void 0) return false;
|
|
2200
|
+
const runSwap = machinesClient.attachMachine ? () => machinesClient.attachMachine(workspaceId, sessionId, sandboxId) : machinesClient.swapActiveSandbox ? () => machinesClient.swapActiveSandbox(workspaceId, sessionId, { target: sandboxId }) : null;
|
|
2201
|
+
if (!runSwap) return false;
|
|
2202
|
+
setAttachingSandboxId(sandboxId);
|
|
2203
|
+
const result = await run(async () => {
|
|
2204
|
+
await runSwap();
|
|
2205
|
+
return true;
|
|
2206
|
+
});
|
|
2207
|
+
setAttachingSandboxId(null);
|
|
2208
|
+
if (result) await refresh();
|
|
2209
|
+
return result === true;
|
|
2210
|
+
},
|
|
2211
|
+
[machinesClient, workspaceId, sessionId, run, refresh]
|
|
2212
|
+
);
|
|
2213
|
+
const fetchSeries = useCallback3(
|
|
2214
|
+
async (enrollmentId, window2 = "1h") => {
|
|
2215
|
+
if (!machinesClient.machineMetricsSeries) return [];
|
|
2216
|
+
return await machinesClient.machineMetricsSeries(workspaceId, enrollmentId, { window: window2 });
|
|
2217
|
+
},
|
|
2218
|
+
[machinesClient, workspaceId]
|
|
2219
|
+
);
|
|
2220
|
+
return {
|
|
2221
|
+
machines: data.machines,
|
|
2222
|
+
activeSandboxId: data.activeSandboxId,
|
|
2223
|
+
activeEpoch: data.activeEpoch,
|
|
2224
|
+
loading,
|
|
2225
|
+
error,
|
|
2226
|
+
refresh,
|
|
2227
|
+
attach,
|
|
2228
|
+
canAttach,
|
|
2229
|
+
fetchSeries,
|
|
2230
|
+
attaching: mutating,
|
|
2231
|
+
attachingSandboxId,
|
|
2232
|
+
mutationError,
|
|
2233
|
+
clearMutationError
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
export {
|
|
2238
|
+
OpenGeniProvider,
|
|
2239
|
+
useOpenGeni,
|
|
2240
|
+
useOpenGeniClient,
|
|
2241
|
+
usePolledValue,
|
|
2242
|
+
useMutationRunner,
|
|
2243
|
+
useSessionEventTrigger,
|
|
2244
|
+
useDebouncedCallback,
|
|
2245
|
+
formatRelativeTime,
|
|
2246
|
+
formatBytes,
|
|
2247
|
+
truncate,
|
|
2248
|
+
stringifyPayload,
|
|
2249
|
+
tryParseJson,
|
|
2250
|
+
CREDIT_EXHAUSTION_MESSAGE,
|
|
2251
|
+
isCreditExhaustion,
|
|
2252
|
+
humanizeFailureReason,
|
|
2253
|
+
cn,
|
|
2254
|
+
useMachines,
|
|
2255
|
+
connectionStatusForState,
|
|
2256
|
+
CONNECTION_STATUS_META,
|
|
2257
|
+
MACHINE_STATE_BADGE_META,
|
|
2258
|
+
ConnectionStatusPill,
|
|
2259
|
+
ConnectionDot,
|
|
2260
|
+
MachineStatusPill,
|
|
2261
|
+
MachineDockBar,
|
|
2262
|
+
SharedMachineDisclosure,
|
|
2263
|
+
MachineMetrics,
|
|
2264
|
+
deriveHealth,
|
|
2265
|
+
HEALTH_TOKEN,
|
|
2266
|
+
healthPulses,
|
|
2267
|
+
MachineHealthPill,
|
|
2268
|
+
MetricSparkline,
|
|
2269
|
+
MachineCard,
|
|
2270
|
+
MetricHistoryChart,
|
|
2271
|
+
METRIC_WINDOWS,
|
|
2272
|
+
WINDOW_LABEL,
|
|
2273
|
+
METRICS,
|
|
2274
|
+
pointsFor,
|
|
2275
|
+
MachineDetail,
|
|
2276
|
+
MachinesDashboard,
|
|
2277
|
+
EnrollmentDeviceFlow,
|
|
2278
|
+
EnrollmentConsent
|
|
2279
|
+
};
|
|
2280
|
+
//# sourceMappingURL=chunk-TOJR776I.js.map
|