mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-08 12:36:44 +03:00
Tabs Config
This commit is contained in:
2
bun.lock
2
bun.lock
@@ -18,7 +18,7 @@
|
|||||||
"@radix-ui/react-select": "^2.2.4",
|
"@radix-ui/react-select": "^2.2.4",
|
||||||
"@radix-ui/react-slot": "^1.2.2",
|
"@radix-ui/react-slot": "^1.2.2",
|
||||||
"@radix-ui/react-switch": "^1.2.5",
|
"@radix-ui/react-switch": "^1.2.5",
|
||||||
"@radix-ui/react-tabs": "^1.1.11",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"@radix-ui/react-tooltip": "^1.2.6",
|
"@radix-ui/react-tooltip": "^1.2.6",
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
"@tanstack/react-virtual": "^3.13.8",
|
"@tanstack/react-virtual": "^3.13.8",
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
"check-db": "bun scripts/manage-db.ts check",
|
"check-db": "bun scripts/manage-db.ts check",
|
||||||
"fix-db": "bun scripts/manage-db.ts fix",
|
"fix-db": "bun scripts/manage-db.ts fix",
|
||||||
"reset-users": "bun scripts/manage-db.ts reset-users",
|
"reset-users": "bun scripts/manage-db.ts reset-users",
|
||||||
|
|
||||||
"startup-recovery": "bun scripts/startup-recovery.ts",
|
"startup-recovery": "bun scripts/startup-recovery.ts",
|
||||||
"startup-recovery-force": "bun scripts/startup-recovery.ts --force",
|
"startup-recovery-force": "bun scripts/startup-recovery.ts --force",
|
||||||
"test-recovery": "bun scripts/test-recovery.ts",
|
"test-recovery": "bun scripts/test-recovery.ts",
|
||||||
@@ -46,7 +45,7 @@
|
|||||||
"@radix-ui/react-select": "^2.2.4",
|
"@radix-ui/react-select": "^2.2.4",
|
||||||
"@radix-ui/react-slot": "^1.2.2",
|
"@radix-ui/react-slot": "^1.2.2",
|
||||||
"@radix-ui/react-switch": "^1.2.5",
|
"@radix-ui/react-switch": "^1.2.5",
|
||||||
"@radix-ui/react-tabs": "^1.1.11",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"@radix-ui/react-tooltip": "^1.2.6",
|
"@radix-ui/react-tooltip": "^1.2.6",
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
"@tanstack/react-virtual": "^3.13.8",
|
"@tanstack/react-virtual": "^3.13.8",
|
||||||
|
|||||||
90
src/components/config/AdvancedOptionsForm.tsx
Normal file
90
src/components/config/AdvancedOptionsForm.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Checkbox } from "../ui/checkbox";
|
||||||
|
import type { AdvancedOptions } from "@/types/config";
|
||||||
|
import { RefreshCw } from "lucide-react";
|
||||||
|
|
||||||
|
interface AdvancedOptionsFormProps {
|
||||||
|
config: AdvancedOptions;
|
||||||
|
setConfig: React.Dispatch<React.SetStateAction<AdvancedOptions>>;
|
||||||
|
onAutoSave?: (config: AdvancedOptions) => Promise<void>;
|
||||||
|
isAutoSaving?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdvancedOptionsForm({
|
||||||
|
config,
|
||||||
|
setConfig,
|
||||||
|
onAutoSave,
|
||||||
|
isAutoSaving = false,
|
||||||
|
}: AdvancedOptionsFormProps) {
|
||||||
|
const handleChange = (name: string, checked: boolean) => {
|
||||||
|
const newConfig = {
|
||||||
|
...config,
|
||||||
|
[name]: checked,
|
||||||
|
};
|
||||||
|
|
||||||
|
setConfig(newConfig);
|
||||||
|
|
||||||
|
// Auto-save
|
||||||
|
if (onAutoSave) {
|
||||||
|
onAutoSave(newConfig);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg font-semibold flex items-center justify-between">
|
||||||
|
Advanced Options
|
||||||
|
{isAutoSaving && (
|
||||||
|
<div className="flex items-center text-sm text-muted-foreground">
|
||||||
|
<RefreshCw className="h-3 w-3 animate-spin mr-1" />
|
||||||
|
<span className="text-xs">Auto-saving...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="skip-forks"
|
||||||
|
checked={config.skipForks}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleChange("skipForks", Boolean(checked))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="skip-forks"
|
||||||
|
className="ml-2 text-sm select-none"
|
||||||
|
>
|
||||||
|
Skip forks
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground ml-6">
|
||||||
|
Don't mirror repositories that are forks of other repositories
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="skip-starred-issues"
|
||||||
|
checked={config.skipStarredIssues}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleChange("skipStarredIssues", Boolean(checked))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="skip-starred-issues"
|
||||||
|
className="ml-2 text-sm select-none"
|
||||||
|
>
|
||||||
|
Don't fetch issues for starred repos
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground ml-6">
|
||||||
|
Skip mirroring issues and pull requests for starred repositories
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ import { GitHubConfigForm } from './GitHubConfigForm';
|
|||||||
import { GiteaConfigForm } from './GiteaConfigForm';
|
import { GiteaConfigForm } from './GiteaConfigForm';
|
||||||
import { ScheduleConfigForm } from './ScheduleConfigForm';
|
import { ScheduleConfigForm } from './ScheduleConfigForm';
|
||||||
import { DatabaseCleanupConfigForm } from './DatabaseCleanupConfigForm';
|
import { DatabaseCleanupConfigForm } from './DatabaseCleanupConfigForm';
|
||||||
|
import { MirrorOptionsForm } from './MirrorOptionsForm';
|
||||||
|
import { AdvancedOptionsForm } from './AdvancedOptionsForm';
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
||||||
import type {
|
import type {
|
||||||
ConfigApiResponse,
|
ConfigApiResponse,
|
||||||
GiteaConfig,
|
GiteaConfig,
|
||||||
@@ -11,6 +14,8 @@ import type {
|
|||||||
SaveConfigApiResponse,
|
SaveConfigApiResponse,
|
||||||
ScheduleConfig,
|
ScheduleConfig,
|
||||||
DatabaseCleanupConfig,
|
DatabaseCleanupConfig,
|
||||||
|
MirrorOptions,
|
||||||
|
AdvancedOptions,
|
||||||
} from '@/types/config';
|
} from '@/types/config';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
@@ -25,6 +30,8 @@ type ConfigState = {
|
|||||||
giteaConfig: GiteaConfig;
|
giteaConfig: GiteaConfig;
|
||||||
scheduleConfig: ScheduleConfig;
|
scheduleConfig: ScheduleConfig;
|
||||||
cleanupConfig: DatabaseCleanupConfig;
|
cleanupConfig: DatabaseCleanupConfig;
|
||||||
|
mirrorOptions: MirrorOptions;
|
||||||
|
advancedOptions: AdvancedOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ConfigTabs() {
|
export function ConfigTabs() {
|
||||||
@@ -32,13 +39,8 @@ export function ConfigTabs() {
|
|||||||
githubConfig: {
|
githubConfig: {
|
||||||
username: '',
|
username: '',
|
||||||
token: '',
|
token: '',
|
||||||
skipForks: false,
|
|
||||||
privateRepositories: false,
|
privateRepositories: false,
|
||||||
mirrorIssues: false,
|
|
||||||
mirrorWiki: false,
|
|
||||||
mirrorStarred: false,
|
mirrorStarred: false,
|
||||||
preserveOrgStructure: false,
|
|
||||||
skipStarredIssues: false,
|
|
||||||
},
|
},
|
||||||
giteaConfig: {
|
giteaConfig: {
|
||||||
url: '',
|
url: '',
|
||||||
@@ -47,6 +49,7 @@ export function ConfigTabs() {
|
|||||||
organization: 'github-mirrors',
|
organization: 'github-mirrors',
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
starredReposOrg: 'github',
|
starredReposOrg: 'github',
|
||||||
|
preserveOrgStructure: false,
|
||||||
},
|
},
|
||||||
scheduleConfig: {
|
scheduleConfig: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -56,6 +59,21 @@ export function ConfigTabs() {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
retentionDays: 604800, // 7 days in seconds
|
retentionDays: 604800, // 7 days in seconds
|
||||||
},
|
},
|
||||||
|
mirrorOptions: {
|
||||||
|
mirrorReleases: false,
|
||||||
|
mirrorMetadata: false,
|
||||||
|
metadataComponents: {
|
||||||
|
issues: false,
|
||||||
|
pullRequests: false,
|
||||||
|
labels: false,
|
||||||
|
milestones: false,
|
||||||
|
wiki: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
advancedOptions: {
|
||||||
|
skipForks: false,
|
||||||
|
skipStarredIssues: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
@@ -65,10 +83,14 @@ export function ConfigTabs() {
|
|||||||
const [isAutoSavingCleanup, setIsAutoSavingCleanup] = useState<boolean>(false);
|
const [isAutoSavingCleanup, setIsAutoSavingCleanup] = useState<boolean>(false);
|
||||||
const [isAutoSavingGitHub, setIsAutoSavingGitHub] = useState<boolean>(false);
|
const [isAutoSavingGitHub, setIsAutoSavingGitHub] = useState<boolean>(false);
|
||||||
const [isAutoSavingGitea, setIsAutoSavingGitea] = useState<boolean>(false);
|
const [isAutoSavingGitea, setIsAutoSavingGitea] = useState<boolean>(false);
|
||||||
|
const [isAutoSavingMirrorOptions, setIsAutoSavingMirrorOptions] = useState<boolean>(false);
|
||||||
|
const [isAutoSavingAdvancedOptions, setIsAutoSavingAdvancedOptions] = useState<boolean>(false);
|
||||||
const autoSaveScheduleTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const autoSaveScheduleTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const autoSaveCleanupTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const autoSaveCleanupTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const autoSaveGitHubTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const autoSaveGitHubTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const autoSaveGiteaTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const autoSaveGiteaTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const autoSaveMirrorOptionsTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const autoSaveAdvancedOptionsTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const isConfigFormValid = (): boolean => {
|
const isConfigFormValid = (): boolean => {
|
||||||
const { githubConfig, giteaConfig } = config;
|
const { githubConfig, giteaConfig } = config;
|
||||||
@@ -133,6 +155,8 @@ export function ConfigTabs() {
|
|||||||
giteaConfig: config.giteaConfig,
|
giteaConfig: config.giteaConfig,
|
||||||
scheduleConfig: scheduleConfig,
|
scheduleConfig: scheduleConfig,
|
||||||
cleanupConfig: config.cleanupConfig,
|
cleanupConfig: config.cleanupConfig,
|
||||||
|
mirrorOptions: config.mirrorOptions,
|
||||||
|
advancedOptions: config.advancedOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -197,6 +221,8 @@ export function ConfigTabs() {
|
|||||||
giteaConfig: config.giteaConfig,
|
giteaConfig: config.giteaConfig,
|
||||||
scheduleConfig: config.scheduleConfig,
|
scheduleConfig: config.scheduleConfig,
|
||||||
cleanupConfig: cleanupConfig,
|
cleanupConfig: cleanupConfig,
|
||||||
|
mirrorOptions: config.mirrorOptions,
|
||||||
|
advancedOptions: config.advancedOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -260,6 +286,8 @@ export function ConfigTabs() {
|
|||||||
giteaConfig: config.giteaConfig,
|
giteaConfig: config.giteaConfig,
|
||||||
scheduleConfig: config.scheduleConfig,
|
scheduleConfig: config.scheduleConfig,
|
||||||
cleanupConfig: config.cleanupConfig,
|
cleanupConfig: config.cleanupConfig,
|
||||||
|
mirrorOptions: config.mirrorOptions,
|
||||||
|
advancedOptions: config.advancedOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -307,6 +335,8 @@ export function ConfigTabs() {
|
|||||||
giteaConfig: giteaConfig,
|
giteaConfig: giteaConfig,
|
||||||
scheduleConfig: config.scheduleConfig,
|
scheduleConfig: config.scheduleConfig,
|
||||||
cleanupConfig: config.cleanupConfig,
|
cleanupConfig: config.cleanupConfig,
|
||||||
|
mirrorOptions: config.mirrorOptions,
|
||||||
|
advancedOptions: config.advancedOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -335,6 +365,104 @@ export function ConfigTabs() {
|
|||||||
}, 500); // 500ms debounce
|
}, 500); // 500ms debounce
|
||||||
}, [user?.id, config.githubConfig, config.scheduleConfig, config.cleanupConfig]);
|
}, [user?.id, config.githubConfig, config.scheduleConfig, config.cleanupConfig]);
|
||||||
|
|
||||||
|
// Auto-save function specifically for mirror options changes
|
||||||
|
const autoSaveMirrorOptions = useCallback(async (mirrorOptions: MirrorOptions) => {
|
||||||
|
if (!user?.id) return;
|
||||||
|
|
||||||
|
// Clear any existing timeout
|
||||||
|
if (autoSaveMirrorOptionsTimeoutRef.current) {
|
||||||
|
clearTimeout(autoSaveMirrorOptionsTimeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce the auto-save to prevent excessive API calls
|
||||||
|
autoSaveMirrorOptionsTimeoutRef.current = setTimeout(async () => {
|
||||||
|
setIsAutoSavingMirrorOptions(true);
|
||||||
|
|
||||||
|
const reqPayload: SaveConfigApiRequest = {
|
||||||
|
userId: user.id!,
|
||||||
|
githubConfig: config.githubConfig,
|
||||||
|
giteaConfig: config.giteaConfig,
|
||||||
|
scheduleConfig: config.scheduleConfig,
|
||||||
|
cleanupConfig: config.cleanupConfig,
|
||||||
|
mirrorOptions: mirrorOptions,
|
||||||
|
advancedOptions: config.advancedOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(reqPayload),
|
||||||
|
});
|
||||||
|
const result: SaveConfigApiResponse = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Silent success - no toast for auto-save
|
||||||
|
// Invalidate config cache so other components get fresh data
|
||||||
|
invalidateConfigCache();
|
||||||
|
} else {
|
||||||
|
showErrorToast(
|
||||||
|
`Auto-save failed: ${result.message || 'Unknown error'}`,
|
||||||
|
toast
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showErrorToast(error, toast);
|
||||||
|
} finally {
|
||||||
|
setIsAutoSavingMirrorOptions(false);
|
||||||
|
}
|
||||||
|
}, 500); // 500ms debounce
|
||||||
|
}, [user?.id, config.githubConfig, config.giteaConfig, config.scheduleConfig, config.cleanupConfig, config.advancedOptions]);
|
||||||
|
|
||||||
|
// Auto-save function specifically for advanced options changes
|
||||||
|
const autoSaveAdvancedOptions = useCallback(async (advancedOptions: AdvancedOptions) => {
|
||||||
|
if (!user?.id) return;
|
||||||
|
|
||||||
|
// Clear any existing timeout
|
||||||
|
if (autoSaveAdvancedOptionsTimeoutRef.current) {
|
||||||
|
clearTimeout(autoSaveAdvancedOptionsTimeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce the auto-save to prevent excessive API calls
|
||||||
|
autoSaveAdvancedOptionsTimeoutRef.current = setTimeout(async () => {
|
||||||
|
setIsAutoSavingAdvancedOptions(true);
|
||||||
|
|
||||||
|
const reqPayload: SaveConfigApiRequest = {
|
||||||
|
userId: user.id!,
|
||||||
|
githubConfig: config.githubConfig,
|
||||||
|
giteaConfig: config.giteaConfig,
|
||||||
|
scheduleConfig: config.scheduleConfig,
|
||||||
|
cleanupConfig: config.cleanupConfig,
|
||||||
|
mirrorOptions: config.mirrorOptions,
|
||||||
|
advancedOptions: advancedOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(reqPayload),
|
||||||
|
});
|
||||||
|
const result: SaveConfigApiResponse = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Silent success - no toast for auto-save
|
||||||
|
// Invalidate config cache so other components get fresh data
|
||||||
|
invalidateConfigCache();
|
||||||
|
} else {
|
||||||
|
showErrorToast(
|
||||||
|
`Auto-save failed: ${result.message || 'Unknown error'}`,
|
||||||
|
toast
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showErrorToast(error, toast);
|
||||||
|
} finally {
|
||||||
|
setIsAutoSavingAdvancedOptions(false);
|
||||||
|
}
|
||||||
|
}, 500); // 500ms debounce
|
||||||
|
}, [user?.id, config.githubConfig, config.giteaConfig, config.scheduleConfig, config.cleanupConfig, config.mirrorOptions]);
|
||||||
|
|
||||||
// Cleanup timeouts on unmount
|
// Cleanup timeouts on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -350,6 +478,12 @@ export function ConfigTabs() {
|
|||||||
if (autoSaveGiteaTimeoutRef.current) {
|
if (autoSaveGiteaTimeoutRef.current) {
|
||||||
clearTimeout(autoSaveGiteaTimeoutRef.current);
|
clearTimeout(autoSaveGiteaTimeoutRef.current);
|
||||||
}
|
}
|
||||||
|
if (autoSaveMirrorOptionsTimeoutRef.current) {
|
||||||
|
clearTimeout(autoSaveMirrorOptionsTimeoutRef.current);
|
||||||
|
}
|
||||||
|
if (autoSaveAdvancedOptionsTimeoutRef.current) {
|
||||||
|
clearTimeout(autoSaveAdvancedOptionsTimeoutRef.current);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -373,6 +507,10 @@ export function ConfigTabs() {
|
|||||||
response.scheduleConfig || config.scheduleConfig,
|
response.scheduleConfig || config.scheduleConfig,
|
||||||
cleanupConfig:
|
cleanupConfig:
|
||||||
response.cleanupConfig || config.cleanupConfig,
|
response.cleanupConfig || config.cleanupConfig,
|
||||||
|
mirrorOptions:
|
||||||
|
response.mirrorOptions || config.mirrorOptions,
|
||||||
|
advancedOptions:
|
||||||
|
response.advancedOptions || config.advancedOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -496,72 +634,118 @@ export function ConfigTabs() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content section */}
|
{/* Content section */}
|
||||||
<div className="flex flex-col gap-y-4">
|
<Tabs defaultValue="connections" className="w-full">
|
||||||
<div className="flex gap-x-4">
|
<TabsList className="grid w-full grid-cols-4">
|
||||||
<GitHubConfigForm
|
<TabsTrigger value="connections">Connections</TabsTrigger>
|
||||||
config={config.githubConfig}
|
<TabsTrigger value="mirror">Mirror Options</TabsTrigger>
|
||||||
setConfig={update =>
|
<TabsTrigger value="schedule">Schedule & Cleanup</TabsTrigger>
|
||||||
setConfig(prev => ({
|
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||||
...prev,
|
</TabsList>
|
||||||
githubConfig:
|
|
||||||
typeof update === 'function'
|
<TabsContent value="connections" className="mt-6">
|
||||||
? update(prev.githubConfig)
|
<div className="flex gap-x-4">
|
||||||
: update,
|
<GitHubConfigForm
|
||||||
}))
|
config={config.githubConfig}
|
||||||
}
|
|
||||||
onAutoSave={autoSaveGitHubConfig}
|
|
||||||
isAutoSaving={isAutoSavingGitHub}
|
|
||||||
/>
|
|
||||||
<GiteaConfigForm
|
|
||||||
config={config.giteaConfig}
|
|
||||||
setConfig={update =>
|
|
||||||
setConfig(prev => ({
|
|
||||||
...prev,
|
|
||||||
giteaConfig:
|
|
||||||
typeof update === 'function'
|
|
||||||
? update(prev.giteaConfig)
|
|
||||||
: update,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
onAutoSave={autoSaveGiteaConfig}
|
|
||||||
isAutoSaving={isAutoSavingGitea}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-x-4">
|
|
||||||
<div className="w-1/2">
|
|
||||||
<ScheduleConfigForm
|
|
||||||
config={config.scheduleConfig}
|
|
||||||
setConfig={update =>
|
setConfig={update =>
|
||||||
setConfig(prev => ({
|
setConfig(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
scheduleConfig:
|
githubConfig:
|
||||||
typeof update === 'function'
|
typeof update === 'function'
|
||||||
? update(prev.scheduleConfig)
|
? update(prev.githubConfig)
|
||||||
: update,
|
: update,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
onAutoSave={autoSaveScheduleConfig}
|
onAutoSave={autoSaveGitHubConfig}
|
||||||
isAutoSaving={isAutoSavingSchedule}
|
isAutoSaving={isAutoSavingGitHub}
|
||||||
/>
|
/>
|
||||||
</div>
|
<GiteaConfigForm
|
||||||
<div className="w-1/2">
|
config={config.giteaConfig}
|
||||||
<DatabaseCleanupConfigForm
|
|
||||||
config={config.cleanupConfig}
|
|
||||||
setConfig={update =>
|
setConfig={update =>
|
||||||
setConfig(prev => ({
|
setConfig(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
cleanupConfig:
|
giteaConfig:
|
||||||
typeof update === 'function'
|
typeof update === 'function'
|
||||||
? update(prev.cleanupConfig)
|
? update(prev.giteaConfig)
|
||||||
: update,
|
: update,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
onAutoSave={autoSaveCleanupConfig}
|
onAutoSave={autoSaveGiteaConfig}
|
||||||
isAutoSaving={isAutoSavingCleanup}
|
isAutoSaving={isAutoSavingGitea}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</TabsContent>
|
||||||
</div>
|
|
||||||
|
<TabsContent value="mirror" className="mt-6">
|
||||||
|
<MirrorOptionsForm
|
||||||
|
config={config.mirrorOptions}
|
||||||
|
setConfig={update =>
|
||||||
|
setConfig(prev => ({
|
||||||
|
...prev,
|
||||||
|
mirrorOptions:
|
||||||
|
typeof update === 'function'
|
||||||
|
? update(prev.mirrorOptions)
|
||||||
|
: update,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
onAutoSave={autoSaveMirrorOptions}
|
||||||
|
isAutoSaving={isAutoSavingMirrorOptions}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="schedule" className="mt-6">
|
||||||
|
<div className="flex gap-x-4">
|
||||||
|
<div className="w-1/2">
|
||||||
|
<ScheduleConfigForm
|
||||||
|
config={config.scheduleConfig}
|
||||||
|
setConfig={update =>
|
||||||
|
setConfig(prev => ({
|
||||||
|
...prev,
|
||||||
|
scheduleConfig:
|
||||||
|
typeof update === 'function'
|
||||||
|
? update(prev.scheduleConfig)
|
||||||
|
: update,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
onAutoSave={autoSaveScheduleConfig}
|
||||||
|
isAutoSaving={isAutoSavingSchedule}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-1/2">
|
||||||
|
<DatabaseCleanupConfigForm
|
||||||
|
config={config.cleanupConfig}
|
||||||
|
setConfig={update =>
|
||||||
|
setConfig(prev => ({
|
||||||
|
...prev,
|
||||||
|
cleanupConfig:
|
||||||
|
typeof update === 'function'
|
||||||
|
? update(prev.cleanupConfig)
|
||||||
|
: update,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
onAutoSave={autoSaveCleanupConfig}
|
||||||
|
isAutoSaving={isAutoSavingCleanup}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="advanced" className="mt-6">
|
||||||
|
<AdvancedOptionsForm
|
||||||
|
config={config.advancedOptions}
|
||||||
|
setConfig={update =>
|
||||||
|
setConfig(prev => ({
|
||||||
|
...prev,
|
||||||
|
advancedOptions:
|
||||||
|
typeof update === 'function'
|
||||||
|
? update(prev.advancedOptions)
|
||||||
|
: update,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
onAutoSave={autoSaveAdvancedOptions}
|
||||||
|
isAutoSaving={isAutoSavingAdvancedOptions}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,21 +30,6 @@ export function GitHubConfigForm({ config, setConfig, onAutoSave, isAutoSaving }
|
|||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { name, value, type, checked } = e.target;
|
const { name, value, type, checked } = e.target;
|
||||||
|
|
||||||
// Special handling for preserveOrgStructure changes
|
|
||||||
if (
|
|
||||||
name === "preserveOrgStructure" &&
|
|
||||||
config.preserveOrgStructure !== checked
|
|
||||||
) {
|
|
||||||
toast.info(
|
|
||||||
"Changing this setting may affect how repositories are accessed in Gitea. " +
|
|
||||||
"Existing mirrored repositories will still be accessible during sync operations.",
|
|
||||||
{
|
|
||||||
duration: 6000,
|
|
||||||
position: "top-center",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newConfig = {
|
const newConfig = {
|
||||||
...config,
|
...config,
|
||||||
[name]: type === "checkbox" ? checked : value,
|
[name]: type === "checkbox" ? checked : value,
|
||||||
@@ -140,32 +125,10 @@ export function GitHubConfigForm({ config, setConfig, onAutoSave, isAutoSaving }
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-3">
|
<h4 className="text-sm font-medium text-foreground">Repository Access</h4>
|
||||||
<div className="flex items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="skip-forks"
|
|
||||||
name="skipForks"
|
|
||||||
checked={config.skipForks}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleChange({
|
|
||||||
target: {
|
|
||||||
name: "skipForks",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: Boolean(checked),
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
} as React.ChangeEvent<HTMLInputElement>)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="skip-forks"
|
|
||||||
className="ml-2 block text-sm select-none"
|
|
||||||
>
|
|
||||||
Skip Forks
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="private-repositories"
|
id="private-repositories"
|
||||||
@@ -186,7 +149,7 @@ export function GitHubConfigForm({ config, setConfig, onAutoSave, isAutoSaving }
|
|||||||
htmlFor="private-repositories"
|
htmlFor="private-repositories"
|
||||||
className="ml-2 block text-sm select-none"
|
className="ml-2 block text-sm select-none"
|
||||||
>
|
>
|
||||||
Mirror Private Repos
|
Include private repos
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -210,121 +173,7 @@ export function GitHubConfigForm({ config, setConfig, onAutoSave, isAutoSaving }
|
|||||||
htmlFor="mirror-starred"
|
htmlFor="mirror-starred"
|
||||||
className="ml-2 block text-sm select-none"
|
className="ml-2 block text-sm select-none"
|
||||||
>
|
>
|
||||||
Mirror Starred Repos
|
Mirror starred repos
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="mirror-issues"
|
|
||||||
name="mirrorIssues"
|
|
||||||
checked={config.mirrorIssues}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleChange({
|
|
||||||
target: {
|
|
||||||
name: "mirrorIssues",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: Boolean(checked),
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
} as React.ChangeEvent<HTMLInputElement>)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="mirror-issues"
|
|
||||||
className="ml-2 block text-sm select-none"
|
|
||||||
>
|
|
||||||
Mirror Issues
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="mirror-wiki"
|
|
||||||
name="mirrorWiki"
|
|
||||||
checked={config.mirrorWiki}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleChange({
|
|
||||||
target: {
|
|
||||||
name: "mirrorWiki",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: Boolean(checked),
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
} as React.ChangeEvent<HTMLInputElement>)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="mirror-wiki"
|
|
||||||
className="ml-2 block text-sm select-none"
|
|
||||||
>
|
|
||||||
Mirror Wiki
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="preserve-org-structure"
|
|
||||||
name="preserveOrgStructure"
|
|
||||||
checked={config.preserveOrgStructure}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleChange({
|
|
||||||
target: {
|
|
||||||
name: "preserveOrgStructure",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: Boolean(checked),
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
} as React.ChangeEvent<HTMLInputElement>)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="preserve-org-structure"
|
|
||||||
className="ml-2 text-sm select-none flex items-center"
|
|
||||||
>
|
|
||||||
Preserve Org Structure
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<span
|
|
||||||
className="ml-1 cursor-pointer align-middle text-muted-foreground"
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<Info size={16} />
|
|
||||||
</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="right" className="max-w-xs text-xs">
|
|
||||||
When enabled, organization repositories will be mirrored to
|
|
||||||
the same organization structure in Gitea. When disabled, all
|
|
||||||
repositories will be mirrored under your Gitea username.
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="skip-starred-issues"
|
|
||||||
name="skipStarredIssues"
|
|
||||||
checked={config.skipStarredIssues}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleChange({
|
|
||||||
target: {
|
|
||||||
name: "skipStarredIssues",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: Boolean(checked),
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
} as React.ChangeEvent<HTMLInputElement>)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="skip-starred-issues"
|
|
||||||
className="ml-2 block text-sm select-none"
|
|
||||||
>
|
|
||||||
Skip Issues for Starred Repos
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "../ui/select";
|
} from "../ui/select";
|
||||||
|
import { Checkbox } from "../ui/checkbox";
|
||||||
import { giteaApi } from "@/lib/api";
|
import { giteaApi } from "@/lib/api";
|
||||||
import type { GiteaConfig, GiteaOrgVisibility } from "@/types/config";
|
import type { GiteaConfig, GiteaOrgVisibility } from "@/types/config";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { Info } from "lucide-react";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||||
|
|
||||||
interface GiteaConfigFormProps {
|
interface GiteaConfigFormProps {
|
||||||
config: GiteaConfig;
|
config: GiteaConfig;
|
||||||
@@ -31,10 +34,27 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving }:
|
|||||||
const handleChange = (
|
const handleChange = (
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||||
) => {
|
) => {
|
||||||
const { name, value } = e.target;
|
const { name, value, type } = e.target;
|
||||||
|
const checked = type === "checkbox" ? (e.target as HTMLInputElement).checked : undefined;
|
||||||
|
|
||||||
|
// Special handling for preserveOrgStructure changes
|
||||||
|
if (
|
||||||
|
name === "preserveOrgStructure" &&
|
||||||
|
config.preserveOrgStructure !== checked
|
||||||
|
) {
|
||||||
|
toast.info(
|
||||||
|
"Changing this setting may affect how repositories are accessed in Gitea. " +
|
||||||
|
"Existing mirrored repositories will still be accessible during sync operations.",
|
||||||
|
{
|
||||||
|
duration: 6000,
|
||||||
|
position: "top-center",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const newConfig = {
|
const newConfig = {
|
||||||
...config,
|
...config,
|
||||||
[name]: value,
|
[name]: type === "checkbox" ? checked : value,
|
||||||
};
|
};
|
||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
|
|
||||||
@@ -153,7 +173,7 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving }:
|
|||||||
htmlFor="organization"
|
htmlFor="organization"
|
||||||
className="block text-sm font-medium mb-1.5"
|
className="block text-sm font-medium mb-1.5"
|
||||||
>
|
>
|
||||||
Default Organization (Optional)
|
Destination organisation (optional)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="organization"
|
id="organization"
|
||||||
@@ -165,10 +185,51 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving }:
|
|||||||
placeholder="Organization name"
|
placeholder="Organization name"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
If specified, repositories will be mirrored to this organization.
|
Repos are created here if no per-repo org is set.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="preserve-org-structure"
|
||||||
|
name="preserveOrgStructure"
|
||||||
|
checked={config.preserveOrgStructure}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleChange({
|
||||||
|
target: {
|
||||||
|
name: "preserveOrgStructure",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: Boolean(checked),
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
|
} as React.ChangeEvent<HTMLInputElement>)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="preserve-org-structure"
|
||||||
|
className="ml-2 text-sm select-none flex items-center"
|
||||||
|
>
|
||||||
|
Mirror GitHub org / team hierarchy
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span
|
||||||
|
className="ml-1 cursor-pointer align-middle text-muted-foreground"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<Info size={16} />
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right" className="max-w-xs text-xs">
|
||||||
|
Creates nested orgs or prefixes in Gitea so the layout matches GitHub.
|
||||||
|
When enabled, organization repositories will be mirrored to
|
||||||
|
the same organization structure in Gitea. When disabled, all
|
||||||
|
repositories will be mirrored under your Gitea username.
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
@@ -222,7 +283,7 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving }:
|
|||||||
placeholder="github"
|
placeholder="github"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
Organization for starred repositories (default: github)
|
Leave blank to use 'github'.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
226
src/components/config/MirrorOptionsForm.tsx
Normal file
226
src/components/config/MirrorOptionsForm.tsx
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Checkbox } from "../ui/checkbox";
|
||||||
|
import type { MirrorOptions } from "@/types/config";
|
||||||
|
import { RefreshCw, Info } from "lucide-react";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||||
|
|
||||||
|
interface MirrorOptionsFormProps {
|
||||||
|
config: MirrorOptions;
|
||||||
|
setConfig: React.Dispatch<React.SetStateAction<MirrorOptions>>;
|
||||||
|
onAutoSave?: (config: MirrorOptions) => Promise<void>;
|
||||||
|
isAutoSaving?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MirrorOptionsForm({
|
||||||
|
config,
|
||||||
|
setConfig,
|
||||||
|
onAutoSave,
|
||||||
|
isAutoSaving = false,
|
||||||
|
}: MirrorOptionsFormProps) {
|
||||||
|
const handleChange = (name: string, checked: boolean) => {
|
||||||
|
let newConfig = { ...config };
|
||||||
|
|
||||||
|
if (name === "mirrorMetadata") {
|
||||||
|
newConfig.mirrorMetadata = checked;
|
||||||
|
// If disabling metadata, also disable all components
|
||||||
|
if (!checked) {
|
||||||
|
newConfig.metadataComponents = {
|
||||||
|
issues: false,
|
||||||
|
pullRequests: false,
|
||||||
|
labels: false,
|
||||||
|
milestones: false,
|
||||||
|
wiki: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (name.startsWith("metadataComponents.")) {
|
||||||
|
const componentName = name.split(".")[1] as keyof typeof config.metadataComponents;
|
||||||
|
newConfig.metadataComponents = {
|
||||||
|
...config.metadataComponents,
|
||||||
|
[componentName]: checked,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newConfig = {
|
||||||
|
...config,
|
||||||
|
[name]: checked,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(newConfig);
|
||||||
|
|
||||||
|
// Auto-save
|
||||||
|
if (onAutoSave) {
|
||||||
|
onAutoSave(newConfig);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg font-semibold flex items-center justify-between">
|
||||||
|
Mirror Options
|
||||||
|
{isAutoSaving && (
|
||||||
|
<div className="flex items-center text-sm text-muted-foreground">
|
||||||
|
<RefreshCw className="h-3 w-3 animate-spin mr-1" />
|
||||||
|
<span className="text-xs">Auto-saving...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
{/* Repository Content */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="text-sm font-medium text-foreground">Repository Content</h4>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="mirror-releases"
|
||||||
|
checked={config.mirrorReleases}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleChange("mirrorReleases", Boolean(checked))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="mirror-releases"
|
||||||
|
className="ml-2 text-sm select-none flex items-center"
|
||||||
|
>
|
||||||
|
Mirror releases
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="ml-1 cursor-pointer text-muted-foreground">
|
||||||
|
<Info size={14} />
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right" className="max-w-xs text-xs">
|
||||||
|
Include GitHub releases and tags in the mirror
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="mirror-metadata"
|
||||||
|
checked={config.mirrorMetadata}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleChange("mirrorMetadata", Boolean(checked))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="mirror-metadata"
|
||||||
|
className="ml-2 text-sm select-none flex items-center"
|
||||||
|
>
|
||||||
|
Mirror metadata
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="ml-1 cursor-pointer text-muted-foreground">
|
||||||
|
<Info size={14} />
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right" className="max-w-xs text-xs">
|
||||||
|
Include issues, pull requests, labels, milestones, and wiki
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Metadata Components */}
|
||||||
|
{config.mirrorMetadata && (
|
||||||
|
<div className="ml-6 space-y-3 border-l-2 border-muted pl-4">
|
||||||
|
<h5 className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||||
|
Metadata Components
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-2">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="metadata-issues"
|
||||||
|
checked={config.metadataComponents.issues}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleChange("metadataComponents.issues", Boolean(checked))
|
||||||
|
}
|
||||||
|
disabled={!config.mirrorMetadata}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="metadata-issues"
|
||||||
|
className="ml-2 text-sm select-none"
|
||||||
|
>
|
||||||
|
Issues
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="metadata-pullRequests"
|
||||||
|
checked={config.metadataComponents.pullRequests}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleChange("metadataComponents.pullRequests", Boolean(checked))
|
||||||
|
}
|
||||||
|
disabled={!config.mirrorMetadata}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="metadata-pullRequests"
|
||||||
|
className="ml-2 text-sm select-none"
|
||||||
|
>
|
||||||
|
Pull requests
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="metadata-labels"
|
||||||
|
checked={config.metadataComponents.labels}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleChange("metadataComponents.labels", Boolean(checked))
|
||||||
|
}
|
||||||
|
disabled={!config.mirrorMetadata}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="metadata-labels"
|
||||||
|
className="ml-2 text-sm select-none"
|
||||||
|
>
|
||||||
|
Labels
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="metadata-milestones"
|
||||||
|
checked={config.metadataComponents.milestones}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleChange("metadataComponents.milestones", Boolean(checked))
|
||||||
|
}
|
||||||
|
disabled={!config.mirrorMetadata}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="metadata-milestones"
|
||||||
|
className="ml-2 text-sm select-none"
|
||||||
|
>
|
||||||
|
Milestones
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="metadata-wiki"
|
||||||
|
checked={config.metadataComponents.wiki}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleChange("metadataComponents.wiki", Boolean(checked))
|
||||||
|
}
|
||||||
|
disabled={!config.mirrorMetadata}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="metadata-wiki"
|
||||||
|
className="ml-2 text-sm select-none"
|
||||||
|
>
|
||||||
|
Wiki
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ export interface GiteaConfig {
|
|||||||
organization: string;
|
organization: string;
|
||||||
visibility: GiteaOrgVisibility;
|
visibility: GiteaOrgVisibility;
|
||||||
starredReposOrg: string;
|
starredReposOrg: string;
|
||||||
|
preserveOrgStructure: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScheduleConfig {
|
export interface ScheduleConfig {
|
||||||
@@ -28,12 +29,24 @@ export interface DatabaseCleanupConfig {
|
|||||||
export interface GitHubConfig {
|
export interface GitHubConfig {
|
||||||
username: string;
|
username: string;
|
||||||
token: string;
|
token: string;
|
||||||
skipForks: boolean;
|
|
||||||
privateRepositories: boolean;
|
privateRepositories: boolean;
|
||||||
mirrorIssues: boolean;
|
|
||||||
mirrorWiki: boolean;
|
|
||||||
mirrorStarred: boolean;
|
mirrorStarred: boolean;
|
||||||
preserveOrgStructure: boolean;
|
}
|
||||||
|
|
||||||
|
export interface MirrorOptions {
|
||||||
|
mirrorReleases: boolean;
|
||||||
|
mirrorMetadata: boolean;
|
||||||
|
metadataComponents: {
|
||||||
|
issues: boolean;
|
||||||
|
pullRequests: boolean;
|
||||||
|
labels: boolean;
|
||||||
|
milestones: boolean;
|
||||||
|
wiki: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdvancedOptions {
|
||||||
|
skipForks: boolean;
|
||||||
skipStarredIssues: boolean;
|
skipStarredIssues: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +56,8 @@ export interface SaveConfigApiRequest {
|
|||||||
giteaConfig: GiteaConfig;
|
giteaConfig: GiteaConfig;
|
||||||
scheduleConfig: ScheduleConfig;
|
scheduleConfig: ScheduleConfig;
|
||||||
cleanupConfig: DatabaseCleanupConfig;
|
cleanupConfig: DatabaseCleanupConfig;
|
||||||
|
mirrorOptions?: MirrorOptions;
|
||||||
|
advancedOptions?: AdvancedOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaveConfigApiResponse {
|
export interface SaveConfigApiResponse {
|
||||||
@@ -65,6 +80,8 @@ export interface ConfigApiResponse {
|
|||||||
giteaConfig: GiteaConfig;
|
giteaConfig: GiteaConfig;
|
||||||
scheduleConfig: ScheduleConfig;
|
scheduleConfig: ScheduleConfig;
|
||||||
cleanupConfig: DatabaseCleanupConfig;
|
cleanupConfig: DatabaseCleanupConfig;
|
||||||
|
mirrorOptions?: MirrorOptions;
|
||||||
|
advancedOptions?: AdvancedOptions;
|
||||||
include: string[];
|
include: string[];
|
||||||
exclude: string[];
|
exclude: string[];
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|||||||
Reference in New Issue
Block a user