Improve stability and functionalities

This commit is contained in:
DomySh
2022-06-30 15:58:03 +02:00
parent 4174654c3c
commit 02124c817b
16 changed files with 210 additions and 123 deletions

View File

@@ -36,8 +36,9 @@ def JWT_SECRET(): return conf.get("secret")
@app.on_event("shutdown") @app.on_event("shutdown")
async def shutdown_event(): async def shutdown_event():
await firewall.close()
db.disconnect() db.disconnect()
await firewall.close()
@app.on_event("startup") @app.on_event("startup")
async def startup_event(): async def startup_event():
@@ -197,7 +198,7 @@ async def get_service_regexes(service_id: str, auth: bool = Depends(is_loggined)
return db.query(""" return db.query("""
SELECT SELECT
regex, mode, regex_id `id`, service_id, is_blacklist, regex, mode, regex_id `id`, service_id, is_blacklist,
blocked_packets n_packets, is_case_sensitive blocked_packets n_packets, is_case_sensitive, active
FROM regexes WHERE service_id = ?; FROM regexes WHERE service_id = ?;
""", service_id) """, service_id)
@@ -206,7 +207,7 @@ async def get_regex_id(regex_id: int, auth: bool = Depends(is_loggined)):
res = db.query(""" res = db.query("""
SELECT SELECT
regex, mode, regex_id `id`, service_id, is_blacklist, regex, mode, regex_id `id`, service_id, is_blacklist,
blocked_packets n_packets, is_case_sensitive blocked_packets n_packets, is_case_sensitive, active
FROM regexes WHERE `id` = ?; FROM regexes WHERE `id` = ?;
""", regex_id) """, regex_id)
if len(res) == 0: raise HTTPException(status_code=400, detail="This regex does not exists!") if len(res) == 0: raise HTTPException(status_code=400, detail="This regex does not exists!")
@@ -221,6 +222,22 @@ async def get_regex_delete(regex_id: int, auth: bool = Depends(is_loggined)):
return {'status': 'ok'} return {'status': 'ok'}
@app.get('/api/regex/{regex_id}/enable')
async def get_regex_delete(regex_id: int, auth: bool = Depends(is_loggined)):
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
db.query('UPDATE regexes SET active=1 WHERE regex_id = ?;', regex_id)
await firewall.get(res[0]["service_id"]).update_filters()
return {'status': 'ok'}
@app.get('/api/regex/{regex_id}/disable')
async def get_regex_delete(regex_id: int, auth: bool = Depends(is_loggined)):
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
db.query('UPDATE regexes SET active=0 WHERE regex_id = ?;', regex_id)
await firewall.get(res[0]["service_id"]).update_filters()
return {'status': 'ok'}
class RegexAddForm(BaseModel): class RegexAddForm(BaseModel):
service_id: str service_id: str
regex: str regex: str
@@ -261,7 +278,7 @@ async def post_services_add(form: ServiceAddForm, auth: bool = Depends(is_loggin
async def frontend_debug_proxy(path): async def frontend_debug_proxy(path):
httpc = httpx.AsyncClient() httpc = httpx.AsyncClient()
req = httpc.build_request("GET",urllib.parse.urljoin(f"http://0.0.0.0:{os.getenv('F_PORT','3000')}", path)) req = httpc.build_request("GET",urllib.parse.urljoin(f"http://127.0.0.1:{os.getenv('F_PORT','3000')}", path))
resp = await httpc.send(req, stream=True) resp = await httpc.send(req, stream=True)
return StreamingResponse(resp.aiter_bytes(),status_code=resp.status_code) return StreamingResponse(resp.aiter_bytes(),status_code=resp.status_code)
@@ -287,7 +304,7 @@ if DEBUG:
@app.websocket("/ws") @app.websocket("/ws")
async def websocket_debug_proxy(ws: WebSocket): async def websocket_debug_proxy(ws: WebSocket):
await ws.accept() await ws.accept()
async with websockets.connect(f"ws://0.0.0.0:{os.getenv('F_PORT','3000')}/ws") as ws_b_client: async with websockets.connect(f"ws://127.0.0.1:{os.getenv('F_PORT','3000')}/ws") as ws_b_client:
fwd_task = asyncio.create_task(forward_websocket(ws, ws_b_client)) fwd_task = asyncio.create_task(forward_websocket(ws, ws_b_client))
rev_task = asyncio.create_task(reverse_websocket(ws, ws_b_client)) rev_task = asyncio.create_task(reverse_websocket(ws, ws_b_client))
await asyncio.gather(fwd_task, rev_task) await asyncio.gather(fwd_task, rev_task)
@@ -298,7 +315,7 @@ async def catch_all(full_path:str):
try: try:
return await frontend_debug_proxy(full_path) return await frontend_debug_proxy(full_path)
except Exception: except Exception:
return {"details":"Frontend not started at "+f"http://0.0.0.0:{os.getenv('F_PORT','3000')}"} return {"details":"Frontend not started at "+f"http://127.0.0.1:{os.getenv('F_PORT','3000')}"}
else: return await react_deploy(full_path) else: return await react_deploy(full_path)

View File

@@ -40,7 +40,7 @@ class Proxy:
if not self.isactive(): if not self.isactive():
try: try:
self.filter_map = self.compile_filters() self.filter_map = self.compile_filters()
filters_codes = list(self.filter_map.keys()) if not in_pause else [] filters_codes = self.get_filter_codes() if not in_pause else []
proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"./proxy") proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"./proxy")
self.process = await asyncio.create_subprocess_exec( self.process = await asyncio.create_subprocess_exec(
@@ -57,8 +57,9 @@ class Proxy:
if stdout_line.startswith("BLOCKED"): if stdout_line.startswith("BLOCKED"):
regex_id = stdout_line.split()[1] regex_id = stdout_line.split()[1]
async with self.filter_map_lock: async with self.filter_map_lock:
self.filter_map[regex_id].blocked+=1 if regex_id in self.filter_map:
if self.callback_blocked_update: self.callback_blocked_update(self.filter_map[regex_id]) self.filter_map[regex_id].blocked+=1
if self.callback_blocked_update: self.callback_blocked_update(self.filter_map[regex_id])
except Exception: except Exception:
return await self.process.wait() return await self.process.wait()
else: else:
@@ -87,9 +88,14 @@ class Proxy:
if self.isactive(): if self.isactive():
async with self.filter_map_lock: async with self.filter_map_lock:
self.filter_map = self.compile_filters() self.filter_map = self.compile_filters()
filters_codes = list(self.filter_map.keys()) filters_codes = self.get_filter_codes()
await self.update_config(filters_codes) await self.update_config(filters_codes)
def get_filter_codes(self):
filters_codes = list(self.filter_map.keys())
filters_codes.sort(key=lambda a: self.filter_map[a].blocked, reverse=True)
return filters_codes
def isactive(self): def isactive(self):
return self.process and self.process.returncode is None return self.process and self.process.returncode is None

View File

@@ -66,6 +66,7 @@ class SQLite():
'blocked_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0', 'blocked_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0',
'regex_id': 'INTEGER PRIMARY KEY', 'regex_id': 'INTEGER PRIMARY KEY',
'is_case_sensitive' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1))', 'is_case_sensitive' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1))',
'active' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1)) DEFAULT 1',
'FOREIGN KEY (service_id)':'REFERENCES services (service_id)', 'FOREIGN KEY (service_id)':'REFERENCES services (service_id)',
}, },
'keys_values': { 'keys_values': {
@@ -109,6 +110,7 @@ class ServiceManager:
callback_blocked_update=self._stats_updater callback_blocked_update=self._stats_updater
) )
self.status = STATUS.STOP self.status = STATUS.STOP
self.wanted_status = STATUS.STOP
self.filters = {} self.filters = {}
self._update_port_from_db() self._update_port_from_db()
self._update_filters_from_db() self._update_filters_from_db()
@@ -131,7 +133,7 @@ class ServiceManager:
SELECT SELECT
regex, mode, regex_id `id`, is_blacklist, regex, mode, regex_id `id`, is_blacklist,
blocked_packets n_packets, is_case_sensitive blocked_packets n_packets, is_case_sensitive
FROM regexes WHERE service_id = ?; FROM regexes WHERE service_id = ? AND active=1;
""", self.id) """, self.id)
#Filter check #Filter check
@@ -162,26 +164,30 @@ class ServiceManager:
async def next(self,to): async def next(self,to):
async with self.lock: async with self.lock:
if self.status != to: return await self._next(to)
# ACTIVE -> PAUSE or PAUSE -> ACTIVE
if (self.status, to) in [(STATUS.ACTIVE, STATUS.PAUSE)]:
await self.proxy.pause()
self._set_status(to)
elif (self.status, to) in [(STATUS.PAUSE, STATUS.ACTIVE)]: async def _next(self, to):
await self.proxy.reload() if self.status != to:
self._set_status(to) # ACTIVE -> PAUSE or PAUSE -> ACTIVE
if (self.status, to) in [(STATUS.ACTIVE, STATUS.PAUSE)]:
await self.proxy.pause()
self._set_status(to)
# ACTIVE -> STOP elif (self.status, to) in [(STATUS.PAUSE, STATUS.ACTIVE)]:
elif (self.status,to) in [(STATUS.ACTIVE, STATUS.STOP), (STATUS.WAIT, STATUS.STOP), (STATUS.PAUSE, STATUS.STOP)]: #Stop proxy await self.proxy.reload()
if self.starter: self.starter.cancel() self._set_status(to)
await self.proxy.stop()
self._set_status(to)
# STOP -> ACTIVE or STOP -> PAUSE # ACTIVE -> STOP
elif (self.status, to) in [(STATUS.STOP, STATUS.ACTIVE), (STATUS.STOP, STATUS.PAUSE)]: elif (self.status,to) in [(STATUS.ACTIVE, STATUS.STOP), (STATUS.WAIT, STATUS.STOP), (STATUS.PAUSE, STATUS.STOP)]: #Stop proxy
self._set_status(STATUS.WAIT) if self.starter: self.starter.cancel()
self.__proxy_starter(to) await self.proxy.stop()
self._set_status(to)
# STOP -> ACTIVE or STOP -> PAUSE
elif (self.status, to) in [(STATUS.STOP, STATUS.ACTIVE), (STATUS.STOP, STATUS.PAUSE)]:
self.wanted_status = to
self._set_status(STATUS.WAIT)
self.__proxy_starter(to)
def _stats_updater(self,filter:Filter): def _stats_updater(self,filter:Filter):
@@ -191,7 +197,9 @@ class ServiceManager:
async with self.lock: async with self.lock:
self._update_port_from_db() self._update_port_from_db()
if self.status in [STATUS.PAUSE, STATUS.ACTIVE]: if self.status in [STATUS.PAUSE, STATUS.ACTIVE]:
await self.proxy.restart(in_pause=(self.status == STATUS.PAUSE)) next_status = self.status if self.status != STATUS.WAIT else self.wanted_status
await self._next(STATUS.STOP)
await self._next(next_status)
def _set_status(self,status): def _set_status(self,status):
self.status = status self.status = status
@@ -233,7 +241,7 @@ class ProxyManager:
async def remove(self,id): async def remove(self,id):
async with self.lock: async with self.lock:
if id in self.proxy_table: if id in self.proxy_table:
await self.proxy_table[id].proxy.stop() await self.proxy_table[id].next(STATUS.STOP)
del self.proxy_table[id] del self.proxy_table[id]
async def reload(self): async def reload(self):

