@burdenoff/microfe-vibecontrols 2026.528.7 → 2026.528.8

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 (33) hide show
  1. package/dist/VibeControlsRoot.js +1 -0
  2. package/dist/VibeControlsRoot.js.map +1 -1
  3. package/dist/components/audit/AuditLogFilters.js +71 -47
  4. package/dist/components/audit/AuditLogFilters.js.map +1 -1
  5. package/dist/components/shared/AgentSetupBanner.js +50 -45
  6. package/dist/components/shared/AgentSetupBanner.js.map +1 -1
  7. package/dist/components/shared/LinkToVibePanel.js +97 -0
  8. package/dist/components/shared/LinkToVibePanel.js.map +1 -0
  9. package/dist/components/shared/index.js +1 -0
  10. package/dist/components/vibe/VibeLinkedEntitiesPanel.js +60 -0
  11. package/dist/components/vibe/VibeLinkedEntitiesPanel.js.map +1 -0
  12. package/dist/components/vibedeck/VibeDeckButtonTile.js +25 -23
  13. package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
  14. package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
  15. package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
  16. package/dist/microfe-vibecontrols.css +1 -1
  17. package/dist/pages/AgentDetailsPage.js +2 -2
  18. package/dist/pages/AgentDetailsPage.js.map +1 -1
  19. package/dist/pages/AnalyticsPage.js +255 -204
  20. package/dist/pages/AnalyticsPage.js.map +1 -1
  21. package/dist/pages/OverviewPage.js +1 -11
  22. package/dist/pages/OverviewPage.js.map +1 -1
  23. package/dist/pages/SessionsPage.js +825 -629
  24. package/dist/pages/SessionsPage.js.map +1 -1
  25. package/dist/pages/VibeAuditPage.js +184 -153
  26. package/dist/pages/VibeAuditPage.js.map +1 -1
  27. package/dist/pages/VibeDeckPage.js +416 -379
  28. package/dist/pages/VibeDeckPage.js.map +1 -1
  29. package/dist/pages/VibeDetailsPage.js +621 -557
  30. package/dist/pages/VibeDetailsPage.js.map +1 -1
  31. package/dist/pages/tunnels/TunnelsTab.js +115 -90
  32. package/dist/pages/tunnels/TunnelsTab.js.map +1 -1
  33. package/package.json +1 -1
@@ -2,51 +2,64 @@ import { useTr as e } from "../../shared/hooks/useTr.js";
2
2
  import { ConfirmDialog as t } from "../../components/shared/ConfirmDialog.js";
3
3
  import { QuotaExhaustedDialog as n } from "../../components/shared/QuotaExhaustedDialog.js";
4
4
  import { getQuotaDialogContent as r, isQuotaExhaustedError as i, markQuotaHandledLocally as a } from "../../utils/quotaUtils.js";
5
- import { useDeleteTunnelMutation as o, useListTunnelsQuery as s, useStartTunnelMutation as c, useStopTunnelMutation as l } from "../../generated/wspace-operations.js";
6
- import { PageHeader as u } from "../../components/shared/PageHeader.js";
7
- import { LoadingState as d } from "../../components/shared/LoadingState.js";
8
- import { ErrorState as f } from "../../components/shared/ErrorState.js";
9
- import { EmptyState as p } from "../../components/shared/EmptyState.js";
10
- import { SearchInput as m } from "../../components/shared/SearchInput.js";
11
- import { FilterDropdown as h } from "../../components/shared/FilterDropdown.js";
12
- import { TunnelCard as g } from "../../components/tunnels/TunnelCard.js";
13
- import { useEffect as _, useMemo as v, useState as y } from "react";
14
- import { Link as b, useNavigate as x } from "react-router-dom";
15
- import { motion as S } from "framer-motion";
16
- import { jsx as C, jsxs as w } from "react/jsx-runtime";
5
+ import { useDeleteTunnelMutation as o, useListAgentsQuery as s, useListTunnelsQuery as c, useStartTunnelMutation as l, useStopTunnelMutation as u } from "../../generated/wspace-operations.js";
6
+ import { PageHeader as d } from "../../components/shared/PageHeader.js";
7
+ import { LoadingState as f } from "../../components/shared/LoadingState.js";
8
+ import { ErrorState as p } from "../../components/shared/ErrorState.js";
9
+ import { EmptyState as m } from "../../components/shared/EmptyState.js";
10
+ import { SearchInput as h } from "../../components/shared/SearchInput.js";
11
+ import { FilterDropdown as g } from "../../components/shared/FilterDropdown.js";
12
+ import { TunnelCard as _ } from "../../components/tunnels/TunnelCard.js";
13
+ import { useEffect as v, useMemo as y, useState as b } from "react";
14
+ import { Link as x, useNavigate as S } from "react-router-dom";
15
+ import { motion as C } from "framer-motion";
16
+ import { jsx as w, jsxs as T } from "react/jsx-runtime";
17
+ import { Button as E } from "@burdenoff/fe-libs/ui";
17
18
  //#region src/pages/tunnels/TunnelsTab.tsx
