mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-10-15 11:51:52 +02:00
Adds a Source dropdown to the configuration card (GitHub, GitLab beta, Gitea/Forgejo) backed by a source provider interface with three adapters. Discovery, mirroring, recovery and cleanup go through the provider; repositories record their source (migration 0015); clone credentials are built per host and a repository from another host is refused at the mirror sites. Issues, pull requests, releases and star lists stay GitHub only. The destination card gets a Gitea/Forgejo dropdown, and both hosts lock once repositories exist, with an explicit confirmation to change. Closes #375. Helps #371. Claude-Session: https://claude.ai/code/session_01Tp9pmi65a8k5jLMQFLf4JX
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
import { useState } from "react";
|
|
import { Lock, LockOpen } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
|
|
interface HostLockNoticeProps {
|
|
/** One line shown next to the lock icon, e.g. "12 repositories imported from GitHub". */
|
|
summary: string;
|
|
/** Dialog title, e.g. "Change the source?". */
|
|
title: string;
|
|
/** What happens to existing repositories when the host changes. */
|
|
consequences: string[];
|
|
/** Label of the confirm button and of the inline button. */
|
|
changeLabel: string;
|
|
unlocked: boolean;
|
|
onUnlock: () => void;
|
|
}
|
|
|
|
/**
|
|
* Inline note for a locked source or destination, with the confirmation
|
|
* dialog that unlocks the fields for a deliberate change.
|
|
*/
|
|
export function HostLockNotice({
|
|
summary,
|
|
title,
|
|
consequences,
|
|
changeLabel,
|
|
unlocked,
|
|
onUnlock,
|
|
}: HostLockNoticeProps) {
|
|
const [open, setOpen] = useState(false);
|
|
|
|
if (unlocked) {
|
|
return (
|
|
<div className="flex items-center gap-2 rounded-lg border border-dashed border-amber-500/40 bg-amber-500/5 px-3 py-2 text-[11px] text-muted-foreground">
|
|
<LockOpen className="h-3.5 w-3.5 shrink-0 text-amber-500" />
|
|
<span>Unlocked for this edit. Existing repositories stay tied to the previous host.</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="flex items-center justify-between gap-3 rounded-lg border border-border bg-muted/40 px-3 py-2">
|
|
<div className="flex items-center gap-2 text-[11px] text-muted-foreground">
|
|
<Lock className="h-3.5 w-3.5 shrink-0" />
|
|
<span>{summary}</span>
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-7 px-2 text-xs"
|
|
onClick={() => setOpen(true)}
|
|
>
|
|
{changeLabel}
|
|
</Button>
|
|
</div>
|
|
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogContent className="sm:max-w-[440px]">
|
|
<DialogHeader>
|
|
<DialogTitle>{title}</DialogTitle>
|
|
<DialogDescription>{summary}.</DialogDescription>
|
|
</DialogHeader>
|
|
<ul className="list-disc space-y-1.5 pl-5 text-sm text-muted-foreground">
|
|
{consequences.map((line) => (
|
|
<li key={line}>{line}</li>
|
|
))}
|
|
</ul>
|
|
<DialogFooter>
|
|
<Button type="button" variant="outline" onClick={() => setOpen(false)}>
|
|
Keep it
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
variant="destructive"
|
|
onClick={() => {
|
|
setOpen(false);
|
|
onUnlock();
|
|
}}
|
|
>
|
|
{changeLabel}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|