mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-08 20:46:44 +03:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bba3d3521 | ||
|
|
be63555e5c | ||
|
|
32a906369f | ||
|
|
064474fd13 | ||
|
|
2ac933b599 | ||
|
|
403fe08bae | ||
|
|
23c7ff7349 | ||
|
|
3169af44cb | ||
|
|
c1d93dbbc6 | ||
|
|
047719cde9 | ||
|
|
13d4b03541 | ||
|
|
f07ae220b0 | ||
|
|
01647445f2 | ||
|
|
13cbf86309 | ||
|
|
792096d209 | ||
|
|
e94de5c9ca |
23
CHANGELOG.md
23
CHANGELOG.md
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.15.0] - 2025-06-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Enhanced OrganizationConfiguration component with improved layout and metadata options
|
||||||
|
- New GitHubMirrorSettings component with better organization and flexibility
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
- Enhanced configuration interface layout and spacing across multiple components
|
||||||
|
- Streamlined OrganizationStrategy component with cleaner imports and better organization
|
||||||
|
- Improved responsive layout for larger screens in configuration forms
|
||||||
|
- Better icon usage and clarity in configuration components
|
||||||
|
- Enhanced tooltip descriptions and component organization
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed mirror to single organization functionality
|
||||||
|
- Resolved organization strategy layout issues
|
||||||
|
- Cleaned up unused imports across multiple components
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
- Simplified component structures by removing unused imports and dependencies
|
||||||
|
- Enhanced layout flexibility in GitHubConfigForm and GiteaConfigForm components
|
||||||
|
- Improved component organization and code clarity
|
||||||
|
|
||||||
## [2.14.0] - 2025-06-17
|
## [2.14.0] - 2025-06-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "gitea-mirror",
|
"name": "gitea-mirror",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.14.0",
|
"version": "2.15.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"bun": ">=1.2.9"
|
"bun": ">=1.2.9"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -598,7 +598,7 @@ export function ConfigTabs() {
|
|||||||
{/* Content section - Grid layout */}
|
{/* Content section - Grid layout */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* GitHub & Gitea connections - Side by side */}
|
{/* GitHub & Gitea connections - Side by side */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:items-stretch">
|
||||||
<GitHubConfigForm
|
<GitHubConfigForm
|
||||||
config={config.githubConfig}
|
config={config.githubConfig}
|
||||||
setConfig={update =>
|
setConfig={update =>
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { githubApi } from "@/lib/api";
|
import { githubApi } from "@/lib/api";
|
||||||
import type { GitHubConfig, MirrorOptions, AdvancedOptions } from "@/types/config";
|
import type { GitHubConfig, MirrorOptions, AdvancedOptions } from "@/types/config";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { Checkbox } from "../ui/checkbox";
|
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { AlertTriangle } from "lucide-react";
|
|
||||||
import { Alert, AlertDescription } from "../ui/alert";
|
|
||||||
import { Info } from "lucide-react";
|
import { Info } from "lucide-react";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
|
||||||
import { GitHubMirrorSettings } from "./GitHubMirrorSettings";
|
import { GitHubMirrorSettings } from "./GitHubMirrorSettings";
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
|
||||||
interface GitHubConfigFormProps {
|
interface GitHubConfigFormProps {
|
||||||
config: GitHubConfig;
|
config: GitHubConfig;
|
||||||
@@ -87,7 +87,7 @@ export function GitHubConfigForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full self-start">
|
<Card className="w-full h-full flex flex-col">
|
||||||
<CardHeader className="flex flex-row items-center justify-between gap-4">
|
<CardHeader className="flex flex-row items-center justify-between gap-4">
|
||||||
<CardTitle className="text-lg font-semibold">
|
<CardTitle className="text-lg font-semibold">
|
||||||
GitHub Configuration
|
GitHub Configuration
|
||||||
@@ -102,7 +102,7 @@ export function GitHubConfigForm({
|
|||||||
</Button>
|
</Button>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="flex flex-col gap-y-6">
|
<CardContent className="flex flex-col gap-y-6 flex-1">
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="github-username"
|
htmlFor="github-username"
|
||||||
@@ -123,12 +123,52 @@ export function GitHubConfigForm({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-1.5">
|
||||||
<label
|
<label
|
||||||
htmlFor="github-token"
|
htmlFor="github-token"
|
||||||
className="block text-sm font-medium mb-1.5"
|
className="block text-sm font-medium"
|
||||||
>
|
>
|
||||||
GitHub Token
|
GitHub Token
|
||||||
</label>
|
</label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="p-0.5 hover:bg-muted rounded-sm transition-colors"
|
||||||
|
>
|
||||||
|
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent side="right" align="start" className="w-80">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-medium text-sm">GitHub Token Requirements</h4>
|
||||||
|
<div className="text-sm space-y-2">
|
||||||
|
<p>
|
||||||
|
You need to create a <span className="font-medium">Classic GitHub PAT Token</span> with the following scopes:
|
||||||
|
</p>
|
||||||
|
<ul className="ml-4 space-y-1 list-disc">
|
||||||
|
<li><code className="text-xs bg-muted px-1 py-0.5 rounded">repo</code></li>
|
||||||
|
<li><code className="text-xs bg-muted px-1 py-0.5 rounded">admin:org</code></li>
|
||||||
|
</ul>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
The organization access is required for mirroring organization repositories.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Generate tokens at{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/settings/tokens"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline font-medium"
|
||||||
|
>
|
||||||
|
github.com/settings/tokens
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
<Input
|
<Input
|
||||||
id="github-token"
|
id="github-token"
|
||||||
name="token"
|
name="token"
|
||||||
@@ -136,7 +176,7 @@ export function GitHubConfigForm({
|
|||||||
value={config.token}
|
value={config.token}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="bg-background"
|
className="bg-background"
|
||||||
placeholder="Your GitHub personal access token"
|
placeholder="Your GitHub token (classic) with repo and admin:org scopes"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
Required for private repositories, organizations, and starred
|
Required for private repositories, organizations, and starred
|
||||||
@@ -165,43 +205,6 @@ export function GitHubConfigForm({
|
|||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CardFooter className="flex-col items-start">
|
|
||||||
<Alert variant="note" className="w-full">
|
|
||||||
<AlertTriangle className="h-4 w-4 text-blue-600 dark:text-blue-400 mr-2" />
|
|
||||||
<AlertDescription className="text-sm">
|
|
||||||
<div className="font-semibold mb-1">Note:</div>
|
|
||||||
<div className="mb-1">
|
|
||||||
You need to create a{" "}
|
|
||||||
<span className="font-semibold">Classic GitHub PAT Token</span>{" "}
|
|
||||||
with following scopes:
|
|
||||||
</div>
|
|
||||||
<ul className="ml-4 mb-1 list-disc">
|
|
||||||
<li>
|
|
||||||
<code>repo</code>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<code>admin:org</code>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div className="mb-1">
|
|
||||||
The organization access is required for mirroring organization
|
|
||||||
repositories.
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
You can generate tokens at{" "}
|
|
||||||
<a
|
|
||||||
href="https://github.com/settings/tokens"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline font-medium hover:text-blue-900 dark:hover:text-blue-200"
|
|
||||||
>
|
|
||||||
github.com/settings/tokens
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</div>
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,22 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
import {
|
import {
|
||||||
Info,
|
Info,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
Star,
|
Star,
|
||||||
Building2,
|
|
||||||
Lock,
|
Lock,
|
||||||
Archive,
|
Archive,
|
||||||
GitPullRequest,
|
GitPullRequest,
|
||||||
@@ -22,7 +27,9 @@ import {
|
|||||||
MessageSquare,
|
MessageSquare,
|
||||||
Target,
|
Target,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
GitFork
|
GitFork,
|
||||||
|
ChevronDown,
|
||||||
|
Funnel
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { GitHubConfig, MirrorOptions, AdvancedOptions } from "@/types/config";
|
import type { GitHubConfig, MirrorOptions, AdvancedOptions } from "@/types/config";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -100,11 +107,12 @@ export function GitHubMirrorSettings({
|
|||||||
Include private repositories
|
Include private repositories
|
||||||
</Label>
|
</Label>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Mirror your private repositories (requires appropriate token permissions)
|
Mirror your private repositories
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="starred-repos"
|
id="starred-repos"
|
||||||
@@ -124,6 +132,47 @@ export function GitHubMirrorSettings({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Lightweight starred repos option - inline to prevent layout shift */}
|
||||||
|
<div className={cn(
|
||||||
|
"flex items-start space-x-3 transition-all duration-200",
|
||||||
|
githubConfig.mirrorStarred
|
||||||
|
? "opacity-100 lg:pl-6 lg:border-l lg:border-border"
|
||||||
|
: "opacity-0 pointer-events-none"
|
||||||
|
)}>
|
||||||
|
<Checkbox
|
||||||
|
id="skip-starred-metadata"
|
||||||
|
checked={advancedOptions.skipStarredIssues}
|
||||||
|
onCheckedChange={(checked) => handleAdvancedChange('skipStarredIssues', !!checked)}
|
||||||
|
disabled={!githubConfig.mirrorStarred}
|
||||||
|
/>
|
||||||
|
<div className="space-y-0.5 flex-1">
|
||||||
|
<Label
|
||||||
|
htmlFor="skip-starred-metadata"
|
||||||
|
className="text-sm font-normal cursor-pointer flex items-center gap-2"
|
||||||
|
>
|
||||||
|
Lightweight mirroring
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Info className="h-3 w-3 text-muted-foreground" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right" className="max-w-xs">
|
||||||
|
<p className="text-xs">
|
||||||
|
When enabled, starred repositories will only mirror code,
|
||||||
|
skipping issues, PRs, and other metadata to reduce storage
|
||||||
|
and improve performance.
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Only code, skip issues and metadata
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -172,7 +221,7 @@ export function GitHubMirrorSettings({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="mirror-metadata"
|
id="mirror-metadata"
|
||||||
@@ -193,92 +242,142 @@ export function GitHubMirrorSettings({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Metadata sub-options */}
|
{/* Metadata multi-select - inline to prevent layout shift */}
|
||||||
{mirrorOptions.mirrorMetadata && (
|
<div className={cn(
|
||||||
<div className="ml-7 space-y-2 p-3 bg-muted/30 dark:bg-muted/10 rounded-md">
|
"flex items-center justify-end transition-opacity duration-200",
|
||||||
<div className="grid grid-cols-2 gap-3">
|
mirrorOptions.mirrorMetadata ? "opacity-100" : "opacity-0 pointer-events-none"
|
||||||
<div className="flex items-center space-x-2">
|
)}>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={!mirrorOptions.mirrorMetadata}
|
||||||
|
className="h-8 text-xs font-normal min-w-[140px] justify-between"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{(() => {
|
||||||
|
const selectedCount = Object.values(mirrorOptions.metadataComponents).filter(Boolean).length;
|
||||||
|
const totalCount = Object.keys(mirrorOptions.metadataComponents).length;
|
||||||
|
if (selectedCount === 0) return "No items selected";
|
||||||
|
if (selectedCount === totalCount) return "All items selected";
|
||||||
|
return `${selectedCount} of ${totalCount} selected`;
|
||||||
|
})()}
|
||||||
|
</span>
|
||||||
|
<ChevronDown className="ml-2 h-3 w-3 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent align="end" className="w-64">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-sm font-medium">Metadata to mirror</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-auto px-2 py-1 text-xs font-normal text-primary hover:text-primary/80"
|
||||||
|
onClick={() => {
|
||||||
|
const allSelected = Object.values(mirrorOptions.metadataComponents).every(Boolean);
|
||||||
|
const newValue = !allSelected;
|
||||||
|
|
||||||
|
// Update all metadata components at once
|
||||||
|
onMirrorOptionsChange({
|
||||||
|
...mirrorOptions,
|
||||||
|
metadataComponents: {
|
||||||
|
issues: newValue,
|
||||||
|
pullRequests: newValue,
|
||||||
|
labels: newValue,
|
||||||
|
milestones: newValue,
|
||||||
|
wiki: newValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.values(mirrorOptions.metadataComponents).every(Boolean) ? 'Deselect all' : 'Select all'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="my-2" />
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="metadata-issues"
|
id="metadata-issues-popup"
|
||||||
checked={mirrorOptions.metadataComponents.issues}
|
checked={mirrorOptions.metadataComponents.issues}
|
||||||
onCheckedChange={(checked) => handleMetadataComponentChange('issues', !!checked)}
|
onCheckedChange={(checked) => handleMetadataComponentChange('issues', !!checked)}
|
||||||
disabled={!isMetadataEnabled}
|
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor="metadata-issues"
|
htmlFor="metadata-issues-popup"
|
||||||
className="text-sm font-normal cursor-pointer flex items-center gap-1.5"
|
className="text-sm font-normal cursor-pointer flex items-center gap-2 flex-1"
|
||||||
>
|
>
|
||||||
<MessageSquare className="h-3 w-3" />
|
<MessageSquare className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
Issues
|
Issues
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="metadata-prs"
|
id="metadata-prs-popup"
|
||||||
checked={mirrorOptions.metadataComponents.pullRequests}
|
checked={mirrorOptions.metadataComponents.pullRequests}
|
||||||
onCheckedChange={(checked) => handleMetadataComponentChange('pullRequests', !!checked)}
|
onCheckedChange={(checked) => handleMetadataComponentChange('pullRequests', !!checked)}
|
||||||
disabled={!isMetadataEnabled}
|
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor="metadata-prs"
|
htmlFor="metadata-prs-popup"
|
||||||
className="text-sm font-normal cursor-pointer flex items-center gap-1.5"
|
className="text-sm font-normal cursor-pointer flex items-center gap-2 flex-1"
|
||||||
>
|
>
|
||||||
<GitPullRequest className="h-3 w-3" />
|
<GitPullRequest className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
Pull Requests
|
Pull Requests
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="metadata-labels"
|
id="metadata-labels-popup"
|
||||||
checked={mirrorOptions.metadataComponents.labels}
|
checked={mirrorOptions.metadataComponents.labels}
|
||||||
onCheckedChange={(checked) => handleMetadataComponentChange('labels', !!checked)}
|
onCheckedChange={(checked) => handleMetadataComponentChange('labels', !!checked)}
|
||||||
disabled={!isMetadataEnabled}
|
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor="metadata-labels"
|
htmlFor="metadata-labels-popup"
|
||||||
className="text-sm font-normal cursor-pointer flex items-center gap-1.5"
|
className="text-sm font-normal cursor-pointer flex items-center gap-2 flex-1"
|
||||||
>
|
>
|
||||||
<Tag className="h-3 w-3" />
|
<Tag className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
Labels
|
Labels
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="metadata-milestones"
|
id="metadata-milestones-popup"
|
||||||
checked={mirrorOptions.metadataComponents.milestones}
|
checked={mirrorOptions.metadataComponents.milestones}
|
||||||
onCheckedChange={(checked) => handleMetadataComponentChange('milestones', !!checked)}
|
onCheckedChange={(checked) => handleMetadataComponentChange('milestones', !!checked)}
|
||||||
disabled={!isMetadataEnabled}
|
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor="metadata-milestones"
|
htmlFor="metadata-milestones-popup"
|
||||||
className="text-sm font-normal cursor-pointer flex items-center gap-1.5"
|
className="text-sm font-normal cursor-pointer flex items-center gap-2 flex-1"
|
||||||
>
|
>
|
||||||
<Target className="h-3 w-3" />
|
<Target className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
Milestones
|
Milestones
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="metadata-wiki"
|
id="metadata-wiki-popup"
|
||||||
checked={mirrorOptions.metadataComponents.wiki}
|
checked={mirrorOptions.metadataComponents.wiki}
|
||||||
onCheckedChange={(checked) => handleMetadataComponentChange('wiki', !!checked)}
|
onCheckedChange={(checked) => handleMetadataComponentChange('wiki', !!checked)}
|
||||||
disabled={!isMetadataEnabled}
|
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor="metadata-wiki"
|
htmlFor="metadata-wiki-popup"
|
||||||
className="text-sm font-normal cursor-pointer flex items-center gap-1.5"
|
className="text-sm font-normal cursor-pointer flex items-center gap-2 flex-1"
|
||||||
>
|
>
|
||||||
<BookOpen className="h-3 w-3" />
|
<BookOpen className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
Wiki
|
Wiki
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -289,7 +388,7 @@ export function GitHubMirrorSettings({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
|
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
|
||||||
<Building2 className="h-4 w-4" />
|
<Funnel className="h-4 w-4" />
|
||||||
Filtering & Behavior
|
Filtering & Behavior
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-xs text-muted-foreground mb-4">
|
<p className="text-xs text-muted-foreground mb-4">
|
||||||
@@ -317,42 +416,6 @@ export function GitHubMirrorSettings({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{githubConfig.mirrorStarred && (
|
|
||||||
<div className="flex items-start space-x-3">
|
|
||||||
<Checkbox
|
|
||||||
id="skip-starred-metadata"
|
|
||||||
checked={advancedOptions.skipStarredIssues}
|
|
||||||
onCheckedChange={(checked) => handleAdvancedChange('skipStarredIssues', !!checked)}
|
|
||||||
/>
|
|
||||||
<div className="space-y-0.5 flex-1">
|
|
||||||
<Label
|
|
||||||
htmlFor="skip-starred-metadata"
|
|
||||||
className="text-sm font-normal cursor-pointer flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<Star className="h-3.5 w-3.5" />
|
|
||||||
Lightweight starred repository mirroring
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<Info className="h-3 w-3 text-muted-foreground" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="right" className="max-w-xs">
|
|
||||||
<p className="text-xs">
|
|
||||||
When enabled, starred repositories will only mirror code,
|
|
||||||
skipping issues, PRs, and other metadata to reduce storage
|
|
||||||
and improve performance.
|
|
||||||
</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</Label>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Only mirror code from starred repos, skip issues and metadata
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,24 +3,14 @@ import { Button } from "@/components/ui/button";
|
|||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "../ui/select";
|
|
||||||
import { Checkbox } from "../ui/checkbox";
|
|
||||||
import { giteaApi } from "@/lib/api";
|
import { giteaApi } from "@/lib/api";
|
||||||
import type { GiteaConfig, GiteaOrgVisibility, MirrorStrategy } from "@/types/config";
|
import type { GiteaConfig, MirrorStrategy } from "@/types/config";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Info } from "lucide-react";
|
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
|
||||||
import { OrganizationStrategy } from "./OrganizationStrategy";
|
import { OrganizationStrategy } from "./OrganizationStrategy";
|
||||||
|
import { OrganizationConfiguration } from "./OrganizationConfiguration";
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
|
|
||||||
interface GiteaConfigFormProps {
|
interface GiteaConfigFormProps {
|
||||||
@@ -133,7 +123,7 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full self-start">
|
<Card className="w-full h-full flex flex-col">
|
||||||
<CardHeader className="flex flex-row items-center justify-between gap-4">
|
<CardHeader className="flex flex-row items-center justify-between gap-4">
|
||||||
<CardTitle className="text-lg font-semibold">
|
<CardTitle className="text-lg font-semibold">
|
||||||
Gitea Configuration
|
Gitea Configuration
|
||||||
@@ -148,7 +138,7 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
|
|||||||
</Button>
|
</Button>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="flex flex-col gap-y-6">
|
<CardContent className="flex flex-col gap-y-6 flex-1">
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="gitea-username"
|
htmlFor="gitea-username"
|
||||||
@@ -210,13 +200,24 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator className="my-2" />
|
<Separator />
|
||||||
|
|
||||||
<OrganizationStrategy
|
<OrganizationStrategy
|
||||||
strategy={mirrorStrategy}
|
strategy={mirrorStrategy}
|
||||||
destinationOrg={config.organization}
|
destinationOrg={config.organization}
|
||||||
starredReposOrg={config.starredReposOrg}
|
starredReposOrg={config.starredReposOrg}
|
||||||
onStrategyChange={setMirrorStrategy}
|
onStrategyChange={setMirrorStrategy}
|
||||||
|
githubUsername={githubUsername}
|
||||||
|
giteaUsername={config.username}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<OrganizationConfiguration
|
||||||
|
strategy={mirrorStrategy}
|
||||||
|
destinationOrg={config.organization}
|
||||||
|
starredReposOrg={config.starredReposOrg}
|
||||||
|
visibility={config.visibility}
|
||||||
onDestinationOrgChange={(org) => {
|
onDestinationOrgChange={(org) => {
|
||||||
const newConfig = { ...config, organization: org };
|
const newConfig = { ...config, organization: org };
|
||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
@@ -227,54 +228,13 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
|
|||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
if (onAutoSave) onAutoSave(newConfig);
|
if (onAutoSave) onAutoSave(newConfig);
|
||||||
}}
|
}}
|
||||||
githubUsername={githubUsername}
|
onVisibilityChange={(visibility) => {
|
||||||
giteaUsername={config.username}
|
const newConfig = { ...config, visibility };
|
||||||
|
setConfig(newConfig);
|
||||||
|
if (onAutoSave) onAutoSave(newConfig);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Separator className="my-2" />
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="visibility"
|
|
||||||
className="block text-sm font-medium mb-1.5"
|
|
||||||
>
|
|
||||||
Organization Visibility
|
|
||||||
</label>
|
|
||||||
<Select
|
|
||||||
name="visibility"
|
|
||||||
value={config.visibility}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
handleChange({
|
|
||||||
target: { name: "visibility", value },
|
|
||||||
} as React.ChangeEvent<HTMLInputElement>)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full border border-input dark:bg-background dark:hover:bg-background">
|
|
||||||
<SelectValue placeholder="Select visibility" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent className="bg-background text-foreground border border-input shadow-sm">
|
|
||||||
{(["public", "private", "limited"] as GiteaOrgVisibility[]).map(
|
|
||||||
(option) => (
|
|
||||||
<SelectItem
|
|
||||||
key={option}
|
|
||||||
value={option}
|
|
||||||
className="cursor-pointer text-sm px-3 py-2 hover:bg-accent focus:bg-accent focus:text-accent-foreground"
|
|
||||||
>
|
|
||||||
{option.charAt(0).toUpperCase() + option.slice(1)}
|
|
||||||
</SelectItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
|
||||||
Visibility for newly created organizations
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CardFooter className="">
|
|
||||||
{/* Footer content can be added here if needed */}
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
163
src/components/config/OrganizationConfiguration.tsx
Normal file
163
src/components/config/OrganizationConfiguration.tsx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Star, Globe, Lock, Shield, Info, MonitorCog } from "lucide-react";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import type { MirrorStrategy, GiteaOrgVisibility } from "@/types/config";
|
||||||
|
|
||||||
|
interface OrganizationConfigurationProps {
|
||||||
|
strategy: MirrorStrategy;
|
||||||
|
destinationOrg?: string;
|
||||||
|
starredReposOrg?: string;
|
||||||
|
visibility: GiteaOrgVisibility;
|
||||||
|
onDestinationOrgChange: (org: string) => void;
|
||||||
|
onStarredReposOrgChange: (org: string) => void;
|
||||||
|
onVisibilityChange: (visibility: GiteaOrgVisibility) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibilityOptions = [
|
||||||
|
{ value: "public" as GiteaOrgVisibility, label: "Public", icon: Globe, description: "Visible to everyone" },
|
||||||
|
{ value: "private" as GiteaOrgVisibility, label: "Private", icon: Lock, description: "Visible to members only" },
|
||||||
|
{ value: "limited" as GiteaOrgVisibility, label: "Limited", icon: Shield, description: "Visible to logged-in users" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OrganizationConfiguration: React.FC<OrganizationConfigurationProps> = ({
|
||||||
|
strategy,
|
||||||
|
destinationOrg,
|
||||||
|
starredReposOrg,
|
||||||
|
visibility,
|
||||||
|
onDestinationOrgChange,
|
||||||
|
onStarredReposOrgChange,
|
||||||
|
onVisibilityChange,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
|
||||||
|
<MonitorCog className="h-4 w-4" />
|
||||||
|
Organization Configuration
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* First row - Organization inputs with consistent layout */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{/* Left column - always shows starred repos org */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="starredReposOrg" className="text-sm font-normal flex items-center gap-2">
|
||||||
|
<Star className="h-3.5 w-3.5" />
|
||||||
|
Starred Repositories Organization
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Starred repositories will be organized separately in this organization</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="starredReposOrg"
|
||||||
|
value={starredReposOrg || ""}
|
||||||
|
onChange={(e) => onStarredReposOrgChange(e.target.value)}
|
||||||
|
placeholder="starred"
|
||||||
|
className=""
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Keep starred repos organized separately
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right column - shows destination org for single-org, empty div for others */}
|
||||||
|
{strategy === "single-org" ? (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="destinationOrg" className="text-sm font-normal flex items-center gap-2">
|
||||||
|
Destination Organization
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>All repositories will be mirrored to this organization</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="destinationOrg"
|
||||||
|
value={destinationOrg || ""}
|
||||||
|
onChange={(e) => onDestinationOrgChange(e.target.value)}
|
||||||
|
placeholder="github-mirrors"
|
||||||
|
className=""
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Organization for consolidated repositories
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="hidden md:block" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Second row - Organization Visibility (always shown) */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-sm font-normal flex items-center gap-2">
|
||||||
|
Organization Visibility
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Default visibility for newly created organizations</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</Label>
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
{visibilityOptions.map((option) => {
|
||||||
|
const Icon = option.icon;
|
||||||
|
const isSelected = visibility === option.value;
|
||||||
|
return (
|
||||||
|
<TooltipProvider key={option.value}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onVisibilityChange(option.value)}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-between px-3 py-2 rounded-md text-sm transition-all",
|
||||||
|
"border group",
|
||||||
|
isSelected
|
||||||
|
? "bg-accent border-accent-foreground/20"
|
||||||
|
: "bg-background hover:bg-accent/50 border-input"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Icon className="h-3.5 w-3.5" />
|
||||||
|
<span>{option.label}</span>
|
||||||
|
</div>
|
||||||
|
<Info className="h-3 w-3 text-muted-foreground opacity-50 group-hover:opacity-100 transition-opacity" />
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p className="text-xs">{option.description}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
@@ -1,22 +1,13 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Info, GitBranch, FolderTree, Star, Building2, User, Building } from "lucide-react";
|
||||||
import { Info, GitBranch, FolderTree, Package, Star, Building2, User, ChevronDown, ChevronUp } from "lucide-react";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Popover,
|
||||||
TooltipContent,
|
PopoverContent,
|
||||||
TooltipProvider,
|
PopoverTrigger,
|
||||||
TooltipTrigger,
|
} from "@/components/ui/popover";
|
||||||
} from "@/components/ui/tooltip";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from "@/components/ui/collapsible";
|
|
||||||
|
|
||||||
export type MirrorStrategy = "preserve" | "single-org" | "flat-user";
|
export type MirrorStrategy = "preserve" | "single-org" | "flat-user";
|
||||||
|
|
||||||
@@ -25,226 +16,201 @@ interface OrganizationStrategyProps {
|
|||||||
destinationOrg?: string;
|
destinationOrg?: string;
|
||||||
starredReposOrg?: string;
|
starredReposOrg?: string;
|
||||||
onStrategyChange: (strategy: MirrorStrategy) => void;
|
onStrategyChange: (strategy: MirrorStrategy) => void;
|
||||||
onDestinationOrgChange: (org: string) => void;
|
|
||||||
onStarredReposOrgChange: (org: string) => void;
|
|
||||||
githubUsername?: string;
|
githubUsername?: string;
|
||||||
giteaUsername?: string;
|
giteaUsername?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const strategyConfig = {
|
const strategyConfig = {
|
||||||
preserve: {
|
preserve: {
|
||||||
title: "Mirror GitHub Structure",
|
title: "Preserve Structure",
|
||||||
icon: FolderTree,
|
icon: FolderTree,
|
||||||
description: "Keep the same organization structure as GitHub",
|
description: "Keep the exact same organization structure as GitHub",
|
||||||
color: "text-blue-600 dark:text-blue-400",
|
color: "text-blue-600 dark:text-blue-400",
|
||||||
bgColor: "bg-blue-50 dark:bg-blue-950/20",
|
bgColor: "bg-blue-50 dark:bg-blue-950/20",
|
||||||
borderColor: "border-blue-200 dark:border-blue-900",
|
borderColor: "border-blue-200 dark:border-blue-900",
|
||||||
details: [
|
repoColors: {
|
||||||
"Personal repos → Your Gitea username",
|
bg: "bg-blue-50 dark:bg-blue-950/30",
|
||||||
"Org repos → Same org name in Gitea",
|
icon: "text-blue-600 dark:text-blue-400"
|
||||||
"Team structure preserved"
|
}
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"single-org": {
|
"single-org": {
|
||||||
title: "Consolidate to One Org",
|
title: "Single Organization",
|
||||||
icon: Building2,
|
icon: Building2,
|
||||||
description: "Mirror all repositories into a single organization",
|
description: "Consolidate all repositories into one Gitea organization",
|
||||||
color: "text-purple-600 dark:text-purple-400",
|
color: "text-purple-600 dark:text-purple-400",
|
||||||
bgColor: "bg-purple-50 dark:bg-purple-950/20",
|
bgColor: "bg-purple-50 dark:bg-purple-950/20",
|
||||||
borderColor: "border-purple-200 dark:border-purple-900",
|
borderColor: "border-purple-200 dark:border-purple-900",
|
||||||
details: [
|
repoColors: {
|
||||||
"All repos in one place",
|
bg: "bg-purple-50 dark:bg-purple-950/30",
|
||||||
"Simplified management",
|
icon: "text-purple-600 dark:text-purple-400"
|
||||||
"Custom organization name"
|
}
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"flat-user": {
|
"flat-user": {
|
||||||
title: "Flat User Structure",
|
title: "User Repositories",
|
||||||
icon: User,
|
icon: User,
|
||||||
description: "Mirror all repositories under your user account",
|
description: "Place all repositories directly under your user account",
|
||||||
color: "text-green-600 dark:text-green-400",
|
color: "text-green-600 dark:text-green-400",
|
||||||
bgColor: "bg-green-50 dark:bg-green-950/20",
|
bgColor: "bg-green-50 dark:bg-green-950/20",
|
||||||
borderColor: "border-green-200 dark:border-green-900",
|
borderColor: "border-green-200 dark:border-green-900",
|
||||||
details: [
|
repoColors: {
|
||||||
"All repos under your username",
|
bg: "bg-green-50 dark:bg-green-950/30",
|
||||||
"No organizations needed",
|
icon: "text-green-600 dark:text-green-400"
|
||||||
"Simple and personal"
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const StrategyVisualizer: React.FC<{
|
const MappingPreview: React.FC<{
|
||||||
strategy: MirrorStrategy;
|
strategy: MirrorStrategy;
|
||||||
|
config: typeof strategyConfig.preserve;
|
||||||
destinationOrg?: string;
|
destinationOrg?: string;
|
||||||
starredReposOrg?: string;
|
starredReposOrg?: string;
|
||||||
githubUsername?: string;
|
githubUsername?: string;
|
||||||
giteaUsername?: string;
|
giteaUsername?: string;
|
||||||
}> = ({ strategy, destinationOrg, starredReposOrg, githubUsername, giteaUsername }) => {
|
}> = ({ strategy, config, destinationOrg, starredReposOrg, githubUsername, giteaUsername }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const displayGithubUsername = githubUsername || "<username>";
|
const displayGithubUsername = githubUsername || "<username>";
|
||||||
const displayGiteaUsername = giteaUsername || "<username>";
|
const displayGiteaUsername = giteaUsername || "<username>";
|
||||||
const isGithubPlaceholder = !githubUsername;
|
const isGithubPlaceholder = !githubUsername;
|
||||||
const isGiteaPlaceholder = !giteaUsername;
|
const isGiteaPlaceholder = !giteaUsername;
|
||||||
const renderPreserveStructure = () => (
|
|
||||||
<div className="flex items-center justify-between gap-8 p-6">
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="text-sm font-medium text-muted-foreground mb-3">GitHub</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
|
||||||
<User className="h-4 w-4" />
|
|
||||||
<span className={cn("text-sm", isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
|
||||||
<Building2 className="h-4 w-4" />
|
|
||||||
<span className="text-sm">my-org/team-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
|
||||||
<Star className="h-4 w-4" />
|
|
||||||
<span className="text-sm">awesome/starred-repo</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
<GitBranch className="h-5 w-5 text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="text-sm font-medium text-muted-foreground mb-3">Gitea</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-blue-50 dark:bg-blue-950/30 rounded">
|
|
||||||
<User className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
|
||||||
<span className={cn("text-sm", isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/my-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-blue-50 dark:bg-blue-950/30 rounded">
|
|
||||||
<Building2 className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
|
||||||
<span className="text-sm">my-org/team-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-blue-50 dark:bg-blue-950/30 rounded">
|
|
||||||
<Building2 className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
|
||||||
<span className="text-sm">{starredReposOrg || "starred"}/starred-repo</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderSingleOrg = () => (
|
|
||||||
<div className="flex items-center justify-between gap-8 p-6">
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="text-sm font-medium text-muted-foreground mb-3">GitHub</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
|
||||||
<User className="h-4 w-4" />
|
|
||||||
<span className={cn("text-sm", isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
|
||||||
<Building2 className="h-4 w-4" />
|
|
||||||
<span className="text-sm">my-org/team-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
|
||||||
<Star className="h-4 w-4" />
|
|
||||||
<span className="text-sm">awesome/starred-repo</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
<GitBranch className="h-5 w-5 text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="text-sm font-medium text-muted-foreground mb-3">Gitea</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-purple-50 dark:bg-purple-950/30 rounded">
|
|
||||||
<Building2 className="h-4 w-4 text-purple-600 dark:text-purple-400" />
|
|
||||||
<span className="text-sm">{destinationOrg || "github-mirrors"}/my-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-purple-50 dark:bg-purple-950/30 rounded">
|
|
||||||
<Building2 className="h-4 w-4 text-purple-600 dark:text-purple-400" />
|
|
||||||
<span className="text-sm">{destinationOrg || "github-mirrors"}/team-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-purple-50 dark:bg-purple-950/30 rounded">
|
|
||||||
<Building2 className="h-4 w-4 text-purple-600 dark:text-purple-400" />
|
|
||||||
<span className="text-sm">{starredReposOrg || "starred"}/starred-repo</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderFlatUser = () => (
|
|
||||||
<div className="flex items-center justify-between gap-8 p-6">
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="text-sm font-medium text-muted-foreground mb-3">GitHub</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
|
||||||
<User className="h-4 w-4" />
|
|
||||||
<span className={cn("text-sm", isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
|
||||||
<Building2 className="h-4 w-4" />
|
|
||||||
<span className="text-sm">my-org/team-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
|
||||||
<Star className="h-4 w-4" />
|
|
||||||
<span className="text-sm">awesome/starred-repo</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
<GitBranch className="h-5 w-5 text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="text-sm font-medium text-muted-foreground mb-3">Gitea</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-green-50 dark:bg-green-950/30 rounded">
|
|
||||||
<User className="h-4 w-4 text-green-600 dark:text-green-400" />
|
|
||||||
<span className={cn("text-sm", isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/my-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-green-50 dark:bg-green-950/30 rounded">
|
|
||||||
<User className="h-4 w-4 text-green-600 dark:text-green-400" />
|
|
||||||
<span className={cn("text-sm", isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/team-repo</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 p-2 bg-green-50 dark:bg-green-950/30 rounded">
|
|
||||||
<Building2 className="h-4 w-4 text-green-600 dark:text-green-400" />
|
|
||||||
<span className="text-sm">{starredReposOrg || "starred"}/starred-repo</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
if (strategy === "preserve") {
|
||||||
return (
|
return (
|
||||||
<div className="mt-4">
|
<div className="flex items-center justify-between gap-6">
|
||||||
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
<div className="flex-1">
|
||||||
<Card className="overflow-hidden">
|
<div className="text-xs font-medium text-muted-foreground mb-2">GitHub</div>
|
||||||
<CollapsibleTrigger className="w-full">
|
<div className="space-y-1.5">
|
||||||
<div className="bg-muted/50 p-3 border-b hover:bg-muted/70 transition-colors cursor-pointer">
|
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
|
||||||
<h4 className="text-sm font-medium flex items-center justify-between">
|
<User className="h-3 w-3" />
|
||||||
<span className="flex items-center gap-2">
|
<span className={cn(isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
|
||||||
<Package className="h-4 w-4" />
|
</div>
|
||||||
Repository Mapping Preview
|
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
|
||||||
</span>
|
<Building2 className="h-3 w-3" />
|
||||||
{isOpen ? (
|
<span>my-org/team-repo</span>
|
||||||
<ChevronUp className="h-4 w-4 text-muted-foreground" />
|
</div>
|
||||||
) : (
|
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
|
||||||
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
<Star className="h-3 w-3" />
|
||||||
)}
|
<span>awesome/starred-repo</span>
|
||||||
</h4>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-xs font-medium text-muted-foreground mb-2">Gitea</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
|
<User className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
|
<span className={cn(isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/my-repo</span>
|
||||||
|
</div>
|
||||||
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
|
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
|
<span>my-org/team-repo</span>
|
||||||
|
</div>
|
||||||
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
|
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
|
<span>{starredReposOrg || "starred"}/starred-repo</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent>
|
|
||||||
{strategy === "preserve" && renderPreserveStructure()}
|
|
||||||
{strategy === "single-org" && renderSingleOrg()}
|
|
||||||
{strategy === "flat-user" && renderFlatUser()}
|
|
||||||
</CollapsibleContent>
|
|
||||||
</Card>
|
|
||||||
</Collapsible>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strategy === "single-org") {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between gap-6">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-xs font-medium text-muted-foreground mb-2">GitHub</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
|
||||||
|
<User className="h-3 w-3" />
|
||||||
|
<span className={cn(isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
|
||||||
|
<Building2 className="h-3 w-3" />
|
||||||
|
<span>my-org/team-repo</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
|
||||||
|
<Star className="h-3 w-3" />
|
||||||
|
<span>awesome/starred-repo</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-xs font-medium text-muted-foreground mb-2">Gitea</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
|
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
|
<span>{destinationOrg || "github-mirrors"}/my-repo</span>
|
||||||
|
</div>
|
||||||
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
|
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
|
<span>{destinationOrg || "github-mirrors"}/team-repo</span>
|
||||||
|
</div>
|
||||||
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
|
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
|
<span>{starredReposOrg || "starred"}/starred-repo</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strategy === "flat-user") {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between gap-6">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-xs font-medium text-muted-foreground mb-2">GitHub</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
|
||||||
|
<User className="h-3 w-3" />
|
||||||
|
<span className={cn(isGithubPlaceholder && "text-muted-foreground italic")}>{displayGithubUsername}/my-repo</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
|
||||||
|
<Building2 className="h-3 w-3" />
|
||||||
|
<span>my-org/team-repo</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 p-1.5 bg-gray-50 dark:bg-gray-800 rounded text-xs">
|
||||||
|
<Star className="h-3 w-3" />
|
||||||
|
<span>awesome/starred-repo</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-xs font-medium text-muted-foreground mb-2">Gitea</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
|
<User className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
|
<span className={cn(isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/my-repo</span>
|
||||||
|
</div>
|
||||||
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
|
<User className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
|
<span className={cn(isGiteaPlaceholder && "text-muted-foreground italic")}>{displayGiteaUsername}/team-repo</span>
|
||||||
|
</div>
|
||||||
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
|
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
|
<span>{starredReposOrg || "starred"}/starred-repo</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
|
export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
|
||||||
@@ -252,23 +218,23 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
|
|||||||
destinationOrg,
|
destinationOrg,
|
||||||
starredReposOrg,
|
starredReposOrg,
|
||||||
onStrategyChange,
|
onStrategyChange,
|
||||||
onDestinationOrgChange,
|
|
||||||
onStarredReposOrgChange,
|
|
||||||
githubUsername,
|
githubUsername,
|
||||||
giteaUsername,
|
giteaUsername,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-1">Organization Strategy</h3>
|
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
|
||||||
<p className="text-sm text-muted-foreground">
|
<Building className="h-4 w-4" />
|
||||||
|
Organization Strategy
|
||||||
|
</h4>
|
||||||
|
<p className="text-xs text-muted-foreground mb-4">
|
||||||
Choose how your repositories will be organized in Gitea
|
Choose how your repositories will be organized in Gitea
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RadioGroup value={strategy} onValueChange={onStrategyChange}>
|
<RadioGroup value={strategy} onValueChange={onStrategyChange}>
|
||||||
<div className="grid gap-4">
|
<div className="grid grid-cols-1 2xl:grid-cols-2 gap-4">
|
||||||
{(Object.entries(strategyConfig) as [MirrorStrategy, typeof strategyConfig.preserve][]).map(([key, config]) => {
|
{(Object.entries(strategyConfig) as [MirrorStrategy, typeof strategyConfig.preserve][]).map(([key, config]) => {
|
||||||
const isSelected = strategy === key;
|
const isSelected = strategy === key;
|
||||||
const Icon = config.icon;
|
const Icon = config.icon;
|
||||||
@@ -283,12 +249,11 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
|
|||||||
!isSelected && "border-muted"
|
!isSelected && "border-muted"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="p-4">
|
<div className="p-3">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-center gap-3">
|
||||||
<RadioGroupItem
|
<RadioGroupItem
|
||||||
value={key}
|
value={key}
|
||||||
id={key}
|
id={key}
|
||||||
className="mt-1"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
@@ -296,36 +261,44 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
|
|||||||
isSelected ? config.bgColor : "bg-muted dark:bg-muted/50"
|
isSelected ? config.bgColor : "bg-muted dark:bg-muted/50"
|
||||||
)}>
|
)}>
|
||||||
<Icon className={cn(
|
<Icon className={cn(
|
||||||
"h-5 w-5",
|
"h-4 w-4",
|
||||||
isSelected ? config.color : "text-muted-foreground dark:text-muted-foreground/70"
|
isSelected ? config.color : "text-muted-foreground dark:text-muted-foreground/70"
|
||||||
)} />
|
)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2">
|
||||||
<h4 className="font-medium">{config.title}</h4>
|
<h4 className="font-medium text-sm">{config.title}</h4>
|
||||||
{isSelected && (
|
|
||||||
<Badge variant="secondary" className="text-xs">
|
|
||||||
Selected
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground mb-3">
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||||||
{config.description}
|
{config.description}
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<Popover>
|
||||||
{config.details.map((detail, idx) => (
|
<PopoverTrigger asChild>
|
||||||
<div key={idx} className="flex items-center gap-2">
|
<button
|
||||||
<div className={cn(
|
type="button"
|
||||||
"h-1.5 w-1.5 rounded-full",
|
className="p-1.5 hover:bg-muted rounded-md transition-colors"
|
||||||
isSelected ? config.bgColor : "bg-muted dark:bg-muted/50"
|
onClick={(e) => e.stopPropagation()}
|
||||||
)} />
|
>
|
||||||
<span className="text-xs text-muted-foreground">{detail}</span>
|
<Info className="h-4 w-4 text-muted-foreground" />
|
||||||
</div>
|
</button>
|
||||||
))}
|
</PopoverTrigger>
|
||||||
</div>
|
<PopoverContent side="left" align="center" className="w-[500px]">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="font-medium text-sm">Repository Mapping Preview</h4>
|
||||||
|
<MappingPreview
|
||||||
|
strategy={key}
|
||||||
|
config={config}
|
||||||
|
destinationOrg={destinationOrg}
|
||||||
|
starredReposOrg={starredReposOrg}
|
||||||
|
githubUsername={githubUsername}
|
||||||
|
giteaUsername={giteaUsername}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -335,76 +308,6 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
||||||
{strategy === "single-org" && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<Card className="p-4 border-purple-200 dark:border-purple-900 bg-purple-50/50 dark:bg-purple-950/20">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="destinationOrg" className="flex items-center gap-2">
|
|
||||||
Destination Organization
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>All repositories will be mirrored to this organization</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="destinationOrg"
|
|
||||||
value={destinationOrg || ""}
|
|
||||||
onChange={(e) => onDestinationOrgChange(e.target.value)}
|
|
||||||
placeholder="github-mirrors"
|
|
||||||
className="mt-1.5"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Card className="p-4 border-orange-200 dark:border-orange-900 bg-orange-50/50 dark:bg-orange-950/20">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="starredReposOrg" className="flex items-center gap-2">
|
|
||||||
<Star className="h-4 w-4 text-orange-600 dark:text-orange-400" />
|
|
||||||
Starred Repositories Organization
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>Starred repositories will be organized separately in this organization</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="starredReposOrg"
|
|
||||||
value={starredReposOrg || ""}
|
|
||||||
onChange={(e) => onStarredReposOrgChange(e.target.value)}
|
|
||||||
placeholder="starred"
|
|
||||||
className="mt-1.5"
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-muted-foreground dark:text-muted-foreground/70 mt-1">
|
|
||||||
Keep starred repos organized separately from your own repositories
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<StrategyVisualizer
|
|
||||||
strategy={strategy}
|
|
||||||
destinationOrg={destinationOrg}
|
|
||||||
starredReposOrg={starredReposOrg}
|
|
||||||
githubUsername={githubUsername}
|
|
||||||
giteaUsername={giteaUsername}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -19,7 +19,11 @@ function TooltipProvider({
|
|||||||
function Tooltip({
|
function Tooltip({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||||
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||||
|
</TooltipProvider>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function TooltipTrigger({
|
function TooltipTrigger({
|
||||||
@@ -40,7 +44,7 @@ function TooltipContent({
|
|||||||
data-slot="tooltip-content"
|
data-slot="tooltip-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-[var(--radix-tooltip-content-transform-origin)] rounded-md px-3 py-1.5 text-xs text-balance",
|
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,17 +1,4 @@
|
|||||||
import { defineCollection, z } from 'astro:content';
|
import { defineCollection, z } from 'astro:content';
|
||||||
|
|
||||||
// Define a schema for the documentation collection
|
// Export empty collections since docs have been moved
|
||||||
const docsCollection = defineCollection({
|
export const collections = {};
|
||||||
type: 'content',
|
|
||||||
schema: z.object({
|
|
||||||
title: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
order: z.number().optional(),
|
|
||||||
updatedDate: z.date().optional(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Export the collections
|
|
||||||
export const collections = {
|
|
||||||
'docs': docsCollection,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -416,7 +416,7 @@ export async function getOrCreateGiteaOrg({
|
|||||||
username: orgName,
|
username: orgName,
|
||||||
full_name: `${orgName} Org`,
|
full_name: `${orgName} Org`,
|
||||||
description: `Mirrored organization from GitHub ${orgName}`,
|
description: `Mirrored organization from GitHub ${orgName}`,
|
||||||
visibility: "public",
|
visibility: config.giteaConfig?.visibility || "public",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -105,8 +105,17 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
console.log(`Repository ${repo.name} will be mirrored to owner: ${owner}`);
|
console.log(`Repository ${repo.name} will be mirrored to owner: ${owner}`);
|
||||||
|
|
||||||
// Check if owner is different from the user (means it's going to an org)
|
// For single-org and starred repos strategies, or when mirroring to an org,
|
||||||
if (owner !== config.giteaConfig?.username) {
|
// always use the org mirroring function to ensure proper organization handling
|
||||||
|
const mirrorStrategy = config.giteaConfig?.mirrorStrategy ||
|
||||||
|
(config.githubConfig?.preserveOrgStructure ? "preserve" : "flat-user");
|
||||||
|
|
||||||
|
const shouldUseOrgMirror =
|
||||||
|
owner !== config.giteaConfig?.username || // Different owner means org
|
||||||
|
mirrorStrategy === "single-org" || // Single-org strategy always uses org
|
||||||
|
repoData.isStarred; // Starred repos always go to org
|
||||||
|
|
||||||
|
if (shouldUseOrgMirror) {
|
||||||
await mirrorGitHubOrgRepoToGiteaOrg({
|
await mirrorGitHubOrgRepoToGiteaOrg({
|
||||||
config,
|
config,
|
||||||
octokit,
|
octokit,
|
||||||
|
|||||||
@@ -137,8 +137,17 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
console.log(`Importing repo: ${repo.name} to owner: ${owner}`);
|
console.log(`Importing repo: ${repo.name} to owner: ${owner}`);
|
||||||
|
|
||||||
// Check if owner is different from the user (means it's going to an org)
|
// For single-org and starred repos strategies, or when mirroring to an org,
|
||||||
if (owner !== config.giteaConfig?.username) {
|
// always use the org mirroring function to ensure proper organization handling
|
||||||
|
const mirrorStrategy = config.giteaConfig?.mirrorStrategy ||
|
||||||
|
(config.githubConfig?.preserveOrgStructure ? "preserve" : "flat-user");
|
||||||
|
|
||||||
|
const shouldUseOrgMirror =
|
||||||
|
owner !== config.giteaConfig?.username || // Different owner means org
|
||||||
|
mirrorStrategy === "single-org" || // Single-org strategy always uses org
|
||||||
|
repoData.isStarred; // Starred repos always go to org
|
||||||
|
|
||||||
|
if (shouldUseOrgMirror) {
|
||||||
await mirrorGitHubOrgRepoToGiteaOrg({
|
await mirrorGitHubOrgRepoToGiteaOrg({
|
||||||
config,
|
config,
|
||||||
octokit,
|
octokit,
|
||||||
|
|||||||
Reference in New Issue
Block a user