18
- var T = 4e3;
19
- function E() {
20
- let E = e(), D = x(), [O, k] = y(""), [A, j] = y("all"), [M, N] = y("all"), [P, F] = y(null), [I, L] = y(!1), [R, z] = y(/* @__PURE__ */ new Set()), { data: B, loading: V, error: H, refetch: U, startPolling: W, stopPolling: G } = s({
19
+ var D = 4e3;
20
+ function O() {
21
+ let O = e(), k = S(), [A, ee] = b(""), [j, M] = b("all"), [N, P] = b("all"), [F, I] = b(null), [L, R] = b(!1), [z, B] = b(/* @__PURE__ */ new Set()), { data: V, loading: H, error: U, refetch: W, startPolling: G, stopPolling: K } = c({
21
22
  variables: {
22
- filter: v(() => {
23
+ filter: y(() => {
23
24
  let e = {};
24
- return A !== "all" && (e.providerKey = A), M !== "all" && (e.status = [M]), O.trim() && (e.search = O.trim()), e;
25
+ return j !== "all" && (e.providerKey = j), N !== "all" && (e.status = [N]), A.trim() && (e.search = A.trim()), e;
25
26
  }, [
26
- A,
27
- M,
28
- O
27
+ j,
28
+ N,
29
+ A
29
30
  ]),
30
31
  pagination: { limit: 50 }
31
32
  },
32
33
  fetchPolicy: "cache-and-network"
33
- }), K = B?.tunnels?.items ?? [], q = R.size > 0 || K.some((e) => e.status === "PROVISIONING" || e.status === "DELETING" || e.status === "ACTIVE" && !e.primaryDomain?.hostname);
34
- _(() => {
35
- q ? W(T) : G();
34
+ }), { data: q } = s({
35
+ variables: { pagination: { limit: 1 } },
36
+ fetchPolicy: "cache-first"
37
+ }), J = (q?.vibecontrolsAgents?.length ?? 0) > 0, Y = V?.tunnels?.items ?? [], X = (e) => {
38
+ if (!J) {
39
+ e.preventDefault();
40
+ let t = {
41
+ message: O("vibecontrols.tunnels.list.createAgentFirst", "Create an agent first to use tunnels"),
42
+ variant: "info"
43
+ };
44
+ window.dispatchEvent(new CustomEvent("vibecontrols:toast", { detail: t })), k("/vibecontrols/agents");
45
+ }
46
+ }, Z = z.size > 0 || Y.some((e) => e.status === "PROVISIONING" || e.status === "DELETING" || e.status === "ACTIVE" && !e.primaryDomain?.hostname);
47
+ v(() => {
48
+ Z ? G(D) : K();
36
49
  }, [
37
- q,
38
- W,
39
- G
50
+ Z,
51
+ G,
52
+ K
40
53
  ]);
41
- let J = (e) => z((t) => new Set([...t, e])), Y = (e) => z((t) => {
54
+ let Q = (e) => B((t) => new Set([...t, e])), $ = (e) => B((t) => {
42
55
  let n = new Set(t);
43
56
  return n.delete(e), n;
44
- }), [X] = c(), Z = async (e) => {
45
- let t = K.find((t) => t.id === e);
57
+ }), [te] = l(), ne = async (e) => {
58
+ let t = Y.find((t) => t.id === e);
46
59
  if (t) {
47
- J(e);
60
+ Q(e);
48
61
  try {
49
- let n = await X({
62
+ let n = await te({
50
63
  variables: { id: e },
51
64
  optimisticResponse: { startTunnel: {
52
65
  ...t,
@@ -54,19 +67,19 @@ function E() {
54
67
  __typename: "Tunnel"
55
68
  } }
56
69
  });
57
- n.error && i(n.error) && (a(), L(!0));
70
+ n.error && i(n.error) && (a(), R(!0));
58
71
  } catch (e) {
59
- i(e) ? (a(), L(!0)) : console.error("[TunnelsTab] startTunnel failed", e);
72
+ i(e) ? (a(), R(!0)) : console.error("[TunnelsTab] startTunnel failed", e);
60
73
  } finally {
61
- Y(e);
74
+ $(e);
62
75
  }
63
76
  }
64
- }, [Q] = l(), $ = async (e) => {
65
- let t = K.find((t) => t.id === e);
77
+ }, [re] = u(), ie = async (e) => {
78
+ let t = Y.find((t) => t.id === e);
66
79
  if (t) {
67
- J(e);
80
+ Q(e);
68
81
  try {
69
- await Q({
82
+ await re({
70
83
  variables: { id: e },
71
84
  optimisticResponse: { stopTunnel: {
72
85
  ...t,
@@ -75,39 +88,43 @@ function E() {
75
88
  } }
76
89
  });
77
90
  } finally {
78
- Y(e);
91
+ $(e);
79
92
  }
80
93
  }
81
- }, [ee, { loading: te }] = o({ onCompleted: () => {
82
- F(null), U();
94
+ }, [ae, { loading: oe }] = o({ onCompleted: () => {
95
+ I(null), W();
83
96
  } });
84
- return /* @__PURE__ */ w("div", {
97
+ return /* @__PURE__ */ T("div", {
85
98
  className: "p-6 max-w-7xl mx-auto",
86
99
  children: [
87
- /* @__PURE__ */ C(u, {
88
- title: E("vibecontrols.tunnels.list.title", "Tunnels"),
89
- description: E("vibecontrols.tunnels.list.description", "Expose local services on *.vibetunnels.com and custom domains."),
90
- actions: /* @__PURE__ */ C(b, {
91
- to: "/vibecontrols/tunnels/new",
92
- className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover transition-colors",
93
- children: E("vibecontrols.tunnels.list.newTunnel", "New tunnel")
100
+ /* @__PURE__ */ w(d, {
101
+ title: O("vibecontrols.tunnels.list.title", "Tunnels"),
102
+ description: O("vibecontrols.tunnels.list.description", "Expose local services on *.vibetunnels.com and custom domains."),
103
+ actions: /* @__PURE__ */ w(E, {
104
+ variant: "default",
105
+ asChild: !0,
106
+ children: /* @__PURE__ */ w(x, {
107
+ to: "/vibecontrols/tunnels/new",
108
+ onClick: X,
109
+ children: O("vibecontrols.tunnels.list.newTunnel", "New tunnel")
110
+ })
94
111
  })
95
112
  }),
96
- /* @__PURE__ */ w("div", {
113
+ /* @__PURE__ */ T("div", {
97
114
  className: "flex flex-col sm:flex-row gap-3 mb-4",
98
115
  children: [
99
- /* @__PURE__ */ C("div", {
116
+ /* @__PURE__ */ w("div", {
100
117
  className: "flex-1",
101
- children: /* @__PURE__ */ C(m, {
102
- value: O,
103
- onChange: k,
118
+ children: /* @__PURE__ */ w(h, {
119
+ value: A,
120
+ onChange: ee,
104
121
  placeholder: "Search tunnels by name or slug"
105
122
  })
106
123
  }),
107
- /* @__PURE__ */ C(h, {
124
+ /* @__PURE__ */ w(g, {
108
125
  label: "Provider",
109
- value: A,
110
- onChange: j,
126
+ value: j,
127
+ onChange: M,
111
128
  options: [
112
129
  {
113
130
  value: "all",
@@ -123,10 +140,10 @@ function E() {
123
140
  }
124
141
  ]
125
142
  }),
126
- /* @__PURE__ */ C(h, {
143
+ /* @__PURE__ */ w(g, {
127
144
  label: "Status",
128
- value: M,
129
- onChange: N,
145
+ value: N,
146
+ onChange: P,
130
147
  options: [
131
148
  {
132
149
  value: "all",
@@ -156,55 +173,63 @@ function E() {
156
173
  })
157
174
  ]
158
175
  }),
159
- V && !B && /* @__PURE__ */ C(d, { message: "Loading tunnels" }),
160
- H && /* @__PURE__ */ C(f, {
176
+ H && !V && /* @__PURE__ */ w(f, { message: "Loading tunnels" }),
177
+ U && /* @__PURE__ */ w(p, {
161
178
  title: "Failed to load tunnels",
162
- message: H.message,
163
- onRetry: () => U()
179
+ message: U.message,
180
+ onRetry: () => W()
181
+ }),
182
+ V && Y.length === 0 && !J && /* @__PURE__ */ w(m, {
183
+ title: O("vibecontrols.tunnels.list.noAgentTitle", "Create an agent first"),
184
+ description: O("vibecontrols.tunnels.list.noAgentDescription", "Tunnels need a VibeControls agent to forward traffic. Install + register an agent, then come back to create your first tunnel."),
185
+ action: /* @__PURE__ */ w(E, {
186
+ variant: "default",
187
+ onClick: () => k("/vibecontrols/agents"),
188
+ children: O("vibecontrols.tunnels.list.setupAgent", "Set up an agent")
189
+ })
164
190
  }),
165
- B && K.length === 0 && /* @__PURE__ */ C(p, {
166
- title: E("vibecontrols.tunnels.list.emptyTitle", "No tunnels yet"),
167
- description: E("vibecontrols.tunnels.list.emptyDescription", "Create your first tunnel to expose a local service."),
168
- action: /* @__PURE__ */ C("button", {
169
- type: "button",
170
- onClick: () => D("/vibecontrols/tunnels/new"),
171
- className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover transition-colors",
172
- children: E("vibecontrols.tunnels.list.createTunnel", "Create tunnel")
191
+ V && Y.length === 0 && J && /* @__PURE__ */ w(m, {
192
+ title: O("vibecontrols.tunnels.list.emptyTitle", "No tunnels yet"),
193
+ description: O("vibecontrols.tunnels.list.emptyDescription", "Create your first tunnel to expose a local service."),
194
+ action: /* @__PURE__ */ w(E, {
195
+ variant: "default",
196
+ onClick: () => k("/vibecontrols/tunnels/new"),
197
+ children: O("vibecontrols.tunnels.list.createTunnel", "Create tunnel")
173
198
  })
174
199
  }),
175
- K.length > 0 && /* @__PURE__ */ C(S.div, {
200
+ Y.length > 0 && /* @__PURE__ */ w(C.div, {
176
201
  initial: { opacity: 0 },
177
202
  animate: { opacity: 1 },
178
203
  className: "space-y-3",
179
- children: K.map((e) => /* @__PURE__ */ C(g, {
204
+ children: Y.map((e) => /* @__PURE__ */ w(_, {
180
205
  tunnel: e,
181
- isPending: R.has(e.id),
182
- onOpen: (e) => D(`/vibecontrols/tunnels/${e}`),
183
- onStart: (e) => void Z(e),
184
- onStop: (e) => void $(e),
185
- onDelete: (e, t) => F({
206
+ isPending: z.has(e.id),
207
+ onOpen: (e) => k(`/vibecontrols/tunnels/${e}`),
208
+ onStart: (e) => void ne(e),
209
+ onStop: (e) => void ie(e),
210
+ onDelete: (e, t) => I({
186
211
  id: e,
187
212
  name: t
188
213
  })
189
214
  }, e.id))
190
215
  }),
191
- /* @__PURE__ */ C(t, {
192
- isOpen: P !== null,
193
- onClose: () => F(null),
216
+ /* @__PURE__ */ w(t, {
217
+ isOpen: F !== null,
218
+ onClose: () => I(null),
194
219
  onConfirm: () => {
195
- P && ee({ variables: { id: P.id } });
220
+ F && ae({ variables: { id: F.id } });
196
221
  },
197
222
  title: "Delete tunnel",
198
- message: P ? `Delete "${P.name}"? This will stop the tunnel and remove all its sessions and domains.` : "",
223
+ message: F ? `Delete "${F.name}"? This will stop the tunnel and remove all its sessions and domains.` : "",
199
224
  confirmLabel: "Delete",
200
225
  variant: "danger",
201
- isLoading: te
226
+ isLoading: oe
202
227
  }),
203
- /* @__PURE__ */ C(n, {
204
- isOpen: I,
205
- onClose: () => L(!1),
228
+ /* @__PURE__ */ w(n, {
229
+ isOpen: L,
230
+ onClose: () => R(!1),
206
231
  onConfirm: () => {
207
- L(!1), D("/billing");
232
+ R(!1), k("/billing");
208
233
  },
209
234
  ...r("tunnel-start")
210
235
  })
@@ -212,6 +237,6 @@ function E() {
212
237
  });
213
238
  }
214
239
  //#endregion
215
- export { E as TunnelsTab };
240
+ export { O as TunnelsTab };
216
241
 
217
242
  //# 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';\n\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 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 [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 tunnels: TunnelRow[] = data?.tunnels?.items ?? [];\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 }\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 } 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 <Link\n to=\"/vibecontrols/tunnels/new\"\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('vibecontrols.tunnels.list.newTunnel', 'New tunnel')}\n </Link>\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 && (\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\n type=\"button\"\n onClick={() => navigate('/vibecontrols/tunnels/new')}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover transition-colors\"\n >\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');\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';\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 [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 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 }\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 } 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');\n }}\n {...getQuotaDialogContent('tunnel-start')}\n />\n </div>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-vibecontrols",
3
- "version": "2026.528.7",
3
+ "version": "2026.528.8",
4
4
  "description": "VibeControls microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [