fix: backend optional types + firewall frontend initial code
This commit is contained in:
@@ -42,7 +42,7 @@ class RegexAddForm(BaseModel):
|
|||||||
service_id: str
|
service_id: str
|
||||||
regex: str
|
regex: str
|
||||||
mode: str
|
mode: str
|
||||||
active: bool|None
|
active: bool|None = None
|
||||||
is_blacklist: bool
|
is_blacklist: bool
|
||||||
is_case_sensitive: bool
|
is_case_sensitive: bool
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class ServiceAddForm(BaseModel):
|
|||||||
|
|
||||||
class ServiceAddResponse(BaseModel):
|
class ServiceAddResponse(BaseModel):
|
||||||
status:str
|
status:str
|
||||||
service_id: str|None
|
service_id: str|None = None
|
||||||
|
|
||||||
app = APIRouter()
|
app = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class ServiceAddForm(BaseModel):
|
|||||||
|
|
||||||
class ServiceAddResponse(BaseModel):
|
class ServiceAddResponse(BaseModel):
|
||||||
status:str
|
status:str
|
||||||
service_id: str|None
|
service_id: str|None = None
|
||||||
|
|
||||||
class GeneralStatModel(BaseModel):
|
class GeneralStatModel(BaseModel):
|
||||||
services: int
|
services: int
|
||||||
|
|||||||
@@ -156,8 +156,8 @@ async def regen_service_port(service_id: str):
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
class ChangePortForm(BaseModel):
|
class ChangePortForm(BaseModel):
|
||||||
port: int|None
|
port: int|None = None
|
||||||
internalPort: int|None
|
internalPort: int|None = None
|
||||||
|
|
||||||
@app.post('/service/{service_id}/change-ports', response_model=StatusMessageModel)
|
@app.post('/service/{service_id}/change-ports', response_model=StatusMessageModel)
|
||||||
async def change_service_ports(service_id: str, change_port:ChangePortForm):
|
async def change_service_ports(service_id: str, change_port:ChangePortForm):
|
||||||
@@ -249,7 +249,7 @@ class RegexAddForm(BaseModel):
|
|||||||
service_id: str
|
service_id: str
|
||||||
regex: str
|
regex: str
|
||||||
mode: str
|
mode: str
|
||||||
active: bool|None
|
active: bool|None = None
|
||||||
is_blacklist: bool
|
is_blacklist: bool
|
||||||
is_case_sensitive: bool
|
is_case_sensitive: bool
|
||||||
|
|
||||||
@@ -272,11 +272,11 @@ async def add_new_regex(form: RegexAddForm):
|
|||||||
class ServiceAddForm(BaseModel):
|
class ServiceAddForm(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
port: PortType
|
port: PortType
|
||||||
internalPort: int|None
|
internalPort: int|None = None
|
||||||
|
|
||||||
class ServiceAddStatus(BaseModel):
|
class ServiceAddStatus(BaseModel):
|
||||||
status:str
|
status:str
|
||||||
id: str|None
|
id: str|None = None
|
||||||
|
|
||||||
class RenameForm(BaseModel):
|
class RenameForm(BaseModel):
|
||||||
name:str
|
name:str
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ def list_routers():
|
|||||||
return [ele[:-3] for ele in list_files(ROUTERS_DIR) if ele != "__init__.py" and " " not in ele and ele.endswith(".py")]
|
return [ele[:-3] for ele in list_files(ROUTERS_DIR) if ele != "__init__.py" and " " not in ele and ele.endswith(".py")]
|
||||||
|
|
||||||
class RouterModule():
|
class RouterModule():
|
||||||
router: None|APIRouter
|
router: APIRouter|None = None
|
||||||
reset: None|Callable
|
reset: Callable|None = None
|
||||||
startup: None|Callable
|
startup: Callable|None = None
|
||||||
shutdown: None|Callable
|
shutdown: Callable|None = None
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
def __init__(self, router: APIRouter, reset: Callable, startup: Callable, shutdown: Callable, name:str):
|
def __init__(self, router: APIRouter, reset: Callable, startup: Callable, shutdown: Callable, name:str):
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class PasswordChangeForm(BaseModel):
|
|||||||
|
|
||||||
class ChangePasswordModel(BaseModel):
|
class ChangePasswordModel(BaseModel):
|
||||||
status: str
|
status: str
|
||||||
access_token: str|None
|
access_token: str|None = None
|
||||||
|
|
||||||
class IpInterface(BaseModel):
|
class IpInterface(BaseModel):
|
||||||
addr: str
|
addr: str
|
||||||
|
|||||||
@@ -5,15 +5,17 @@ import { ImCross } from 'react-icons/im';
|
|||||||
import { Outlet, Route, Routes } from 'react-router-dom';
|
import { Outlet, Route, Routes } from 'react-router-dom';
|
||||||
import MainLayout from './components/MainLayout';
|
import MainLayout from './components/MainLayout';
|
||||||
import { PasswordSend, ServerStatusResponse } from './js/models';
|
import { PasswordSend, ServerStatusResponse } from './js/models';
|
||||||
import { errorNotify, fireUpdateRequest, getstatus, HomeRedirector, IS_DEV, login, setpassword } from './js/utils';
|
import { DEV_IP_BACKEND, errorNotify, fireUpdateRequest, getstatus, HomeRedirector, IS_DEV, login, setpassword } from './js/utils';
|
||||||
import NFRegex from './pages/NFRegex';
|
import NFRegex from './pages/NFRegex';
|
||||||
import io from 'socket.io-client';
|
import io from 'socket.io-client';
|
||||||
import RegexProxy from './pages/RegexProxy';
|
import RegexProxy from './pages/RegexProxy';
|
||||||
import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails';
|
import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails';
|
||||||
import ServiceDetailsProxyRegex from './pages/RegexProxy/ServiceDetails';
|
import ServiceDetailsProxyRegex from './pages/RegexProxy/ServiceDetails';
|
||||||
import PortHijack from './pages/PortHijack';
|
import PortHijack from './pages/PortHijack';
|
||||||
|
import { Firewall } from './pages/Firewall';
|
||||||
|
|
||||||
const socket = io({transports: ["websocket", "polling"], path:"/sock", host:IS_DEV?"127.0.0.1:4444":undefined });
|
|
||||||
|
const socket = IS_DEV?io("ws://"+DEV_IP_BACKEND, {transports: ["websocket", "polling"], path:"/sock" }):io({transports: ["websocket", "polling"], path:"/sock"});
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
@@ -154,6 +156,7 @@ function App() {
|
|||||||
<Route path="regexproxy" element={<RegexProxy><Outlet /></RegexProxy>} >
|
<Route path="regexproxy" element={<RegexProxy><Outlet /></RegexProxy>} >
|
||||||
<Route path=":srv" element={<ServiceDetailsProxyRegex />} />
|
<Route path=":srv" element={<ServiceDetailsProxyRegex />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="firewall" element={<Firewall />} />
|
||||||
<Route path="porthijack" element={<PortHijack />} />
|
<Route path="porthijack" element={<PortHijack />} />
|
||||||
<Route path="*" element={<HomeRedirector />} />
|
<Route path="*" element={<HomeRedirector />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
70
frontend/src/components/Firewall/utils.ts
Normal file
70
frontend/src/components/Firewall/utils.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { RegexAddForm, RegexFilter, ServerResponse } from "../../js/models"
|
||||||
|
import { getapi, postapi } from "../../js/utils"
|
||||||
|
|
||||||
|
export type GeneralStats = {
|
||||||
|
rules:number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Protocol {
|
||||||
|
TCP = "tcp",
|
||||||
|
UDP = "udp",
|
||||||
|
ANY = "any"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ActionType {
|
||||||
|
ACCEPT = "accept",
|
||||||
|
DROP = "drop",
|
||||||
|
REJECT = "reject"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum RuleMode {
|
||||||
|
OUT = "O",
|
||||||
|
IN = "I",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Rule = {
|
||||||
|
active: boolean
|
||||||
|
name:string,
|
||||||
|
proto: Protocol,
|
||||||
|
ip_src: string,
|
||||||
|
ip_dst: string,
|
||||||
|
port_src_from: number,
|
||||||
|
port_dst_from: number,
|
||||||
|
port_src_to: number,
|
||||||
|
port_dst_to: number,
|
||||||
|
action: ActionType,
|
||||||
|
mode: RuleMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RuleInfo = {
|
||||||
|
rules: Rule[]
|
||||||
|
policy: ActionType
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type ServerResponseListed = {
|
||||||
|
status:(ServerResponse & {rule_id:number})[]|string,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const firewall = {
|
||||||
|
stats: async () => {
|
||||||
|
return await getapi("firewall/stats") as GeneralStats;
|
||||||
|
},
|
||||||
|
rules: async() => {
|
||||||
|
return await getapi("firewall/rules") as RuleInfo;
|
||||||
|
},
|
||||||
|
rulenable: async (rule_id:number) => {
|
||||||
|
return await getapi(`firewall/rule/${rule_id}/enable`) as ServerResponse;
|
||||||
|
},
|
||||||
|
ruledisable: async (rule_id:number) => {
|
||||||
|
return await getapi(`firewall/rule/${rule_id}/disable`) as ServerResponse;
|
||||||
|
},
|
||||||
|
rulerename: async (rule_id:number, name: string) => {
|
||||||
|
const { status } = await postapi(`firewall/rule/${rule_id}/rename`,{ name }) as ServerResponse;
|
||||||
|
return status === "ok"?undefined:status
|
||||||
|
},
|
||||||
|
servicesadd: async (data:RuleInfo) => {
|
||||||
|
return await postapi("firewall/rules/set", data) as ServerResponseListed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Group, MantineColor, Navbar, ScrollArea, Text, ThemeIcon, Title, UnstyledButton } from "@mantine/core";
|
import { Box, Collapse, Divider, Group, MantineColor, Navbar, ScrollArea, Text, ThemeIcon, Title, UnstyledButton } from "@mantine/core";
|
||||||
import React from "react";
|
import { useState } from "react";
|
||||||
import { IoMdGitNetwork } from "react-icons/io";
|
import { IoMdGitNetwork } from "react-icons/io";
|
||||||
import { MdTransform } from "react-icons/md";
|
import { MdOutlineExpandLess, MdOutlineExpandMore, MdTransform } from "react-icons/md";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { getmainpath } from "../../js/utils";
|
import { getmainpath } from "../../js/utils";
|
||||||
import { GrDirections } from "react-icons/gr";
|
import { GrDirections } from "react-icons/gr";
|
||||||
|
import { PiWallLight } from "react-icons/pi";
|
||||||
|
|
||||||
function NavBarButton({ navigate, closeNav, name, icon, color, disabled }:
|
function NavBarButton({ navigate, closeNav, name, icon, color, disabled, onClick }:
|
||||||
{ navigate: string, closeNav: () => void, name:string, icon:any, color:MantineColor, disabled?:boolean }) {
|
{ navigate?: string, closeNav: () => void, name:string, icon:any, color:MantineColor, disabled?:boolean, onClick?:CallableFunction }) {
|
||||||
const navigator = useNavigate()
|
const navigator = useNavigate()
|
||||||
|
|
||||||
return <UnstyledButton sx={(theme) => ({
|
return <UnstyledButton sx={(theme) => ({
|
||||||
@@ -22,7 +23,10 @@ function NavBarButton({ navigate, closeNav, name, icon, color, disabled }:
|
|||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
|
theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
|
||||||
},
|
},
|
||||||
})} onClick={()=>{navigator(`/${navigate}`);closeNav()}} disabled={disabled}>
|
})} onClick={()=>{
|
||||||
|
if(navigate){navigator(`/${navigate}`);closeNav()}
|
||||||
|
if (onClick) onClick()
|
||||||
|
}} disabled={disabled}>
|
||||||
<Group>
|
<Group>
|
||||||
<ThemeIcon color={color} variant="light">
|
<ThemeIcon color={color} variant="light">
|
||||||
{icon}
|
{icon}
|
||||||
@@ -33,16 +37,26 @@ function NavBarButton({ navigate, closeNav, name, icon, color, disabled }:
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function NavBar({ closeNav, opened }: {closeNav: () => void, opened: boolean}) {
|
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)
|
||||||
|
|
||||||
return <Navbar p="md" hiddenBreakpoint="md" hidden={!opened} width={{ md: 300 }}>
|
return <Navbar p="md" hiddenBreakpoint="md" hidden={!opened} width={{ md: 300 }}>
|
||||||
<Navbar.Section px="xs" mt="xs">
|
<Navbar.Section px="xs" mt="xs">
|
||||||
<Title order={3}>[Fi]*regex 🔥</Title>
|
<Title order={3}>[Fi]*regex 🔥</Title>
|
||||||
</Navbar.Section>
|
</Navbar.Section>
|
||||||
<hr style={{width:"100%"}}/>
|
<Divider my="xs" />
|
||||||
|
|
||||||
<Navbar.Section grow component={ScrollArea} px="xs" mt="xs">
|
<Navbar.Section grow component={ScrollArea} px="xs" mt="xs">
|
||||||
<NavBarButton navigate="nfregex" closeNav={closeNav} name="Netfilter Regex" color="blue" icon={<IoMdGitNetwork />} />
|
<NavBarButton navigate="firewall" closeNav={closeNav} name="Firewall Rules" color="red" icon={<PiWallLight />} />
|
||||||
<NavBarButton navigate="regexproxy" closeNav={closeNav} name="TCP Proxy Regex Filter" color="lime" icon={<MdTransform />} />
|
<NavBarButton navigate="nfregex" closeNav={closeNav} name="Netfilter Regex" color="lime" icon={<IoMdGitNetwork />} />
|
||||||
<NavBarButton navigate="porthijack" closeNav={closeNav} name="Hijack Port to Proxy" color="red" icon={<GrDirections />} />
|
<NavBarButton navigate="porthijack" closeNav={closeNav} name="Hijack Port to Proxy" color="blue" icon={<GrDirections />} />
|
||||||
|
<Divider my="xs" label="Advanced" labelPosition="center" />
|
||||||
|
<NavBarButton closeNav={closeNav} name="Deprecated options" color="gray" icon={toggle ? <MdOutlineExpandLess /> : <MdOutlineExpandMore />} onClick={()=>setToggleState(!toggleState)} disabled={advancedSelected}/>
|
||||||
|
<Collapse in={toggle}>
|
||||||
|
<NavBarButton navigate="regexproxy" closeNav={closeNav} name="TCP Proxy Regex Filter" color="grape" icon={<MdTransform />} />
|
||||||
|
</Collapse>
|
||||||
</Navbar.Section>
|
</Navbar.Section>
|
||||||
|
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ export type ServiceAddForm = {
|
|||||||
ip_dst: string,
|
ip_dst: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ServiceAddResponse = {
|
export type ServiceAddResponse = ServerResponse & { service_id: string }
|
||||||
status: string,
|
|
||||||
service_id?: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const porthijack = {
|
export const porthijack = {
|
||||||
stats: async () => {
|
stats: async () => {
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ interface PortInputProps extends NumberInputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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():"")
|
||||||
|
const {fullWidth, ...propeties} = props
|
||||||
return <NumberInput
|
return <NumberInput
|
||||||
variant={props.variant?props.variant:"filled"}
|
variant={props.variant?props.variant:"filled"}
|
||||||
hideControls
|
hideControls
|
||||||
placeholder="80"
|
placeholder="80"
|
||||||
min={props.min?props.min:1}
|
min={props.min?props.min:1}
|
||||||
max={props.max?props.min:65535}
|
max={props.max?props.min:65535}
|
||||||
style={props.fullWidth?props.style:{ width: "75px", ...props.style }}
|
style={fullWidth?props.style:{ width: "75px", ...props.style }}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
const value = parseInt((e.target as HTMLInputElement).value)
|
const value = parseInt((e.target as HTMLInputElement).value)
|
||||||
if (value > 65535) {
|
if (value > 65535) {
|
||||||
@@ -28,7 +28,7 @@ const PortInput = React.forwardRef<HTMLInputElement, PortInputProps>( (props, r
|
|||||||
props.onInput?.(e)
|
props.onInput?.(e)
|
||||||
}}
|
}}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...propeties}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ export const regex_ipv6_no_cidr = "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|
|
|||||||
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 DEV_IP_BACKEND = "127.0.0.1:4444"
|
||||||
|
|
||||||
export async function getapi(path:string):Promise<any>{
|
export async function getapi(path:string):Promise<any>{
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
fetch(`${IS_DEV?"http://127.0.0.1:4444":""}/api/${path}`,{
|
fetch(`${IS_DEV?`http://${DEV_IP_BACKEND}`:""}/api/${path}`,{
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
headers: { "Authorization" : "Bearer " + window.localStorage.getItem("access_token")}
|
headers: { "Authorization" : "Bearer " + window.localStorage.getItem("access_token")}
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
@@ -35,7 +37,7 @@ export async function getapi(path:string):Promise<any>{
|
|||||||
|
|
||||||
export async function postapi(path:string,data:any,is_form:boolean=false):Promise<any>{
|
export async function postapi(path:string,data:any,is_form:boolean=false):Promise<any>{
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
fetch(`${IS_DEV?"http://127.0.0.1:4444":""}/api/${path}`, {
|
fetch(`${IS_DEV?`http://${DEV_IP_BACKEND}`:""}/api/${path}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
cache: 'no-cache',
|
cache: 'no-cache',
|
||||||
|
|||||||
55
frontend/src/pages/Firewall/index.tsx
Normal file
55
frontend/src/pages/Firewall/index.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { ActionIcon, Badge, LoadingOverlay, Space, Title, Tooltip } from "@mantine/core"
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { BsPlusLg } from "react-icons/bs"
|
||||||
|
import { ActionType, GeneralStats, RuleInfo, firewall } from "../../components/Firewall/utils";
|
||||||
|
import { errorNotify, eventUpdateName, fireUpdateRequest } from "../../js/utils";
|
||||||
|
import { useWindowEvent } from "@mantine/hooks";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const Firewall = () => {
|
||||||
|
|
||||||
|
const [generalStats, setGeneralStats] = useState<GeneralStats>({ rules: 0 });
|
||||||
|
const [rules, setRules] = useState<RuleInfo>({rules:[], policy:ActionType.ACCEPT});
|
||||||
|
const [loader, setLoader] = useState(true);
|
||||||
|
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
const updateInfo = async () => {
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
firewall.stats().then(res => {
|
||||||
|
setGeneralStats(res)
|
||||||
|
}).catch(
|
||||||
|
err => errorNotify("General Info Auto-Update failed!", err.toString())
|
||||||
|
),
|
||||||
|
firewall.rules().then(res => {
|
||||||
|
setRules(res)
|
||||||
|
}).catch(err => {
|
||||||
|
errorNotify("Home Page Auto-Update failed!", err.toString())
|
||||||
|
})
|
||||||
|
])
|
||||||
|
setLoader(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
useWindowEvent(eventUpdateName, updateInfo)
|
||||||
|
useEffect(fireUpdateRequest,[])
|
||||||
|
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Space h="sm" />
|
||||||
|
<div className='center-flex'>
|
||||||
|
<Title order={4}>Firewall Rules</Title>
|
||||||
|
<div className='flex-spacer' />
|
||||||
|
<Badge size="sm" color="green" variant="filled">Rules: {generalStats.rules}</Badge>
|
||||||
|
<Space w="xs" />
|
||||||
|
<Tooltip label="Add a new rule" position='bottom' color="blue" opened={tooltipAddOpened}>
|
||||||
|
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled"
|
||||||
|
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
|
||||||
|
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<LoadingOverlay visible={loader} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user