From 8be0dd413cbefee20f0c5c4d72a6117f5494e419 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 2 Nov 2020 13:34:51 -0800 Subject: [PATCH 01/75] switched from fastapi-users to fastapi-jwt-auth --- backend/api/auth/auth.py | 92 +++------------------ backend/api/db/crud/__init__.py | 1 + backend/api/db/crud/users.py | 27 ++++++ backend/api/db/models/users.py | 12 ++- backend/api/db/schemas/__init__.py | 1 + backend/api/db/schemas/users.py | 15 +++- backend/api/main.py | 106 +++++++++++------------- backend/api/routers/app_settings.py | 15 ++-- backend/api/routers/auth.py | 13 --- backend/api/routers/compose.py | 9 +- backend/api/routers/resources.py | 27 +++--- backend/api/routers/templates.py | 13 ++- backend/api/routers/user.py | 8 -- backend/api/routers/users.py | 52 ++++++++++++ backend/api/utils/auth.py | 34 ++++---- frontend/src/App.vue | 12 +-- frontend/src/main.js | 19 ++++- frontend/src/store/modules/auth.js | 122 +++++++++++----------------- frontend/src/views/Home.vue | 97 ++++++++++++---------- 19 files changed, 331 insertions(+), 344 deletions(-) create mode 100644 backend/api/db/crud/users.py delete mode 100644 backend/api/routers/auth.py delete mode 100644 backend/api/routers/user.py create mode 100644 backend/api/routers/users.py diff --git a/backend/api/auth/auth.py b/backend/api/auth/auth.py index 09185e4a..87d976a1 100644 --- a/backend/api/auth/auth.py +++ b/backend/api/auth/auth.py @@ -1,88 +1,20 @@ -import databases -import sqlalchemy -from fastapi import FastAPI -from fastapi_users import models -from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase -from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base -from fastapi_users.db import BaseUserDatabase -from fastapi_users.authentication import CookieAuthentication -from fastapi_users import FastAPIUsers -from fastapi_users.password import get_password_hash +from typing import Tuple -from ..settings import Settings +from passlib import pwd +from passlib.context import CryptContext -settings = Settings() +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -SECRET = settings.SECRET_KEY -auth_backends = [] +def verify_and_update_password( + plain_password: str, hashed_password: str +) -> Tuple[bool, str]: + return pwd_context.verify_and_update(plain_password, hashed_password) -cookie_authentication = CookieAuthentication( - secret=SECRET, lifetime_seconds=3600, cookie_secure=False -) -auth_backends.append(cookie_authentication) +def get_password_hash(password: str) -> str: + return pwd_context.hash(password) -class User(models.BaseUser): - pass - - -class UserCreate(models.BaseUserCreate): - pass - - -class UserUpdate(User, models.BaseUserUpdate): - pass - - -class UserDB(User, models.BaseUserDB): - pass - - -DATABASE_URL = settings.SQLALCHEMY_DATABASE_URI - -database = databases.Database(DATABASE_URL) - -Base: DeclarativeMeta = declarative_base() - - -class UserTable(Base, SQLAlchemyBaseUserTable): - pass - - -engine = sqlalchemy.create_engine( - DATABASE_URL, connect_args={"check_same_thread": False} -) - -Base.metadata.create_all(engine) - -users = UserTable.__table__ -user_db = SQLAlchemyUserDatabase(UserDB, database, users) - -app = FastAPI() - -fastapi_users = FastAPIUsers( - user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, -) - - -async def fake_get_active_user(): - DISABLE_AUTH = settings.DISABLE_AUTH - if DISABLE_AUTH == "True": - return True - else: - await fastapi_users.get_current_active_user() - - -if settings.DISABLE_AUTH != "True": - get_active_user = fastapi_users.get_current_active_user -else: - get_active_user = fake_get_active_user -# get_active_user = fastapi_users.get_current_active_user -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) +def generate_password() -> str: + return pwd.genword() \ No newline at end of file diff --git a/backend/api/db/crud/__init__.py b/backend/api/db/crud/__init__.py index b4b29d83..0ceedca8 100644 --- a/backend/api/db/crud/__init__.py +++ b/backend/api/db/crud/__init__.py @@ -1,2 +1,3 @@ from .templates import * from .settings import * +from .users import * \ No newline at end of file diff --git a/backend/api/db/crud/users.py b/backend/api/db/crud/users.py new file mode 100644 index 00000000..e6d7f71d --- /dev/null +++ b/backend/api/db/crud/users.py @@ -0,0 +1,27 @@ +from sqlalchemy.orm import Session +from passlib.context import CryptContext +from . import models, schemas +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +def get_user(db: Session, user_id: int): + return db.query(models.User).filter(models.User.id == user_id).first() + +def get_user_by_name(db: Session, username: str): + return db.query(models.User).filter(models.User.username == username).first() + +def get_users(db: Session, skip: int = 0, limit: int = 100): + return db.query(models.User).offset(skip).limit(limit).all() + +def create_user(db: Session, user: schemas.UserCreate): + _hashed_password = get_password_hash(user.password) + db_user = models.User(username=user.username, hashed_password=_hashed_password) + db.add(db_user) + db.commit() + db.refresh(db_user) + return(db_user) + +def verify_password(plain_password, hashed_password): + return pwd_context.verify(plain_password, hashed_password) + +def get_password_hash(password): + return pwd_context.hash(password) \ No newline at end of file diff --git a/backend/api/db/models/users.py b/backend/api/db/models/users.py index 4e39d677..602e55d7 100644 --- a/backend/api/db/models/users.py +++ b/backend/api/db/models/users.py @@ -1,2 +1,10 @@ -from ..database import Base -from fastapi_users.db import SQLAlchemyBaseUserTable +from sqlalchemy import Boolean, Column, ForeignKey, Integer, String +from backend.api.db.database import Base + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + username = Column(String, unique=True, index=True) + hashed_password = Column(String) + is_active = Column(Boolean, default=True) \ No newline at end of file diff --git a/backend/api/db/schemas/__init__.py b/backend/api/db/schemas/__init__.py index 78b62b2f..e47494d5 100644 --- a/backend/api/db/schemas/__init__.py +++ b/backend/api/db/schemas/__init__.py @@ -1,2 +1,3 @@ from .apps import * from .templates import * +from .users import * \ No newline at end of file diff --git a/backend/api/db/schemas/users.py b/backend/api/db/schemas/users.py index 6c27de78..3d9ff495 100644 --- a/backend/api/db/schemas/users.py +++ b/backend/api/db/schemas/users.py @@ -1 +1,14 @@ -from fastapi_users import models +from pydantic import BaseModel + +class UserBase(BaseModel): + username: str + +class UserCreate(UserBase): + password: str + +class User(UserBase): + id: int + is_active: bool + + class Config: + orm_mode = True \ No newline at end of file diff --git a/backend/api/main.py b/backend/api/main.py index bc70ef86..e24277c9 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -1,93 +1,84 @@ import uvicorn -from fastapi import Depends, FastAPI, Header, HTTPException -from .routers import apps, templates, app_settings, resources, auth, user, compose +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 .db import models -from .db.database import SessionLocal, engine +from fastapi_jwt_auth import AuthJWT +from fastapi_jwt_auth.exceptions import AuthJWTException +from pydantic import BaseModel +from sqlalchemy.orm import Session + from .routers.app_settings import ( read_template_variables, set_template_variables, - SessionLocal, ) -from sqlalchemy.orm import Session - from .settings import Settings + from .utils import get_db -from .auth import ( - fastapi_users, - cookie_authentication, - database, - users, - user_create, - UserDB, - get_password_hash, -) +from backend.api.db.crud import create_user, get_users +from backend.api.db.models import User, TemplateVariables +from backend.api.db.database import SessionLocal +from backend.api.db.schemas import UserCreate app = FastAPI(root_path="/api") -models.Base.metadata.create_all(bind=engine) - settings = Settings() -print(settings.DISABLE_AUTH) +class jwtSettings(BaseModel): + authjwt_secret_key: str = settings.SECRET_KEY + authjwt_token_location: set = {'headers', 'cookies'} + authjwt_cookie_secure: bool = False + authjwt_cookie_csrf_protect: bool = True + authjwt_cookie_samesite: str = 'lax' + + +@AuthJWT.load_config +def get_config(): + return jwtSettings() + + +@app.exception_handler(AuthJWTException) +def authjwt_exception_handler(request: Request, exc: AuthJWTException): + return JSONResponse( + status_code=exc.status_code, + content={"detail": exc.message} + ) + +app.include_router(users.router, prefix="/auth", tags=["users"]) +app.include_router(apps.router, prefix="/apps", tags=["apps"]) app.include_router( - apps.router, - prefix="/apps", - tags=["apps"], - # dependencies=[Depends(get_token_header)], - responses={404: {"description": "Not found"}}, -) -app.include_router( - resources.router, prefix="/resources", tags=["resources"], + resources.router, + prefix="/resources", + tags=["resources"], ) -if settings.DISABLE_AUTH == "True": - app.include_router(auth.router, prefix="/auth", tags=["auth"]) -else: - app.include_router( - fastapi_users.get_auth_router(cookie_authentication), - prefix="/auth", - tags=["auth"], - ) -if settings.DISABLE_AUTH == "True": - app.include_router(user.router, prefix="/users", tags=["users"]) -else: - app.include_router( - fastapi_users.get_users_router(), prefix="/users", tags=["users"] - ) app.include_router( templates.router, prefix="/templates", tags=["templates"], - responses={404: {"description": "Not found"}}, ) app.include_router(compose.router, prefix="/compose", tags=["compose"]) app.include_router(app_settings.router, prefix="/settings", tags=["settings"]) @app.on_event("startup") -async def startup(): - await database.connect() +async def startup(db: Session = Depends(get_db)): + # await database.connect() # Clear old db migrations delete_alembic = "DROP TABLE IF EXISTS alembic_version;" - await database.execute(delete_alembic) - users_exist = await database.fetch_all(query=users.select()) + # await database.execute(delete_alembic) + users_exist = get_users(db=SessionLocal()) if users_exist: print("Users Exist") else: print("No Users. Creating the default user.") # 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, + user = UserCreate( + username=settings.ADMIN_EMAIL, password=settings.ADMIN_PASSWORD ) - user_created = await user_create(base_user) + create_user(db=SessionLocal(), user=user) template_variables_exist = read_template_variables(SessionLocal()) if template_variables_exist: print("Template Variables Exist") @@ -96,17 +87,12 @@ async def startup(): t_vars = settings.BASE_TEMPLATE_VARIABLES t_var_list = [] for t in t_vars: - template_variables = models.TemplateVariables( + template_variables = TemplateVariables( variable=t.get("variable"), replacement=t.get("replacement") ) 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() - - if __name__ == "__main__": uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) diff --git a/backend/api/routers/app_settings.py b/backend/api/routers/app_settings.py index 2c4ce53e..2066fe28 100644 --- a/backend/api/routers/app_settings.py +++ b/backend/api/routers/app_settings.py @@ -9,7 +9,6 @@ from ..db.models import containers from ..db.database import SessionLocal, engine from ..utils.auth import get_db -from ..auth import get_active_user from ..actions.apps import update_self, check_self_update from ..actions import resources from ..settings import Settings @@ -25,7 +24,7 @@ @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) @@ -34,7 +33,7 @@ def read_template_variables(db: Session = Depends(get_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) @@ -45,27 +44,27 @@ def set_template_variables( @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)]) +@router.post("/export", ) def import_settings(db: Session = Depends(get_db), upload: UploadFile = File(...)): return crud.import_settings(db=db, upload=upload) -@router.get("/prune/{resource}", dependencies=[Depends(get_active_user)]) +@router.get("/prune/{resource}", ) def prune_resources(resource: str): return resources.prune_resources(resource) -@router.get("/update", dependencies=[Depends(get_active_user)]) +@router.get("/update", ) def update_self(): return update_self() -@router.get("/check/update", dependencies=[Depends(get_active_user)]) +@router.get("/check/update", ) def _check_self_update(): return check_self_update() diff --git a/backend/api/routers/auth.py b/backend/api/routers/auth.py deleted file mode 100644 index b9d5870f..00000000 --- a/backend/api/routers/auth.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter() - - -@router.post("/login") -def fakelogin(): - return {"email": "admin@yacht.local", "authDisabled": True} - - -@router.get("/check") -def auth_check(): - return {"email": "admin@yacht.local", "authDisabled": True} diff --git a/backend/api/routers/compose.py b/backend/api/routers/compose.py index f5218ac0..3a37bce8 100644 --- a/backend/api/routers/compose.py +++ b/backend/api/routers/compose.py @@ -1,6 +1,5 @@ from fastapi import APIRouter, Depends, HTTPException from typing import List -from ..auth import get_active_user from ..actions.compose import ( get_compose_projects, compose_action, @@ -11,21 +10,21 @@ router = APIRouter() -@router.get("/", dependencies=[Depends(get_active_user)]) +@router.get("/") def get_projects(): return get_compose_projects() -@router.get("/{project_name}", dependencies=[Depends(get_active_user)]) +@router.get("/{project_name}") def get_project(project_name): return get_compose(project_name) -@router.get("/{project_name}/{action}", dependencies=[Depends(get_active_user)]) +@router.get("/{project_name}/{action}") def get_compose_action(project_name, action): return compose_action(project_name, action) -@router.get("/{project_name}/{action}/{app}", dependencies=[Depends(get_active_user)]) +@router.get("/{project_name}/{action}/{app}") def get_compose_app_action(project_name, action, app): return compose_app_action(project_name, action, app) diff --git a/backend/api/routers/resources.py b/backend/api/routers/resources.py index 58568d49..8b7f127e 100644 --- a/backend/api/routers/resources.py +++ b/backend/api/routers/resources.py @@ -1,75 +1,74 @@ from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from typing import List -from ..auth import get_active_user from ..actions import resources from ..db.schemas.resources import ImageWrite, VolumeWrite, NetworkWrite router = APIRouter() ### Images ### -@router.get("/images/", dependencies=[Depends(get_active_user)]) +@router.get("/images/", ) def get_images(): return resources.get_images() -@router.post("/images/", dependencies=[Depends(get_active_user)]) +@router.post("/images/", ) def write_image(image: ImageWrite): return resources.write_image(image.image) -@router.get("/images/{image_id}", dependencies=[Depends(get_active_user)]) +@router.get("/images/{image_id}", ) def get_image(image_id): return resources.get_image(image_id) -@router.get("/images/{image_id}/pull", dependencies=[Depends(get_active_user)]) +@router.get("/images/{image_id}/pull", ) def pull_image(image_id): return resources.update_image(image_id) -@router.delete("/images/{image_id}", dependencies=[Depends(get_active_user)]) +@router.delete("/images/{image_id}", ) def delete_image(image_id): return resources.delete_image(image_id) ### Volumes ### -@router.get("/volumes/", dependencies=[Depends(get_active_user)]) +@router.get("/volumes/", ) def get_volumes(): return resources.get_volumes() -@router.post("/volumes/", dependencies=[Depends(get_active_user)]) +@router.post("/volumes/", ) def write_volume(name: VolumeWrite): return resources.write_volume(name.name) -@router.get("/volumes/{volume_name}", dependencies=[Depends(get_active_user)]) +@router.get("/volumes/{volume_name}", ) def get_volume(volume_name): return resources.get_volume(volume_name) -@router.delete("/volumes/{volume_name}", dependencies=[Depends(get_active_user)]) +@router.delete("/volumes/{volume_name}", ) def delete_volume(volume_name): return resources.delete_volume(volume_name) ### Networks ### -@router.get("/networks/", dependencies=[Depends(get_active_user)]) +@router.get("/networks/", ) def get_networks(): return resources.get_networks() -@router.post("/networks/", dependencies=[Depends(get_active_user)]) +@router.post("/networks/", ) def write_network(form: NetworkWrite): return resources.write_network(form) -@router.get("/networks/{network_name}", dependencies=[Depends(get_active_user)]) +@router.get("/networks/{network_name}", ) def get_network(network_name): return resources.get_network(network_name) -@router.delete("/networks/{network_name}", dependencies=[Depends(get_active_user)]) +@router.delete("/networks/{network_name}", ) def delete_network(network_name): return resources.delete_network(network_name) diff --git a/backend/api/routers/templates.py b/backend/api/routers/templates.py index ebaffac2..c28c414c 100644 --- a/backend/api/routers/templates.py +++ b/backend/api/routers/templates.py @@ -9,7 +9,6 @@ from ..db.models import containers from ..db.database import SessionLocal, engine from ..utils import get_db -from ..auth import get_active_user containers.Base.metadata.create_all(bind=engine) @@ -20,7 +19,7 @@ @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) @@ -30,7 +29,7 @@ def index(db: Session = Depends(get_db)): @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) @@ -40,14 +39,14 @@ def show(id: int, db: Session = Depends(get_db)): @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)] + "/", response_model=schemas.TemplateRead ) def add_template(template: schemas.TemplateBase, db: Session = Depends(get_db)): existing_template = crud.get_template(db=db, url=template.url) @@ -59,7 +58,7 @@ def add_template(template: schemas.TemplateBase, db: Session = Depends(get_db)): @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) @@ -68,7 +67,7 @@ def refresh_template(id: int, db: Session = Depends(get_db)): @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) diff --git a/backend/api/routers/user.py b/backend/api/routers/user.py deleted file mode 100644 index 3685cc3c..00000000 --- a/backend/api/routers/user.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter() - - -@router.get("/me") -def fakelogin(): - return {"email": "admin@yacht.local", "authDisabled": True} diff --git a/backend/api/routers/users.py b/backend/api/routers/users.py new file mode 100644 index 00000000..fc3cba66 --- /dev/null +++ b/backend/api/routers/users.py @@ -0,0 +1,52 @@ +from fastapi import APIRouter, Depends, HTTPException +from fastapi_jwt_auth import AuthJWT +from backend.api.db import models, crud, schemas, database +from sqlalchemy.orm import Session +from backend.api.utils import get_db + +router = APIRouter() + +@router.post('/create', response_model=schemas.User) +def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): + db_user = crud.get_user_by_name(db, username=user.username) + if db_user: + raise HTTPException(status_code=400, detail="Username already in use") + return crud.create_user(db=db, user=user) + + +@router.post('/login') +def login(user: schemas.UserCreate, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): + _user = db.query(models.User).filter(models.User.username==user.username).first() + if _user is not None and crud.verify_password(user.password, _user.hashed_password): + # Create Tokens + access_token = Authorize.create_access_token(subject=user.username) + refresh_token = Authorize.create_refresh_token(subject=user.username) + + # Assign cookies + Authorize.set_access_cookies(access_token) + Authorize.set_refresh_cookies(refresh_token) + return {'login': 'successful', 'username': _user.username, 'access_token': access_token} + else: + raise HTTPException(status_code=400, detail="Invalid Username or Password.") + +@router.post('/refresh') +def refresh(Authorize: AuthJWT = Depends()): + Authorize.jwt_refresh_token_required() + + current_user = Authorize.get_jwt_subject() + new_access_token = Authorize.create_access_token(subject=current_user) + + Authorize.set_access_cookies(new_access_token) + return {'refresh': 'successful', 'access_token': new_access_token} + +@router.get('/me', response_model=schemas.User) +def get_user(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): + Authorize.jwt_required() + current_user = Authorize.get_jwt_subject() + return crud.get_user_by_name(db=db, username=current_user) + +@router.get('/logout') +def logout(Authorize: AuthJWT = Depends()): + Authorize.jwt_required() + Authorize.unset_jwt_cookies() + return {"msg": "Logout Successful"} diff --git a/backend/api/utils/auth.py b/backend/api/utils/auth.py index dda59e1a..1b7a482e 100644 --- a/backend/api/utils/auth.py +++ b/backend/api/utils/auth.py @@ -1,8 +1,6 @@ -from ..auth import cookie_authentication -from ..auth import user_db from ..settings import Settings -from ..db.database import SessionLocal -from fastapi import WebSocket +from backend.api.db.database import SessionLocal +from fastapi import WebSocket, Depends settings = Settings() @@ -14,17 +12,17 @@ def get_db(): finally: db.close() - -async def websocket_auth(websocket: WebSocket): - try: - cookie = websocket._cookies["fastapiusersauth"] - user = await cookie_authentication(cookie, user_db) - if user and user.is_active: - return user - elif settings.DISABLE_AUTH == "True": - return True - except: - if settings.DISABLE_AUTH == "True": - return True - else: - return None +# async def websocket_auth(websocket: WebSocket, Authorize: AuthJWT = Depends()): +# try: +# cookie = websocket._cookies["access_token_cookie"] +# csrf_cookie = websocket._cookies["csrf_access_token"] +# _verify_jwt_in_request(token = cookie, type_token='cookie', token_from='cookie') +# if user and user.is_active: +# return user +# elif settings.DISABLE_AUTH == "True": +# return True +# except: +# if settings.DISABLE_AUTH == "True": +# return True +# else: +# return None \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 3ae91968..453263c8 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -33,7 +33,7 @@ diff --git a/frontend/src/components/compose/ProjectList.vue b/frontend/src/components/compose/ProjectList.vue index 54673a24..1725cc1f 100644 --- a/frontend/src/components/compose/ProjectList.vue +++ b/frontend/src/components/compose/ProjectList.vue @@ -253,7 +253,7 @@ export default { } }, computed: { - ...mapState("projects", ["projects", "isLoading"]), + ...mapState("projects", ["projects", "isLoading"]) }, mounted() { this.readProjects(); diff --git a/frontend/src/components/nav/Sidebar.vue b/frontend/src/components/nav/Sidebar.vue index a3da6a07..1509a5ba 100644 --- a/frontend/src/components/nav/Sidebar.vue +++ b/frontend/src/components/nav/Sidebar.vue @@ -65,7 +65,7 @@ export default { to: "/", icon: "mdi-view-dashboard", text: "Dashboard", - divider: true, + divider: true }, { icon: "mdi-application", @@ -74,14 +74,14 @@ export default { { text: "View Applications", to: "/apps", - icon: "mdi-view-list", + icon: "mdi-view-list" }, { text: "New Application", to: "/apps/deploy", - icon: "mdi-plus", - }, - ], + icon: "mdi-plus" + } + ] }, { icon: "mdi-folder", @@ -90,14 +90,14 @@ export default { { text: "View Templates", to: "/templates", - icon: "mdi-view-list", + icon: "mdi-view-list" }, { text: "New Template", to: "/templates/new", - icon: "mdi-plus", - }, - ], + icon: "mdi-plus" + } + ] }, { icon: "mdi-book-open", @@ -106,9 +106,9 @@ export default { { text: "View Projects", to: "/projects", - icon: "mdi-view-list", - }, - ], + icon: "mdi-view-list" + } + ] }, { icon: "mdi-cube-outline", @@ -117,27 +117,27 @@ export default { { text: "Images", to: "/resources/images", - icon: "mdi-disc", + icon: "mdi-disc" }, { text: "Volumes", to: "/resources/volumes", - icon: "mdi-database", + icon: "mdi-database" }, { text: "Networks", to: "/resources/networks", - icon: "mdi-network", - }, - ], + icon: "mdi-network" + } + ] }, { to: "/settings/info", icon: "mdi-cog", - text: "Settings", - }, - ], - }), + text: "Settings" + } + ] + }) }; diff --git a/frontend/src/main.js b/frontend/src/main.js index 7aa5ae5a..04f3dfe2 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -18,8 +18,8 @@ Vue.config.productionTip = false; // Handle Token Refresh on 401 function createAxiosResponseInterceptor() { const interceptor = axios.interceptors.response.use( - (response) => response, - (error) => { + response => response, + error => { if (error.response.status !== 401) { return Promise.reject(error); } @@ -29,22 +29,22 @@ function createAxiosResponseInterceptor() { return store .dispatch("auth/AUTH_REFRESH") .then(() => { - error.response.config.xsrfCookieName = "csrf_access_token" - error.response.config.xsrfHeaderName = "X-CSRF-TOKEN" - console.log(error.response.config) + error.response.config.xsrfCookieName = "csrf_access_token"; + error.response.config.xsrfHeaderName = "X-CSRF-TOKEN"; + console.log(error.response.config); return axios(error.response.config); }) - .catch((error) => { + .catch(error => { if (error.response.status != 401) { - return Promise.reject(error) - }else{ - store.dispatch("auth/AUTH_LOGOUT"); - this.router.push("/"); - return Promise.reject(error); + return Promise.reject(error); + } else { + store.dispatch("auth/AUTH_LOGOUT"); + this.router.push("/"); + return Promise.reject(error); } }) .finally(() => { - createAxiosResponseInterceptor() + createAxiosResponseInterceptor(); }); } ); diff --git a/frontend/src/plugins/vuetify.js b/frontend/src/plugins/vuetify.js index 9bfa4dfc..3f805741 100644 --- a/frontend/src/plugins/vuetify.js +++ b/frontend/src/plugins/vuetify.js @@ -13,21 +13,21 @@ function theme() { secondary: "#424242", background: "#000000", tabs: "#1E1E1E", - foreground: "#1E1E1E", + foreground: "#1E1E1E" }, light: { primary: "#41b883", secondary: "#c4c4c4", background: "#FFFFFF", tabs: "#FFFFFF", - foreground: "#FFFFFF", - }, + foreground: "#FFFFFF" + } }, dark: true, options: { - customProperties: true, - }, - }, + customProperties: true + } + } }, DigitalOcean: { theme: { @@ -38,20 +38,20 @@ function theme() { secondary: "#F3F5F9", background: "#FFFFFF", tabs: "#FFFFFF", - foreground: "#FFFFFF", + foreground: "#FFFFFF" }, dark: { primary: "#008bcf", secondary: "#424242", background: "#000000", tabs: "#1E1E1E", - foreground: "#1E1E1E", - }, + foreground: "#1E1E1E" + } }, options: { - customProperties: true, - }, - }, + customProperties: true + } + } }, OMV: { theme: { @@ -62,21 +62,21 @@ function theme() { secondary: "#5DACDF", background: "#FFFFFF", tabs: "#5DACDF", - foreground: "#ECEFF1", + foreground: "#ECEFF1" }, dark: { primary: "#3A6D9C", secondary: "#2B5174", background: "#132433", tabs: "#333B53", - foreground: "#333B53", - }, + foreground: "#333B53" + } }, options: { - customProperties: true, - }, - }, - }, + customProperties: true + } + } + } }; return presetThemes[process.env.VUE_APP_THEME || "Default"]; } diff --git a/frontend/src/store/modules/auth.js b/frontend/src/store/modules/auth.js index c58c3383..715d7c72 100644 --- a/frontend/src/store/modules/auth.js +++ b/frontend/src/store/modules/auth.js @@ -5,20 +5,20 @@ import { AUTH_LOGOUT, AUTH_REFRESH, AUTH_CLEAR, - AUTH_CHANGE_PASS, + AUTH_CHANGE_PASS } from "../actions/auth"; import axios from "axios"; import router from "@/router/index"; const state = { status: "", - username: localStorage.getItem("username") || "", + username: localStorage.getItem("username") || "" }; const getters = { - isAuthenticated: (state) => !!state.username, - authStatus: (state) => state.status, - getUsername: (state) => state.username, + isAuthenticated: state => !!state.username, + authStatus: state => state.status, + getUsername: state => state.username }; const actions = { @@ -28,7 +28,7 @@ const actions = { const url = "/api/auth/login"; axios .post(url, credentials, { withCredentials: true }) - .then((resp) => { + .then(resp => { localStorage.setItem("username", resp.data.username); axios.defaults.withCredentials = true; axios.defaults.xsrfCookieName = "csrf_access_token"; @@ -36,9 +36,9 @@ const actions = { commit(AUTH_SUCCESS, resp); resolve(resp); }) - .catch((err) => { + .catch(err => { commit(AUTH_ERROR, err); - commit("snackbar/setErr", err, {root: true}) + commit("snackbar/setErr", err, { root: true }); localStorage.removeItem("username"); reject(err); }); @@ -46,37 +46,41 @@ const actions = { }, [AUTH_LOGOUT]: ({ commit }) => { - return new Promise((resolve) => { + return new Promise(resolve => { commit(AUTH_REQUEST); const url = "/api/auth/logout"; axios - .get( - url, - {}, - { withCredentials: true } - ) - .then((resp) => { + .get(url, {}, { withCredentials: true }) + .then(resp => { commit(AUTH_CLEAR, resp); localStorage.removeItem("username"); router.push({ path: "/" }); resolve(resp); }) - .catch((error) => { + .catch(error => { console.log(error); commit(AUTH_CLEAR); }); }); }, [AUTH_REFRESH]: ({ commit }) => { - return new Promise((resolve) => { + return new Promise(resolve => { commit(AUTH_REQUEST); const url = "/api/auth/refresh"; axios - .post(url, {}, { xsrfCookieName: "csrf_refresh_token", xsrfHeaderName: "X-CSRF-TOKEN", withCredentials: true }) - .then((resp) => { + .post( + url, + {}, + { + xsrfCookieName: "csrf_refresh_token", + xsrfHeaderName: "X-CSRF-TOKEN", + withCredentials: true + } + ) + .then(resp => { resolve(resp); }) - .catch((error) => { + .catch(error => { console.log(error); commit(AUTH_CLEAR); }); @@ -88,7 +92,7 @@ const actions = { const url = "/api/auth/me"; axios .post(url, credentials) - .then((resp) => { + .then(resp => { localStorage.setItem("username", resp.data.username); commit(AUTH_SUCCESS, resp); resolve(resp); @@ -96,29 +100,29 @@ const actions = { .finally(() => { router.push({ path: `/user/info` }); }) - .catch((err) => { + .catch(err => { reject(err); }); }); - }, + } }; const mutations = { - [AUTH_REQUEST]: (state) => { + [AUTH_REQUEST]: state => { state.status = "loading"; }, [AUTH_SUCCESS]: (state, resp) => { state.status = "success"; state.username = resp.data.username; }, - [AUTH_ERROR]: (state) => { + [AUTH_ERROR]: state => { state.status = "error"; }, - [AUTH_CLEAR]: (state) => { + [AUTH_CLEAR]: state => { state.accessToken = ""; state.refreshToken = ""; state.username = ""; - }, + } }; export default { @@ -126,5 +130,5 @@ export default { state, mutations, getters, - actions, -}; \ No newline at end of file + actions +}; diff --git a/frontend/src/store/modules/projects.js b/frontend/src/store/modules/projects.js index 3bab87b5..6761dedc 100644 --- a/frontend/src/store/modules/projects.js +++ b/frontend/src/store/modules/projects.js @@ -112,7 +112,7 @@ const actions = { .then(response => { const projects = response.data; commit("setProjects", projects); - dispatch("apps/readApps", null, { root: true }) + dispatch("apps/readApps", null, { root: true }); }) .catch(err => { commit("snackbar/setErr", err, { root: true }); @@ -129,7 +129,7 @@ const actions = { .then(response => { const projects = response.data; commit("setProjects", projects); - dispatch("apps/readApps", null, { root: true }) + dispatch("apps/readApps", null, { root: true }); }) .catch(err => { commit("snackbar/setErr", err, { root: true }); @@ -137,7 +137,7 @@ const actions = { .finally(() => { commit("setLoading", false); }); - }, + } }; const getters = { From b5fca483cbdb779323ddc5f399490a880a7d16f0 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Tue, 10 Nov 2020 08:43:37 -0800 Subject: [PATCH 06/75] linted python --- backend/api/actions/apps.py | 8 +++--- backend/api/actions/compose.py | 4 +-- backend/api/auth/auth.py | 1 + backend/api/db/crud/__init__.py | 2 +- backend/api/db/crud/users.py | 14 +++++++--- backend/api/db/models/users.py | 3 ++- backend/api/db/schemas/__init__.py | 2 +- backend/api/db/schemas/users.py | 5 +++- backend/api/main.py | 18 +++++-------- backend/api/routers/app_settings.py | 34 ++++++++++++------------ backend/api/routers/apps.py | 12 ++++----- backend/api/routers/compose.py | 1 + backend/api/routers/resources.py | 27 ++++++++++--------- backend/api/routers/templates.py | 39 +++++++++++++--------------- backend/api/routers/users.py | 40 ++++++++++++++++++++--------- backend/api/utils/auth.py | 3 ++- 16 files changed, 116 insertions(+), 97 deletions(-) diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index 4420e69a..84b6fa77 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -297,18 +297,16 @@ def check_self_update(): yacht = dclient.containers.get(yacht_id) except Exception as exc: print(exc) - if hasattr(exc, 'response') and exc.response.status_code == 404: + if hasattr(exc, "response") and exc.response.status_code == 404: raise HTTPException( status_code=exc.response.status_code, detail="Unable to get Yacht container ID", ) - elif hasattr(exc, 'response'): + elif hasattr(exc, "response"): raise HTTPException( status_code=exc.response.status_code, detail=exc.explanation ) else: - raise HTTPException( - status_code=400, detail=exc.args - ) + raise HTTPException(status_code=400, detail=exc.args) return _update_check(yacht.image.tags[0]) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 1db42d03..ceed313e 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -55,9 +55,7 @@ def compose_action(name, action): def compose_app_action( - name, - action, - app, + name, action, app, ): files = find_yml_files(settings.COMPOSE_DIR) diff --git a/backend/api/auth/auth.py b/backend/api/auth/auth.py index 6f5f99c1..dc790979 100644 --- a/backend/api/auth/auth.py +++ b/backend/api/auth/auth.py @@ -9,6 +9,7 @@ settings = Settings() + def verify_and_update_password( plain_password: str, hashed_password: str ) -> Tuple[bool, str]: diff --git a/backend/api/db/crud/__init__.py b/backend/api/db/crud/__init__.py index 0ceedca8..1ad06096 100644 --- a/backend/api/db/crud/__init__.py +++ b/backend/api/db/crud/__init__.py @@ -1,3 +1,3 @@ from .templates import * from .settings import * -from .users import * \ No newline at end of file +from .users import * diff --git a/backend/api/db/crud/users.py b/backend/api/db/crud/users.py index 4d808921..5f97fdaa 100644 --- a/backend/api/db/crud/users.py +++ b/backend/api/db/crud/users.py @@ -2,25 +2,31 @@ from passlib.context import CryptContext from . import models, schemas from fastapi.exceptions import HTTPException + pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + def get_user(db: Session, user_id: int): return db.query(models.User).filter(models.User.id == user_id).first() + def get_user_by_name(db: Session, username: str): return db.query(models.User).filter(models.User.username == username).first() + def get_users(db: Session, skip: int = 0, limit: int = 100): return db.query(models.User).offset(skip).limit(limit).all() + def create_user(db: Session, user: schemas.UserCreate): _hashed_password = get_password_hash(user.password) db_user = models.User(username=user.username, hashed_password=_hashed_password) db.add(db_user) db.commit() db.refresh(db_user) - return (db_user) - + return db_user + + def update_user(db: Session, user: schemas.UserCreate, current_user): _hashed_password = get_password_hash(user.password) _user = get_user_by_name(db=db, username=current_user) @@ -36,8 +42,10 @@ def update_user(db: Session, user: schemas.UserCreate, current_user): raise HTTPException(status_code=400, detail=exc) return _user + def verify_password(plain_password, hashed_password): return pwd_context.verify(plain_password, hashed_password) + def get_password_hash(password): - return pwd_context.hash(password) \ No newline at end of file + return pwd_context.hash(password) diff --git a/backend/api/db/models/users.py b/backend/api/db/models/users.py index 23b22ac3..b9588a98 100644 --- a/backend/api/db/models/users.py +++ b/backend/api/db/models/users.py @@ -1,10 +1,11 @@ from sqlalchemy import Boolean, Column, ForeignKey, Integer, String from ..database import Base + class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String, unique=True, index=True) hashed_password = Column(String) - is_active = Column(Boolean, default=True) \ No newline at end of file + is_active = Column(Boolean, default=True) diff --git a/backend/api/db/schemas/__init__.py b/backend/api/db/schemas/__init__.py index e47494d5..1fba38c4 100644 --- a/backend/api/db/schemas/__init__.py +++ b/backend/api/db/schemas/__init__.py @@ -1,3 +1,3 @@ from .apps import * from .templates import * -from .users import * \ No newline at end of file +from .users import * diff --git a/backend/api/db/schemas/users.py b/backend/api/db/schemas/users.py index 3d9ff495..f94d5da6 100644 --- a/backend/api/db/schemas/users.py +++ b/backend/api/db/schemas/users.py @@ -1,14 +1,17 @@ from pydantic import BaseModel + class UserBase(BaseModel): username: str + class UserCreate(UserBase): password: str + class User(UserBase): id: int is_active: 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 1649f6d4..6a65aac5 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -29,10 +29,10 @@ class jwtSettings(BaseModel): authjwt_secret_key: str = settings.SECRET_KEY - authjwt_token_location: set = {'headers', 'cookies'} + 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 = "lax" @AuthJWT.load_config @@ -42,22 +42,16 @@ def get_config(): @app.exception_handler(AuthJWTException) def authjwt_exception_handler(request: Request, exc: AuthJWTException): - return JSONResponse( - status_code=exc.status_code, - content={"detail": exc.message} - ) + return JSONResponse(status_code=exc.status_code, content={"detail": exc.message}) + app.include_router(users.router, prefix="/auth", tags=["users"]) app.include_router(apps.router, prefix="/apps", tags=["apps"]) app.include_router( - resources.router, - prefix="/resources", - tags=["resources"], + resources.router, prefix="/resources", tags=["resources"], ) app.include_router( - templates.router, - prefix="/templates", - tags=["templates"], + templates.router, prefix="/templates", tags=["templates"], ) app.include_router(compose.router, prefix="/compose", tags=["compose"]) app.include_router(app_settings.router, prefix="/settings", tags=["settings"]) diff --git a/backend/api/routers/app_settings.py b/backend/api/routers/app_settings.py index bd2cffe1..a2a56cc5 100644 --- a/backend/api/routers/app_settings.py +++ b/backend/api/routers/app_settings.py @@ -23,56 +23,58 @@ @router.get( - "/variables", - response_model=List[schemas.TemplateVariables], - + "/variables", response_model=List[schemas.TemplateVariables], ) -def read_template_variables(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): +def read_template_variables( + db: Session = Depends(get_db), Authorize: AuthJWT = Depends() +): Authorize.jwt_required() return crud.read_template_variables(db=db) @router.post( - "/variables", - response_model=List[schemas.TemplateVariables], - + "/variables", response_model=List[schemas.TemplateVariables], ) def set_template_variables( - new_variables: List[schemas.TemplateVariables], db: Session = Depends(get_db), Authorize: AuthJWT = Depends() + new_variables: List[schemas.TemplateVariables], + db: Session = Depends(get_db), + Authorize: AuthJWT = Depends(), ): Authorize.jwt_required() return crud.set_template_variables(new_variables=new_variables, db=db) @router.get( - "/export", - response_model=schemas.Import_Export, - + "/export", response_model=schemas.Import_Export, ) def export_settings(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): Authorize.jwt_required() return crud.export_settings(db=db) -@router.post("/export", ) -def import_settings(db: Session = Depends(get_db), upload: UploadFile = File(...), Authorize: AuthJWT = Depends()): +@router.post("/export",) +def import_settings( + db: Session = Depends(get_db), + upload: UploadFile = File(...), + Authorize: AuthJWT = Depends(), +): Authorize.jwt_required() return crud.import_settings(db=db, upload=upload) -@router.get("/prune/{resource}", ) +@router.get("/prune/{resource}",) def prune_resources(resource: str, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.prune_resources(resource) -@router.get("/update", ) +@router.get("/update",) def update_self(Authorize: AuthJWT = Depends()): Authorize.jwt_required() return update_self() -@router.get("/check/update", ) +@router.get("/check/update",) def _check_self_update(Authorize: AuthJWT = Depends()): Authorize.jwt_required() return check_self_update() diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index 7b565581..0069606b 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -34,6 +34,7 @@ def index(Authorize: AuthJWT = Depends()): Authorize.jwt_required() return actions.get_apps() + @router.get("/{app_name}/updates") def check_app_updates(app_name, Authorize: AuthJWT = Depends()): Authorize.jwt_required() @@ -80,7 +81,7 @@ def deploy_app(template: schemas.DeployForm, Authorize: AuthJWT = Depends()): async def logs(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depends()): try: csrf = websocket._cookies["csrf_access_token"] - Authorize.jwt_required("websocket",websocket=websocket,csrf_token=csrf) + Authorize.jwt_required("websocket", websocket=websocket, csrf_token=csrf) except AuthJWTException: await websocket.close(code=status.WS_1008_POLICY_VIOLATION) await websocket.accept() @@ -96,15 +97,13 @@ async def logs(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depends return e else: await websocket.close(code=status.WS_1011_INTERNAL_ERROR) - - @router.websocket("/{app_name}/stats") async def stats(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depends()): try: csrf = websocket._cookies["csrf_access_token"] - Authorize.jwt_required("websocket",websocket=websocket,csrf_token=csrf) + Authorize.jwt_required("websocket", websocket=websocket, csrf_token=csrf) except AuthJWTException: await websocket.close(code=status.WS_1008_POLICY_VIOLATION) await websocket.accept() @@ -151,11 +150,12 @@ async def stats(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depend else: await websocket.close(code=status.WS_1011_INTERNAL_ERROR) + @router.websocket("/stats") async def dashboard(websocket: WebSocket, Authorize: AuthJWT = Depends()): try: csrf = websocket._cookies["csrf_access_token"] - Authorize.jwt_required("websocket",websocket=websocket,csrf_token=csrf) + Authorize.jwt_required("websocket", websocket=websocket, csrf_token=csrf) except AuthJWTException: await websocket.close(code=status.WS_1008_POLICY_VIOLATION) await websocket.accept() @@ -206,4 +206,4 @@ async def process_container(name, stats, websocket): try: await websocket.send_text(json.dumps(full_stats)) except Exception as e: - pass \ No newline at end of file + pass diff --git a/backend/api/routers/compose.py b/backend/api/routers/compose.py index e6bc340b..63e8f91f 100644 --- a/backend/api/routers/compose.py +++ b/backend/api/routers/compose.py @@ -7,6 +7,7 @@ get_compose, ) from fastapi_jwt_auth import AuthJWT + router = APIRouter() diff --git a/backend/api/routers/resources.py b/backend/api/routers/resources.py index 4b0bdf95..6972ceff 100644 --- a/backend/api/routers/resources.py +++ b/backend/api/routers/resources.py @@ -7,82 +7,81 @@ router = APIRouter() ### Images ### -@router.get("/images/", ) +@router.get("/images/",) def get_images(Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.get_images() -@router.post("/images/", ) +@router.post("/images/",) def write_image(image: ImageWrite, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.write_image(image.image) -@router.get("/images/{image_id}", ) +@router.get("/images/{image_id}",) def get_image(image_id, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.get_image(image_id) -@router.get("/images/{image_id}/pull", ) +@router.get("/images/{image_id}/pull",) def pull_image(image_id, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.update_image(image_id) -@router.delete("/images/{image_id}", ) +@router.delete("/images/{image_id}",) def delete_image(image_id, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.delete_image(image_id) ### Volumes ### -@router.get("/volumes/", ) +@router.get("/volumes/",) def get_volumes(Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.get_volumes() -@router.post("/volumes/", ) +@router.post("/volumes/",) def write_volume(name: VolumeWrite, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.write_volume(name.name) -@router.get("/volumes/{volume_name}", ) +@router.get("/volumes/{volume_name}",) def get_volume(volume_name, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.get_volume(volume_name) -@router.delete("/volumes/{volume_name}", ) +@router.delete("/volumes/{volume_name}",) def delete_volume(volume_name, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.delete_volume(volume_name) ### Networks ### -@router.get("/networks/", ) +@router.get("/networks/",) def get_networks(Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.get_networks() -@router.post("/networks/", ) +@router.post("/networks/",) def write_network(form: NetworkWrite, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.write_network(form) -@router.get("/networks/{network_name}", ) +@router.get("/networks/{network_name}",) def get_network(network_name, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.get_network(network_name) -@router.delete("/networks/{network_name}", ) +@router.delete("/networks/{network_name}",) def delete_network(network_name, Authorize: AuthJWT = Depends()): Authorize.jwt_required() return resources.delete_network(network_name) - diff --git a/backend/api/routers/templates.py b/backend/api/routers/templates.py index 26eb2b68..eac314b4 100644 --- a/backend/api/routers/templates.py +++ b/backend/api/routers/templates.py @@ -10,6 +10,7 @@ from ..db.database import SessionLocal, engine from ..utils import get_db from fastapi_jwt_auth import AuthJWT + containers.Base.metadata.create_all(bind=engine) @@ -17,9 +18,7 @@ @router.get( - "/", - response_model=List[schemas.TemplateRead], - + "/", response_model=List[schemas.TemplateRead], ) def index(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): Authorize.jwt_required() @@ -28,9 +27,7 @@ def index(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): @router.get( - "/{id}", - response_model=schemas.TemplateItems, - + "/{id}", response_model=schemas.TemplateItems, ) def show(id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): Authorize.jwt_required() @@ -39,19 +36,19 @@ def show(id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()) @router.delete( - "/{id}", - response_model=schemas.TemplateRead, - + "/{id}", response_model=schemas.TemplateRead, ) def delete(id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): Authorize.jwt_required() return crud.delete_template(db=db, template_id=id) -@router.post( - "/", response_model=schemas.TemplateRead -) -def add_template(template: schemas.TemplateBase, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): +@router.post("/", response_model=schemas.TemplateRead) +def add_template( + template: schemas.TemplateBase, + db: Session = Depends(get_db), + Authorize: AuthJWT = Depends(), +): Authorize.jwt_required() existing_template = crud.get_template(db=db, url=template.url) if existing_template: @@ -60,20 +57,20 @@ def add_template(template: schemas.TemplateBase, db: Session = Depends(get_db), @router.get( - "/{id}/refresh", - response_model=schemas.TemplateRead, - + "/{id}/refresh", response_model=schemas.TemplateRead, ) -def refresh_template(id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): +def refresh_template( + id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends() +): Authorize.jwt_required() return crud.refresh_template(db=db, template_id=id) @router.get( - "/app/{id}", - response_model=schemas.TemplateItem, - + "/app/{id}", response_model=schemas.TemplateItem, ) -def read_app_template(id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): +def read_app_template( + id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends() +): Authorize.jwt_required() return crud.read_app_template(db=db, app_id=id) diff --git a/backend/api/routers/users.py b/backend/api/routers/users.py index 53b851a7..76b298d4 100644 --- a/backend/api/routers/users.py +++ b/backend/api/routers/users.py @@ -6,7 +6,8 @@ router = APIRouter() -@router.post('/create', response_model=schemas.User) + +@router.post("/create", response_model=schemas.User) def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): db_user = crud.get_user_by_name(db, username=user.username) if db_user: @@ -14,9 +15,13 @@ def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): return crud.create_user(db=db, user=user) -@router.post('/login') -def login(user: schemas.UserCreate, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): - _user = db.query(models.User).filter(models.User.username==user.username).first() +@router.post("/login") +def login( + user: schemas.UserCreate, + db: Session = Depends(get_db), + Authorize: AuthJWT = Depends(), +): + _user = db.query(models.User).filter(models.User.username == user.username).first() if _user is not None and crud.verify_password(user.password, _user.hashed_password): # Create Tokens access_token = Authorize.create_access_token(subject=user.username) @@ -25,11 +30,16 @@ def login(user: schemas.UserCreate, db: Session = Depends(get_db), Authorize: Au # Assign cookies Authorize.set_access_cookies(access_token) Authorize.set_refresh_cookies(refresh_token) - return {'login': 'successful', 'username': _user.username, 'access_token': access_token} + return { + "login": "successful", + "username": _user.username, + "access_token": access_token, + } else: raise HTTPException(status_code=400, detail="Invalid Username or Password.") -@router.post('/refresh') + +@router.post("/refresh") def refresh(Authorize: AuthJWT = Depends()): Authorize.jwt_refresh_token_required() @@ -37,22 +47,28 @@ def refresh(Authorize: AuthJWT = Depends()): new_access_token = Authorize.create_access_token(subject=current_user) Authorize.set_access_cookies(new_access_token) - return {'refresh': 'successful', 'access_token': new_access_token} + return {"refresh": "successful", "access_token": new_access_token} + -@router.get('/me', response_model=schemas.User) +@router.get("/me", response_model=schemas.User) def get_user(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): Authorize.jwt_required() current_user = Authorize.get_jwt_subject() return crud.get_user_by_name(db=db, username=current_user) -@router.post('/me', response_model=schemas.User) -def update_user(user: schemas.UserCreate, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): + +@router.post("/me", response_model=schemas.User) +def update_user( + user: schemas.UserCreate, + db: Session = Depends(get_db), + Authorize: AuthJWT = Depends(), +): Authorize.jwt_required() current_user = Authorize.get_jwt_subject() return crud.update_user(db=db, user=user, current_user=current_user) - -@router.get('/logout') + +@router.get("/logout") def logout(Authorize: AuthJWT = Depends()): Authorize.jwt_required() Authorize.unset_jwt_cookies() diff --git a/backend/api/utils/auth.py b/backend/api/utils/auth.py index cd3b11f6..3624dff4 100644 --- a/backend/api/utils/auth.py +++ b/backend/api/utils/auth.py @@ -12,6 +12,7 @@ def get_db(): finally: db.close() + # async def websocket_auth(websocket: WebSocket, Authorize: AuthJWT = Depends()): # try: # cookie = websocket._cookies["access_token_cookie"] @@ -25,4 +26,4 @@ def get_db(): # if settings.DISABLE_AUTH == "True": # return True # else: -# return None \ No newline at end of file +# return None From fa874f634c359f256a22786fa82417870db7d41a Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Tue, 10 Nov 2020 09:29:14 -0800 Subject: [PATCH 07/75] updated db migration stuff; --- backend/api/actions/resources.py | 2 +- backend/api/db/models/users.py | 9 +++++---- .../src/components/applications/ApplicationsForm.vue | 9 +++++++-- frontend/src/components/auth/LoginForm.vue | 6 +----- frontend/src/components/nav/Appbar.vue | 2 +- frontend/src/store/modules/networks.js | 2 +- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/backend/api/actions/resources.py b/backend/api/actions/resources.py index b04c44d5..fbe14ba4 100644 --- a/backend/api/actions/resources.py +++ b/backend/api/actions/resources.py @@ -178,7 +178,7 @@ def get_networks(): if attrs.get("inUse") == None: attrs.update({"inUse": False}) if attrs.get("Labels", {}).get("com.docker.compose.project"): - attrs.update({'Project': attrs['Labels']['com.docker.compose.project']}) + attrs.update({"Project": attrs["Labels"]["com.docker.compose.project"]}) network_list.append(attrs) return network_list diff --git a/backend/api/db/models/users.py b/backend/api/db/models/users.py index b9588a98..44246f7e 100644 --- a/backend/api/db/models/users.py +++ b/backend/api/db/models/users.py @@ -3,9 +3,10 @@ class User(Base): - __tablename__ = "users" + __tablename__ = "user" id = Column(Integer, primary_key=True, index=True) - username = Column(String, unique=True, index=True) - hashed_password = Column(String) - is_active = Column(Boolean, default=True) + username = Column("email", String, 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/frontend/src/components/applications/ApplicationsForm.vue b/frontend/src/components/applications/ApplicationsForm.vue index cdf4712e..8689c77e 100644 --- a/frontend/src/components/applications/ApplicationsForm.vue +++ b/frontend/src/components/applications/ApplicationsForm.vue @@ -130,7 +130,10 @@ label="Network" clearable v-model="form.network" - :disabled="form.network_mode !== undefined && form.network_mode !== ''" + :disabled=" + form.network_mode !== undefined && + form.network_mode !== '' + " /> @@ -139,7 +142,9 @@ label="Network Mode" clearable v-model="form.network_mode" - :disabled="form.network !== undefined && form.network !== ''" + :disabled=" + form.network !== undefined && form.network !== '' + " /> diff --git a/frontend/src/components/auth/LoginForm.vue b/frontend/src/components/auth/LoginForm.vue index c1927277..d0aef513 100644 --- a/frontend/src/components/auth/LoginForm.vue +++ b/frontend/src/components/auth/LoginForm.vue @@ -1,11 +1,7 @@ + + + mdi-file-document-edit-outline + + Edit + + @@ -506,11 +515,11 @@ export default { ProjectAction: "projects/ProjectAction", readApps: "apps/readApps" }), + editProject(projectname) { + this.$router.push({ path: `/projects/${projectname}/edit`}) + }, getStatus(name) { for (var app in this.apps) { - console.log( - this.project.name + "_" + name + "_1" + " vs " + this.apps[app].name - ); if ( this.apps[app].name == name || this.apps[app].name == this.project.name + "_" + name + "_1" diff --git a/frontend/src/components/compose/ProjectEditor.vue b/frontend/src/components/compose/ProjectEditor.vue index 49fb9d05..8f3098d0 100644 --- a/frontend/src/components/compose/ProjectEditor.vue +++ b/frontend/src/components/compose/ProjectEditor.vue @@ -13,12 +13,16 @@
- + New Compose Template + + Edit {{this.form.name}} Project + diff --git a/frontend/src/components/compose/ProjectList.vue b/frontend/src/components/compose/ProjectList.vue index db6a4649..2a36ce2f 100644 --- a/frontend/src/components/compose/ProjectList.vue +++ b/frontend/src/components/compose/ProjectList.vue @@ -162,6 +162,12 @@ View + + + mdi-file-document-edit-outline + + Edit + { - const project = response.data; - commit("setProject", project); - }) - .catch(err => { - commit("snackbar/setErr", err, { root: true }); - }) - .finally(() => { - commit("setLoading", false); - }); + return new Promise((resolve, reject) => { + axios + .get(url) + .then(response => { + const app = response.data; + commit("setLoading", false); + resolve(app); + }) + .finally(() => { + commit("setLoading", false); + }) + .catch(error => { + commit("snackbar/setErr", error, { root: true }); + reject(error); + }); + }); }, writeProject({ commit }, payload) { commit("setLoading", true); From 345ac0e4996d5a363c6d21722ea221ffd43222de Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Wed, 6 Jan 2021 15:55:43 -0800 Subject: [PATCH 12/75] linted --- backend/api/actions/compose.py | 8 ++++- backend/api/auth/auth.py | 3 +- backend/api/db/schemas/__init__.py | 1 + backend/api/db/schemas/compose.py | 14 ++++++++ backend/api/main.py | 21 +++++++++--- backend/api/routers/app_settings.py | 25 ++++++++++---- backend/api/routers/apps.py | 2 +- backend/api/routers/compose.py | 12 ++++++- backend/api/routers/resources.py | 52 +++++++++++++++++++++-------- backend/api/routers/templates.py | 15 ++++++--- backend/api/routers/users.py | 11 ++++-- 11 files changed, 128 insertions(+), 36 deletions(-) create mode 100644 backend/api/db/schemas/compose.py diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index ceed313e..194357be 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -55,7 +55,9 @@ def compose_action(name, action): def compose_app_action( - name, action, app, + name, + action, + app, ): files = find_yml_files(settings.COMPOSE_DIR) @@ -174,6 +176,8 @@ def get_compose(name): networks.append(network) for service in loaded_compose.get("services"): services[service] = loaded_compose["services"][service] + _content = open(file) + content = _content.read() compose_object = { "name": project, "path": file, @@ -181,7 +185,9 @@ def get_compose(name): "services": services, "volumes": volumes, "networks": networks, + "content": content, } + print(compose_object["content"]) return compose_object else: raise HTTPException(404, "Project " + name + " not found") diff --git a/backend/api/auth/auth.py b/backend/api/auth/auth.py index b4d2f8ea..53daa0af 100644 --- a/backend/api/auth/auth.py +++ b/backend/api/auth/auth.py @@ -26,8 +26,9 @@ def get_password_hash(password: str) -> str: def generate_password() -> str: return pwd.genword() + def auth_check(Authorize): if settings.DISABLE_AUTH == "True": return else: - return Authorize.jwt_required() \ No newline at end of file + return Authorize.jwt_required() diff --git a/backend/api/db/schemas/__init__.py b/backend/api/db/schemas/__init__.py index 1fba38c4..01c0561d 100644 --- a/backend/api/db/schemas/__init__.py +++ b/backend/api/db/schemas/__init__.py @@ -1,3 +1,4 @@ from .apps import * from .templates import * from .users import * +from .compose import * diff --git a/backend/api/db/schemas/compose.py b/backend/api/db/schemas/compose.py new file mode 100644 index 00000000..d1b0fa32 --- /dev/null +++ b/backend/api/db/schemas/compose.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel +from typing import Any, Optional + + +class Compose(BaseModel): + name: str + + +class ComposeWrite(Compose): + content: Optional[Any] + + +class ComposeRead(ComposeWrite): + path: str diff --git a/backend/api/main.py b/backend/api/main.py index e39e3500..7bb2c780 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -43,7 +43,10 @@ def get_config(): @app.exception_handler(AuthJWTException) def authjwt_exception_handler(request: Request, exc: AuthJWTException): status_code = exc.status_code - if exc.message == "Signature verification failed" or exc.message == "Signature has expired": + if ( + exc.message == "Signature verification failed" + or exc.message == "Signature has expired" + ): status_code = 401 return JSONResponse(status_code=status_code, content={"detail": exc.message}) @@ -51,10 +54,14 @@ def authjwt_exception_handler(request: Request, exc: AuthJWTException): app.include_router(users.router, prefix="/auth", tags=["users"]) app.include_router(apps.router, prefix="/apps", tags=["apps"]) app.include_router( - resources.router, prefix="/resources", tags=["resources"], + resources.router, + prefix="/resources", + tags=["resources"], ) app.include_router( - templates.router, prefix="/templates", tags=["templates"], + templates.router, + prefix="/templates", + tags=["templates"], ) app.include_router(compose.router, prefix="/compose", tags=["compose"]) app.include_router(app_settings.router, prefix="/settings", tags=["settings"]) @@ -67,7 +74,13 @@ async def startup(db: Session = Depends(get_db)): delete_alembic = "DROP TABLE IF EXISTS alembic_version;" # await database.execute(delete_alembic) users_exist = get_users(db=SessionLocal()) - print("DISABLE_AUTH = "+settings.DISABLE_AUTH + ' ('+str(type(settings.DISABLE_AUTH)) + ')') + print( + "DISABLE_AUTH = " + + str(settings.DISABLE_AUTH) + + " (" + + str(type(settings.DISABLE_AUTH)) + + ")" + ) if users_exist: print("Users Exist") else: diff --git a/backend/api/routers/app_settings.py b/backend/api/routers/app_settings.py index a06b6192..e673d864 100644 --- a/backend/api/routers/app_settings.py +++ b/backend/api/routers/app_settings.py @@ -25,7 +25,8 @@ @router.get( - "/variables", response_model=List[schemas.TemplateVariables], + "/variables", + response_model=List[schemas.TemplateVariables], ) def read_template_variables( db: Session = Depends(get_db), Authorize: AuthJWT = Depends() @@ -35,7 +36,8 @@ def read_template_variables( @router.post( - "/variables", response_model=List[schemas.TemplateVariables], + "/variables", + response_model=List[schemas.TemplateVariables], ) def set_template_variables( new_variables: List[schemas.TemplateVariables], @@ -47,14 +49,17 @@ def set_template_variables( @router.get( - "/export", response_model=schemas.Import_Export, + "/export", + response_model=schemas.Import_Export, ) def export_settings(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): auth_check(Authorize) return crud.export_settings(db=db) -@router.post("/export",) +@router.post( + "/export", +) def import_settings( db: Session = Depends(get_db), upload: UploadFile = File(...), @@ -64,19 +69,25 @@ def import_settings( return crud.import_settings(db=db, upload=upload) -@router.get("/prune/{resource}",) +@router.get( + "/prune/{resource}", +) def prune_resources(resource: str, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.prune_resources(resource) -@router.get("/update",) +@router.get( + "/update", +) def update_self(Authorize: AuthJWT = Depends()): auth_check(Authorize) return update_self() -@router.get("/check/update",) +@router.get( + "/check/update", +) def _check_self_update(Authorize: AuthJWT = Depends()): auth_check(Authorize) return check_self_update() diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index f78e2803..0b86fcac 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -159,7 +159,7 @@ async def stats(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depend @router.websocket("/stats") async def dashboard(websocket: WebSocket, Authorize: AuthJWT = Depends()): - if settings.DISABLE_AUTH == 'True': + if settings.DISABLE_AUTH == "True": pass else: try: diff --git a/backend/api/routers/compose.py b/backend/api/routers/compose.py index d5009b8f..6b92c9f9 100644 --- a/backend/api/routers/compose.py +++ b/backend/api/routers/compose.py @@ -1,13 +1,15 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Form from typing import List from ..actions.compose import ( get_compose_projects, compose_action, compose_app_action, get_compose, + write_compose, ) from fastapi_jwt_auth import AuthJWT from ..auth import auth_check +from ..db.schemas.compose import * router = APIRouter() @@ -34,3 +36,11 @@ def get_compose_action(project_name, action, Authorize: AuthJWT = Depends()): def get_compose_app_action(project_name, action, app, Authorize: AuthJWT = Depends()): auth_check(Authorize) return compose_app_action(project_name, action, app) + + +@router.post("/{project_name}/edit", response_model=ComposeRead) +def write_compose_project( + project_name, compose: 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 c4b9af74..f30c9d4a 100644 --- a/backend/api/routers/resources.py +++ b/backend/api/routers/resources.py @@ -9,81 +9,107 @@ router = APIRouter() ### Images ### -@router.get("/images/",) +@router.get( + "/images/", +) def get_images(Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.get_images() -@router.post("/images/",) +@router.post( + "/images/", +) def write_image(image: ImageWrite, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.write_image(image.image) -@router.get("/images/{image_id}",) +@router.get( + "/images/{image_id}", +) def get_image(image_id, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.get_image(image_id) -@router.get("/images/{image_id}/pull",) +@router.get( + "/images/{image_id}/pull", +) def pull_image(image_id, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.update_image(image_id) -@router.delete("/images/{image_id}",) +@router.delete( + "/images/{image_id}", +) def delete_image(image_id, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.delete_image(image_id) ### Volumes ### -@router.get("/volumes/",) +@router.get( + "/volumes/", +) def get_volumes(Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.get_volumes() -@router.post("/volumes/",) +@router.post( + "/volumes/", +) def write_volume(name: VolumeWrite, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.write_volume(name.name) -@router.get("/volumes/{volume_name}",) +@router.get( + "/volumes/{volume_name}", +) def get_volume(volume_name, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.get_volume(volume_name) -@router.delete("/volumes/{volume_name}",) +@router.delete( + "/volumes/{volume_name}", +) def delete_volume(volume_name, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.delete_volume(volume_name) ### Networks ### -@router.get("/networks/",) +@router.get( + "/networks/", +) def get_networks(Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.get_networks() -@router.post("/networks/",) +@router.post( + "/networks/", +) def write_network(form: NetworkWrite, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.write_network(form) -@router.get("/networks/{network_name}",) +@router.get( + "/networks/{network_name}", +) def get_network(network_name, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.get_network(network_name) -@router.delete("/networks/{network_name}",) +@router.delete( + "/networks/{network_name}", +) def delete_network(network_name, Authorize: AuthJWT = Depends()): auth_check(Authorize) return resources.delete_network(network_name) diff --git a/backend/api/routers/templates.py b/backend/api/routers/templates.py index e6965bda..13e69f9b 100644 --- a/backend/api/routers/templates.py +++ b/backend/api/routers/templates.py @@ -19,7 +19,8 @@ @router.get( - "/", response_model=List[schemas.TemplateRead], + "/", + response_model=List[schemas.TemplateRead], ) def index(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): auth_check(Authorize) @@ -28,7 +29,8 @@ def index(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): @router.get( - "/{id}", response_model=schemas.TemplateItems, + "/{id}", + response_model=schemas.TemplateItems, ) def show(id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): auth_check(Authorize) @@ -37,7 +39,8 @@ def show(id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()) @router.delete( - "/{id}", response_model=schemas.TemplateRead, + "/{id}", + response_model=schemas.TemplateRead, ) def delete(id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): auth_check(Authorize) @@ -58,7 +61,8 @@ def add_template( @router.get( - "/{id}/refresh", response_model=schemas.TemplateRead, + "/{id}/refresh", + response_model=schemas.TemplateRead, ) def refresh_template( id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends() @@ -68,7 +72,8 @@ def refresh_template( @router.get( - "/app/{id}", response_model=schemas.TemplateItem, + "/app/{id}", + response_model=schemas.TemplateItem, ) def read_app_template( id: int, db: Session = Depends(get_db), Authorize: AuthJWT = Depends() diff --git a/backend/api/routers/users.py b/backend/api/routers/users.py index 3441bec9..4ffb0835 100644 --- a/backend/api/routers/users.py +++ b/backend/api/routers/users.py @@ -9,8 +9,13 @@ router = APIRouter() settings = Settings() + @router.post("/create", response_model=schemas.User) -def create_user(user: schemas.UserCreate, Authorize: AuthJWT = Depends(), db: Session = Depends(get_db)): +def create_user( + user: schemas.UserCreate, + Authorize: AuthJWT = Depends(), + db: Session = Depends(get_db), +): auth_check(Authorize) db_user = crud.get_user_by_name(db, username=user.username) if db_user: @@ -56,11 +61,11 @@ def refresh(Authorize: AuthJWT = Depends()): @router.get("/me", response_model=schemas.User) def get_user(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()): auth_check(Authorize) - if settings.DISABLE_AUTH == 'True': + if settings.DISABLE_AUTH == "True": current_user = models.User current_user.authDisabled = True current_user.id = 0 - current_user.username = 'user' + current_user.username = "user" current_user.is_active = True current_user.is_superuser = True return current_user From ecfdba0acec2f4631241eb0340f7d3107f6c48c4 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Thu, 7 Jan 2021 09:21:34 -0800 Subject: [PATCH 13/75] added delete functionality inside details; visual changes --- backend/api/actions/compose.py | 37 +- backend/api/routers/compose.py | 6 + backend/requirements.txt | 2 +- frontend/src/components/auth/LoginForm.vue | 12 +- .../src/components/compose/ProjectDetails.vue | 334 ++++++++---------- .../src/components/compose/ProjectEditor.vue | 70 ++-- .../src/components/compose/ProjectList.vue | 330 +++++++++-------- frontend/src/router/index.js | 2 +- frontend/src/store/actions/auth.js | 2 +- frontend/src/store/modules/auth.js | 14 +- 10 files changed, 397 insertions(+), 412 deletions(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 194357be..af41635a 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -3,6 +3,7 @@ import os import yaml import pathlib +import shutil from ..settings import Settings from ..utils.compose import find_yml_files, get_readme_file, get_logo_file @@ -145,7 +146,7 @@ def get_compose_projects(): _project = { "name": project, "path": file, - "version": loaded_compose["version"], + "version": loaded_compose.get("version", "3.9"), "services": services, "volumes": volumes, "networks": networks, @@ -160,7 +161,7 @@ def get_compose(name): try: files = find_yml_files(settings.COMPOSE_DIR + name) except Exception as exc: - print(exc) + raise HTTPException(exc.status_code, exc.detail) for project, file in files.items(): if name == project: networks = [] @@ -181,7 +182,7 @@ def get_compose(name): compose_object = { "name": project, "path": file, - "version": loaded_compose["version"], + "version": loaded_compose.get("version", '3.9'), "services": services, "volumes": volumes, "networks": networks, @@ -194,10 +195,30 @@ def get_compose(name): def write_compose(compose): - print(compose) - pathlib.Path("config/compose/" + compose.name).mkdir(parents=True) - f = open("config/compose/" + compose.name + "/docker-compose.yml", "a") - f.write(compose.content) - f.close() + if not os.path.exists(settings.COMPOSE_DIR + compose.name): + pathlib.Path(settings.COMPOSE_DIR + compose.name).mkdir(parents=True) + with open(settings.COMPOSE_DIR + compose.name + "/docker-compose.yml", "w") as f: + try: + f.write(compose.content) + f.close() + except Exception as exc: + raise HTTPException(exc.status_code, exc.detail) return get_compose(name=compose.name) + +def delete_compose(project_name): + if not os.path.exists(settings.COMPOSE_DIR+project_name): + raise HTTPException(404, "Project directory not found.") + elif not os.path.exists(settings.COMPOSE_DIR + project_name+"/docker.compose.yml"): + raise HTTPException(404, "Project docker-compose.yml not found.") + else: + try: + with open(settings.COMPOSE_DIR + project_name + 'docker-compose.yml'): + pass + except OSError as exc: + raise HTTPException(400,exc.strerror) + try: + shutil.rmtree(settings.COMPOSE_DIR+project_name) + except Exception as exc: + raise HTTPException(exc.status_code, exc.strerror) + return get_compose_projects() diff --git a/backend/api/routers/compose.py b/backend/api/routers/compose.py index 6b92c9f9..9cc91bc4 100644 --- a/backend/api/routers/compose.py +++ b/backend/api/routers/compose.py @@ -6,6 +6,7 @@ compose_app_action, get_compose, write_compose, + delete_compose, ) from fastapi_jwt_auth import AuthJWT from ..auth import auth_check @@ -25,6 +26,10 @@ def get_project(project_name, Authorize: AuthJWT = Depends()): auth_check(Authorize) return get_compose(project_name) +@router.get("/{project_name}/delete") +def delete_compose_project(project_name, Authorize: AuthJWT = Depends()): + auth_check(Authorize) + return delete_compose(project_name) @router.get("/{project_name}/{action}") def get_compose_action(project_name, action, Authorize: AuthJWT = Depends()): @@ -44,3 +49,4 @@ def write_compose_project( ): auth_check(Authorize) return write_compose(compose=compose) + diff --git a/backend/requirements.txt b/backend/requirements.txt index a41d3cba..b49488d7 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -21,7 +21,7 @@ python-jose==3.2.0 makefun==1.9.2 passlib==1.7.4 pycparser==2.20 -pydantic==1.6.1 +pydantic==1.7.3 PyJWT==1.7.1 python-multipart==0.0.5 PyYAML==5.3.1 diff --git a/frontend/src/components/auth/LoginForm.vue b/frontend/src/components/auth/LoginForm.vue index 965e6edc..814f58c8 100644 --- a/frontend/src/components/auth/LoginForm.vue +++ b/frontend/src/components/auth/LoginForm.vue @@ -68,25 +68,25 @@ import { themeLogo } from "../../config.js"; export default { components: { ValidationProvider, - ValidationObserver, + ValidationObserver }, data() { return { username: "", password: "", - show: false, + show: false }; }, methods: { ...mapActions({ login: "auth/AUTH_REQUEST", - authCheck: "auth/AUTH_CHECK", + authCheck: "auth/AUTH_CHECK" }), onSubmit() { this.login({ username: this.username, - password: this.password, + password: this.password }); }, themeLogo() { @@ -97,14 +97,14 @@ export default { } else if (this.$vuetify.theme.dark == false) { return lightLogo; } - }, + } }, mounted() { this.authCheck(); }, created() { this.authCheck(); - }, + } }; diff --git a/frontend/src/components/compose/ProjectDetails.vue b/frontend/src/components/compose/ProjectDetails.vue index dd004299..e758a704 100644 --- a/frontend/src/components/compose/ProjectDetails.vue +++ b/frontend/src/components/compose/ProjectDetails.vue @@ -10,101 +10,116 @@ /> - {{ project.name }} - - - - - - mdi-file-document-edit-outline - - Edit - - - - - mdi-arrow-up-bold - - Up - - - - mdi-arrow-down-bold - - Down - - - - - mdi-play - - Start - - - - mdi-stop - - Stop - - - - mdi-refresh - - Restart - - - - - mdi-update - - Pull - - - - mdi-plus-box-multiple - - Create - - - + + {{ project.name }} + - - mdi-fire - - Kill - + + + + + mdi-arrow-up-bold + + Up + + + + mdi-arrow-down-bold + + Down + + + + + mdi-play + + Start + + + + mdi-stop + + Stop + + + + mdi-refresh + + Restart + + + + + mdi-update + + Pull + + + + mdi-plus-box-multiple + + Create + + + + + mdi-fire + + Kill + - - - mdi-delete - - Remove - - - + + + mdi-delete + + Remove + + + + + + + Edit + mdi-file-document-edit-outline + + + Delete + mdi-trash-can-outline + + + - - Project Details - + Project Details - - Name - + Name {{ project.name }} - - Path - + Path {{ project.path }} - - Version - + Version {{ project.version }} @@ -150,9 +157,7 @@ - - Services - + Services - + {{ service }} ({{ project.services[service].image || "No Image" }}) @@ -183,7 +188,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'up' + Action: 'up', }) " > @@ -197,7 +202,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'start' + Action: 'start', }) " > @@ -210,7 +215,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'stop' + Action: 'stop', }) " > @@ -223,7 +228,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'restart' + Action: 'restart', }) " > @@ -237,7 +242,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'pull' + Action: 'pull', }) " > @@ -251,7 +256,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'kill' + Action: 'kill', }) " > @@ -264,7 +269,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'rm' + Action: 'rm', }) " > @@ -275,84 +280,62 @@
- - Container Name - + Container Name {{ project.services[service].container_name }} - - Image - + Image {{ project.services[service].image }} - - Env File - + Env File {{ project.services[service].env_file.join(", ") }} - - Depends on - + Depends on {{ project.services[service].depends_on.join(", ") }} - - Restart Policy - + Restart Policy {{ project.services[service].restart }} - - Read Only - + Read Only {{ project.services[service].read_only }} - - Networks - + Networks {{ project.services[service].networks.join(", ") }} - - Ports - + Ports {{ project.services[service].ports.join(", ") }} - - Volumes - + Volumes - - Host - - - Container - + Host + Container @@ -374,20 +357,14 @@ - - Environment - + Environment - - Variable - - - Value - + Variable + Value @@ -409,20 +386,14 @@ - - Labels - + Labels - - Label - - - Value - + Label + Value @@ -444,9 +415,7 @@ - - Command - + Command @@ -472,17 +441,13 @@ - - Networks - + Networks {{ project.networks.join(", ") }} - - Volumes - + Volumes {{ project.volumes.join(", ") }} @@ -501,22 +466,22 @@ export default { ...mapState("projects", ["project", "projects", "isLoading"]), ...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`}) + this.$router.push({ path: `/projects/${projectname}/edit` }); }, getStatus(name) { for (var app in this.apps) { @@ -533,13 +498,18 @@ export default { const projectName = this.$route.params.projectName; this.readProject(projectName); this.readApps(); - } + }, }, created() { const projectName = this.$route.params.projectName; this.readProject(projectName); this.readApps(); - } + }, + async mounted() { + const projectName = this.$route.params.projectName; + await this.readProject(projectName); + await this.readApps(); + }, }; diff --git a/frontend/src/components/compose/ProjectEditor.vue b/frontend/src/components/compose/ProjectEditor.vue index 8f3098d0..7239b7fb 100644 --- a/frontend/src/components/compose/ProjectEditor.vue +++ b/frontend/src/components/compose/ProjectEditor.vue @@ -11,29 +11,31 @@ -->
- - - - New Compose Template - - - Edit {{this.form.name}} Project - - - - - - - - submit - - + + + + New Compose Template + + + Edit {{ this.form.name }} Project + + + + + + + + submit + +
{ + .then(response => { this.$router.push({ path: `/projects/${response.data.name}` }); }) - .catch((err) => { + .catch(err => { console.log(err); }); }, @@ -92,15 +94,15 @@ export default { const project = await this.readProject(projectName); this.form = { name: project.name || "", - content: project.content || "", - } + content: project.content || "" + }; this.existing = true; } - }, + } }, async created() { await this.populateForm(); - }, + } }; @@ -111,4 +113,4 @@ export default { .ace_gutter-active-line { z-index: 1; } - \ No newline at end of file + diff --git a/frontend/src/components/compose/ProjectList.vue b/frontend/src/components/compose/ProjectList.vue index 2a36ce2f..b8582e97 100644 --- a/frontend/src/components/compose/ProjectList.vue +++ b/frontend/src/components/compose/ProjectList.vue @@ -39,172 +39,160 @@ + + mdi-delete + + Remove +
+
+ + + + mdi-eye + + View + + + + mdi-file-document-edit-outline + + Edit + + + + + mdi-delete + + Delete + +
+ + {{ + item.name + }} + + + Unused + + +
+ + {{ item.version }} + +
+ +
+ + {{ item.path }} + +
+ +
+ + {{ Object.keys(item.services).length }} + +
+ @@ -218,7 +206,7 @@ export default { selectedProject: null, deleteDialog: false, form: { - name: "" + name: "", }, createDialog: false, search: "", @@ -226,24 +214,24 @@ export default { { text: "Name", value: "name", - sortable: true + sortable: true, }, { text: "Version", value: "version", - sortable: true + sortable: true, }, { text: "Services", value: "services", - sortable: false + sortable: false, }, { text: "Path", value: "path", - sortable: true - } - ] + sortable: true, + }, + ], }; }, methods: { @@ -255,18 +243,18 @@ export default { this.$router.push({ path: `/projects/${item.name}` }); }, editProject(projectname) { - this.$router.push({ path: `/projects/${projectname}/edit`}) + this.$router.push({ path: `/projects/${projectname}/edit` }); }, projectDetails(projectname) { this.$router.push({ path: `/projects/${projectname}` }); - } + }, }, computed: { - ...mapState("projects", ["projects", "isLoading"]) + ...mapState("projects", ["projects", "isLoading"]), }, mounted() { this.readProjects(); - } + }, }; diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index bb46916a..e5ecf3db 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -24,7 +24,7 @@ import ApplicationDeployFromTemplate from "../components/applications/Applicatio import Project from "../views/Project.vue"; import ProjectList from "../components/compose/ProjectList.vue"; import ProjectDetails from "../components/compose/ProjectDetails.vue"; -import ProjectEditor from "../components/compose/ProjectEditor.vue" +import ProjectEditor from "../components/compose/ProjectEditor.vue"; // Resources import Resources from "../views/Resources.vue"; diff --git a/frontend/src/store/actions/auth.js b/frontend/src/store/actions/auth.js index 5431f696..6c731172 100644 --- a/frontend/src/store/actions/auth.js +++ b/frontend/src/store/actions/auth.js @@ -6,4 +6,4 @@ export const AUTH_CLEAR = "AUTH_CLEAR"; export const AUTH_REFRESH = "AUTH_REFRESH"; export const AUTH_CHANGE_PASS = "AUTH_CHANGE_PASS"; export const AUTH_CHECK = "AUTH_CHECK"; -export const AUTH_DISABLED = "AUTH_DISABLED" +export const AUTH_DISABLED = "AUTH_DISABLED"; diff --git a/frontend/src/store/modules/auth.js b/frontend/src/store/modules/auth.js index 7b0d3362..532ebffc 100644 --- a/frontend/src/store/modules/auth.js +++ b/frontend/src/store/modules/auth.js @@ -111,14 +111,12 @@ const actions = { [AUTH_CHECK]: ({ commit }) => { commit(AUTH_REQUEST); const url = "/api/auth/me"; - axios - .get(url) - .then(resp => { - console.log(resp) - localStorage.setItem("username", resp.data.username); - commit(AUTH_DISABLED); - commit(AUTH_SUCCESS, resp); - }) + axios.get(url).then(resp => { + console.log(resp); + localStorage.setItem("username", resp.data.username); + commit(AUTH_DISABLED); + commit(AUTH_SUCCESS, resp); + }); } }; From 70d7dd73e28ac63ec8b1610107cc6b45c4acd763 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Thu, 7 Jan 2021 10:25:16 -0800 Subject: [PATCH 14/75] error catching for permissions error on mkdir command in write_compose() --- backend/api/actions/compose.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index af41635a..3cf57c2f 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -196,7 +196,10 @@ def get_compose(name): def write_compose(compose): if not os.path.exists(settings.COMPOSE_DIR + compose.name): - pathlib.Path(settings.COMPOSE_DIR + compose.name).mkdir(parents=True) + try: + pathlib.Path(settings.COMPOSE_DIR + compose.name).mkdir(parents=True) + except Exception as exc: + raise HTTPException(exc.status_code, exc.detail) with open(settings.COMPOSE_DIR + compose.name + "/docker-compose.yml", "w") as f: try: f.write(compose.content) From 20b2861c697f5e0db9a8d3a7921c212405ad2a03 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Thu, 7 Jan 2021 11:03:10 -0800 Subject: [PATCH 15/75] added delete dialog to projects --- backend/api/actions/compose.py | 2 +- backend/api/routers/compose.py | 9 ++-- .../src/components/compose/ProjectDetails.vue | 46 +++++++++++++++---- .../src/components/compose/ProjectList.vue | 27 +++++++++++ frontend/src/store/modules/projects.js | 5 +- 5 files changed, 73 insertions(+), 16 deletions(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 3cf57c2f..ada0bbc1 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -182,7 +182,7 @@ def get_compose(name): compose_object = { "name": project, "path": file, - "version": loaded_compose.get("version", '3.9'), + "version": loaded_compose.get("version", '-'), "services": services, "volumes": volumes, "networks": networks, diff --git a/backend/api/routers/compose.py b/backend/api/routers/compose.py index 9cc91bc4..7d538fa7 100644 --- a/backend/api/routers/compose.py +++ b/backend/api/routers/compose.py @@ -26,15 +26,14 @@ def get_project(project_name, Authorize: AuthJWT = Depends()): auth_check(Authorize) return get_compose(project_name) -@router.get("/{project_name}/delete") -def delete_compose_project(project_name, Authorize: AuthJWT = Depends()): - auth_check(Authorize) - return delete_compose(project_name) @router.get("/{project_name}/{action}") def get_compose_action(project_name, action, Authorize: AuthJWT = Depends()): auth_check(Authorize) - return compose_action(project_name, action) + if action == "delete": + return delete_compose(project_name) + else: + return compose_action(project_name, action) @router.get("/{project_name}/{action}/{app}") diff --git a/frontend/src/components/compose/ProjectDetails.vue b/frontend/src/components/compose/ProjectDetails.vue index e758a704..d1304a01 100644 --- a/frontend/src/components/compose/ProjectDetails.vue +++ b/frontend/src/components/compose/ProjectDetails.vue @@ -114,7 +114,13 @@ Edit mdi-file-document-edit-outline - + Delete mdi-trash-can-outline @@ -452,6 +458,32 @@ {{ project.volumes.join(", ") }} + + + + Delete {{ selectedProject["name"] }} project? + + + The project directory and all files within it will be permanently + deleted. This action cannot be revoked. + + + + Cancel + + Delete + + + + @@ -460,7 +492,10 @@ import { mapActions, mapGetters, mapState } from "vuex"; export default { data() { - return {}; + return { + selectedProject: null, + deleteDialog: false, + }; }, computed: { ...mapState("projects", ["project", "projects", "isLoading"]), @@ -500,16 +535,11 @@ export default { this.readApps(); }, }, - created() { + mounted() { const projectName = this.$route.params.projectName; this.readProject(projectName); this.readApps(); }, - async mounted() { - const projectName = this.$route.params.projectName; - await this.readProject(projectName); - await this.readApps(); - }, }; diff --git a/frontend/src/components/compose/ProjectList.vue b/frontend/src/components/compose/ProjectList.vue index b8582e97..a09291ba 100644 --- a/frontend/src/components/compose/ProjectList.vue +++ b/frontend/src/components/compose/ProjectList.vue @@ -195,6 +195,33 @@ + + + + Delete {{selectedProject['name']}} project? + + + The project directory and all files within it will be permanently deleted. + This action cannot be revoked. + + + + + Cancel + + + Delete + + + + diff --git a/frontend/src/store/modules/projects.js b/frontend/src/store/modules/projects.js index 1885c1c0..a9af5613 100644 --- a/frontend/src/store/modules/projects.js +++ b/frontend/src/store/modules/projects.js @@ -78,9 +78,10 @@ const actions = { axios .get(url) .then(response => { - const app = response.data; + const project = response.data; commit("setLoading", false); - resolve(app); + commit("setProject", project) + resolve(project); }) .finally(() => { commit("setLoading", false); From a1d437f30922518c566f03194f609aeb5add4c05 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Thu, 7 Jan 2021 11:13:46 -0800 Subject: [PATCH 16/75] use a background task for updating yacht --- backend/api/actions/apps.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index 84b6fa77..a267c781 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -1,6 +1,6 @@ from sqlalchemy.orm import Session from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException +from fastapi import HTTPException, BackgroundTasks from ..db import models, schemas from ..utils import * from ..utils import check_updates as _update_check @@ -252,7 +252,7 @@ def app_update(app_name): return get_apps() -def update_self(): +def update_self(background_tasks: BackgroundTasks): dclient = docker.from_env() bash_command = "head -1 /proc/self/cgroup|cut -d/ -f3" yacht_id = ( @@ -271,21 +271,20 @@ def update_self(): raise HTTPException( status_code=exc.response.status_code, detail=exc.explanation ) + background_tasks.add_task(update_self_in_background, yacht) + return {'result': 'successful'} +def update_self_in_background(yacht): + dclient = docker.from_env() volumes = {"/var/run/docker.sock": {"bind": "/var/run/docker.sock", "mode": "rw"}} print("**** Updating " + yacht.name + "****") - updater = dclient.containers.run( + dclient.containers.run( image="containrrr/watchtower:latest", command="--cleanup --run-once " + yacht.name, remove=True, detach=True, volumes=volumes, ) - result = updater - print(result) - time.sleep(1) - return result - def check_self_update(): dclient = docker.from_env() From 6219458657766bf99e263ec11b7e4f71636dd4c9 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Thu, 7 Jan 2021 13:09:29 -0800 Subject: [PATCH 17/75] fixed router push after project deletion on details page --- backend/api/actions/compose.py | 9 ++++----- backend/api/routers/compose.py | 13 +++++++------ frontend/src/components/compose/ProjectDetails.vue | 6 ++++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index ada0bbc1..91f4d1f2 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -188,7 +188,6 @@ def get_compose(name): "networks": networks, "content": content, } - print(compose_object["content"]) return compose_object else: raise HTTPException(404, "Project " + name + " not found") @@ -210,18 +209,18 @@ def write_compose(compose): return get_compose(name=compose.name) def delete_compose(project_name): - if not os.path.exists(settings.COMPOSE_DIR+project_name): + if not os.path.exists('/'+settings.COMPOSE_DIR+project_name): raise HTTPException(404, "Project directory not found.") - elif not os.path.exists(settings.COMPOSE_DIR + project_name+"/docker.compose.yml"): + elif not os.path.exists('/'+settings.COMPOSE_DIR + project_name+"/docker-compose.yml"): raise HTTPException(404, "Project docker-compose.yml not found.") else: try: - with open(settings.COMPOSE_DIR + project_name + 'docker-compose.yml'): + with open('/'+settings.COMPOSE_DIR + project_name + '/docker-compose.yml'): pass except OSError as exc: raise HTTPException(400,exc.strerror) try: - shutil.rmtree(settings.COMPOSE_DIR+project_name) + shutil.rmtree('/'+settings.COMPOSE_DIR+project_name) except Exception as exc: raise HTTPException(exc.status_code, exc.strerror) return get_compose_projects() diff --git a/backend/api/routers/compose.py b/backend/api/routers/compose.py index 7d538fa7..fb2df33a 100644 --- a/backend/api/routers/compose.py +++ b/backend/api/routers/compose.py @@ -35,6 +35,12 @@ def get_compose_action(project_name, action, Authorize: AuthJWT = Depends()): else: return compose_action(project_name, action) +@router.post("/{project_name}/edit", response_model=ComposeRead) +def write_compose_project( + project_name, compose: ComposeWrite, Authorize: AuthJWT = Depends() +): + auth_check(Authorize) + return write_compose(compose=compose) @router.get("/{project_name}/{action}/{app}") def get_compose_app_action(project_name, action, app, Authorize: AuthJWT = Depends()): @@ -42,10 +48,5 @@ def get_compose_app_action(project_name, action, app, Authorize: AuthJWT = Depen return compose_app_action(project_name, action, app) -@router.post("/{project_name}/edit", response_model=ComposeRead) -def write_compose_project( - project_name, compose: ComposeWrite, Authorize: AuthJWT = Depends() -): - auth_check(Authorize) - return write_compose(compose=compose) + diff --git a/frontend/src/components/compose/ProjectDetails.vue b/frontend/src/components/compose/ProjectDetails.vue index d1304a01..6ba231bc 100644 --- a/frontend/src/components/compose/ProjectDetails.vue +++ b/frontend/src/components/compose/ProjectDetails.vue @@ -475,8 +475,7 @@ color="error" @click=" ProjectAction({ Name: selectedProject.name, Action: 'delete' }); - deleteDialog = false; - router.push({ name: 'View Projects'}) + postDelete(); " > Delete @@ -518,6 +517,9 @@ export default { editProject(projectname) { this.$router.push({ path: `/projects/${projectname}/edit` }); }, + postDelete(){ + this.$router.push({ name: 'View Projects'}) + }, getStatus(name) { for (var app in this.apps) { if ( From facead895cfa6696b0463ac1335e9a396a1e3287 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Thu, 7 Jan 2021 14:25:39 -0800 Subject: [PATCH 18/75] fixed router push after project deletion on details page --- frontend/src/components/compose/ProjectEditor.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/components/compose/ProjectEditor.vue b/frontend/src/components/compose/ProjectEditor.vue index 7239b7fb..cdf72c6b 100644 --- a/frontend/src/components/compose/ProjectEditor.vue +++ b/frontend/src/components/compose/ProjectEditor.vue @@ -113,4 +113,7 @@ export default { .ace_gutter-active-line { z-index: 1; } +.ace_editor { + z-index: 1; +} From 04334bfcfc31d1578546d87c548066feec44ce1a Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Thu, 7 Jan 2021 17:18:02 -0800 Subject: [PATCH 19/75] added in light/dark mode for project editor --- frontend/src/components/compose/ProjectEditor.vue | 10 +++++++++- frontend/src/main.js | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/compose/ProjectEditor.vue b/frontend/src/components/compose/ProjectEditor.vue index cdf72c6b..c8a2410b 100644 --- a/frontend/src/components/compose/ProjectEditor.vue +++ b/frontend/src/components/compose/ProjectEditor.vue @@ -41,7 +41,7 @@ v-model="form.content" @init="editorInit" lang="yaml" - theme="twilight" + :theme='editorTheming()' :height="windowHeight" :width="windowWidth" class="editor" @@ -76,6 +76,14 @@ export default { editorInit() { require("brace/mode/yaml"); require("brace/theme/twilight"); + require("brace/theme/textmate"); + }, + editorTheming(){ + if ( this.$vuetify.theme.dark == false) { + return "textmate" + } else { + return "twilight" + } }, submitCompose() { let url = `/api/compose/${this.form.name}/edit`; diff --git a/frontend/src/main.js b/frontend/src/main.js index 04f3dfe2..ccbb48b2 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -20,7 +20,7 @@ function createAxiosResponseInterceptor() { const interceptor = axios.interceptors.response.use( response => response, error => { - if (error.response.status !== 401) { + if (error.response.status !== 401 || error.response.status !== 403) { return Promise.reject(error); } @@ -35,7 +35,7 @@ function createAxiosResponseInterceptor() { return axios(error.response.config); }) .catch(error => { - if (error.response.status != 401) { + if (error.response.status !== 401 || error.response.status !== 403) { return Promise.reject(error); } else { store.dispatch("auth/AUTH_LOGOUT"); From 5cd8fa44b031b1e51fa0f1c8332163bf6108732f Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Fri, 8 Jan 2021 07:47:53 -0800 Subject: [PATCH 20/75] fixed command separating each word if there's only one (projects) --- .../src/components/compose/ProjectDetails.vue | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/compose/ProjectDetails.vue b/frontend/src/components/compose/ProjectDetails.vue index 6ba231bc..0f67f219 100644 --- a/frontend/src/components/compose/ProjectDetails.vue +++ b/frontend/src/components/compose/ProjectDetails.vue @@ -180,7 +180,7 @@ {{ getStatus( project.services[service].container_name || service - ) || "missing" + ) || "not created" }} @@ -425,7 +425,11 @@ - + + + + + {{ project.services[service].command }} + + + @@ -475,7 +486,7 @@ color="error" @click=" ProjectAction({ Name: selectedProject.name, Action: 'delete' }); - postDelete(); + postDelete(); " > Delete @@ -517,8 +528,8 @@ export default { editProject(projectname) { this.$router.push({ path: `/projects/${projectname}/edit` }); }, - postDelete(){ - this.$router.push({ name: 'View Projects'}) + postDelete() { + this.$router.push({ name: "View Projects" }); }, getStatus(name) { for (var app in this.apps) { From 1d4c197414943e46eccb6bfdb67cb730642b8ba9 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Fri, 8 Jan 2021 08:14:22 -0800 Subject: [PATCH 21/75] fixed buttons by updating vuetify; removed sidebar for new app/template/project and using fab instead --- frontend/package-lock.json | 6 +- frontend/package.json | 2 +- .../applications/ApplicationsList.vue | 322 +++++++++--------- .../src/components/compose/ProjectList.vue | 9 + frontend/src/components/nav/Sidebar.vue | 65 +--- .../components/templates/TemplatesList.vue | 109 +++--- 6 files changed, 242 insertions(+), 271 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6c597556..2bb2e956 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16335,9 +16335,9 @@ } }, "vuetify": { - "version": "2.4.1", - "resolved": "https://registry.npm.taobao.org/vuetify/download/vuetify-2.4.1.tgz", - "integrity": "sha1-gW5QJrBpBlSumUcWGsnTXI/uBCg=" + "version": "2.4.2", + "resolved": "https://registry.npm.taobao.org/vuetify/download/vuetify-2.4.2.tgz?cache=0&sync_timestamp=1609965378707&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvuetify%2Fdownload%2Fvuetify-2.4.2.tgz", + "integrity": "sha1-030WDB/GskH8FmoymAs0WQoBfW4=" }, "vuetify-loader": { "version": "1.6.0", diff --git a/frontend/package.json b/frontend/package.json index 95b65538..592d5679 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,7 +21,7 @@ "vue-chartjs": "^3.5.1", "vue-router": "^3.4.9", "vue2-ace-editor": "0.0.15", - "vuetify": "^2.4.1", + "vuetify": "^2.4.2", "vuex": "^3.6.0" }, "devDependencies": { diff --git a/frontend/src/components/applications/ApplicationsList.vue b/frontend/src/components/applications/ApplicationsList.vue index 9936296f..08664f9d 100644 --- a/frontend/src/components/applications/ApplicationsList.vue +++ b/frontend/src/components/applications/ApplicationsList.vue @@ -34,6 +34,15 @@ + + mdi-plus + + + Columns + + + + + mdi-delete + + Remove + + + + {{ item.name }} + + + Update Available + + + +
+ {{ item.Config.Labels["com.docker.compose.project"] || "-" }} +
+ +
+ {{ item.State.Status }} +
+ + + + + + {{ port.hport }} + + + + + {{ item.Config.Image }} + + {{ item.Created | formatDate }} + @@ -260,37 +246,37 @@ export default { text: "Name", value: "name", sortable: true, - align: "start" + align: "start", // width: "30%", }, project: { text: "Project", value: "project", - sortable: true + sortable: true, }, status: { text: "Status", value: "status", - sortable: true + sortable: true, // width: "10%", }, image: { text: "Image", value: "image", - sortable: true + sortable: true, }, ports: { text: "Ports", value: "ports", - sortable: true + sortable: true, }, created: { text: "Created At", value: "created", - sortable: true - } + sortable: true, + }, }, - selectedHeaders: [] + selectedHeaders: [], }; }, methods: { @@ -298,7 +284,7 @@ export default { readApps: "apps/readApps", AppAction: "apps/AppAction", Update: "apps/AppUpdate", - checkUpdate: "apps/checkAppUpdate" + checkUpdate: "apps/checkAppUpdate", }), handleRowClick(appName) { this.$router.push({ path: `/apps${appName.Name}/info` }); @@ -308,7 +294,7 @@ export default { for (var k in data) { if (data[k]) { o = o.concat( - data[k].map(function(x) { + data[k].map(function (x) { return { cport: k, hip: x.HostIp, hport: x.HostPort }; }) ); @@ -318,7 +304,7 @@ export default { }, refresh() { this.readApps(); - } + }, }, computed: { ...mapState("apps", [ @@ -326,11 +312,11 @@ export default { "isLoading", "isLoadingValue", "action", - "updatable" + "updatable", ]), showHeaders() { - return this.headers.filter(s => this.selectedHeaders.includes(s)); - } + return this.headers.filter((s) => this.selectedHeaders.includes(s)); + }, }, created() { this.headers = Object.values(this.headersMap); @@ -338,7 +324,7 @@ export default { }, mounted() { this.readApps(); - } + }, }; diff --git a/frontend/src/components/compose/ProjectList.vue b/frontend/src/components/compose/ProjectList.vue index a09291ba..0e6f5127 100644 --- a/frontend/src/components/compose/ProjectList.vue +++ b/frontend/src/components/compose/ProjectList.vue @@ -11,6 +11,15 @@ Projects + + mdi-plus + diff --git a/frontend/src/components/templates/TemplatesList.vue b/frontend/src/components/templates/TemplatesList.vue index cb051864..bb4d65fe 100644 --- a/frontend/src/components/templates/TemplatesList.vue +++ b/frontend/src/components/templates/TemplatesList.vue @@ -11,6 +11,15 @@ Templates + + mdi-plus + +
+ {{ item.title }} + + + + + + mdi-eye + + View + + + + mdi-update + + Update + + + + + mdi-delete + + Delete + + + +
+ + {{ item.created_at | formatDate }} + + {{ item.updated_at | formatDate }} + @@ -127,42 +136,42 @@ export default { text: "Title", value: "title", sortable: true, - align: "start" + align: "start", }, { text: "Created At", value: "created_at", sortable: true, - width: "20%" + width: "20%", }, { text: "Updated At", value: "updated_at", sortable: true, - width: "20%" - } - ] + width: "20%", + }, + ], }; }, methods: { ...mapActions({ deleteTemplate: "templates/deleteTemplate", readTemplates: "templates/readTemplates", - updateTemplate: "templates/updateTemplate" + updateTemplate: "templates/updateTemplate", }), handleRowClick(value) { this.$router.push({ path: `/templates/${value.id}` }); }, templateDetails(templateId) { this.$router.push({ path: `/templates/${templateId}` }); - } + }, }, computed: { - ...mapState("templates", ["templates", "isLoading"]) + ...mapState("templates", ["templates", "isLoading"]), }, mounted() { this.readTemplates(); - } + }, }; From 13079a5e95064189092f419530f8946b6d388124 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Fri, 8 Jan 2021 08:17:15 -0800 Subject: [PATCH 22/75] formatted;linted --- .../applications/ApplicationsList.vue | 325 ++++++++-------- .../src/components/compose/ProjectDetails.vue | 26 +- .../src/components/compose/ProjectEditor.vue | 10 +- .../src/components/compose/ProjectList.vue | 354 +++++++++--------- frontend/src/components/nav/Sidebar.vue | 26 +- .../components/templates/TemplatesList.vue | 112 +++--- frontend/src/store/modules/projects.js | 2 +- 7 files changed, 439 insertions(+), 416 deletions(-) diff --git a/frontend/src/components/applications/ApplicationsList.vue b/frontend/src/components/applications/ApplicationsList.vue index 08664f9d..b26e32cd 100644 --- a/frontend/src/components/applications/ApplicationsList.vue +++ b/frontend/src/components/applications/ApplicationsList.vue @@ -34,15 +34,9 @@ - - mdi-plus - + + mdi-plus + + + Columns + + + + + mdi-delete + + Remove + + + + {{ item.name }} + + + Update Available + + + +
+ {{ item.Config.Labels["com.docker.compose.project"] || "-" }} + +
+ +
+ {{ item.State.Status }} +
+ + + + + + {{ port.hport }} + + + + + {{ item.Config.Image }} + + {{ item.Created | formatDate }} + @@ -246,37 +263,37 @@ export default { text: "Name", value: "name", sortable: true, - align: "start", + align: "start" // width: "30%", }, project: { text: "Project", value: "project", - sortable: true, + sortable: true }, status: { text: "Status", value: "status", - sortable: true, + sortable: true // width: "10%", }, image: { text: "Image", value: "image", - sortable: true, + sortable: true }, ports: { text: "Ports", value: "ports", - sortable: true, + sortable: true }, created: { text: "Created At", value: "created", - sortable: true, - }, + sortable: true + } }, - selectedHeaders: [], + selectedHeaders: [] }; }, methods: { @@ -284,7 +301,7 @@ export default { readApps: "apps/readApps", AppAction: "apps/AppAction", Update: "apps/AppUpdate", - checkUpdate: "apps/checkAppUpdate", + checkUpdate: "apps/checkAppUpdate" }), handleRowClick(appName) { this.$router.push({ path: `/apps${appName.Name}/info` }); @@ -294,7 +311,7 @@ export default { for (var k in data) { if (data[k]) { o = o.concat( - data[k].map(function (x) { + data[k].map(function(x) { return { cport: k, hip: x.HostIp, hport: x.HostPort }; }) ); @@ -304,7 +321,7 @@ export default { }, refresh() { this.readApps(); - }, + } }, computed: { ...mapState("apps", [ @@ -312,11 +329,11 @@ export default { "isLoading", "isLoadingValue", "action", - "updatable", + "updatable" ]), showHeaders() { - return this.headers.filter((s) => this.selectedHeaders.includes(s)); - }, + return this.headers.filter(s => this.selectedHeaders.includes(s)); + } }, created() { this.headers = Object.values(this.headersMap); @@ -324,7 +341,7 @@ export default { }, mounted() { this.readApps(); - }, + } }; diff --git a/frontend/src/components/compose/ProjectDetails.vue b/frontend/src/components/compose/ProjectDetails.vue index 0f67f219..bd8b82f0 100644 --- a/frontend/src/components/compose/ProjectDetails.vue +++ b/frontend/src/components/compose/ProjectDetails.vue @@ -194,7 +194,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'up', + Action: 'up' }) " > @@ -208,7 +208,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'start', + Action: 'start' }) " > @@ -221,7 +221,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'stop', + Action: 'stop' }) " > @@ -234,7 +234,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'restart', + Action: 'restart' }) " > @@ -248,7 +248,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'pull', + Action: 'pull' }) " > @@ -262,7 +262,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'kill', + Action: 'kill' }) " > @@ -275,7 +275,7 @@ projectAppAction({ Project: project.name, Name: service, - Action: 'rm', + Action: 'rm' }) " > @@ -504,26 +504,26 @@ export default { data() { return { selectedProject: null, - deleteDialog: false, + deleteDialog: false }; }, computed: { ...mapState("projects", ["project", "projects", "isLoading"]), ...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` }); @@ -546,13 +546,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(); - }, + } }; diff --git a/frontend/src/components/compose/ProjectEditor.vue b/frontend/src/components/compose/ProjectEditor.vue index c8a2410b..d8f36500 100644 --- a/frontend/src/components/compose/ProjectEditor.vue +++ b/frontend/src/components/compose/ProjectEditor.vue @@ -41,7 +41,7 @@ v-model="form.content" @init="editorInit" lang="yaml" - :theme='editorTheming()' + :theme="editorTheming()" :height="windowHeight" :width="windowWidth" class="editor" @@ -78,11 +78,11 @@ export default { require("brace/theme/twilight"); require("brace/theme/textmate"); }, - editorTheming(){ - if ( this.$vuetify.theme.dark == false) { - return "textmate" + editorTheming() { + if (this.$vuetify.theme.dark == false) { + return "textmate"; } else { - return "twilight" + return "twilight"; } }, submitCompose() { diff --git a/frontend/src/components/compose/ProjectList.vue b/frontend/src/components/compose/ProjectList.vue index 0e6f5127..05365fcc 100644 --- a/frontend/src/components/compose/ProjectList.vue +++ b/frontend/src/components/compose/ProjectList.vue @@ -11,15 +11,9 @@ Projects - - mdi-plus - + + mdi-plus + + Unused + + +
+ + {{ item.version }} + +
+ +
+ + {{ item.path }} + +
+ +
+ + {{ Object.keys(item.services).length }} + +
+ - Delete {{selectedProject['name']}} project? + Delete {{ selectedProject["name"] }} project? - The project directory and all files within it will be permanently deleted. - This action cannot be revoked. + The project directory and all files within it will be permanently + deleted. This action cannot be revoked. @@ -242,7 +254,7 @@ export default { selectedProject: null, deleteDialog: false, form: { - name: "", + name: "" }, createDialog: false, search: "", @@ -250,30 +262,30 @@ export default { { text: "Name", value: "name", - sortable: true, + sortable: true }, { text: "Version", value: "version", - sortable: true, + sortable: true }, { text: "Services", value: "services", - sortable: false, + sortable: false }, { text: "Path", value: "path", - sortable: true, - }, - ], + sortable: true + } + ] }; }, methods: { ...mapActions({ readProjects: "projects/readProjects", - ProjectAction: "projects/ProjectAction", + ProjectAction: "projects/ProjectAction" }), handleRowClick(item) { this.$router.push({ path: `/projects/${item.name}` }); @@ -283,14 +295,14 @@ export default { }, projectDetails(projectname) { this.$router.push({ path: `/projects/${projectname}` }); - }, + } }, computed: { - ...mapState("projects", ["projects", "isLoading"]), + ...mapState("projects", ["projects", "isLoading"]) }, mounted() { this.readProjects(); - }, + } }; diff --git a/frontend/src/components/nav/Sidebar.vue b/frontend/src/components/nav/Sidebar.vue index 51cc4788..8ded3e5c 100644 --- a/frontend/src/components/nav/Sidebar.vue +++ b/frontend/src/components/nav/Sidebar.vue @@ -65,22 +65,22 @@ export default { to: "/", icon: "mdi-view-dashboard", text: "Dashboard", - divider: true, + divider: true }, { text: "View Applications", to: "/apps", - icon: "mdi-application", + icon: "mdi-application" }, { text: "View Templates", to: "/templates", - icon: "mdi-folder", + icon: "mdi-folder" }, { text: "View Projects", to: "/projects", - icon: "mdi-book-open", + icon: "mdi-book-open" }, { icon: "mdi-cube-outline", @@ -89,27 +89,27 @@ export default { { text: "Images", to: "/resources/images", - icon: "mdi-disc", + icon: "mdi-disc" }, { text: "Volumes", to: "/resources/volumes", - icon: "mdi-database", + icon: "mdi-database" }, { text: "Networks", to: "/resources/networks", - icon: "mdi-network", - }, - ], + icon: "mdi-network" + } + ] }, { to: "/settings/info", icon: "mdi-cog", - text: "Settings", - }, - ], - }), + text: "Settings" + } + ] + }) }; diff --git a/frontend/src/components/templates/TemplatesList.vue b/frontend/src/components/templates/TemplatesList.vue index bb4d65fe..7eb0dda3 100644 --- a/frontend/src/components/templates/TemplatesList.vue +++ b/frontend/src/components/templates/TemplatesList.vue @@ -11,15 +11,9 @@ Templates - - mdi-plus - + + mdi-plus + +
+ {{ item.title }} + + + + + + mdi-eye + + View + + + + mdi-update + + Update + + + + + mdi-delete + + Delete + + + +
+ + {{ item.created_at | formatDate }} + + {{ item.updated_at | formatDate }} +
@@ -136,42 +130,42 @@ export default { text: "Title", value: "title", sortable: true, - align: "start", + align: "start" }, { text: "Created At", value: "created_at", sortable: true, - width: "20%", + width: "20%" }, { text: "Updated At", value: "updated_at", sortable: true, - width: "20%", - }, - ], + width: "20%" + } + ] }; }, methods: { ...mapActions({ deleteTemplate: "templates/deleteTemplate", readTemplates: "templates/readTemplates", - updateTemplate: "templates/updateTemplate", + updateTemplate: "templates/updateTemplate" }), handleRowClick(value) { this.$router.push({ path: `/templates/${value.id}` }); }, templateDetails(templateId) { this.$router.push({ path: `/templates/${templateId}` }); - }, + } }, computed: { - ...mapState("templates", ["templates", "isLoading"]), + ...mapState("templates", ["templates", "isLoading"]) }, mounted() { this.readTemplates(); - }, + } }; diff --git a/frontend/src/store/modules/projects.js b/frontend/src/store/modules/projects.js index a9af5613..f3c4fe1d 100644 --- a/frontend/src/store/modules/projects.js +++ b/frontend/src/store/modules/projects.js @@ -80,7 +80,7 @@ const actions = { .then(response => { const project = response.data; commit("setLoading", false); - commit("setProject", project) + commit("setProject", project); resolve(project); }) .finally(() => { From 75f66bbb574f5a5d73cc581f0349a136bf4c812a Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Fri, 8 Jan 2021 09:26:09 -0800 Subject: [PATCH 23/75] changed sidebar titles --- frontend/src/components/nav/Sidebar.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/nav/Sidebar.vue b/frontend/src/components/nav/Sidebar.vue index 8ded3e5c..1f898c59 100644 --- a/frontend/src/components/nav/Sidebar.vue +++ b/frontend/src/components/nav/Sidebar.vue @@ -68,17 +68,17 @@ export default { divider: true }, { - text: "View Applications", + text: "Applications", to: "/apps", icon: "mdi-application" }, { - text: "View Templates", + text: "Templates", to: "/templates", icon: "mdi-folder" }, { - text: "View Projects", + text: "Projects", to: "/projects", icon: "mdi-book-open" }, From f4da19e9a3fd62e09a631c59971feecbdbf98ef4 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Fri, 8 Jan 2021 10:02:46 -0800 Subject: [PATCH 24/75] changed User schema to allow for UUID, str, or int to be compatible with previous db models --- backend/api/db/schemas/users.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/api/db/schemas/users.py b/backend/api/db/schemas/users.py index f94d5da6..246f5ace 100644 --- a/backend/api/db/schemas/users.py +++ b/backend/api/db/schemas/users.py @@ -1,4 +1,6 @@ from pydantic import BaseModel +from uuid import UUID +from typing import Union class UserBase(BaseModel): @@ -10,7 +12,7 @@ class UserCreate(UserBase): class User(UserBase): - id: int + id: Union[int, str, UUID] is_active: bool class Config: From 3b8605df5bc1dfd8117220354d27fb6f751a612e Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Fri, 8 Jan 2021 14:58:07 -0800 Subject: [PATCH 25/75] working on update feature --- backend/api/actions/apps.py | 2 +- backend/api/routers/app_settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index a267c781..4dd49df7 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -252,7 +252,7 @@ def app_update(app_name): return get_apps() -def update_self(background_tasks: BackgroundTasks): +def _update_self(background_tasks: BackgroundTasks): dclient = docker.from_env() bash_command = "head -1 /proc/self/cgroup|cut -d/ -f3" yacht_id = ( diff --git a/backend/api/routers/app_settings.py b/backend/api/routers/app_settings.py index e673d864..70fef499 100644 --- a/backend/api/routers/app_settings.py +++ b/backend/api/routers/app_settings.py @@ -9,7 +9,7 @@ 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.apps import _update_self, check_self_update from ..actions import resources from ..settings import Settings import yaml From 24dd96da8b05a78b572429330036a6ebe4b7910b Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 11 Jan 2021 17:04:04 -0800 Subject: [PATCH 26/75] changed sig verification error to 401;changed action url --- backend/api/auth/auth.py | 11 +++++++++-- backend/api/routers/compose.py | 4 ++-- frontend/src/main.js | 4 ++-- frontend/src/store/modules/projects.js | 4 ++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/backend/api/auth/auth.py b/backend/api/auth/auth.py index 53daa0af..f773bea0 100644 --- a/backend/api/auth/auth.py +++ b/backend/api/auth/auth.py @@ -2,8 +2,9 @@ from ..settings import Settings -from fastapi import Depends +from fastapi import Depends, HTTPException from fastapi_jwt_auth import AuthJWT +from fastapi_jwt_auth.exceptions import JWTDecodeError from passlib import pwd from passlib.context import CryptContext @@ -31,4 +32,10 @@ def auth_check(Authorize): if settings.DISABLE_AUTH == "True": return else: - return Authorize.jwt_required() + try: + return Authorize.jwt_required() + except JWTDecodeError as exc: + status_code = exc.status_code + if exc.message == "Signature verification failed": + status_code = 401 + raise HTTPException(status_code=status_code, detail=exc.message) diff --git a/backend/api/routers/compose.py b/backend/api/routers/compose.py index fb2df33a..4f5d6b77 100644 --- a/backend/api/routers/compose.py +++ b/backend/api/routers/compose.py @@ -27,7 +27,7 @@ def get_project(project_name, Authorize: AuthJWT = Depends()): return get_compose(project_name) -@router.get("/{project_name}/{action}") +@router.get("/{project_name}/actions/{action}") def get_compose_action(project_name, action, Authorize: AuthJWT = Depends()): auth_check(Authorize) if action == "delete": @@ -42,7 +42,7 @@ def write_compose_project( auth_check(Authorize) return write_compose(compose=compose) -@router.get("/{project_name}/{action}/{app}") +@router.get("/{project_name}/actions/{action}/{app}") def get_compose_app_action(project_name, action, app, Authorize: AuthJWT = Depends()): auth_check(Authorize) return compose_app_action(project_name, action, app) diff --git a/frontend/src/main.js b/frontend/src/main.js index ccbb48b2..91c5af11 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -20,7 +20,7 @@ function createAxiosResponseInterceptor() { const interceptor = axios.interceptors.response.use( response => response, error => { - if (error.response.status !== 401 || error.response.status !== 403) { + if (error.response.status !== 401) { return Promise.reject(error); } @@ -35,7 +35,7 @@ function createAxiosResponseInterceptor() { return axios(error.response.config); }) .catch(error => { - if (error.response.status !== 401 || error.response.status !== 403) { + if (error.response.status !== 401) { return Promise.reject(error); } else { store.dispatch("auth/AUTH_LOGOUT"); diff --git a/frontend/src/store/modules/projects.js b/frontend/src/store/modules/projects.js index f3c4fe1d..ffc41b31 100644 --- a/frontend/src/store/modules/projects.js +++ b/frontend/src/store/modules/projects.js @@ -111,7 +111,7 @@ const actions = { }, ProjectAction({ commit, dispatch }, { Name, Action }) { commit("setLoading", true); - const url = `/api/compose/${Name}/${Action}`; + const url = `/api/compose/actions/${Name}/${Action}`; axios .get(url) .then(response => { @@ -128,7 +128,7 @@ const actions = { }, ProjectAppAction({ commit, dispatch }, { Project, Name, Action }) { commit("setLoading", true); - const url = `/api/compose/${Project}/${Action}/${Name}`; + const url = `/api/compose/${Project}/actions/${Action}/${Name}`; axios .get(url) .then(response => { From 9fe122fe57ac38f73c1035c5dc09f321e2fb8ddc Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Thu, 14 Jan 2021 07:47:16 -0800 Subject: [PATCH 27/75] fixing vuex store function for project actions --- .github/workflows/build-devel.yml | 38 ++++++++++++++++++-------- .github/workflows/build-do.yml | 26 ------------------ .github/workflows/build-omv.yml | 26 ------------------ .github/workflows/build-vue.yml | 25 ----------------- frontend/src/store/modules/projects.js | 2 +- 5 files changed, 28 insertions(+), 89 deletions(-) delete mode 100644 .github/workflows/build-do.yml delete mode 100644 .github/workflows/build-omv.yml delete mode 100644 .github/workflows/build-vue.yml diff --git a/.github/workflows/build-devel.yml b/.github/workflows/build-devel.yml index 2b240715..2bd724b0 100644 --- a/.github/workflows/build-devel.yml +++ b/.github/workflows/build-devel.yml @@ -11,15 +11,31 @@ jobs: steps: - name: checkout code uses: actions/checkout@v2 - - name: install buildx - id: buildx - uses: crazy-max/ghaction-docker-buildx@v1 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.CR_PAT }} + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v2 with: - qemu-version: latest - - name: login to docker hub - run: echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - - name: build the image - run: | - docker buildx build --push \ - --tag selfhostedpro/yacht:devel \ - --platform linux/amd64,linux/arm,linux/arm64 . + platforms: linux/amd64,linux/arm64,linux/arm + push: true + tags: | + selfhostedpro/yacht:devel + ghcr.io/selfhostedpro/yacht:devel diff --git a/.github/workflows/build-do.yml b/.github/workflows/build-do.yml deleted file mode 100644 index 5037c370..00000000 --- a/.github/workflows/build-do.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Build DigitalOcean - -on: - push: - branches: - - master - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: checkout code - uses: actions/checkout@v2 - - name: install buildx - id: buildx - uses: crazy-max/ghaction-docker-buildx@v1 - with: - qemu-version: latest - - name: login to docker hub - run: echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - - name: build the image - run: | - docker buildx build --push \ - --tag selfhostedpro/yacht:do \ - --build-arg VUE_APP_THEME=DigitalOcean \ - --platform linux/amd64,linux/arm,linux/arm64 . diff --git a/.github/workflows/build-omv.yml b/.github/workflows/build-omv.yml deleted file mode 100644 index 8d921296..00000000 --- a/.github/workflows/build-omv.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Build OMV - -on: - push: - branches: - - master - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: checkout code - uses: actions/checkout@v2 - - name: install buildx - id: buildx - uses: crazy-max/ghaction-docker-buildx@v1 - with: - qemu-version: latest - - name: login to docker hub - run: echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - - name: build the image - run: | - docker buildx build --push \ - --tag selfhostedpro/yacht:omv \ - --build-arg VUE_APP_THEME=OMV \ - --platform linux/amd64,linux/arm,linux/arm64 . diff --git a/.github/workflows/build-vue.yml b/.github/workflows/build-vue.yml deleted file mode 100644 index 0af66907..00000000 --- a/.github/workflows/build-vue.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Build Vue - -on: - push: - branches: - - master - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: checkout code - uses: actions/checkout@v2 - - name: install buildx - id: buildx - uses: crazy-max/ghaction-docker-buildx@v1 - with: - qemu-version: latest - - name: login to docker hub - run: echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - - name: build the image - run: | - docker buildx build --push \ - --tag selfhostedpro/yacht:vue \ - --platform linux/amd64,linux/arm,linux/arm64 . diff --git a/frontend/src/store/modules/projects.js b/frontend/src/store/modules/projects.js index ffc41b31..06fe9764 100644 --- a/frontend/src/store/modules/projects.js +++ b/frontend/src/store/modules/projects.js @@ -111,7 +111,7 @@ const actions = { }, ProjectAction({ commit, dispatch }, { Name, Action }) { commit("setLoading", true); - const url = `/api/compose/actions/${Name}/${Action}`; + const url = `/api/compose/${Name}/actions/${Action}`; axios .get(url) .then(response => { From 154b7f77b9d546633465aee27cdb14bb81a1ab37 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Fri, 15 Jan 2021 17:45:17 -0800 Subject: [PATCH 28/75] fixed DISABLE_AUTH still showing settings --- .github/workflows/build.yml | 41 +++++++++++++++++++++++++++---------- frontend/src/App.vue | 14 ++++++------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9d088542..e106495a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,15 +11,34 @@ jobs: steps: - name: checkout code uses: actions/checkout@v2 - - name: install buildx - id: buildx - uses: crazy-max/ghaction-docker-buildx@v1 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.CR_PAT }} + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v2 with: - qemu-version: latest - - name: login to docker hub - run: echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - - name: build the image - run: | - docker buildx build --push \ - --tag selfhostedpro/yacht:latest \ - --platform linux/amd64,linux/arm,linux/arm64 . + platforms: linux/amd64,linux/arm64,linux/arm + push: true + tags: | + selfhostedpro/yacht:latest + selfhostedpro/yacht:do + selfhostedpro/yacht:omv + selfhostedpro/yacht:vue + ghcr.io/selfhostedpro/yacht:latest diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 180251fd..930be393 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -33,7 +33,7 @@ From 6799fdcdc693e42034a45fe92ba46fe3b9b425e5 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Sat, 6 Feb 2021 18:44:57 -0800 Subject: [PATCH 37/75] clear env for docker-compose --- backend/api/actions/compose.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 5500afa2..da1fb2b0 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -20,7 +20,7 @@ def compose_action(name, action): action, "-d", _cwd=os.path.dirname(compose["path"]), - _env=None + _env={'clear_env': 'true'} ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) @@ -30,14 +30,14 @@ def compose_action(name, action): "up", "--no-start", _cwd=os.path.dirname(compose["path"]), - _env=None + _env={'clear_env': 'true'} ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) else: try: _action = docker_compose( - action, _cwd=os.path.dirname(compose["path"], _env=None) + action, _cwd=os.path.dirname(compose["path"],_env={'clear_env': 'true'}) ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) @@ -69,7 +69,7 @@ def compose_app_action( "-d", app, _cwd=os.path.dirname(compose["path"]), - _env=None + _env={'clear_env': 'true'} ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) @@ -80,7 +80,7 @@ def compose_app_action( "--no-start", app, _cwd=os.path.dirname(compose["path"]), - _env=None + _env={'clear_env': 'true'} ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) @@ -92,7 +92,7 @@ def compose_app_action( "--stop", app, _cwd=os.path.dirname(compose["path"]), - _env=None + _env={'clear_env': 'true'} ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) @@ -102,7 +102,7 @@ def compose_app_action( action, app, _cwd=os.path.dirname(compose["path"]), - _env=None + _env={'clear_env': 'true'} ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) From d7e9a91514d07382b4c724121a7dc1c3d6945af7 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Sun, 7 Feb 2021 12:55:57 -0800 Subject: [PATCH 38/75] fixed docker-compose down --- backend/api/actions/compose.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index da1fb2b0..db1e21dc 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -37,9 +37,10 @@ def compose_action(name, action): else: try: _action = docker_compose( - action, _cwd=os.path.dirname(compose["path"],_env={'clear_env': 'true'}) + action, _cwd=os.path.dirname(compose["path"]),_env={'clear_env': 'true'} ) except Exception as exc: + print(exc) raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) if _action.stdout.decode("UTF-8").rstrip(): _output = _action.stdout.decode("UTF-8").rstrip() From 2e6a60798b7d4b4f4240d14422d16b73a9d130eb Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Sun, 7 Feb 2021 16:26:16 -0800 Subject: [PATCH 39/75] added VUE_APP_VERSION --- .github/workflows/build-devel.yml | 8 +++++++- Dockerfile | 2 ++ frontend/src/components/serverSettings/ServerInfo.vue | 3 +-- frontend/src/views/ServerSettings.vue | 6 ++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-devel.yml b/.github/workflows/build-devel.yml index 2bd724b0..5e9a6360 100644 --- a/.github/workflows/build-devel.yml +++ b/.github/workflows/build-devel.yml @@ -9,7 +9,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - name: checkout code + - name: Get date + id: date + run: echo "name=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + + - name: Checkout code uses: actions/checkout@v2 - name: Set up QEMU @@ -36,6 +40,8 @@ jobs: with: platforms: linux/amd64,linux/arm64,linux/arm push: true + args: | + VUE_APP_VERSION=devel-${{ steps.date.outputs.date }} tags: | selfhostedpro/yacht:devel ghcr.io/selfhostedpro/yacht:devel diff --git a/Dockerfile b/Dockerfile index e4ff7b40..3a95716f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,8 @@ RUN npm run build FROM lsiobase/alpine:3.12 as deploy-stage # MAINTANER Your Name "info@selfhosted.pro" +ARG VUE_APP_VERSION=devel + # Set Variables ENV PYTHONIOENCODING=UTF-8 ENV THEME=Default diff --git a/frontend/src/components/serverSettings/ServerInfo.vue b/frontend/src/components/serverSettings/ServerInfo.vue index 8b8f87c1..84bbb23e 100644 --- a/frontend/src/components/serverSettings/ServerInfo.vue +++ b/frontend/src/components/serverSettings/ServerInfo.vue @@ -1,5 +1,5 @@ diff --git a/frontend/src/views/ServerSettings.vue b/frontend/src/views/ServerSettings.vue index 2db31318..67f046fa 100644 --- a/frontend/src/views/ServerSettings.vue +++ b/frontend/src/views/ServerSettings.vue @@ -21,6 +21,7 @@ + Version: {{get_version()}} @@ -32,6 +33,11 @@ import ServerSettingsNav from "../components/serverSettings/ServerSettingsNav"; export default { components: { Nav: ServerSettingsNav + }, + methods: { + get_version(){ + return process.env.VUE_APP_VERSION + }, } }; From 8faf0a6533f0065deb73b0956040777559df2928 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Sun, 7 Feb 2021 17:57:24 -0800 Subject: [PATCH 40/75] fixing build var --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3a95716f..4a1aa49b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ FROM lsiobase/alpine:3.12 as deploy-stage # MAINTANER Your Name "info@selfhosted.pro" ARG VUE_APP_VERSION=devel - +ENV VUE_APP_VERSION=${VUE_APP_VERSION} # Set Variables ENV PYTHONIOENCODING=UTF-8 ENV THEME=Default From da0cf68896b003323f7afb236bf469925310bf72 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Sun, 7 Feb 2021 20:57:13 -0800 Subject: [PATCH 41/75] fixing versioning --- .github/workflows/build-devel.yml | 4 ++-- frontend/src/views/ServerSettings.vue | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-devel.yml b/.github/workflows/build-devel.yml index 5e9a6360..cc63960c 100644 --- a/.github/workflows/build-devel.yml +++ b/.github/workflows/build-devel.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Get date id: date - run: echo "name=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + run: echo "NOW::$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV - name: Checkout code uses: actions/checkout@v2 @@ -41,7 +41,7 @@ jobs: platforms: linux/amd64,linux/arm64,linux/arm push: true args: | - VUE_APP_VERSION=devel-${{ steps.date.outputs.date }} + VUE_APP_VERSION=devel-$NOW tags: | selfhostedpro/yacht:devel ghcr.io/selfhostedpro/yacht:devel diff --git a/frontend/src/views/ServerSettings.vue b/frontend/src/views/ServerSettings.vue index 67f046fa..625a0f4d 100644 --- a/frontend/src/views/ServerSettings.vue +++ b/frontend/src/views/ServerSettings.vue @@ -32,13 +32,15 @@ import ServerSettingsNav from "../components/serverSettings/ServerSettingsNav"; export default { components: { - Nav: ServerSettingsNav + Nav: ServerSettingsNav, }, methods: { - get_version(){ - return process.env.VUE_APP_VERSION + get_version() { + if (process.env.VUE_APP_VERSION) { + return process.env.VUE_APP_VERSION; + } else return null; }, - } + }, }; From 11e5631abe3a8f40e1d10d2e731c5b30e0f2b5c8 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 06:13:52 -0800 Subject: [PATCH 42/75] triggering action --- frontend/src/components/HelloWorld.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/HelloWorld.vue b/frontend/src/components/HelloWorld.vue index acf3c839..ce082ff4 100644 --- a/frontend/src/components/HelloWorld.vue +++ b/frontend/src/components/HelloWorld.vue @@ -19,7 +19,7 @@ For help and collaboration with other Vuetify developers,
please join our online Discord CommunityDiscord Community test

From f94a40fc67183423a593c423720ea921850b1d36 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 06:15:35 -0800 Subject: [PATCH 43/75] updating action --- .github/workflows/build-devel.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-devel.yml b/.github/workflows/build-devel.yml index cc63960c..d8caeeb4 100644 --- a/.github/workflows/build-devel.yml +++ b/.github/workflows/build-devel.yml @@ -9,9 +9,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - name: Get date - id: date - run: echo "NOW::$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV + - name: Get current time + uses: 1466587594/get-current-time@v2 + id: current-time + with: + format: YYYYMMDD-HH - name: Checkout code uses: actions/checkout@v2 @@ -37,11 +39,13 @@ jobs: - name: Build and push uses: docker/build-push-action@v2 + env: + F_TIME: "${{ steps.current-time.outputs.formattedTime }}" with: platforms: linux/amd64,linux/arm64,linux/arm push: true - args: | - VUE_APP_VERSION=devel-$NOW + build-args: | + VUE_APP_VERSION=devel-$F_TIME tags: | selfhostedpro/yacht:devel ghcr.io/selfhostedpro/yacht:devel From 43173a284f2e1623a50dfeb0f059fc83c0564f6b Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 07:10:38 -0800 Subject: [PATCH 44/75] fixed version info --- Dockerfile | 5 +++-- frontend/package.json | 2 +- frontend/src/views/ServerSettings.vue | 16 +++++++--------- root/etc/cont-init.d/32-env | 1 + 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4a1aa49b..c06910c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ # Build Vue FROM node:14.5.0-alpine as build-stage +ARG VUE_APP_VERSION +ENV VUE_APP_VERSION=${VUE_APP_VERSION} + WORKDIR /app COPY ./frontend/package*.json ./ RUN npm install @@ -11,8 +14,6 @@ RUN npm run build FROM lsiobase/alpine:3.12 as deploy-stage # MAINTANER Your Name "info@selfhosted.pro" -ARG VUE_APP_VERSION=devel -ENV VUE_APP_VERSION=${VUE_APP_VERSION} # Set Variables ENV PYTHONIOENCODING=UTF-8 ENV THEME=Default diff --git a/frontend/package.json b/frontend/package.json index 592d5679..aa396a0a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "yacht", - "version": "0.1.0", + "version": "0.0.6", "private": true, "scripts": { "serve": "vue-cli-service serve", diff --git a/frontend/src/views/ServerSettings.vue b/frontend/src/views/ServerSettings.vue index 625a0f4d..c5c7cc7c 100644 --- a/frontend/src/views/ServerSettings.vue +++ b/frontend/src/views/ServerSettings.vue @@ -21,7 +21,7 @@ - Version: {{get_version()}} + Version: {{version}} @@ -31,16 +31,14 @@ // import { mapState } from "vuex"; import ServerSettingsNav from "../components/serverSettings/ServerSettingsNav"; export default { + data() { + return { + version: process.env.VUE_APP_VERSION || 'unreleased', + }; + }, components: { Nav: ServerSettingsNav, - }, - methods: { - get_version() { - if (process.env.VUE_APP_VERSION) { - return process.env.VUE_APP_VERSION; - } else return null; - }, - }, + } }; diff --git a/root/etc/cont-init.d/32-env b/root/etc/cont-init.d/32-env index 8d76ca9e..639a78c1 100644 --- a/root/etc/cont-init.d/32-env +++ b/root/etc/cont-init.d/32-env @@ -9,5 +9,6 @@ do sed -i 's|VUE_APP_THEME|'${THEME}'|g' $file sed -i 's|VUE_APP_LOGO|'${LOGO}'|g' $file + # sed -i 's|VUE_APP_VERSION|'${VERSION}'|g' $file done \ No newline at end of file From 2e5423931c7260975d1124dfa0e82f83d12f2312 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 08:18:55 -0800 Subject: [PATCH 45/75] change version build-arg --- .github/workflows/build-devel.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build-devel.yml b/.github/workflows/build-devel.yml index d8caeeb4..ec3efa4a 100644 --- a/.github/workflows/build-devel.yml +++ b/.github/workflows/build-devel.yml @@ -39,13 +39,11 @@ jobs: - name: Build and push uses: docker/build-push-action@v2 - env: - F_TIME: "${{ steps.current-time.outputs.formattedTime }}" with: platforms: linux/amd64,linux/arm64,linux/arm push: true build-args: | - VUE_APP_VERSION=devel-$F_TIME + VUE_APP_VERSION=devel-${{ steps.current-time.outputs.formattedTime }} tags: | selfhostedpro/yacht:devel ghcr.io/selfhostedpro/yacht:devel From 67e5bfd53d0a1f0b67bf58279eb8f524b07f2db0 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 08:34:08 -0800 Subject: [PATCH 46/75] setting up cache for builds --- .github/workflows/build-devel.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/build-devel.yml b/.github/workflows/build-devel.yml index ec3efa4a..7f5373b1 100644 --- a/.github/workflows/build-devel.yml +++ b/.github/workflows/build-devel.yml @@ -24,6 +24,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 + - name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: Login to GitHub Container Registry uses: docker/login-action@v1 with: @@ -47,3 +55,5 @@ jobs: tags: | selfhostedpro/yacht:devel ghcr.io/selfhostedpro/yacht:devel + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache From 3462ac81be1209b1d371106a03f6ffd5060f31cf Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 09:49:57 -0800 Subject: [PATCH 47/75] fixed error handling on compose projects --- backend/api/actions/compose.py | 1 - frontend/src/store/modules/projects.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index db1e21dc..737e9cfc 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -40,7 +40,6 @@ def compose_action(name, action): action, _cwd=os.path.dirname(compose["path"]),_env={'clear_env': 'true'} ) except Exception as exc: - print(exc) raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) if _action.stdout.decode("UTF-8").rstrip(): _output = _action.stdout.decode("UTF-8").rstrip() diff --git a/frontend/src/store/modules/projects.js b/frontend/src/store/modules/projects.js index d0d20737..8cc06938 100644 --- a/frontend/src/store/modules/projects.js +++ b/frontend/src/store/modules/projects.js @@ -123,6 +123,7 @@ const actions = { const projects = response.data; commit("setProjects", projects); dispatch("apps/readApps", null, { root: true }); + commit("snackbar/setMessage", `${Name} has been ${Action}ed.`, { root: true }) }) .catch(err => { console.log(err) @@ -131,7 +132,6 @@ const actions = { .finally(() => { commit("setLoading", false); commit("setAction", '') - commit("snackbar/setMessage", `${Name} has been ${Action}ed.`, { root: true }) }); }, ProjectAppAction({ commit, dispatch }, { Project, Name, Action }) { @@ -144,6 +144,7 @@ const actions = { const projects = response.data; commit("setProjects", projects); dispatch("apps/readApps", null, { root: true }); + commit("snackbar/setMessage", `${Name} has been ${Action}ed.`, { root: true }) }) .catch(err => { commit("snackbar/setErr", err, { root: true }); @@ -151,7 +152,6 @@ const actions = { .finally(() => { commit("setLoading", false); commit("setAction", '') - commit("snackbar/setMessage", `${Name} has been ${Action}ed.`, { root: true }) }); } }; From fcef6990e6bcac2bf4d08d9e77c831e95d09a253 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 09:51:37 -0800 Subject: [PATCH 48/75] fixed error handling on compose projects --- backend/api/actions/compose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 737e9cfc..80858a54 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -61,7 +61,7 @@ def compose_app_action( files = find_yml_files(settings.COMPOSE_DIR) compose = get_compose(name) - print("docker-compose -f " + compose["path"] + " " + action + " " + app) + print('RUNNING: '+compose["path"] + " docker-compose " + " " + action + " " + app) if action == "up": try: _action = docker_compose( From c8765cf6c0289d4b264bec7b089f5099ea131445 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 09:54:26 -0800 Subject: [PATCH 49/75] changed version format for devel --- .github/workflows/build-devel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-devel.yml b/.github/workflows/build-devel.yml index 7f5373b1..742ddf57 100644 --- a/.github/workflows/build-devel.yml +++ b/.github/workflows/build-devel.yml @@ -13,7 +13,7 @@ jobs: uses: 1466587594/get-current-time@v2 id: current-time with: - format: YYYYMMDD-HH + format: YYYY-MM-DD-HH - name: Checkout code uses: actions/checkout@v2 From cd36e02b536b14e4aac07f0623f7390e7737c9e3 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 10:51:47 -0800 Subject: [PATCH 50/75] check for DOCKER_HOST for docker-compose commands --- backend/api/actions/compose.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 80858a54..74645d6d 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -14,13 +14,14 @@ def compose_action(name, action): files = find_yml_files(settings.COMPOSE_DIR) compose = get_compose(name) + env = os.environ.copy() if action == "up": try: _action = docker_compose( action, "-d", _cwd=os.path.dirname(compose["path"]), - _env={'clear_env': 'true'} + _env=check_dockerhost(env) ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) @@ -30,14 +31,14 @@ def compose_action(name, action): "up", "--no-start", _cwd=os.path.dirname(compose["path"]), - _env={'clear_env': 'true'} + _env=check_dockerhost(env) ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) else: try: _action = docker_compose( - action, _cwd=os.path.dirname(compose["path"]),_env={'clear_env': 'true'} + action, _cwd=os.path.dirname(compose["path"]),_env=check_dockerhost(env) ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) @@ -52,6 +53,11 @@ def compose_action(name, action): print(_output) return get_compose_projects() +def check_dockerhost(environment): + if environment.get("DOCKER_HOST"): + return environment["DOCKER_HOST"] + else: + return {'clear_env': 'true'} def compose_app_action( name, @@ -61,6 +67,7 @@ def compose_app_action( files = find_yml_files(settings.COMPOSE_DIR) compose = get_compose(name) + env = os.environ.copy() print('RUNNING: '+compose["path"] + " docker-compose " + " " + action + " " + app) if action == "up": try: @@ -69,7 +76,7 @@ def compose_app_action( "-d", app, _cwd=os.path.dirname(compose["path"]), - _env={'clear_env': 'true'} + _env=check_dockerhost(env) ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) @@ -80,7 +87,7 @@ def compose_app_action( "--no-start", app, _cwd=os.path.dirname(compose["path"]), - _env={'clear_env': 'true'} + _env=check_dockerhost(env) ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) @@ -92,7 +99,7 @@ def compose_app_action( "--stop", app, _cwd=os.path.dirname(compose["path"]), - _env={'clear_env': 'true'} + _env=check_dockerhost(env) ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) @@ -102,7 +109,7 @@ def compose_app_action( action, app, _cwd=os.path.dirname(compose["path"]), - _env={'clear_env': 'true'} + _env=check_dockerhost(env) ) except Exception as exc: raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) From 329bf6306ac4a5ac9a0dbb60e7ef9b70c7fc2c47 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 10:55:00 -0800 Subject: [PATCH 51/75] added build version tags to devel images --- .github/workflows/build-devel.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-devel.yml b/.github/workflows/build-devel.yml index 742ddf57..a59d063a 100644 --- a/.github/workflows/build-devel.yml +++ b/.github/workflows/build-devel.yml @@ -54,6 +54,8 @@ jobs: VUE_APP_VERSION=devel-${{ steps.current-time.outputs.formattedTime }} tags: | selfhostedpro/yacht:devel + selfhostedpro/yacht:devel-${{ steps.current-time.outputs.formattedTime }} ghcr.io/selfhostedpro/yacht:devel + ghcr.io/selfhostedpro/yacht:devel-${{ steps.current-time.outputs.formattedTime }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache From b483cb24dd2868b33caa7948cd2d363945b11c2f Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 13:37:16 -0800 Subject: [PATCH 52/75] fixed DOCKER_HOST format; added error handling for docker socket on readapps --- backend/api/actions/apps.py | 5 ++++- backend/api/actions/compose.py | 37 ++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index 11c8fe33..5cd2e25d 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -46,7 +46,10 @@ def check_app_update(app_name): def get_apps(): apps_list = [] - dclient = docker.from_env() + try: + dclient = docker.from_env() + except docker.errors.DockerException as exc: + raise HTTPException(status_code=500, detail=exc.args) try: apps = dclient.containers.list(all=True) except Exception as exc: diff --git a/backend/api/actions/compose.py b/backend/api/actions/compose.py index 74645d6d..e4884d0f 100644 --- a/backend/api/actions/compose.py +++ b/backend/api/actions/compose.py @@ -24,7 +24,10 @@ def compose_action(name, action): _env=check_dockerhost(env) ) except Exception as exc: - raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + if hasattr(exc, 'stderr'): + raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + else: + raise HTTPException(400, exc) elif action == "create": try: _action = docker_compose( @@ -34,14 +37,20 @@ def compose_action(name, action): _env=check_dockerhost(env) ) except Exception as exc: - raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + if hasattr(exc, 'stderr'): + raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + else: + raise HTTPException(400, exc) else: try: _action = docker_compose( action, _cwd=os.path.dirname(compose["path"]),_env=check_dockerhost(env) ) except Exception as exc: - raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + if hasattr(exc, 'stderr'): + raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + else: + raise HTTPException(400, exc) if _action.stdout.decode("UTF-8").rstrip(): _output = _action.stdout.decode("UTF-8").rstrip() elif _action.stderr.decode("UTF-8").rstrip(): @@ -55,7 +64,7 @@ def compose_action(name, action): def check_dockerhost(environment): if environment.get("DOCKER_HOST"): - return environment["DOCKER_HOST"] + return {'DOCKER_HOST': environment["DOCKER_HOST"]} else: return {'clear_env': 'true'} @@ -79,7 +88,10 @@ def compose_app_action( _env=check_dockerhost(env) ) except Exception as exc: - raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + if hasattr(exc, 'stderr'): + raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + else: + raise HTTPException(400, exc) elif action == "create": try: _action = docker_compose( @@ -90,7 +102,10 @@ def compose_app_action( _env=check_dockerhost(env) ) except Exception as exc: - raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + if hasattr(exc, 'stderr'): + raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + else: + raise HTTPException(400, exc) elif action == "rm": try: _action = docker_compose( @@ -102,7 +117,10 @@ def compose_app_action( _env=check_dockerhost(env) ) except Exception as exc: - raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + if hasattr(exc, 'stderr'): + raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + else: + raise HTTPException(400, exc) else: try: _action = docker_compose( @@ -112,7 +130,10 @@ def compose_app_action( _env=check_dockerhost(env) ) except Exception as exc: - raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + if hasattr(exc, 'stderr'): + raise HTTPException(400, exc.stderr.decode("UTF-8").rstrip()) + else: + raise HTTPException(400, exc) if _action.stdout.decode("UTF-8").rstrip(): output = _action.stdout.decode("UTF-8").rstrip() elif _action.stderr.decode("UTF-8").rstrip(): From 62a01c74a3b485f508f0face37c415368c03c15b Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 13:38:08 -0800 Subject: [PATCH 53/75] changed time format in version --- .github/workflows/build-devel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-devel.yml b/.github/workflows/build-devel.yml index a59d063a..a9a4d4ba 100644 --- a/.github/workflows/build-devel.yml +++ b/.github/workflows/build-devel.yml @@ -13,7 +13,7 @@ jobs: uses: 1466587594/get-current-time@v2 id: current-time with: - format: YYYY-MM-DD-HH + format: YYYY-MM-DD--HH - name: Checkout code uses: actions/checkout@v2 From 021302789dc31b2d914fe6e19fdc177c3c25f1fb Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 15:50:37 -0800 Subject: [PATCH 54/75] fixing appbar menu disappearing even if logged in and auth is enabled --- frontend/src/store/actions/auth.js | 1 + frontend/src/store/modules/auth.js | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/frontend/src/store/actions/auth.js b/frontend/src/store/actions/auth.js index 6c731172..afef8746 100644 --- a/frontend/src/store/actions/auth.js +++ b/frontend/src/store/actions/auth.js @@ -7,3 +7,4 @@ export const AUTH_REFRESH = "AUTH_REFRESH"; export const AUTH_CHANGE_PASS = "AUTH_CHANGE_PASS"; export const AUTH_CHECK = "AUTH_CHECK"; export const AUTH_DISABLED = "AUTH_DISABLED"; +export const AUTH_ENABLED = "AUTH_ENABLED"; diff --git a/frontend/src/store/modules/auth.js b/frontend/src/store/modules/auth.js index 532ebffc..decf80d2 100644 --- a/frontend/src/store/modules/auth.js +++ b/frontend/src/store/modules/auth.js @@ -7,7 +7,8 @@ import { AUTH_CLEAR, AUTH_CHANGE_PASS, AUTH_CHECK, - AUTH_DISABLED + AUTH_DISABLED, + AUTH_ENABLED } from "../actions/auth"; import axios from "axios"; import router from "@/router/index"; @@ -113,10 +114,17 @@ const actions = { const url = "/api/auth/me"; axios.get(url).then(resp => { console.log(resp); - localStorage.setItem("username", resp.data.username); - commit(AUTH_DISABLED); - commit(AUTH_SUCCESS, resp); - }); + if (resp.data.authDisabled == true) { + localStorage.setItem("username", resp.data.username); + commit(AUTH_DISABLED); + commit(AUTH_SUCCESS, resp); + } else { + commit(AUTH_ENABLED) + } + }) + .catch(() => { + commit(AUTH_ENABLED); + }) } }; @@ -137,6 +145,9 @@ const mutations = { [AUTH_DISABLED]: state => { state.authDisabled = true; }, + [AUTH_ENABLED]: state => { + state.authDisabled = false + }, [AUTH_CLEAR]: state => { state.accessToken = ""; state.refreshToken = ""; From cd01353f04cc6fed4c1bc7802ba748772343b9b6 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 17:55:14 -0800 Subject: [PATCH 55/75] added ability to edit an application --- backend/api/actions/apps.py | 27 +++- backend/api/db/schemas/apps.py | 1 + .../applications/ApplicationsForm.vue | 146 ++++++++++++++---- .../applications/ApplicationsList.vue | 50 +++--- frontend/src/router/index.js | 7 +- frontend/src/store/modules/templates.js | 2 +- 6 files changed, 174 insertions(+), 59 deletions(-) diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index 5cd2e25d..fa178166 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -121,13 +121,16 @@ def deploy_app(template: schemas.DeployForm): conv_labels2data(template.labels), conv_sysctls2data(template.sysctls), conv_caps2data(template.cap_add), + edit=template.edit or False ) - except HTTPException as exc: - raise HTTPException(status_code=exc.status_code, detail=exc.detail) + # except HTTPException as exc: + # raise HTTPException(status_code=exc.status_code, detail=exc.detail) + # except Exception as exc: + # raise HTTPException( + # status_code=exc.response.status_code, detail=exc.explanation + # ) except Exception as exc: - raise HTTPException( - status_code=exc.response.status_code, detail=exc.explanation - ) + print(exc) print("done deploying") return schemas.DeployLogs(logs=launch.logs()) @@ -158,8 +161,22 @@ def launch_app( labels, sysctls, caps, + edit ): dclient = docker.from_env() + if edit != False: + try: + dclient.containers.get(name) + try: + running_app = dclient.containers.get(name) + running_app.remove(force=True) + except Exception as e: + raise e + except Exception as e: + raise e + + + combined_labels = Merge(portlabels, labels) try: lauch = dclient.containers.run( diff --git a/backend/api/db/schemas/apps.py b/backend/api/db/schemas/apps.py index 19e11989..0edd1119 100644 --- a/backend/api/db/schemas/apps.py +++ b/backend/api/db/schemas/apps.py @@ -50,6 +50,7 @@ class DeployForm(BaseModel): cap_add: Optional[List[str]] network_mode: Optional[str] network: Optional[str] + edit: Optional[bool] # LOGS # diff --git a/frontend/src/components/applications/ApplicationsForm.vue b/frontend/src/components/applications/ApplicationsForm.vue index 8689c77e..7d95a20c 100644 --- a/frontend/src/components/applications/ApplicationsForm.vue +++ b/frontend/src/components/applications/ApplicationsForm.vue @@ -677,7 +677,7 @@ import { ValidationObserver, ValidationProvider } from "vee-validate"; export default { components: { ValidationProvider, - ValidationObserver + ValidationObserver, }, data() { return { @@ -698,7 +698,7 @@ export default { devices: [], labels: [], sysctls: [], - cap_add: [] + cap_add: [], }, network_modes: ["bridge", "none", "host"], isLoading: false, @@ -725,21 +725,22 @@ export default { "SYS_BOOT", "LEASE", "WAKE_ALARM", - "BLOCK_SUSPEND" - ] + "BLOCK_SUSPEND", + ], }; }, calculated: { ...mapState("networks", ["networks"]), - ...mapState("volumes", ["volumes"]) + ...mapState("volumes", ["volumes"]), }, methods: { ...mapActions({ - readApp: "templates/readApp", - readNetworks: "networks/_readNetworks" + readTemplateApp: "templates/readTemplateApp", + readNetworks: "networks/_readNetworks", + readApp: "apps/readApp", }), ...mapMutations({ - setErr: "snackbar/setErr" + setErr: "snackbar/setErr", }), addPort() { this.form.ports.push({ hport: "", cport: "", proto: "tcp" }); @@ -783,6 +784,65 @@ export default { removeCap_add(index) { this.form.cap_add.splice(index, 1); }, + transform_ports(ports) { + let portlist = []; + for (let port in ports) { + let _port = port.split("/"); + var cport = _port[0]; + var hport = ports[port][0].HostPort; + var proto = _port[1]; + var label = ""; + let port_entry = { + cport: cport, + hport: hport, + proto: proto, + label: label, + }; + portlist.push(port_entry); + } + return portlist; + }, + transform_volumes(volumes) { + let volumelist = []; + for (let volume in volumes) { + let container = volumes[volume].Destination || ""; + let bind = volumes[volume].Source || ""; + let volume_entry = { + bind: bind, + container: container, + }; + volumelist.push(volume_entry); + } + return volumelist; + }, + transform_env(envs) { + let envlist = []; + for (let env in envs) { + let _env = envs[env].split("="); + let name = _env[0]; + let value = _env[1]; + let env_entry = { + label: name, + name: name, + default: value, + }; + envlist.push(env_entry); + } + return envlist; + }, + transform_labels(labels) { + let labellist = []; + for (let label in labels) { + let name = label; + let value = labels[label]; + let label_entry = { + name: name, + value: value, + }; + labellist.push(label_entry); + } + return labellist; + }, nextStep(n) { if (n === this.deploySteps) { // this.deployStep = 1; @@ -801,7 +861,7 @@ export default { this.isLoading = false; this.$router.push({ name: "View Applications" }); }) - .catch(err => { + .catch((err) => { this.isLoading = false; this.setErr(err); }); @@ -813,36 +873,56 @@ export default { } }, async populateForm() { - const appId = this.$route.params.appId; - if (appId != null) { - try { - const app = await this.readApp(appId); - this.form = { - name: app.name || "", - image: app.image || "", - restart_policy: app.restart_policy || "", - network: app.network, - network_mode: app.network_mode, - ports: app.ports || [], - volumes: app.volumes || [], - env: app.env || [], - devices: app.devices || [], - labels: app.labels || [], - sysctls: app.sysctls || [], - cap_add: app.cap_add || [] - }; - this.notes = app.notes || null; - } catch (error) { - console.error(error, error.response); - this.setErr(error); + if (this.$route.params.appId) { + const appId = this.$route.params.appId; + if (appId != null) { + try { + const app = await this.readTemplateApp(appId); + this.form = { + name: app.name || "", + image: app.image || "", + restart_policy: app.restart_policy || "", + network: app.network, + network_mode: app.network_mode, + ports: app.ports || [], + volumes: app.volumes || [], + env: app.env || [], + devices: app.devices || [], + labels: app.labels || [], + sysctls: app.sysctls || [], + cap_add: app.cap_add || [], + }; + this.notes = app.notes || null; + } catch (error) { + console.error(error, error.response); + this.setErr(error); + } } + } else if (this.$route.params.appName) { + const appName = this.$route.params.appName; + const app = await this.readApp(appName); + console.log(app); + this.form = { + name: app.name || "", + image: app.Config.Image || "", + restart_policy: app.HostConfig.RestartPolicy.Name || "", + network: Object.keys(app.NetworkSettings.Networks)[0] || "", + ports: this.transform_ports(app.ports) || [], + volumes: this.transform_volumes(app.Mounts) || [], + env: this.transform_env(app.Config.Env) || [], + devices: [], + labels: this.transform_labels(app.Config.Labels) || [], + sysctls: this.transform_labels(app.HostConfig.Sysctls), + cap_add: app.HostConfig.CapAdd || [], + edit: true + }; } - } + }, }, async created() { await this.populateForm(); await this.populateNetworks(); - } + }, }; diff --git a/frontend/src/components/applications/ApplicationsList.vue b/frontend/src/components/applications/ApplicationsList.vue index b26e32cd..7679b84d 100644 --- a/frontend/src/components/applications/ApplicationsList.vue +++ b/frontend/src/components/applications/ApplicationsList.vue @@ -52,10 +52,10 @@ color="secondary" > + + Columns + + + + + mdi-file-document-edit-outline + + Edit + + @@ -263,37 +272,37 @@ export default { text: "Name", value: "name", sortable: true, - align: "start" + align: "start", // width: "30%", }, project: { text: "Project", value: "project", - sortable: true + sortable: true, }, status: { text: "Status", value: "status", - sortable: true + sortable: true, // width: "10%", }, image: { text: "Image", value: "image", - sortable: true + sortable: true, }, ports: { text: "Ports", value: "ports", - sortable: true + sortable: true, }, created: { text: "Created At", value: "created", - sortable: true - } + sortable: true, + }, }, - selectedHeaders: [] + selectedHeaders: [], }; }, methods: { @@ -301,17 +310,20 @@ export default { readApps: "apps/readApps", AppAction: "apps/AppAction", Update: "apps/AppUpdate", - checkUpdate: "apps/checkAppUpdate" + checkUpdate: "apps/checkAppUpdate", }), handleRowClick(appName) { this.$router.push({ path: `/apps${appName.Name}/info` }); }, + editClick(appName){ + this.$router.push({path: `/apps/edit/${appName.Name}`}) + }, convPorts(data) { let o = []; for (var k in data) { if (data[k]) { o = o.concat( - data[k].map(function(x) { + data[k].map(function (x) { return { cport: k, hip: x.HostIp, hport: x.HostPort }; }) ); @@ -321,7 +333,7 @@ export default { }, refresh() { this.readApps(); - } + }, }, computed: { ...mapState("apps", [ @@ -329,11 +341,11 @@ export default { "isLoading", "isLoadingValue", "action", - "updatable" + "updatable", ]), showHeaders() { - return this.headers.filter(s => this.selectedHeaders.includes(s)); - } + return this.headers.filter((s) => this.selectedHeaders.includes(s)); + }, }, created() { this.headers = Object.values(this.headersMap); @@ -341,7 +353,7 @@ export default { }, mounted() { this.readApps(); - } + }, }; diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index e5ecf3db..850f6562 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -93,6 +93,11 @@ const routes = [ path: "deploy/:appId", component: ApplicationsForm }, + { + name: "Edit", + path: "edit/:appName", + component: ApplicationsForm + }, { name: "Deploy from Template", path: "templates", @@ -131,7 +136,7 @@ const routes = [ name: "Stats", path: "stats", component: AppStats - } + }, ] } ] diff --git a/frontend/src/store/modules/templates.js b/frontend/src/store/modules/templates.js index d657f963..3e1e8281 100644 --- a/frontend/src/store/modules/templates.js +++ b/frontend/src/store/modules/templates.js @@ -153,7 +153,7 @@ const actions = { commit("setLoading", false); }); }, - readApp({ commit }, Name) { + readTemplateApp({ commit }, Name) { const url = `/api/templates/app/${Name}`; commit("setLoading", true); return new Promise((resolve, reject) => { From 4489242f86a47668cc3fb86de49df2db0e08defb Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 17:56:37 -0800 Subject: [PATCH 56/75] added edit button in application details --- .../applications/ApplicationDetails.vue | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/applications/ApplicationDetails.vue b/frontend/src/components/applications/ApplicationDetails.vue index 1100af4a..22ef1de8 100644 --- a/frontend/src/components/applications/ApplicationDetails.vue +++ b/frontend/src/components/applications/ApplicationDetails.vue @@ -14,6 +14,15 @@
+ + + mdi-file-document-edit-outline + + Edit + + @@ -91,7 +100,7 @@ import ApplicationDetailsNav from "./ApplicationDetailsComponents/ApplicationDet import { mapActions, mapGetters, mapState } from "vuex"; export default { components: { - Nav: ApplicationDetailsNav + Nav: ApplicationDetailsNav, }, data() { return { @@ -101,28 +110,31 @@ export default { cpu_percent: [], mem_percent: [], mem_current: [], - mem_total: [] + mem_total: [], }, connection: null, - statConnection: null + statConnection: null, }; }, computed: { ...mapState("apps", ["apps", "app", "isLoading", "processes"]), ...mapGetters({ - getAppByName: "apps/getAppByName" + getAppByName: "apps/getAppByName", }), app() { const appName = this.$route.params.appName; return this.getAppByName(appName); - } + }, }, methods: { ...mapActions({ readApp: "apps/readApp", readAppProcesses: "apps/readAppProcesses", - AppAction: "apps/AppAction" + AppAction: "apps/AppAction", }), + editClick(appName) { + this.$router.push({ path: `/apps/edit/${appName.Name}` }); + }, refresh() { const appName = this.$route.params.appName; this.readApp(appName); @@ -148,7 +160,7 @@ export default { ); }; - this.connection.onmessage = event => { + this.connection.onmessage = (event) => { this.logs.push(event.data); }; }, @@ -174,7 +186,7 @@ export default { JSON.stringify({ type: "onopen", data: "Connected!" }) ); }; - this.statConnection.onmessage = event => { + this.statConnection.onmessage = (event) => { let statsGroup = JSON.parse(event.data); this.stats.time.push(statsGroup.time); this.stats.cpu_percent.push(Math.round(statsGroup.cpu_percent)); @@ -199,7 +211,7 @@ export default { ); this.statConnection.close(1000, "Leaving page or refreshing"); // this.statConnection.close(1001, "Leaving page or refreshing"); - } + }, }, created() { const appName = this.$route.params.appName; @@ -216,7 +228,7 @@ export default { beforeDestroy() { this.closeLogs(); this.closeStats(); - } + }, }; From 82c05833b6d820b2f92d314a71319d7617c22081 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Mon, 8 Feb 2021 18:20:59 -0800 Subject: [PATCH 57/75] added port label to edit form --- backend/api/actions/apps.py | 15 +++++++-------- .../components/applications/ApplicationsForm.vue | 8 ++++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index fa178166..0262760d 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -123,14 +123,12 @@ def deploy_app(template: schemas.DeployForm): conv_caps2data(template.cap_add), edit=template.edit or False ) - # except HTTPException as exc: - # raise HTTPException(status_code=exc.status_code, detail=exc.detail) - # except Exception as exc: - # raise HTTPException( - # status_code=exc.response.status_code, detail=exc.explanation - # ) + except HTTPException as exc: + raise HTTPException(status_code=exc.status_code, detail=exc.detail) except Exception as exc: - print(exc) + raise HTTPException( + status_code=exc.response.status_code, detail=exc.explanation + ) print("done deploying") return schemas.DeployLogs(logs=launch.logs()) @@ -138,7 +136,8 @@ def deploy_app(template: schemas.DeployForm): def Merge(dict1, dict2): if dict1 and dict2: - return dict2.update(dict1) + updated_dict = dict2.update(dict1) + return dict2 elif dict1: return dict1 elif dict2: diff --git a/frontend/src/components/applications/ApplicationsForm.vue b/frontend/src/components/applications/ApplicationsForm.vue index 7d95a20c..d488c9b9 100644 --- a/frontend/src/components/applications/ApplicationsForm.vue +++ b/frontend/src/components/applications/ApplicationsForm.vue @@ -3,7 +3,7 @@

Deploy {{ form.name }} Date: Tue, 9 Feb 2021 08:47:26 -0800 Subject: [PATCH 58/75] allow empty port settings in edit --- frontend/src/components/applications/ApplicationsForm.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/applications/ApplicationsForm.vue b/frontend/src/components/applications/ApplicationsForm.vue index d488c9b9..50456b9d 100644 --- a/frontend/src/components/applications/ApplicationsForm.vue +++ b/frontend/src/components/applications/ApplicationsForm.vue @@ -787,10 +787,10 @@ export default { transform_ports(ports, app) { let portlist = []; for (let port in ports) { - let _port = port.split("/"); - var cport = _port[0]; - var hport = ports[port][0].HostPort; - var proto = _port[1]; + let _port = port.split("/") || ''; + var cport = _port[0] || ''; + var hport = ports[port][0].HostPort || ''; + var proto = _port[1] || ''; var label = app.Config.Labels[`local.yacht.port.${hport}`] || ""; let port_entry = { cport: cport, From 7ef0bba2f28342d6773ad2a6a11c3767816ef441 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Tue, 9 Feb 2021 12:56:28 -0800 Subject: [PATCH 59/75] scroll with log --- frontend/package-lock.json | 7 ++++++- frontend/package.json | 1 + .../applications/ApplicationDetailsComponents/AppLogs.vue | 8 +++++--- frontend/src/main.js | 3 +++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2bb2e956..f1b6b8c4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,6 +1,6 @@ { "name": "yacht", - "version": "0.1.0", + "version": "0.0.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -15926,6 +15926,11 @@ "@types/chart.js": "^2.7.55" } }, + "vue-chat-scroll": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vue-chat-scroll/-/vue-chat-scroll-1.4.0.tgz", + "integrity": "sha512-taHcwJJadZwJR4C4t/fv+R9ZXXSrwrpQKP17La/ep5q7IyH5i3BvscgSpXwu7s8TPP9T9n5n3JDnO+vRsZ/mBQ==" + }, "vue-cli-plugin-apollo": { "version": "0.21.3", "resolved": "https://registry.npm.taobao.org/vue-cli-plugin-apollo/download/vue-cli-plugin-apollo-0.21.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index aa396a0a..4253a485 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "vee-validate": "^3.4.5", "vue": "^2.6.12", "vue-chartjs": "^3.5.1", + "vue-chat-scroll": "^1.4.0", "vue-router": "^3.4.9", "vue2-ace-editor": "0.0.15", "vuetify": "^2.4.2", diff --git a/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue b/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue index d45fbbc6..60e52f21 100644 --- a/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue +++ b/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue @@ -11,6 +11,7 @@ @@ -32,7 +33,8 @@ export default { props: ["app", "logs"], data() { - return {}; + return { + }; } }; diff --git a/frontend/src/main.js b/frontend/src/main.js index 91c5af11..40074448 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -3,6 +3,7 @@ import Vue from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; +import VueChatScroll from 'vue-chat-scroll'; // API Calls import axios from "axios"; // UI Framework @@ -13,6 +14,8 @@ import "./vee-validate"; // Animations require("animate.css/animate.compat.css"); +Vue.use(VueChatScroll) + Vue.config.productionTip = false; // Handle Token Refresh on 401 From 955d98d235061b0ccf32211e998c92f64af48bf8 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Tue, 9 Feb 2021 13:17:30 -0800 Subject: [PATCH 60/75] set VUE_APP_VERSION on the latest tag --- .github/workflows/build.yml | 31 +++++++++++++++++++++++++------ backend/api/routers/apps.py | 2 +- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e106495a..21e93486 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,13 @@ jobs: build: runs-on: ubuntu-latest steps: - - name: checkout code + - name: Get current time + uses: 1466587594/get-current-time@v2 + id: current-time + with: + format: YYYY-MM-DD--HH + + - name: Checkout code uses: actions/checkout@v2 - name: Set up QEMU @@ -18,6 +24,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 + - name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: Login to GitHub Container Registry uses: docker/login-action@v1 with: @@ -36,9 +50,14 @@ jobs: with: platforms: linux/amd64,linux/arm64,linux/arm push: true + build-args: | + VUE_APP_VERSION=v0.0.6-alpha-${{ steps.current-time.outputs.formattedTime }} tags: | - selfhostedpro/yacht:latest - selfhostedpro/yacht:do - selfhostedpro/yacht:omv - selfhostedpro/yacht:vue - ghcr.io/selfhostedpro/yacht:latest + selfhostedpro/yacht + selfhostedpro/yacht:latest-${{ steps.current-time.outputs.formattedTime }} + selfhostedpro/yacht:v0.0.6-alpha-${{ steps.current-time.outputs.formattedTime }} + ghcr.io/selfhostedpro/yacht + ghcr.io/selfhostedpro/yacht:latest-${{ steps.current-time.outputs.formattedTime }} + ghcr.io/selfhostedpro/yacht:v0.0.6-alpha-${{ steps.current-time.outputs.formattedTime }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index 0b86fcac..25a643f0 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -94,7 +94,7 @@ async def logs(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depends container: DockerContainer = await docker.containers.get(app_name) if container._container["State"]["Status"] == "running": stats = container.stats(stream=True) - logs = container.log(stdout=True, stderr=True, follow=True) + logs = container.log(stdout=True, stderr=True, follow=True, tail=1000) async for line in logs: try: await websocket.send_text(line) From 76b54552d8072667c47526a1c5ece9ca4099b10f Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Tue, 9 Feb 2021 13:20:08 -0800 Subject: [PATCH 61/75] update readme.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 18393099..aa024563 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ [![Layers](https://img.shields.io/microbadger/layers/selfhostedpro/yacht?color=%2341B883&label=Layers&logo=docker&logoColor=%2341B883&style=for-the-badge)](https://hub.docker.com/r/selfhostedpro/yacht) [![Open Collective](https://img.shields.io/opencollective/all/selfhostedpro.svg?color=%2341B883&logoColor=%2341B883&style=for-the-badge&label=Supporters&logo=open%20collective)](https://opencollective.com/selfhostedpro "please consider helping me by either donating or contributing") + + + + ## Yacht Yacht is a container management UI with a focus on templates and 1-click deployments. From 71c2939e2554592ed8617f061b043596b8d3a067 Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Tue, 9 Feb 2021 14:49:44 -0800 Subject: [PATCH 62/75] fixed udp ports in edit form --- .../components/applications/ApplicationsForm.vue | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/applications/ApplicationsForm.vue b/frontend/src/components/applications/ApplicationsForm.vue index 50456b9d..b757f132 100644 --- a/frontend/src/components/applications/ApplicationsForm.vue +++ b/frontend/src/components/applications/ApplicationsForm.vue @@ -787,10 +787,14 @@ export default { transform_ports(ports, app) { let portlist = []; for (let port in ports) { - let _port = port.split("/") || ''; - var cport = _port[0] || ''; - var hport = ports[port][0].HostPort || ''; - var proto = _port[1] || ''; + let _port = port.split("/") || ""; + var cport = _port[0] || ""; + if (ports[port]) { + var hport = ports[port][0].HostPort || ""; + } else { + continue; + } + var proto = _port[1] || ""; var label = app.Config.Labels[`local.yacht.port.${hport}`] || ""; let port_entry = { cport: cport, @@ -914,7 +918,7 @@ export default { labels: this.transform_labels(app.Config.Labels) || [], sysctls: this.transform_labels(app.HostConfig.Sysctls), cap_add: app.HostConfig.CapAdd || [], - edit: true + edit: true, }; } }, From 2523a7c861f4d1b991aaa472a4230b4730ec821c Mon Sep 17 00:00:00 2001 From: SelfhostedPro Date: Wed, 10 Feb 2021 06:26:42 -0800 Subject: [PATCH 63/75] fixed stepper in deploy form; tailing logs to last 200 logs; fix changing name in edit to duplicate the image --- backend/api/actions/apps.py | 7 ++++--- backend/api/routers/apps.py | 5 ++--- .../ApplicationDetailsComponents/AppLogs.vue | 9 ++++----- .../components/applications/ApplicationsForm.vue | 13 +++++++------ frontend/src/store/modules/images.js | 1 + 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/backend/api/actions/apps.py b/backend/api/actions/apps.py index 0262760d..620b42c2 100644 --- a/backend/api/actions/apps.py +++ b/backend/api/actions/apps.py @@ -136,7 +136,7 @@ def deploy_app(template: schemas.DeployForm): def Merge(dict1, dict2): if dict1 and dict2: - updated_dict = dict2.update(dict1) + dict2.update(dict1) return dict2 elif dict1: return dict1 @@ -163,7 +163,7 @@ def launch_app( edit ): dclient = docker.from_env() - if edit != False: + if edit == True: try: dclient.containers.get(name) try: @@ -172,7 +172,8 @@ def launch_app( except Exception as e: raise e except Exception as e: - raise e + # User probably changed the name so it doesn't conflict. If this is the case we'll just spin up a second container. + pass diff --git a/backend/api/routers/apps.py b/backend/api/routers/apps.py index 25a643f0..c34a9096 100644 --- a/backend/api/routers/apps.py +++ b/backend/api/routers/apps.py @@ -93,8 +93,7 @@ async def logs(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depends async with aiodocker.Docker() as docker: container: DockerContainer = await docker.containers.get(app_name) if container._container["State"]["Status"] == "running": - stats = container.stats(stream=True) - logs = container.log(stdout=True, stderr=True, follow=True, tail=1000) + logs = container.log(stdout=True, stderr=True, follow=True, tail=200) async for line in logs: try: await websocket.send_text(line) @@ -206,7 +205,7 @@ async def process_container(name, stats, websocket): cpu_percent, cpu_system, cpu_total = await calculate_cpu_percent2( line, cpu_total, cpu_system ) - except KeyError as e: + except KeyError: print("error while getting new CPU stats: %r, falling back") cpu_percent = await calculate_cpu_percent(line) diff --git a/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue b/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue index 60e52f21..18ccbda6 100644 --- a/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue +++ b/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue @@ -1,8 +1,6 @@ - - - mdi-file-document-edit-outline - - Edit - - + + + mdi-file-document-edit-outline + + Edit + + @@ -100,7 +98,7 @@ import ApplicationDetailsNav from "./ApplicationDetailsComponents/ApplicationDet import { mapActions, mapGetters, mapState } from "vuex"; export default { components: { - Nav: ApplicationDetailsNav, + Nav: ApplicationDetailsNav }, data() { return { @@ -110,27 +108,27 @@ export default { cpu_percent: [], mem_percent: [], mem_current: [], - mem_total: [], + mem_total: [] }, connection: null, - statConnection: null, + statConnection: null }; }, computed: { ...mapState("apps", ["apps", "app", "isLoading", "processes"]), ...mapGetters({ - getAppByName: "apps/getAppByName", + getAppByName: "apps/getAppByName" }), app() { const appName = this.$route.params.appName; return this.getAppByName(appName); - }, + } }, methods: { ...mapActions({ readApp: "apps/readApp", readAppProcesses: "apps/readAppProcesses", - AppAction: "apps/AppAction", + AppAction: "apps/AppAction" }), editClick(appName) { this.$router.push({ path: `/apps/edit/${appName.Name}` }); @@ -160,7 +158,7 @@ export default { ); }; - this.connection.onmessage = (event) => { + this.connection.onmessage = event => { this.logs.push(event.data); }; }, @@ -186,7 +184,7 @@ export default { JSON.stringify({ type: "onopen", data: "Connected!" }) ); }; - this.statConnection.onmessage = (event) => { + this.statConnection.onmessage = event => { let statsGroup = JSON.parse(event.data); this.stats.time.push(statsGroup.time); this.stats.cpu_percent.push(Math.round(statsGroup.cpu_percent)); @@ -211,7 +209,7 @@ export default { ); this.statConnection.close(1000, "Leaving page or refreshing"); // this.statConnection.close(1001, "Leaving page or refreshing"); - }, + } }, created() { const appName = this.$route.params.appName; @@ -228,7 +226,7 @@ export default { beforeDestroy() { this.closeLogs(); this.closeStats(); - }, + } }; diff --git a/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue b/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue index 18ccbda6..36d93fb1 100644 --- a/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue +++ b/frontend/src/components/applications/ApplicationDetailsComponents/AppLogs.vue @@ -32,9 +32,14 @@ export default { props: ["app", "logs"], data() { return { - scrollOptions: { enable: true, always: false, smooth: false, notSmoothOnInit: true }, + scrollOptions: { + enable: true, + always: false, + smooth: false, + notSmoothOnInit: true + } }; - }, + } }; diff --git a/frontend/src/components/applications/ApplicationsForm.vue b/frontend/src/components/applications/ApplicationsForm.vue index 8f1f1a84..b4ce46d9 100644 --- a/frontend/src/components/applications/ApplicationsForm.vue +++ b/frontend/src/components/applications/ApplicationsForm.vue @@ -677,7 +677,7 @@ import { ValidationObserver, ValidationProvider } from "vee-validate"; export default { components: { ValidationProvider, - ValidationObserver, + ValidationObserver }, data() { return { @@ -698,7 +698,7 @@ export default { devices: [], labels: [], sysctls: [], - cap_add: [], + cap_add: [] }, network_modes: ["bridge", "none", "host"], isLoading: false, @@ -725,22 +725,22 @@ export default { "SYS_BOOT", "LEASE", "WAKE_ALARM", - "BLOCK_SUSPEND", - ], + "BLOCK_SUSPEND" + ] }; }, calculated: { ...mapState("networks", ["networks"]), - ...mapState("volumes", ["volumes"]), + ...mapState("volumes", ["volumes"]) }, methods: { ...mapActions({ readTemplateApp: "templates/readTemplateApp", readNetworks: "networks/_readNetworks", - readApp: "apps/readApp", + readApp: "apps/readApp" }), ...mapMutations({ - setErr: "snackbar/setErr", + setErr: "snackbar/setErr" }), addPort() { this.form.ports.push({ hport: "", cport: "", proto: "tcp" }); @@ -800,7 +800,7 @@ export default { cport: cport, hport: hport, proto: proto, - label: label, + label: label }; portlist.push(port_entry); } @@ -813,7 +813,7 @@ export default { let bind = volumes[volume].Source || ""; let volume_entry = { bind: bind, - container: container, + container: container }; volumelist.push(volume_entry); } @@ -828,7 +828,7 @@ export default { let env_entry = { label: name, name: name, - default: value, + default: value }; envlist.push(env_entry); } @@ -841,7 +841,7 @@ export default { let value = labels[label]; let label_entry = { name: name, - value: value, + value: value }; labellist.push(label_entry); } @@ -865,7 +865,7 @@ export default { this.isLoading = false; this.$router.push({ name: "View Applications" }); }) - .catch((err) => { + .catch(err => { this.isLoading = false; this.deployStep = 1; this.setErr(err); @@ -895,7 +895,7 @@ export default { devices: app.devices || [], labels: app.labels || [], sysctls: app.sysctls || [], - cap_add: app.cap_add || [], + cap_add: app.cap_add || [] }; this.notes = app.notes || null; } catch (error) { @@ -919,15 +919,15 @@ export default { labels: this.transform_labels(app.Config.Labels) || [], sysctls: this.transform_labels(app.HostConfig.Sysctls), cap_add: app.HostConfig.CapAdd || [], - edit: true, + edit: true }; } - }, + } }, async created() { await this.populateForm(); await this.populateNetworks(); - }, + } }; diff --git a/frontend/src/components/applications/ApplicationsList.vue b/frontend/src/components/applications/ApplicationsList.vue index 7679b84d..d1cc4b47 100644 --- a/frontend/src/components/applications/ApplicationsList.vue +++ b/frontend/src/components/applications/ApplicationsList.vue @@ -52,10 +52,10 @@ color="secondary" > + + Columns + + - + mdi-file-document-edit-outline Edit - + @@ -272,37 +270,37 @@ export default { text: "Name", value: "name", sortable: true, - align: "start", + align: "start" // width: "30%", }, project: { text: "Project", value: "project", - sortable: true, + sortable: true }, status: { text: "Status", value: "status", - sortable: true, + sortable: true // width: "10%", }, image: { text: "Image", value: "image", - sortable: true, + sortable: true }, ports: { text: "Ports", value: "ports", - sortable: true, + sortable: true }, created: { text: "Created At", value: "created", - sortable: true, - }, + sortable: true + } }, - selectedHeaders: [], + selectedHeaders: [] }; }, methods: { @@ -310,20 +308,20 @@ export default { readApps: "apps/readApps", AppAction: "apps/AppAction", Update: "apps/AppUpdate", - checkUpdate: "apps/checkAppUpdate", + checkUpdate: "apps/checkAppUpdate" }), handleRowClick(appName) { this.$router.push({ path: `/apps${appName.Name}/info` }); }, - editClick(appName){ - this.$router.push({path: `/apps/edit/${appName.Name}`}) + editClick(appName) { + this.$router.push({ path: `/apps/edit/${appName.Name}` }); }, convPorts(data) { let o = []; for (var k in data) { if (data[k]) { o = o.concat( - data[k].map(function (x) { + data[k].map(function(x) { return { cport: k, hip: x.HostIp, hport: x.HostPort }; }) ); @@ -333,7 +331,7 @@ export default { }, refresh() { this.readApps(); - }, + } }, computed: { ...mapState("apps", [ @@ -341,11 +339,11 @@ export default { "isLoading", "isLoadingValue", "action", - "updatable", + "updatable" ]), showHeaders() { - return this.headers.filter((s) => this.selectedHeaders.includes(s)); - }, + return this.headers.filter(s => this.selectedHeaders.includes(s)); + } }, created() { this.headers = Object.values(this.headersMap); @@ -353,7 +351,7 @@ export default { }, mounted() { this.readApps(); - }, + } }; diff --git a/frontend/src/components/compose/ProjectDetails.vue b/frontend/src/components/compose/ProjectDetails.vue index cbff7e26..e6a56bef 100644 --- a/frontend/src/components/compose/ProjectDetails.vue +++ b/frontend/src/components/compose/ProjectDetails.vue @@ -127,7 +127,9 @@ - Running docker-compose {{ action }} ... + Running docker-compose {{ action }} ... Project Details diff --git a/frontend/src/components/compose/ProjectList.vue b/frontend/src/components/compose/ProjectList.vue index 6be7a6fb..ac036d6f 100644 --- a/frontend/src/components/compose/ProjectList.vue +++ b/frontend/src/components/compose/ProjectList.vue @@ -23,7 +23,9 @@ hide-details > - Running docker-compose {{ action }} ... + Running docker-compose {{ action }} ...