feat: add branch dropdown and new branch creation UI
- Replace free-text branch input with dropdown of available branches - Add 'Create new branch...' option with name and base branch inputs - Load branches from API when repository is selected in clone mode - Pass newBranch parameter to createInstance API
This commit is contained in:
@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { listProjects } from "../api/projects";
|
||||
import type { Project } from "../types";
|
||||
import { listRepositories, type GitRepository } from "../api/git_repositories";
|
||||
import { listRepositories, listRepositoryBranches, type GitRepository, type Branch } from "../api/git_repositories";
|
||||
import {
|
||||
getUserSessions,
|
||||
type Session,
|
||||
@@ -43,6 +43,12 @@ export const SessionsPage = () => {
|
||||
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[]>([]);
|
||||
|
||||
@@ -116,6 +122,33 @@ export const SessionsPage = () => {
|
||||
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(() => {
|
||||
const checkHealth = async () => {
|
||||
@@ -213,7 +246,8 @@ export const SessionsPage = () => {
|
||||
selectedToolType,
|
||||
displayName || undefined,
|
||||
cloneMode,
|
||||
cloneMode === "clone" ? branch : undefined
|
||||
isCreatingNewBranch ? baseBranch : branch,
|
||||
isCreatingNewBranch ? newBranchName : undefined
|
||||
);
|
||||
|
||||
// Auto-start the instance
|
||||
@@ -227,6 +261,10 @@ export const SessionsPage = () => {
|
||||
setDisplayName("");
|
||||
setCloneMode("mount");
|
||||
setBranch("main");
|
||||
setIsCreatingNewBranch(false);
|
||||
setNewBranchName("");
|
||||
setBaseBranch("");
|
||||
setBranches([]);
|
||||
await loadSessions();
|
||||
} catch (error) {
|
||||
setCreateStatus("error");
|
||||
@@ -687,14 +725,61 @@ export const SessionsPage = () => {
|
||||
<>
|
||||
<label className="form-field">
|
||||
Branch
|
||||
<input
|
||||
type="text"
|
||||
value={branch}
|
||||
onChange={(e) => setBranch(e.target.value)}
|
||||
placeholder="main"
|
||||
/>
|
||||
{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">
|
||||
{(() => {
|
||||
|
||||
Reference in New Issue
Block a user