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 %}
Are you sure you want to delete {{ user.name }}?
+