BIN
db/firegex.db Normal file

Binary file not shown.

View File

@@ -1,13 +1,13 @@
{ {
"files": { "files": {
"main.css": "/static/css/main.c375ae17.css", "main.css": "/static/css/main.c375ae17.css",
"main.js": "/static/js/main.cd9ce6a2.js", "main.js": "/static/js/main.a6ace121.js",
"index.html": "/index.html", "index.html": "/index.html",
"main.c375ae17.css.map": "/static/css/main.c375ae17.css.map", "main.c375ae17.css.map": "/static/css/main.c375ae17.css.map",
"main.cd9ce6a2.js.map": "/static/js/main.cd9ce6a2.js.map" "main.a6ace121.js.map": "/static/js/main.a6ace121.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.c375ae17.css", "static/css/main.c375ae17.css",
"static/js/main.cd9ce6a2.js" "static/js/main.a6ace121.js"
] ]
} }

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/site.webmanifest"><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#FFFFFFFF"/><meta name="description" content="Firegex by Pwnzer0tt1"/><title>Firegex</title><script defer="defer" src="/static/js/main.cd9ce6a2.js"></script><link href="/static/css/main.c375ae17.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/site.webmanifest"><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#FFFFFFFF"/><meta name="description" content="Firegex by Pwnzer0tt1"/><title>Firegex</title><script defer="defer" src="/static/js/main.a6ace121.js"></script><link href="/static/css/main.c375ae17.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -15,12 +15,15 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
const form = useForm({ const form = useForm({
initialValues: { initialValues: {
name:"", name:"",
port:1, port:8080,
internalPort:30001,
chosenInternalPort:false,
autostart: true autostart: true
}, },
validationRules:{ validationRules:{
name: (value) => value !== ""?true:false, name: (value) => value !== ""?true:false,
port: (value) => value>0 && value<65536 port: (value) => value>0 && value<65536,
internalPort: (value) => value>0 && value<65536,
} }
}) })
@@ -66,15 +69,34 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
placeholder="8080" placeholder="8080"
min={1} min={1}
max={65535} max={65535}
label="Service port" label="Public Service port"
{...form.getInputProps('port')} {...form.getInputProps('port')}
/> />
{form.values.chosenInternalPort?<>
<Space h="md" />
<NumberInput
placeholder="8080"
min={1}
max={65535}
label="Internal Proxy Port"
{...form.getInputProps('internalPort')}
/>
<Space h="sm" />
</>:null}
<Space h="xl" />
<Switch
label="Auto-Start Service"
{...form.getInputProps('autostart', { type: 'checkbox' })}
/>
<Space h="md" /> <Space h="md" />
<Switch <Switch
label="Auto-Start Service" label="Choose internal port"
{...form.getInputProps('autostart', { type: 'checkbox' })} {...form.getInputProps('chosenInternalPort', { type: 'checkbox' })}
/> />
<Group position="right" mt="md"> <Group position="right" mt="md">

