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 8ac7c1b4..d31edfa2 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
@@ -22,6 +22,7 @@
router = APIRouter()
+
def get_db():
db = SessionLocal()
try:
@@ -29,40 +30,93 @@ 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 ws(websocket: WebSocket, app_name: str):
+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)
+ 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
+ }
+ 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 a84e443d..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}
@@ -173,4 +190,67 @@ 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
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 03804bbb..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",
@@ -4009,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",
@@ -4295,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",
@@ -4324,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",
@@ -4364,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",
@@ -5003,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",
@@ -5762,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",
@@ -5851,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",
@@ -5864,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"
}
@@ -7309,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",
@@ -8807,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",
@@ -9350,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",
@@ -11411,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",
@@ -11851,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",
@@ -13953,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",
@@ -14497,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",
@@ -14975,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",
@@ -16014,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",
@@ -16069,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",
@@ -16904,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",
@@ -17034,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 e688f563..4e3e5017 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -8,19 +8,15 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
- "@vue/cli": "^4.5.3",
+ "@vue/cli": "^4.5.4",
"animate.css": "^4.1.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-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 bf697d38..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,6 +46,17 @@ export default {
data() {
return {
logs: [],
+ stats: {
+ cpu_percent: [],
+ mem_percent: [],
+ mem_current: [],
+ blk_read: [],
+ blk_write: [],
+ net_rx: [],
+ net_tx: []
+ },
+ connection: null,
+ statConnection: null
};
},
computed: {
@@ -65,26 +81,70 @@ export default {
this.readAppProcesses(appName);
this.closeLogs();
this.readAppLogs(appName);
+ this.readAppStats(appName);
+ this.closeStats();
},
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(1000, "Leaving page or refreshing");
+ // this.connection.close("Leaving page or refreshing", 1001);
+ },
+ 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) => {
+ 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.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(1000, "Leaving page or refreshing");
+ // this.statConnection.close(1001, "Leaving page or refreshing");
+
+ },
},
created() {
const appName = this.$route.params.appName;
@@ -96,10 +156,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..6a7d86c0
--- /dev/null
+++ b/frontend/src/components/applications/ApplicationDetailsComponents/AppStats.vue
@@ -0,0 +1,76 @@
+
+
+
+ Stats
+
+
+ Start the app to view stats
+
+
+
+ CPU Usage {{ stats.cpu_percent[stats.cpu_percent.length - 1] }}%
+
+
+ (0-100%)
+ Max: {{ Math.max.apply(Math, stats.cpu_percent) }}%
+
+
+
+
+
+
+ Memory Usage {{ stats.mem_percent[stats.mem_percent.length - 1] }}%,
+ {{ formatBytes(stats.mem_current[stats.mem_current.length - 1]) }}
+
+
+ (0-100%)
+ Max: {{ Math.max.apply(Math, stats.mem_percent) }}%,
+ {{ formatBytes(Math.max.apply(Math, stats.mem_current)) }}
+
+
+
+
+
+
+
+
+
+
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
+