@burdenoff/microfe-vibecontrols 2026.611.2 → 2026.611.4

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.
@@ -0,0 +1,165 @@
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { LoadingState as t } from "../shared/LoadingState.js";
3
+ import { ErrorState as n } from "../shared/ErrorState.js";
4
+ import { EmptyState as r } from "../shared/EmptyState.js";
5
+ import { Card as i } from "../shared/Card.js";
6
+ import "../shared/index.js";
7
+ import { cancelQueueItem as a, fetchQueueItems as o } from "../../services/aiApi.js";
8
+ import { notify as s } from "../../lib/toast.js";
9
+ import { useCallback as c, useEffect as l, useState as u } from "react";
10
+ import { AlertCircle as d, Ban as f, CheckCircle2 as p, Clock as m, ListChecks as h, Loader2 as g, RefreshCw as _, X as v } from "lucide-react";
11
+ import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
12
+ //#region src/components/ai/AIQueueTab.tsx
13
+ var S = {
14
+ pending: {
15
+ label: "Pending",
16
+ cls: "bg-status-warning-bg text-status-warning-text",
17
+ icon: m
18
+ },
19
+ processing: {
20
+ label: "Processing",
21
+ cls: "bg-action-primary-bg/20 text-action-primary-bg",
22
+ icon: g
23
+ },
24
+ completed: {
25
+ label: "Completed",
26
+ cls: "bg-status-success-bg text-status-success-text",
27
+ icon: p
28
+ },
29
+ failed: {
30
+ label: "Failed",
31
+ cls: "bg-status-error-bg text-status-error-text",
32
+ icon: d
33
+ },
34
+ cancelled: {
35
+ label: "Cancelled",
36
+ cls: "bg-bg-sunken text-text-secondary",
37
+ icon: f
38
+ }
39
+ };
40
+ function C({ agent: d, onNavigateToSession: f }) {
41
+ let p = e(), [m, g] = u([]), [C, w] = u(!0), [T, E] = u(null), [D, O] = u(null), k = c(async (e) => {
42
+ if (d.id) {
43
+ e && w(!0);
44
+ try {
45
+ g(await o(d)), E(null);
46
+ } catch (e) {
47
+ E(e.message);
48
+ } finally {
49
+ e && w(!1);
50
+ }
51
+ }
52
+ }, [d]);
53
+ l(() => {
54
+ k(!0);
55
+ let e = window.setInterval(() => void k(!1), 15e3);
56
+ return () => window.clearInterval(e);
57
+ }, [k]);
58
+ let A = async (e) => {
59
+ O(e);
60
+ try {
61
+ await a(d, e), s.success(p("aiQueue.cancelled", "Queued item cancelled")), await k(!1);
62
+ } catch (e) {
63
+ s.error(p("aiQueue.cancelFailed", "Could not cancel"), e.message);
64
+ } finally {
65
+ O(null);
66
+ }
67
+ };
68
+ if (!d.id) return /* @__PURE__ */ b(r, {
69
+ icon: /* @__PURE__ */ b(h, { className: "size-10" }),
70
+ title: p("aiQueue.noAgentTitle", "No active agent"),
71
+ description: p("aiQueue.noAgentDesc", "Start or attach an agent to queue AI prompts for background processing.")
72
+ });
73
+ if (C && m.length === 0) return /* @__PURE__ */ b(t, {});
74
+ if (T && m.length === 0) return /* @__PURE__ */ b(n, {
75
+ message: T,
76
+ onRetry: () => void k(!0)
77
+ });
78
+ let j = m.filter((e) => e.status === "pending" || e.status === "processing").length;
79
+ return /* @__PURE__ */ x("div", {
80
+ className: "space-y-4",
81
+ children: [/* @__PURE__ */ x("div", {
82
+ className: "flex items-center justify-between",
83
+ children: [/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ x("h3", {
84
+ className: "flex items-center gap-2 text-base font-semibold text-text-primary",
85
+ children: [/* @__PURE__ */ b(h, { className: "size-5" }), p("aiQueue.title", "AI Queue")]
86
+ }), /* @__PURE__ */ b("p", {
87
+ className: "text-sm text-text-secondary",
88
+ children: p("aiQueue.subtitle", "Prompts queued for background processing on this agent — queue at night, review in the morning.")
89
+ })] }), /* @__PURE__ */ x("button", {
90
+ type: "button",
91
+ onClick: () => void k(!0),
92
+ className: "flex items-center gap-1.5 rounded-md border border-border-default px-3 py-1.5 text-sm text-text-primary hover:bg-bg-sunken",
93
+ children: [/* @__PURE__ */ b(_, { className: `size-4 ${C ? "animate-spin" : ""}` }), p("common.refresh", "Refresh")]
94
+ })]
95
+ }), m.length === 0 ? /* @__PURE__ */ b(r, {
96
+ icon: /* @__PURE__ */ b(h, { className: "size-10" }),
97
+ title: p("aiQueue.emptyTitle", "Queue is empty"),
98
+ description: p("aiQueue.emptyDesc", "Dispatch a prompt with \"Queue\" instead of \"Run\" to have the agent process it in the background.")
99
+ }) : /* @__PURE__ */ x(y, { children: [j > 0 && /* @__PURE__ */ x("p", {
100
+ className: "text-xs text-text-tertiary",
101
+ children: [
102
+ j,
103
+ " ",
104
+ p("aiQueue.inFlight", "in flight"),
105
+ " · ",
106
+ m.length,
107
+ " ",
108
+ p("aiQueue.total", "total")
109
+ ]
110
+ }), /* @__PURE__ */ b(i, { children: /* @__PURE__ */ b("ul", {
111
+ className: "divide-y divide-border-subtle",
112
+ children: m.map((e) => {
113
+ let t = S[e.status], n = t.icon;
114
+ return /* @__PURE__ */ x("li", {
115
+ className: "flex items-center gap-3 px-4 py-3",
116
+ children: [
117
+ /* @__PURE__ */ x("span", {
118
+ className: `inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ${t.cls}`,
119
+ children: [/* @__PURE__ */ b(n, { className: `size-3.5 ${e.status === "processing" ? "animate-spin" : ""}` }), t.label]
120
+ }),
121
+ /* @__PURE__ */ x("div", {
122
+ className: "min-w-0 flex-1",
123
+ children: [
124
+ /* @__PURE__ */ b("button", {
125
+ type: "button",
126
+ onClick: () => f?.(e.sessionId),
127
+ className: "truncate text-left font-mono text-sm text-text-primary hover:underline",
128
+ title: p("aiQueue.openSession", "Open the session this prompt runs in"),
129
+ children: e.sessionId
130
+ }),
131
+ /* @__PURE__ */ x("div", {
132
+ className: "text-xs text-text-tertiary",
133
+ children: [
134
+ p("aiQueue.attempt", "attempt"),
135
+ " ",
136
+ e.attempts,
137
+ "/",
138
+ e.maxAttempts,
139
+ e.createdAt ? ` · ${new Date(e.createdAt).toLocaleString()}` : ""
140
+ ]
141
+ }),
142
+ e.lastError && /* @__PURE__ */ b("div", {
143
+ className: "mt-0.5 truncate text-xs text-status-error-text",
144
+ children: e.lastError
145
+ })
146
+ ]
147
+ }),
148
+ e.status === "pending" && /* @__PURE__ */ b("button", {
149
+ type: "button",
150
+ onClick: () => void A(e.id),
151
+ disabled: D === e.id,
152
+ className: "shrink-0 rounded p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50",
153
+ "aria-label": p("common.cancel", "Cancel"),
154
+ children: /* @__PURE__ */ b(v, { className: "size-4" })
155
+ })
156
+ ]
157
+ }, e.id);
158
+ })
159
+ }) })] })]
160
+ });
161
+ }
162
+ //#endregion
163
+ export { C as AIQueueTab };
164
+
165
+ //# sourceMappingURL=AIQueueTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AIQueueTab.js","names":[],"sources":["../../../src/components/ai/AIQueueTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport {\n ListChecks,\n RefreshCw,\n X,\n AlertCircle,\n Clock,\n Loader2,\n CheckCircle2,\n Ban,\n} from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AIQueueItem, AgentRef } from '@/services/aiApi';\nimport { fetchQueueItems, cancelQueueItem } from '@/services/aiApi';\nimport { notify } from '@/lib/toast';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AIQueueTabProps {\n agent: AgentRef;\n onNavigateToSession?: (sessionId: string) => void;\n}\n\nconst STATUS_META: Record<\n AIQueueItem['status'],\n { label: string; cls: string; icon: typeof Clock }\n> = {\n pending: { label: 'Pending', cls: 'bg-status-warning-bg text-status-warning-text', icon: Clock },\n processing: {\n label: 'Processing',\n cls: 'bg-action-primary-bg/20 text-action-primary-bg',\n icon: Loader2,\n },\n completed: {\n label: 'Completed',\n cls: 'bg-status-success-bg text-status-success-text',\n icon: CheckCircle2,\n },\n failed: { label: 'Failed', cls: 'bg-status-error-bg text-status-error-text', icon: AlertCircle },\n cancelled: { label: 'Cancelled', cls: 'bg-bg-sunken text-text-secondary', icon: Ban },\n};\n\n/**\n * AIQueueTab — the \"queue overnight, review in the morning\" surface. Lists the queued AI prompts on\n * the active agent (the agent runs them in the background), with per-item status, attempts, and\n * errors, plus cancel for anything still pending. Auto-refreshes so a morning check shows the latest\n * results. Backed by the agent's `/ai/queue` API (fetchQueueItems / cancelQueueItem) — the same\n * governed agent surface the rest of AI mode uses.\n */\nexport function AIQueueTab({ agent, onNavigateToSession }: AIQueueTabProps): React.ReactElement {\n const tr = useTr();\n const [items, setItems] = useState<AIQueueItem[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [cancelling, setCancelling] = useState<string | null>(null);\n\n const load = useCallback(\n async (showSpinner: boolean): Promise<void> => {\n if (!agent.id) return;\n if (showSpinner) setLoading(true);\n try {\n const data = await fetchQueueItems(agent);\n setItems(data);\n setError(null);\n } catch (err) {\n setError((err as Error).message);\n } finally {\n if (showSpinner) setLoading(false);\n }\n },\n [agent]\n );\n\n useEffect(() => {\n void load(true);\n // Poll while items are still in flight so a left-open tab updates itself overnight.\n const timer = window.setInterval(() => void load(false), 15000);\n return () => window.clearInterval(timer);\n }, [load]);\n\n const handleCancel = async (id: string): Promise<void> => {\n setCancelling(id);\n try {\n await cancelQueueItem(agent, id);\n notify.success(tr('aiQueue.cancelled', 'Queued item cancelled'));\n await load(false);\n } catch (err) {\n notify.error(tr('aiQueue.cancelFailed', 'Could not cancel'), (err as Error).message);\n } finally {\n setCancelling(null);\n }\n };\n\n if (!agent.id) {\n return (\n <EmptyState\n icon={<ListChecks className=\"size-10\" />}\n title={tr('aiQueue.noAgentTitle', 'No active agent')}\n description={tr(\n 'aiQueue.noAgentDesc',\n 'Start or attach an agent to queue AI prompts for background processing.'\n )}\n />\n );\n }\n\n if (loading && items.length === 0) return <LoadingState />;\n if (error && items.length === 0) {\n return <ErrorState message={error} onRetry={() => void load(true)} />;\n }\n\n const pendingCount = items.filter(\n (i) => i.status === 'pending' || i.status === 'processing'\n ).length;\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <ListChecks className=\"size-5\" />\n {tr('aiQueue.title', 'AI Queue')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'aiQueue.subtitle',\n 'Prompts queued for background processing on this agent — queue at night, review in the morning.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void load(true)}\n className=\"flex items-center gap-1.5 rounded-md border border-border-default px-3 py-1.5 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n {tr('common.refresh', 'Refresh')}\n </button>\n </div>\n\n {items.length === 0 ? (\n <EmptyState\n icon={<ListChecks className=\"size-10\" />}\n title={tr('aiQueue.emptyTitle', 'Queue is empty')}\n description={tr(\n 'aiQueue.emptyDesc',\n 'Dispatch a prompt with \"Queue\" instead of \"Run\" to have the agent process it in the background.'\n )}\n />\n ) : (\n <>\n {pendingCount > 0 && (\n <p className=\"text-xs text-text-tertiary\">\n {pendingCount} {tr('aiQueue.inFlight', 'in flight')} · {items.length}{' '}\n {tr('aiQueue.total', 'total')}\n </p>\n )}\n <Card>\n <ul className=\"divide-y divide-border-subtle\">\n {items.map((item) => {\n const meta = STATUS_META[item.status];\n const StatusIcon = meta.icon;\n return (\n <li key={item.id} className=\"flex items-center gap-3 px-4 py-3\">\n <span\n className={`inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ${meta.cls}`}\n >\n <StatusIcon\n className={`size-3.5 ${item.status === 'processing' ? 'animate-spin' : ''}`}\n />\n {meta.label}\n </span>\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => onNavigateToSession?.(item.sessionId)}\n className=\"truncate text-left font-mono text-sm text-text-primary hover:underline\"\n title={tr('aiQueue.openSession', 'Open the session this prompt runs in')}\n >\n {item.sessionId}\n </button>\n <div className=\"text-xs text-text-tertiary\">\n {tr('aiQueue.attempt', 'attempt')} {item.attempts}/{item.maxAttempts}\n {item.createdAt ? ` · ${new Date(item.createdAt).toLocaleString()}` : ''}\n </div>\n {item.lastError && (\n <div className=\"mt-0.5 truncate text-xs text-status-error-text\">\n {item.lastError}\n </div>\n )}\n </div>\n {item.status === 'pending' && (\n <button\n type=\"button\"\n onClick={() => void handleCancel(item.id)}\n disabled={cancelling === item.id}\n className=\"shrink-0 rounded p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50\"\n aria-label={tr('common.cancel', 'Cancel')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </li>\n );\n })}\n </ul>\n </Card>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ import { useRevokeTunnelPublicAccessMutation as e, useSetTunnelPublicAccessMutation as t } from "../../generated/wspace-operations.js";
2
+ import { notify as n } from "../../lib/toast.js";
3
+ import { useState as r } from "react";
4
+ import { useI18n as i } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
5
+ import { Check as a, Clock as o, Copy as s, Globe as c, ShieldOff as l } from "lucide-react";
6
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
7
+ //#region src/components/tunnels/TunnelPublicAccessPanel.tsx
8
+ var f = [
9
+ {
10
+ key: "1h",
11
+ seconds: 3600
12
+ },
13
+ {
14
+ key: "4h",
15
+ seconds: 14400
16
+ },
17
+ {
18
+ key: "24h",
19
+ seconds: 86400
20
+ },
21
+ {
22
+ key: "7d",
23
+ seconds: 604800
24
+ }
25
+ ], p = ({ tunnelId: p, tunnelStatus: m, publicAccessExpiresAt: h, publicUrl: g, onChanged: _ }) => {
26
+ let { t: v } = i(), y = (e, t) => {
27
+ let n = v(e);
28
+ return n === e ? t : n;
29
+ }, [b, x] = r(f[1].seconds), [S, C] = r(!1), [w, { loading: T }] = t(), [E, { loading: D }] = e(), O = h ? new Date(h) : null, k = !!(O && O.getTime() > Date.now()), A = (e) => ({
30
+ "1h": "1 hour",
31
+ "4h": "4 hours",
32
+ "24h": "24 hours",
33
+ "7d": "7 days"
34
+ })[e] ?? e, j = async () => {
35
+ try {
36
+ await w({ variables: {
37
+ tunnelId: p,
38
+ ttlSeconds: b
39
+ } }), _(), n.success(y("tunPublic.granted", "Public access granted"), y("tunPublic.grantedDesc", "The link auto-expires when the window ends."));
40
+ } catch (e) {
41
+ n.error(y("tunPublic.grantFailed", "Could not grant access"), e.message);
42
+ }
43
+ }, M = async () => {
44
+ try {
45
+ await E({ variables: { tunnelId: p } }), _(), n.success(y("tunPublic.revoked", "Public access revoked — the link is dead"));
46
+ } catch (e) {
47
+ n.error(y("tunPublic.revokeFailed", "Could not revoke"), e.message);
48
+ }
49
+ }, N = async () => {
50
+ if (g) try {
51
+ await navigator.clipboard.writeText(g), C(!0), n.success(y("tunPublic.copied", "Public URL copied")), window.setTimeout(() => C(!1), 1500);
52
+ } catch {
53
+ n.error(y("tunPublic.copyFailed", "Could not copy — copy it manually"), g);
54
+ }
55
+ };
56
+ return /* @__PURE__ */ d("section", {
57
+ className: "md:col-span-2 rounded-lg border border-border-subtle bg-bg-surface p-4",
58
+ children: [/* @__PURE__ */ d("h3", {
59
+ className: "mb-3 flex items-center gap-2 text-sm font-semibold text-text-primary",
60
+ children: [/* @__PURE__ */ u(c, { className: "size-4" }), y("tunPublic.title", "Time-limited public access")]
61
+ }), k && O ? /* @__PURE__ */ d("div", {
62
+ className: "space-y-3",
63
+ children: [
64
+ /* @__PURE__ */ d("div", {
65
+ className: "flex items-center gap-2 rounded-md bg-status-success-bg px-3 py-2 text-sm text-status-success-text",
66
+ children: [/* @__PURE__ */ u(o, { className: "size-4 shrink-0" }), /* @__PURE__ */ d("span", { children: [
67
+ y("tunPublic.liveUntil", "Public until"),
68
+ " ",
69
+ /* @__PURE__ */ u("strong", { children: O.toLocaleString() }),
70
+ m !== "ACTIVE" && /* @__PURE__ */ u("span", {
71
+ className: "ml-1 text-text-tertiary",
72
+ children: y("tunPublic.notRunning", "(start the tunnel for the URL to serve)")
73
+ })
74
+ ] })]
75
+ }),
76
+ g && /* @__PURE__ */ d("div", {
77
+ className: "flex items-center gap-2 rounded-md border border-border-default px-3 py-2",
78
+ children: [/* @__PURE__ */ u("span", {
79
+ className: "flex-1 truncate font-mono text-xs text-text-secondary",
80
+ children: g
81
+ }), /* @__PURE__ */ u("button", {
82
+ type: "button",
83
+ onClick: () => void N(),
84
+ className: "p-1 text-text-secondary hover:text-text-primary",
85
+ "aria-label": y("common.copy", "Copy"),
86
+ children: S ? /* @__PURE__ */ u(a, { className: "size-4 text-status-success-text" }) : /* @__PURE__ */ u(s, { className: "size-4" })
87
+ })]
88
+ }),
89
+ /* @__PURE__ */ d("button", {
90
+ type: "button",
91
+ onClick: () => void M(),
92
+ disabled: D,
93
+ className: "flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken disabled:opacity-50",
94
+ children: [/* @__PURE__ */ u(l, { className: "size-4" }), y("tunPublic.revoke", "Revoke now")]
95
+ })
96
+ ]
97
+ }) : /* @__PURE__ */ d("div", {
98
+ className: "space-y-3",
99
+ children: [/* @__PURE__ */ u("p", {
100
+ className: "text-sm text-text-secondary",
101
+ children: y("tunPublic.off", "Public access is off. Grant a time-boxed window and the link tears itself down when it ends.")
102
+ }), /* @__PURE__ */ d("div", {
103
+ className: "flex flex-wrap items-end gap-3",
104
+ children: [/* @__PURE__ */ d("label", {
105
+ className: "flex-1 min-w-[10rem]",
106
+ children: [/* @__PURE__ */ u("span", {
107
+ className: "text-sm text-text-secondary",
108
+ children: y("tunPublic.window", "Access window")
109
+ }), /* @__PURE__ */ u("select", {
110
+ value: b,
111
+ onChange: (e) => x(Number(e.target.value)),
112
+ className: "mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary",
113
+ children: f.map((e) => /* @__PURE__ */ u("option", {
114
+ value: e.seconds,
115
+ children: y(`tunPublic.ttl.${e.key}`, A(e.key))
116
+ }, e.key))
117
+ })]
118
+ }), /* @__PURE__ */ d("button", {
119
+ type: "button",
120
+ onClick: () => void j(),
121
+ disabled: T,
122
+ className: "flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm text-action-primary-text hover:opacity-90 disabled:opacity-50",
123
+ children: [/* @__PURE__ */ u(c, { className: "size-4" }), y("tunPublic.grant", "Grant public access")]
124
+ })]
125
+ })]
126
+ })]
127
+ });
128
+ };
129
+ //#endregion
130
+ export { p as TunnelPublicAccessPanel };
131
+
132
+ //# sourceMappingURL=TunnelPublicAccessPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TunnelPublicAccessPanel.js","names":[],"sources":["../../../src/components/tunnels/TunnelPublicAccessPanel.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Globe, Copy, Check, Clock, ShieldOff } from 'lucide-react';\n\nimport { notify } from '@/lib/toast';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useSetTunnelPublicAccessMutation,\n useRevokeTunnelPublicAccessMutation,\n} from '@/generated/wspace-operations';\n\ninterface TunnelPublicAccessPanelProps {\n tunnelId: string;\n tunnelStatus: string;\n publicAccessExpiresAt?: string | null;\n publicUrl?: string | null;\n onChanged: () => void;\n}\n\n// TTL choices in seconds, inside the svc's 300 (5m) … 2592000 (30d) bounds.\nconst TTL_OPTIONS = [\n { key: '1h', seconds: 3600 },\n { key: '4h', seconds: 14_400 },\n { key: '24h', seconds: 86_400 },\n { key: '7d', seconds: 604_800 },\n];\n\n/**\n * TunnelPublicAccessPanel — grant a time-boxed public link to a tunnel and have it tear itself down.\n * Picking a window stamps `publicAccessExpiresAt = now + ttl`; a backend sweep stops the tunnel (the\n * public URL goes dead) the moment it lapses, so a \"share my localhost for 4 hours\" or \"open this DB\n * port to a contractor until tonight\" link can't outlive its purpose. Revoke kills it immediately.\n * This is the UI half of the beta-tester-tunnel / local-port-share / database-port-forward stories.\n */\nexport const TunnelPublicAccessPanel: FC<TunnelPublicAccessPanelProps> = ({\n tunnelId,\n tunnelStatus,\n publicAccessExpiresAt,\n publicUrl,\n onChanged,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const [ttlSeconds, setTtlSeconds] = useState(TTL_OPTIONS[1].seconds);\n const [copied, setCopied] = useState(false);\n const [setAccess, { loading: granting }] = useSetTunnelPublicAccessMutation();\n const [revokeAccess, { loading: revoking }] = useRevokeTunnelPublicAccessMutation();\n\n const expiresAt = publicAccessExpiresAt ? new Date(publicAccessExpiresAt) : null;\n const isActive = Boolean(expiresAt && expiresAt.getTime() > Date.now());\n const ttlLabel = (key: string): string =>\n ({ '1h': '1 hour', '4h': '4 hours', '24h': '24 hours', '7d': '7 days' })[key] ?? key;\n\n const handleGrant = async (): Promise<void> => {\n try {\n await setAccess({ variables: { tunnelId, ttlSeconds } });\n onChanged();\n notify.success(\n tr('tunPublic.granted', 'Public access granted'),\n tr('tunPublic.grantedDesc', 'The link auto-expires when the window ends.')\n );\n } catch (err) {\n notify.error(tr('tunPublic.grantFailed', 'Could not grant access'), (err as Error).message);\n }\n };\n\n const handleRevoke = async (): Promise<void> => {\n try {\n await revokeAccess({ variables: { tunnelId } });\n onChanged();\n notify.success(tr('tunPublic.revoked', 'Public access revoked — the link is dead'));\n } catch (err) {\n notify.error(tr('tunPublic.revokeFailed', 'Could not revoke'), (err as Error).message);\n }\n };\n\n const copyUrl = async (): Promise<void> => {\n if (!publicUrl) return;\n try {\n await navigator.clipboard.writeText(publicUrl);\n setCopied(true);\n notify.success(tr('tunPublic.copied', 'Public URL copied'));\n window.setTimeout(() => setCopied(false), 1500);\n } catch {\n notify.error(tr('tunPublic.copyFailed', 'Could not copy — copy it manually'), publicUrl);\n }\n };\n\n return (\n <section className=\"md:col-span-2 rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"mb-3 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Globe className=\"size-4\" />\n {tr('tunPublic.title', 'Time-limited public access')}\n </h3>\n\n {isActive && expiresAt ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2 rounded-md bg-status-success-bg px-3 py-2 text-sm text-status-success-text\">\n <Clock className=\"size-4 shrink-0\" />\n <span>\n {tr('tunPublic.liveUntil', 'Public until')}{' '}\n <strong>{expiresAt.toLocaleString()}</strong>\n {tunnelStatus !== 'ACTIVE' && (\n <span className=\"ml-1 text-text-tertiary\">\n {tr('tunPublic.notRunning', '(start the tunnel for the URL to serve)')}\n </span>\n )}\n </span>\n </div>\n {publicUrl && (\n <div className=\"flex items-center gap-2 rounded-md border border-border-default px-3 py-2\">\n <span className=\"flex-1 truncate font-mono text-xs text-text-secondary\">\n {publicUrl}\n </span>\n <button\n type=\"button\"\n onClick={() => void copyUrl()}\n className=\"p-1 text-text-secondary hover:text-text-primary\"\n aria-label={tr('common.copy', 'Copy')}\n >\n {copied ? (\n <Check className=\"size-4 text-status-success-text\" />\n ) : (\n <Copy className=\"size-4\" />\n )}\n </button>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => void handleRevoke()}\n disabled={revoking}\n className=\"flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken disabled:opacity-50\"\n >\n <ShieldOff className=\"size-4\" />\n {tr('tunPublic.revoke', 'Revoke now')}\n </button>\n </div>\n ) : (\n <div className=\"space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'tunPublic.off',\n 'Public access is off. Grant a time-boxed window and the link tears itself down when it ends.'\n )}\n </p>\n <div className=\"flex flex-wrap items-end gap-3\">\n <label className=\"flex-1 min-w-[10rem]\">\n <span className=\"text-sm text-text-secondary\">\n {tr('tunPublic.window', 'Access window')}\n </span>\n <select\n value={ttlSeconds}\n onChange={(e) => setTtlSeconds(Number(e.target.value))}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {TTL_OPTIONS.map((o) => (\n <option key={o.key} value={o.seconds}>\n {tr(`tunPublic.ttl.${o.key}`, ttlLabel(o.key))}\n </option>\n ))}\n </select>\n </label>\n <button\n type=\"button\"\n onClick={() => void handleGrant()}\n disabled={granting}\n className=\"flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n <Globe className=\"size-4\" />\n {tr('tunPublic.grant', 'Grant public access')}\n </button>\n </div>\n </div>\n )}\n </section>\n );\n};\n"],"mappings":"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"}