React Interface v1
This commit is contained in:
@@ -30,6 +30,18 @@ serv_id = serv_id.lower()
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/api/service/<serv>
|
||||||
|
|
||||||
|
{
|
||||||
|
"id":"serv_id",
|
||||||
|
"name":"text",
|
||||||
|
"status":"stop"/"wait"/"active"/"pause",
|
||||||
|
"public_port":1234,
|
||||||
|
"internal_port":44444,
|
||||||
|
"n_packets":1,
|
||||||
|
"n_regex":1,
|
||||||
|
}
|
||||||
|
|
||||||
/api/service/<serv>/stop
|
/api/service/<serv>/stop
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -63,7 +75,9 @@ serv_id = serv_id.lower()
|
|||||||
/api/service/<serv>/regexes
|
/api/service/<serv>/regexes
|
||||||
|
|
||||||
[
|
[
|
||||||
"5787":{
|
{
|
||||||
|
"id":5787,
|
||||||
|
"service_id":"serv_id",
|
||||||
"regex":"base64"
|
"regex":"base64"
|
||||||
"is_blacklist":true,
|
"is_blacklist":true,
|
||||||
"mode":"C","S","B" // C->S S->C BOTH
|
"mode":"C","S","B" // C->S S->C BOTH
|
||||||
@@ -73,6 +87,7 @@ serv_id = serv_id.lower()
|
|||||||
|
|
||||||
/api/regex/<regex_id>
|
/api/regex/<regex_id>
|
||||||
{
|
{
|
||||||
|
"id":5787,
|
||||||
"service_id":"serv_id",
|
"service_id":"serv_id",
|
||||||
"regex":"base64"
|
"regex":"base64"
|
||||||
"is_blacklist":true,
|
"is_blacklist":true,
|
||||||
|
|||||||
81
frontend/package-lock.json
generated
81
frontend/package-lock.json
generated
@@ -22,6 +22,7 @@
|
|||||||
"@types/node": "^16.11.39",
|
"@types/node": "^16.11.39",
|
||||||
"@types/react": "^18.0.12",
|
"@types/react": "^18.0.12",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-icons": "^4.4.0",
|
"react-icons": "^4.4.0",
|
||||||
@@ -5286,6 +5287,25 @@
|
|||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/batch": {
|
"node_modules/batch": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||||
@@ -5458,6 +5478,29 @@
|
|||||||
"node-int64": "^0.4.0"
|
"node-int64": "^0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/buffer-from": {
|
"node_modules/buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
@@ -8825,6 +8868,25 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ieee754": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
|
||||||
@@ -20686,6 +20748,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||||
},
|
},
|
||||||
|
"base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||||
|
},
|
||||||
"batch": {
|
"batch": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||||
@@ -20822,6 +20889,15 @@
|
|||||||
"node-int64": "^0.4.0"
|
"node-int64": "^0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"buffer": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||||
|
"requires": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"buffer-from": {
|
"buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
@@ -23262,6 +23338,11 @@
|
|||||||
"harmony-reflect": "^1.4.6"
|
"harmony-reflect": "^1.4.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ieee754": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||||
|
},
|
||||||
"ignore": {
|
"ignore": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@types/node": "^16.11.39",
|
"@types/node": "^16.11.39",
|
||||||
"@types/react": "^18.0.12",
|
"@types/react": "^18.0.12",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-icons": "^4.4.0",
|
"react-icons": "^4.4.0",
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Outlet, Route, Routes } from 'react-router-dom';
|
||||||
import MainLayout from './components/MainLayout';
|
import MainLayout from './components/MainLayout';
|
||||||
import HomePage from './pages/HomePage';
|
import HomePage from './pages/HomePage';
|
||||||
import Service from './pages/Service';
|
import ServiceDetails from './pages/ServiceDetails';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return <MainLayout>
|
return <Routes>
|
||||||
<Routes>
|
<Route element={<MainLayout><Outlet /></MainLayout>}>
|
||||||
<Route index element={<HomePage />} />
|
<Route index element={<HomePage />} />
|
||||||
<Route path=":srv_id" element={<Service />} />
|
<Route path=":srv_id" element={<ServiceDetails />} />
|
||||||
<Route path="*" element={<Navigate to="/" />} />
|
<Route path="*" element={<Navigate to="/" />} />
|
||||||
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</MainLayout>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
82
frontend/src/components/AddNewRegex.tsx
Executable file
82
frontend/src/components/AddNewRegex.tsx
Executable file
@@ -0,0 +1,82 @@
|
|||||||
|
import { Button, Group, NumberInput, Space, TextInput, Notification } from '@mantine/core';
|
||||||
|
import { useForm } from '@mantine/hooks';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { ServiceAddForm } from '../js/models';
|
||||||
|
import { addservice } from '../js/utils';
|
||||||
|
import { ImCross } from "react-icons/im"
|
||||||
|
|
||||||
|
function AddNewRegex({ closePopup, service }:{ closePopup:()=>void, service:string }) {
|
||||||
|
|
||||||
|
return <></>
|
||||||
|
/*
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
regex:"",
|
||||||
|
is_blacklist:true,
|
||||||
|
mode:"B"
|
||||||
|
},
|
||||||
|
validationRules:{
|
||||||
|
regex: (value) => value !== ""?true:false,
|
||||||
|
port: (value) => value>0 && value<65536
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const [submitLoading, setSubmitLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string|null>(null)
|
||||||
|
|
||||||
|
const submitRequest = (values:ServiceAddForm) =>{
|
||||||
|
setSubmitLoading(true)
|
||||||
|
addservice(values).then( res => {
|
||||||
|
if (!res){
|
||||||
|
setSubmitLoading(false)
|
||||||
|
closePopup();
|
||||||
|
}else{
|
||||||
|
setSubmitLoading(false)
|
||||||
|
setError("Invalid request! [ "+res+" ]")
|
||||||
|
}
|
||||||
|
}).catch( err => {
|
||||||
|
setSubmitLoading(false)
|
||||||
|
setError("Request Failed! [ "+err+" ]")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return <form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
|
<TextInput
|
||||||
|
required
|
||||||
|
label="Service name"
|
||||||
|
placeholder="Challenge 01"
|
||||||
|
{...form.getInputProps('name')}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
<NumberInput
|
||||||
|
required
|
||||||
|
placeholder="8080"
|
||||||
|
min={1}
|
||||||
|
max={65535}
|
||||||
|
label="Service port"
|
||||||
|
{...form.getInputProps('port')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button loading={submitLoading} type="submit">Add Service</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
{error?<>
|
||||||
|
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||||
|
Error: {error}
|
||||||
|
</Notification><Space h="md" /></>:null}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddNewRegex;
|
||||||
77
frontend/src/components/AddNewService.tsx
Executable file
77
frontend/src/components/AddNewService.tsx
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
import { Button, Group, NumberInput, Space, TextInput, Notification } from '@mantine/core';
|
||||||
|
import { useForm } from '@mantine/hooks';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { ServiceAddForm } from '../js/models';
|
||||||
|
import { addservice } from '../js/utils';
|
||||||
|
import { ImCross } from "react-icons/im"
|
||||||
|
|
||||||
|
function AddNewService({ closePopup }:{ closePopup:()=>void }) {
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
name:"",
|
||||||
|
port:1,
|
||||||
|
},
|
||||||
|
validationRules:{
|
||||||
|
name: (value) => value !== ""?true:false,
|
||||||
|
port: (value) => value>0 && value<65536
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const [submitLoading, setSubmitLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string|null>(null)
|
||||||
|
|
||||||
|
const submitRequest = (values:ServiceAddForm) =>{
|
||||||
|
setSubmitLoading(true)
|
||||||
|
addservice(values).then( res => {
|
||||||
|
if (!res){
|
||||||
|
setSubmitLoading(false)
|
||||||
|
closePopup();
|
||||||
|
}else{
|
||||||
|
setSubmitLoading(false)
|
||||||
|
setError("Invalid request! [ "+res+" ]")
|
||||||
|
}
|
||||||
|
}).catch( err => {
|
||||||
|
setSubmitLoading(false)
|
||||||
|
setError("Request Failed! [ "+err+" ]")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return <form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
|
<TextInput
|
||||||
|
required
|
||||||
|
label="Service name"
|
||||||
|
placeholder="Challenge 01"
|
||||||
|
{...form.getInputProps('name')}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
<NumberInput
|
||||||
|
required
|
||||||
|
placeholder="8080"
|
||||||
|
min={1}
|
||||||
|
max={65535}
|
||||||
|
label="Service port"
|
||||||
|
{...form.getInputProps('port')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button loading={submitLoading} type="submit">Add Service</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
{error?<>
|
||||||
|
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||||
|
Error: {error}
|
||||||
|
</Notification><Space h="md" /></>:null}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddNewService;
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { ActionIcon, Badge } from '@mantine/core';
|
import { ActionIcon, Badge, Modal } from '@mantine/core';
|
||||||
import style from "./Header.module.scss";
|
import style from "./Header.module.scss";
|
||||||
import { generalstats } from '../../js/utils';
|
import { generalstats } from '../../js/utils';
|
||||||
import { GeneralStats, update_freq } from '../../js/models';
|
import { GeneralStats, notification_time, update_freq } from '../../js/models';
|
||||||
import { BsPlusLg } from "react-icons/bs"
|
import { BsPlusLg } from "react-icons/bs"
|
||||||
import { AiFillHome } from "react-icons/ai"
|
import { AiFillHome } from "react-icons/ai"
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
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() {
|
function Header() {
|
||||||
|
|
||||||
@@ -17,14 +21,28 @@ function Header() {
|
|||||||
const updateInfo = () => {
|
const updateInfo = () => {
|
||||||
generalstats().then(res => {
|
generalstats().then(res => {
|
||||||
setGeneralStats(res)
|
setGeneralStats(res)
|
||||||
setTimeout(updateInfo, update_freq)
|
|
||||||
}).catch(
|
}).catch(
|
||||||
err =>{
|
err =>{
|
||||||
setTimeout(updateInfo, update_freq)}
|
showNotification({
|
||||||
)
|
autoClose: notification_time,
|
||||||
|
title: "General Info Auto-Update failed!",
|
||||||
|
message: "[ "+err+" ]",
|
||||||
|
color: 'red',
|
||||||
|
icon: <ImCross />,
|
||||||
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(updateInfo,[]);
|
useEffect(()=>{
|
||||||
|
updateInfo()
|
||||||
|
const updater = setInterval(updateInfo, update_freq)
|
||||||
|
return () => { clearInterval(updater) }
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
const {srv_id} = useParams()
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const closeModal = () => {setOpen(false);}
|
||||||
|
|
||||||
return <div id="header-page" className={style.header}>
|
return <div id="header-page" className={style.header}>
|
||||||
<div className={style.logo} >LOGO</div>
|
<div className={style.logo} >LOGO</div>
|
||||||
@@ -40,7 +58,17 @@ function Header() {
|
|||||||
<AiFillHome size="25px" />
|
<AiFillHome size="25px" />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
:null}
|
:null}
|
||||||
<ActionIcon color="blue" size="xl" radius="md" variant="filled"><BsPlusLg size="20px" /></ActionIcon>
|
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"><BsPlusLg size="20px" /></ActionIcon>
|
||||||
|
{srv_id?
|
||||||
|
<Modal size="xl" title="Add a new regex filter" opened={open} onClose={closeModal} closeOnClickOutside={false} centered>
|
||||||
|
<AddNewRegex closePopup={closeModal} service={srv_id} />
|
||||||
|
</Modal>:
|
||||||
|
<Modal size="xl" title="Add a new service" opened={open} onClose={closeModal} closeOnClickOutside={false} centered>
|
||||||
|
<AddNewService closePopup={closeModal} />
|
||||||
|
</Modal>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
<div style={{marginLeft:"40px"}}></div>
|
<div style={{marginLeft:"40px"}}></div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,22 @@
|
|||||||
import { Container, MantineProvider } from '@mantine/core';
|
import { Container, MantineProvider, Space } from '@mantine/core';
|
||||||
import React, { useEffect, useState } from 'react';
|
import { NotificationsProvider } from '@mantine/notifications';
|
||||||
import { Service, update_freq } from '../js/models';
|
import React from 'react';
|
||||||
import { servicelist } from '../js/utils';
|
|
||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
|
|
||||||
function MainLayout({ children }:{ children:any }) {
|
function MainLayout({ children }:{ children:any }) {
|
||||||
|
|
||||||
const [services, setServices] = useState<Service[]>([]);
|
|
||||||
|
|
||||||
const updateInfo = () => {
|
|
||||||
servicelist().then(res => {
|
|
||||||
setServices(res)
|
|
||||||
setTimeout(updateInfo, update_freq)
|
|
||||||
}).catch(
|
|
||||||
err =>{
|
|
||||||
setTimeout(updateInfo, update_freq)}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(updateInfo,[]);
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<MantineProvider theme={{ colorScheme: 'dark' }} withGlobalStyles withNormalizeCSS>
|
<MantineProvider theme={{ colorScheme: 'dark' }} withGlobalStyles withNormalizeCSS>
|
||||||
|
<NotificationsProvider>
|
||||||
<Header />
|
<Header />
|
||||||
<div style={{marginTop:"50px"}}/>
|
<Space h="xl" />
|
||||||
<Container size="xl" style={{minHeight:"58vh"}}>
|
<Container size="xl" style={{minHeight:"57.5vh"}}>
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
<div style={{marginTop:"50px"}}/>
|
<Space h="xl" />
|
||||||
<Footer />
|
<Footer />
|
||||||
|
</NotificationsProvider>
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
frontend/src/components/RegexView/RegexView.module.scss
Executable file
6
frontend/src/components/RegexView/RegexView.module.scss
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
.box{
|
||||||
|
padding:30px;
|
||||||
|
margin:5px;
|
||||||
|
}
|
||||||
19
frontend/src/components/RegexView/index.tsx
Executable file
19
frontend/src/components/RegexView/index.tsx
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
import { TextInput } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
import { RegexFilter } from '../../js/models';
|
||||||
|
import { getHumanReadableRegex } from '../../js/utils';
|
||||||
|
|
||||||
|
import style from "./RegexView.module.scss";
|
||||||
|
|
||||||
|
|
||||||
|
function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
|
||||||
|
return <div className={style.box}>
|
||||||
|
<TextInput
|
||||||
|
disabled
|
||||||
|
value={getHumanReadableRegex(regexInfo.regex)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RegexView;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ActionIcon, Badge, Grid, Space, Title } from '@mantine/core';
|
import { ActionIcon, Badge, Grid, Space, Title } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FaPause, FaPlay } from 'react-icons/fa';
|
import { FaPause, FaPlay, FaStop } from 'react-icons/fa';
|
||||||
import { Service } from '../../js/models';
|
import { Service } from '../../js/models';
|
||||||
import { MdOutlineArrowForwardIos } from "react-icons/md"
|
import { MdOutlineArrowForwardIos } from "react-icons/md"
|
||||||
import style from "./ServiceRow.module.scss";
|
import style from "./ServiceRow.module.scss";
|
||||||
@@ -19,7 +19,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
|
|||||||
<Grid className={style.row} style={{width:"100%"}}>
|
<Grid className={style.row} style={{width:"100%"}}>
|
||||||
<Grid.Col span={4}>
|
<Grid.Col span={4}>
|
||||||
<div className="center-flex-row">
|
<div className="center-flex-row">
|
||||||
<div className="center-flex"><Title className={style.name}>{service.name}</Title> <Badge size="xl" variant="filled">:{service.public_port}</Badge></div>
|
<div className="center-flex"><Title className={style.name}>{service.name}</Title> <Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient">:{service.public_port}</Badge></div>
|
||||||
<Badge color={status_color} size="xl" radius="md">{service.internal_port} {"->"} {service.public_port}</Badge>
|
<Badge color={status_color} size="xl" radius="md">{service.internal_port} {"->"} {service.public_port}</Badge>
|
||||||
</div>
|
</div>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
@@ -32,7 +32,9 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
|
|||||||
</div>
|
</div>
|
||||||
<Space w="xl" /><Space w="xl" />
|
<Space w="xl" /><Space w="xl" />
|
||||||
<div className="center-flex">
|
<div className="center-flex">
|
||||||
<ActionIcon color="red" size="xl" radius="md" variant="filled" disabled={!["wait","active"].includes(service.status)?true:false}><FaPause size="20px" /></ActionIcon>
|
<ActionIcon color={service.status === "pause"?"yellow":"red"} 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"/>
|
<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" variant="filled" disabled={!["stop","pause"].includes(service.status)?true:false}><FaPlay size="20px" /></ActionIcon>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,3 +22,14 @@ body {
|
|||||||
.flex-spacer{
|
.flex-spacer{
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
margin:3px;
|
||||||
|
background: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #757575;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
@@ -3,20 +3,12 @@ import ReactDOM from 'react-dom/client';
|
|||||||
import { BrowserRouter } from "react-router-dom"
|
import { BrowserRouter } from "react-router-dom"
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById('root') as HTMLElement
|
document.getElementById('root') as HTMLElement
|
||||||
);
|
);
|
||||||
root.render(
|
root.render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
<App />
|
||||||
</React.StrictMode>
|
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
||||||
reportWebVitals();
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
export const update_freq = 3000;
|
export const update_freq = 5000;
|
||||||
|
export const notification_time = 2000;
|
||||||
|
|
||||||
export type GeneralStats = {
|
export type GeneralStats = {
|
||||||
services:number,
|
services:number,
|
||||||
@@ -17,3 +18,21 @@ export type Service = {
|
|||||||
n_packets:number,
|
n_packets:number,
|
||||||
n_regex:number,
|
n_regex:number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ServiceAddForm = {
|
||||||
|
name:string,
|
||||||
|
port:number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type ServerResponse = {
|
||||||
|
status:string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RegexFilter = {
|
||||||
|
id:number,
|
||||||
|
service_id:string,
|
||||||
|
regex:string
|
||||||
|
is_blacklist:boolean,
|
||||||
|
mode:string // C->S S->C BOTH
|
||||||
|
}
|
||||||
@@ -1,14 +1,55 @@
|
|||||||
import { GeneralStats, Service } from "./models";
|
import { GeneralStats, Service, ServiceAddForm, ServerResponse, RegexFilter } from "./models";
|
||||||
|
|
||||||
export async function getapi(path:string):Promise<any>{
|
export async function getapi(path:string):Promise<any>{
|
||||||
return await fetch(`/api/${path}`).then( res => res.json() )
|
return await fetch(`/api/${path}`).then( res => res.json() )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function postapi(path:string,data:any):Promise<any>{
|
||||||
|
return await fetch(`/api/${path}`, {
|
||||||
|
method: 'POST',
|
||||||
|
cache: 'no-cache',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
export async function generalstats():Promise<GeneralStats>{
|
|
||||||
|
export async function generalstats(){
|
||||||
return await getapi("general-stats") as GeneralStats;
|
return await getapi("general-stats") as GeneralStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function servicelist():Promise<Service[]>{
|
export async function servicelist(){
|
||||||
return await getapi("services") as Service[];
|
return await getapi("services") as Service[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function serviceinfo(service_id:string){
|
||||||
|
return await getapi(`service/${service_id}`) as Service;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addservice(data:ServiceAddForm) {
|
||||||
|
const { status } = await postapi("services/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[];
|
||||||
|
}
|
||||||
|
|
||||||
|
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++){
|
||||||
|
const byte = String.fromCharCode(regex[i]);
|
||||||
|
if (unescapedChars.includes(byte)){
|
||||||
|
res+=byte
|
||||||
|
}else{
|
||||||
|
res+="%"+regex[i].toString(16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { Space, Title } from '@mantine/core';
|
||||||
|
import { showNotification } from '@mantine/notifications';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Navigate, useNavigate, useRoutes } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import ServiceRow from '../components/ServiceRow';
|
import ServiceRow from '../components/ServiceRow';
|
||||||
import { Service, update_freq } from '../js/models';
|
import { notification_time, Service, update_freq } from '../js/models';
|
||||||
import { servicelist } from '../js/utils';
|
import { servicelist } from '../js/utils';
|
||||||
|
import { ImCross } from "react-icons/im"
|
||||||
|
|
||||||
|
|
||||||
function HomePage() {
|
function HomePage() {
|
||||||
@@ -32,19 +35,28 @@ function HomePage() {
|
|||||||
const updateInfo = () => {
|
const updateInfo = () => {
|
||||||
servicelist().then(res => {
|
servicelist().then(res => {
|
||||||
setServices(res)
|
setServices(res)
|
||||||
setTimeout(updateInfo, update_freq)
|
|
||||||
}).catch(
|
}).catch(
|
||||||
err =>{
|
err =>{
|
||||||
setTimeout(updateInfo, update_freq)}
|
showNotification({
|
||||||
)
|
autoClose: notification_time,
|
||||||
|
title: "Home Page Auto-Update failed!",
|
||||||
|
message: "[ "+err+" ]",
|
||||||
|
color: 'red',
|
||||||
|
icon: <ImCross />,
|
||||||
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(updateInfo,[]);
|
useEffect(()=>{
|
||||||
|
updateInfo()
|
||||||
|
const updater = setInterval(updateInfo, update_freq)
|
||||||
|
return () => { clearInterval(updater) }
|
||||||
|
}, []);
|
||||||
|
|
||||||
return <div id="service-list" className="center-flex-row">
|
return <div id="service-list" className="center-flex-row">
|
||||||
{services.map( srv => <ServiceRow service={srv} key={srv.id} onClick={()=>{
|
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.id} onClick={()=>{
|
||||||
navigator("/"+srv.id)
|
navigator("/"+srv.id)
|
||||||
}} />)}
|
}} />):<><Space h="xl" /> <Title className='center-flex' order={1}>No services found! Add one clicking the button above</Title></>}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
function Service() {
|
|
||||||
const {srv_id} = useParams()
|
|
||||||
return <div>
|
|
||||||
Service: {srv_id}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Service;
|
|
||||||
97
frontend/src/pages/ServiceDetails.tsx
Executable file
97
frontend/src/pages/ServiceDetails.tsx
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
import { Grid, Space, Title } from '@mantine/core';
|
||||||
|
import { showNotification } from '@mantine/notifications';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { ImCross } from 'react-icons/im';
|
||||||
|
import { 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';
|
||||||
|
|
||||||
|
function ServiceDetails() {
|
||||||
|
const {srv_id} = useParams()
|
||||||
|
|
||||||
|
const [serviceInfo, setServiceInfo] = useState<Service>({
|
||||||
|
id:srv_id?srv_id:"",
|
||||||
|
internal_port:0,
|
||||||
|
n_packets:0,
|
||||||
|
n_regex:0,
|
||||||
|
name:srv_id?srv_id:"",
|
||||||
|
public_port:0,
|
||||||
|
status:"🤔"
|
||||||
|
})
|
||||||
|
|
||||||
|
const [regexesList, setRegexesList] = useState<RegexFilter[]>([
|
||||||
|
{
|
||||||
|
id:3546,
|
||||||
|
is_blacklist:true,
|
||||||
|
mode:"B",
|
||||||
|
regex:"d2VkcmZoaXdlZGZoYnVp",
|
||||||
|
service_id:"ctfe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id:3546,
|
||||||
|
is_blacklist:true,
|
||||||
|
mode:"B",
|
||||||
|
regex:"d2VkcmZoaXdlZGZoYnVp",
|
||||||
|
service_id:"ctfe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id:3546,
|
||||||
|
is_blacklist:true,
|
||||||
|
mode:"B",
|
||||||
|
regex:"d2VkcmZoaXdlZGZoYnVp",
|
||||||
|
service_id:"ctfe"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const updateInfo = async () => {
|
||||||
|
if (!srv_id) return
|
||||||
|
let error = false;
|
||||||
|
await serviceinfo(srv_id).then(res => {
|
||||||
|
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;
|
||||||
|
})
|
||||||
|
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;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
updateInfo()
|
||||||
|
const updater = setInterval(updateInfo, update_freq)
|
||||||
|
return () => { clearInterval(updater) }
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<ServiceRow service={serviceInfo}></ServiceRow>
|
||||||
|
{regexesList.length === 0?
|
||||||
|
<><Space h="xl" /> <Title className='center-flex' order={1}>No regex found for this service! Add one clicking the add button above</Title></>:
|
||||||
|
<Grid>
|
||||||
|
{regexesList.map( (regexInfo) => <Grid.Col key={regexInfo.id} span={6}><RegexView regexInfo={regexInfo}/></Grid.Col>)}
|
||||||
|
</Grid>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceDetails;
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { ReportHandler } from 'web-vitals';
|
|
||||||
|
|
||||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
|
||||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
|
||||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
|
||||||
getCLS(onPerfEntry);
|
|
||||||
getFID(onPerfEntry);
|
|
||||||
getFCP(onPerfEntry);
|
|
||||||
getLCP(onPerfEntry);
|
|
||||||
getTTFB(onPerfEntry);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default reportWebVitals;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
|
||||||
// allows you to do things like:
|
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
|
||||||
import '@testing-library/jest-dom';
|
|
||||||
Reference in New Issue
Block a user