@burdenoff/microfe-vibecontrols 2026.526.2 → 2026.526.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.
|
@@ -5,21 +5,26 @@ import { CheckCircle2 as a, Clock as o, Database as s, Loader2 as c, Play as l,
|
|
|
5
5
|
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
6
6
|
//#region src/components/agents/backup/AgentBackupTab.tsx
|
|
7
7
|
function b(e) {
|
|
8
|
-
|
|
8
|
+
if (!e) return "Backup failed.";
|
|
9
|
+
let t = e.toLowerCase();
|
|
10
|
+
return t.includes("undefined is not an object") || t.includes("cannot read property") || t.includes("cannot read properties of") || /\btypeerror:/.test(t) || /\breferenceerror:/.test(t) ? e.includes("createResult.data.createFolder") ? "Backup failed: the agent could not reach the workspace files service (missing workspace context). Re-push credentials from the agent's Security tab and try again. See agent logs for the full error." : "Backup failed: the agent threw an internal error. See agent logs for the full error." : e.length > 200 ? e.slice(0, 200) + "…" : e;
|
|
9
11
|
}
|
|
10
12
|
function x(e) {
|
|
11
|
-
return e <
|
|
13
|
+
return e < 1024 ? `${e} B` : e < 1024 * 1024 ? `${(e / 1024).toFixed(1)} KB` : `${(e / (1024 * 1024)).toFixed(1)} MB`;
|
|
12
14
|
}
|
|
13
15
|
function S(e) {
|
|
16
|
+
return e < 1e3 ? `${e}ms` : `${(e / 1e3).toFixed(1)}s`;
|
|
17
|
+
}
|
|
18
|
+
function C(e) {
|
|
14
19
|
return new Date(e).toLocaleString();
|
|
15
20
|
}
|
|
16
|
-
function
|
|
17
|
-
let
|
|
18
|
-
let n = await fetch(`${
|
|
21
|
+
function w({ agentTunnelUrl: w, agentApiKey: T, profile: E }) {
|
|
22
|
+
let D = e(), [O, k] = i(null), [A, j] = i(null), [M, N] = i([]), [P, F] = i(!0), [I, L] = i(null), [R, z] = i(null), [B, V] = i(null), [H, U] = i(!1), W = w.replace(/\/+$/, ""), G = n(async (e, t) => {
|
|
23
|
+
let n = await fetch(`${W}${e}`, {
|
|
19
24
|
...t,
|
|
20
25
|
headers: {
|
|
21
26
|
"Content-Type": "application/json",
|
|
22
|
-
"x-agent-api-key":
|
|
27
|
+
"x-agent-api-key": T,
|
|
23
28
|
...t?.headers
|
|
24
29
|
}
|
|
25
30
|
});
|
|
@@ -28,109 +33,117 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
28
33
|
throw Error(e || `HTTP ${n.status}`);
|
|
29
34
|
}
|
|
30
35
|
return n.json();
|
|
31
|
-
}, [
|
|
36
|
+
}, [W, T]), K = n(async () => {
|
|
32
37
|
try {
|
|
33
|
-
|
|
38
|
+
F(!0), z(null);
|
|
34
39
|
let [e, n, r] = await Promise.all([
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
G(t(E, "/backup/config")),
|
|
41
|
+
G(t(E, "/backup/status")),
|
|
42
|
+
G(`${t(E, "/backup/list")}?limit=20`)
|
|
38
43
|
]);
|
|
39
|
-
|
|
44
|
+
k(e.config), j(n.status), N(r.backups || []);
|
|
40
45
|
} catch (e) {
|
|
41
46
|
let t = e instanceof Error ? e.message : String(e);
|
|
42
|
-
|
|
47
|
+
z(t === "Failed to fetch" ? `Cannot reach agent REST API at ${W}. Ensure the agent is running and its tunnel URL is accessible.` : t);
|
|
43
48
|
} finally {
|
|
44
|
-
|
|
49
|
+
F(!1);
|
|
45
50
|
}
|
|
46
|
-
}, [
|
|
51
|
+
}, [G, W]);
|
|
47
52
|
r(() => {
|
|
48
|
-
|
|
49
|
-
}, [
|
|
50
|
-
if (
|
|
51
|
-
let e = setTimeout(() =>
|
|
53
|
+
K();
|
|
54
|
+
}, [K]), r(() => {
|
|
55
|
+
if (B) {
|
|
56
|
+
let e = setTimeout(() => V(null), 3e3);
|
|
52
57
|
return () => clearTimeout(e);
|
|
53
58
|
}
|
|
54
|
-
}, [
|
|
55
|
-
let
|
|
59
|
+
}, [B]);
|
|
60
|
+
let q = async (e) => {
|
|
56
61
|
try {
|
|
57
|
-
|
|
58
|
-
let n =
|
|
59
|
-
|
|
62
|
+
L("backup"), z(null);
|
|
63
|
+
let n = await G(`${t(E, "/backup/run")}?force=${e}`, { method: "POST" }), r = n?.backup, i = typeof n?.error == "string" && n.error || (r && typeof r.error == "string" && r.error !== "Skipped: no changes" ? r.error : null);
|
|
64
|
+
if (i) {
|
|
65
|
+
z(b(i));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!r) {
|
|
69
|
+
z(b(""));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
r.error === "Skipped: no changes" ? V("No changes detected — backup skipped") : V(`Backup completed: ${x(r.dbSizeBytes)} in ${S(r.durationMs)}`), await K();
|
|
60
73
|
} catch (e) {
|
|
61
|
-
|
|
74
|
+
z(b(e instanceof Error ? e.message : String(e)));
|
|
62
75
|
} finally {
|
|
63
|
-
|
|
76
|
+
L(null);
|
|
64
77
|
}
|
|
65
|
-
},
|
|
78
|
+
}, J = async (e, n) => {
|
|
66
79
|
try {
|
|
67
|
-
|
|
68
|
-
let r = await
|
|
80
|
+
L(`restore-${e}`), z(null);
|
|
81
|
+
let r = await G(t(E, "/backup/restore"), {
|
|
69
82
|
method: "POST",
|
|
70
83
|
body: JSON.stringify({
|
|
71
84
|
backupId: e,
|
|
72
85
|
dryRun: n
|
|
73
86
|
})
|
|
74
87
|
});
|
|
75
|
-
r.success ?
|
|
88
|
+
r.success ? V(r.message) : z(r.message);
|
|
76
89
|
} catch (e) {
|
|
77
|
-
|
|
90
|
+
z(e instanceof Error ? e.message : String(e));
|
|
78
91
|
} finally {
|
|
79
|
-
|
|
92
|
+
L(null);
|
|
80
93
|
}
|
|
81
|
-
},
|
|
94
|
+
}, Y = async () => {
|
|
82
95
|
try {
|
|
83
|
-
|
|
84
|
-
let e = await
|
|
96
|
+
L("toggle");
|
|
97
|
+
let e = await G(t(E, "/backup/config"), {
|
|
85
98
|
method: "PUT",
|
|
86
|
-
body: JSON.stringify({ enabled: !
|
|
99
|
+
body: JSON.stringify({ enabled: !O?.enabled })
|
|
87
100
|
});
|
|
88
|
-
|
|
101
|
+
k(e.config), V(e.config.enabled ? "Backup enabled" : "Backup disabled"), await K();
|
|
89
102
|
} catch (e) {
|
|
90
|
-
|
|
103
|
+
z(e instanceof Error ? e.message : String(e));
|
|
91
104
|
} finally {
|
|
92
|
-
|
|
105
|
+
L(null);
|
|
93
106
|
}
|
|
94
|
-
},
|
|
107
|
+
}, X = async () => {
|
|
95
108
|
try {
|
|
96
|
-
|
|
97
|
-
let e = await
|
|
98
|
-
e.ok ?
|
|
109
|
+
L("test");
|
|
110
|
+
let e = await G(t(E, "/backup/test-connection"), { method: "POST" });
|
|
111
|
+
e.ok ? V(`Connection OK: ${e.message}`) : z(`Connection failed: ${e.message}`);
|
|
99
112
|
} catch (e) {
|
|
100
|
-
|
|
113
|
+
z(e instanceof Error ? e.message : String(e));
|
|
101
114
|
} finally {
|
|
102
|
-
|
|
115
|
+
L(null);
|
|
103
116
|
}
|
|
104
|
-
},
|
|
117
|
+
}, Z = async (e) => {
|
|
105
118
|
try {
|
|
106
|
-
|
|
119
|
+
L(`delete-${e}`), await G(t(E, `/backup/backups/${e}`), { method: "DELETE" }), V("Backup deleted"), await K();
|
|
107
120
|
} catch (e) {
|
|
108
|
-
|
|
121
|
+
z(e instanceof Error ? e.message : String(e));
|
|
109
122
|
} finally {
|
|
110
|
-
|
|
123
|
+
L(null);
|
|
111
124
|
}
|
|
112
125
|
};
|
|
113
|
-
return
|
|
126
|
+
return P ? /* @__PURE__ */ y("div", {
|
|
114
127
|
className: "flex items-center justify-center py-12 text-text-secondary",
|
|
115
|
-
children: [/* @__PURE__ */ v(c, { className: "w-5 h-5 animate-spin mr-2" }),
|
|
128
|
+
children: [/* @__PURE__ */ v(c, { className: "w-5 h-5 animate-spin mr-2" }), D("vibecontrols.agents.backup.loading", "Loading backup settings...")]
|
|
116
129
|
}) : /* @__PURE__ */ y("div", {
|
|
117
130
|
className: "space-y-6",
|
|
118
131
|
children: [
|
|
119
|
-
|
|
132
|
+
R && /* @__PURE__ */ y("div", {
|
|
120
133
|
className: "flex items-center gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text",
|
|
121
134
|
children: [
|
|
122
135
|
/* @__PURE__ */ v(g, { className: "w-4 h-4 shrink-0" }),
|
|
123
|
-
|
|
136
|
+
R,
|
|
124
137
|
/* @__PURE__ */ v("button", {
|
|
125
|
-
onClick: () =>
|
|
138
|
+
onClick: () => z(null),
|
|
126
139
|
className: "ml-auto text-xs underline",
|
|
127
140
|
children: "Dismiss"
|
|
128
141
|
})
|
|
129
142
|
]
|
|
130
143
|
}),
|
|
131
|
-
|
|
144
|
+
B && /* @__PURE__ */ y("div", {
|
|
132
145
|
className: "flex items-center gap-2 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3 text-sm text-status-success-text",
|
|
133
|
-
children: [/* @__PURE__ */ v(a, { className: "w-4 h-4 shrink-0" }),
|
|
146
|
+
children: [/* @__PURE__ */ v(a, { className: "w-4 h-4 shrink-0" }), B]
|
|
134
147
|
}),
|
|
135
148
|
/* @__PURE__ */ y("div", {
|
|
136
149
|
className: "rounded-lg border border-border-default bg-bg-surface p-5",
|
|
@@ -141,17 +154,17 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
141
154
|
className: "flex items-center gap-2",
|
|
142
155
|
children: [/* @__PURE__ */ v(s, { className: "w-5 h-5 text-action-primary-bg" }), /* @__PURE__ */ v("h3", {
|
|
143
156
|
className: "font-semibold text-text-primary",
|
|
144
|
-
children:
|
|
157
|
+
children: D("vibecontrols.agents.backup.title", "Database Backup")
|
|
145
158
|
})]
|
|
146
159
|
}), /* @__PURE__ */ y("div", {
|
|
147
160
|
className: "flex items-center gap-2",
|
|
148
161
|
children: [/* @__PURE__ */ v("button", {
|
|
149
|
-
onClick:
|
|
162
|
+
onClick: K,
|
|
150
163
|
className: "p-2 rounded-lg hover:bg-bg-sunken transition-colors text-text-secondary",
|
|
151
164
|
title: "Refresh",
|
|
152
165
|
children: /* @__PURE__ */ v(u, { className: "w-4 h-4" })
|
|
153
166
|
}), /* @__PURE__ */ v("button", {
|
|
154
|
-
onClick: () =>
|
|
167
|
+
onClick: () => U(!H),
|
|
155
168
|
className: "p-2 rounded-lg hover:bg-bg-sunken transition-colors text-text-secondary",
|
|
156
169
|
title: "Settings",
|
|
157
170
|
children: /* @__PURE__ */ v(f, { className: "w-4 h-4" })
|
|
@@ -163,84 +176,84 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
163
176
|
children: [
|
|
164
177
|
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
165
178
|
className: "text-xs text-text-muted mb-1",
|
|
166
|
-
children:
|
|
179
|
+
children: D("vibecontrols.agents.backup.status", "Status")
|
|
167
180
|
}), /* @__PURE__ */ v("div", {
|
|
168
181
|
className: "flex items-center gap-1.5",
|
|
169
|
-
children:
|
|
182
|
+
children: O?.enabled ? /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("div", { className: "h-2 w-2 rounded-full bg-status-success-bg" }), /* @__PURE__ */ v("span", {
|
|
170
183
|
className: "text-sm font-medium text-text-primary",
|
|
171
|
-
children:
|
|
184
|
+
children: D("vibecontrols.agents.backup.enabled", "Enabled")
|
|
172
185
|
})] }) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("div", { className: "h-2 w-2 rounded-full bg-text-tertiary" }), /* @__PURE__ */ v("span", {
|
|
173
186
|
className: "text-sm font-medium text-text-secondary",
|
|
174
|
-
children:
|
|
187
|
+
children: D("vibecontrols.agents.backup.disabled", "Disabled")
|
|
175
188
|
})] })
|
|
176
189
|
})] }),
|
|
177
190
|
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
178
191
|
className: "text-xs text-text-muted mb-1",
|
|
179
|
-
children:
|
|
192
|
+
children: D("vibecontrols.agents.backup.scheduler", "Scheduler")
|
|
180
193
|
}), /* @__PURE__ */ v("span", {
|
|
181
194
|
className: "text-sm font-medium text-text-primary",
|
|
182
|
-
children:
|
|
195
|
+
children: A?.schedulerRunning ? "Running" : "Stopped"
|
|
183
196
|
})] }),
|
|
184
197
|
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
185
198
|
className: "text-xs text-text-muted mb-1",
|
|
186
|
-
children:
|
|
199
|
+
children: D("vibecontrols.agents.backup.totalBackups", "Total Backups")
|
|
187
200
|
}), /* @__PURE__ */ v("span", {
|
|
188
201
|
className: "text-sm font-medium text-text-primary",
|
|
189
|
-
children:
|
|
202
|
+
children: A?.totalBackups ?? 0
|
|
190
203
|
})] }),
|
|
191
204
|
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
192
205
|
className: "text-xs text-text-muted mb-1",
|
|
193
|
-
children:
|
|
206
|
+
children: D("vibecontrols.agents.backup.target", "Target")
|
|
194
207
|
}), /* @__PURE__ */ v("span", {
|
|
195
208
|
className: "text-sm font-medium text-text-primary",
|
|
196
|
-
children:
|
|
209
|
+
children: A?.storageTarget ?? "N/A"
|
|
197
210
|
})] })
|
|
198
211
|
]
|
|
199
212
|
}),
|
|
200
|
-
|
|
213
|
+
A?.lastBackupTime && /* @__PURE__ */ y("div", {
|
|
201
214
|
className: "flex flex-wrap items-center gap-3 text-xs text-text-muted",
|
|
202
215
|
children: [/* @__PURE__ */ y("span", { children: [
|
|
203
216
|
/* @__PURE__ */ v(o, { className: "w-3 h-3 inline mr-1" }),
|
|
204
217
|
"Last: ",
|
|
205
|
-
|
|
206
|
-
] }),
|
|
218
|
+
C(A.lastBackupTime)
|
|
219
|
+
] }), A.nextScheduledBackup && /* @__PURE__ */ y("span", { children: ["Next: ", C(A.nextScheduledBackup)] })]
|
|
207
220
|
}),
|
|
208
221
|
/* @__PURE__ */ y("div", {
|
|
209
222
|
className: "flex flex-wrap gap-2 mt-4 pt-4 border-t border-border-subtle",
|
|
210
223
|
children: [
|
|
211
224
|
/* @__PURE__ */ y("button", {
|
|
212
|
-
onClick: () =>
|
|
213
|
-
disabled: !!
|
|
225
|
+
onClick: () => q(!0),
|
|
226
|
+
disabled: !!I,
|
|
214
227
|
className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-action-primary-bg text-action-primary-text text-sm font-medium hover:bg-action-primary-bg-hover disabled:opacity-50 transition-colors",
|
|
215
|
-
children: [
|
|
228
|
+
children: [I === "backup" ? /* @__PURE__ */ v(c, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ v(m, { className: "w-4 h-4" }), D("vibecontrols.agents.backup.backupNow", "Backup Now")]
|
|
216
229
|
}),
|
|
217
230
|
/* @__PURE__ */ y("button", {
|
|
218
|
-
onClick: () =>
|
|
219
|
-
disabled: !!
|
|
231
|
+
onClick: () => q(!1),
|
|
232
|
+
disabled: !!I,
|
|
220
233
|
className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors",
|
|
221
|
-
children: [/* @__PURE__ */ v(l, { className: "w-4 h-4" }),
|
|
234
|
+
children: [/* @__PURE__ */ v(l, { className: "w-4 h-4" }), D("vibecontrols.agents.backup.backupIfChanged", "Backup if Changed")]
|
|
222
235
|
}),
|
|
223
236
|
/* @__PURE__ */ y("button", {
|
|
224
|
-
onClick:
|
|
225
|
-
disabled: !!
|
|
237
|
+
onClick: Y,
|
|
238
|
+
disabled: !!I,
|
|
226
239
|
className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors",
|
|
227
|
-
children: [
|
|
240
|
+
children: [I === "toggle" ? /* @__PURE__ */ v(c, { className: "w-4 h-4 animate-spin" }) : O?.enabled ? /* @__PURE__ */ v(g, { className: "w-4 h-4" }) : /* @__PURE__ */ v(a, { className: "w-4 h-4" }), O?.enabled ? D("vibecontrols.agents.backup.disableSchedule", "Disable Schedule") : D("vibecontrols.agents.backup.enableSchedule", "Enable Schedule")]
|
|
228
241
|
}),
|
|
229
242
|
/* @__PURE__ */ y("button", {
|
|
230
|
-
onClick:
|
|
231
|
-
disabled: !!
|
|
243
|
+
onClick: X,
|
|
244
|
+
disabled: !!I,
|
|
232
245
|
className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors",
|
|
233
|
-
children: [
|
|
246
|
+
children: [I === "test" ? /* @__PURE__ */ v(c, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ v(h, { className: "w-4 h-4" }), D("vibecontrols.agents.backup.testConnection", "Test Connection")]
|
|
234
247
|
})
|
|
235
248
|
]
|
|
236
249
|
})
|
|
237
250
|
]
|
|
238
251
|
}),
|
|
239
|
-
|
|
252
|
+
H && O && /* @__PURE__ */ y("div", {
|
|
240
253
|
className: "rounded-lg border border-border-default bg-bg-surface p-5",
|
|
241
254
|
children: [/* @__PURE__ */ y("h4", {
|
|
242
255
|
className: "font-semibold text-text-primary mb-3 flex items-center gap-2",
|
|
243
|
-
children: [/* @__PURE__ */ v(f, { className: "w-4 h-4" }),
|
|
256
|
+
children: [/* @__PURE__ */ v(f, { className: "w-4 h-4" }), D("vibecontrols.agents.backup.configuration", "Configuration")]
|
|
244
257
|
}), /* @__PURE__ */ y("div", {
|
|
245
258
|
className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm",
|
|
246
259
|
children: [
|
|
@@ -252,7 +265,7 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
252
265
|
" ",
|
|
253
266
|
/* @__PURE__ */ v("span", {
|
|
254
267
|
className: "text-text-primary font-medium",
|
|
255
|
-
children:
|
|
268
|
+
children: O.scheduleIntervalMs >= 36e5 ? `${O.scheduleIntervalMs / 36e5}h` : `${O.scheduleIntervalMs / 6e4}m`
|
|
256
269
|
})
|
|
257
270
|
] }),
|
|
258
271
|
/* @__PURE__ */ y("div", { children: [
|
|
@@ -263,7 +276,7 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
263
276
|
" ",
|
|
264
277
|
/* @__PURE__ */ v("span", {
|
|
265
278
|
className: "text-text-primary font-medium",
|
|
266
|
-
children:
|
|
279
|
+
children: O.changeOnlyBackup ? "On" : "Off"
|
|
267
280
|
})
|
|
268
281
|
] }),
|
|
269
282
|
/* @__PURE__ */ y("div", { children: [
|
|
@@ -274,10 +287,10 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
274
287
|
" ",
|
|
275
288
|
/* @__PURE__ */ v("span", {
|
|
276
289
|
className: "text-text-primary font-medium",
|
|
277
|
-
children:
|
|
290
|
+
children: O.storageTarget
|
|
278
291
|
})
|
|
279
292
|
] }),
|
|
280
|
-
|
|
293
|
+
O.customS3 && /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ y("div", { children: [
|
|
281
294
|
/* @__PURE__ */ v("span", {
|
|
282
295
|
className: "text-text-muted",
|
|
283
296
|
children: "S3 Bucket:"
|
|
@@ -285,7 +298,7 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
285
298
|
" ",
|
|
286
299
|
/* @__PURE__ */ v("span", {
|
|
287
300
|
className: "text-text-primary font-medium",
|
|
288
|
-
children:
|
|
301
|
+
children: O.customS3.bucket
|
|
289
302
|
})
|
|
290
303
|
] }), /* @__PURE__ */ y("div", { children: [
|
|
291
304
|
/* @__PURE__ */ v("span", {
|
|
@@ -295,10 +308,10 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
295
308
|
" ",
|
|
296
309
|
/* @__PURE__ */ v("span", {
|
|
297
310
|
className: "text-text-primary font-medium",
|
|
298
|
-
children:
|
|
311
|
+
children: O.customS3.region
|
|
299
312
|
})
|
|
300
313
|
] })] }),
|
|
301
|
-
|
|
314
|
+
O.retentionDays && /* @__PURE__ */ y("div", { children: [
|
|
302
315
|
/* @__PURE__ */ v("span", {
|
|
303
316
|
className: "text-text-muted",
|
|
304
317
|
children: "Retention:"
|
|
@@ -306,10 +319,10 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
306
319
|
" ",
|
|
307
320
|
/* @__PURE__ */ y("span", {
|
|
308
321
|
className: "text-text-primary font-medium",
|
|
309
|
-
children: [
|
|
322
|
+
children: [O.retentionDays, " days"]
|
|
310
323
|
})
|
|
311
324
|
] }),
|
|
312
|
-
|
|
325
|
+
O.maxBackups && /* @__PURE__ */ y("div", { children: [
|
|
313
326
|
/* @__PURE__ */ v("span", {
|
|
314
327
|
className: "text-text-muted",
|
|
315
328
|
children: "Max Backups:"
|
|
@@ -317,7 +330,7 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
317
330
|
" ",
|
|
318
331
|
/* @__PURE__ */ v("span", {
|
|
319
332
|
className: "text-text-primary font-medium",
|
|
320
|
-
children:
|
|
333
|
+
children: O.maxBackups
|
|
321
334
|
})
|
|
322
335
|
] })
|
|
323
336
|
]
|
|
@@ -329,22 +342,22 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
329
342
|
className: "font-semibold text-text-primary mb-3 flex items-center gap-2",
|
|
330
343
|
children: [
|
|
331
344
|
/* @__PURE__ */ v(o, { className: "w-4 h-4" }),
|
|
332
|
-
|
|
345
|
+
D("vibecontrols.agents.backup.history", "Backup History"),
|
|
333
346
|
/* @__PURE__ */ y("span", {
|
|
334
347
|
className: "text-xs text-text-muted font-normal",
|
|
335
348
|
children: [
|
|
336
349
|
"(",
|
|
337
|
-
|
|
350
|
+
M.length,
|
|
338
351
|
" shown)"
|
|
339
352
|
]
|
|
340
353
|
})
|
|
341
354
|
]
|
|
342
|
-
}),
|
|
355
|
+
}), M.length === 0 ? /* @__PURE__ */ v("p", {
|
|
343
356
|
className: "text-sm text-text-muted py-4 text-center",
|
|
344
|
-
children:
|
|
357
|
+
children: D("vibecontrols.agents.backup.noBackups", "No backups yet. Click \"Backup Now\" to create one.")
|
|
345
358
|
}) : /* @__PURE__ */ v("div", {
|
|
346
359
|
className: "space-y-2",
|
|
347
|
-
children:
|
|
360
|
+
children: M.map((e) => /* @__PURE__ */ y("div", {
|
|
348
361
|
className: "flex items-center justify-between p-3 rounded-lg border border-border-subtle hover:bg-bg-sunken transition-colors",
|
|
349
362
|
children: [/* @__PURE__ */ y("div", {
|
|
350
363
|
className: "flex items-center gap-3 min-w-0",
|
|
@@ -355,13 +368,13 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
355
368
|
className: "min-w-0",
|
|
356
369
|
children: [/* @__PURE__ */ v("p", {
|
|
357
370
|
className: "text-sm font-medium text-text-primary truncate",
|
|
358
|
-
children:
|
|
371
|
+
children: C(e.timestamp)
|
|
359
372
|
}), /* @__PURE__ */ y("p", {
|
|
360
373
|
className: "text-xs text-text-muted truncate",
|
|
361
374
|
children: [
|
|
362
|
-
|
|
375
|
+
x(e.dbSizeBytes),
|
|
363
376
|
" · ",
|
|
364
|
-
|
|
377
|
+
S(e.durationMs),
|
|
365
378
|
" ·",
|
|
366
379
|
" ",
|
|
367
380
|
e.storageTarget
|
|
@@ -372,27 +385,27 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
372
385
|
className: "flex items-center gap-1 shrink-0",
|
|
373
386
|
children: [
|
|
374
387
|
/* @__PURE__ */ v("button", {
|
|
375
|
-
onClick: () =>
|
|
376
|
-
disabled: !!
|
|
388
|
+
onClick: () => J(e.id, !0),
|
|
389
|
+
disabled: !!I,
|
|
377
390
|
className: "p-1.5 rounded hover:bg-bg-canvas text-text-secondary transition-colors",
|
|
378
391
|
title: "Validate (dry run)",
|
|
379
|
-
children:
|
|
392
|
+
children: I === `restore-${e.id}` ? /* @__PURE__ */ v(c, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ v(a, { className: "w-4 h-4" })
|
|
380
393
|
}),
|
|
381
394
|
/* @__PURE__ */ v("button", {
|
|
382
395
|
onClick: () => {
|
|
383
|
-
confirm("Restore this backup? The agent will need to restart.") &&
|
|
396
|
+
confirm("Restore this backup? The agent will need to restart.") && J(e.id, !1);
|
|
384
397
|
},
|
|
385
|
-
disabled: !!
|
|
398
|
+
disabled: !!I,
|
|
386
399
|
className: "p-1.5 rounded hover:bg-bg-canvas text-text-secondary transition-colors",
|
|
387
400
|
title: "Restore",
|
|
388
401
|
children: /* @__PURE__ */ v(d, { className: "w-4 h-4" })
|
|
389
402
|
}),
|
|
390
403
|
/* @__PURE__ */ v("button", {
|
|
391
|
-
onClick: () =>
|
|
392
|
-
disabled: !!
|
|
404
|
+
onClick: () => Z(e.id),
|
|
405
|
+
disabled: !!I,
|
|
393
406
|
className: "p-1.5 rounded text-text-secondary transition-colors hover:bg-status-error-bg/80 hover:text-status-error-text",
|
|
394
407
|
title: "Delete",
|
|
395
|
-
children:
|
|
408
|
+
children: I === `delete-${e.id}` ? /* @__PURE__ */ v(c, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ v(p, { className: "w-4 h-4" })
|
|
396
409
|
})
|
|
397
410
|
]
|
|
398
411
|
})]
|
|
@@ -403,6 +416,6 @@ function C({ agentTunnelUrl: C, agentApiKey: w, profile: T }) {
|
|
|
403
416
|
});
|
|
404
417
|
}
|
|
405
418
|
//#endregion
|
|
406
|
-
export {
|
|
419
|
+
export { w as AgentBackupTab };
|
|
407
420
|
|
|
408
421
|
//# sourceMappingURL=AgentBackupTab.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentBackupTab.js","names":[],"sources":["../../../../src/components/agents/backup/AgentBackupTab.tsx"],"sourcesContent":["/**\n * AgentBackupTab — UI for managing SQLite database backups of a VibeControls agent.\n * Talks directly to the agent's REST API via its tunnel URL.\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport {\n Database,\n Upload,\n RefreshCw,\n Settings,\n Play,\n Clock,\n CheckCircle2,\n XCircle,\n Loader2,\n Trash2,\n RotateCcw,\n Wifi,\n} from 'lucide-react';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { agentApiPath } from '@/utils/agentApiPath';\n\ninterface BackupConfig {\n enabled: boolean;\n scheduleIntervalMs: number;\n changeOnlyBackup: boolean;\n storageTarget: 'wspace' | 'custom-s3';\n customS3?: {\n endpoint: string;\n bucket: string;\n region: string;\n accessKeyId: string;\n secretAccessKey: string;\n pathPrefix?: string;\n };\n retentionDays?: number;\n maxBackups?: number;\n}\n\ninterface BackupRecord {\n id: string;\n timestamp: string;\n agentName: string;\n dbSizeBytes: number;\n sha256Hash: string;\n storageTarget: string;\n storagePath: string;\n durationMs: number;\n status: string;\n error?: string;\n}\n\ninterface BackupStatus {\n enabled: boolean;\n schedulerRunning: boolean;\n lastBackupTime: string | null;\n lastBackupHash: string | null;\n nextScheduledBackup: string | null;\n totalBackups: number;\n storageTarget: string;\n}\n\ninterface AgentBackupTabProps {\n agentTunnelUrl: string;\n agentApiKey: string;\n /** Canonical CLI profile name on the remote agent. */\n profile: string;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n return `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction formatTime(iso: string): string {\n return new Date(iso).toLocaleString();\n}\n\nexport function AgentBackupTab({ agentTunnelUrl, agentApiKey, profile }: AgentBackupTabProps) {\n const tr = useTr();\n const [config, setConfig] = useState<BackupConfig | null>(null);\n const [status, setStatus] = useState<BackupStatus | null>(null);\n const [backups, setBackups] = useState<BackupRecord[]>([]);\n const [loading, setLoading] = useState(true);\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [successMsg, setSuccessMsg] = useState<string | null>(null);\n const [showConfig, setShowConfig] = useState(false);\n\n const apiUrl = agentTunnelUrl.replace(/\\/+$/, '');\n\n const apiFetch = useCallback(\n async (path: string, options?: RequestInit) => {\n const res = await fetch(`${apiUrl}${path}`, {\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n 'x-agent-api-key': agentApiKey,\n ...options?.headers,\n },\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(text || `HTTP ${res.status}`);\n }\n return res.json();\n },\n [apiUrl, agentApiKey]\n );\n\n const refresh = useCallback(async () => {\n try {\n setLoading(true);\n setError(null);\n const [configRes, statusRes, listRes] = await Promise.all([\n apiFetch(agentApiPath(profile, '/backup/config')),\n apiFetch(agentApiPath(profile, '/backup/status')),\n apiFetch(`${agentApiPath(profile, '/backup/list')}?limit=20`),\n ]);\n setConfig(configRes.config);\n setStatus(statusRes.status);\n setBackups(listRes.backups || []);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n setError(\n msg === 'Failed to fetch'\n ? `Cannot reach agent REST API at ${apiUrl}. Ensure the agent is running and its tunnel URL is accessible.`\n : msg\n );\n } finally {\n setLoading(false);\n }\n }, [apiFetch, apiUrl]);\n\n useEffect(() => {\n refresh();\n }, [refresh]);\n\n // Clear success message after 3s\n useEffect(() => {\n if (successMsg) {\n const t = setTimeout(() => setSuccessMsg(null), 3000);\n return () => clearTimeout(t);\n }\n }, [successMsg]);\n\n const handleRunBackup = async (force: boolean) => {\n try {\n setActionLoading('backup');\n setError(null);\n const res = await apiFetch(`${agentApiPath(profile, '/backup/run')}?force=${force}`, {\n method: 'POST',\n });\n const b = res.backup;\n if (b.error === 'Skipped: no changes') {\n setSuccessMsg('No changes detected — backup skipped');\n } else {\n setSuccessMsg(\n `Backup completed: ${formatBytes(b.dbSizeBytes)} in ${formatDuration(b.durationMs)}`\n );\n }\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleRestore = async (backupId: string, dryRun: boolean) => {\n try {\n setActionLoading(`restore-${backupId}`);\n setError(null);\n const res = await apiFetch(agentApiPath(profile, '/backup/restore'), {\n method: 'POST',\n body: JSON.stringify({ backupId, dryRun }),\n });\n if (res.success) {\n setSuccessMsg(res.message);\n } else {\n setError(res.message);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleToggleEnabled = async () => {\n try {\n setActionLoading('toggle');\n const res = await apiFetch(agentApiPath(profile, '/backup/config'), {\n method: 'PUT',\n body: JSON.stringify({ enabled: !config?.enabled }),\n });\n setConfig(res.config);\n setSuccessMsg(res.config.enabled ? 'Backup enabled' : 'Backup disabled');\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleTestConnection = async () => {\n try {\n setActionLoading('test');\n const res = await apiFetch(agentApiPath(profile, '/backup/test-connection'), {\n method: 'POST',\n });\n if (res.ok) {\n setSuccessMsg(`Connection OK: ${res.message}`);\n } else {\n setError(`Connection failed: ${res.message}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleDeleteBackup = async (backupId: string) => {\n try {\n setActionLoading(`delete-${backupId}`);\n await apiFetch(agentApiPath(profile, `/backup/backups/${backupId}`), {\n method: 'DELETE',\n });\n setSuccessMsg('Backup deleted');\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12 text-text-secondary\">\n <Loader2 className=\"w-5 h-5 animate-spin mr-2\" />\n {tr('vibecontrols.agents.backup.loading', 'Loading backup settings...')}\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Status Messages */}\n {error && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\">\n <XCircle className=\"w-4 h-4 shrink-0\" />\n {error}\n <button onClick={() => setError(null)} className=\"ml-auto text-xs underline\">\n Dismiss\n </button>\n </div>\n )}\n {successMsg && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3 text-sm text-status-success-text\">\n <CheckCircle2 className=\"w-4 h-4 shrink-0\" />\n {successMsg}\n </div>\n )}\n\n {/* Status Card */}\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"flex items-center gap-2\">\n <Database className=\"w-5 h-5 text-action-primary-bg\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.agents.backup.title', 'Database Backup')}\n </h3>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={refresh}\n className=\"p-2 rounded-lg hover:bg-bg-sunken transition-colors text-text-secondary\"\n title=\"Refresh\"\n >\n <RefreshCw className=\"w-4 h-4\" />\n </button>\n <button\n onClick={() => setShowConfig(!showConfig)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken transition-colors text-text-secondary\"\n title=\"Settings\"\n >\n <Settings className=\"w-4 h-4\" />\n </button>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4 mb-4\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.status', 'Status')}\n </p>\n <div className=\"flex items-center gap-1.5\">\n {config?.enabled ? (\n <>\n <div className=\"h-2 w-2 rounded-full bg-status-success-bg\" />\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.agents.backup.enabled', 'Enabled')}\n </span>\n </>\n ) : (\n <>\n <div className=\"h-2 w-2 rounded-full bg-text-tertiary\" />\n <span className=\"text-sm font-medium text-text-secondary\">\n {tr('vibecontrols.agents.backup.disabled', 'Disabled')}\n </span>\n </>\n )}\n </div>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.scheduler', 'Scheduler')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.schedulerRunning ? 'Running' : 'Stopped'}\n </span>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.totalBackups', 'Total Backups')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.totalBackups ?? 0}\n </span>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.target', 'Target')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.storageTarget ?? 'N/A'}\n </span>\n </div>\n </div>\n\n {status?.lastBackupTime && (\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n <span>\n <Clock className=\"w-3 h-3 inline mr-1\" />\n Last: {formatTime(status.lastBackupTime)}\n </span>\n {status.nextScheduledBackup && (\n <span>Next: {formatTime(status.nextScheduledBackup)}</span>\n )}\n </div>\n )}\n\n {/* Action Buttons */}\n <div className=\"flex flex-wrap gap-2 mt-4 pt-4 border-t border-border-subtle\">\n <button\n onClick={() => handleRunBackup(true)}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-action-primary-bg text-action-primary-text text-sm font-medium hover:bg-action-primary-bg-hover disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'backup' ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Upload className=\"w-4 h-4\" />\n )}\n {tr('vibecontrols.agents.backup.backupNow', 'Backup Now')}\n </button>\n <button\n onClick={() => handleRunBackup(false)}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n <Play className=\"w-4 h-4\" />\n {tr('vibecontrols.agents.backup.backupIfChanged', 'Backup if Changed')}\n </button>\n <button\n onClick={handleToggleEnabled}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'toggle' ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : config?.enabled ? (\n <XCircle className=\"w-4 h-4\" />\n ) : (\n <CheckCircle2 className=\"w-4 h-4\" />\n )}\n {config?.enabled\n ? tr('vibecontrols.agents.backup.disableSchedule', 'Disable Schedule')\n : tr('vibecontrols.agents.backup.enableSchedule', 'Enable Schedule')}\n </button>\n <button\n onClick={handleTestConnection}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'test' ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Wifi className=\"w-4 h-4\" />\n )}\n {tr('vibecontrols.agents.backup.testConnection', 'Test Connection')}\n </button>\n </div>\n </div>\n\n {/* Configuration Panel (collapsible) */}\n {showConfig && config && (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <h4 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Settings className=\"w-4 h-4\" />\n {tr('vibecontrols.agents.backup.configuration', 'Configuration')}\n </h4>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4 text-sm\">\n <div>\n <span className=\"text-text-muted\">Schedule Interval:</span>{' '}\n <span className=\"text-text-primary font-medium\">\n {config.scheduleIntervalMs >= 3600000\n ? `${config.scheduleIntervalMs / 3600000}h`\n : `${config.scheduleIntervalMs / 60000}m`}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Change Detection:</span>{' '}\n <span className=\"text-text-primary font-medium\">\n {config.changeOnlyBackup ? 'On' : 'Off'}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Storage Target:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.storageTarget}</span>\n </div>\n {config.customS3 && (\n <>\n <div>\n <span className=\"text-text-muted\">S3 Bucket:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.customS3.bucket}</span>\n </div>\n <div>\n <span className=\"text-text-muted\">S3 Region:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.customS3.region}</span>\n </div>\n </>\n )}\n {config.retentionDays && (\n <div>\n <span className=\"text-text-muted\">Retention:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.retentionDays} days</span>\n </div>\n )}\n {config.maxBackups && (\n <div>\n <span className=\"text-text-muted\">Max Backups:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.maxBackups}</span>\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Backup History */}\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <h4 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Clock className=\"w-4 h-4\" />\n {tr('vibecontrols.agents.backup.history', 'Backup History')}\n <span className=\"text-xs text-text-muted font-normal\">({backups.length} shown)</span>\n </h4>\n\n {backups.length === 0 ? (\n <p className=\"text-sm text-text-muted py-4 text-center\">\n {tr(\n 'vibecontrols.agents.backup.noBackups',\n 'No backups yet. Click \"Backup Now\" to create one.'\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {backups.map((b) => (\n <div\n key={b.id}\n className=\"flex items-center justify-between p-3 rounded-lg border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div\n className={`w-8 h-8 rounded-lg flex items-center justify-center shrink-0 ${\n b.status === 'completed'\n ? 'border border-status-success-border bg-status-success-bg-subtle'\n : 'border border-status-error-border bg-status-error-bg-subtle'\n }`}\n >\n {b.status === 'completed' ? (\n <CheckCircle2 className=\"w-4 h-4 text-status-success-text\" />\n ) : (\n <XCircle className=\"w-4 h-4 text-status-error-text\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {formatTime(b.timestamp)}\n </p>\n <p className=\"text-xs text-text-muted truncate\">\n {formatBytes(b.dbSizeBytes)} · {formatDuration(b.durationMs)} ·{' '}\n {b.storageTarget}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <button\n onClick={() => handleRestore(b.id, true)}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded hover:bg-bg-canvas text-text-secondary transition-colors\"\n title=\"Validate (dry run)\"\n >\n {actionLoading === `restore-${b.id}` ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"w-4 h-4\" />\n )}\n </button>\n <button\n onClick={() => {\n if (confirm('Restore this backup? The agent will need to restart.')) {\n handleRestore(b.id, false);\n }\n }}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded hover:bg-bg-canvas text-text-secondary transition-colors\"\n title=\"Restore\"\n >\n <RotateCcw className=\"w-4 h-4\" />\n </button>\n <button\n onClick={() => handleDeleteBackup(b.id)}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded text-text-secondary transition-colors hover:bg-status-error-bg/80 hover:text-status-error-text\"\n title=\"Delete\"\n >\n {actionLoading === `delete-${b.id}` ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Trash2 className=\"w-4 h-4\" />\n )}\n </button>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentBackupTab.js","names":[],"sources":["../../../../src/components/agents/backup/AgentBackupTab.tsx"],"sourcesContent":["/**\n * AgentBackupTab — UI for managing SQLite database backups of a VibeControls agent.\n * Talks directly to the agent's REST API via its tunnel URL.\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport {\n Database,\n Upload,\n RefreshCw,\n Settings,\n Play,\n Clock,\n CheckCircle2,\n XCircle,\n Loader2,\n Trash2,\n RotateCcw,\n Wifi,\n} from 'lucide-react';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { agentApiPath } from '@/utils/agentApiPath';\n\n/**\n * Detect error strings that look like a raw JavaScript runtime exception\n * the agent leaked through to the platform — e.g.\n * `undefined is not an object (evaluating 'createResult.data.createFolder')`.\n * The actual root cause lives in the agent's backup plugin (it doesn't\n * forward x-workspace-id when calling files-svc, so the GraphQL response\n * has no `data` and the plugin blindly dereferences it). Until the agent\n * fix lands we strip the JS-frame and show the user something they can act\n * on instead of pasting a stack-trace fragment into the UI. [BOFF-2613]\n */\nfunction sanitizeBackupError(raw: string): string {\n if (!raw) return 'Backup failed.';\n const m = raw.toLowerCase();\n // JS runtime exception leaked from the agent.\n if (\n m.includes('undefined is not an object') ||\n m.includes('cannot read property') ||\n m.includes('cannot read properties of') ||\n /\\btypeerror:/.test(m) ||\n /\\breferenceerror:/.test(m)\n ) {\n // Specific, actionable hint for the known x-workspace-id case.\n if (raw.includes('createResult.data.createFolder')) {\n return \"Backup failed: the agent could not reach the workspace files service (missing workspace context). Re-push credentials from the agent's Security tab and try again. See agent logs for the full error.\";\n }\n return 'Backup failed: the agent threw an internal error. See agent logs for the full error.';\n }\n if (raw.length > 200) return raw.slice(0, 200) + '…';\n return raw;\n}\n\ninterface BackupConfig {\n enabled: boolean;\n scheduleIntervalMs: number;\n changeOnlyBackup: boolean;\n storageTarget: 'wspace' | 'custom-s3';\n customS3?: {\n endpoint: string;\n bucket: string;\n region: string;\n accessKeyId: string;\n secretAccessKey: string;\n pathPrefix?: string;\n };\n retentionDays?: number;\n maxBackups?: number;\n}\n\ninterface BackupRecord {\n id: string;\n timestamp: string;\n agentName: string;\n dbSizeBytes: number;\n sha256Hash: string;\n storageTarget: string;\n storagePath: string;\n durationMs: number;\n status: string;\n error?: string;\n}\n\ninterface BackupStatus {\n enabled: boolean;\n schedulerRunning: boolean;\n lastBackupTime: string | null;\n lastBackupHash: string | null;\n nextScheduledBackup: string | null;\n totalBackups: number;\n storageTarget: string;\n}\n\ninterface AgentBackupTabProps {\n agentTunnelUrl: string;\n agentApiKey: string;\n /** Canonical CLI profile name on the remote agent. */\n profile: string;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n return `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction formatTime(iso: string): string {\n return new Date(iso).toLocaleString();\n}\n\nexport function AgentBackupTab({ agentTunnelUrl, agentApiKey, profile }: AgentBackupTabProps) {\n const tr = useTr();\n const [config, setConfig] = useState<BackupConfig | null>(null);\n const [status, setStatus] = useState<BackupStatus | null>(null);\n const [backups, setBackups] = useState<BackupRecord[]>([]);\n const [loading, setLoading] = useState(true);\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [successMsg, setSuccessMsg] = useState<string | null>(null);\n const [showConfig, setShowConfig] = useState(false);\n\n const apiUrl = agentTunnelUrl.replace(/\\/+$/, '');\n\n const apiFetch = useCallback(\n async (path: string, options?: RequestInit) => {\n const res = await fetch(`${apiUrl}${path}`, {\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n 'x-agent-api-key': agentApiKey,\n ...options?.headers,\n },\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(text || `HTTP ${res.status}`);\n }\n return res.json();\n },\n [apiUrl, agentApiKey]\n );\n\n const refresh = useCallback(async () => {\n try {\n setLoading(true);\n setError(null);\n const [configRes, statusRes, listRes] = await Promise.all([\n apiFetch(agentApiPath(profile, '/backup/config')),\n apiFetch(agentApiPath(profile, '/backup/status')),\n apiFetch(`${agentApiPath(profile, '/backup/list')}?limit=20`),\n ]);\n setConfig(configRes.config);\n setStatus(statusRes.status);\n setBackups(listRes.backups || []);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n setError(\n msg === 'Failed to fetch'\n ? `Cannot reach agent REST API at ${apiUrl}. Ensure the agent is running and its tunnel URL is accessible.`\n : msg\n );\n } finally {\n setLoading(false);\n }\n }, [apiFetch, apiUrl]);\n\n useEffect(() => {\n refresh();\n }, [refresh]);\n\n // Clear success message after 3s\n useEffect(() => {\n if (successMsg) {\n const t = setTimeout(() => setSuccessMsg(null), 3000);\n return () => clearTimeout(t);\n }\n }, [successMsg]);\n\n const handleRunBackup = async (force: boolean) => {\n try {\n setActionLoading('backup');\n setError(null);\n const res = await apiFetch(`${agentApiPath(profile, '/backup/run')}?force=${force}`, {\n method: 'POST',\n });\n // The agent's `/backup/run` returns HTTP 200 even when the backup\n // failed internally — the failure surfaces as `res.error` (top-level)\n // or as `res.backup === null`. Treat both as failures here and surface\n // a sanitized message. [BOFF-2613]\n const b = res?.backup;\n const failureMessage: string | null =\n (typeof res?.error === 'string' && res.error) ||\n (b && typeof b.error === 'string' && b.error !== 'Skipped: no changes' ? b.error : null);\n\n if (failureMessage) {\n setError(sanitizeBackupError(failureMessage));\n return;\n }\n if (!b) {\n // No backup payload AND no error string — defensive.\n setError(sanitizeBackupError(''));\n return;\n }\n if (b.error === 'Skipped: no changes') {\n setSuccessMsg('No changes detected — backup skipped');\n } else {\n setSuccessMsg(\n `Backup completed: ${formatBytes(b.dbSizeBytes)} in ${formatDuration(b.durationMs)}`\n );\n }\n await refresh();\n } catch (err) {\n setError(sanitizeBackupError(err instanceof Error ? err.message : String(err)));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleRestore = async (backupId: string, dryRun: boolean) => {\n try {\n setActionLoading(`restore-${backupId}`);\n setError(null);\n const res = await apiFetch(agentApiPath(profile, '/backup/restore'), {\n method: 'POST',\n body: JSON.stringify({ backupId, dryRun }),\n });\n if (res.success) {\n setSuccessMsg(res.message);\n } else {\n setError(res.message);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleToggleEnabled = async () => {\n try {\n setActionLoading('toggle');\n const res = await apiFetch(agentApiPath(profile, '/backup/config'), {\n method: 'PUT',\n body: JSON.stringify({ enabled: !config?.enabled }),\n });\n setConfig(res.config);\n setSuccessMsg(res.config.enabled ? 'Backup enabled' : 'Backup disabled');\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleTestConnection = async () => {\n try {\n setActionLoading('test');\n const res = await apiFetch(agentApiPath(profile, '/backup/test-connection'), {\n method: 'POST',\n });\n if (res.ok) {\n setSuccessMsg(`Connection OK: ${res.message}`);\n } else {\n setError(`Connection failed: ${res.message}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleDeleteBackup = async (backupId: string) => {\n try {\n setActionLoading(`delete-${backupId}`);\n await apiFetch(agentApiPath(profile, `/backup/backups/${backupId}`), {\n method: 'DELETE',\n });\n setSuccessMsg('Backup deleted');\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12 text-text-secondary\">\n <Loader2 className=\"w-5 h-5 animate-spin mr-2\" />\n {tr('vibecontrols.agents.backup.loading', 'Loading backup settings...')}\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Status Messages */}\n {error && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\">\n <XCircle className=\"w-4 h-4 shrink-0\" />\n {error}\n <button onClick={() => setError(null)} className=\"ml-auto text-xs underline\">\n Dismiss\n </button>\n </div>\n )}\n {successMsg && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3 text-sm text-status-success-text\">\n <CheckCircle2 className=\"w-4 h-4 shrink-0\" />\n {successMsg}\n </div>\n )}\n\n {/* Status Card */}\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"flex items-center gap-2\">\n <Database className=\"w-5 h-5 text-action-primary-bg\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.agents.backup.title', 'Database Backup')}\n </h3>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={refresh}\n className=\"p-2 rounded-lg hover:bg-bg-sunken transition-colors text-text-secondary\"\n title=\"Refresh\"\n >\n <RefreshCw className=\"w-4 h-4\" />\n </button>\n <button\n onClick={() => setShowConfig(!showConfig)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken transition-colors text-text-secondary\"\n title=\"Settings\"\n >\n <Settings className=\"w-4 h-4\" />\n </button>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4 mb-4\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.status', 'Status')}\n </p>\n <div className=\"flex items-center gap-1.5\">\n {config?.enabled ? (\n <>\n <div className=\"h-2 w-2 rounded-full bg-status-success-bg\" />\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.agents.backup.enabled', 'Enabled')}\n </span>\n </>\n ) : (\n <>\n <div className=\"h-2 w-2 rounded-full bg-text-tertiary\" />\n <span className=\"text-sm font-medium text-text-secondary\">\n {tr('vibecontrols.agents.backup.disabled', 'Disabled')}\n </span>\n </>\n )}\n </div>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.scheduler', 'Scheduler')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.schedulerRunning ? 'Running' : 'Stopped'}\n </span>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.totalBackups', 'Total Backups')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.totalBackups ?? 0}\n </span>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.target', 'Target')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.storageTarget ?? 'N/A'}\n </span>\n </div>\n </div>\n\n {status?.lastBackupTime && (\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n <span>\n <Clock className=\"w-3 h-3 inline mr-1\" />\n Last: {formatTime(status.lastBackupTime)}\n </span>\n {status.nextScheduledBackup && (\n <span>Next: {formatTime(status.nextScheduledBackup)}</span>\n )}\n </div>\n )}\n\n {/* Action Buttons */}\n <div className=\"flex flex-wrap gap-2 mt-4 pt-4 border-t border-border-subtle\">\n <button\n onClick={() => handleRunBackup(true)}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-action-primary-bg text-action-primary-text text-sm font-medium hover:bg-action-primary-bg-hover disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'backup' ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Upload className=\"w-4 h-4\" />\n )}\n {tr('vibecontrols.agents.backup.backupNow', 'Backup Now')}\n </button>\n <button\n onClick={() => handleRunBackup(false)}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n <Play className=\"w-4 h-4\" />\n {tr('vibecontrols.agents.backup.backupIfChanged', 'Backup if Changed')}\n </button>\n <button\n onClick={handleToggleEnabled}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'toggle' ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : config?.enabled ? (\n <XCircle className=\"w-4 h-4\" />\n ) : (\n <CheckCircle2 className=\"w-4 h-4\" />\n )}\n {config?.enabled\n ? tr('vibecontrols.agents.backup.disableSchedule', 'Disable Schedule')\n : tr('vibecontrols.agents.backup.enableSchedule', 'Enable Schedule')}\n </button>\n <button\n onClick={handleTestConnection}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'test' ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Wifi className=\"w-4 h-4\" />\n )}\n {tr('vibecontrols.agents.backup.testConnection', 'Test Connection')}\n </button>\n </div>\n </div>\n\n {/* Configuration Panel (collapsible) */}\n {showConfig && config && (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <h4 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Settings className=\"w-4 h-4\" />\n {tr('vibecontrols.agents.backup.configuration', 'Configuration')}\n </h4>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4 text-sm\">\n <div>\n <span className=\"text-text-muted\">Schedule Interval:</span>{' '}\n <span className=\"text-text-primary font-medium\">\n {config.scheduleIntervalMs >= 3600000\n ? `${config.scheduleIntervalMs / 3600000}h`\n : `${config.scheduleIntervalMs / 60000}m`}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Change Detection:</span>{' '}\n <span className=\"text-text-primary font-medium\">\n {config.changeOnlyBackup ? 'On' : 'Off'}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Storage Target:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.storageTarget}</span>\n </div>\n {config.customS3 && (\n <>\n <div>\n <span className=\"text-text-muted\">S3 Bucket:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.customS3.bucket}</span>\n </div>\n <div>\n <span className=\"text-text-muted\">S3 Region:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.customS3.region}</span>\n </div>\n </>\n )}\n {config.retentionDays && (\n <div>\n <span className=\"text-text-muted\">Retention:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.retentionDays} days</span>\n </div>\n )}\n {config.maxBackups && (\n <div>\n <span className=\"text-text-muted\">Max Backups:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.maxBackups}</span>\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Backup History */}\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <h4 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Clock className=\"w-4 h-4\" />\n {tr('vibecontrols.agents.backup.history', 'Backup History')}\n <span className=\"text-xs text-text-muted font-normal\">({backups.length} shown)</span>\n </h4>\n\n {backups.length === 0 ? (\n <p className=\"text-sm text-text-muted py-4 text-center\">\n {tr(\n 'vibecontrols.agents.backup.noBackups',\n 'No backups yet. Click \"Backup Now\" to create one.'\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {backups.map((b) => (\n <div\n key={b.id}\n className=\"flex items-center justify-between p-3 rounded-lg border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div\n className={`w-8 h-8 rounded-lg flex items-center justify-center shrink-0 ${\n b.status === 'completed'\n ? 'border border-status-success-border bg-status-success-bg-subtle'\n : 'border border-status-error-border bg-status-error-bg-subtle'\n }`}\n >\n {b.status === 'completed' ? (\n <CheckCircle2 className=\"w-4 h-4 text-status-success-text\" />\n ) : (\n <XCircle className=\"w-4 h-4 text-status-error-text\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {formatTime(b.timestamp)}\n </p>\n <p className=\"text-xs text-text-muted truncate\">\n {formatBytes(b.dbSizeBytes)} · {formatDuration(b.durationMs)} ·{' '}\n {b.storageTarget}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <button\n onClick={() => handleRestore(b.id, true)}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded hover:bg-bg-canvas text-text-secondary transition-colors\"\n title=\"Validate (dry run)\"\n >\n {actionLoading === `restore-${b.id}` ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"w-4 h-4\" />\n )}\n </button>\n <button\n onClick={() => {\n if (confirm('Restore this backup? The agent will need to restart.')) {\n handleRestore(b.id, false);\n }\n }}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded hover:bg-bg-canvas text-text-secondary transition-colors\"\n title=\"Restore\"\n >\n <RotateCcw className=\"w-4 h-4\" />\n </button>\n <button\n onClick={() => handleDeleteBackup(b.id)}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded text-text-secondary transition-colors hover:bg-status-error-bg/80 hover:text-status-error-text\"\n title=\"Delete\"\n >\n {actionLoading === `delete-${b.id}` ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Trash2 className=\"w-4 h-4\" />\n )}\n </button>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,57 +1,63 @@
|
|
|
1
1
|
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
2
|
import { agentAiGet as t, agentAiMutate as n } from "../../services/aiApi.js";
|
|
3
|
-
import { validateAliasName as
|
|
4
|
-
import { useCallback as
|
|
5
|
-
import { Check as ne, Loader2 as a,
|
|
3
|
+
import { validateAliasName as ee } from "./aliasReservedNames.js";
|
|
4
|
+
import { useCallback as r, useEffect as te, useState as i } from "react";
|
|
5
|
+
import { Check as ne, Loader2 as a, PackageOpen as re, Pencil as ie, Play as o, Plus as s, Terminal as ae, Trash2 as c, X as l } from "lucide-react";
|
|
6
6
|
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/aliases/AliasManager.tsx
|
|
8
|
+
function oe(e) {
|
|
9
|
+
if (!e) return !1;
|
|
10
|
+
let t = e.toLowerCase();
|
|
11
|
+
return t.includes("not installed on the agent") || t.includes("not_found") || t.includes(" 404");
|
|
12
|
+
}
|
|
8
13
|
var p = 80;
|
|
9
|
-
function
|
|
14
|
+
function se(e, t = p) {
|
|
10
15
|
return e ? e.length > t ? `${e.slice(0, t)}…` : e : "";
|
|
11
16
|
}
|
|
12
17
|
function m({ agent: p }) {
|
|
13
|
-
let m = e(), h = `/api/profiles/${encodeURIComponent(p.profile ?? "default")}/aliases`, [g,
|
|
14
|
-
_(!0), y(null);
|
|
18
|
+
let m = e(), h = `/api/profiles/${encodeURIComponent(p.profile ?? "default")}/aliases`, [g, ce] = i([]), [le, _] = i(!0), [v, y] = i(null), [ue, b] = i(!1), [x, S] = i(""), [C, w] = i(""), [T, E] = i(""), [D, O] = i(!1), [k, A] = i(null), [de, j] = i(null), [M, N] = i(""), [P, F] = i(""), [I, L] = i(!1), [R, z] = i(null), [B, V] = i(""), [H, U] = i(null), [W, G] = i(!1), [K, q] = i(null), [J, Y] = i(null), [X, Z] = i(!1), Q = r(async () => {
|
|
19
|
+
_(!0), y(null), b(!1);
|
|
15
20
|
try {
|
|
16
21
|
let e = await t(p.id, h);
|
|
17
|
-
|
|
22
|
+
ce(Array.isArray(e) ? e : e.items ?? []);
|
|
18
23
|
} catch (e) {
|
|
19
|
-
|
|
24
|
+
let t = e instanceof Error ? e.message : String(e);
|
|
25
|
+
oe(t) ? b(!0) : y(t);
|
|
20
26
|
} finally {
|
|
21
27
|
_(!1);
|
|
22
28
|
}
|
|
23
|
-
}, [p.id]);
|
|
29
|
+
}, [p.id, h]);
|
|
24
30
|
te(() => {
|
|
25
31
|
Q();
|
|
26
32
|
}, [Q]);
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
-
let e =
|
|
33
|
+
let fe = async () => {
|
|
34
|
+
A(null);
|
|
35
|
+
let e = ee(x.trim());
|
|
30
36
|
if (e) {
|
|
31
|
-
|
|
37
|
+
A(e);
|
|
32
38
|
return;
|
|
33
39
|
}
|
|
34
|
-
if (!
|
|
35
|
-
|
|
40
|
+
if (!C.trim()) {
|
|
41
|
+
A(m("aliases.commandRequired", "Command is required"));
|
|
36
42
|
return;
|
|
37
43
|
}
|
|
38
|
-
|
|
44
|
+
O(!0);
|
|
39
45
|
try {
|
|
40
46
|
await n(p.id, h, "POST", {
|
|
41
|
-
name:
|
|
42
|
-
command:
|
|
43
|
-
description:
|
|
44
|
-
}),
|
|
47
|
+
name: x.trim(),
|
|
48
|
+
command: C,
|
|
49
|
+
description: T || void 0
|
|
50
|
+
}), S(""), w(""), E(""), await Q();
|
|
45
51
|
} catch (e) {
|
|
46
|
-
|
|
52
|
+
A(e instanceof Error ? e.message : String(e));
|
|
47
53
|
} finally {
|
|
48
|
-
|
|
54
|
+
O(!1);
|
|
49
55
|
}
|
|
50
|
-
},
|
|
56
|
+
}, pe = (e) => {
|
|
51
57
|
j(e.name), N(e.command), F(e.description ?? "");
|
|
52
58
|
}, $ = () => {
|
|
53
59
|
j(null), N(""), F("");
|
|
54
|
-
},
|
|
60
|
+
}, me = async (e) => {
|
|
55
61
|
L(!0);
|
|
56
62
|
try {
|
|
57
63
|
await n(p.id, `${h}/${encodeURIComponent(e.name)}`, "PUT", {
|
|
@@ -63,7 +69,7 @@ function m({ agent: p }) {
|
|
|
63
69
|
} finally {
|
|
64
70
|
L(!1);
|
|
65
71
|
}
|
|
66
|
-
},
|
|
72
|
+
}, he = async () => {
|
|
67
73
|
if (J) {
|
|
68
74
|
Z(!0);
|
|
69
75
|
try {
|
|
@@ -74,7 +80,7 @@ function m({ agent: p }) {
|
|
|
74
80
|
Z(!1);
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
|
-
},
|
|
83
|
+
}, ge = async () => {
|
|
78
84
|
if (R) {
|
|
79
85
|
G(!0), q(null), U(null);
|
|
80
86
|
try {
|
|
@@ -87,7 +93,26 @@ function m({ agent: p }) {
|
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
95
|
};
|
|
90
|
-
return /* @__PURE__ */ f("div", {
|
|
96
|
+
return ue ? /* @__PURE__ */ f("div", {
|
|
97
|
+
className: "rounded-lg border border-border-default bg-bg-surface p-8 text-center space-y-3",
|
|
98
|
+
children: [
|
|
99
|
+
/* @__PURE__ */ d(re, { className: "w-8 h-8 mx-auto text-text-muted" }),
|
|
100
|
+
/* @__PURE__ */ d("h3", {
|
|
101
|
+
className: "text-base font-medium text-text-primary",
|
|
102
|
+
children: m("aliases.pluginMissingTitle", "Aliases plugin not installed")
|
|
103
|
+
}),
|
|
104
|
+
/* @__PURE__ */ d("p", {
|
|
105
|
+
className: "text-sm text-text-secondary max-w-md mx-auto",
|
|
106
|
+
children: m("aliases.pluginMissingBody", "This agent doesn't have the aliases plugin installed yet. Install @vibecontrols/vibe-plugin-aliases from the Plugins tab to enable per-agent shell aliases.")
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ d("button", {
|
|
109
|
+
type: "button",
|
|
110
|
+
onClick: Q,
|
|
111
|
+
className: "inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-text-secondary border border-border-default rounded-md hover:bg-bg-sunken transition-colors",
|
|
112
|
+
children: m("aliases.retry", "Retry")
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
}) : /* @__PURE__ */ f("div", {
|
|
91
116
|
className: "space-y-4",
|
|
92
117
|
children: [
|
|
93
118
|
/* @__PURE__ */ f("div", {
|
|
@@ -101,43 +126,43 @@ function m({ agent: p }) {
|
|
|
101
126
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-3",
|
|
102
127
|
children: [/* @__PURE__ */ d("input", {
|
|
103
128
|
type: "text",
|
|
104
|
-
value:
|
|
105
|
-
onChange: (e) =>
|
|
129
|
+
value: x,
|
|
130
|
+
onChange: (e) => S(e.target.value),
|
|
106
131
|
placeholder: m("aliases.namePlaceholder", "Name (a-z, 0-9, _, -)"),
|
|
107
132
|
className: "px-3 py-2 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono"
|
|
108
133
|
}), /* @__PURE__ */ d("input", {
|
|
109
134
|
type: "text",
|
|
110
|
-
value:
|
|
111
|
-
onChange: (e) =>
|
|
135
|
+
value: T,
|
|
136
|
+
onChange: (e) => E(e.target.value),
|
|
112
137
|
placeholder: m("aliases.descriptionPlaceholder", "Description (optional)"),
|
|
113
138
|
className: "px-3 py-2 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg"
|
|
114
139
|
})]
|
|
115
140
|
}),
|
|
116
141
|
/* @__PURE__ */ d("textarea", {
|
|
117
|
-
value:
|
|
118
|
-
onChange: (e) =>
|
|
142
|
+
value: C,
|
|
143
|
+
onChange: (e) => w(e.target.value),
|
|
119
144
|
placeholder: m("aliases.commandPlaceholder", "Command (e.g. git status)"),
|
|
120
145
|
rows: 3,
|
|
121
146
|
className: "w-full px-3 py-2 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono"
|
|
122
147
|
}),
|
|
123
|
-
|
|
148
|
+
k && /* @__PURE__ */ d("div", {
|
|
124
149
|
className: "text-xs text-status-error-text",
|
|
125
|
-
children:
|
|
150
|
+
children: k
|
|
126
151
|
}),
|
|
127
152
|
/* @__PURE__ */ d("div", {
|
|
128
153
|
className: "flex justify-end",
|
|
129
154
|
children: /* @__PURE__ */ f("button", {
|
|
130
|
-
onClick:
|
|
131
|
-
disabled:
|
|
155
|
+
onClick: fe,
|
|
156
|
+
disabled: D || !x.trim() || !C.trim(),
|
|
132
157
|
className: "inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50",
|
|
133
|
-
children: [
|
|
158
|
+
children: [D ? /* @__PURE__ */ d(a, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ d(s, { className: "w-4 h-4" }), m("aliases.create", "Create")]
|
|
134
159
|
})
|
|
135
160
|
})
|
|
136
161
|
]
|
|
137
162
|
}),
|
|
138
163
|
/* @__PURE__ */ d("div", {
|
|
139
164
|
className: "rounded-lg border border-border-default overflow-hidden",
|
|
140
|
-
children:
|
|
165
|
+
children: le ? /* @__PURE__ */ d("div", {
|
|
141
166
|
className: "p-8 text-center text-text-secondary",
|
|
142
167
|
children: /* @__PURE__ */ d(a, { className: "w-5 h-5 animate-spin mx-auto" })
|
|
143
168
|
}) : v ? /* @__PURE__ */ d("div", {
|
|
@@ -173,7 +198,7 @@ function m({ agent: p }) {
|
|
|
173
198
|
})
|
|
174
199
|
] })
|
|
175
200
|
}), /* @__PURE__ */ d("tbody", { children: g.map((e) => {
|
|
176
|
-
let t =
|
|
201
|
+
let t = de === e.name;
|
|
177
202
|
return /* @__PURE__ */ f("tr", {
|
|
178
203
|
className: "border-t border-border-default",
|
|
179
204
|
children: [
|
|
@@ -191,7 +216,7 @@ function m({ agent: p }) {
|
|
|
191
216
|
}) : /* @__PURE__ */ d("code", {
|
|
192
217
|
className: "text-xs text-text-primary",
|
|
193
218
|
title: e.command,
|
|
194
|
-
children:
|
|
219
|
+
children: se(e.command)
|
|
195
220
|
})
|
|
196
221
|
}),
|
|
197
222
|
/* @__PURE__ */ d("td", {
|
|
@@ -214,7 +239,7 @@ function m({ agent: p }) {
|
|
|
214
239
|
children: /* @__PURE__ */ d("div", {
|
|
215
240
|
className: "flex items-center justify-end gap-1",
|
|
216
241
|
children: t ? /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d("button", {
|
|
217
|
-
onClick: () => void
|
|
242
|
+
onClick: () => void me(e),
|
|
218
243
|
disabled: I,
|
|
219
244
|
className: "p-1.5 rounded hover:bg-bg-sunken text-status-success-text",
|
|
220
245
|
title: m("aliases.save", "Save"),
|
|
@@ -234,10 +259,10 @@ function m({ agent: p }) {
|
|
|
234
259
|
children: /* @__PURE__ */ d(o, { className: "w-4 h-4" })
|
|
235
260
|
}),
|
|
236
261
|
/* @__PURE__ */ d("button", {
|
|
237
|
-
onClick: () =>
|
|
262
|
+
onClick: () => pe(e),
|
|
238
263
|
className: "p-1.5 rounded hover:bg-bg-sunken text-text-secondary",
|
|
239
264
|
title: m("aliases.edit", "Edit"),
|
|
240
|
-
children: /* @__PURE__ */ d(
|
|
265
|
+
children: /* @__PURE__ */ d(ie, { className: "w-4 h-4" })
|
|
241
266
|
}),
|
|
242
267
|
/* @__PURE__ */ d("button", {
|
|
243
268
|
onClick: () => Y(e),
|
|
@@ -263,7 +288,7 @@ function m({ agent: p }) {
|
|
|
263
288
|
children: [/* @__PURE__ */ f("h2", {
|
|
264
289
|
className: "text-lg font-semibold text-text-primary flex items-center gap-2",
|
|
265
290
|
children: [
|
|
266
|
-
/* @__PURE__ */ d(
|
|
291
|
+
/* @__PURE__ */ d(ae, { className: "w-5 h-5" }),
|
|
267
292
|
m("aliases.runTitle", "Run alias"),
|
|
268
293
|
": ",
|
|
269
294
|
/* @__PURE__ */ d("code", { children: R.name })
|
|
@@ -332,7 +357,7 @@ function m({ agent: p }) {
|
|
|
332
357
|
className: "px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated",
|
|
333
358
|
children: m("common.close", "Close")
|
|
334
359
|
}), /* @__PURE__ */ f("button", {
|
|
335
|
-
onClick: () => void
|
|
360
|
+
onClick: () => void ge(),
|
|
336
361
|
disabled: W,
|
|
337
362
|
className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover disabled:opacity-50",
|
|
338
363
|
children: [W ? /* @__PURE__ */ d(a, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ d(o, { className: "w-4 h-4" }), m("aliases.run", "Run")]
|
|
@@ -368,7 +393,7 @@ function m({ agent: p }) {
|
|
|
368
393
|
className: "px-3 py-1.5 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary hover:bg-bg-elevated",
|
|
369
394
|
children: m("common.cancel", "Cancel")
|
|
370
395
|
}), /* @__PURE__ */ f("button", {
|
|
371
|
-
onClick: () => void
|
|
396
|
+
onClick: () => void he(),
|
|
372
397
|
disabled: X,
|
|
373
398
|
className: "inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-status-error-text bg-status-error-bg rounded-md hover:opacity-80 disabled:opacity-50",
|
|
374
399
|
children: [X ? /* @__PURE__ */ d(a, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ d(c, { className: "w-4 h-4" }), m("aliases.delete", "Delete")]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AliasManager.js","names":[],"sources":["../../../src/components/aliases/AliasManager.tsx"],"sourcesContent":["/**\n * AliasManager — per-agent CRUDL for shell aliases exposed by\n * vibecontrols-agent under `/api/aliases/*`.\n *\n * Pairs with the agent's Phase A4 ship.\n *\n * Endpoints (proxied through wspace-vibecontrols-svc → agent):\n * GET /api/aliases → { items: Alias[] }\n * GET /api/aliases/:name → Alias\n * POST /api/aliases → Alias\n * PUT /api/aliases/:name → Alias\n * DELETE /api/aliases/:name\n * POST /api/aliases/:name/execute → { exit_code, stdout, stderr }\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { Plus, Trash2, Play, Pencil, Check, X, Loader2, Terminal } from 'lucide-react';\nimport { agentAiGet, agentAiMutate } from '@/services/aiApi';\nimport { useTr } from '@/shared/hooks/useTr';\nimport { validateAliasName } from './aliasReservedNames';\n\ninterface Alias {\n name: string;\n command: string;\n description?: string | null;\n updatedAt?: string | null;\n createdAt?: string | null;\n}\n\ninterface AliasListResponse {\n items?: Alias[];\n}\n\ninterface AliasExecuteResult {\n exit_code: number;\n stdout: string;\n stderr: string;\n}\n\ninterface AliasManagerProps {\n agent: { id: string; profile?: string };\n}\n\nconst TRUNCATE = 80;\n\nfunction truncate(str: string, n = TRUNCATE): string {\n if (!str) return '';\n return str.length > n ? `${str.slice(0, n)}…` : str;\n}\n\nexport function AliasManager({ agent }: AliasManagerProps) {\n const tr = useTr();\n // Canonical agent URL hierarchy: agent → profile → plugins/data.\n // The aliases plugin lives under `/api/profiles/<profile>/aliases/*`.\n // No client-side rewrites — every consumer constructs the canonical\n // path inline so the URL the agent sees is the URL the FE wrote.\n const profile = encodeURIComponent(agent.profile ?? 'default');\n const aliasesBase = `/api/profiles/${profile}/aliases`;\n const [aliases, setAliases] = useState<Alias[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n // Add-new form\n const [newName, setNewName] = useState('');\n const [newCommand, setNewCommand] = useState('');\n const [newDescription, setNewDescription] = useState('');\n const [creating, setCreating] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n\n // Edit row\n const [editingName, setEditingName] = useState<string | null>(null);\n const [editCommand, setEditCommand] = useState('');\n const [editDescription, setEditDescription] = useState('');\n const [savingEdit, setSavingEdit] = useState(false);\n\n // Run modal\n const [runTarget, setRunTarget] = useState<Alias | null>(null);\n const [runArgs, setRunArgs] = useState('');\n const [runResult, setRunResult] = useState<AliasExecuteResult | null>(null);\n const [runRunning, setRunRunning] = useState(false);\n const [runError, setRunError] = useState<string | null>(null);\n\n // Delete confirm\n const [deleteTarget, setDeleteTarget] = useState<Alias | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n const refresh = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const data = await agentAiGet<AliasListResponse | Alias[]>(agent.id, aliasesBase);\n const items = Array.isArray(data) ? data : (data.items ?? []);\n setAliases(items);\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n setLoading(false);\n }\n }, [agent.id]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n // ── Create ────────────────────────────────────────────────────────\n const handleCreate = async () => {\n setCreateError(null);\n const nameErr = validateAliasName(newName.trim());\n if (nameErr) {\n setCreateError(nameErr);\n return;\n }\n if (!newCommand.trim()) {\n setCreateError(tr('aliases.commandRequired', 'Command is required'));\n return;\n }\n setCreating(true);\n try {\n await agentAiMutate(agent.id, aliasesBase, 'POST', {\n name: newName.trim(),\n command: newCommand,\n description: newDescription || undefined,\n });\n setNewName('');\n setNewCommand('');\n setNewDescription('');\n await refresh();\n } catch (e) {\n setCreateError(e instanceof Error ? e.message : String(e));\n } finally {\n setCreating(false);\n }\n };\n\n // ── Edit ──────────────────────────────────────────────────────────\n const startEdit = (alias: Alias) => {\n setEditingName(alias.name);\n setEditCommand(alias.command);\n setEditDescription(alias.description ?? '');\n };\n\n const cancelEdit = () => {\n setEditingName(null);\n setEditCommand('');\n setEditDescription('');\n };\n\n const saveEdit = async (alias: Alias) => {\n setSavingEdit(true);\n try {\n await agentAiMutate(agent.id, `${aliasesBase}/${encodeURIComponent(alias.name)}`, 'PUT', {\n command: editCommand,\n description: editDescription || undefined,\n });\n cancelEdit();\n await refresh();\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n setSavingEdit(false);\n }\n };\n\n // ── Delete ────────────────────────────────────────────────────────\n const confirmDelete = async () => {\n if (!deleteTarget) return;\n setDeleting(true);\n try {\n await agentAiMutate(\n agent.id,\n `${aliasesBase}/${encodeURIComponent(deleteTarget.name)}`,\n 'DELETE'\n );\n setDeleteTarget(null);\n await refresh();\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n setDeleting(false);\n }\n };\n\n // ── Run ───────────────────────────────────────────────────────────\n const runAlias = async () => {\n if (!runTarget) return;\n setRunRunning(true);\n setRunError(null);\n setRunResult(null);\n try {\n const args = runArgs.split(/\\s+/).filter(Boolean);\n const result = await agentAiMutate<AliasExecuteResult>(\n agent.id,\n `${aliasesBase}/${encodeURIComponent(runTarget.name)}/execute`,\n 'POST',\n { args }\n );\n setRunResult(result);\n } catch (e) {\n setRunError(e instanceof Error ? e.message : String(e));\n } finally {\n setRunRunning(false);\n }\n };\n\n return (\n <div className=\"space-y-4\">\n {/* Add new alias form */}\n <div className=\"rounded-lg border border-border-default bg-bg-sunken p-4 space-y-3\">\n <h3 className=\"text-sm font-medium text-text-primary flex items-center gap-2\">\n <Plus className=\"w-4 h-4\" />\n {tr('aliases.createTitle', 'New alias')}\n </h3>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <input\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n placeholder={tr('aliases.namePlaceholder', 'Name (a-z, 0-9, _, -)')}\n className=\"px-3 py-2 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n />\n <input\n type=\"text\"\n value={newDescription}\n onChange={(e) => setNewDescription(e.target.value)}\n placeholder={tr('aliases.descriptionPlaceholder', 'Description (optional)')}\n className=\"px-3 py-2 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n <textarea\n value={newCommand}\n onChange={(e) => setNewCommand(e.target.value)}\n placeholder={tr('aliases.commandPlaceholder', 'Command (e.g. git status)')}\n rows={3}\n className=\"w-full px-3 py-2 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n />\n {createError && <div className=\"text-xs text-status-error-text\">{createError}</div>}\n <div className=\"flex justify-end\">\n <button\n onClick={handleCreate}\n disabled={creating || !newName.trim() || !newCommand.trim()}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {creating ? <Loader2 className=\"w-4 h-4 animate-spin\" /> : <Plus className=\"w-4 h-4\" />}\n {tr('aliases.create', 'Create')}\n </button>\n </div>\n </div>\n\n {/* List */}\n <div className=\"rounded-lg border border-border-default overflow-hidden\">\n {loading ? (\n <div className=\"p-8 text-center text-text-secondary\">\n <Loader2 className=\"w-5 h-5 animate-spin mx-auto\" />\n </div>\n ) : error ? (\n <div className=\"p-4 text-sm text-status-error-text\">{error}</div>\n ) : aliases.length === 0 ? (\n <div className=\"p-8 text-center text-sm text-text-secondary\">\n {tr('aliases.empty', 'No aliases yet. Create one above.')}\n </div>\n ) : (\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken text-text-secondary\">\n <tr>\n <th className=\"text-left px-3 py-2 font-medium\">{tr('aliases.colName', 'Name')}</th>\n <th className=\"text-left px-3 py-2 font-medium\">\n {tr('aliases.colCommand', 'Command')}\n </th>\n <th className=\"text-left px-3 py-2 font-medium\">\n {tr('aliases.colDescription', 'Description')}\n </th>\n <th className=\"text-left px-3 py-2 font-medium\">\n {tr('aliases.colUpdatedAt', 'Updated')}\n </th>\n <th className=\"text-right px-3 py-2 font-medium\">\n {tr('aliases.colActions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {aliases.map((alias) => {\n const isEditing = editingName === alias.name;\n return (\n <tr key={alias.name} className=\"border-t border-border-default\">\n <td className=\"px-3 py-2 font-mono text-text-primary align-top\">\n {alias.name}\n </td>\n <td className=\"px-3 py-2 align-top\">\n {isEditing ? (\n <textarea\n value={editCommand}\n onChange={(e) => setEditCommand(e.target.value)}\n rows={2}\n className=\"w-full px-2 py-1 text-xs bg-bg-surface border border-border-default rounded text-text-primary font-mono\"\n />\n ) : (\n <code className=\"text-xs text-text-primary\" title={alias.command}>\n {truncate(alias.command)}\n </code>\n )}\n </td>\n <td className=\"px-3 py-2 align-top\">\n {isEditing ? (\n <input\n value={editDescription}\n onChange={(e) => setEditDescription(e.target.value)}\n className=\"w-full px-2 py-1 text-xs bg-bg-surface border border-border-default rounded text-text-primary\"\n />\n ) : (\n <span className=\"text-xs text-text-secondary\">\n {alias.description ?? '—'}\n </span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary align-top\">\n {alias.updatedAt ? new Date(alias.updatedAt).toLocaleString() : '—'}\n </td>\n <td className=\"px-3 py-2 align-top\">\n <div className=\"flex items-center justify-end gap-1\">\n {isEditing ? (\n <>\n <button\n onClick={() => void saveEdit(alias)}\n disabled={savingEdit}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-status-success-text\"\n title={tr('aliases.save', 'Save')}\n >\n {savingEdit ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Check className=\"w-4 h-4\" />\n )}\n </button>\n <button\n onClick={cancelEdit}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-text-secondary\"\n title={tr('common.cancel', 'Cancel')}\n >\n <X className=\"w-4 h-4\" />\n </button>\n </>\n ) : (\n <>\n <button\n onClick={() => {\n setRunTarget(alias);\n setRunArgs('');\n setRunResult(null);\n setRunError(null);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-action-primary-bg\"\n title={tr('aliases.run', 'Run')}\n >\n <Play className=\"w-4 h-4\" />\n </button>\n <button\n onClick={() => startEdit(alias)}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-text-secondary\"\n title={tr('aliases.edit', 'Edit')}\n >\n <Pencil className=\"w-4 h-4\" />\n </button>\n <button\n onClick={() => setDeleteTarget(alias)}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-status-error-text\"\n title={tr('aliases.delete', 'Delete')}\n >\n <Trash2 className=\"w-4 h-4\" />\n </button>\n </>\n )}\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n )}\n </div>\n\n {/* Run modal */}\n {runTarget && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <div className=\"w-full max-w-2xl bg-bg-surface rounded-lg shadow-xl max-h-[90vh] flex flex-col\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary flex items-center gap-2\">\n <Terminal className=\"w-5 h-5\" />\n {tr('aliases.runTitle', 'Run alias')}: <code>{runTarget.name}</code>\n </h2>\n <button onClick={() => setRunTarget(null)} className=\"p-1 rounded hover:bg-bg-sunken\">\n <X className=\"w-5 h-5 text-text-secondary\" />\n </button>\n </div>\n <div className=\"flex-1 overflow-y-auto p-6 space-y-3\">\n <div className=\"text-xs text-text-secondary font-mono break-all\">\n $ {runTarget.command}\n </div>\n <input\n type=\"text\"\n value={runArgs}\n onChange={(e) => setRunArgs(e.target.value)}\n placeholder={tr('aliases.argsPlaceholder', 'Args (space-separated, optional)')}\n className=\"w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary font-mono\"\n />\n {runError && <div className=\"text-sm text-status-error-text\">{runError}</div>}\n {runResult && (\n <div className=\"space-y-2\">\n <div className=\"text-xs text-text-secondary\">\n {tr('aliases.exitCode', 'Exit code')}:{' '}\n <span\n className={\n runResult.exit_code === 0\n ? 'text-status-success-text font-mono'\n : 'text-status-error-text font-mono'\n }\n >\n {runResult.exit_code}\n </span>\n </div>\n {runResult.stdout && (\n <div>\n <div className=\"text-xs text-text-secondary mb-1\">stdout</div>\n <pre className=\"text-xs bg-bg-sunken p-3 rounded font-mono whitespace-pre-wrap break-all max-h-64 overflow-y-auto text-text-primary\">\n {runResult.stdout}\n </pre>\n </div>\n )}\n {runResult.stderr && (\n <div>\n <div className=\"text-xs text-text-secondary mb-1\">stderr</div>\n <pre className=\"text-xs bg-bg-sunken p-3 rounded font-mono whitespace-pre-wrap break-all max-h-64 overflow-y-auto text-status-error-text\">\n {runResult.stderr}\n </pre>\n </div>\n )}\n </div>\n )}\n </div>\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-default bg-bg-sunken\">\n <button\n onClick={() => setRunTarget(null)}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated\"\n >\n {tr('common.close', 'Close')}\n </button>\n <button\n onClick={() => void runAlias()}\n disabled={runRunning}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {runRunning ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Play className=\"w-4 h-4\" />\n )}\n {tr('aliases.run', 'Run')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Delete confirm */}\n {deleteTarget && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <div className=\"w-full max-w-md bg-bg-surface rounded-lg shadow-xl p-6 space-y-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('aliases.deleteTitle', 'Delete alias?')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr('aliases.deleteConfirm', 'This permanently removes the alias from this profile.')}{' '}\n <code className=\"font-mono text-text-primary\">{deleteTarget.name}</code>\n </p>\n <div className=\"flex justify-end gap-3\">\n <button\n onClick={() => setDeleteTarget(null)}\n className=\"px-3 py-1.5 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary hover:bg-bg-elevated\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n onClick={() => void confirmDelete()}\n disabled={deleting}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-status-error-text bg-status-error-bg rounded-md hover:opacity-80 disabled:opacity-50\"\n >\n {deleting ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Trash2 className=\"w-4 h-4\" />\n )}\n {tr('aliases.delete', 'Delete')}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AliasManager.js","names":[],"sources":["../../../src/components/aliases/AliasManager.tsx"],"sourcesContent":["/**\n * AliasManager — per-agent CRUDL for shell aliases exposed by\n * vibecontrols-agent under `/api/aliases/*`.\n *\n * Pairs with the agent's Phase A4 ship.\n *\n * Endpoints (proxied through wspace-vibecontrols-svc → agent):\n * GET /api/aliases → { items: Alias[] }\n * GET /api/aliases/:name → Alias\n * POST /api/aliases → Alias\n * PUT /api/aliases/:name → Alias\n * DELETE /api/aliases/:name\n * POST /api/aliases/:name/execute → { exit_code, stdout, stderr }\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { Plus, Trash2, Play, Pencil, Check, X, Loader2, Terminal, PackageOpen } from 'lucide-react';\nimport { agentAiGet, agentAiMutate } from '@/services/aiApi';\nimport { useTr } from '@/shared/hooks/useTr';\nimport { validateAliasName } from './aliasReservedNames';\n\n/**\n * Detect the signatures that mean \"the aliases plugin isn't installed on\n * this agent.\" The agent answers 404 with body `NOT_FOUND` when no plugin\n * is mounted at `/api/profiles/<profile>/aliases`; the svc proxy\n * sanitizes that to the \"feature is not installed\" string (see aiApi\n * `sanitizeError`). We also catch the underlying error text directly so\n * we don't depend on `sanitizeError` keeping that exact phrasing.\n *\n * Reviewer follow-up: deliberately NOT matching Apollo's default\n * `Unexpected error.` mask here. It would catch today's\n * `WorkspaceRequiredError`-from-`agentAiGet` symptom AND legitimate\n * future errors (auth failures, timeouts, etc.) once the plugin ships,\n * masking them as \"plugin not installed\" — a worse UX than the honest\n * raw error. The right cure for the workspace-context surface lives at\n * the svc resolver (translate to a typed GraphQL error code the FE can\n * pivot on) and the aiApi sanitizer; tracked in BOFF-2613.\n */\nfunction isAliasesPluginUnavailable(msg: string): boolean {\n if (!msg) return false;\n const m = msg.toLowerCase();\n return m.includes('not installed on the agent') || m.includes('not_found') || m.includes(' 404');\n}\n\ninterface Alias {\n name: string;\n command: string;\n description?: string | null;\n updatedAt?: string | null;\n createdAt?: string | null;\n}\n\ninterface AliasListResponse {\n items?: Alias[];\n}\n\ninterface AliasExecuteResult {\n exit_code: number;\n stdout: string;\n stderr: string;\n}\n\ninterface AliasManagerProps {\n agent: { id: string; profile?: string };\n}\n\nconst TRUNCATE = 80;\n\nfunction truncate(str: string, n = TRUNCATE): string {\n if (!str) return '';\n return str.length > n ? `${str.slice(0, n)}…` : str;\n}\n\nexport function AliasManager({ agent }: AliasManagerProps) {\n const tr = useTr();\n // Canonical agent URL hierarchy: agent → profile → plugins/data.\n // The aliases plugin lives under `/api/profiles/<profile>/aliases/*`.\n // No client-side rewrites — every consumer constructs the canonical\n // path inline so the URL the agent sees is the URL the FE wrote.\n const profile = encodeURIComponent(agent.profile ?? 'default');\n const aliasesBase = `/api/profiles/${profile}/aliases`;\n const [aliases, setAliases] = useState<Alias[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n // Distinguish \"the agent doesn't have this plugin installed\" from a generic\n // error so we can show a clean empty-state with an actionable hint instead\n // of a scary stack-trace fragment. [BOFF-2613]\n const [pluginUnavailable, setPluginUnavailable] = useState(false);\n\n // Add-new form\n const [newName, setNewName] = useState('');\n const [newCommand, setNewCommand] = useState('');\n const [newDescription, setNewDescription] = useState('');\n const [creating, setCreating] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n\n // Edit row\n const [editingName, setEditingName] = useState<string | null>(null);\n const [editCommand, setEditCommand] = useState('');\n const [editDescription, setEditDescription] = useState('');\n const [savingEdit, setSavingEdit] = useState(false);\n\n // Run modal\n const [runTarget, setRunTarget] = useState<Alias | null>(null);\n const [runArgs, setRunArgs] = useState('');\n const [runResult, setRunResult] = useState<AliasExecuteResult | null>(null);\n const [runRunning, setRunRunning] = useState(false);\n const [runError, setRunError] = useState<string | null>(null);\n\n // Delete confirm\n const [deleteTarget, setDeleteTarget] = useState<Alias | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n const refresh = useCallback(async () => {\n setLoading(true);\n setError(null);\n setPluginUnavailable(false);\n try {\n const data = await agentAiGet<AliasListResponse | Alias[]>(agent.id, aliasesBase);\n const items = Array.isArray(data) ? data : (data.items ?? []);\n setAliases(items);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (isAliasesPluginUnavailable(msg)) {\n // Plugin not installed on this agent (or upstream context-required\n // error that we treat the same way for this tab) — show empty-state\n // instead of a scary error banner. [BOFF-2613]\n setPluginUnavailable(true);\n } else {\n setError(msg);\n }\n } finally {\n setLoading(false);\n }\n }, [agent.id, aliasesBase]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n // ── Create ────────────────────────────────────────────────────────\n const handleCreate = async () => {\n setCreateError(null);\n const nameErr = validateAliasName(newName.trim());\n if (nameErr) {\n setCreateError(nameErr);\n return;\n }\n if (!newCommand.trim()) {\n setCreateError(tr('aliases.commandRequired', 'Command is required'));\n return;\n }\n setCreating(true);\n try {\n await agentAiMutate(agent.id, aliasesBase, 'POST', {\n name: newName.trim(),\n command: newCommand,\n description: newDescription || undefined,\n });\n setNewName('');\n setNewCommand('');\n setNewDescription('');\n await refresh();\n } catch (e) {\n setCreateError(e instanceof Error ? e.message : String(e));\n } finally {\n setCreating(false);\n }\n };\n\n // ── Edit ──────────────────────────────────────────────────────────\n const startEdit = (alias: Alias) => {\n setEditingName(alias.name);\n setEditCommand(alias.command);\n setEditDescription(alias.description ?? '');\n };\n\n const cancelEdit = () => {\n setEditingName(null);\n setEditCommand('');\n setEditDescription('');\n };\n\n const saveEdit = async (alias: Alias) => {\n setSavingEdit(true);\n try {\n await agentAiMutate(agent.id, `${aliasesBase}/${encodeURIComponent(alias.name)}`, 'PUT', {\n command: editCommand,\n description: editDescription || undefined,\n });\n cancelEdit();\n await refresh();\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n setSavingEdit(false);\n }\n };\n\n // ── Delete ────────────────────────────────────────────────────────\n const confirmDelete = async () => {\n if (!deleteTarget) return;\n setDeleting(true);\n try {\n await agentAiMutate(\n agent.id,\n `${aliasesBase}/${encodeURIComponent(deleteTarget.name)}`,\n 'DELETE'\n );\n setDeleteTarget(null);\n await refresh();\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n setDeleting(false);\n }\n };\n\n // ── Run ───────────────────────────────────────────────────────────\n const runAlias = async () => {\n if (!runTarget) return;\n setRunRunning(true);\n setRunError(null);\n setRunResult(null);\n try {\n const args = runArgs.split(/\\s+/).filter(Boolean);\n const result = await agentAiMutate<AliasExecuteResult>(\n agent.id,\n `${aliasesBase}/${encodeURIComponent(runTarget.name)}/execute`,\n 'POST',\n { args }\n );\n setRunResult(result);\n } catch (e) {\n setRunError(e instanceof Error ? e.message : String(e));\n } finally {\n setRunRunning(false);\n }\n };\n\n if (pluginUnavailable) {\n // The aliases plugin isn't installed on this agent. Show a clean,\n // actionable empty-state instead of the form + the raw error string.\n // [BOFF-2613]\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-8 text-center space-y-3\">\n <PackageOpen className=\"w-8 h-8 mx-auto text-text-muted\" />\n <h3 className=\"text-base font-medium text-text-primary\">\n {tr('aliases.pluginMissingTitle', 'Aliases plugin not installed')}\n </h3>\n <p className=\"text-sm text-text-secondary max-w-md mx-auto\">\n {tr(\n 'aliases.pluginMissingBody',\n \"This agent doesn't have the aliases plugin installed yet. Install @vibecontrols/vibe-plugin-aliases from the Plugins tab to enable per-agent shell aliases.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={refresh}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-text-secondary border border-border-default rounded-md hover:bg-bg-sunken transition-colors\"\n >\n {tr('aliases.retry', 'Retry')}\n </button>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Add new alias form */}\n <div className=\"rounded-lg border border-border-default bg-bg-sunken p-4 space-y-3\">\n <h3 className=\"text-sm font-medium text-text-primary flex items-center gap-2\">\n <Plus className=\"w-4 h-4\" />\n {tr('aliases.createTitle', 'New alias')}\n </h3>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <input\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n placeholder={tr('aliases.namePlaceholder', 'Name (a-z, 0-9, _, -)')}\n className=\"px-3 py-2 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n />\n <input\n type=\"text\"\n value={newDescription}\n onChange={(e) => setNewDescription(e.target.value)}\n placeholder={tr('aliases.descriptionPlaceholder', 'Description (optional)')}\n className=\"px-3 py-2 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n <textarea\n value={newCommand}\n onChange={(e) => setNewCommand(e.target.value)}\n placeholder={tr('aliases.commandPlaceholder', 'Command (e.g. git status)')}\n rows={3}\n className=\"w-full px-3 py-2 text-sm bg-bg-surface border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n />\n {createError && <div className=\"text-xs text-status-error-text\">{createError}</div>}\n <div className=\"flex justify-end\">\n <button\n onClick={handleCreate}\n disabled={creating || !newName.trim() || !newCommand.trim()}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {creating ? <Loader2 className=\"w-4 h-4 animate-spin\" /> : <Plus className=\"w-4 h-4\" />}\n {tr('aliases.create', 'Create')}\n </button>\n </div>\n </div>\n\n {/* List */}\n <div className=\"rounded-lg border border-border-default overflow-hidden\">\n {loading ? (\n <div className=\"p-8 text-center text-text-secondary\">\n <Loader2 className=\"w-5 h-5 animate-spin mx-auto\" />\n </div>\n ) : error ? (\n <div className=\"p-4 text-sm text-status-error-text\">{error}</div>\n ) : aliases.length === 0 ? (\n <div className=\"p-8 text-center text-sm text-text-secondary\">\n {tr('aliases.empty', 'No aliases yet. Create one above.')}\n </div>\n ) : (\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken text-text-secondary\">\n <tr>\n <th className=\"text-left px-3 py-2 font-medium\">{tr('aliases.colName', 'Name')}</th>\n <th className=\"text-left px-3 py-2 font-medium\">\n {tr('aliases.colCommand', 'Command')}\n </th>\n <th className=\"text-left px-3 py-2 font-medium\">\n {tr('aliases.colDescription', 'Description')}\n </th>\n <th className=\"text-left px-3 py-2 font-medium\">\n {tr('aliases.colUpdatedAt', 'Updated')}\n </th>\n <th className=\"text-right px-3 py-2 font-medium\">\n {tr('aliases.colActions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {aliases.map((alias) => {\n const isEditing = editingName === alias.name;\n return (\n <tr key={alias.name} className=\"border-t border-border-default\">\n <td className=\"px-3 py-2 font-mono text-text-primary align-top\">\n {alias.name}\n </td>\n <td className=\"px-3 py-2 align-top\">\n {isEditing ? (\n <textarea\n value={editCommand}\n onChange={(e) => setEditCommand(e.target.value)}\n rows={2}\n className=\"w-full px-2 py-1 text-xs bg-bg-surface border border-border-default rounded text-text-primary font-mono\"\n />\n ) : (\n <code className=\"text-xs text-text-primary\" title={alias.command}>\n {truncate(alias.command)}\n </code>\n )}\n </td>\n <td className=\"px-3 py-2 align-top\">\n {isEditing ? (\n <input\n value={editDescription}\n onChange={(e) => setEditDescription(e.target.value)}\n className=\"w-full px-2 py-1 text-xs bg-bg-surface border border-border-default rounded text-text-primary\"\n />\n ) : (\n <span className=\"text-xs text-text-secondary\">\n {alias.description ?? '—'}\n </span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary align-top\">\n {alias.updatedAt ? new Date(alias.updatedAt).toLocaleString() : '—'}\n </td>\n <td className=\"px-3 py-2 align-top\">\n <div className=\"flex items-center justify-end gap-1\">\n {isEditing ? (\n <>\n <button\n onClick={() => void saveEdit(alias)}\n disabled={savingEdit}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-status-success-text\"\n title={tr('aliases.save', 'Save')}\n >\n {savingEdit ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Check className=\"w-4 h-4\" />\n )}\n </button>\n <button\n onClick={cancelEdit}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-text-secondary\"\n title={tr('common.cancel', 'Cancel')}\n >\n <X className=\"w-4 h-4\" />\n </button>\n </>\n ) : (\n <>\n <button\n onClick={() => {\n setRunTarget(alias);\n setRunArgs('');\n setRunResult(null);\n setRunError(null);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-action-primary-bg\"\n title={tr('aliases.run', 'Run')}\n >\n <Play className=\"w-4 h-4\" />\n </button>\n <button\n onClick={() => startEdit(alias)}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-text-secondary\"\n title={tr('aliases.edit', 'Edit')}\n >\n <Pencil className=\"w-4 h-4\" />\n </button>\n <button\n onClick={() => setDeleteTarget(alias)}\n className=\"p-1.5 rounded hover:bg-bg-sunken text-status-error-text\"\n title={tr('aliases.delete', 'Delete')}\n >\n <Trash2 className=\"w-4 h-4\" />\n </button>\n </>\n )}\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n )}\n </div>\n\n {/* Run modal */}\n {runTarget && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <div className=\"w-full max-w-2xl bg-bg-surface rounded-lg shadow-xl max-h-[90vh] flex flex-col\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary flex items-center gap-2\">\n <Terminal className=\"w-5 h-5\" />\n {tr('aliases.runTitle', 'Run alias')}: <code>{runTarget.name}</code>\n </h2>\n <button onClick={() => setRunTarget(null)} className=\"p-1 rounded hover:bg-bg-sunken\">\n <X className=\"w-5 h-5 text-text-secondary\" />\n </button>\n </div>\n <div className=\"flex-1 overflow-y-auto p-6 space-y-3\">\n <div className=\"text-xs text-text-secondary font-mono break-all\">\n $ {runTarget.command}\n </div>\n <input\n type=\"text\"\n value={runArgs}\n onChange={(e) => setRunArgs(e.target.value)}\n placeholder={tr('aliases.argsPlaceholder', 'Args (space-separated, optional)')}\n className=\"w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary font-mono\"\n />\n {runError && <div className=\"text-sm text-status-error-text\">{runError}</div>}\n {runResult && (\n <div className=\"space-y-2\">\n <div className=\"text-xs text-text-secondary\">\n {tr('aliases.exitCode', 'Exit code')}:{' '}\n <span\n className={\n runResult.exit_code === 0\n ? 'text-status-success-text font-mono'\n : 'text-status-error-text font-mono'\n }\n >\n {runResult.exit_code}\n </span>\n </div>\n {runResult.stdout && (\n <div>\n <div className=\"text-xs text-text-secondary mb-1\">stdout</div>\n <pre className=\"text-xs bg-bg-sunken p-3 rounded font-mono whitespace-pre-wrap break-all max-h-64 overflow-y-auto text-text-primary\">\n {runResult.stdout}\n </pre>\n </div>\n )}\n {runResult.stderr && (\n <div>\n <div className=\"text-xs text-text-secondary mb-1\">stderr</div>\n <pre className=\"text-xs bg-bg-sunken p-3 rounded font-mono whitespace-pre-wrap break-all max-h-64 overflow-y-auto text-status-error-text\">\n {runResult.stderr}\n </pre>\n </div>\n )}\n </div>\n )}\n </div>\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-default bg-bg-sunken\">\n <button\n onClick={() => setRunTarget(null)}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated\"\n >\n {tr('common.close', 'Close')}\n </button>\n <button\n onClick={() => void runAlias()}\n disabled={runRunning}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {runRunning ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Play className=\"w-4 h-4\" />\n )}\n {tr('aliases.run', 'Run')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Delete confirm */}\n {deleteTarget && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <div className=\"w-full max-w-md bg-bg-surface rounded-lg shadow-xl p-6 space-y-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('aliases.deleteTitle', 'Delete alias?')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr('aliases.deleteConfirm', 'This permanently removes the alias from this profile.')}{' '}\n <code className=\"font-mono text-text-primary\">{deleteTarget.name}</code>\n </p>\n <div className=\"flex justify-end gap-3\">\n <button\n onClick={() => setDeleteTarget(null)}\n className=\"px-3 py-1.5 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary hover:bg-bg-elevated\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n onClick={() => void confirmDelete()}\n disabled={deleting}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-status-error-text bg-status-error-bg rounded-md hover:opacity-80 disabled:opacity-50\"\n >\n {deleting ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Trash2 className=\"w-4 h-4\" />\n )}\n {tr('aliases.delete', 'Delete')}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|