Add frontend #1
@@ -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),
|
||||
});
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
@@ -151,6 +171,7 @@ const OverviewTab = ({ site, deployments }: { site: Site; deployments?: Deployme
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default OverviewTab;
|
||||
|
||||
Reference in New Issue
Block a user