add/fix: frontend and backend improves
This commit is contained in:
@@ -117,24 +117,24 @@ def parse_and_check_rule(rule:RuleModel):
|
|||||||
rule.ip_src = ip_parse(rule.ip_src)
|
rule.ip_src = ip_parse(rule.ip_src)
|
||||||
rule.ip_dst = ip_parse(rule.ip_dst)
|
rule.ip_dst = ip_parse(rule.ip_dst)
|
||||||
except ValueError:
|
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_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)
|
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"]:
|
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"]:
|
if rule.action not in ["accept", "drop", "reject"]:
|
||||||
return {"status":"Invalid action"}
|
raise HTTPException(status_code=400, detail="Invalid action")
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
@app.post('/rules/set', response_model=RuleAddResponse)
|
@app.post('/rules/set', response_model=RuleAddResponse)
|
||||||
async def add_new_service(form: RuleFormAdd):
|
async def add_new_service(form: RuleFormAdd):
|
||||||
"""Add a new service"""
|
"""Add a new service"""
|
||||||
if form.policy not in ["accept", "drop", "reject"]:
|
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]
|
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)]
|
errors = [({"rule":i} | ele) for i, ele in enumerate(rules) if isinstance(ele, dict)]
|
||||||
if len(errors) > 0:
|
if len(errors) > 0:
|
||||||
@@ -160,5 +160,5 @@ async def add_new_service(form: RuleFormAdd):
|
|||||||
)
|
)
|
||||||
db.set("POLICY", form.policy)
|
db.set("POLICY", form.policy)
|
||||||
except sqlite3.IntegrityError:
|
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()
|
return await apply_changes()
|
||||||
|
|||||||
@@ -174,11 +174,11 @@ async def service_delete(service_id: str):
|
|||||||
async def service_rename(service_id: str, form: RenameForm):
|
async def service_rename(service_id: str, form: RenameForm):
|
||||||
"""Request to change the name of a specific service"""
|
"""Request to change the name of a specific service"""
|
||||||
form.name = refactor_name(form.name)
|
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:
|
try:
|
||||||
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
|
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
|
||||||
except sqlite3.IntegrityError:
|
except sqlite3.IntegrityError:
|
||||||
return {'status': 'This name is already used'}
|
raise HTTPException(status_code=400, detail="This name is already used")
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@@ -242,12 +242,12 @@ async def add_new_regex(form: RegexAddForm):
|
|||||||
try:
|
try:
|
||||||
re.compile(b64decode(form.regex))
|
re.compile(b64decode(form.regex))
|
||||||
except Exception:
|
except Exception:
|
||||||
return {"status":"Invalid regex"}
|
raise HTTPException(status_code=400, detail="Invalid regex")
|
||||||
try:
|
try:
|
||||||
db.query("INSERT INTO regexes (service_id, regex, is_blacklist, mode, is_case_sensitive, active ) VALUES (?, ?, ?, ?, ?, ?);",
|
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 )
|
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:
|
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 firewall.get(form.service_id).update_filters()
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
@@ -259,16 +259,16 @@ async def add_new_service(form: ServiceAddForm):
|
|||||||
try:
|
try:
|
||||||
form.ip_int = ip_parse(form.ip_int)
|
form.ip_int = ip_parse(form.ip_int)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return {"status":"Invalid address"}
|
raise HTTPException(status_code=400, detail="Invalid address")
|
||||||
if form.proto not in ["tcp", "udp"]:
|
if form.proto not in ["tcp", "udp"]:
|
||||||
return {"status":"Invalid protocol"}
|
raise HTTPException(status_code=400, detail="Invalid protocol")
|
||||||
srv_id = None
|
srv_id = None
|
||||||
try:
|
try:
|
||||||
srv_id = gen_service_id()
|
srv_id = gen_service_id()
|
||||||
db.query("INSERT INTO services (service_id ,name, port, status, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?)",
|
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)
|
srv_id, refactor_name(form.name), form.port, STATUS.STOP, form.proto, form.ip_int)
|
||||||
except sqlite3.IntegrityError:
|
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 firewall.reload()
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok', 'service_id': srv_id}
|
return {'status': 'ok', 'service_id': srv_id}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"@types/react": "^18.0.12",
|
"@types/react": "^18.0.12",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"clsx": "^2.0.0",
|
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-icons": "^4.4.0",
|
"react-icons": "^4.4.0",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const ActionTypeSelector = (props:Omit<SegmentedControlProps, "data">) =>
|
|||||||
label: 'Drop',
|
label: 'Drop',
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
size={props.size?props.size:"xs"}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -8,13 +8,14 @@ export const ModeSelector = (props:Omit<SegmentedControlProps, "data">) => (
|
|||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
value: RuleMode.IN,
|
value: RuleMode.IN,
|
||||||
label: 'IN',
|
label: 'Inbound',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: RuleMode.OUT,
|
value: RuleMode.OUT,
|
||||||
label: 'OUT',
|
label: 'Outbound',
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
size={props.size?props.size:"xs"}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -18,6 +18,7 @@ export const ProtocolSelector = (props:Omit<SegmentedControlProps, "data">) => (
|
|||||||
label: 'ANY',
|
label: 'ANY',
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
size={props.size?props.size:"xs"}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
16
frontend/src/components/OnOffButton.tsx
Normal file
16
frontend/src/components/OnOffButton.tsx
Normal file
@@ -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<PolymorphicComponentProps<"button",ActionIconProps>, "value">{
|
||||||
|
value: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OnOffButton = ({value, ...props}:IOnOffButtonProps) => {
|
||||||
|
return <ActionIcon color={props.color?props.color:(value?"green":"red")} {...props}>
|
||||||
|
{value?<TiTick size={20} />:<ImCross size={12} />}
|
||||||
|
</ActionIcon>
|
||||||
|
}
|
||||||
@@ -1,21 +1,11 @@
|
|||||||
import { Input, NumberInput, NumberInputProps, TextInput, TextInputProps } from "@mantine/core"
|
import { Input, NumberInput, NumberInputProps, TextInput, TextInputProps } from "@mantine/core"
|
||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
|
import { regex_port, regex_range_port } from "../js/utils"
|
||||||
|
|
||||||
interface PortInputProps extends NumberInputProps {
|
interface PortInputProps extends NumberInputProps {
|
||||||
fullWidth?: boolean
|
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<HTMLInputElement, PortInputProps>( (props, ref) => {
|
const PortInput = React.forwardRef<HTMLInputElement, PortInputProps>( (props, ref) => {
|
||||||
const [oldValue, setOldValue] = useState<string>(props.defaultValue?props.defaultValue.toString():"")
|
const [oldValue, setOldValue] = useState<string>(props.defaultValue?props.defaultValue.toString():"")
|
||||||
@@ -29,8 +19,7 @@ const PortInput = React.forwardRef<HTMLInputElement, PortInputProps>( (props, r
|
|||||||
style={fullWidth?props.style:{ width: "75px", ...props.style }}
|
style={fullWidth?props.style:{ width: "75px", ...props.style }}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
const target = e.target as HTMLInputElement
|
const target = e.target as HTMLInputElement
|
||||||
target.value = target.value?valueParse(target.value, oldValue):""
|
target.value.match(regex_port)?setOldValue(target.value):target.value = oldValue
|
||||||
setOldValue(target.value)
|
|
||||||
props.onInput?.(e)
|
props.onInput?.(e)
|
||||||
}}
|
}}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -46,10 +35,7 @@ interface PortRangeInputProps extends TextInputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const PortRangeInput = React.forwardRef<HTMLInputElement, PortRangeInputProps>( (props, ref) => {
|
export const PortRangeInput = React.forwardRef<HTMLInputElement, PortRangeInputProps>( (props, ref) => {
|
||||||
const oldValueStates = [
|
const [oldValue, setOldValue] = useState<string>(props.defaultValue?props.defaultValue.toString():"")
|
||||||
useState<string>(props.defaultValue?props.defaultValue?.toString().split("-")[0]:""),
|
|
||||||
useState<string|undefined>(props.defaultValue?.toString().split("-")[1])
|
|
||||||
]
|
|
||||||
const {fullWidth, defaultValues, ...propeties} = props
|
const {fullWidth, defaultValues, ...propeties} = props
|
||||||
let defaultValuesInt = defaultValues
|
let defaultValuesInt = defaultValues
|
||||||
if (defaultValuesInt?.length == 2 && defaultValuesInt[0] == defaultValuesInt[1]){
|
if (defaultValuesInt?.length == 2 && defaultValuesInt[0] == defaultValuesInt[1]){
|
||||||
@@ -62,13 +48,7 @@ export const PortRangeInput = React.forwardRef<HTMLInputElement, PortRangeInput
|
|||||||
style={fullWidth?props.style:{ width: "150px", ...props.style }}
|
style={fullWidth?props.style:{ width: "150px", ...props.style }}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
const target = e.target as HTMLInputElement
|
const target = e.target as HTMLInputElement
|
||||||
const splitted = target.value.split("-")
|
target.value.match(regex_range_port)?setOldValue(target.value):target.value = oldValue
|
||||||
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("-")
|
|
||||||
props.onInput?.(e)
|
props.onInput?.(e)
|
||||||
}}
|
}}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -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_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 = "^(([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_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 DEV_IP_BACKEND = "192.168.231.3:4444"
|
||||||
|
|
||||||
export const queryClient = new QueryClient({ defaultOptions: { queries: {
|
export const queryClient = new QueryClient({ defaultOptions: { queries: {
|
||||||
@@ -45,6 +46,7 @@ export async function getapi(path:string):Promise<any>{
|
|||||||
|
|
||||||
export function getErrorMessage(e: any) {
|
export function getErrorMessage(e: any) {
|
||||||
let error = "Unknown error";
|
let error = "Unknown error";
|
||||||
|
if(typeof e == "string") return e
|
||||||
if (e.response) {
|
if (e.response) {
|
||||||
// The request was made and the server responded with a status code
|
// The request was made and the server responded with a status code
|
||||||
// that falls out of the range of 2xx
|
// 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){
|
export function b64encode(data:number[]|string){
|
||||||
return Buffer.from(data).toString('base64')
|
return Buffer.from(data).toString('base64')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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 { useEffect, useState } from "react";
|
||||||
import { BsPlusLg } from "react-icons/bs"
|
import { BsPlusLg, BsTrashFill } from "react-icons/bs"
|
||||||
import { rem, Text } from '@mantine/core';
|
import { rem } from '@mantine/core';
|
||||||
import { ActionType, Rule, firewall, firewallRulesQuery } from "../../components/Firewall/utils";
|
import { ActionType, Protocol, Rule, RuleMode, firewall, firewallRulesQuery } from "../../components/Firewall/utils";
|
||||||
import cx from 'clsx'
|
import { errorNotify, getErrorMessage, makeid, okNotify } from "../../js/utils";
|
||||||
import { errorNotify, getErrorMessage, okNotify } from "../../js/utils";
|
|
||||||
import { useListState } from '@mantine/hooks';
|
import { useListState } from '@mantine/hooks';
|
||||||
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
|
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
|
||||||
import { TbGripVertical, TbReload } from "react-icons/tb";
|
import { TbGripVertical, TbReload } from "react-icons/tb";
|
||||||
import classes from './DndListHandle.module.scss';
|
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { TiTick } from "react-icons/ti";
|
import { TiTick } from "react-icons/ti";
|
||||||
import YesNoModal from "../../components/YesNoModal";
|
import YesNoModal from "../../components/YesNoModal";
|
||||||
@@ -17,80 +15,28 @@ import { InterfaceInput } from "../../components/InterfaceInput";
|
|||||||
import { ActionTypeSelector } from "../../components/Firewall/ActionTypeSelector";
|
import { ActionTypeSelector } from "../../components/Firewall/ActionTypeSelector";
|
||||||
import { ProtocolSelector } from "../../components/Firewall/ProtocolSelector";
|
import { ProtocolSelector } from "../../components/Firewall/ProtocolSelector";
|
||||||
import { ModeSelector } from "../../components/Firewall/ModeSelector";
|
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 = () => {
|
export const Firewall = () => {
|
||||||
|
|
||||||
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
|
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
|
||||||
const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false);
|
const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false);
|
||||||
const [tooltipApplyOpened, setTooltipApplyOpened] = useState(false);
|
const [tooltipApplyOpened, setTooltipApplyOpened] = useState(false);
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [currentPolicy, setCurrentPolicy] = useState<ActionType>(ActionType.ACCEPT)
|
const [currentPolicy, setCurrentPolicy] = useState<ActionType>(ActionType.ACCEPT)
|
||||||
|
const [tooltipAddRulOpened, setTooltipAddRulOpened] = useState(false)
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const rules = firewallRulesQuery()
|
const rules = firewallRulesQuery()
|
||||||
const [state, handlers] = useListState<Rule>([]);
|
const [state, handlers] = useListState<Rule & {rule_id:string}>([]);
|
||||||
const [enableFwModal, setEnableFwModal] = useState(false)
|
const [enableFwModal, setEnableFwModal] = useState(false)
|
||||||
const [applyChangeModal, setApplyChangeModal] = useState(false)
|
const [applyChangeModal, setApplyChangeModal] = useState(false)
|
||||||
|
|
||||||
|
const [updateMevalueinternal, internalUpdateme] = useState(false)
|
||||||
|
const updateMe = () => {
|
||||||
|
internalUpdateme(!updateMevalueinternal)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(()=> {
|
||||||
if(rules.isError)
|
if(rules.isError)
|
||||||
errorNotify("Firewall Update failed!", getErrorMessage(rules.error))
|
errorNotify("Firewall Update failed!", getErrorMessage(rules.error))
|
||||||
@@ -99,12 +45,15 @@ export const Firewall = () => {
|
|||||||
useEffect(()=> {
|
useEffect(()=> {
|
||||||
if(!rules.isLoading && rules.isFetched && !rules.isFetching){
|
if(!rules.isLoading && rules.isFetched && !rules.isFetching){
|
||||||
setCurrentPolicy(rules.data?.policy??ActionType.ACCEPT)
|
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])
|
},[rules.isFetched, rules.isLoading, rules.isFetching])
|
||||||
|
|
||||||
const fwEnabled = rules.data?.enabled??false
|
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 = () => {
|
const enableFirewall = () => {
|
||||||
if (valuesChanged){
|
if (valuesChanged){
|
||||||
@@ -145,49 +94,189 @@ export const Firewall = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const applyChangesRaw = () => {
|
const emptyRuleAdd = () => {
|
||||||
return firewall.ruleset({rules:state, policy:currentPolicy})
|
handlers.insert(0,{
|
||||||
.then(()=>okNotify("Firewall rules applied", "The firewall rules has been applied"))
|
rule_id: makeid(30),
|
||||||
.catch((e)=>errorNotify("Firewall rules apply failed!", getErrorMessage(e)))
|
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) => (
|
const items = state.map((item, index) => (
|
||||||
<Draggable key={index} index={index} draggableId={index.toString()}>
|
<Draggable key={item.rule_id} index={index} draggableId={item.rule_id}>
|
||||||
{(provided, snapshot) => {
|
{(provided, snapshot) => {
|
||||||
const customInt = [
|
const customInt = [
|
||||||
{ value: "0.0.0.0/0", netint: "ANY IPv4", label: "0.0.0.0/0" },
|
{ value: "0.0.0.0/0", netint: "ANY IPv4", label: "0.0.0.0/0" },
|
||||||
{ value: "::/0", netint: "ANY IPv6", label: "::/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) || item.ip_dst == "any"?[]:[{ value: item.ip_src, netint: "SELECTED", label: item.ip_src }]
|
||||||
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) || item.ip_dst == "any"?[]:[{ value: item.ip_dst, netint: "SELECTED", label: item.ip_dst }]
|
||||||
const dst_custom_int = customInt.map(v => v.value).includes(item.ip_dst)?[]:[{ value: item.ip_src, 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 <div
|
return <div
|
||||||
className={cx(classes.item, { [classes.itemDragging]: snapshot.isDragging })}
|
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
>
|
>
|
||||||
<div {...provided.dragHandleProps} className={classes.dragHandle}>
|
<div className='center-flex' style={{width:"100%"}}>
|
||||||
<TbGripVertical style={{ width: rem(18), height: rem(18) }} />
|
<div {...provided.dragHandleProps}>
|
||||||
|
<TbGripVertical style={{ width: rem(30), height: rem(40) }} />
|
||||||
</div>
|
</div>
|
||||||
<Space w="sm" />
|
<Space w="sm" />
|
||||||
<Switch checked={item.active} />
|
<div className="center-flex-row" style={{width:"100%"}}>
|
||||||
<div>
|
<div className="center-flex" style={{width:"97%"}}>
|
||||||
<Text>{item.name}</Text>
|
<OnOffButton value={item.active} onClick={() =>{
|
||||||
<InterfaceInput initialCustomInterfaces={[...src_custom_int, ...customInt]} defaultValue={item.ip_src}/>
|
item.active = !item.active
|
||||||
<PortRangeInput defaultValues={[item.port_src_from, item.port_src_to]} />
|
updateMe()
|
||||||
<InterfaceInput initialCustomInterfaces={[...dst_custom_int, ...customInt]} defaultValue={item.ip_dst}/>
|
}} size="lg" variant="filled" radius="md" />
|
||||||
<PortRangeInput defaultValues={[item.port_dst_from, item.port_dst_to]} />
|
<Space w="sm" />
|
||||||
<ActionTypeSelector
|
<ActionIcon color="red" onClick={()=>handlers.remove(index)} size="lg" radius="md" variant="filled"><BsTrashFill size={18} /></ActionIcon>
|
||||||
value={item.action}
|
<Space w="sm" />
|
||||||
/>
|
<TextInput defaultValue={item.name} onChange={(v)=>{item.name = v.target.value;updateMe()}} style={{width:"100%"}}/>
|
||||||
<ProtocolSelector
|
</div>
|
||||||
value={item.proto}
|
<Space h="sm" />
|
||||||
/>
|
<div className="center-flex" style={{width:"97%"}}>
|
||||||
|
<div style={{width:"100%"}}>
|
||||||
|
<InterfaceInput initialCustomInterfaces={[...src_custom_int, ...customInt]} value={srcIp} onChange={v => ip_setter(item, v, {src:true})} disabled={!ipFilteringEnabled} />
|
||||||
|
<Space h="sm" />
|
||||||
|
<div className="center-flex" style={{width:"100%"}}>
|
||||||
|
<OnOffButton value={srcPortEnabled} onClick={() =>{
|
||||||
|
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" />
|
||||||
|
<Space w="xs" />
|
||||||
|
<PortRangeInput onChange={v => port_range_setter(item, v.target.value, {src:true})} value={srcPortValue} disabled={!srcPortEnabled || proto_any} style={{width:"100%"}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Space w="lg" />
|
||||||
|
<LuArrowBigRightDash size={100} />
|
||||||
|
<Space w="lg" />
|
||||||
|
<div style={{width:"100%"}}>
|
||||||
|
<InterfaceInput initialCustomInterfaces={[...dst_custom_int, ...customInt]} defaultValue={dstIp} onChange={v => ip_setter(item, v, {dst:true})} disabled={!ipFilteringEnabled} />
|
||||||
|
<Space h="sm" />
|
||||||
|
<div className="center-flex" style={{width:"100%"}}>
|
||||||
|
<OnOffButton value={dstPortEnabled} onClick={() =>{
|
||||||
|
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" />
|
||||||
|
<Space w="xs" />
|
||||||
|
<PortRangeInput onChange={v => port_range_setter(item, v.target.value, {dst:true})} value={dstPortValue} disabled={!dstPortEnabled || proto_any} style={{width:"100%"}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="center-flex-row">
|
||||||
|
<ActionTypeSelector
|
||||||
|
value={item.action}
|
||||||
|
onChange={(value)=>{item.action = value as ActionType;updateMe()}}
|
||||||
|
/>
|
||||||
|
<Space h="xs" />
|
||||||
<ModeSelector
|
<ModeSelector
|
||||||
value={item.mode}
|
value={item.mode}
|
||||||
|
onChange={(value)=>{item.mode = value as RuleMode;updateMe()}}
|
||||||
/>
|
/>
|
||||||
Filter IP Layer: <Switch checked={!ip_layer_not_filtered} />
|
<Space h="xs" />
|
||||||
|
<ProtocolSelector
|
||||||
|
value={item.proto}
|
||||||
|
onChange={(value)=>{item.proto = value as Protocol;updateMe()}}
|
||||||
|
/>
|
||||||
|
<Space h="xs" />
|
||||||
|
|
||||||
|
<Button size="xs" variant="light" color={ipFilteringEnabled?"lime":"red"} onClick={()=>set_filtering_ip(!ipFilteringEnabled)} >{ipFilteringEnabled?"IP Filtering ON":"IP Filtering OFF"}</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<Space h="md" />
|
||||||
</div>
|
</div>
|
||||||
}}
|
}}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
@@ -212,7 +301,7 @@ export const Firewall = () => {
|
|||||||
<Badge size="sm" color="green" variant="filled">Rules: {rules.isLoading?0:rules.data?.rules.length}</Badge>
|
<Badge size="sm" color="green" variant="filled">Rules: {rules.isLoading?0:rules.data?.rules.length}</Badge>
|
||||||
<Space w="xs" />
|
<Space w="xs" />
|
||||||
<Tooltip label="Add a new rule" position='bottom' color="blue" opened={tooltipAddOpened}>
|
<Tooltip label="Add a new rule" position='bottom' color="blue" opened={tooltipAddOpened}>
|
||||||
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled"
|
<ActionIcon color="blue" onClick={emptyRuleAdd} size="lg" radius="md" variant="filled"
|
||||||
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
|
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
|
||||||
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
|
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -234,20 +323,30 @@ export const Firewall = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Space h="xl" />
|
<Space h="xl" />
|
||||||
|
|
||||||
<DragDropContext
|
{items.length > 0?<DragDropContext
|
||||||
onDragEnd={({ destination, source }) =>
|
onDragEnd={({ destination, source }) =>
|
||||||
handlers.reorder({ from: source.index, to: destination?.index || 0 })
|
handlers.reorder({ from: source.index, to: destination?.index || 0 })
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Droppable droppableId="dnd-list" direction="vertical">
|
<Droppable droppableId="dnd-list" direction="vertical">
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div {...provided.droppableProps} ref={provided.innerRef}>
|
<div {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
{items}
|
{items}
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Droppable>
|
</Droppable>
|
||||||
</DragDropContext>
|
</DragDropContext>:<>
|
||||||
|
<Space h="xl"/> <Title className='center-flex' align='center' order={3}>No rule found! Add one clicking the "+" buttons</Title>
|
||||||
|
<Space h="xl" /> <Space h="xl" />
|
||||||
|
<div className='center-flex'>
|
||||||
|
<Tooltip label="Add a new rule" color="blue" opened={tooltipAddRulOpened}>
|
||||||
|
<ActionIcon color="blue" onClick={emptyRuleAdd} size="xl" radius="md" variant="filled"
|
||||||
|
onFocus={() => setTooltipAddRulOpened(false)} onBlur={() => setTooltipAddRulOpened(false)}
|
||||||
|
onMouseEnter={() => setTooltipAddRulOpened(true)} onMouseLeave={() => setTooltipAddRulOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
|
||||||
<YesNoModal
|
<YesNoModal
|
||||||
title='Are you sure to apply the changes to the firewall?'
|
title='Are you sure to apply the changes to the firewall?'
|
||||||
|
|||||||
Reference in New Issue
Block a user