feat: allow creating external repositories directly from git mount editor

- Add createExternalRepository API function
- Update GitMountEditor with "+ Add new repository..." option
- Show form to enter repo name and remote URL
- Auto-create external repo and refresh list on success
- Update config-profiles page to pass onCreateRepository handler
This commit is contained in:
Alex Blank
2026-05-27 11:17:27 +02:00
parent f14fc37e75
commit baabd1fa62
4 changed files with 137 additions and 17 deletions
+7
View File
@@ -60,6 +60,13 @@ export async function createRepository(
return response.data;
}
export async function createExternalRepository(
data: GitRepositoryCreate
): Promise<GitRepository> {
const response = await apiClient.post<GitRepository>("/repositories", data);
return response.data;
}
export async function deleteRepository(projectId: string, repoId: string): Promise<void> {
await apiClient.delete(`/projects/${projectId}/repositories/${repoId}`);
}
+96 -16
View File
@@ -7,9 +7,10 @@ interface GitMountEditorProps {
mounts: GitMount[];
repositories: GitRepository[];
onChange: (mounts: GitMount[]) => void;
onCreateRepository?: (name: string, remoteUrl: string) => Promise<GitRepository>;
}
export const GitMountEditor = ({ mounts, repositories, onChange }: GitMountEditorProps) => {
export const GitMountEditor = ({ mounts, repositories, onChange, onCreateRepository }: GitMountEditorProps) => {
const [editingIndex, setEditingIndex] = useState<number | null>(null);
const [newMount, setNewMount] = useState<GitMount>({
repo_id: "",
@@ -58,6 +59,7 @@ export const GitMountEditor = ({ mounts, repositories, onChange }: GitMountEdito
onSave={(updated) => handleUpdate(index, updated)}
onCancel={() => setEditingIndex(null)}
validatePath={validatePath}
onCreateRepository={onCreateRepository}
/>
) : (
<div className="git-mount-display">
@@ -105,6 +107,7 @@ export const GitMountEditor = ({ mounts, repositories, onChange }: GitMountEdito
onSave={handleAdd}
onCancel={() => setNewMount({ repo_id: "", source_path: ".", target_path: "", branch: "" })}
validatePath={validatePath}
onCreateRepository={onCreateRepository}
isNew
/>
</div>
@@ -118,12 +121,17 @@ interface GitMountFormProps {
onSave: (mount: GitMount) => void;
onCancel: () => void;
validatePath: (path: string, isTarget: boolean) => string | null;
onCreateRepository?: (name: string, remoteUrl: string) => Promise<GitRepository>;
isNew?: boolean;
}
const GitMountForm = ({ mount, repositories, onSave, onCancel, validatePath, isNew }: GitMountFormProps) => {
const GitMountForm = ({ mount, repositories, onSave, onCancel, validatePath, onCreateRepository, isNew }: GitMountFormProps) => {
const [form, setForm] = useState<GitMount>({ ...mount });
const [errors, setErrors] = useState<Record<string, string>>({});
const [isCreatingRepo, setIsCreatingRepo] = useState(false);
const [newRepoName, setNewRepoName] = useState("");
const [newRepoUrl, setNewRepoUrl] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (field: keyof GitMount, value: string) => {
setForm((prev) => ({ ...prev, [field]: value }));
@@ -136,6 +144,26 @@ const GitMountForm = ({ mount, repositories, onSave, onCancel, validatePath, isN
}
};
const handleCreateRepo = async () => {
if (!onCreateRepository || !newRepoName.trim() || !newRepoUrl.trim()) return;
setIsSubmitting(true);
try {
const repo = await onCreateRepository(newRepoName.trim(), newRepoUrl.trim());
handleChange("repo_id", repo.id);
setIsCreatingRepo(false);
setNewRepoName("");
setNewRepoUrl("");
} catch (err) {
setErrors((prev) => ({
...prev,
repo_id: err instanceof Error ? err.message : "Failed to create repository",
}));
} finally {
setIsSubmitting(false);
}
};
const handleSubmit = () => {
const newErrors: Record<string, string> = {};
@@ -164,19 +192,71 @@ const GitMountForm = ({ mount, repositories, onSave, onCancel, validatePath, isN
<div className="git-mount-form">
<div className="form-row">
<label>Repository</label>
<select
value={form.repo_id}
onChange={(e) => handleChange("repo_id", e.target.value)}
className={errors.repo_id ? "error" : ""}
>
<option value="">Select a repository...</option>
{repositories.map((repo) => (
<option key={repo.id} value={repo.id}>
{repo.name}
</option>
))}
</select>
{errors.repo_id && <span className="error-text">{errors.repo_id}</span>}
{!isCreatingRepo ? (
<>
<select
value={form.repo_id}
onChange={(e) => {
if (e.target.value === "__new__") {
setIsCreatingRepo(true);
} else {
handleChange("repo_id", e.target.value);
}
}}
className={errors.repo_id ? "error" : ""}
>
<option value="">Select a repository...</option>
{repositories.map((repo) => (
<option key={repo.id} value={repo.id}>
{repo.name}
</option>
))}
{onCreateRepository && (
<option value="__new__">+ Add new repository...</option>
)}
</select>
{errors.repo_id && <span className="error-text">{errors.repo_id}</span>}
</>
) : (
<div className="new-repo-form">
<input
type="text"
value={newRepoName}
onChange={(e) => setNewRepoName(e.target.value)}
placeholder="Repository name"
disabled={isSubmitting}
/>
<input
type="text"
value={newRepoUrl}
onChange={(e) => setNewRepoUrl(e.target.value)}
placeholder="https://github.com/user/repo.git"
disabled={isSubmitting}
/>
<div className="new-repo-actions">
<button
type="button"
className="primary-button small"
onClick={handleCreateRepo}
disabled={isSubmitting || !newRepoName.trim() || !newRepoUrl.trim()}
>
{isSubmitting ? "Creating..." : "Create Repository"}
</button>
<button
type="button"
className="secondary-button small"
onClick={() => {
setIsCreatingRepo(false);
setNewRepoName("");
setNewRepoUrl("");
}}
disabled={isSubmitting}
>
Cancel
</button>
</div>
</div>
)}
</div>
<div className="form-row">
@@ -226,4 +306,4 @@ const GitMountForm = ({ mount, repositories, onSave, onCancel, validatePath, isN
</div>
</div>
);
};
};
+9 -1
View File
@@ -19,7 +19,7 @@ import {
type ResolvedProfile,
} from "../api/config_profiles";
import { listProjects } from "../api/projects";
import { listAllUserRepositories, type GitRepository } from "../api/git_repositories";
import { listAllUserRepositories, createExternalRepository, type GitRepository } from "../api/git_repositories";
import type { Project } from "../types";
import { listToolTypes, type ToolType } from "../api/tool_types";
import { GitMountEditor } from "../components/git-mount-editor";
@@ -1259,6 +1259,14 @@ export const ConfigProfilesPage = () => {
mounts={formData.git_mounts || []}
repositories={repositories}
onChange={(git_mounts) => updateFormField("git_mounts", git_mounts)}
onCreateRepository={async (name, remoteUrl) => {
const repo = await createExternalRepository({
name,
remote_url: remoteUrl,
});
setRepositories((prev) => [...prev, repo]);
return repo;
}}
/>
</div>
+25
View File
@@ -4131,3 +4131,28 @@ a.nav-item,
margin-top: 0.5rem;
}
.new-repo-form {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.75rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 0.375rem;
}
.new-repo-form input {
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
background: var(--surface);
color: var(--text);
font-size: 0.875rem;
}
.new-repo-actions {
display: flex;
gap: 0.5rem;
margin-top: 0.25rem;
}