@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.
Files changed (62) hide show
  1. package/dist/components/agents/AgentConnectionRecovery.js +100 -94
  2. package/dist/components/agents/AgentConnectionRecovery.js.map +1 -1
  3. package/dist/components/agents/AgentMaintenanceTab.js +177 -160
  4. package/dist/components/agents/AgentMaintenanceTab.js.map +1 -1
  5. package/dist/components/agents/OpenInBrowserEditorButton.js +102 -94
  6. package/dist/components/agents/OpenInBrowserEditorButton.js.map +1 -1
  7. package/dist/components/assistant/AssistantChatArea.js +1 -0
  8. package/dist/components/assistant/AssistantChatArea.js.map +1 -1
  9. package/dist/components/assistant/AssistantModeSelector.js +20 -19
  10. package/dist/components/assistant/AssistantModeSelector.js.map +1 -1
  11. package/dist/components/tunnels/TunnelCard.js +3 -3
  12. package/dist/components/tunnels/TunnelCard.js.map +1 -1
  13. package/dist/components/tunnels/TunnelPublicAccessPanel.js +36 -36
  14. package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
  15. package/dist/config/assistantUsecases.js +11 -0
  16. package/dist/config/assistantUsecases.js.map +1 -1
  17. package/dist/constants/permissions.js +38 -6
  18. package/dist/constants/permissions.js.map +1 -1
  19. package/dist/index.js +4 -3
  20. package/dist/pages/CatalogDetailPage.js +155 -155
  21. package/dist/pages/CatalogDetailPage.js.map +1 -1
  22. package/dist/pages/CatalogPage.js +110 -109
  23. package/dist/pages/CatalogPage.js.map +1 -1
  24. package/dist/pages/ComponentDetailPage.js +338 -338
  25. package/dist/pages/ComponentDetailPage.js.map +1 -1
  26. package/dist/pages/ComponentsPage.js +104 -103
  27. package/dist/pages/ComponentsPage.js.map +1 -1
  28. package/dist/pages/TargetDetailsPage.js +140 -132
  29. package/dist/pages/TargetDetailsPage.js.map +1 -1
  30. package/dist/pages/TemplateDetailPage.js +201 -199
  31. package/dist/pages/TemplateDetailPage.js.map +1 -1
  32. package/dist/pages/TemplatesPage.js +62 -61
  33. package/dist/pages/TemplatesPage.js.map +1 -1
  34. package/dist/pages/VibeDetailsPage.js +568 -555
  35. package/dist/pages/VibeDetailsPage.js.map +1 -1
  36. package/dist/pages/ai/AIBookmarksPage.js +104 -89
  37. package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
  38. package/dist/pages/ai/AIPage.js +116 -107
  39. package/dist/pages/ai/AIPage.js.map +1 -1
  40. package/dist/pages/ai/AISessionDetailPage.js +46 -34
  41. package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
  42. package/dist/pages/ai/AgentManagerPage.js +112 -100
  43. package/dist/pages/ai/AgentManagerPage.js.map +1 -1
  44. package/dist/pages/tunnels/TunnelDetailsPage.js +177 -153
  45. package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
  46. package/dist/pages/tunnels/TunnelsTab.js +119 -111
  47. package/dist/pages/tunnels/TunnelsTab.js.map +1 -1
  48. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +24 -24
  49. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
  50. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +9 -8
  51. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
  52. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +13 -13
  53. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
  54. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +19 -15
  55. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
  56. package/dist/services/assistantApi.js +9 -3
  57. package/dist/services/assistantApi.js.map +1 -1
  58. package/dist/services/createSandboxAssistantTransport.js +120 -0
  59. package/dist/services/createSandboxAssistantTransport.js.map +1 -0
  60. package/dist/store/assistantStore.js +1 -1
  61. package/dist/store/assistantStore.js.map +1 -1
  62. 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 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 p } from "../../components/shared/EntityTagPicker.js";