View File

@@ -1,11 +1,12 @@
import { Grid, Text, Title, Badge, Space, ActionIcon, Tooltip } from '@mantine/core'; import { Grid, Text, Title, Badge, Space, ActionIcon, Tooltip } from '@mantine/core';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { RegexFilter } from '../../js/models'; import { RegexFilter } from '../../js/models';
import { deleteregex, errorNotify, fireUpdateRequest, getHumanReadableRegex, okNotify } from '../../js/utils'; import { activateregex, deactivateregex, deleteregex, errorNotify, fireUpdateRequest, getHumanReadableRegex, okNotify } from '../../js/utils';
import style from "./RegexView.module.scss"; import style from "./RegexView.module.scss";
import { BsTrashFill } from "react-icons/bs" import { BsTrashFill } from "react-icons/bs"
import YesNoModal from '../YesNoModal'; import YesNoModal from '../YesNoModal';
import FilterTypeSelector from '../FilterTypeSelector'; import FilterTypeSelector from '../FilterTypeSelector';
import { FaPause, FaPlay } from 'react-icons/fa';
function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) { function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
@@ -17,7 +18,8 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
let regex_expr = getHumanReadableRegex(regexInfo.regex); let regex_expr = getHumanReadableRegex(regexInfo.regex);
const [deleteModal, setDeleteModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false);
const [tooltipOpened, setTooltipOpened] = useState(false); const [deleteTooltipOpened, setDeleteTooltipOpened] = useState(false);
const [statusTooltipOpened, setStatusTooltipOpened] = useState(false);
const deleteRegex = () => { const deleteRegex = () => {
deleteregex(regexInfo.id).then(res => { deleteregex(regexInfo.id).then(res => {
@@ -30,6 +32,19 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
}).catch( err => errorNotify(`Regex ${regex_expr} deleation failed!`,`Error: ${err}`)) }).catch( err => errorNotify(`Regex ${regex_expr} deleation failed!`,`Error: ${err}`))
} }
const changeRegexStatus = () => {
(regexInfo.active?deactivateregex:activateregex)(regexInfo.id).then(res => {
if(!res){
okNotify(`Regex ${regex_expr} ${regexInfo.active?"deactivated":"activated"} successfully!`,`Regex '${regex_expr}' ID:${regexInfo.id} has been ${regexInfo.active?"deactivated":"activated"}!`)
fireUpdateRequest()
}else{
errorNotify(`Regex ${regex_expr} ${regexInfo.active?"deactivation":"activation"} failed!`,`Error: ${res}`)
}
}).catch( err => errorNotify(`Regex ${regex_expr} ${regexInfo.active?"deactivation":"activation"} failed!`,`Error: ${err}`))
}
return <div className={style.box}> return <div className={style.box}>
<Grid> <Grid>
<Grid.Col span={2}> <Grid.Col span={2}>
@@ -38,14 +53,22 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
<Grid.Col span={8}> <Grid.Col span={8}>
<Text className={style.regex_text}> {regex_expr}</Text> <Text className={style.regex_text}> {regex_expr}</Text>
</Grid.Col> </Grid.Col>
<Grid.Col span={2}> <Grid.Col span={2} className='center-flex'>
<Tooltip label="Delete regex" zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="red" opened={tooltipOpened} tooltipId="tooltip-id"> <Space w="xs" />
<Tooltip label={regexInfo.active?"Deactivate":"Activate"} zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color={regexInfo.active?"orange":"teal"} opened={statusTooltipOpened}>
<ActionIcon color={regexInfo.active?"orange":"teal"} onClick={changeRegexStatus} size="xl" radius="md" variant="filled"
onFocus={() => setStatusTooltipOpened(false)} onBlur={() => setStatusTooltipOpened(false)}
onMouseEnter={() => setStatusTooltipOpened(true)} onMouseLeave={() => setStatusTooltipOpened(false)}
>{regexInfo.active?<FaPause size="20px" />:<FaPlay size="20px" />}</ActionIcon>
</Tooltip>
<Space w="xs" />
<Tooltip label="Delete regex" zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="red" opened={deleteTooltipOpened} >
<ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled" <ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled"
aria-describedby="tooltip-id" onFocus={() => setDeleteTooltipOpened(false)} onBlur={() => setDeleteTooltipOpened(false)}
onFocus={() => setTooltipOpened(false)} onBlur={() => setTooltipOpened(false)} onMouseEnter={() => setDeleteTooltipOpened(true)} onMouseLeave={() => setDeleteTooltipOpened(false)}
onMouseEnter={() => setTooltipOpened(true)} onMouseLeave={() => setTooltipOpened(false)}
><BsTrashFill size={22} /></ActionIcon> ><BsTrashFill size={22} /></ActionIcon>
</Tooltip> </Tooltip>
</Grid.Col> </Grid.Col>
<Grid.Col span={2} /> <Grid.Col span={2} />
<Grid.Col className='center-flex-row' span={4}> <Grid.Col className='center-flex-row' span={4}>
@@ -58,9 +81,12 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
/> />
<Space h="md" /> <Space h="md" />
<div className='center-flex'> <div className='center-flex'>
<Badge size="md" color="green" variant="filled">Service: {regexInfo.service_id}</Badge> <Badge size="md" color="cyan" variant="filled">Service: {regexInfo.service_id}</Badge>
<Space w="xs" />
<Badge size="md" color={regexInfo.active?"lime":"red"} variant="filled">{regexInfo.active?"ACTIVE":"DISABLED"}</Badge>
<Space w="xs" /> <Space w="xs" />
<Badge size="md" color="gray" variant="filled">ID: {regexInfo.id}</Badge> <Badge size="md" color="gray" variant="filled">ID: {regexInfo.id}</Badge>
</div> </div>
</Grid.Col> </Grid.Col>
<Grid.Col style={{width:"100%"}} span={6}> <Grid.Col style={{width:"100%"}} span={6}>

View File

@@ -1,14 +1,17 @@
import { ActionIcon, Badge, Grid, MediaQuery, Space, Title, Tooltip } from '@mantine/core'; import { ActionIcon, Badge, Divider, Grid, MediaQuery, Menu, Space, Title, Tooltip } from '@mantine/core';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FaPause, FaPlay, FaStop } 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";
import YesNoModal from '../YesNoModal'; import YesNoModal from '../YesNoModal';
import { errorNotify, fireUpdateRequest, okNotify, pauseservice, startservice, stopservice } from '../../js/utils'; import { deleteservice, errorNotify, fireUpdateRequest, okNotify, pauseservice, regenport, startservice, stopservice } from '../../js/utils';
import { BsArrowRepeat, BsTrashFill } from 'react-icons/bs';
import { TbNumbers } from 'react-icons/tb';
import { BiRename } from 'react-icons/bi'
//"status":"stop"/"wait"/"active"/"pause", //"status":"stop"/"wait"/"active"/"pause",
function ServiceRow({ service, onClick, additional_buttons }:{ service:Service, onClick?:()=>void, additional_buttons?:any }) { function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) {
let status_color = "gray"; let status_color = "gray";
switch(service.status){ switch(service.status){
@@ -21,6 +24,8 @@ function ServiceRow({ service, onClick, additional_buttons }:{ service:Service,
const [stopModal, setStopModal] = useState(false); const [stopModal, setStopModal] = useState(false);
const [buttonLoading, setButtonLoading] = useState(false) const [buttonLoading, setButtonLoading] = useState(false)
const [tooltipStopOpened, setTooltipStopOpened] = useState(false); const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
const [deleteModal, setDeleteModal] = useState(false)
const [changePortModal, setChangePortModal] = useState(false)
const stopService = async () => { const stopService = async () => {
setButtonLoading(true) setButtonLoading(true)
@@ -68,6 +73,31 @@ function ServiceRow({ service, onClick, additional_buttons }:{ service:Service,
} }
const deleteService = () => {
deleteservice(service.id).then(res => {
if (!res){
okNotify("Service delete complete!",`The service ${service.id} has been deleted!`)
fireUpdateRequest();
}else
errorNotify("An error occurred while deleting a service",`Error: ${res}`)
}).catch(err => {
errorNotify("An error occurred while deleting a service",`Error: ${err}`)
})
}
const changePort = () => {
regenport(service.id).then(res => {
if (!res){
okNotify("Service port regeneration completed!",`The service ${service.id} has changed the internal port!`)
fireUpdateRequest();
}else
errorNotify("An error occurred while changing the internal service port",`Error: ${res}`)
}).catch(err => {
errorNotify("An error occurred while changing the internal service port",`Error: ${err}`)
})
}
return <> return <>
<Grid className={style.row} justify="flex-end" style={{width:"100%"}}> <Grid className={style.row} justify="flex-end" style={{width:"100%"}}>
<Grid.Col md={4} xs={12}> <Grid.Col md={4} xs={12}>
@@ -110,7 +140,21 @@ function ServiceRow({ service, onClick, additional_buttons }:{ service:Service,
<><Space w="xl" /><Space w="xl" /></> <><Space w="xl" /><Space w="xl" /></>
</MediaQuery> </MediaQuery>
<div className="center-flex"> <div className="center-flex">
{additional_buttons} <Menu>
<Menu.Label><b>Rename service</b></Menu.Label>
<Menu.Item icon={<BiRename size={18} />} onClick={()=>{}}>Change service name</Menu.Item>
<Divider />
<Menu.Label><b>Public proxy port</b></Menu.Label>
<Menu.Item icon={<TbNumbers size={18} />} onClick={()=>{}}>Change port</Menu.Item>
<Divider />
<Menu.Label><b>Internal proxy port</b></Menu.Label>
<Menu.Item icon={<BsArrowRepeat size={18} />} onClick={()=>setChangePortModal(true)}>Regen port</Menu.Item>
<Menu.Item icon={<TbNumbers size={18} />} onClick={()=>{}}>Choose port</Menu.Item>
<Divider />
<Menu.Label><b>Delete service</b></Menu.Label>
<Menu.Item color="red" icon={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</Menu>
<Space w="md"/>
{["pause","wait"].includes(service.status)? {["pause","wait"].includes(service.status)?
<Tooltip label="Stop service" zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="orange" opened={tooltipStopOpened} tooltipId="tooltip-stop-id"> <Tooltip label="Stop service" zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="orange" opened={tooltipStopOpened} tooltipId="tooltip-stop-id">
@@ -159,6 +203,20 @@ function ServiceRow({ service, onClick, additional_buttons }:{ service:Service,
opened={stopModal} opened={stopModal}
/> />
<hr style={{width:"100%"}}/> <hr style={{width:"100%"}}/>
<YesNoModal
title='Are you sure to delete this service?'
description={`You are going to delete the service '${service.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={deleteService}
opened={deleteModal}
/>
<YesNoModal
title='Are you sure to change the proxy internal port?'
description={`You are going to change the proxy port '${service.internal_port}'. This will cause the shutdown of your service temporarily! ⚠️`}
onClose={()=>setChangePortModal(false)}
action={changePort}
opened={changePortModal}
/>
</> </>
} }

View File

@@ -59,7 +59,8 @@ export type RegexFilter = {
is_blacklist:boolean, is_blacklist:boolean,
is_case_sensitive:boolean, is_case_sensitive:boolean,
mode:string //C S B => C->S S->C BOTH mode:string //C S B => C->S S->C BOTH
n_packets:number n_packets:number,
active:boolean
} }
export type RegexAddForm = { export type RegexAddForm = {

View File

@@ -99,6 +99,16 @@ export async function deleteregex(regex_id:number){
return status === "ok"?undefined:status return status === "ok"?undefined:status
} }
export async function activateregex(regex_id:number){
const { status } = await getapi(`regex/${regex_id}/enable`) as ServerResponse;
return status === "ok"?undefined:status
}
export async function deactivateregex(regex_id:number){
const { status } = await getapi(`regex/${regex_id}/disable`) as ServerResponse;
return status === "ok"?undefined:status
}
export async function startservice(service_id:string){ export async function startservice(service_id:string){
const { status } = await getapi(`service/${service_id}/start`) as ServerResponse; const { status } = await getapi(`service/${service_id}/start`) as ServerResponse;
return status === "ok"?undefined:status return status === "ok"?undefined:status
@@ -127,6 +137,7 @@ export async function deleteservice(service_id:string) {
return status === "ok"?undefined:status return status === "ok"?undefined:status
} }
export async function addregex(data:RegexAddForm) { export async function addregex(data:RegexAddForm) {
const { status } = await postapi("regexes/add",data) as ServerResponse; const { status } = await postapi("regexes/add",data) as ServerResponse;
return status === "ok"?undefined:status return status === "ok"?undefined:status

View File

@@ -1,15 +1,12 @@
import { ActionIcon, Grid, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core'; import { ActionIcon, Grid, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { BsTrashFill } from 'react-icons/bs';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import RegexView from '../components/RegexView'; import RegexView from '../components/RegexView';
import ServiceRow from '../components/ServiceRow'; import ServiceRow from '../components/ServiceRow';
import AddNewRegex from '../components/AddNewRegex'; import AddNewRegex from '../components/AddNewRegex';
import { BsPlusLg } from "react-icons/bs"; import { BsPlusLg } from "react-icons/bs";
import YesNoModal from '../components/YesNoModal';
import { RegexFilter, Service } from '../js/models'; import { RegexFilter, Service } from '../js/models';
import { deleteservice, errorNotify, eventUpdateName, fireUpdateRequest, okNotify, regenport, serviceinfo, serviceregexlist } from '../js/utils'; import { errorNotify, eventUpdateName, fireUpdateRequest, serviceinfo, serviceregexlist } from '../js/utils';
import { BsArrowRepeat } from "react-icons/bs"
import { useWindowEvent } from '@mantine/hooks'; import { useWindowEvent } from '@mantine/hooks';
function ServiceDetails() { function ServiceDetails() {
@@ -54,58 +51,12 @@ function ServiceDetails() {
const navigator = useNavigate() const navigator = useNavigate()
const [deleteModal, setDeleteModal] = useState(false)
const [changePortModal, setChangePortModal] = useState(false)
const [tooltipDeleteOpened, setTooltipDeleteOpened] = useState(false);
const [tooltipChangeOpened, setTooltipChangeOpened] = useState(false);
const [tooltipAddRegexOpened, setTooltipAddRegexOpened] = useState(false); const [tooltipAddRegexOpened, setTooltipAddRegexOpened] = useState(false);
const deleteService = () => {
deleteservice(serviceInfo.id).then(res => {
if (!res){
okNotify("Service delete complete!",`The service ${serviceInfo.id} has been deleted!`)
updateInfo();
}else
errorNotify("An error occurred while deleting a service",`Error: ${res}`)
}).catch(err => {
errorNotify("An error occurred while deleting a service",`Error: ${err}`)
})
}
const changePort = () => {
regenport(serviceInfo.id).then(res => {
if (!res){
okNotify("Service port regeneration completed!",`The service ${serviceInfo.id} has changed the internal port!`)
updateInfo();
}else
errorNotify("An error occurred while changing the internal service port",`Error: ${res}`)
}).catch(err => {
errorNotify("An error occurred while changing the internal service port",`Error: ${err}`)
})
}
return <div> return <div>
<LoadingOverlay visible={loader} /> <LoadingOverlay visible={loader} />
<ServiceRow service={serviceInfo} additional_buttons={<> <ServiceRow service={serviceInfo} />
<Tooltip label="Delete service" zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="red" opened={tooltipDeleteOpened} tooltipId="tooltip-delete-id">
<ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled"
aria-describedby="tooltip-delete-id"
onFocus={() => setTooltipDeleteOpened(false)} onBlur={() => setTooltipDeleteOpened(false)}
onMouseEnter={() => setTooltipDeleteOpened(true)} onMouseLeave={() => setTooltipDeleteOpened(false)}
><BsTrashFill size={22} /></ActionIcon>
</Tooltip>
<Space w="md"/>
<Tooltip label="Change proxy port" zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="blue" opened={tooltipChangeOpened} tooltipId="tooltip-change-id">
<ActionIcon color="blue" onClick={()=>setChangePortModal(true)} size="xl" radius="md" variant="filled"
aria-describedby="tooltip-change-id"
onFocus={() => setTooltipChangeOpened(false)} onBlur={() => setTooltipChangeOpened(false)}
onMouseEnter={() => setTooltipChangeOpened(true)} onMouseLeave={() => setTooltipChangeOpened(false)}
><BsArrowRepeat size={28} /></ActionIcon>
</Tooltip>
<Space w="md"/>
</>}></ServiceRow>
{regexesList.length === 0?<> {regexesList.length === 0?<>
<Space h="xl" /> <Space h="xl" />
<Title className='center-flex' align='center' order={3}>No regex found for this service! Add one by clicking the "+" buttons</Title> <Title className='center-flex' align='center' order={3}>No regex found for this service! Add one by clicking the "+" buttons</Title>
@@ -126,20 +77,7 @@ function ServiceDetails() {
{srv_id?<AddNewRegex opened={open} onClose={closeModal} service={srv_id} />:null} {srv_id?<AddNewRegex opened={open} onClose={closeModal} service={srv_id} />:null}
<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={deleteService}
opened={deleteModal}
/>
<YesNoModal
title='Are you sure to change the proxy internal port?'
description={`You are going to change the proxy port '${serviceInfo.internal_port}'. This will cause the shutdown of your service temporarily! ⚠️`}
onClose={()=>setChangePortModal(false)}
action={changePort}
opened={changePortModal}
/>
</div> </div>
} }