diff --git a/apps/web/src/api/git_repositories.ts b/apps/web/src/api/git_repositories.ts index 373886e..cbacb1f 100644 --- a/apps/web/src/api/git_repositories.ts +++ b/apps/web/src/api/git_repositories.ts @@ -60,6 +60,13 @@ export async function createRepository( return response.data; } +export async function createExternalRepository( + data: GitRepositoryCreate +): Promise { + const response = await apiClient.post("/repositories", data); + return response.data; +} + export async function deleteRepository(projectId: string, repoId: string): Promise { await apiClient.delete(`/projects/${projectId}/repositories/${repoId}`); } diff --git a/apps/web/src/components/git-mount-editor.tsx b/apps/web/src/components/git-mount-editor.tsx index 7a4eed0..d5dc8c8 100644 --- a/apps/web/src/components/git-mount-editor.tsx +++ b/apps/web/src/components/git-mount-editor.tsx @@ -7,9 +7,10 @@ interface GitMountEditorProps { mounts: GitMount[]; repositories: GitRepository[]; onChange: (mounts: GitMount[]) => void; + onCreateRepository?: (name: string, remoteUrl: string) => Promise; } -export const GitMountEditor = ({ mounts, repositories, onChange }: GitMountEditorProps) => { +export const GitMountEditor = ({ mounts, repositories, onChange, onCreateRepository }: GitMountEditorProps) => { const [editingIndex, setEditingIndex] = useState(null); const [newMount, setNewMount] = useState({ 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} /> ) : (
@@ -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 />
@@ -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; 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({ ...mount }); const [errors, setErrors] = useState>({}); + 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 = {}; @@ -164,19 +192,71 @@ const GitMountForm = ({ mount, repositories, onSave, onCancel, validatePath, isN
- - {errors.repo_id && {errors.repo_id}} + {!isCreatingRepo ? ( + <> + + {errors.repo_id && {errors.repo_id}} + + ) : ( +
+ setNewRepoName(e.target.value)} + placeholder="Repository name" + disabled={isSubmitting} + /> + setNewRepoUrl(e.target.value)} + placeholder="https://github.com/user/repo.git" + disabled={isSubmitting} + /> +
+ + +
+
+ )}
@@ -226,4 +306,4 @@ const GitMountForm = ({ mount, repositories, onSave, onCancel, validatePath, isN
); -}; +}; \ No newline at end of file diff --git a/apps/web/src/pages/config-profiles.tsx b/apps/web/src/pages/config-profiles.tsx index 7d641d2..a2a6407 100644 --- a/apps/web/src/pages/config-profiles.tsx +++ b/apps/web/src/pages/config-profiles.tsx @@ -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; + }} /> diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index d618f01..8c00b45 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -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; +} +