11
- import { EntityShareButton as ee } from "../../components/sharing/EntityShareButton.js";
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 te } from "../../components/tunnels/TunnelStatusBadge.js";
14
- import { ProviderBadge as ne } from "../../components/tunnels/ProviderBadge.js";
15
- import { TunnelOverviewTab as re } from "./tabs/TunnelOverviewTab.js";
16
- import { TunnelEndpointsTab as ie } from "./tabs/TunnelEndpointsTab.js";
17
- import { TunnelSessionsTab as ae } from "./tabs/TunnelSessionsTab.js";
18
- import { TunnelDomainsTab as oe } from "./tabs/TunnelDomainsTab.js";
19
- import { TunnelUsageTab as se } from "./tabs/TunnelUsageTab.js";
20
- import { TunnelCliTab as m } from "./tabs/TunnelCliTab.js";
21
- import { TunnelAuditTab as h } from "./tabs/TunnelAuditTab.js";
22
- import { TunnelSettingsTab as g } from "./tabs/TunnelSettingsTab.js";
23
- import { useEffect as _, useMemo as v, useState as y } from "react";
24
- import { AlertCircle as b, ArrowLeft as ce, Loader2 as le, Play as ue, Square as de, Tag as fe } from "lucide-react";
25
- import { useLocation as pe, useNavigate as x, useParams as S, useSearchParams as C } from "react-router-dom";
26
- import { jsx as w, jsxs as T } from "react/jsx-runtime";
27
- import { useEventBus as E } from "@burdenoff/fe-libs/shared/events";
28
- import { Tabs as D } from "@burdenoff/fe-libs/chrome";
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 O() {
31
- let O = r(), { tunnelId: k = "" } = S(), A = x(), j = pe(), M = E(), { workspaceId: N } = l(), [P, F] = C(), [I, L] = y(!1), [R, z] = y(!1), [me, B] = y(() => j.state?.autoPromptStart === !0), { data: V, loading: he, error: H, refetch: U, startPolling: W, stopPolling: G } = e({
32
- variables: { id: k },
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: !k
37
+ skip: !A || w || !T
35
38
  });
36
- _(() => {
37
- j.state?.autoPromptStart && A(j.pathname + j.search, {
39
+ d(() => {
40
+ M.state?.autoPromptStart && j(M.pathname + M.search, {
38
41
  replace: !0,
39
42
  state: {}
40
43
  });
41
44
  }, []);
42
- let [ge] = t(), [_e] = n(), K = async () => {
43
- let e = V?.tunnel;
44
- if (!(!e || I)) {
45
- B(!1), L(!0);
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 ge({
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 && 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,
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
- s(e) ? (c(), z(!0), M.emitCustom("vibecontrols.quota.tunnel_limit_hit", { workspaceId: N ?? void 0 })) : console.error("[TunnelDetailsPage] startTunnel failed", e);
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
- }, ve = async () => {
66
- let e = V?.tunnel;
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 _e({
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
- }, q = V?.tunnel?.status === "PROVISIONING" || V?.tunnel?.status === "DELETING" || V?.tunnel?.status === "ACTIVE" && !V.tunnel.primaryDomain?.hostname;
83
- _(() => {
84
- q ? W(4e3) : G();
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
- q,
87
- W,
88
- G
89
+ K,
90
+ U,
91
+ W
89
92
  ]);
90
- let J = v(() => [
91
- {
92
- id: "overview",
93
- label: O("vibecontrols.tunnels.detail.overview", "Overview")
94
- },
95
- {
96
- id: "endpoints",
97
- label: O("vibecontrols.tunnels.detail.endpoints", "Endpoints")
98
- },
99
- {
100
- id: "sessions",
101
- label: O("vibecontrols.tunnels.detail.sessions", "Sessions")
102
- },
103
- {
104
- id: "domains",
105
- label: O("vibecontrols.tunnels.detail.domains", "Domains")
106
- },
107
- {
108
- id: "usage",
109
- label: O("vibecontrols.tunnels.detail.usage", "Usage")
110
- },
111
- {
112
- id: "cli",
113
- label: O("vibecontrols.tunnels.detail.cli", "CLI")
114
- },
115
- {
116
- id: "audit",
117
- label: O("vibecontrols.tunnels.detail.audit", "Audit")
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: O("vibecontrols.tunnels.detail.settings", "Settings")
122
- }
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 });
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 (he && !V) return /* @__PURE__ */ w(d, { message: "Loading tunnel" });
128
- if (H) return /* @__PURE__ */ w(f, {
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: H.message,
131
- onRetry: () => U()
144
+ message: V.message,
145
+ onRetry: () => H()
132
146
  });
133
- if (!V?.tunnel) return /* @__PURE__ */ w(f, {
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: () => A("/vibecontrols/tunnels")
150
+ onRetry: () => j("/vibecontrols/tunnels")
137
151
  });
138
- let X = V.tunnel, Z = I || X.status === "PROVISIONING" || X.status === "DELETING", Q = X.status === "ACTIVE", be = Q && !Z, $ = !Q && !Z;
139
- return /* @__PURE__ */ T("div", {
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__ */ T("div", {
156
+ /* @__PURE__ */ b("div", {
143
157
  className: "px-6 pt-6 pb-2 border-b border-border-subtle",
144
158
  children: [
145
- /* @__PURE__ */ T("button", {
159
+ /* @__PURE__ */ b("button", {
146
160
  type: "button",
147
- onClick: () => A("/vibecontrols/tunnels"),
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__ */ w(ce, { size: 16 }), "Back to Tunnels"]
163
+ children: [/* @__PURE__ */ y(p, { size: 16 }), "Back to Tunnels"]
150
164
  }),
151
- /* @__PURE__ */ w(u, {
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__ */ T("div", {
172
+ actions: /* @__PURE__ */ b("div", {
159
173
  className: "flex items-center gap-2",
160
174
  children: [
161
- Z && /* @__PURE__ */ T("span", {
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__ */ w(le, { className: "size-4 mr-1.5 animate-spin" }), Q ? O("vibecontrols.tunnels.stopping", "Stopping…") : O("vibecontrols.tunnels.starting", "Starting…")]
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__ */ T("button", {
179
+ $ && k && /* @__PURE__ */ b("button", {
166
180
  type: "button",
167
- onClick: () => void K(),
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__ */ w(ue, { className: "size-4 mr-1.5" }), O("vibecontrols.tunnels.detail.startTunnel", "Start tunnel")]
183
+ children: [/* @__PURE__ */ y(h, { className: "size-4 mr-1.5" }), S("vibecontrols.tunnels.detail.startTunnel", "Start tunnel")]
170
184
  }),
171
- be && /* @__PURE__ */ T("button", {
185
+ je && k && /* @__PURE__ */ b("button", {
172
186
  type: "button",
173
- onClick: () => void ve(),
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__ */ w(de, { className: "size-4 mr-1.5" }), O("vibecontrols.tunnels.detail.stopTunnel", "Stop tunnel")]
189
+ children: [/* @__PURE__ */ y(g, { className: "size-4 mr-1.5" }), S("vibecontrols.tunnels.detail.stopTunnel", "Stop tunnel")]
176
190
  }),
177
- /* @__PURE__ */ w(te, { status: X.status }),
178
- /* @__PURE__ */ w(ne, { providerKey: X.providerKey }),
179
- /* @__PURE__ */ w(ee, {
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: O("vibecontrols.tunnels.detail.share", "Share"),
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__ */ T("div", {
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__ */ w(b, {
208
+ /* @__PURE__ */ y(ge, {
195
209
  className: "mt-0.5 size-4 flex-shrink-0",
196
210
  "aria-hidden": "true"
197
211
  }),
198
- /* @__PURE__ */ T("div", {
212
+ /* @__PURE__ */ b("div", {
199
213
  className: "min-w-0 flex-1",
200
- children: [/* @__PURE__ */ w("p", {
214
+ children: [/* @__PURE__ */ y("p", {
201
215
  className: "font-medium",
202
- children: O("vibecontrols.tunnels.detail.startFailed", "The tunnel failed to start")
203
- }), /* @__PURE__ */ w("p", {
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__ */ w("button", {
222
+ $ && k && /* @__PURE__ */ y("button", {
209
223
  type: "button",
210
- onClick: () => void K(),
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: O("vibecontrols.tunnels.detail.retry", "Retry")
226
+ children: S("vibecontrols.tunnels.detail.retry", "Retry")
213
227
  })
214
228
  ]
215
229
  }),
216
- /* @__PURE__ */ w("div", {
230
+ /* @__PURE__ */ y("div", {
217
231
  className: "mt-2",
218
- children: /* @__PURE__ */ w(D, {
219
- items: J,
232
+ children: /* @__PURE__ */ y(x, {
233
+ items: q,
220
234
  activeTabId: Y,
221
- onTabChange: ye,
235
+ onTabChange: Ae,
222
236
  variant: "underline"
223
237
  })
224
238
  })
225
239
  ]
226
240
  }),
227
- /* @__PURE__ */ T("div", {
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__ */ w(fe, {
243
+ children: [/* @__PURE__ */ y(_, {
230
244
  className: "mt-1 size-4 flex-shrink-0 text-text-secondary",
231
245
  "aria-hidden": "true"
232
- }), /* @__PURE__ */ w("div", {
246
+ }), /* @__PURE__ */ y("div", {
233
247
  className: "min-w-0 flex-1",
234
- children: /* @__PURE__ */ w(p, {
248
+ children: /* @__PURE__ */ y(re, {
235
249
  entityType: "VIBECONTROLS_TUNNEL",
236
250
  entityId: X.id
237
251
  })
238
252
  })]
239
253
  }),
240
- /* @__PURE__ */ T("div", {
254
+ /* @__PURE__ */ b("div", {
241
255
  className: "flex-1 min-h-0 overflow-auto p-6",
242
256
  children: [
243
- Y === "overview" && /* @__PURE__ */ w(re, {
257
+ Y === "overview" && /* @__PURE__ */ y(se, {
244
258
  tunnel: X,
245
- onChanged: () => void U()
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 === "endpoints" && /* @__PURE__ */ w(ie, { tunnel: X }),
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
- onDeleted: () => A("/vibecontrols/tunnels")
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__ */ w(i, {
260
- isOpen: me && $,
261
- onClose: () => B(!1),
262
- onConfirm: () => void K(),
263
- title: O("vibecontrols.tunnels.detail.startPromptTitle", "Start this tunnel now?"),
264
- 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."),
265
- confirmLabel: O("vibecontrols.tunnels.detail.startTunnel", "Start tunnel"),
266
- cancelLabel: O("vibecontrols.tunnels.detail.startPromptLater", "Not now"),
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__ */ w(a, {
271
- isOpen: R,
272
- onClose: () => z(!1),
294
+ /* @__PURE__ */ y(ne, {
295
+ isOpen: we,
296
+ onClose: () => R(!1),
273
297
  onConfirm: () => {
274
- z(!1), A("/billing/plans");
298
+ R(!1), j("/billing/plans");
275
299
  },
276
- ...o("tunnel-start")
300
+ ...r("tunnel-start")
277
301
  })
278
302
  ]
279
303
  });
280
304
  }
281
305
  //#endregion
282
- export { O as TunnelDetailsPage };
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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"}