@burdenoff/microfe-vibecontrols 2026.601.23 → 2026.602.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/vibes/DeleteVibeDialog.js +130 -0
- package/dist/components/vibes/DeleteVibeDialog.js.map +1 -0
- package/dist/components/vibes/VibeCard.js +64 -56
- package/dist/components/vibes/VibeCard.js.map +1 -1
- package/dist/components/vibes/VibeList.js +132 -113
- package/dist/components/vibes/VibeList.js.map +1 -1
- package/dist/components/vibes/index.js +1 -0
- package/dist/generated/wspace-operations.js +2 -2
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +560 -523
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibesPage.js +311 -280
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +192 -80
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,89 +1,141 @@
|
|
|
1
|
-
import { useGetTunnelQuery as e } from "../../generated/wspace-operations.js";
|
|
2
|
-
import { useTr as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { useGetTunnelQuery as e, useStartTunnelMutation as t, useStopTunnelMutation as n } from "../../generated/wspace-operations.js";
|
|
2
|
+
import { useTr as r } from "../../shared/hooks/useTr.js";
|
|
3
|
+
import { ConfirmDialog as i } from "../../components/shared/ConfirmDialog.js";
|
|
4
|
+
import { QuotaExhaustedDialog as a } from "../../components/shared/QuotaExhaustedDialog.js";
|
|
5
|
+
import { getQuotaDialogContent as o, isQuotaExhaustedError as s, markQuotaHandledLocally as c } from "../../utils/quotaUtils.js";
|
|
6
|
+
import { useVibeControls as l } from "../../providers/VibeControlsProvider.js";
|
|
7
|
+
import { PageHeader as u } from "../../components/shared/PageHeader.js";
|
|
8
|
+
import { LoadingState as d } from "../../components/shared/LoadingState.js";
|
|
9
|
+
import { ErrorState as f } from "../../components/shared/ErrorState.js";
|
|
10
|
+
import { EntityTagPicker as ee } from "../../components/shared/EntityTagPicker.js";
|
|
11
|
+
import { EntityShareButton as te } from "../../components/sharing/EntityShareButton.js";
|
|
8
12
|
import "../../components/sharing/index.js";
|
|
9
|
-
import { TunnelStatusBadge as
|
|
10
|
-
import { ProviderBadge as
|
|
11
|
-
import { TunnelOverviewTab as
|
|
12
|
-
import { TunnelEndpointsTab as
|
|
13
|
-
import { TunnelSessionsTab as
|
|
14
|
-
import { TunnelDomainsTab as
|
|
15
|
-
import { TunnelUsageTab as
|
|
16
|
-
import { TunnelCliTab as
|
|
17
|
-
import { TunnelAuditTab as
|
|
18
|
-
import { TunnelSettingsTab as
|
|
19
|
-
import { useEffect as
|
|
20
|
-
import {
|
|
21
|
-
import { useNavigate as x, useParams as S, useSearchParams as C } from "react-router-dom";
|
|
13
|
+
import { TunnelStatusBadge as ne } from "../../components/tunnels/TunnelStatusBadge.js";
|
|
14
|
+
import { ProviderBadge as re } from "../../components/tunnels/ProviderBadge.js";
|
|
15
|
+
import { TunnelOverviewTab as ie } from "./tabs/TunnelOverviewTab.js";
|
|
16
|
+
import { TunnelEndpointsTab as ae } from "./tabs/TunnelEndpointsTab.js";
|
|
17
|
+
import { TunnelSessionsTab as oe } from "./tabs/TunnelSessionsTab.js";
|
|
18
|
+
import { TunnelDomainsTab as se } from "./tabs/TunnelDomainsTab.js";
|
|
19
|
+
import { TunnelUsageTab as ce } from "./tabs/TunnelUsageTab.js";
|
|
20
|
+
import { TunnelCliTab as p } from "./tabs/TunnelCliTab.js";
|
|
21
|
+
import { TunnelAuditTab as m } from "./tabs/TunnelAuditTab.js";
|
|
22
|
+
import { TunnelSettingsTab as h } from "./tabs/TunnelSettingsTab.js";
|
|
23
|
+
import { useEffect as g, useMemo as _, useState as v } from "react";
|
|
24
|
+
import { AlertCircle as y, ArrowLeft as le, Loader2 as ue, Play as de, Square as fe, Tag as pe } from "lucide-react";
|
|
25
|
+
import { useLocation as b, useNavigate as x, useParams as S, useSearchParams as C } from "react-router-dom";
|
|
22
26
|
import { jsx as w, jsxs as T } from "react/jsx-runtime";
|
|
23
|
-
import {
|
|
27
|
+
import { useEventBus as E } from "@burdenoff/fe-libs/shared/events";
|
|
28
|
+
import { Tabs as D } from "@burdenoff/fe-libs/chrome";
|
|
24
29
|
//#region src/pages/tunnels/TunnelDetailsPage.tsx
|
|
25
|
-
function
|
|
26
|
-
let
|
|
27
|
-
variables: { id:
|
|
30
|
+
function O() {
|
|
31
|
+
let O = r(), { tunnelId: k = "" } = S(), A = x(), j = b(), M = E(), { workspaceId: N } = l(), [P, F] = C(), [I, L] = v(!1), [R, z] = v(!1), [B, V] = v(() => j.state?.autoPromptStart === !0), { data: H, loading: me, error: U, refetch: he, startPolling: W, stopPolling: G } = e({
|
|
32
|
+
variables: { id: k },
|
|
28
33
|
fetchPolicy: "cache-and-network",
|
|
29
|
-
skip: !
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
skip: !k
|
|
35
|
+
});
|
|
36
|
+
g(() => {
|
|
37
|
+
j.state?.autoPromptStart && A(j.pathname + j.search, {
|
|
38
|
+
replace: !0,
|
|
39
|
+
state: {}
|
|
40
|
+
});
|
|
41
|
+
}, []);
|
|
42
|
+
let [ge] = t(), [_e] = n(), K = async () => {
|
|
43
|
+
let e = H?.tunnel;
|
|
44
|
+
if (!(!e || I)) {
|
|
45
|
+
V(!1), L(!0);
|
|
46
|
+
try {
|
|
47
|
+
let t = await ge({
|
|
48
|
+
variables: { id: e.id },
|
|
49
|
+
optimisticResponse: { startTunnel: {
|
|
50
|
+
...e,
|
|
51
|
+
status: "PROVISIONING",
|
|
52
|
+
__typename: "Tunnel"
|
|
53
|
+
} }
|
|
54
|
+
});
|
|
55
|
+
t.error && s(t.error) ? (c(), z(!0), M.emitCustom("vibecontrols.quota.tunnel_limit_hit", { workspaceId: N ?? void 0 })) : t.error || M.emitCustom("vibecontrols.tunnel.started", {
|
|
56
|
+
workspaceId: N ?? void 0,
|
|
57
|
+
tunnelId: e.id
|
|
58
|
+
});
|
|
59
|
+
} catch (e) {
|
|
60
|
+
s(e) ? (c(), z(!0), M.emitCustom("vibecontrols.quota.tunnel_limit_hit", { workspaceId: N ?? void 0 })) : console.error("[TunnelDetailsPage] startTunnel failed", e);
|
|
61
|
+
} finally {
|
|
62
|
+
L(!1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}, ve = async () => {
|
|
66
|
+
let e = H?.tunnel;
|
|
67
|
+
if (!(!e || I)) {
|
|
68
|
+
L(!0);
|
|
69
|
+
try {
|
|
70
|
+
await _e({
|
|
71
|
+
variables: { id: e.id },
|
|
72
|
+
optimisticResponse: { stopTunnel: {
|
|
73
|
+
...e,
|
|
74
|
+
status: "STOPPED",
|
|
75
|
+
__typename: "Tunnel"
|
|
76
|
+
} }
|
|
77
|
+
});
|
|
78
|
+
} finally {
|
|
79
|
+
L(!1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}, q = H?.tunnel?.status === "PROVISIONING" || H?.tunnel?.status === "DELETING" || H?.tunnel?.status === "ACTIVE" && !H.tunnel.primaryDomain?.hostname;
|
|
83
|
+
g(() => {
|
|
84
|
+
q ? W(4e3) : G();
|
|
33
85
|
}, [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
86
|
+
q,
|
|
87
|
+
W,
|
|
88
|
+
G
|
|
37
89
|
]);
|
|
38
|
-
let
|
|
90
|
+
let J = _(() => [
|
|
39
91
|
{
|
|
40
92
|
id: "overview",
|
|
41
|
-
label:
|
|
93
|
+
label: O("vibecontrols.tunnels.detail.overview", "Overview")
|
|
42
94
|
},
|
|
43
95
|
{
|
|
44
96
|
id: "endpoints",
|
|
45
|
-
label:
|
|
97
|
+
label: O("vibecontrols.tunnels.detail.endpoints", "Endpoints")
|
|
46
98
|
},
|
|
47
99
|
{
|
|
48
100
|
id: "sessions",
|
|
49
|
-
label:
|
|
101
|
+
label: O("vibecontrols.tunnels.detail.sessions", "Sessions")
|
|
50
102
|
},
|
|
51
103
|
{
|
|
52
104
|
id: "domains",
|
|
53
|
-
label:
|
|
105
|
+
label: O("vibecontrols.tunnels.detail.domains", "Domains")
|
|
54
106
|
},
|
|
55
107
|
{
|
|
56
108
|
id: "usage",
|
|
57
|
-
label:
|
|
109
|
+
label: O("vibecontrols.tunnels.detail.usage", "Usage")
|
|
58
110
|
},
|
|
59
111
|
{
|
|
60
112
|
id: "cli",
|
|
61
|
-
label:
|
|
113
|
+
label: O("vibecontrols.tunnels.detail.cli", "CLI")
|
|
62
114
|
},
|
|
63
115
|
{
|
|
64
116
|
id: "audit",
|
|
65
|
-
label:
|
|
117
|
+
label: O("vibecontrols.tunnels.detail.audit", "Audit")
|
|
66
118
|
},
|
|
67
119
|
{
|
|
68
120
|
id: "settings",
|
|
69
|
-
label:
|
|
121
|
+
label: O("vibecontrols.tunnels.detail.settings", "Settings")
|
|
70
122
|
}
|
|
71
|
-
], [
|
|
72
|
-
let t = new URLSearchParams(
|
|
73
|
-
e ===
|
|
123
|
+
], [O]), Y = P.get("tab") ?? J[0].id, ye = (e) => {
|
|
124
|
+
let t = new URLSearchParams(P);
|
|
125
|
+
e === J[0].id ? t.delete("tab") : t.set("tab", e), F(t, { replace: !0 });
|
|
74
126
|
};
|
|
75
|
-
if (
|
|
76
|
-
if (
|
|
127
|
+
if (me && !H) return /* @__PURE__ */ w(d, { message: "Loading tunnel" });
|
|
128
|
+
if (U) return /* @__PURE__ */ w(f, {
|
|
77
129
|
title: "Failed to load tunnel",
|
|
78
|
-
message:
|
|
79
|
-
onRetry: () =>
|
|
130
|
+
message: U.message,
|
|
131
|
+
onRetry: () => he()
|
|
80
132
|
});
|
|
81
|
-
if (!
|
|
133
|
+
if (!H?.tunnel) return /* @__PURE__ */ w(f, {
|
|
82
134
|
title: "Tunnel not found",
|
|
83
135
|
message: "This tunnel may have been deleted.",
|
|
84
|
-
onRetry: () =>
|
|
136
|
+
onRetry: () => A("/vibecontrols/tunnels")
|
|
85
137
|
});
|
|
86
|
-
let H =
|
|
138
|
+
let X = H.tunnel, Z = I || X.status === "PROVISIONING" || X.status === "DELETING", Q = X.status === "ACTIVE", be = Q && !Z, $ = !Q && !Z;
|
|
87
139
|
return /* @__PURE__ */ T("div", {
|
|
88
140
|
className: "flex flex-col h-full",
|
|
89
141
|
children: [
|
|
@@ -92,40 +144,81 @@ function D() {
|
|
|
92
144
|
children: [
|
|
93
145
|
/* @__PURE__ */ T("button", {
|
|
94
146
|
type: "button",
|
|
95
|
-
onClick: () =>
|
|
147
|
+
onClick: () => A("/vibecontrols/tunnels"),
|
|
96
148
|
className: "flex items-center gap-1.5 mb-4 text-sm text-text-secondary hover:text-text-primary transition-colors",
|
|
97
|
-
children: [/* @__PURE__ */ w(
|
|
149
|
+
children: [/* @__PURE__ */ w(le, { size: 16 }), "Back to Tunnels"]
|
|
98
150
|
}),
|
|
99
|
-
/* @__PURE__ */ w(
|
|
151
|
+
/* @__PURE__ */ w(u, {
|
|
100
152
|
breadcrumb: [{
|
|
101
153
|
label: "Tunnels",
|
|
102
154
|
path: "/vibecontrols/tunnels"
|
|
103
|
-
}, { label:
|
|
104
|
-
title:
|
|
105
|
-
description:
|
|
155
|
+
}, { label: X.name }],
|
|
156
|
+
title: X.name,
|
|
157
|
+
description: X.description ?? `${X.protocol}:${X.localPort} on ${X.localHost}`,
|
|
106
158
|
actions: /* @__PURE__ */ T("div", {
|
|
107
159
|
className: "flex items-center gap-2",
|
|
108
160
|
children: [
|
|
109
|
-
/* @__PURE__ */
|
|
110
|
-
|
|
111
|
-
|
|
161
|
+
Z && /* @__PURE__ */ T("span", {
|
|
162
|
+
className: "inline-flex items-center rounded-md border border-border-default px-3 py-2 text-sm text-text-muted",
|
|
163
|
+
children: [/* @__PURE__ */ w(ue, { className: "size-4 mr-1.5 animate-spin" }), Q ? O("vibecontrols.tunnels.stopping", "Stopping…") : O("vibecontrols.tunnels.starting", "Starting…")]
|
|
164
|
+
}),
|
|
165
|
+
$ && /* @__PURE__ */ T("button", {
|
|
166
|
+
type: "button",
|
|
167
|
+
onClick: () => void K(),
|
|
168
|
+
className: "inline-flex items-center rounded-md bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-fg hover:bg-action-primary-bg-hover focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40",
|
|
169
|
+
children: [/* @__PURE__ */ w(de, { className: "size-4 mr-1.5" }), O("vibecontrols.tunnels.detail.startTunnel", "Start tunnel")]
|
|
170
|
+
}),
|
|
171
|
+
be && /* @__PURE__ */ T("button", {
|
|
172
|
+
type: "button",
|
|
173
|
+
onClick: () => void ve(),
|
|
174
|
+
className: "inline-flex items-center rounded-md border border-border-default px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-elevated focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40",
|
|
175
|
+
children: [/* @__PURE__ */ w(fe, { className: "size-4 mr-1.5" }), O("vibecontrols.tunnels.detail.stopTunnel", "Stop tunnel")]
|
|
176
|
+
}),
|
|
177
|
+
/* @__PURE__ */ w(ne, { status: X.status }),
|
|
178
|
+
/* @__PURE__ */ w(re, { providerKey: X.providerKey }),
|
|
179
|
+
/* @__PURE__ */ w(te, {
|
|
112
180
|
entity: "tunnel",
|
|
113
181
|
resource: {
|
|
114
|
-
id:
|
|
115
|
-
name:
|
|
182
|
+
id: X.id,
|
|
183
|
+
name: X.name
|
|
116
184
|
},
|
|
117
|
-
label:
|
|
185
|
+
label: O("vibecontrols.tunnels.detail.share", "Share"),
|
|
118
186
|
variant: "button"
|
|
119
187
|
})
|
|
120
188
|
]
|
|
121
189
|
})
|
|
122
190
|
}),
|
|
191
|
+
X.status === "ERROR" && X.lastError && /* @__PURE__ */ T("div", {
|
|
192
|
+
className: "mt-3 flex items-start gap-2 rounded-md bg-status-error-bg px-3 py-2 text-sm text-status-error-fg",
|
|
193
|
+
children: [
|
|
194
|
+
/* @__PURE__ */ w(y, {
|
|
195
|
+
className: "mt-0.5 size-4 flex-shrink-0",
|
|
196
|
+
"aria-hidden": "true"
|
|
197
|
+
}),
|
|
198
|
+
/* @__PURE__ */ T("div", {
|
|
199
|
+
className: "min-w-0 flex-1",
|
|
200
|
+
children: [/* @__PURE__ */ w("p", {
|
|
201
|
+
className: "font-medium",
|
|
202
|
+
children: O("vibecontrols.tunnels.detail.startFailed", "The tunnel failed to start")
|
|
203
|
+
}), /* @__PURE__ */ w("p", {
|
|
204
|
+
className: "mt-0.5 break-words text-status-error-fg/90",
|
|
205
|
+
children: X.lastError
|
|
206
|
+
})]
|
|
207
|
+
}),
|
|
208
|
+
$ && /* @__PURE__ */ w("button", {
|
|
209
|
+
type: "button",
|
|
210
|
+
onClick: () => void K(),
|
|
211
|
+
className: "flex-shrink-0 rounded-md border border-status-error-fg/30 px-2.5 py-1 text-xs font-medium hover:bg-status-error-bg/60",
|
|
212
|
+
children: O("vibecontrols.tunnels.detail.retry", "Retry")
|
|
213
|
+
})
|
|
214
|
+
]
|
|
215
|
+
}),
|
|
123
216
|
/* @__PURE__ */ w("div", {
|
|
124
217
|
className: "mt-2",
|
|
125
|
-
children: /* @__PURE__ */ w(
|
|
126
|
-
items:
|
|
127
|
-
activeTabId:
|
|
128
|
-
onTabChange:
|
|
218
|
+
children: /* @__PURE__ */ w(D, {
|
|
219
|
+
items: J,
|
|
220
|
+
activeTabId: Y,
|
|
221
|
+
onTabChange: ye,
|
|
129
222
|
variant: "underline"
|
|
130
223
|
})
|
|
131
224
|
})
|
|
@@ -133,37 +226,56 @@ function D() {
|
|
|
133
226
|
}),
|
|
134
227
|
/* @__PURE__ */ T("div", {
|
|
135
228
|
className: "flex items-start gap-2 border-b border-border-subtle bg-bg-surface px-6 py-2",
|
|
136
|
-
children: [/* @__PURE__ */ w(
|
|
229
|
+
children: [/* @__PURE__ */ w(pe, {
|
|
137
230
|
className: "mt-1 size-4 flex-shrink-0 text-text-secondary",
|
|
138
231
|
"aria-hidden": "true"
|
|
139
232
|
}), /* @__PURE__ */ w("div", {
|
|
140
233
|
className: "min-w-0 flex-1",
|
|
141
|
-
children: /* @__PURE__ */ w(
|
|
234
|
+
children: /* @__PURE__ */ w(ee, {
|
|
142
235
|
entityType: "VIBECONTROLS_TUNNEL",
|
|
143
|
-
entityId:
|
|
236
|
+
entityId: X.id
|
|
144
237
|
})
|
|
145
238
|
})]
|
|
146
239
|
}),
|
|
147
240
|
/* @__PURE__ */ T("div", {
|
|
148
241
|
className: "flex-1 min-h-0 overflow-auto p-6",
|
|
149
242
|
children: [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
tunnel:
|
|
159
|
-
onDeleted: () =>
|
|
243
|
+
Y === "overview" && /* @__PURE__ */ w(ie, { tunnel: X }),
|
|
244
|
+
Y === "endpoints" && /* @__PURE__ */ w(ae, { tunnel: X }),
|
|
245
|
+
Y === "sessions" && /* @__PURE__ */ w(oe, { tunnelId: X.id }),
|
|
246
|
+
Y === "domains" && /* @__PURE__ */ w(se, { tunnel: X }),
|
|
247
|
+
Y === "usage" && /* @__PURE__ */ w(ce, { tunnelId: X.id }),
|
|
248
|
+
Y === "cli" && /* @__PURE__ */ w(p, { tunnel: X }),
|
|
249
|
+
Y === "audit" && /* @__PURE__ */ w(m, { tunnelId: X.id }),
|
|
250
|
+
Y === "settings" && /* @__PURE__ */ w(h, {
|
|
251
|
+
tunnel: X,
|
|
252
|
+
onDeleted: () => A("/vibecontrols/tunnels")
|
|
160
253
|
})
|
|
161
254
|
]
|
|
255
|
+
}),
|
|
256
|
+
/* @__PURE__ */ w(i, {
|
|
257
|
+
isOpen: B && $,
|
|
258
|
+
onClose: () => V(!1),
|
|
259
|
+
onConfirm: () => void K(),
|
|
260
|
+
title: O("vibecontrols.tunnels.detail.startPromptTitle", "Start this tunnel now?"),
|
|
261
|
+
message: O("vibecontrols.tunnels.detail.startPromptMessage", "Your tunnel was created as a draft. Start it to bring it online and get its public URL. You can stop it anytime from this page."),
|
|
262
|
+
confirmLabel: O("vibecontrols.tunnels.detail.startTunnel", "Start tunnel"),
|
|
263
|
+
cancelLabel: O("vibecontrols.tunnels.detail.startPromptLater", "Not now"),
|
|
264
|
+
variant: "default",
|
|
265
|
+
isLoading: I
|
|
266
|
+
}),
|
|
267
|
+
/* @__PURE__ */ w(a, {
|
|
268
|
+
isOpen: R,
|
|
269
|
+
onClose: () => z(!1),
|
|
270
|
+
onConfirm: () => {
|
|
271
|
+
z(!1), A("/billing/plans");
|
|
272
|
+
},
|
|
273
|
+
...o("tunnel-start")
|
|
162
274
|
})
|
|
163
275
|
]
|
|
164
276
|
});
|
|
165
277
|
}
|
|
166
278
|
//#endregion
|
|
167
|
-
export {
|
|
279
|
+
export { O as TunnelDetailsPage };
|
|
168
280
|
|
|
169
281
|
//# sourceMappingURL=TunnelDetailsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TunnelDetailsPage.js","names":[],"sources":["../../../src/pages/tunnels/TunnelDetailsPage.tsx"],"sourcesContent":["import { useEffect, useMemo } from 'react';\nimport { useParams, useNavigate, useSearchParams } from 'react-router-dom';\nimport { Tabs, type TabItem } from '@burdenoff/fe-libs/chrome';\n\nimport { Tag, ArrowLeft } from 'lucide-react';\nimport { PageHeader } from '@/components/shared/PageHeader';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { ErrorState } from '@/components/shared/ErrorState';\nimport { TunnelStatusBadge } from '@/components/tunnels/TunnelStatusBadge';\nimport { ProviderBadge } from '@/components/tunnels/ProviderBadge';\nimport { EntityShareButton } from '@/components/sharing';\nimport { useGetTunnelQuery } from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\n\nimport { TunnelOverviewTab } from './tabs/TunnelOverviewTab';\nimport { TunnelEndpointsTab } from './tabs/TunnelEndpointsTab';\nimport { TunnelSessionsTab } from './tabs/TunnelSessionsTab';\nimport { TunnelDomainsTab } from './tabs/TunnelDomainsTab';\nimport { TunnelUsageTab } from './tabs/TunnelUsageTab';\nimport { TunnelCliTab } from './tabs/TunnelCliTab';\nimport { TunnelAuditTab } from './tabs/TunnelAuditTab';\nimport { TunnelSettingsTab } from './tabs/TunnelSettingsTab';\n\nexport function TunnelDetailsPage() {\n const tr = useTr();\n const { tunnelId = '' } = useParams();\n const navigate = useNavigate();\n const [params, setParams] = useSearchParams();\n\n const { data, loading, error, refetch, startPolling, stopPolling } = useGetTunnelQuery({\n variables: { id: tunnelId },\n fetchPolicy: 'cache-and-network',\n skip: !tunnelId,\n });\n\n // Poll while transitioning OR ACTIVE-but-no-domain (Cloudflare URL arrives async).\n const needsPoll =\n data?.tunnel?.status === 'PROVISIONING' ||\n data?.tunnel?.status === 'DELETING' ||\n (data?.tunnel?.status === 'ACTIVE' && !data.tunnel.primaryDomain?.hostname);\n useEffect(() => {\n if (needsPoll) {\n startPolling(4000);\n } else {\n stopPolling();\n }\n }, [needsPoll, startPolling, stopPolling]);\n\n const tabs: TabItem[] = useMemo(\n () => [\n { id: 'overview', label: tr('vibecontrols.tunnels.detail.overview', 'Overview') },\n { id: 'endpoints', label: tr('vibecontrols.tunnels.detail.endpoints', 'Endpoints') },\n { id: 'sessions', label: tr('vibecontrols.tunnels.detail.sessions', 'Sessions') },\n { id: 'domains', label: tr('vibecontrols.tunnels.detail.domains', 'Domains') },\n { id: 'usage', label: tr('vibecontrols.tunnels.detail.usage', 'Usage') },\n { id: 'cli', label: tr('vibecontrols.tunnels.detail.cli', 'CLI') },\n { id: 'audit', label: tr('vibecontrols.tunnels.detail.audit', 'Audit') },\n { id: 'settings', label: tr('vibecontrols.tunnels.detail.settings', 'Settings') },\n ],\n [tr]\n );\n\n const activeId = params.get('tab') ?? tabs[0].id;\n\n const handleTabChange = (id: string) => {\n const next = new URLSearchParams(params);\n if (id === tabs[0].id) next.delete('tab');\n else next.set('tab', id);\n setParams(next, { replace: true });\n };\n\n if (loading && !data) return <LoadingState message=\"Loading tunnel\" />;\n if (error) {\n return (\n <ErrorState title=\"Failed to load tunnel\" message={error.message} onRetry={() => refetch()} />\n );\n }\n if (!data?.tunnel) {\n return (\n <ErrorState\n title=\"Tunnel not found\"\n message=\"This tunnel may have been deleted.\"\n onRetry={() => navigate('/vibecontrols/tunnels')}\n />\n );\n }\n\n const tunnel = data.tunnel;\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"px-6 pt-6 pb-2 border-b border-border-subtle\">\n <button\n type=\"button\"\n onClick={() => navigate('/vibecontrols/tunnels')}\n className=\"flex items-center gap-1.5 mb-4 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft size={16} />\n Back to Tunnels\n </button>\n <PageHeader\n breadcrumb={[{ label: 'Tunnels', path: '/vibecontrols/tunnels' }, { label: tunnel.name }]}\n title={tunnel.name}\n description={\n tunnel.description ?? `${tunnel.protocol}:${tunnel.localPort} on ${tunnel.localHost}`\n }\n actions={\n <div className=\"flex items-center gap-2\">\n <TunnelStatusBadge status={tunnel.status} />\n <ProviderBadge providerKey={tunnel.providerKey} />\n <EntityShareButton\n entity=\"tunnel\"\n resource={{ id: tunnel.id, name: tunnel.name }}\n label={tr('vibecontrols.tunnels.detail.share', 'Share')}\n variant=\"button\"\n />\n </div>\n }\n />\n <div className=\"mt-2\">\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n </div>\n <div className=\"flex items-start gap-2 border-b border-border-subtle bg-bg-surface px-6 py-2\">\n <Tag className=\"mt-1 size-4 flex-shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n <div className=\"min-w-0 flex-1\">\n <EntityTagPicker entityType=\"VIBECONTROLS_TUNNEL\" entityId={tunnel.id} />\n </div>\n </div>\n <div className=\"flex-1 min-h-0 overflow-auto p-6\">\n {activeId === 'overview' && <TunnelOverviewTab tunnel={tunnel} />}\n {activeId === 'endpoints' && <TunnelEndpointsTab tunnel={tunnel} />}\n {activeId === 'sessions' && <TunnelSessionsTab tunnelId={tunnel.id} />}\n {activeId === 'domains' && <TunnelDomainsTab tunnel={tunnel} />}\n {activeId === 'usage' && <TunnelUsageTab tunnelId={tunnel.id} />}\n {activeId === 'cli' && <TunnelCliTab tunnel={tunnel} />}\n {activeId === 'audit' && <TunnelAuditTab tunnelId={tunnel.id} />}\n {activeId === 'settings' && (\n <TunnelSettingsTab tunnel={tunnel} onDeleted={() => navigate('/vibecontrols/tunnels')} />\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TunnelDetailsPage.js","names":[],"sources":["../../../src/pages/tunnels/TunnelDetailsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useParams, useNavigate, useSearchParams, useLocation } from 'react-router-dom';\nimport { Tabs, type TabItem } from '@burdenoff/fe-libs/chrome';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\n\nimport { Tag, ArrowLeft, Play, Square, Loader2, AlertCircle } from 'lucide-react';\nimport { PageHeader } from '@/components/shared/PageHeader';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { ErrorState } from '@/components/shared/ErrorState';\nimport { ConfirmDialog } from '@/components/shared/ConfirmDialog';\nimport { QuotaExhaustedDialog } from '@/components/shared/QuotaExhaustedDialog';\nimport { TunnelStatusBadge } from '@/components/tunnels/TunnelStatusBadge';\nimport { ProviderBadge } from '@/components/tunnels/ProviderBadge';\nimport { EntityShareButton } from '@/components/sharing';\nimport {\n useGetTunnelQuery,\n useStartTunnelMutation,\n useStopTunnelMutation,\n} from '@/generated/wspace-operations';\nimport type { TunnelStatus } from '@/generated/wspace-types';\nimport {\n isQuotaExhaustedError,\n markQuotaHandledLocally,\n getQuotaDialogContent,\n} from '@/utils/quotaUtils';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\nimport { useTr } from '../../shared/hooks/useTr';\n\nimport { TunnelOverviewTab } from './tabs/TunnelOverviewTab';\nimport { TunnelEndpointsTab } from './tabs/TunnelEndpointsTab';\nimport { TunnelSessionsTab } from './tabs/TunnelSessionsTab';\nimport { TunnelDomainsTab } from './tabs/TunnelDomainsTab';\nimport { TunnelUsageTab } from './tabs/TunnelUsageTab';\nimport { TunnelCliTab } from './tabs/TunnelCliTab';\nimport { TunnelAuditTab } from './tabs/TunnelAuditTab';\nimport { TunnelSettingsTab } from './tabs/TunnelSettingsTab';\n\nexport function TunnelDetailsPage() {\n const tr = useTr();\n const { tunnelId = '' } = useParams();\n const navigate = useNavigate();\n const location = useLocation();\n const bus = useEventBus();\n const { workspaceId } = useVibeControls();\n const [params, setParams] = useSearchParams();\n const [isPending, setIsPending] = useState(false);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n // Set by the create wizard so a freshly created tunnel offers to start\n // immediately instead of leaving the user on a DRAFT with no obvious action.\n const [showStartPrompt, setShowStartPrompt] = useState(\n () => (location.state as { autoPromptStart?: boolean } | null)?.autoPromptStart === true\n );\n\n const { data, loading, error, refetch, startPolling, stopPolling } = useGetTunnelQuery({\n variables: { id: tunnelId },\n fetchPolicy: 'cache-and-network',\n skip: !tunnelId,\n });\n\n // Clear the one-shot auto-prompt flag from history so a refresh / back-forward\n // navigation doesn't re-open the dialog.\n useEffect(() => {\n if ((location.state as { autoPromptStart?: boolean } | null)?.autoPromptStart) {\n navigate(location.pathname + location.search, { replace: true, state: {} });\n }\n // Only run once on mount — intentionally not reacting to later nav changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const [startTunnel] = useStartTunnelMutation();\n const [stopTunnel] = useStopTunnelMutation();\n\n const handleStart = async () => {\n const tunnel = data?.tunnel;\n if (!tunnel || isPending) return;\n setShowStartPrompt(false);\n setIsPending(true);\n try {\n const result = await startTunnel({\n variables: { id: tunnel.id },\n optimisticResponse: {\n startTunnel: { ...tunnel, status: 'PROVISIONING' as TunnelStatus, __typename: 'Tunnel' },\n },\n });\n if (result.error && isQuotaExhaustedError(result.error)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.tunnel_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\n } else if (!result.error) {\n bus.emitCustom('vibecontrols.tunnel.started', {\n workspaceId: workspaceId ?? undefined,\n tunnelId: tunnel.id,\n });\n }\n } catch (err) {\n // Only the quota dialog is reserved for quota errors; everything else is\n // surfaced via the tunnel's lastError banner after the refetch.\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.tunnel_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\n } else {\n console.error('[TunnelDetailsPage] startTunnel failed', err);\n }\n } finally {\n setIsPending(false);\n }\n };\n\n const handleStop = async () => {\n const tunnel = data?.tunnel;\n if (!tunnel || isPending) return;\n setIsPending(true);\n try {\n await stopTunnel({\n variables: { id: tunnel.id },\n optimisticResponse: {\n stopTunnel: { ...tunnel, status: 'STOPPED' as TunnelStatus, __typename: 'Tunnel' },\n },\n });\n } finally {\n setIsPending(false);\n }\n };\n\n // Poll while transitioning OR ACTIVE-but-no-domain (Cloudflare URL arrives async).\n const needsPoll =\n data?.tunnel?.status === 'PROVISIONING' ||\n data?.tunnel?.status === 'DELETING' ||\n (data?.tunnel?.status === 'ACTIVE' && !data.tunnel.primaryDomain?.hostname);\n useEffect(() => {\n if (needsPoll) {\n startPolling(4000);\n } else {\n stopPolling();\n }\n }, [needsPoll, startPolling, stopPolling]);\n\n const tabs: TabItem[] = useMemo(\n () => [\n { id: 'overview', label: tr('vibecontrols.tunnels.detail.overview', 'Overview') },\n { id: 'endpoints', label: tr('vibecontrols.tunnels.detail.endpoints', 'Endpoints') },\n { id: 'sessions', label: tr('vibecontrols.tunnels.detail.sessions', 'Sessions') },\n { id: 'domains', label: tr('vibecontrols.tunnels.detail.domains', 'Domains') },\n { id: 'usage', label: tr('vibecontrols.tunnels.detail.usage', 'Usage') },\n { id: 'cli', label: tr('vibecontrols.tunnels.detail.cli', 'CLI') },\n { id: 'audit', label: tr('vibecontrols.tunnels.detail.audit', 'Audit') },\n { id: 'settings', label: tr('vibecontrols.tunnels.detail.settings', 'Settings') },\n ],\n [tr]\n );\n\n const activeId = params.get('tab') ?? tabs[0].id;\n\n const handleTabChange = (id: string) => {\n const next = new URLSearchParams(params);\n if (id === tabs[0].id) next.delete('tab');\n else next.set('tab', id);\n setParams(next, { replace: true });\n };\n\n if (loading && !data) return <LoadingState message=\"Loading tunnel\" />;\n if (error) {\n return (\n <ErrorState title=\"Failed to load tunnel\" message={error.message} onRetry={() => refetch()} />\n );\n }\n if (!data?.tunnel) {\n return (\n <ErrorState\n title=\"Tunnel not found\"\n message=\"This tunnel may have been deleted.\"\n onRetry={() => navigate('/vibecontrols/tunnels')}\n />\n );\n }\n\n const tunnel = data.tunnel;\n const isTransitioning =\n isPending || tunnel.status === 'PROVISIONING' || tunnel.status === 'DELETING';\n const isActive = tunnel.status === 'ACTIVE';\n const canStop = isActive && !isTransitioning;\n const canStart = !isActive && !isTransitioning;\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"px-6 pt-6 pb-2 border-b border-border-subtle\">\n <button\n type=\"button\"\n onClick={() => navigate('/vibecontrols/tunnels')}\n className=\"flex items-center gap-1.5 mb-4 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft size={16} />\n Back to Tunnels\n </button>\n <PageHeader\n breadcrumb={[{ label: 'Tunnels', path: '/vibecontrols/tunnels' }, { label: tunnel.name }]}\n title={tunnel.name}\n description={\n tunnel.description ?? `${tunnel.protocol}:${tunnel.localPort} on ${tunnel.localHost}`\n }\n actions={\n <div className=\"flex items-center gap-2\">\n {isTransitioning && (\n <span className=\"inline-flex items-center rounded-md border border-border-default px-3 py-2 text-sm text-text-muted\">\n <Loader2 className=\"size-4 mr-1.5 animate-spin\" />\n {isActive\n ? tr('vibecontrols.tunnels.stopping', 'Stopping…')\n : tr('vibecontrols.tunnels.starting', 'Starting…')}\n </span>\n )}\n {canStart && (\n <button\n type=\"button\"\n onClick={() => void handleStart()}\n className=\"inline-flex items-center rounded-md bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-fg hover:bg-action-primary-bg-hover focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n >\n <Play className=\"size-4 mr-1.5\" />\n {tr('vibecontrols.tunnels.detail.startTunnel', 'Start tunnel')}\n </button>\n )}\n {canStop && (\n <button\n type=\"button\"\n onClick={() => void handleStop()}\n className=\"inline-flex items-center rounded-md border border-border-default px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-elevated focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n >\n <Square className=\"size-4 mr-1.5\" />\n {tr('vibecontrols.tunnels.detail.stopTunnel', 'Stop tunnel')}\n </button>\n )}\n <TunnelStatusBadge status={tunnel.status} />\n <ProviderBadge providerKey={tunnel.providerKey} />\n <EntityShareButton\n entity=\"tunnel\"\n resource={{ id: tunnel.id, name: tunnel.name }}\n label={tr('vibecontrols.tunnels.detail.share', 'Share')}\n variant=\"button\"\n />\n </div>\n }\n />\n {tunnel.status === 'ERROR' && tunnel.lastError && (\n <div className=\"mt-3 flex items-start gap-2 rounded-md bg-status-error-bg px-3 py-2 text-sm text-status-error-fg\">\n <AlertCircle className=\"mt-0.5 size-4 flex-shrink-0\" aria-hidden=\"true\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium\">\n {tr('vibecontrols.tunnels.detail.startFailed', 'The tunnel failed to start')}\n </p>\n <p className=\"mt-0.5 break-words text-status-error-fg/90\">{tunnel.lastError}</p>\n </div>\n {canStart && (\n <button\n type=\"button\"\n onClick={() => void handleStart()}\n className=\"flex-shrink-0 rounded-md border border-status-error-fg/30 px-2.5 py-1 text-xs font-medium hover:bg-status-error-bg/60\"\n >\n {tr('vibecontrols.tunnels.detail.retry', 'Retry')}\n </button>\n )}\n </div>\n )}\n <div className=\"mt-2\">\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n </div>\n <div className=\"flex items-start gap-2 border-b border-border-subtle bg-bg-surface px-6 py-2\">\n <Tag className=\"mt-1 size-4 flex-shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n <div className=\"min-w-0 flex-1\">\n <EntityTagPicker entityType=\"VIBECONTROLS_TUNNEL\" entityId={tunnel.id} />\n </div>\n </div>\n <div className=\"flex-1 min-h-0 overflow-auto p-6\">\n {activeId === 'overview' && <TunnelOverviewTab tunnel={tunnel} />}\n {activeId === 'endpoints' && <TunnelEndpointsTab tunnel={tunnel} />}\n {activeId === 'sessions' && <TunnelSessionsTab tunnelId={tunnel.id} />}\n {activeId === 'domains' && <TunnelDomainsTab tunnel={tunnel} />}\n {activeId === 'usage' && <TunnelUsageTab tunnelId={tunnel.id} />}\n {activeId === 'cli' && <TunnelCliTab tunnel={tunnel} />}\n {activeId === 'audit' && <TunnelAuditTab tunnelId={tunnel.id} />}\n {activeId === 'settings' && (\n <TunnelSettingsTab tunnel={tunnel} onDeleted={() => navigate('/vibecontrols/tunnels')} />\n )}\n </div>\n\n <ConfirmDialog\n isOpen={showStartPrompt && canStart}\n onClose={() => setShowStartPrompt(false)}\n onConfirm={() => void handleStart()}\n title={tr('vibecontrols.tunnels.detail.startPromptTitle', 'Start this tunnel now?')}\n message={tr(\n 'vibecontrols.tunnels.detail.startPromptMessage',\n 'Your tunnel was created as a draft. Start it to bring it online and get its public URL. You can stop it anytime from this page.'\n )}\n confirmLabel={tr('vibecontrols.tunnels.detail.startTunnel', 'Start tunnel')}\n cancelLabel={tr('vibecontrols.tunnels.detail.startPromptLater', 'Not now')}\n variant=\"default\"\n isLoading={isPending}\n />\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n {...getQuotaDialogContent('tunnel-start')}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|