React Interface v3

This commit is contained in:
DomySh
2022-06-12 16:44:54 +02:00
parent 98652b0fa9
commit 3139cbf288
10 changed files with 284 additions and 163 deletions

View File

@@ -1,33 +1,60 @@
import { Button, Group, NumberInput, Space, TextInput, Notification } from '@mantine/core';
import { Button, Group, NumberInput, Space, TextInput, Notification, Switch, NativeSelect } from '@mantine/core';
import { useForm } from '@mantine/hooks';
import React, { useState } from 'react';
import { ServiceAddForm } from '../js/models';
import { addservice } from '../js/utils';
import { RegexAddForm, ServiceAddForm } from '../js/models';
import { addregex, addservice, b64encode, validateRegex } from '../js/utils';
import { ImCross } from "react-icons/im"
import FilterTypeSelector from './FilterTypeSelector';
type RegexAddInfo = {
regex:string,
type:string,
mode:string,
regex_exact:boolean,
percentage_encoding:boolean
}
function AddNewRegex({ closePopup, service }:{ closePopup:()=>void, service:string }) {
return <></>
/*
const form = useForm({
initialValues: {
regex:"",
is_blacklist:true,
mode:"B"
type:"blacklist",
mode:"C <-> S",
regex_exact:false,
percentage_encoding:false
},
validationRules:{
regex: (value) => value !== ""?true:false,
port: (value) => value>0 && value<65536
regex: (value) => value !== "" && validateRegex(value),
type: (value) => ["blacklist","whitelist"].includes(value),
mode: (value) => ['C -> S', 'S -> C', 'C <-> S'].includes(value)
}
})
const [submitLoading, setSubmitLoading] = useState(false)
const [error, setError] = useState<string|null>(null)
const submitRequest = (values:ServiceAddForm) =>{
const submitRequest = (values:RegexAddInfo) => {
setSubmitLoading(true)
addservice(values).then( res => {
const filter_mode = ({'C -> S':'C', 'S -> C':'S', 'C <-> S':'B'}[values.mode])
let final_regex = values.regex
if (values.percentage_encoding){
final_regex = decodeURIComponent(final_regex)
}
if(!values.regex_exact){
final_regex = ".*"+final_regex+".*"
}
const request:RegexAddForm = {
is_blacklist:values.type !== "whitelist",
service_id: service,
mode: filter_mode?filter_mode:"B",
regex: b64encode(final_regex)
}
setSubmitLoading(false)
addregex(request).then( res => {
if (!res){
setSubmitLoading(false)
closePopup();
@@ -39,32 +66,44 @@ function AddNewRegex({ closePopup, service }:{ closePopup:()=>void, service:stri
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}
return <form onSubmit={form.onSubmit(submitRequest)}>
<TextInput
required
label="Service name"
placeholder="Challenge 01"
{...form.getInputProps('name')}
label="Regex"
placeholder="[A-Z0-9]{31}="
{...form.getInputProps('regex')}
/>
<Space h="md" />
<NumberInput
<Switch
label="Use percentage encoding for binary values"
{...form.getInputProps('percentage_encoding', { type: 'checkbox' })}
/>
<Space h="md" />
<Switch
label="Match the exactly the regex"
{...form.getInputProps('regex_exact', { type: 'checkbox' })}
/>
<Space h="md" />
<NativeSelect
data={['C -> S', 'S -> C', 'C <-> S']}
label="Choose the source of the packets to filter"
variant="filled"
required
placeholder="8080"
min={1}
max={65535}
label="Service port"
{...form.getInputProps('port')}
{...form.getInputProps('mode')}
/>
<Space h="md" />
<FilterTypeSelector
size="md"
color="gray"
required
{...form.getInputProps('type')}
/>
<Group position="right" mt="md">
<Button loading={submitLoading} type="submit">Add Service</Button>
<Button loading={submitLoading} type="submit">Add Filter</Button>
</Group>
<Space h="md" />
@@ -75,7 +114,6 @@ function AddNewRegex({ closePopup, service }:{ closePopup:()=>void, service:stri
</Notification><Space h="md" /></>:null}
</form>
*/
}

View File

