add: created components for rules visualization

This commit is contained in:
Domingo Dirutigliano
2023-09-24 22:50:42 +02:00
parent 4c4b80fd23
commit d195f2c7cd
8 changed files with 211 additions and 63 deletions

View 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}
/>
)

View 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}
/>
)

View 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}
/>
)

View 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}
/>
}

View File

@@ -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>
</> </>

View File

@@ -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

View File

@@ -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[];
} }

View File

@@ -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)}
/> />