diff --git a/apollo/BUILD.bazel b/apollo/BUILD.bazel index e69de29..979b8c5 100644 --- a/apollo/BUILD.bazel +++ b/apollo/BUILD.bazel @@ -0,0 +1,9 @@ +load("@aspect_rules_py//py:defs.bzl", "py_library") + +py_library( + name = "apollo_lib", + srcs = ["publishing_tools/apollo_tree.py"], + imports = [".."], + visibility = ["//:__subpackages__"], + deps = ["@pypi_aiohttp//:pkg"], +) diff --git a/apollo/rpmworker/rocky_linux.sql b/apollo/rpmworker/rocky_linux.sql index b71b5db..c176d8a 100644 --- a/apollo/rpmworker/rocky_linux.sql +++ b/apollo/rpmworker/rocky_linux.sql @@ -29,21 +29,21 @@ values (5, true, 'aarch64', 'http://dl.rockylinux.org/pub/rocky/9/NFV/aarch64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/NFV/aarch64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/NFV/source/tree/repodata/repomd.xml', 'NFV'), (5, true, 'aarch64', 'http://dl.rockylinux.org/pub/rocky/9/CRB/aarch64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/CRB/aarch64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/CRB/source/tree/repodata/repomd.xml', 'CRB'), (5, true, 'aarch64', 'http://dl.rockylinux.org/pub/rocky/9/ResilientStorage/aarch64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/ResilientStorage/aarch64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/ResilientStorage/source/tree/repodata/repomd.xml', 'ResilientStorage'), -(5, true, 'aarch64', 'http://dl.rockylinux.org/pub/rocky/9/SAP/x86_64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/x86_64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/source/tree/repodata/repomd.xml', 'SAP'), -(5, true, 'aarch64', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/x86_64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/x86_64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/source/tree/repodata/repomd.xml', 'SAPHANA'), +(5, true, 'aarch64', 'http://dl.rockylinux.org/pub/rocky/9/SAP/aarch64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/aarch64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/source/tree/repodata/repomd.xml', 'SAP'), +(5, true, 'aarch64', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/aarch64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/aarch64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/source/tree/repodata/repomd.xml', 'SAPHANA'), (6, true, 's390x', 'http://dl.rockylinux.org/pub/rocky/9/BaseOS/s390x/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/BaseOS/s390x/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/BaseOS/source/tree/repodata/repomd.xml', 'BaseOS'), (6, true, 's390x', 'http://dl.rockylinux.org/pub/rocky/9/AppStream/s390x/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/AppStream/s390x/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/AppStream/source/tree/repodata/repomd.xml', 'AppStream'), (6, true, 's390x', 'http://dl.rockylinux.org/pub/rocky/9/HighAvailability/s390x/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/HighAvailability/s390x/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/HighAvailability/source/tree/repodata/repomd.xml', 'HighAvailability'), (6, true, 's390x', 'http://dl.rockylinux.org/pub/rocky/9/NFV/s390x/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/NFV/s390x/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/NFV/source/tree/repodata/repomd.xml', 'NFV'), (6, true, 's390x', 'http://dl.rockylinux.org/pub/rocky/9/CRB/s390x/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/CRB/s390x/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/CRB/source/tree/repodata/repomd.xml', 'CRB'), (6, true, 's390x', 'http://dl.rockylinux.org/pub/rocky/9/ResilientStorage/s390x/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/ResilientStorage/s390x/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/ResilientStorage/source/tree/repodata/repomd.xml', 'ResilientStorage'), -(6, true, 's390x', 'http://dl.rockylinux.org/pub/rocky/9/SAP/x86_64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/x86_64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/source/tree/repodata/repomd.xml', 'SAP'), -(6, true, 's390x', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/x86_64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/x86_64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/source/tree/repodata/repomd.xml', 'SAPHANA'), +(6, true, 's390x', 'http://dl.rockylinux.org/pub/rocky/9/SAP/s390x/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/s390x/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/source/tree/repodata/repomd.xml', 'SAP'), +(6, true, 's390x', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/s390x/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/s390x/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/source/tree/repodata/repomd.xml', 'SAPHANA'), (7, true, 'ppc64le', 'http://dl.rockylinux.org/pub/rocky/9/BaseOS/ppc64le/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/BaseOS/ppc64le/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/BaseOS/source/tree/repodata/repomd.xml', 'BaseOS'), (7, true, 'ppc64le', 'http://dl.rockylinux.org/pub/rocky/9/AppStream/ppc64le/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/AppStream/ppc64le/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/AppStream/source/tree/repodata/repomd.xml', 'AppStream'), (7, true, 'ppc64le', 'http://dl.rockylinux.org/pub/rocky/9/HighAvailability/ppc64le/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/HighAvailability/ppc64le/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/HighAvailability/source/tree/repodata/repomd.xml', 'HighAvailability'), (7, true, 'ppc64le', 'http://dl.rockylinux.org/pub/rocky/9/NFV/ppc64le/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/NFV/ppc64le/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/NFV/source/tree/repodata/repomd.xml', 'NFV'), (7, true, 'ppc64le', 'http://dl.rockylinux.org/pub/rocky/9/CRB/ppc64le/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/CRB/ppc64le/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/CRB/source/tree/repodata/repomd.xml', 'CRB'), (7, true, 'ppc64le', 'http://dl.rockylinux.org/pub/rocky/9/ResilientStorage/ppc64le/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/ResilientStorage/ppc64le/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/ResilientStorage/source/tree/repodata/repomd.xml', 'ResilientStorage'), -(7, true, 'ppc64le', 'http://dl.rockylinux.org/pub/rocky/9/SAP/x86_64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/x86_64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/source/tree/repodata/repomd.xml', 'SAP'), -(7, true, 'ppc64le', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/x86_64/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/x86_64/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/source/tree/repodata/repomd.xml', 'SAPHANA'); +(7, true, 'ppc64le', 'http://dl.rockylinux.org/pub/rocky/9/SAP/ppc64le/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/ppc64le/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAP/source/tree/repodata/repomd.xml', 'SAP'), +(7, true, 'ppc64le', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/ppc64le/os/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/ppc64le/debug/tree/repodata/repomd.xml', 'http://dl.rockylinux.org/pub/rocky/9/SAPHANA/source/tree/repodata/repomd.xml', 'SAPHANA'); diff --git a/apollo/server/BUILD.bazel b/apollo/server/BUILD.bazel index 725f348..b9d52b6 100644 --- a/apollo/server/BUILD.bazel +++ b/apollo/server/BUILD.bazel @@ -6,6 +6,7 @@ py_library( srcs = [ "roles.py", "routes/admin_index.py", + "routes/admin_users.py", "routes/advisories.py", "routes/api_advisories.py", "routes/api_compat.py", @@ -14,6 +15,7 @@ py_library( "routes/api_updateinfo.py", "routes/login.py", "routes/logout.py", + "routes/profile.py", "routes/red_hat_advisories.py", "routes/statistics.py", "server.py", diff --git a/apollo/server/roles.py b/apollo/server/roles.py index 89bad11..875e73f 100644 --- a/apollo/server/roles.py +++ b/apollo/server/roles.py @@ -1,2 +1,3 @@ ADMIN = "admin" ELEVATED = "elevated" +POSSIBLE_ROLES = [ADMIN, ELEVATED] diff --git a/apollo/server/routes/admin_users.py b/apollo/server/routes/admin_users.py new file mode 100644 index 0000000..c45c9d6 --- /dev/null +++ b/apollo/server/routes/admin_users.py @@ -0,0 +1,234 @@ +import secrets +from math import ceil + +from fastapi import APIRouter, Request, Depends, Form +from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi_pagination import Params +from fastapi_pagination.ext.tortoise import paginate + +from apollo.db import User +from apollo.server import roles +from apollo.server.utils import templates, pwd_context + +router = APIRouter(tags=["non-api"]) + + +def validate_user(request: Request, user: User): + if user.role not in roles.POSSIBLE_ROLES: + return templates.TemplateResponse( + "admin_user_new.jinja", { + "request": request, + "should_hide_form": True, + "error": f"Invalid role {user.role}", + } + ) + + if not user.name or len(user.name) < 2: + return templates.TemplateResponse( + "admin_user_new.jinja", { + "request": request, + "should_hide_form": True, + "error": "Name is too short", + } + ) + + if not user.email or len(user.email) < 3 or "@" not in user.email: + return templates.TemplateResponse( + "admin_user_new.jinja", { + "request": request, + "should_hide_form": True, + "error": "Invalid email", + } + ) + + +@router.get("/", response_class=HTMLResponse) +async def admin_users(request: Request, params: Params = Depends()): + params.size = 50 + users = await paginate( + User.all().order_by("created_at"), + params=params, + ) + + return templates.TemplateResponse( + "admin_users.jinja", { + "request": request, + "users": users, + "users_pages": ceil(users.total / users.size), + } + ) + + +@router.get("/new", response_class=HTMLResponse) +async def admin_user_new(request: Request): + return templates.TemplateResponse( + "admin_user_new.jinja", { + "request": request, + } + ) + + +@router.post("/new", response_class=HTMLResponse) +async def admin_user_new_post( + request: Request, + name: str = Form(default=None), + email: str = Form(default=None), + role: str = Form(default=None), +): + user = User(name=name, email=email, role=role) + validation = validate_user(request, user) + if validation: + return validation + + random_password = secrets.token_urlsafe(16) + user.password = pwd_context.hash(random_password) + + await user.save() + + return templates.TemplateResponse( + "admin_user_new.jinja", { + "request": request, + "should_hide_form": True, + "gen_password": random_password, + "email": email, + } + ) + + +@router.get("/{user_id}", response_class=HTMLResponse) +async def admin_user(request: Request, user_id: int): + user = await User.get_or_none(id=user_id) + if user is None: + return templates.TemplateResponse( + "error.jinja", { + "request": request, + "message": f"User with id {user_id} not found", + } + ) + + return templates.TemplateResponse( + "admin_user.jinja", { + "request": request, + "user": user, + } + ) + + +@router.post("/{user_id}", response_class=HTMLResponse) +async def admin_user_post( + request: Request, + user_id: int, + name: str = Form(default=None), + email: str = Form(default=None), + role: str = Form(default=None), +): + user = await User.get_or_none(id=user_id) + if user is None: + return templates.TemplateResponse( + "error.jinja", { + "request": request, + "message": f"User with id {user_id} not found", + } + ) + + user.name = name + user.email = email + user.role = role + + validation = validate_user(request, user) + if validation: + return validation + + await user.save() + + return templates.TemplateResponse( + "admin_user.jinja", { + "request": request, + "user": user, + "title": "Successfully updated user", + "kind": "success", + } + ) + + +@router.post("/{user_id}/password", response_class=HTMLResponse) +async def admin_user_password_post( + request: Request, + user_id: int, + new_password: str = Form(default=None), + confirm_password: str = Form(default=None), +): + user = await User.get_or_none(id=user_id) + if user is None: + return templates.TemplateResponse( + "error.jinja", { + "request": request, + "message": f"User with id {user_id} not found", + } + ) + + if new_password != confirm_password: + return templates.TemplateResponse( + "admin_user.jinja", { + "request": request, + "user": user, + "title": "Passwords do not match", + "kind": "error", + } + ) + + if not new_password or len(new_password) < 8: + return templates.TemplateResponse( + "admin_user.jinja", { + "request": request, + "user": user, + "title": "Password is too short", + "kind": "error", + } + ) + + user.password = pwd_context.hash(new_password) + await user.save() + + return templates.TemplateResponse( + "admin_user.jinja", { + "request": request, + "user": user, + "title": "Successfully updated password", + "kind": "success", + } + ) + + +@router.post("/{user_id}/delete", response_class=HTMLResponse) +async def admin_user_delete(request: Request, user_id: int): + user = await User.get_or_none(id=user_id) + if user is None: + return templates.TemplateResponse( + "error.jinja", { + "request": request, + "message": f"User with id {user_id} not found", + } + ) + + # Cannot delete yourself + if user.id == request.state.user.id: + return templates.TemplateResponse( + "error.jinja", { + "request": request, + "message": "Cannot delete yourself", + } + ) + + # Cannot delete admins + if user.role == "admin": + return templates.TemplateResponse( + "error.jinja", { + "request": request, + "message": "Cannot delete admin users", + } + ) + + await user.delete() + + return RedirectResponse("/admin/users", status_code=302) diff --git a/apollo/server/routes/profile.py b/apollo/server/routes/profile.py new file mode 100644 index 0000000..1cb7003 --- /dev/null +++ b/apollo/server/routes/profile.py @@ -0,0 +1,83 @@ +from fastapi import APIRouter, Request, Form +from fastapi.responses import HTMLResponse + +from apollo.server.utils import templates, pwd_context + +router = APIRouter(tags=["non-api"]) + + +@router.get("/", response_class=HTMLResponse) +async def profile(request: Request): + return templates.TemplateResponse("profile.jinja", { + "request": request, + }) + + +@router.post("/", response_class=HTMLResponse) +async def profile_post( + request: Request, + current_password: str = Form(default=None), + new_password: str = Form(default=None), + confirm_password: str = Form(default=None), +): + if not current_password or not new_password or not confirm_password: + return templates.TemplateResponse( + "profile.jinja", { + "request": request, + "notification": + { + "kind": "error", + "title": "Please fill out all fields", + } + } + ) + + actual_current_password = request.state.user.password + if not pwd_context.verify(current_password, actual_current_password): + return templates.TemplateResponse( + "profile.jinja", { + "request": request, + "notification": + { + "kind": "error", + "title": "Current password is incorrect", + } + } + ) + + if new_password != confirm_password: + return templates.TemplateResponse( + "profile.jinja", { + "request": request, + "notification": + { + "kind": "error", + "title": "New passwords do not match", + } + } + ) + + if len(new_password) < 8: + return templates.TemplateResponse( + "profile.jinja", { + "request": request, + "notification": + { + "kind": "error", + "title": "New password must be at least 8 characters", + } + } + ) + + request.state.user.password = pwd_context.hash(new_password) + await request.state.user.save() + + return templates.TemplateResponse( + "profile.jinja", { + "request": request, + "notification": { + "kind": "success", + "title": "Password updated", + } + } + ) diff --git a/apollo/server/server.py b/apollo/server/server.py index 22a88e9..7ba34ad 100644 --- a/apollo/server/server.py +++ b/apollo/server/server.py @@ -14,7 +14,9 @@ from apollo.server.routes.advisories import router as advisories_router from apollo.server.routes.statistics import router as statistics_router from apollo.server.routes.login import router as login_router from apollo.server.routes.logout import router as logout_router +from apollo.server.routes.profile import router as profile_router from apollo.server.routes.admin_index import router as admin_index_router +from apollo.server.routes.admin_users import router as admin_users_router from apollo.server.routes.red_hat_advisories import router as red_hat_advisories_router from apollo.server.routes.api_advisories import router as api_advisories_router from apollo.server.routes.api_updateinfo import router as api_updateinfo_router @@ -22,7 +24,7 @@ from apollo.server.routes.api_red_hat import router as api_red_hat_router from apollo.server.routes.api_compat import router as api_compat_router from apollo.server.routes.api_osv import router as api_osv_router from apollo.server.settings import SECRET_KEY, SettingsMiddleware, get_setting -from apollo.server.utils import admin_user_scheme, templates +from apollo.server.utils import admin_user_scheme, user_scheme, templates from apollo.db import Settings from common.info import Info @@ -33,10 +35,14 @@ from common.fastapi import StaticFilesSym, RenderErrorTemplateException app = FastAPI() app.mount( - "/static", StaticFilesSym(directory="apollo/server/static"), name="static" + "/static", + StaticFilesSym(directory="apollo/server/static"), + name="static", ) app.mount( - "/assets", StaticFilesSym(directory="apollo/server/assets"), name="assets" + "/assets", + StaticFilesSym(directory="apollo/server/assets"), + name="assets", ) app.add_middleware(SettingsMiddleware) @@ -45,11 +51,21 @@ app.include_router(advisories_router) app.include_router(statistics_router, prefix="/statistics") app.include_router(login_router, prefix="/login") app.include_router(logout_router, prefix="/logout") +app.include_router( + profile_router, + prefix="/profile", + dependencies=[Depends(user_scheme)], +) app.include_router( admin_index_router, prefix="/admin", dependencies=[Depends(admin_user_scheme)] ) +app.include_router( + admin_users_router, + prefix="/admin/users", + dependencies=[Depends(admin_user_scheme)] +) app.include_router(red_hat_advisories_router, prefix="/red_hat") app.include_router(api_advisories_router, prefix="/api/v3/advisories") app.include_router(api_updateinfo_router, prefix="/api/v3/updateinfo") diff --git a/apollo/server/static/index.ts b/apollo/server/static/index.ts index 387a619..ed0d8bd 100644 --- a/apollo/server/static/index.ts +++ b/apollo/server/static/index.ts @@ -9,22 +9,31 @@ import '@carbon/web-components/es/components/button'; import '@carbon/web-components/es/components/notification'; import '@carbon/web-components/es/components/tag'; import '@carbon/web-components/es/components/list'; +import '@carbon/web-components/es/components/select'; +import '@carbon/web-components/es/components/modal'; function fixForm() { const buttons = document.querySelectorAll('bx-btn'); buttons.forEach((button) => { - if (!button.getAttribute('form_id')) { - return; + let form: any = null; + if (button.getAttribute('form_id')) { + form = document.querySelector('form#' + button.getAttribute('form_id')); } - const form: any = document.querySelector( - 'form#' + button.getAttribute('form_id') - ); - if (form) { - button.addEventListener('click', () => { + // If it has "open_modal" attribute, open the modal + const modalId = button.getAttribute('open_modal'); + + button.addEventListener('click', () => { + if (form) { form.submit(); - }); - } + } + if (modalId) { + const modal: any = document.querySelector('bx-modal#' + modalId); + if (modal) { + modal.open = true; + } + } + }); }); // Also do the same for bx-input and enter key @@ -62,6 +71,7 @@ document.addEventListener('DOMContentLoaded', function () { // Add "active" if location has prefix, e.g. /admin/ -> /admin // For / only we need to check if the location is exactly / const pathname = window.location.pathname; + let currentActive: any = null; document.querySelectorAll('bx-side-nav-link').forEach((el) => { const href = el.getAttribute('href'); if (href === '/') { @@ -70,6 +80,10 @@ document.addEventListener('DOMContentLoaded', function () { } } else if (pathname.startsWith(href || '')) { el.setAttribute('active', ''); + if (currentActive) { + currentActive.removeAttribute('active'); + } + currentActive = el; } }); @@ -90,5 +104,14 @@ document.addEventListener('DOMContentLoaded', function () { } } + // For all bx-select, elements, using the "set_value" attribute, set the value + // of the select element to the value of the attribute. + document.querySelectorAll('bx-select').forEach((el: any) => { + const setValue = el.getAttribute('set_value'); + if (setValue) { + el.value = setValue; + } + }); + fixForm(); }); diff --git a/apollo/server/templates/admin_layout.jinja b/apollo/server/templates/admin_layout.jinja index d55a2b8..07ba836 100644 --- a/apollo/server/templates/admin_layout.jinja +++ b/apollo/server/templates/admin_layout.jinja @@ -16,6 +16,19 @@ .bx--container { margin-left: 16rem } + + bx-inline-notification { + padding-left: 16rem; + } + + #apollo-notification-wrapper { + margin: 0; + } + + .top-notification { + width: 100%; + max-width: 100% !important; + } } .bx--with-rail .bx--container { @@ -43,9 +56,9 @@ {% block content %} - General - Users - OIDC + General + Users + OIDC diff --git a/apollo/server/templates/admin_user.jinja b/apollo/server/templates/admin_user.jinja new file mode 100644 index 0000000..7765c32 --- /dev/null +++ b/apollo/server/templates/admin_user.jinja @@ -0,0 +1,60 @@ +{% extends "admin_layout.jinja" %} + +{% block admin_content %} +

Update user

+ +
+ + + Name + + + Email + + + Admin + Elevated + + + Update user + + +
+ +

Change password

+
+ + + New password + + + Confirm new password + + + Change password + + +
+ +

Danger zone

+ + + + + Delete user + + +

Are you sure you want to delete {{ user.name }}?

+
+ + Cancel + Delete + +
+ +
+
+ + Delete user + +{% endblock %} \ No newline at end of file diff --git a/apollo/server/templates/admin_user_new.jinja b/apollo/server/templates/admin_user_new.jinja new file mode 100644 index 0000000..d03fef1 --- /dev/null +++ b/apollo/server/templates/admin_user_new.jinja @@ -0,0 +1,42 @@ +{% extends "admin_layout.jinja" %} + +{% block admin_content %} +

Create new user

+ +{% if not should_hide_form %} +
+ + + Name + + + Email + + + + Admin + Elevated + +

A random password will be generated on creation

+ + Create new user + +
+
+{% else %} +{% endif %} +{% if error %} +
+
+ {{ error }} +
+
+{% endif %} +{% if gen_password %} +
+
+ User {{ email }} successfully created with password {{ gen_password }} +
+
+{% endif %} +{% endblock %} \ No newline at end of file diff --git a/apollo/server/templates/admin_users.jinja b/apollo/server/templates/admin_users.jinja new file mode 100644 index 0000000..78ee215 --- /dev/null +++ b/apollo/server/templates/admin_users.jinja @@ -0,0 +1,39 @@ +{% extends "admin_layout.jinja" %} + +{% block admin_content %} +
+

Users

+ Create new user +
+ + + + + + + + + + + + ID + Name + Created at + Email + Role + + + + {% for user in users.items -%} + + {{ user.id }} + {{ user.name }} + {{ user.created_at.date() }} + {{ user.email }} + {{ user.role }} + + {% endfor %} + + + +{% endblock %} \ No newline at end of file diff --git a/apollo/server/templates/layout.jinja b/apollo/server/templates/layout.jinja index c9134ea..76fda72 100644 --- a/apollo/server/templates/layout.jinja +++ b/apollo/server/templates/layout.jinja @@ -56,6 +56,10 @@ .apollo-outer bx-inline-notification~#apollo-notification-wrapper>bx-inline-notification { margin-top: 2rem; } + + #apollo-notification-wrapper { + margin: 0 3.5rem; + } {% if notification %} @@ -111,7 +115,7 @@ {% include "light_icon.jinja" %} {% if request.session.get("user.name") %} - {{ request.session.get("user.name") }} + {{ request.session.get("user.name") }} Logout {% else %} Login @@ -122,11 +126,12 @@
{% block outer_content %}{% endblock %} {% if title %} - + {% endif %} {% if notification %} -
+
diff --git a/apollo/server/templates/profile.jinja b/apollo/server/templates/profile.jinja new file mode 100644 index 0000000..8e0ac6e --- /dev/null +++ b/apollo/server/templates/profile.jinja @@ -0,0 +1,24 @@ +{% extends "layout.jinja" %} + +{% block content %} +

Profile

+ +
+
+ + + Current password + + + New password + + + Confirm new password + + + Update password + + +
+
+{% endblock %} \ No newline at end of file diff --git a/apollo/server/utils.py b/apollo/server/utils.py index ca09f88..de930cf 100644 --- a/apollo/server/utils.py +++ b/apollo/server/utils.py @@ -42,7 +42,9 @@ async def user_scheme(request: Request, raise_exc=True) -> User: ) else: return None - return await User.get(id=user_id) + user = await User.get(id=user_id) + request.state.user = user + return user async def is_admin_user(request: Request) -> bool: diff --git a/apollo/tests/publishing_tools/BUILD.bazel b/apollo/tests/publishing_tools/BUILD.bazel index e69de29..b6bfef2 100644 --- a/apollo/tests/publishing_tools/BUILD.bazel +++ b/apollo/tests/publishing_tools/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_python//python:defs.bzl", "py_test") + +py_test( + name = "test_apollo_tree", + srcs = ["test_apollo_tree.py"], + imports = ["../../.."], + deps = [ + "//apollo:apollo_lib", + "//common:common_lib", + "@pypi_pytest//:pkg", + ], +) diff --git a/common/BUILD.bazel b/common/BUILD.bazel index 8390ba1..2e43e46 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -15,6 +15,7 @@ py_library( visibility = ["//:__subpackages__"], deps = [ "@pypi_fastapi//:pkg", + "@pypi_fastapi_pagination//:pkg", "@pypi_pydantic//:pkg", "@pypi_temporalio//:pkg", "@pypi_tortoise_orm//:pkg", diff --git a/deploy/apollo/apollo-rpmworker/values.yaml b/deploy/apollo/apollo-rpmworker/values.yaml index be06a6b..33cdd12 100644 --- a/deploy/apollo/apollo-rpmworker/values.yaml +++ b/deploy/apollo/apollo-rpmworker/values.yaml @@ -8,7 +8,7 @@ image: repository: ghcr.io/resf/apollo-rpmworker pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. - tag: "15c4ca5" + tag: "d157846" imagePullSecrets: [] nameOverride: "" diff --git a/deploy/apollo/apollo-server/values.yaml b/deploy/apollo/apollo-server/values.yaml index 1464d7a..0ee7080 100644 --- a/deploy/apollo/apollo-server/values.yaml +++ b/deploy/apollo/apollo-server/values.yaml @@ -8,7 +8,7 @@ image: repository: ghcr.io/resf/apollo-server pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. - tag: "b498d3b" + tag: "bb5159c" imagePullSecrets: [] nameOverride: "" @@ -71,24 +71,17 @@ ingress: # hosts: # - chart-example.local -resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi +resources: + requests: + cpu: 300m + memory: 256Mi autoscaling: enabled: true - minReplicas: 1 - maxReplicas: 10 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 + minReplicas: 5 + maxReplicas: 20 + targetCPUUtilizationPercentage: 60 + targetMemoryUtilizationPercentage: 50 nodeSelector: {}