diff --git a/backend/app.py b/backend/app.py index 2705400..445c30f 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,5 +1,6 @@ from base64 import b64decode import sqlite3, uvicorn, sys, secrets, re, os, asyncio, httpx, urllib, websockets +from typing import Union from fastapi import FastAPI, HTTPException, WebSocket, Depends from pydantic import BaseModel, BaseSettings from fastapi.responses import FileResponse, StreamingResponse @@ -22,7 +23,7 @@ class Settings(BaseSettings): JWT_ALGORITHM: str = "HS256" REACT_BUILD_DIR: str = "../frontend/build/" if not ON_DOCKER else "frontend/" REACT_HTML_PATH: str = os.path.join(REACT_BUILD_DIR,"index.html") - VERSION = "2.0.0" + VERSION = "1.3.0" settings = Settings() @@ -36,7 +37,6 @@ def JWT_SECRET(): return conf.get("secret") @app.on_event("shutdown") async def shutdown_event(): - db.disconnect() await firewall.close() @@ -192,6 +192,31 @@ async def get_regen_port(service_id: str, auth: bool = Depends(is_loggined)): await firewall.get(service_id).update_port() return {'status': 'ok'} +class ChangePortForm(BaseModel): + port: Union[int, None] + internalPort: Union[int, None] + +@app.post('/api/service/{service_id}/change-ports') +async def get_regen_port(service_id: str, change_port:ChangePortForm, auth: bool = Depends(is_loggined)): + if change_port.port is None and change_port.internalPort is None: + return {'status': 'Invalid Request!'} + try: + sql_inj = "" + query = [] + if not change_port.port is None: + sql_inj+=" public_port = ? " + query.append(change_port.port) + if not change_port.internalPort is None: + sql_inj+=" internal_port = ? " + query.append(change_port.internalPort) + query.append(service_id) + db.query(f'UPDATE services SET {sql_inj} WHERE service_id = ?;', *query) + except sqlite3.IntegrityError: + return {'status': 'Name or/and port of the service has been already assigned to another service'} + await firewall.get(service_id).update_port() + return {'status': 'ok'} + + @app.get('/api/service/{service_id}/regexes') async def get_service_regexes(service_id: str, auth: bool = Depends(is_loggined)): @@ -263,17 +288,19 @@ async def post_regexes_add(form: RegexAddForm, auth: bool = Depends(is_loggined) class ServiceAddForm(BaseModel): name: str port: int + internalPort: Union[int, None] @app.post('/api/services/add') async def post_services_add(form: ServiceAddForm, auth: bool = Depends(is_loggined)): serv_id = gen_service_id(db) try: + internal_port = form.internalPort if form.internalPort else gen_internal_port(db) db.query("INSERT INTO services (name, service_id, internal_port, public_port, status) VALUES (?, ?, ?, ?, ?)", - form.name, serv_id, gen_internal_port(db), form.port, 'stop') - await firewall.reload() + form.name, serv_id, internal_port, form.port, 'stop') except sqlite3.IntegrityError: - return {'status': 'Name or/and port of the service has been already assigned to another service'} - + return {'status': 'Name or/and ports of the service has been already assigned to another service'} + await firewall.reload() + return {'status': 'ok', "id": serv_id } async def frontend_debug_proxy(path): diff --git a/backend/utils.py b/backend/utils.py index b917ef6..94a122a 100755 --- a/backend/utils.py +++ b/backend/utils.py @@ -54,7 +54,7 @@ class SQLite(): 'services': { 'status': 'VARCHAR(100) NOT NULL', 'service_id': 'VARCHAR(100) PRIMARY KEY', - 'internal_port': 'INT NOT NULL CHECK(internal_port > 0 and internal_port < 65536) UNIQUE', + 'internal_port': 'INT NOT NULL CHECK(internal_port > 0 and internal_port < 65536)', 'public_port': 'INT NOT NULL CHECK(internal_port > 0 and internal_port < 65536) UNIQUE', 'name': 'VARCHAR(100) NOT NULL' }, diff --git a/frontend/build/asset-manifest.json b/frontend/build/asset-manifest.json index c403b4c..97000d0 100755 --- a/frontend/build/asset-manifest.json +++ b/frontend/build/asset-manifest.json @@ -1,13 +1,13 @@ { "files": { "main.css": "/static/css/main.c375ae17.css", - "main.js": "/static/js/main.a6ace121.js", + "main.js": "/static/js/main.bf062beb.js", "index.html": "/index.html", "main.c375ae17.css.map": "/static/css/main.c375ae17.css.map", - "main.a6ace121.js.map": "/static/js/main.a6ace121.js.map" + "main.bf062beb.js.map": "/static/js/main.bf062beb.js.map" }, "entrypoints": [ "static/css/main.c375ae17.css", - "static/js/main.a6ace121.js" + "static/js/main.bf062beb.js" ] } \ No newline at end of file diff --git a/frontend/build/index.html b/frontend/build/index.html index bddea6b..42fbbfa 100755 --- a/frontend/build/index.html +++ b/frontend/build/index.html @@ -1 +1 @@ -
a||125d?(a.sortIndex=c,f(t,a),null===h(r)&&a===h(t)&&(B?(E(L),L=-1):B=!0,K(H,c-d))):(a.sortIndex=e,f(r,a),A||z||(A=!0,I(J)));return a};\nexports.unstable_shouldYield=M;exports.unstable_wrapCallback=function(a){var b=y;return function(){var c=y;y=b;try{return a.apply(this,arguments)}finally{y=c}}};\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/scheduler.production.min.js');\n} else {\n module.exports = require('./cjs/scheduler.development.js');\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","export default function _arrayLikeToArray(arr, len) {\n if (len == null || len > arr.length) len = arr.length;\n\n for (var i = 0, arr2 = new Array(len); i < len; i++) {\n arr2[i] = arr[i];\n }\n\n return arr2;\n}","import arrayLikeToArray from \"./arrayLikeToArray.js\";\nexport default function _unsupportedIterableToArray(o, minLen) {\n if (!o) return;\n if (typeof o === \"string\") return arrayLikeToArray(o, minLen);\n var n = Object.prototype.toString.call(o).slice(8, -1);\n if (n === \"Object\" && o.constructor) n = o.constructor.name;\n if (n === \"Map\" || n === \"Set\") return Array.from(o);\n if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen);\n}","import arrayWithHoles from \"./arrayWithHoles.js\";\nimport iterableToArrayLimit from \"./iterableToArrayLimit.js\";\nimport unsupportedIterableToArray from \"./unsupportedIterableToArray.js\";\nimport nonIterableRest from \"./nonIterableRest.js\";\nexport default function _slicedToArray(arr, i) {\n return arrayWithHoles(arr) || iterableToArrayLimit(arr, i) || unsupportedIterableToArray(arr, i) || nonIterableRest();\n}","export default function _arrayWithHoles(arr) {\n if (Array.isArray(arr)) return arr;\n}","export default function _iterableToArrayLimit(arr, i) {\n var _i = arr == null ? null : typeof Symbol !== \"undefined\" && arr[Symbol.iterator] || arr[\"@@iterator\"];\n\n if (_i == null) return;\n var _arr = [];\n var _n = true;\n var _d = false;\n\n var _s, _e;\n\n try {\n for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {\n _arr.push(_s.value);\n\n if (i && _arr.length === i) break;\n }\n } catch (err) {\n _d = true;\n _e = err;\n } finally {\n try {\n if (!_n && _i[\"return\"] != null) _i[\"return\"]();\n } finally {\n if (_d) throw _e;\n }\n }\n\n return _arr;\n}","export default function _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}","export default function _extends() {\n _extends = Object.assign ? Object.assign.bind() : function (target) {\n for (var i = 1; i < arguments.length; i++) {\n var source = arguments[i];\n\n for (var key in source) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n target[key] = source[key];\n }\n }\n }\n\n return target;\n };\n return _extends.apply(this, arguments);\n}","import * as React from \"react\";\nimport type { History, Location } from \"history\";\nimport { Action as NavigationType } from \"history\";\n\nimport type { RouteMatch } from \"./router\";\n\n/**\n * A Navigator is a \"location changer\"; it's how you get to different locations.\n *\n * Every history instance conforms to the Navigator interface, but the\n * distinction is useful primarily when it comes to the low-level