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