Add select for git servers when creating site

This commit is contained in:
2026-05-03 19:36:24 +02:00
parent a68368df3c
commit c3f4c95150
6 changed files with 253 additions and 187 deletions
@@ -0,0 +1,20 @@
import { Code2 } from 'lucide-react';
import type { GitServerType } from '../api/types/gitserver';
import { GiteaIcon } from './icons/GiteaIcon';
import { GitHubIcon } from './icons/GitHubIcon';
import { GitLabIcon } from './icons/GitLabIcon';
const GitServerTypeIcon = ({ type, size = 32 }: { type: GitServerType; size?: number }) => {
switch (type) {
case 'github':
return <GitHubIcon size={size} />;
case 'gitlab':
return <GitLabIcon size={size} />;
case 'gitea':
return <GiteaIcon size={size} />;
default:
return <Code2 />;
}
};
export default GitServerTypeIcon;
@@ -10,6 +10,9 @@ export function useDeleteGitServer() {
qc.invalidateQueries({
queryKey: ['gitservers'],
});
qc.invalidateQueries({
queryKey: ['gitServer'],
});
},
});
}
@@ -0,0 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import { getGitServerById } from '../../../api/gitservers.api';
export function useGitServer(id: string) {
return useQuery({
queryKey: ['gitServer', id],
queryFn: () => getGitServerById(id),
});
}
+4 -19
View File
@@ -33,9 +33,7 @@ import {
SelectTrigger,
SelectValue,
} from '../../components/ui/select';
import { GiteaIcon } from '../../components/icons/GiteaIcon';
import { GitLabIcon } from '../../components/icons/GitLabIcon';
import { GitHubIcon } from '../../components/icons/GitHubIcon';
import GitServerTypeIcon from '../../components/GitServerTypeIcon';
const DeleteGitServerDialog = ({ gitServerId }: { gitServerId: string }) => {
const [open, setOpen] = useState(false);
@@ -60,8 +58,8 @@ const DeleteGitServerDialog = ({ gitServerId }: { gitServerId: string }) => {
<DialogHeader>
<DialogTitle>Delete git server</DialogTitle>
<DialogDescription>
Are you sure you want to delete this git server? This action cannot be
undone.
Are you sure you want to delete this git server? This will delete all
associated sites and cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
@@ -81,24 +79,11 @@ const DeleteGitServerDialog = ({ gitServerId }: { gitServerId: string }) => {
);
};
const GitServerTypeIcon = ({ type }: { type: GitServerType }) => {
switch (type) {
case 'github':
return <GitHubIcon size={32} />;
case 'gitlab':
return <GitLabIcon size={32} />;
case 'gitea':
return <GiteaIcon size={32} />;
default:
return <Code2 />;
}
};
const GitServerRow = ({ gitServer }: { gitServer: GitServer }) => {
return (
<div className="flex items-center gap-4 p-4 rounded-lg border">
<div className="text-2xl">
<GitServerTypeIcon type={gitServer.type} />
<GitServerTypeIcon type={gitServer.type} size={32} />
</div>
<div className="flex-1">
<p className="font-medium">{gitServer.name}</p>
+69 -41
View File
@@ -17,47 +17,32 @@ import {
import { Plus, Zap, Copy, Check, AlertTriangle } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useCreateSite } from '../../hooks/api/useCreateSite';
import { useNavigate } from 'react-router';
import { Link, useNavigate } from 'react-router';
import GitServerTypeIcon from '../../components/GitServerTypeIcon';
import { useGitServers } from '../../hooks/api/gitservers/useGitServers';
const GIT_SERVERS = [
{
value: 'github',
label: 'GitHub',
icon: (
<svg viewBox="0 0 24 24" className="w-6 h-6 fill-current">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
</svg>
),
},
{
value: 'gitlab',
label: 'GitLab',
icon: (
<svg viewBox="0 0 24 24" className="w-6 h-6 fill-current">
<path d="M23.955 13.587l-1.342-4.135-2.664-8.189a.455.455 0 00-.867 0L16.418 9.45H7.582L4.918 1.263a.455.455 0 00-.867 0L1.387 9.452.045 13.587a.924.924 0 00.331 1.023L12 23.054l11.624-8.443a.92.92 0 00.331-1.024" />
</svg>
),
},
];
interface ParsedRepo {
gitServer: string;
owner: string;
repository: string;
}
const parseRepoUrl = (url: string) => {
const parseRepoUrl = (url: string): ParsedRepo | null => {
const cleaned = url
.trim()
.replace(/\.git$/, '')
.replace(/\/$/, '');
const patterns = [
/^https?:\/\/(github\.com|gitlab\.com)\/([^/]+)\/([^/]+)/,
/^git@(github\.com|gitlab\.com):([^/]+)\/([^/]+)/,
];
for (const pattern of patterns) {
const match = cleaned.match(pattern);
if (match) {
const host = match[1];
const server = host === 'github.com' ? 'github' : 'gitlab';
return { gitServer: server, owner: match[2], repository: match[3] };
// HTTPS: https://host/owner/repo
const httpsMatch = cleaned.match(/^https?:\/\/([^/]+)\/([^/]+)\/([^/]+)/);
if (httpsMatch) {
return { gitServer: httpsMatch[1], owner: httpsMatch[2], repository: httpsMatch[3] };
}
// SSH: git@host:owner/repo
const sshMatch = cleaned.match(/^git@([^:]+):([^/]+)\/([^/]+)/);
if (sshMatch) {
return { gitServer: sshMatch[1], owner: sshMatch[2], repository: sshMatch[3] };
}
return null;
@@ -80,13 +65,28 @@ const NewSite = () => {
const [copiedToken, setCopiedToken] = useState(false);
const [copiedCurl, setCopiedCurl] = useState(false);
const createNewSite = useCreateSite();
const {
data: gitServers,
isLoading: isLoadingGitServers,
error: gitServersError,
} = useGitServers();
const handleQuickImport = () => {
if (!repoUrl.trim()) return;
const parsed = parseRepoUrl(repoUrl);
if (parsed) {
setGitServer(parsed.gitServer);
const gitServerEntry = gitServers?.find((server) =>
server.baseUrl.includes(parsed.gitServer)
);
if (gitServerEntry) {
setGitServer(gitServerEntry.id);
} else {
setUrlError(
'Git server not found. Please make sure the git server is added to the app.'
);
return;
}
setOwner(parsed.owner);
setRepository(parsed.repository);
setUrlError('');
@@ -209,15 +209,15 @@ const NewSite = () => {
<div className="space-y-2">
<Label>Git Server</Label>
<div className="grid grid-cols-2 gap-3">
{GIT_SERVERS.map((server) => (
{gitServers?.map((server) => (
<button
key={server.value}
key={server.id}
type="button"
onClick={() => setGitServer(server.value)}
onClick={() => setGitServer(server.id)}
className={cn(
'flex items-center gap-3 rounded-lg border-2 p-4 text-left transition-colors',
'hover:bg-accent/15',
gitServer === server.value
gitServer === server.id
? 'border-primary bg-primary/5'
: 'border-border hover:border-accent/35'
)}
@@ -225,16 +225,44 @@ const NewSite = () => {
<div
className={cn(
'shrink-0',
gitServer === server.value
gitServer === server.id
? 'text-primary'
: 'text-muted-foreground'
)}
>
{server.icon}
<GitServerTypeIcon type={server.type} />
</div>
<div>
<span className="font-medium">{server.name}</span>
<span className="block text-sm text-muted-foreground">
{server.baseUrl}
</span>
</div>
<span className="font-medium">{server.label}</span>
</button>
))}
{!isLoadingGitServers &&
!gitServersError &&
gitServers?.length === 0 && (
<div className="col-span-2 text-muted-foreground">
No git servers found. Please add a git server first.{' '}
<Link
to="/gitservers"
className="text-primary hover:underline"
>
Add Git Server
</Link>
</div>
)}
{isLoadingGitServers && (
<div className="col-span-2 flex items-center justify-center p-4">
Loading git servers...
</div>
)}
{gitServersError && (
<div className="col-span-2 flex items-center justify-center p-4 text-destructive">
Error loading git servers
</div>
)}
</div>
</div>
@@ -19,13 +19,23 @@ import {
} from '../../components/ui/table';
import type { Deployment } from '../../api/types/deployments';
import DeploymentStatusBadge from '../../components/DeploymentStatusBadge';
import { useGitServer } from '../../hooks/api/gitservers/useGitServer';
import type { GitServer } from '../../api/types/gitserver';
const repoUrl = (site: Site) => {
const host = site.git_server === 'github' ? 'github.com' : 'gitlab.com';
return `https://${host}/${site.owner}/${site.repository}`;
const repoUrl = (site: Site, gitServer: GitServer | null) => {
if (!gitServer) return 'man';
const baseUrl = gitServer.baseUrl.replace(/\/+$/, ''); // Remove trailing slashes
return `${gitServer.protocol}://${baseUrl}/${site.owner}/${site.repository}`;
};
const OverviewTab = ({ site, deployments }: { site: Site; deployments?: Deployment[] }) => (
const OverviewTab = ({ site, deployments }: { site: Site; deployments?: Deployment[] }) => {
const {
data: gitServer,
isLoading: isGitServerLoading,
error: gitServerError,
} = useGitServer(site.git_server);
return (
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
@@ -34,13 +44,19 @@ const OverviewTab = ({ site, deployments }: { site: Site; deployments?: Deployme
<CardContent className="space-y-3 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Provider</span>
<span className="capitalize">{site.git_server}</span>
{isGitServerLoading ? (
<span>Loading...</span>
) : gitServerError ? (
<span>Error loading git server</span>
) : (
<span>{gitServer?.name}</span>
)}
</div>
<Separator />
<div className="flex justify-between">
<span className="text-muted-foreground">Owner / Repo</span>
<a
href={repoUrl(site)}
href={repoUrl(site, gitServer || null)}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-primary hover:underline"
@@ -136,7 +152,11 @@ const OverviewTab = ({ site, deployments }: { site: Site; deployments?: Deployme
</TableCell>
<TableCell className="text-right text-muted-foreground">
{d.start_time && d.finish_time
? formatDuration(d.start_time, d.finish_time, false)
? formatDuration(
d.start_time,
d.finish_time,
false
)
: '—'}
</TableCell>
<TableCell className="text-right text-muted-foreground">
@@ -152,5 +172,6 @@ const OverviewTab = ({ site, deployments }: { site: Site; deployments?: Deployme
</Card>
</div>
);
};
export default OverviewTab;