firewall Frontend Improvements
This commit is contained in:
@@ -50,7 +50,7 @@ export type ServerResponseListed = {
|
||||
}
|
||||
|
||||
export const rulesQueryKey = ["firewall","rules"]
|
||||
export const firewallRulesQuery = () => useQuery({queryKey:rulesQueryKey, queryFn:firewall.rules})
|
||||
export const firewallRulesQuery = () => useQuery({queryKey:rulesQueryKey, queryFn:firewall.rules, refetchInterval: false})
|
||||
|
||||
export const firewall = {
|
||||
rules: async() => {
|
||||
@@ -72,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:RuleAddForm) => {
|
||||
ruleset: async (data:RuleAddForm) => {
|
||||
return await postapi("firewall/rules/set", data) as ServerResponseListed;
|
||||
}
|
||||
}
|
||||
@@ -97,8 +97,8 @@ export function getapiobject(){
|
||||
|
||||
export function HomeRedirector(){
|
||||
const section = sessionStorage.getItem("home_section")
|
||||
const path = section?`/${section}`:`/nfregex`
|
||||
return <Navigate to={path} />
|
||||
const path = section?`/${section}`:`/firewall`
|
||||
return <Navigate to={path} replace/>
|
||||
}
|
||||
|
||||
export function fireUpdateRequest(){ //TODO: change me: specify what to update
|
||||
|
||||
24
frontend/src/pages/Firewall/DndListHandle.module.scss
Normal file
24
frontend/src/pages/Firewall/DndListHandle.module.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
.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,42 +1,261 @@
|
||||
import { ActionIcon, Badge, LoadingOverlay, Space, Title, Tooltip } from "@mantine/core"
|
||||
import { ActionIcon, Badge, LoadingOverlay, NativeSelect, SegmentedControl, Space, Switch, Title, Tooltip } from "@mantine/core"
|
||||
import { useEffect, useState } from "react";
|
||||
import { BsPlusLg } from "react-icons/bs"
|
||||
import { firewall, firewallRulesQuery } from "../../components/Firewall/utils";
|
||||
import { errorNotify, getErrorMessage } from "../../js/utils";
|
||||
|
||||
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 { 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";
|
||||
|
||||
/*
|
||||
{
|
||||
"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>(ActionType.ACCEPT)
|
||||
const queryClient = useQueryClient()
|
||||
const rules = firewallRulesQuery()
|
||||
|
||||
const [state, handlers] = useListState<Rule>([]);
|
||||
const [enableFwModal, setEnableFwModal] = useState(false)
|
||||
const [applyChangeModal, setApplyChangeModal] = useState(false)
|
||||
|
||||
useEffect(()=> {
|
||||
if(rules.isError)
|
||||
errorNotify("Firewall Update failed!", getErrorMessage(rules.error))
|
||||
},[rules.isError])
|
||||
|
||||
useEffect(()=> {
|
||||
if(!rules.isLoading && rules.isSuccess)
|
||||
handlers.setState(JSON.parse(JSON.stringify(rules.data?.rules??[])))
|
||||
},[rules.isSuccess, rules.isLoading])
|
||||
|
||||
const fwEnabled = rules.data?.enabled??false
|
||||
const valuesChanged = JSON.stringify(rules.data?.rules) != JSON.stringify(state) || rules.data?.policy != currentPolicy
|
||||
|
||||
const enableFirewall = () => {
|
||||
if (valuesChanged){
|
||||
applyChangesRaw().then(()=>enableFirewallRaw())
|
||||
}else{
|
||||
enableFirewallRaw()
|
||||
}
|
||||
}
|
||||
|
||||
const enableFirewallRaw = () => {
|
||||
return firewall.enable()
|
||||
.then(()=>okNotify("Firewall enabled", "The firewall has been enabled"))
|
||||
.catch((e)=>errorNotify("Firewall enable failed!", getErrorMessage(e)))
|
||||
}
|
||||
|
||||
const disableFirewallRaw = () => {
|
||||
return firewall.disable()
|
||||
.then(()=>okNotify("Firewall disabled", "The firewall has been disabled"))
|
||||
.catch((e)=>errorNotify("Firewall disable failed!", getErrorMessage(e)))
|
||||
}
|
||||
|
||||
const switchState = () => {
|
||||
if (fwEnabled)
|
||||
disableFirewallRaw()
|
||||
else
|
||||
if ([ActionType.REJECT, ActionType.DROP].includes(currentPolicy) || valuesChanged){
|
||||
setEnableFwModal(true)
|
||||
}else{
|
||||
enableFirewall()
|
||||
}
|
||||
}
|
||||
|
||||
const applyChanges = () => {
|
||||
if (fwEnabled && rules.data?.policy == ActionType.ACCEPT && [ActionType.REJECT, ActionType.DROP].includes(currentPolicy)){
|
||||
setApplyChangeModal(true)
|
||||
}else{
|
||||
applyChangesRaw()
|
||||
}
|
||||
}
|
||||
|
||||
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 items = state.map((item, index) => (
|
||||
<Draggable key={index} index={index} draggableId={index.toString()}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className={cx(classes.item, { [classes.itemDragging]: snapshot.isDragging })}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
>
|
||||
<div {...provided.dragHandleProps} className={classes.dragHandle}>
|
||||
<TbGripVertical style={{ width: rem(18), height: rem(18) }} />
|
||||
</div>
|
||||
<Space w="sm" />
|
||||
<Switch
|
||||
defaultChecked
|
||||
label="I agree to sell my privacy"
|
||||
/>
|
||||
<div>
|
||||
<Text>{item.name}</Text>
|
||||
<Text c="dimmed" size="sm">
|
||||
{JSON.stringify(item)}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
));
|
||||
|
||||
|
||||
return <>
|
||||
<Space h="sm" />
|
||||
<LoadingOverlay visible={rules.isLoading} />
|
||||
<div className='center-flex'>
|
||||
<Title order={4}>Firewall Rules</Title>
|
||||
<div className='flex-spacer' />
|
||||
<Title order={3}>Firewall Rules</Title>
|
||||
<div className='flex-spacer' />
|
||||
Enabled: <Space w="sm" /> <Switch checked={fwEnabled} onChange={switchState} />
|
||||
<Space w="sm" />
|
||||
Policy:
|
||||
<Space w="xs" />
|
||||
<SegmentedControl
|
||||
data={[
|
||||
{
|
||||
value: ActionType.ACCEPT,
|
||||
label: 'Accept',
|
||||
},
|
||||
{
|
||||
value: ActionType.REJECT,
|
||||
label: 'Reject',
|
||||
},
|
||||
{
|
||||
value: ActionType.DROP,
|
||||
label: 'Drop',
|
||||
}
|
||||
]}
|
||||
value={currentPolicy}
|
||||
onChange={(value)=>setCurrentPolicy(value as ActionType)}
|
||||
/>
|
||||
<Space w="xs" />
|
||||
<Badge size="sm" color="green" variant="filled">Rules: {rules.isLoading?0:rules.data?.rules.length}</Badge>
|
||||
<Space w="xs" />
|
||||
<Badge size="sm" color="yellow" variant="filled">Policy: {rules.isLoading?"unknown":rules.data?.policy}</Badge>
|
||||
<Space w="xs" />
|
||||
<Badge size="sm" color="violet" variant="filled">Enabled: {rules.isLoading?"?":(rules.data?.enabled?"🟢":"🔴")}</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>
|
||||
<Space w="xs" />
|
||||
<Tooltip label="Refresh" position='bottom' color="indigo" opened={tooltipRefreshOpened}>
|
||||
<ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["firewall"])} size="lg" radius="md" variant="filled"
|
||||
loading={rules.isFetching}
|
||||
onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)}
|
||||
onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}><TbReload size={18} /></ActionIcon>
|
||||
</Tooltip>
|
||||
<Space w="xs" />
|
||||
<Tooltip label="Apply" position='bottom' color="grape" opened={tooltipApplyOpened}>
|
||||
<ActionIcon color="grape" onClick={applyChanges} size="lg" radius="md" variant="filled"
|
||||
onFocus={() => setTooltipApplyOpened(false)} onBlur={() => setTooltipApplyOpened(false)}
|
||||
onMouseEnter={() => setTooltipApplyOpened(true)} onMouseLeave={() => setTooltipApplyOpened(false)}
|
||||
disabled={!valuesChanged}
|
||||
><TiTick size={22} /></ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<LoadingOverlay visible={rules.isLoading} />
|
||||
<Space h="xl" />
|
||||
|
||||
<DragDropContext
|
||||
onDragEnd={({ destination, source }) =>
|
||||
handlers.reorder({ from: source.index, to: destination?.index || 0 })
|
||||
}
|
||||
>
|
||||
<Droppable droppableId="dnd-list" direction="vertical">
|
||||
{(provided) => (
|
||||
<div {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{items}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
|
||||
<YesNoModal
|
||||
title='Are you sure to apply the changes to the firewall?'
|
||||
description={`If there is a malconfiguration you can lose the access to your server! ⚠️`}
|
||||
onClose={()=>setEnableFwModal(false) }
|
||||
action={enableFirewall}
|
||||
opened={enableFwModal}
|
||||
/>
|
||||
|
||||
<YesNoModal
|
||||
title='Are you sure to apply the changes to the firewall?'
|
||||
description={`If there is a malconfiguration you can lose the access to your server! ⚠️`}
|
||||
onClose={()=>setApplyChangeModal(false) }
|
||||
action={applyChangesRaw}
|
||||
opened={applyChangeModal}
|
||||
/>
|
||||
|
||||
</>
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import { nfregexServiceQuery } from '../../components/NFRegex/utils';
|
||||
import { errorNotify, getErrorMessage } from '../../js/utils';
|
||||
import AddNewService from '../../components/NFRegex/AddNewService';
|
||||
import AddNewRegex from '../../components/AddNewRegex';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { TbReload } from 'react-icons/tb';
|
||||
|
||||
|
||||
function NFRegex({ children }: { children: any }) {
|
||||
@@ -14,16 +16,16 @@ function NFRegex({ children }: { children: any }) {
|
||||
const navigator = useNavigate()
|
||||
const [open, setOpen] = useState(false);
|
||||
const {srv} = useParams()
|
||||
const queryClient = useQueryClient()
|
||||
const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false);
|
||||
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
|
||||
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
|
||||
|
||||
const services = nfregexServiceQuery()
|
||||
|
||||
useEffect(()=> {
|
||||
if(services.isError){
|
||||
if(services.isError)
|
||||
errorNotify("NFRegex Update failed!", getErrorMessage(services.error))
|
||||
}
|
||||
|
||||
},[services.isError])
|
||||
|
||||
const closeModal = () => {setOpen(false);}
|
||||
@@ -51,6 +53,13 @@ function NFRegex({ children }: { children: any }) {
|
||||
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
|
||||
</Tooltip>
|
||||
}
|
||||
<Space w="xs" />
|
||||
<Tooltip label="Refresh" position='bottom' color="indigo" opened={tooltipRefreshOpened}>
|
||||
<ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["nfregex"])} size="lg" radius="md" variant="filled"
|
||||
loading={services.isFetching}
|
||||
onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)}
|
||||
onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}><TbReload size={18} /></ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div id="service-list" className="center-flex-row">
|
||||
{srv?null:<>
|
||||
|
||||
@@ -5,6 +5,8 @@ import ServiceRow from '../../components/PortHijack/ServiceRow';
|
||||
import { porthijackServiceQuery } from '../../components/PortHijack/utils';
|
||||
import { errorNotify, getErrorMessage } from '../../js/utils';
|
||||
import AddNewService from '../../components/PortHijack/AddNewService';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { TbReload } from 'react-icons/tb';
|
||||
|
||||
|
||||
function PortHijack() {
|
||||
@@ -12,14 +14,15 @@ function PortHijack() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
|
||||
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
|
||||
const queryClient = useQueryClient()
|
||||
const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false);
|
||||
|
||||
|
||||
const services = porthijackServiceQuery()
|
||||
|
||||
useEffect(()=>{
|
||||
if(services.isError){
|
||||
if(services.isError)
|
||||
errorNotify("Porthijack Update failed!", getErrorMessage(services.error))
|
||||
}
|
||||
},[services.isError])
|
||||
|
||||
const closeModal = () => {setOpen(false);}
|
||||
@@ -36,6 +39,13 @@ function PortHijack() {
|
||||
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
|
||||
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
|
||||
</Tooltip>
|
||||
<Space w="xs" />
|
||||
<Tooltip label="Refresh" position='bottom' color="indigo" opened={tooltipRefreshOpened}>
|
||||
<ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["porthijack"])} size="lg" radius="md" variant="filled"
|
||||
loading={services.isFetching}
|
||||
onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)}
|
||||
onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}><TbReload size={18} /></ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div id="service-list" className="center-flex-row">
|
||||
<LoadingOverlay visible={services.isLoading} />
|
||||
@@ -52,7 +62,6 @@ function PortHijack() {
|
||||
</>}
|
||||
<AddNewService opened={open} onClose={closeModal} />
|
||||
</div>
|
||||
<AddNewService opened={open} onClose={closeModal} />
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import { regexproxyServiceQuery } from '../../components/RegexProxy/utils';
|
||||
import { errorNotify, getErrorMessage } from '../../js/utils';
|
||||
import AddNewService from '../../components/RegexProxy/AddNewService';
|
||||
import AddNewRegex from '../../components/AddNewRegex';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { TbReload } from 'react-icons/tb';
|
||||
|
||||
|
||||
function RegexProxy({ children }: { children: any }) {
|
||||
@@ -16,6 +18,8 @@ function RegexProxy({ children }: { children: any }) {
|
||||
const {srv} = useParams()
|
||||
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
|
||||
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
|
||||
const queryClient = useQueryClient()
|
||||
const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false);
|
||||
|
||||
const services = regexproxyServiceQuery()
|
||||
|
||||
@@ -50,6 +54,13 @@ function RegexProxy({ children }: { children: any }) {
|
||||
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
|
||||
</Tooltip>
|
||||
}
|
||||
<Space w="xs" />
|
||||
<Tooltip label="Refresh" position='bottom' color="indigo" opened={tooltipRefreshOpened}>
|
||||
<ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["regexproxy"])} size="lg" radius="md" variant="filled"
|
||||
loading={services.isFetching}
|
||||
onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)}
|
||||
onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}><TbReload size={18} /></ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div id="service-list" className="center-flex-row">
|
||||
{srv?null:<>
|
||||
|
||||
Reference in New Issue
Block a user