1d3818e725
- dms-lxc.sh: Proxmox-Host-Installer (unprivilegierter LXC, Debian 13, Docker), curl-Self-Download, Multi-Domain-DKIM, SnappyMail-Provisionierung, PVE-Firewall - Stack: docker-mailserver, Node-Admin-API (Supabase-Auth), React-Admin-UI (OPENBUREAU-Look), SnappyMail (Shibui-Theme), Rspamd-Web-UI, docker-socket-proxy - Admin: Postfächer/Aliase/Catch-all/Quota, editierbare Domains+Settings, Server (Quota/Queue über abgesicherte Bridge), Status & DNS - Hardening: no-new-privileges, Whitelisted exec-Bridge, Rspamd-Passwort, .env chmod 600, PVE-CT-Firewall, generisch/teilbar (keine festen Domains) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
73 lines
2.8 KiB
React
73 lines
2.8 KiB
React
import { useState } from 'react';
|
|
import { useGetOne, Title, useNotify } from 'react-admin';
|
|
import { Card, CardContent, Typography, Box, Chip, Stack, Divider, Button } from '@mui/material';
|
|
import VpnKeyIcon from '@mui/icons-material/VpnKey';
|
|
import { apiFetch } from './dataProvider';
|
|
|
|
const Mono = ({ children }) => (
|
|
<Box
|
|
component="pre"
|
|
sx={{
|
|
background: '#1e1e1e', color: '#e0e0e0', p: 1.5, borderRadius: 1, m: 0,
|
|
overflowX: 'auto', fontSize: 13, whiteSpace: 'pre-wrap', wordBreak: 'break-all',
|
|
}}
|
|
>
|
|
{children}
|
|
</Box>
|
|
);
|
|
|
|
const DkimButton = ({ domain, onDone }) => {
|
|
const [busy, setBusy] = useState(false);
|
|
const notify = useNotify();
|
|
const gen = () => {
|
|
setBusy(true);
|
|
apiFetch('/mailserver/dkim', { method: 'POST', body: JSON.stringify({ domain }) })
|
|
.then(() => { notify(`DKIM für ${domain} erzeugt`, { type: 'success' }); onDone?.(); })
|
|
.catch((e) => notify(e?.body?.error || 'DKIM-Erzeugung fehlgeschlagen', { type: 'warning' }))
|
|
.finally(() => setBusy(false));
|
|
};
|
|
return (
|
|
<Button size="small" startIcon={<VpnKeyIcon />} onClick={gen} disabled={busy} sx={{ mt: 1 }}>
|
|
{busy ? 'Erzeuge …' : 'DKIM erzeugen / erneuern'}
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
export const StatusPage = () => {
|
|
const { data, isLoading, error, refetch } = useGetOne('status', { id: 'status' });
|
|
|
|
if (isLoading) return <Card><CardContent>Lädt …</CardContent></Card>;
|
|
if (error) return <Card><CardContent>Fehler beim Laden des Status.</CardContent></Card>;
|
|
|
|
return (
|
|
<Card>
|
|
<Title title="Status & DNS" />
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>Mailserver</Typography>
|
|
<Stack direction="row" spacing={1} sx={{ mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
|
<Chip label={`Host: ${data.fqdn}`} />
|
|
<Chip color="primary" label={`${data.accounts} Postfächer`} />
|
|
<Chip color="secondary" label={`${data.aliases} Aliase`} />
|
|
{(data.domains || []).map((d) => <Chip key={d} variant="outlined" label={d} />)}
|
|
</Stack>
|
|
|
|
<Typography variant="h6" gutterBottom>DNS — Mailhost (einmalig)</Typography>
|
|
<Mono>{[data.host?.a, data.host?.ptr].filter(Boolean).join('\n')}</Mono>
|
|
|
|
{(data.records || []).map((r) => (
|
|
<Box key={r.domain} sx={{ mt: 3 }}>
|
|
<Divider sx={{ mb: 1 }} />
|
|
<Typography variant="h6" gutterBottom>{r.domain}</Typography>
|
|
<Mono>{[r.mx, r.spf, r.dmarc].join('\n')}</Mono>
|
|
<Typography variant="subtitle2" sx={{ mt: 1 }}>DKIM (TXT)</Typography>
|
|
{r.dkim
|
|
? <Mono>{r.dkim}</Mono>
|
|
: <Typography color="text.secondary" variant="body2">Noch kein DKIM-Schlüssel.</Typography>}
|
|
<DkimButton domain={r.domain} onDone={refetch} />
|
|
</Box>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|