feat: add session name input to tool starter
ToolStarter (used by FAB and workspace detail): - Add Session Name text input, defaulting to workspace.name - Pass user-provided name to createInstance display_name parameter - If left empty or only whitespace, falls back to auto-generated name Quality gates: tsc --noEmit pass, npm run build pass, 82/82 tests pass
This commit is contained in:
@@ -171,8 +171,7 @@ export function SessionCard({
|
||||
{" "}
|
||||
/ <span>{session.repository_name}</span>
|
||||
</>
|
||||
)}
|
||||
{" "}
|
||||
)}{" "}
|
||||
· {session.tool_type_name}
|
||||
</p>
|
||||
{session.clone_mode && (
|
||||
|
||||
@@ -3,128 +3,137 @@ import { SessionCard } from "./session-card";
|
||||
import type { InstanceHealth } from "../../../api/sessions";
|
||||
|
||||
export interface SessionListProps {
|
||||
sessions: Session[];
|
||||
onOpen?: (session: Session) => void;
|
||||
onStart?: (session: Session) => void;
|
||||
onStop?: (session: Session) => void;
|
||||
onDelete?: (session: Session) => void;
|
||||
onRecreateTunnel?: (session: Session) => void;
|
||||
onRename?: (session: Session, newName: string) => void;
|
||||
actionBusyId?: string | null;
|
||||
tunnelHealth?: Record<string, InstanceHealth>;
|
||||
showGrouping?: boolean;
|
||||
activeTitle?: string;
|
||||
recentTitle?: string;
|
||||
maxRecent?: number;
|
||||
emptyMessage?: string;
|
||||
sessions: Session[];
|
||||
onOpen?: (session: Session) => void;
|
||||
onStart?: (session: Session) => void;
|
||||
onStop?: (session: Session) => void;
|
||||
onDelete?: (session: Session) => void;
|
||||
onRecreateTunnel?: (session: Session) => void;
|
||||
onRename?: (session: Session, newName: string) => void;
|
||||
actionBusyId?: string | null;
|
||||
tunnelHealth?: Record<string, InstanceHealth>;
|
||||
showGrouping?: boolean;
|
||||
activeTitle?: string;
|
||||
recentTitle?: string;
|
||||
maxRecent?: number;
|
||||
emptyMessage?: string;
|
||||
}
|
||||
|
||||
const activeStatuses = ["running", "building", "starting", "probing", "pending", "unhealthy"];
|
||||
const activeStatuses = [
|
||||
"running",
|
||||
"building",
|
||||
"starting",
|
||||
"probing",
|
||||
"pending",
|
||||
"unhealthy",
|
||||
];
|
||||
const recentStatuses = ["stopped", "error"];
|
||||
|
||||
export function SessionList({
|
||||
sessions,
|
||||
onOpen,
|
||||
onStart,
|
||||
onStop,
|
||||
onDelete,
|
||||
onRecreateTunnel,
|
||||
onRename,
|
||||
actionBusyId = null,
|
||||
tunnelHealth = {},
|
||||
showGrouping = true,
|
||||
activeTitle = "Active Sessions",
|
||||
recentTitle = "Recent Sessions",
|
||||
maxRecent = 5,
|
||||
emptyMessage = "No sessions",
|
||||
sessions,
|
||||
onOpen,
|
||||
onStart,
|
||||
onStop,
|
||||
onDelete,
|
||||
onRecreateTunnel,
|
||||
onRename,
|
||||
actionBusyId = null,
|
||||
tunnelHealth = {},
|
||||
showGrouping = true,
|
||||
activeTitle = "Active Sessions",
|
||||
recentTitle = "Recent Sessions",
|
||||
maxRecent = 5,
|
||||
emptyMessage = "No sessions",
|
||||
}: SessionListProps) {
|
||||
const activeSessions = sessions.filter((s) => activeStatuses.includes(s.status));
|
||||
const recentSessions = sessions
|
||||
.filter((s) => recentStatuses.includes(s.status))
|
||||
.slice(0, maxRecent);
|
||||
const activeSessions = sessions.filter((s) =>
|
||||
activeStatuses.includes(s.status),
|
||||
);
|
||||
const recentSessions = sessions
|
||||
.filter((s) => recentStatuses.includes(s.status))
|
||||
.slice(0, maxRecent);
|
||||
|
||||
if (!showGrouping) {
|
||||
return (
|
||||
<div className="sessions-grid">
|
||||
{sessions.length === 0 ? (
|
||||
<p className="muted">{emptyMessage}</p>
|
||||
) : (
|
||||
sessions.map((session) => (
|
||||
<SessionCard
|
||||
key={session.id}
|
||||
session={session}
|
||||
onOpen={onOpen}
|
||||
onStart={onStart}
|
||||
onStop={onStop}
|
||||
onDelete={onDelete}
|
||||
onRecreateTunnel={onRecreateTunnel}
|
||||
onRename={onRename}
|
||||
isBusy={actionBusyId === session.id}
|
||||
tunnelHealth={tunnelHealth[session.id] || null}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!showGrouping) {
|
||||
return (
|
||||
<div className="sessions-grid">
|
||||
{sessions.length === 0 ? (
|
||||
<p className="muted">{emptyMessage}</p>
|
||||
) : (
|
||||
sessions.map((session) => (
|
||||
<SessionCard
|
||||
key={session.id}
|
||||
session={session}
|
||||
onOpen={onOpen}
|
||||
onStart={onStart}
|
||||
onStop={onStop}
|
||||
onDelete={onDelete}
|
||||
onRecreateTunnel={onRecreateTunnel}
|
||||
onRename={onRename}
|
||||
isBusy={actionBusyId === session.id}
|
||||
tunnelHealth={tunnelHealth[session.id] || null}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="session-list">
|
||||
{/* Active Sessions */}
|
||||
<div className="session-group">
|
||||
<div className="session-group-header">
|
||||
<h3>{activeTitle}</h3>
|
||||
{activeSessions.length > 0 && (
|
||||
<span className="badge">{activeSessions.length}</span>
|
||||
)}
|
||||
</div>
|
||||
{activeSessions.length === 0 ? (
|
||||
<p className="muted">No active sessions</p>
|
||||
) : (
|
||||
<div className="sessions-grid">
|
||||
{activeSessions.map((session) => (
|
||||
<SessionCard
|
||||
key={session.id}
|
||||
session={session}
|
||||
onOpen={onOpen}
|
||||
onStart={onStart}
|
||||
onStop={onStop}
|
||||
onDelete={onDelete}
|
||||
onRecreateTunnel={onRecreateTunnel}
|
||||
onRename={onRename}
|
||||
isBusy={actionBusyId === session.id}
|
||||
tunnelHealth={tunnelHealth[session.id] || null}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<div className="session-list">
|
||||
{/* Active Sessions */}
|
||||
<div className="session-group">
|
||||
<div className="session-group-header">
|
||||
<h3>{activeTitle}</h3>
|
||||
{activeSessions.length > 0 && (
|
||||
<span className="badge">{activeSessions.length}</span>
|
||||
)}
|
||||
</div>
|
||||
{activeSessions.length === 0 ? (
|
||||
<p className="muted">No active sessions</p>
|
||||
) : (
|
||||
<div className="sessions-grid">
|
||||
{activeSessions.map((session) => (
|
||||
<SessionCard
|
||||
key={session.id}
|
||||
session={session}
|
||||
onOpen={onOpen}
|
||||
onStart={onStart}
|
||||
onStop={onStop}
|
||||
onDelete={onDelete}
|
||||
onRecreateTunnel={onRecreateTunnel}
|
||||
onRename={onRename}
|
||||
isBusy={actionBusyId === session.id}
|
||||
tunnelHealth={tunnelHealth[session.id] || null}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Recent Sessions */}
|
||||
{recentSessions.length > 0 && (
|
||||
<div className="session-group">
|
||||
<div className="session-group-header">
|
||||
<h3>{recentTitle}</h3>
|
||||
<span className="badge">{recentSessions.length}</span>
|
||||
</div>
|
||||
<div className="sessions-grid">
|
||||
{recentSessions.map((session) => (
|
||||
<SessionCard
|
||||
key={session.id}
|
||||
session={session}
|
||||
onOpen={onOpen}
|
||||
onStart={onStart}
|
||||
onStop={onStop}
|
||||
onDelete={onDelete}
|
||||
onRecreateTunnel={onRecreateTunnel}
|
||||
onRename={onRename}
|
||||
isBusy={actionBusyId === session.id}
|
||||
tunnelHealth={tunnelHealth[session.id] || null}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
{/* Recent Sessions */}
|
||||
{recentSessions.length > 0 && (
|
||||
<div className="session-group">
|
||||
<div className="session-group-header">
|
||||
<h3>{recentTitle}</h3>
|
||||
<span className="badge">{recentSessions.length}</span>
|
||||
</div>
|
||||
<div className="sessions-grid">
|
||||
{recentSessions.map((session) => (
|
||||
<SessionCard
|
||||
key={session.id}
|
||||
session={session}
|
||||
onOpen={onOpen}
|
||||
onStart={onStart}
|
||||
onStop={onStop}
|
||||
onDelete={onDelete}
|
||||
onRecreateTunnel={onRecreateTunnel}
|
||||
onRename={onRename}
|
||||
isBusy={actionBusyId === session.id}
|
||||
tunnelHealth={tunnelHealth[session.id] || null}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user