add: created components for rules visualization
This commit is contained in:
23
frontend/src/components/Firewall/ActionTypeSelector.tsx
Normal file
23
frontend/src/components/Firewall/ActionTypeSelector.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||||
|
import { ActionType } from "./utils";
|
||||||
|
|
||||||
|
|
||||||
|
export const ActionTypeSelector = (props:Omit<SegmentedControlProps, "data">) => (
|
||||||
|
<SegmentedControl
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
value: ActionType.ACCEPT,
|
||||||
|
label: 'Accept',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ActionType.REJECT,
|
||||||
|
label: 'Reject',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ActionType.DROP,
|
||||||
|
label: 'Drop',
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
20
frontend/src/components/Firewall/ModeSelector.tsx
Normal file
20
frontend/src/components/Firewall/ModeSelector.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||||
|
import { RuleMode } from "./utils";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const ModeSelector = (props:Omit<SegmentedControlProps, "data">) => (
|
||||||
|
<SegmentedControl
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
value: RuleMode.IN,
|
||||||
|
label: 'IN',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: RuleMode.OUT,
|
||||||
|
label: 'OUT',
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
23
frontend/src/components/Firewall/ProtocolSelector.tsx
Normal file
23
frontend/src/components/Firewall/ProtocolSelector.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||||
|
import { Protocol } from "./utils";
|
||||||
|
|
||||||
|
|
||||||
|
export const ProtocolSelector = (props:Omit<SegmentedControlProps, "data">) => (
|
||||||
|
<SegmentedControl
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
value: Protocol.TCP,
|
||||||
|
label: 'TCP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: Protocol.UDP,
|
||||||
|
label: 'UDP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: Protocol.ANY,
|
||||||
|
label: 'ANY',
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
48
frontend/src/components/InterfaceInput.tsx
Normal file
48
frontend/src/components/InterfaceInput.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { AutocompleteItem, Select, SelectProps } from "@mantine/core";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { ipInterfacesQuery } from "../js/utils";
|
||||||
|
|
||||||
|
|
||||||
|
const AutoCompleteItem = React.forwardRef<HTMLDivElement, ItemProps>(
|
||||||
|
({ netint, value, ...props }: ItemProps, ref) => <div ref={ref} {...props}>
|
||||||
|
( <b>{netint}</b> ) -{">"} <b>{value}</b>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
interface ItemProps extends AutocompleteItem {
|
||||||
|
netint: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InterfaceInputProps extends Omit<SelectProps, "data">{
|
||||||
|
initialCustomInterfaces?:AutocompleteItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InterfaceInput = (props:InterfaceInputProps) => {
|
||||||
|
|
||||||
|
const { initialCustomInterfaces, ...propeties } = props
|
||||||
|
|
||||||
|
const [customIpInterfaces, setCustomIpInterfaces] = useState<AutocompleteItem[]>(initialCustomInterfaces??[]);
|
||||||
|
const interfacesQuery = ipInterfacesQuery()
|
||||||
|
|
||||||
|
const interfaces = (!interfacesQuery.isLoading?
|
||||||
|
(interfacesQuery.data!.map(item => ({netint:item.name, value:item.addr, label:item.addr})) as AutocompleteItem[]):
|
||||||
|
[])
|
||||||
|
|
||||||
|
return <Select
|
||||||
|
placeholder="10.1.1.1"
|
||||||
|
itemComponent={AutoCompleteItem}
|
||||||
|
data={[...customIpInterfaces, ...interfaces]}
|
||||||
|
searchable
|
||||||
|
dropdownPosition="bottom"
|
||||||
|
maxDropdownHeight={200}
|
||||||
|
creatable
|
||||||
|
getCreateLabel={(query) => `+ Use this: ${query}`}
|
||||||
|
onCreate={(query) => {
|
||||||
|
const item = { value: query, netint: "CUSTOM", label: query };
|
||||||
|
setCustomIpInterfaces((current) => [...current, item]);
|
||||||
|
return item;
|
||||||
|
}}
|
||||||
|
style={props.style?{width:"100%", ...props.style}:{width:"100%"}}
|
||||||
|
{...propeties}
|
||||||
|
/>
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { AutocompleteItem, Select, Space, Title } from "@mantine/core"
|
import { AutocompleteItem, Select, Space, Title } from "@mantine/core"
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
import { getipinterfaces } from "../js/utils";
|
import { ipInterfacesQuery } from "../js/utils";
|
||||||
import PortInput from "./PortInput";
|
import PortInput from "./PortInput";
|
||||||
import { UseFormReturnType } from "@mantine/form/lib/types";
|
import { UseFormReturnType } from "@mantine/form/lib/types";
|
||||||
|
import { InterfaceInput } from "./InterfaceInput";
|
||||||
|
|
||||||
interface ItemProps extends AutocompleteItem {
|
interface ItemProps extends AutocompleteItem {
|
||||||
netint: string;
|
netint: string;
|
||||||
@@ -15,39 +16,21 @@ const AutoCompleteItem = React.forwardRef<HTMLDivElement, ItemProps>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
export default function PortAndInterface({ form, int_name, port_name, label }:{ form:UseFormReturnType<any>, int_name:string, port_name:string, label?:string }) {
|
export default function PortAndInterface({ form, int_name, port_name, label, orientation }:{ form:UseFormReturnType<any>, int_name:string, port_name:string, label?:string, orientation?:"line"|"column" }) {
|
||||||
|
|
||||||
const [ipInterfaces, setIpInterfaces] = useState<AutocompleteItem[]>([]);
|
|
||||||
|
|
||||||
useEffect(()=>{
|
|
||||||
getipinterfaces().then(data => {
|
|
||||||
setIpInterfaces(data.map(item => ({netint:item.name, value:item.addr, label:item.addr})));
|
|
||||||
})
|
|
||||||
},[])
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
{label?<>
|
{label?<>
|
||||||
<Title order={6}>{label}</Title>
|
<Title order={6}>{label}</Title>
|
||||||
<Space h="xs" /></> :null}
|
<Space h="xs" /></> :null}
|
||||||
<div className='center-flex' style={{width:"100%"}}>
|
<div className={(!orientation || orientation == "line")?'center-flex':"center-flex-row"} style={{width:"100%"}}>
|
||||||
<Select
|
<InterfaceInput
|
||||||
placeholder="10.1.1.1"
|
|
||||||
itemComponent={AutoCompleteItem}
|
|
||||||
data={ipInterfaces}
|
|
||||||
searchable
|
|
||||||
dropdownPosition="bottom"
|
|
||||||
maxDropdownHeight={200}
|
|
||||||
creatable
|
|
||||||
getCreateLabel={(query) => `+ Use this: ${query}`}
|
|
||||||
onCreate={(query) => {
|
|
||||||
const item = { value: query, netint: "CUSTOM", label: query };
|
|
||||||
setIpInterfaces((current) => [...current, item]);
|
|
||||||
return item;
|
|
||||||
}}
|
|
||||||
{...form.getInputProps(int_name)}
|
{...form.getInputProps(int_name)}
|
||||||
style={{width:"100%"}}
|
|
||||||
/>
|
/>
|
||||||
<Space w="sm" /><span style={{marginTop:"-3px", fontSize:"1.5em"}}>:</span><Space w="sm" />
|
{(!orientation || orientation == "line")?
|
||||||
|
<><Space w="sm" /><span style={{marginTop:"-3px", fontSize:"1.5em"}}>:</span><Space w="sm" /></>:
|
||||||
|
<Space h="md" />}
|
||||||
<PortInput {...form.getInputProps(port_name)} />
|
<PortInput {...form.getInputProps(port_name)} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
import { NumberInput, NumberInputProps } from "@mantine/core"
|
import { Input, NumberInput, NumberInputProps, TextInput, TextInputProps } from "@mantine/core"
|
||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
|
|
||||||
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():"")
|
||||||
const {fullWidth, ...propeties} = props
|
const {fullWidth, ...propeties} = props
|
||||||
@@ -16,15 +28,9 @@ const PortInput = React.forwardRef<HTMLInputElement, PortInputProps>( (props, r
|
|||||||
max={props.max?props.min:65535}
|
max={props.max?props.min:65535}
|
||||||
style={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 target = e.target as HTMLInputElement
|
||||||
if (value > 65535) {
|
target.value = target.value?valueParse(target.value, oldValue):""
|
||||||
(e.target as HTMLInputElement).value = oldValue
|
setOldValue(target.value)
|
||||||
} else if (value < 1) {
|
|
||||||
(e.target as HTMLInputElement).value = oldValue
|
|
||||||
}else{
|
|
||||||
(e.target as HTMLInputElement).value = value.toString()
|
|
||||||
}
|
|
||||||
setOldValue((e.target as HTMLInputElement).value)
|
|
||||||
props.onInput?.(e)
|
props.onInput?.(e)
|
||||||
}}
|
}}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -33,4 +39,43 @@ const PortInput = React.forwardRef<HTMLInputElement, PortInputProps>( (props, r
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
interface PortRangeInputProps extends TextInputProps {
|
||||||
|
fullWidth?: boolean,
|
||||||
|
defaultValues?: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PortRangeInput = React.forwardRef<HTMLInputElement, PortRangeInputProps>( (props, ref) => {
|
||||||
|
const oldValueStates = [
|
||||||
|
useState<string>(props.defaultValue?props.defaultValue?.toString().split("-")[0]:""),
|
||||||
|
useState<string|undefined>(props.defaultValue?.toString().split("-")[1])
|
||||||
|
]
|
||||||
|
const {fullWidth, defaultValues, ...propeties} = props
|
||||||
|
let defaultValuesInt = defaultValues
|
||||||
|
if (defaultValuesInt?.length == 2 && defaultValuesInt[0] == defaultValuesInt[1]){
|
||||||
|
defaultValuesInt = [defaultValuesInt[0]]
|
||||||
|
}
|
||||||
|
|
||||||
|
return <TextInput
|
||||||
|
variant={props.variant?props.variant:"filled"}
|
||||||
|
placeholder="1000-1337"
|
||||||
|
style={fullWidth?props.style:{ width: "150px", ...props.style }}
|
||||||
|
onInput={(e) => {
|
||||||
|
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("-")
|
||||||
|
props.onInput?.(e)
|
||||||
|
}}
|
||||||
|
ref={ref}
|
||||||
|
defaultValue={defaultValuesInt?defaultValuesInt.map(v => v.toString()).join("-"):props.defaultValue}
|
||||||
|
{...propeties}
|
||||||
|
/>
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
export default PortInput
|
export default PortInput
|
||||||
@@ -6,7 +6,7 @@ import { nfregex } from "../components/NFRegex/utils";
|
|||||||
import { regexproxy } from "../components/RegexProxy/utils";
|
import { regexproxy } from "../components/RegexProxy/utils";
|
||||||
import { ChangePassword, IpInterface, LoginResponse, PasswordSend, ServerResponse, ServerResponseToken, ServerStatusResponse } from "./models";
|
import { ChangePassword, IpInterface, LoginResponse, PasswordSend, ServerResponse, ServerResponseToken, ServerStatusResponse } from "./models";
|
||||||
import { Buffer } from "buffer"
|
import { Buffer } from "buffer"
|
||||||
import { QueryClient } from "@tanstack/react-query";
|
import { QueryClient, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
export const IS_DEV = import.meta.env.DEV
|
export const IS_DEV = import.meta.env.DEV
|
||||||
|
|
||||||
@@ -111,6 +111,8 @@ export async function resetfiregex(delete_data:boolean = false){
|
|||||||
return (status === "ok"?undefined:status)
|
return (status === "ok"?undefined:status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ipInterfacesQuery = () => useQuery(["ipinterfaces"], getipinterfaces)
|
||||||
|
|
||||||
export async function getipinterfaces(){
|
export async function getipinterfaces(){
|
||||||
return await getapi("interfaces") as IpInterface[];
|
return await getapi("interfaces") as IpInterface[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ActionIcon, Badge, LoadingOverlay, NativeSelect, SegmentedControl, Space, Switch, Title, Tooltip } from "@mantine/core"
|
import { ActionIcon, Badge, LoadingOverlay, Space, Switch, Title, Tooltip } from "@mantine/core"
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { BsPlusLg } from "react-icons/bs"
|
import { BsPlusLg } from "react-icons/bs"
|
||||||
import { rem, Text } from '@mantine/core';
|
import { rem, Text } from '@mantine/core';
|
||||||
@@ -12,6 +12,11 @@ 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";
|
||||||
|
import { PortRangeInput } from "../../components/PortInput";
|
||||||
|
import { InterfaceInput } from "../../components/InterfaceInput";
|
||||||
|
import { ActionTypeSelector } from "../../components/Firewall/ActionTypeSelector";
|
||||||
|
import { ProtocolSelector } from "../../components/Firewall/ProtocolSelector";
|
||||||
|
import { ModeSelector } from "../../components/Firewall/ModeSelector";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
@@ -148,8 +153,14 @@ export const Firewall = () => {
|
|||||||
|
|
||||||
const items = state.map((item, index) => (
|
const items = state.map((item, index) => (
|
||||||
<Draggable key={index} index={index} draggableId={index.toString()}>
|
<Draggable key={index} index={index} draggableId={index.toString()}>
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => {
|
||||||
<div
|
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 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 }]
|
||||||
|
return <div
|
||||||
className={cx(classes.item, { [classes.itemDragging]: snapshot.isDragging })}
|
className={cx(classes.item, { [classes.itemDragging]: snapshot.isDragging })}
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
@@ -158,18 +169,25 @@ export const Firewall = () => {
|
|||||||
<TbGripVertical style={{ width: rem(18), height: rem(18) }} />
|
<TbGripVertical style={{ width: rem(18), height: rem(18) }} />
|
||||||
</div>
|
</div>
|
||||||
<Space w="sm" />
|
<Space w="sm" />
|
||||||
<Switch
|
<Switch checked={item.active} />
|
||||||
defaultChecked
|
|
||||||
label="I agree to sell my privacy"
|
|
||||||
/>
|
|
||||||
<div>
|
<div>
|
||||||
<Text>{item.name}</Text>
|
<Text>{item.name}</Text>
|
||||||
<Text c="dimmed" size="sm">
|
<InterfaceInput initialCustomInterfaces={[...src_custom_int, ...customInt]} defaultValue={item.ip_src}/>
|
||||||
{JSON.stringify(item)}
|
<PortRangeInput defaultValues={[item.port_src_from, item.port_src_to]} />
|
||||||
</Text>
|
<InterfaceInput initialCustomInterfaces={[...dst_custom_int, ...customInt]} defaultValue={item.ip_dst}/>
|
||||||
|
<PortRangeInput defaultValues={[item.port_dst_from, item.port_dst_to]} />
|
||||||
|
<ActionTypeSelector
|
||||||
|
value={item.action}
|
||||||
|
/>
|
||||||
|
<ProtocolSelector
|
||||||
|
value={item.proto}
|
||||||
|
/>
|
||||||
|
<ModeSelector
|
||||||
|
value={item.mode}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
}}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -184,21 +202,7 @@ export const Firewall = () => {
|
|||||||
<Space w="sm" />
|
<Space w="sm" />
|
||||||
Policy:
|
Policy:
|
||||||
<Space w="xs" />
|
<Space w="xs" />
|
||||||
<SegmentedControl
|
<ActionTypeSelector
|
||||||
data={[
|
|
||||||
{
|
|
||||||
value: ActionType.ACCEPT,
|
|
||||||
label: 'Accept',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ActionType.REJECT,
|
|
||||||
label: 'Reject',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ActionType.DROP,
|
|
||||||
label: 'Drop',
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
value={currentPolicy}
|
value={currentPolicy}
|
||||||
onChange={(value)=>setCurrentPolicy(value as ActionType)}
|
onChange={(value)=>setCurrentPolicy(value as ActionType)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user