@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,67 +1,72 @@
1
- import { useDeleteTunnelMutation as e, useListAgentsQuery as t, useListTunnelsQuery as n, useStartTunnelMutation as r, useStopTunnelMutation as i } from "../../generated/wspace-operations.js";
2
- import { useTr as a } from "../../shared/hooks/useTr.js";
3
- import { ConfirmDialog as o } from "../../components/shared/ConfirmDialog.js";
4
- import { QuotaExhaustedDialog as s } from "../../components/shared/QuotaExhaustedDialog.js";
5
- import { getQuotaDialogContent as c, isQuotaExhaustedError as l, markQuotaHandledLocally as u } from "../../utils/quotaUtils.js";
6
- import { useVibeControls as ee } from "../../providers/VibeControlsProvider.js";
7
- import { PageHeader as te } from "../../components/shared/PageHeader.js";
8
- import { LoadingState as ne } from "../../components/shared/LoadingState.js";
9
- import { ErrorState as re } from "../../components/shared/ErrorState.js";
10
- import { EmptyState as d } from "../../components/shared/EmptyState.js";
11
- import { SearchInput as f } from "../../components/shared/SearchInput.js";
12
- import { FilterDropdown as p } from "../../components/shared/FilterDropdown.js";
1
+ import { useDeleteTunnelMutation as e, useListAgentsQuery as t, useListTunnelsQuery as n, useStartTunnelMutation as r, useStopTunnelMutation as ee } from "../../generated/wspace-operations.js";
2
+ import { useTr as te } from "../../shared/hooks/useTr.js";
3
+ import { ConfirmDialog as ne } from "../../components/shared/ConfirmDialog.js";
4
+ import { QuotaExhaustedDialog as i } from "../../components/shared/QuotaExhaustedDialog.js";
5
+ import { getQuotaDialogContent as re, isQuotaExhaustedError as a, markQuotaHandledLocally as o } from "../../utils/quotaUtils.js";
6
+ import { useVibeControls as ie } 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 { EmptyState as u } from "../../components/shared/EmptyState.js";
11
+ import { SearchInput as d } from "../../components/shared/SearchInput.js";
12
+ import { FilterDropdown as f } from "../../components/shared/FilterDropdown.js";
13
+ import { VIBE_PERMISSIONS as p } from "../../constants/permissions.js";
13
14
  import { TunnelCard as m } from "../../components/tunnels/TunnelCard.js";
14
- import { useEffect as h, useMemo as g, useState as _ } from "react";
15
- import { Link as v, useNavigate as y } from "react-router-dom";
16
- import { motion as b } from "framer-motion";
17
- import { jsx as x, jsxs as S } from "react/jsx-runtime";
18
- import { Button as C } from "@burdenoff/fe-libs/ui";
19
- import { useEventBus as w } from "@burdenoff/fe-libs/shared/events";
15
+ import { useEffect as ae, useMemo as oe, useState as h } from "react";
16
+ import { Link as g, useNavigate as se } from "react-router-dom";
17
+ import { usePermissions as ce } from "@burdenoff/fe-libs/shared/providers/shell";
18
+ import { motion as le } from "framer-motion";
19
+ import { jsx as _, jsxs as v } from "react/jsx-runtime";
20
+ import { Button as y } from "@burdenoff/fe-libs/ui";
21
+ import { useEventBus as b } from "@burdenoff/fe-libs/shared/events";
22
+ import { AccessDenied as x } from "@burdenoff/fe-libs/shared/components";
20
23
  //#region src/pages/tunnels/TunnelsTab.tsx
