From c0ea0eb331dee0997b6b3197e26a6e8adbf8c598 Mon Sep 17 00:00:00 2001 From: Domingo Dirutigliano Date: Mon, 25 Sep 2023 18:10:12 +0200 Subject: [PATCH] add/fix: frontend and backend improves --- backend/routers/firewall.py | 16 +- backend/routers/nfregex.py | 14 +- frontend/package.json | 1 - .../Firewall/ActionTypeSelector.tsx | 1 + .../src/components/Firewall/ModeSelector.tsx | 5 +- .../components/Firewall/ProtocolSelector.tsx | 1 + frontend/src/components/OnOffButton.tsx | 16 + frontend/src/components/PortInput.tsx | 28 +- frontend/src/js/utils.tsx | 16 +- .../pages/Firewall/DndListHandle.module.scss | 24 -- frontend/src/pages/Firewall/index.tsx | 319 ++++++++++++------ 11 files changed, 264 insertions(+), 177 deletions(-) create mode 100644 frontend/src/components/OnOffButton.tsx delete mode 100644 frontend/src/pages/Firewall/DndListHandle.module.scss diff --git a/backend/routers/firewall.py b/backend/routers/firewall.py index e415c21..3af7b6d 100644 --- a/backend/routers/firewall.py +++ b/backend/routers/firewall.py @@ -117,24 +117,24 @@ def parse_and_check_rule(rule:RuleModel): rule.ip_src = ip_parse(rule.ip_src) rule.ip_dst = ip_parse(rule.ip_dst) except ValueError: - return {"status":"Invalid address"} + raise HTTPException(status_code=400, detail="Invalid address") + if ip_family(rule.ip_dst) != ip_family(rule.ip_src): + raise HTTPException(status_code=400, detail="Destination and source addresses must be of the same family") rule.port_dst_from, rule.port_dst_to = min(rule.port_dst_from, rule.port_dst_to), max(rule.port_dst_from, rule.port_dst_to) rule.port_src_from, rule.port_src_to = min(rule.port_src_from, rule.port_src_to), max(rule.port_src_from, rule.port_src_to) - - if ip_family(rule.ip_dst) != ip_family(rule.ip_src): - return {"status":"Destination and source addresses must be of the same family"} + if rule.proto not in ["tcp", "udp", "any"]: - return {"status":"Invalid protocol"} + raise HTTPException(status_code=400, detail="Invalid protocol") if rule.action not in ["accept", "drop", "reject"]: - return {"status":"Invalid action"} + raise HTTPException(status_code=400, detail="Invalid action") return rule @app.post('/rules/set', response_model=RuleAddResponse) async def add_new_service(form: RuleFormAdd): """Add a new service""" if form.policy not in ["accept", "drop", "reject"]: - return {"status": "Invalid policy"} + raise HTTPException(status_code=400, detail="Invalid policy") rules = [parse_and_check_rule(ele) for ele in form.rules] errors = [({"rule":i} | ele) for i, ele in enumerate(rules) if isinstance(ele, dict)] if len(errors) > 0: @@ -160,5 +160,5 @@ async def add_new_service(form: RuleFormAdd): ) db.set("POLICY", form.policy) except sqlite3.IntegrityError: - return {'status': 'Error saving the rules: maybe there are duplicated rules'} + raise HTTPException(status_code=400, detail="Error saving the rules: maybe there are duplicated rules") return await apply_changes() diff --git a/backend/routers/nfregex.py b/backend/routers/nfregex.py index 78da678..8e66e47 100644 --- a/backend/routers/nfregex.py +++ b/backend/routers/nfregex.py @@ -174,11 +174,11 @@ async def service_delete(service_id: str): async def service_rename(service_id: str, form: RenameForm): """Request to change the name of a specific service""" form.name = refactor_name(form.name) - if not form.name: return {'status': 'The name cannot be empty!'} + if not form.name: raise HTTPException(status_code=400, detail="The name cannot be empty!") try: db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id) except sqlite3.IntegrityError: - return {'status': 'This name is already used'} + raise HTTPException(status_code=400, detail="This name is already used") await refresh_frontend() return {'status': 'ok'} @@ -242,12 +242,12 @@ async def add_new_regex(form: RegexAddForm): try: re.compile(b64decode(form.regex)) except Exception: - return {"status":"Invalid regex"} + raise HTTPException(status_code=400, detail="Invalid regex") try: db.query("INSERT INTO regexes (service_id, regex, is_blacklist, mode, is_case_sensitive, active ) VALUES (?, ?, ?, ?, ?, ?);", form.service_id, form.regex, form.is_blacklist, form.mode, form.is_case_sensitive, True if form.active is None else form.active ) except sqlite3.IntegrityError: - return {'status': 'An identical regex already exists'} + raise HTTPException(status_code=400, detail="An identical regex already exists") await firewall.get(form.service_id).update_filters() await refresh_frontend() @@ -259,16 +259,16 @@ async def add_new_service(form: ServiceAddForm): try: form.ip_int = ip_parse(form.ip_int) except ValueError: - return {"status":"Invalid address"} + raise HTTPException(status_code=400, detail="Invalid address") if form.proto not in ["tcp", "udp"]: - return {"status":"Invalid protocol"} + raise HTTPException(status_code=400, detail="Invalid protocol") srv_id = None try: srv_id = gen_service_id() db.query("INSERT INTO services (service_id ,name, port, status, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?)", srv_id, refactor_name(form.name), form.port, STATUS.STOP, form.proto, form.ip_int) except sqlite3.IntegrityError: - return {'status': 'This type of service already exists'} + raise HTTPException(status_code=400, detail="This type of service already exists") await firewall.reload() await refresh_frontend() return {'status': 'ok', 'service_id': srv_id} diff --git a/frontend/package.json b/frontend/package.json index 44f4f96..1db4e33 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,6 @@ "@types/react": "^18.0.12", "@types/react-dom": "^18.0.5", "buffer": "^6.0.3", - "clsx": "^2.0.0", "react": "^18.1.0", "react-dom": "^18.1.0", "react-icons": "^4.4.0", diff --git a/frontend/src/components/Firewall/ActionTypeSelector.tsx b/frontend/src/components/Firewall/ActionTypeSelector.tsx index 5230e99..cc85825 100644 --- a/frontend/src/components/Firewall/ActionTypeSelector.tsx +++ b/frontend/src/components/Firewall/ActionTypeSelector.tsx @@ -18,6 +18,7 @@ export const ActionTypeSelector = (props:Omit) => label: 'Drop', } ]} + size={props.size?props.size:"xs"} {...props} /> ) \ No newline at end of file diff --git a/frontend/src/components/Firewall/ModeSelector.tsx b/frontend/src/components/Firewall/ModeSelector.tsx index b6a18d2..2efff3e 100644 --- a/frontend/src/components/Firewall/ModeSelector.tsx +++ b/frontend/src/components/Firewall/ModeSelector.tsx @@ -8,13 +8,14 @@ export const ModeSelector = (props:Omit) => ( data={[ { value: RuleMode.IN, - label: 'IN', + label: 'Inbound', }, { value: RuleMode.OUT, - label: 'OUT', + label: 'Outbound', } ]} + size={props.size?props.size:"xs"} {...props} /> ) \ No newline at end of file diff --git a/frontend/src/components/Firewall/ProtocolSelector.tsx b/frontend/src/components/Firewall/ProtocolSelector.tsx index bd91f91..8aaa749 100644 --- a/frontend/src/components/Firewall/ProtocolSelector.tsx +++ b/frontend/src/components/Firewall/ProtocolSelector.tsx @@ -18,6 +18,7 @@ export const ProtocolSelector = (props:Omit) => ( label: 'ANY', } ]} + size={props.size?props.size:"xs"} {...props} /> ) \ No newline at end of file diff --git a/frontend/src/components/OnOffButton.tsx b/frontend/src/components/OnOffButton.tsx new file mode 100644 index 0000000..8ba91c0 --- /dev/null +++ b/frontend/src/components/OnOffButton.tsx @@ -0,0 +1,16 @@ +import { ActionIcon, ActionIconProps } from "@mantine/core" +import { ImCross } from "react-icons/im" +import { TiTick } from "react-icons/ti" +import {PolymorphicComponentProps} from "@mantine/utils" + + + +interface IOnOffButtonProps extends Omit, "value">{ + value: boolean, +} + +export const OnOffButton = ({value, ...props}:IOnOffButtonProps) => { + return + {value?:} + +} \ No newline at end of file diff --git a/frontend/src/components/PortInput.tsx b/frontend/src/components/PortInput.tsx index 01b63fa..2b965b2 100644 --- a/frontend/src/components/PortInput.tsx +++ b/frontend/src/components/PortInput.tsx @@ -1,21 +1,11 @@ import { Input, NumberInput, NumberInputProps, TextInput, TextInputProps } from "@mantine/core" import React, { useState } from "react" +import { regex_port, regex_range_port } from "../js/utils" interface PortInputProps extends NumberInputProps { fullWidth?: boolean } -const valueParse = (raw_value:string, oldValue:string = "") => { - const value = parseInt(raw_value) - if (value > 65535) { - return oldValue - } else if (value < 1) { - return oldValue - }else{ - return value.toString() - } -} - const PortInput = React.forwardRef( (props, ref) => { const [oldValue, setOldValue] = useState(props.defaultValue?props.defaultValue.toString():"") @@ -29,8 +19,7 @@ const PortInput = React.forwardRef( (props, r style={fullWidth?props.style:{ width: "75px", ...props.style }} onInput={(e) => { const target = e.target as HTMLInputElement - target.value = target.value?valueParse(target.value, oldValue):"" - setOldValue(target.value) + target.value.match(regex_port)?setOldValue(target.value):target.value = oldValue props.onInput?.(e) }} ref={ref} @@ -46,10 +35,7 @@ interface PortRangeInputProps extends TextInputProps { } export const PortRangeInput = React.forwardRef( (props, ref) => { - const oldValueStates = [ - useState(props.defaultValue?props.defaultValue?.toString().split("-")[0]:""), - useState(props.defaultValue?.toString().split("-")[1]) - ] + const [oldValue, setOldValue] = useState(props.defaultValue?props.defaultValue.toString():"") const {fullWidth, defaultValues, ...propeties} = props let defaultValuesInt = defaultValues if (defaultValuesInt?.length == 2 && defaultValuesInt[0] == defaultValuesInt[1]){ @@ -62,13 +48,7 @@ export const PortRangeInput = React.forwardRef { const target = e.target as HTMLInputElement - const splitted = target.value.split("-") - const parsedValues = [splitted[0], splitted[1]].map((value, index) => { - const res = value?valueParse(value,oldValueStates[index][0]):value - if (res != oldValueStates[index][0]) oldValueStates[index][1](value) - return res - }) - target.value = parsedValues.filter((v, i) => (v !== undefined && (i != 0 || v !== ""))).join("-") + target.value.match(regex_range_port)?setOldValue(target.value):target.value = oldValue props.onInput?.(e) }} ref={ref} diff --git a/frontend/src/js/utils.tsx b/frontend/src/js/utils.tsx index 7d9a59a..3bd14e6 100644 --- a/frontend/src/js/utils.tsx +++ b/frontend/src/js/utils.tsx @@ -14,7 +14,8 @@ export const regex_ipv6 = "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0 export const regex_ipv6_no_cidr = "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*$"; export const regex_ipv4 = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(3[0-2]|[1-2][0-9]|[0-9]))?$" export const regex_ipv4_no_cidr = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" - +export const regex_port = "^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?$" +export const regex_range_port = "^(([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(-([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?)?)?$" export const DEV_IP_BACKEND = "192.168.231.3:4444" export const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -45,6 +46,7 @@ export async function getapi(path:string):Promise{ export function getErrorMessage(e: any) { let error = "Unknown error"; + if(typeof e == "string") return e if (e.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx @@ -166,6 +168,18 @@ export function okNotify(title:string, description:string ){ }); } +export const makeid = (length:number) => { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + let counter = 0; + while (counter < length) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + counter += 1; + } + return result; +} + export function b64encode(data:number[]|string){ return Buffer.from(data).toString('base64') } diff --git a/frontend/src/pages/Firewall/DndListHandle.module.scss b/frontend/src/pages/Firewall/DndListHandle.module.scss deleted file mode 100644 index c9fc138..0000000 --- a/frontend/src/pages/Firewall/DndListHandle.module.scss +++ /dev/null @@ -1,24 +0,0 @@ -.item { - display: flex; - align-items: center; - border-radius: var(--mantine-radius-md); - border: rem(1px) solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5)); - padding: var(--mantine-spacing-sm) var(--mantine-spacing-xl); - padding-left: calc(var(--mantine-spacing-xl) - var(--mantine-spacing-md)); - background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-5)); - margin-bottom: var(--mantine-spacing-sm); -} - -.itemDragging { - box-shadow: var(--mantine-shadow-sm); -} - -.dragHandle { - display: flex; - align-items: center; - justify-content: center; - height: 100%; - color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-1)); - padding-left: var(--mantine-spacing-md); - padding-right: var(--mantine-spacing-md); -} \ No newline at end of file diff --git a/frontend/src/pages/Firewall/index.tsx b/frontend/src/pages/Firewall/index.tsx index 5c39e6a..3b5eaea 100644 --- a/frontend/src/pages/Firewall/index.tsx +++ b/frontend/src/pages/Firewall/index.tsx @@ -1,14 +1,12 @@ -import { ActionIcon, Badge, LoadingOverlay, Space, Switch, Title, Tooltip } from "@mantine/core" +import { ActionIcon, Badge, Button, LoadingOverlay, Space, Switch, TextInput, Title, Tooltip } from "@mantine/core" import { useEffect, useState } from "react"; -import { BsPlusLg } from "react-icons/bs" -import { rem, Text } from '@mantine/core'; -import { ActionType, Rule, firewall, firewallRulesQuery } from "../../components/Firewall/utils"; -import cx from 'clsx' -import { errorNotify, getErrorMessage, okNotify } from "../../js/utils"; +import { BsPlusLg, BsTrashFill } from "react-icons/bs" +import { rem } from '@mantine/core'; +import { ActionType, Protocol, Rule, RuleMode, firewall, firewallRulesQuery } from "../../components/Firewall/utils"; +import { errorNotify, getErrorMessage, makeid, okNotify } from "../../js/utils"; import { useListState } from '@mantine/hooks'; import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'; import { TbGripVertical, TbReload } from "react-icons/tb"; -import classes from './DndListHandle.module.scss'; import { useQueryClient } from "@tanstack/react-query"; import { TiTick } from "react-icons/ti"; import YesNoModal from "../../components/YesNoModal"; @@ -17,80 +15,28 @@ import { InterfaceInput } from "../../components/InterfaceInput"; import { ActionTypeSelector } from "../../components/Firewall/ActionTypeSelector"; import { ProtocolSelector } from "../../components/Firewall/ProtocolSelector"; import { ModeSelector } from "../../components/Firewall/ModeSelector"; +import { OnOffButton } from "../../components/OnOffButton"; +import { LuArrowBigRightDash } from "react-icons/lu" - /* - { - "rules": [ - { - "active": true, - "name": "R1", - "proto": "tcp", - "ip_src": "0.0.0.0/0", - "ip_dst": "0.0.0.0/0", - "port_src_from": 1, - "port_dst_from": 3030, - "port_src_to": 65535, - "port_dst_to": 3030, - "action": "reject", - "mode": "I" - }, - { - "active": true, - "name": "R2", - "proto": "tcp", - "ip_src": "0.0.0.0/0", - "ip_dst": "0.0.0.0/0", - "port_src_from": 1, - "port_dst_from": 3030, - "port_src_to": 65535, - "port_dst_to": 3030, - "action": "drop", - "mode": "O" - }, - { - "active": false, - "name": "R3", - "proto": "udp", - "ip_src": "192.168.0.1/24", - "ip_dst": "0.0.0.0/0", - "port_src_from": 1, - "port_dst_from": 2020, - "port_src_to": 65535, - "port_dst_to": 2020, - "action": "drop", - "mode": "I" - }, - { - "active": true, - "name": "R4", - "proto": "any", - "ip_src": "::/0", - "ip_dst": "fdfd::ffff:123/64", - "port_src_from": 1, - "port_dst_from": 1, - "port_src_to": 1, - "port_dst_to": 1, - "action": "accept", - "mode": "I" - } - ], - "policy": "accept" -} - */ export const Firewall = () => { const [tooltipAddOpened, setTooltipAddOpened] = useState(false); const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false); const [tooltipApplyOpened, setTooltipApplyOpened] = useState(false); - const [open, setOpen] = useState(false); const [currentPolicy, setCurrentPolicy] = useState(ActionType.ACCEPT) + const [tooltipAddRulOpened, setTooltipAddRulOpened] = useState(false) const queryClient = useQueryClient() const rules = firewallRulesQuery() - const [state, handlers] = useListState([]); + const [state, handlers] = useListState([]); const [enableFwModal, setEnableFwModal] = useState(false) const [applyChangeModal, setApplyChangeModal] = useState(false) + const [updateMevalueinternal, internalUpdateme] = useState(false) + const updateMe = () => { + internalUpdateme(!updateMevalueinternal) + } + useEffect(()=> { if(rules.isError) errorNotify("Firewall Update failed!", getErrorMessage(rules.error)) @@ -99,12 +45,15 @@ export const Firewall = () => { useEffect(()=> { if(!rules.isLoading && rules.isFetched && !rules.isFetching){ setCurrentPolicy(rules.data?.policy??ActionType.ACCEPT) - handlers.setState(JSON.parse(JSON.stringify(rules.data?.rules??[]))) + handlers.setState(JSON.parse(JSON.stringify((rules.data?.rules??[]).map( v => ({rule_id: makeid(30), ...v}))))) } },[rules.isFetched, rules.isLoading, rules.isFetching]) const fwEnabled = rules.data?.enabled??false - const valuesChanged = JSON.stringify(rules.data?.rules) != JSON.stringify(state) || rules.data?.policy != currentPolicy + const valuesChanged = JSON.stringify(rules.data?.rules) != JSON.stringify(state.map(v => { + const {rule_id, ...rest} = v + return rest + })) || rules.data?.policy != currentPolicy const enableFirewall = () => { if (valuesChanged){ @@ -145,49 +94,189 @@ export const Firewall = () => { } } - const applyChangesRaw = () => { - return firewall.ruleset({rules:state, policy:currentPolicy}) - .then(()=>okNotify("Firewall rules applied", "The firewall rules has been applied")) - .catch((e)=>errorNotify("Firewall rules apply failed!", getErrorMessage(e))) + const emptyRuleAdd = () => { + handlers.insert(0,{ + rule_id: makeid(30), + active: true, + name: "Rule name", + proto: Protocol.TCP, + ip_src: "any", + ip_dst: "any", + port_src_from: 1, + port_dst_from: 8080, + port_src_to: 65535, + port_dst_to: 8080, + action: ActionType.ACCEPT, + mode: RuleMode.IN + }) } + const parseRules = () => { + return state + } + + const applyChangesRaw = () => { + const parsedRules = parseRules() + if (parsedRules === undefined){ + errorNotify("Firewall rules apply failed!", "The firewall rules are not valid") + return Promise.reject() + }else{ + return firewall.ruleset({rules:state, policy:currentPolicy}) + .then(()=>okNotify("Firewall rules applied", "The firewall rules has been applied")) + .catch((e)=>errorNotify("Firewall rules apply failed!", getErrorMessage(e))) + } + } + + + const items = state.map((item, index) => ( - + {(provided, snapshot) => { const customInt = [ { value: "0.0.0.0/0", netint: "ANY IPv4", label: "0.0.0.0/0" }, { value: "::/0", netint: "ANY IPv6", label: "::/0" } ] - const ip_layer_not_filtered = item.ip_dst == "any" || item.ip_src == "any" - const src_custom_int = customInt.map(v => v.value).includes(item.ip_src)?[]:[{ value: item.ip_src, netint: "SELECTED", label: item.ip_src }] - const dst_custom_int = customInt.map(v => v.value).includes(item.ip_dst)?[]:[{ value: item.ip_src, netint: "SELECTED", label: item.ip_dst }] + const src_custom_int = customInt.map(v => v.value).includes(item.ip_src) || item.ip_dst == "any"?[]:[{ value: item.ip_src, netint: "SELECTED", label: item.ip_src }] + const dst_custom_int = customInt.map(v => v.value).includes(item.ip_dst) || item.ip_dst == "any"?[]:[{ value: item.ip_dst, netint: "SELECTED", label: item.ip_dst }] + const [srcPortEnabled, setSrcPortEnabled] = useState(item.port_src_from != 1 || item.port_src_to != 65535) + const [dstPortEnabled, setDstPortEnabled] = useState(item.port_dst_from != 1 || item.port_dst_to != 65535) + const [srcPortValue, setSrcPortValue] = useState(item.port_src_from==item.port_src_to?`${item.port_src_from}`:`${item.port_src_from}-${item.port_src_to}`) + const [dstPortValue, setDstPortValue] = useState(item.port_dst_from==item.port_dst_to?`${item.port_dst_from}`:`${item.port_dst_from}-${item.port_dst_to}`) + const [ipFilteringEnabled, setIpFilteringEnabled] = useState(!(item.ip_dst == "any" || item.ip_src == "any")) + + const [srcIp, setSrcIp] = useState(item.ip_src!="any"?item.ip_src:"") + const [dstIp, setDstIp] = useState(item.ip_dst!="any"?item.ip_dst:"") + + const port_range_setter = (rule:Rule, v:string, {src=false, dst=false}:{src?:boolean, dst?:boolean}) => { + const elements = v.split("-") + const values = [elements[0]?parseInt(elements[0]):0, elements[1]?parseInt(elements[1]):0] + values[1] = values[1]?values[1]:values[0] + if (src){ + rule.port_src_from = values[0] + rule.port_src_to = values[1] + setSrcPortValue(v) + } + if (dst){ + rule.port_dst_from = values[0] + rule.port_dst_to = values[1] + setDstPortValue(v) + } + updateMe() + } + + const ip_setter = (rule:Rule, v:string|null, {src=false, dst=false}:{src?:boolean, dst?:boolean}) => { + const values = v?v:"" + if (src){ + rule.ip_src = values + setSrcIp(values) + } + if (dst){ + rule.ip_dst = values + setDstIp(values) + } + updateMe() + } + + const set_filtering_ip = (value:boolean) => { + if (!value){ + item.ip_src = "any" + item.ip_dst = "any" + }else{ + item.ip_src = srcIp + item.ip_dst = dstIp + } + setIpFilteringEnabled(value) + updateMe() + } + + const proto_any = item.proto == Protocol.ANY + return
-
- +
+
+
- -
- {item.name} - - - - - - +
+
+ { + item.active = !item.active + updateMe() + }} size="lg" variant="filled" radius="md" /> + + handlers.remove(index)} size="lg" radius="md" variant="filled"> + + {item.name = v.target.value;updateMe()}} style={{width:"100%"}}/> +
+ +
+
+ ip_setter(item, v, {src:true})} disabled={!ipFilteringEnabled} /> + +
+ { + const value = !srcPortEnabled + setSrcPortEnabled(value) + if (!value){ + item.port_src_from = 1 + item.port_src_to = 65535 + updateMe() + }else{ + port_range_setter(item, srcPortValue, {src:true}) + } + }} size="lg" disabled={proto_any} variant="light" /> + + port_range_setter(item, v.target.value, {src:true})} value={srcPortValue} disabled={!srcPortEnabled || proto_any} style={{width:"100%"}} /> +
+
+ + + +
+ ip_setter(item, v, {dst:true})} disabled={!ipFilteringEnabled} /> + +
+ { + const value = !dstPortEnabled + setDstPortEnabled(value) + if (!value){ + item.port_dst_from = 1 + item.port_dst_to = 65535 + updateMe() + }else{ + port_range_setter(item, dstPortValue, {dst:true}) + } + }} size="lg" disabled={proto_any} variant="light" /> + + port_range_setter(item, v.target.value, {dst:true})} value={dstPortValue} disabled={!dstPortEnabled || proto_any} style={{width:"100%"}} /> +
+
+
+
+
+ {item.action = value as ActionType;updateMe()}} + /> + {item.mode = value as RuleMode;updateMe()}} /> - Filter IP Layer: + + {item.proto = value as Protocol;updateMe()}} + /> + + +
+
+
}} @@ -212,7 +301,7 @@ export const Firewall = () => { Rules: {rules.isLoading?0:rules.data?.rules.length} - setOpen(true)} size="lg" radius="md" variant="filled" + setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> @@ -234,20 +323,30 @@ export const Firewall = () => {
- - handlers.reorder({ from: source.index, to: destination?.index || 0 }) - } - > - - {(provided) => ( -
- {items} - {provided.placeholder} -
- )} -
-
+ {items.length > 0? + handlers.reorder({ from: source.index, to: destination?.index || 0 }) + } + > + + {(provided) => ( +
+ {items} + {provided.placeholder} +
+ )} +
+
:<> + No rule found! Add one clicking the "+" buttons + +
+ + setTooltipAddRulOpened(false)} onBlur={() => setTooltipAddRulOpened(false)} + onMouseEnter={() => setTooltipAddRulOpened(true)} onMouseLeave={() => setTooltipAddRulOpened(false)}> + +
+}