Files
DOCKERMAILSERVER-LXC/stack/admin/src/Status.jsx
T
karim 1d3818e725 docker-mailserver LXC für Proxmox: Stack + Admin-UI + Webmail + Hardening
- 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>
2026-06-02 02:26:28 +02:00

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