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 { listProjects } from "../api/projects";
|
||||||
import type { Project } from "../types";
|
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 {
|
import {
|
||||||
getUserSessions,
|
getUserSessions,
|
||||||
type Session,
|
type Session,
|
||||||
@@ -43,6 +43,12 @@ export const SessionsPage = () => {
|
|||||||
const [branch, setBranch] = useState("main");
|
const [branch, setBranch] = useState("main");
|
||||||
const [sshKeys, setSshKeys] = useState<SSHKey[]>([]);
|
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 [dirtyDeleteSession, setDirtyDeleteSession] = useState<Session | null>(null);
|
||||||
const [dirtyDeleteFiles, setDirtyDeleteFiles] = useState<string[]>([]);
|
const [dirtyDeleteFiles, setDirtyDeleteFiles] = useState<string[]>([]);
|
||||||
|
|
||||||
@@ -116,6 +122,33 @@ export const SessionsPage = () => {
|
|||||||
void loadSshKeys();
|
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
|
// Poll health every 30 seconds for active instances
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkHealth = async () => {
|
const checkHealth = async () => {
|
||||||
@@ -213,7 +246,8 @@ export const SessionsPage = () => {
|
|||||||
selectedToolType,
|
selectedToolType,
|
||||||
displayName || undefined,
|
displayName || undefined,
|
||||||
cloneMode,
|
cloneMode,
|
||||||
cloneMode === "clone" ? branch : undefined
|
isCreatingNewBranch ? baseBranch : branch,
|
||||||
|
isCreatingNewBranch ? newBranchName : undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
// Auto-start the instance
|
// Auto-start the instance
|
||||||
@@ -227,6 +261,10 @@ export const SessionsPage = () => {
|
|||||||
setDisplayName("");
|
setDisplayName("");
|
||||||
setCloneMode("mount");
|
setCloneMode("mount");
|
||||||
setBranch("main");
|
setBranch("main");
|
||||||
|
setIsCreatingNewBranch(false);
|
||||||
|
setNewBranchName("");
|
||||||
|
setBaseBranch("");
|
||||||
|
setBranches([]);
|
||||||
await loadSessions();
|
await loadSessions();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setCreateStatus("error");
|
setCreateStatus("error");
|
||||||
@@ -687,14 +725,61 @@ export const SessionsPage = () => {
|
|||||||
<>
|
<>
|
||||||
<label className="form-field">
|
<label className="form-field">
|
||||||
Branch
|
Branch
|
||||||
<input
|
{isLoadingBranches ? (
|
||||||
type="text"
|
<span className="muted">Loading branches...</span>
|
||||||
value={branch}
|
) : (
|
||||||
onChange={(e) => setBranch(e.target.value)}
|
<select
|
||||||
placeholder="main"
|
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>
|
</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 && (
|
{selectedRepo && (
|
||||||
<div className="form-field ssh-key-info">
|
<div className="form-field ssh-key-info">
|
||||||
{(() => {
|
{(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user