Compare commits
3 Commits
6b3ad99097
...
d11b20575b
| Author | SHA1 | Date | |
|---|---|---|---|
| d11b20575b | |||
| 45fe0e4d6f | |||
| 32198ff592 |
@@ -31,7 +31,8 @@ export default function RepoSubNav(props: RepoSubNavProps) {
|
||||
mb: 2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1
|
||||
gap: 1,
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ReactNode } from 'react'
|
||||
|
||||
type SectionCardProps = {
|
||||
title: ReactNode
|
||||
subtitle?: string
|
||||
subtitle?: ReactNode
|
||||
actions?: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
@@ -37,11 +37,15 @@ export default function SectionCard(props: SectionCardProps) {
|
||||
{typeof props.title === 'string' ? <Typography variant="h6">{props.title}</Typography> : props.title}
|
||||
{props.actions ? <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>{props.actions}</Box> : null}
|
||||
</Box>
|
||||
{props.subtitle ? (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{props.subtitle}
|
||||
</Typography>
|
||||
) : null}
|
||||
{props.subtitle
|
||||
? typeof props.subtitle === 'string'
|
||||
? (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{props.subtitle}
|
||||
</Typography>
|
||||
)
|
||||
: props.subtitle
|
||||
: null}
|
||||
</Box>
|
||||
<Box sx={{ display: 'grid', gap: 1, px: 2, pt: 1.25, pb: 2 }}>
|
||||
{props.children}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import Alert from '@mui/material/Alert'
|
||||
import { Box, Button, Checkbox, FormControlLabel, MenuItem, Paper, TextField, Typography } from '@mui/material'
|
||||
import { Box, Button, Checkbox, FormControlLabel, MenuItem, TextField, Typography } from '@mui/material'
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'
|
||||
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { api, AuthSettings } from '../api'
|
||||
import SectionCard from '../components/SectionCard'
|
||||
|
||||
export default function AdminAuthLdapPage() {
|
||||
const [settings, setSettings] = useState<AuthSettings>({
|
||||
@@ -27,6 +30,7 @@ export default function AdminAuthLdapPage() {
|
||||
const [testing, setTesting] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [saved, setSaved] = useState(false)
|
||||
const [testError, setTestError] = useState<string | null>(null)
|
||||
const [testResult, setTestResult] = useState<string | null>(null)
|
||||
const [testUsername, setTestUsername] = useState('')
|
||||
const [testPassword, setTestPassword] = useState('')
|
||||
@@ -71,7 +75,7 @@ export default function AdminAuthLdapPage() {
|
||||
controller = new AbortController()
|
||||
testControllerRef.current = controller
|
||||
setTesting(true)
|
||||
setError(null)
|
||||
setTestError(null)
|
||||
setTestResult(null)
|
||||
try {
|
||||
const result = await api.testAuthSettings(
|
||||
@@ -89,7 +93,7 @@ export default function AdminAuthLdapPage() {
|
||||
return
|
||||
}
|
||||
const message = err instanceof Error ? err.message : 'LDAP test failed'
|
||||
setError(message)
|
||||
setTestError(message)
|
||||
} finally {
|
||||
setTesting(false)
|
||||
testControllerRef.current = null
|
||||
@@ -104,21 +108,43 @@ export default function AdminAuthLdapPage() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
const testSubtitle = testError ? (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, color: 'error.main' }}>
|
||||
<ErrorOutlineIcon fontSize="small" />
|
||||
<Typography variant="body2" color="inherit">
|
||||
{testError}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : testResult ? (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, color: 'success.main' }}>
|
||||
<CheckCircleOutlineIcon fontSize="small" />
|
||||
<Typography variant="body2" color="inherit">
|
||||
{testResult}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : 'Optional user bind'
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: 'grid', gap: 1, minWidth: 0 }}>
|
||||
<Typography variant="h5">
|
||||
Admin: Site Authentication
|
||||
</Typography>
|
||||
<Paper sx={{ p: 2, maxWidth: 820 }}>
|
||||
{loading ? (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||
Loading...
|
||||
</Typography>
|
||||
) : null}
|
||||
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null}
|
||||
{saved ? <Alert severity="success" sx={{ mb: 1 }}>Saved.</Alert> : null}
|
||||
{testResult ? <Alert severity="success" sx={{ mb: 1 }}>{testResult}</Alert> : null}
|
||||
<Box sx={{ display: 'grid', gap: 1 }}>
|
||||
{loading ? (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Loading...
|
||||
</Typography>
|
||||
) : null}
|
||||
{error ? <Alert severity="error">{error}</Alert> : null}
|
||||
{saved ? <Alert severity="success">Saved.</Alert> : null}
|
||||
<Box sx={{ display: 'grid', gap: 1, width: '100%', minWidth: 0 }}>
|
||||
<SectionCard
|
||||
title="General"
|
||||
actions={
|
||||
<Button variant="contained" onClick={handleSave} disabled={saving || loading}>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<TextField
|
||||
select
|
||||
label="Auth Mode"
|
||||
@@ -129,9 +155,18 @@ export default function AdminAuthLdapPage() {
|
||||
<MenuItem value="ldap">ldap</MenuItem>
|
||||
<MenuItem value="hybrid">hybrid</MenuItem>
|
||||
</TextField>
|
||||
<Typography variant="subtitle2" sx={{ mt: 1 }}>
|
||||
OIDC
|
||||
</Typography>
|
||||
</SectionCard>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gap: 1,
|
||||
width: '100%',
|
||||
minWidth: 0,
|
||||
gridTemplateColumns: { xs: '1fr', lg: 'minmax(0, 1fr) minmax(0, 1fr)' },
|
||||
alignItems: 'start'
|
||||
}}
|
||||
>
|
||||
<SectionCard title="OIDC">
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={settings.oidc_enabled} onChange={(event) => setSettings((prev) => ({ ...prev, oidc_enabled: event.target.checked }))} />}
|
||||
label="Enable OIDC login"
|
||||
@@ -173,9 +208,8 @@ export default function AdminAuthLdapPage() {
|
||||
control={<Checkbox checked={settings.oidc_tls_insecure_skip_verify} onChange={(event) => setSettings((prev) => ({ ...prev, oidc_tls_insecure_skip_verify: event.target.checked }))} />}
|
||||
label="OIDC TLS insecure skip verify (testing/self-signed only)"
|
||||
/>
|
||||
<Typography variant="subtitle2" sx={{ mt: 1 }}>
|
||||
LDAP
|
||||
</Typography>
|
||||
</SectionCard>
|
||||
<SectionCard title="LDAP">
|
||||
<TextField label="LDAP URL" value={settings.ldap_url} onChange={(event) => setSettings((prev) => ({ ...prev, ldap_url: event.target.value }))} />
|
||||
<TextField label="Bind DN" value={settings.ldap_bind_dn} onChange={(event) => setSettings((prev) => ({ ...prev, ldap_bind_dn: event.target.value }))} />
|
||||
<TextField
|
||||
@@ -199,21 +233,21 @@ export default function AdminAuthLdapPage() {
|
||||
control={<Checkbox checked={settings.ldap_tls_insecure_skip_verify} onChange={(event) => setSettings((prev) => ({ ...prev, ldap_tls_insecure_skip_verify: event.target.checked }))} />}
|
||||
label="TLS insecure skip verify (testing/self-signed only)"
|
||||
/>
|
||||
<Typography variant="subtitle2" sx={{ mt: 1 }}>
|
||||
Test (optional user bind)
|
||||
</Typography>
|
||||
<TextField label="Test Username" value={testUsername} onChange={(event) => setTestUsername(event.target.value)} />
|
||||
<TextField label="Test Password" type="password" value={testPassword} onChange={(event) => setTestPassword(event.target.value)} />
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 1 }}>
|
||||
<Button variant="outlined" onClick={handleTest} color={testing ? 'warning' : 'primary'}>
|
||||
</SectionCard>
|
||||
</Box>
|
||||
<SectionCard
|
||||
title="Test LDAP"
|
||||
subtitle={testSubtitle}
|
||||
actions={
|
||||
<Button variant="outlined" onClick={handleTest} color={testing ? 'warning' : 'primary'} disabled={loading}>
|
||||
{testing ? 'Cancel Test' : 'Test Connection'}
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSave} disabled={saving}>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
}
|
||||
>
|
||||
<TextField label="Test Username" value={testUsername} onChange={(event) => setTestUsername(event.target.value)} />
|
||||
<TextField label="Test Password" type="password" value={testPassword} onChange={(event) => setTestPassword(event.target.value)} />
|
||||
</SectionCard>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ export default function BranchesPage() {
|
||||
{loadError}
|
||||
</Typography>
|
||||
) : null}
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Paper sx={{ p: 2, backgroundColor: 'transparent' }}>
|
||||
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
|
||||
<TextField
|
||||
size="small"
|
||||
|
||||
@@ -135,7 +135,7 @@ export default function CommitsPage() {
|
||||
{loadError}
|
||||
</Typography>
|
||||
) : null}
|
||||
<Paper sx={{ p: 2, mb: 1 }}>
|
||||
<Paper sx={{ p: 2, mb: 1, backgroundColor: 'transparent' }}>
|
||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<Box sx={{ minWidth: 220, flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="subtitle2">Branch</Typography>
|
||||
@@ -195,7 +195,7 @@ export default function CommitsPage() {
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Paper sx={{ p: 2, backgroundColor: 'transparent' }}>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Compare Branches
|
||||
</Typography>
|
||||
|
||||
@@ -577,7 +577,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
||||
</Typography>
|
||||
) : null}
|
||||
<Box sx={{ display: 'flex', alignItems: 'stretch', gap: 1, mb: 1 }}>
|
||||
<Paper sx={{ p: 1, display: 'flex', alignItems: 'center', gap: 1, minWidth: 240 }}>
|
||||
<Paper sx={{ p: 1, display: 'flex', alignItems: 'center', gap: 1, minWidth: 240, backgroundColor: 'transparent' }}>
|
||||
<Typography variant="subtitle2" color="text.secondary">
|
||||
Branch
|
||||
</Typography>
|
||||
@@ -670,7 +670,13 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
||||
</Box>
|
||||
<Divider sx={{ mb: 1 }} />
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: `${sidebarOpen ? '280px' : '44px'} minmax(0, 1fr)`, gap: 1 }}>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Paper
|
||||
sx={{
|
||||
p: 2,
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.015)'
|
||||
}}
|
||||
>
|
||||
{sidebarOpen ? (
|
||||
<>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
||||
@@ -779,7 +785,13 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Paper
|
||||
sx={{
|
||||
p: 2,
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.015)'
|
||||
}}
|
||||
>
|
||||
{selectedFile ? (
|
||||
<Box sx={{ mt: 0 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
@@ -879,7 +891,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
||||
</Box>
|
||||
{previewTab === 'content' ? (
|
||||
isImageFileSelected ? (
|
||||
<Paper variant="outlined" sx={{ p: 1, mt: 1 }}>
|
||||
<Paper variant="outlined" sx={{ p: 1, mt: 1, backgroundColor: 'transparent' }}>
|
||||
<Box
|
||||
component="img"
|
||||
src={buildBlobRawUrl(selectedFile, imagePreviewRef || ref)}
|
||||
@@ -888,7 +900,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
||||
/>
|
||||
</Paper>
|
||||
) : (
|
||||
<Paper variant="outlined" sx={{ p: 1, pr: 0.5, mt: 1 }}>
|
||||
<Paper variant="outlined" sx={{ p: 1, pr: 0.5, mt: 1, backgroundColor: 'transparent' }}>
|
||||
<CodeBlock code={content} language={detectLanguage(selectedFile)} showLineNumbers />
|
||||
</Paper>
|
||||
)
|
||||
@@ -928,7 +940,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
||||
) : null}
|
||||
{previewTab === 'diff' ? (
|
||||
diff ? (
|
||||
<Paper variant="outlined" sx={{ p: 1, pr: 0.5, mt: 1 }}>
|
||||
<Paper variant="outlined" sx={{ p: 1, pr: 0.5, mt: 1, backgroundColor: 'transparent' }}>
|
||||
<CodeBlock code={diff} language="diff" />
|
||||
</Paper>
|
||||
) : (
|
||||
@@ -940,7 +952,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
||||
</Box>
|
||||
) : readmeContent ? (
|
||||
readmeKind === 'markdown' ? (
|
||||
<Paper variant="outlined" sx={{ p: 1, mt: 1 }}>
|
||||
<Paper variant="outlined" sx={{ p: 1, mt: 1, backgroundColor: 'transparent' }}>
|
||||
<Box>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
@@ -988,7 +1000,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
||||
</Box>
|
||||
</Paper>
|
||||
) : (
|
||||
<Paper variant="outlined" sx={{ p: 1, mt: 1 }}>
|
||||
<Paper variant="outlined" sx={{ p: 1, mt: 1, backgroundColor: 'transparent' }}>
|
||||
<CodeBlock code={readmeContent} language="text" showLineNumbers />
|
||||
</Paper>
|
||||
)
|
||||
|
||||
@@ -818,7 +818,13 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
|
||||
</Typography>
|
||||
) : null}
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: `${sidebarOpen ? '280px' : '44px'} minmax(0, 1fr)`, gap: 1, mb: 1 }}>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Paper
|
||||
sx={{
|
||||
p: 2,
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.015)'
|
||||
}}
|
||||
>
|
||||
{sidebarOpen ? (
|
||||
<>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
||||
@@ -1044,7 +1050,13 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Paper
|
||||
sx={{
|
||||
p: 2,
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.015)'
|
||||
}}
|
||||
>
|
||||
{rpmSelected && rpmSelectedEntry && rpmSelectedEntry.name.toLowerCase().endsWith('.rpm') ? (
|
||||
<>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
|
||||
Reference in New Issue
Block a user