@@ -0,0 +1,32 @@
import { Box, Center, SegmentedControl } from "@mantine/core";
import React from "react";
import { FaListAlt } from "react-icons/fa";
import { TiCancel } from "react-icons/ti";
export default function FilterTypeSelector(props:any){
return <SegmentedControl
data={[
{
value: 'blacklist',
label: (
<Center style={{color:"#FFF"}}>
<TiCancel size={23} color="red"/>
<Box ml={10}>Blacklist</Box>
</Center>
),
},
{
value: 'whitelist',
label: (
<Center style={{color:"#FFF"}}>
<FaListAlt size={16} color="gray"/>
<Box ml={10}>Whitelist</Box>
</Center>
),
},
]}
{...props}
/>
}

View File

@@ -1,15 +1,14 @@
import React, { useEffect, useState } from 'react';
import { ActionIcon, Badge, Modal } from '@mantine/core';
import style from "./Header.module.scss";
import { generalstats } from '../../js/utils';
import { GeneralStats, notification_time, update_freq } from '../../js/models';
import { errorNotify, generalstats } from '../../js/utils';
import { GeneralStats, update_freq } from '../../js/models';
import { BsPlusLg } from "react-icons/bs"
import { AiFillHome } from "react-icons/ai"
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import AddNewRegex from '../AddNewRegex';
import AddNewService from '../AddNewService';
import { showNotification } from '@mantine/notifications';
import { ImCross } from 'react-icons/im';
function Header() {
@@ -22,15 +21,8 @@ function Header() {
generalstats().then(res => {
setGeneralStats(res)
}).catch(
err =>{
showNotification({
autoClose: notification_time,
title: "General Info Auto-Update failed!",
message: "[ "+err+" ]",
color: 'red',
icon: <ImCross />,
});
})
err => errorNotify("General Info Auto-Update failed!", err.toString())
)
}
useEffect(()=>{

View File

@@ -1,18 +1,20 @@
import { Center, Grid, SegmentedControl, Text, Title, Box, Badge, Space, ActionIcon } from '@mantine/core';
import React from 'react';
import React, { useState } from 'react';
import { RegexFilter } from '../../js/models';
import { getHumanReadableRegex } from '../../js/utils';
import style from "./RegexView.module.scss";
import { FaListAlt } from "react-icons/fa"
import { TiCancel } from "react-icons/ti"
import { BsTrashFill } from "react-icons/bs"
import YesNoModal from '../YesNoModal';
import FilterTypeSelector from '../FilterTypeSelector';
function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
const mode_string = regexInfo.mode == "C"? "C -> S":
regexInfo.mode == "S"? "S -> C":
regexInfo.mode == "B"? "S <-> C": "🤔"
const mode_string = regexInfo.mode === "C"? "C -> S":
regexInfo.mode === "S"? "S -> C":
regexInfo.mode === "B"? "S <-> C": "🤔"
let regex_expr = getHumanReadableRegex(regexInfo.regex);
let exact_regex = true;
@@ -22,6 +24,8 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
exact_regex = false;
}
const [deleteModal, setDeleteModal] = useState(false);
return <div className={style.box}>
<Grid>
<Grid.Col span={2}>
@@ -31,55 +35,42 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
<Text className={style.regex_text}> {regex_expr}</Text>
</Grid.Col>
<Grid.Col span={2}>
<ActionIcon color="red" onClick={()=>{}} size="xl" radius="md" variant="filled"><BsTrashFill size={22} /></ActionIcon>
<ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled"><BsTrashFill size={22} /></ActionIcon>
</Grid.Col>
<Grid.Col span={2} />
<Grid.Col className='center-flex-row' span={4}>
<Space h="xs" />
<SegmentedControl
data={[
{
value: 'blacklist',
label: (
<Center style={{color:"#FFF"}}>
<TiCancel size={23} color="red"/>
<Box ml={10}>Blacklist</Box>
</Center>
),
},
{
value: 'whitelist',
label: (
<Center style={{color:"#FFF"}}>
<FaListAlt size={16} color="gray"/>
<Box ml={10}>Whitelist</Box>
</Center>
),
},
]}
<FilterTypeSelector
size="md"
color="gray"
disabled
/>
<Space h="md" />
<div className='center-flex'>
<Badge size="md" color="green" variant="filled">Service: {regexInfo.service_id}</Badge>
<Space w="xs" />
<Badge size="md" color="gray" variant="filled">ID: {regexInfo.id}</Badge>
</div>
</Grid.Col>
<Grid.Col style={{width:"100%"}} span={6}>
value={regexInfo.is_blacklist?"blacklist":"whitelist"}
/>
<Space h="md" />
<div className='center-flex'>
<Badge size="md" color="green" variant="filled">Service: {regexInfo.service_id}</Badge>
<Space w="xs" />
<Badge size="md" color="gray" variant="filled">ID: {regexInfo.id}</Badge>
</div>
</Grid.Col>
<Grid.Col style={{width:"100%"}} span={6}>
<Space h="xs" />
<div className='center-flex-row'>
<Badge size="md" color={exact_regex?"grape":"pink"} variant="filled">Match: {exact_regex?"EXACT":"FIND"}</Badge>
<Space h="xs" />
<div className='center-flex-row'>
<Badge size="md" color={exact_regex?"grape":"pink"} variant="filled">Match: {exact_regex?"EXACT":"FIND"}</Badge>
<Space h="xs" />
<Badge size="md" color="yellow" variant="filled">Packets filtered: {regexInfo.n_packets}</Badge>
<Space h="xs" />
<Badge size="md" color="blue" variant="filled">Mode: {mode_string}</Badge>
</div>
</Grid.Col>
<Badge size="md" color="yellow" variant="filled">Packets filtered: {regexInfo.n_packets}</Badge>
<Space h="xs" />
<Badge size="md" color="blue" variant="filled">Mode: {mode_string}</Badge>
</div>
</Grid.Col>
</Grid>
<YesNoModal
title='Are you sure to delete this regex?'
description={`You are going to delete the regex '${regex_expr}', causing the restart of the firewall if it is active.`}
onClose={()=>setDeleteModal(false)}
action={()=>console.log("Delete regex please!")}
opened={deleteModal}
/>
</div>
}

