Compare commits

...

3 Commits

7 changed files with 117 additions and 54 deletions

View File

@@ -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 }}>

View File

@@ -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}

View File

@@ -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>
)
}

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>
)

View File

@@ -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' }}>