@burdenoff/microfe-vibecontrols 2026.529.2 → 2026.529.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.
@@ -1,32 +1,25 @@
1
- import { AgentStatusBadge as e } from "./AgentStatusBadge.js";
2
- import { useState as t } from "react";
3
- import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
- import { Clock as r, Cpu as i, Globe as a, Link as o, MoreVertical as s, Play as c, RefreshCw as l, Server as u, Settings as d, Square as f } from "lucide-react";
5
- import { AnimatePresence as p, motion as m } from "framer-motion";
6
- import { jsx as h, jsxs as g } from "react/jsx-runtime";
1
+ import { getAgentStatus as e } from "./agentStatus.js";
2
+ import { AgentStatusBadge as t } from "./AgentStatusBadge.js";
3
+ import { useState as n } from "react";
4
+ import { useI18n as r } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
5
+ import { Clock as i, Cpu as a, Globe as o, Link as s, MoreVertical as c, Play as l, RefreshCw as u, Server as d, Settings as f, Square as p } from "lucide-react";
6
+ import { AnimatePresence as m, motion as h } from "framer-motion";
7
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
7
8
  //#region src/components/agents/AgentCard.tsx
8
- var _ = ({ agent: _, onSelect: v, onConfigure: y, onSettings: b, onStart: x, onStop: S, onDisconnect: C, onEditTunnelUrl: w, selected: T = !1, onToggleSelect: E, className: D = "", healthErrorMessage: O }) => {
9
- let { t: k } = n(), A = (e, t) => {
10
- let n = k(e);
9
+ 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 }) => {
10
+ let { t: A } = r(), j = (e, t) => {
11
+ let n = A(e);
11
12
  return n === e ? t : n;
12
- }, j = y || b, [M, N] = t(!1), P = (() => {
13
- if (_.status) return _.status;
14
- if (!_.isActive) return "OFFLINE";
15
- if (_.lastHeartbeat) {
16
- let e = new Date(_.lastHeartbeat).getTime();
17
- if (Date.now() - e > 300 * 1e3) return "DEGRADED";
18
- }
19
- return "ACTIVE";
20
- })(), F = (e) => {
21
- if (!e) return A("agentsPage.never", "Never");
13
+ }, M = b || x, [N, P] = n(!1), F = e(v), I = (e) => {
14
+ if (!e) return j("agentsPage.never", "Never");
22
15
  let t = Date.now() - new Date(e).getTime();
23
- 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`;
24
- }, I = {
16
+ return t < 6e4 ? j("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`;
17
+ }, L = {
25
18
  darwin: "🍎",
26
19
  linux: "🐧",
27
20
  windows: "🪟"
28
- }[_.platform || ""] || "💻";
29
- return /* @__PURE__ */ g(m.div, {
21
+ }[v.platform || ""] || "💻";
22
+ return /* @__PURE__ */ _(h.div, {
30
23
  whileHover: {
31
24
  y: -2,
32
25
  transition: {
@@ -40,70 +33,70 @@ var _ = ({ agent: _, onSelect: v, onConfigure: y, onSettings: b, onStart: x, onS
40
33
  }
41
34
  },
42
35
  whileTap: { scale: .98 },
43
- className: `bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${T ? "border-action-primary-bg ring-1 ring-action-primary-bg/30" : "border-border-default"} ${D}`,
44
- onClick: () => v?.(_),
36
+ className: `bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${E ? "border-action-primary-bg ring-1 ring-action-primary-bg/30" : "border-border-default"} ${O}`,
37
+ onClick: () => y?.(v),
45
38
  children: [
46
- /* @__PURE__ */ g("div", {
39
+ /* @__PURE__ */ _("div", {
47
40
  className: "flex items-start justify-between gap-2",
48
- children: [/* @__PURE__ */ g("div", {
41
+ children: [/* @__PURE__ */ _("div", {
49
42
  className: "flex items-start gap-2 sm:gap-3 min-w-0 flex-1",
50
43
  children: [
51
- E && /* @__PURE__ */ h("button", {
44
+ D && /* @__PURE__ */ g("button", {
52
45
  type: "button",
53
46
  onClick: (e) => {
54
- e.stopPropagation(), E();
47
+ e.stopPropagation(), D();
55
48
  },
56
- className: `flex-shrink-0 size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${T ? "bg-action-primary-bg border-action-primary-bg text-action-primary-text" : "border-border-default hover:border-border-strong bg-bg-surface"} flex items-center justify-center`,
57
- children: T && /* @__PURE__ */ h("svg", {
49
+ className: `flex-shrink-0 size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${E ? "bg-action-primary-bg border-action-primary-bg text-action-primary-text" : "border-border-default hover:border-border-strong bg-bg-surface"} flex items-center justify-center`,
50
+ children: E && /* @__PURE__ */ g("svg", {
58
51
  className: "size-3",
59
52
  fill: "none",
60
53
  viewBox: "0 0 24 24",
61
54
  stroke: "currentColor",
62
55
  strokeWidth: 3,
63
- children: /* @__PURE__ */ h("path", {
56
+ children: /* @__PURE__ */ g("path", {
64
57
  strokeLinecap: "round",
65
58
  strokeLinejoin: "round",
66
59
  d: "M5 13l4 4L19 7"
67
60
  })
68
61
  })
69
62
  }),
70
- /* @__PURE__ */ h("div", {
63
+ /* @__PURE__ */ g("div", {
71
64
  className: "flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center",
72
- children: /* @__PURE__ */ h(u, { className: "size-4 sm:w-5 sm:h-5 text-text-secondary" })
65
+ children: /* @__PURE__ */ g(d, { className: "size-4 sm:w-5 sm:h-5 text-text-secondary" })
73
66
  }),
74
- /* @__PURE__ */ g("div", {
67
+ /* @__PURE__ */ _("div", {
75
68
  className: "min-w-0 flex-1",
76
- children: [/* @__PURE__ */ g("div", {
69
+ children: [/* @__PURE__ */ _("div", {
77
70
  className: "flex items-center gap-1.5 sm:gap-2 min-w-0",
78
- children: [/* @__PURE__ */ h("h3", {
71
+ children: [/* @__PURE__ */ g("h3", {
79
72
  className: "font-semibold text-text-primary truncate min-w-0 max-w-[120px] sm:max-w-none text-sm sm:text-base",
80
- children: _.name
81
- }), /* @__PURE__ */ h("span", {
73
+ children: v.name
74
+ }), /* @__PURE__ */ g("span", {
82
75
  className: "text-base sm:text-lg flex-shrink-0",
83
- children: I
76
+ children: L
84
77
  })]
85
- }), /* @__PURE__ */ h("p", {
78
+ }), /* @__PURE__ */ g("p", {
86
79
  className: "text-xs sm:text-sm text-text-secondary truncate",
87
- children: _.hostname || `ID: ${_.id}`
80
+ children: v.hostname || `ID: ${v.id}`
88
81
  })]
89
82
  })
90
83
  ]
91
- }), /* @__PURE__ */ g("div", {
84
+ }), /* @__PURE__ */ _("div", {
92
85
  className: "flex items-center gap-1 sm:gap-2 flex-shrink-0",
93
- children: [/* @__PURE__ */ h(e, {
94
- status: P,
95
- reason: O ?? null
96
- }), (j || x || S) && /* @__PURE__ */ g("div", {
86
+ children: [/* @__PURE__ */ g(t, {
87
+ status: F,
88
+ reason: k ?? null
89
+ }), (M || S || C) && /* @__PURE__ */ _("div", {
97
90
  className: "relative",
98
- children: [/* @__PURE__ */ h("button", {
91
+ children: [/* @__PURE__ */ g("button", {
99
92
  type: "button",
100
93
  onClick: (e) => {
101
- e.stopPropagation(), N(!M);
94
+ e.stopPropagation(), P(!N);
102
95
  },
103
96
  className: "p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors flex items-center justify-center",
104
97
  "aria-label": "Agent actions menu",
105
- children: /* @__PURE__ */ h(s, { className: "size-4 text-text-primary opacity-70 hover:opacity-100" })
106
- }), /* @__PURE__ */ h(p, { children: M && /* @__PURE__ */ g(m.div, {
98
+ children: /* @__PURE__ */ g(c, { className: "size-4 text-text-primary opacity-70 hover:opacity-100" })
99
+ }), /* @__PURE__ */ g(m, { children: N && /* @__PURE__ */ _(h.div, {
107
100
  initial: {
108
101
  opacity: 0,
109
102
  scale: .95,
@@ -122,102 +115,102 @@ var _ = ({ agent: _, onSelect: v, onConfigure: y, onSettings: b, onStart: x, onS
122
115
  transition: { duration: .15 },
123
116
  className: "absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10",
124
117
  children: [
125
- P === "ACTIVE" && S && /* @__PURE__ */ g(m.button, {
118
+ F === "ACTIVE" && C && /* @__PURE__ */ _(h.button, {
126
119
  whileTap: { scale: .97 },
127
120
  onClick: (e) => {
128
- e.stopPropagation(), S(_), N(!1);
121
+ e.stopPropagation(), C(v), P(!1);
129
122
  },
130
123
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
131
- children: [/* @__PURE__ */ h(f, { className: "size-4" }), A("agentsPage.stopAgent", "Stop Agent")]
124
+ children: [/* @__PURE__ */ g(p, { className: "size-4" }), j("agentsPage.stopAgent", "Stop Agent")]
132
125
  }),
133
- (P === "STOPPED" || P === "OFFLINE") && x && /* @__PURE__ */ g(m.button, {
126
+ (F === "STOPPED" || F === "OFFLINE") && S && /* @__PURE__ */ _(h.button, {
134
127
  whileTap: { scale: .97 },
135
128
  onClick: (e) => {
136
- e.stopPropagation(), x(_), N(!1);
129
+ e.stopPropagation(), S(v), P(!1);
137
130
  },
138
131
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
139
- children: [/* @__PURE__ */ h(c, { className: "size-4" }), A("agentsPage.startAgent", "Start Agent")]
132
+ children: [/* @__PURE__ */ g(l, { className: "size-4" }), j("agentsPage.startAgent", "Start Agent")]
140
133
  }),
141
- P === "DEGRADED" && x && /* @__PURE__ */ g(m.button, {
134
+ F === "DEGRADED" && S && /* @__PURE__ */ _(h.button, {
142
135
  whileTap: { scale: .97 },
143
136
  onClick: (e) => {
144
- e.stopPropagation(), x(_), N(!1);
137
+ e.stopPropagation(), S(v), P(!1);
145
138
  },
146
139
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
147
- title: O ? `Retry lifecycle/start — last error: ${O}` : "Retry lifecycle/start",
148
- children: [/* @__PURE__ */ h(l, { className: "size-4" }), A("agentsPage.reconnectAgent", "Reconnect")]
140
+ title: k ? `Retry lifecycle/start — last error: ${k}` : "Retry lifecycle/start",
141
+ children: [/* @__PURE__ */ g(u, { className: "size-4" }), j("agentsPage.reconnectAgent", "Reconnect")]
149
142
  }),
150
- (P === "ACTIVE" || P === "STOPPED" || P === "OFFLINE" || P === "DEGRADED") && C && /* @__PURE__ */ g(m.button, {
143
+ (F === "ACTIVE" || F === "STOPPED" || F === "OFFLINE" || F === "DEGRADED") && w && /* @__PURE__ */ _(h.button, {
151
144
  whileTap: { scale: .97 },
152
145
  onClick: (e) => {
153
- e.stopPropagation(), C(_), N(!1);
146
+ e.stopPropagation(), w(v), P(!1);
154
147
  },
155
148
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken",
156
- children: [/* @__PURE__ */ h(f, { className: "size-4" }), P === "OFFLINE" ? "Clear Connection" : "Disconnect"]
149
+ children: [/* @__PURE__ */ g(p, { className: "size-4" }), F === "OFFLINE" ? "Clear Connection" : "Disconnect"]
157
150
  }),
158
- w && /* @__PURE__ */ g(m.button, {
151
+ T && /* @__PURE__ */ _(h.button, {
159
152
  whileTap: { scale: .97 },
160
153
  onClick: (e) => {
161
- e.stopPropagation(), w(_), N(!1);
154
+ e.stopPropagation(), T(v), P(!1);
162
155
  },
163
156
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
164
- children: [/* @__PURE__ */ h(o, { className: "size-4" }), "Edit Tunnel"]
157
+ children: [/* @__PURE__ */ g(s, { className: "size-4" }), "Edit Tunnel"]
165
158
  }),
166
- j && /* @__PURE__ */ g(m.button, {
159
+ M && /* @__PURE__ */ _(h.button, {
167
160
  whileTap: { scale: .97 },
168
161
  onClick: (e) => {
169
- e.stopPropagation(), j(_), N(!1);
162
+ e.stopPropagation(), M(v), P(!1);
170
163
  },
171
164
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-b-lg",
172
- children: [/* @__PURE__ */ h(d, { className: "size-4" }), "Settings"]
165
+ children: [/* @__PURE__ */ g(f, { className: "size-4" }), "Settings"]
173
166
  })
174
167
  ]
175
168
  }) })]
176
169
  })]
177
170
  })]
178
171
  }),
179
- /* @__PURE__ */ g("div", {
172
+ /* @__PURE__ */ _("div", {
180
173
  className: "mt-4 grid grid-cols-2 gap-3 text-sm",
181
- children: [/* @__PURE__ */ g("div", {
174
+ children: [/* @__PURE__ */ _("div", {
182
175
  className: "flex items-center gap-2 text-text-secondary",
183
- children: [/* @__PURE__ */ h(r, { className: "size-4" }), /* @__PURE__ */ h("span", { children: F(_.lastHeartbeat) })]
184
- }), _.architecture && /* @__PURE__ */ g("div", {
176
+ children: [/* @__PURE__ */ g(i, { className: "size-4" }), /* @__PURE__ */ g("span", { children: I(v.lastHeartbeat) })]
177
+ }), v.architecture && /* @__PURE__ */ _("div", {
185
178
  className: "flex items-center gap-2 text-text-secondary",
186
- children: [/* @__PURE__ */ h(i, { className: "size-4" }), /* @__PURE__ */ h("span", { children: _.architecture })]
179
+ children: [/* @__PURE__ */ g(a, { className: "size-4" }), /* @__PURE__ */ g("span", { children: v.architecture })]
187
180
  })]
188
181
  }),
189
- _.tunnelUrl && /* @__PURE__ */ g("div", {
182
+ v.tunnelUrl && /* @__PURE__ */ _("div", {
190
183
  className: "mt-3 flex items-center gap-2 text-sm",
191
184
  children: [
192
- /* @__PURE__ */ h(a, { className: "size-4 flex-shrink-0 text-text-secondary" }),
193
- /* @__PURE__ */ h("span", {
185
+ /* @__PURE__ */ g(o, { className: "size-4 flex-shrink-0 text-text-secondary" }),
186
+ /* @__PURE__ */ g("span", {
194
187
  className: "text-text-secondary flex-shrink-0",
195
188
  children: "Tunnel:"
196
189
  }),
197
- /* @__PURE__ */ h("a", {
198
- href: _.tunnelUrl,
190
+ /* @__PURE__ */ g("a", {
191
+ href: v.tunnelUrl,
199
192
  target: "_blank",
200
193
  rel: "noopener noreferrer",
201
194
  onClick: (e) => e.stopPropagation(),
202
195
  className: "text-action-primary-text hover:underline truncate",
203
- children: _.tunnelUrl
196
+ children: v.tunnelUrl
204
197
  })
205
198
  ]
206
199
  }),
207
- _.capabilities && _.capabilities.length > 0 && /* @__PURE__ */ g("div", {
200
+ v.capabilities && v.capabilities.length > 0 && /* @__PURE__ */ _("div", {
208
201
  className: "mt-3 flex flex-wrap gap-1",
209
- children: [_.capabilities.slice(0, 4).map((e) => /* @__PURE__ */ h("span", {
202
+ children: [v.capabilities.slice(0, 4).map((e) => /* @__PURE__ */ g("span", {
210
203
  className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded",
211
204
  children: e
212
- }, e)), _.capabilities.length > 4 && /* @__PURE__ */ g("span", {
205
+ }, e)), v.capabilities.length > 4 && /* @__PURE__ */ _("span", {
213
206
  className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded",
214
- children: ["+", _.capabilities.length - 4]
207
+ children: ["+", v.capabilities.length - 4]
215
208
  })]
216
209
  })
217
210
  ]
218
211
  });
219
212
  };
220
213
  //#endregion
221
- export { _ as AgentCard };
214
+ export { v as AgentCard };
222
215
 
223
216
  //# 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';\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 getStatus = (): AgentStatus => {\n if (agent.status) return agent.status;\n if (!agent.isActive) return 'OFFLINE';\n\n if (agent.lastHeartbeat) {\n const lastHeartbeatTime = new Date(agent.lastHeartbeat).getTime();\n const now = Date.now();\n const diff = now - lastHeartbeatTime;\n\n // If heartbeat is older than 5 minutes, consider it inactive\n if (diff > 5 * 60 * 1000) return 'DEGRADED';\n }\n\n return 'ACTIVE';\n };\n\n const status = getStatus();\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 </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 status={status} reason={healthErrorMessage ?? null} />\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 { 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';\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 </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 status={status} reason={healthErrorMessage ?? null} />\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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"}