View File

@@ -1,9 +1,10 @@
import { ActionIcon, Badge, Grid, Space, Title } from '@mantine/core';
import React from 'react';
import React, { useState } from 'react';
import { FaPause, FaPlay, FaStop } from 'react-icons/fa';
import { Service } from '../../js/models';
import { MdOutlineArrowForwardIos } from "react-icons/md"
import style from "./ServiceRow.module.scss";
import YesNoModal from '../YesNoModal';
//"status":"stop"/"wait"/"active"/"pause",
function ServiceRow({ service, onClick, additional_buttons }:{ service:Service, onClick?:()=>void, additional_buttons?:any }) {
@@ -15,6 +16,31 @@ function ServiceRow({ service, onClick, additional_buttons }:{ service:Service,
case "active": status_color = "teal"; break;
case "pause": status_color = "cyan"; break;
}
const [stopModal, setStopModal] = useState(false);
const [buttonLoading, setButtonLoading] = useState(false)
const stopService = () => {
setButtonLoading(true)
console.log("Stop this service please!")
setButtonLoading(false)
}
const startService = () => {
setButtonLoading(true)
console.log("Start this service please!")
setButtonLoading(false)
}
const pauseService = () => {
if (service.status === "pause") return setStopModal(true)
setButtonLoading(true)
console.log("Pause this service please!")
setButtonLoading(false)
}
return <>
<Grid className={style.row} style={{width:"100%"}}>
<Grid.Col span={4}>
@@ -33,17 +59,29 @@ function ServiceRow({ service, onClick, additional_buttons }:{ service:Service,
<Space w="xl" /><Space w="xl" />
<div className="center-flex">
{additional_buttons}
<ActionIcon color={service.status === "pause"?"yellow":"red"} size="xl" radius="md" variant="filled" disabled={!["wait","active","pause"].includes(service.status)?true:false}>
<ActionIcon color={service.status === "pause"?"yellow":"red"} loading={buttonLoading}
onClick={pauseService} size="xl" radius="md" variant="filled"
disabled={!["wait","active","pause"].includes(service.status)?true:false}>
{service.status === "pause"?<FaStop size="20px" />:<FaPause size="20px" />}
</ActionIcon>
<Space w="md"/>
<ActionIcon color="teal" size="xl" radius="md" variant="filled" disabled={!["stop","pause"].includes(service.status)?true:false}><FaPlay size="20px" /></ActionIcon>
<ActionIcon color="teal" size="xl" radius="md" onClick={startService} loading={buttonLoading}
variant="filled" disabled={!["stop","pause"].includes(service.status)?true:false}>
<FaPlay size="20px" />
</ActionIcon>
</div>
<Space w="xl" /><Space w="xl" />
{onClick?<MdOutlineArrowForwardIos onClick={onClick} style={{cursor:"pointer"}} size="45px" />:null}
<Space w="xl" />
</Grid.Col>
</Grid>
<YesNoModal
title='Are you sure to stop this service!'
description={`You are going to delete the service '${service.id}', causing the stopping of the firewall. This will cause the shutdown of your service ⚠️!`}
onClose={()=>setStopModal(false)}
action={stopService}
opened={stopModal}
/>
<hr style={{width:"100%"}}/>
</>
}

View File

@@ -0,0 +1,19 @@
import { Button, Group, Modal } from '@mantine/core';
import React from 'react';
function YesNoModal( { title, description, action, onClose, opened}:{ title:string, description:string, onClose:()=>void, action:()=>void, opened:boolean} ){
return <Modal size="xl" title={title} opened={opened} onClose={onClose} centered>
{description}
<Group position="right" mt="md">
<Button onClick={()=>{
onClose()
action()
}} color="teal" type="submit">Yes</Button>
<Button onClick={onClose} color="red" type="submit">No</Button>
</Group>
</Modal>
}
export default YesNoModal;

View File

@@ -1,6 +1,6 @@
export const update_freq = 5000;
export const update_freq = 3000;
export const notification_time = 2000;
export type GeneralStats = {
@@ -37,3 +37,10 @@ export type RegexFilter = {
mode:string //C S B => C->S S->C BOTH
n_packets:number
}
export type RegexAddForm = {
"service_id":string,
"regex":string,
"is_blacklist":boolean,
"mode":string // C->S S->C BOTH
}

View File

@@ -1,4 +1,9 @@
import { GeneralStats, Service, ServiceAddForm, ServerResponse, RegexFilter } from "./models";
import { showNotification } from "@mantine/notifications";
import { ImCross } from "react-icons/im";
import { TiTick } from "react-icons/ti"
import { GeneralStats, Service, ServiceAddForm, ServerResponse, RegexFilter, notification_time, RegexAddForm } from "./models";
var Buffer = require('buffer').Buffer
export async function getapi(path:string):Promise<any>{
return await fetch(`/api/${path}`).then( res => res.json() )
@@ -33,6 +38,11 @@ export async function addservice(data:ServiceAddForm) {
return status === "ok"?undefined:status
}
export async function addregex(data:RegexAddForm) {
const { status } = await postapi("regexes/add",data) as ServerResponse;
return status === "ok"?undefined:status
}
export async function serviceregexlist(service_id:string){
return await getapi(`service/${service_id}/regexes`) as RegexFilter[];
}
@@ -40,7 +50,6 @@ export async function serviceregexlist(service_id:string){
const unescapedChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$&\'()*+,-./:;<=>?@[\\]^_`{|}~ ";
export function getHumanReadableRegex(regexB64:string){
var Buffer = require('buffer').Buffer
const regex = Buffer.from(regexB64, "base64")
let res = ""
for (let i=0; i < regex.length; i++){
@@ -53,3 +62,44 @@ export function getHumanReadableRegex(regexB64:string){
}
return res
}
export function errorNotify(title:string, description:string ){
showNotification({
autoClose: notification_time,
title: title,
message: description,
color: 'red',
icon: <ImCross />,
});
}
export function okNotify(title:string, description:string ){
showNotification({
autoClose: notification_time,
title: title,
message: description,
color: 'teal',
icon: <TiTick />,
});
}
export function validateRegex(pattern:string) {
var parts = pattern.split('/'),
regex = pattern,
options = "";
if (parts.length > 1) {
regex = parts[1];
options = parts[2];
}
try {
new RegExp(regex, options);
return true;
}
catch(e) {
return false;
}
}
export function b64encode(data:string){
return Buffer.from(data).toString('base64')
}

View File

@@ -1,50 +1,22 @@
import { Space, Title } from '@mantine/core';
import { showNotification } from '@mantine/notifications';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import ServiceRow from '../components/ServiceRow';
import { notification_time, Service, update_freq } from '../js/models';
import { servicelist } from '../js/utils';
import { ImCross } from "react-icons/im"
import { Service, update_freq } from '../js/models';
import { errorNotify, servicelist } from '../js/utils';
function HomePage() {
const [services, setServices] = useState<Service[]>([
{
id:"ctfe",
internal_port:18080,
n_packets: 30,
n_regex: 40,
name:"CTFe",
public_port:80,
status:"pause"
},
{
id:"saas",
internal_port:18080,
n_packets: 30,
n_regex: 40,
name:"SaaS",
public_port:5000,
status:"active"
}
]);
const [services, setServices] = useState<Service[]>([]);
const navigator = useNavigate()
const updateInfo = () => {
servicelist().then(res => {
setServices(res)
}).catch(
err =>{
showNotification({
autoClose: notification_time,
title: "Home Page Auto-Update failed!",
message: "[ "+err+" ]",
color: 'red',
icon: <ImCross />,
});
})
err => errorNotify("Home Page Auto-Update failed!", err.toString())
)
}
useEffect(()=>{

View File

@@ -1,13 +1,12 @@
import { ActionIcon, Grid, Space, Title } from '@mantine/core';
import { showNotification } from '@mantine/notifications';
import React, { useEffect, useState } from 'react';
import { BsTrashFill } from 'react-icons/bs';
import { ImCross } from 'react-icons/im';
import { useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import RegexView from '../components/RegexView';
import ServiceRow from '../components/ServiceRow';
import { notification_time, RegexFilter, Service, update_freq } from '../js/models';
import { serviceinfo, serviceregexlist } from '../js/utils';
import YesNoModal from '../components/YesNoModal';
import { RegexFilter, Service, update_freq } from '../js/models';
import { errorNotify, serviceinfo, serviceregexlist } from '../js/utils';
function ServiceDetails() {
const {srv_id} = useParams()
@@ -22,24 +21,9 @@ function ServiceDetails() {
status:"🤔"
})
const [regexesList, setRegexesList] = useState<RegexFilter[]>([
{
id:3546,
is_blacklist:true,
mode:"B",
regex:"LipmbGFnX2NoZWNrLio=",
service_id:"ctfe",
n_packets:5,
},
{
id:3546,
is_blacklist:true,
mode:"B",
regex:"d2VkcmZoaXdlZGZoYnVp",
service_id:"ctfe",
n_packets: 54
}
])
const [regexesList, setRegexesList] = useState<RegexFilter[]>([])
const navigator = useNavigate()
const updateInfo = async () => {
if (!srv_id) return
@@ -48,28 +32,17 @@ function ServiceDetails() {
setServiceInfo(res)
}).catch(
err =>{
showNotification({
autoClose: notification_time,
title: `Updater for ${srv_id} service failed [General Info]!`,
message: "[ "+err+" ]",
color: 'red',
icon: <ImCross />,
});
error = true;
errorNotify(`Updater for ${srv_id} service failed [General Info]!`, err.toString())
error = true;
navigator("/")
})
if (error) return
await serviceregexlist(srv_id).then(res => {
setRegexesList(res)
}).catch(
err =>{
showNotification({
autoClose: notification_time,
title: `Updater for ${srv_id} service failed [Regex list]!`,
message: "[ "+err+" ]",
color: 'red',
icon: <ImCross />,
});
error = true;
errorNotify(`Updater for ${srv_id} service failed [Regex list]!`, err.toString())
error = true;
})
}
@@ -79,9 +52,11 @@ function ServiceDetails() {
return () => { clearInterval(updater) }
}, []);
const [deleteModal, setDeleteModal] = useState(false)
return <>
<ServiceRow service={serviceInfo} additional_buttons={<>
<ActionIcon color="red" onClick={()=>{}} size="xl" radius="md" variant="filled"><BsTrashFill size={22} /></ActionIcon>
<ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled"><BsTrashFill size={22} /></ActionIcon>
<Space w="md"/>
</>}></ServiceRow>
{regexesList.length === 0?
@@ -90,6 +65,13 @@ function ServiceDetails() {
{regexesList.map( (regexInfo) => <Grid.Col key={regexInfo.id} span={6}><RegexView regexInfo={regexInfo}/></Grid.Col>)}
</Grid>
}
<YesNoModal
title='Are you sure to delete this service?'
description={`You are going to delete the service '${serviceInfo.id}', causing the stopping of the firewall and deleting all the regex associated. This will cause the shutdown of your service ⚠️!`}
onClose={()=>setDeleteModal(false)}
action={()=>console.log("Delete the service please!")}
opened={deleteModal}
/>
</>
}