added git ssh support and ablity to download repo via zip, tar.gz, and bundle

This commit is contained in:
2026-05-17 20:09:55 +02:00
parent e7c64e583b
commit 5147c6bddb
13 changed files with 633 additions and 20 deletions
+23
View File
@@ -0,0 +1,23 @@
import { useQuery } from '@tanstack/react-query'
import { z } from 'zod'
import { api } from '../client'
export interface InstanceConfig {
sshHost: string
sshPort: string
instanceName: string
}
const instanceSchema = z.object({
sshHost: z.string(),
sshPort: z.string(),
instanceName: z.string(),
})
export function useInstance() {
return useQuery<InstanceConfig>({
queryKey: ['instance'],
queryFn: () => api.get<InstanceConfig>('/api/v1/instance', instanceSchema),
staleTime: Infinity,
})
}
+80 -16
View File
@@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { useRepo, useRepoTree, useRepoBlob, useRepoBranches } from '../api/queries/repos'
import { useEnvironments } from '../api/queries/environments'
import { useInstance } from '../api/queries/instance'
import { TreeBrowser } from '../components/repos/TreeBrowser'
import { RepoListSkeleton } from '../ui/Skeleton'
import { RepoAvatar } from '../ui/RepoAvatar'
@@ -14,6 +15,7 @@ export default function RepoPage() {
const [searchParams, setSearchParams] = useSearchParams()
const [showBranches, setShowBranches] = useState(false)
const [showClone, setShowClone] = useState(false)
const [cloneTab, setCloneTab] = useState<'https' | 'ssh'>('https')
const branchRef = useRef<HTMLDivElement>(null)
const cloneRef = useRef<HTMLDivElement>(null)
@@ -23,6 +25,7 @@ export default function RepoPage() {
const { data: repo, isLoading, isError } = useRepo(owner, repoName)
const { data: branches } = useRepoBranches(owner, repoName)
const { data: environments } = useEnvironments(owner, repoName)
const { data: instance } = useInstance()
const { track } = useRecentRepos()
useEffect(() => { if (owner && repoName) track(owner, repoName) }, [owner, repoName])
@@ -42,6 +45,14 @@ export default function RepoPage() {
const branch = ref || repo.defaultBranch
const cloneUrl = `${window.location.origin}/${owner}/${repoName}.git`
const sshHost = instance?.sshHost ?? window.location.hostname
const sshPort = instance?.sshPort ?? '2222'
const sshUrl = sshPort === '22'
? `git@${sshHost}:${owner}/${repoName}.git`
: `ssh://git@${sshHost}:${sshPort}/${owner}/${repoName}.git`
const archiveBase = `/api/v1/repos/${owner}/${repoName}/archive?ref=${encodeURIComponent(branch)}`
function switchBranch(b: string) {
setSearchParams({ ref: b, ...(path ? { path } : {}) })
setShowBranches(false)
@@ -123,17 +134,64 @@ export default function RepoPage() {
</svg>
</button>
{showClone && (
<div className="absolute right-0 top-full mt-1 w-80 bg-[var(--c-surface)] border border-[var(--c-border)] rounded-lg shadow-xl z-50 p-4">
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-2">Clone over HTTP</p>
<div className="flex items-center gap-2 bg-[var(--c-surface-muted)] border border-[var(--c-border)] rounded px-3 py-2">
<code className="text-xs text-[var(--c-text)] flex-1 truncate">{cloneUrl}</code>
<button
onClick={() => navigator.clipboard.writeText(cloneUrl)}
className="text-[10px] text-[var(--c-brand)] hover:underline shrink-0"
>
Copy
</button>
<div className="absolute right-0 top-full mt-1 w-96 bg-[var(--c-surface)] border border-[var(--c-border)] rounded-lg shadow-xl z-50 p-4 space-y-3">
{/* Clone URL tabs */}
<div>
<div className="flex gap-1 mb-2">
{(['https', 'ssh'] as const).map(tab => (
<button
key={tab}
onClick={() => setCloneTab(tab)}
className={`px-2.5 py-1 rounded text-xs font-medium transition-colors ${
cloneTab === tab
? 'bg-[var(--c-brand)] text-white'
: 'text-[var(--c-muted)] hover:text-[var(--c-text)] hover:bg-[var(--c-surface-muted)]'
}`}
>
{tab.toUpperCase()}
</button>
))}
</div>
<div className="flex items-center gap-2 bg-[var(--c-surface-muted)] border border-[var(--c-border)] rounded px-3 py-2">
<code className="text-xs text-[var(--c-text)] flex-1 truncate">
{cloneTab === 'https' ? cloneUrl : sshUrl}
</code>
<button
onClick={() => navigator.clipboard.writeText(cloneTab === 'https' ? cloneUrl : sshUrl)}
className="text-[10px] text-[var(--c-brand)] hover:underline shrink-0"
>
Copy
</button>
</div>
{cloneTab === 'ssh' && (
<p className="text-[10px] text-[var(--c-muted)] mt-1.5">
Requires an SSH key added to your account settings.
</p>
)}
</div>
{/* Archive download */}
<div>
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-1.5">Download</p>
<div className="flex gap-2">
{[
{ label: 'ZIP', format: 'zip' },
{ label: 'tar.gz', format: 'tar.gz' },
{ label: 'Bundle', format: 'bundle' },
].map(({ label, format }) => (
<a
key={format}
href={`${archiveBase}&format=${format}`}
download
className="flex-1 text-center px-2 py-1.5 text-xs font-medium border border-[var(--c-border)] rounded hover:bg-[var(--c-surface-muted)] text-[var(--c-text)] transition-colors"
>
{label}
</a>
))}
</div>
</div>
</div>
)}
</div>
@@ -141,7 +199,7 @@ export default function RepoPage() {
</div>
{repo.isEmpty ? (
<GettingStarted repoName={repoName} branch={branch} cloneUrl={cloneUrl} />
<GettingStarted repoName={repoName} branch={branch} cloneUrl={cloneUrl} sshUrl={sshUrl} />
) : (
<>
{/* Branch selector */}
@@ -231,8 +289,8 @@ function ReadmePreview({ owner, repo, ref }: { owner: string; repo: string; ref:
)
}
function GettingStarted({ repoName, branch, cloneUrl }: {
repoName: string; branch: string; cloneUrl: string
function GettingStarted({ repoName, branch, cloneUrl, sshUrl }: {
repoName: string; branch: string; cloneUrl: string; sshUrl: string
}) {
return (
<div className="border border-[var(--c-border)] rounded-lg overflow-hidden">
@@ -241,9 +299,15 @@ function GettingStarted({ repoName, branch, cloneUrl }: {
<p className="text-xs text-[var(--c-muted)] mt-0.5">Push your first commit to get started.</p>
</div>
<div className="px-5 py-5 space-y-6 text-sm">
<div>
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-2">Clone over HTTP</p>
<CopyBlock value={cloneUrl} />
<div className="grid grid-cols-2 gap-3">
<div>
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-2">Clone over HTTPS</p>
<CopyBlock value={cloneUrl} />
</div>
<div>
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-2">Clone over SSH</p>
<CopyBlock value={sshUrl} />
</div>
</div>
<div>
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-2">or push an existing repository</p>