diff --git a/src/lib/components/AddRepositoryModal.svelte b/src/lib/components/AddRepositoryModal.svelte index 51a18ac..d620cd1 100644 --- a/src/lib/components/AddRepositoryModal.svelte +++ b/src/lib/components/AddRepositoryModal.svelte @@ -1,4 +1,6 @@ + + + +
+
+ + +
+
+ +{#if open} + + +{/if} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e3151b0..d17cfce 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -8,9 +8,11 @@ let { data }: { data: PageData } = $props(); - // Local mutable copy; refreshRepositories() keeps it up to date after mutations. - // Intentionally captures initial value from server load — mutations happen via fetch. - let repositories = $state(data.repositories ?? []); // svelte-disable state_referenced_locally + // Initialized empty; $effect syncs from data prop on every navigation/reload. + let repositories = $state([]); + $effect(() => { + repositories = data.repositories ?? []; + }); let showAddModal = $state(false); let confirmDeleteId = $state(null); let activeJobIds = $state>({}); diff --git a/src/routes/api/v1/fs/browse/+server.ts b/src/routes/api/v1/fs/browse/+server.ts new file mode 100644 index 0000000..2da2ee5 --- /dev/null +++ b/src/routes/api/v1/fs/browse/+server.ts @@ -0,0 +1,36 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import fs from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; + +export const GET: RequestHandler = ({ url }) => { + const rawPath = url.searchParams.get('path') ?? os.homedir(); + const target = path.resolve(rawPath); + + let entries: { name: string; path: string; isGitRepo: boolean }[] = []; + let error: string | null = null; + let resolved = target; + + try { + const items = fs.readdirSync(target, { withFileTypes: true }); + entries = items + .filter((d) => d.isDirectory() && !d.name.startsWith('.')) + .map((d) => { + const full = path.join(target, d.name); + const isGitRepo = fs.existsSync(path.join(full, '.git')); + return { name: d.name, path: full, isGitRepo }; + }) + .sort((a, b) => { + // git repos first, then alphabetical + if (a.isGitRepo !== b.isGitRepo) return a.isGitRepo ? -1 : 1; + return a.name.localeCompare(b.name); + }); + } catch (e) { + error = (e as NodeJS.ErrnoException).code === 'EACCES' ? 'Permission denied' : 'Path not found'; + } + + const parent = target !== path.parse(target).root ? path.dirname(target) : null; + + return json({ path: resolved, parent, entries, error }); +}; diff --git a/src/routes/repos/[id]/+page.svelte b/src/routes/repos/[id]/+page.svelte index 86e6891..6da717a 100644 --- a/src/routes/repos/[id]/+page.svelte +++ b/src/routes/repos/[id]/+page.svelte @@ -7,9 +7,13 @@ let { data }: { data: PageData } = $props(); - // Local mutable copies updated via fetch — intentionally capturing initial server values. - let repo = $state(data.repo); // svelte-disable state_referenced_locally - let recentJobs = $state(data.recentJobs ?? []); // svelte-disable state_referenced_locally + // Initialized empty; $effect syncs from data prop on every navigation/reload. + let repo = $state({} as Repository & { versions?: RepositoryVersion[] }); + let recentJobs = $state([]); + $effect(() => { + if (data.repo) repo = data.repo; + recentJobs = data.recentJobs ?? []; + }); let showDeleteConfirm = $state(false); let activeJobId = $state(null); let errorMessage = $state(null);