From d8061985d66abe2048ebde3f6066c3b8a2ab90eb Mon Sep 17 00:00:00 2001 From: Ilya Starchak <66072408+ilyastar9999@users.noreply.github.com> Date: Wed, 10 Dec 2025 02:27:35 +0300 Subject: [PATCH] asdas --- backend/routers/setup.py | 223 +++++++++++++++++ frontend/src/pages/Setup/index.tsx | 278 +++++++++++++++++++++ frontend/src/pages/TrafficViewer/index.tsx | 1 - setup.example.json | 20 ++ 4 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 backend/routers/setup.py create mode 100644 frontend/src/pages/Setup/index.tsx create mode 100644 setup.example.json diff --git a/backend/routers/setup.py b/backend/routers/setup.py new file mode 100644 index 0000000..fd36929 --- /dev/null +++ b/backend/routers/setup.py @@ -0,0 +1,223 @@ +from fastapi import APIRouter, HTTPException, UploadFile, File +from pydantic import BaseModel +import json +from typing import List, Optional +from utils.models import StatusMessageModel +from routers import nfproxy, nfregex, porthijack, firewall + +class ServiceConfig(BaseModel): + name: str + port: int + proto: str + ip_int: str + fail_open: bool = True + +class PortHijackServiceConfig(BaseModel): + name: str + public_port: int + proxy_port: int + proto: str + ip_src: str + ip_dst: str + +class FirewallRuleConfig(BaseModel): + mode: str + src: str + dst: str + in_int: str + out_int: str + proto: str + sport: str + dport: str + +class SetupConfig(BaseModel): + services: Optional[List[ServiceConfig]] = [] + porthijack: Optional[List[PortHijackServiceConfig]] = [] + firewall: Optional[List[FirewallRuleConfig]] = [] + +class SetupResponse(BaseModel): + status: str + services_created: int = 0 + porthijack_created: int = 0 + firewall_created: int = 0 + errors: List[str] = [] + +app = APIRouter() + +@app.post("/import", response_model=SetupResponse) +async def import_setup(config: SetupConfig): + """ + Import services and rules from a setup configuration. + Creates basic services without filters or regex rules. + """ + errors = [] + services_count = 0 + porthijack_count = 0 + firewall_count = 0 + + # Import Services + if config.services: + for service_config in config.services: + try: + # Determine which module to use based on protocol + # HTTP -> NFProxy, TCP/UDP -> can use either (prefer NFProxy) + if service_config.proto in ["tcp", "http", "udp"]: + # Create NFProxy service + try: + add_form = nfproxy.ServiceAddForm( + name=service_config.name, + port=service_config.port, + proto=service_config.proto, + ip_int=service_config.ip_int, + fail_open=service_config.fail_open + ) + result = await nfproxy.add_service(add_form) + + if result.status == "ok": + services_count += 1 + else: + errors.append(f"Service '{service_config.name}': Failed to create") + except Exception as e: + errors.append(f"Service '{service_config.name}': {str(e)}") + else: + errors.append(f"Service '{service_config.name}': Unsupported protocol '{service_config.proto}'") + + except Exception as e: + errors.append(f"Service '{service_config.name}': {str(e)}") + + # Import PortHijack services + if config.porthijack: + for service_config in config.porthijack: + try: + add_form = porthijack.ServiceAddForm( + name=service_config.name, + public_port=service_config.public_port, + proxy_port=service_config.proxy_port, + proto=service_config.proto, + ip_src=service_config.ip_src, + ip_dst=service_config.ip_dst + ) + result = await porthijack.add_service(add_form) + + if result.status == "ok": + porthijack_count += 1 + else: + errors.append(f"PortHijack service '{service_config.name}': Failed to create") + except Exception as e: + errors.append(f"PortHijack service '{service_config.name}': {str(e)}") + + # Import Firewall rules + if config.firewall: + for rule_config in config.firewall: + try: + rule_form = firewall.RuleFormAdd( + mode=rule_config.mode, + src=rule_config.src, + dst=rule_config.dst, + in_int=rule_config.in_int, + out_int=rule_config.out_int, + proto=rule_config.proto, + sport=rule_config.sport, + dport=rule_config.dport + ) + await firewall.add_rule(rule_form) + firewall_count += 1 + except Exception as e: + errors.append(f"Firewall rule: {str(e)}") + + return SetupResponse( + status="ok" if len(errors) == 0 else "partial", + services_created=services_count, + porthijack_created=porthijack_count, + firewall_created=firewall_count, + errors=errors + ) + +@app.post("/import/file") +async def import_setup_file(file: UploadFile = File(...)): + """ + Import services from an uploaded JSON file. + """ + try: + content = await file.read() + config_dict = json.loads(content.decode('utf-8')) + config = SetupConfig(**config_dict) + return await import_setup(config) + except json.JSONDecodeError as e: + raise HTTPException(status_code=400, detail=f"Invalid JSON: {str(e)}") + except Exception as e: + raise HTTPException(status_code=400, detail=f"Error processing file: {str(e)}") + +@app.get("/export") +async def export_setup(): + """ + Export all current services and rules as a JSON configuration. + Exports only service definitions without filters or regexes. + """ + config = { + "services": [], + "porthijack": [], + "firewall": [] + } + + # Export NFProxy services + try: + nfproxy_services = await nfproxy.get_services() + for service in nfproxy_services: + config["services"].append({ + "name": service.name, + "port": service.port, + "proto": service.proto, + "ip_int": service.ip_int, + "fail_open": service.fail_open + }) + except: + pass + + # Export NFRegex services + try: + nfregex_services = await nfregex.get_services() + for service in nfregex_services: + config["services"].append({ + "name": service.name, + "port": service.port, + "proto": service.proto, + "ip_int": service.ip_int, + "fail_open": service.fail_open + }) + except: + pass + + # Export PortHijack services + try: + porthijack_services = await porthijack.get_services() + for service in porthijack_services: + config["porthijack"].append({ + "name": service.name, + "public_port": service.public_port, + "proxy_port": service.proxy_port, + "proto": service.proto, + "ip_src": service.ip_src, + "ip_dst": service.ip_dst + }) + except: + pass + + # Export Firewall rules + try: + fw_rules = await firewall.get_rules() + for rule in fw_rules: + config["firewall"].append({ + "mode": rule.mode, + "src": rule.src, + "dst": rule.dst, + "in_int": rule.in_int, + "out_int": rule.out_int, + "proto": rule.proto, + "sport": rule.sport, + "dport": rule.dport + }) + except: + pass + + return config diff --git a/frontend/src/pages/Setup/index.tsx b/frontend/src/pages/Setup/index.tsx new file mode 100644 index 0000000..740408b --- /dev/null +++ b/frontend/src/pages/Setup/index.tsx @@ -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(null); + const [importing, setImporting] = useState(false); + const [exporting, setExporting] = useState(false); + const [importResult, setImportResult] = useState(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 ( + + + <ThemeIcon radius="md" size="lg" variant='filled' color='cyan'> + <MdSettings size={24} /> + </ThemeIcon> + <Space w="sm" /> + Setup Import/Export + + + Import or export your Firegex configuration including all services and rules + + + + + + {/* Export Section */} + + Export Configuration + + Download all current services and rules as a JSON file + + + + + {/* Import from File Section */} + + Import from File + + Upload a setup.json file to create services and rules + + + + {(props) => ( + + )} + + + + + + {/* Import from JSON Section */} + + Import from JSON + + Paste a JSON configuration directly + +