React Interface v1

This commit is contained in:
DomySh
2022-06-12 12:09:50 +02:00
parent af63ab176f
commit e62e655fdc
20 changed files with 547 additions and 110 deletions

View File

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

View File

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

View File

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

View File

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

View 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;

View 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;

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
.box{
padding:30px;
margin:5px;
}

View 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;

View File

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

View File

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

View 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;

View File

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

View File

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