Improved stability

This commit is contained in:
DomySh
2022-06-13 10:59:05 +02:00
parent cff484a976
commit b53768b5d2
16 changed files with 270 additions and 159 deletions

View File

@@ -15,6 +15,9 @@ docker-compose.yml
# production # production
/frontend/build /frontend/build
/backend/db/firegex.db
/backend/db/firegex.db-journal
# misc # misc
**/.DS_Store **/.DS_Store
**/.env.local **/.env.local

2
.gitignore vendored
View File

@@ -9,6 +9,8 @@
# testing # testing
/frontend/coverage /frontend/coverage
/backend/db/firegex.db
/backend/db/firegex.db-journal
# production # production
/frontend/build /frontend/build

View File

@@ -6,14 +6,9 @@
## TODO ## TODO
1. custom windows docker-compose 1. custom windows docker-compose
2. fix glich change page with loading screen 2. backend checks and errors
3. Instant refresh after an add or delete 3. back and frontend password
4. backend checks and errors 4. compile c++ -O3
5. frontend requests on buttons
6. frontend messages on success and some failure
7. back and frontend password
8. volume on the database
9. compile c++ -O3
# #
# Documentation # Documentation
@@ -24,6 +19,7 @@
- [Service info](#get-apiserviceserv) - [Service info](#get-apiserviceserv)
- [Stop service](#get-apiserviceservstop) - [Stop service](#get-apiserviceservstop)
- [Start service](#get-apiserviceservstart) - [Start service](#get-apiserviceservstart)
- [Pause service](#get-apiserviceservpause)
- [Delete service](#get-apiserviceservdelete) - [Delete service](#get-apiserviceservdelete)
- [Terminate service](#get-apiserviceservterminate) - [Terminate service](#get-apiserviceservterminate)
- [Regenerate public port](#get-apiserviceservregen-port) - [Regenerate public port](#get-apiserviceservregen-port)
@@ -106,7 +102,7 @@
``` ```
# #
## **GET** **```/api/service/<serv>/terminate```** ## **GET** **```/api/service/<serv>/pause```**
### Server response: ### Server response:
```json ```json
{ {

View File

@@ -1,4 +1,4 @@
import sqlite3, random, string, subprocess import sqlite3, random, string, subprocess, sys, threading, os
from flask import Flask, jsonify, request, abort from flask import Flask, jsonify, request, abort
@@ -7,41 +7,61 @@ class SQLite():
self.conn = None self.conn = None
self.cur = None self.cur = None
self.db_name = db_name self.db_name = db_name
self.lock = threading.Lock()
def connect(self) -> None: def connect(self) -> None:
if not os.path.exists("db"): os.mkdir("db")
try: try:
self.conn = sqlite3.connect(self.db_name + '.db', check_same_thread = False) self.conn = sqlite3.connect("db/" + self.db_name + '.db', check_same_thread = False)
except: except Exception:
with open(self.db_name + '.db', 'x') as f: with open(self.db_name + '.db', 'x') as f:
pass pass
self.conn = sqlite3.connect("db/" + self.db_name + '.db', check_same_thread = False)
self.conn = sqlite3.connect(self.db_name + '.db', check_same_thread = False)
self.cur = self.conn.cursor()
def disconnect(self) -> None: def disconnect(self) -> None:
self.conn.close() self.conn.close()
def check_integrity(self, tables = {}) -> None: def check_integrity(self, tables = {}) -> None:
cur = self.conn.cursor()
for t in tables: for t in tables:
self.cur.execute(''' cur.execute('''
SELECT name FROM sqlite_master WHERE type='table' AND name='{}'; SELECT name FROM sqlite_master WHERE type='table' AND name='{}';
'''.format(t)) '''.format(t))
if len(self.cur.fetchall()) == 0: if len(cur.fetchall()) == 0:
self.cur.execute('''CREATE TABLE main.{}({});'''.format(t, ''.join([(c + ' ' + tables[t][c] + ', ') for c in tables[t]])[:-2])) cur.execute('''CREATE TABLE main.{}({});'''.format(t, ''.join([(c + ' ' + tables[t][c] + ', ') for c in tables[t]])[:-2]))
cur.close()
def query(self, query, values = ()): def query(self, query, values = ()):
self.cur.execute(query, values) cur = self.conn.cursor()
return self.cur.fetchall() try:
with self.lock:
cur.execute(query, values)
return cur.fetchall()
finally:
cur.close()
self.conn.commit()
def from_name_get_id(name):
serv_id = name.strip().replace(" ","-")
serv_id = "".join([c for c in serv_id if c in (string.ascii_uppercase + string.ascii_lowercase + string.digits + "-")])
return serv_id.lower()
def gen_internal_port():
while True:
res = random.randint(30000, 45000)
if len(db.query('SELECT 1 FROM services WHERE internal_port = ?;', (res,))) == 0:
break
return res
# DB init # DB init
db = SQLite('firegex') db = SQLite('firegex')
db.connect() db.connect()
app = Flask(__name__) app = Flask(__name__)
DEBUG = len(sys.argv) > 1 and sys.argv[1] == "DEBUG"
@app.route('/api/general-stats') @app.route('/api/general-stats')
def get_general_stats(): def get_general_stats():
n_services = db.query(''' n_services = db.query('''
@@ -54,15 +74,12 @@ def get_general_stats():
SELECT SUM(blocked_packets) FROM regexes; SELECT SUM(blocked_packets) FROM regexes;
''')[0][0] ''')[0][0]
res = { return {
'services': n_services, 'services': n_services,
'regexes': n_regexes, 'regexes': n_regexes,
'closed': n_packets if n_packets else 0 'closed': n_packets if n_packets else 0
} }
return res
@app.route('/api/services') @app.route('/api/services')
def get_services(): def get_services():
res = [] res = []
@@ -86,14 +103,10 @@ def get_services():
@app.route('/api/service/<serv>') @app.route('/api/service/<serv>')
def get_service(serv): def get_service(serv):
q = db.query('SELECT * FROM services WHERE service_id = ?;', (serv,)) q = db.query('SELECT * FROM services WHERE service_id = ?;', (serv,))
res = {}
if len(q) != 0: if len(q) != 0:
n_regex = db.query('SELECT COUNT (*) FROM regexes WHERE service_id = ?;', (serv,))[0][0] n_regex = db.query('SELECT COUNT (*) FROM regexes WHERE service_id = ?;', (serv,))[0][0]
n_packets = db.query('SELECT SUM(blocked_packets) FROM regexes WHERE service_id = ?;', (serv,))[0][0] n_packets = db.query('SELECT SUM(blocked_packets) FROM regexes WHERE service_id = ?;', (serv,))[0][0]
return {
print(q[0])
res = {
'id': q[0][1], 'id': q[0][1],
'status': q[0][0], 'status': q[0][0],
'public_port': q[0][3], 'public_port': q[0][3],
@@ -105,167 +118,146 @@ def get_service(serv):
else: else:
return abort(404) return abort(404)
return res
@app.route('/api/service/<serv>/stop') @app.route('/api/service/<serv>/stop')
def get_service_stop(serv): def get_service_stop(serv):
db.query(''' db.query('''
UPDATE services SET status = 'stop' WHERE service_id = ?; UPDATE services SET status = 'stop' WHERE service_id = ?;
''', (serv,)) ''', (serv,))
res = { return {
'status': 'ok' 'status': 'ok'
} }
return res @app.route('/api/service/<serv>/pause')
def get_service_pause(serv):
db.query('''
UPDATE services SET status = 'pause' WHERE service_id = ?;
''', (serv,))
return {
'status': 'ok'
}
@app.route('/api/service/<serv>/start') @app.route('/api/service/<serv>/start')
def get_service_start(serv): def get_service_start(serv):
db.query(''' db.query('''
UPDATE services SET status = 'active' WHERE service_id = ?; UPDATE services SET status = 'wait' WHERE service_id = ?;
''', (serv,)) ''', (serv,))
res = { return {
'status': 'ok' 'status': 'ok'
} }
return res
@app.route('/api/service/<serv>/delete') @app.route('/api/service/<serv>/delete')
def get_service_delete(serv): def get_service_delete(serv):
db.query(''' db.query('DELETE FROM services WHERE service_id = ?;', (serv,))
DELETE FROM services WHERE service_id = ?; db.query('DELETE FROM regexes WHERE service_id = ?;', (serv,))
''', (serv,))
res = { return {
'status': 'ok' 'status': 'ok'
} }
return res
@app.route('/api/service/<serv>/terminate')
def get_service_termite(serv):
db.query('''
UPDATE services SET status = 'stop' WHERE service_id = ?;
''', (serv,))
res = {
'status': 'ok'
}
return res
@app.route('/api/service/<serv>/regen-port') @app.route('/api/service/<serv>/regen-port')
def get_regen_port(serv): def get_regen_port(serv):
db.query('UPDATE services SET public_port = ? WHERE service_id = ?;', (random.randint(30000, 45000), serv)) db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', (gen_internal_port(), serv))
return {
res = {
'status': 'ok' 'status': 'ok'
} }
return res
@app.route('/api/service/<serv>/regexes') @app.route('/api/service/<serv>/regexes')
def get_service_regexes(serv): def get_service_regexes(serv):
res = [] return jsonify([
for i in db.query('SELECT * FROM regexes WHERE service_id = ?;', (serv,)): {
res.append({ 'id': row[5],
'id': i[5], 'service_id': row[2],
'service_id': i[2], 'regex': row[0],
'regex': i[0], 'is_blacklist': True if row[3] == "1" else False,
'is_blacklist': i[3], 'mode': row[1],
'mode': i[1] 'n_packets': row[4],
}) } for row in db.query('SELECT * FROM regexes WHERE service_id = ?;', (serv,))
])
return jsonify(res)
@app.route('/api/regex/<int:regex_id>') @app.route('/api/regex/<int:regex_id>')
def get_regex_id(regex_id): def get_regex_id(regex_id):
q = db.query('SELECT * FROM regexes WHERE regex_id = ?;', (regex_id,)) q = db.query('SELECT * FROM regexes WHERE regex_id = ?;', (regex_id,))
res = {}
if len(q) != 0: if len(q) != 0:
res = { return {
'id': regex_id, 'id': regex_id,
'service_id': q[0][2], 'service_id': q[0][2],
'regex': q[0][0], 'regex': q[0][0],
'is_blacklist': q[0][3], 'is_blacklist': True if q[0][3] == "1" else False,
'mode': q[0][1] 'mode': q[0][1],
'n_packets': q[0][4],
} }
else:
return res return abort(404)
@app.route('/api/regex/<int:regex_id>/delete') @app.route('/api/regex/<int:regex_id>/delete')
def get_regex_delete(regex_id): def get_regex_delete(regex_id):
db.query('DELETE FROM regexes WHERE regex_id = ?;', (regex_id,)) db.query('DELETE FROM regexes WHERE regex_id = ?;', (regex_id,))
res = { return {
'status': 'ok' 'status': 'ok'
} }
return res
@app.route('/api/regexes/add', methods = ['POST']) @app.route('/api/regexes/add', methods = ['POST'])
def post_regexes_add(): def post_regexes_add():
req = request.get_json(force = True) req = request.get_json(force = True)
db.query(''' db.query('''
INSERT INTO regexes (regex_id, service_id, regex, is_blacklist, mode) VALUES (?, ?, ?, ?, ?); INSERT INTO regexes (service_id, regex, is_blacklist, mode) VALUES (?, ?, ?, ?);
''', (random.randint(1, 1 << 32), req['service_id'], req['regex'], req['is_blacklist'], req['mode'])) ''', (req['service_id'], req['regex'], req['is_blacklist'], req['mode']))
res = { return {
'status': 'ok' 'status': 'ok'
} }
return res
@app.route('/api/services/add', methods = ['POST']) @app.route('/api/services/add', methods = ['POST'])
def post_services_add(): def post_services_add():
req = request.get_json(force = True) req = request.get_json(force = True)
serv_id = from_name_get_id(req['name'])
serv_id = req['name'].strip().replace(" ","-") try:
serv_id = "".join([c for c in serv_id if c in (string.ascii_uppercase + string.ascii_lowercase + string.digits + "-")]) db.query('''
serv_id = serv_id.lower() INSERT INTO services (name, service_id, internal_port, public_port, status) VALUES (?, ?, ?, ?, ?)
''', (req['name'], serv_id, gen_internal_port(), req['port'], 'stop'))
except sqlite3.IntegrityError:
return {'status': 'Name or/and port of the service has been already assigned to another service'}
db.query(''' return {'status': 'ok'}
INSERT INTO services (name, service_id, internal_port, public_port, status) VALUES (?, ?, ?, ?, ?)
''', (req['name'], serv_id, req['port'], random.randint(30000, 45000), 'stop'))
res = { if DEBUG:
'status': 'ok' from flask_cors import CORS
} cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
return res
if __name__ == '__main__': if __name__ == '__main__':
db.check_integrity({ db.check_integrity({
'services': {
'status': 'VARCHAR(100)',
'service_id': 'VARCHAR(100) PRIMARY KEY',
'internal_port': 'INT NOT NULL UNIQUE',
'public_port': 'INT NOT NULL UNIQUE',
'name': 'VARCHAR(100) NOT NULL'
},
'regexes': { 'regexes': {
'regex': 'TEXT NOT NULL', 'regex': 'TEXT NOT NULL',
'mode': 'CHAR(1)', 'mode': 'VARCHAR(1)',
'service_id': 'TEXT NOT NULL', 'service_id': 'VARCHAR(100) NOT NULL',
'is_blacklist': 'CHAR(50) NOT NULL', 'is_blacklist': 'VARCHAR(1) NOT NULL',
'blocked_packets': 'INTEGER DEFAULT 0', 'blocked_packets': 'INTEGER NOT NULL DEFAULT 0',
'regex_id': 'INTEGER NOT NULL' 'regex_id': 'INTEGER PRIMARY KEY',
'FOREIGN KEY (service_id)':'REFERENCES services (service_id)'
}, },
'services': {
'status': 'CHAR(50)',
'service_id': 'TEXT NOT NULL',
'internal_port': 'INT NOT NULL',
'public_port': 'INT NOT NULL',
'name': 'TEXT NOT NULL'
}
}) })
#uwsgi if DEBUG:
subprocess.run(["uwsgi","--socket","/tmp/uwsgi.sock","--master","--module","app:app"]) app.run(host="0.0.0.0", port=8080 ,debug=True)
else:
subprocess.run(["uwsgi","--socket","./uwsgi.sock","--master","--module","app:app"])

View File

@@ -6,3 +6,4 @@ Jinja2==3.1.2
MarkupSafe==2.1.1 MarkupSafe==2.1.1
Werkzeug==2.1.2 Werkzeug==2.1.2
uwsgi uwsgi
flask-cors

View File

@@ -14,12 +14,12 @@ http{
location / { location / {
include proxy_params; include proxy_params;
proxy_pass http://unix:/tmp/react.sock; proxy_pass http://unix:/execute/react.sock;
} }
location /api/ { location /api/ {
include uwsgi_params; include uwsgi_params;
uwsgi_pass unix:/tmp/uwsgi.sock; uwsgi_pass unix:/execute/uwsgi.sock;
} }
} }

View File

@@ -17,7 +17,7 @@ killasgroup=true
[program:frontend] [program:frontend]
directory=/execute directory=/execute
user = nobody user = nobody
command=serve -s frontend -l unix:/tmp/react.sock command=serve -s frontend -l unix:/execute/react.sock
startsecs=10 startsecs=10
stopsignal=QUIT stopsignal=QUIT
stopasgroup=true stopasgroup=true

View File

@@ -9,4 +9,6 @@ services:
- 80:80 - 80:80
environment: environment:
- NGINX_PORT=80 - NGINX_PORT=80
volumes:
- /execute/db

View File

@@ -2,7 +2,7 @@ import { Button, Group, Space, TextInput, Notification, Switch, NativeSelect } f
import { useForm } from '@mantine/hooks'; import { useForm } from '@mantine/hooks';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { RegexAddForm } from '../js/models'; import { RegexAddForm } from '../js/models';
import { addregex, b64encode, validateRegex } from '../js/utils'; import { addregex, b64encode, getHumanReadableRegex, okNotify, validateRegex } from '../js/utils';
import { ImCross } from "react-icons/im" import { ImCross } from "react-icons/im"
import FilterTypeSelector from './FilterTypeSelector'; import FilterTypeSelector from './FilterTypeSelector';
@@ -58,6 +58,7 @@ function AddNewRegex({ closePopup, service }:{ closePopup:()=>void, service:stri
if (!res){ if (!res){
setSubmitLoading(false) setSubmitLoading(false)
closePopup(); closePopup();
okNotify(`Regex ${getHumanReadableRegex(request.regex)} has been added`, `Successfully added ${request.is_blacklist?"blacklist":"whitelist"} regex to ${request.service_id} service`)
}else{ }else{
setSubmitLoading(false) setSubmitLoading(false)
setError("Invalid request! [ "+res+" ]") setError("Invalid request! [ "+res+" ]")

View File

@@ -2,7 +2,7 @@ import { Button, Group, NumberInput, Space, TextInput, Notification } from '@man
import { useForm } from '@mantine/hooks'; import { useForm } from '@mantine/hooks';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { ServiceAddForm } from '../js/models'; import { ServiceAddForm } from '../js/models';
import { addservice } from '../js/utils'; import { addservice, okNotify } from '../js/utils';
import { ImCross } from "react-icons/im" import { ImCross } from "react-icons/im"
function AddNewService({ closePopup }:{ closePopup:()=>void }) { function AddNewService({ closePopup }:{ closePopup:()=>void }) {
@@ -27,6 +27,7 @@ function AddNewService({ closePopup }:{ closePopup:()=>void }) {
if (!res){ if (!res){
setSubmitLoading(false) setSubmitLoading(false)
closePopup(); closePopup();
okNotify(`Service ${values.name} has been added`, `Successfully added ${values.name} with port ${values.port}`)
}else{ }else{
setSubmitLoading(false) setSubmitLoading(false)
setError("Invalid request! [ "+res+" ]") setError("Invalid request! [ "+res+" ]")

View File

@@ -1,7 +1,7 @@
import { Grid, Text, Title, Badge, Space, ActionIcon } from '@mantine/core'; import { Grid, Text, Title, Badge, Space, ActionIcon } from '@mantine/core';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { RegexFilter } from '../../js/models'; import { RegexFilter } from '../../js/models';
import { getHumanReadableRegex } from '../../js/utils'; import { deleteregex, errorNotify, 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';
@@ -24,6 +24,16 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
const [deleteModal, setDeleteModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false);
const deleteRegex = () => {
deleteregex(regexInfo.id).then(res => {
if(!res){
okNotify(`Regex ${regex_expr} deleted successfully!`,`Regex '${regex_expr}' ID:${regexInfo.id} has been deleted!`)
}else{
errorNotify(`Regex ${regex_expr} deleation failed!`,`Error: ${res}`)
}
}).catch( err => errorNotify(`Regex ${regex_expr} deleation failed!`,`Error: ${err}`))
}
return <div className={style.box}> return <div className={style.box}>
<Grid> <Grid>
<Grid.Col span={2}> <Grid.Col span={2}>
@@ -66,7 +76,7 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
title='Are you sure to delete this regex?' title='Are you sure to delete this regex?'
description={`You are going to delete the regex '${regex_expr}', causing the restart of the firewall if it is active.`} description={`You are going to delete the regex '${regex_expr}', causing the restart of the firewall if it is active.`}
onClose={()=>setDeleteModal(false)} onClose={()=>setDeleteModal(false)}
action={()=>console.log("Delete regex please!")} action={deleteRegex}
opened={deleteModal} opened={deleteModal}
/> />

View File

@@ -5,6 +5,7 @@ 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, okNotify, pauseservice, startservice, stopservice } from '../../js/utils';
//"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, additional_buttons }:{ service:Service, onClick?:()=>void, additional_buttons?:any }) {
@@ -20,27 +21,49 @@ 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 stopService = () => { const stopService = async () => {
setButtonLoading(true) setButtonLoading(true)
console.log("Stop this service please!") await stopservice(service.id).then(res => {
if(!res){
okNotify(`Service ${service.id} stopped successfully!`,`The service ${service.name} has been stopped!`)
}else{
errorNotify(`An error as occurred during the stopping of the service ${service.id}`,`Error: ${res}`)
}
}).catch(err => {
errorNotify(`An error as occurred during the stopping of the service ${service.id}`,`Error: ${err}`)
})
setButtonLoading(false) setButtonLoading(false)
} }
const startService = () => { const startService = async () => {
setButtonLoading(true) setButtonLoading(true)
console.log("Start this service please!") await startservice(service.id).then(res => {
if(!res){
okNotify(`Service ${service.id} started successfully!`,`The service ${service.name} has been started!`)
}else{
errorNotify(`An error as occurred during the starting of the service ${service.id}`,`Error: ${res}`)
}
}).catch(err => {
errorNotify(`An error as occurred during the starting of the service ${service.id}`,`Error: ${err}`)
})
setButtonLoading(false) setButtonLoading(false)
} }
const pauseService = () => { const pauseService = async () => {
if (service.status === "pause") return setStopModal(true)
setButtonLoading(true) setButtonLoading(true)
console.log("Pause this service please!") await pauseservice(service.id).then(res => {
if(!res){
okNotify(`Service ${service.id} paused successfully!`,`The service ${service.name} has been paused (Transparent mode)!`)
}else{
errorNotify(`An error as occurred during the pausing of the service ${service.id}`,`Error: ${res}`)
}
}).catch(err => {
errorNotify(`An error as occurred during the pausing of the service ${service.id}`,`Error: ${err}`)
})
setButtonLoading(false) setButtonLoading(false)
} }
return <> return <>
<Grid className={style.row} style={{width:"100%"}}> <Grid className={style.row} style={{width:"100%"}}>
<Grid.Col span={4}> <Grid.Col span={4}>
@@ -59,11 +82,19 @@ function ServiceRow({ service, onClick, additional_buttons }:{ service:Service,
<Space w="xl" /><Space w="xl" /> <Space w="xl" /><Space w="xl" />
<div className="center-flex"> <div className="center-flex">
{additional_buttons} {additional_buttons}
<ActionIcon color={service.status === "pause"?"yellow":"red"} loading={buttonLoading} {["pause","wait"].includes(service.status)?
<ActionIcon color="yellow" loading={buttonLoading}
onClick={stopService} size="xl" radius="md" variant="filled"
disabled={service.status === "stop"}>
<FaStop size="20px" />
</ActionIcon>:
<ActionIcon color="red" loading={buttonLoading}
onClick={pauseService} size="xl" radius="md" variant="filled" onClick={pauseService} size="xl" radius="md" variant="filled"
disabled={!["wait","active","pause"].includes(service.status)?true:false}> disabled={service.status === "stop"}>
{service.status === "pause"?<FaStop size="20px" />:<FaPause size="20px" />} <FaPause size="20px" />
</ActionIcon> </ActionIcon>
}
<Space w="md"/> <Space w="md"/>
<ActionIcon color="teal" size="xl" radius="md" onClick={startService} loading={buttonLoading} <ActionIcon color="teal" size="xl" radius="md" onClick={startService} loading={buttonLoading}
variant="filled" disabled={!["stop","pause"].includes(service.status)?true:false}> variant="filled" disabled={!["stop","pause"].includes(service.status)?true:false}>

View File

@@ -1,7 +1,7 @@
export const update_freq = 3000; export const update_freq = 2000;
export const notification_time = 2000; export const notification_time = 1500;
export type GeneralStats = { export type GeneralStats = {
services:number, services:number,
@@ -38,8 +38,8 @@ export type RegexFilter = {
} }
export type RegexAddForm = { export type RegexAddForm = {
"service_id":string, service_id:string,
"regex":string, regex:string,
"is_blacklist":boolean, is_blacklist:boolean,
"mode":string // C->S S->C BOTH mode:string // C->S S->C BOTH
} }

View File

@@ -5,12 +5,14 @@ import { GeneralStats, Service, ServiceAddForm, ServerResponse, RegexFilter, not
var Buffer = require('buffer').Buffer var Buffer = require('buffer').Buffer
const custom_url = ""//"http://127.0.0.1:8080"
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(`${custom_url}/api/${path}`).then( res => res.json() )
} }
export async function postapi(path:string,data:any):Promise<any>{ export async function postapi(path:string,data:any):Promise<any>{
return await fetch(`/api/${path}`, { return await fetch(`${custom_url}/api/${path}`, {
method: 'POST', method: 'POST',
cache: 'no-cache', cache: 'no-cache',
headers: { headers: {
@@ -33,11 +35,40 @@ export async function serviceinfo(service_id:string){
return await getapi(`service/${service_id}`) as Service; return await getapi(`service/${service_id}`) as Service;
} }
export async function deleteregex(regex_id:number){
const { status } = await getapi(`regex/${regex_id}/delete`) as ServerResponse;
return status === "ok"?undefined:status
}
export async function startservice(service_id:string){
const { status } = await getapi(`service/${service_id}/start`) as ServerResponse;
return status === "ok"?undefined:status
}
export async function stopservice(service_id:string){
const { status } = await getapi(`service/${service_id}/stop`) as ServerResponse;
return status === "ok"?undefined:status
}
export async function pauseservice(service_id:string){
const { status } = await getapi(`service/${service_id}/pause`) as ServerResponse;
return status === "ok"?undefined:status
}
export async function regenport(service_id:string){
const { status } = await getapi(`service/${service_id}/regen-port`) as ServerResponse;
return status === "ok"?undefined:status
}
export async function addservice(data:ServiceAddForm) { export async function addservice(data:ServiceAddForm) {
const { status } = await postapi("services/add",data) as ServerResponse; const { status } = await postapi("services/add",data) as ServerResponse;
return status === "ok"?undefined:status return status === "ok"?undefined:status
} }
export async function deleteservice(service_id:string) {
const { status } = await getapi(`service/${service_id}/delete`) as ServerResponse;
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,4 +1,4 @@
import { Space, Title } from '@mantine/core'; import { LoadingOverlay, Space, Title } from '@mantine/core';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import ServiceRow from '../components/ServiceRow'; import ServiceRow from '../components/ServiceRow';
@@ -9,14 +9,16 @@ import { errorNotify, servicelist } from '../js/utils';
function HomePage() { function HomePage() {
const [services, setServices] = useState<Service[]>([]); const [services, setServices] = useState<Service[]>([]);
const [loader, setLoader] = useState(true);
const navigator = useNavigate() const navigator = useNavigate()
const updateInfo = () => { const updateInfo = async () => {
servicelist().then(res => { await servicelist().then(res => {
setServices(res) setServices(res)
}).catch( }).catch(err => {
err => errorNotify("Home Page Auto-Update failed!", err.toString()) errorNotify("Home Page Auto-Update failed!", err.toString())
) })
setLoader(false)
} }
useEffect(()=>{ useEffect(()=>{
@@ -25,7 +27,10 @@ function HomePage() {
return () => { clearInterval(updater) } return () => { clearInterval(updater) }
}, []); }, []);
return <div id="service-list" className="center-flex-row"> return <div id="service-list" className="center-flex-row">
<LoadingOverlay visible={loader} />
{services.length > 0?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={3}>No services found! Add one clicking the button above</Title></>} }} />):<><Space h="xl"/> <Title className='center-flex' order={3}>No services found! Add one clicking the button above</Title></>}

View File

@@ -1,4 +1,4 @@
import { ActionIcon, Grid, Space, Title } from '@mantine/core'; import { ActionIcon, Grid, LoadingOverlay, Space, Title } from '@mantine/core';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { BsTrashFill } from 'react-icons/bs'; import { BsTrashFill } from 'react-icons/bs';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
@@ -6,7 +6,8 @@ import RegexView from '../components/RegexView';
import ServiceRow from '../components/ServiceRow'; import ServiceRow from '../components/ServiceRow';
import YesNoModal from '../components/YesNoModal'; import YesNoModal from '../components/YesNoModal';
import { RegexFilter, Service, update_freq } from '../js/models'; import { RegexFilter, Service, update_freq } from '../js/models';
import { errorNotify, serviceinfo, serviceregexlist } from '../js/utils'; import { deleteservice, errorNotify, okNotify, regenport, serviceinfo, serviceregexlist } from '../js/utils';
import { BsArrowRepeat } from "react-icons/bs"
function ServiceDetails() { function ServiceDetails() {
const {srv_id} = useParams() const {srv_id} = useParams()
@@ -22,6 +23,7 @@ function ServiceDetails() {
}) })
const [regexesList, setRegexesList] = useState<RegexFilter[]>([]) const [regexesList, setRegexesList] = useState<RegexFilter[]>([])
const [loader, setLoader] = useState(true);
const navigator = useNavigate() const navigator = useNavigate()
@@ -41,20 +43,47 @@ function ServiceDetails() {
}).catch( }).catch(
err => errorNotify(`Updater for ${srv_id} service failed [Regex list]!`, err.toString()) err => errorNotify(`Updater for ${srv_id} service failed [Regex list]!`, err.toString())
) )
setLoader(false)
} }
useEffect(()=>{ useEffect(()=>{
updateInfo() updateInfo()
const updater = setInterval(updateInfo, update_freq) const updater = setInterval(updateInfo, update_freq)
return () => { clearInterval(updater) } return () => { clearInterval(updater) }
}); },[]);
const [deleteModal, setDeleteModal] = useState(false) const [deleteModal, setDeleteModal] = useState(false)
const [changePortModal, setChangePortModal] = useState(false)
return <> const deleteService = () => {
deleteservice(serviceInfo.id).then(res => {
if (!res)
okNotify("Service delete complete!",`The service ${serviceInfo.id} has been deleted!`)
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!`)
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>
<LoadingOverlay visible={loader} />
<ServiceRow service={serviceInfo} additional_buttons={<> <ServiceRow service={serviceInfo} additional_buttons={<>
<ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled"><BsTrashFill size={22} /></ActionIcon> <ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled"><BsTrashFill size={22} /></ActionIcon>
<Space w="md"/> <Space w="md"/>
<ActionIcon color="blue" onClick={()=>setChangePortModal(true)} size="xl" radius="md" variant="filled"><BsArrowRepeat size={28} /></ActionIcon>
<Space w="md"/>
</>}></ServiceRow> </>}></ServiceRow>
{regexesList.length === 0? {regexesList.length === 0?
<><Space h="xl" /> <Title className='center-flex' order={3}>No regex found for this service! Add one clicking the add button above</Title></>: <><Space h="xl" /> <Title className='center-flex' order={3}>No regex found for this service! Add one clicking the add button above</Title></>:
@@ -66,10 +95,17 @@ function ServiceDetails() {
title='Are you sure to delete this service?' 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 ⚠️!`} 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)} onClose={()=>setDeleteModal(false)}
action={()=>console.log("Delete the service please!")} action={deleteService}
opened={deleteModal} 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>
} }
export default ServiceDetails; export default ServiceDetails;