@burdenoff/microfe-vibecontrols 2026.531.16 → 2026.601.1
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/components/agent-manager/controls/ModelSelector.js +1 -1
- package/dist/components/agent-manager/controls/ModelSelector.js.map +1 -1
- package/dist/components/agents/AgentCard.js +106 -85
- package/dist/components/agents/AgentCard.js.map +1 -1
- package/dist/components/agents/AgentStatusBadge.js +25 -24
- package/dist/components/agents/AgentStatusBadge.js.map +1 -1
- package/dist/components/shared/LinkToVibePanel.js +36 -26
- package/dist/components/shared/LinkToVibePanel.js.map +1 -1
- package/dist/components/shared/PluginGate.js +72 -68
- package/dist/components/shared/PluginGate.js.map +1 -1
- package/dist/components/shared/cardMotion.js +28 -0
- package/dist/components/shared/cardMotion.js.map +1 -0
- package/dist/components/templates/TemplateCard.js +43 -50
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +57 -49
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/vibe/VibeLinkedEntitiesPanel.js +121 -72
- package/dist/components/vibe/VibeLinkedEntitiesPanel.js.map +1 -1
- package/dist/components/vibes/VibeCard.js +65 -72
- package/dist/components/vibes/VibeCard.js.map +1 -1
- package/dist/generated/wspace-operations.js +964 -886
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/index.js +10 -10
- package/dist/pages/ActionsPage.js +297 -268
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +11 -11
- package/dist/pages/AnalyticsPage.js +2 -2
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +16 -16
- package/dist/pages/VibeAuditPage.js +7 -7
- package/dist/pages/VibeDeckPage.js +180 -180
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/ai/AIPage.js +2 -2
- package/dist/pages/ai/AIPage.js.map +1 -1
- package/dist/pages/ai/AgentManagerPage.js +162 -127
- package/dist/pages/ai/AgentManagerPage.js.map +1 -1
- package/dist/utils/agentHealth.js +31 -0
- package/dist/utils/agentHealth.js.map +1 -0
- package/package.json +2 -2
|
@@ -151,7 +151,7 @@ function m({ value: m, onChange: h, provider: g, agentId: _, profile: v }) {
|
|
|
151
151
|
className: "w-full px-3 py-1.5 text-xs border-b border-border-default bg-transparent text-text-primary placeholder:text-text-tertiary focus:outline-none sticky top-0 bg-bg-surface"
|
|
152
152
|
}),
|
|
153
153
|
N.map((e) => /* @__PURE__ */ c("div", { children: e.models.length > 0 && /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c("div", {
|
|
154
|
-
className: "px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-text-
|
|
154
|
+
className: "px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-text-secondary bg-bg-sunken",
|
|
155
155
|
children: e.label
|
|
156
156
|
}), e.models.map((e) => /* @__PURE__ */ c("button", {
|
|
157
157
|
type: "button",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModelSelector.js","names":[],"sources":["../../../../src/components/agent-manager/controls/ModelSelector.tsx"],"sourcesContent":["/**\n * Model selector dropdown.\n *\n * Displays a compact button with the current model name that opens a\n * grouped dropdown of models filtered by SDK provider.\n */\n\nimport { useState, useRef, useEffect, useCallback } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport { fetchModels } from '@/services/aiApi';\nimport type { AgentSdkType } from '@/types/agentManager';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface ModelSelectorProps {\n value: string;\n onChange: (model: string) => void;\n provider?: AgentSdkType;\n agentId?: string;\n /** Canonical CLI profile name on the remote agent. */\n profile?: string;\n}\n\ninterface ModelGroup {\n label: string;\n sdk: AgentSdkType;\n models: string[];\n}\n\nconst MODEL_GROUPS: ModelGroup[] = [\n { label: 'Claude', sdk: 'claude', models: [] },\n { label: 'Codex', sdk: 'codex', models: [] },\n { label: 'Gemini', sdk: 'gemini', models: [] },\n { label: 'OpenCode', sdk: 'opencode', models: [] },\n { label: 'OpenAI-compatible', sdk: 'openai-compat', models: [] },\n { label: 'Goose', sdk: 'goose', models: [] },\n { label: 'Amp', sdk: 'amp', models: [] },\n { label: 'Copilot', sdk: 'copilot', models: [] },\n { label: 'Crush', sdk: 'crush', models: [] },\n { label: 'Pi', sdk: 'pi', models: [] },\n { label: 'Aider', sdk: 'aider', models: [] },\n { label: 'Plandex', sdk: 'plandex', models: [] },\n];\n\n/** Truncate model name for compact display */\nfunction displayName(model: string): string {\n // Remove common prefixes for compact display\n return model\n .replace(/^opencode\\//, '')\n .replace('claude-', '')\n .replace('-20250514', '')\n .replace('-20251001', '');\n}\n\nfunction modelGroupLabel(provider: AgentSdkType): string {\n return MODEL_GROUPS.find((group) => group.sdk === provider)?.label ?? provider;\n}\n\nfunction uniqueModels(models: string[], currentValue: string): string[] {\n return Array.from(new Set([currentValue, ...models].filter(Boolean)));\n}\n\nexport function ModelSelector({ value, onChange, provider, agentId, profile }: ModelSelectorProps) {\n const tr = useTr();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const [agentModels, setAgentModels] = useState<string[] | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n const [pos, setPos] = useState({ bottom: 0, left: 0 });\n\n const filteredGroups =\n provider && agentModels\n ? [\n {\n label: modelGroupLabel(provider),\n sdk: provider,\n models: uniqueModels(agentModels, value),\n },\n ]\n : provider\n ? MODEL_GROUPS.filter((g) => g.sdk === provider)\n : MODEL_GROUPS;\n\n const handleClickOutside = useCallback((e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n }, []);\n\n useEffect(() => {\n if (open) {\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }\n }, [open, handleClickOutside]);\n\n useEffect(() => {\n if (!open) setSearch('');\n }, [open]);\n\n useEffect(() => {\n if (!open || !agentId || !provider) return;\n\n let cancelled = false;\n setAgentModels(null);\n void fetchModels({ id: agentId, profile: profile ?? 'default' }, provider)\n .then((models) => {\n if (cancelled) return;\n const modelIds = models.map((model) => model.id).filter(Boolean);\n setAgentModels(modelIds);\n })\n .catch(() => {\n if (!cancelled) setAgentModels([]);\n });\n\n return () => {\n cancelled = true;\n };\n }, [agentId, open, profile, provider]);\n\n const searchLower = search.toLowerCase();\n const searchedGroups = filteredGroups.map((g) => ({\n ...g,\n models: search ? g.models.filter((m) => m.toLowerCase().includes(searchLower)) : g.models,\n }));\n\n const handleToggle = () => {\n if (!open && buttonRef.current) {\n const rect = buttonRef.current.getBoundingClientRect();\n setPos({ bottom: window.innerHeight - rect.top + 4, left: rect.left });\n }\n setOpen((prev) => !prev);\n };\n\n return (\n <div ref={containerRef} className=\"relative\">\n <button\n ref={buttonRef}\n type=\"button\"\n onClick={handleToggle}\n className=\"flex items-center gap-1 px-2 py-1 text-xs font-medium rounded border border-border-default bg-bg-surface text-text-primary hover:bg-bg-elevated hover:border-border-strong transition-colors max-w-[160px]\"\n title={value}\n >\n <span className=\"truncate\">{displayName(value)}</span>\n <ChevronDown className=\"size-3 flex-shrink-0\" />\n </button>\n\n {open && (\n <div\n className=\"fixed w-64 max-h-72 overflow-y-auto rounded-lg border border-border-default bg-bg-surface shadow-lg z-[100]\"\n style={{ bottom: pos.bottom, left: pos.left }}\n >\n <input\n aria-label=\"Search...\"\n type=\"text\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search...\"\n className=\"w-full px-3 py-1.5 text-xs border-b border-border-default bg-transparent text-text-primary placeholder:text-text-tertiary focus:outline-none sticky top-0 bg-bg-surface\"\n />\n {searchedGroups.map((group) => (\n <div key={group.sdk}>\n {group.models.length > 0 && (\n <>\n <div className=\"px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-text-tertiary bg-bg-sunken\">\n {group.label}\n </div>\n {group.models.map((model) => (\n <button\n key={model}\n type=\"button\"\n onClick={() => {\n onChange(model);\n setOpen(false);\n }}\n className={`w-full text-left px-3 py-2 text-sm hover:bg-bg-elevated transition-colors ${\n model === value\n ? 'text-text-primary bg-bg-sunken font-medium'\n : 'text-text-primary'\n }`}\n >\n {model}\n </button>\n ))}\n </>\n )}\n </div>\n ))}\n {searchedGroups.every((g) => g.models.length === 0) && (\n <div className=\"px-3 py-4 text-sm text-text-tertiary text-center\">\n {tr('vibecontrols.agentManager.controls.noModelsAvailable', 'No models available')}\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ModelSelector.js","names":[],"sources":["../../../../src/components/agent-manager/controls/ModelSelector.tsx"],"sourcesContent":["/**\n * Model selector dropdown.\n *\n * Displays a compact button with the current model name that opens a\n * grouped dropdown of models filtered by SDK provider.\n */\n\nimport { useState, useRef, useEffect, useCallback } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport { fetchModels } from '@/services/aiApi';\nimport type { AgentSdkType } from '@/types/agentManager';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface ModelSelectorProps {\n value: string;\n onChange: (model: string) => void;\n provider?: AgentSdkType;\n agentId?: string;\n /** Canonical CLI profile name on the remote agent. */\n profile?: string;\n}\n\ninterface ModelGroup {\n label: string;\n sdk: AgentSdkType;\n models: string[];\n}\n\nconst MODEL_GROUPS: ModelGroup[] = [\n { label: 'Claude', sdk: 'claude', models: [] },\n { label: 'Codex', sdk: 'codex', models: [] },\n { label: 'Gemini', sdk: 'gemini', models: [] },\n { label: 'OpenCode', sdk: 'opencode', models: [] },\n { label: 'OpenAI-compatible', sdk: 'openai-compat', models: [] },\n { label: 'Goose', sdk: 'goose', models: [] },\n { label: 'Amp', sdk: 'amp', models: [] },\n { label: 'Copilot', sdk: 'copilot', models: [] },\n { label: 'Crush', sdk: 'crush', models: [] },\n { label: 'Pi', sdk: 'pi', models: [] },\n { label: 'Aider', sdk: 'aider', models: [] },\n { label: 'Plandex', sdk: 'plandex', models: [] },\n];\n\n/** Truncate model name for compact display */\nfunction displayName(model: string): string {\n // Remove common prefixes for compact display\n return model\n .replace(/^opencode\\//, '')\n .replace('claude-', '')\n .replace('-20250514', '')\n .replace('-20251001', '');\n}\n\nfunction modelGroupLabel(provider: AgentSdkType): string {\n return MODEL_GROUPS.find((group) => group.sdk === provider)?.label ?? provider;\n}\n\nfunction uniqueModels(models: string[], currentValue: string): string[] {\n return Array.from(new Set([currentValue, ...models].filter(Boolean)));\n}\n\nexport function ModelSelector({ value, onChange, provider, agentId, profile }: ModelSelectorProps) {\n const tr = useTr();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const [agentModels, setAgentModels] = useState<string[] | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n const [pos, setPos] = useState({ bottom: 0, left: 0 });\n\n const filteredGroups =\n provider && agentModels\n ? [\n {\n label: modelGroupLabel(provider),\n sdk: provider,\n models: uniqueModels(agentModels, value),\n },\n ]\n : provider\n ? MODEL_GROUPS.filter((g) => g.sdk === provider)\n : MODEL_GROUPS;\n\n const handleClickOutside = useCallback((e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n }, []);\n\n useEffect(() => {\n if (open) {\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }\n }, [open, handleClickOutside]);\n\n useEffect(() => {\n if (!open) setSearch('');\n }, [open]);\n\n useEffect(() => {\n if (!open || !agentId || !provider) return;\n\n let cancelled = false;\n setAgentModels(null);\n void fetchModels({ id: agentId, profile: profile ?? 'default' }, provider)\n .then((models) => {\n if (cancelled) return;\n const modelIds = models.map((model) => model.id).filter(Boolean);\n setAgentModels(modelIds);\n })\n .catch(() => {\n if (!cancelled) setAgentModels([]);\n });\n\n return () => {\n cancelled = true;\n };\n }, [agentId, open, profile, provider]);\n\n const searchLower = search.toLowerCase();\n const searchedGroups = filteredGroups.map((g) => ({\n ...g,\n models: search ? g.models.filter((m) => m.toLowerCase().includes(searchLower)) : g.models,\n }));\n\n const handleToggle = () => {\n if (!open && buttonRef.current) {\n const rect = buttonRef.current.getBoundingClientRect();\n setPos({ bottom: window.innerHeight - rect.top + 4, left: rect.left });\n }\n setOpen((prev) => !prev);\n };\n\n return (\n <div ref={containerRef} className=\"relative\">\n <button\n ref={buttonRef}\n type=\"button\"\n onClick={handleToggle}\n className=\"flex items-center gap-1 px-2 py-1 text-xs font-medium rounded border border-border-default bg-bg-surface text-text-primary hover:bg-bg-elevated hover:border-border-strong transition-colors max-w-[160px]\"\n title={value}\n >\n <span className=\"truncate\">{displayName(value)}</span>\n <ChevronDown className=\"size-3 flex-shrink-0\" />\n </button>\n\n {open && (\n <div\n className=\"fixed w-64 max-h-72 overflow-y-auto rounded-lg border border-border-default bg-bg-surface shadow-lg z-[100]\"\n style={{ bottom: pos.bottom, left: pos.left }}\n >\n <input\n aria-label=\"Search...\"\n type=\"text\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search...\"\n className=\"w-full px-3 py-1.5 text-xs border-b border-border-default bg-transparent text-text-primary placeholder:text-text-tertiary focus:outline-none sticky top-0 bg-bg-surface\"\n />\n {searchedGroups.map((group) => (\n <div key={group.sdk}>\n {group.models.length > 0 && (\n <>\n <div className=\"px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-text-secondary bg-bg-sunken\">\n {group.label}\n </div>\n {group.models.map((model) => (\n <button\n key={model}\n type=\"button\"\n onClick={() => {\n onChange(model);\n setOpen(false);\n }}\n className={`w-full text-left px-3 py-2 text-sm hover:bg-bg-elevated transition-colors ${\n model === value\n ? 'text-text-primary bg-bg-sunken font-medium'\n : 'text-text-primary'\n }`}\n >\n {model}\n </button>\n ))}\n </>\n )}\n </div>\n ))}\n {searchedGroups.every((g) => g.models.length === 0) && (\n <div className=\"px-3 py-4 text-sm text-text-tertiary text-center\">\n {tr('vibecontrols.agentManager.controls.noModelsAvailable', 'No models available')}\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { getAgentStatus as e } from "./agentStatus.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
2
|
+
import { classifyAgentHealth as t } from "../../utils/agentHealth.js";
|
|
3
|
+
import { AgentStatusBadge as n } from "./AgentStatusBadge.js";
|
|
4
|
+
import { useState as r } from "react";
|
|
5
|
+
import { useI18n as i } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
6
|
+
import { Clock as a, Cpu as o, Globe as s, Link as c, MoreVertical as l, Play as u, RefreshCw as d, Server as f, Settings as p, Square as m } from "lucide-react";
|
|
7
|
+
import { AnimatePresence as h, motion as g } from "framer-motion";
|
|
8
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
8
9
|
//#region src/components/agents/AgentCard.tsx
|
|
9
|
-
var
|
|
10
|
-
let { t:
|
|
11
|
-
let n =
|
|
10
|
+
var y = ({ agent: y, onSelect: b, onConfigure: x, onSettings: S, onStart: C, onStop: w, onDisconnect: T, onEditTunnelUrl: E, selected: D = !1, onToggleSelect: O, className: k = "", healthErrorMessage: A }) => {
|
|
11
|
+
let { t: j } = i(), M = (e, t) => {
|
|
12
|
+
let n = j(e);
|
|
12
13
|
return n === e ? t : n;
|
|
13
|
-
},
|
|
14
|
-
if (!e) return
|
|
14
|
+
}, N = x || S, [P, F] = r(!1), I = e(y), L = (e) => {
|
|
15
|
+
if (!e) return M("agentsPage.never", "Never");
|
|
15
16
|
let t = Date.now() - new Date(e).getTime();
|
|
16
|
-
return t < 6e4 ?
|
|
17
|
-
},
|
|
17
|
+
return t < 6e4 ? M("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`;
|
|
18
|
+
}, R = {
|
|
18
19
|
darwin: "🍎",
|
|
19
20
|
linux: "🐧",
|
|
20
21
|
windows: "🪟"
|
|
21
|
-
}[
|
|
22
|
-
return /* @__PURE__ */
|
|
22
|
+
}[y.platform || ""] || "💻";
|
|
23
|
+
return /* @__PURE__ */ v(g.div, {
|
|
23
24
|
whileHover: {
|
|
24
25
|
y: -2,
|
|
25
26
|
transition: {
|
|
@@ -33,74 +34,94 @@ var v = ({ agent: v, onSelect: y, onConfigure: b, onSettings: x, onStart: S, onS
|
|
|
33
34
|
}
|
|
34
35
|
},
|
|
35
36
|
whileTap: { scale: .98 },
|
|
36
|
-
className: `bg-bg-surface border rounded-lg p-4 h-full hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${
|
|
37
|
-
onClick: () =>
|
|
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),
|
|
38
39
|
children: [
|
|
39
|
-
/* @__PURE__ */
|
|
40
|
+
/* @__PURE__ */ v("div", {
|
|
40
41
|
className: "flex items-start justify-between gap-2",
|
|
41
|
-
children: [/* @__PURE__ */
|
|
42
|
+
children: [/* @__PURE__ */ v("div", {
|
|
42
43
|
className: "flex items-start gap-2 sm:gap-3 min-w-0 flex-1",
|
|
43
44
|
children: [
|
|
44
|
-
|
|
45
|
+
O && /* @__PURE__ */ _("button", {
|
|
45
46
|
type: "button",
|
|
46
47
|
onClick: (e) => {
|
|
47
|
-
e.stopPropagation(),
|
|
48
|
+
e.stopPropagation(), O();
|
|
48
49
|
},
|
|
49
|
-
className: `flex-shrink-0 size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${
|
|
50
|
-
children:
|
|
50
|
+
className: `flex-shrink-0 size-5 min-w-[1.25rem] min-h-[1.25rem] rounded border transition-colors mt-0.5 ${D ? "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`,
|
|
51
|
+
children: D && /* @__PURE__ */ _("svg", {
|
|
51
52
|
className: "size-3",
|
|
52
53
|
fill: "none",
|
|
53
54
|
viewBox: "0 0 24 24",
|
|
54
55
|
stroke: "currentColor",
|
|
55
56
|
strokeWidth: 3,
|
|
56
|
-
children: /* @__PURE__ */
|
|
57
|
+
children: /* @__PURE__ */ _("path", {
|
|
57
58
|
strokeLinecap: "round",
|
|
58
59
|
strokeLinejoin: "round",
|
|
59
60
|
d: "M5 13l4 4L19 7"
|
|
60
61
|
})
|
|
61
62
|
})
|
|
62
63
|
}),
|
|
63
|
-
/* @__PURE__ */
|
|
64
|
+
/* @__PURE__ */ _("div", {
|
|
64
65
|
className: "flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center",
|
|
65
|
-
children: /* @__PURE__ */
|
|
66
|
+
children: /* @__PURE__ */ _(f, { className: "size-4 sm:w-5 sm:h-5 text-text-secondary" })
|
|
66
67
|
}),
|
|
67
|
-
/* @__PURE__ */
|
|
68
|
+
/* @__PURE__ */ v("div", {
|
|
68
69
|
className: "min-w-0 flex-1",
|
|
69
|
-
children: [
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
70
|
+
children: [
|
|
71
|
+
/* @__PURE__ */ v("div", {
|
|
72
|
+
className: "flex items-center gap-1.5 sm:gap-2 min-w-0",
|
|
73
|
+
children: [/* @__PURE__ */ _("h3", {
|
|
74
|
+
className: "font-semibold text-text-primary truncate min-w-0 max-w-[120px] sm:max-w-none text-sm sm:text-base",
|
|
75
|
+
children: y.name
|
|
76
|
+
}), /* @__PURE__ */ _("span", {
|
|
77
|
+
className: "text-base sm:text-lg flex-shrink-0",
|
|
78
|
+
children: R
|
|
79
|
+
})]
|
|
80
|
+
}),
|
|
81
|
+
/* @__PURE__ */ _("p", {
|
|
82
|
+
className: "text-xs sm:text-sm text-text-secondary truncate",
|
|
83
|
+
children: y.hostname || `ID: ${y.id}`
|
|
84
|
+
}),
|
|
85
|
+
(() => {
|
|
86
|
+
let e = t(I, A ?? null);
|
|
87
|
+
return e.phase === "none" ? null : /* @__PURE__ */ v("p", {
|
|
88
|
+
className: `mt-1 flex items-start gap-1 text-xs ${e.phase === "attention" ? "text-status-warning-text" : "text-text-tertiary"}`,
|
|
89
|
+
children: [e.inProgress && /* @__PURE__ */ _(d, {
|
|
90
|
+
className: "size-3 mt-0.5 flex-shrink-0 animate-spin",
|
|
91
|
+
"aria-hidden": "true"
|
|
92
|
+
}), /* @__PURE__ */ v("span", { children: [
|
|
93
|
+
/* @__PURE__ */ v("span", {
|
|
94
|
+
className: "font-medium",
|
|
95
|
+
children: [e.label, "."]
|
|
96
|
+
}),
|
|
97
|
+
" ",
|
|
98
|
+
e.actionHint
|
|
99
|
+
] })]
|
|
100
|
+
});
|
|
101
|
+
})()
|
|
102
|
+
]
|
|
82
103
|
})
|
|
83
104
|
]
|
|
84
|
-
}), /* @__PURE__ */
|
|
105
|
+
}), /* @__PURE__ */ v("div", {
|
|
85
106
|
className: "flex items-center gap-1 sm:gap-2 flex-shrink-0",
|
|
86
|
-
children: [/* @__PURE__ */
|
|
87
|
-
status:
|
|
88
|
-
reason:
|
|
89
|
-
linkTo: `/vibecontrols/agents/${
|
|
107
|
+
children: [/* @__PURE__ */ _(n, {
|
|
108
|
+
status: I,
|
|
109
|
+
reason: A ?? null,
|
|
110
|
+
linkTo: `/vibecontrols/agents/${y.id}?tab=connection`,
|
|
90
111
|
onClick: (e) => {
|
|
91
112
|
e.stopPropagation();
|
|
92
113
|
}
|
|
93
|
-
}), (
|
|
114
|
+
}), (N || C || w) && /* @__PURE__ */ v("div", {
|
|
94
115
|
className: "relative",
|
|
95
|
-
children: [/* @__PURE__ */
|
|
116
|
+
children: [/* @__PURE__ */ _("button", {
|
|
96
117
|
type: "button",
|
|
97
118
|
onClick: (e) => {
|
|
98
|
-
e.stopPropagation(),
|
|
119
|
+
e.stopPropagation(), F(!P);
|
|
99
120
|
},
|
|
100
121
|
className: "p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors flex items-center justify-center",
|
|
101
122
|
"aria-label": "Agent actions menu",
|
|
102
|
-
children: /* @__PURE__ */
|
|
103
|
-
}), /* @__PURE__ */
|
|
123
|
+
children: /* @__PURE__ */ _(l, { className: "size-4 text-text-primary opacity-70 hover:opacity-100" })
|
|
124
|
+
}), /* @__PURE__ */ _(h, { children: P && /* @__PURE__ */ v(g.div, {
|
|
104
125
|
initial: {
|
|
105
126
|
opacity: 0,
|
|
106
127
|
scale: .95,
|
|
@@ -119,102 +140,102 @@ var v = ({ agent: v, onSelect: y, onConfigure: b, onSettings: x, onStart: S, onS
|
|
|
119
140
|
transition: { duration: .15 },
|
|
120
141
|
className: "absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10",
|
|
121
142
|
children: [
|
|
122
|
-
|
|
143
|
+
I === "ACTIVE" && w && /* @__PURE__ */ v(g.button, {
|
|
123
144
|
whileTap: { scale: .97 },
|
|
124
145
|
onClick: (e) => {
|
|
125
|
-
e.stopPropagation(),
|
|
146
|
+
e.stopPropagation(), w(y), F(!1);
|
|
126
147
|
},
|
|
127
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",
|
|
128
|
-
children: [/* @__PURE__ */
|
|
149
|
+
children: [/* @__PURE__ */ _(m, { className: "size-4" }), M("agentsPage.stopAgent", "Stop Agent")]
|
|
129
150
|
}),
|
|
130
|
-
(
|
|
151
|
+
(I === "STOPPED" || I === "OFFLINE") && C && /* @__PURE__ */ v(g.button, {
|
|
131
152
|
whileTap: { scale: .97 },
|
|
132
153
|
onClick: (e) => {
|
|
133
|
-
e.stopPropagation(),
|
|
154
|
+
e.stopPropagation(), C(y), F(!1);
|
|
134
155
|
},
|
|
135
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",
|
|
136
|
-
children: [/* @__PURE__ */
|
|
157
|
+
children: [/* @__PURE__ */ _(u, { className: "size-4" }), M("agentsPage.startAgent", "Start Agent")]
|
|
137
158
|
}),
|
|
138
|
-
|
|
159
|
+
I === "DEGRADED" && C && /* @__PURE__ */ v(g.button, {
|
|
139
160
|
whileTap: { scale: .97 },
|
|
140
161
|
onClick: (e) => {
|
|
141
|
-
e.stopPropagation(),
|
|
162
|
+
e.stopPropagation(), C(y), F(!1);
|
|
142
163
|
},
|
|
143
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",
|
|
144
|
-
title:
|
|
145
|
-
children: [/* @__PURE__ */
|
|
165
|
+
title: A ? `Retry lifecycle/start — last error: ${A}` : "Retry lifecycle/start",
|
|
166
|
+
children: [/* @__PURE__ */ _(d, { className: "size-4" }), M("agentsPage.reconnectAgent", "Reconnect")]
|
|
146
167
|
}),
|
|
147
|
-
(
|
|
168
|
+
(I === "ACTIVE" || I === "STOPPED" || I === "OFFLINE" || I === "DEGRADED") && T && /* @__PURE__ */ v(g.button, {
|
|
148
169
|
whileTap: { scale: .97 },
|
|
149
170
|
onClick: (e) => {
|
|
150
|
-
e.stopPropagation(),
|
|
171
|
+
e.stopPropagation(), T(y), F(!1);
|
|
151
172
|
},
|
|
152
173
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-action-danger-text hover:bg-bg-sunken",
|
|
153
|
-
children: [/* @__PURE__ */
|
|
174
|
+
children: [/* @__PURE__ */ _(m, { className: "size-4" }), I === "OFFLINE" ? "Clear Connection" : "Disconnect"]
|
|
154
175
|
}),
|
|
155
|
-
|
|
176
|
+
E && /* @__PURE__ */ v(g.button, {
|
|
156
177
|
whileTap: { scale: .97 },
|
|
157
178
|
onClick: (e) => {
|
|
158
|
-
e.stopPropagation(),
|
|
179
|
+
e.stopPropagation(), E(y), F(!1);
|
|
159
180
|
},
|
|
160
181
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
161
|
-
children: [/* @__PURE__ */
|
|
182
|
+
children: [/* @__PURE__ */ _(c, { className: "size-4" }), "Edit Tunnel"]
|
|
162
183
|
}),
|
|
163
|
-
|
|
184
|
+
N && /* @__PURE__ */ v(g.button, {
|
|
164
185
|
whileTap: { scale: .97 },
|
|
165
186
|
onClick: (e) => {
|
|
166
|
-
e.stopPropagation(),
|
|
187
|
+
e.stopPropagation(), N(y), F(!1);
|
|
167
188
|
},
|
|
168
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",
|
|
169
|
-
children: [/* @__PURE__ */
|
|
190
|
+
children: [/* @__PURE__ */ _(p, { className: "size-4" }), "Settings"]
|
|
170
191
|
})
|
|
171
192
|
]
|
|
172
193
|
}) })]
|
|
173
194
|
})]
|
|
174
195
|
})]
|
|
175
196
|
}),
|
|
176
|
-
/* @__PURE__ */
|
|
197
|
+
/* @__PURE__ */ v("div", {
|
|
177
198
|
className: "mt-4 grid grid-cols-2 gap-3 text-sm",
|
|
178
|
-
children: [/* @__PURE__ */
|
|
199
|
+
children: [/* @__PURE__ */ v("div", {
|
|
179
200
|
className: "flex items-center gap-2 text-text-secondary",
|
|
180
|
-
children: [/* @__PURE__ */
|
|
181
|
-
}),
|
|
201
|
+
children: [/* @__PURE__ */ _(a, { className: "size-4" }), /* @__PURE__ */ _("span", { children: L(y.lastHeartbeat) })]
|
|
202
|
+
}), y.architecture && /* @__PURE__ */ v("div", {
|
|
182
203
|
className: "flex items-center gap-2 text-text-secondary",
|
|
183
|
-
children: [/* @__PURE__ */
|
|
204
|
+
children: [/* @__PURE__ */ _(o, { className: "size-4" }), /* @__PURE__ */ _("span", { children: y.architecture })]
|
|
184
205
|
})]
|
|
185
206
|
}),
|
|
186
|
-
|
|
207
|
+
y.tunnelUrl && /* @__PURE__ */ v("div", {
|
|
187
208
|
className: "mt-3 flex items-center gap-2 text-sm",
|
|
188
209
|
children: [
|
|
189
|
-
/* @__PURE__ */
|
|
190
|
-
/* @__PURE__ */
|
|
210
|
+
/* @__PURE__ */ _(s, { className: "size-4 flex-shrink-0 text-text-secondary" }),
|
|
211
|
+
/* @__PURE__ */ _("span", {
|
|
191
212
|
className: "text-text-secondary flex-shrink-0",
|
|
192
213
|
children: "Tunnel:"
|
|
193
214
|
}),
|
|
194
|
-
/* @__PURE__ */
|
|
195
|
-
href:
|
|
215
|
+
/* @__PURE__ */ _("a", {
|
|
216
|
+
href: y.tunnelUrl,
|
|
196
217
|
target: "_blank",
|
|
197
218
|
rel: "noopener noreferrer",
|
|
198
219
|
onClick: (e) => e.stopPropagation(),
|
|
199
220
|
className: "text-action-primary-text hover:underline truncate",
|
|
200
|
-
children:
|
|
221
|
+
children: y.tunnelUrl
|
|
201
222
|
})
|
|
202
223
|
]
|
|
203
224
|
}),
|
|
204
|
-
|
|
225
|
+
y.capabilities && y.capabilities.length > 0 && /* @__PURE__ */ v("div", {
|
|
205
226
|
className: "mt-3 flex flex-wrap gap-1",
|
|
206
|
-
children: [
|
|
227
|
+
children: [y.capabilities.slice(0, 4).map((e) => /* @__PURE__ */ _("span", {
|
|
207
228
|
className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded",
|
|
208
229
|
children: e
|
|
209
|
-
}, e)),
|
|
230
|
+
}, e)), y.capabilities.length > 4 && /* @__PURE__ */ v("span", {
|
|
210
231
|
className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded",
|
|
211
|
-
children: ["+",
|
|
232
|
+
children: ["+", y.capabilities.length - 4]
|
|
212
233
|
})]
|
|
213
234
|
})
|
|
214
235
|
]
|
|
215
236
|
});
|
|
216
237
|
};
|
|
217
238
|
//#endregion
|
|
218
|
-
export {
|
|
239
|
+
export { y as AgentCard };
|
|
219
240
|
|
|
220
241
|
//# 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';\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\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":";;;;;;;;AAoDA,IAAa,KAAiC,EAC5C,UACA,aACA,gBACA,eACA,YACA,WACA,iBACA,oBACA,cAAW,IACX,mBACA,eAAY,IACZ,4BACI;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAAqB;EAC5C,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;IAG1B,IAAkB,KAAe,GACjC,CAAC,GAAU,KAAe,EAAS,GAAM,EAEzC,IAAS,EAAe,EAAM,EAE9B,KAAuB,MAAyC;AACpE,MAAI,CAAC,EAAW,QAAO,EAAG,oBAAoB,QAAQ;EACtD,IAAM,IAAO,KAAK,KAAK,GAAG,IAAI,KAAK,EAAU,CAAC,SAAS;AAIvD,SAHI,IAAO,MAAc,EAAG,sBAAsB,WAAW,GACzD,IAAO,OAAgB,GAAG,KAAK,MAAM,IAAO,IAAM,CAAC,SACnD,IAAO,QAAiB,GAAG,KAAK,MAAM,IAAO,KAAQ,CAAC,SACnD,GAAG,KAAK,MAAM,IAAO,MAAS,CAAC;IAGlC,IACJ;EACE,QAAQ;EACR,OAAO;EACP,SAAS;EACV,CAAC,EAAM,YAAY,OAAO;AAE7B,QACE,kBAAC,EAAO,KAAR;EACE,YAAY;GAAE,GAAG;GAAI,YAAY;IAAE,UAAU;IAAM,MAAM;KAAC;KAAK;KAAG;KAAK;KAAE;IAAE;GAAE;EAC7E,UAAU,EAAE,OAAO,KAAM;EACzB,WAAW,uHACT,IACI,8DACA,wBACL,GAAG;EACJ,eAAe,IAAW,EAAM;YARlC;GAUE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEG,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAM;AAEd,QADA,EAAE,iBAAiB,EACnB,GAAgB;;OAElB,WAAW,gGACT,IACI,2EACA,iEACL;iBAEA,KACC,kBAAC,OAAD;QACE,WAAU;QACV,MAAK;QACL,SAAQ;QACR,QAAO;QACP,aAAa;kBAEb,kBAAC,QAAD;SAAM,eAAc;SAAQ,gBAAe;SAAQ,GAAE;SAAmB,CAAA;QACpE,CAAA;OAED,CAAA;MAEX,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAQ,WAAU,4CAA6C,CAAA;OAC3D,CAAA;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAM;SACJ,CAAA,EACL,kBAAC,QAAD;SAAM,WAAU;mBAAsC;SAAoB,CAAA,CACtE;WACN,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAM,YAAY,OAAO,EAAM;QAC9B,CAAA,CACA;;MACF;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACU;MACR,QAAQ,KAAsB;MAC9B,QAAQ,wBAAwB,EAAM,GAAG;MACzC,UAAU,MAAU;AAMlB,SAAM,iBAAiB;;MAEzB,CAAA,GACA,KAAmB,KAAW,MAC9B,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAM;AAEd,QADA,EAAE,iBAAiB,EACnB,EAAY,CAAC,EAAS;;OAExB,WAAU;OACV,cAAW;iBAEX,kBAAC,GAAD,EAAc,WAAU,yDAA0D,CAAA;OAC3E,CAAA,EACT,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,EAAO,KAAR;OACE,SAAS;QAAE,SAAS;QAAG,OAAO;QAAM,GAAG;QAAI;OAC3C,SAAS;QAAE,SAAS;QAAG,OAAO;QAAG,GAAG;QAAG;OACvC,MAAM;QAAE,SAAS;QAAG,OAAO;QAAM,GAAG;QAAI;OACxC,YAAY,EAAE,UAAU,KAAM;OAC9B,WAAU;iBALZ;QAOG,MAAW,YAAY,KACtB,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAO,EAAM,EACb,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAC5B,EAAG,wBAAwB,aAAa,CAC3B;;SAEhB,MAAW,aAAa,MAAW,cAAc,KACjD,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAQ,EAAM,EACd,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,yBAAyB,cAAc,CAC7B;;QAEjB,MAAW,cAAc,KAQxB,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAQ,EAAM,EACd,EAAY,GAAM;;SAEpB,WAAU;SACV,OACE,IACI,uCAAuC,MACvC;mBAXR,CAcE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EAAG,6BAA6B,YAAY,CAC/B;;SAEhB,MAAW,YACX,MAAW,aACX,MAAW,aACX,MAAW,eACX,KACE,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAa,EAAM,EACnB,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAC5B,MAAW,YAAY,qBAAqB,aAC/B;;QAEnB,KACC,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAgB,EAAM,EACtB,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAAA,cAEb;;QAEjB,KACC,kBAAC,EAAO,QAAR;SACE,UAAU,EAAE,OAAO,KAAM;SACzB,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAgB,EAAM,EACtB,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA,EAAA,WAEjB;;QAEP;UAEC,CAAA,CACd;QAEJ;OACF;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA,EAC5B,kBAAC,QAAD,EAAA,UAAO,EAAoB,EAAM,cAAc,EAAQ,CAAA,CACnD;QACL,EAAM,gBACL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA,EAC1B,kBAAC,QAAD,EAAA,UAAO,EAAM,cAAoB,CAAA,CAC7B;OAEJ;;GAEL,EAAM,aAKL,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAO,WAAU,4CAA6C,CAAA;KAC9D,kBAAC,QAAD;MAAM,WAAU;gBAAoC;MAAc,CAAA;KAClE,kBAAC,KAAD;MACE,MAAM,EAAM;MACZ,QAAO;MACP,KAAI;MACJ,UAAU,MAAM,EAAE,iBAAiB;MACnC,WAAU;gBAET,EAAM;MACL,CAAA;KACA;;GAGP,EAAM,gBAAgB,EAAM,aAAa,SAAS,KACjD,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAM,aAAa,MAAM,GAAG,EAAE,CAAC,KAAK,MACnC,kBAAC,QAAD;KAEE,WAAU;eAET;KACI,EAJA,EAIA,CACP,EACD,EAAM,aAAa,SAAS,KAC3B,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAA+E,KAC3E,EAAM,aAAa,SAAS,EACzB;OAEL;;GAEG"}
|
|
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,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { classifyAgentHealth as e } from "../../utils/agentHealth.js";
|
|
2
|
+
import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
3
|
+
import { Loader2 as n } from "lucide-react";
|
|
4
|
+
import { Fragment as r, jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
4
5
|
//#region src/components/agents/AgentStatusBadge.tsx
|
|
5
|
-
var
|
|
6
|
+
var o = {
|
|
6
7
|
REGISTERING: {
|
|
7
8
|
label: "Registering",
|
|
8
9
|
color: "bg-status-info-bg text-status-info-text border-status-info-border"
|
|
@@ -36,33 +37,33 @@ var a = {
|
|
|
36
37
|
label: "Deleted",
|
|
37
38
|
color: "bg-status-error-bg text-status-error-text border-status-error-border"
|
|
38
39
|
}
|
|
39
|
-
},
|
|
40
|
-
let { t:
|
|
41
|
-
let n =
|
|
40
|
+
}, s = "See connection log for details", c = ({ status: c, className: l = "", reason: u, linkTo: d, onClick: f }) => {
|
|
41
|
+
let { t: p } = t(), m = (e, t) => {
|
|
42
|
+
let n = p(e);
|
|
42
43
|
return n === e ? t : n;
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
let
|
|
44
|
+
}, h = o[c], g = !!d || !!f, _ = e(c, u), v = [];
|
|
45
|
+
_.phase === "none" ? u && (c === "DEGRADED" || c === "OFFLINE") && v.push(`${h.label}: ${u}`) : (v.push(`${_.label} — ${_.actionHint}`), u && v.push(`Details: ${u}`)), g && v.push(m("agents.statusBadge.connectionLogHint", s));
|
|
46
|
+
let y = v.length > 0 ? v.join(" · ") : void 0, b = !!h.spinner || _.inProgress, x = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${h.color} ${l}`, S = g ? " cursor-pointer hover:opacity-90 active:scale-95 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong" : "", C = /* @__PURE__ */ a(r, { children: [b && /* @__PURE__ */ i(n, {
|
|
46
47
|
className: "size-3 animate-spin",
|
|
47
48
|
"aria-hidden": "true"
|
|
48
|
-
}),
|
|
49
|
-
return
|
|
50
|
-
href:
|
|
51
|
-
title:
|
|
52
|
-
"aria-label":
|
|
53
|
-
className:
|
|
49
|
+
}), h.label] });
|
|
50
|
+
return g ? /* @__PURE__ */ i("a", {
|
|
51
|
+
href: d ?? "#",
|
|
52
|
+
title: y,
|
|
53
|
+
"aria-label": y ?? h.label,
|
|
54
|
+
className: x + S,
|
|
54
55
|
onClick: (e) => {
|
|
55
|
-
e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0 ||
|
|
56
|
+
e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0 || f && f(e);
|
|
56
57
|
},
|
|
57
|
-
children:
|
|
58
|
-
}) : /* @__PURE__ */
|
|
59
|
-
title:
|
|
60
|
-
"aria-label":
|
|
61
|
-
className:
|
|
62
|
-
children:
|
|
58
|
+
children: C
|
|
59
|
+
}) : /* @__PURE__ */ i("span", {
|
|
60
|
+
title: y,
|
|
61
|
+
"aria-label": y ?? h.label,
|
|
62
|
+
className: x,
|
|
63
|
+
children: C
|
|
63
64
|
});
|
|
64
65
|
};
|
|
65
66
|
//#endregion
|
|
66
|
-
export {
|
|
67
|
+
export { c as AgentStatusBadge };
|
|
67
68
|
|
|
68
69
|
//# sourceMappingURL=AgentStatusBadge.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentStatusBadge.js","names":[],"sources":["../../../src/components/agents/AgentStatusBadge.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Loader2 } from 'lucide-react';\nimport type { FC, MouseEvent as ReactMouseEvent } from 'react';\nimport type { AgentStatus } from './types';\n\nexport interface AgentStatusBadgeProps {\n status: AgentStatus;\n className?: string;\n /**\n * Optional probe error to expose as a hover tooltip (and aria-label).\n * Surfaces the actual reason a `DEGRADED` agent is unhealthy — most\n * commonly \"boot state awaiting-config\" when the gateway-auth push\n * during onboarding silently failed. Without this, users see a yellow\n * badge with no signal about WHY, and have to curl /health/ready\n * themselves to debug.\n */\n reason?: string | null;\n /**\n * Optional href that turns the badge into an anchor pointing at the\n * agent's Connection Log tab. When this is set the badge is keyboard-\n * focusable, supports Ctrl/Cmd-click to open in a new tab, and surfaces\n * a \"click to see connection log\" tooltip suffix so users know it's\n * actionable. Provide together with `onClick` if you also need a SPA\n * navigation handler — the anchor `href` will still be the source of\n * truth for the browser's new-tab affordance.\n */\n linkTo?: string;\n /**\n * SPA navigation callback fired on unmodified left-click. When the\n * caller is already on the agent's detail page this is typically a\n * `setActiveTab('connection')` call; when the caller is on a list view\n * it's typically a `navigate(detailUrl + '?tab=connection')` call. The\n * anchor still owns the modifier-key path so Ctrl-click opens the\n * destination in a new tab regardless.\n */\n onClick?: (event: ReactMouseEvent<HTMLAnchorElement>) => void;\n}\n\nconst statusConfig: Record<AgentStatus, { label: string; color: string; spinner?: boolean }> = {\n REGISTERING: {\n label: 'Registering',\n color: 'bg-status-info-bg text-status-info-text border-status-info-border',\n },\n CONNECTING: {\n label: 'Connecting',\n color: 'bg-status-info-bg text-status-info-text border-status-info-border',\n spinner: true,\n },\n ACTIVE: {\n label: 'Active',\n color: 'bg-status-success-bg text-status-success-text border-status-success-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n IDLE: {\n label: 'Idle',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n DEGRADED: {\n label: 'Degraded',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n OFFLINE: {\n label: 'Offline',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n },\n DELETED: {\n label: 'Deleted',\n color: 'bg-status-error-bg text-status-error-text border-status-error-border',\n },\n};\n\nconst CONNECTION_LOG_HINT = 'See connection log for details';\n\n/**\n * AgentStatusBadge - Display agent status with appropriate styling.\n *\n * When `linkTo` or `onClick` is supplied the badge renders as an anchor\n * and becomes the canonical entry point into the agent's Connection Log\n * tab. This keeps the user one click away from the timeline of probe /\n * register / gateway-auth-push events any time they spot a non-Active\n * badge.\n */\nexport const AgentStatusBadge: FC<AgentStatusBadgeProps> = ({\n status,\n className = '',\n reason,\n linkTo,\n onClick,\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 const config = statusConfig[status];\n const isInteractive = Boolean(linkTo) || Boolean(onClick);\n const tooltipParts: string[] = [];\n if (reason && (status === 'DEGRADED' || status === 'OFFLINE')) {\n tooltipParts.push(`${config.label}: ${reason}`);\n }\n if (isInteractive) {\n tooltipParts.push(tr('agents.statusBadge.connectionLogHint', CONNECTION_LOG_HINT));\n }\n const tooltip = tooltipParts.length > 0 ? tooltipParts.join(' · ') : undefined;\n\n const baseClassName = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${config.color} ${className}`;\n const interactiveClassName = isInteractive\n ? ' cursor-pointer hover:opacity-90 active:scale-95 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong'\n : '';\n\n const content = (\n <>\n {
|
|
1
|
+
{"version":3,"file":"AgentStatusBadge.js","names":[],"sources":["../../../src/components/agents/AgentStatusBadge.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Loader2 } from 'lucide-react';\nimport type { FC, MouseEvent as ReactMouseEvent } from 'react';\nimport type { AgentStatus } from './types';\nimport { classifyAgentHealth } from '../../utils/agentHealth';\n\nexport interface AgentStatusBadgeProps {\n status: AgentStatus;\n className?: string;\n /**\n * Optional probe error to expose as a hover tooltip (and aria-label).\n * Surfaces the actual reason a `DEGRADED` agent is unhealthy — most\n * commonly \"boot state awaiting-config\" when the gateway-auth push\n * during onboarding silently failed. Without this, users see a yellow\n * badge with no signal about WHY, and have to curl /health/ready\n * themselves to debug.\n */\n reason?: string | null;\n /**\n * Optional href that turns the badge into an anchor pointing at the\n * agent's Connection Log tab. When this is set the badge is keyboard-\n * focusable, supports Ctrl/Cmd-click to open in a new tab, and surfaces\n * a \"click to see connection log\" tooltip suffix so users know it's\n * actionable. Provide together with `onClick` if you also need a SPA\n * navigation handler — the anchor `href` will still be the source of\n * truth for the browser's new-tab affordance.\n */\n linkTo?: string;\n /**\n * SPA navigation callback fired on unmodified left-click. When the\n * caller is already on the agent's detail page this is typically a\n * `setActiveTab('connection')` call; when the caller is on a list view\n * it's typically a `navigate(detailUrl + '?tab=connection')` call. The\n * anchor still owns the modifier-key path so Ctrl-click opens the\n * destination in a new tab regardless.\n */\n onClick?: (event: ReactMouseEvent<HTMLAnchorElement>) => void;\n}\n\nconst statusConfig: Record<AgentStatus, { label: string; color: string; spinner?: boolean }> = {\n REGISTERING: {\n label: 'Registering',\n color: 'bg-status-info-bg text-status-info-text border-status-info-border',\n },\n CONNECTING: {\n label: 'Connecting',\n color: 'bg-status-info-bg text-status-info-text border-status-info-border',\n spinner: true,\n },\n ACTIVE: {\n label: 'Active',\n color: 'bg-status-success-bg text-status-success-text border-status-success-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n IDLE: {\n label: 'Idle',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n DEGRADED: {\n label: 'Degraded',\n color: 'bg-status-warning-bg text-status-warning-text border-status-warning-border',\n },\n OFFLINE: {\n label: 'Offline',\n color: 'bg-bg-sunken text-text-secondary border-border-default',\n },\n DELETED: {\n label: 'Deleted',\n color: 'bg-status-error-bg text-status-error-text border-status-error-border',\n },\n};\n\nconst CONNECTION_LOG_HINT = 'See connection log for details';\n\n/**\n * AgentStatusBadge - Display agent status with appropriate styling.\n *\n * When `linkTo` or `onClick` is supplied the badge renders as an anchor\n * and becomes the canonical entry point into the agent's Connection Log\n * tab. This keeps the user one click away from the timeline of probe /\n * register / gateway-auth-push events any time they spot a non-Active\n * badge.\n */\nexport const AgentStatusBadge: FC<AgentStatusBadgeProps> = ({\n status,\n className = '',\n reason,\n linkTo,\n onClick,\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 const config = statusConfig[status];\n const isInteractive = Boolean(linkTo) || Boolean(onClick);\n\n // Classify a degraded/offline agent so the badge can say whether work is\n // happening behind the scenes (spinner, \"no action needed\") or the user has\n // an action item — rather than just a yellow badge with a raw health string.\n const health = classifyAgentHealth(status, reason);\n\n const tooltipParts: string[] = [];\n if (health.phase !== 'none') {\n tooltipParts.push(`${health.label} — ${health.actionHint}`);\n if (reason) tooltipParts.push(`Details: ${reason}`);\n } else if (reason && (status === 'DEGRADED' || status === 'OFFLINE')) {\n tooltipParts.push(`${config.label}: ${reason}`);\n }\n if (isInteractive) {\n tooltipParts.push(tr('agents.statusBadge.connectionLogHint', CONNECTION_LOG_HINT));\n }\n const tooltip = tooltipParts.length > 0 ? tooltipParts.join(' · ') : undefined;\n\n // Show the in-progress spinner for CONNECTING and for a DEGRADED agent that\n // is still provisioning, so \"something is happening\" is visible at a glance.\n const showSpinner = Boolean(config.spinner) || health.inProgress;\n\n const baseClassName = `inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${config.color} ${className}`;\n const interactiveClassName = isInteractive\n ? ' cursor-pointer hover:opacity-90 active:scale-95 transition-transform duration-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-strong'\n : '';\n\n const content = (\n <>\n {showSpinner && <Loader2 className=\"size-3 animate-spin\" aria-hidden=\"true\" />}\n {config.label}\n </>\n );\n\n if (isInteractive) {\n return (\n <a\n href={linkTo ?? '#'}\n title={tooltip}\n aria-label={tooltip ?? config.label}\n className={baseClassName + interactiveClassName}\n onClick={(event) => {\n // Let modifier-clicks reach the browser's default tab-opener so\n // Ctrl/Cmd-click + middle-click open the connection log in a\n // new tab. SPA navigation only fires on plain left-click.\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.button !== 0) {\n return;\n }\n if (onClick) {\n onClick(event);\n }\n }}\n >\n {content}\n </a>\n );\n }\n\n return (\n <span title={tooltip} aria-label={tooltip ?? config.label} className={baseClassName}>\n {content}\n </span>\n );\n};\n"],"mappings":";;;;;AAuCA,IAAM,IAAyF;CAC7F,aAAa;EACX,OAAO;EACP,OAAO;EACR;CACD,YAAY;EACV,OAAO;EACP,OAAO;EACP,SAAS;EACV;CACD,QAAQ;EACN,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,MAAM;EACJ,OAAO;EACP,OAAO;EACR;CACD,UAAU;EACR,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACF,EAEK,IAAsB,kCAWf,KAA+C,EAC1D,WACA,eAAY,IACZ,WACA,WACA,iBACI;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAAqB;EAC5C,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;IAE1B,IAAS,EAAa,IACtB,IAAgB,EAAQ,KAAW,EAAQ,GAK3C,IAAS,EAAoB,GAAQ,EAAO,EAE5C,IAAyB,EAAE;AAOjC,CANI,EAAO,UAAU,SAGV,MAAW,MAAW,cAAc,MAAW,cACxD,EAAa,KAAK,GAAG,EAAO,MAAM,IAAI,IAAS,IAH/C,EAAa,KAAK,GAAG,EAAO,MAAM,KAAK,EAAO,aAAa,EACvD,KAAQ,EAAa,KAAK,YAAY,IAAS,GAIjD,KACF,EAAa,KAAK,EAAG,wCAAwC,EAAoB,CAAC;CAEpF,IAAM,IAAU,EAAa,SAAS,IAAI,EAAa,KAAK,MAAM,GAAG,KAAA,GAI/D,IAAc,EAAQ,EAAO,WAAY,EAAO,YAEhD,IAAgB,uHAAuH,EAAO,MAAM,GAAG,KACvJ,IAAuB,IACzB,wKACA,IAEE,IACJ,kBAAA,GAAA,EAAA,UAAA,CACG,KAAe,kBAAC,GAAD;EAAS,WAAU;EAAsB,eAAY;EAAS,CAAA,EAC7E,EAAO,MACP,EAAA,CAAA;AA2BL,QAxBI,IAEA,kBAAC,KAAD;EACE,MAAM,KAAU;EAChB,OAAO;EACP,cAAY,KAAW,EAAO;EAC9B,WAAW,IAAgB;EAC3B,UAAU,MAAU;AAId,KAAM,WAAW,EAAM,WAAW,EAAM,YAAY,EAAM,WAAW,KAGrE,KACF,EAAQ,EAAM;;YAIjB;EACC,CAAA,GAKN,kBAAC,QAAD;EAAM,OAAO;EAAS,cAAY,KAAW,EAAO;EAAO,WAAW;YACnE;EACI,CAAA"}
|