21
- var ie = 4e3;
22
- function T() {
23
- let T = a(), E = y(), D = w(), { workspaceId: O } = ee(), [k, A] = _(""), [j, M] = _("all"), [N, P] = _("all"), [F, I] = _(null), [L, R] = _(!1), [z, B] = _(/* @__PURE__ */ new Set()), { data: V, loading: H, error: U, refetch: W, startPolling: G, stopPolling: K } = n({
24
+ var S = 4e3;
25
+ function C() {
26
+ let C = te(), { hasPermission: w, isLoading: T } = ce(), E = w(p.TUNNEL_READ), D = w(p.TUNNEL_CREATE), O = w(p.TUNNEL_EXECUTE), ue = w(p.TUNNEL_DELETE), de = w(p.AGENT_READ), k = se(), A = b(), { workspaceId: j } = ie(), [M, N] = h(""), [P, F] = h("all"), [I, L] = h("all"), [R, z] = h(null), [B, V] = h(!1), [H, U] = h(/* @__PURE__ */ new Set()), { data: W, loading: fe, error: G, refetch: K, startPolling: q, stopPolling: J } = n({
24
27
  variables: {
25
- filter: g(() => {
28
+ filter: oe(() => {
26
29
  let e = {};
27
- return j !== "all" && (e.providerKey = j), N !== "all" && (e.status = [N]), k.trim() && (e.search = k.trim()), e;
30
+ return P !== "all" && (e.providerKey = P), I !== "all" && (e.status = [I]), M.trim() && (e.search = M.trim()), e;
28
31
  }, [
29
- j,
30
- N,
31
- k
32
+ P,
33
+ I,
34
+ M
32
35
  ]),
33
36
  pagination: { limit: 50 }
34
37
  },
35
- fetchPolicy: "cache-and-network"
36
- }), { data: q } = t({
38
+ fetchPolicy: "cache-and-network",
39
+ skip: T || !E
40
+ }), { data: pe } = t({
37
41
  variables: { pagination: { limit: 1 } },
42
+ skip: T || !de,
38
43
  fetchPolicy: "cache-first"
39
- }), J = (q?.vibecontrolsAgents?.length ?? 0) > 0, Y = V?.tunnels?.items ?? [], ae = (e) => {
40
- if (D.emitCustom("vibecontrols.tunnel.create_clicked", { workspaceId: O ?? void 0 }), !J) {
44
+ }), Y = (pe?.vibecontrolsAgents?.length ?? 0) > 0, X = W?.tunnels?.items ?? [], me = (e) => {
45
+ if (A.emitCustom("vibecontrols.tunnel.create_clicked", { workspaceId: j ?? void 0 }), !Y) {
41
46
  e.preventDefault();
42
47
  let t = {
43
- message: T("vibecontrols.tunnels.list.createAgentFirst", "Create an agent first to use tunnels"),
48
+ message: C("vibecontrols.tunnels.list.createAgentFirst", "Create an agent first to use tunnels"),
44
49
  variant: "info"
45
50
  };
46
- window.dispatchEvent(new CustomEvent("vibecontrols:toast", { detail: t })), E("/vibecontrols/agents");
51
+ window.dispatchEvent(new CustomEvent("vibecontrols:toast", { detail: t })), k("/vibecontrols/agents");
47
52
  }
48
- }, X = z.size > 0 || Y.some((e) => e.status === "PROVISIONING" || e.status === "DELETING" || e.status === "ACTIVE" && !e.primaryDomain?.hostname);
49
- h(() => {
50
- X ? G(ie) : K();
53
+ }, Z = H.size > 0 || X.some((e) => e.status === "PROVISIONING" || e.status === "DELETING" || e.status === "ACTIVE" && !e.primaryDomain?.hostname);
54
+ ae(() => {
55
+ Z ? q(S) : J();
51
56
  }, [
52
- X,
53
- G,
54
- K
57
+ Z,
58
+ q,
59
+ J
55
60
  ]);
56
- let Z = (e) => B((t) => new Set([...t, e])), Q = (e) => B((t) => {
61
+ let Q = (e) => U((t) => new Set([...t, e])), $ = (e) => U((t) => {
57
62
  let n = new Set(t);
58
63
  return n.delete(e), n;
59
- }), [oe] = r(), se = async (e) => {
60
- let t = Y.find((t) => t.id === e);
64
+ }), [he] = r(), ge = async (e) => {
65
+ let t = X.find((t) => t.id === e);
61
66
  if (t) {
62
- Z(e);
67
+ Q(e);
63
68
  try {
64
- let n = await oe({
69
+ let n = await he({
65
70
  variables: { id: e },
66
71
  optimisticResponse: { startTunnel: {
67
72
  ...t,
@@ -69,22 +74,22 @@ function T() {
69
74
  __typename: "Tunnel"
70
75
  } }
71
76
  });
72
- n.error && l(n.error) ? (u(), R(!0), D.emitCustom("vibecontrols.quota.tunnel_limit_hit", { workspaceId: O ?? void 0 })) : n.error || D.emitCustom("vibecontrols.tunnel.started", {
73
- workspaceId: O ?? void 0,
77
+ n.error && a(n.error) ? (o(), V(!0), A.emitCustom("vibecontrols.quota.tunnel_limit_hit", { workspaceId: j ?? void 0 })) : n.error || A.emitCustom("vibecontrols.tunnel.started", {
78
+ workspaceId: j ?? void 0,
74
79
  tunnelId: e
75
80
  });
76
81
  } catch (e) {
77
- l(e) ? (u(), R(!0), D.emitCustom("vibecontrols.quota.tunnel_limit_hit", { workspaceId: O ?? void 0 })) : console.error("[TunnelsTab] startTunnel failed", e);
82
+ a(e) ? (o(), V(!0), A.emitCustom("vibecontrols.quota.tunnel_limit_hit", { workspaceId: j ?? void 0 })) : console.error("[TunnelsTab] startTunnel failed", e);
78
83
  } finally {
79
- Q(e);
84
+ $(e);
80
85
  }
81
86
  }
82
- }, [ce] = i(), le = async (e) => {
83
- let t = Y.find((t) => t.id === e);
87
+ }, [_e] = ee(), ve = async (e) => {
88
+ let t = X.find((t) => t.id === e);
84
89
  if (t) {
85
- Z(e);
90
+ Q(e);
86
91
  try {
87
- await ce({
92
+ await _e({
88
93
  variables: { id: e },
89
94
  optimisticResponse: { stopTunnel: {
90
95
  ...t,
@@ -93,43 +98,43 @@ function T() {
93
98
  } }
94
99
  });
95
100
  } finally {
96
- Q(e);
101
+ $(e);
97
102
  }
98
103
  }
99
- }, [$, { loading: ue }] = e({ onCompleted: () => {
100
- I(null), W();
104
+ }, [ye, { loading: be }] = e({ onCompleted: () => {
105
+ z(null), K();
101
106
  } });
102
- return /* @__PURE__ */ S("div", {
107
+ return T ? /* @__PURE__ */ _(c, { message: C("common.loadingPermissions", "Loading permissions…") }) : E ? /* @__PURE__ */ v("div", {
103
108
  className: "p-6 max-w-7xl mx-auto",
104
109
  children: [
105
- /* @__PURE__ */ x(te, {
106
- title: T("vibecontrols.tunnels.list.title", "Tunnels"),
107
- description: T("vibecontrols.tunnels.list.description", "Expose local services on *.vibetunnels.com and custom domains."),
108
- actions: /* @__PURE__ */ x(C, {
110
+ /* @__PURE__ */ _(s, {
111
+ title: C("vibecontrols.tunnels.list.title", "Tunnels"),
112
+ description: C("vibecontrols.tunnels.list.description", "Expose local services on *.vibetunnels.com and custom domains."),
113
+ actions: D ? /* @__PURE__ */ _(y, {
109
114
  variant: "default",
110
115
  asChild: !0,
111
- children: /* @__PURE__ */ x(v, {
116
+ children: /* @__PURE__ */ _(g, {
112
117
  to: "/vibecontrols/tunnels/new",
113
- onClick: ae,
114
- children: T("vibecontrols.tunnels.list.newTunnel", "New tunnel")
118
+ onClick: me,
119
+ children: C("vibecontrols.tunnels.list.newTunnel", "New tunnel")
115
120
  })
116
- })
121
+ }) : void 0
117
122
  }),
118
- /* @__PURE__ */ S("div", {
123
+ /* @__PURE__ */ v("div", {
119
124
  className: "flex flex-col sm:flex-row gap-3 mb-4",
120
125
  children: [
121
- /* @__PURE__ */ x("div", {
126
+ /* @__PURE__ */ _("div", {
122
127
  className: "flex-1",
123
- children: /* @__PURE__ */ x(f, {
124
- value: k,
125
- onChange: A,
128
+ children: /* @__PURE__ */ _(d, {
129
+ value: M,
130
+ onChange: N,
126
131
  placeholder: "Search tunnels by name or slug"
127
132
  })
128
133
  }),
129
- /* @__PURE__ */ x(p, {
134
+ /* @__PURE__ */ _(f, {
130
135
  label: "Provider",
131
- value: j,
132
- onChange: M,
136
+ value: P,
137
+ onChange: F,
133
138
  options: [
134
139
  {
135
140
  value: "all",
@@ -145,10 +150,10 @@ function T() {
145
150
  }
146
151
  ]
147
152
  }),
148
- /* @__PURE__ */ x(p, {
153
+ /* @__PURE__ */ _(f, {
149
154
  label: "Status",
150
- value: N,
151
- onChange: P,
155
+ value: I,
156
+ onChange: L,
152
157
  options: [
153
158
  {
154
159
  value: "all",
@@ -178,70 +183,73 @@ function T() {
178
183
  })
179
184
  ]
180
185
  }),
181
- H && !V && /* @__PURE__ */ x(ne, { message: "Loading tunnels" }),
182
- U && /* @__PURE__ */ x(re, {
186
+ fe && !W && /* @__PURE__ */ _(c, { message: "Loading tunnels" }),
187
+ G && /* @__PURE__ */ _(l, {
183
188
  title: "Failed to load tunnels",
184
- message: U.message,
185
- onRetry: () => W()
189
+ message: G.message,
190
+ onRetry: () => K()
186
191
  }),
187
- V && Y.length === 0 && !J && /* @__PURE__ */ x(d, {
188
- title: T("vibecontrols.tunnels.list.noAgentTitle", "Create an agent first"),
189
- description: T("vibecontrols.tunnels.list.noAgentDescription", "Tunnels need a VibeControls agent to forward traffic. Install + register an agent, then come back to create your first tunnel."),
190
- action: /* @__PURE__ */ x(C, {
192
+ W && X.length === 0 && !Y && /* @__PURE__ */ _(u, {
193
+ title: C("vibecontrols.tunnels.list.noAgentTitle", "Create an agent first"),
194
+ description: C("vibecontrols.tunnels.list.noAgentDescription", "Tunnels need a VibeControls agent to forward traffic. Install + register an agent, then come back to create your first tunnel."),
195
+ action: /* @__PURE__ */ _(y, {
191
196
  variant: "default",
192
- onClick: () => E("/vibecontrols/agents"),
193
- children: T("vibecontrols.tunnels.list.setupAgent", "Set up an agent")
197
+ onClick: () => k("/vibecontrols/agents"),
198
+ children: C("vibecontrols.tunnels.list.setupAgent", "Set up an agent")
194
199
  })
195
200
  }),
196
- V && Y.length === 0 && J && /* @__PURE__ */ x(d, {
197
- title: T("vibecontrols.tunnels.list.emptyTitle", "No tunnels yet"),
198
- description: T("vibecontrols.tunnels.list.emptyDescription", "Create your first tunnel to expose a local service."),
199
- action: /* @__PURE__ */ x(C, {
201
+ W && X.length === 0 && Y && /* @__PURE__ */ _(u, {
202
+ title: C("vibecontrols.tunnels.list.emptyTitle", "No tunnels yet"),
203
+ description: C("vibecontrols.tunnels.list.emptyDescription", "Create your first tunnel to expose a local service."),
204
+ action: D ? /* @__PURE__ */ _(y, {
200
205
  variant: "default",
201
- onClick: () => E("/vibecontrols/tunnels/new"),
202
- children: T("vibecontrols.tunnels.list.createTunnel", "Create tunnel")
203
- })
206
+ onClick: () => k("/vibecontrols/tunnels/new"),
207
+ children: C("vibecontrols.tunnels.list.createTunnel", "Create tunnel")
208
+ }) : void 0
204
209
  }),
205
- Y.length > 0 && /* @__PURE__ */ x(b.div, {
210
+ X.length > 0 && /* @__PURE__ */ _(le.div, {
206
211
  initial: { opacity: 0 },
207
212
  animate: { opacity: 1 },
208
213
  className: "space-y-3",
209
- children: Y.map((e) => /* @__PURE__ */ x(m, {
214
+ children: X.map((e) => /* @__PURE__ */ _(m, {
210
215
  tunnel: e,
211
- isPending: z.has(e.id),
212
- onOpen: (e) => E(`/vibecontrols/tunnels/${e}`),
213
- onStart: (e) => void se(e),
214
- onStop: (e) => void le(e),
215
- onDelete: (e, t) => I({
216
+ isPending: H.has(e.id),
217
+ onOpen: (e) => k(`/vibecontrols/tunnels/${e}`),
218
+ onStart: O ? (e) => void ge(e) : void 0,
219
+ onStop: O ? (e) => void ve(e) : void 0,
220
+ onDelete: ue ? (e, t) => z({
216
221
  id: e,
217
222
  name: t
218
- })
223
+ }) : void 0
219
224
  }, e.id))
220
225
  }),
221
- /* @__PURE__ */ x(o, {
222
- isOpen: F !== null,
223
- onClose: () => I(null),
226
+ /* @__PURE__ */ _(ne, {
227
+ isOpen: R !== null,
228
+ onClose: () => z(null),
224
229
  onConfirm: () => {
225
- F && $({ variables: { id: F.id } });
230
+ R && ye({ variables: { id: R.id } });
226
231
  },
227
232
  title: "Delete tunnel",
228
- message: F ? `Delete "${F.name}"? This will stop the tunnel and remove all its sessions and domains.` : "",
233
+ message: R ? `Delete "${R.name}"? This will stop the tunnel and remove all its sessions and domains.` : "",
229
234
  confirmLabel: "Delete",
230
235
  variant: "danger",
231
- isLoading: ue
236
+ isLoading: be
232
237
  }),
233
- /* @__PURE__ */ x(s, {
234
- isOpen: L,
235
- onClose: () => R(!1),
238
+ /* @__PURE__ */ _(i, {
239
+ isOpen: B,
240
+ onClose: () => V(!1),
236
241
  onConfirm: () => {
237
- R(!1), E("/billing/plans");
242
+ V(!1), k("/billing/plans");
238
243
  },
239
- ...c("tunnel-start")
244
+ ...re("tunnel-start")
240
245
  })
241
246
  ]
247
+ }) : /* @__PURE__ */ _(x, {
248
+ title: C("vibecontrols.tunnels.list.denied.title", "Access Denied"),
249
+ description: C("vibecontrols.tunnels.list.denied.description", "You do not have permission to view tunnels.")
242
250
  });
243
251
  }
244
252
  //#endregion
245
- export { T as TunnelsTab };
253
+ export { C as TunnelsTab };
246
254
 
247
255
  //# sourceMappingURL=TunnelsTab.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TunnelsTab.js","names":[],"sources":["../../../src/pages/tunnels/TunnelsTab.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Link, useNavigate } from 'react-router-dom';\nimport { motion } from 'framer-motion';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\n\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { PageHeader } from '@/components/shared/PageHeader';\nimport { SearchInput } from '@/components/shared/SearchInput';\nimport { FilterDropdown } from '@/components/shared/FilterDropdown';\nimport { ConfirmDialog } from '@/components/shared/ConfirmDialog';\nimport { QuotaExhaustedDialog } from '@/components/shared/QuotaExhaustedDialog';\nimport { EmptyState } from '@/components/shared/EmptyState';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { ErrorState } from '@/components/shared/ErrorState';\nimport { TunnelCard } from '@/components/tunnels/TunnelCard';\nimport type { TunnelStatus, TunnelProviderKey } from '@/generated/wspace-types';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useListTunnelsQuery,\n useListAgentsQuery,\n useStartTunnelMutation,\n useStopTunnelMutation,\n useDeleteTunnelMutation,\n type ListTunnelsQuery,\n} from '@/generated/wspace-operations';\nimport {\n isQuotaExhaustedError,\n markQuotaHandledLocally,\n getQuotaDialogContent,\n} from '@/utils/quotaUtils';\n\ntype TunnelRow = NonNullable<ListTunnelsQuery['tunnels']>['items'][number];\n\nconst TRANSITION_POLL_MS = 4000;\n\nexport function TunnelsTab() {\n const tr = useTr();\n const navigate = useNavigate();\n const bus = useEventBus();\n const { workspaceId } = useVibeControls();\n const [search, setSearch] = useState('');\n const [providerFilter, setProviderFilter] = useState<string>('all');\n const [statusFilter, setStatusFilter] = useState<string>('all');\n const [pendingDelete, setPendingDelete] = useState<{ id: string; name: string } | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());\n\n const filter = useMemo(() => {\n const f: Record<string, unknown> = {};\n if (providerFilter !== 'all') {\n f.providerKey = providerFilter as TunnelProviderKey;\n }\n if (statusFilter !== 'all') {\n f.status = [statusFilter as TunnelStatus];\n }\n if (search.trim()) {\n f.search = search.trim();\n }\n return f;\n }, [providerFilter, statusFilter, search]);\n\n const { data, loading, error, refetch, startPolling, stopPolling } = useListTunnelsQuery({\n variables: { filter, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n });\n\n const { data: agentsData } = useListAgentsQuery({\n variables: { pagination: { limit: 1 } },\n fetchPolicy: 'cache-first',\n });\n const hasAnyAgent = (agentsData?.vibecontrolsAgents?.length ?? 0) > 0;\n\n const tunnels: TunnelRow[] = data?.tunnels?.items ?? [];\n\n const handleNewTunnelClick = (event: React.MouseEvent) => {\n bus.emitCustom('vibecontrols.tunnel.create_clicked', { workspaceId: workspaceId ?? undefined });\n if (!hasAnyAgent) {\n event.preventDefault();\n // Lightweight toast via window event — falls back to console if no host listener.\n const detail = {\n message: tr(\n 'vibecontrols.tunnels.list.createAgentFirst',\n 'Create an agent first to use tunnels'\n ),\n variant: 'info' as const,\n };\n window.dispatchEvent(new CustomEvent('vibecontrols:toast', { detail }));\n navigate('/vibecontrols/agents');\n }\n };\n\n // Poll while any tunnel is in a transient state OR is ACTIVE but has no\n // domain yet (Cloudflare assigns the URL ~5-15 s after start returns).\n const needsPoll =\n pendingIds.size > 0 ||\n tunnels.some(\n (t) =>\n t.status === 'PROVISIONING' ||\n t.status === 'DELETING' ||\n (t.status === 'ACTIVE' && !t.primaryDomain?.hostname)\n );\n useEffect(() => {\n if (needsPoll) {\n startPolling(TRANSITION_POLL_MS);\n } else {\n stopPolling();\n }\n }, [needsPoll, startPolling, stopPolling]);\n\n const addPending = (id: string) => setPendingIds((prev) => new Set([...prev, id]));\n const removePending = (id: string) =>\n setPendingIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n\n const [startTunnel] = useStartTunnelMutation();\n\n const handleStartTunnel = async (id: string) => {\n const existing = tunnels.find((t) => t.id === id);\n if (!existing) return;\n addPending(id);\n try {\n const result = await startTunnel({\n variables: { id },\n optimisticResponse: {\n startTunnel: {\n ...existing,\n status: 'PROVISIONING' as TunnelStatus,\n __typename: 'Tunnel',\n },\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: id,\n });\n }\n } catch (err) {\n // Only open the quota modal for actual quota errors — pre-existing\n // bug spotted on the BOFF-2625 review: any tunnel-start failure\n // (network / validation / auth) was opening the quota dialog.\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('[TunnelsTab] startTunnel failed', err);\n }\n } finally {\n removePending(id);\n }\n };\n\n const [stopTunnel] = useStopTunnelMutation();\n\n const handleStopTunnel = async (id: string) => {\n const existing = tunnels.find((t) => t.id === id);\n if (!existing) return;\n addPending(id);\n try {\n await stopTunnel({\n variables: { id },\n optimisticResponse: {\n stopTunnel: { ...existing, status: 'STOPPED' as TunnelStatus, __typename: 'Tunnel' },\n },\n });\n } finally {\n removePending(id);\n }\n };\n\n const [deleteTunnel, { loading: deleting }] = useDeleteTunnelMutation({\n onCompleted: () => {\n setPendingDelete(null);\n void refetch();\n },\n });\n\n return (\n <div className=\"p-6 max-w-7xl mx-auto\">\n <PageHeader\n title={tr('vibecontrols.tunnels.list.title', 'Tunnels')}\n description={tr(\n 'vibecontrols.tunnels.list.description',\n 'Expose local services on *.vibetunnels.com and custom domains.'\n )}\n actions={\n <Button variant=\"default\" asChild>\n <Link to=\"/vibecontrols/tunnels/new\" onClick={handleNewTunnelClick}>\n {tr('vibecontrols.tunnels.list.newTunnel', 'New tunnel')}\n </Link>\n </Button>\n }\n />\n\n <div className=\"flex flex-col sm:flex-row gap-3 mb-4\">\n <div className=\"flex-1\">\n <SearchInput\n value={search}\n onChange={setSearch}\n placeholder=\"Search tunnels by name or slug\"\n />\n </div>\n <FilterDropdown\n label=\"Provider\"\n value={providerFilter}\n onChange={setProviderFilter}\n options={[\n { value: 'all', label: 'All providers' },\n { value: 'VIBETUNNELS', label: 'Vibe Tunnels (default)' },\n { value: 'CLOUDFLARE', label: 'Cloudflare Tunnels' },\n ]}\n />\n <FilterDropdown\n label=\"Status\"\n value={statusFilter}\n onChange={setStatusFilter}\n options={[\n { value: 'all', label: 'All statuses' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PROVISIONING', label: 'Provisioning' },\n { value: 'STOPPED', label: 'Stopped' },\n { value: 'ERROR', label: 'Error' },\n ]}\n />\n </div>\n\n {loading && !data && <LoadingState message=\"Loading tunnels\" />}\n {error && (\n <ErrorState\n title=\"Failed to load tunnels\"\n message={error.message}\n onRetry={() => refetch()}\n />\n )}\n\n {data && tunnels.length === 0 && !hasAnyAgent && (\n <EmptyState\n title={tr('vibecontrols.tunnels.list.noAgentTitle', 'Create an agent first')}\n description={tr(\n 'vibecontrols.tunnels.list.noAgentDescription',\n 'Tunnels need a VibeControls agent to forward traffic. Install + register an agent, then come back to create your first tunnel.'\n )}\n action={\n <Button variant=\"default\" onClick={() => navigate('/vibecontrols/agents')}>\n {tr('vibecontrols.tunnels.list.setupAgent', 'Set up an agent')}\n </Button>\n }\n />\n )}\n\n {data && tunnels.length === 0 && hasAnyAgent && (\n <EmptyState\n title={tr('vibecontrols.tunnels.list.emptyTitle', 'No tunnels yet')}\n description={tr(\n 'vibecontrols.tunnels.list.emptyDescription',\n 'Create your first tunnel to expose a local service.'\n )}\n action={\n <Button variant=\"default\" onClick={() => navigate('/vibecontrols/tunnels/new')}>\n {tr('vibecontrols.tunnels.list.createTunnel', 'Create tunnel')}\n </Button>\n }\n />\n )}\n\n {tunnels.length > 0 && (\n <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className=\"space-y-3\">\n {tunnels.map((tunnel: TunnelRow) => (\n <TunnelCard\n key={tunnel.id}\n tunnel={tunnel}\n isPending={pendingIds.has(tunnel.id)}\n onOpen={(id) => navigate(`/vibecontrols/tunnels/${id}`)}\n onStart={(id) => void handleStartTunnel(id)}\n onStop={(id) => void handleStopTunnel(id)}\n onDelete={(id, name) => setPendingDelete({ id, name })}\n />\n ))}\n </motion.div>\n )}\n\n <ConfirmDialog\n isOpen={pendingDelete !== null}\n onClose={() => setPendingDelete(null)}\n onConfirm={() => {\n if (pendingDelete) {\n void deleteTunnel({ variables: { id: pendingDelete.id } });\n }\n }}\n title=\"Delete tunnel\"\n message={\n pendingDelete\n ? `Delete \"${pendingDelete.name}\"? This will stop the tunnel and remove all its sessions and domains.`\n : ''\n }\n confirmLabel=\"Delete\"\n variant=\"danger\"\n isLoading={deleting}\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":"TunnelsTab.js","names":[],"sources":["../../../src/pages/tunnels/TunnelsTab.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Link, useNavigate } from 'react-router-dom';\nimport { motion } from 'framer-motion';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\n\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { PageHeader } from '@/components/shared/PageHeader';\nimport { SearchInput } from '@/components/shared/SearchInput';\nimport { FilterDropdown } from '@/components/shared/FilterDropdown';\nimport { ConfirmDialog } from '@/components/shared/ConfirmDialog';\nimport { QuotaExhaustedDialog } from '@/components/shared/QuotaExhaustedDialog';\nimport { EmptyState } from '@/components/shared/EmptyState';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { ErrorState } from '@/components/shared/ErrorState';\nimport { TunnelCard } from '@/components/tunnels/TunnelCard';\nimport type { TunnelStatus, TunnelProviderKey } from '@/generated/wspace-types';\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';\nimport {\n useListTunnelsQuery,\n useListAgentsQuery,\n useStartTunnelMutation,\n useStopTunnelMutation,\n useDeleteTunnelMutation,\n type ListTunnelsQuery,\n} from '@/generated/wspace-operations';\nimport {\n isQuotaExhaustedError,\n markQuotaHandledLocally,\n getQuotaDialogContent,\n} from '@/utils/quotaUtils';\n\ntype TunnelRow = NonNullable<ListTunnelsQuery['tunnels']>['items'][number];\n\nconst TRANSITION_POLL_MS = 4000;\n\nexport function TunnelsTab() {\n const tr = useTr();\n // Tunnel reads are gated on `tunnel:read`; creation on `tunnel:create`;\n // start/stop on `tunnel:execute`; deletion on `tunnel:delete`.\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(VIBE_PERMISSIONS.TUNNEL_READ);\n const canCreate = hasPermission(VIBE_PERMISSIONS.TUNNEL_CREATE);\n const canExecute = hasPermission(VIBE_PERMISSIONS.TUNNEL_EXECUTE);\n const canDelete = hasPermission(VIBE_PERMISSIONS.TUNNEL_DELETE);\n const canReadAgents = hasPermission(VIBE_PERMISSIONS.AGENT_READ);\n const navigate = useNavigate();\n const bus = useEventBus();\n const { workspaceId } = useVibeControls();\n const [search, setSearch] = useState('');\n const [providerFilter, setProviderFilter] = useState<string>('all');\n const [statusFilter, setStatusFilter] = useState<string>('all');\n const [pendingDelete, setPendingDelete] = useState<{ id: string; name: string } | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());\n\n const filter = useMemo(() => {\n const f: Record<string, unknown> = {};\n if (providerFilter !== 'all') {\n f.providerKey = providerFilter as TunnelProviderKey;\n }\n if (statusFilter !== 'all') {\n f.status = [statusFilter as TunnelStatus];\n }\n if (search.trim()) {\n f.search = search.trim();\n }\n return f;\n }, [providerFilter, statusFilter, search]);\n\n const { data, loading, error, refetch, startPolling, stopPolling } = useListTunnelsQuery({\n variables: { filter, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n skip: permissionsLoading || !canRead,\n });\n\n const { data: agentsData } = useListAgentsQuery({\n variables: { pagination: { limit: 1 } },\n skip: permissionsLoading || !canReadAgents,\n fetchPolicy: 'cache-first',\n });\n const hasAnyAgent = (agentsData?.vibecontrolsAgents?.length ?? 0) > 0;\n\n const tunnels: TunnelRow[] = data?.tunnels?.items ?? [];\n\n const handleNewTunnelClick = (event: React.MouseEvent) => {\n bus.emitCustom('vibecontrols.tunnel.create_clicked', { workspaceId: workspaceId ?? undefined });\n if (!hasAnyAgent) {\n event.preventDefault();\n // Lightweight toast via window event — falls back to console if no host listener.\n const detail = {\n message: tr(\n 'vibecontrols.tunnels.list.createAgentFirst',\n 'Create an agent first to use tunnels'\n ),\n variant: 'info' as const,\n };\n window.dispatchEvent(new CustomEvent('vibecontrols:toast', { detail }));\n navigate('/vibecontrols/agents');\n }\n };\n\n // Poll while any tunnel is in a transient state OR is ACTIVE but has no\n // domain yet (Cloudflare assigns the URL ~5-15 s after start returns).\n const needsPoll =\n pendingIds.size > 0 ||\n tunnels.some(\n (t) =>\n t.status === 'PROVISIONING' ||\n t.status === 'DELETING' ||\n (t.status === 'ACTIVE' && !t.primaryDomain?.hostname)\n );\n useEffect(() => {\n if (needsPoll) {\n startPolling(TRANSITION_POLL_MS);\n } else {\n stopPolling();\n }\n }, [needsPoll, startPolling, stopPolling]);\n\n const addPending = (id: string) => setPendingIds((prev) => new Set([...prev, id]));\n const removePending = (id: string) =>\n setPendingIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n\n const [startTunnel] = useStartTunnelMutation();\n\n const handleStartTunnel = async (id: string) => {\n const existing = tunnels.find((t) => t.id === id);\n if (!existing) return;\n addPending(id);\n try {\n const result = await startTunnel({\n variables: { id },\n optimisticResponse: {\n startTunnel: {\n ...existing,\n status: 'PROVISIONING' as TunnelStatus,\n __typename: 'Tunnel',\n },\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: id,\n });\n }\n } catch (err) {\n // Only open the quota modal for actual quota errors — pre-existing\n // bug spotted on the BOFF-2625 review: any tunnel-start failure\n // (network / validation / auth) was opening the quota dialog.\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('[TunnelsTab] startTunnel failed', err);\n }\n } finally {\n removePending(id);\n }\n };\n\n const [stopTunnel] = useStopTunnelMutation();\n\n const handleStopTunnel = async (id: string) => {\n const existing = tunnels.find((t) => t.id === id);\n if (!existing) return;\n addPending(id);\n try {\n await stopTunnel({\n variables: { id },\n optimisticResponse: {\n stopTunnel: { ...existing, status: 'STOPPED' as TunnelStatus, __typename: 'Tunnel' },\n },\n });\n } finally {\n removePending(id);\n }\n };\n\n const [deleteTunnel, { loading: deleting }] = useDeleteTunnelMutation({\n onCompleted: () => {\n setPendingDelete(null);\n void refetch();\n },\n });\n\n if (permissionsLoading) {\n return <LoadingState message={tr('common.loadingPermissions', 'Loading permissions…')} />;\n }\n\n if (!canRead) {\n return (\n <AccessDenied\n title={tr('vibecontrols.tunnels.list.denied.title', 'Access Denied')}\n description={tr(\n 'vibecontrols.tunnels.list.denied.description',\n 'You do not have permission to view tunnels.'\n )}\n />\n );\n }\n\n return (\n <div className=\"p-6 max-w-7xl mx-auto\">\n <PageHeader\n title={tr('vibecontrols.tunnels.list.title', 'Tunnels')}\n description={tr(\n 'vibecontrols.tunnels.list.description',\n 'Expose local services on *.vibetunnels.com and custom domains.'\n )}\n actions={\n canCreate ? (\n <Button variant=\"default\" asChild>\n <Link to=\"/vibecontrols/tunnels/new\" onClick={handleNewTunnelClick}>\n {tr('vibecontrols.tunnels.list.newTunnel', 'New tunnel')}\n </Link>\n </Button>\n ) : undefined\n }\n />\n\n <div className=\"flex flex-col sm:flex-row gap-3 mb-4\">\n <div className=\"flex-1\">\n <SearchInput\n value={search}\n onChange={setSearch}\n placeholder=\"Search tunnels by name or slug\"\n />\n </div>\n <FilterDropdown\n label=\"Provider\"\n value={providerFilter}\n onChange={setProviderFilter}\n options={[\n { value: 'all', label: 'All providers' },\n { value: 'VIBETUNNELS', label: 'Vibe Tunnels (default)' },\n { value: 'CLOUDFLARE', label: 'Cloudflare Tunnels' },\n ]}\n />\n <FilterDropdown\n label=\"Status\"\n value={statusFilter}\n onChange={setStatusFilter}\n options={[\n { value: 'all', label: 'All statuses' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PROVISIONING', label: 'Provisioning' },\n { value: 'STOPPED', label: 'Stopped' },\n { value: 'ERROR', label: 'Error' },\n ]}\n />\n </div>\n\n {loading && !data && <LoadingState message=\"Loading tunnels\" />}\n {error && (\n <ErrorState\n title=\"Failed to load tunnels\"\n message={error.message}\n onRetry={() => refetch()}\n />\n )}\n\n {data && tunnels.length === 0 && !hasAnyAgent && (\n <EmptyState\n title={tr('vibecontrols.tunnels.list.noAgentTitle', 'Create an agent first')}\n description={tr(\n 'vibecontrols.tunnels.list.noAgentDescription',\n 'Tunnels need a VibeControls agent to forward traffic. Install + register an agent, then come back to create your first tunnel.'\n )}\n action={\n <Button variant=\"default\" onClick={() => navigate('/vibecontrols/agents')}>\n {tr('vibecontrols.tunnels.list.setupAgent', 'Set up an agent')}\n </Button>\n }\n />\n )}\n\n {data && tunnels.length === 0 && hasAnyAgent && (\n <EmptyState\n title={tr('vibecontrols.tunnels.list.emptyTitle', 'No tunnels yet')}\n description={tr(\n 'vibecontrols.tunnels.list.emptyDescription',\n 'Create your first tunnel to expose a local service.'\n )}\n action={\n canCreate ? (\n <Button variant=\"default\" onClick={() => navigate('/vibecontrols/tunnels/new')}>\n {tr('vibecontrols.tunnels.list.createTunnel', 'Create tunnel')}\n </Button>\n ) : undefined\n }\n />\n )}\n\n {tunnels.length > 0 && (\n <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className=\"space-y-3\">\n {tunnels.map((tunnel: TunnelRow) => (\n <TunnelCard\n key={tunnel.id}\n tunnel={tunnel}\n isPending={pendingIds.has(tunnel.id)}\n onOpen={(id) => navigate(`/vibecontrols/tunnels/${id}`)}\n onStart={canExecute ? (id) => void handleStartTunnel(id) : undefined}\n onStop={canExecute ? (id) => void handleStopTunnel(id) : undefined}\n onDelete={canDelete ? (id, name) => setPendingDelete({ id, name }) : undefined}\n />\n ))}\n </motion.div>\n )}\n\n <ConfirmDialog\n isOpen={pendingDelete !== null}\n onClose={() => setPendingDelete(null)}\n onConfirm={() => {\n if (pendingDelete) {\n void deleteTunnel({ variables: { id: pendingDelete.id } });\n }\n }}\n title=\"Delete tunnel\"\n message={\n pendingDelete\n ? `Delete \"${pendingDelete.name}\"? This will stop the tunnel and remove all its sessions and domains.`\n : ''\n }\n confirmLabel=\"Delete\"\n variant=\"danger\"\n isLoading={deleting}\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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"}
@@ -5,27 +5,27 @@ import { DomainVerificationBadge as s } from "../../../components/tunnels/Domain
5
5
  import { Fragment as c, useState as l } from "react";
6
6
  import { jsx as u, jsxs as d } from "react/jsx-runtime";
7
7
  //#region src/pages/tunnels/tabs/TunnelDomainsTab.tsx
8
- function f({ tunnel: f }) {
9
- let p = a(), { refetch: m } = n({ variables: { id: f.id } }), [h, g] = l(""), [_, v] = l(null), [y, { loading: b }] = r({
8
+ function f({ tunnel: f, canCreate: p, canUpdate: m }) {
9
+ let h = a(), { refetch: g } = n({ variables: { id: f.id } }), [_, v] = l(""), [y, b] = l(null), [x, { loading: S }] = r({
10
10
  onCompleted: async (e) => {
11
- await m(), e.registerCustomTunnelDomain.id && await x({ variables: {
11
+ await g(), e.registerCustomTunnelDomain.id && await C({ variables: {
12
12
  tunnelId: f.id,
13
13
  domainId: e.registerCustomTunnelDomain.id
14
- } }), g(""), v(null);
14
+ } }), v(""), b(null);
15
15
  },
16
- onError: (e) => v(e.message)
17
- }), [x] = e({ onCompleted: () => void m() }), [S] = t({ onCompleted: () => void m() }), [C] = i({ onCompleted: () => void m() }), w = async () => {
18
- let e = h.trim().toLowerCase();
19
- e && await y({ variables: { input: { hostname: e } } });
20
- }, T = f.domains ?? [];
16
+ onError: (e) => b(e.message)
17
+ }), [C] = e({ onCompleted: () => void g() }), [w] = t({ onCompleted: () => void g() }), [T] = i({ onCompleted: () => void g() }), E = async () => {
18
+ let e = _.trim().toLowerCase();
19
+ e && await x({ variables: { input: { hostname: e } } });
20
+ }, D = f.domains ?? [];
21
21
  return /* @__PURE__ */ d("div", {
22
22
  className: "max-w-4xl",
23
- children: [/* @__PURE__ */ d("section", {
23
+ children: [p && /* @__PURE__ */ d("section", {
24
24
  className: "rounded-lg border border-border-subtle bg-bg-surface p-4 mb-6",
25
25
  children: [
26
26
  /* @__PURE__ */ u("h3", {
27
27
  className: "text-sm font-semibold text-text-primary mb-3",
28
- children: p("vibecontrols.tunnels.domains.attachCustomDomain", "Attach a custom domain")
28
+ children: h("vibecontrols.tunnels.domains.attachCustomDomain", "Attach a custom domain")
29
29
  }),
30
30
  /* @__PURE__ */ u("p", {
31
31
  className: "text-xs text-text-secondary mb-3",
@@ -36,26 +36,26 @@ function f({ tunnel: f }) {
36
36
  children: [/* @__PURE__ */ u("input", {
37
37
  "aria-label": "preview.example.com",
38
38
  type: "text",
39
- value: h,
40
- onChange: (e) => g(e.target.value),
39
+ value: _,
40
+ onChange: (e) => v(e.target.value),
41
41
  placeholder: "preview.example.com",
42
42
  className: "flex-1 rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-border-focus focus:outline-none"
43
43
  }), /* @__PURE__ */ u("button", {
44
44
  type: "button",
45
- disabled: b || !h.trim(),
46
- onClick: () => void w(),
45
+ disabled: S || !_.trim(),
46
+ onClick: () => void E(),
47
47
  className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50",
48
- children: b ? p("vibecontrols.tunnels.domains.adding", "Adding...") : p("vibecontrols.tunnels.domains.addDomain", "Add domain")
48
+ children: S ? h("vibecontrols.tunnels.domains.adding", "Adding...") : h("vibecontrols.tunnels.domains.addDomain", "Add domain")
49
49
  })]
50
50
  }),
51
- _ && /* @__PURE__ */ u("p", {
51
+ y && /* @__PURE__ */ u("p", {
52
52
  className: "mt-2 text-xs text-status-error-fg",
53
- children: _
53
+ children: y
54
54
  })
55
55
  ]
56
56
  }), /* @__PURE__ */ u("div", {
57
57
  className: "rounded-lg border border-border-subtle bg-bg-surface overflow-x-auto",
58
- children: T.length === 0 ? /* @__PURE__ */ u("div", {
58
+ children: D.length === 0 ? /* @__PURE__ */ u("div", {
59
59
  className: "p-4 text-sm text-text-muted",
60
60
  children: "No domains attached. Add one above or reserve a *.vibetunnels.com subdomain from the Tunnels tab."
61
61
  }) : /* @__PURE__ */ d("table", {
@@ -82,7 +82,7 @@ function f({ tunnel: f }) {
82
82
  ]
83
83
  }) }), /* @__PURE__ */ u("tbody", {
84
84
  className: "divide-y divide-border-subtle",
85
- children: T.map((e) => /* @__PURE__ */ d(c, { children: [/* @__PURE__ */ d("tr", {
85
+ children: D.map((e) => /* @__PURE__ */ d(c, { children: [/* @__PURE__ */ d("tr", {
86
86
  className: "align-middle",
87
87
  children: [
88
88
  /* @__PURE__ */ u("td", {
@@ -107,14 +107,14 @@ function f({ tunnel: f }) {
107
107
  className: "px-4 py-3 whitespace-nowrap text-right",
108
108
  children: /* @__PURE__ */ d("div", {
109
109
  className: "flex gap-2 justify-end",
110
- children: [e.verificationStatus === o.Pending && /* @__PURE__ */ u("button", {
110
+ children: [m && e.verificationStatus === o.Pending && /* @__PURE__ */ u("button", {
111
111
  type: "button",
112
- onClick: () => void C({ variables: { domainId: e.id } }),
112
+ onClick: () => void T({ variables: { domainId: e.id } }),
113
113
  className: "rounded-md border border-border-default px-3 py-1 text-xs text-text-primary hover:bg-bg-elevated",
114
114
  children: "Verify"
115
- }), /* @__PURE__ */ u("button", {
115
+ }), m && /* @__PURE__ */ u("button", {
116
116
  type: "button",
117
- onClick: () => void S({ variables: {
117
+ onClick: () => void w({ variables: {
118
118
  tunnelId: f.id,
119
119
  domainId: e.id
120
120
  } }),
@@ -1 +1 @@
1
- {"version":3,"file":"TunnelDomainsTab.js","names":[],"sources":["../../../../src/pages/tunnels/tabs/TunnelDomainsTab.tsx"],"sourcesContent":["import { Fragment, useState } from 'react';\nimport type { GetTunnelQuery } from '@/generated/wspace-operations';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport {\n useRegisterCustomTunnelDomainMutation,\n useAttachTunnelDomainMutation,\n useDetachTunnelDomainMutation,\n useVerifyTunnelDomainMutation,\n useGetTunnelQuery,\n} from '@/generated/wspace-operations';\nimport { TunnelDomainVerificationStatus } from '@/generated/wspace-types';\nimport { DomainVerificationBadge } from '@/components/tunnels/DomainVerificationBadge';\n\ntype TunnelDetails = NonNullable<GetTunnelQuery['tunnel']>;\n\nexport function TunnelDomainsTab({ tunnel }: { tunnel: TunnelDetails }) {\n const tr = useTr();\n const { refetch } = useGetTunnelQuery({ variables: { id: tunnel.id } });\n const [newHostname, setNewHostname] = useState('');\n const [addError, setAddError] = useState<string | null>(null);\n\n const [registerDomain, { loading: adding }] = useRegisterCustomTunnelDomainMutation({\n onCompleted: async (result) => {\n await refetch();\n if (result.registerCustomTunnelDomain.id) {\n await attachDomain({\n variables: {\n tunnelId: tunnel.id,\n domainId: result.registerCustomTunnelDomain.id,\n },\n });\n }\n setNewHostname('');\n setAddError(null);\n },\n onError: (err) => setAddError(err.message),\n });\n const [attachDomain] = useAttachTunnelDomainMutation({\n onCompleted: () => void refetch(),\n });\n const [detachDomain] = useDetachTunnelDomainMutation({\n onCompleted: () => void refetch(),\n });\n const [verifyDomain] = useVerifyTunnelDomainMutation({\n onCompleted: () => void refetch(),\n });\n\n const handleAdd = async () => {\n const trimmed = newHostname.trim().toLowerCase();\n if (!trimmed) return;\n await registerDomain({ variables: { input: { hostname: trimmed } } });\n };\n\n const domains = tunnel.domains ?? [];\n\n return (\n <div className=\"max-w-4xl\">\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 mb-6\">\n <h3 className=\"text-sm font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.tunnels.domains.attachCustomDomain', 'Attach a custom domain')}\n </h3>\n <p className=\"text-xs text-text-secondary mb-3\">\n Register a user-owned FQDN. You'll be asked to create a TXT record to prove ownership\n before the domain can be attached.\n </p>\n <div className=\"flex gap-2\">\n <input\n aria-label=\"preview.example.com\"\n type=\"text\"\n value={newHostname}\n onChange={(e) => setNewHostname(e.target.value)}\n placeholder=\"preview.example.com\"\n className=\"flex-1 rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-border-focus focus:outline-none\"\n />\n <button\n type=\"button\"\n disabled={adding || !newHostname.trim()}\n onClick={() => void handleAdd()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {adding\n ? tr('vibecontrols.tunnels.domains.adding', 'Adding...')\n : tr('vibecontrols.tunnels.domains.addDomain', 'Add domain')}\n </button>\n </div>\n {addError && <p className=\"mt-2 text-xs text-status-error-fg\">{addError}</p>}\n </section>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface overflow-x-auto\">\n {domains.length === 0 ? (\n <div className=\"p-4 text-sm text-text-muted\">\n No domains attached. Add one above or reserve a *.vibetunnels.com subdomain from the\n Tunnels tab.\n </div>\n ) : (\n <table className=\"min-w-[480px] w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-sunken\">\n <th className=\"px-4 py-2 text-left text-xs font-medium text-text-muted\">Domain</th>\n <th className=\"px-4 py-2 text-left text-xs font-medium text-text-muted whitespace-nowrap\">\n Status\n </th>\n <th className=\"px-4 py-2 text-left text-xs font-medium text-text-muted whitespace-nowrap\">\n Type\n </th>\n <th className=\"px-4 py-2 text-right text-xs font-medium text-text-muted whitespace-nowrap\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-subtle\">\n {domains.map((domain) => (\n <Fragment key={domain.id}>\n <tr className=\"align-middle\">\n <td className=\"px-4 py-3\">\n <div className=\"font-mono text-sm text-text-primary break-all min-w-[140px]\">\n {domain.hostname}\n </div>\n </td>\n <td className=\"px-4 py-3 whitespace-nowrap\">\n <DomainVerificationBadge status={domain.verificationStatus} />\n </td>\n <td className=\"px-4 py-3 text-xs text-text-muted whitespace-nowrap\">\n {domain.type}\n {domain.isPrimary && <span className=\"ml-1\">• primary</span>}\n </td>\n <td className=\"px-4 py-3 whitespace-nowrap text-right\">\n <div className=\"flex gap-2 justify-end\">\n {domain.verificationStatus === TunnelDomainVerificationStatus.Pending && (\n <button\n type=\"button\"\n onClick={() =>\n void verifyDomain({ variables: { domainId: domain.id } })\n }\n className=\"rounded-md border border-border-default px-3 py-1 text-xs text-text-primary hover:bg-bg-elevated\"\n >\n Verify\n </button>\n )}\n <button\n type=\"button\"\n onClick={() =>\n void detachDomain({\n variables: { tunnelId: tunnel.id, domainId: domain.id },\n })\n }\n className=\"rounded-md border border-border-default px-3 py-1 text-xs text-text-primary hover:bg-bg-elevated\"\n >\n Detach\n </button>\n </div>\n </td>\n </tr>\n {domain.verificationStatus === TunnelDomainVerificationStatus.Pending &&\n domain.challengeRecordName &&\n domain.challengeToken && (\n <tr>\n <td colSpan={4} className=\"px-4 pb-3 pt-0\">\n <div className=\"rounded-md bg-bg-sunken p-2 text-xs text-text-secondary\">\n <div>Create this TXT record to verify:</div>\n <div className=\"font-mono text-text-primary mt-1 break-all\">\n {domain.challengeRecordName} IN TXT &quot;{domain.challengeToken}\n &quot;\n </div>\n </div>\n </td>\n </tr>\n )}\n </Fragment>\n ))}\n </tbody>\n </table>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TunnelDomainsTab.js","names":[],"sources":["../../../../src/pages/tunnels/tabs/TunnelDomainsTab.tsx"],"sourcesContent":["import { Fragment, useState } from 'react';\nimport type { GetTunnelQuery } from '@/generated/wspace-operations';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport {\n useRegisterCustomTunnelDomainMutation,\n useAttachTunnelDomainMutation,\n useDetachTunnelDomainMutation,\n useVerifyTunnelDomainMutation,\n useGetTunnelQuery,\n} from '@/generated/wspace-operations';\nimport { TunnelDomainVerificationStatus } from '@/generated/wspace-types';\nimport { DomainVerificationBadge } from '@/components/tunnels/DomainVerificationBadge';\n\ntype TunnelDetails = NonNullable<GetTunnelQuery['tunnel']>;\n\nexport function TunnelDomainsTab({\n tunnel,\n canCreate,\n canUpdate,\n}: {\n tunnel: TunnelDetails;\n canCreate: boolean;\n canUpdate: boolean;\n}) {\n const tr = useTr();\n const { refetch } = useGetTunnelQuery({ variables: { id: tunnel.id } });\n const [newHostname, setNewHostname] = useState('');\n const [addError, setAddError] = useState<string | null>(null);\n\n const [registerDomain, { loading: adding }] = useRegisterCustomTunnelDomainMutation({\n onCompleted: async (result) => {\n await refetch();\n if (result.registerCustomTunnelDomain.id) {\n await attachDomain({\n variables: {\n tunnelId: tunnel.id,\n domainId: result.registerCustomTunnelDomain.id,\n },\n });\n }\n setNewHostname('');\n setAddError(null);\n },\n onError: (err) => setAddError(err.message),\n });\n const [attachDomain] = useAttachTunnelDomainMutation({\n onCompleted: () => void refetch(),\n });\n const [detachDomain] = useDetachTunnelDomainMutation({\n onCompleted: () => void refetch(),\n });\n const [verifyDomain] = useVerifyTunnelDomainMutation({\n onCompleted: () => void refetch(),\n });\n\n const handleAdd = async () => {\n const trimmed = newHostname.trim().toLowerCase();\n if (!trimmed) return;\n await registerDomain({ variables: { input: { hostname: trimmed } } });\n };\n\n const domains = tunnel.domains ?? [];\n\n return (\n <div className=\"max-w-4xl\">\n {canCreate && (\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 mb-6\">\n <h3 className=\"text-sm font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.tunnels.domains.attachCustomDomain', 'Attach a custom domain')}\n </h3>\n <p className=\"text-xs text-text-secondary mb-3\">\n Register a user-owned FQDN. You'll be asked to create a TXT record to prove ownership\n before the domain can be attached.\n </p>\n <div className=\"flex gap-2\">\n <input\n aria-label=\"preview.example.com\"\n type=\"text\"\n value={newHostname}\n onChange={(e) => setNewHostname(e.target.value)}\n placeholder=\"preview.example.com\"\n className=\"flex-1 rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-border-focus focus:outline-none\"\n />\n <button\n type=\"button\"\n disabled={adding || !newHostname.trim()}\n onClick={() => void handleAdd()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {adding\n ? tr('vibecontrols.tunnels.domains.adding', 'Adding...')\n : tr('vibecontrols.tunnels.domains.addDomain', 'Add domain')}\n </button>\n </div>\n {addError && <p className=\"mt-2 text-xs text-status-error-fg\">{addError}</p>}\n </section>\n )}\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface overflow-x-auto\">\n {domains.length === 0 ? (\n <div className=\"p-4 text-sm text-text-muted\">\n No domains attached. Add one above or reserve a *.vibetunnels.com subdomain from the\n Tunnels tab.\n </div>\n ) : (\n <table className=\"min-w-[480px] w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-sunken\">\n <th className=\"px-4 py-2 text-left text-xs font-medium text-text-muted\">Domain</th>\n <th className=\"px-4 py-2 text-left text-xs font-medium text-text-muted whitespace-nowrap\">\n Status\n </th>\n <th className=\"px-4 py-2 text-left text-xs font-medium text-text-muted whitespace-nowrap\">\n Type\n </th>\n <th className=\"px-4 py-2 text-right text-xs font-medium text-text-muted whitespace-nowrap\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-subtle\">\n {domains.map((domain) => (\n <Fragment key={domain.id}>\n <tr className=\"align-middle\">\n <td className=\"px-4 py-3\">\n <div className=\"font-mono text-sm text-text-primary break-all min-w-[140px]\">\n {domain.hostname}\n </div>\n </td>\n <td className=\"px-4 py-3 whitespace-nowrap\">\n <DomainVerificationBadge status={domain.verificationStatus} />\n </td>\n <td className=\"px-4 py-3 text-xs text-text-muted whitespace-nowrap\">\n {domain.type}\n {domain.isPrimary && <span className=\"ml-1\">• primary</span>}\n </td>\n <td className=\"px-4 py-3 whitespace-nowrap text-right\">\n <div className=\"flex gap-2 justify-end\">\n {canUpdate &&\n domain.verificationStatus === TunnelDomainVerificationStatus.Pending && (\n <button\n type=\"button\"\n onClick={() =>\n void verifyDomain({ variables: { domainId: domain.id } })\n }\n className=\"rounded-md border border-border-default px-3 py-1 text-xs text-text-primary hover:bg-bg-elevated\"\n >\n Verify\n </button>\n )}\n {canUpdate && (\n <button\n type=\"button\"\n onClick={() =>\n void detachDomain({\n variables: { tunnelId: tunnel.id, domainId: domain.id },\n })\n }\n className=\"rounded-md border border-border-default px-3 py-1 text-xs text-text-primary hover:bg-bg-elevated\"\n >\n Detach\n </button>\n )}\n </div>\n </td>\n </tr>\n {domain.verificationStatus === TunnelDomainVerificationStatus.Pending &&\n domain.challengeRecordName &&\n domain.challengeToken && (\n <tr>\n <td colSpan={4} className=\"px-4 pb-3 pt-0\">\n <div className=\"rounded-md bg-bg-sunken p-2 text-xs text-text-secondary\">\n <div>Create this TXT record to verify:</div>\n <div className=\"font-mono text-text-primary mt-1 break-all\">\n {domain.challengeRecordName} IN TXT &quot;{domain.challengeToken}\n &quot;\n </div>\n </div>\n </td>\n </tr>\n )}\n </Fragment>\n ))}\n </tbody>\n </table>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}