@burdenoff/microfe-vibecontrols 2026.601.5 → 2026.601.7
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/sessions/EntityInfoPopover.js +183 -0
- package/dist/components/sessions/EntityInfoPopover.js.map +1 -0
- package/dist/components/sessions/SessionCard.js +80 -72
- package/dist/components/sessions/SessionCard.js.map +1 -1
- package/dist/components/sessions/SessionGroup.js +49 -0
- package/dist/components/sessions/SessionGroup.js.map +1 -0
- package/dist/components/sessions/SessionInfoPanel.js +128 -0
- package/dist/components/sessions/SessionInfoPanel.js.map +1 -0
- package/dist/components/sessions/SessionList.js +109 -96
- package/dist/components/sessions/SessionList.js.map +1 -1
- package/dist/components/sessions/SessionsCommandPalette.js +36 -0
- package/dist/components/sessions/SessionsCommandPalette.js.map +1 -0
- package/dist/components/sessions/ShortcutCheatsheet.js +73 -0
- package/dist/components/sessions/ShortcutCheatsheet.js.map +1 -0
- package/dist/components/sessions/TerminalPanel.js +640 -537
- package/dist/components/sessions/TerminalPanel.js.map +1 -1
- package/dist/components/sessions/groupSessions.js +46 -0
- package/dist/components/sessions/groupSessions.js.map +1 -0
- package/dist/components/sessions/index.js +7 -0
- package/dist/components/sessions/sessionShortcuts.js +149 -0
- package/dist/components/sessions/sessionShortcuts.js.map +1 -0
- package/dist/components/shared/FilterDropdown.js +14 -13
- package/dist/components/shared/FilterDropdown.js.map +1 -1
- package/dist/components/shared/LinkToVibePanel.js.map +1 -1
- package/dist/components/vibe/VibeLinkedEntitiesPanel.js.map +1 -1
- package/dist/generated/wspace-operations.js +21 -0
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/node_modules/@apollo/client/utilities/internal/getMainDefinition.js +17 -0
- package/dist/node_modules/@apollo/client/utilities/internal/getMainDefinition.js.map +1 -0
- package/dist/pages/AgentGraphPage.js +65 -65
- package/dist/pages/AgentGraphPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +1189 -920
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +105 -92
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/VibeCalendarPage.js +2 -1
- package/dist/pages/VibeCalendarPage.js.map +1 -1
- package/dist/pages/ai/AgentManagerPage.js.map +1 -1
- package/dist/providers/VibeControlsProvider.js +147 -113
- package/dist/providers/VibeControlsProvider.js.map +1 -1
- package/dist/store/vibeControlsStore.js +16 -1
- package/dist/store/vibeControlsStore.js.map +1 -1
- package/dist/translations/en.js +97 -2
- package/dist/translations/en.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
+
import { useVibeNavigate as t } from "../../providers/VibeControlsProvider.js";
|
|
3
|
+
import { getAgentStatus as n } from "../agents/agentStatus.js";
|
|
4
|
+
import { Clock as r, Cpu as i, ExternalLink as a, MonitorSmartphone as o, Network as s, Server as c, Wifi as l } from "lucide-react";
|
|
5
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
6
|
+
import { Popover as f, PopoverContent as p, PopoverTrigger as m } from "@burdenoff/fe-libs/ui";
|
|
7
|
+
//#region src/components/sessions/EntityInfoPopover.tsx
|
|
8
|
+
function h(e) {
|
|
9
|
+
if (!e) return "—";
|
|
10
|
+
let t = Date.now() - new Date(e).getTime();
|
|
11
|
+
return Number.isFinite(t) ? t < 6e4 ? "just now" : t < 36e5 ? `${Math.floor(t / 6e4)}m ago` : t < 864e5 ? `${Math.floor(t / 36e5)}h ago` : new Date(e).toLocaleDateString() : "—";
|
|
12
|
+
}
|
|
13
|
+
var g = {
|
|
14
|
+
ACTIVE: "bg-status-success-text",
|
|
15
|
+
DEGRADED: "bg-status-warning-text",
|
|
16
|
+
OFFLINE: "bg-status-error-text"
|
|
17
|
+
}, _ = {
|
|
18
|
+
DIRECT: "bg-bg-sunken text-text-secondary",
|
|
19
|
+
SSH: "bg-status-success-bg text-status-success-text",
|
|
20
|
+
RDP: "bg-status-warning-bg text-status-warning-text",
|
|
21
|
+
VNC: "bg-action-primary-bg/15 text-action-primary-text"
|
|
22
|
+
};
|
|
23
|
+
function v({ icon: e, label: t, value: n }) {
|
|
24
|
+
return /* @__PURE__ */ d("div", {
|
|
25
|
+
className: "flex items-center justify-between gap-3 py-1 text-xs",
|
|
26
|
+
children: [/* @__PURE__ */ d("span", {
|
|
27
|
+
className: "flex items-center gap-1.5 text-text-tertiary",
|
|
28
|
+
children: [e, t]
|
|
29
|
+
}), /* @__PURE__ */ u("span", {
|
|
30
|
+
className: "font-medium text-text-primary truncate max-w-[160px]",
|
|
31
|
+
title: n,
|
|
32
|
+
children: n
|
|
33
|
+
})]
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function y({ agent: o, children: s, className: _ = "" }) {
|
|
37
|
+
let y = e(), b = t(), x = n(o);
|
|
38
|
+
return /* @__PURE__ */ d(f, { children: [/* @__PURE__ */ u(m, {
|
|
39
|
+
asChild: !0,
|
|
40
|
+
children: s ?? /* @__PURE__ */ d("button", {
|
|
41
|
+
type: "button",
|
|
42
|
+
onClick: (e) => e.stopPropagation(),
|
|
43
|
+
"data-testid": "session-agent-chip",
|
|
44
|
+
className: `inline-flex items-center gap-1.5 px-1.5 py-0.5 rounded-md text-xs text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors max-w-[140px] ${_}`,
|
|
45
|
+
title: o.name,
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ u("span", {
|
|
48
|
+
className: `size-1.5 rounded-full shrink-0 ${g[x] ?? "bg-text-muted"}`,
|
|
49
|
+
"aria-hidden": !0
|
|
50
|
+
}),
|
|
51
|
+
/* @__PURE__ */ u(c, { className: "size-3 shrink-0" }),
|
|
52
|
+
/* @__PURE__ */ u("span", {
|
|
53
|
+
className: "truncate",
|
|
54
|
+
children: o.name
|
|
55
|
+
})
|
|
56
|
+
]
|
|
57
|
+
})
|
|
58
|
+
}), /* @__PURE__ */ d(p, {
|
|
59
|
+
align: "start",
|
|
60
|
+
className: "w-64 p-3",
|
|
61
|
+
onClick: (e) => e.stopPropagation(),
|
|
62
|
+
"data-testid": "session-agent-popover",
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ d("div", {
|
|
65
|
+
className: "flex items-center gap-2 mb-2",
|
|
66
|
+
children: [
|
|
67
|
+
/* @__PURE__ */ u("span", {
|
|
68
|
+
className: `size-2 rounded-full shrink-0 ${g[x] ?? "bg-text-muted"}`,
|
|
69
|
+
"aria-hidden": !0
|
|
70
|
+
}),
|
|
71
|
+
/* @__PURE__ */ u("span", {
|
|
72
|
+
className: "text-sm font-semibold text-text-primary truncate",
|
|
73
|
+
children: o.name
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ u("span", {
|
|
76
|
+
className: "ml-auto text-[10px] uppercase tracking-wide text-text-tertiary",
|
|
77
|
+
children: x
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
}),
|
|
81
|
+
/* @__PURE__ */ d("div", {
|
|
82
|
+
className: "border-t border-border-subtle pt-1",
|
|
83
|
+
children: [
|
|
84
|
+
/* @__PURE__ */ u(v, {
|
|
85
|
+
icon: /* @__PURE__ */ u(c, { className: "size-3" }),
|
|
86
|
+
label: y("sessions.info.host", "Host"),
|
|
87
|
+
value: o.hostname || "—"
|
|
88
|
+
}),
|
|
89
|
+
/* @__PURE__ */ u(v, {
|
|
90
|
+
icon: /* @__PURE__ */ u(i, { className: "size-3" }),
|
|
91
|
+
label: y("sessions.info.platform", "Platform"),
|
|
92
|
+
value: o.platform || "—"
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ u(v, {
|
|
95
|
+
icon: /* @__PURE__ */ u(l, { className: "size-3" }),
|
|
96
|
+
label: y("sessions.info.tunnel", "Tunnel"),
|
|
97
|
+
value: o.tunnelStatus || (o.tunnelUrl ? "set" : "—")
|
|
98
|
+
}),
|
|
99
|
+
/* @__PURE__ */ u(v, {
|
|
100
|
+
icon: /* @__PURE__ */ u(r, { className: "size-3" }),
|
|
101
|
+
label: y("sessions.info.heartbeat", "Heartbeat"),
|
|
102
|
+
value: h(o.lastHeartbeat)
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
}),
|
|
106
|
+
/* @__PURE__ */ d("button", {
|
|
107
|
+
type: "button",
|
|
108
|
+
onClick: () => b(`/agents/${o.id}`),
|
|
109
|
+
"data-testid": "session-agent-view",
|
|
110
|
+
className: "mt-2 w-full inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors",
|
|
111
|
+
children: [/* @__PURE__ */ u(a, { className: "size-3" }), y("sessions.info.viewAgent", "View agent")]
|
|
112
|
+
})
|
|
113
|
+
]
|
|
114
|
+
})] });
|
|
115
|
+
}
|
|
116
|
+
function b({ target: n, children: r, className: i = "" }) {
|
|
117
|
+
let c = e(), h = t();
|
|
118
|
+
return /* @__PURE__ */ d(f, { children: [/* @__PURE__ */ u(m, {
|
|
119
|
+
asChild: !0,
|
|
120
|
+
children: r ?? /* @__PURE__ */ d("button", {
|
|
121
|
+
type: "button",
|
|
122
|
+
onClick: (e) => e.stopPropagation(),
|
|
123
|
+
"data-testid": "session-target-chip",
|
|
124
|
+
className: `inline-flex items-center gap-1.5 px-1.5 py-0.5 rounded-md text-xs text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors max-w-[150px] ${i}`,
|
|
125
|
+
title: `${n.name} · ${n.type}`,
|
|
126
|
+
children: [
|
|
127
|
+
/* @__PURE__ */ u(o, { className: "size-3 shrink-0" }),
|
|
128
|
+
/* @__PURE__ */ u("span", {
|
|
129
|
+
className: "truncate",
|
|
130
|
+
children: n.name
|
|
131
|
+
}),
|
|
132
|
+
/* @__PURE__ */ u("span", {
|
|
133
|
+
className: `shrink-0 px-1 py-0 rounded text-[9px] font-semibold ${_[n.type] ?? "bg-bg-sunken text-text-secondary"}`,
|
|
134
|
+
children: n.type
|
|
135
|
+
})
|
|
136
|
+
]
|
|
137
|
+
})
|
|
138
|
+
}), /* @__PURE__ */ d(p, {
|
|
139
|
+
align: "start",
|
|
140
|
+
className: "w-60 p-3",
|
|
141
|
+
onClick: (e) => e.stopPropagation(),
|
|
142
|
+
"data-testid": "session-target-popover",
|
|
143
|
+
children: [
|
|
144
|
+
/* @__PURE__ */ d("div", {
|
|
145
|
+
className: "flex items-center gap-2 mb-2",
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ u(o, { className: "size-3.5 text-text-secondary shrink-0" }),
|
|
148
|
+
/* @__PURE__ */ u("span", {
|
|
149
|
+
className: "text-sm font-semibold text-text-primary truncate",
|
|
150
|
+
children: n.name
|
|
151
|
+
}),
|
|
152
|
+
/* @__PURE__ */ u("span", {
|
|
153
|
+
className: `ml-auto shrink-0 px-1.5 py-0.5 rounded text-[10px] font-semibold ${_[n.type] ?? "bg-bg-sunken text-text-secondary"}`,
|
|
154
|
+
children: n.type
|
|
155
|
+
})
|
|
156
|
+
]
|
|
157
|
+
}),
|
|
158
|
+
/* @__PURE__ */ d("div", {
|
|
159
|
+
className: "border-t border-border-subtle pt-1",
|
|
160
|
+
children: [/* @__PURE__ */ u(v, {
|
|
161
|
+
icon: /* @__PURE__ */ u(l, { className: "size-3" }),
|
|
162
|
+
label: c("sessions.info.status", "Status"),
|
|
163
|
+
value: n.status || "—"
|
|
164
|
+
}), /* @__PURE__ */ u(v, {
|
|
165
|
+
icon: /* @__PURE__ */ u(s, { className: "size-3" }),
|
|
166
|
+
label: c("sessions.info.address", "Address"),
|
|
167
|
+
value: n.host ? `${n.host}${n.port ? `:${n.port}` : ""}` : "—"
|
|
168
|
+
})]
|
|
169
|
+
}),
|
|
170
|
+
/* @__PURE__ */ d("button", {
|
|
171
|
+
type: "button",
|
|
172
|
+
onClick: () => h(`/targets/${n.id}`),
|
|
173
|
+
"data-testid": "session-target-view",
|
|
174
|
+
className: "mt-2 w-full inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors",
|
|
175
|
+
children: [/* @__PURE__ */ u(a, { className: "size-3" }), c("sessions.info.viewTarget", "View target")]
|
|
176
|
+
})
|
|
177
|
+
]
|
|
178
|
+
})] });
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
export { y as AgentInfoPopover, b as TargetInfoPopover };
|
|
182
|
+
|
|
183
|
+
//# sourceMappingURL=EntityInfoPopover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityInfoPopover.js","names":[],"sources":["../../../src/components/sessions/EntityInfoPopover.tsx"],"sourcesContent":["/**\n * EntityInfoPopover — quick inline detail for the agent / target a\n * session runs on. Anchored on the agent/target chips in the session\n * list, cards and the terminal status bar so operators can confirm\n * \"which machine / is it healthy\" without leaving the page, with a\n * one-click jump to the full AgentDetails / TargetDetails page.\n */\nimport { Popover, PopoverTrigger, PopoverContent } from '@burdenoff/fe-libs/ui';\nimport { Server, MonitorSmartphone, ExternalLink, Cpu, Clock, Wifi, Network } from 'lucide-react';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useTr } from '../../shared/hooks/useTr';\nimport type { Session, SessionTarget, TargetType } from './types';\n\ntype SessionAgent = NonNullable<Session['agent']>;\n\n/** Relative \"x ago\" for a heartbeat / timestamp. */\nfunction relativeTime(ts: string | null | undefined): string {\n if (!ts) return '—';\n const diff = Date.now() - new Date(ts).getTime();\n if (!Number.isFinite(diff)) return '—';\n if (diff < 60_000) return 'just now';\n if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;\n if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;\n return new Date(ts).toLocaleDateString();\n}\n\nconst statusDotClass: Record<string, string> = {\n ACTIVE: 'bg-status-success-text',\n DEGRADED: 'bg-status-warning-text',\n OFFLINE: 'bg-status-error-text',\n};\n\nconst targetTypeBadgeClass: Record<TargetType, string> = {\n DIRECT: 'bg-bg-sunken text-text-secondary',\n SSH: 'bg-status-success-bg text-status-success-text',\n RDP: 'bg-status-warning-bg text-status-warning-text',\n VNC: 'bg-action-primary-bg/15 text-action-primary-text',\n};\n\nfunction InfoRow({ icon, label, value }: { icon: React.ReactNode; label: string; value: string }) {\n return (\n <div className=\"flex items-center justify-between gap-3 py-1 text-xs\">\n <span className=\"flex items-center gap-1.5 text-text-tertiary\">\n {icon}\n {label}\n </span>\n <span className=\"font-medium text-text-primary truncate max-w-[160px]\" title={value}>\n {value}\n </span>\n </div>\n );\n}\n\nexport interface AgentInfoPopoverProps {\n agent: SessionAgent;\n /** Optional custom trigger; defaults to a chip with the agent name + status dot. */\n children?: React.ReactNode;\n className?: string;\n}\n\n/** Popover summarising an agent's health + a link to its detail page. */\nexport function AgentInfoPopover({ agent, children, className = '' }: AgentInfoPopoverProps) {\n const tr = useTr();\n const navigate = useVibeNavigate();\n const status = getAgentStatus(agent);\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n {children ?? (\n <button\n type=\"button\"\n onClick={(e) => e.stopPropagation()}\n data-testid=\"session-agent-chip\"\n className={`inline-flex items-center gap-1.5 px-1.5 py-0.5 rounded-md text-xs text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors max-w-[140px] ${className}`}\n title={agent.name}\n >\n <span\n className={`size-1.5 rounded-full shrink-0 ${statusDotClass[status] ?? 'bg-text-muted'}`}\n aria-hidden\n />\n <Server className=\"size-3 shrink-0\" />\n <span className=\"truncate\">{agent.name}</span>\n </button>\n )}\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"w-64 p-3\"\n onClick={(e) => e.stopPropagation()}\n data-testid=\"session-agent-popover\"\n >\n <div className=\"flex items-center gap-2 mb-2\">\n <span\n className={`size-2 rounded-full shrink-0 ${statusDotClass[status] ?? 'bg-text-muted'}`}\n aria-hidden\n />\n <span className=\"text-sm font-semibold text-text-primary truncate\">{agent.name}</span>\n <span className=\"ml-auto text-[10px] uppercase tracking-wide text-text-tertiary\">\n {status}\n </span>\n </div>\n <div className=\"border-t border-border-subtle pt-1\">\n <InfoRow\n icon={<Server className=\"size-3\" />}\n label={tr('sessions.info.host', 'Host')}\n value={agent.hostname || '—'}\n />\n <InfoRow\n icon={<Cpu className=\"size-3\" />}\n label={tr('sessions.info.platform', 'Platform')}\n value={agent.platform || '—'}\n />\n <InfoRow\n icon={<Wifi className=\"size-3\" />}\n label={tr('sessions.info.tunnel', 'Tunnel')}\n value={agent.tunnelStatus || (agent.tunnelUrl ? 'set' : '—')}\n />\n <InfoRow\n icon={<Clock className=\"size-3\" />}\n label={tr('sessions.info.heartbeat', 'Heartbeat')}\n value={relativeTime(agent.lastHeartbeat)}\n />\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/agents/${agent.id}`)}\n data-testid=\"session-agent-view\"\n className=\"mt-2 w-full inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <ExternalLink className=\"size-3\" />\n {tr('sessions.info.viewAgent', 'View agent')}\n </button>\n </PopoverContent>\n </Popover>\n );\n}\n\nexport interface TargetInfoPopoverProps {\n target: SessionTarget;\n children?: React.ReactNode;\n className?: string;\n}\n\n/** Popover summarising the target machine + a link to its detail page. */\nexport function TargetInfoPopover({ target, children, className = '' }: TargetInfoPopoverProps) {\n const tr = useTr();\n const navigate = useVibeNavigate();\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n {children ?? (\n <button\n type=\"button\"\n onClick={(e) => e.stopPropagation()}\n data-testid=\"session-target-chip\"\n className={`inline-flex items-center gap-1.5 px-1.5 py-0.5 rounded-md text-xs text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors max-w-[150px] ${className}`}\n title={`${target.name} · ${target.type}`}\n >\n <MonitorSmartphone className=\"size-3 shrink-0\" />\n <span className=\"truncate\">{target.name}</span>\n <span\n className={`shrink-0 px-1 py-0 rounded text-[9px] font-semibold ${targetTypeBadgeClass[target.type] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {target.type}\n </span>\n </button>\n )}\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"w-60 p-3\"\n onClick={(e) => e.stopPropagation()}\n data-testid=\"session-target-popover\"\n >\n <div className=\"flex items-center gap-2 mb-2\">\n <MonitorSmartphone className=\"size-3.5 text-text-secondary shrink-0\" />\n <span className=\"text-sm font-semibold text-text-primary truncate\">{target.name}</span>\n <span\n className={`ml-auto shrink-0 px-1.5 py-0.5 rounded text-[10px] font-semibold ${targetTypeBadgeClass[target.type] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {target.type}\n </span>\n </div>\n <div className=\"border-t border-border-subtle pt-1\">\n <InfoRow\n icon={<Wifi className=\"size-3\" />}\n label={tr('sessions.info.status', 'Status')}\n value={target.status || '—'}\n />\n <InfoRow\n icon={<Network className=\"size-3\" />}\n label={tr('sessions.info.address', 'Address')}\n value={target.host ? `${target.host}${target.port ? `:${target.port}` : ''}` : '—'}\n />\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/targets/${target.id}`)}\n data-testid=\"session-target-view\"\n className=\"mt-2 w-full inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <ExternalLink className=\"size-3\" />\n {tr('sessions.info.viewTarget', 'View target')}\n </button>\n </PopoverContent>\n </Popover>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
2
|
import { SessionStatusBadge as t } from "./SessionStatusBadge.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { AgentInfoPopover as n, TargetInfoPopover as r } from "./EntityInfoPopover.js";
|
|
4
|
+
import { useState as i } from "react";
|
|
5
|
+
import { Clock as a, MoreVertical as o, Play as s, RotateCcw as c, Share2 as l, Square as u, Terminal as d, Trash2 as f } from "lucide-react";
|
|
6
|
+
import { AnimatePresence as p, motion as m } from "framer-motion";
|
|
7
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
7
8
|
//#region src/components/sessions/SessionCard.tsx
|
|
8
|
-
var
|
|
9
|
+
var _ = {
|
|
9
10
|
TMUX: "🖥️",
|
|
10
11
|
SSH: "🔐",
|
|
11
12
|
TERMINAL: "💻",
|
|
12
13
|
SCRIPT: "📜",
|
|
13
14
|
CUSTOM: "⚙️"
|
|
14
|
-
},
|
|
15
|
-
let
|
|
15
|
+
}, v = ({ session: v, onOpen: y, onStart: b, onStop: x, onRestart: S, onTerminate: C, onOpenTerminal: w, onShare: T, selected: E = !1, onToggleSelect: D, className: O = "" }) => {
|
|
16
|
+
let k = e(), [A, j] = i(!1), M = (e) => {
|
|
16
17
|
if (!e) return "Never";
|
|
17
18
|
let t = Date.now() - new Date(e).getTime();
|
|
18
19
|
return t < 6e4 ? "Just now" : t < 36e5 ? `${Math.floor(t / 6e4)}m ago` : t < 864e5 ? `${Math.floor(t / 36e5)}h ago` : new Date(e).toLocaleDateString();
|
|
19
|
-
},
|
|
20
|
-
return /* @__PURE__ */ m
|
|
20
|
+
}, N = v.status === "RUNNING", P = v.status === "STOPPED" || v.status === "TERMINATED";
|
|
21
|
+
return /* @__PURE__ */ g(m.div, {
|
|
21
22
|
whileHover: {
|
|
22
23
|
y: -2,
|
|
23
24
|
transition: {
|
|
@@ -31,60 +32,67 @@ var h = {
|
|
|
31
32
|
}
|
|
32
33
|
},
|
|
33
34
|
whileTap: { scale: .98 },
|
|
34
|
-
className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${
|
|
35
|
-
onClick: () =>
|
|
35
|
+
className: `bg-bg-surface border rounded-lg p-4 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}`,
|
|
36
|
+
onClick: () => y?.(v),
|
|
36
37
|
children: [
|
|
37
|
-
/* @__PURE__ */
|
|
38
|
+
/* @__PURE__ */ g("div", {
|
|
38
39
|
className: "flex items-start justify-between gap-2",
|
|
39
|
-
children: [/* @__PURE__ */
|
|
40
|
+
children: [/* @__PURE__ */ g("div", {
|
|
40
41
|
className: "flex items-start gap-2 sm:gap-3 min-w-0 flex-1",
|
|
41
42
|
children: [
|
|
42
|
-
|
|
43
|
+
D && /* @__PURE__ */ h("div", {
|
|
43
44
|
className: "flex-shrink-0 pt-0.5",
|
|
44
|
-
children: /* @__PURE__ */
|
|
45
|
+
children: /* @__PURE__ */ h("input", {
|
|
45
46
|
"aria-label": "control",
|
|
46
47
|
type: "checkbox",
|
|
47
|
-
checked:
|
|
48
|
+
checked: E,
|
|
48
49
|
onChange: (e) => {
|
|
49
|
-
e.stopPropagation(),
|
|
50
|
+
e.stopPropagation(), D();
|
|
50
51
|
},
|
|
51
52
|
onClick: (e) => e.stopPropagation(),
|
|
52
53
|
className: "size-4 rounded border-border-default cursor-pointer"
|
|
53
54
|
})
|
|
54
55
|
}),
|
|
55
|
-
/* @__PURE__ */
|
|
56
|
+
/* @__PURE__ */ h("div", {
|
|
56
57
|
className: "flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center",
|
|
57
|
-
children: /* @__PURE__ */
|
|
58
|
+
children: /* @__PURE__ */ h(d, { className: "size-4 sm:w-5 sm:h-5 text-text-secondary" })
|
|
58
59
|
}),
|
|
59
|
-
/* @__PURE__ */
|
|
60
|
+
/* @__PURE__ */ g("div", {
|
|
60
61
|
className: "min-w-0 flex-1",
|
|
61
|
-
children: [
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
children: [
|
|
63
|
+
/* @__PURE__ */ g("div", {
|
|
64
|
+
className: "flex items-center gap-1.5 sm:gap-2 flex-wrap",
|
|
65
|
+
children: [/* @__PURE__ */ h("h3", {
|
|
66
|
+
className: "font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base",
|
|
67
|
+
children: v.name
|
|
68
|
+
}), /* @__PURE__ */ h("span", {
|
|
69
|
+
className: "text-base sm:text-lg flex-shrink-0",
|
|
70
|
+
children: _[v.type] || "💻"
|
|
71
|
+
})]
|
|
72
|
+
}),
|
|
73
|
+
/* @__PURE__ */ h("p", {
|
|
74
|
+
className: "text-xs sm:text-sm text-text-secondary",
|
|
75
|
+
children: v.type
|
|
76
|
+
}),
|
|
77
|
+
v.agent && /* @__PURE__ */ g("div", {
|
|
78
|
+
className: "mt-1.5 flex items-center gap-1 flex-wrap",
|
|
79
|
+
children: [/* @__PURE__ */ h(n, { agent: v.agent }), v.agent.target && /* @__PURE__ */ h(r, { target: v.agent.target })]
|
|
80
|
+
})
|
|
81
|
+
]
|
|
74
82
|
})
|
|
75
83
|
]
|
|
76
|
-
}), /* @__PURE__ */
|
|
84
|
+
}), /* @__PURE__ */ g("div", {
|
|
77
85
|
className: "flex items-center gap-1 sm:gap-2 flex-shrink-0",
|
|
78
|
-
children: [/* @__PURE__ */
|
|
86
|
+
children: [/* @__PURE__ */ h(t, { status: v.status }), (b || x || S || C) && /* @__PURE__ */ g("div", {
|
|
79
87
|
className: "relative",
|
|
80
|
-
children: [/* @__PURE__ */
|
|
88
|
+
children: [/* @__PURE__ */ h("button", {
|
|
81
89
|
type: "button",
|
|
82
90
|
onClick: (e) => {
|
|
83
|
-
e.stopPropagation(),
|
|
91
|
+
e.stopPropagation(), j(!A);
|
|
84
92
|
},
|
|
85
93
|
className: "p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors",
|
|
86
|
-
children: /* @__PURE__ */
|
|
87
|
-
}), /* @__PURE__ */ p
|
|
94
|
+
children: /* @__PURE__ */ h(o, { className: "size-4 text-text-secondary" })
|
|
95
|
+
}), /* @__PURE__ */ h(p, { children: A && /* @__PURE__ */ g(m.div, {
|
|
88
96
|
initial: {
|
|
89
97
|
opacity: 0,
|
|
90
98
|
scale: .95,
|
|
@@ -103,53 +111,53 @@ var h = {
|
|
|
103
111
|
transition: { duration: .15 },
|
|
104
112
|
className: "absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10",
|
|
105
113
|
children: [
|
|
106
|
-
|
|
114
|
+
P && b && /* @__PURE__ */ g(m.button, {
|
|
107
115
|
whileTap: { scale: .97 },
|
|
108
116
|
onClick: (e) => {
|
|
109
|
-
e.stopPropagation(), v
|
|
117
|
+
e.stopPropagation(), b(v), j(!1);
|
|
110
118
|
},
|
|
111
119
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
|
|
112
|
-
children: [/* @__PURE__ */
|
|
120
|
+
children: [/* @__PURE__ */ h(s, { className: "size-4" }), k("vibecontrols.sessions.card.start", "Start")]
|
|
113
121
|
}),
|
|
114
|
-
|
|
122
|
+
N && x && /* @__PURE__ */ g(m.button, {
|
|
115
123
|
whileTap: { scale: .97 },
|
|
116
124
|
onClick: (e) => {
|
|
117
|
-
e.stopPropagation(),
|
|
125
|
+
e.stopPropagation(), x(v), j(!1);
|
|
118
126
|
},
|
|
119
127
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
120
|
-
children: [/* @__PURE__ */
|
|
128
|
+
children: [/* @__PURE__ */ h(u, { className: "size-4" }), k("vibecontrols.sessions.card.stop", "Stop")]
|
|
121
129
|
}),
|
|
122
|
-
|
|
130
|
+
N && S && /* @__PURE__ */ g(m.button, {
|
|
123
131
|
whileTap: { scale: .97 },
|
|
124
132
|
onClick: (e) => {
|
|
125
|
-
e.stopPropagation(),
|
|
133
|
+
e.stopPropagation(), S(v), j(!1);
|
|
126
134
|
},
|
|
127
135
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
128
|
-
children: [/* @__PURE__ */
|
|
136
|
+
children: [/* @__PURE__ */ h(c, { className: "size-4" }), k("vibecontrols.sessions.card.restart", "Restart")]
|
|
129
137
|
}),
|
|
130
|
-
|
|
138
|
+
N && w && /* @__PURE__ */ g(m.button, {
|
|
131
139
|
whileTap: { scale: .97 },
|
|
132
140
|
onClick: (e) => {
|
|
133
|
-
e.stopPropagation(),
|
|
141
|
+
e.stopPropagation(), w(v), j(!1);
|
|
134
142
|
},
|
|
135
143
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
136
|
-
children: [/* @__PURE__ */
|
|
144
|
+
children: [/* @__PURE__ */ h(d, { className: "size-4" }), k("vibecontrols.sessions.card.openTerminal", "Open Terminal")]
|
|
137
145
|
}),
|
|
138
|
-
|
|
146
|
+
N && T && /* @__PURE__ */ g(m.button, {
|
|
139
147
|
whileTap: { scale: .97 },
|
|
140
148
|
onClick: (e) => {
|
|
141
|
-
e.stopPropagation(),
|
|
149
|
+
e.stopPropagation(), T(v), j(!1);
|
|
142
150
|
},
|
|
143
151
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
144
|
-
children: [/* @__PURE__ */
|
|
152
|
+
children: [/* @__PURE__ */ h(l, { className: "size-4" }), k("vibecontrols.sessions.card.shareSession", "Share Session")]
|
|
145
153
|
}),
|
|
146
|
-
|
|
154
|
+
C && /* @__PURE__ */ g(m.button, {
|
|
147
155
|
whileTap: { scale: .97 },
|
|
148
156
|
onClick: (e) => {
|
|
149
|
-
e.stopPropagation(),
|
|
157
|
+
e.stopPropagation(), C(v), j(!1);
|
|
150
158
|
},
|
|
151
159
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg",
|
|
152
|
-
children: [/* @__PURE__ */
|
|
160
|
+
children: [/* @__PURE__ */ h(f, { className: "size-4" }), k("vibecontrols.sessions.card.terminate", "Terminate")]
|
|
153
161
|
})
|
|
154
162
|
]
|
|
155
163
|
}) })]
|
|
@@ -157,42 +165,42 @@ var h = {
|
|
|
157
165
|
})]
|
|
158
166
|
}),
|
|
159
167
|
(() => {
|
|
160
|
-
let e =
|
|
161
|
-
return t ? /* @__PURE__ */
|
|
168
|
+
let e = v.metadata || {}, t = typeof e.tmuxSessionName == "string" && e.tmuxSessionName || typeof e.weztermWorkspace == "string" && e.weztermWorkspace || typeof e.zellijSessionName == "string" && e.zellijSessionName || typeof e.externalSessionName == "string" && e.externalSessionName || v.providerSessionName || null;
|
|
169
|
+
return t ? /* @__PURE__ */ g("div", {
|
|
162
170
|
className: "mt-2 text-[10px] font-mono text-text-tertiary",
|
|
163
171
|
title: `Provider session: ${t}`,
|
|
164
172
|
"data-testid": "session-card-external-name",
|
|
165
|
-
children: [/* @__PURE__ */
|
|
173
|
+
children: [/* @__PURE__ */ g("span", {
|
|
166
174
|
className: "opacity-70",
|
|
167
|
-
children: [
|
|
168
|
-
}), /* @__PURE__ */
|
|
175
|
+
children: [v.type.toLowerCase(), ": "]
|
|
176
|
+
}), /* @__PURE__ */ h("span", {
|
|
169
177
|
className: "break-all",
|
|
170
178
|
children: t
|
|
171
179
|
})]
|
|
172
180
|
}) : null;
|
|
173
181
|
})(),
|
|
174
|
-
|
|
182
|
+
v.command && /* @__PURE__ */ g("div", {
|
|
175
183
|
className: "mt-3 text-xs font-mono bg-bg-sunken p-2 rounded truncate text-text-secondary",
|
|
176
|
-
children: ["$ ",
|
|
184
|
+
children: ["$ ", v.command]
|
|
177
185
|
}),
|
|
178
|
-
|
|
186
|
+
v.lastOutput && /* @__PURE__ */ h("div", {
|
|
179
187
|
className: "mt-2 text-xs font-mono bg-bg-sunken text-status-success-text p-2 rounded truncate",
|
|
180
|
-
children:
|
|
188
|
+
children: v.lastOutput
|
|
181
189
|
}),
|
|
182
|
-
/* @__PURE__ */
|
|
190
|
+
/* @__PURE__ */ g("div", {
|
|
183
191
|
className: "mt-3 flex items-center justify-between text-sm text-text-secondary",
|
|
184
|
-
children: [/* @__PURE__ */
|
|
192
|
+
children: [/* @__PURE__ */ g("div", {
|
|
185
193
|
className: "flex items-center gap-2",
|
|
186
|
-
children: [/* @__PURE__ */
|
|
187
|
-
}),
|
|
194
|
+
children: [/* @__PURE__ */ h(a, { className: "size-4" }), /* @__PURE__ */ h("span", { children: M(v.updatedAt) })]
|
|
195
|
+
}), v.pid && /* @__PURE__ */ g("span", {
|
|
188
196
|
className: "text-xs font-mono",
|
|
189
|
-
children: ["PID: ",
|
|
197
|
+
children: ["PID: ", v.pid]
|
|
190
198
|
})]
|
|
191
199
|
})
|
|
192
200
|
]
|
|
193
201
|
});
|
|
194
202
|
};
|
|
195
203
|
//#endregion
|
|
196
|
-
export {
|
|
204
|
+
export { v as SessionCard };
|
|
197
205
|
|
|
198
206
|
//# sourceMappingURL=SessionCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/components/sessions/SessionCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Clock,\n MoreVertical,\n Play,\n Square,\n RotateCcw,\n Trash2,\n Share2,\n} from 'lucide-react';\nimport { SessionStatusBadge } from './SessionStatusBadge';\nimport type { Session } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionCardProps {\n session: Session;\n onOpen?: (session: Session) => void;\n onStart?: (session: Session) => void;\n onStop?: (session: Session) => void;\n onRestart?: (session: Session) => void;\n onTerminate?: (session: Session) => void;\n onOpenTerminal?: (session: Session) => void;\n /** Callback to open the share dialog */\n onShare?: (session: Session) => void;\n /** Whether this card is selected for bulk operations */\n selected?: boolean;\n /** Toggle selection for bulk operations */\n onToggleSelect?: () => void;\n className?: string;\n}\n\nconst sessionTypeIcons: Record<string, string> = {\n TMUX: '🖥️',\n SSH: '🔐',\n TERMINAL: '💻',\n SCRIPT: '📜',\n CUSTOM: '⚙️',\n};\n\n/**\n * SessionCard - Display terminal session information\n */\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onOpen,\n onStart,\n onStop,\n onRestart,\n onTerminate,\n onOpenTerminal,\n onShare,\n selected = false,\n onToggleSelect,\n className = '',\n}) => {\n const tr = useTr();\n const [showMenu, setShowMenu] = useState(false);\n\n const formatTime = (timestamp: string | undefined | null) => {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return '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 new Date(timestamp).toLocaleDateString();\n };\n\n const isRunning = session.status === 'RUNNING';\n const isStopped = session.status === 'STOPPED' || session.status === 'TERMINATED';\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 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${selected ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30' : 'border-border-default'} ${className}`}\n onClick={() => onOpen?.(session)}\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 {onToggleSelect && (\n <div className=\"flex-shrink-0 pt-0.5\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selected}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-4 rounded border-border-default cursor-pointer\"\n />\n </div>\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 <Terminal 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 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {session.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">\n {sessionTypeIcons[session.type] || '💻'}\n </span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary\">{session.type}</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 <SessionStatusBadge status={session.status} />\n {(onStart || onStop || onRestart || onTerminate) && (\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\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\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 {isStopped && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(session);\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('vibecontrols.sessions.card.start', 'Start')}\n </motion.button>\n )}\n {isRunning && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(session);\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 <Square className=\"size-4\" />\n {tr('vibecontrols.sessions.card.stop', 'Stop')}\n </motion.button>\n )}\n {isRunning && onRestart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onRestart(session);\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 <RotateCcw className=\"size-4\" />\n {tr('vibecontrols.sessions.card.restart', 'Restart')}\n </motion.button>\n )}\n {isRunning && onOpenTerminal && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onOpenTerminal(session);\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 <Terminal className=\"size-4\" />\n {tr('vibecontrols.sessions.card.openTerminal', 'Open Terminal')}\n </motion.button>\n )}\n {isRunning && onShare && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(session);\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 <Share2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.shareSession', 'Share Session')}\n </motion.button>\n )}\n {onTerminate && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onTerminate(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.terminate', 'Terminate')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n {/* Provider-native session name (e.g. tmux session name like\n `vibe-051c9c62`). Surfaced so users can recognise their session\n if they ever need to attach from a CLI (`tmux attach -t …`)\n and so reconnect-after-restart flows are obvious. */}\n {(() => {\n const meta = (session.metadata as Record<string, unknown> | null) || {};\n const externalName =\n (typeof meta.tmuxSessionName === 'string' && meta.tmuxSessionName) ||\n (typeof meta.weztermWorkspace === 'string' && meta.weztermWorkspace) ||\n (typeof meta.zellijSessionName === 'string' && meta.zellijSessionName) ||\n (typeof meta.externalSessionName === 'string' && meta.externalSessionName) ||\n session.providerSessionName ||\n null;\n return externalName ? (\n <div\n className=\"mt-2 text-[10px] font-mono text-text-tertiary\"\n title={`Provider session: ${externalName}`}\n data-testid=\"session-card-external-name\"\n >\n <span className=\"opacity-70\">{session.type.toLowerCase()}: </span>\n <span className=\"break-all\">{externalName}</span>\n </div>\n ) : null;\n })()}\n\n {session.command && (\n <div className=\"mt-3 text-xs font-mono bg-bg-sunken p-2 rounded truncate text-text-secondary\">\n $ {session.command}\n </div>\n )}\n\n {session.lastOutput && (\n <div className=\"mt-2 text-xs font-mono bg-bg-sunken text-status-success-text p-2 rounded truncate\">\n {session.lastOutput}\n </div>\n )}\n\n <div className=\"mt-3 flex items-center justify-between text-sm text-text-secondary\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-4\" />\n <span>{formatTime(session.updatedAt)}</span>\n </div>\n {session.pid && <span className=\"text-xs font-mono\">PID: {session.pid}</span>}\n </div>\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/components/sessions/SessionCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Clock,\n MoreVertical,\n Play,\n Square,\n RotateCcw,\n Trash2,\n Share2,\n} from 'lucide-react';\nimport { SessionStatusBadge } from './SessionStatusBadge';\nimport { AgentInfoPopover, TargetInfoPopover } from './EntityInfoPopover';\nimport type { Session } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionCardProps {\n session: Session;\n onOpen?: (session: Session) => void;\n onStart?: (session: Session) => void;\n onStop?: (session: Session) => void;\n onRestart?: (session: Session) => void;\n onTerminate?: (session: Session) => void;\n onOpenTerminal?: (session: Session) => void;\n /** Callback to open the share dialog */\n onShare?: (session: Session) => void;\n /** Whether this card is selected for bulk operations */\n selected?: boolean;\n /** Toggle selection for bulk operations */\n onToggleSelect?: () => void;\n className?: string;\n}\n\nconst sessionTypeIcons: Record<string, string> = {\n TMUX: '🖥️',\n SSH: '🔐',\n TERMINAL: '💻',\n SCRIPT: '📜',\n CUSTOM: '⚙️',\n};\n\n/**\n * SessionCard - Display terminal session information\n */\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onOpen,\n onStart,\n onStop,\n onRestart,\n onTerminate,\n onOpenTerminal,\n onShare,\n selected = false,\n onToggleSelect,\n className = '',\n}) => {\n const tr = useTr();\n const [showMenu, setShowMenu] = useState(false);\n\n const formatTime = (timestamp: string | undefined | null) => {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return '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 new Date(timestamp).toLocaleDateString();\n };\n\n const isRunning = session.status === 'RUNNING';\n const isStopped = session.status === 'STOPPED' || session.status === 'TERMINATED';\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 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${selected ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30' : 'border-border-default'} ${className}`}\n onClick={() => onOpen?.(session)}\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 {onToggleSelect && (\n <div className=\"flex-shrink-0 pt-0.5\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selected}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-4 rounded border-border-default cursor-pointer\"\n />\n </div>\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 <Terminal 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 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {session.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">\n {sessionTypeIcons[session.type] || '💻'}\n </span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary\">{session.type}</p>\n {session.agent && (\n <div className=\"mt-1.5 flex items-center gap-1 flex-wrap\">\n <AgentInfoPopover agent={session.agent} />\n {session.agent.target && <TargetInfoPopover target={session.agent.target} />}\n </div>\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 <SessionStatusBadge status={session.status} />\n {(onStart || onStop || onRestart || onTerminate) && (\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\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\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 {isStopped && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(session);\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('vibecontrols.sessions.card.start', 'Start')}\n </motion.button>\n )}\n {isRunning && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(session);\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 <Square className=\"size-4\" />\n {tr('vibecontrols.sessions.card.stop', 'Stop')}\n </motion.button>\n )}\n {isRunning && onRestart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onRestart(session);\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 <RotateCcw className=\"size-4\" />\n {tr('vibecontrols.sessions.card.restart', 'Restart')}\n </motion.button>\n )}\n {isRunning && onOpenTerminal && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onOpenTerminal(session);\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 <Terminal className=\"size-4\" />\n {tr('vibecontrols.sessions.card.openTerminal', 'Open Terminal')}\n </motion.button>\n )}\n {isRunning && onShare && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(session);\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 <Share2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.shareSession', 'Share Session')}\n </motion.button>\n )}\n {onTerminate && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onTerminate(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.terminate', 'Terminate')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n {/* Provider-native session name (e.g. tmux session name like\n `vibe-051c9c62`). Surfaced so users can recognise their session\n if they ever need to attach from a CLI (`tmux attach -t …`)\n and so reconnect-after-restart flows are obvious. */}\n {(() => {\n const meta = (session.metadata as Record<string, unknown> | null) || {};\n const externalName =\n (typeof meta.tmuxSessionName === 'string' && meta.tmuxSessionName) ||\n (typeof meta.weztermWorkspace === 'string' && meta.weztermWorkspace) ||\n (typeof meta.zellijSessionName === 'string' && meta.zellijSessionName) ||\n (typeof meta.externalSessionName === 'string' && meta.externalSessionName) ||\n session.providerSessionName ||\n null;\n return externalName ? (\n <div\n className=\"mt-2 text-[10px] font-mono text-text-tertiary\"\n title={`Provider session: ${externalName}`}\n data-testid=\"session-card-external-name\"\n >\n <span className=\"opacity-70\">{session.type.toLowerCase()}: </span>\n <span className=\"break-all\">{externalName}</span>\n </div>\n ) : null;\n })()}\n\n {session.command && (\n <div className=\"mt-3 text-xs font-mono bg-bg-sunken p-2 rounded truncate text-text-secondary\">\n $ {session.command}\n </div>\n )}\n\n {session.lastOutput && (\n <div className=\"mt-2 text-xs font-mono bg-bg-sunken text-status-success-text p-2 rounded truncate\">\n {session.lastOutput}\n </div>\n )}\n\n <div className=\"mt-3 flex items-center justify-between text-sm text-text-secondary\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-4\" />\n <span>{formatTime(session.updatedAt)}</span>\n </div>\n {session.pid && <span className=\"text-xs font-mono\">PID: {session.pid}</span>}\n </div>\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
+
import { ChevronDown as t, ChevronRight as n } from "lucide-react";
|
|
3
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
4
|
+
//#region src/components/sessions/SessionGroup.tsx
|
|
5
|
+
function a({ label: a, count: o, collapsed: s, onToggleCollapse: c, allSelected: l, someSelected: u, onToggleSelectGroup: d, showSelect: f = !0, children: p }) {
|
|
6
|
+
let m = e();
|
|
7
|
+
return /* @__PURE__ */ i("div", {
|
|
8
|
+
"data-testid": "session-group",
|
|
9
|
+
"data-group-label": a,
|
|
10
|
+
children: [/* @__PURE__ */ i("div", {
|
|
11
|
+
className: "flex items-center gap-2 px-1 py-1.5 sticky top-0 z-[1] bg-bg-surface/95 backdrop-blur-sm",
|
|
12
|
+
children: [f && /* @__PURE__ */ r("input", {
|
|
13
|
+
"aria-label": m("sessions.group.selectGroup", "Select group"),
|
|
14
|
+
type: "checkbox",
|
|
15
|
+
checked: l,
|
|
16
|
+
ref: (e) => {
|
|
17
|
+
e && (e.indeterminate = !l && u);
|
|
18
|
+
},
|
|
19
|
+
onChange: (e) => {
|
|
20
|
+
e.stopPropagation(), d();
|
|
21
|
+
},
|
|
22
|
+
onClick: (e) => e.stopPropagation(),
|
|
23
|
+
className: "size-3.5 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg"
|
|
24
|
+
}), /* @__PURE__ */ i("button", {
|
|
25
|
+
type: "button",
|
|
26
|
+
onClick: c,
|
|
27
|
+
className: "flex items-center gap-1.5 text-sm font-semibold text-text-primary hover:text-text-link transition-colors min-w-0",
|
|
28
|
+
children: [
|
|
29
|
+
r(s ? n : t, { className: "size-3.5 text-text-tertiary shrink-0" }),
|
|
30
|
+
/* @__PURE__ */ r("span", {
|
|
31
|
+
className: "truncate",
|
|
32
|
+
children: a
|
|
33
|
+
}),
|
|
34
|
+
/* @__PURE__ */ r("span", {
|
|
35
|
+
className: "ml-1 inline-flex items-center justify-center min-w-[1.25rem] px-1.5 py-0.5 text-[10px] font-medium rounded-full bg-bg-sunken text-text-secondary",
|
|
36
|
+
children: o
|
|
37
|
+
})
|
|
38
|
+
]
|
|
39
|
+
})]
|
|
40
|
+
}), !s && /* @__PURE__ */ r("div", {
|
|
41
|
+
className: "pb-2",
|
|
42
|
+
children: p
|
|
43
|
+
})]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { a as SessionGroup };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=SessionGroup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionGroup.js","names":[],"sources":["../../../src/components/sessions/SessionGroup.tsx"],"sourcesContent":["/**\n * SessionGroup — a collapsible section wrapping one group of sessions in\n * the grid/list views. The header carries the group label, a count, and a\n * tri-state select-all checkbox that toggles only this group's ids in the\n * page-level selection Set (so the global bulk-action bar keeps working).\n */\nimport type { ReactNode } from 'react';\nimport { ChevronDown, ChevronRight } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionGroupProps {\n label: string;\n count: number;\n collapsed: boolean;\n onToggleCollapse: () => void;\n /** All sessions in this group are selected. */\n allSelected: boolean;\n /** Some (but not all) sessions in this group are selected. */\n someSelected: boolean;\n /** Toggle selection of every session in this group. */\n onToggleSelectGroup: () => void;\n /** Whether to show the per-group select checkbox (bulk mode). */\n showSelect?: boolean;\n children: ReactNode;\n}\n\nexport function SessionGroup({\n label,\n count,\n collapsed,\n onToggleCollapse,\n allSelected,\n someSelected,\n onToggleSelectGroup,\n showSelect = true,\n children,\n}: SessionGroupProps) {\n const tr = useTr();\n return (\n <div data-testid=\"session-group\" data-group-label={label}>\n <div className=\"flex items-center gap-2 px-1 py-1.5 sticky top-0 z-[1] bg-bg-surface/95 backdrop-blur-sm\">\n {showSelect && (\n <input\n aria-label={tr('sessions.group.selectGroup', 'Select group')}\n type=\"checkbox\"\n checked={allSelected}\n ref={(el) => {\n if (el) el.indeterminate = !allSelected && someSelected;\n }}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelectGroup();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-3.5 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg\"\n />\n )}\n <button\n type=\"button\"\n onClick={onToggleCollapse}\n className=\"flex items-center gap-1.5 text-sm font-semibold text-text-primary hover:text-text-link transition-colors min-w-0\"\n >\n {collapsed ? (\n <ChevronRight className=\"size-3.5 text-text-tertiary shrink-0\" />\n ) : (\n <ChevronDown className=\"size-3.5 text-text-tertiary shrink-0\" />\n )}\n <span className=\"truncate\">{label}</span>\n <span className=\"ml-1 inline-flex items-center justify-center min-w-[1.25rem] px-1.5 py-0.5 text-[10px] font-medium rounded-full bg-bg-sunken text-text-secondary\">\n {count}\n </span>\n </button>\n </div>\n {!collapsed && <div className=\"pb-2\">{children}</div>}\n </div>\n );\n}\n"],"mappings":";;;;AA0BA,SAAgB,EAAa,EAC3B,UACA,UACA,cACA,qBACA,gBACA,iBACA,wBACA,gBAAa,IACb,eACoB;CACpB,IAAM,IAAK,GAAO;AAClB,QACE,kBAAC,OAAD;EAAK,eAAY;EAAgB,oBAAkB;YAAnD,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,KACC,kBAAC,SAAD;IACE,cAAY,EAAG,8BAA8B,eAAe;IAC5D,MAAK;IACL,SAAS;IACT,MAAM,MAAO;AACX,KAAI,MAAI,EAAG,gBAAgB,CAAC,KAAe;;IAE7C,WAAW,MAAM;AAEf,KADA,EAAE,iBAAiB,EACnB,GAAqB;;IAEvB,UAAU,MAAM,EAAE,iBAAiB;IACnC,WAAU;IACV,CAAA,EAEJ,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAHZ;KAMI,EADD,IACE,IAEA,GAFD,EAAc,WAAU,wCAAyC,CAED;KAElE,kBAAC,QAAD;MAAM,WAAU;gBAAY;MAAa,CAAA;KACzC,kBAAC,QAAD;MAAM,WAAU;gBACb;MACI,CAAA;KACA;MACL;MACL,CAAC,KAAa,kBAAC,OAAD;GAAK,WAAU;GAAQ;GAAe,CAAA,CACjD"}
|