Compare commits

...

3 Commits

7 changed files with 117 additions and 54 deletions
+2 -1
View File
@@ -31,7 +31,8 @@ export default function RepoSubNav(props: RepoSubNavProps) {
mb: 2, mb: 2,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: 1 gap: 1,
backgroundColor: 'transparent'
}} }}
> >
<Box sx={{ display: 'flex', gap: 1 }}> <Box sx={{ display: 'flex', gap: 1 }}>
+10 -6
View File
@@ -5,7 +5,7 @@ import { ReactNode } from 'react'
type SectionCardProps = { type SectionCardProps = {
title: ReactNode title: ReactNode
subtitle?: string subtitle?: ReactNode
actions?: ReactNode actions?: ReactNode
children: 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} {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} {props.actions ? <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>{props.actions}</Box> : null}
</Box> </Box>
{props.subtitle ? ( {props.subtitle
<Typography variant="body2" color="text.secondary"> ? typeof props.subtitle === 'string'
{props.subtitle} ? (
</Typography> <Typography variant="body2" color="text.secondary">
) : null} {props.subtitle}
</Typography>
)
: props.subtitle
: null}
</Box> </Box>
<Box sx={{ display: 'grid', gap: 1, px: 2, pt: 1.25, pb: 2 }}> <Box sx={{ display: 'grid', gap: 1, px: 2, pt: 1.25, pb: 2 }}>
{props.children} {props.children}
+68 -34
View File
@@ -1,7 +1,10 @@
import Alert from '@mui/material/Alert' 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 { useEffect, useRef, useState } from 'react'
import { api, AuthSettings } from '../api' import { api, AuthSettings } from '../api'
import SectionCard from '../components/SectionCard'
export default function AdminAuthLdapPage() { export default function AdminAuthLdapPage() {
const [settings, setSettings] = useState<AuthSettings>({ const [settings, setSettings] = useState<AuthSettings>({
@@ -27,6 +30,7 @@ export default function AdminAuthLdapPage() {
const [testing, setTesting] = useState(false) const [testing, setTesting] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [saved, setSaved] = useState(false) const [saved, setSaved] = useState(false)
const [testError, setTestError] = useState<string | null>(null)
const [testResult, setTestResult] = useState<string | null>(null) const [testResult, setTestResult] = useState<string | null>(null)
const [testUsername, setTestUsername] = useState('') const [testUsername, setTestUsername] = useState('')
const [testPassword, setTestPassword] = useState('') const [testPassword, setTestPassword] = useState('')
@@ -71,7 +75,7 @@ export default function AdminAuthLdapPage() {
controller = new AbortController() controller = new AbortController()
testControllerRef.current = controller testControllerRef.current = controller
setTesting(true) setTesting(true)
setError(null) setTestError(null)
setTestResult(null) setTestResult(null)
try { try {
const result = await api.testAuthSettings( const result = await api.testAuthSettings(
@@ -89,7 +93,7 @@ export default function AdminAuthLdapPage() {
return return
} }
const message = err instanceof Error ? err.message : 'LDAP test failed' const message = err instanceof Error ? err.message : 'LDAP test failed'
setError(message) setTestError(message)
} finally { } finally {
setTesting(false) setTesting(false)
testControllerRef.current = null 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 ( return (
<Box> <Box sx={{ display: 'grid', gap: 1, minWidth: 0 }}>
<Typography variant="h5" sx={{ mb: 2 }}> <Typography variant="h5">
Admin: Site Authentication Admin: Site Authentication
</Typography> </Typography>
<Paper sx={{ p: 2, maxWidth: 820 }}> {loading ? (
{loading ? ( <Typography variant="body2" color="text.secondary">
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}> Loading...
Loading... </Typography>
</Typography> ) : null}
) : null} {error ? <Alert severity="error">{error}</Alert> : null}
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} {saved ? <Alert severity="success">Saved.</Alert> : null}
{saved ? <Alert severity="success" sx={{ mb: 1 }}>Saved.</Alert> : null} <Box sx={{ display: 'grid', gap: 1, width: '100%', minWidth: 0 }}>
{testResult ? <Alert severity="success" sx={{ mb: 1 }}>{testResult}</Alert> : null} <SectionCard
<Box sx={{ display: 'grid', gap: 1 }}> title="General"
actions={
<Button variant="contained" onClick={handleSave} disabled={saving || loading}>
{saving ? 'Saving...' : 'Save'}
</Button>
}
>
<TextField <TextField
select select
label="Auth Mode" label="Auth Mode"
@@ -129,9 +155,18 @@ export default function AdminAuthLdapPage() {
<MenuItem value="ldap">ldap</MenuItem> <MenuItem value="ldap">ldap</MenuItem>
<MenuItem value="hybrid">hybrid</MenuItem> <MenuItem value="hybrid">hybrid</MenuItem>
</TextField> </TextField>
<Typography variant="subtitle2" sx={{ mt: 1 }}> </SectionCard>
OIDC <Box
</Typography> 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 <FormControlLabel
control={<Checkbox checked={settings.oidc_enabled} onChange={(event) => setSettings((prev) => ({ ...prev, oidc_enabled: event.target.checked }))} />} control={<Checkbox checked={settings.oidc_enabled} onChange={(event) => setSettings((prev) => ({ ...prev, oidc_enabled: event.target.checked }))} />}
label="Enable OIDC login" 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 }))} />} 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)" label="OIDC TLS insecure skip verify (testing/self-signed only)"
/> />
<Typography variant="subtitle2" sx={{ mt: 1 }}> </SectionCard>
LDAP <SectionCard title="LDAP">
</Typography>
<TextField label="LDAP URL" value={settings.ldap_url} onChange={(event) => setSettings((prev) => ({ ...prev, ldap_url: event.target.value }))} /> <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 label="Bind DN" value={settings.ldap_bind_dn} onChange={(event) => setSettings((prev) => ({ ...prev, ldap_bind_dn: event.target.value }))} />
<TextField <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 }))} />} 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)" label="TLS insecure skip verify (testing/self-signed only)"
/> />
<Typography variant="subtitle2" sx={{ mt: 1 }}> </SectionCard>
Test (optional user bind) </Box>
</Typography> <SectionCard
<TextField label="Test Username" value={testUsername} onChange={(event) => setTestUsername(event.target.value)} /> title="Test LDAP"
<TextField label="Test Password" type="password" value={testPassword} onChange={(event) => setTestPassword(event.target.value)} /> subtitle={testSubtitle}
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 1 }}> actions={
<Button variant="outlined" onClick={handleTest} color={testing ? 'warning' : 'primary'}> <Button variant="outlined" onClick={handleTest} color={testing ? 'warning' : 'primary'} disabled={loading}>
{testing ? 'Cancel Test' : 'Test Connection'} {testing ? 'Cancel Test' : 'Test Connection'}
</Button> </Button>
<Button variant="contained" onClick={handleSave} disabled={saving}> }
{saving ? 'Saving...' : 'Save'} >
</Button> <TextField label="Test Username" value={testUsername} onChange={(event) => setTestUsername(event.target.value)} />
</Box> <TextField label="Test Password" type="password" value={testPassword} onChange={(event) => setTestPassword(event.target.value)} />
</Box> </SectionCard>
</Paper> </Box>
</Box> </Box>
) )
} }
+1 -1
View File
@@ -229,7 +229,7 @@ export default function BranchesPage() {
{loadError} {loadError}
</Typography> </Typography>
) : null} ) : null}
<Paper sx={{ p: 2 }}> <Paper sx={{ p: 2, backgroundColor: 'transparent' }}>
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}> <Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
<TextField <TextField
size="small" size="small"
+2 -2
View File
@@ -135,7 +135,7 @@ export default function CommitsPage() {
{loadError} {loadError}
</Typography> </Typography>
) : null} ) : 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={{ display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'center' }}>
<Box sx={{ minWidth: 220, flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ minWidth: 220, flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="subtitle2">Branch</Typography> <Typography variant="subtitle2">Branch</Typography>
@@ -195,7 +195,7 @@ export default function CommitsPage() {
</Button> </Button>
</Box> </Box>
</Paper> </Paper>
<Paper sx={{ p: 2 }}> <Paper sx={{ p: 2, backgroundColor: 'transparent' }}>
<Typography variant="subtitle1" gutterBottom> <Typography variant="subtitle1" gutterBottom>
Compare Branches Compare Branches
</Typography> </Typography>
+20 -8
View File
@@ -577,7 +577,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
</Typography> </Typography>
) : null} ) : null}
<Box sx={{ display: 'flex', alignItems: 'stretch', gap: 1, mb: 1 }}> <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"> <Typography variant="subtitle2" color="text.secondary">
Branch Branch
</Typography> </Typography>
@@ -670,7 +670,13 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
</Box> </Box>
<Divider sx={{ mb: 1 }} /> <Divider sx={{ mb: 1 }} />
<Box sx={{ display: 'grid', gridTemplateColumns: `${sidebarOpen ? '280px' : '44px'} minmax(0, 1fr)`, gap: 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 ? ( {sidebarOpen ? (
<> <>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
@@ -779,7 +785,13 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
</Box> </Box>
)} )}
</Paper> </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 ? ( {selectedFile ? (
<Box sx={{ mt: 0 }}> <Box sx={{ mt: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
@@ -879,7 +891,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
</Box> </Box>
{previewTab === 'content' ? ( {previewTab === 'content' ? (
isImageFileSelected ? ( isImageFileSelected ? (
<Paper variant="outlined" sx={{ p: 1, mt: 1 }}> <Paper variant="outlined" sx={{ p: 1, mt: 1, backgroundColor: 'transparent' }}>
<Box <Box
component="img" component="img"
src={buildBlobRawUrl(selectedFile, imagePreviewRef || ref)} src={buildBlobRawUrl(selectedFile, imagePreviewRef || ref)}
@@ -888,7 +900,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
/> />
</Paper> </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 /> <CodeBlock code={content} language={detectLanguage(selectedFile)} showLineNumbers />
</Paper> </Paper>
) )
@@ -928,7 +940,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
) : null} ) : null}
{previewTab === 'diff' ? ( {previewTab === 'diff' ? (
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" /> <CodeBlock code={diff} language="diff" />
</Paper> </Paper>
) : ( ) : (
@@ -940,7 +952,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
</Box> </Box>
) : readmeContent ? ( ) : readmeContent ? (
readmeKind === 'markdown' ? ( readmeKind === 'markdown' ? (
<Paper variant="outlined" sx={{ p: 1, mt: 1 }}> <Paper variant="outlined" sx={{ p: 1, mt: 1, backgroundColor: 'transparent' }}>
<Box> <Box>
<ReactMarkdown <ReactMarkdown
remarkPlugins={[remarkGfm]} remarkPlugins={[remarkGfm]}
@@ -988,7 +1000,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
</Box> </Box>
</Paper> </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 /> <CodeBlock code={readmeContent} language="text" showLineNumbers />
</Paper> </Paper>
) )
+14 -2
View File
@@ -818,7 +818,13 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
</Typography> </Typography>
) : null} ) : null}
<Box sx={{ display: 'grid', gridTemplateColumns: `${sidebarOpen ? '280px' : '44px'} minmax(0, 1fr)`, gap: 1, mb: 1 }}> <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 ? ( {sidebarOpen ? (
<> <>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
@@ -1044,7 +1050,13 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
</Box> </Box>
)} )}
</Paper> </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') ? ( {rpmSelected && rpmSelectedEntry && rpmSelectedEntry.name.toLowerCase().endsWith('.rpm') ? (
<> <>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>