@burdenoff/microfe-vibecontrols 2026.608.6 → 2026.610.2
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.
- package/dist/VibeControlsRoot.js +81 -59
- package/dist/VibeControlsRoot.js.map +1 -1
- package/dist/components/agents/AgentCard.js +123 -175
- package/dist/components/agents/AgentCard.js.map +1 -1
- package/dist/components/agents/AgentConnectionRecovery.js +129 -73
- package/dist/components/agents/AgentConnectionRecovery.js.map +1 -1
- package/dist/components/agents/AgentForm.js +464 -370
- package/dist/components/agents/AgentForm.js.map +1 -1
- package/dist/components/agents/AgentList.js +261 -208
- package/dist/components/agents/AgentList.js.map +1 -1
- package/dist/components/agents/AgentMaintenanceTab.js +278 -215
- package/dist/components/agents/AgentMaintenanceTab.js.map +1 -1
- package/dist/components/agents/AgentStatusBadge.js +8 -7
- package/dist/components/agents/AgentStatusBadge.js.map +1 -1
- package/dist/components/agents/AgentStorageConfig.js +154 -104
- package/dist/components/agents/AgentStorageConfig.js.map +1 -1
- package/dist/components/agents/ConfigureAgentDialog.js +196 -0
- package/dist/components/agents/ConfigureAgentDialog.js.map +1 -0
- package/dist/components/agents/ConnectionLogPanel.js +135 -99
- package/dist/components/agents/ConnectionLogPanel.js.map +1 -1
- package/dist/components/agents/CreateSessionDialog.js +168 -0
- package/dist/components/agents/CreateSessionDialog.js.map +1 -0
- package/dist/components/agents/GatewayAuthCard.js +283 -0
- package/dist/components/agents/GatewayAuthCard.js.map +1 -0
- package/dist/components/agents/OpenInBrowserEditorButton.js +124 -74
- package/dist/components/agents/OpenInBrowserEditorButton.js.map +1 -1
- package/dist/components/agents/OpenInEditorMenu.js +230 -177
- package/dist/components/agents/OpenInEditorMenu.js.map +1 -1
- package/dist/components/agents/PluginUIPanel.js +292 -199
- package/dist/components/agents/PluginUIPanel.js.map +1 -1
- package/dist/components/agents/RemoteTaskPermissionsCard.js +101 -81
- package/dist/components/agents/RemoteTaskPermissionsCard.js.map +1 -1
- package/dist/components/agents/index.js +3 -0
- package/dist/components/ai/InstructionFilesManager.js +2 -2
- package/dist/components/ai/InstructionFilesManager.js.map +1 -1
- package/dist/components/graph/ConnectionPermissionsDialog.js +83 -99
- package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
- package/dist/components/graph/MeshAgentNode.js +2 -2
- package/dist/components/graph/MeshAgentNode.js.map +1 -1
- package/dist/components/graph/MeshDeployWizard.js +334 -313
- package/dist/components/graph/MeshDeployWizard.js.map +1 -1
- package/dist/components/graph/MeshGraphView.js +115 -124
- package/dist/components/graph/MeshGraphView.js.map +1 -1
- package/dist/components/graph/nodeIcons.js +10 -11
- package/dist/components/graph/nodeIcons.js.map +1 -1
- package/dist/components/shared/ActionMenu.js +101 -0
- package/dist/components/shared/ActionMenu.js.map +1 -0
- package/dist/components/shared/Card.js +9 -20
- package/dist/components/shared/Card.js.map +1 -1
- package/dist/components/shared/ConfirmDialog.js +31 -37
- package/dist/components/shared/ConfirmDialog.js.map +1 -1
- package/dist/components/shared/ErrorState.js +7 -5
- package/dist/components/shared/ErrorState.js.map +1 -1
- package/dist/components/shared/InstallTabs.js +1 -1
- package/dist/components/shared/InstallTabs.js.map +1 -1
- package/dist/components/shared/LoadingState.js +1 -13
- package/dist/components/shared/LoadingState.js.map +1 -1
- package/dist/components/shared/MotionTabPanel.js +43 -0
- package/dist/components/shared/MotionTabPanel.js.map +1 -0
- package/dist/components/shared/PlatformIcon.js +37 -0
- package/dist/components/shared/PlatformIcon.js.map +1 -0
- package/dist/components/shared/PromptDialog.js +81 -0
- package/dist/components/shared/PromptDialog.js.map +1 -0
- package/dist/components/shared/StatCard.js +8 -8
- package/dist/components/shared/StatCard.js.map +1 -1
- package/dist/components/shared/StatusBadge.js +2 -2
- package/dist/components/shared/StatusBadge.js.map +1 -1
- package/dist/components/shared/cardMotion.js +3 -10
- package/dist/components/shared/cardMotion.js.map +1 -1
- package/dist/components/shared/index.js +4 -0
- package/dist/components/shared/platformIconPaths.js +10 -0
- package/dist/components/shared/platformIconPaths.js.map +1 -0
- package/dist/components/targets/TargetCard.js +116 -177
- package/dist/components/targets/TargetCard.js.map +1 -1
- package/dist/generated/wspace-types.js +3 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/lib/toast.js +36 -0
- package/dist/lib/toast.js.map +1 -0
- package/dist/pages/AgentDetailsPage.js +1107 -1699
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentGraphPage.js +214 -238
- package/dist/pages/AgentGraphPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +405 -464
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +202 -184
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +795 -865
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/shared/hooks/useField.js +19 -0
- package/dist/shared/hooks/useField.js.map +1 -0
- package/dist/translations/en.js +821 -126
- package/dist/translations/en.js.map +1 -1
- package/package.json +2 -1
|
@@ -1,96 +1,109 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { jsx as
|
|
1
|
+
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
+
import { ActionMenu as t } from "../shared/ActionMenu.js";
|
|
3
|
+
import { PlatformIcon as n } from "../shared/PlatformIcon.js";
|
|
4
|
+
import { getAgentStatus as r } from "./agentStatus.js";
|
|
5
|
+
import { classifyAgentHealth as i } from "../../utils/agentHealth.js";
|
|
6
|
+
import { AgentStatusBadge as a } from "./AgentStatusBadge.js";
|
|
7
|
+
import { Clock as o, Cpu as s, Globe as c, Link as l, Play as u, RefreshCw as d, Server as f, Settings as p, Square as m } from "lucide-react";
|
|
8
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
9
|
+
import { Checkbox as _ } from "@burdenoff/fe-libs/ui";
|
|
9
10
|
//#region src/components/agents/AgentCard.tsx
|
|
10
|
-
var
|
|
11
|
-
let
|
|
12
|
-
|
|
13
|
-
return n === e ? t : n;
|
|
14
|
-
}, N = x || S, [P, F] = r(!1), I = e(y), L = (e) => {
|
|
15
|
-
if (!e) return M("agentsPage.never", "Never");
|
|
11
|
+
var v = ({ agent: v, onSelect: y, onConfigure: b, onSettings: x, onStart: S, onStop: C, onDisconnect: w, onEditTunnelUrl: T, selected: E = !1, onToggleSelect: D, className: O = "", healthErrorMessage: k }) => {
|
|
12
|
+
let A = e(), j = b || x, M = r(v), N = (e) => {
|
|
13
|
+
if (!e) return A("agentsPage.never", "Never");
|
|
16
14
|
let t = Date.now() - new Date(e).getTime();
|
|
17
|
-
return t < 6e4 ?
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
15
|
+
return t < 6e4 ? A("agentsPage.justNow", "Just now") : t < 36e5 ? `${Math.floor(t / 6e4)}m ago` : t < 864e5 ? `${Math.floor(t / 36e5)}h ago` : `${Math.floor(t / 864e5)}d ago`;
|
|
16
|
+
}, P = [];
|
|
17
|
+
return M === "ACTIVE" && C && P.push({
|
|
18
|
+
key: "stop",
|
|
19
|
+
label: A("agentsPage.stopAgent", "Stop Agent"),
|
|
20
|
+
icon: m,
|
|
21
|
+
onSelect: () => C(v)
|
|
22
|
+
}), (M === "STOPPED" || M === "OFFLINE") && S && P.push({
|
|
23
|
+
key: "start",
|
|
24
|
+
label: A("agentsPage.startAgent", "Start Agent"),
|
|
25
|
+
icon: u,
|
|
26
|
+
onSelect: () => S(v)
|
|
27
|
+
}), M === "DEGRADED" && S && P.push({
|
|
28
|
+
key: "reconnect",
|
|
29
|
+
label: A("agentsPage.reconnectAgent", "Reconnect"),
|
|
30
|
+
icon: d,
|
|
31
|
+
onSelect: () => S(v)
|
|
32
|
+
}), (M === "ACTIVE" || M === "STOPPED" || M === "OFFLINE" || M === "DEGRADED") && w && P.push({
|
|
33
|
+
key: "disconnect",
|
|
34
|
+
label: M === "OFFLINE" ? A("agentsPage.clearConnection", "Clear Connection") : A("agentsPage.disconnect", "Disconnect"),
|
|
35
|
+
icon: m,
|
|
36
|
+
onSelect: () => w(v),
|
|
37
|
+
variant: "danger"
|
|
38
|
+
}), T && P.push({
|
|
39
|
+
key: "edit-tunnel",
|
|
40
|
+
label: A("agentsPage.editTunnel", "Edit Tunnel"),
|
|
41
|
+
icon: l,
|
|
42
|
+
onSelect: () => T(v)
|
|
43
|
+
}), j && P.push({
|
|
44
|
+
key: "settings",
|
|
45
|
+
label: A("agentsPage.settings", "Settings"),
|
|
46
|
+
icon: p,
|
|
47
|
+
onSelect: () => j(v)
|
|
48
|
+
}), /* @__PURE__ */ g("div", {
|
|
49
|
+
role: "button",
|
|
50
|
+
tabIndex: 0,
|
|
51
|
+
className: `bg-bg-surface border rounded-card p-4 h-full hover:border-border-strong hover:shadow-[var(--shadow-elevation-1)] transition-[colors,box-shadow] duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 focus-visible:ring-offset-bg-canvas ${E ? "border-action-primary-bg ring-1 ring-action-primary-bg/30" : "border-border-default"} ${O}`,
|
|
52
|
+
onClick: () => y?.(v),
|
|
53
|
+
onKeyDown: (e) => {
|
|
54
|
+
e.target === e.currentTarget && (e.key === "Enter" || e.key === " ") && (e.preventDefault(), y?.(v));
|
|
35
55
|
},
|
|
36
|
-
whileTap: { scale: .98 },
|
|
37
|
-
className: `bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${D ? "border-action-primary-bg ring-1 ring-action-primary-bg/30" : "border-border-default"} ${k}`,
|
|
38
|
-
onClick: () => b?.(y),
|
|
39
56
|
children: [
|
|
40
|
-
/* @__PURE__ */
|
|
57
|
+
/* @__PURE__ */ g("div", {
|
|
41
58
|
className: "flex items-start justify-between gap-2",
|
|
42
|
-
children: [/* @__PURE__ */
|
|
59
|
+
children: [/* @__PURE__ */ g("div", {
|
|
43
60
|
className: "flex items-start gap-2 sm:gap-3 min-w-0 flex-1",
|
|
44
61
|
children: [
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
D && /* @__PURE__ */ h("span", {
|
|
63
|
+
className: "flex flex-shrink-0 items-center justify-center max-md:min-h-11 max-md:min-w-11 max-md:-m-2 md:mt-0.5",
|
|
47
64
|
onClick: (e) => {
|
|
48
|
-
e.stopPropagation(),
|
|
65
|
+
e.stopPropagation(), e.target === e.currentTarget && D();
|
|
49
66
|
},
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
stroke: "currentColor",
|
|
56
|
-
strokeWidth: 3,
|
|
57
|
-
children: /* @__PURE__ */ _("path", {
|
|
58
|
-
strokeLinecap: "round",
|
|
59
|
-
strokeLinejoin: "round",
|
|
60
|
-
d: "M5 13l4 4L19 7"
|
|
61
|
-
})
|
|
67
|
+
children: /* @__PURE__ */ h(_, {
|
|
68
|
+
checked: E,
|
|
69
|
+
onCheckedChange: () => D(),
|
|
70
|
+
onClick: (e) => e.stopPropagation(),
|
|
71
|
+
"aria-label": A("agentsPage.selectAgent", "Select {{name}}", { name: v.name })
|
|
62
72
|
})
|
|
63
73
|
}),
|
|
64
|
-
/* @__PURE__ */
|
|
65
|
-
className: "flex-shrink-0 size-8 sm:
|
|
66
|
-
children: /* @__PURE__ */
|
|
74
|
+
/* @__PURE__ */ h("div", {
|
|
75
|
+
className: "flex-shrink-0 size-8 sm:size-10 rounded-lg bg-bg-sunken flex items-center justify-center",
|
|
76
|
+
children: /* @__PURE__ */ h(f, {
|
|
77
|
+
className: "size-4 sm:size-5 text-text-secondary",
|
|
78
|
+
"aria-hidden": "true"
|
|
79
|
+
})
|
|
67
80
|
}),
|
|
68
|
-
/* @__PURE__ */
|
|
81
|
+
/* @__PURE__ */ g("div", {
|
|
69
82
|
className: "min-w-0 flex-1",
|
|
70
83
|
children: [
|
|
71
|
-
/* @__PURE__ */
|
|
84
|
+
/* @__PURE__ */ g("div", {
|
|
72
85
|
className: "flex items-center gap-1.5 sm:gap-2 min-w-0",
|
|
73
|
-
children: [/* @__PURE__ */
|
|
74
|
-
className: "font-semibold text-text-primary truncate min-w-0
|
|
75
|
-
children:
|
|
76
|
-
}), /* @__PURE__ */
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
children: [/* @__PURE__ */ h("h3", {
|
|
87
|
+
className: "font-semibold text-text-primary truncate min-w-0 text-sm sm:text-base",
|
|
88
|
+
children: v.name
|
|
89
|
+
}), /* @__PURE__ */ h(n, {
|
|
90
|
+
platform: v.platform,
|
|
91
|
+
className: "size-4 flex-shrink-0 text-text-secondary"
|
|
79
92
|
})]
|
|
80
93
|
}),
|
|
81
|
-
/* @__PURE__ */
|
|
94
|
+
/* @__PURE__ */ h("p", {
|
|
82
95
|
className: "text-xs sm:text-sm text-text-secondary truncate",
|
|
83
|
-
children:
|
|
96
|
+
children: v.hostname || `ID: ${v.id}`
|
|
84
97
|
}),
|
|
85
98
|
(() => {
|
|
86
|
-
let e =
|
|
87
|
-
return e.phase === "none" ? null : /* @__PURE__ */
|
|
88
|
-
className: `mt-1 flex items-start gap-1 text-xs ${e.phase === "attention" ? "text-status-warning-text" : "text-
|
|
89
|
-
children: [e.inProgress && /* @__PURE__ */
|
|
99
|
+
let e = i(M, k ?? null);
|
|
100
|
+
return e.phase === "none" ? null : /* @__PURE__ */ g("p", {
|
|
101
|
+
className: `mt-1 flex items-start gap-1 text-xs ${e.phase === "attention" ? "text-status-warning-text" : "text-content-tertiary"}`,
|
|
102
|
+
children: [e.inProgress && /* @__PURE__ */ h(d, {
|
|
90
103
|
className: "size-3 mt-0.5 flex-shrink-0 animate-spin",
|
|
91
104
|
"aria-hidden": "true"
|
|
92
|
-
}), /* @__PURE__ */
|
|
93
|
-
/* @__PURE__ */
|
|
105
|
+
}), /* @__PURE__ */ g("span", { children: [
|
|
106
|
+
/* @__PURE__ */ g("span", {
|
|
94
107
|
className: "font-medium",
|
|
95
108
|
children: [e.label, "."]
|
|
96
109
|
}),
|
|
@@ -102,140 +115,75 @@ var y = ({ agent: y, onSelect: b, onConfigure: x, onSettings: S, onStart: C, onS
|
|
|
102
115
|
]
|
|
103
116
|
})
|
|
104
117
|
]
|
|
105
|
-
}), /* @__PURE__ */
|
|
118
|
+
}), /* @__PURE__ */ g("div", {
|
|
106
119
|
className: "flex items-center gap-1 sm:gap-2 flex-shrink-0",
|
|
107
|
-
children: [/* @__PURE__ */
|
|
108
|
-
status:
|
|
109
|
-
reason:
|
|
110
|
-
linkTo: `/vibecontrols/agents/${
|
|
120
|
+
children: [/* @__PURE__ */ h(a, {
|
|
121
|
+
status: M,
|
|
122
|
+
reason: k ?? null,
|
|
123
|
+
linkTo: `/vibecontrols/agents/${v.id}?tab=connection`,
|
|
111
124
|
onClick: (e) => {
|
|
112
125
|
e.stopPropagation();
|
|
113
126
|
}
|
|
114
|
-
}), (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
e.stopPropagation(), F(!P);
|
|
120
|
-
},
|
|
121
|
-
className: "p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors flex items-center justify-center",
|
|
122
|
-
"aria-label": "Agent actions menu",
|
|
123
|
-
children: /* @__PURE__ */ _(l, { className: "size-4 text-text-primary opacity-70 hover:opacity-100" })
|
|
124
|
-
}), /* @__PURE__ */ _(h, { children: P && /* @__PURE__ */ v(g.div, {
|
|
125
|
-
initial: {
|
|
126
|
-
opacity: 0,
|
|
127
|
-
scale: .95,
|
|
128
|
-
y: -4
|
|
129
|
-
},
|
|
130
|
-
animate: {
|
|
131
|
-
opacity: 1,
|
|
132
|
-
scale: 1,
|
|
133
|
-
y: 0
|
|
134
|
-
},
|
|
135
|
-
exit: {
|
|
136
|
-
opacity: 0,
|
|
137
|
-
scale: .95,
|
|
138
|
-
y: -4
|
|
139
|
-
},
|
|
140
|
-
transition: { duration: .15 },
|
|
141
|
-
className: "absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10",
|
|
142
|
-
children: [
|
|
143
|
-
I === "ACTIVE" && w && /* @__PURE__ */ v(g.button, {
|
|
144
|
-
whileTap: { scale: .97 },
|
|
145
|
-
onClick: (e) => {
|
|
146
|
-
e.stopPropagation(), w(y), F(!1);
|
|
147
|
-
},
|
|
148
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
|
|
149
|
-
children: [/* @__PURE__ */ _(m, { className: "size-4" }), M("agentsPage.stopAgent", "Stop Agent")]
|
|
150
|
-
}),
|
|
151
|
-
(I === "STOPPED" || I === "OFFLINE") && C && /* @__PURE__ */ v(g.button, {
|
|
152
|
-
whileTap: { scale: .97 },
|
|
153
|
-
onClick: (e) => {
|
|
154
|
-
e.stopPropagation(), C(y), F(!1);
|
|
155
|
-
},
|
|
156
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
|
|
157
|
-
children: [/* @__PURE__ */ _(u, { className: "size-4" }), M("agentsPage.startAgent", "Start Agent")]
|
|
158
|
-
}),
|
|
159
|
-
I === "DEGRADED" && C && /* @__PURE__ */ v(g.button, {
|
|
160
|
-
whileTap: { scale: .97 },
|
|
161
|
-
onClick: (e) => {
|
|
162
|
-
e.stopPropagation(), C(y), F(!1);
|
|
163
|
-
},
|
|
164
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
|
|
165
|
-
title: A ? `Retry lifecycle/start — last error: ${A}` : "Retry lifecycle/start",
|
|
166
|
-
children: [/* @__PURE__ */ _(d, { className: "size-4" }), M("agentsPage.reconnectAgent", "Reconnect")]
|
|
167
|
-
}),
|
|
168
|
-
(I === "ACTIVE" || I === "STOPPED" || I === "OFFLINE" || I === "DEGRADED") && T && /* @__PURE__ */ v(g.button, {
|
|
169
|
-
whileTap: { scale: .97 },
|
|
170
|
-
onClick: (e) => {
|
|
171
|
-
e.stopPropagation(), T(y), F(!1);
|
|
172
|
-
},
|
|
173
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken",
|
|
174
|
-
children: [/* @__PURE__ */ _(m, { className: "size-4" }), I === "OFFLINE" ? "Clear Connection" : "Disconnect"]
|
|
175
|
-
}),
|
|
176
|
-
E && /* @__PURE__ */ v(g.button, {
|
|
177
|
-
whileTap: { scale: .97 },
|
|
178
|
-
onClick: (e) => {
|
|
179
|
-
e.stopPropagation(), E(y), F(!1);
|
|
180
|
-
},
|
|
181
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
182
|
-
children: [/* @__PURE__ */ _(c, { className: "size-4" }), "Edit Tunnel"]
|
|
183
|
-
}),
|
|
184
|
-
N && /* @__PURE__ */ v(g.button, {
|
|
185
|
-
whileTap: { scale: .97 },
|
|
186
|
-
onClick: (e) => {
|
|
187
|
-
e.stopPropagation(), N(y), F(!1);
|
|
188
|
-
},
|
|
189
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-b-lg",
|
|
190
|
-
children: [/* @__PURE__ */ _(p, { className: "size-4" }), "Settings"]
|
|
191
|
-
})
|
|
192
|
-
]
|
|
193
|
-
}) })]
|
|
127
|
+
}), (j || S || C) && P.length > 0 && /* @__PURE__ */ h(t, {
|
|
128
|
+
ariaLabel: A("agentsPage.agentActionsMenu", "Agent actions menu"),
|
|
129
|
+
title: A("agentsPage.agentActions", "Agent actions"),
|
|
130
|
+
align: "end",
|
|
131
|
+
items: P
|
|
194
132
|
})]
|
|
195
133
|
})]
|
|
196
134
|
}),
|
|
197
|
-
/* @__PURE__ */
|
|
135
|
+
/* @__PURE__ */ g("div", {
|
|
198
136
|
className: "mt-4 grid grid-cols-2 gap-3 text-sm",
|
|
199
|
-
children: [/* @__PURE__ */
|
|
137
|
+
children: [/* @__PURE__ */ g("div", {
|
|
200
138
|
className: "flex items-center gap-2 text-text-secondary",
|
|
201
|
-
children: [/* @__PURE__ */
|
|
202
|
-
|
|
139
|
+
children: [/* @__PURE__ */ h(o, {
|
|
140
|
+
className: "size-4",
|
|
141
|
+
"aria-hidden": "true"
|
|
142
|
+
}), /* @__PURE__ */ h("span", { children: N(v.lastHeartbeat) })]
|
|
143
|
+
}), v.architecture && /* @__PURE__ */ g("div", {
|
|
203
144
|
className: "flex items-center gap-2 text-text-secondary",
|
|
204
|
-
children: [/* @__PURE__ */
|
|
145
|
+
children: [/* @__PURE__ */ h(s, {
|
|
146
|
+
className: "size-4",
|
|
147
|
+
"aria-hidden": "true"
|
|
148
|
+
}), /* @__PURE__ */ h("span", { children: v.architecture })]
|
|
205
149
|
})]
|
|
206
150
|
}),
|
|
207
|
-
|
|
151
|
+
v.tunnelUrl && /* @__PURE__ */ g("div", {
|
|
208
152
|
className: "mt-3 flex items-center gap-2 text-sm",
|
|
209
153
|
children: [
|
|
210
|
-
/* @__PURE__ */
|
|
211
|
-
|
|
154
|
+
/* @__PURE__ */ h(c, {
|
|
155
|
+
className: "size-4 flex-shrink-0 text-text-secondary",
|
|
156
|
+
"aria-hidden": "true"
|
|
157
|
+
}),
|
|
158
|
+
/* @__PURE__ */ h("span", {
|
|
212
159
|
className: "text-text-secondary flex-shrink-0",
|
|
213
160
|
children: "Tunnel:"
|
|
214
161
|
}),
|
|
215
|
-
/* @__PURE__ */
|
|
216
|
-
href:
|
|
162
|
+
/* @__PURE__ */ h("a", {
|
|
163
|
+
href: v.tunnelUrl,
|
|
217
164
|
target: "_blank",
|
|
218
165
|
rel: "noopener noreferrer",
|
|
219
166
|
onClick: (e) => e.stopPropagation(),
|
|
220
|
-
|
|
221
|
-
|
|
167
|
+
"aria-label": A("agentsPage.tunnelOpensInNewTab", "{{url}} (opens in new tab)", { url: v.tunnelUrl }),
|
|
168
|
+
className: "min-w-0 truncate text-text-link hover:text-text-linkHover hover:underline",
|
|
169
|
+
children: v.tunnelUrl
|
|
222
170
|
})
|
|
223
171
|
]
|
|
224
172
|
}),
|
|
225
|
-
|
|
173
|
+
v.capabilities && v.capabilities.length > 0 && /* @__PURE__ */ g("div", {
|
|
226
174
|
className: "mt-3 flex flex-wrap gap-1",
|
|
227
|
-
children: [
|
|
228
|
-
className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded",
|
|
175
|
+
children: [v.capabilities.slice(0, 4).map((e) => /* @__PURE__ */ h("span", {
|
|
176
|
+
className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded-pill",
|
|
229
177
|
children: e
|
|
230
|
-
}, e)),
|
|
231
|
-
className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded",
|
|
232
|
-
children: ["+",
|
|
178
|
+
}, e)), v.capabilities.length > 4 && /* @__PURE__ */ g("span", {
|
|
179
|
+
className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded-pill",
|
|
180
|
+
children: ["+", v.capabilities.length - 4]
|
|
233
181
|
})]
|
|
234
182
|
})
|
|
235
183
|
]
|
|
236
184
|
});
|
|
237
185
|
};
|
|
238
186
|
//#endregion
|
|
239
|
-
export {
|
|
187
|
+
export { v as AgentCard };
|
|
240
188
|
|
|
241
189
|
//# sourceMappingURL=AgentCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentCard.js","names":[],"sources":["../../../src/components/agents/AgentCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Server,\n Clock,\n Cpu,\n MoreVertical,\n Play,\n Square,\n Settings,\n Globe,\n Link,\n RefreshCw,\n} from 'lucide-react';\nimport { AgentStatusBadge } from './AgentStatusBadge';\nimport type { Agent, AgentStatus } from './types';\nimport { getAgentStatus } from './agentStatus';\nimport { classifyAgentHealth } from '../../utils/agentHealth';\n\nexport interface AgentCardProps {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onConfigure?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onEditTunnelUrl?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n className?: string;\n /**\n * Last error from /health/ready or the live probe — shown in the badge\n * tooltip when the agent is degraded so users know WHY without curling\n * the tunnel themselves.\n */\n healthErrorMessage?: string | null;\n}\n\n/**\n * AgentCard - Display agent information in a card format\n *\n * @example\n * ```tsx\n * <AgentCard\n * agent={myAgent}\n * onSelect={handleSelect}\n * onConfigure={handleConfigure}\n * />\n * ```\n */\nexport const AgentCard: FC<AgentCardProps> = ({\n agent,\n onSelect,\n onConfigure,\n onSettings,\n onStart,\n onStop,\n onDisconnect,\n onEditTunnelUrl,\n selected = false,\n onToggleSelect,\n className = '',\n healthErrorMessage,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n // Support both onConfigure and onSettings\n const handleConfigure = onConfigure || onSettings;\n const [showMenu, setShowMenu] = useState(false);\n\n const status = getAgentStatus(agent);\n\n const formatLastHeartbeat = (timestamp: string | null | undefined) => {\n if (!timestamp) return tr('agentsPage.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('agentsPage.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n };\n\n const platformIcon =\n {\n darwin: '🍎',\n linux: '🐧',\n windows: '🪟',\n }[agent.platform || ''] || '💻';\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${\n selected\n ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30'\n : 'border-border-default'\n } ${className}`}\n onClick={() => onSelect?.(agent)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {/* Selection Checkbox */}\n {onToggleSelect && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n className={`flex-shrink-0 size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${\n selected\n ? 'bg-action-primary-bg border-action-primary-bg text-action-primary-text'\n : 'border-border-default hover:border-border-strong bg-bg-surface'\n } flex items-center justify-center`}\n >\n {selected && (\n <svg\n className=\"size-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )}\n </button>\n )}\n <div className=\"flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Server className=\"size-4 sm:w-5 sm:h-5 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate min-w-0 max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {agent.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">{platformIcon}</span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary truncate\">\n {agent.hostname || `ID: ${agent.id}`}\n </p>\n {(() => {\n // Always-visible (not just hover) guidance when degraded, so the\n // user knows whether it's self-healing or needs their action.\n const health = classifyAgentHealth(status, healthErrorMessage ?? null);\n if (health.phase === 'none') return null;\n return (\n <p\n className={`mt-1 flex items-start gap-1 text-xs ${\n health.phase === 'attention' ? 'text-status-warning-text' : 'text-text-tertiary'\n }`}\n >\n {health.inProgress && (\n <RefreshCw\n className=\"size-3 mt-0.5 flex-shrink-0 animate-spin\"\n aria-hidden=\"true\"\n />\n )}\n <span>\n <span className=\"font-medium\">{health.label}.</span> {health.actionHint}\n </span>\n </p>\n );\n })()}\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <AgentStatusBadge\n status={status}\n reason={healthErrorMessage ?? null}\n linkTo={`/vibecontrols/agents/${agent.id}?tab=connection`}\n onClick={(event) => {\n // The card itself has a `onSelect` click target that\n // routes to the agent detail. Stop propagation so a\n // badge click doesn't *also* fire that handler — the\n // anchor's href already routes to the right tab and\n // React Router picks it up via the surrounding shell.\n event.stopPropagation();\n }}\n />\n {(handleConfigure || onStart || onStop) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors flex items-center justify-center\"\n aria-label=\"Agent actions menu\"\n >\n <MoreVertical className=\"size-4 text-text-primary opacity-70 hover:opacity-100\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {status === 'ACTIVE' && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Square className=\"size-4\" />\n {tr('agentsPage.stopAgent', 'Stop Agent')}\n </motion.button>\n )}\n {(status === 'STOPPED' || status === 'OFFLINE') && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Play className=\"size-4\" />\n {tr('agentsPage.startAgent', 'Start Agent')}\n </motion.button>\n )}\n {status === 'DEGRADED' && onStart && (\n // Reconnect on a degraded agent fires the same\n // startVibecontrolsAgent mutation as a fresh start —\n // svc-side that POSTs lifecycle/start to the agent's\n // tunnel, which usually clears transient failures\n // (tunnel hiccup, finalize retry). Labelled\n // \"Reconnect\" not \"Start\" so the user knows it's a\n // recovery action, not creating a new instance.\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n title={\n healthErrorMessage\n ? `Retry lifecycle/start — last error: ${healthErrorMessage}`\n : 'Retry lifecycle/start'\n }\n >\n <RefreshCw className=\"size-4\" />\n {tr('agentsPage.reconnectAgent', 'Reconnect')}\n </motion.button>\n )}\n {(status === 'ACTIVE' ||\n status === 'STOPPED' ||\n status === 'OFFLINE' ||\n status === 'DEGRADED') &&\n onDisconnect && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDisconnect(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken\"\n >\n <Square className=\"size-4\" />\n {status === 'OFFLINE' ? 'Clear Connection' : 'Disconnect'}\n </motion.button>\n )}\n {onEditTunnelUrl && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEditTunnelUrl(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Link className=\"size-4\" />\n Edit Tunnel\n </motion.button>\n )}\n {handleConfigure && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n handleConfigure(agent);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-b-lg\"\n >\n <Settings className=\"size-4\" />\n Settings\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"mt-4 grid grid-cols-2 gap-3 text-sm\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"size-4\" />\n <span>{formatLastHeartbeat(agent.lastHeartbeat)}</span>\n </div>\n {agent.architecture && (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Cpu className=\"size-4\" />\n <span>{agent.architecture}</span>\n </div>\n )}\n </div>\n\n {agent.tunnelUrl && (\n // Plain \"Tunnel: <link>\" row. The colored status chip that used\n // to sit here was a redundant second status surface — the\n // canonical state lives in the <AgentStatusBadge> top-right.\n // The Globe glyph stays as a neutral icon (no status color).\n <div className=\"mt-3 flex items-center gap-2 text-sm\">\n <Globe className=\"size-4 flex-shrink-0 text-text-secondary\" />\n <span className=\"text-text-secondary flex-shrink-0\">Tunnel:</span>\n <a\n href={agent.tunnelUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className=\"text-action-primary-text hover:underline truncate\"\n >\n {agent.tunnelUrl}\n </a>\n </div>\n )}\n\n {agent.capabilities && agent.capabilities.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {agent.capabilities.slice(0, 4).map((cap) => (\n <span\n key={cap}\n className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n {cap}\n </span>\n ))}\n {agent.capabilities.length > 4 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{agent.capabilities.length - 4}\n </span>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentCard.js","names":[],"sources":["../../../src/components/agents/AgentCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { Server, Clock, Cpu, Play, Square, Settings, Globe, Link, RefreshCw } from 'lucide-react';\nimport { Checkbox } from '@burdenoff/fe-libs/ui';\nimport { AgentStatusBadge } from './AgentStatusBadge';\nimport type { Agent } from './types';\nimport { getAgentStatus } from './agentStatus';\nimport { classifyAgentHealth } from '../../utils/agentHealth';\nimport { ActionMenu, type ActionMenuItem } from '../shared/ActionMenu';\nimport { PlatformIcon } from '../shared/PlatformIcon';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface AgentCardProps {\n agent: Agent;\n onSelect?: (agent: Agent) => void;\n onConfigure?: (agent: Agent) => void;\n onSettings?: (agent: Agent) => void;\n onStart?: (agent: Agent) => void;\n onStop?: (agent: Agent) => void;\n onDisconnect?: (agent: Agent) => void;\n onEditTunnelUrl?: (agent: Agent) => void;\n selected?: boolean;\n onToggleSelect?: () => void;\n className?: string;\n /**\n * Last error from /health/ready or the live probe — shown in the badge\n * tooltip when the agent is degraded so users know WHY without curling\n * the tunnel themselves.\n */\n healthErrorMessage?: string | null;\n}\n\n/**\n * AgentCard - Display agent information in a card format\n *\n * @example\n * ```tsx\n * <AgentCard\n * agent={myAgent}\n * onSelect={handleSelect}\n * onConfigure={handleConfigure}\n * />\n * ```\n */\nexport const AgentCard: FC<AgentCardProps> = ({\n agent,\n onSelect,\n onConfigure,\n onSettings,\n onStart,\n onStop,\n onDisconnect,\n onEditTunnelUrl,\n selected = false,\n onToggleSelect,\n className = '',\n healthErrorMessage,\n}) => {\n const tr = useTr();\n // Support both onConfigure and onSettings\n const handleConfigure = onConfigure || onSettings;\n\n const status = getAgentStatus(agent);\n\n const formatLastHeartbeat = (timestamp: string | null | undefined) => {\n if (!timestamp) return tr('agentsPage.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('agentsPage.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n };\n\n const menuItems: ActionMenuItem[] = [];\n if (status === 'ACTIVE' && onStop) {\n menuItems.push({\n key: 'stop',\n label: tr('agentsPage.stopAgent', 'Stop Agent'),\n icon: Square,\n onSelect: () => onStop(agent),\n });\n }\n if ((status === 'STOPPED' || status === 'OFFLINE') && onStart) {\n menuItems.push({\n key: 'start',\n label: tr('agentsPage.startAgent', 'Start Agent'),\n icon: Play,\n onSelect: () => onStart(agent),\n });\n }\n if (status === 'DEGRADED' && onStart) {\n // Reconnect on a degraded agent fires the same startVibecontrolsAgent\n // mutation as a fresh start — svc-side that POSTs lifecycle/start to the\n // agent's tunnel, which usually clears transient failures (tunnel hiccup,\n // finalize retry). Labelled \"Reconnect\" not \"Start\" so the user knows\n // it's a recovery action, not creating a new instance.\n menuItems.push({\n key: 'reconnect',\n label: tr('agentsPage.reconnectAgent', 'Reconnect'),\n icon: RefreshCw,\n onSelect: () => onStart(agent),\n });\n }\n if (\n (status === 'ACTIVE' ||\n status === 'STOPPED' ||\n status === 'OFFLINE' ||\n status === 'DEGRADED') &&\n onDisconnect\n ) {\n menuItems.push({\n key: 'disconnect',\n label:\n status === 'OFFLINE'\n ? tr('agentsPage.clearConnection', 'Clear Connection')\n : tr('agentsPage.disconnect', 'Disconnect'),\n icon: Square,\n onSelect: () => onDisconnect(agent),\n variant: 'danger',\n });\n }\n if (onEditTunnelUrl) {\n menuItems.push({\n key: 'edit-tunnel',\n label: tr('agentsPage.editTunnel', 'Edit Tunnel'),\n icon: Link,\n onSelect: () => onEditTunnelUrl(agent),\n });\n }\n if (handleConfigure) {\n menuItems.push({\n key: 'settings',\n label: tr('agentsPage.settings', 'Settings'),\n icon: Settings,\n onSelect: () => handleConfigure(agent),\n });\n }\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n className={`bg-bg-surface border rounded-card p-4 h-full hover:border-border-strong hover:shadow-[var(--shadow-elevation-1)] transition-[colors,box-shadow] duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 focus-visible:ring-offset-bg-canvas ${\n selected\n ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30'\n : 'border-border-default'\n } ${className}`}\n onClick={() => onSelect?.(agent)}\n onKeyDown={(e) => {\n if (e.target !== e.currentTarget) return; // children (menu, checkbox, links) handle their own keys\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onSelect?.(agent);\n }\n }}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {/* Selection Checkbox */}\n {onToggleSelect && (\n <span\n className=\"flex flex-shrink-0 items-center justify-center max-md:min-h-11 max-md:min-w-11 max-md:-m-2 md:mt-0.5\"\n onClick={(e) => {\n e.stopPropagation();\n if (e.target === e.currentTarget) onToggleSelect();\n }}\n >\n <Checkbox\n checked={selected}\n onCheckedChange={() => onToggleSelect()}\n onClick={(e) => e.stopPropagation()}\n aria-label={tr('agentsPage.selectAgent', 'Select {{name}}', { name: agent.name })}\n />\n </span>\n )}\n <div className=\"flex-shrink-0 size-8 sm:size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Server className=\"size-4 sm:size-5 text-text-secondary\" aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate min-w-0 text-sm sm:text-base\">\n {agent.name}\n </h3>\n <PlatformIcon\n platform={agent.platform}\n className=\"size-4 flex-shrink-0 text-text-secondary\"\n />\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary truncate\">\n {agent.hostname || `ID: ${agent.id}`}\n </p>\n {(() => {\n // Always-visible (not just hover) guidance when degraded, so the\n // user knows whether it's self-healing or needs their action.\n const health = classifyAgentHealth(status, healthErrorMessage ?? null);\n if (health.phase === 'none') return null;\n return (\n <p\n className={`mt-1 flex items-start gap-1 text-xs ${\n health.phase === 'attention'\n ? 'text-status-warning-text'\n : 'text-content-tertiary'\n }`}\n >\n {health.inProgress && (\n <RefreshCw\n className=\"size-3 mt-0.5 flex-shrink-0 animate-spin\"\n aria-hidden=\"true\"\n />\n )}\n <span>\n <span className=\"font-medium\">{health.label}.</span> {health.actionHint}\n </span>\n </p>\n );\n })()}\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <AgentStatusBadge\n status={status}\n reason={healthErrorMessage ?? null}\n linkTo={`/vibecontrols/agents/${agent.id}?tab=connection`}\n onClick={(event) => {\n // The card itself has a `onSelect` click target that\n // routes to the agent detail. Stop propagation so a\n // badge click doesn't *also* fire that handler — the\n // anchor's href already routes to the right tab and\n // React Router picks it up via the surrounding shell.\n event.stopPropagation();\n }}\n />\n {(handleConfigure || onStart || onStop) && menuItems.length > 0 && (\n <ActionMenu\n ariaLabel={tr('agentsPage.agentActionsMenu', 'Agent actions menu')}\n title={tr('agentsPage.agentActions', 'Agent actions')}\n align=\"end\"\n items={menuItems}\n />\n )}\n </div>\n </div>\n\n <div className=\"mt-4 grid grid-cols-2 gap-3 text-sm\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"size-4\" aria-hidden=\"true\" />\n <span>{formatLastHeartbeat(agent.lastHeartbeat)}</span>\n </div>\n {agent.architecture && (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Cpu className=\"size-4\" aria-hidden=\"true\" />\n <span>{agent.architecture}</span>\n </div>\n )}\n </div>\n\n {agent.tunnelUrl && (\n // Plain \"Tunnel: <link>\" row. The colored status chip that used\n // to sit here was a redundant second status surface — the\n // canonical state lives in the <AgentStatusBadge> top-right.\n // The Globe glyph stays as a neutral icon (no status color).\n <div className=\"mt-3 flex items-center gap-2 text-sm\">\n <Globe className=\"size-4 flex-shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n <span className=\"text-text-secondary flex-shrink-0\">Tunnel:</span>\n <a\n href={agent.tunnelUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n aria-label={tr('agentsPage.tunnelOpensInNewTab', '{{url}} (opens in new tab)', {\n url: agent.tunnelUrl,\n })}\n className=\"min-w-0 truncate text-text-link hover:text-text-linkHover hover:underline\"\n >\n {agent.tunnelUrl}\n </a>\n </div>\n )}\n\n {agent.capabilities && agent.capabilities.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {agent.capabilities.slice(0, 4).map((cap) => (\n <span\n key={cap}\n className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded-pill\"\n >\n {cap}\n </span>\n ))}\n {agent.capabilities.length > 4 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded-pill\">\n +{agent.capabilities.length - 4}\n </span>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|