diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index 7c4f8d1c..dc442a51 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -5,6 +5,8 @@ from ..utils import * from datetime import datetime +import time +import subprocess import docker @@ -21,12 +23,22 @@ def get_running_apps(): return apps_list +def check_app_updates(): + apps_list = [] + dclient = docker.from_env() + apps = dclient.containers.list(all=True) + for app in apps: + if check_updates(app.image.tags[0]): + apps_list.append(app.name) + return apps_list + def get_apps(): apps_list = [] dclient = docker.from_env() apps = dclient.containers.list(all=True) for app in apps: attrs = app.attrs + attrs.update(conv2dict('name', app.name)) attrs.update(conv2dict('ports', app.ports)) attrs.update(conv2dict('short_id', app.short_id)) @@ -39,7 +51,7 @@ def get_app(app_name): dclient = docker.from_env() app = dclient.containers.get(app_name) attrs = app.attrs - + attrs.update(conv2dict('ports', app.ports)) attrs.update(conv2dict('short_id', app.short_id)) attrs.update(conv2dict('name', app.name)) @@ -148,6 +160,63 @@ def app_action(app_name, action): apps_list = get_apps() return apps_list +def app_update(app_name): + dclient = docker.from_env() + try: + old = dclient.containers.get(app_name) + except Exception as exc: + print(exc) + if exc.response.status_code == 404: + raise HTTPException(status_code=exc.response.status_code, detail="Unable to get container ID") + else: + raise HTTPException(status_code=exc.response.status_code, detail=exc.explanation) + + volumes ={'/var/run/docker.sock': {'bind':'/var/run/docker.sock', 'mode': 'rw'}} + try: + updater = dclient.containers.run( + image='containrrr/watchtower:latest', + command='--run-once '+old.name, + remove=True, + detach=True, + volumes=volumes + ) + except Exception as exc: + print(exc) + raise HTTPException(status_code=exc.response.status_code, detail=exc.explanation) + + print('**** Updating '+old.name+'****') + result = updater.wait(timeout=120) + print(result) + time.sleep(1) + return get_apps() + +def update_self(): + dclient = docker.from_env() + bash_command = "head -1 /proc/self/cgroup|cut -d/ -f3" + yacht_id = subprocess.check_output(['bash','-c', bash_command]).decode('UTF-8').strip() + try: + yacht = dclient.containers.get(yacht_id) + except Exception as exc: + print(exc) + if exc.response.status_code == 404: + raise HTTPException(status_code=exc.response.status_code, detail="Unable to get Yacht container ID") + else: + raise HTTPException(status_code=exc.response.status_code, detail=exc.explanation) + + volumes ={'/var/run/docker.sock': {'bind':'/var/run/docker.sock', 'mode': 'rw'}} + print('**** Updating '+yacht.name+'****') + updater = dclient.containers.run( + image='containrrr/watchtower:latest', + command='--run-once '+yacht.name, + remove=True, + detach=True, + volumes=volumes + ) + result = updater.wait(timeout=120) + print(result) + time.sleep(1) + return result + def prune_images(): dclient = docker.from_env() deleted_everything = {} @@ -159,4 +228,25 @@ def prune_images(): deleted_everything.update(deleted_volumes) deleted_everything.update(deleted_images) - return deleted_everything \ No newline at end of file + return deleted_everything +def prune_resources(resource): + dclient = docker.from_env() + action = getattr(dclient, resource) + deleted_resource = action.prune() + return deleted_resource + + +def check_self_update(): + dclient = docker.from_env() + bash_command = "head -1 /proc/self/cgroup|cut -d/ -f3" + yacht_id = subprocess.check_output(['bash','-c', bash_command]).decode('UTF-8').strip() + try: + yacht = dclient.containers.get(yacht_id) + except Exception as exc: + print(exc) + if exc.response.status_code == 404: + raise HTTPException(status_code=exc.response.status_code, detail="Unable to get Yacht container ID") + else: + raise HTTPException(status_code=exc.response.status_code, detail=exc.explanation) + + return check_updates(yacht.image.tags[0]) \ No newline at end of file diff --git a/backend/api/db/schemas/templates.py b/backend/api/db/schemas/templates.py index 38ab9b09..9fc8d3ff 100644 --- a/backend/api/db/schemas/templates.py +++ b/backend/api/db/schemas/templates.py @@ -42,6 +42,8 @@ class TemplateRead(TemplateBase): updated_at: datetime created_at: datetime +class TemplateReadAll(TemplateBase): + items: List[TemplateItem] = [] class TemplateItems(TemplateRead): items: List[TemplateItem] = [] diff --git a/backend/api/routers/app_settings.py b/backend/api/routers/app_settings.py index c0093039..85f43f08 100644 --- a/backend/api/routers/app_settings.py +++ b/backend/api/routers/app_settings.py @@ -37,6 +37,14 @@ def export_settings(db: Session = Depends(get_db)): def import_settings(db: Session = Depends(get_db), upload: UploadFile = File(...)): return crud.import_settings(db=db, upload=upload) -@router.get("/prune", dependencies=[Depends(get_active_user)]) -def prune_images(): - return apps.prune_images() \ No newline at end of file +@router.get("/prune/{resource}", dependencies=[Depends(get_active_user)]) +def prune_resources(resource: str): + return apps.prune_resources(resource) + +@router.get('/update', dependencies=[Depends(get_active_user)]) +def update_self(): + return apps.update_self() + +@router.get('/check/update', dependencies=[Depends(get_active_user)]) +def check_self_update(): + return apps.check_self_update() \ No newline at end of file diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index d8be06a9..9c2756bc 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -36,6 +36,9 @@ def get_db(): def index(): return actions.get_apps() +@router.get('/updates', dependencies=[Depends(get_active_user)]) +def check_updates(): + return actions.check_app_updates() @router.get("/{app_name}", dependencies=[Depends(get_active_user)]) def get_container_details(app_name): @@ -51,6 +54,9 @@ def get_container_processes(app_name): def get_container_logs(app_name): return actions.get_app_logs(app_name=app_name) +@router.get("/{app_name}/update", dependencies=[Depends(get_active_user)]) +def update_container(app_name): + return actions.app_update(app_name) @router.get("/{app_name}/{action}", dependencies=[Depends(get_active_user)]) def container_actions(app_name, action): @@ -116,7 +122,6 @@ async def stats(websocket: WebSocket, app_name: str): cpu_percent = await calculate_cpu_percent(line) full_stats = { - "time": line['read'], "cpu_percent": cpu_percent, "mem_current": mem_current, "mem_total": mem_total, @@ -174,7 +179,6 @@ async def process_container(name, stats, websocket): full_stats = { "name": name, - "time": line['read'], "cpu_percent": cpu_percent, "mem_current": mem_current, "mem_total": mem_total, diff --git a/backend/api/utils.py b/backend/api/utils.py index b0878b9e..81574bfb 100644 --- a/backend/api/utils.py +++ b/backend/api/utils.py @@ -10,6 +10,7 @@ from .auth import user_db from .settings import Settings import aiodocker +import docker import json settings = Settings() @@ -49,7 +50,7 @@ def conv_ports2dict(data: List[str]) -> List[Dict[str, str]]: for port_data in data: for label, port in port_data.items(): if not re.match(REGEXP_PORT_ASSIGN, port, flags=re.IGNORECASE): - raise ValueError('Malformed port assignment.') + raise HTTPException(status_code=500, detail='Malformed port assignment.'+port_data) hport, cport = None, port if delim in cport: @@ -65,7 +66,7 @@ def conv_ports2dict(data: List[str]) -> List[Dict[str, str]]: portlst = [] for port_data in data: if not re.match(REGEXP_PORT_ASSIGN, port_data, flags=re.IGNORECASE): - raise ValueError('Malformed port assignment.') + raise HTTPException(status_code=500, detail='Malformed port assignment.'+port_data) hport, cport = None, port_data if delim in cport: @@ -182,6 +183,14 @@ def conv_volumes2data(data): def conv_env2data(data): # Set is depracated. Name is the actual value. Label is the name of the field. # Label is the label of the label field. + db = SessionLocal() + t_variables = db.query(models.TemplateVariables).all() + + for i,variable in enumerate(data): + for t_var in t_variables: + if t_var.variable in variable.default: + new_var = data[i].default.replace(t_var.variable, t_var.replacement) + variable.default = new_var delim = '=' return [delim.join((d.name, d.default)) for d in data] @@ -337,3 +346,26 @@ async def get_app_stats(app_name): "mem_percent": (mem_current / mem_total) * 100.0, } yield json.dumps(full_stats) + +def get_update_ports(ports): + if ports: + portdir={} + for hport in ports: + for d in ports[hport]: + portdir.update({str(hport): d.get('HostPort') }) + return portdir + else: + return None + +def check_updates(tag): + if tag: + dclient = docker.from_env() + current = dclient.images.get(tag) + new = dclient.images.get_registry_data(tag) + if new.attrs['Descriptor']['digest'] in current.attrs['RepoDigests'][0]: + return False + else: + return True + + else: + return False \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8a7c3ed0..5f114617 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10942,9 +10942,9 @@ } }, "moment": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.28.0.tgz", - "integrity": "sha512-Z5KOjYmnHyd/ukynmFd/WwyXHd7L4J9vTI/nn5Ap9AVUgaAE15VvQ9MOGmJJygEUklupqIrFnor/tjTwRU+tQw==" + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz", + "integrity": "sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA==" }, "move-concurrently": { "version": "1.0.1", @@ -11101,9 +11101,9 @@ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, "node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", "dev": true }, "node-ipc": { @@ -13510,12 +13510,12 @@ "dev": true }, "selfsigned": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", - "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", "dev": true, "requires": { - "node-forge": "0.9.0" + "node-forge": "^0.10.0" } }, "semver": { @@ -15496,9 +15496,9 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "vee-validate": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-3.3.11.tgz", - "integrity": "sha512-+/M2g0VYwBDX8o7PkW/CRUxDkyudo9C/8S/Q7EoGV1duIrK2Q9W08BFcbDkkNnSAVVfknPOWkFGHeAGOgfOxCw==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-3.4.0.tgz", + "integrity": "sha512-qxcqrGYr2gchZOjyhBKu7G4NOyOTmH6vk8APGdxAiL/0Iy2QZP/vcOYNbx7+zMhg7P3q418AseHR1sbckB5I+A==" }, "vendors": { "version": "1.0.4", @@ -15912,9 +15912,9 @@ } }, "vue-router": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.3.tgz", - "integrity": "sha512-BADg1mjGWX18Dpmy6bOGzGNnk7B/ZA0RxuA6qedY/YJwirMfKXIDzcccmHbQI0A6k5PzMdMloc0ElHfyOoX35A==" + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.5.tgz", + "integrity": "sha512-ioRY5QyDpXM9TDjOX6hX79gtaMXSVDDzSlbIlyAmbHNteIL81WIVB2e+jbzV23vzxtoV0krdS2XHm+GxFg+Nxg==" }, "vue-style-loader": { "version": "4.1.2", @@ -15951,9 +15951,9 @@ "dev": true }, "vuetify": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.10.tgz", - "integrity": "sha512-KzL/MhZ7ajubm9kwbdCoA/cRV50RX+a5Hcqiwt7Am1Fni2crDtl2no05UNwKroTfscrYYf07gq3WIFSurPsnCA==" + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.12.tgz", + "integrity": "sha512-FSt1pzpf0/Lh0xuctAPB7RiLbUl7bzVc7ejbXLLhfmgm7zD7yabuhVYuyVda/SzokjZMGS3j1lNu2lLfdrz0oQ==" }, "vuetify-loader": { "version": "1.6.0", diff --git a/frontend/package.json b/frontend/package.json index 4a418697..98646dca 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,12 +14,12 @@ "chart.js": "^2.9.3", "chartjs-plugin-crosshair": "^1.1.6", "core-js": "^3.6.5", - "moment": "^2.28.0", - "vee-validate": "^3.3.11", + "moment": "^2.29.0", + "vee-validate": "^3.4.0", "vue": "^2.6.12", "vue-chartjs": "^3.5.1", - "vue-router": "^3.4.3", - "vuetify": "^2.3.10", + "vue-router": "^3.4.5", + "vuetify": "^2.3.12", "vuex": "^3.4.0" }, "devDependencies": { diff --git a/frontend/src/components/applications/ApplicationDeployFromTemplate.vue b/frontend/src/components/applications/ApplicationDeployFromTemplate.vue new file mode 100644 index 00000000..8cf296b1 --- /dev/null +++ b/frontend/src/components/applications/ApplicationDeployFromTemplate.vue @@ -0,0 +1,402 @@ + + + + + diff --git a/frontend/src/components/applications/ApplicationDetails.vue b/frontend/src/components/applications/ApplicationDetails.vue index 9a27c386..c444254a 100644 --- a/frontend/src/components/applications/ApplicationDetails.vue +++ b/frontend/src/components/applications/ApplicationDetails.vue @@ -7,6 +7,56 @@ {{ app.name }} + + + + + + mdi-play + + Start + + + + mdi-stop + + Stop + + + + mdi-refresh + + Restart + + + + + mdi-fire + + Kill + + + + mdi-delete + + Remove + + + View and Manage {{ app.name }} @@ -70,8 +120,8 @@ export default { methods: { ...mapActions({ readApp: "apps/readApp", - readAppProcesses: "apps/readAppProcesses" - // readAppLogs: "apps/readAppLogs", + readAppProcesses: "apps/readAppProcesses", + AppAction: "apps/AppAction" }), refresh() { const appName = this.$route.params.appName; @@ -84,8 +134,14 @@ export default { }, readAppLogs(appName) { console.log("Starting connection to Logs"); + var proto = ""; + if (location.protocol == "http:") { + proto = "ws://"; + } else { + proto = "wss://"; + } this.connection = new WebSocket( - `ws://${location.hostname}:${location.port}/api/apps/${appName}/livelogs` + `${proto}${location.hostname}:${location.port}/api/apps/${appName}/livelogs` ); this.connection.onopen = () => { this.connection.send( @@ -105,9 +161,15 @@ export default { }, readAppStats(appName) { console.log("Starting connection to Stats"); + var sproto = ""; + if (location.protocol == "http:") { + sproto = "ws://"; + } else { + sproto = "wss://"; + } this.statConnection = new WebSocket( - `ws://${location.hostname}:${location.port}/api/apps/${appName}/stats` + `${sproto}${location.hostname}:${location.port}/api/apps/${appName}/stats` ); this.statConnection.onopen = () => { this.statConnection.send( diff --git a/frontend/src/components/applications/ApplicationDetailsComponents/AppContent.vue b/frontend/src/components/applications/ApplicationDetailsComponents/AppContent.vue index 2f56ac3b..bd831a24 100644 --- a/frontend/src/components/applications/ApplicationDetailsComponents/AppContent.vue +++ b/frontend/src/components/applications/ApplicationDetailsComponents/AppContent.vue @@ -258,8 +258,8 @@ @@ -80,19 +69,6 @@ export default { setSuccess: "snackbar/setSuccess", setErr: "snackbar/setErr" }), - prune_images() { - axios({ - url: "/api/settings/prune", - method: "GET", - responseType: "text/json" - }) - .then(response => { - this.setSuccess(response); - }) - .catch(err => { - this.setErr(err); - }); - }, export_settings() { axios({ url: "/api/settings/export", diff --git a/frontend/src/components/serverSettings/ServerSettingsNav.vue b/frontend/src/components/serverSettings/ServerSettingsNav.vue index e52e56f8..289d4657 100644 --- a/frontend/src/components/serverSettings/ServerSettingsNav.vue +++ b/frontend/src/components/serverSettings/ServerSettingsNav.vue @@ -8,6 +8,14 @@ mdi-view-list-outlineTemplate Variables + + mdi-trash-can-outline + Prune + + + mdi-update + Update + diff --git a/frontend/src/components/serverSettings/ServerUpdate.vue b/frontend/src/components/serverSettings/ServerUpdate.vue new file mode 100644 index 00000000..86946f17 --- /dev/null +++ b/frontend/src/components/serverSettings/ServerUpdate.vue @@ -0,0 +1,98 @@ + + + diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 1b2d1954..8ee4751e 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -13,6 +13,7 @@ import AppStats from "../components/applications/ApplicationDetailsComponents/Ap import ApplicationDetails from "../components/applications/ApplicationDetails.vue"; import ApplicationsList from "../components/applications/ApplicationsList.vue"; import ApplicationsForm from "../components/applications/ApplicationsForm.vue"; +import ApplicationDeployFromTemplate from "../components/applications/ApplicationDeployFromTemplate.vue"; import Container from "../views/Container.vue"; import UserSettings from "../views/UserSettings.vue"; import ChangePasswordForm from "../components/userSettings/ChangePasswordForm.vue"; @@ -20,6 +21,8 @@ import UserInfo from "../components/userSettings/UserInfo.vue"; import ServerSettings from "../views/ServerSettings.vue"; import ServerInfo from "../components/serverSettings/ServerInfo.vue"; import ServerVariables from "../components/serverSettings/ServerVariables.vue"; +import Prune from "../components/serverSettings/Prune.vue"; +import ServerUpdate from "../components/serverSettings/ServerUpdate.vue"; Vue.use(VueRouter); @@ -60,6 +63,11 @@ const routes = [ path: "deploy/:appId", component: ApplicationsForm }, + { + name: "Deploy from Template", + path: "templates", + component: ApplicationDeployFromTemplate + }, { name: "View Applications", path: "/", @@ -127,6 +135,16 @@ const routes = [ name: "Template Variables", path: "templateVariables", component: ServerVariables + }, + { + name: "Prune", + path: "prune", + component: Prune + }, + { + name: "Update Yacht", + path: "update", + component: ServerUpdate } ] }, diff --git a/frontend/src/store/modules/apps.js b/frontend/src/store/modules/apps.js index 875db0b0..47a554aa 100644 --- a/frontend/src/store/modules/apps.js +++ b/frontend/src/store/modules/apps.js @@ -2,9 +2,11 @@ import axios from "axios"; const state = { apps: [], + updatable: [], logs: [], processes: [], - isLoading: false + isLoading: false, + action: "" }; const mutations = { @@ -27,17 +29,28 @@ const mutations = { }, setLoading(state, loading) { state.isLoading = loading; + }, + setAction(state, action) { + state.action = action; + }, + setUpdatable(state, updatable) { + state.updatable = updatable; + }, + setUpdated(state, updated) { + let index = state.updatable.indexOf(updated); + state.updatable.splice(index, 1); } }; const actions = { readApps({ commit }) { commit("setLoading", true); + commit("setAction", "Getting Apps ..."); const url = "/api/apps/"; axios .get(url) .then(response => { - const apps = response.data; + var apps = response.data; commit("setApps", apps); }) .catch(err => { @@ -45,6 +58,26 @@ const actions = { }) .finally(() => { commit("setLoading", false); + commit("setAction", ""); + }); + }, + checkAppsUpdates({ commit }) { + commit("setLoading", true); + commit("setAction", "Checking for updates..."); + const url = "/api/apps/updates"; + axios + .get(url) + .then(response => { + console.log(response); + const apps = response.data; + commit("setUpdatable", apps); + }) + .catch(err => { + commit("snackbar/setErr", err, { root: true }); + }) + .finally(() => { + commit("setLoading", false); + commit("setAction", ""); }); }, readApp({ commit }, Name) { @@ -92,6 +125,7 @@ const actions = { }, AppAction({ commit }, { Name, Action }) { commit("setLoading", true); + commit("setAction", Action + " " + Name + " ..."); const url = `/api/apps/${Name}/${Action}`; axios .get(url) @@ -103,7 +137,11 @@ const actions = { commit("snackbar/setErr", err, { root: true }); }) .finally(() => { + if (Action == "update") { + commit("setUpdated", Name); + } commit("setLoading", false); + commit("setAction", ""); }); } }; diff --git a/frontend/src/store/modules/snackbar.js b/frontend/src/store/modules/snackbar.js index 45cc7e1a..32136867 100644 --- a/frontend/src/store/modules/snackbar.js +++ b/frontend/src/store/modules/snackbar.js @@ -29,6 +29,13 @@ const mutations = { state.btnColor = "black"; state.visible = true; }, + setMessage(state, message) { + state.content = message; + state.bottom = true; + state.color = "primary"; + state.btnColor = "black"; + state.visible = true; + }, clearSnack(state) { state.content = ""; (state.bottom = false), (state.btnColor = ""); diff --git a/frontend/src/store/modules/templates.js b/frontend/src/store/modules/templates.js index 4cf7b58b..bf82a1e8 100644 --- a/frontend/src/store/modules/templates.js +++ b/frontend/src/store/modules/templates.js @@ -61,6 +61,33 @@ const actions = { commit("setLoading", false); }); }, + readTemplatesAndItems({ commit }) { + commit("setLoading", true); + const url = "/api/templates/"; + axios + .get(url) + .then(response => { + const templates = response.data; + templates.forEach(function(template) { + let temp_url = `/api/templates/${template.id}`; + axios + .get(temp_url) + .then(response => { + commit("setTemplate", response.data); + }) + .catch(err => { + commit("snackbar/setErr", err, { root: true }); + }); + }); + commit("setTemplates", templates); + }) + .catch(err => { + commit("snackbar/setErr", err, { root: true }); + }) + .finally(() => { + commit("setLoading", false); + }); + }, readTemplate({ commit }, id) { commit("setLoading", true); const url = `/api/templates/${id}`; diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index 95f1df31..9d3dc3e1 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -89,8 +89,15 @@ export default { window.location.reload(); console.log(err); }); + var proto = ""; + if (location.protocol == "http:") { + proto = "ws://"; + } else { + proto = "wss://"; + } + console.log(location.protocol); this.statConnection = new WebSocket( - `ws://${location.hostname}:${location.port}/api/apps/stats` + `${proto}${location.hostname}:${location.port}/api/apps/stats` ); this.statConnection.onopen = () => { this.statConnection.send( @@ -113,7 +120,7 @@ export default { this.stats[statsGroup.name].mem_total.push(statsGroup.mem_total); for (let key in this.stats[statsGroup.name]) { if ( - this.stats[statsGroup.name][key].length > 5 && + this.stats[statsGroup.name][key].length > 3 && Array.isArray(this.stats[statsGroup.name][key]) ) { this.stats[statsGroup.name][key].shift(); diff --git a/nginx.conf b/nginx.conf index eafdbbdd..ac193dd2 100644 --- a/nginx.conf +++ b/nginx.conf @@ -17,11 +17,16 @@ http { sendfile on; keepalive_timeout 5; + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + upstream api_server { server unix:/tmp/gunicorn.sock fail_timeout=0; } - # Paths for Vue and Flask + # Paths for Vue and FastAPI server { listen *:8000; @@ -37,13 +42,16 @@ http { root /usr/share/nginx/html; } - # Flask + # FastAPI location /api/ { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; + + proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; + client_body_temp_path /var/www/client_body_temp; proxy_temp_path /var/www/proxy_temp; # we don't want nginx trying to do something clever with