feat: add GenerateModal component
This commit is contained in:
94
frontend/src/components/GenerateModal.tsx
Normal file
94
frontend/src/components/GenerateModal.tsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import Dialog from '@mui/material/Dialog'
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle'
|
||||||
|
import DialogContent from '@mui/material/DialogContent'
|
||||||
|
import DialogActions from '@mui/material/DialogActions'
|
||||||
|
import Button from '@mui/material/Button'
|
||||||
|
import Tabs from '@mui/material/Tabs'
|
||||||
|
import Tab from '@mui/material/Tab'
|
||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import IconButton from '@mui/material/IconButton'
|
||||||
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
||||||
|
import DownloadIcon from '@mui/icons-material/Download'
|
||||||
|
import { configsApi } from '../api'
|
||||||
|
|
||||||
|
interface GeneratedFiles {
|
||||||
|
zones: string
|
||||||
|
interfaces: string
|
||||||
|
policy: string
|
||||||
|
rules: string
|
||||||
|
masq: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean
|
||||||
|
configId: number
|
||||||
|
configName: string
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const TABS = ['zones', 'interfaces', 'policy', 'rules', 'masq'] as const
|
||||||
|
|
||||||
|
export default function GenerateModal({ open, configId, configName, onClose }: Props) {
|
||||||
|
const [tab, setTab] = useState(0)
|
||||||
|
const [files, setFiles] = useState<GeneratedFiles | null>(null)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const handleOpen = async () => {
|
||||||
|
if (files) return
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const res = await configsApi.generate(configId, 'json')
|
||||||
|
setFiles(res.data)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownloadZip = async () => {
|
||||||
|
const res = await configsApi.generate(configId, 'zip')
|
||||||
|
const url = URL.createObjectURL(new Blob([res.data]))
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = `${configName}-shorewall.zip`
|
||||||
|
a.click()
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCopy = (text: string) => navigator.clipboard.writeText(text)
|
||||||
|
|
||||||
|
if (open && !files && !loading) handleOpen()
|
||||||
|
|
||||||
|
const currentFile = files ? files[TABS[tab]] : ''
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
||||||
|
<DialogTitle>Generated Shorewall Config — {configName}</DialogTitle>
|
||||||
|
<DialogContent sx={{ p: 0 }}>
|
||||||
|
<Tabs value={tab} onChange={(_, v) => setTab(v)} sx={{ borderBottom: 1, borderColor: 'divider', px: 2 }}>
|
||||||
|
{TABS.map((t) => <Tab key={t} label={t} />)}
|
||||||
|
</Tabs>
|
||||||
|
<Box sx={{ position: 'relative', p: 2 }}>
|
||||||
|
<Tooltip title="Copy">
|
||||||
|
<IconButton size="small" sx={{ position: 'absolute', top: 16, right: 16 }} onClick={() => handleCopy(currentFile)}>
|
||||||
|
<ContentCopyIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Box
|
||||||
|
component="pre"
|
||||||
|
sx={{ fontFamily: 'monospace', fontSize: 13, bgcolor: '#1e293b', color: '#e2e8f0', p: 2, borderRadius: 1, overflowX: 'auto', minHeight: 300, whiteSpace: 'pre' }}
|
||||||
|
>
|
||||||
|
{loading ? 'Generating…' : currentFile}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button startIcon={<DownloadIcon />} onClick={handleDownloadZip} variant="outlined">
|
||||||
|
Download ZIP
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onClose}>Close</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user