@burdenoff/microfe-vibecontrols 2026.613.1 → 2026.618.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/agents/AgentConnectionRecovery.js +100 -94
- package/dist/components/agents/AgentConnectionRecovery.js.map +1 -1
- package/dist/components/agents/AgentMaintenanceTab.js +177 -160
- package/dist/components/agents/AgentMaintenanceTab.js.map +1 -1
- package/dist/components/agents/OpenInBrowserEditorButton.js +102 -94
- package/dist/components/agents/OpenInBrowserEditorButton.js.map +1 -1
- package/dist/components/assistant/AssistantChatArea.js +1 -0
- package/dist/components/assistant/AssistantChatArea.js.map +1 -1
- package/dist/components/assistant/AssistantModeSelector.js +20 -19
- package/dist/components/assistant/AssistantModeSelector.js.map +1 -1
- package/dist/components/tunnels/TunnelCard.js +3 -3
- package/dist/components/tunnels/TunnelCard.js.map +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js +36 -36
- package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
- package/dist/config/assistantUsecases.js +11 -0
- package/dist/config/assistantUsecases.js.map +1 -1
- package/dist/constants/permissions.js +38 -6
- package/dist/constants/permissions.js.map +1 -1
- package/dist/index.js +4 -3
- package/dist/pages/CatalogDetailPage.js +155 -155
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +110 -109
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +338 -338
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +104 -103
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +140 -132
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +201 -199
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/TemplatesPage.js +62 -61
- package/dist/pages/TemplatesPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +568 -555
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/ai/AIBookmarksPage.js +104 -89
- package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
- package/dist/pages/ai/AIPage.js +116 -107
- package/dist/pages/ai/AIPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +46 -34
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/pages/ai/AgentManagerPage.js +112 -100
- package/dist/pages/ai/AgentManagerPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +177 -153
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelsTab.js +119 -111
- package/dist/pages/tunnels/TunnelsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +24 -24
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +9 -8
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +13 -13
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +19 -15
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
- package/dist/services/assistantApi.js +9 -3
- package/dist/services/assistantApi.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js +120 -0
- package/dist/services/createSandboxAssistantTransport.js.map +1 -0
- package/dist/store/assistantStore.js +1 -1
- package/dist/store/assistantStore.js.map +1 -1
- package/package.json +5 -1
|
@@ -1,50 +1,53 @@
|
|
|
1
1
|
import { useGetTunnelQuery as e, useStartTunnelMutation as t, useStopTunnelMutation as n } from "../../generated/wspace-operations.js";
|
|
2
|
-
import { useTr as
|
|
3
|
-
import { ConfirmDialog as
|
|
4
|
-
import { QuotaExhaustedDialog as
|
|
5
|
-
import { getQuotaDialogContent as
|
|
6
|
-
import { useVibeControls as
|
|
7
|
-
import { PageHeader as
|
|
8
|
-
import { LoadingState as
|
|
9
|
-
import { ErrorState as
|
|
10
|
-
import { EntityTagPicker as
|
|
11
|
-
import {
|
|
2
|
+
import { useTr as ee } from "../../shared/hooks/useTr.js";
|
|
3
|
+
import { ConfirmDialog as te } from "../../components/shared/ConfirmDialog.js";
|
|
4
|
+
import { QuotaExhaustedDialog as ne } from "../../components/shared/QuotaExhaustedDialog.js";
|
|
5
|
+
import { getQuotaDialogContent as r, isQuotaExhaustedError as i, markQuotaHandledLocally as a } from "../../utils/quotaUtils.js";
|
|
6
|
+
import { useVibeControls as o } from "../../providers/VibeControlsProvider.js";
|
|
7
|
+
import { PageHeader as s } from "../../components/shared/PageHeader.js";
|
|
8
|
+
import { LoadingState as c } from "../../components/shared/LoadingState.js";
|
|
9
|
+
import { ErrorState as l } from "../../components/shared/ErrorState.js";
|
|
10
|
+
import { EntityTagPicker as re } from "../../components/shared/EntityTagPicker.js";
|
|
11
|
+
import { VIBE_PERMISSIONS as u } from "../../constants/permissions.js";
|
|
12
|
+
import { EntityShareButton as ie } from "../../components/sharing/EntityShareButton.js";
|
|
12
13
|
import "../../components/sharing/index.js";
|
|
13
|
-
import { TunnelStatusBadge as
|
|
14
|
-
import { ProviderBadge as
|
|
15
|
-
import { TunnelOverviewTab as
|
|
16
|
-
import { TunnelEndpointsTab as
|
|
17
|
-
import { TunnelSessionsTab as
|
|
18
|
-
import { TunnelDomainsTab as
|
|
19
|
-
import { TunnelUsageTab as
|
|
20
|
-
import { TunnelCliTab as
|
|
21
|
-
import { TunnelAuditTab as
|
|
22
|
-
import { TunnelSettingsTab as
|
|
23
|
-
import { useEffect as
|
|
24
|
-
import { AlertCircle as
|
|
25
|
-
import { useLocation as
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
14
|
+
import { TunnelStatusBadge as ae } from "../../components/tunnels/TunnelStatusBadge.js";
|
|
15
|
+
import { ProviderBadge as oe } from "../../components/tunnels/ProviderBadge.js";
|
|
16
|
+
import { TunnelOverviewTab as se } from "./tabs/TunnelOverviewTab.js";
|
|
17
|
+
import { TunnelEndpointsTab as ce } from "./tabs/TunnelEndpointsTab.js";
|
|
18
|
+
import { TunnelSessionsTab as le } from "./tabs/TunnelSessionsTab.js";
|
|
19
|
+
import { TunnelDomainsTab as ue } from "./tabs/TunnelDomainsTab.js";
|
|
20
|
+
import { TunnelUsageTab as de } from "./tabs/TunnelUsageTab.js";
|
|
21
|
+
import { TunnelCliTab as fe } from "./tabs/TunnelCliTab.js";
|
|
22
|
+
import { TunnelAuditTab as pe } from "./tabs/TunnelAuditTab.js";
|
|
23
|
+
import { TunnelSettingsTab as me } from "./tabs/TunnelSettingsTab.js";
|
|
24
|
+
import { useEffect as d, useMemo as he, useState as f } from "react";
|
|
25
|
+
import { AlertCircle as ge, ArrowLeft as p, Loader2 as m, Play as h, Square as g, Tag as _ } from "lucide-react";
|
|
26
|
+
import { useLocation as v, useNavigate as _e, useParams as ve, useSearchParams as ye } from "react-router-dom";
|
|
27
|
+
import { usePermissions as be } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
28
|
+
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
29
|
+
import { useEventBus as xe } from "@burdenoff/fe-libs/shared/events";
|
|
30
|
+
import { AccessDenied as Se } from "@burdenoff/fe-libs/shared/components";
|
|
31
|
+
import { Tabs as x } from "@burdenoff/fe-libs/chrome";
|
|
29
32
|
//#region src/pages/tunnels/TunnelDetailsPage.tsx
|
|
30
|
-
function
|
|
31
|
-
let O =
|
|
32
|
-
variables: { id:
|
|
33
|
+
function S() {
|
|
34
|
+
let S = ee(), { hasPermission: C, isLoading: w } = be(), T = C(u.TUNNEL_READ), E = C(u.TUNNEL_CREATE), D = C(u.TUNNEL_UPDATE), O = C(u.TUNNEL_DELETE), k = C(u.TUNNEL_EXECUTE), { tunnelId: A = "" } = ve(), j = _e(), M = v(), N = xe(), { workspaceId: P } = o(), [F, Ce] = ye(), [I, L] = f(!1), [we, R] = f(!1), [Te, z] = f(() => M.state?.autoPromptStart === !0), { data: B, loading: Ee, error: V, refetch: H, startPolling: U, stopPolling: W } = e({
|
|
35
|
+
variables: { id: A },
|
|
33
36
|
fetchPolicy: "cache-and-network",
|
|
34
|
-
skip: !
|
|
37
|
+
skip: !A || w || !T
|
|
35
38
|
});
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
d(() => {
|
|
40
|
+
M.state?.autoPromptStart && j(M.pathname + M.search, {
|
|
38
41
|
replace: !0,
|
|
39
42
|
state: {}
|
|
40
43
|
});
|
|
41
44
|
}, []);
|
|
42
|
-
let [
|
|
43
|
-
let e =
|
|
44
|
-
if (!(!e || I)) {
|
|
45
|
-
|
|
45
|
+
let [De] = t(), [Oe] = n(), G = async () => {
|
|
46
|
+
let e = B?.tunnel;
|
|
47
|
+
if (!(!e || I || !k)) {
|
|
48
|
+
z(!1), L(!0);
|
|
46
49
|
try {
|
|
47
|
-
let t = await
|
|
50
|
+
let t = await De({
|
|
48
51
|
variables: { id: e.id },
|
|
49
52
|
optimisticResponse: { startTunnel: {
|
|
50
53
|
...e,
|
|
@@ -52,22 +55,22 @@ function O() {
|
|
|
52
55
|
__typename: "Tunnel"
|
|
53
56
|
} }
|
|
54
57
|
});
|
|
55
|
-
t.error &&
|
|
56
|
-
workspaceId:
|
|
58
|
+
t.error && i(t.error) ? (a(), R(!0), N.emitCustom("vibecontrols.quota.tunnel_limit_hit", { workspaceId: P ?? void 0 })) : t.error || N.emitCustom("vibecontrols.tunnel.started", {
|
|
59
|
+
workspaceId: P ?? void 0,
|
|
57
60
|
tunnelId: e.id
|
|
58
61
|
});
|
|
59
62
|
} catch (e) {
|
|
60
|
-
|
|
63
|
+
i(e) ? (a(), R(!0), N.emitCustom("vibecontrols.quota.tunnel_limit_hit", { workspaceId: P ?? void 0 })) : console.error("[TunnelDetailsPage] startTunnel failed", e);
|
|
61
64
|
} finally {
|
|
62
65
|
L(!1);
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
|
-
},
|
|
66
|
-
let e =
|
|
67
|
-
if (!(!e || I)) {
|
|
68
|
+
}, ke = async () => {
|
|
69
|
+
let e = B?.tunnel;
|
|
70
|
+
if (!(!e || I || !k)) {
|
|
68
71
|
L(!0);
|
|
69
72
|
try {
|
|
70
|
-
await
|
|
73
|
+
await Oe({
|
|
71
74
|
variables: { id: e.id },
|
|
72
75
|
optimisticResponse: { stopTunnel: {
|
|
73
76
|
...e,
|
|
@@ -79,206 +82,227 @@ function O() {
|
|
|
79
82
|
L(!1);
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
}, K = B?.tunnel?.status === "PROVISIONING" || B?.tunnel?.status === "DELETING" || B?.tunnel?.status === "ACTIVE" && !B.tunnel.primaryDomain?.hostname;
|
|
86
|
+
d(() => {
|
|
87
|
+
K ? U(4e3) : W();
|
|
85
88
|
}, [
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
K,
|
|
90
|
+
U,
|
|
91
|
+
W
|
|
89
92
|
]);
|
|
90
|
-
let
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
93
|
+
let q = he(() => {
|
|
94
|
+
let e = [
|
|
95
|
+
{
|
|
96
|
+
id: "overview",
|
|
97
|
+
label: S("vibecontrols.tunnels.detail.overview", "Overview")
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "endpoints",
|
|
101
|
+
label: S("vibecontrols.tunnels.detail.endpoints", "Endpoints")
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "sessions",
|
|
105
|
+
label: S("vibecontrols.tunnels.detail.sessions", "Sessions")
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "domains",
|
|
109
|
+
label: S("vibecontrols.tunnels.detail.domains", "Domains")
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "usage",
|
|
113
|
+
label: S("vibecontrols.tunnels.detail.usage", "Usage")
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "cli",
|
|
117
|
+
label: S("vibecontrols.tunnels.detail.cli", "CLI")
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "audit",
|
|
121
|
+
label: S("vibecontrols.tunnels.detail.audit", "Audit")
|
|
122
|
+
}
|
|
123
|
+
];
|
|
124
|
+
return (D || O) && e.push({
|
|
120
125
|
id: "settings",
|
|
121
|
-
label:
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
label: S("vibecontrols.tunnels.detail.settings", "Settings")
|
|
127
|
+
}), e;
|
|
128
|
+
}, [
|
|
129
|
+
O,
|
|
130
|
+
D,
|
|
131
|
+
S
|
|
132
|
+
]), J = F.get("tab"), Y = J && q.some((e) => e.id === J) ? J : q[0].id, Ae = (e) => {
|
|
133
|
+
let t = new URLSearchParams(F);
|
|
134
|
+
e === q[0].id ? t.delete("tab") : t.set("tab", e), Ce(t, { replace: !0 });
|
|
126
135
|
};
|
|
127
|
-
if (
|
|
128
|
-
if (
|
|
136
|
+
if (w) return /* @__PURE__ */ y(c, { message: "Loading permissions" });
|
|
137
|
+
if (!T) return /* @__PURE__ */ y(Se, {
|
|
138
|
+
title: S("vibecontrols.tunnels.detail.denied.title", "Access Denied"),
|
|
139
|
+
description: S("vibecontrols.tunnels.detail.denied.description", "You do not have permission to view this tunnel.")
|
|
140
|
+
});
|
|
141
|
+
if (Ee && !B) return /* @__PURE__ */ y(c, { message: "Loading tunnel" });
|
|
142
|
+
if (V) return /* @__PURE__ */ y(l, {
|
|
129
143
|
title: "Failed to load tunnel",
|
|
130
|
-
message:
|
|
131
|
-
onRetry: () =>
|
|
144
|
+
message: V.message,
|
|
145
|
+
onRetry: () => H()
|
|
132
146
|
});
|
|
133
|
-
if (!
|
|
147
|
+
if (!B?.tunnel) return /* @__PURE__ */ y(l, {
|
|
134
148
|
title: "Tunnel not found",
|
|
135
149
|
message: "This tunnel may have been deleted.",
|
|
136
|
-
onRetry: () =>
|
|
150
|
+
onRetry: () => j("/vibecontrols/tunnels")
|
|
137
151
|
});
|
|
138
|
-
let X =
|
|
139
|
-
return /* @__PURE__ */
|
|
152
|
+
let X = B.tunnel, Z = I || X.status === "PROVISIONING" || X.status === "DELETING", Q = X.status === "ACTIVE", je = Q && !Z, $ = !Q && !Z;
|
|
153
|
+
return /* @__PURE__ */ b("div", {
|
|
140
154
|
className: "flex flex-col h-full",
|
|
141
155
|
children: [
|
|
142
|
-
/* @__PURE__ */
|
|
156
|
+
/* @__PURE__ */ b("div", {
|
|
143
157
|
className: "px-6 pt-6 pb-2 border-b border-border-subtle",
|
|
144
158
|
children: [
|
|
145
|
-
/* @__PURE__ */
|
|
159
|
+
/* @__PURE__ */ b("button", {
|
|
146
160
|
type: "button",
|
|
147
|
-
onClick: () =>
|
|
161
|
+
onClick: () => j("/vibecontrols/tunnels"),
|
|
148
162
|
className: "flex items-center gap-1.5 mb-4 text-sm text-text-secondary hover:text-text-primary transition-colors",
|
|
149
|
-
children: [/* @__PURE__ */
|
|
163
|
+
children: [/* @__PURE__ */ y(p, { size: 16 }), "Back to Tunnels"]
|
|
150
164
|
}),
|
|
151
|
-
/* @__PURE__ */
|
|
165
|
+
/* @__PURE__ */ y(s, {
|
|
152
166
|
breadcrumb: [{
|
|
153
167
|
label: "Tunnels",
|
|
154
168
|
path: "/vibecontrols/tunnels"
|
|
155
169
|
}, { label: X.name }],
|
|
156
170
|
title: X.name,
|
|
157
171
|
description: X.description ?? `${X.protocol}:${X.localPort} on ${X.localHost}`,
|
|
158
|
-
actions: /* @__PURE__ */
|
|
172
|
+
actions: /* @__PURE__ */ b("div", {
|
|
159
173
|
className: "flex items-center gap-2",
|
|
160
174
|
children: [
|
|
161
|
-
Z && /* @__PURE__ */
|
|
175
|
+
Z && /* @__PURE__ */ b("span", {
|
|
162
176
|
className: "inline-flex items-center rounded-md border border-border-default px-3 py-2 text-sm text-text-muted",
|
|
163
|
-
children: [/* @__PURE__ */
|
|
177
|
+
children: [/* @__PURE__ */ y(m, { className: "size-4 mr-1.5 animate-spin" }), Q ? S("vibecontrols.tunnels.stopping", "Stopping…") : S("vibecontrols.tunnels.starting", "Starting…")]
|
|
164
178
|
}),
|
|
165
|
-
$ && /* @__PURE__ */
|
|
179
|
+
$ && k && /* @__PURE__ */ b("button", {
|
|
166
180
|
type: "button",
|
|
167
|
-
onClick: () => void
|
|
181
|
+
onClick: () => void G(),
|
|
168
182
|
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__ */
|
|
183
|
+
children: [/* @__PURE__ */ y(h, { className: "size-4 mr-1.5" }), S("vibecontrols.tunnels.detail.startTunnel", "Start tunnel")]
|
|
170
184
|
}),
|
|
171
|
-
|
|
185
|
+
je && k && /* @__PURE__ */ b("button", {
|
|
172
186
|
type: "button",
|
|
173
|
-
onClick: () => void
|
|
187
|
+
onClick: () => void ke(),
|
|
174
188
|
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__ */
|
|
189
|
+
children: [/* @__PURE__ */ y(g, { className: "size-4 mr-1.5" }), S("vibecontrols.tunnels.detail.stopTunnel", "Stop tunnel")]
|
|
176
190
|
}),
|
|
177
|
-
/* @__PURE__ */
|
|
178
|
-
/* @__PURE__ */
|
|
179
|
-
/* @__PURE__ */
|
|
191
|
+
/* @__PURE__ */ y(ae, { status: X.status }),
|
|
192
|
+
/* @__PURE__ */ y(oe, { providerKey: X.providerKey }),
|
|
193
|
+
/* @__PURE__ */ y(ie, {
|
|
180
194
|
entity: "tunnel",
|
|
181
195
|
resource: {
|
|
182
196
|
id: X.id,
|
|
183
197
|
name: X.name
|
|
184
198
|
},
|
|
185
|
-
label:
|
|
199
|
+
label: S("vibecontrols.tunnels.detail.share", "Share"),
|
|
186
200
|
variant: "button"
|
|
187
201
|
})
|
|
188
202
|
]
|
|
189
203
|
})
|
|
190
204
|
}),
|
|
191
|
-
X.status === "ERROR" && X.lastError && /* @__PURE__ */
|
|
205
|
+
X.status === "ERROR" && X.lastError && /* @__PURE__ */ b("div", {
|
|
192
206
|
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
207
|
children: [
|
|
194
|
-
/* @__PURE__ */
|
|
208
|
+
/* @__PURE__ */ y(ge, {
|
|
195
209
|
className: "mt-0.5 size-4 flex-shrink-0",
|
|
196
210
|
"aria-hidden": "true"
|
|
197
211
|
}),
|
|
198
|
-
/* @__PURE__ */
|
|
212
|
+
/* @__PURE__ */ b("div", {
|
|
199
213
|
className: "min-w-0 flex-1",
|
|
200
|
-
children: [/* @__PURE__ */
|
|
214
|
+
children: [/* @__PURE__ */ y("p", {
|
|
201
215
|
className: "font-medium",
|
|
202
|
-
children:
|
|
203
|
-
}), /* @__PURE__ */
|
|
216
|
+
children: S("vibecontrols.tunnels.detail.startFailed", "The tunnel failed to start")
|
|
217
|
+
}), /* @__PURE__ */ y("p", {
|
|
204
218
|
className: "mt-0.5 break-words text-status-error-fg/90",
|
|
205
219
|
children: X.lastError
|
|
206
220
|
})]
|
|
207
221
|
}),
|
|
208
|
-
$ && /* @__PURE__ */
|
|
222
|
+
$ && k && /* @__PURE__ */ y("button", {
|
|
209
223
|
type: "button",
|
|
210
|
-
onClick: () => void
|
|
224
|
+
onClick: () => void G(),
|
|
211
225
|
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:
|
|
226
|
+
children: S("vibecontrols.tunnels.detail.retry", "Retry")
|
|
213
227
|
})
|
|
214
228
|
]
|
|
215
229
|
}),
|
|
216
|
-
/* @__PURE__ */
|
|
230
|
+
/* @__PURE__ */ y("div", {
|
|
217
231
|
className: "mt-2",
|
|
218
|
-
children: /* @__PURE__ */
|
|
219
|
-
items:
|
|
232
|
+
children: /* @__PURE__ */ y(x, {
|
|
233
|
+
items: q,
|
|
220
234
|
activeTabId: Y,
|
|
221
|
-
onTabChange:
|
|
235
|
+
onTabChange: Ae,
|
|
222
236
|
variant: "underline"
|
|
223
237
|
})
|
|
224
238
|
})
|
|
225
239
|
]
|
|
226
240
|
}),
|
|
227
|
-
/* @__PURE__ */
|
|
241
|
+
/* @__PURE__ */ b("div", {
|
|
228
242
|
className: "flex items-start gap-2 border-b border-border-subtle bg-bg-surface px-6 py-2",
|
|
229
|
-
children: [/* @__PURE__ */
|
|
243
|
+
children: [/* @__PURE__ */ y(_, {
|
|
230
244
|
className: "mt-1 size-4 flex-shrink-0 text-text-secondary",
|
|
231
245
|
"aria-hidden": "true"
|
|
232
|
-
}), /* @__PURE__ */
|
|
246
|
+
}), /* @__PURE__ */ y("div", {
|
|
233
247
|
className: "min-w-0 flex-1",
|
|
234
|
-
children: /* @__PURE__ */
|
|
248
|
+
children: /* @__PURE__ */ y(re, {
|
|
235
249
|
entityType: "VIBECONTROLS_TUNNEL",
|
|
236
250
|
entityId: X.id
|
|
237
251
|
})
|
|
238
252
|
})]
|
|
239
253
|
}),
|
|
240
|
-
/* @__PURE__ */
|
|
254
|
+
/* @__PURE__ */ b("div", {
|
|
241
255
|
className: "flex-1 min-h-0 overflow-auto p-6",
|
|
242
256
|
children: [
|
|
243
|
-
Y === "overview" && /* @__PURE__ */
|
|
257
|
+
Y === "overview" && /* @__PURE__ */ y(se, {
|
|
244
258
|
tunnel: X,
|
|
245
|
-
|
|
259
|
+
canUpdate: D,
|
|
260
|
+
onChanged: () => void H()
|
|
261
|
+
}),
|
|
262
|
+
Y === "endpoints" && /* @__PURE__ */ y(ce, { tunnel: X }),
|
|
263
|
+
Y === "sessions" && /* @__PURE__ */ y(le, {
|
|
264
|
+
tunnelId: X.id,
|
|
265
|
+
canExecute: k
|
|
246
266
|
}),
|
|
247
|
-
Y === "
|
|
248
|
-
Y === "sessions" && /* @__PURE__ */ w(ae, { tunnelId: X.id }),
|
|
249
|
-
Y === "domains" && /* @__PURE__ */ w(oe, { tunnel: X }),
|
|
250
|
-
Y === "usage" && /* @__PURE__ */ w(se, { tunnelId: X.id }),
|
|
251
|
-
Y === "cli" && /* @__PURE__ */ w(m, { tunnel: X }),
|
|
252
|
-
Y === "audit" && /* @__PURE__ */ w(h, { tunnelId: X.id }),
|
|
253
|
-
Y === "settings" && /* @__PURE__ */ w(g, {
|
|
267
|
+
Y === "domains" && /* @__PURE__ */ y(ue, {
|
|
254
268
|
tunnel: X,
|
|
255
|
-
|
|
269
|
+
canCreate: E,
|
|
270
|
+
canUpdate: D
|
|
271
|
+
}),
|
|
272
|
+
Y === "usage" && /* @__PURE__ */ y(de, { tunnelId: X.id }),
|
|
273
|
+
Y === "cli" && /* @__PURE__ */ y(fe, { tunnel: X }),
|
|
274
|
+
Y === "audit" && /* @__PURE__ */ y(pe, { tunnelId: X.id }),
|
|
275
|
+
Y === "settings" && /* @__PURE__ */ y(me, {
|
|
276
|
+
tunnel: X,
|
|
277
|
+
canUpdate: D,
|
|
278
|
+
canDelete: O,
|
|
279
|
+
onDeleted: () => j("/vibecontrols/tunnels")
|
|
256
280
|
})
|
|
257
281
|
]
|
|
258
282
|
}),
|
|
259
|
-
/* @__PURE__ */
|
|
260
|
-
isOpen:
|
|
261
|
-
onClose: () =>
|
|
262
|
-
onConfirm: () => void
|
|
263
|
-
title:
|
|
264
|
-
message:
|
|
265
|
-
confirmLabel:
|
|
266
|
-
cancelLabel:
|
|
283
|
+
/* @__PURE__ */ y(te, {
|
|
284
|
+
isOpen: Te && $ && k,
|
|
285
|
+
onClose: () => z(!1),
|
|
286
|
+
onConfirm: () => void G(),
|
|
287
|
+
title: S("vibecontrols.tunnels.detail.startPromptTitle", "Start this tunnel now?"),
|
|
288
|
+
message: S("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."),
|
|
289
|
+
confirmLabel: S("vibecontrols.tunnels.detail.startTunnel", "Start tunnel"),
|
|
290
|
+
cancelLabel: S("vibecontrols.tunnels.detail.startPromptLater", "Not now"),
|
|
267
291
|
variant: "default",
|
|
268
292
|
isLoading: I
|
|
269
293
|
}),
|
|
270
|
-
/* @__PURE__ */
|
|
271
|
-
isOpen:
|
|
272
|
-
onClose: () =>
|
|
294
|
+
/* @__PURE__ */ y(ne, {
|
|
295
|
+
isOpen: we,
|
|
296
|
+
onClose: () => R(!1),
|
|
273
297
|
onConfirm: () => {
|
|
274
|
-
|
|
298
|
+
R(!1), j("/billing/plans");
|
|
275
299
|
},
|
|
276
|
-
...
|
|
300
|
+
...r("tunnel-start")
|
|
277
301
|
})
|
|
278
302
|
]
|
|
279
303
|
});
|
|
280
304
|
}
|
|
281
305
|
//#endregion
|
|
282
|
-
export {
|
|
306
|
+
export { S as TunnelDetailsPage };
|
|
283
307
|
|
|
284
308
|
//# sourceMappingURL=TunnelDetailsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
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' && (\n <TunnelOverviewTab tunnel={tunnel} onChanged={() => void refetch()} />\n )}\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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"}
|
|
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 { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\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 // Tunnel reads are gated on `tunnel:read`; start/stop are `tunnel:execute`\n // (both seeded in wspace-rbac-svc). Skip the query when the user can't read\n // so we show a clean denied state instead of a guaranteed 403.\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(VIBE_PERMISSIONS.TUNNEL_READ);\n const canCreate = hasPermission(VIBE_PERMISSIONS.TUNNEL_CREATE);\n const canUpdate = hasPermission(VIBE_PERMISSIONS.TUNNEL_UPDATE);\n const canDelete = hasPermission(VIBE_PERMISSIONS.TUNNEL_DELETE);\n const canExecute = hasPermission(VIBE_PERMISSIONS.TUNNEL_EXECUTE);\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 || permissionsLoading || !canRead,\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 || !canExecute) 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 || !canExecute) 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 const baseTabs: TabItem[] = [\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 ];\n\n if (canUpdate || canDelete) {\n baseTabs.push({\n id: 'settings',\n label: tr('vibecontrols.tunnels.detail.settings', 'Settings'),\n });\n }\n\n return baseTabs;\n }, [canDelete, canUpdate, tr]);\n\n const requestedTab = params.get('tab');\n const activeId =\n requestedTab && tabs.some((tab) => tab.id === requestedTab) ? requestedTab : 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 (permissionsLoading) return <LoadingState message=\"Loading permissions\" />;\n\n if (!canRead) {\n return (\n <AccessDenied\n title={tr('vibecontrols.tunnels.detail.denied.title', 'Access Denied')}\n description={tr(\n 'vibecontrols.tunnels.detail.denied.description',\n 'You do not have permission to view this tunnel.'\n )}\n />\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 && canExecute && (\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 && canExecute && (\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 && canExecute && (\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' && (\n <TunnelOverviewTab\n tunnel={tunnel}\n canUpdate={canUpdate}\n onChanged={() => void refetch()}\n />\n )}\n {activeId === 'endpoints' && <TunnelEndpointsTab tunnel={tunnel} />}\n {activeId === 'sessions' && (\n <TunnelSessionsTab tunnelId={tunnel.id} canExecute={canExecute} />\n )}\n {activeId === 'domains' && (\n <TunnelDomainsTab tunnel={tunnel} canCreate={canCreate} canUpdate={canUpdate} />\n )}\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\n tunnel={tunnel}\n canUpdate={canUpdate}\n canDelete={canDelete}\n onDeleted={() => navigate('/vibecontrols/tunnels')}\n />\n )}\n </div>\n\n <ConfirmDialog\n isOpen={showStartPrompt && canStart && canExecute}\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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"}
|