Improved stability
This commit is contained in:
@@ -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
2
.gitignore
vendored
@@ -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
|
||||||
|
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
206
backend/app.py
206
backend/app.py
@@ -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"])
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -9,4 +9,6 @@ services:
|
|||||||
- 80:80
|
- 80:80
|
||||||
environment:
|
environment:
|
||||||
- NGINX_PORT=80
|
- NGINX_PORT=80
|
||||||
|
volumes:
|
||||||
|
- /execute/db
|
||||||
|
|
||||||
@@ -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+" ]")
|
||||||
|
|||||||
@@ -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+" ]")
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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></>}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user