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 React, { useEffect, useState } from "react"
import { getipinterfaces } from "../js/utils";
import { ipInterfacesQuery } from "../js/utils";
import PortInput from "./PortInput";
import { UseFormReturnType } from "@mantine/form/lib/types";
import { InterfaceInput } from "./InterfaceInput";
interface ItemProps extends AutocompleteItem {
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 <>
{label?<>
<Title order={6}>{label}</Title>
<Space h="xs" /></> :null}
<div className='center-flex' style={{width:"100%"}}>
<Select
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;
}}
<div className={(!orientation || orientation == "line")?'center-flex':"center-flex-row"} style={{width:"100%"}}>
<InterfaceInput
{...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)} />
</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"
interface PortInputProps extends NumberInputProps {
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 [oldValue, setOldValue] = useState<string>(props.defaultValue?props.defaultValue.toString():"")
const {fullWidth, ...propeties} = props
@@ -16,15 +28,9 @@ const PortInput = React.forwardRef<HTMLInputElement, PortInputProps>( (props, r
max={props.max?props.min:65535}
style={fullWidth?props.style:{ width: "75px", ...props.style }}
onInput={(e) => {
const value = parseInt((e.target as HTMLInputElement).value)
if (value > 65535) {
(e.target as HTMLInputElement).value = oldValue
} else if (value < 1) {
(e.target as HTMLInputElement).value = oldValue
}else{
(e.target as HTMLInputElement).value = value.toString()
}
setOldValue((e.target as HTMLInputElement).value)
const target = e.target as HTMLInputElement
target.value = target.value?valueParse(target.value, oldValue):""
setOldValue(target.value)
props.onInput?.(e)
}}
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

View File

@@ -6,7 +6,7 @@ import { nfregex } from "../components/NFRegex/utils";
import { regexproxy } from "../components/RegexProxy/utils";
import { ChangePassword, IpInterface, LoginResponse, PasswordSend, ServerResponse, ServerResponseToken, ServerStatusResponse } from "./models";
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
@@ -111,6 +111,8 @@ export async function resetfiregex(delete_data:boolean = false){
return (status === "ok"?undefined:status)
}
export const ipInterfacesQuery = () => useQuery(["ipinterfaces"], getipinterfaces)
export async function getipinterfaces(){
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 { BsPlusLg } from "react-icons/bs"
import { rem, Text } from '@mantine/core';
@@ -12,6 +12,11 @@ import classes from './DndListHandle.module.scss';
import { useQueryClient } from "@tanstack/react-query";
import { TiTick } from "react-icons/ti";
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) => (
<Draggable key={index} index={index} draggableId={index.toString()}>
{(provided, snapshot) => (
<div
{(provided, snapshot) => {
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 })}
ref={provided.innerRef}
{...provided.draggableProps}
@@ -158,18 +169,25 @@ export const Firewall = () => {
<TbGripVertical style={{ width: rem(18), height: rem(18) }} />
</div>
<Space w="sm" />
<Switch
defaultChecked
label="I agree to sell my privacy"
/>
<Switch checked={item.active} />
<div>
<Text>{item.name}</Text>
<Text c="dimmed" size="sm">
{JSON.stringify(item)}
</Text>
<InterfaceInput initialCustomInterfaces={[...src_custom_int, ...customInt]} defaultValue={item.ip_src}/>
<PortRangeInput defaultValues={[item.port_src_from, item.port_src_to]} />
<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>
)}
}}
</Draggable>
));
@@ -184,21 +202,7 @@ export const Firewall = () => {
<Space w="sm" />
Policy:
<Space w="xs" />
<SegmentedControl
data={[
{
value: ActionType.ACCEPT,
label: 'Accept',
},
{
value: ActionType.REJECT,
label: 'Reject',
},
{
value: ActionType.DROP,
label: 'Drop',
}
]}
<ActionTypeSelector
value={currentPolicy}
onChange={(value)=>setCurrentPolicy(value as ActionType)}
/>