diff --git a/backend/modules/firewall/firewall.py b/backend/modules/firewall/firewall.py index e0a3c04..576ea83 100644 --- a/backend/modules/firewall/firewall.py +++ b/backend/modules/firewall/firewall.py @@ -20,5 +20,8 @@ class FirewallManager: async def reload(self): async with self.lock: - nft.set(map(Rule.from_dict, self.db.query('SELECT * FROM rules WHERE active = 1 ORDER BY rule_id;')), policy=self.db.get('POLICY', 'accept')) + if self.db.get("ENABLED", "0") == "1": + nft.set(map(Rule.from_dict, self.db.query('SELECT * FROM rules WHERE active = 1 ORDER BY rule_id;')), policy=self.db.get('POLICY', 'accept')) + else: + nft.reset() diff --git a/backend/routers/firewall.py b/backend/routers/firewall.py index 13d11e9..3c835ae 100644 --- a/backend/routers/firewall.py +++ b/backend/routers/firewall.py @@ -20,9 +20,14 @@ class RuleModel(BaseModel): action: str mode:str -class RuleForm(BaseModel): +class RuleFormAdd(BaseModel): rules: list[RuleModel] policy: str + +class RuleInfo(BaseModel): + rules: list[RuleModel] + policy: str + enabled: bool class RuleAddResponse(BaseModel): status:str|list[dict] @@ -30,9 +35,6 @@ class RuleAddResponse(BaseModel): class RenameForm(BaseModel): name:str -class GeneralStatModel(BaseModel): - rules: int - app = APIRouter() db = SQLite('db/firewall-rules.db', { @@ -85,18 +87,27 @@ async def apply_changes(): await refresh_frontend() return {'status': 'ok'} -@app.get('/stats', response_model=GeneralStatModel) -async def get_general_stats(): - """Get firegex general status about rules""" - return db.query("SELECT (SELECT COUNT(*) FROM rules) rules")[0] - -@app.get('/rules', response_model=RuleForm) +@app.get('/rules', response_model=RuleInfo) async def get_rule_list(): """Get the list of existent firegex rules""" return { "policy": db.get("POLICY", "accept"), - "rules": db.query("SELECT active, name, proto, ip_src, ip_dst, port_src_from, port_dst_from, port_src_to, port_dst_to, action, mode FROM rules ORDER BY rule_id;") + "rules": db.query("SELECT active, name, proto, ip_src, ip_dst, port_src_from, port_dst_from, port_src_to, port_dst_to, action, mode FROM rules ORDER BY rule_id;"), + "enabled": db.get("ENABLED", "0") == "1" } + +@app.get('/enable', response_model=StatusMessageModel) +async def enable_firewall(): + """Request enabling the firewall""" + db.set("ENABLED", "1") + return await apply_changes() + +@app.get('/disable', response_model=StatusMessageModel) +async def disable_firewall(): + """Request disabling the firewall""" + db.set("ENABLED", "0") + return await apply_changes() + @app.get('/rule/{rule_id}/disable', response_model=StatusMessageModel) async def service_disable(rule_id: str): """Request disabling a specific rule""" @@ -147,7 +158,7 @@ def parse_and_check_rule(rule:RuleModel): @app.post('/rules/set', response_model=RuleAddResponse) -async def add_new_service(form: RuleForm): +async def add_new_service(form: RuleFormAdd): """Add a new service""" if form.policy not in ["accept", "drop", "reject"]: return {"status": "Invalid policy"} diff --git a/backend/routers/nfregex.py b/backend/routers/nfregex.py index 45856d1..78da678 100644 --- a/backend/routers/nfregex.py +++ b/backend/routers/nfregex.py @@ -10,11 +10,6 @@ from utils.sqlite import SQLite from utils import ip_parse, refactor_name, refresh_frontend, PortType from utils.models import ResetRequest, StatusMessageModel -class GeneralStatModel(BaseModel): - closed:int - regexes: int - services: int - class ServiceModel(BaseModel): status: str service_id: str @@ -116,16 +111,6 @@ def gen_service_id(): firewall = FirewallManager(db) -@app.get('/stats', response_model=GeneralStatModel) -async def get_general_stats(): - """Get firegex general status about services""" - return db.query(""" - SELECT - (SELECT COALESCE(SUM(blocked_packets),0) FROM regexes) closed, - (SELECT COUNT(*) FROM regexes) regexes, - (SELECT COUNT(*) FROM services) services - """)[0] - @app.get('/services', response_model=list[ServiceModel]) async def get_service_list(): """Get the list of existent firegex services""" @@ -200,6 +185,7 @@ async def service_rename(service_id: str, form: RenameForm): @app.get('/service/{service_id}/regexes', response_model=list[RegexModel]) async def get_service_regexe_list(service_id: str): """Get the list of the regexes of a service""" + if not db.query("SELECT 1 FROM services s WHERE s.service_id = ?;", service_id): raise HTTPException(status_code=400, detail="This service does not exists!") return db.query(""" SELECT regex, mode, regex_id `id`, service_id, is_blacklist, diff --git a/backend/routers/porthijack.py b/backend/routers/porthijack.py index 18d1bd1..cebe2f6 100644 --- a/backend/routers/porthijack.py +++ b/backend/routers/porthijack.py @@ -34,9 +34,6 @@ class ServiceAddResponse(BaseModel): status:str service_id: str|None = None -class GeneralStatModel(BaseModel): - services: int - app = APIRouter() db = SQLite('db/port-hijacking.db', { @@ -87,14 +84,6 @@ def gen_service_id(): firewall = FirewallManager(db) -@app.get('/stats', response_model=GeneralStatModel) -async def get_general_stats(): - """Get firegex general status about services""" - return db.query(""" - SELECT - (SELECT COUNT(*) FROM services) services - """)[0] - @app.get('/services', response_model=list[ServiceModel]) async def get_service_list(): """Get the list of existent firegex services""" diff --git a/backend/routers/regexproxy.py b/backend/routers/regexproxy.py index ca60dc0..81a72ca 100644 --- a/backend/routers/regexproxy.py +++ b/backend/routers/regexproxy.py @@ -196,6 +196,7 @@ class RegexModel(BaseModel): @app.get('/service/{service_id}/regexes', response_model=list[RegexModel]) async def get_service_regexe_list(service_id: str): """Get the list of the regexes of a service""" + if not db.query("SELECT 1 FROM services s WHERE s.service_id = ?;", service_id): raise HTTPException(status_code=400, detail="This service does not exists!") return db.query(""" SELECT regex, mode, regex_id `id`, service_id, is_blacklist, diff --git a/frontend/package.json b/frontend/package.json index ae3486c..747515a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "@mantine/notifications": "^6.0.13", "@mantine/prism": "^6.0.13", "@mantine/spotlight": "^6.0.13", + "@tanstack/react-query": "^4.35.3", "@testing-library/dom": "^9.3.0", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", @@ -53,6 +54,7 @@ ] }, "devDependencies": { + "@tanstack/react-query-devtools": "^4.35.3", "@vitejs/plugin-react": "^4.0.0", "vite": "^4.3.9", "vite-plugin-svgr": "^3.2.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a6e4831..8152f53 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,6 @@ import { Button, Group, Loader, LoadingOverlay, Notification, Space, PasswordInput, Title } from '@mantine/core'; import { useForm } from '@mantine/form'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ImCross } from 'react-icons/im'; import { Outlet, Route, Routes } from 'react-router-dom'; import MainLayout from './components/MainLayout'; @@ -52,11 +52,6 @@ function App() { } },[]) - useEffect(()=>{ - const updater = setInterval(fireUpdateRequest,6000) - return () => clearInterval(updater) - },[]) - const form = useForm({ initialValues: { password:"", diff --git a/frontend/src/components/AddNewRegex.tsx b/frontend/src/components/AddNewRegex.tsx index be212d2..c6293be 100644 --- a/frontend/src/components/AddNewRegex.tsx +++ b/frontend/src/components/AddNewRegex.tsx @@ -1,6 +1,6 @@ import { Button, Group, Space, TextInput, Notification, Switch, NativeSelect, Modal } from '@mantine/core'; import { useForm } from '@mantine/form'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { RegexAddForm } from '../js/models'; import { b64decode, b64encode, getapiobject, okNotify } from '../js/utils'; import { ImCross } from "react-icons/im" diff --git a/frontend/src/components/Firewall/utils.ts b/frontend/src/components/Firewall/utils.ts index 3906a64..b51e7a7 100644 --- a/frontend/src/components/Firewall/utils.ts +++ b/frontend/src/components/Firewall/utils.ts @@ -1,10 +1,7 @@ -import { RegexAddForm, RegexFilter, ServerResponse } from "../../js/models" +import { useQuery } from "@tanstack/react-query" +import { ServerResponse } from "../../js/models" import { getapi, postapi } from "../../js/utils" -export type GeneralStats = { - rules:number, -} - export enum Protocol { TCP = "tcp", UDP = "udp", @@ -37,6 +34,12 @@ export type Rule = { } export type RuleInfo = { + rules: Rule[] + policy: ActionType, + enabled: boolean +} + +export type RuleAddForm = { rules: Rule[] policy: ActionType } @@ -46,14 +49,19 @@ export type ServerResponseListed = { status:(ServerResponse & {rule_id:number})[]|string, } +export const rulesQueryKey = ["firewall","rules"] +export const firewallRulesQuery = () => useQuery({queryKey:rulesQueryKey, queryFn:firewall.rules}) export const firewall = { - stats: async () => { - return await getapi("firewall/stats") as GeneralStats; - }, rules: async() => { return await getapi("firewall/rules") as RuleInfo; }, + enable: async() => { + return await getapi("firewall/enable") as ServerResponse; + }, + disable: async() => { + return await getapi("firewall/disable") as ServerResponse; + }, rulenable: async (rule_id:number) => { return await getapi(`firewall/rule/${rule_id}/enable`) as ServerResponse; }, @@ -64,7 +72,7 @@ export const firewall = { const { status } = await postapi(`firewall/rule/${rule_id}/rename`,{ name }) as ServerResponse; return status === "ok"?undefined:status }, - servicesadd: async (data:RuleInfo) => { + servicesadd: async (data:RuleAddForm) => { return await postapi("firewall/rules/set", data) as ServerResponseListed; } } \ No newline at end of file diff --git a/frontend/src/components/Header/ResetModal.tsx b/frontend/src/components/Header/ResetModal.tsx index 177c6d3..b0e2fe4 100644 --- a/frontend/src/components/Header/ResetModal.tsx +++ b/frontend/src/components/Header/ResetModal.tsx @@ -1,6 +1,6 @@ import { Button, Group, Modal, Notification, Space, Switch, Text } from "@mantine/core"; import { useForm } from "@mantine/form"; -import React, { useState } from "react" +import { useState } from "react" import { ImCross } from "react-icons/im"; import { okNotify, resetfiregex } from "../../js/utils"; diff --git a/frontend/src/components/Header/ResetPasswordModal.tsx b/frontend/src/components/Header/ResetPasswordModal.tsx index c3adeb5..ad880d8 100644 --- a/frontend/src/components/Header/ResetPasswordModal.tsx +++ b/frontend/src/components/Header/ResetPasswordModal.tsx @@ -1,6 +1,6 @@ import { Button, Group, Modal, Notification, PasswordInput, Space, Switch } from "@mantine/core"; import { useForm } from "@mantine/form"; -import React, { useState } from "react" +import { useState } from "react" import { ImCross } from "react-icons/im"; import { ChangePassword } from "../../js/models"; import { changepassword, okNotify } from "../../js/utils"; diff --git a/frontend/src/components/MainLayout.tsx b/frontend/src/components/MainLayout.tsx index d5956e4..8705288 100644 --- a/frontend/src/components/MainLayout.tsx +++ b/frontend/src/components/MainLayout.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState } from 'react'; -import { ActionIcon, Container, Menu, Space, ThemeIcon } from '@mantine/core'; +import { useEffect, useState } from 'react'; +import { ActionIcon, Container, Menu, Space } from '@mantine/core'; import { AppShell } from '@mantine/core'; import NavBar from './NavBar'; import FooterPage from './Footer'; diff --git a/frontend/src/components/NFRegex/AddNewService.tsx b/frontend/src/components/NFRegex/AddNewService.tsx index 928f1bd..4aef819 100644 --- a/frontend/src/components/NFRegex/AddNewService.tsx +++ b/frontend/src/components/NFRegex/AddNewService.tsx @@ -1,6 +1,6 @@ import { Button, Group, Space, TextInput, Notification, Modal, Switch, SegmentedControl } from '@mantine/core'; import { useForm } from '@mantine/form'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { okNotify, regex_ipv4, regex_ipv6 } from '../../js/utils'; import { ImCross } from "react-icons/im" import { nfregex } from './utils'; diff --git a/frontend/src/components/NFRegex/ServiceRow/RenameForm.tsx b/frontend/src/components/NFRegex/ServiceRow/RenameForm.tsx index ea71c43..ef44830 100644 --- a/frontend/src/components/NFRegex/ServiceRow/RenameForm.tsx +++ b/frontend/src/components/NFRegex/ServiceRow/RenameForm.tsx @@ -1,6 +1,6 @@ import { Button, Group, Space, TextInput, Notification, Modal } from '@mantine/core'; import { useForm } from '@mantine/form'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { okNotify } from '../../../js/utils'; import { ImCross } from "react-icons/im" import { nfregex, Service } from '../utils'; diff --git a/frontend/src/components/NFRegex/ServiceRow/index.tsx b/frontend/src/components/NFRegex/ServiceRow/index.tsx index 4433a16..dff4293 100644 --- a/frontend/src/components/NFRegex/ServiceRow/index.tsx +++ b/frontend/src/components/NFRegex/ServiceRow/index.tsx @@ -1,7 +1,7 @@ import { ActionIcon, Badge, Divider, Grid, MediaQuery, Menu, Space, Title, Tooltip } from '@mantine/core'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { FaPlay, FaStop } from 'react-icons/fa'; -import { nfregex, Service } from '../utils'; +import { nfregex, Service, serviceQueryKey } from '../utils'; import { MdOutlineArrowForwardIos } from "react-icons/md" import style from "./index.module.scss"; import YesNoModal from '../../YesNoModal'; @@ -10,6 +10,7 @@ import { BsTrashFill } from 'react-icons/bs'; import { BiRename } from 'react-icons/bi' import RenameForm from './RenameForm'; import { MenuDropDownWithButton } from '../../MainLayout'; +import { useQueryClient } from '@tanstack/react-query'; function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) { @@ -19,6 +20,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) case "active": status_color = "teal"; break; } + const queryClient = useQueryClient() const [buttonLoading, setButtonLoading] = useState(false) const [tooltipStopOpened, setTooltipStopOpened] = useState(false); const [deleteModal, setDeleteModal] = useState(false) @@ -30,6 +32,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) await nfregex.servicestop(service.service_id).then(res => { if(!res){ okNotify(`Service ${service.name} stopped successfully!`,`The service on ${service.port} has been stopped!`) + queryClient.invalidateQueries(serviceQueryKey) }else{ errorNotify(`An error as occurred during the stopping of the service ${service.port}`,`Error: ${res}`) } @@ -44,6 +47,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) await nfregex.servicestart(service.service_id).then(res => { if(!res){ okNotify(`Service ${service.name} started successfully!`,`The service on ${service.port} has been started!`) + queryClient.invalidateQueries(serviceQueryKey) }else{ errorNotify(`An error as occurred during the starting of the service ${service.port}`,`Error: ${res}`) } @@ -57,6 +61,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) nfregex.servicedelete(service.service_id).then(res => { if (!res){ okNotify("Service delete complete!",`The service ${service.name} has been deleted!`) + queryClient.invalidateQueries(serviceQueryKey) }else errorNotify("An error occurred while deleting a service",`Error: ${res}`) }).catch(err => { diff --git a/frontend/src/components/NFRegex/utils.ts b/frontend/src/components/NFRegex/utils.ts index 4d6c309..cdf35f8 100644 --- a/frontend/src/components/NFRegex/utils.ts +++ b/frontend/src/components/NFRegex/utils.ts @@ -1,12 +1,7 @@ import { RegexFilter, ServerResponse } from "../../js/models" import { getapi, postapi } from "../../js/utils" import { RegexAddForm } from "../../js/models" - -export type GeneralStats = { - services:number, - closed:number, - regexes:number -} +import { useQuery, useQueryClient } from "@tanstack/react-query" export type Service = { name:string, @@ -31,12 +26,16 @@ export type ServiceAddResponse = { service_id?: string, } +export const serviceQueryKey = ["nfregex","services"] +export const statsQueryKey = ["nfregex","stats"] +export const nfregexServiceQuery = () => useQuery({queryKey:serviceQueryKey, queryFn:nfregex.services}) +export const nfregexServiceRegexesQuery = (service_id:string) => useQuery({ + queryKey:[...serviceQueryKey,service_id,"regexes"], + queryFn:() => nfregex.serviceregexes(service_id) +}) export const nfregex = { - stats: async () => { - return await getapi("nfregex/stats") as GeneralStats; - }, services: async () => { return await getapi("nfregex/services") as Service[]; }, diff --git a/frontend/src/components/NavBar/index.tsx b/frontend/src/components/NavBar/index.tsx index c340747..e322621 100644 --- a/frontend/src/components/NavBar/index.tsx +++ b/frontend/src/components/NavBar/index.tsx @@ -1,4 +1,4 @@ -import { Box, Collapse, Divider, Group, MantineColor, Navbar, ScrollArea, Text, ThemeIcon, Title, UnstyledButton } from "@mantine/core"; +import { Collapse, Divider, Group, MantineColor, Navbar, ScrollArea, Text, ThemeIcon, Title, UnstyledButton } from "@mantine/core"; import { useState } from "react"; import { IoMdGitNetwork } from "react-icons/io"; import { MdOutlineExpandLess, MdOutlineExpandMore, MdTransform } from "react-icons/md"; @@ -37,10 +37,8 @@ function NavBarButton({ navigate, closeNav, name, icon, color, disabled, onClick } export default function NavBar({ closeNav, opened }: {closeNav: () => void, opened: boolean}) { - const [toggleState, setToggleState] = useState(false); - const advancedPaths = ["regexproxy"] - const advancedSelected = advancedPaths.includes(getmainpath()) - const toggle = (toggleState||advancedSelected) + const [toggle, setToggleState] = useState(false); + return