Merge branch 'feat/session-branch-selection' into dev
Resolved conflicts: - Moved branch selection UI from inline sessions.tsx to CreateSessionForm component - Integrated branch dropdown and new branch creation into CreateSessionForm - Removed duplicate branch state management from sessions.tsx All branch selection tests pass (7/7).
This commit is contained in:
@@ -2,13 +2,14 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { getDashboardSummary, type DashboardSummary } from "../api/dashboard";
|
||||
import { createInstance, getUserSessions, startInstance, stopInstance, deleteInstance, recreateInstanceTunnel, checkInstanceHealth, type Session as SessionApi, type InstanceHealth } from "../api/sessions";
|
||||
import { getUserSessions, startInstance, stopInstance, deleteInstance, recreateInstanceTunnel, checkInstanceHealth, type Session as SessionApi, type InstanceHealth } from "../api/sessions";
|
||||
import { listProjects } from "../api/projects";
|
||||
import { listRepositories, type GitRepository } from "../api/git_repositories";
|
||||
import { listToolTypes, type ToolType } from "../api/tool_types";
|
||||
import { updateUserConfig } from "../api/settings";
|
||||
import type { Project } from "../types";
|
||||
import { Icon } from "../components/icon";
|
||||
import { CreateSessionForm } from "../components/create-session-form";
|
||||
|
||||
type HomeStatus = "loading" | "ready" | "error";
|
||||
|
||||
@@ -29,10 +30,6 @@ export const HomePage = () => {
|
||||
const [repositories, setRepositories] = useState<GitRepository[]>([]);
|
||||
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
||||
const [selectedProject, setSelectedProject] = useState("");
|
||||
const [selectedRepo, setSelectedRepo] = useState("");
|
||||
const [selectedToolType, setSelectedToolType] = useState("");
|
||||
const [displayName, setDisplayName] = useState("");
|
||||
const [saveState, setSaveState] = useState<"idle" | "saving" | "error">("idle");
|
||||
const [actionBusy, setActionBusy] = useState<string | null>(null);
|
||||
const [stopConfirmId, setStopConfirmId] = useState<string | null>(null);
|
||||
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);
|
||||
@@ -133,24 +130,10 @@ export const HomePage = () => {
|
||||
[safeSessions]
|
||||
);
|
||||
|
||||
const handleCreate = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (!selectedProject || !selectedRepo || !selectedToolType) return;
|
||||
|
||||
setSaveState("saving");
|
||||
try {
|
||||
const instance = await createInstance(selectedProject, selectedRepo, selectedToolType, displayName || undefined);
|
||||
await startInstance(selectedProject, selectedRepo, instance.id);
|
||||
await updateUserConfig({ last_session_id: instance.id });
|
||||
setDisplayName("");
|
||||
setSelectedProject("");
|
||||
setSelectedRepo("");
|
||||
setSelectedToolType("");
|
||||
setSaveState("idle");
|
||||
await loadHome();
|
||||
} catch {
|
||||
setSaveState("error");
|
||||
}
|
||||
const handleCreateSuccess = async (instance: { id: string }) => {
|
||||
await updateUserConfig({ last_session_id: instance.id });
|
||||
setSelectedProject("");
|
||||
await loadHome();
|
||||
};
|
||||
|
||||
const handleOpen = (session: SessionView) => {
|
||||
@@ -358,41 +341,13 @@ export const HomePage = () => {
|
||||
<h2>Start a session</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form className="stack create-session-form" onSubmit={handleCreate}>
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
Project
|
||||
<select value={selectedProject} onChange={(event) => { setSelectedProject(event.target.value); setSelectedRepo(""); }}>
|
||||
<option value="">Select project...</option>
|
||||
{projects.map((project) => <option key={project.id} value={project.id}>{project.name}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
Repository
|
||||
<select value={selectedRepo} onChange={(event) => setSelectedRepo(event.target.value)} disabled={!selectedProject}>
|
||||
<option value="">Select repository...</option>
|
||||
{repositories.map((repo) => <option key={repo.id} value={repo.id}>{repo.name}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
Tool type
|
||||
<select value={selectedToolType} onChange={(event) => setSelectedToolType(event.target.value)}>
|
||||
<option value="">Select tool...</option>
|
||||
{toolTypes.map((tool) => <option key={tool.id} value={tool.id}>{tool.display_name}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<label className="form-field">
|
||||
Display name
|
||||
<input type="text" value={displayName} onChange={(event) => setDisplayName(event.target.value)} placeholder="My Development Environment" />
|
||||
</label>
|
||||
<div className="form-actions">
|
||||
<button className="primary-button" type="submit" disabled={saveState === "saving"}>
|
||||
{saveState === "saving" ? <><Icon name="loading" size="sm" /> Creating...</> : <><Icon name="add" size="sm" /> Create Session</>}
|
||||
</button>
|
||||
{saveState === "error" && <span className="error-text">Failed to create session</span>}
|
||||
</div>
|
||||
</form>
|
||||
<CreateSessionForm
|
||||
projects={projects}
|
||||
repositories={repositories}
|
||||
toolTypes={toolTypes}
|
||||
onProjectChange={(projectId) => setSelectedProject(projectId)}
|
||||
onSuccess={handleCreateSuccess}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{recentSessions.length > 0 && (
|
||||
|
||||
@@ -254,6 +254,8 @@ export const RepoWorkspace = () => {
|
||||
<InstanceList
|
||||
projectId={projectId!}
|
||||
repoId={selectedRepoId}
|
||||
projectName={project?.name}
|
||||
repoName={repositories.find((r) => r.id === selectedRepoId)?.name}
|
||||
toolTypes={toolTypes}
|
||||
/>
|
||||
)}
|
||||
|
||||
+45
-313
@@ -3,24 +3,21 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { listProjects } from "../api/projects";
|
||||
import type { Project } from "../types";
|
||||
import { listRepositories, listRepositoryBranches, type GitRepository, type Branch } from "../api/git_repositories";
|
||||
import { listRepositories, type GitRepository } from "../api/git_repositories";
|
||||
import {
|
||||
getUserSessions,
|
||||
type Session,
|
||||
deleteInstance,
|
||||
stopInstance,
|
||||
startInstance,
|
||||
checkInstanceHealth,
|
||||
recreateInstanceTunnel,
|
||||
} from "../api/sessions";
|
||||
import { listToolTypes, type ToolType } from "../api/tool_types";
|
||||
import { createInstance } from "../api/sessions";
|
||||
import { getUserConfig, updateUserConfig } from "../api/settings";
|
||||
import { listSSHKeys, type SSHKey } from "../api/ssh_keys";
|
||||
import { Icon } from "../components/icon";
|
||||
import { CreateSessionForm } from "../components/create-session-form";
|
||||
|
||||
type SessionsStatus = "loading" | "ready" | "error";
|
||||
type CreateStatus = "idle" | "creating" | "error";
|
||||
|
||||
export const SessionsPage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -31,23 +28,7 @@ export const SessionsPage = () => {
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [repositories, setRepositories] = useState<GitRepository[]>([]);
|
||||
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
||||
|
||||
const [selectedProject, setSelectedProject] = useState<string>("");
|
||||
const [selectedRepo, setSelectedRepo] = useState<string>("");
|
||||
const [selectedToolType, setSelectedToolType] = useState<string>("");
|
||||
const [displayName, setDisplayName] = useState("");
|
||||
const [createStatus, setCreateStatus] = useState<CreateStatus>("idle");
|
||||
const [createError, setCreateError] = useState<string | null>(null);
|
||||
|
||||
const [cloneMode, setCloneMode] = useState<"mount" | "clone">("mount");
|
||||
const [branch, setBranch] = useState("main");
|
||||
const [sshKeys, setSshKeys] = useState<SSHKey[]>([]);
|
||||
|
||||
const [branches, setBranches] = useState<Branch[]>([]);
|
||||
const [isLoadingBranches, setIsLoadingBranches] = useState(false);
|
||||
const [isCreatingNewBranch, setIsCreatingNewBranch] = useState(false);
|
||||
const [newBranchName, setNewBranchName] = useState("");
|
||||
const [baseBranch, setBaseBranch] = useState("");
|
||||
|
||||
const [dirtyDeleteSession, setDirtyDeleteSession] = useState<Session | null>(null);
|
||||
const [dirtyDeleteFiles, setDirtyDeleteFiles] = useState<string[]>([]);
|
||||
@@ -66,6 +47,8 @@ export const SessionsPage = () => {
|
||||
}>>({});
|
||||
const [recreatingId, setRecreatingId] = useState<string | null>(null);
|
||||
const [expandedProbeId, setExpandedProbeId] = useState<string | null>(null);
|
||||
const [loadingSessionId, setLoadingSessionId] = useState<string | null>(null);
|
||||
const [loadingAction, setLoadingAction] = useState<string>("");
|
||||
|
||||
const loadSessions = useCallback(async () => {
|
||||
setStatus("loading");
|
||||
@@ -110,44 +93,7 @@ export const SessionsPage = () => {
|
||||
void loadToolTypes();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadSshKeys = async () => {
|
||||
try {
|
||||
const data = await listSSHKeys();
|
||||
setSshKeys(data);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
void loadSshKeys();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadBranches = async () => {
|
||||
if (!selectedRepo || !selectedProject || cloneMode !== "clone") {
|
||||
setBranches([]);
|
||||
setIsCreatingNewBranch(false);
|
||||
setNewBranchName("");
|
||||
setBaseBranch("");
|
||||
return;
|
||||
}
|
||||
setIsLoadingBranches(true);
|
||||
try {
|
||||
const data = await listRepositoryBranches(selectedProject, selectedRepo);
|
||||
setBranches(data.branches);
|
||||
const defaultBranch = data.default_branch;
|
||||
setBaseBranch(defaultBranch);
|
||||
if (!branch || !data.branches.find((b) => b.name === branch)) {
|
||||
setBranch(defaultBranch);
|
||||
}
|
||||
} catch {
|
||||
setBranches([]);
|
||||
} finally {
|
||||
setIsLoadingBranches(false);
|
||||
}
|
||||
};
|
||||
void loadBranches();
|
||||
}, [selectedRepo, selectedProject, cloneMode]);
|
||||
|
||||
// Poll health every 30 seconds for active instances
|
||||
useEffect(() => {
|
||||
@@ -221,72 +167,30 @@ export const SessionsPage = () => {
|
||||
[sessions, lastSessionId]
|
||||
);
|
||||
|
||||
const handleCreate = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setCreateError(null);
|
||||
|
||||
if (!selectedProject || !selectedRepo || !selectedToolType) {
|
||||
setCreateError("Project, repository, and tool type are required");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cloneMode === "clone") {
|
||||
const repo = repositories.find((r) => r.id === selectedRepo);
|
||||
if (!repo?.ssh_key_id) {
|
||||
setCreateError("Repository must have an SSH key assigned for clone mode");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setCreateStatus("creating");
|
||||
try {
|
||||
const instance = await createInstance(
|
||||
selectedProject,
|
||||
selectedRepo,
|
||||
selectedToolType,
|
||||
displayName || undefined,
|
||||
cloneMode,
|
||||
isCreatingNewBranch ? baseBranch : branch,
|
||||
isCreatingNewBranch ? newBranchName : undefined
|
||||
);
|
||||
|
||||
// Auto-start the instance
|
||||
await startInstance(selectedProject, selectedRepo, instance.id);
|
||||
|
||||
await updateUserConfig({ last_session_id: instance.id });
|
||||
setCreateStatus("idle");
|
||||
setSelectedProject("");
|
||||
setSelectedRepo("");
|
||||
setSelectedToolType("");
|
||||
setDisplayName("");
|
||||
setCloneMode("mount");
|
||||
setBranch("main");
|
||||
setIsCreatingNewBranch(false);
|
||||
setNewBranchName("");
|
||||
setBaseBranch("");
|
||||
setBranches([]);
|
||||
await loadSessions();
|
||||
} catch (error) {
|
||||
setCreateStatus("error");
|
||||
const axiosError = error as { response?: { data?: { detail?: string } } };
|
||||
const message = axiosError.response?.data?.detail;
|
||||
setCreateError(
|
||||
typeof message === "string" ? message : "Failed to create session"
|
||||
);
|
||||
}
|
||||
const handleCreateSuccess = async (instance: { id: string }) => {
|
||||
await updateUserConfig({ last_session_id: instance.id });
|
||||
setSelectedProject("");
|
||||
await loadSessions();
|
||||
};
|
||||
|
||||
const handleStop = async (sessionId: string, projectId: string, repoId: string) => {
|
||||
setLoadingSessionId(sessionId);
|
||||
setLoadingAction("Stopping...");
|
||||
try {
|
||||
await stopInstance(projectId, repoId, sessionId);
|
||||
setStopConfirmId(null);
|
||||
await loadSessions();
|
||||
} catch {
|
||||
setStopConfirmId(null);
|
||||
} finally {
|
||||
setLoadingSessionId(null);
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (sessionId: string, projectId: string, repoId: string, force = false) => {
|
||||
setLoadingSessionId(sessionId);
|
||||
setLoadingAction("Deleting...");
|
||||
try {
|
||||
await deleteInstance(projectId, repoId, sessionId, force);
|
||||
setDeleteConfirmId(null);
|
||||
@@ -306,11 +210,15 @@ export const SessionsPage = () => {
|
||||
}
|
||||
}
|
||||
setDeleteConfirmId(null);
|
||||
} finally {
|
||||
setLoadingSessionId(null);
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecreateTunnel = async (session: Session) => {
|
||||
setRecreatingId(session.id);
|
||||
setLoadingSessionId(session.id);
|
||||
setLoadingAction("Recreating tunnel...");
|
||||
try {
|
||||
await recreateInstanceTunnel(
|
||||
session.project_id,
|
||||
@@ -322,7 +230,8 @@ export const SessionsPage = () => {
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setRecreatingId(null);
|
||||
setLoadingSessionId(null);
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -407,7 +316,15 @@ export const SessionsPage = () => {
|
||||
)}
|
||||
|
||||
{/* Active Sessions */}
|
||||
<div className="active-sessions-section">
|
||||
<div className={`active-sessions-section ${loadingSessionId ? "dimmed" : ""}`}>
|
||||
{loadingSessionId && (
|
||||
<div className="loading-overlay">
|
||||
<div className="loading-content">
|
||||
<Icon name="loading" size="lg" />
|
||||
<p>{loadingAction}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<h2>
|
||||
Active Sessions
|
||||
{activeSessions.length > 0 && (
|
||||
@@ -445,17 +362,18 @@ export const SessionsPage = () => {
|
||||
{tunnelHealth[session.id] && tunnelHealth[session.id].tunnel_status === "error_response" && (
|
||||
<span className="status-badge warning">app error ({tunnelHealth[session.id].tunnel_status_code})</span>
|
||||
)}
|
||||
{tunnelHealth[session.id]?.last_probe_output && (
|
||||
{tunnelHealth[session.id]?.probe_status && tunnelHealth[session.id]?.probe_status !== "not_applicable" && (
|
||||
<div className="probe-output-section">
|
||||
<button
|
||||
className="probe-toggle"
|
||||
className={`probe-toggle probe-${tunnelHealth[session.id].probe_status}`}
|
||||
onClick={() => setExpandedProbeId(expandedProbeId === session.id ? null : session.id)}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="info" size="sm" />
|
||||
{expandedProbeId === session.id ? "Hide probe output" : "Show probe output"}
|
||||
Probe: {tunnelHealth[session.id].probe_status}
|
||||
{expandedProbeId === session.id ? " (hide)" : " (show)"}
|
||||
</button>
|
||||
{expandedProbeId === session.id && (
|
||||
{expandedProbeId === session.id && tunnelHealth[session.id]?.last_probe_output && (
|
||||
<pre className="probe-output">
|
||||
{tunnelHealth[session.id].last_probe_output}
|
||||
</pre>
|
||||
@@ -642,201 +560,15 @@ export const SessionsPage = () => {
|
||||
{/* Create Session */}
|
||||
<div className="create-session-section">
|
||||
<h2>Create New Session</h2>
|
||||
<form onSubmit={handleCreate} className="card stack create-session-form">
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
Project
|
||||
<select
|
||||
value={selectedProject}
|
||||
onChange={(e) => {
|
||||
setSelectedProject(e.target.value);
|
||||
setSelectedRepo("");
|
||||
}}
|
||||
>
|
||||
<option value="">Select project...</option>
|
||||
{projects.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
{p.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="form-field">
|
||||
Repository
|
||||
<select
|
||||
value={selectedRepo}
|
||||
onChange={(e) => setSelectedRepo(e.target.value)}
|
||||
disabled={!selectedProject}
|
||||
>
|
||||
<option value="">Select repository...</option>
|
||||
{repositories.map((r) => (
|
||||
<option key={r.id} value={r.id}>
|
||||
{r.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="form-field">
|
||||
Tool Type
|
||||
<select
|
||||
value={selectedToolType}
|
||||
onChange={(e) => setSelectedToolType(e.target.value)}
|
||||
>
|
||||
<option value="">Select tool...</option>
|
||||
{toolTypes.map((t) => (
|
||||
<option key={t.id} value={t.id}>
|
||||
{t.display_name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
Repository Access
|
||||
<div className="radio-group">
|
||||
<label className="radio-label">
|
||||
<input
|
||||
type="radio"
|
||||
name="cloneMode"
|
||||
value="mount"
|
||||
checked={cloneMode === "mount"}
|
||||
onChange={(e) => setCloneMode(e.target.value as "mount" | "clone")}
|
||||
/>
|
||||
Mount (live sync)
|
||||
</label>
|
||||
<label className="radio-label">
|
||||
<input
|
||||
type="radio"
|
||||
name="cloneMode"
|
||||
value="clone"
|
||||
checked={cloneMode === "clone"}
|
||||
onChange={(e) => setCloneMode(e.target.value as "mount" | "clone")}
|
||||
/>
|
||||
Clone fresh copy
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{cloneMode === "clone" && (
|
||||
<>
|
||||
<label className="form-field">
|
||||
Branch
|
||||
{isLoadingBranches ? (
|
||||
<span className="muted">Loading branches...</span>
|
||||
) : (
|
||||
<select
|
||||
value={isCreatingNewBranch ? "__new__" : branch}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value === "__new__") {
|
||||
setIsCreatingNewBranch(true);
|
||||
setNewBranchName("");
|
||||
} else {
|
||||
setIsCreatingNewBranch(false);
|
||||
setBranch(value);
|
||||
setBaseBranch(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{branches.map((b) => (
|
||||
<option key={b.name} value={b.name}>
|
||||
{b.name} {b.is_default ? "(default)" : ""}
|
||||
</option>
|
||||
))}
|
||||
<option value="__new__">Create new branch...</option>
|
||||
</select>
|
||||
)}
|
||||
</label>
|
||||
|
||||
{isCreatingNewBranch && (
|
||||
<>
|
||||
<label className="form-field">
|
||||
New Branch Name
|
||||
<input
|
||||
type="text"
|
||||
value={newBranchName}
|
||||
onChange={(e) => setNewBranchName(e.target.value)}
|
||||
placeholder="feature/my-new-branch"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
Base Branch
|
||||
<select
|
||||
value={baseBranch}
|
||||
onChange={(e) => setBaseBranch(e.target.value)}
|
||||
>
|
||||
{branches.map((b) => (
|
||||
<option key={b.name} value={b.name}>
|
||||
{b.name} {b.is_default ? "(default)" : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedRepo && (
|
||||
<div className="form-field ssh-key-info">
|
||||
{(() => {
|
||||
const repo = repositories.find((r) => r.id === selectedRepo);
|
||||
if (!repo) return null;
|
||||
if (repo.ssh_key_id) {
|
||||
const key = sshKeys.find((k) => k.id === repo.ssh_key_id);
|
||||
return (
|
||||
<span className="success-text">
|
||||
SSH key: {key?.name || "Assigned"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="warning-text">
|
||||
No SSH key assigned to this repository. Clone mode requires an SSH key.
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<label className="form-field">
|
||||
Display Name (optional)
|
||||
<input
|
||||
type="text"
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
placeholder="My Development Environment"
|
||||
/>
|
||||
</label>
|
||||
|
||||
{createError && <p className="error-text">{createError}</p>}
|
||||
|
||||
<div className="form-actions">
|
||||
<button
|
||||
className="primary-button"
|
||||
type="submit"
|
||||
disabled={createStatus === "creating"}
|
||||
>
|
||||
{createStatus === "creating" ? (
|
||||
<>
|
||||
<Icon name="loading" size="sm" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon name="add" size="sm" />
|
||||
Create Session
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<CreateSessionForm
|
||||
projects={projects}
|
||||
repositories={repositories}
|
||||
toolTypes={toolTypes}
|
||||
onProjectChange={(projectId) => {
|
||||
setSelectedProject(projectId);
|
||||
}}
|
||||
onSuccess={handleCreateSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Dirty Delete Confirmation Modal */}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { TerminalComponent } from "../components/terminal";
|
||||
import { Icon } from "../components/icon";
|
||||
import { MobileTerminalWrapper } from "../components/mobile-terminal-wrapper";
|
||||
import { useMobileViewport } from "../hooks/use-mobile-viewport";
|
||||
|
||||
export const TerminalPage: React.FC = () => {
|
||||
const { instanceId } = useParams<{ instanceId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const isMobile = useMobileViewport();
|
||||
|
||||
if (!instanceId) {
|
||||
return (
|
||||
@@ -16,6 +18,16 @@ export const TerminalPage: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<MobileTerminalWrapper
|
||||
instanceId={instanceId}
|
||||
onBack={() => navigate(-1)}
|
||||
onClose={() => navigate(-1)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="terminal-page">
|
||||
<div className="terminal-page-header">
|
||||
@@ -24,7 +36,6 @@ export const TerminalPage: React.FC = () => {
|
||||
onClick={() => navigate(-1)}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="arrow-left" size="sm" />
|
||||
Back
|
||||
</button>
|
||||
<h1>Terminal</h1>
|
||||
@@ -32,6 +43,7 @@ export const TerminalPage: React.FC = () => {
|
||||
<TerminalComponent
|
||||
instanceId={instanceId}
|
||||
onClose={() => navigate(-1)}
|
||||
isMobile={false}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user