diff --git a/frontend/src/components/AddNewRegex.tsx b/frontend/src/components/AddNewRegex.tsx index d435635..6cc87d6 100755 --- a/frontend/src/components/AddNewRegex.tsx +++ b/frontend/src/components/AddNewRegex.tsx @@ -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'; -function AddNewRegex({ closePopup, service }:{ closePopup:()=>void, service:string }) { - return <> - /* +type RegexAddInfo = { + regex:string, + type:string, + mode:string, + regex_exact:boolean, + percentage_encoding:boolean +} + +function AddNewRegex({ closePopup, service }:{ closePopup:()=>void, service:string }) { 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(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
- - + + + + 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')} /> - - - + - + @@ -75,7 +114,6 @@ function AddNewRegex({ closePopup, service }:{ closePopup:()=>void, service:stri :null} - */ } diff --git a/frontend/src/components/FilterTypeSelector.tsx b/frontend/src/components/FilterTypeSelector.tsx new file mode 100755 index 0000000..0c858ae --- /dev/null +++ b/frontend/src/components/FilterTypeSelector.tsx @@ -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 + + Blacklist + + ), + }, + { + value: 'whitelist', + label: ( +
+ + Whitelist +
+ ), + }, + ]} + {...props} + /> +} \ No newline at end of file diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx index f945153..5be289e 100755 --- a/frontend/src/components/Header/index.tsx +++ b/frontend/src/components/Header/index.tsx @@ -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: , - }); - }) + err => errorNotify("General Info Auto-Update failed!", err.toString()) + ) } useEffect(()=>{ diff --git a/frontend/src/components/RegexView/index.tsx b/frontend/src/components/RegexView/index.tsx index afbe669..dd0232f 100755 --- a/frontend/src/components/RegexView/index.tsx +++ b/frontend/src/components/RegexView/index.tsx @@ -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
@@ -31,55 +35,42 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) { {regex_expr} - {}} size="xl" radius="md" variant="filled"> + setDeleteModal(true)} size="xl" radius="md" variant="filled"> - - - Blacklist - - ), - }, - { - value: 'whitelist', - label: ( -
- - Whitelist -
- ), - }, - ]} + - -
- Service: {regexInfo.service_id} - - ID: {regexInfo.id} -
-
- + value={regexInfo.is_blacklist?"blacklist":"whitelist"} + /> + +
+ Service: {regexInfo.service_id} + + ID: {regexInfo.id} +
+
+ + +
+ Match: {exact_regex?"EXACT":"FIND"} -
- Match: {exact_regex?"EXACT":"FIND"} - - Packets filtered: {regexInfo.n_packets} - - Mode: {mode_string} -
- - + Packets filtered: {regexInfo.n_packets} + + Mode: {mode_string} +
+
+ setDeleteModal(false)} + action={()=>console.log("Delete regex please!")} + opened={deleteModal} + />
} diff --git a/frontend/src/components/ServiceRow/index.tsx b/frontend/src/components/ServiceRow/index.tsx index b284d6c..6b31559 100755 --- a/frontend/src/components/ServiceRow/index.tsx +++ b/frontend/src/components/ServiceRow/index.tsx @@ -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 <> @@ -33,17 +59,29 @@ function ServiceRow({ service, onClick, additional_buttons }:{ service:Service,
{additional_buttons} - + {service.status === "pause"?:} - + + +
{onClick?:null}
+ setStopModal(false)} + action={stopService} + opened={stopModal} + />
} diff --git a/frontend/src/components/YesNoModal.tsx b/frontend/src/components/YesNoModal.tsx new file mode 100755 index 0000000..8833bcf --- /dev/null +++ b/frontend/src/components/YesNoModal.tsx @@ -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 + {description} + + + + + + +} + +export default YesNoModal; \ No newline at end of file diff --git a/frontend/src/js/models.ts b/frontend/src/js/models.ts index fbd4dbd..ea91abc 100755 --- a/frontend/src/js/models.ts +++ b/frontend/src/js/models.ts @@ -1,6 +1,6 @@ -export const update_freq = 5000; +export const update_freq = 3000; export const notification_time = 2000; export type GeneralStats = { @@ -36,4 +36,11 @@ export type RegexFilter = { is_blacklist:boolean, 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 } \ No newline at end of file diff --git a/frontend/src/js/utils.ts b/frontend/src/js/utils.tsx similarity index 53% rename from frontend/src/js/utils.ts rename to frontend/src/js/utils.tsx index 378deca..6c43b1c 100755 --- a/frontend/src/js/utils.ts +++ b/frontend/src/js/utils.tsx @@ -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{ 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++){ @@ -52,4 +61,45 @@ 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: , + }); +} + +export function okNotify(title:string, description:string ){ + showNotification({ + autoClose: notification_time, + title: title, + message: description, + color: 'teal', + icon: , + }); +} + +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') } \ No newline at end of file diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index c28fe6a..6640a7b 100755 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -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([ - { - 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([]); 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: , - }); - }) + err => errorNotify("Home Page Auto-Update failed!", err.toString()) + ) } useEffect(()=>{ diff --git a/frontend/src/pages/ServiceDetails.tsx b/frontend/src/pages/ServiceDetails.tsx index e1493a0..9685090 100755 --- a/frontend/src/pages/ServiceDetails.tsx +++ b/frontend/src/pages/ServiceDetails.tsx @@ -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([ - { - 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([]) + + 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: , - }); - 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: , - }); - error = true; + errorNotify(`Updater for ${srv_id} service failed [Regex list]!`, err.toString()) + error = true; }) } @@ -78,10 +51,12 @@ function ServiceDetails() { const updater = setInterval(updateInfo, update_freq) return () => { clearInterval(updater) } }, []); + + const [deleteModal, setDeleteModal] = useState(false) return <> - {}} size="xl" radius="md" variant="filled"> + setDeleteModal(true)} size="xl" radius="md" variant="filled"> }> {regexesList.length === 0? @@ -90,6 +65,13 @@ function ServiceDetails() { {regexesList.map( (regexInfo) => )} } + setDeleteModal(false)} + action={()=>console.log("Delete the service please!")} + opened={deleteModal} + /> }