From 2a8828632b0cc92bad0702686164520dfd238e4a Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Sat, 20 Feb 2021 12:33:47 -0800 Subject: [PATCH 01/20] working on support bundle --- Nextcloud_bundle.zip | Bin 0 -> 2159 bytes backend/api/actions/compose.py | 33 ++++++++++++++++++++++++++++++++- backend/api/main.py | 2 +- backend/api/routers/compose.py | 6 ++++++ backend/api/settings.py | 2 +- 5 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 Nextcloud_bundle.zip diff --git a/Nextcloud_bundle.zip b/Nextcloud_bundle.zip new file mode 100644 index 0000000000000000000000000000000000000000..7a8c4f4047913501d7ec25cbdc6bf8ff27026928 GIT binary patch literal 2159 zcma)8+iuf95KSo;$r3M!4;b-)+6Q}`Qi`yIK&S{+twaRng%(ANy-k+dyVma7O`s3` z0Ui;5#|Q8g%-AK(x(2e5bD24F_A)bPGumIjd4E9T*NelqDyYZ1w+91y?xIbJnM-qB zWl^ptM|LNaO1h6w@KdQKP+4K1G`g0V$lyfg!UC_k%=xEW0C&J@ZkVYQvpY<RfI zlMbSP1V>(6rj!(zSeEY&;Bw zs_8(ID;}H{I&=8A#mRVF!#LzwMn~P{3c^)J0E;nReB0Q(GM~daz<5l~1~4A`K88-h zAc_GTgYP}Yz~jby4jY?rzT~ASA5WFmQ3xiIr_;t`bIGl+|6ZAo>*sp;?*2twDkin9Oi@x%F!A6`qnCe-DDg|~He%#mH{X8@zdc_Y4CuLs*G;C=Q(>@% zi&9$=&5ImcB|u>y^-kcyRy2GN{2@(HY8Lks{Q!^!pJ2Ks)0CuyE%iNHAR8cUx{>s; z;sr+dUes=Bv?_!`At7FX5Jo5h&zgo_SWp^#%i)J-uPUQU!49;-4L8BH&Z`0wzhGh| zcT?sx!|N*JjwhG>a~;rn>Amrh+AZ;HZ$C=#X2Y_z|2Ol`ST9_`VDwgd*A>7 literal 0 HcmV?d00001 diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 053cfd1e..697cfc77 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -1,9 +1,12 @@ -from fastapi import HTTPException +from fastapi import HTTPException, Response from sh import docker_compose import os import yaml import pathlib import shutil +import docker +import io +import zipfile from ..settings import Settings from ..utils.compose import find_yml_files, get_readme_file, get_logo_file @@ -297,3 +300,31 @@ def delete_compose(project_name): except Exception as exc: raise HTTPException(exc.status_code, exc.strerror) return get_compose_projects() + +def generate_support_bundle(project_name): + dclient = docker.from_env() + with zipfile.ZipFile(project_name + "_bundle.zip", "w") as zf: + try: + files = find_yml_files(settings.COMPOSE_DIR + project_name) + except Exception as exc: + raise HTTPException(exc.status_code, exc.detail) + for project, file in files.items(): + if project_name == project: + services = {} + logs = {} + compose = open(file) + loaded_compose = yaml.load(compose, Loader=yaml.SafeLoader) + for service in loaded_compose.get("services"): + _service = dclient.containers.get(service) + service_log = _service.logs() + zf.writestr(service+'.log', service_log) + _content = open(file) + content = _content.read() + zf.writestr('docker-compose.yml', content) + zf.close() + + resp = Response(zf, media_type="application/x-zip-compressed") + resp['Content-Disposition'] = 'attachment; filename=%s' % project_name + "_bundle.zip" + return resp + else: + raise HTTPException(404, "Project " + project_name + " not found") diff --git a/backend/api/main.py b/backend/api/main.py index 7bb2c780..6b51dd09 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -32,7 +32,7 @@ class jwtSettings(BaseModel): authjwt_token_location: set = {"headers", "cookies"} authjwt_cookie_secure: bool = False authjwt_cookie_csrf_protect: bool = True - authjwt_cookie_samesite: str = "lax" + authjwt_cookie_samesite: str = settings.SAME_SITE_COOKIES @AuthJWT.load_config diff --git a/backend/api/routers/compose.py b/backend/api/routers/compose.py index 7e86b68f..c83ee9d7 100644 --- a/backend/api/routers/compose.py +++ b/backend/api/routers/compose.py @@ -7,6 +7,7 @@ get_compose, write_compose, delete_compose, + generate_support_bundle ) from fastapi_jwt_auth import AuthJWT from ..auth import auth_check @@ -48,3 +49,8 @@ def write_compose_project( def get_compose_app_action(project_name, action, app, Authorize: AuthJWT = Depends()): auth_check(Authorize) return compose_app_action(project_name, action, app) + +@router.get("/{project_name}/support") +def get_support_bundle(project_name, Authorize: AuthJWT = Depends()): + auth_check(Authorize) + return generate_support_bundle(project_name) \ No newline at end of file diff --git a/backend/api/settings.py b/backend/api/settings.py index 2bedc1e2..a05c69c1 100644 --- a/backend/api/settings.py +++ b/backend/api/settings.py @@ -18,7 +18,7 @@ class Settings(BaseSettings): ADMIN_EMAIL = os.environ.get("ADMIN_EMAIL", "admin@yacht.local") ACCESS_TOKEN_EXPIRES = os.environ.get("ACCESS_TOKEN_EXPIRES", 15) REFRESH_TOKEN_EXPIRES = os.environ.get("REFRESH_TOKEN_EXPIRES", 1) - SAME_SITE_COOKIES = os.environ.get("SAME_SITE_COOKIES", True) + SAME_SITE_COOKIES = os.environ.get("SAME_SITE_COOKIES", 'lax') DISABLE_AUTH = os.environ.get("DISABLE_AUTH", False) BASE_TEMPLATE_VARIABLES = [ {"variable": "!config", "replacement": "/yacht/AppData/Config"}, From 9f8be1692a45aaf65f393d23322b1d88a60a5c06 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Sun, 21 Feb 2021 09:05:22 -0800 Subject: [PATCH 02/20] added support bundle to bottom of project details --- backend/api/actions/compose.py | 59 ++++++++++-------- .../src/components/compose/ProjectDetails.vue | 61 ++++++++++++++----- 2 files changed, 80 insertions(+), 40 deletions(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 697cfc77..225570b1 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -1,4 +1,5 @@ from fastapi import HTTPException, Response +from fastapi.responses import StreamingResponse from sh import docker_compose import os import yaml @@ -302,29 +303,35 @@ def delete_compose(project_name): return get_compose_projects() def generate_support_bundle(project_name): - dclient = docker.from_env() - with zipfile.ZipFile(project_name + "_bundle.zip", "w") as zf: - try: - files = find_yml_files(settings.COMPOSE_DIR + project_name) - except Exception as exc: - raise HTTPException(exc.status_code, exc.detail) - for project, file in files.items(): - if project_name == project: - services = {} - logs = {} - compose = open(file) - loaded_compose = yaml.load(compose, Loader=yaml.SafeLoader) - for service in loaded_compose.get("services"): - _service = dclient.containers.get(service) - service_log = _service.logs() - zf.writestr(service+'.log', service_log) - _content = open(file) - content = _content.read() - zf.writestr('docker-compose.yml', content) - zf.close() - - resp = Response(zf, media_type="application/x-zip-compressed") - resp['Content-Disposition'] = 'attachment; filename=%s' % project_name + "_bundle.zip" - return resp - else: - raise HTTPException(404, "Project " + project_name + " not found") + files = find_yml_files(settings.COMPOSE_DIR + project_name) + if project_name in files: + dclient = docker.from_env() + stream = io.BytesIO() + with zipfile.ZipFile(stream, "w") as zf, open(files[project_name], 'r') as fp: + compose = yaml.load(fp, Loader=yaml.SafeLoader) + for _service in compose.get("services"): + if len(compose.get("services").keys()) <2: + try: + service = dclient.containers.get(_service) + except docker.errors.NotFound as exc: + raise HTTPException(exc.status_code, detail='container ' + _service + ' not found') + else: + try: + service = dclient.containers.get(project_name+'_'+_service) + except docker.errors.NotFound as exc: + raise HTTPException(exc.status_code, detail='container ' + _service + ' not found') + service_log = service.logs() + zf.writestr(f"{_service}.log", service_log) + fp.seek(0) + # It is possible that ".write(...)" has better memory management here. + zf.writestr("docker-compose.yml", fp.read()) + stream.seek(0) + return StreamingResponse( + stream, + media_type="application/x-zip-compressed", + headers={ + "Content-Disposition": f"attachment;filename={project_name}_bundle.zip" + } + ) + else: + raise HTTPException(404, f"Project {project_name} not found.") \ No newline at end of file diff --git a/frontend/src/components/compose/ProjectDetails.vue b/frontend/src/components/compose/ProjectDetails.vue index e6a56bef..da525b01 100644 --- a/frontend/src/components/compose/ProjectDetails.vue +++ b/frontend/src/components/compose/ProjectDetails.vue @@ -186,7 +186,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'up' + Action: 'up', }) " > @@ -200,7 +200,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'start' + Action: 'start', }) " > @@ -213,7 +213,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'stop' + Action: 'stop', }) " > @@ -226,7 +226,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'restart' + Action: 'restart', }) " > @@ -240,7 +240,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'pull' + Action: 'pull', }) " > @@ -254,7 +254,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'kill' + Action: 'kill', }) " > @@ -267,7 +267,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'rm' + Action: 'rm', }) " > @@ -365,7 +365,11 @@ Value - + + + + + {{ index }} + + + {{ value }} + + + @@ -461,6 +483,11 @@ {{ project.volumes.join(", ") }} + + Download Support Bundle + Download the logs and docker-compose to get help with your project + Download + @@ -496,26 +523,26 @@ export default { data() { return { selectedProject: null, - deleteDialog: false + deleteDialog: false, }; }, computed: { ...mapState("projects", ["project", "projects", "isLoading", "action"]), ...mapState("apps", ["apps"]), ...mapGetters({ - getProjectByName: "projects/getProjectByName" + getProjectByName: "projects/getProjectByName", }), project() { const projectName = this.$route.params.projectName; return this.getProjectByName(projectName); - } + }, }, methods: { ...mapActions({ readProject: "projects/readProject", projectAppAction: "projects/ProjectAppAction", ProjectAction: "projects/ProjectAction", - readApps: "apps/readApps" + readApps: "apps/readApps", }), editProject(projectname) { this.$router.push({ path: `/projects/${projectname}/edit` }); @@ -523,6 +550,12 @@ export default { postDelete() { this.$router.push({ name: "View Projects" }); }, + isObject(val) { + if (val === null) { + return false; + } + return typeof val === "function" || typeof val === "object"; + }, getStatus(name) { for (var app in this.apps) { if ( @@ -538,13 +571,13 @@ export default { const projectName = this.$route.params.projectName; this.readProject(projectName); this.readApps(); - } + }, }, mounted() { const projectName = this.$route.params.projectName; this.readProject(projectName); this.readApps(); - } + }, }; From 2e450d682a6442e6743cd20de70b639f218d4599 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Sun, 21 Feb 2021 19:48:01 -0800 Subject: [PATCH 03/20] refined project container name detection --- backend/api/actions/compose.py | 40 ++++++++++++++----- .../src/components/compose/ProjectDetails.vue | 12 ++++-- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 225570b1..0b6a6029 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -302,28 +302,50 @@ def delete_compose(project_name): raise HTTPException(exc.status_code, exc.strerror) return get_compose_projects() + def generate_support_bundle(project_name): files = find_yml_files(settings.COMPOSE_DIR + project_name) if project_name in files: dclient = docker.from_env() stream = io.BytesIO() - with zipfile.ZipFile(stream, "w") as zf, open(files[project_name], 'r') as fp: + with zipfile.ZipFile(stream, "w") as zf, open(files[project_name], "r") as fp: compose = yaml.load(fp, Loader=yaml.SafeLoader) + # print(compose) + # print(compose.get("services")) for _service in compose.get("services"): - if len(compose.get("services").keys()) <2: + print() + if len(compose.get("services").keys()) < 2: try: - service = dclient.containers.get(_service) + if compose.get("services")[_service].get("container_name"): + service = dclient.containers.get( + compose.get("services")[_service].get("container_name") + ) + else: + service = dclient.containers.get(_service) except docker.errors.NotFound as exc: - raise HTTPException(exc.status_code, detail='container ' + _service + ' not found') - else: + raise HTTPException( + exc.status_code, + detail="container " + _service + " not found", + ) + else: try: - service = dclient.containers.get(project_name+'_'+_service) + if compose.get("services")[_service].get("container_name"): + service = dclient.containers.get( + compose.get("services")[_service].get("container_name") + ) + else: + service = dclient.containers.get( + project_name.lower() + "_" + _service + "_1" + ) except docker.errors.NotFound as exc: - raise HTTPException(exc.status_code, detail='container ' + _service + ' not found') + raise HTTPException( + exc.status_code, + detail="container " + _service + " not found", + ) service_log = service.logs() zf.writestr(f"{_service}.log", service_log) fp.seek(0) - # It is possible that ".write(...)" has better memory management here. + # It is possible that ".write(...)" has better memory management here. zf.writestr("docker-compose.yml", fp.read()) stream.seek(0) return StreamingResponse( @@ -331,7 +353,7 @@ def generate_support_bundle(project_name): media_type="application/x-zip-compressed", headers={ "Content-Disposition": f"attachment;filename={project_name}_bundle.zip" - } + }, ) else: raise HTTPException(404, f"Project {project_name} not found.") \ No newline at end of file diff --git a/frontend/src/components/compose/ProjectDetails.vue b/frontend/src/components/compose/ProjectDetails.vue index da525b01..3b339d7d 100644 --- a/frontend/src/components/compose/ProjectDetails.vue +++ b/frontend/src/components/compose/ProjectDetails.vue @@ -485,8 +485,13 @@ Download Support Bundle - Download the logs and docker-compose to get help with your project - Download + + Download the logs and docker-compose to get help with your + project + Download @@ -560,7 +565,7 @@ export default { for (var app in this.apps) { if ( this.apps[app].name == name || - this.apps[app].name == this.project.name + "_" + name + "_1" + this.apps[app].name == this.project.name.toLowerCase() + "_" + name + "_1" ) { return this.apps[app].State.Status; } @@ -576,6 +581,7 @@ export default { mounted() { const projectName = this.$route.params.projectName; this.readProject(projectName); + this.readApps(); }, }; From ba2b3b35c1bf03b1fa685f6bfc974c402f450b70 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Sun, 21 Feb 2021 19:56:45 -0800 Subject: [PATCH 04/20] fixed labels in templates to use label instead of name --- backend/api/db/schemas/apps.py | 2 +- backend/api/utils/apps.py | 2 +- frontend/src/components/applications/ApplicationsForm.vue | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/api/db/schemas/apps.py b/backend/api/db/schemas/apps.py index c51c35e8..dde8184b 100644 --- a/backend/api/db/schemas/apps.py +++ b/backend/api/db/schemas/apps.py @@ -32,7 +32,7 @@ class DevicesSchema(BaseModel): class LabelSchema(BaseModel): - name: str + label: str value: str diff --git a/backend/api/utils/apps.py b/backend/api/utils/apps.py index e6cc5ced..f7400780 100644 --- a/backend/api/utils/apps.py +++ b/backend/api/utils/apps.py @@ -132,7 +132,7 @@ def conv_devices2data(data): def conv_labels2data(data): if data: - return dict((d.name, d.value) for d in data) + return dict((d.label, d.value) for d in data) else: labels = {} return labels diff --git a/frontend/src/components/applications/ApplicationsForm.vue b/frontend/src/components/applications/ApplicationsForm.vue index 87b5f676..4e14dfb5 100644 --- a/frontend/src/components/applications/ApplicationsForm.vue +++ b/frontend/src/components/applications/ApplicationsForm.vue @@ -551,7 +551,7 @@ > Date: Sun, 21 Feb 2021 20:02:46 -0800 Subject: [PATCH 05/20] working on fixing theme being unset after auth timeout --- frontend/src/App.vue | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 930be393..c11919df 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -45,25 +45,36 @@ export default { Sidebar: Sidebar, Appbar: Appbar, LoginForm: LoginForm, - snackbar: snackbar + snackbar: snackbar, }, data: () => ({}), computed: { ...mapGetters({ isLoggedIn: "auth/isAuthenticated", - authDisabled: "auth/authDisabled" + authDisabled: "auth/authDisabled", }), theme() { return this.$vuetify.theme.dark ? "dark" : "light"; - } + }, }, methods: { ...mapActions({ - authCheck: "auth/AUTH_CHECK" - }) + authCheck: "auth/AUTH_CHECK", + }), }, created() { this.authCheck(); + const dark_theme = localStorage.getItem("dark_theme"); + const theme = JSON.parse(localStorage.getItem("theme")); + + if (dark_theme == "false") { + this.$vuetify.theme.dark = false; + } else if (dark_theme == "true") { + this.$vuetify.theme.dark = true; + } + if (theme) { + this.$vuetify.theme.themes = theme; + } }, mounted() { const dark_theme = localStorage.getItem("dark_theme"); @@ -77,7 +88,7 @@ export default { if (theme) { this.$vuetify.theme.themes = theme; } - } + }, }; From 2977e83650834b42f5f621a99a4d6c17fc2a3281 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Sun, 21 Feb 2021 20:33:07 -0800 Subject: [PATCH 06/20] remove accidental bundle; add mysql support back in --- Dockerfile | 1 + Nextcloud_bundle.zip | Bin 2159 -> 0 bytes backend/requirements.txt | 1 + 3 files changed, 2 insertions(+) delete mode 100644 Nextcloud_bundle.zip diff --git a/Dockerfile b/Dockerfile index c06910c9..411da584 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,7 @@ RUN \ python3-dev \ libffi-dev \ ruby-dev \ + mysql-dev \ postgresql-dev &&\ echo "**** install packages ****" && \ apk add --no-cache \ diff --git a/Nextcloud_bundle.zip b/Nextcloud_bundle.zip deleted file mode 100644 index 7a8c4f4047913501d7ec25cbdc6bf8ff27026928..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2159 zcma)8+iuf95KSo;$r3M!4;b-)+6Q}`Qi`yIK&S{+twaRng%(ANy-k+dyVma7O`s3` z0Ui;5#|Q8g%-AK(x(2e5bD24F_A)bPGumIjd4E9T*NelqDyYZ1w+91y?xIbJnM-qB zWl^ptM|LNaO1h6w@KdQKP+4K1G`g0V$lyfg!UC_k%=xEW0C&J@ZkVYQvpY<RfI zlMbSP1V>(6rj!(zSeEY&;Bw zs_8(ID;}H{I&=8A#mRVF!#LzwMn~P{3c^)J0E;nReB0Q(GM~daz<5l~1~4A`K88-h zAc_GTgYP}Yz~jby4jY?rzT~ASA5WFmQ3xiIr_;t`bIGl+|6ZAo>*sp;?*2twDkin9Oi@x%F!A6`qnCe-DDg|~He%#mH{X8@zdc_Y4CuLs*G;C=Q(>@% zi&9$=&5ImcB|u>y^-kcyRy2GN{2@(HY8Lks{Q!^!pJ2Ks)0CuyE%iNHAR8cUx{>s; z;sr+dUes=Bv?_!`At7FX5Jo5h&zgo_SWp^#%i)J-uPUQU!49;-4L8BH&Z`0wzhGh| zcT?sx!|N*JjwhG>a~;rn>Amrh+AZ;HZ$C=#X2Y_z|2Ol`ST9_`VDwgd*A>7 diff --git a/backend/requirements.txt b/backend/requirements.txt index a19380a6..9aa5074c 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -19,6 +19,7 @@ httptools==0.1.1 idna==2.10 python-jose==3.2.0 makefun==1.9.2 +mysqlclient==2.0.3 passlib==1.7.4 psycopg2==2.8.6 pycparser==2.20 From 404ddf927b71ab713641711cf354f76956a90863 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 22 Feb 2021 06:12:39 -0800 Subject: [PATCH 07/20] fix permissions replacement on /config/compose; will ony change owner on /config/data.sqlite and other files in /config; --- root/etc/cont-init.d/30-config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/root/etc/cont-init.d/30-config b/root/etc/cont-init.d/30-config index a57440e7..5bb68aab 100644 --- a/root/etc/cont-init.d/30-config +++ b/root/etc/cont-init.d/30-config @@ -3,8 +3,8 @@ # permissions chown -R abc:abc \ /app -chown -R abc:abc \ - /config +find /config -type d \( -path /config/compose \) -prune -o \ + -exec chown abc:abc {} \; chown -R abc:abc \ /alembic # non-root docker From edb54fb41292f797a9f40d5e65b82ea3c2cd3ae8 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 22 Feb 2021 07:27:58 -0800 Subject: [PATCH 08/20] fixed mysql support --- Dockerfile | 6 +++--- backend/api/db/database.py | 11 ++++++++--- backend/api/db/models/users.py | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 411da584..a075bbbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,13 +29,13 @@ RUN \ make \ python3-dev \ libffi-dev \ - ruby-dev \ - mysql-dev \ - postgresql-dev &&\ + ruby-dev &&\ echo "**** install packages ****" && \ apk add --no-cache \ python3 \ py3-pip \ + postgresql-dev \ + mysql-dev \ nginx &&\ gem install sass &&\ echo "**** Installing Python Modules ****" && \ diff --git a/backend/api/db/database.py b/backend/api/db/database.py index 439ae45d..4012bed0 100644 --- a/backend/api/db/database.py +++ b/backend/api/db/database.py @@ -7,9 +7,14 @@ SQLALCHEMY_DATABASE_URL = settings.SQLALCHEMY_DATABASE_URI -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) +if 'sqlite:///' in SQLALCHEMY_DATABASE_URL: + engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} + ) +else: + engine = create_engine( + SQLALCHEMY_DATABASE_URL + ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() diff --git a/backend/api/db/models/users.py b/backend/api/db/models/users.py index 44246f7e..c60bc2c9 100644 --- a/backend/api/db/models/users.py +++ b/backend/api/db/models/users.py @@ -6,7 +6,7 @@ class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, index=True) - username = Column("email", String, unique=True, index=True, nullable=False) + username = Column("email", String(length=264), unique=True, index=True, nullable=False) hashed_password = Column(String(length=72), nullable=False) is_active = Column(Boolean, default=True, nullable=False) is_superuser = Column(Boolean, default=False, nullable=False) From 794bd26ddd2131075d36cb8cec8467b507386317 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 22 Feb 2021 11:07:06 -0800 Subject: [PATCH 09/20] changed barchart to use progress bar --- frontend/src/views/Home.vue | 58 +++++++++++++++---------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index d4414b45..82f1af35 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -40,26 +40,24 @@ class="text-left pt-0 AppTitle" > CPU Usage: - {{ app.cpu_percent[app.cpu_percent.length - 1] }}% + {{ app.cpu_percent }}% +
MEM Usage: - {{ app.mem_percent[app.mem_percent.length - 1] }}%, + {{ app.mem_percent }}%, {{ - formatBytes(app.mem_current[app.mem_current.length - 1]) + formatBytes(app.mem_current) }} + CPU Usage: {{ app.cpu_percent[app.cpu_percent.length - 1] }}% + >CPU Usage: {{ app.cpu_percent }}%
- MEM Usage: {{ app.mem_percent[app.mem_percent.length - 1] }}%, - {{ formatBytes(app.mem_current[app.mem_current.length - 1]) }} + MEM Usage: {{ app.mem_percent }}%, + {{ formatBytes(app.mem_current) }}
-
@@ -70,11 +68,7 @@ From d96664cdc49375d83ec0945e12a62471c409f7d7 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 22 Feb 2021 13:01:35 -0800 Subject: [PATCH 11/20] added progress back in --- .../components/applications/ApplicationDeployFromTemplate.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/components/applications/ApplicationDeployFromTemplate.vue b/frontend/src/components/applications/ApplicationDeployFromTemplate.vue index b73707b7..fd5390fc 100644 --- a/frontend/src/components/applications/ApplicationDeployFromTemplate.vue +++ b/frontend/src/components/applications/ApplicationDeployFromTemplate.vue @@ -20,6 +20,9 @@
+ + + {{template.title}} From cb6eb568ac74370c8e619fea42b39ad7cff3fcf5 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 22 Feb 2021 14:33:19 -0800 Subject: [PATCH 12/20] optimize websocket closing to save on resource usage --- backend/api/routers/apps.py | 49 ++++++++++++++++++++++++++++--------- backend/api/utils/apps.py | 2 +- backend/requirements.txt | 2 +- frontend/src/views/Home.vue | 8 +++--- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index 312c30de..79ec4c4b 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -1,5 +1,6 @@ -from fastapi import APIRouter, Depends, HTTPException, WebSocket, status, Cookie +from fastapi import APIRouter, Depends, HTTPException, WebSocket, status, Cookie, WebSocketDisconnect from typing import List +from websockets.exceptions import ConnectionClosedOK from sqlalchemy.orm import Session from sqlalchemy.exc import IntegrityError @@ -100,8 +101,14 @@ async def logs(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depends async for line in logs: try: await websocket.send_text(line) - except Exception as e: - return e + except ConnectionClosedOK as e: + await websocket.close(code=e.code) + break + except RuntimeError as e: + if e.args[0] == 'Cannot call "send" once a close message has been sent.': + break + else: + print(e) else: await websocket.close(code=status.WS_1011_INTERNAL_ERROR) @@ -156,8 +163,15 @@ async def stats(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depend } try: await websocket.send_text(json.dumps(full_stats)) - except Exception as e: - return e + + except ConnectionClosedOK as e: + await websocket.close(code=e.code) + break + except RuntimeError as e: + if e.args[0] == 'Cannot call "send" once a close message has been sent.': + break + else: + print(e) else: await websocket.close(code=status.WS_1011_INTERNAL_ERROR) @@ -218,12 +232,23 @@ async def process_container(name, stats, websocket): full_stats = { "name": name, - "cpu_percent": cpu_percent, - "mem_current": mem_current, - "mem_total": mem_total, - "mem_percent": mem_percent, + "cpu_percent": round(cpu_percent,0), + "mem_current": round(mem_current, 0), + "mem_total": round(mem_total,0), + "mem_percent": round(mem_percent,0), } try: - await websocket.send_text(json.dumps(full_stats)) - except Exception as e: - pass + if 'last_stats' in locals() and full_stats != last_stats: + last_stats = full_stats + await websocket.send_text(json.dumps(full_stats)) + elif 'last_stats' not in locals(): + last_stats = full_stats + await websocket.send_text(json.dumps(full_stats)) + except ConnectionClosedOK as e: + await websocket.close(code=e.code) + break + except RuntimeError as e: + if e.args[0] == 'Cannot call "send" once a close message has been sent.': + break + else: + print(e) diff --git a/backend/api/utils/apps.py b/backend/api/utils/apps.py index f7400780..fe3de57e 100644 --- a/backend/api/utils/apps.py +++ b/backend/api/utils/apps.py @@ -251,7 +251,7 @@ async def get_app_stats(app_name): line, cpu_total, cpu_system ) except KeyError as e: - print("error while getting new CPU stats: %r, falling back") + print(f"error while getting new CPU stats: {e}, falling back") cpu_percent = await calculate_cpu_percent(line) full_stats = { diff --git a/backend/requirements.txt b/backend/requirements.txt index 9aa5074c..472af8e1 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -11,7 +11,7 @@ dnspython==2.0.0 docker==4.3.1 docker-compose==1.27.3 email-validator==1.1.1 -fastapi==0.61.0 +fastapi==0.63.0 fastapi-jwt-auth==0.5.0 gunicorn==20.0.4 h11==0.9.0 diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index 82f1af35..fcf973a7 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -40,15 +40,15 @@ class="text-left pt-0 AppTitle" > CPU Usage: - {{ app.cpu_percent }}% + {{ app.cpu_percent }}%
MEM Usage: + {{ app.mem_percent }}%, {{ formatBytes(app.mem_current) }} - Date: Mon, 22 Feb 2021 15:00:03 -0800 Subject: [PATCH 13/20] doing bytes formatting on the backend --- backend/api/routers/apps.py | 4 ++-- backend/api/utils/apps.py | 9 +++++++++ frontend/src/views/Home.vue | 17 ++--------------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index 79ec4c4b..54c8103e 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -16,6 +16,7 @@ calculate_cpu_percent2, calculate_network_bytes, get_app_stats, + format_bytes ) from fastapi_jwt_auth import AuthJWT @@ -233,8 +234,7 @@ async def process_container(name, stats, websocket): full_stats = { "name": name, "cpu_percent": round(cpu_percent,0), - "mem_current": round(mem_current, 0), - "mem_total": round(mem_total,0), + "mem_current": format_bytes(mem_current), "mem_percent": round(mem_percent,0), } try: diff --git a/backend/api/utils/apps.py b/backend/api/utils/apps.py index fe3de57e..09a7f4c7 100644 --- a/backend/api/utils/apps.py +++ b/backend/api/utils/apps.py @@ -301,3 +301,12 @@ def check_updates(tag): else: return False + +def format_bytes(size): + power = 2**10 + n=0 + power_labels = {0: 'B',1: 'KB', 2: 'MB', 3: 'GB'} + while size > power: + size /= power + n += 1 + return str(round(size))+' '+ str(power_labels[n]) \ No newline at end of file diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index fcf973a7..f8554a1f 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -47,7 +47,7 @@ {{ app.mem_percent }}%, {{ - formatBytes(app.mem_current) + app.mem_current }} @@ -55,7 +55,7 @@ >CPU Usage: {{ app.cpu_percent }}%
MEM Usage: {{ app.mem_percent }}%, - {{ formatBytes(app.mem_current) }} + {{ app.mem_current }}
@@ -105,7 +105,6 @@ export default { this.stats[statsGroup.name].cpu_percent = statsGroup.cpu_percent; this.stats[statsGroup.name].mem_percent = statsGroup.mem_percent; this.stats[statsGroup.name].mem_current = statsGroup.mem_current; - this.stats[statsGroup.name].mem_total = statsGroup.mem_total; // for (let key in this.stats[statsGroup.name]) { // if ( // this.stats[statsGroup.name][key].length > 3 && @@ -127,7 +126,6 @@ export default { this.stats[statsGroup.name].cpu_percent = ''; this.stats[statsGroup.name].mem_percent = ''; this.stats[statsGroup.name].mem_current = ''; - this.stats[statsGroup.name].mem_total = ''; }, sortByTitle(arr) { let sorted = Object.keys(arr) @@ -151,17 +149,6 @@ export default { ); this.statConnection.close(1000, "Leaving page or refreshing"); }, - formatBytes(bytes) { - if (bytes === 0) return "0 Bytes"; - const decimals = 2; - const k = 1024; - const dm = decimals < 0 ? 0 : decimals; - const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - - const i = Math.floor(Math.log(bytes) / Math.log(k)); - - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; - }, fillStats(app) { let datacollection = { labels: ["Resource Usage"], From 5f934bca733d255b386f06198789b18fe8b53b93 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 22 Feb 2021 19:16:11 -0800 Subject: [PATCH 14/20] changed all imports to a better format --- backend/api/actions/__init__.py | 6 ++-- backend/api/actions/apps.py | 44 +++++++++++++++++++---------- backend/api/actions/compose.py | 6 ++-- backend/api/actions/resources.py | 1 - backend/api/auth/__init__.py | 1 - backend/api/auth/auth.py | 5 ++-- backend/api/db/crud/__init__.py | 3 -- backend/api/db/crud/settings.py | 7 +---- backend/api/db/crud/templates.py | 19 +++++++------ backend/api/db/crud/users.py | 3 +- backend/api/db/database.py | 2 +- backend/api/db/models/__init__.py | 2 -- backend/api/db/models/containers.py | 3 +- backend/api/db/models/users.py | 5 ++-- backend/api/db/schemas/__init__.py | 4 --- backend/api/db/schemas/resources.py | 1 - backend/api/db/schemas/templates.py | 4 +-- backend/api/main.py | 36 +++++++++++++---------- backend/api/routers/app_settings.py | 36 +++++++++++++---------- backend/api/routers/apps.py | 25 ++++------------ backend/api/routers/compose.py | 16 +++++------ backend/api/routers/resources.py | 10 +++---- backend/api/routers/templates.py | 15 +++++----- backend/api/routers/users.py | 11 +++++--- backend/api/utils/__init__.py | 5 ---- backend/api/utils/apps.py | 9 +++--- backend/api/utils/auth.py | 1 - backend/api/utils/templates.py | 2 +- 28 files changed, 138 insertions(+), 144 deletions(-) diff --git a/backend/api/actions/__init__.py b/backend/api/actions/__init__.py index c948eca5..dabfb16e 100644 --- a/backend/api/actions/__init__.py +++ b/backend/api/actions/__init__.py @@ -1,3 +1,3 @@ -from .apps import * -from .compose import * -from .resources import * +from . import apps +from . import compose +from . import resources \ No newline at end of file diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index 1b26c499..cc93a43c 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -1,12 +1,26 @@ -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException, BackgroundTasks -from ..db import models, schemas -from ..utils import * -from ..utils import check_updates as _update_check -from docker.errors import APIError - -from datetime import datetime +from fastapi import HTTPException + +from api.db.schemas.apps import ( + DeployLogs, + DeployForm, + AppLogs, + Processes +) +from api.utils.apps import ( + conv_caps2data, + conv_devices2data, + conv_env2data, + conv_image2data, + conv_labels2data, + conv_portlabels2data, + conv_ports2data, + conv_restart2data, + conv_sysctls2data, + conv_volumes2data, + _check_updates, +) +from api.utils.templates import conv2dict + import time import subprocess import docker @@ -48,7 +62,7 @@ def check_app_update(app_name): ) if app.attrs["Config"]["Image"]: - if _update_check(app.attrs["Config"]["Image"]): + if _check_updates(app.attrs["Config"]["Image"]): app.attrs.update(conv2dict("isUpdatable", True)) app.attrs.update(conv2dict("name", app.name)) app.attrs.update(conv2dict("ports", app.ports)) @@ -119,7 +133,7 @@ def get_app_processes(app_name): app = dclient.containers.get(app_name) if app.status == "running": processes = app.top() - return schemas.Processes( + return Processes( Processes=processes["Processes"], Titles=processes["Titles"] ) else: @@ -136,7 +150,7 @@ def get_app_logs(app_name): dclient = docker.from_env() app = dclient.containers.get(app_name) if app.status == "running": - return schemas.AppLogs(logs=app.logs()) + return AppLogs(logs=app.logs()) else: return None @@ -147,7 +161,7 @@ def get_app_logs(app_name): """ -def deploy_app(template: schemas.DeployForm): +def deploy_app(template: DeployForm): try: launch = launch_app( template.name, @@ -174,7 +188,7 @@ def deploy_app(template: schemas.DeployForm): ) print("done deploying") - return schemas.DeployLogs(logs=launch.logs()) + return DeployLogs(logs=launch.logs()) """ @@ -416,4 +430,4 @@ def check_self_update(): else: raise HTTPException(status_code=400, detail=exc.args) - return _update_check(yacht.image.tags[0]) + return _check_updates(yacht.image.tags[0]) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 0b6a6029..ec0654b2 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -1,4 +1,4 @@ -from fastapi import HTTPException, Response +from fastapi import HTTPException from fastapi.responses import StreamingResponse from sh import docker_compose import os @@ -9,8 +9,8 @@ import io import zipfile -from ..settings import Settings -from ..utils.compose import find_yml_files, get_readme_file, get_logo_file +from api.settings import Settings +from api.utils.compose import find_yml_files settings = Settings() diff --git a/backend/api/actions/resources.py b/backend/api/actions/resources.py index fbe14ba4..9ee25bd1 100644 --- a/backend/api/actions/resources.py +++ b/backend/api/actions/resources.py @@ -1,5 +1,4 @@ import docker -import json from fastapi import HTTPException ### IMAGES ### diff --git a/backend/api/auth/__init__.py b/backend/api/auth/__init__.py index 35c1920f..e69de29b 100644 --- a/backend/api/auth/__init__.py +++ b/backend/api/auth/__init__.py @@ -1 +0,0 @@ -from .auth import * diff --git a/backend/api/auth/auth.py b/backend/api/auth/auth.py index 9c39101b..6da2837c 100644 --- a/backend/api/auth/auth.py +++ b/backend/api/auth/auth.py @@ -1,9 +1,8 @@ from typing import Tuple -from ..settings import Settings +from api.settings import Settings -from fastapi import Depends, HTTPException -from fastapi_jwt_auth import AuthJWT +from fastapi import HTTPException from fastapi_jwt_auth.exceptions import JWTDecodeError from passlib import pwd diff --git a/backend/api/db/crud/__init__.py b/backend/api/db/crud/__init__.py index 1ad06096..e69de29b 100644 --- a/backend/api/db/crud/__init__.py +++ b/backend/api/db/crud/__init__.py @@ -1,3 +0,0 @@ -from .templates import * -from .settings import * -from .users import * diff --git a/backend/api/db/crud/settings.py b/backend/api/db/crud/settings.py index 169e3bcb..dff46846 100644 --- a/backend/api/db/crud/settings.py +++ b/backend/api/db/crud/settings.py @@ -1,12 +1,7 @@ from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm.session import make_transient - -from .. import models, schemas +from api.db.models import containers as models from datetime import datetime -import urllib.request -import sqlite3 import json diff --git a/backend/api/db/crud/templates.py b/backend/api/db/crud/templates.py index eb28f405..6bd28ee2 100644 --- a/backend/api/db/crud/templates.py +++ b/backend/api/db/crud/templates.py @@ -1,11 +1,13 @@ from sqlalchemy.orm import Session from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm.session import make_transient from fastapi import HTTPException -from .. import models, schemas -from ...utils import conv_ports2dict, conv_sysctls2dict +from api.db.models import containers as models +from api.utils.templates import ( + conv_sysctls2dict, + conv_ports2dict +) from datetime import datetime import urllib.request @@ -46,12 +48,12 @@ def delete_template(db: Session, template_id: int): return _template -def add_template(db: Session, template: models.containers.Template): +def add_template(db: Session, template: models.Template): try: _template_path = urlparse(template.url).path ext = os.path.splitext(_template_path)[1] # Opens the JSON and iterate over the content. - _template = models.containers.Template(title=template.title, url=template.url) + _template = models.Template(title=template.title, url=template.url) with urllib.request.urlopen(template.url) as file: if ext.rstrip() in (".yml", ".yaml"): loaded_file = yaml.load(file, Loader=yaml.SafeLoader) @@ -67,7 +69,7 @@ def add_template(db: Session, template: models.containers.Template): # Optional use classmethod from_dict try: - template_content = models.containers.TemplateItem( + template_content = models.TemplateItem( type=int(entry.get("type", 1)), title=entry["title"], platform=entry["platform"], @@ -100,7 +102,7 @@ def add_template(db: Session, template: models.containers.Template): sysctls = conv_sysctls2dict(entry.get("sysctls", [])) # Optional use classmethod from_dict - template_content = models.containers.TemplateItem( + template_content = models.TemplateItem( type=int(entry.get("type", 1)), title=entry["title"], platform=entry["platform"], @@ -134,7 +136,6 @@ def add_template(db: Session, template: models.containers.Template): # TODO raises IntegrityError on duplicates (uniqueness) # status db.rollback() - pass return get_template(db=db, url=template.url) @@ -192,7 +193,7 @@ def refresh_template(db: Session, template_id: id): sysctls = conv_sysctls2dict(entry.get("sysctls", [])) # Optional use classmethod from_dict - template_content = models.containers.TemplateItem( + template_content = models.TemplateItem( type=int(entry["type"]), title=entry["title"], platform=entry["platform"], diff --git a/backend/api/db/crud/users.py b/backend/api/db/crud/users.py index 5f97fdaa..0f909a40 100644 --- a/backend/api/db/crud/users.py +++ b/backend/api/db/crud/users.py @@ -1,6 +1,7 @@ from sqlalchemy.orm import Session from passlib.context import CryptContext -from . import models, schemas +from api.db.models import users as models +from api.db.schemas import users as schemas from fastapi.exceptions import HTTPException pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") diff --git a/backend/api/db/database.py b/backend/api/db/database.py index 4012bed0..54f231d4 100644 --- a/backend/api/db/database.py +++ b/backend/api/db/database.py @@ -1,6 +1,6 @@ from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker, session +from sqlalchemy.orm import sessionmaker from ..settings import Settings settings = Settings() diff --git a/backend/api/db/models/__init__.py b/backend/api/db/models/__init__.py index fe11b44a..e69de29b 100644 --- a/backend/api/db/models/__init__.py +++ b/backend/api/db/models/__init__.py @@ -1,2 +0,0 @@ -from .containers import * -from .users import * diff --git a/backend/api/db/models/containers.py b/backend/api/db/models/containers.py index 389244c1..3f29f9b8 100644 --- a/backend/api/db/models/containers.py +++ b/backend/api/db/models/containers.py @@ -1,5 +1,4 @@ from sqlalchemy import ( - Boolean, Column, ForeignKey, Integer, @@ -10,7 +9,7 @@ ) from sqlalchemy.orm import relationship -from ..database import Base +from api.db.database import Base from datetime import datetime diff --git a/backend/api/db/models/users.py b/backend/api/db/models/users.py index c60bc2c9..e1a36e32 100644 --- a/backend/api/db/models/users.py +++ b/backend/api/db/models/users.py @@ -1,6 +1,5 @@ -from sqlalchemy import Boolean, Column, ForeignKey, Integer, String -from ..database import Base - +from sqlalchemy import Boolean, Column, Integer, String +from api.db.database import Base class User(Base): __tablename__ = "user" diff --git a/backend/api/db/schemas/__init__.py b/backend/api/db/schemas/__init__.py index 01c0561d..e69de29b 100644 --- a/backend/api/db/schemas/__init__.py +++ b/backend/api/db/schemas/__init__.py @@ -1,4 +0,0 @@ -from .apps import * -from .templates import * -from .users import * -from .compose import * diff --git a/backend/api/db/schemas/resources.py b/backend/api/db/schemas/resources.py index 3e93481e..ad765668 100644 --- a/backend/api/db/schemas/resources.py +++ b/backend/api/db/schemas/resources.py @@ -1,4 +1,3 @@ -from fastapi import FastAPI from pydantic import BaseModel from typing import Optional diff --git a/backend/api/db/schemas/templates.py b/backend/api/db/schemas/templates.py index b57c72ff..e8963f33 100644 --- a/backend/api/db/schemas/templates.py +++ b/backend/api/db/schemas/templates.py @@ -1,7 +1,7 @@ from __future__ import annotations -from typing import List, Optional, Union +from typing import List, Optional from datetime import datetime -from pydantic import BaseModel, Json +from pydantic import BaseModel class TemplateItem(BaseModel): diff --git a/backend/api/main.py b/backend/api/main.py index 6b51dd09..d81525d9 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -1,26 +1,32 @@ import uvicorn -from fastapi import Depends, FastAPI, Header, HTTPException, Request -from fastapi.responses import JSONResponse, RedirectResponse -from .routers import apps, templates, app_settings, resources, compose, users -import uuid - +from fastapi import Depends, FastAPI, Request +from fastapi.responses import JSONResponse from fastapi_jwt_auth import AuthJWT from fastapi_jwt_auth.exceptions import AuthJWTException from pydantic import BaseModel from sqlalchemy.orm import Session -from .db.crud.templates import ( +from api.settings import Settings +from api.utils.auth import get_db +from api.db.models.containers import TemplateVariables +from api.db.database import SessionLocal +from api.db.schemas.users import UserCreate +from api.db.crud.users import( + create_user, + get_users +) +from api.routers import ( + apps, + app_settings, + compose, + resources, + templates, + users +) +from api.db.crud.templates import ( read_template_variables, - set_template_variables, + set_template_variables ) -from .settings import Settings - -from .utils import get_db - -from .db.crud import create_user, get_users -from .db.models import User, TemplateVariables -from .db.database import SessionLocal -from .db.schemas import UserCreate app = FastAPI(root_path="/api") diff --git a/backend/api/routers/app_settings.py b/backend/api/routers/app_settings.py index 1288d7b6..a0105062 100644 --- a/backend/api/routers/app_settings.py +++ b/backend/api/routers/app_settings.py @@ -1,21 +1,27 @@ -from fastapi import APIRouter, Depends, HTTPException, File, UploadFile, BackgroundTasks -from fastapi.responses import FileResponse +from fastapi import APIRouter, BackgroundTasks, Depends, File, UploadFile from typing import List from sqlalchemy.orm import Session -from datetime import datetime - -from ..db import crud, schemas -from ..db.models import containers -from ..db.database import SessionLocal, engine -from ..utils.auth import get_db -from ..actions.apps import _update_self, check_self_update -from ..actions import resources -from ..settings import Settings -import yaml + +from api.utils.auth import get_db +from api.auth.auth import auth_check + +from api.db.crud import templates as crud +from api.db.crud import settings as scrud +from api.db.schemas import templates as schemas +from api.db.models import containers +from api.db.database import engine + +from api.actions import resources +from api.actions.apps import ( + _update_self, + check_self_update +) + +from api.settings import Settings + from fastapi_jwt_auth import AuthJWT -from ..auth import auth_check containers.Base.metadata.create_all(bind=engine) @@ -54,7 +60,7 @@ def set_template_variables( ) def export_settings(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): auth_check(Authorize) - return crud.export_settings(db=db) + return scrud.export_settings(db=db) @router.post( @@ -66,7 +72,7 @@ def import_settings( Authorize: AuthJWT = Depends(), ): auth_check(Authorize) - return crud.import_settings(db=db, upload=upload) + return scrud.import_settings(db=db, upload=upload) @router.get( diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index 54c8103e..6168532c 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -1,34 +1,21 @@ -from fastapi import APIRouter, Depends, HTTPException, WebSocket, status, Cookie, WebSocketDisconnect -from typing import List +from fastapi import APIRouter, Depends, WebSocket, status from websockets.exceptions import ConnectionClosedOK -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm.session import make_transient - -from ..db import models, schemas -from ..db.models import containers -from ..db.database import SessionLocal, engine -from .. import actions -from ..utils import ( - calculate_blkio_bytes, +from api.db.schemas import apps as schemas +import api.actions.apps as actions +from api.settings import Settings +from api.auth.auth import auth_check +from api.utils.apps import ( calculate_cpu_percent, calculate_cpu_percent2, - calculate_network_bytes, - get_app_stats, format_bytes ) from fastapi_jwt_auth import AuthJWT from fastapi_jwt_auth.exceptions import AuthJWTException -import docker as sdocker import aiodocker -from datetime import datetime -import urllib.request import json import asyncio -from ..settings import Settings -from ..auth import auth_check settings = Settings() diff --git a/backend/api/routers/compose.py b/backend/api/routers/compose.py index c83ee9d7..91a84520 100644 --- a/backend/api/routers/compose.py +++ b/backend/api/routers/compose.py @@ -1,6 +1,7 @@ -from fastapi import APIRouter, Depends, HTTPException, Form -from typing import List -from ..actions.compose import ( +from fastapi import APIRouter, Depends +from fastapi_jwt_auth import AuthJWT + +from api.actions.compose import ( get_compose_projects, compose_action, compose_app_action, @@ -9,9 +10,8 @@ delete_compose, generate_support_bundle ) -from fastapi_jwt_auth import AuthJWT -from ..auth import auth_check -from ..db.schemas.compose import * +from api.auth.auth import auth_check +from api.db.schemas import compose as schemas router = APIRouter() @@ -37,9 +37,9 @@ def get_compose_action(project_name, action, Authorize: AuthJWT = Depends()): return compose_action(project_name, action) -@router.post("/{project_name}/edit", response_model=ComposeRead) +@router.post("/{project_name}/edit", response_model=schemas.ComposeRead) def write_compose_project( - project_name, compose: ComposeWrite, Authorize: AuthJWT = Depends() + project_name, compose: schemas.ComposeWrite, Authorize: AuthJWT = Depends() ): auth_check(Authorize) return write_compose(compose=compose) diff --git a/backend/api/routers/resources.py b/backend/api/routers/resources.py index f30c9d4a..6a50ecf0 100644 --- a/backend/api/routers/resources.py +++ b/backend/api/routers/resources.py @@ -1,11 +1,11 @@ from __future__ import annotations -from fastapi import APIRouter, Depends, HTTPException -from typing import List -from ..actions import resources -from ..db.schemas.resources import ImageWrite, VolumeWrite, NetworkWrite +from fastapi import APIRouter, Depends from fastapi_jwt_auth import AuthJWT -from ..auth import auth_check +from api.actions import resources +from api.db.schemas.resources import ImageWrite, VolumeWrite, NetworkWrite +from api.utils.auth import get_db +from api.auth.auth import auth_check router = APIRouter() ### Images ### diff --git a/backend/api/routers/templates.py b/backend/api/routers/templates.py index 13e69f9b..687ae71b 100644 --- a/backend/api/routers/templates.py +++ b/backend/api/routers/templates.py @@ -3,16 +3,17 @@ from typing import List from sqlalchemy.orm import Session -from datetime import datetime -from ..db import crud, schemas -from ..db.models import containers -from ..db.database import SessionLocal, engine -from ..utils import get_db +import api.db.crud.templates as crud +import api.db.schemas.templates as schemas +from api.db.models.containers import Base +from api.db.database import engine +from api.utils.auth import get_db +from api.auth.auth import auth_check + from fastapi_jwt_auth import AuthJWT -from ..auth import auth_check -containers.Base.metadata.create_all(bind=engine) +Base.metadata.create_all(bind=engine) router = APIRouter() diff --git a/backend/api/routers/users.py b/backend/api/routers/users.py index 480a6ee9..87b182e2 100644 --- a/backend/api/routers/users.py +++ b/backend/api/routers/users.py @@ -1,10 +1,13 @@ from fastapi import APIRouter, Depends, HTTPException from fastapi_jwt_auth import AuthJWT -from ..db import models, crud, schemas, database from sqlalchemy.orm import Session -from ..utils import get_db -from ..auth import auth_check -from ..settings import Settings + +from api.utils.auth import get_db +from api.auth.auth import auth_check +from api.settings import Settings +from api.db.crud import users as crud +from api.db.models import users as models +from api.db.schemas import users as schemas router = APIRouter() settings = Settings() diff --git a/backend/api/utils/__init__.py b/backend/api/utils/__init__.py index 572cf4ce..e69de29b 100644 --- a/backend/api/utils/__init__.py +++ b/backend/api/utils/__init__.py @@ -1,5 +0,0 @@ -from .apps import * -from .auth import * -from .compose import * -from .resources import * -from .templates import * diff --git a/backend/api/utils/apps.py b/backend/api/utils/apps.py index 09a7f4c7..7985f0e3 100644 --- a/backend/api/utils/apps.py +++ b/backend/api/utils/apps.py @@ -1,6 +1,7 @@ -from ..db import models -from ..db.database import SessionLocal -from ..settings import Settings +import api.db.models.containers as models +from api.db.database import SessionLocal +from api.settings import Settings + import aiodocker import docker from docker.errors import APIError @@ -276,7 +277,7 @@ def get_update_ports(ports): return None -def check_updates(tag): +def _check_updates(tag): if tag: dclient = docker.from_env() try: diff --git a/backend/api/utils/auth.py b/backend/api/utils/auth.py index 3624dff4..a008a41c 100644 --- a/backend/api/utils/auth.py +++ b/backend/api/utils/auth.py @@ -1,6 +1,5 @@ from ..settings import Settings from ..db.database import SessionLocal -from fastapi import WebSocket, Depends settings = Settings() diff --git a/backend/api/utils/templates.py b/backend/api/utils/templates.py index 505f1cda..9c4f2490 100644 --- a/backend/api/utils/templates.py +++ b/backend/api/utils/templates.py @@ -1,6 +1,6 @@ import re from fastapi import HTTPException -from typing import Dict, List, Optional +from typing import Dict, List # For Templates REGEXP_PORT_ASSIGN = r"^(?:(?:\d{1,5}:)?\d{1,5}|:\d{1,5})/(?:tcp|udp)$" From 2cf68c95cc527a4568e919c708ae877ff1912ac3 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 22 Feb 2021 19:16:54 -0800 Subject: [PATCH 15/20] formatted --- backend/api/actions/__init__.py | 2 +- backend/api/actions/apps.py | 15 ++++----------- backend/api/actions/compose.py | 2 +- backend/api/db/crud/templates.py | 5 +---- backend/api/db/database.py | 6 ++---- backend/api/db/models/users.py | 5 ++++- backend/api/db/schemas/users.py | 2 +- backend/api/main.py | 19 +++---------------- backend/api/routers/app_settings.py | 5 +---- backend/api/routers/apps.py | 26 ++++++++++++++------------ backend/api/routers/compose.py | 5 +++-- backend/api/settings.py | 2 +- backend/api/utils/apps.py | 9 +++++---- 13 files changed, 41 insertions(+), 62 deletions(-) diff --git a/backend/api/actions/__init__.py b/backend/api/actions/__init__.py index dabfb16e..ca510152 100644 --- a/backend/api/actions/__init__.py +++ b/backend/api/actions/__init__.py @@ -1,3 +1,3 @@ from . import apps from . import compose -from . import resources \ No newline at end of file +from . import resources diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index cc93a43c..652af115 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -1,11 +1,6 @@ from fastapi import HTTPException -from api.db.schemas.apps import ( - DeployLogs, - DeployForm, - AppLogs, - Processes -) +from api.db.schemas.apps import DeployLogs, DeployForm, AppLogs, Processes from api.utils.apps import ( conv_caps2data, conv_devices2data, @@ -133,9 +128,7 @@ def get_app_processes(app_name): app = dclient.containers.get(app_name) if app.status == "running": processes = app.top() - return Processes( - Processes=processes["Processes"], Titles=processes["Titles"] - ) + return Processes(Processes=processes["Processes"], Titles=processes["Titles"]) else: return None @@ -178,7 +171,7 @@ def deploy_app(template: DeployForm): conv_sysctls2data(template.sysctls), conv_caps2data(template.cap_add), edit=template.edit or False, - id=template.id or None + id=template.id or None, ) except HTTPException as exc: raise HTTPException(status_code=exc.status_code, detail=exc.detail) @@ -232,7 +225,7 @@ def launch_app( sysctls, caps, edit, - id + id, ): dclient = docker.from_env() if edit == True: diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index ec0654b2..7d0556c4 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -356,4 +356,4 @@ def generate_support_bundle(project_name): }, ) else: - raise HTTPException(404, f"Project {project_name} not found.") \ No newline at end of file + raise HTTPException(404, f"Project {project_name} not found.") diff --git a/backend/api/db/crud/templates.py b/backend/api/db/crud/templates.py index 6bd28ee2..0e713337 100644 --- a/backend/api/db/crud/templates.py +++ b/backend/api/db/crud/templates.py @@ -4,10 +4,7 @@ from fastapi import HTTPException from api.db.models import containers as models -from api.utils.templates import ( - conv_sysctls2dict, - conv_ports2dict -) +from api.utils.templates import conv_sysctls2dict, conv_ports2dict from datetime import datetime import urllib.request diff --git a/backend/api/db/database.py b/backend/api/db/database.py index 54f231d4..c633bfa1 100644 --- a/backend/api/db/database.py +++ b/backend/api/db/database.py @@ -7,14 +7,12 @@ SQLALCHEMY_DATABASE_URL = settings.SQLALCHEMY_DATABASE_URI -if 'sqlite:///' in SQLALCHEMY_DATABASE_URL: +if "sqlite:///" in SQLALCHEMY_DATABASE_URL: engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} ) else: - engine = create_engine( - SQLALCHEMY_DATABASE_URL - ) + engine = create_engine(SQLALCHEMY_DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() diff --git a/backend/api/db/models/users.py b/backend/api/db/models/users.py index e1a36e32..c88b5c29 100644 --- a/backend/api/db/models/users.py +++ b/backend/api/db/models/users.py @@ -1,11 +1,14 @@ from sqlalchemy import Boolean, Column, Integer, String from api.db.database import Base + class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, index=True) - username = Column("email", String(length=264), unique=True, index=True, nullable=False) + username = Column( + "email", String(length=264), unique=True, index=True, nullable=False + ) hashed_password = Column(String(length=72), nullable=False) is_active = Column(Boolean, default=True, nullable=False) is_superuser = Column(Boolean, default=False, nullable=False) diff --git a/backend/api/db/schemas/users.py b/backend/api/db/schemas/users.py index 1037347b..69bd1854 100644 --- a/backend/api/db/schemas/users.py +++ b/backend/api/db/schemas/users.py @@ -17,4 +17,4 @@ class User(UserBase): authDisabled: Optional[bool] class Config: - orm_mode = True \ No newline at end of file + orm_mode = True diff --git a/backend/api/main.py b/backend/api/main.py index d81525d9..656404a7 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -11,22 +11,9 @@ from api.db.models.containers import TemplateVariables from api.db.database import SessionLocal from api.db.schemas.users import UserCreate -from api.db.crud.users import( - create_user, - get_users -) -from api.routers import ( - apps, - app_settings, - compose, - resources, - templates, - users -) -from api.db.crud.templates import ( - read_template_variables, - set_template_variables -) +from api.db.crud.users import create_user, get_users +from api.routers import apps, app_settings, compose, resources, templates, users +from api.db.crud.templates import read_template_variables, set_template_variables app = FastAPI(root_path="/api") diff --git a/backend/api/routers/app_settings.py b/backend/api/routers/app_settings.py index a0105062..6ebb6e40 100644 --- a/backend/api/routers/app_settings.py +++ b/backend/api/routers/app_settings.py @@ -13,10 +13,7 @@ from api.db.database import engine from api.actions import resources -from api.actions.apps import ( - _update_self, - check_self_update -) +from api.actions.apps import _update_self, check_self_update from api.settings import Settings diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index 6168532c..e6294d98 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -5,11 +5,7 @@ import api.actions.apps as actions from api.settings import Settings from api.auth.auth import auth_check -from api.utils.apps import ( - calculate_cpu_percent, - calculate_cpu_percent2, - format_bytes -) +from api.utils.apps import calculate_cpu_percent, calculate_cpu_percent2, format_bytes from fastapi_jwt_auth import AuthJWT from fastapi_jwt_auth.exceptions import AuthJWTException @@ -93,7 +89,10 @@ async def logs(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depends await websocket.close(code=e.code) break except RuntimeError as e: - if e.args[0] == 'Cannot call "send" once a close message has been sent.': + if ( + e.args[0] + == 'Cannot call "send" once a close message has been sent.' + ): break else: print(e) @@ -151,12 +150,15 @@ async def stats(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depend } try: await websocket.send_text(json.dumps(full_stats)) - + except ConnectionClosedOK as e: await websocket.close(code=e.code) break except RuntimeError as e: - if e.args[0] == 'Cannot call "send" once a close message has been sent.': + if ( + e.args[0] + == 'Cannot call "send" once a close message has been sent.' + ): break else: print(e) @@ -220,15 +222,15 @@ async def process_container(name, stats, websocket): full_stats = { "name": name, - "cpu_percent": round(cpu_percent,0), + "cpu_percent": round(cpu_percent, 0), "mem_current": format_bytes(mem_current), - "mem_percent": round(mem_percent,0), + "mem_percent": round(mem_percent, 0), } try: - if 'last_stats' in locals() and full_stats != last_stats: + if "last_stats" in locals() and full_stats != last_stats: last_stats = full_stats await websocket.send_text(json.dumps(full_stats)) - elif 'last_stats' not in locals(): + elif "last_stats" not in locals(): last_stats = full_stats await websocket.send_text(json.dumps(full_stats)) except ConnectionClosedOK as e: diff --git a/backend/api/routers/compose.py b/backend/api/routers/compose.py index 91a84520..80b0743d 100644 --- a/backend/api/routers/compose.py +++ b/backend/api/routers/compose.py @@ -8,7 +8,7 @@ get_compose, write_compose, delete_compose, - generate_support_bundle + generate_support_bundle, ) from api.auth.auth import auth_check from api.db.schemas import compose as schemas @@ -50,7 +50,8 @@ def get_compose_app_action(project_name, action, app, Authorize: AuthJWT = Depen auth_check(Authorize) return compose_app_action(project_name, action, app) + @router.get("/{project_name}/support") def get_support_bundle(project_name, Authorize: AuthJWT = Depends()): auth_check(Authorize) - return generate_support_bundle(project_name) \ No newline at end of file + return generate_support_bundle(project_name) diff --git a/backend/api/settings.py b/backend/api/settings.py index a05c69c1..6c503abc 100644 --- a/backend/api/settings.py +++ b/backend/api/settings.py @@ -18,7 +18,7 @@ class Settings(BaseSettings): ADMIN_EMAIL = os.environ.get("ADMIN_EMAIL", "admin@yacht.local") ACCESS_TOKEN_EXPIRES = os.environ.get("ACCESS_TOKEN_EXPIRES", 15) REFRESH_TOKEN_EXPIRES = os.environ.get("REFRESH_TOKEN_EXPIRES", 1) - SAME_SITE_COOKIES = os.environ.get("SAME_SITE_COOKIES", 'lax') + SAME_SITE_COOKIES = os.environ.get("SAME_SITE_COOKIES", "lax") DISABLE_AUTH = os.environ.get("DISABLE_AUTH", False) BASE_TEMPLATE_VARIABLES = [ {"variable": "!config", "replacement": "/yacht/AppData/Config"}, diff --git a/backend/api/utils/apps.py b/backend/api/utils/apps.py index 7985f0e3..80ed057b 100644 --- a/backend/api/utils/apps.py +++ b/backend/api/utils/apps.py @@ -303,11 +303,12 @@ def _check_updates(tag): else: return False + def format_bytes(size): - power = 2**10 - n=0 - power_labels = {0: 'B',1: 'KB', 2: 'MB', 3: 'GB'} + power = 2 ** 10 + n = 0 + power_labels = {0: "B", 1: "KB", 2: "MB", 3: "GB"} while size > power: size /= power n += 1 - return str(round(size))+' '+ str(power_labels[n]) \ No newline at end of file + return str(round(size)) + " " + str(power_labels[n]) From beefd0d07726ffb20a023b09d85f0da470670a00 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 22 Feb 2021 19:23:09 -0800 Subject: [PATCH 16/20] fixing codacy problems --- backend/api/actions/apps.py | 8 ++++---- backend/api/routers/apps.py | 1 + backend/api/routers/resources.py | 1 - 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index 652af115..adefbf0d 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -171,7 +171,7 @@ def deploy_app(template: DeployForm): conv_sysctls2data(template.sysctls), conv_caps2data(template.cap_add), edit=template.edit or False, - id=template.id or None, + _id=template.id or None, ) except HTTPException as exc: raise HTTPException(status_code=exc.status_code, detail=exc.detail) @@ -225,14 +225,14 @@ def launch_app( sysctls, caps, edit, - id, + _id, ): dclient = docker.from_env() if edit == True: try: - dclient.containers.get(id) + dclient.containers.get(_id) try: - running_app = dclient.containers.get(id) + running_app = dclient.containers.get(_id) running_app.remove(force=True) except Exception as e: raise e diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index e6294d98..55a7e387 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -202,6 +202,7 @@ async def process_container(name, stats, websocket): cpu_total = 0.0 cpu_system = 0.0 cpu_percent = 0.0 + last_stats = {} async for line in stats: if line["memory_stats"]: mem_current = line["memory_stats"]["usage"] diff --git a/backend/api/routers/resources.py b/backend/api/routers/resources.py index 6a50ecf0..51828a60 100644 --- a/backend/api/routers/resources.py +++ b/backend/api/routers/resources.py @@ -4,7 +4,6 @@ from api.actions import resources from api.db.schemas.resources import ImageWrite, VolumeWrite, NetworkWrite -from api.utils.auth import get_db from api.auth.auth import auth_check router = APIRouter() From bbf6f4483ad8f0118d912490f091a4505893bb4e Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 22 Feb 2021 19:24:25 -0800 Subject: [PATCH 17/20] removing unused imports --- backend/api/actions/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/api/actions/__init__.py b/backend/api/actions/__init__.py index ca510152..e69de29b 100644 --- a/backend/api/actions/__init__.py +++ b/backend/api/actions/__init__.py @@ -1,3 +0,0 @@ -from . import apps -from . import compose -from . import resources From 47474796fde57d817b6d1f94dad06e47ca33c501 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Tue, 23 Feb 2021 09:04:35 -0800 Subject: [PATCH 18/20] Update Home.vue Use mem_percent for memory bar. --- frontend/src/views/Home.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index f8554a1f..c73bafb9 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -44,7 +44,7 @@ {{ app.cpu_percent }}%
MEM Usage: - + {{ app.mem_percent }}%, {{ app.mem_current From 942c27b4ad730902c78689495fe801365126fb19 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Tue, 23 Feb 2021 09:05:41 -0800 Subject: [PATCH 19/20] Update ApplicationsList.vue Use primary color for port labels --- frontend/src/components/applications/ApplicationsList.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/applications/ApplicationsList.vue b/frontend/src/components/applications/ApplicationsList.vue index d1cc4b47..5602991f 100644 --- a/frontend/src/components/applications/ApplicationsList.vue +++ b/frontend/src/components/applications/ApplicationsList.vue @@ -210,7 +210,7 @@ v-bind="attrs" class="mx-1" v-if="port.hip == '0.0.0.0'" - color="indigo darken-2" + color="primary" label small :href="'http://' + host_ip + ':' + port.hport" From 5f4fdaabc2c0704002a5814a6f6de20ad7a00dcd Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Wed, 24 Feb 2021 14:12:37 -0800 Subject: [PATCH 20/20] Update requirements.txt --- backend/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 472af8e1..fb9c2c52 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -8,8 +8,8 @@ chardet==3.0.4 click==7.1.2 databases==0.3.2 dnspython==2.0.0 -docker==4.3.1 -docker-compose==1.27.3 +docker==4.4.4 +docker-compose==1.28.4 email-validator==1.1.1 fastapi==0.63.0 fastapi-jwt-auth==0.5.0 @@ -38,4 +38,4 @@ uvicorn==0.11.8 uvloop==0.14.0 websocket-client==0.57.0 websockets==8.1 -wheel==0.36.2 \ No newline at end of file +wheel==0.36.2