Skip to content

Commit 6aee921

Browse files
authored
feat: add support for github connector settings (#615)
1 parent e7e54c0 commit 6aee921

File tree

7 files changed

+658
-0
lines changed

7 files changed

+658
-0
lines changed
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import { useTranslation } from '@/hooks/use-translation';
5+
import DialogWrapper, { DialogAction } from '@/components/ui/dialog-wrapper';
6+
import { Button } from '@/components/ui/button';
7+
import { Badge } from '@/components/ui/badge';
8+
import { Skeleton } from '@/components/ui/skeleton';
9+
import {
10+
Github,
11+
Settings,
12+
Trash2,
13+
RefreshCw,
14+
Plus,
15+
Check,
16+
ExternalLink,
17+
AlertCircle
18+
} from 'lucide-react';
19+
import { GithubConnector } from '@/redux/types/github';
20+
import useGithubConnectorSettings from '../../hooks/use-github-connector-settings';
21+
import { Alert, AlertDescription } from '@/components/ui/alert';
22+
import { DeleteDialog } from '@/components/ui/delete-dialog';
23+
24+
interface ConnectorItemProps {
25+
connector: GithubConnector;
26+
isActive: boolean;
27+
onSetActive: (id: string) => void;
28+
onDelete: (id: string) => void;
29+
onReset: (id: string) => void;
30+
isDeleting: boolean;
31+
isUpdating: boolean;
32+
}
33+
34+
const ConnectorItem: React.FC<ConnectorItemProps> = ({
35+
connector,
36+
isActive,
37+
onSetActive,
38+
onDelete,
39+
onReset,
40+
isDeleting,
41+
isUpdating
42+
}) => {
43+
const { t } = useTranslation();
44+
const [showDeleteDialog, setShowDeleteDialog] = React.useState(false);
45+
46+
const handleDelete = () => {
47+
setShowDeleteDialog(true);
48+
};
49+
50+
const confirmDelete = () => {
51+
onDelete(connector.id);
52+
setShowDeleteDialog(false);
53+
};
54+
55+
return (
56+
<>
57+
<div
58+
className={`flex items-center justify-between p-4 rounded-lg border transition-colors ${
59+
isActive ? 'border-primary bg-primary/5' : 'border-border bg-card'
60+
}`}
61+
>
62+
<div className="flex items-center gap-3 flex-1 min-w-0">
63+
<div className="rounded-full bg-primary/10 p-2 shrink-0">
64+
<Github size={20} className="text-primary" />
65+
</div>
66+
<div className="flex-1 min-w-0">
67+
<div className="flex items-center gap-2 mb-1">
68+
<p className="font-medium text-sm truncate">{connector.name || connector.slug}</p>
69+
{isActive && (
70+
<Badge variant="default" className="text-xs">
71+
<Check size={12} className="mr-1" />
72+
{t('selfHost.connectorSettings.active' as any)}
73+
</Badge>
74+
)}
75+
</div>
76+
<p className="text-xs text-muted-foreground truncate">
77+
{t('selfHost.connectorSettings.connector.slug' as any)}: {connector.slug || 'N/A'}
78+
</p>
79+
{connector.installation_id && (
80+
<p className="text-xs text-muted-foreground truncate">
81+
{t('selfHost.connectorSettings.connector.installationId' as any)}:{' '}
82+
{connector.installation_id.substring(0, 8)}...
83+
</p>
84+
)}
85+
</div>
86+
</div>
87+
<div className="flex items-center gap-2 shrink-0">
88+
{!isActive && (
89+
<Button
90+
variant="outline"
91+
size="sm"
92+
onClick={() => onSetActive(connector.id)}
93+
disabled={isDeleting || isUpdating}
94+
>
95+
{t('selfHost.connectorSettings.actions.switch.label' as any)}
96+
</Button>
97+
)}
98+
<Button
99+
variant="outline"
100+
size="sm"
101+
onClick={() => onReset(connector.id)}
102+
disabled={isDeleting || isUpdating}
103+
title={t('selfHost.connectorSettings.actions.reset.label' as any)}
104+
>
105+
<RefreshCw size={16} />
106+
</Button>
107+
<Button
108+
variant="destructive"
109+
size="sm"
110+
onClick={handleDelete}
111+
disabled={isDeleting || isUpdating}
112+
title={t('selfHost.connectorSettings.actions.delete.label' as any)}
113+
>
114+
<Trash2 size={16} />
115+
</Button>
116+
</div>
117+
</div>
118+
119+
<DeleteDialog
120+
open={showDeleteDialog}
121+
onOpenChange={setShowDeleteDialog}
122+
title={t('selfHost.connectorSettings.actions.delete.dialog.title' as any)}
123+
description={t('selfHost.connectorSettings.actions.delete.dialog.description' as any).replace(
124+
'{name}',
125+
connector.name || connector.slug
126+
)}
127+
onConfirm={confirmDelete}
128+
confirmText={
129+
isDeleting
130+
? t('selfHost.connectorSettings.actions.delete.dialog.deleting' as any)
131+
: t('selfHost.connectorSettings.actions.delete.dialog.confirm' as any)
132+
}
133+
cancelText={t('selfHost.connectorSettings.actions.delete.dialog.cancel' as any)}
134+
isDeleting={isDeleting}
135+
variant="destructive"
136+
icon={Trash2}
137+
/>
138+
</>
139+
);
140+
};
141+
142+
interface GitHubConnectorSettingsModalProps {
143+
open: boolean;
144+
onOpenChange: (open: boolean) => void;
145+
onAddNew?: () => void;
146+
}
147+
148+
const GitHubConnectorSettingsModal: React.FC<GitHubConnectorSettingsModalProps> = ({
149+
open,
150+
onOpenChange,
151+
onAddNew
152+
}) => {
153+
const { t } = useTranslation();
154+
const {
155+
connectors,
156+
activeConnector,
157+
activeConnectorId,
158+
isLoadingConnectors,
159+
isDeleting,
160+
isUpdating,
161+
handleSetActiveConnector,
162+
handleDeleteConnector,
163+
handleResetConnector,
164+
setIsSettingsModalOpen
165+
} = useGithubConnectorSettings();
166+
167+
const handleClose = () => {
168+
setIsSettingsModalOpen(false);
169+
onOpenChange(false);
170+
};
171+
172+
const handleAddNew = () => {
173+
handleClose();
174+
onAddNew?.();
175+
};
176+
177+
const actions: DialogAction[] = [
178+
{
179+
label: t('common.cancel'),
180+
onClick: handleClose,
181+
variant: 'outline'
182+
},
183+
...(onAddNew
184+
? [
185+
{
186+
label: t('selfHost.connectorSettings.actions.addNew' as any),
187+
onClick: handleAddNew,
188+
variant: 'default' as const,
189+
icon: Plus
190+
}
191+
]
192+
: [])
193+
];
194+
195+
return (
196+
<DialogWrapper
197+
open={open}
198+
onOpenChange={handleClose}
199+
title={t('selfHost.connectorSettings.title' as any)}
200+
description={t('selfHost.connectorSettings.description' as any)}
201+
actions={actions}
202+
size="lg"
203+
>
204+
<div className="space-y-4">
205+
{isLoadingConnectors ? (
206+
<div className="space-y-3">
207+
{[1, 2, 3].map((i) => (
208+
<Skeleton key={i} className="h-20 w-full" />
209+
))}
210+
</div>
211+
) : connectors.length === 0 ? (
212+
<Alert>
213+
<AlertCircle className="h-4 w-4" />
214+
<AlertDescription>
215+
{t('selfHost.connectorSettings.noConnectors' as any)}
216+
</AlertDescription>
217+
</Alert>
218+
) : (
219+
<>
220+
<div className="space-y-3">
221+
{connectors.map((connector) => (
222+
<ConnectorItem
223+
key={connector.id}
224+
connector={connector}
225+
isActive={connector.id === activeConnectorId}
226+
onSetActive={handleSetActiveConnector}
227+
onDelete={handleDeleteConnector}
228+
onReset={handleResetConnector}
229+
isDeleting={isDeleting === connector.id}
230+
isUpdating={isUpdating === connector.id}
231+
/>
232+
))}
233+
</div>
234+
{activeConnector && (
235+
<div className="pt-4 border-t">
236+
<div className="flex items-start gap-3">
237+
<Settings size={18} className="text-muted-foreground mt-0.5 shrink-0" />
238+
<div className="flex-1 space-y-2">
239+
<div>
240+
<p className="text-sm font-medium mb-1">
241+
{t('selfHost.connectorSettings.currentConnector.title' as any)}
242+
</p>
243+
<div className="flex items-center gap-2 mb-2">
244+
<Badge variant="secondary" className="text-xs">
245+
{activeConnector.name || activeConnector.slug}
246+
</Badge>
247+
</div>
248+
</div>
249+
<p className="text-xs text-muted-foreground leading-relaxed">
250+
{t('selfHost.connectorSettings.currentConnector.description' as any)}
251+
</p>
252+
{activeConnector.installation_id && (
253+
<a
254+
href={`https://github.com/settings/installations/${activeConnector.installation_id}`}
255+
target="_blank"
256+
rel="noopener noreferrer"
257+
className="inline-flex items-center gap-1.5 text-xs text-primary hover:underline font-medium"
258+
>
259+
{t('selfHost.connectorSettings.currentConnector.viewOnGithub' as any)}
260+
<ExternalLink size={12} />
261+
</a>
262+
)}
263+
</div>
264+
</div>
265+
</div>
266+
)}
267+
</>
268+
)}
269+
</div>
270+
</DialogWrapper>
271+
);
272+
};
273+
274+
export default GitHubConnectorSettingsModal;
275+

view/app/self-host/components/github-repositories/list-repositories.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ import { DahboardUtilityHeader } from '@/components/layout/dashboard-page-header
55
import GithubRepositories, { GithubRepositoriesSkeletonLoader } from './repository-card';
66
import PaginationWrapper from '@/components/ui/pagination';
77
import { useTranslation } from '@/hooks/use-translation';
8+
import { Button } from '@/components/ui/button';
9+
import { Settings } from 'lucide-react';
10+
import GitHubConnectorSettingsModal from '../github-connector/github-connector-settings-modal';
11+
import useGithubConnectorSettings from '../../hooks/use-github-connector-settings';
12+
import { useRouter } from 'next/navigation';
813

914
function ListRepositories() {
1015
const { t } = useTranslation();
16+
const router = useRouter();
1117
const {
1218
isLoading,
1319
setSelectedRepository,
@@ -22,6 +28,15 @@ function ListRepositories() {
2228
paginatedApplications,
2329
onSelectRepository
2430
} = useGithubRepoPagination();
31+
const {
32+
isSettingsModalOpen,
33+
openSettingsModal,
34+
closeSettingsModal
35+
} = useGithubConnectorSettings();
36+
37+
const handleAddNewConnector = () => {
38+
router.push('/self-host?github_setup=true');
39+
};
2540

2641
const renderGithubRepositories = () => {
2742
if (isLoading) {
@@ -66,8 +81,24 @@ function ListRepositories() {
6681
sortOptions={sortOptions}
6782
label={t('selfHost.repositories.title')}
6883
className="mt-5 mb-5"
84+
children={
85+
<Button
86+
variant="outline"
87+
size="sm"
88+
onClick={openSettingsModal}
89+
className="flex items-center gap-2"
90+
>
91+
<Settings size={16} />
92+
{t('selfHost.repositories.settings' as any)}
93+
</Button>
94+
}
6995
/>
7096
{renderGithubRepositories()}
97+
<GitHubConnectorSettingsModal
98+
open={isSettingsModalOpen}
99+
onOpenChange={closeSettingsModal}
100+
onAddNew={handleAddNewConnector}
101+
/>
71102
</div>
72103
);
73104
}

0 commit comments

Comments
 (0)