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:
@@ -41,7 +41,8 @@ const SessionItem = ({ session }: { session: Session }) => {
|
|||||||
// - Everything else falls back to the project page
|
// - Everything else falls back to the project page
|
||||||
const hasTerminal = session.tool_type_interfaces.includes("terminal");
|
const hasTerminal = session.tool_type_interfaces.includes("terminal");
|
||||||
const hasWeb = session.tool_type_interfaces.includes("web");
|
const hasWeb = session.tool_type_interfaces.includes("web");
|
||||||
const href = session.url && hasWeb
|
const href =
|
||||||
|
session.url && hasWeb
|
||||||
? session.url
|
? session.url
|
||||||
: hasTerminal
|
: hasTerminal
|
||||||
? `/instances/${session.id}/terminal`
|
? `/instances/${session.id}/terminal`
|
||||||
|
|||||||
@@ -171,8 +171,7 @@ export function SessionCard({
|
|||||||
{" "}
|
{" "}
|
||||||
/ <span>{session.repository_name}</span>
|
/ <span>{session.repository_name}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}{" "}
|
||||||
{" "}
|
|
||||||
· {session.tool_type_name}
|
· {session.tool_type_name}
|
||||||
</p>
|
</p>
|
||||||
{session.clone_mode && (
|
{session.clone_mode && (
|
||||||
|
|||||||
@@ -19,7 +19,14 @@ export interface SessionListProps {
|
|||||||
emptyMessage?: string;
|
emptyMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeStatuses = ["running", "building", "starting", "probing", "pending", "unhealthy"];
|
const activeStatuses = [
|
||||||
|
"running",
|
||||||
|
"building",
|
||||||
|
"starting",
|
||||||
|
"probing",
|
||||||
|
"pending",
|
||||||
|
"unhealthy",
|
||||||
|
];
|
||||||
const recentStatuses = ["stopped", "error"];
|
const recentStatuses = ["stopped", "error"];
|
||||||
|
|
||||||
export function SessionList({
|
export function SessionList({
|
||||||
@@ -38,7 +45,9 @@ export function SessionList({
|
|||||||
maxRecent = 5,
|
maxRecent = 5,
|
||||||
emptyMessage = "No sessions",
|
emptyMessage = "No sessions",
|
||||||
}: SessionListProps) {
|
}: SessionListProps) {
|
||||||
const activeSessions = sessions.filter((s) => activeStatuses.includes(s.status));
|
const activeSessions = sessions.filter((s) =>
|
||||||
|
activeStatuses.includes(s.status),
|
||||||
|
);
|
||||||
const recentSessions = sessions
|
const recentSessions = sessions
|
||||||
.filter((s) => recentStatuses.includes(s.status))
|
.filter((s) => recentStatuses.includes(s.status))
|
||||||
.slice(0, maxRecent);
|
.slice(0, maxRecent);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export function ToolStarter({
|
|||||||
const [sshKeysLoading, setSshKeysLoading] = useState(true);
|
const [sshKeysLoading, setSshKeysLoading] = useState(true);
|
||||||
const [selectedSshKeyIds, setSelectedSshKeyIds] = useState<string[]>([]);
|
const [selectedSshKeyIds, setSelectedSshKeyIds] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [displayName, setDisplayName] = useState(workspace.name);
|
||||||
const [starting, setStarting] = useState(false);
|
const [starting, setStarting] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -119,7 +120,7 @@ export function ToolStarter({
|
|||||||
workspace.project_id,
|
workspace.project_id,
|
||||||
workspace.repo_id,
|
workspace.repo_id,
|
||||||
selectedToolTypeId,
|
selectedToolTypeId,
|
||||||
workspace.name,
|
displayName.trim() || undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -140,7 +141,7 @@ export function ToolStarter({
|
|||||||
} finally {
|
} finally {
|
||||||
setStarting(false);
|
setStarting(false);
|
||||||
}
|
}
|
||||||
}, [selectedToolTypeId, selectedProfileId, workspace, onStarted]);
|
}, [selectedToolTypeId, selectedProfileId, displayName, workspace, onStarted]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tool-starter">
|
<div className="tool-starter">
|
||||||
@@ -187,6 +188,19 @@ export function ToolStarter({
|
|||||||
{toolTypesError && <span className="error-text">{toolTypesError}</span>}
|
{toolTypesError && <span className="error-text">{toolTypesError}</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Session Name */}
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="session-name">Session Name</label>
|
||||||
|
<input
|
||||||
|
id="session-name"
|
||||||
|
type="text"
|
||||||
|
value={displayName}
|
||||||
|
onChange={(e) => setDisplayName(e.target.value)}
|
||||||
|
placeholder="My dev environment"
|
||||||
|
disabled={starting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Config Profile */}
|
{/* Config Profile */}
|
||||||
{selectedToolTypeId && (
|
{selectedToolTypeId && (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
|
|||||||
@@ -39,8 +39,11 @@ export const useTerminalPage = () => {
|
|||||||
const focusInputRef = useRef<(() => void) | null>(null);
|
const focusInputRef = useRef<(() => void) | null>(null);
|
||||||
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
||||||
const [showSpecialKeysPanel, setShowSpecialKeysPanel] = useState(false);
|
const [showSpecialKeysPanel, setShowSpecialKeysPanel] = useState(false);
|
||||||
const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(null);
|
const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(
|
||||||
const { isOpen: isKeyboardOpen, height: keyboardHeight } = useVirtualKeyboard();
|
null,
|
||||||
|
);
|
||||||
|
const { isOpen: isKeyboardOpen, height: keyboardHeight } =
|
||||||
|
useVirtualKeyboard();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
sessions,
|
sessions,
|
||||||
@@ -124,7 +127,10 @@ export const useTerminalPage = () => {
|
|||||||
break;
|
break;
|
||||||
case "w":
|
case "w":
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (activeSessionId && window.confirm("Close this terminal session?")) {
|
if (
|
||||||
|
activeSessionId &&
|
||||||
|
window.confirm("Close this terminal session?")
|
||||||
|
) {
|
||||||
void closeSession(activeSessionId);
|
void closeSession(activeSessionId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -139,7 +145,8 @@ export const useTerminalPage = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (activeSessionId) {
|
if (activeSessionId) {
|
||||||
const idx = sessions.findIndex((s) => s.id === activeSessionId);
|
const idx = sessions.findIndex((s) => s.id === activeSessionId);
|
||||||
if (idx < sessions.length - 1) setActiveSessionId(sessions[idx + 1].id);
|
if (idx < sessions.length - 1)
|
||||||
|
setActiveSessionId(sessions[idx + 1].id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "r":
|
case "r":
|
||||||
|
|||||||
@@ -89,4 +89,3 @@
|
|||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user