From 2b17d26bbcd5300e64c6515b7811a1f056f0460d Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 17 Aug 2020 21:09:34 -0700 Subject: [PATCH 1/2] working on charts --- backend/api/routers/apps.py | 43 +++++++++- backend/api/utils.py | 58 ++++++++++++- frontend/package-lock.json | 82 +++++++++++++++++++ frontend/package.json | 2 + .../applications/ApplicationDetails.vue | 61 ++++++++++---- .../ApplicationDetailsComponents/AppStats.vue | 82 +++++++++++++++++++ .../ApplicationDetailsNav.vue | 3 + frontend/src/main.js | 3 +- frontend/src/router/index.js | 6 ++ 9 files changed, 318 insertions(+), 22 deletions(-) create mode 100644 frontend/src/components/applications/ApplicationDetailsComponents/AppStats.vue diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index 8ac7c1b4..fe041599 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -10,7 +10,7 @@ from ..db.database import SessionLocal, engine from .. import actions from ..auth import get_active_user, User -from ..utils import websocket_auth +from ..utils import websocket_auth, calculate_blkio_bytes, calculate_cpu_percent, calculate_cpu_percent2, calculate_network_bytes import docker import aiodocker @@ -54,7 +54,7 @@ def deploy_app(template: schemas.DeployForm): return actions.deploy_app(template=template) @router.websocket("/{app_name}/livelogs") -async def ws(websocket: WebSocket, app_name: str): +async def logs(websocket: WebSocket, app_name: str): auth_success = await websocket_auth(websocket= websocket) if auth_success: @@ -64,5 +64,44 @@ async def ws(websocket: WebSocket, app_name: str): logs = container.log(stdout=True, stderr=True, follow=True) async for line in logs: await websocket.send_text(line) + else: + await websocket.close(code=status.WS_1008_POLICY_VIOLATION) + +@router.websocket("/{app_name}/stats") +async def stats(websocket: WebSocket, app_name: str): + + auth_success = await websocket_auth(websocket= websocket) + if auth_success: + await websocket.accept() + async with aiodocker.Docker() as docker: + cpu_total = 0.0 + cpu_system = 0.0 + cpu_percent = 0.0 + + container: DockerContainer = await docker.containers.get(app_name) + stats = container.stats(stream=True) + async for line in stats: + blk_read, blk_write = await calculate_blkio_bytes(line) + net_read, net_write = await calculate_network_bytes(line) + mem_current = line["memory_stats"]["usage"] + mem_total = line["memory_stats"]["limit"] + + try: + cpu_percent, cpu_system, cpu_total = await calculate_cpu_percent2(line, cpu_total, cpu_system) + except KeyError as e: + print("error while getting new CPU stats: %r, falling back") + cpu_percent = await calculate_cpu_percent(line) + + full_stats = { + "cpu_percent": cpu_percent, + "mem_current": mem_current, + "mem_total": line["memory_stats"]["limit"], + "mem_percent": (mem_current / mem_total) * 100.0, + "blk_read": blk_read, + "blk_write": blk_write, + "net_rx": net_read, + "net_tx": net_write + } + await websocket.send_text(json.dumps(full_stats)) else: await websocket.close(code=status.WS_1008_POLICY_VIOLATION) \ No newline at end of file diff --git a/backend/api/utils.py b/backend/api/utils.py index a84e443d..d13570c0 100644 --- a/backend/api/utils.py +++ b/backend/api/utils.py @@ -173,4 +173,60 @@ async def websocket_auth( if user and user.is_active: return user except: - return None \ No newline at end of file + return None + +async def calculate_cpu_percent(d): + cpu_count = len(d["cpu_stats"]["cpu_usage"]["percpu_usage"]) + cpu_percent = 0.0 + cpu_delta = float(d["cpu_stats"]["cpu_usage"]["total_usage"]) - \ + float(d["precpu_stats"]["cpu_usage"]["total_usage"]) + system_delta = float(d["cpu_stats"]["system_cpu_usage"]) - \ + float(d["precpu_stats"]["system_cpu_usage"]) + if system_delta > 0.0: + cpu_percent = cpu_delta / system_delta * 100.0 * cpu_count + return cpu_percent + +async def calculate_cpu_percent2(d, previous_cpu, previous_system): + cpu_percent = 0.0 + cpu_total = float(d["cpu_stats"]["cpu_usage"]["total_usage"]) + cpu_delta = cpu_total - previous_cpu + cpu_system = float(d["cpu_stats"]["system_cpu_usage"]) + system_delta = cpu_system - previous_system + online_cpus = d["cpu_stats"].get("online_cpus", len(d["cpu_stats"]["cpu_usage"]["percpu_usage"])) + if system_delta > 0.0: + cpu_percent = (cpu_delta / system_delta) * online_cpus * 100.0 + return cpu_percent, cpu_system, cpu_total + +async def calculate_blkio_bytes(d): + bytes_stats = graceful_chain_get(d, "blkio_stats", "io_service_bytes_recursive") + if not bytes_stats: + return 0, 0 + r = 0 + w = 0 + for s in bytes_stats: + if s["op"] == "Read": + r += s["value"] + elif s["op"] == "Write": + w += s["value"] + return r, w + +async def calculate_network_bytes(d): + networks = graceful_chain_get(d, "networks") + if not networks: + return 0, 0 + r = 0 + t = 0 + for if_name, data in networks.items(): + r += data["rx_bytes"] + t += data["tx_bytes"] + return r, t + +def graceful_chain_get(d, *args, default=None): + t = d + for a in args: + try: + t = t[a] + except (KeyError, ValueError, TypeError, AttributeError): + print("can't get %r from %s", a, t) + return default + return t \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 03804bbb..cef42061 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3370,6 +3370,19 @@ "picomatch": "^2.0.4" } }, + "apexcharts": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.20.0.tgz", + "integrity": "sha512-DuQ9SlFPJBJwamYudzwf/Z6KMaIRUhnVBQk/TBa3mXzN2SwS3GgGz7V39OS1GfcPlPUTTy8vXv91M8pYmfFkCg==", + "requires": { + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, "apollo": { "version": "2.30.2", "resolved": "https://registry.npmjs.org/apollo/-/apollo-2.30.2.tgz", @@ -14553,6 +14566,70 @@ "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", "dev": true }, + "svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "requires": { + "svg.js": "^2.0.1" + } + }, + "svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=", + "requires": { + "svg.js": ">=2.3.x" + } + }, + "svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=", + "requires": { + "svg.js": "^2.2.5" + } + }, + "svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "requires": { + "svg.js": "^2.4.0" + } + }, + "svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "requires": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "dependencies": { + "svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "requires": { + "svg.js": "^2.2.5" + } + } + } + }, + "svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "requires": { + "svg.js": "^2.6.5" + } + }, "svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -15670,6 +15747,11 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" }, + "vue-apexcharts": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.6.0.tgz", + "integrity": "sha512-sT6tuVTLBwfH3TA7azecDNS/W70bmz14ZJI7aE7QIqcG9I6OywyH7x3hcOeY1v1DxttI8Svc5RuYj4Dd+A5F4g==" + }, "vue-cli-plugin-apollo": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/vue-cli-plugin-apollo/-/vue-cli-plugin-apollo-0.21.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index e688f563..64ff85e6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "dependencies": { "@vue/cli": "^4.5.3", "animate.css": "^4.1.0", + "apexcharts": "^3.20.0", "axios": "^0.19.2", "core-js": "^3.6.5", "moment": "^2.27.0", @@ -17,6 +18,7 @@ "socket.io-client": "^2.3.0", "vee-validate": "^3.3.7", "vue": "^2.6.11", + "vue-apexcharts": "^1.6.0", "vue-router": "^3.2.0", "vue-socket.io": "^3.0.9", "vue-socket.io-extended": "^4.0.4", diff --git a/frontend/src/components/applications/ApplicationDetails.vue b/frontend/src/components/applications/ApplicationDetails.vue index bf697d38..63338021 100644 --- a/frontend/src/components/applications/ApplicationDetails.vue +++ b/frontend/src/components/applications/ApplicationDetails.vue @@ -21,7 +21,7 @@ leave-active-class="animated slideOutRight" mode="out-in" > - + @@ -41,6 +41,7 @@ export default { data() { return { logs: [], + stats: [] }; }, computed: { @@ -65,26 +66,48 @@ export default { this.readAppProcesses(appName); this.closeLogs(); this.readAppLogs(appName); + this.readAppStats(appName) }, readAppLogs(appName) { - console.log("Starting connection to Websocket"); - this.connection = new WebSocket( - `ws://${location.hostname}:${location.port}/api/apps/${appName}/livelogs` - ); - this.connection.onopen = () => { - this.connection.send(JSON.stringify({ type: "onopen", data: "Connected!" })); - }; + console.log("Starting connection to Websocket"); + this.connection = new WebSocket( + `ws://${location.hostname}:${location.port}/api/apps/${appName}/livelogs` + ); + this.connection.onopen = () => { + this.connection.send( + JSON.stringify({ type: "onopen", data: "Connected!" }) + ); + }; - this.connection.onmessage = (event) => { - console.log(event) - this.logs.push(event.data); - }; + this.connection.onmessage = (event) => { + this.logs.push(event.data); + }; }, closeLogs() { - this.logs = [] - this.connection.send(JSON.stringify({ message: 'Closing Websocket'})) - this.connection.close(1001, "Leaving log page or refreshing") - } + this.logs = []; + this.connection.send(JSON.stringify({ message: "Closing Websocket" })); + this.connection.close(1001, "Leaving log page or refreshing"); + }, + readAppStats(appName) { + console.log("Starting connection to Websocket"); + this.statConnection = new WebSocket( + `ws://${location.hostname}:${location.port}/api/apps/${appName}/stats` + ); + this.statConnection.onopen = () => { + this.statConnection.send( + JSON.stringify({ type: "onopen", data: "Connected!" }) + ); + }; + + this.statConnection.onmessage = (event) => { + this.stats.push(JSON.parse(event.data)); + }; + }, + closeStats() { + this.stats = []; + this.statConnection.send(JSON.stringify({ message: "Closing Websocket" })); + this.statConnection.close(1001, "Leaving stats page or refreshing"); + }, }, created() { const appName = this.$route.params.appName; @@ -96,10 +119,12 @@ export default { await this.readApp(appName); await this.readAppProcesses(appName); await this.readAppLogs(appName); + await this.readAppStats(appName); }, beforeDestroy() { - this.closeLogs() - } + this.closeLogs(); + this.closeStats(); + }, }; diff --git a/frontend/src/components/applications/ApplicationDetailsComponents/AppStats.vue b/frontend/src/components/applications/ApplicationDetailsComponents/AppStats.vue new file mode 100644 index 00000000..a3b0f177 --- /dev/null +++ b/frontend/src/components/applications/ApplicationDetailsComponents/AppStats.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/frontend/src/components/applications/ApplicationDetailsComponents/ApplicationDetailsNav.vue b/frontend/src/components/applications/ApplicationDetailsComponents/ApplicationDetailsNav.vue index 42b18a4a..9de9c3c7 100644 --- a/frontend/src/components/applications/ApplicationDetailsComponents/ApplicationDetailsNav.vue +++ b/frontend/src/components/applications/ApplicationDetailsComponents/ApplicationDetailsNav.vue @@ -10,6 +10,9 @@ mdi-book-open-outlineLogs + + mdi-gaugeStats + Date: Wed, 19 Aug 2020 14:15:54 -0700 Subject: [PATCH 2/2] working charts using vuetify sparklines --- backend/api/actions/apps.py | 31 +- backend/api/auth/__init__.py | 2 +- backend/api/auth/auth.py | 9 +- backend/api/db/crud/__init__.py | 2 +- backend/api/db/crud/settings.py | 18 +- backend/api/db/crud/templates.py | 94 ++-- backend/api/db/models/containers.py | 55 +- backend/api/db/models/users.py | 2 - backend/api/db/schemas/__init__.py | 2 +- backend/api/db/schemas/apps.py | 13 +- backend/api/db/schemas/templates.py | 17 +- backend/api/main.py | 16 +- backend/api/routers/app_settings.py | 9 +- backend/api/routers/apps.py | 29 +- backend/api/routers/templates.py | 12 +- backend/api/settings.py | 33 +- backend/api/utils.py | 64 ++- frontend/package-lock.json | 476 ++---------------- frontend/package.json | 12 +- .../applications/ApplicationDetails.vue | 51 +- .../ApplicationDetailsComponents/AppStats.vue | 88 ++-- frontend/src/main.js | 3 - 22 files changed, 394 insertions(+), 644 deletions(-) diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index 0854398d..555ba51e 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -7,12 +7,13 @@ from datetime import datetime import docker + def get_apps(): apps_list = [] dclient = docker.from_env() apps = dclient.containers.list(all=True) for app in apps: - attrs=app.attrs + attrs = app.attrs attrs.update(conv2dict('name', app.name)) attrs.update(conv2dict('ports', app.ports)) attrs.update(conv2dict('short_id', app.short_id)) @@ -20,6 +21,7 @@ def get_apps(): return apps_list + def get_app(app_name): dclient = docker.from_env() app = dclient.containers.get(app_name) @@ -31,6 +33,7 @@ def get_app(app_name): return attrs + def get_app_processes(app_name): dclient = docker.from_env() app = dclient.containers.get(app_name) @@ -40,6 +43,7 @@ def get_app_processes(app_name): else: return None + def get_app_logs(app_name): dclient = docker.from_env() app = dclient.containers.get(app_name) @@ -48,25 +52,27 @@ def get_app_logs(app_name): else: return None + def deploy_app(template: schemas.DeployForm): try: launch = launch_app( - template.name, - template.image, - conv_restart2data(template.restart_policy), - conv_ports2data(template.ports), - conv_volumes2data(template.volumes), - conv_env2data(template.env), - conv_sysctls2data(template.sysctls), - conv_caps2data(template.cap_add) + template.name, + template.image, + conv_restart2data(template.restart_policy), + conv_ports2data(template.ports), + conv_volumes2data(template.volumes), + conv_env2data(template.env), + conv_sysctls2data(template.sysctls), + conv_caps2data(template.cap_add) ) - - except Exception as exc: raise + except Exception as exc: + raise print('done deploying') return schemas.DeployLogs(logs=launch.logs()) + def launch_app(name, image, restart_policy, ports, volumes, env, sysctls, caps): dclient = docker.from_env() lauch = dclient.containers.run( @@ -89,6 +95,7 @@ def launch_app(name, image, restart_policy, ports, volumes, env, sysctls, caps): Env: {env}''') return lauch + def app_action(app_name, action): err = None dclient = docker.from_env() @@ -105,4 +112,4 @@ def app_action(app_name, action): except Exception as exc: err = exc.explination apps_list = get_apps() - return apps_list \ No newline at end of file + return apps_list diff --git a/backend/api/auth/__init__.py b/backend/api/auth/__init__.py index d7c8ad39..35c1920f 100644 --- a/backend/api/auth/__init__.py +++ b/backend/api/auth/__init__.py @@ -1 +1 @@ -from .auth import * \ No newline at end of file +from .auth import * diff --git a/backend/api/auth/auth.py b/backend/api/auth/auth.py index dca274d4..88e7e389 100644 --- a/backend/api/auth/auth.py +++ b/backend/api/auth/auth.py @@ -10,16 +10,18 @@ from fastapi_users.password import get_password_hash from ..settings import Settings -settings=Settings() +settings = Settings() SECRET = settings.SECRET_KEY auth_backends = [] -cookie_authentication = CookieAuthentication(secret=SECRET, lifetime_seconds=3600, cookie_secure=False) +cookie_authentication = CookieAuthentication( + secret=SECRET, lifetime_seconds=3600, cookie_secure=False) auth_backends.append(cookie_authentication) + class User(models.BaseUser): pass @@ -71,5 +73,6 @@ class UserTable(Base, SQLAlchemyBaseUserTable): get_auth_router = fastapi_users.get_auth_router get_password_hash = get_password_hash + async def user_create(UD): - await fastapi_users.db.create(UD) \ No newline at end of file + await fastapi_users.db.create(UD) diff --git a/backend/api/db/crud/__init__.py b/backend/api/db/crud/__init__.py index 94753a03..b4b29d83 100644 --- a/backend/api/db/crud/__init__.py +++ b/backend/api/db/crud/__init__.py @@ -1,2 +1,2 @@ from .templates import * -from .settings import * \ No newline at end of file +from .settings import * diff --git a/backend/api/db/crud/settings.py b/backend/api/db/crud/settings.py index 200a2f50..5720c5f1 100644 --- a/backend/api/db/crud/settings.py +++ b/backend/api/db/crud/settings.py @@ -9,17 +9,19 @@ import sqlite3 import json + def export_settings(db: Session): file_export = {} file_export['templates'] = db.query(models.Template).all() file_export['variables'] = db.query(models.TemplateVariables).all() return(file_export) + def import_settings(db: Session, upload): import_file = upload.file.read() decoded_import = import_file.decode('utf-8') import_contents = json.loads(decoded_import) - + _templates = import_contents['templates'] _variables = import_contents['variables'] @@ -27,7 +29,8 @@ def import_settings(db: Session, upload): _var_list = [] for template in _templates: - template_model = models.Template(id = template['id'], title=template['title'], url=template['url'], updated_at=datetime.fromisoformat(template['updated_at']), created_at=datetime.fromisoformat(template['created_at'])) + template_model = models.Template(id=template['id'], title=template['title'], url=template['url'], updated_at=datetime.fromisoformat( + template['updated_at']), created_at=datetime.fromisoformat(template['created_at'])) for item in template['items']: _item = models.TemplateItem(**item) template_model.items.append(_item) @@ -36,16 +39,15 @@ def import_settings(db: Session, upload): for variable in _variables: variable_model = models.TemplateVariables(**variable) _var_list.append(variable_model) - - #Remove Existing + + # Remove Existing db.query(models.TemplateVariables).delete() db.query(models.Template).delete() db.query(models.TemplateItem).delete() - - #Add New + # Add New db.add_all(_template_list) db.add_all(_var_list) db.commit() - response = { 'success': 'Import Successful'} - return(response) \ No newline at end of file + response = {'success': 'Import Successful'} + return(response) diff --git a/backend/api/db/crud/templates.py b/backend/api/db/crud/templates.py index 250ac4d7..0e3cbba0 100644 --- a/backend/api/db/crud/templates.py +++ b/backend/api/db/crud/templates.py @@ -9,52 +9,61 @@ import urllib.request import json -### Templates +# Templates + + def get_templates(db: Session): return db.query(models.Template).all() + def get_template(db: Session, url: str): return db.query(models.Template).filter(models.Template.url == url).first() + def get_template_by_id(db: Session, id: int): return db.query(models.Template).filter(models.Template.id == id).first() + def get_template_items(db: Session, template_id: int): return db.query(models.TemplateItem).filter(models.TemplateItem.template_id == template_id).all() + def delete_template(db: Session, template_id: int): - _template = db.query(models.Template).filter(models.Template.id == template_id).first() + _template = db.query(models.Template).filter( + models.Template.id == template_id).first() db.delete(_template) db.commit() return _template + def add_template(db: Session, template: models.containers.Template): try: - # Opens the JSON and iterate over the content. - _template = models.containers.Template(title = template.title, url = template.url) + # Opens the JSON and iterate over the content. + _template = models.containers.Template( + title=template.title, url=template.url) with urllib.request.urlopen(template.url) as file: for entry in json.load(file): ports = conv_ports2dict(entry.get('ports', [])) sysctls = conv_sysctls2dict(entry.get('sysctls', [])) - + # Optional use classmethod from_dict template_content = models.containers.TemplateItem( - type = int(entry['type']), - title = entry['title'], - platform = entry['platform'], - description = entry.get('description', ''), - name = entry.get('name', entry['title'].lower()), - logo = entry.get('logo', ''), # default logo here! - image = entry.get('image', ''), - notes = entry.get('note', ''), - categories = entry.get('categories', ''), - restart_policy = entry.get('restart_policy'), - ports = ports, - volumes = entry.get('volumes', []), - env = entry.get('env', []), - sysctls = sysctls, - cap_add = entry.get('cap_add', []) + type=int(entry['type']), + title=entry['title'], + platform=entry['platform'], + description=entry.get('description', ''), + name=entry.get('name', entry['title'].lower()), + logo=entry.get('logo', ''), # default logo here! + image=entry.get('image', ''), + notes=entry.get('note', ''), + categories=entry.get('categories', ''), + restart_policy=entry.get('restart_policy'), + ports=ports, + volumes=entry.get('volumes', []), + env=entry.get('env', []), + sysctls=sysctls, + cap_add=entry.get('cap_add', []) ) _template.items.append(template_content) except (OSError, TypeError, ValueError) as err: @@ -73,8 +82,10 @@ def add_template(db: Session, template: models.containers.Template): return get_template(db=db, url=template.url) + def refresh_template(db: Session, template_id: id): - template = db.query(models.Template).filter(models.Template.id == template_id).first() + template = db.query(models.Template).filter( + models.Template.id == template_id).first() items = [] try: @@ -85,21 +96,21 @@ def refresh_template(db: Session, template_id: id): sysctls = conv_sysctls2dict(entry.get('sysctls', [])) item = models.TemplateItem( - type = int(entry['type']), - title = entry['title'], - platform = entry['platform'], - description = entry.get('description', ''), - name = entry.get('name', entry['title'].lower()), - logo = entry.get('logo', ''), # default logo here! - image = entry.get('image', ''), - notes = entry.get('note', ''), - categories = entry.get('categories', ''), - restart_policy = entry.get('restart_policy'), - ports = ports, - volumes = entry.get('volumes', []), - env = entry.get('env', []), - sysctls = sysctls, - cap_add = entry.get('cap_add', []) + type=int(entry['type']), + title=entry['title'], + platform=entry['platform'], + description=entry.get('description', ''), + name=entry.get('name', entry['title'].lower()), + logo=entry.get('logo', ''), # default logo here! + image=entry.get('image', ''), + notes=entry.get('note', ''), + categories=entry.get('categories', ''), + restart_policy=entry.get('restart_policy'), + ports=ports, + volumes=entry.get('volumes', []), + env=entry.get('env', []), + sysctls=sysctls, + cap_add=entry.get('cap_add', []) ) items.append(item) except Exception as exc: @@ -110,7 +121,6 @@ def refresh_template(db: Session, template_id: id): # make_transient(template) # db.commit() - template.updated_at = datetime.utcnow() template.items = items @@ -125,18 +135,21 @@ def refresh_template(db: Session, template_id: id): return template + def read_app_template(db, app_id): try: - template_item = db.query(models.TemplateItem).filter(models.TemplateItem.id == app_id).first() + template_item = db.query(models.TemplateItem).filter( + models.TemplateItem.id == app_id).first() return template_item except Exception as exc: print('App template not found') raise + def set_template_variables(db: Session, new_variables: models.TemplateVariables): try: template_vars = db.query(models.TemplateVariables).all() - + variables = [] t_vars = new_variables @@ -156,7 +169,8 @@ def set_template_variables(db: Session, new_variables: models.TemplateVariables) return new_template_variables except IntegrityError as err: - abort(400, { 'error': 'Bad Request' }) + abort(400, {'error': 'Bad Request'}) + def read_template_variables(db: Session): return db.query(models.TemplateVariables).all() diff --git a/backend/api/db/models/containers.py b/backend/api/db/models/containers.py index 9463d236..e6d5a993 100644 --- a/backend/api/db/models/containers.py +++ b/backend/api/db/models/containers.py @@ -5,68 +5,71 @@ from datetime import datetime + class Template(Base): __tablename__ = 'templates' id = Column(Integer, primary_key=True) # alternative: DateTime(timezone=True), sqlalchemy.sql.func.now() created_at = Column(DateTime, - nullable=False, unique=False, index=False, - default=datetime.utcnow) + nullable=False, unique=False, index=False, + default=datetime.utcnow) updated_at = Column(DateTime, - nullable=False, unique=False, index=False, - default=datetime.utcnow, onupdate=datetime.utcnow) + nullable=False, unique=False, index=False, + default=datetime.utcnow, onupdate=datetime.utcnow) # rename to title title = Column(String(255), - nullable=False, unique=True, index=True) + nullable=False, unique=True, index=True) url = Column(Text, - nullable=False, unique=True, index=False) + nullable=False, unique=True, index=False) items = relationship('TemplateItem', - backref='template', cascade='all, delete-orphan') + backref='template', cascade='all, delete-orphan') + class TemplateItem(Base): __tablename__ = 'template_item' id = Column(Integer, primary_key=True) - type= Column(Integer, - nullable=False, unique=False, index=False) + type = Column(Integer, + nullable=False, unique=False, index=False) title = Column(String(255), - nullable=False, unique=False, index=True) + nullable=False, unique=False, index=True) platform = Column(String(64), - nullable=False, unique=False, index=False) + nullable=False, unique=False, index=False) description = Column(Text, - nullable=True, unique=False, index=False) + nullable=True, unique=False, index=False) name = Column(String(255), - nullable=True, unique=False, index=True) + nullable=True, unique=False, index=True) logo = Column(Text, - nullable=True, unique=False, index=False) + nullable=True, unique=False, index=False) image = Column(String(128), - nullable=False, unique=False, index=False) + nullable=False, unique=False, index=False) notes = Column(Text, - nullable=True, unique=False, index=False) + nullable=True, unique=False, index=False) categories = Column(JSON, - nullable=True, unique=False, index=False) + nullable=True, unique=False, index=False) restart_policy = Column(String(20), - nullable=True, unique=False, index=False) + nullable=True, unique=False, index=False) ports = Column(JSON, - nullable=True, unique=False, index=False) + nullable=True, unique=False, index=False) volumes = Column(JSON, - nullable=True, unique=False, index=False) + nullable=True, unique=False, index=False) env = Column(JSON, - nullable=True, unique=False, index=False) + nullable=True, unique=False, index=False) sysctls = Column(JSON, - nullable=True, unique=False, index=False) + nullable=True, unique=False, index=False) cap_add = Column(JSON, - nullable=True, unique=False, index=False) + nullable=True, unique=False, index=False) template_id = Column(Integer, - ForeignKey('templates.id')) + ForeignKey('templates.id')) + class TemplateVariables(Base): __tablename__ = 'template_variables' id = Column(Integer, primary_key=True) variable = Column(String(255), - nullable=False, unique=True, index=True) + nullable=False, unique=True, index=True) replacement = Column(String(255), - nullable=False, unique=True, index=True) \ No newline at end of file + nullable=False, unique=True, index=True) diff --git a/backend/api/db/models/users.py b/backend/api/db/models/users.py index 51f63f8f..4e39d677 100644 --- a/backend/api/db/models/users.py +++ b/backend/api/db/models/users.py @@ -1,4 +1,2 @@ from ..database import Base from fastapi_users.db import SQLAlchemyBaseUserTable - - diff --git a/backend/api/db/schemas/__init__.py b/backend/api/db/schemas/__init__.py index dd4a4fe5..78b62b2f 100644 --- a/backend/api/db/schemas/__init__.py +++ b/backend/api/db/schemas/__init__.py @@ -1,2 +1,2 @@ from .apps import * -from .templates import * \ No newline at end of file +from .templates import * diff --git a/backend/api/db/schemas/apps.py b/backend/api/db/schemas/apps.py index 5233fc14..972917a6 100644 --- a/backend/api/db/schemas/apps.py +++ b/backend/api/db/schemas/apps.py @@ -1,25 +1,30 @@ from typing import List, Optional from pydantic import BaseModel + class PortsSchema(BaseModel): cport: str hport: Optional[str] proto: str + class VolumesSchema(BaseModel): container: str bind: str + class EnvSchema(BaseModel): label: str default: str name: Optional[str] description: Optional[str] + class SysctlsSchema(BaseModel): name: str value: str + class DeployForm(BaseModel): name: str image: str @@ -31,12 +36,18 @@ class DeployForm(BaseModel): sysctls: Optional[List[SysctlsSchema]] cap_add: Optional[List[str]] # LOGS # + + class DeployLogs(BaseModel): logs: str + + class AppLogs(BaseModel): logs: str # Processes # + + class Processes(BaseModel): Processes: List[List[str]] - Titles: List[str] \ No newline at end of file + Titles: List[str] diff --git a/backend/api/db/schemas/templates.py b/backend/api/db/schemas/templates.py index 291b456b..9c74f44b 100644 --- a/backend/api/db/schemas/templates.py +++ b/backend/api/db/schemas/templates.py @@ -3,6 +3,7 @@ from datetime import datetime from pydantic import BaseModel, Json + class TemplateItem(BaseModel): id: int type: int @@ -20,10 +21,12 @@ class TemplateItem(BaseModel): env: Optional[List] = [] sysctls: Optional[List] = [] cap_add: Optional[List] = [] - + class Config: orm_mode = True ### TEMPLATE #### + + class TemplateBase(BaseModel): title: str url: str @@ -31,11 +34,13 @@ class TemplateBase(BaseModel): class Config: orm_mode = True + class TemplateRead(TemplateBase): id: int updated_at: datetime created_at: datetime - + + class TemplateItems(TemplateRead): items: List[TemplateItem] = [] @@ -44,19 +49,25 @@ class Config: ### TEMPLATES END ### ### TEMPLATE VARIABLES ### + + class TemplateVariables(BaseModel): variable: str replacement: str class Config: orm_mode = True + + class ReadTemplateVariables(TemplateVariables): id: int ### Export/Import ### + + class Import_Export(BaseModel): templates: List[TemplateItems] = [] variables: List[ReadTemplateVariables] = [] -TemplateItems.update_forward_refs() \ No newline at end of file +TemplateItems.update_forward_refs() diff --git a/backend/api/main.py b/backend/api/main.py index e6342284..5216004f 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -46,6 +46,8 @@ prefix="/settings", tags=["settings"] ) + + @app.on_event("startup") async def startup(): await database.connect() @@ -54,14 +56,14 @@ async def startup(): print("users exist") else: print("no users") - ### This is where I'm having trouble + # This is where I'm having trouble hashed_password = get_password_hash(settings.ADMIN_PASSWORD) base_user = UserDB( - id = uuid.uuid4(), - email = settings.ADMIN_EMAIL, - hashed_password = hashed_password, - is_active = True, - is_superuser = True + id=uuid.uuid4(), + email=settings.ADMIN_EMAIL, + hashed_password=hashed_password, + is_active=True, + is_superuser=True ) user_created = await user_create(base_user) template_variables_exist = read_template_variables(SessionLocal()) @@ -78,6 +80,8 @@ async def startup(): ) t_var_list.append(template_variables) set_template_variables(new_variables=t_var_list, db=SessionLocal()) + + @app.on_event("shutdown") async def shutdown(): await database.disconnect() diff --git a/backend/api/routers/app_settings.py b/backend/api/routers/app_settings.py index 20f85861..ce361352 100644 --- a/backend/api/routers/app_settings.py +++ b/backend/api/routers/app_settings.py @@ -14,21 +14,24 @@ containers.Base.metadata.create_all(bind=engine) - router = APIRouter() + @router.get("/variables", response_model=List[schemas.TemplateVariables], dependencies=[Depends(get_active_user)]) def read_template_variables(db: Session = Depends(get_db)): return crud.read_template_variables(db=db) + @router.post("/variables", response_model=List[schemas.TemplateVariables], dependencies=[Depends(get_active_user)]) -def set_template_variables(new_variables: List[schemas.TemplateVariables],db: Session = Depends(get_db)): +def set_template_variables(new_variables: List[schemas.TemplateVariables], db: Session = Depends(get_db)): return crud.set_template_variables(new_variables=new_variables, db=db) + @router.get("/export", response_model=schemas.Import_Export, dependencies=[Depends(get_active_user)]) def export_settings(db: Session = Depends(get_db)): return crud.export_settings(db=db) + @router.post("/export", dependencies=[Depends(get_active_user)]) def import_settings(db: Session = Depends(get_db), upload: UploadFile = File(...)): - return crud.import_settings(db=db, upload=upload) \ No newline at end of file + return crud.import_settings(db=db, upload=upload) diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index fe041599..d31edfa2 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -22,6 +22,7 @@ router = APIRouter() + def get_db(): db = SessionLocal() try: @@ -29,48 +30,59 @@ def get_db(): finally: db.close() + @router.get("/", dependencies=[Depends(get_active_user)]) def index(): return actions.get_apps() + @router.get("/{app_name}", dependencies=[Depends(get_active_user)]) def get_container_details(app_name): return actions.get_app(app_name=app_name) + @router.get("/{app_name}/processes", response_model=schemas.Processes, dependencies=[Depends(get_active_user)]) def get_container_processes(app_name): return actions.get_app_processes(app_name=app_name) + @router.get("/{app_name}/logs", response_model=schemas.AppLogs, dependencies=[Depends(get_active_user)]) def get_container_logs(app_name): return actions.get_app_logs(app_name=app_name) + @router.get("/{app_name}/{action}", dependencies=[Depends(get_active_user)]) -def container_actions(app_name,action): - return actions.app_action(app_name,action) +def container_actions(app_name, action): + return actions.app_action(app_name, action) + @router.post("/deploy", response_model=schemas.DeployLogs, dependencies=[Depends(get_active_user)]) def deploy_app(template: schemas.DeployForm): return actions.deploy_app(template=template) + @router.websocket("/{app_name}/livelogs") async def logs(websocket: WebSocket, app_name: str): - auth_success = await websocket_auth(websocket= websocket) + auth_success = await websocket_auth(websocket=websocket) if auth_success: await websocket.accept() async with aiodocker.Docker() as docker: container: DockerContainer = await docker.containers.get(app_name) logs = container.log(stdout=True, stderr=True, follow=True) async for line in logs: - await websocket.send_text(line) + try: + await websocket.send_text(line) + except Exception as e: + return e else: await websocket.close(code=status.WS_1008_POLICY_VIOLATION) + @router.websocket("/{app_name}/stats") async def stats(websocket: WebSocket, app_name: str): - auth_success = await websocket_auth(websocket= websocket) + auth_success = await websocket_auth(websocket=websocket) if auth_success: await websocket.accept() async with aiodocker.Docker() as docker: @@ -102,6 +114,9 @@ async def stats(websocket: WebSocket, app_name: str): "net_rx": net_read, "net_tx": net_write } - await websocket.send_text(json.dumps(full_stats)) + try: + await websocket.send_text(json.dumps(full_stats)) + except Exception as e: + return e else: - await websocket.close(code=status.WS_1008_POLICY_VIOLATION) \ No newline at end of file + await websocket.close(code=status.WS_1008_POLICY_VIOLATION) diff --git a/backend/api/routers/templates.py b/backend/api/routers/templates.py index f77eb53e..d6116745 100644 --- a/backend/api/routers/templates.py +++ b/backend/api/routers/templates.py @@ -14,34 +14,40 @@ containers.Base.metadata.create_all(bind=engine) - router = APIRouter() + @router.get("/", response_model=List[schemas.TemplateRead], dependencies=[Depends(get_active_user)]) def index(db: Session = Depends(get_db)): templates = crud.get_templates(db=db) return templates + @router.get("/{id}", response_model=schemas.TemplateItems, dependencies=[Depends(get_active_user)]) def show(id: int, db: Session = Depends(get_db)): template = crud.get_template_by_id(db=db, id=id) return template + @router.delete("/{id}", response_model=schemas.TemplateRead, dependencies=[Depends(get_active_user)]) def delete(id: int, db: Session = Depends(get_db)): return crud.delete_template(db=db, template_id=id) + @router.post("/", response_model=schemas.TemplateRead, dependencies=[Depends(get_active_user)]) def add_template(template: schemas.TemplateBase, db: Session = Depends(get_db)): existing_template = crud.get_template(db=db, url=template.url) if existing_template: - raise HTTPException(status_code=400, detail="Template already in Database.") + raise HTTPException( + status_code=400, detail="Template already in Database.") return crud.add_template(db=db, template=template) + @router.get("/{id}/refresh", response_model=schemas.TemplateRead, dependencies=[Depends(get_active_user)]) def refresh_template(id: int, db: Session = Depends(get_db)): return crud.refresh_template(db=db, template_id=id) + @router.get("/app/{id}", response_model=schemas.TemplateItem, dependencies=[Depends(get_active_user)]) def read_app_template(id: int, db: Session = Depends(get_db)): - return crud.read_app_template(db=db, app_id=id) \ No newline at end of file + return crud.read_app_template(db=db, app_id=id) diff --git a/backend/api/settings.py b/backend/api/settings.py index e31165b0..92413bde 100644 --- a/backend/api/settings.py +++ b/backend/api/settings.py @@ -4,6 +4,7 @@ basedir = os.path.abspath(os.path.dirname(__file__)) + class Settings(BaseSettings): app_name: str = "Yacht API" SECRET_KEY = os.environ.get('SECRET_KEY', secrets.token_hex(16)) @@ -12,20 +13,20 @@ class Settings(BaseSettings): ACCESS_TOKEN_EXPIRES = os.environ.get('ACCESS_TOKEN_EXPIRES', 15) REFRESH_TOKEN_EXPIRES = os.environ.get('REFRESH_TOKEN_EXPIRES', 1) BASE_TEMPLATE_VARIABLES = [ - {"variable": "!config", "replacement": "/yacht/AppData/Config"}, - {"variable": "!data", "replacement": "/yacht/AppData/Data"}, - {"variable": "!media", "replacement": "/yacht/Media/"}, - {"variable": "!downloads", "replacement": "/yacht/Downloads/"}, - {"variable": "!music", "replacement": "/yacht/Media/Music"}, - {"variable": "!playlists", "replacement": "/yacht/Media/Playlists"}, - {"variable": "!podcasts", "replacement": "/yacht/Media/Podcasts"}, - {"variable": "!books", "replacement": "/yacht/Media/Books"}, - {"variable": "!comics", "replacement": "/yacht/Media/Comics"}, - {"variable": "!tv", "replacement": "/yacht/Media/TV"}, - {"variable": "!movies", "replacement": "/yacht/Media/Movies"}, - {"variable": "!pictures", "replacement": "/yacht/Media/Photos"}, - {"variable": "!localtime", "replacement": "/etc/localtime"}, - {"variable": "!logs", "replacement": "/yacht/AppData/Logs"}, - ] + {"variable": "!config", "replacement": "/yacht/AppData/Config"}, + {"variable": "!data", "replacement": "/yacht/AppData/Data"}, + {"variable": "!media", "replacement": "/yacht/Media/"}, + {"variable": "!downloads", "replacement": "/yacht/Downloads/"}, + {"variable": "!music", "replacement": "/yacht/Media/Music"}, + {"variable": "!playlists", "replacement": "/yacht/Media/Playlists"}, + {"variable": "!podcasts", "replacement": "/yacht/Media/Podcasts"}, + {"variable": "!books", "replacement": "/yacht/Media/Books"}, + {"variable": "!comics", "replacement": "/yacht/Media/Comics"}, + {"variable": "!tv", "replacement": "/yacht/Media/TV"}, + {"variable": "!movies", "replacement": "/yacht/Media/Movies"}, + {"variable": "!pictures", "replacement": "/yacht/Media/Photos"}, + {"variable": "!localtime", "replacement": "/etc/localtime"}, + {"variable": "!logs", "replacement": "/yacht/AppData/Logs"}, + ] SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', - 'sqlite:///config/data.sqlite') + 'sqlite:///config/data.sqlite') diff --git a/backend/api/utils.py b/backend/api/utils.py index d13570c0..c70ccdee 100644 --- a/backend/api/utils.py +++ b/backend/api/utils.py @@ -11,6 +11,7 @@ from .settings import Settings settings = Settings() + def get_db(): db = SessionLocal() try: @@ -18,7 +19,8 @@ def get_db(): finally: db.close() -#For Templates + +# For Templates REGEXP_PORT_ASSIGN = r'^(?:(?:\d{1,5}:)?\d{1,5}|:\d{1,5})/(?:tcp|udp)$' # Input Format: @@ -36,19 +38,22 @@ def get_db(): # }, # ... # ] -def conv_ports2dict(data: List[str]) -> List[Dict[str,str]]: + + +def conv_ports2dict(data: List[str]) -> List[Dict[str, str]]: delim = ':' portlst = [] for port_data in data: if not re.match(REGEXP_PORT_ASSIGN, port_data, flags=re.IGNORECASE): raise ValueError('Malformed port assignment.') - hport,cport = None,port_data + hport, cport = None, port_data if delim in cport: - hport,cport = cport.split(delim, 1) - if not hport: hport = None - cport,proto = cport.split('/', 1) - portlst.append({ 'cport': cport, 'hport': hport, 'proto': proto }) + hport, cport = cport.split(delim, 1) + if not hport: + hport = None + cport, proto = cport.split('/', 1) + portlst.append({'cport': cport, 'hport': hport, 'proto': proto}) return portlst # Input Format: @@ -64,15 +69,18 @@ def conv_ports2dict(data: List[str]) -> List[Dict[str,str]]: # 'value': '0' # } # ] + + def conv_sysctls2dict(data: List[Dict[str, str]]) -> List[Dict[str, str]]: - return [{ 'name': k, 'value': v } for item in data for k,v in item.items()] + return [{'name': k, 'value': v} for item in data for k, v in item.items()] + def conv2dict(name, value): - _tmp_attr = { name: value} + _tmp_attr = {name: value} return _tmp_attr -### For Deploy Form +# For Deploy Form # Input Format: # [ @@ -93,8 +101,9 @@ def conv_ports2data(data): cport = d.cport hport = d.hport proto = d.proto - if not hport: hport = None - ports.update({str(cport)+'/'+proto:hport for d in data}) + if not hport: + hport = None + ports.update({str(cport)+'/'+proto: hport for d in data}) return ports # Input Format: @@ -110,6 +119,8 @@ def conv_ports2data(data): # '/home/user1/': {'bind': '/mnt/vol2', 'mode': 'rw'}, # '/var/www': {'bind': '/mnt/vol1', 'mode': 'ro'} # } + + def conv_volumes2data(data): db = SessionLocal() t_variables = db.query(models.TemplateVariables).all() @@ -118,9 +129,11 @@ def conv_volumes2data(data): if volume.bind: for t_var in t_variables: if t_var.variable in volume.bind: - new_path = volume.bind.replace(t_var.variable, t_var.replacement) - volume.bind = new_path - volume_data = dict((d.bind, {'bind': d.container, 'mode': 'rw'}) for d in data) + new_path = volume.bind.replace( + t_var.variable, t_var.replacement) + volume.bind = new_path + volume_data = dict( + (d.bind, {'bind': d.container, 'mode': 'rw'}) for d in data) return volume_data @@ -150,12 +163,16 @@ def conv_sysctls2data(data): else: sysctls = None return sysctls + + def conv_caps2data(data): if data: return data else: caps = None return caps + + def conv_restart2data(data): if data: return {'name': data} @@ -175,30 +192,35 @@ async def websocket_auth( except: return None + async def calculate_cpu_percent(d): cpu_count = len(d["cpu_stats"]["cpu_usage"]["percpu_usage"]) cpu_percent = 0.0 cpu_delta = float(d["cpu_stats"]["cpu_usage"]["total_usage"]) - \ - float(d["precpu_stats"]["cpu_usage"]["total_usage"]) + float(d["precpu_stats"]["cpu_usage"]["total_usage"]) system_delta = float(d["cpu_stats"]["system_cpu_usage"]) - \ - float(d["precpu_stats"]["system_cpu_usage"]) + float(d["precpu_stats"]["system_cpu_usage"]) if system_delta > 0.0: cpu_percent = cpu_delta / system_delta * 100.0 * cpu_count return cpu_percent + async def calculate_cpu_percent2(d, previous_cpu, previous_system): cpu_percent = 0.0 cpu_total = float(d["cpu_stats"]["cpu_usage"]["total_usage"]) cpu_delta = cpu_total - previous_cpu cpu_system = float(d["cpu_stats"]["system_cpu_usage"]) system_delta = cpu_system - previous_system - online_cpus = d["cpu_stats"].get("online_cpus", len(d["cpu_stats"]["cpu_usage"]["percpu_usage"])) + online_cpus = d["cpu_stats"].get("online_cpus", len( + d["cpu_stats"]["cpu_usage"]["percpu_usage"])) if system_delta > 0.0: cpu_percent = (cpu_delta / system_delta) * online_cpus * 100.0 return cpu_percent, cpu_system, cpu_total + async def calculate_blkio_bytes(d): - bytes_stats = graceful_chain_get(d, "blkio_stats", "io_service_bytes_recursive") + bytes_stats = graceful_chain_get( + d, "blkio_stats", "io_service_bytes_recursive") if not bytes_stats: return 0, 0 r = 0 @@ -210,6 +232,7 @@ async def calculate_blkio_bytes(d): w += s["value"] return r, w + async def calculate_network_bytes(d): networks = graceful_chain_get(d, "networks") if not networks: @@ -221,6 +244,7 @@ async def calculate_network_bytes(d): t += data["tx_bytes"] return r, t + def graceful_chain_get(d, *args, default=None): t = d for a in args: @@ -229,4 +253,4 @@ def graceful_chain_get(d, *args, default=None): except (KeyError, ValueError, TypeError, AttributeError): print("can't get %r from %s", a, t) return default - return t \ No newline at end of file + return t diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cef42061..0da377c5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,9 +24,9 @@ } }, "@apollo/protobufjs": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.4.tgz", - "integrity": "sha512-EE3zx+/D/wur/JiLp6VCiw1iYdyy1lCJMf8CGPkLeDt5QJrN4N8tKFx33Ah4V30AUQzMk7Uz4IXKZ1LOj124gA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.5.tgz", + "integrity": "sha512-ZtyaBH1icCgqwIGb3zrtopV2D5Q8yxibkJzlaViM08eOhTQc7rACdYu0pfORFfhllvdMZ3aq69vifYHszY4gNA==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -2088,6 +2088,11 @@ "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz", "integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==" }, + "@types/http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA==" + }, "@types/inquirer": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", @@ -2141,14 +2146,15 @@ "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==" }, "@types/koa": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.3.tgz", - "integrity": "sha512-ABxVkrNWa4O/Jp24EYI/hRNqEVRlhB9g09p48neQp4m3xL1TJtdWk2NyNQSMCU45ejeELMQZBYyfstyVvO2H3Q==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.4.tgz", + "integrity": "sha512-Etqs0kdqbuAsNr5k6mlZQelpZKVwMu9WPRHVVTLnceZlhr0pYmblRNJbCgoCMzKWWePldydU0AYEOX4Q9fnGUQ==", "requires": { "@types/accepts": "*", "@types/content-disposition": "*", "@types/cookies": "*", "@types/http-assert": "*", + "@types/http-errors": "*", "@types/keygrip": "*", "@types/koa-compose": "*", "@types/node": "*" @@ -2233,11 +2239,6 @@ "@types/mime": "*" } }, - "@types/socket.io-client": { - "version": "1.4.33", - "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.33.tgz", - "integrity": "sha512-m4LnxkljsI9fMsjwpW5QhRpMixo2BeeLpFmg0AE+sS4H1pzAd/cs/ftTiL60FLZgfFa8PFRPx5KsHu8O0bADKQ==" - }, "@types/through": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", @@ -2368,16 +2369,16 @@ } }, "@vue/cli": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/@vue/cli/-/cli-4.5.3.tgz", - "integrity": "sha512-tlafU7No50JCqWsfAClrkINht8UUrt2IMraFVPq/q5OtYqEvAyd3BKK9hWxi5nC8kgwN/+rauca+ZYaJxmLJJQ==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@vue/cli/-/cli-4.5.4.tgz", + "integrity": "sha512-UFhxsmiKtXxZwvuW0HB+35bEIovAjYg9oiA9uyOMkDh3ZTf90FmyPre09xKviLn0B+0WnzD35P+ZB/bgJZ/HOA==", "requires": { "@types/ejs": "^2.7.0", "@types/inquirer": "^6.5.0", - "@vue/cli-shared-utils": "^4.5.3", - "@vue/cli-ui": "^4.5.3", - "@vue/cli-ui-addon-webpack": "^4.5.3", - "@vue/cli-ui-addon-widgets": "^4.5.3", + "@vue/cli-shared-utils": "^4.5.4", + "@vue/cli-ui": "^4.5.4", + "@vue/cli-ui-addon-webpack": "^4.5.4", + "@vue/cli-ui-addon-widgets": "^4.5.4", "boxen": "^4.1.0", "cmd-shim": "^3.0.3", "commander": "^2.20.0", @@ -2409,9 +2410,9 @@ }, "dependencies": { "@vue/cli-shared-utils": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-4.5.3.tgz", - "integrity": "sha512-AjXSll67gpYWyjGOyHrwofLuxa7vL8KM6aUQCII+cHlFQey6oLS5bAWq9qcIM0P2ZyD+6i0fooNCihIuNrX4yg==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-4.5.4.tgz", + "integrity": "sha512-7ZwAvGxl5szGuaJCc4jdPy/2Lb7oJvG847MDF+7pZ7FVl6bURwbUJjiUwL6DTxvpC4vch6B4tXfVvZFjzVP/bw==", "requires": { "@hapi/joi": "^15.0.1", "chalk": "^2.4.2", @@ -2724,12 +2725,12 @@ } }, "@vue/cli-ui": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/@vue/cli-ui/-/cli-ui-4.5.3.tgz", - "integrity": "sha512-8hyhZq2Hyoz1grEvroQjM5cSjOeJogQ3SrVRCi8Ah+PdzPRYGWAKWE3kl5FB9nZyDILiE8QfoQBYiilFx0a/IA==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@vue/cli-ui/-/cli-ui-4.5.4.tgz", + "integrity": "sha512-1FXRes+Xl/018OdF8pSZKxQzT+NwUpBm5/DbKkNxcxJOSgEE/sIUGmQjyQPWyYyFs6hugbIsNpEJgdYumUUjbQ==", "requires": { "@akryum/winattr": "^3.0.0", - "@vue/cli-shared-utils": "^4.5.3", + "@vue/cli-shared-utils": "^4.5.4", "apollo-server-express": "^2.13.1", "clone": "^2.1.1", "deepmerge": "^4.2.2", @@ -2760,9 +2761,9 @@ }, "dependencies": { "@vue/cli-shared-utils": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-4.5.3.tgz", - "integrity": "sha512-AjXSll67gpYWyjGOyHrwofLuxa7vL8KM6aUQCII+cHlFQey6oLS5bAWq9qcIM0P2ZyD+6i0fooNCihIuNrX4yg==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-4.5.4.tgz", + "integrity": "sha512-7ZwAvGxl5szGuaJCc4jdPy/2Lb7oJvG847MDF+7pZ7FVl6bURwbUJjiUwL6DTxvpC4vch6B4tXfVvZFjzVP/bw==", "requires": { "@hapi/joi": "^15.0.1", "chalk": "^2.4.2", @@ -2801,14 +2802,14 @@ } }, "@vue/cli-ui-addon-webpack": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/@vue/cli-ui-addon-webpack/-/cli-ui-addon-webpack-4.5.3.tgz", - "integrity": "sha512-W7vUCfYeRaYR32eaTVVmyaQ0AXJBltFklR3Ll/JYg8WExwC29KaiVEE+tXzS7B68Ewzf4oqDUeu0zfFvMCG4yg==" + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@vue/cli-ui-addon-webpack/-/cli-ui-addon-webpack-4.5.4.tgz", + "integrity": "sha512-J5yYNdrcBvDboYwX+I9KEpv2QdV39X+YoZl2x+mYAgc4d+Forf2mmGipBarnAOgR58d/yapFRrtQLK1dUNpk9A==" }, "@vue/cli-ui-addon-widgets": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/@vue/cli-ui-addon-widgets/-/cli-ui-addon-widgets-4.5.3.tgz", - "integrity": "sha512-sfhVUlsrnfJAEMMg3MF7/H+/p4WA7q07r6b3gB+TJwf5WJ2BmJeRG6ppFOFg8bQ9ZziUmaGNENpCwCw0pgPumA==" + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@vue/cli-ui-addon-widgets/-/cli-ui-addon-widgets-4.5.4.tgz", + "integrity": "sha512-/7R3dMrv/tEP4AM22hBDPfXPF24OE/2ddR3cTF1c1fSoHT/LcUvqrlHIb8SlVgl3S7WvF9Prrunp+KzkHW9bSA==" }, "@vue/compiler-core": { "version": "3.0.0-rc.5", @@ -3219,11 +3220,6 @@ "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", "dev": true }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" - }, "aggregate-error": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", @@ -3370,19 +3366,6 @@ "picomatch": "^2.0.4" } }, - "apexcharts": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.20.0.tgz", - "integrity": "sha512-DuQ9SlFPJBJwamYudzwf/Z6KMaIRUhnVBQk/TBa3mXzN2SwS3GgGz7V39OS1GfcPlPUTTy8vXv91M8pYmfFkCg==", - "requires": { - "svg.draggable.js": "^2.2.2", - "svg.easing.js": "^2.0.0", - "svg.filter.js": "^2.0.2", - "svg.pathmorphing.js": "^0.1.3", - "svg.resize.js": "^1.4.3", - "svg.select.js": "^3.0.1" - } - }, "apollo": { "version": "2.30.2", "resolved": "https://registry.npmjs.org/apollo/-/apollo-2.30.2.tgz", @@ -4022,11 +4005,6 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" - }, "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -4308,21 +4286,11 @@ } } }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" - }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, - "base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" - }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -4337,14 +4305,6 @@ "tweetnacl": "^0.14.3" } }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "requires": { - "callsite": "1.0.0" - } - }, "bfj": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", @@ -4377,11 +4337,6 @@ "safe-buffer": "^5.1.1" } }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" - }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -5016,11 +4971,6 @@ "caller-callsite": "^2.0.0" } }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" - }, "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", @@ -5775,21 +5725,11 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" - }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" - }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -5864,11 +5804,11 @@ } }, "configstore": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", - "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.5.tgz", + "integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==", "requires": { - "dot-prop": "^4.1.0", + "dot-prop": "^4.2.1", "graceful-fs": "^4.1.2", "make-dir": "^1.0.0", "unique-string": "^1.0.0", @@ -5877,9 +5817,9 @@ }, "dependencies": { "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz", + "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", "requires": { "is-obj": "^1.0.0" } @@ -7322,71 +7262,6 @@ "once": "^1.4.0" } }, - "engine.io": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", - "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", - "requires": { - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "0.3.1", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", - "ws": "^7.1.2" - }, - "dependencies": { - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" - } - } - }, - "engine.io-client": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.3.tgz", - "integrity": "sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw==", - "requires": { - "component-emitter": "~1.3.0", - "component-inherit": "0.0.3", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "~6.1.0", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" - }, - "dependencies": { - "ws": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", - "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, - "engine.io-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", - "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "~1.0.2" - } - }, "entities": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", @@ -8820,26 +8695,6 @@ } } }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "requires": { - "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - } - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -9363,11 +9218,6 @@ "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" - }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -11424,11 +11274,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" - }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -11864,22 +11709,6 @@ "parse5": "^5.1.1" } }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "requires": { - "better-assert": "~1.0.0" - } - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -13966,109 +13795,6 @@ } } }, - "socket.io": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", - "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", - "requires": { - "debug": "~4.1.0", - "engine.io": "~3.4.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.3.0", - "socket.io-parser": "~3.4.0" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - }, - "socket.io-parser": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", - "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", - "requires": { - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "isarray": "2.0.1" - } - } - } - }, - "socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" - }, - "socket.io-client": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", - "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "engine.io-client": "~3.4.0", - "has-binary2": "~1.0.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "~3.3.0", - "to-array": "0.1.4" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - } - } - }, - "socket.io-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", - "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", - "requires": { - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "isarray": "2.0.1" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, "sockjs": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", @@ -14510,9 +14236,9 @@ } }, "subscriptions-transport-ws": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.17.tgz", - "integrity": "sha512-hNHi2N80PBz4T0V0QhnnsMGvG3XDFDS9mS6BhZ3R12T6EBywC8d/uJscsga0cVO4DKtXCkCRrWm2sOYrbOdhEA==", + "version": "0.9.18", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz", + "integrity": "sha512-tztzcBTNoEbuErsVQpTN2xUNN/efAZXyCyL5m3x4t6SKrEiTL2N8SaKWBFWM4u56pL79ULif3zjyeq+oV+nOaA==", "requires": { "backo2": "^1.0.2", "eventemitter3": "^3.1.0", @@ -14566,70 +14292,6 @@ "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", "dev": true }, - "svg.draggable.js": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", - "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", - "requires": { - "svg.js": "^2.0.1" - } - }, - "svg.easing.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", - "integrity": "sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=", - "requires": { - "svg.js": ">=2.3.x" - } - }, - "svg.filter.js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", - "integrity": "sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=", - "requires": { - "svg.js": "^2.2.5" - } - }, - "svg.js": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", - "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" - }, - "svg.pathmorphing.js": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", - "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", - "requires": { - "svg.js": "^2.4.0" - } - }, - "svg.resize.js": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", - "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", - "requires": { - "svg.js": "^2.6.5", - "svg.select.js": "^2.1.2" - }, - "dependencies": { - "svg.select.js": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", - "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", - "requires": { - "svg.js": "^2.2.5" - } - } - } - }, - "svg.select.js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", - "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", - "requires": { - "svg.js": "^2.6.5" - } - }, "svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -15052,11 +14714,6 @@ "os-tmpdir": "~1.0.2" } }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -15747,11 +15404,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" }, - "vue-apexcharts": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.6.0.tgz", - "integrity": "sha512-sT6tuVTLBwfH3TA7azecDNS/W70bmz14ZJI7aE7QIqcG9I6OywyH7x3hcOeY1v1DxttI8Svc5RuYj4Dd+A5F4g==" - }, "vue-cli-plugin-apollo": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/vue-cli-plugin-apollo/-/vue-cli-plugin-apollo-0.21.3.tgz", @@ -16096,25 +15748,9 @@ } }, "vue-router": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.2.tgz", - "integrity": "sha512-n3Ok70hW0EpcJF4lcWIwSHAQbFTnIOLl/fhO8+oTs4jHNtBNsovcVvPZeTOyKEd8C3xF1Crft2ASuOiVT5K1mw==" - }, - "vue-socket.io": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/vue-socket.io/-/vue-socket.io-3.0.10.tgz", - "integrity": "sha512-XGYjD30Q9xAeHpBnp2SU+ljEe59qpGKaAQe4VOO9ezuly09MlzrT2ZZPJu3BVFpQwKdjQDz1I2fV9r4YjlZDCA==", - "requires": { - "socket.io-client": "^2.1.1" - } - }, - "vue-socket.io-extended": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/vue-socket.io-extended/-/vue-socket.io-extended-4.0.4.tgz", - "integrity": "sha512-vSbhWmoHGhkm0143TTPXqbfMEO0uLfF0MX0tmhRaInzWCQliCei5BjmMBRDuqEOuSZuvnKHGjpUKCifFeo3BDg==", - "requires": { - "@types/socket.io-client": "1.4.33" - } + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.3.tgz", + "integrity": "sha512-BADg1mjGWX18Dpmy6bOGzGNnk7B/ZA0RxuA6qedY/YJwirMfKXIDzcccmHbQI0A6k5PzMdMloc0ElHfyOoX35A==" }, "vue-style-loader": { "version": "4.1.2", @@ -16151,9 +15787,9 @@ "dev": true }, "vuetify": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.8.tgz", - "integrity": "sha512-T754w4jV3eeuEwEfRTpHdlo76ZRUVTBeMMzVyqSEr61AfhDSYf9rJfHOlhfXG4aaQbZ34dRR48bzRiS4ICz2fQ==" + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.9.tgz", + "integrity": "sha512-E5K9flTvS21tCkHgqDBMl0BY/Rld4SLUaJpQ+sQdL8/2uPcWmWLrdumn4SI8LBFojE0UP1GSaH4zKuxLL36fYg==" }, "vuetify-loader": { "version": "1.6.0", @@ -16986,11 +16622,6 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" - }, "xss": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.8.tgz", @@ -17116,11 +16747,6 @@ "fd-slicer": "~1.1.0" } }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" - }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 64ff85e6..4e3e5017 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,21 +8,15 @@ "lint": "vue-cli-service lint" }, "dependencies": { - "@vue/cli": "^4.5.3", + "@vue/cli": "^4.5.4", "animate.css": "^4.1.0", - "apexcharts": "^3.20.0", "axios": "^0.19.2", "core-js": "^3.6.5", "moment": "^2.27.0", - "socket.io": "^2.3.0", - "socket.io-client": "^2.3.0", "vee-validate": "^3.3.7", "vue": "^2.6.11", - "vue-apexcharts": "^1.6.0", - "vue-router": "^3.2.0", - "vue-socket.io": "^3.0.9", - "vue-socket.io-extended": "^4.0.4", - "vuetify": "^2.3.4", + "vue-router": "^3.4.3", + "vuetify": "^2.3.9", "vuex": "^3.4.0" }, "devDependencies": { diff --git a/frontend/src/components/applications/ApplicationDetails.vue b/frontend/src/components/applications/ApplicationDetails.vue index 63338021..98dd3f81 100644 --- a/frontend/src/components/applications/ApplicationDetails.vue +++ b/frontend/src/components/applications/ApplicationDetails.vue @@ -21,7 +21,12 @@ leave-active-class="animated slideOutRight" mode="out-in" > - + @@ -41,7 +46,17 @@ export default { data() { return { logs: [], - stats: [] + stats: { + cpu_percent: [], + mem_percent: [], + mem_current: [], + blk_read: [], + blk_write: [], + net_rx: [], + net_tx: [] + }, + connection: null, + statConnection: null }; }, computed: { @@ -66,7 +81,8 @@ export default { this.readAppProcesses(appName); this.closeLogs(); this.readAppLogs(appName); - this.readAppStats(appName) + this.readAppStats(appName); + this.closeStats(); }, readAppLogs(appName) { console.log("Starting connection to Websocket"); @@ -86,7 +102,8 @@ export default { closeLogs() { this.logs = []; this.connection.send(JSON.stringify({ message: "Closing Websocket" })); - this.connection.close(1001, "Leaving log page or refreshing"); + this.connection.close(1000, "Leaving page or refreshing"); + // this.connection.close("Leaving page or refreshing", 1001); }, readAppStats(appName) { console.log("Starting connection to Websocket"); @@ -100,13 +117,33 @@ export default { }; this.statConnection.onmessage = (event) => { - this.stats.push(JSON.parse(event.data)); + let statsGroup = JSON.parse(event.data); + this.stats.cpu_percent.push(Math.round(statsGroup.cpu_percent)); + this.stats.mem_percent.push(Math.round(statsGroup.mem_percent)); + this.stats.mem_current.push(statsGroup.mem_current) + this.stats.net_rx.push(statsGroup.net_rx); + this.stats.net_tx.push(statsGroup.net_tx); + this.stats.blk_write.push(statsGroup.blk_write); + this.stats.blk_read.push(statsGroup.blk_read); + for (let key in this.stats) { + if (this.stats[key].length > 300) { + this.stats[key].shift() + } + } }; }, closeStats() { - this.stats = []; + this.stats.cpu_percent = []; + this.stats.mem_percent = []; + this.stats.mem_current = []; + this.stats.net_rx = []; + this.stats.net_tx = []; + this.stats.blk_read = []; + this.stats.blk_write = []; this.statConnection.send(JSON.stringify({ message: "Closing Websocket" })); - this.statConnection.close(1001, "Leaving stats page or refreshing"); + this.statConnection.close(1000, "Leaving page or refreshing"); + // this.statConnection.close(1001, "Leaving page or refreshing"); + }, }, created() { diff --git a/frontend/src/components/applications/ApplicationDetailsComponents/AppStats.vue b/frontend/src/components/applications/ApplicationDetailsComponents/AppStats.vue index a3b0f177..6a7d86c0 100644 --- a/frontend/src/components/applications/ApplicationDetailsComponents/AppStats.vue +++ b/frontend/src/components/applications/ApplicationDetailsComponents/AppStats.vue @@ -1,71 +1,65 @@ @@ -77,6 +71,6 @@ export default { background-color: black; } .keep-whitespace { - white-space:pre + white-space: pre; } diff --git a/frontend/src/main.js b/frontend/src/main.js index 6210fcb3..b8a2d16e 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -9,13 +9,11 @@ import axios from "axios"; import vuetify from "./plugins/vuetify"; // Form Validation import VueUtils from "./plugins/vueutils"; -import VueApexCharts from 'vue-apexcharts' import "./vee-validate"; // Websockets // Animations require("animate.css/animate.compat.css"); -// Socket.io Vue.config.productionTip = false; @@ -42,7 +40,6 @@ function createAxiosResponseInterceptor() { // Call interceptor createAxiosResponseInterceptor(); Vue.use(VueUtils); -Vue.component('apexchart', VueApexCharts) new Vue({ router, store,