mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-07 12:06:46 +03:00
704 lines
24 KiB
TypeScript
704 lines
24 KiB
TypeScript
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
import { GitHubConfigForm } from './GitHubConfigForm';
|
|
import { GiteaConfigForm } from './GiteaConfigForm';
|
|
import { AutomationSettings } from './AutomationSettings';
|
|
import { SSOSettings } from './SSOSettings';
|
|
import type {
|
|
ConfigApiResponse,
|
|
GiteaConfig,
|
|
GitHubConfig,
|
|
SaveConfigApiRequest,
|
|
SaveConfigApiResponse,
|
|
ScheduleConfig,
|
|
DatabaseCleanupConfig,
|
|
MirrorOptions,
|
|
AdvancedOptions,
|
|
} from '@/types/config';
|
|
import { Button } from '../ui/button';
|
|
import { useAuth } from '@/hooks/useAuth';
|
|
import { apiRequest, showErrorToast } from '@/lib/utils';
|
|
import { RefreshCw } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
import { invalidateConfigCache } from '@/hooks/useConfigStatus';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
|
|
type ConfigState = {
|
|
githubConfig: GitHubConfig;
|
|
giteaConfig: GiteaConfig;
|
|
scheduleConfig: ScheduleConfig;
|
|
cleanupConfig: DatabaseCleanupConfig;
|
|
mirrorOptions: MirrorOptions;
|
|
advancedOptions: AdvancedOptions;
|
|
};
|
|
|
|
export function ConfigTabs() {
|
|
const [config, setConfig] = useState<ConfigState>({
|
|
githubConfig: {
|
|
username: '',
|
|
token: '',
|
|
privateRepositories: false,
|
|
mirrorStarred: false,
|
|
},
|
|
giteaConfig: {
|
|
url: '',
|
|
username: '',
|
|
token: '',
|
|
organization: 'github-mirrors',
|
|
visibility: 'public',
|
|
starredReposOrg: 'starred',
|
|
preserveOrgStructure: false,
|
|
},
|
|
scheduleConfig: {
|
|
enabled: false, // Don't set defaults here - will be loaded from API
|
|
interval: 0, // Will be replaced with actual value from API
|
|
},
|
|
cleanupConfig: {
|
|
enabled: false, // Don't set defaults here - will be loaded from API
|
|
retentionDays: 0, // Will be replaced with actual value from API
|
|
deleteIfNotInGitHub: true,
|
|
orphanedRepoAction: "archive",
|
|
dryRun: false,
|
|
deleteFromGitea: false,
|
|
protectedRepos: [],
|
|
},
|
|
mirrorOptions: {
|
|
mirrorReleases: false,
|
|
mirrorLFS: false,
|
|
mirrorMetadata: false,
|
|
metadataComponents: {
|
|
issues: false,
|
|
pullRequests: false,
|
|
labels: false,
|
|
milestones: false,
|
|
wiki: false,
|
|
},
|
|
},
|
|
advancedOptions: {
|
|
skipForks: false,
|
|
starredCodeOnly: false,
|
|
},
|
|
});
|
|
const { user } = useAuth();
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isSyncing, setIsSyncing] = useState<boolean>(false);
|
|
|
|
const [isAutoSavingSchedule, setIsAutoSavingSchedule] = useState<boolean>(false);
|
|
const [isAutoSavingCleanup, setIsAutoSavingCleanup] = useState<boolean>(false);
|
|
const [isAutoSavingGitHub, setIsAutoSavingGitHub] = useState<boolean>(false);
|
|
const [isAutoSavingGitea, setIsAutoSavingGitea] = useState<boolean>(false);
|
|
const autoSaveScheduleTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
const autoSaveCleanupTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
const autoSaveGitHubTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
const autoSaveGiteaTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
const isConfigFormValid = (): boolean => {
|
|
const { githubConfig, giteaConfig } = config;
|
|
const isGitHubValid = !!(
|
|
githubConfig.username.trim() && githubConfig.token.trim()
|
|
);
|
|
const isGiteaValid = !!(
|
|
giteaConfig.url.trim() &&
|
|
giteaConfig.username.trim() &&
|
|
giteaConfig.token.trim()
|
|
);
|
|
return isGitHubValid && isGiteaValid;
|
|
};
|
|
|
|
const isGitHubConfigValid = (): boolean => {
|
|
const { githubConfig } = config;
|
|
return !!(githubConfig.username.trim() && githubConfig.token.trim());
|
|
};
|
|
|
|
// Removed the problematic useEffect that was causing circular dependencies
|
|
// The lastRun and nextRun should be managed by the backend and fetched via API
|
|
|
|
const handleImportGitHubData = async () => {
|
|
if (!user?.id) return;
|
|
setIsSyncing(true);
|
|
try {
|
|
const result = await apiRequest<{ success: boolean; message?: string }>(
|
|
`/sync?userId=${user.id}`,
|
|
{ method: 'POST' },
|
|
);
|
|
result.success
|
|
? toast.success(
|
|
'GitHub data imported successfully! Head to the Repositories page to start mirroring.',
|
|
)
|
|
: toast.error(
|
|
`Failed to import GitHub data: ${
|
|
result.message || 'Unknown error'
|
|
}`,
|
|
);
|
|
} catch (error) {
|
|
toast.error(
|
|
`Error importing GitHub data: ${
|
|
error instanceof Error ? error.message : String(error)
|
|
}`,
|
|
);
|
|
} finally {
|
|
setIsSyncing(false);
|
|
}
|
|
};
|
|
|
|
// Auto-save function specifically for schedule config changes
|
|
const autoSaveScheduleConfig = useCallback(async (scheduleConfig: ScheduleConfig) => {
|
|
if (!user?.id) return;
|
|
|
|
// Clear any existing timeout
|
|
if (autoSaveScheduleTimeoutRef.current) {
|
|
clearTimeout(autoSaveScheduleTimeoutRef.current);
|
|
}
|
|
|
|
// Debounce the auto-save to prevent excessive API calls
|
|
autoSaveScheduleTimeoutRef.current = setTimeout(async () => {
|
|
setIsAutoSavingSchedule(true);
|
|
|
|
const reqPayload: SaveConfigApiRequest = {
|
|
userId: user.id!,
|
|
githubConfig: config.githubConfig,
|
|
giteaConfig: config.giteaConfig,
|
|
scheduleConfig: scheduleConfig,
|
|
cleanupConfig: config.cleanupConfig,
|
|
mirrorOptions: config.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
|
|
// Removed refreshUser() call to prevent page reload
|
|
// Invalidate config cache so other components get fresh data
|
|
invalidateConfigCache();
|
|
|
|
// Fetch updated config to get the recalculated nextRun time
|
|
try {
|
|
const updatedResponse = await apiRequest<ConfigApiResponse>(
|
|
`/config?userId=${user.id}`,
|
|
{ method: 'GET' },
|
|
);
|
|
if (updatedResponse && !updatedResponse.error) {
|
|
setConfig(prev => ({
|
|
...prev,
|
|
scheduleConfig: updatedResponse.scheduleConfig || prev.scheduleConfig,
|
|
}));
|
|
}
|
|
} catch (fetchError) {
|
|
console.warn('Failed to fetch updated config after auto-save:', fetchError);
|
|
}
|
|
} else {
|
|
showErrorToast(
|
|
`Auto-save failed: ${result.message || 'Unknown error'}`,
|
|
toast
|
|
);
|
|
}
|
|
} catch (error) {
|
|
showErrorToast(error, toast);
|
|
} finally {
|
|
setIsAutoSavingSchedule(false);
|
|
}
|
|
}, 500); // 500ms debounce
|
|
}, [user?.id, config.githubConfig, config.giteaConfig, config.cleanupConfig]);
|
|
|
|
// Auto-save function specifically for cleanup config changes
|
|
const autoSaveCleanupConfig = useCallback(async (cleanupConfig: DatabaseCleanupConfig) => {
|
|
if (!user?.id) return;
|
|
|
|
// Clear any existing timeout
|
|
if (autoSaveCleanupTimeoutRef.current) {
|
|
clearTimeout(autoSaveCleanupTimeoutRef.current);
|
|
}
|
|
|
|
// Debounce the auto-save to prevent excessive API calls
|
|
autoSaveCleanupTimeoutRef.current = setTimeout(async () => {
|
|
setIsAutoSavingCleanup(true);
|
|
|
|
const reqPayload: SaveConfigApiRequest = {
|
|
userId: user.id!,
|
|
githubConfig: config.githubConfig,
|
|
giteaConfig: config.giteaConfig,
|
|
scheduleConfig: config.scheduleConfig,
|
|
cleanupConfig: cleanupConfig,
|
|
mirrorOptions: config.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();
|
|
|
|
// Fetch updated config to get the recalculated nextRun time
|
|
try {
|
|
const updatedResponse = await apiRequest<ConfigApiResponse>(
|
|
`/config?userId=${user.id}`,
|
|
{ method: 'GET' },
|
|
);
|
|
if (updatedResponse && !updatedResponse.error) {
|
|
setConfig(prev => ({
|
|
...prev,
|
|
cleanupConfig: updatedResponse.cleanupConfig || prev.cleanupConfig,
|
|
}));
|
|
}
|
|
} catch (fetchError) {
|
|
console.warn('Failed to fetch updated config after auto-save:', fetchError);
|
|
}
|
|
} else {
|
|
showErrorToast(
|
|
`Auto-save failed: ${result.message || 'Unknown error'}`,
|
|
toast
|
|
);
|
|
}
|
|
} catch (error) {
|
|
showErrorToast(error, toast);
|
|
} finally {
|
|
setIsAutoSavingCleanup(false);
|
|
}
|
|
}, 500); // 500ms debounce
|
|
}, [user?.id, config.githubConfig, config.giteaConfig, config.scheduleConfig]);
|
|
|
|
// Auto-save function specifically for GitHub config changes
|
|
const autoSaveGitHubConfig = useCallback(async (githubConfig: GitHubConfig) => {
|
|
if (!user?.id) return;
|
|
|
|
// Clear any existing timeout
|
|
if (autoSaveGitHubTimeoutRef.current) {
|
|
clearTimeout(autoSaveGitHubTimeoutRef.current);
|
|
}
|
|
|
|
// Debounce the auto-save to prevent excessive API calls
|
|
autoSaveGitHubTimeoutRef.current = setTimeout(async () => {
|
|
setIsAutoSavingGitHub(true);
|
|
|
|
const reqPayload: SaveConfigApiRequest = {
|
|
userId: user.id!,
|
|
githubConfig: githubConfig,
|
|
giteaConfig: config.giteaConfig,
|
|
scheduleConfig: config.scheduleConfig,
|
|
cleanupConfig: config.cleanupConfig,
|
|
mirrorOptions: config.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 {
|
|
setIsAutoSavingGitHub(false);
|
|
}
|
|
}, 500); // 500ms debounce
|
|
}, [user?.id, config.giteaConfig, config.scheduleConfig, config.cleanupConfig]);
|
|
|
|
// Auto-save function specifically for Gitea config changes
|
|
const autoSaveGiteaConfig = useCallback(async (giteaConfig: GiteaConfig) => {
|
|
if (!user?.id) return;
|
|
|
|
// Clear any existing timeout
|
|
if (autoSaveGiteaTimeoutRef.current) {
|
|
clearTimeout(autoSaveGiteaTimeoutRef.current);
|
|
}
|
|
|
|
// Debounce the auto-save to prevent excessive API calls
|
|
autoSaveGiteaTimeoutRef.current = setTimeout(async () => {
|
|
setIsAutoSavingGitea(true);
|
|
|
|
const reqPayload: SaveConfigApiRequest = {
|
|
userId: user.id!,
|
|
githubConfig: config.githubConfig,
|
|
giteaConfig: giteaConfig,
|
|
scheduleConfig: config.scheduleConfig,
|
|
cleanupConfig: config.cleanupConfig,
|
|
mirrorOptions: config.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 {
|
|
setIsAutoSavingGitea(false);
|
|
}
|
|
}, 500); // 500ms debounce
|
|
}, [user?.id, config.githubConfig, config.scheduleConfig, config.cleanupConfig]);
|
|
|
|
// Auto-save function for mirror options (handled within GitHub config)
|
|
const autoSaveMirrorOptions = useCallback(async (mirrorOptions: MirrorOptions) => {
|
|
if (!user?.id) return;
|
|
|
|
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) {
|
|
invalidateConfigCache();
|
|
} else {
|
|
showErrorToast(
|
|
`Auto-save failed: ${result.message || 'Unknown error'}`,
|
|
toast
|
|
);
|
|
}
|
|
} catch (error) {
|
|
showErrorToast(error, toast);
|
|
}
|
|
}, [user?.id, config.githubConfig, config.giteaConfig, config.scheduleConfig, config.cleanupConfig, config.advancedOptions]);
|
|
|
|
// Auto-save function for advanced options (handled within GitHub config)
|
|
const autoSaveAdvancedOptions = useCallback(async (advancedOptions: AdvancedOptions) => {
|
|
if (!user?.id) return;
|
|
|
|
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) {
|
|
invalidateConfigCache();
|
|
} else {
|
|
showErrorToast(
|
|
`Auto-save failed: ${result.message || 'Unknown error'}`,
|
|
toast
|
|
);
|
|
}
|
|
} catch (error) {
|
|
showErrorToast(error, toast);
|
|
}
|
|
}, [user?.id, config.githubConfig, config.giteaConfig, config.scheduleConfig, config.cleanupConfig, config.mirrorOptions]);
|
|
|
|
// Cleanup timeouts on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
if (autoSaveScheduleTimeoutRef.current) {
|
|
clearTimeout(autoSaveScheduleTimeoutRef.current);
|
|
}
|
|
if (autoSaveCleanupTimeoutRef.current) {
|
|
clearTimeout(autoSaveCleanupTimeoutRef.current);
|
|
}
|
|
if (autoSaveGitHubTimeoutRef.current) {
|
|
clearTimeout(autoSaveGitHubTimeoutRef.current);
|
|
}
|
|
if (autoSaveGiteaTimeoutRef.current) {
|
|
clearTimeout(autoSaveGiteaTimeoutRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!user?.id) return;
|
|
|
|
const fetchConfig = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const response = await apiRequest<ConfigApiResponse>(
|
|
`/config?userId=${user.id}`,
|
|
{ method: 'GET' },
|
|
);
|
|
if (response && !response.error) {
|
|
setConfig({
|
|
githubConfig:
|
|
response.githubConfig || config.githubConfig,
|
|
giteaConfig:
|
|
response.giteaConfig || config.giteaConfig,
|
|
scheduleConfig:
|
|
response.scheduleConfig || config.scheduleConfig,
|
|
cleanupConfig: {
|
|
...config.cleanupConfig,
|
|
...response.cleanupConfig, // Merge to preserve all fields
|
|
},
|
|
mirrorOptions: {
|
|
...config.mirrorOptions,
|
|
...response.mirrorOptions, // Merge to preserve all fields including new mirrorLFS
|
|
},
|
|
advancedOptions:
|
|
response.advancedOptions || config.advancedOptions,
|
|
});
|
|
|
|
}
|
|
} catch (error) {
|
|
console.warn(
|
|
'Could not fetch configuration, using defaults:',
|
|
error,
|
|
);
|
|
}
|
|
setIsLoading(false);
|
|
};
|
|
|
|
fetchConfig();
|
|
}, [user?.id]); // Only depend on user.id, not the entire user object
|
|
|
|
function ConfigCardSkeleton() {
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header section */}
|
|
<div className="flex flex-row justify-between items-start">
|
|
<div className="flex flex-col gap-y-1.5">
|
|
<Skeleton className="h-8 w-48" />
|
|
<Skeleton className="h-4 w-72" />
|
|
</div>
|
|
<div className="flex gap-x-4">
|
|
<Skeleton className="h-10 w-36" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content section - Grid layout */}
|
|
<div className="space-y-4">
|
|
{/* GitHub & Gitea connections - Side by side */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="border rounded-lg p-4">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<Skeleton className="h-6 w-40" />
|
|
<Skeleton className="h-9 w-32" />
|
|
</div>
|
|
<div className="space-y-4">
|
|
<Skeleton className="h-20 w-full" />
|
|
<Skeleton className="h-20 w-full" />
|
|
<Skeleton className="h-1 w-full" />
|
|
<Skeleton className="h-32 w-full" />
|
|
<Skeleton className="h-48 w-full" />
|
|
<Skeleton className="h-32 w-full" />
|
|
</div>
|
|
</div>
|
|
<div className="border rounded-lg p-4">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<Skeleton className="h-6 w-40" />
|
|
<Skeleton className="h-9 w-32" />
|
|
</div>
|
|
<div className="space-y-4">
|
|
<Skeleton className="h-20 w-full" />
|
|
<Skeleton className="h-20 w-full" />
|
|
<Skeleton className="h-20 w-full" />
|
|
<Skeleton className="h-64 w-full" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Automation & Maintenance - Full width */}
|
|
<div className="border rounded-lg p-4">
|
|
<Skeleton className="h-8 w-48 mb-4" />
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<div className="space-y-4">
|
|
<Skeleton className="h-6 w-40" />
|
|
<Skeleton className="h-16 w-full" />
|
|
<Skeleton className="h-24 w-full" />
|
|
</div>
|
|
<div className="space-y-4">
|
|
<Skeleton className="h-6 w-40" />
|
|
<Skeleton className="h-16 w-full" />
|
|
<Skeleton className="h-24 w-full" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return isLoading ? (
|
|
<div className="space-y-6">
|
|
<ConfigCardSkeleton />
|
|
</div>
|
|
) : (
|
|
<div className="space-y-6">
|
|
{/* Header section */}
|
|
<div className="flex flex-col md:flex-row justify-between gap-y-4 items-start">
|
|
<div className="flex flex-col gap-y-1.5">
|
|
<h1 className="text-2xl font-semibold leading-none tracking-tight">
|
|
Configuration
|
|
</h1>
|
|
<p className="text-sm text-muted-foreground">
|
|
Configure your GitHub and Gitea connections, and set up automatic
|
|
mirroring.
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-x-4 w-full md:w-auto">
|
|
<Button
|
|
onClick={handleImportGitHubData}
|
|
disabled={isSyncing || !isGitHubConfigValid()}
|
|
title={
|
|
!isGitHubConfigValid()
|
|
? 'Please fill GitHub username and token fields'
|
|
: isSyncing
|
|
? 'Import in progress'
|
|
: 'Import GitHub Data'
|
|
}
|
|
className="w-full md:w-auto"
|
|
>
|
|
{isSyncing ? (
|
|
<>
|
|
<RefreshCw className="h-4 w-4 animate-spin mr-1" />
|
|
Import GitHub Data
|
|
</>
|
|
) : (
|
|
<>
|
|
<RefreshCw className="h-4 w-4 mr-1" />
|
|
Import GitHub Data
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content section - Tabs layout */}
|
|
<Tabs defaultValue="connections" className="space-y-4">
|
|
<TabsList className="grid w-full grid-cols-3">
|
|
<TabsTrigger value="connections">Connections</TabsTrigger>
|
|
<TabsTrigger value="automation">Automation</TabsTrigger>
|
|
<TabsTrigger value="sso">Authentication</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="connections" className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:items-stretch">
|
|
<GitHubConfigForm
|
|
config={config.githubConfig}
|
|
setConfig={update =>
|
|
setConfig(prev => ({
|
|
...prev,
|
|
githubConfig:
|
|
typeof update === 'function'
|
|
? update(prev.githubConfig)
|
|
: update,
|
|
}))
|
|
}
|
|
mirrorOptions={config.mirrorOptions}
|
|
setMirrorOptions={update =>
|
|
setConfig(prev => ({
|
|
...prev,
|
|
mirrorOptions:
|
|
typeof update === 'function'
|
|
? update(prev.mirrorOptions)
|
|
: update,
|
|
}))
|
|
}
|
|
advancedOptions={config.advancedOptions}
|
|
setAdvancedOptions={update =>
|
|
setConfig(prev => ({
|
|
...prev,
|
|
advancedOptions:
|
|
typeof update === 'function'
|
|
? update(prev.advancedOptions)
|
|
: update,
|
|
}))
|
|
}
|
|
onAutoSave={autoSaveGitHubConfig}
|
|
onMirrorOptionsAutoSave={autoSaveMirrorOptions}
|
|
onAdvancedOptionsAutoSave={autoSaveAdvancedOptions}
|
|
isAutoSaving={isAutoSavingGitHub}
|
|
/>
|
|
<GiteaConfigForm
|
|
config={config.giteaConfig}
|
|
setConfig={update =>
|
|
setConfig(prev => ({
|
|
...prev,
|
|
giteaConfig:
|
|
typeof update === 'function'
|
|
? update(prev.giteaConfig)
|
|
: update,
|
|
}))
|
|
}
|
|
onAutoSave={autoSaveGiteaConfig}
|
|
isAutoSaving={isAutoSavingGitea}
|
|
githubUsername={config.githubConfig.username}
|
|
/>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="automation" className="space-y-4">
|
|
<AutomationSettings
|
|
scheduleConfig={config.scheduleConfig}
|
|
cleanupConfig={config.cleanupConfig}
|
|
onScheduleChange={(newConfig) => {
|
|
setConfig(prev => ({ ...prev, scheduleConfig: newConfig }));
|
|
autoSaveScheduleConfig(newConfig);
|
|
}}
|
|
onCleanupChange={(newConfig) => {
|
|
setConfig(prev => ({ ...prev, cleanupConfig: newConfig }));
|
|
autoSaveCleanupConfig(newConfig);
|
|
}}
|
|
isAutoSavingSchedule={isAutoSavingSchedule}
|
|
isAutoSavingCleanup={isAutoSavingCleanup}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="sso" className="space-y-4">
|
|
<SSOSettings />
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
);
|
|
}
|