This commit is contained in:
Ilya Starchak
2025-12-10 02:27:35 +03:00
parent c237112077
commit d8061985d6
4 changed files with 521 additions and 1 deletions

View File

@@ -0,0 +1,278 @@
import { Box, Button, Code, Divider, FileButton, Group, List, Paper, Space, Stack, Text, Textarea, ThemeIcon, Title } from '@mantine/core';
import { useState } from 'react';
import { FaCheck, FaDownload, FaExclamationTriangle, FaTimes, FaUpload } from 'react-icons/fa';
import { MdSettings } from 'react-icons/md';
import { getapi, isMediumScreen, postapi } from '../../js/utils';
import { errorNotify, successNotify } from '../../js/utils';
export default function SetupPage() {
const [file, setFile] = useState<File | null>(null);
const [importing, setImporting] = useState(false);
const [exporting, setExporting] = useState(false);
const [importResult, setImportResult] = useState<any>(null);
const [configJson, setConfigJson] = useState('');
const isMedium = isMediumScreen();
const handleExport = async () => {
setExporting(true);
try {
const response = await getapi('/setup/export');
const blob = new Blob([JSON.stringify(response, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `firegex-setup-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
successNotify('Configuration exported successfully');
} catch (err) {
errorNotify('Failed to export configuration', String(err));
} finally {
setExporting(false);
}
};
const handleImportFile = async () => {
if (!file) {
errorNotify('Please select a file first', '');
return;
}
setImporting(true);
setImportResult(null);
try {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/setup/import/file', {
method: 'POST',
body: formData
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Import failed');
}
const result = await response.json();
setImportResult(result);
if (result.status === 'ok') {
successNotify('Configuration imported successfully');
} else {
errorNotify('Configuration imported with errors', 'Check the results below');
}
} catch (err) {
errorNotify('Failed to import configuration', String(err));
} finally {
setImporting(false);
}
};
const handleImportJson = async () => {
if (!configJson.trim()) {
errorNotify('Please enter a JSON configuration', '');
return;
}
setImporting(true);
setImportResult(null);
try {
const config = JSON.parse(configJson);
const result = await postapi('/setup/import', config);
setImportResult(result);
if (result.status === 'ok') {
successNotify('Configuration imported successfully');
} else {
errorNotify('Configuration imported with errors', 'Check the results below');
}
} catch (err) {
if (err instanceof SyntaxError) {
errorNotify('Invalid JSON format', String(err));
} else {
errorNotify('Failed to import configuration', String(err));
}
} finally {
setImporting(false);
}
};
return (
<Box px="md" mt="lg">
<Title order={1} className="center-flex">
<ThemeIcon radius="md" size="lg" variant='filled' color='cyan'>
<MdSettings size={24} />
</ThemeIcon>
<Space w="sm" />
Setup Import/Export
</Title>
<Text c="dimmed" mt="sm">
Import or export your Firegex configuration including all services and rules
</Text>
<Divider my="lg" />
<Stack gap="xl">
{/* Export Section */}
<Paper shadow="sm" p="lg" withBorder>
<Title order={3} mb="md">Export Configuration</Title>
<Text c="dimmed" mb="md">
Download all current services and rules as a JSON file
</Text>
<Button
leftSection={<FaDownload />}
onClick={handleExport}
loading={exporting}
color="teal"
size="md"
>
Export to JSON
</Button>
</Paper>
{/* Import from File Section */}
<Paper shadow="sm" p="lg" withBorder>
<Title order={3} mb="md">Import from File</Title>
<Text c="dimmed" mb="md">
Upload a setup.json file to create services and rules
</Text>
<Group>
<FileButton onChange={setFile} accept="application/json">
{(props) => (
<Button {...props} variant="outline" color="cyan">
{file ? file.name : 'Select JSON File'}
</Button>
)}
</FileButton>
<Button
leftSection={<FaUpload />}
onClick={handleImportFile}
loading={importing}
disabled={!file}
color="blue"
size="md"
>
Import from File
</Button>
</Group>
</Paper>
{/* Import from JSON Section */}
<Paper shadow="sm" p="lg" withBorder>
<Title order={3} mb="md">Import from JSON</Title>
<Text c="dimmed" mb="md">
Paste a JSON configuration directly
</Text>
<Textarea
placeholder='{"services": [], "porthijack": [], "firewall": []}'
value={configJson}
onChange={(e) => setConfigJson(e.currentTarget.value)}
minRows={10}
maxRows={20}
mb="md"
styles={{ input: { fontFamily: 'monospace', fontSize: '12px' } }}
/>
<Button
leftSection={<FaUpload />}
onClick={handleImportJson}
loading={importing}
disabled={!configJson.trim()}
color="blue"
size="md"
>
Import from JSON
</Button>
</Paper>
{/* Import Results */}
{importResult && (
<Paper shadow="sm" p="lg" withBorder>
<Title order={3} mb="md">
<Group>
{importResult.status === 'ok' ? (
<ThemeIcon color="teal" size="lg" radius="xl">
<FaCheck />
</ThemeIcon>
) : (
<ThemeIcon color="yellow" size="lg" radius="xl">
<FaExclamationTriangle />
</ThemeIcon>
)}
Import Results
</Group>
</Title>
<Stack gap="md">
<Group>
<Text fw={500}>Services:</Text>
<Text c={importResult.services_created > 0 ? "teal" : "dimmed"}>
{importResult.services_created} created
</Text>
</Group>
<Group>
<Text fw={500}>PortHijack Services:</Text>
<Text c={importResult.porthijack_created > 0 ? "teal" : "dimmed"}>
{importResult.porthijack_created} created
</Text>
</Group>
<Group>
<Text fw={500}>Firewall Rules:</Text>
<Text c={importResult.firewall_created > 0 ? "teal" : "dimmed"}>
{importResult.firewall_created} created
</Text>
</Group>
{importResult.errors && importResult.errors.length > 0 && (
<>
<Divider />
<Text fw={500} c="red">Errors:</Text>
<List
spacing="xs"
size="sm"
icon={
<ThemeIcon color="red" size={20} radius="xl">
<FaTimes size={12} />
</ThemeIcon>
}
>
{importResult.errors.map((error: string, idx: number) => (
<List.Item key={idx}>
<Code>{error}</Code>
</List.Item>
))}
</List>
</>
)}
</Stack>
</Paper>
)}
{/* Example Configuration */}
<Paper shadow="sm" p="lg" withBorder>
<Title order={3} mb="md">Example Configuration</Title>
<Text c="dimmed" mb="md">
Here's an example of the JSON structure:
</Text>
<Code block>{`{
"services": [
{
"name": "Example HTTP Service",
"port": 8080,
"proto": "http",
"ip_int": "0.0.0.0",
"fail_open": true
}
],
"porthijack": [],
"firewall": []
}`}</Code>
</Paper>
</Stack>
</Box>
);
}

View File

@@ -254,7 +254,6 @@ export default function TrafficViewer() {
<MdDoubleArrow size="24px" />
</ActionIcon>
</Tooltip>
</Tooltip>
</Group>
</Box>
</Group>