From 40eb2f538ceb59c378803b282c8a18aa9d77ee04 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Tue, 11 Aug 2026 22:19:50 +0200 Subject: [PATCH 001/247] feat: add actions to the table --- examples/admin/authors.py | 17 +++++++++++++++++ openadmin/spec/table.py | 14 +++++++++++++- uv.lock | 2 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/examples/admin/authors.py b/examples/admin/authors.py index 82aaef8c..3fb1b53a 100644 --- a/examples/admin/authors.py +++ b/examples/admin/authors.py @@ -38,6 +38,14 @@ async def get_authors_with_bio(session: AsyncSessionDep): return result.scalar_one() +@page.action( + "Delete Author", + is_hidden=True, +) +async def delete_author(id: str) -> spec.Action: + return {"toast": f"User with id {id} deleted"} + + @page.stat( "Avg Books per Author", icon="library", @@ -83,6 +91,15 @@ async def get_all_authors( else author.bio, "book_count": count, "__view__": f"{author.first_name} {author.last_name}", + "__actions__": [ + { + "label": "Delete this user", + "action": delete_author, + "query": { + "id": author.id, + }, + } + ], } for author, count in result.all() ] diff --git a/openadmin/spec/table.py b/openadmin/spec/table.py index 6f0f627f..fa97ca7c 100644 --- a/openadmin/spec/table.py +++ b/openadmin/spec/table.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later from collections.abc import Iterable -from typing import Literal, NotRequired +from typing import Any, Literal, NotRequired from typing_extensions import TypedDict @@ -22,6 +22,17 @@ class ColumnConfigValue(TypedDict): color: NotRequired[Color] +class ActionConfig(TypedDict): + action: str + label: NotRequired[str] + icon: NotRequired[Icon] + color: NotRequired[Color] + + query: dict[str, Any] + body: dict[str, Any] + form: dict[str, Any] + + class TableComponent(TypedDict): type: Literal["table"] id: str @@ -42,6 +53,7 @@ class TableComponent(TypedDict): "TableRow", { "__view__": str | int | float | bool | None, + "__actions__": list[ActionConfig], }, extra_items=str | int | float | bool | None, ) diff --git a/uv.lock b/uv.lock index 3d49466c..0cc6ac1b 100644 --- a/uv.lock +++ b/uv.lock @@ -510,7 +510,7 @@ wheels = [ [[package]] name = "openadmin" -version = "0.7.12" +version = "0.7.14" source = { editable = "." } dependencies = [ { name = "fastapi", extra = ["standard"] }, From f90e31d82129621552b3f41735c94471615ce899 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Tue, 11 Aug 2026 22:27:34 +0200 Subject: [PATCH 002/247] feat: added adding id to function object --- openadmin/fastapi/admin_page.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openadmin/fastapi/admin_page.py b/openadmin/fastapi/admin_page.py index 72d7207c..b843074e 100644 --- a/openadmin/fastapi/admin_page.py +++ b/openadmin/fastapi/admin_page.py @@ -475,6 +475,8 @@ def _(func: Callable[..., spec.Action | Awaitable[spec.Action]]) -> Callable: item["body"] = utils.get_body_params(func) item["form"] = utils.get_form_params(func) + func.__openadmin_action_id__ = item["id"] # type: ignore + return fastapi_decorator(func) return _ From 9e25961fb08ae38c4d66a8c35e3541dcaba23321 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Wed, 12 Aug 2026 09:53:42 +0200 Subject: [PATCH 003/247] feat: added auth --- examples/admin/auth.py | 17 +++++++++++ openadmin/fastapi/__init__.py | 11 ++++++- openadmin/fastapi/admin_auth.py | 52 ++++++++++++++++++++++++++++++++ openadmin/fastapi/admin_panel.py | 10 +++++- openadmin/fastapi/req.py | 5 +++ 5 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 examples/admin/auth.py create mode 100644 openadmin/fastapi/admin_auth.py diff --git a/examples/admin/auth.py b/examples/admin/auth.py new file mode 100644 index 00000000..858fed36 --- /dev/null +++ b/examples/admin/auth.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2026 OpenAdmin +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from fastapi import Request + +from openadmin.fastapi import AdminAuth, LoginReq + +auth = AdminAuth() + + +@auth.login() +def login(req: Request, login: LoginReq) -> None: ... + + +@auth.authenticate() +def authenticate(req: Request) -> None: ... diff --git a/openadmin/fastapi/__init__.py b/openadmin/fastapi/__init__.py index cbfae99d..e5a3c8e8 100644 --- a/openadmin/fastapi/__init__.py +++ b/openadmin/fastapi/__init__.py @@ -2,8 +2,17 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later +from .admin_auth import AdminAuth from .admin_page import AdminPage from .admin_panel import AdminPanel from .deps import PageDep, SearchQueryDep +from .req import LoginReq -__all__ = ["AdminPage", "AdminPanel", "PageDep", "SearchQueryDep"] +__all__ = [ + "AdminAuth", + "AdminPage", + "AdminPanel", + "LoginReq", + "PageDep", + "SearchQueryDep", +] diff --git a/openadmin/fastapi/admin_auth.py b/openadmin/fastapi/admin_auth.py new file mode 100644 index 00000000..206f9405 --- /dev/null +++ b/openadmin/fastapi/admin_auth.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2026 OpenAdmin +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from collections.abc import Awaitable, Callable + +from fastapi import APIRouter, Request + +from .req import LoginReq + + +class AdminAuth: + def __init__(self) -> None: + self.router = APIRouter() + self.authenticate_func: Callable[[Request], None | Awaitable[None]] | None = ( + None + ) + + def login(self): + return self.__create_login_decorator( + self.router.post( + "/login", + ) + ) + + def authenticate(self): + return self.__create_authenticate_decorator() + + def __create_login_decorator( + self, + fastapi_decorator: Callable, + ): + def _( + func: Callable[[Request, LoginReq], None], + ) -> Callable: + + return fastapi_decorator(func) + + return _ + + def __create_authenticate_decorator( + self, + ): + def _( + func: Callable[[Request], None | Awaitable[None]], + ) -> Callable: + + self.authenticate_func = func + + return func + + return _ diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index 49fdba05..b559eff3 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -9,17 +9,25 @@ from openadmin import spec from . import exc_handler, utils +from .admin_auth import AdminAuth from .admin_page import AdminPage _FRONTEND_DIR = Path(__file__).parent.parent / "__client__" class AdminPanel: - def __init__(self, name: str, *, description: str | None = None) -> None: + def __init__( + self, + name: str, + *, + description: str | None = None, + auth: AdminAuth | None = None, + ) -> None: self.version = "1.0.0" self.name = name self.description = description self.sections: list[spec.Section] = [] + self.auth = auth self.app = FastAPI( exception_handlers={ diff --git a/openadmin/fastapi/req.py b/openadmin/fastapi/req.py index 90247df7..01926003 100644 --- a/openadmin/fastapi/req.py +++ b/openadmin/fastapi/req.py @@ -8,3 +8,8 @@ class PaginationParams(BaseModel): page: int per_page: int + + +class LoginReq(BaseModel): + username: str + password: str From 532a23c0ef565367684ced209ca35f595823cb95 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Wed, 12 Aug 2026 09:54:19 +0200 Subject: [PATCH 004/247] ref --- examples/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/main.py b/examples/main.py index 29358146..38407022 100644 --- a/examples/main.py +++ b/examples/main.py @@ -9,6 +9,7 @@ from .admin import ( analytics, + auth, authors, books, control_panel, @@ -34,7 +35,9 @@ ) admin_panel = AdminPanel( - "Book Library Admin", description="Manage and explore the book catalog" + "Book Library Admin", + description="Manage and explore the book catalog", + auth=auth.auth, ) admin_panel.section( From 2d67edf8089f55747b8fb4e8aa45e1aa8372ba3e Mon Sep 17 00:00:00 2001 From: Mykyta Date: Wed, 12 Aug 2026 09:56:54 +0200 Subject: [PATCH 005/247] ref --- examples/admin/auth.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/admin/auth.py b/examples/admin/auth.py index 858fed36..fcebe7b8 100644 --- a/examples/admin/auth.py +++ b/examples/admin/auth.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -from fastapi import Request +from fastapi import Request, HTTPException, status from openadmin.fastapi import AdminAuth, LoginReq @@ -10,8 +10,10 @@ @auth.login() -def login(req: Request, login: LoginReq) -> None: ... +def login(req: Request, login: LoginReq) -> None: + raise HTTPException(status.HTTP_401_UNAUTHORIZED, 'UNAUTHORIZED login') @auth.authenticate() -def authenticate(req: Request) -> None: ... +def authenticate(req: Request) -> None: + raise HTTPException(status.HTTP_401_UNAUTHORIZED, 'UNAUTHORIZED authenticate') From bda18215d8b7f9d24cff5249e1c6dcb571183ddc Mon Sep 17 00:00:00 2001 From: Mykyta Date: Wed, 12 Aug 2026 10:06:15 +0200 Subject: [PATCH 006/247] ref --- examples/admin/auth.py | 6 +++--- openadmin/fastapi/admin_auth.py | 14 +++++++------- openadmin/fastapi/admin_panel.py | 9 ++++++++- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/examples/admin/auth.py b/examples/admin/auth.py index fcebe7b8..103e5824 100644 --- a/examples/admin/auth.py +++ b/examples/admin/auth.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -from fastapi import Request, HTTPException, status +from fastapi import HTTPException, Request, status from openadmin.fastapi import AdminAuth, LoginReq @@ -11,9 +11,9 @@ @auth.login() def login(req: Request, login: LoginReq) -> None: - raise HTTPException(status.HTTP_401_UNAUTHORIZED, 'UNAUTHORIZED login') + raise HTTPException(status.HTTP_401_UNAUTHORIZED, "UNAUTHORIZED login") @auth.authenticate() def authenticate(req: Request) -> None: - raise HTTPException(status.HTTP_401_UNAUTHORIZED, 'UNAUTHORIZED authenticate') + raise HTTPException(status.HTTP_401_UNAUTHORIZED, "UNAUTHORIZED authenticate") diff --git a/openadmin/fastapi/admin_auth.py b/openadmin/fastapi/admin_auth.py index 206f9405..6d603ec5 100644 --- a/openadmin/fastapi/admin_auth.py +++ b/openadmin/fastapi/admin_auth.py @@ -15,26 +15,26 @@ def __init__(self) -> None: self.authenticate_func: Callable[[Request], None | Awaitable[None]] | None = ( None ) + self.login_func: ( + Callable[[Request, LoginReq], None | Awaitable[None]] | None + ) = None def login(self): - return self.__create_login_decorator( - self.router.post( - "/login", - ) - ) + return self.__create_login_decorator() def authenticate(self): return self.__create_authenticate_decorator() def __create_login_decorator( self, - fastapi_decorator: Callable, ): def _( func: Callable[[Request, LoginReq], None], ) -> Callable: - return fastapi_decorator(func) + self.login_func = func + + return func return _ diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index b559eff3..36068aef 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -5,7 +5,7 @@ from pathlib import Path -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, HTTPException, status from openadmin import spec from . import exc_handler, utils @@ -82,4 +82,11 @@ def __mount_spec_route(self, app: FastAPI) -> None: description="Returns the OpenAdmin specification for this admin panel.", )(lambda: self.spec) + app.post( + '/auth/login', + status_code=status.HTTP_204_NO_CONTENT, + summary="Log in", + description="Log in user route" + )(self.auth.login_func) + app.frontend("/", directory=str(_FRONTEND_DIR), fallback="index.html") From 8edaec03cfa24d29b275a0febb360010ff8c5cfb Mon Sep 17 00:00:00 2001 From: Mykyta Date: Wed, 12 Aug 2026 10:09:58 +0200 Subject: [PATCH 007/247] ref --- openadmin/fastapi/admin_auth.py | 16 ++++++++++++---- openadmin/fastapi/admin_panel.py | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/openadmin/fastapi/admin_auth.py b/openadmin/fastapi/admin_auth.py index 6d603ec5..39aa30b6 100644 --- a/openadmin/fastapi/admin_auth.py +++ b/openadmin/fastapi/admin_auth.py @@ -12,12 +12,12 @@ class AdminAuth: def __init__(self) -> None: self.router = APIRouter() - self.authenticate_func: Callable[[Request], None | Awaitable[None]] | None = ( - None + self.authenticate_func: Callable[[Request], None | Awaitable[None]] = ( + self.__create_default_authenticate() ) self.login_func: ( - Callable[[Request, LoginReq], None | Awaitable[None]] | None - ) = None + Callable[[Request, LoginReq], None | Awaitable[None]] + ) = self.__create_default_login() def login(self): return self.__create_login_decorator() @@ -50,3 +50,11 @@ def _( return func return _ + + def __create_default_login( + self, + ) -> Callable[[Request, LoginReq], None | Awaitable[None]]: ... + + def __create_default_authenticate( + self, + ) -> Callable[[Request], None | Awaitable[None]]: ... diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index 36068aef..7a0b98aa 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -83,10 +83,10 @@ def __mount_spec_route(self, app: FastAPI) -> None: )(lambda: self.spec) app.post( - '/auth/login', + "/auth/login", status_code=status.HTTP_204_NO_CONTENT, summary="Log in", - description="Log in user route" + description="Log in user route", )(self.auth.login_func) app.frontend("/", directory=str(_FRONTEND_DIR), fallback="index.html") From deb99345f4f90211572457d983b42036074fd231 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Wed, 12 Aug 2026 10:11:42 +0200 Subject: [PATCH 008/247] ref --- openadmin/fastapi/admin_auth.py | 6 +++--- openadmin/fastapi/admin_panel.py | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/openadmin/fastapi/admin_auth.py b/openadmin/fastapi/admin_auth.py index 39aa30b6..6f306c6c 100644 --- a/openadmin/fastapi/admin_auth.py +++ b/openadmin/fastapi/admin_auth.py @@ -15,9 +15,9 @@ def __init__(self) -> None: self.authenticate_func: Callable[[Request], None | Awaitable[None]] = ( self.__create_default_authenticate() ) - self.login_func: ( - Callable[[Request, LoginReq], None | Awaitable[None]] - ) = self.__create_default_login() + self.login_func: Callable[[Request, LoginReq], None | Awaitable[None]] = ( + self.__create_default_login() + ) def login(self): return self.__create_login_decorator() diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index 7a0b98aa..bc7d46da 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -82,11 +82,12 @@ def __mount_spec_route(self, app: FastAPI) -> None: description="Returns the OpenAdmin specification for this admin panel.", )(lambda: self.spec) - app.post( - "/auth/login", - status_code=status.HTTP_204_NO_CONTENT, - summary="Log in", - description="Log in user route", - )(self.auth.login_func) + if self.auth: + app.post( + "/auth/login", + status_code=status.HTTP_204_NO_CONTENT, + summary="Log in", + description="Log in user route", + )(self.auth.login_func) app.frontend("/", directory=str(_FRONTEND_DIR), fallback="index.html") From b1fddb0dc557345c30f1830a3a1d596393cae448 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Wed, 12 Aug 2026 10:13:28 +0200 Subject: [PATCH 009/247] ref --- openadmin/fastapi/admin_panel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index bc7d46da..8ef80b61 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -35,7 +35,7 @@ def __init__( Exception: exc_handler.app_exception_handler, } ) - self.__mount_spec_route(self.app) + self.__mount_internal_routes(self.app) @property def spec(self) -> spec.Spec: @@ -74,7 +74,7 @@ def section( tags=[name], ) - def __mount_spec_route(self, app: FastAPI) -> None: + def __mount_internal_routes(self, app: FastAPI) -> None: app.get( "/openadmin.json", response_model=spec.Spec, From 25660355b48042fc26ff6024a48c70f19daddb57 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 19:41:17 +0200 Subject: [PATCH 010/247] feat: add create authenticate dep --- openadmin/fastapi/deps.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openadmin/fastapi/deps.py b/openadmin/fastapi/deps.py index 5cb567f6..3414030e 100644 --- a/openadmin/fastapi/deps.py +++ b/openadmin/fastapi/deps.py @@ -2,9 +2,9 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -from typing import Annotated +from typing import Annotated, Awaitable, Callable -from fastapi import Depends, Query +from fastapi import Depends, Query, Request from .req import PaginationParams @@ -21,6 +21,11 @@ def get_search_query( ) -> str | None: return search +def create_authenticate_dep(auth_func: Callable[[Request], None | Awaitable[None]]): + def _(req: Request): + auth_func(req) + + return Depends(_) PageDep = Annotated[PaginationParams, Depends(pagination_params)] SearchQueryDep = Annotated[str | None, Depends(get_search_query)] From 346d61f089573c7c57f624d008e7da9e67b5ba38 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 19:43:42 +0200 Subject: [PATCH 011/247] ref --- openadmin/fastapi/admin_panel.py | 11 +++++++++-- openadmin/fastapi/deps.py | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index 8ef80b61..d9784601 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -8,7 +8,7 @@ from fastapi import FastAPI, HTTPException, status from openadmin import spec -from . import exc_handler, utils +from . import deps, exc_handler, utils from .admin_auth import AdminAuth from .admin_page import AdminPage @@ -33,7 +33,14 @@ def __init__( exception_handlers={ HTTPException: exc_handler.http_exception_handler, Exception: exc_handler.app_exception_handler, - } + }, + dependencies=[ + deps.create_authenticate_dep( + self.auth.authenticate_func, + ), + ] + if self.auth + else None, ) self.__mount_internal_routes(self.app) diff --git a/openadmin/fastapi/deps.py b/openadmin/fastapi/deps.py index 3414030e..551e0b87 100644 --- a/openadmin/fastapi/deps.py +++ b/openadmin/fastapi/deps.py @@ -2,7 +2,8 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -from typing import Annotated, Awaitable, Callable +from collections.abc import Awaitable, Callable +from typing import Annotated from fastapi import Depends, Query, Request @@ -21,11 +22,13 @@ def get_search_query( ) -> str | None: return search + def create_authenticate_dep(auth_func: Callable[[Request], None | Awaitable[None]]): def _(req: Request): auth_func(req) return Depends(_) + PageDep = Annotated[PaginationParams, Depends(pagination_params)] SearchQueryDep = Annotated[str | None, Depends(get_search_query)] From 3b27cb1dd9d545c7c0e0b1bb804892070c4c691c Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:32:17 +0200 Subject: [PATCH 012/247] ref --- openadmin/fastapi/deps.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openadmin/fastapi/deps.py b/openadmin/fastapi/deps.py index 551e0b87..df8e544c 100644 --- a/openadmin/fastapi/deps.py +++ b/openadmin/fastapi/deps.py @@ -23,8 +23,12 @@ def get_search_query( return search -def create_authenticate_dep(auth_func: Callable[[Request], None | Awaitable[None]]): +def create_authenticate_dep(auth_func: Callable[[Request], None | Awaitable[None]], skip: list[str] | None = None,): def _(req: Request): + + if req.url.path in (skip or []): + return + auth_func(req) return Depends(_) From b53c12d36e10bc5789bf3f3823b3e16f91610da0 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:32:43 +0200 Subject: [PATCH 013/247] ref --- openadmin/fastapi/deps.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openadmin/fastapi/deps.py b/openadmin/fastapi/deps.py index df8e544c..ea3f2f0e 100644 --- a/openadmin/fastapi/deps.py +++ b/openadmin/fastapi/deps.py @@ -23,7 +23,10 @@ def get_search_query( return search -def create_authenticate_dep(auth_func: Callable[[Request], None | Awaitable[None]], skip: list[str] | None = None,): +def create_authenticate_dep( + auth_func: Callable[[Request], None | Awaitable[None]], + skip: list[str] | None = None, +): def _(req: Request): if req.url.path in (skip or []): From 35b0a94c497bd3fdce228ac5f541ef12558905fa Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:36:41 +0200 Subject: [PATCH 014/247] ref --- examples/admin/auth.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/admin/auth.py b/examples/admin/auth.py index 103e5824..749a0add 100644 --- a/examples/admin/auth.py +++ b/examples/admin/auth.py @@ -11,9 +11,12 @@ @auth.login() def login(req: Request, login: LoginReq) -> None: - raise HTTPException(status.HTTP_401_UNAUTHORIZED, "UNAUTHORIZED login") + req.session.update({"token": "a"}) @auth.authenticate() def authenticate(req: Request) -> None: - raise HTTPException(status.HTTP_401_UNAUTHORIZED, "UNAUTHORIZED authenticate") + token = req.session.get("token") + + if not token == "a": + raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Unauthorized") From 8b73ef8cb0befad621cf49e22eb80603d95480ac Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:42:53 +0200 Subject: [PATCH 015/247] ref --- examples/main.py | 3 +++ openadmin/fastapi/deps.py | 5 ----- pyproject.toml | 1 + uv.lock | 11 +++++++++++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/main.py b/examples/main.py index 38407022..3e8fac49 100644 --- a/examples/main.py +++ b/examples/main.py @@ -4,6 +4,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from starlette.middleware.sessions import SessionMiddleware from openadmin.fastapi import AdminPanel @@ -34,6 +35,8 @@ allow_headers=["*"], ) +app.add_middleware(SessionMiddleware, secret_key="test") + admin_panel = AdminPanel( "Book Library Admin", description="Manage and explore the book catalog", diff --git a/openadmin/fastapi/deps.py b/openadmin/fastapi/deps.py index ea3f2f0e..81ece6df 100644 --- a/openadmin/fastapi/deps.py +++ b/openadmin/fastapi/deps.py @@ -25,13 +25,8 @@ def get_search_query( def create_authenticate_dep( auth_func: Callable[[Request], None | Awaitable[None]], - skip: list[str] | None = None, ): def _(req: Request): - - if req.url.path in (skip or []): - return - auth_func(req) return Depends(_) diff --git a/pyproject.toml b/pyproject.toml index 89757632..ba1b7dba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ readme = "README.md" requires-python = ">=3.14" dependencies = [ "fastapi[standard]>=0.136.3", + "itsdangerous>=2.2.0", "typing_extensions>=4.12.0", ] classifiers = [ diff --git a/uv.lock b/uv.lock index 0cc6ac1b..26be6977 100644 --- a/uv.lock +++ b/uv.lock @@ -424,6 +424,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -514,6 +523,7 @@ version = "0.7.14" source = { editable = "." } dependencies = [ { name = "fastapi", extra = ["standard"] }, + { name = "itsdangerous" }, { name = "typing-extensions" }, ] @@ -535,6 +545,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "fastapi", extras = ["standard"], specifier = ">=0.136.3" }, + { name = "itsdangerous", specifier = ">=2.2.0" }, { name = "typing-extensions", specifier = ">=4.12.0" }, ] From 60318ed0bd7f501fd66148141b9219dcd8d60e74 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:43:10 +0200 Subject: [PATCH 016/247] ref --- openadmin/fastapi/admin_panel.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index d9784601..cd610f35 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -34,13 +34,6 @@ def __init__( HTTPException: exc_handler.http_exception_handler, Exception: exc_handler.app_exception_handler, }, - dependencies=[ - deps.create_authenticate_dep( - self.auth.authenticate_func, - ), - ] - if self.auth - else None, ) self.__mount_internal_routes(self.app) From 22c3710723b40f2d1a15bc87134a4d9e53f6a955 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:51:11 +0200 Subject: [PATCH 017/247] ref --- openadmin/fastapi/admin_panel.py | 51 ++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index cd610f35..6e1bf11a 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -5,7 +5,7 @@ from pathlib import Path -from fastapi import FastAPI, HTTPException, status +from fastapi import APIRouter, FastAPI, HTTPException, status from openadmin import spec from . import deps, exc_handler, utils @@ -28,14 +28,7 @@ def __init__( self.description = description self.sections: list[spec.Section] = [] self.auth = auth - - self.app = FastAPI( - exception_handlers={ - HTTPException: exc_handler.http_exception_handler, - Exception: exc_handler.app_exception_handler, - }, - ) - self.__mount_internal_routes(self.app) + self.app = self.__create_app() @property def spec(self) -> spec.Spec: @@ -91,3 +84,43 @@ def __mount_internal_routes(self, app: FastAPI) -> None: )(self.auth.login_func) app.frontend("/", directory=str(_FRONTEND_DIR), fallback="index.html") + + def __create_app(self) -> FastAPI: + app = FastAPI( + exception_handlers={ + HTTPException: exc_handler.http_exception_handler, + Exception: exc_handler.app_exception_handler, + }, + ) + + frontend_router = APIRouter() + frontend_router.frontend( + "/", directory=str(_FRONTEND_DIR), fallback="index.html" + ) + + api_router = APIRouter( + dependencies=[deps.create_authenticate_dep(self.auth.authenticate_func)] + if self.auth + else None + ) + + auth_router = APIRouter() + if self.auth: + auth_router.post( + "/login", + status_code=status.HTTP_204_NO_CONTENT, + summary="Log in", + description="Log in user route", + )(self.auth.login_func) + + app.include_router( + prefix="/auth", + router=auth_router, + ) + app.include_router( + prefix="/api", + router=api_router, + ) + app.include_router(frontend_router) + + return app From 52da6c8b3db9e26323a5d3cd806caf66ae882f45 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:55:06 +0200 Subject: [PATCH 018/247] ref --- openadmin/fastapi/admin_panel.py | 70 ++++++++++++-------------------- 1 file changed, 26 insertions(+), 44 deletions(-) diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index 6e1bf11a..bc4d5402 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -28,7 +28,22 @@ def __init__( self.description = description self.sections: list[spec.Section] = [] self.auth = auth - self.app = self.__create_app() + + self.app = FastAPI( + exception_handlers={ + HTTPException: exc_handler.http_exception_handler, + Exception: exc_handler.app_exception_handler, + }, + ) + self.frontend_router = APIRouter() + self.api_router = APIRouter( + dependencies=[deps.create_authenticate_dep(self.auth.authenticate_func)] + if self.auth + else None + ) + self.auth_router = APIRouter() + + self.__mount_initial_routes() @property def spec(self) -> spec.Spec: @@ -67,60 +82,27 @@ def section( tags=[name], ) - def __mount_internal_routes(self, app: FastAPI) -> None: - app.get( - "/openadmin.json", - response_model=spec.Spec, - summary="Get the OpenAdmin specification", - description="Returns the OpenAdmin specification for this admin panel.", - )(lambda: self.spec) - - if self.auth: - app.post( - "/auth/login", - status_code=status.HTTP_204_NO_CONTENT, - summary="Log in", - description="Log in user route", - )(self.auth.login_func) - - app.frontend("/", directory=str(_FRONTEND_DIR), fallback="index.html") - - def __create_app(self) -> FastAPI: - app = FastAPI( - exception_handlers={ - HTTPException: exc_handler.http_exception_handler, - Exception: exc_handler.app_exception_handler, - }, - ) - - frontend_router = APIRouter() - frontend_router.frontend( + def __mount_initial_routes(self): + self.frontend_router.frontend( "/", directory=str(_FRONTEND_DIR), fallback="index.html" ) - api_router = APIRouter( - dependencies=[deps.create_authenticate_dep(self.auth.authenticate_func)] - if self.auth - else None - ) - - auth_router = APIRouter() if self.auth: - auth_router.post( + self.auth_router.post( "/login", status_code=status.HTTP_204_NO_CONTENT, summary="Log in", description="Log in user route", )(self.auth.login_func) - app.include_router( + self.app.include_router( prefix="/auth", - router=auth_router, + router=self.auth_router, ) - app.include_router( + self.app.include_router( prefix="/api", - router=api_router, + router=self.api_router, + ) + self.app.include_router( + self.frontend_router, ) - app.include_router(frontend_router) - - return app From 51bad2d596e69ebb735f042de9e5ec88592d5400 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:56:45 +0200 Subject: [PATCH 019/247] ref --- openadmin/fastapi/admin_panel.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index bc4d5402..d96ab1cd 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -83,6 +83,13 @@ def section( ) def __mount_initial_routes(self): + self.api_router.get( + "/openadmin.json", + response_model=spec.Spec, + summary="Get the OpenAdmin specification", + description="Returns the OpenAdmin specification for this admin panel.", + )(lambda: self.spec) + self.frontend_router.frontend( "/", directory=str(_FRONTEND_DIR), fallback="index.html" ) From 654c59179c71f88092bdd3a83796891e83548023 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:58:26 +0200 Subject: [PATCH 020/247] ref --- examples/admin/auth.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/admin/auth.py b/examples/admin/auth.py index 749a0add..67f614f1 100644 --- a/examples/admin/auth.py +++ b/examples/admin/auth.py @@ -10,13 +10,14 @@ @auth.login() -def login(req: Request, login: LoginReq) -> None: - req.session.update({"token": "a"}) +def login(req: Request, login_req: LoginReq) -> None: + if login_req.username == 'admin' and login_req.password == 'admin': + req.session.update({"token": "admin-token"}) @auth.authenticate() def authenticate(req: Request) -> None: token = req.session.get("token") - if not token == "a": + if not token == "admin-token": raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Unauthorized") From 5becdb1d818d78fa24cf049562ab687cbe3b33ce Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:58:39 +0200 Subject: [PATCH 021/247] ref --- examples/admin/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/admin/auth.py b/examples/admin/auth.py index 67f614f1..a31dea0d 100644 --- a/examples/admin/auth.py +++ b/examples/admin/auth.py @@ -11,7 +11,7 @@ @auth.login() def login(req: Request, login_req: LoginReq) -> None: - if login_req.username == 'admin' and login_req.password == 'admin': + if login_req.username == "admin" and login_req.password == "admin": req.session.update({"token": "admin-token"}) From 3a8d221538c7537fb54905b312594d006e488dd8 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 21:59:22 +0200 Subject: [PATCH 022/247] ref --- client/src/composables/openadmin-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/composables/openadmin-spec.ts b/client/src/composables/openadmin-spec.ts index 1f29fb44..1d563c30 100644 --- a/client/src/composables/openadmin-spec.ts +++ b/client/src/composables/openadmin-spec.ts @@ -13,7 +13,7 @@ export const useOpenAdminSpec = () => { return useQuery({ queryKey: ["openadmin-spec"], queryFn: async () => { - const response = await fetch("openadmin.json") + const response = await fetch("api/openadmin.json") const data = await response.json() if (!response.ok) { From 48f003b17b07fdbf4fa277702022c26468958c85 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 22:22:54 +0200 Subject: [PATCH 023/247] ref --- client/src/schemas/login.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 client/src/schemas/login.ts diff --git a/client/src/schemas/login.ts b/client/src/schemas/login.ts new file mode 100644 index 00000000..39217a2e --- /dev/null +++ b/client/src/schemas/login.ts @@ -0,0 +1,8 @@ +import z from 'zod' + +export const loginSchema = z.object({ + username: z.string().min(1).max(100), + password: z.string().min(1).max(100), +}) + +export type Login = z.infer \ No newline at end of file From c7a4124d0a33e5dc1402aebc8f85fa5a3098f367 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 22:23:58 +0200 Subject: [PATCH 024/247] ref --- client/bun.lock | 109 ++++++++++++++++++++++++++++++++++++++++++-- client/package.json | 1 + 2 files changed, 106 insertions(+), 4 deletions(-) diff --git a/client/bun.lock b/client/bun.lock index 9315ff5d..d15bb635 100644 --- a/client/bun.lock +++ b/client/bun.lock @@ -16,6 +16,7 @@ "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.3", "vue": "^3.5.40", + "vue-router": "^5.2.0", "vue-sonner": "^2.0.9", "zod": "^4.4.3", }, @@ -34,15 +35,15 @@ "packages": { "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], - "@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], + "@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + "@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], @@ -52,7 +53,7 @@ "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], - "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + "@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="], "@biomejs/biome": ["@biomejs/biome@2.5.7", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.7", "@biomejs/cli-darwin-x64": "2.5.7", "@biomejs/cli-linux-arm64": "2.5.7", "@biomejs/cli-linux-arm64-musl": "2.5.7", "@biomejs/cli-linux-x64": "2.5.7", "@biomejs/cli-linux-x64-musl": "2.5.7", "@biomejs/cli-win32-arm64": "2.5.7", "@biomejs/cli-win32-x64": "2.5.7" }, "bin": { "biome": "bin/biome" } }, "sha512-zr8K/DcY5tYsQOQwqMJ0AWElo6QgmgNI7idXgXLhevVszlt8RGVpesEJPqx3ThazLaOwjJ5Y8fz3BtH5fGZNsw=="], @@ -278,6 +279,8 @@ "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], + "@types/jsesc": ["@types/jsesc@2.5.1", "", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="], + "@types/leaflet": ["@types/leaflet@1.7.6", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-Emkz3V08QnlelSbpT46OEAx+TBZYTOX2r1yM7W+hWg5+djHtQ1GbEXBDRLaqQDOYcDI51Ss0ayoqoKD4CtLUDA=="], "@types/mapbox__point-geometry": ["@types/mapbox__point-geometry@0.1.4", "", {}, "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA=="], @@ -324,6 +327,8 @@ "@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="], + "@vue-macros/common": ["@vue-macros/common@3.1.4", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw=="], + "@vue/compiler-core": ["@vue/compiler-core@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg=="], "@vue/compiler-dom": ["@vue/compiler-dom@3.5.41", "", { "dependencies": { "@vue/compiler-core": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw=="], @@ -334,6 +339,10 @@ "@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="], + "@vue/devtools-kit": ["@vue/devtools-kit@8.2.1", "", { "dependencies": { "@vue/devtools-shared": "^8.2.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ=="], + + "@vue/devtools-shared": ["@vue/devtools-shared@8.2.1", "", {}, "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g=="], + "@vue/language-core": ["@vue/language-core@3.3.9", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.1", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-in/68oAa4BCtVY6n/nkuhLIkV8DHYd2UivedJ6cMZ6UYtlq9jaoaSNUBHYCVO44z3nKg7MdE5OBoHKt5SxeBKQ=="], "@vue/reactivity": ["@vue/reactivity@3.5.41", "", { "dependencies": { "@vue/shared": "3.5.41" } }, "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA=="], @@ -354,20 +363,32 @@ "@vueuse/shared": ["@vueuse/shared@14.4.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g=="], + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + "alien-signals": ["alien-signals@3.2.1", "", {}, "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g=="], "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + "ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="], + + "ast-walker-scope": ["ast-walker-scope@0.9.0", "", { "dependencies": { "@babel/parser": "^7.29.2", "@babel/types": "^7.29.0", "ast-kit": "^2.2.0" } }, "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A=="], + "babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="], + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], + "convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="], "cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="], @@ -470,6 +491,8 @@ "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "exsolve": ["exsolve@1.1.1", "", {}, "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="], @@ -492,6 +515,8 @@ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], @@ -516,6 +541,8 @@ "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + "kdbush": ["kdbush@3.0.0", "", {}, "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew=="], "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], @@ -548,14 +575,20 @@ "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + "local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="], + "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="], + "maplibre-gl": ["maplibre-gl@2.4.0", "", { "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/mapbox-gl-supported": "^2.0.1", "@mapbox/point-geometry": "^0.1.0", "@mapbox/tiny-sdf": "^2.0.5", "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", "@types/geojson": "^7946.0.10", "@types/mapbox__point-geometry": "^0.1.2", "@types/mapbox__vector-tile": "^1.3.0", "@types/pbf": "^3.0.2", "csscolorparser": "~1.0.3", "earcut": "^2.2.4", "geojson-vt": "^3.2.1", "gl-matrix": "^3.4.3", "global-prefix": "^3.0.0", "murmurhash-js": "^1.0.0", "pbf": "^3.2.1", "potpack": "^1.0.2", "quickselect": "^2.0.0", "supercluster": "^7.1.5", "tinyqueue": "^2.0.3", "vt-pbf": "^3.1.3" } }, "sha512-csNFylzntPmHWidczfgCZpvbTSmhaWvLRj9e1ezUDBEPizGgshgm3ea1T5TCNEEBq0roauu7BPuRZjA3wO4KqA=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], @@ -564,6 +597,8 @@ "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], + "nostics": ["nostics@1.2.0", "", {}, "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg=="], + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], @@ -576,20 +611,30 @@ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "pbf": ["pbf@3.3.0", "", { "dependencies": { "ieee754": "^1.1.12", "resolve-protobuf-schema": "^2.1.0" }, "bin": { "pbf": "bin/pbf" } }, "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q=="], + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + "pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="], + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], "potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="], "protocol-buffers-schema": ["protocol-buffers-schema@3.6.1", "", {}, "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ=="], + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + "quickselect": ["quickselect@2.0.0", "", {}, "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="], + "readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], + "reka-ui": ["reka-ui@2.10.1", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.5", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-drcOQ4rQtDYAcGCsyQBqQg8QQ+H3B+zDaMJU0h8KPEPMa7g9BHu3zcOi4OB39XJSWizceFoNO0Z9tctSGLOXqg=="], "remove-accents": ["remove-accents@0.5.0", "", {}, "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="], @@ -608,6 +653,8 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], + "source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -642,8 +689,14 @@ "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + "unplugin": ["unplugin@3.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "peerDependencies": { "@farmfe/core": "*", "@rspack/core": "*", "bun-types-no-globals": "*", "esbuild": "*", "rolldown": "*", "rollup": "*", "unloader": "*", "vite": "*", "webpack": "*" }, "optionalPeers": ["@farmfe/core", "@rspack/core", "bun-types-no-globals", "esbuild", "rolldown", "rollup", "unloader", "vite", "webpack"] }, "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg=="], + + "unplugin-utils": ["unplugin-utils@0.3.2", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.4" } }, "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA=="], + "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="], "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], @@ -654,16 +707,34 @@ "vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="], + "vue-router": ["vue-router@5.2.0", "", { "dependencies": { "@babel/generator": "^8.0.0", "@vue-macros/common": "^3.1.3", "@vue/devtools-api": "^8.1.5", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.2.1", "magic-string": "^0.30.21", "mlly": "^1.8.2", "muggle-string": "^0.4.1", "nostics": "^1.1.4", "pathe": "^2.0.3", "picomatch": "^4.0.5", "scule": "^1.3.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2", "yaml": "^2.9.0" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", "pinia": "^3.0.4 || ^4.0.2", "vite": "^7.3.0 || ^8.0.0", "vue": "^3.5.34 || ^4.0.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia", "vite"] }, "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw=="], + "vue-sonner": ["vue-sonner@2.0.9", "", { "peerDependencies": { "@nuxt/kit": "^4.0.3", "@nuxt/schema": "^4.0.3", "nuxt": "^4.0.3" }, "optionalPeers": ["@nuxt/kit", "@nuxt/schema", "nuxt"] }, "sha512-i6BokNlNDL93fpzNxN/LZSn6D6MzlO+i3qXt6iVZne3x1k7R46d5HlFB4P8tYydhgqOrRbIZEsnRd3kG7qGXyw=="], "vue-tsc": ["vue-tsc@3.3.9", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.9" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-TS3Y1ux/IRoE8OCP2PpACAeOseuIs0UvWrcr7u+w3PmfY+SlCfEf8zjrBgnQksHUgLpthi5vHlffcQTQTdPBZA=="], + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + "which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], "yaml": ["yaml@1.10.3", "", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="], "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/generator/@babel/parser": ["@babel/parser@8.0.4", "", { "dependencies": { "@babel/types": "^8.0.4" }, "bin": "./bin/babel-parser.js" }, "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g=="], + + "@babel/helper-module-imports/@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@babel/parser/@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@babel/template/@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@babel/traverse/@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], + + "@babel/traverse/@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + "@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], @@ -680,12 +751,36 @@ "@types/d3-sankey/@types/d3-shape": ["@types/d3-shape@1.3.12", "", { "dependencies": { "@types/d3-path": "^1" } }, "sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q=="], + "ast-walker-scope/@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "topojson-client/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "vue-router/@vue/devtools-api": ["@vue/devtools-api@8.2.1", "", { "dependencies": { "@vue/devtools-kit": "^8.2.1" } }, "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A=="], + + "vue-router/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "@babel/helper-module-imports/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-module-imports/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/template/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/template/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + "@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], "@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], @@ -710,8 +805,14 @@ "@types/d3-sankey/@types/d3-shape/@types/d3-path": ["@types/d3-path@1.0.11", "", {}, "sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw=="], + "ast-walker-scope/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "ast-walker-scope/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], } } diff --git a/client/package.json b/client/package.json index b5aacd0d..45ac5255 100644 --- a/client/package.json +++ b/client/package.json @@ -26,6 +26,7 @@ "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.3", "vue": "^3.5.40", + "vue-router": "^5.2.0", "vue-sonner": "^2.0.9", "zod": "^4.4.3" }, From c399aa9067da5e208832d8bd3a06d5584d04326a Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 22:31:38 +0200 Subject: [PATCH 025/247] ref --- client/src/composables/auth.ts | 29 +++++++++++++++++++++++++++++ client/src/schemas/login.ts | 12 ++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 client/src/composables/auth.ts diff --git a/client/src/composables/auth.ts b/client/src/composables/auth.ts new file mode 100644 index 00000000..461c2c26 --- /dev/null +++ b/client/src/composables/auth.ts @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2026 OpenAdmin +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { errorSchema } from "@/schemas/error" +import { useMutation, useQueryClient } from "@tanstack/vue-query" +import { toast } from "vue-sonner" + +export const useLogin = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (body) => { + const response = await fetch("auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }) + + if (!response.ok) { + const data = await response.json() + const error = errorSchema.parse(data) + toast.error(error.message) + throw error + } + }, + onSuccess: () => queryClient.invalidateQueries({ queryKey: ["openadmin-spec"] }), + }) +} diff --git a/client/src/schemas/login.ts b/client/src/schemas/login.ts index 39217a2e..73d27a75 100644 --- a/client/src/schemas/login.ts +++ b/client/src/schemas/login.ts @@ -1,8 +1,12 @@ -import z from 'zod' +// SPDX-FileCopyrightText: 2026 OpenAdmin +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import z from "zod" export const loginSchema = z.object({ - username: z.string().min(1).max(100), - password: z.string().min(1).max(100), + username: z.string().min(1).max(100), + password: z.string().min(1).max(100), }) -export type Login = z.infer \ No newline at end of file +export type Login = z.infer From 0a6dbeb126cd04280cd63c4333fc7ed26fc58746 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 22:41:31 +0200 Subject: [PATCH 026/247] feat: add use login form --- client/bun.lock | 13 +++++++++++++ client/package.json | 1 + client/src/composables/auth.ts | 23 +++++++++++++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/client/bun.lock b/client/bun.lock index d15bb635..e2f3207c 100644 --- a/client/bun.lock +++ b/client/bun.lock @@ -7,6 +7,7 @@ "dependencies": { "@lucide/vue": "^1.30.0", "@tailwindcss/vite": "^4.3.3", + "@tanstack/vue-form": "^1.33.5", "@tanstack/vue-query": "^5.101.4", "@unovis/vue": "^1.6.7", "@vueuse/core": "^14.4.0", @@ -199,14 +200,26 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.3.3", "", { "dependencies": { "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw=="], + "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.4", "", { "bin": { "intent": "./bin/intent.js" } }, "sha512-6T5Yop/793YI+H+5J8Hsyj4kCih9sl4t3ElLgKioW5hk3ocn+ZdSJ94tT7vL7uabxSugWYBZlOTMPzEw2puvQw=="], + + "@tanstack/form-core": ["@tanstack/form-core@1.33.5", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.11.0" } }, "sha512-3dfx9MBP0aq5sXKteikG629X9oviptrQj0IFRk9YGcb+lB7Kv5x8S17oOSk1wUWgjQZ4xVJEMbKwOAODymocgA=="], + "@tanstack/match-sorter-utils": ["@tanstack/match-sorter-utils@8.19.4", "", { "dependencies": { "remove-accents": "0.5.0" } }, "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg=="], + "@tanstack/pacer-lite": ["@tanstack/pacer-lite@0.1.1", "", {}, "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w=="], + "@tanstack/query-core": ["@tanstack/query-core@5.101.4", "", {}, "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw=="], + "@tanstack/store": ["@tanstack/store@0.11.1", "", {}, "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA=="], + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.7", "", {}, "sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA=="], + "@tanstack/vue-form": ["@tanstack/vue-form@1.33.5", "", { "dependencies": { "@tanstack/form-core": "1.33.5", "@tanstack/vue-store": "^0.11.0" }, "peerDependencies": { "vue": "^3.4.0" } }, "sha512-IvNlJ1LGFqt2pkwwCM+ltMWeBpF/qUeP9vlQwknMJphrIjJtZfmVr+VaN4BsOEbZQVK2vDkglqT+u64LTTjyoA=="], + "@tanstack/vue-query": ["@tanstack/vue-query@5.101.4", "", { "dependencies": { "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/query-core": "5.101.4", "@vue/devtools-api": "^6.6.3", "vue-demi": "^0.14.10" }, "peerDependencies": { "@vue/composition-api": "^1.1.2", "vue": "^2.6.0 || ^3.3.0" }, "optionalPeers": ["@vue/composition-api"] }, "sha512-UYjkUZhnWQIFGNb7SdgjCitAftNyYQfOIVUh6vBUQAG4SQKNMSBKeQERFDubpxLAMpIBJTaOrE8fM4c1kZcIGQ=="], + "@tanstack/vue-store": ["@tanstack/vue-store@0.11.1", "", { "dependencies": { "@tanstack/store": "0.11.1", "vue-demi": "^0.14.10" }, "peerDependencies": { "@vue/composition-api": "^1.2.1", "vue": "^2.5.0 || ^3.0.0" }, "optionalPeers": ["@vue/composition-api"] }, "sha512-0YmYwbiCQKhzfFTLpHybjoAVUh3GkQVYGguvGTzk/0wx0oWuni60huvRs1RqqRLJH6nig3PTYuLuQQ+X5s05gA=="], + "@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.35", "", { "dependencies": { "@tanstack/virtual-core": "3.17.7" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-lOfSPvgPdlaH6Qy+CyIc3XpycitaSQ9GECndGpTuDiu+uDA1am+90yWXwzDSd/20ZM196ggWJLS+Qb6WjVd/OA=="], "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], diff --git a/client/package.json b/client/package.json index 45ac5255..800f1def 100644 --- a/client/package.json +++ b/client/package.json @@ -17,6 +17,7 @@ "dependencies": { "@lucide/vue": "^1.30.0", "@tailwindcss/vite": "^4.3.3", + "@tanstack/vue-form": "^1.33.5", "@tanstack/vue-query": "^5.101.4", "@unovis/vue": "^1.6.7", "@vueuse/core": "^14.4.0", diff --git a/client/src/composables/auth.ts b/client/src/composables/auth.ts index 461c2c26..e1e36f17 100644 --- a/client/src/composables/auth.ts +++ b/client/src/composables/auth.ts @@ -3,14 +3,33 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import { errorSchema } from "@/schemas/error" +import { type Login, loginSchema } from "@/schemas/login" import { useMutation, useQueryClient } from "@tanstack/vue-query" import { toast } from "vue-sonner" +import { useForm } from "@tanstack/vue-form" -export const useLogin = () => { +export const useLoginForm = () => { + const { mutate } = useLogin() + + return useForm({ + defaultValues: { + username: "", + password: "", + } satisfies Login, + validators: { + onChange: loginSchema, + }, + onSubmit: async ({ value }) => { + mutate(value) + }, + }) +} + +const useLogin = () => { const queryClient = useQueryClient() return useMutation({ - mutationFn: async (body) => { + mutationFn: async (body: Login) => { const response = await fetch("auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, From 61c4b2d5525996f38e61a02cec1c18d564495a5c Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 22:48:54 +0200 Subject: [PATCH 027/247] ref --- client/src/views/HomeView.vue | 3 +++ client/src/views/LoginView.vue | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 client/src/views/HomeView.vue create mode 100644 client/src/views/LoginView.vue diff --git a/client/src/views/HomeView.vue b/client/src/views/HomeView.vue new file mode 100644 index 00000000..12bbd477 --- /dev/null +++ b/client/src/views/HomeView.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/client/src/views/LoginView.vue b/client/src/views/LoginView.vue new file mode 100644 index 00000000..2139a43d --- /dev/null +++ b/client/src/views/LoginView.vue @@ -0,0 +1,3 @@ + \ No newline at end of file From a8e1d3087abdb7c354230c22e0cdc954dcb4be35 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 22:51:49 +0200 Subject: [PATCH 028/247] ref --- client/src/schemas/login.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/schemas/login.ts b/client/src/schemas/login.ts index 73d27a75..a00af0cf 100644 --- a/client/src/schemas/login.ts +++ b/client/src/schemas/login.ts @@ -5,8 +5,8 @@ import z from "zod" export const loginSchema = z.object({ - username: z.string().min(1).max(100), - password: z.string().min(1).max(100), + username: z.string().min(1, "Username is required").max(100), + password: z.string().min(1, "Password is required").max(100), }) export type Login = z.infer From 5db4425e56d068f932469d794c2277e5ba1c06e7 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:06:49 +0200 Subject: [PATCH 029/247] ref --- client/src/views/HomeView.vue | 10 ++++++++-- client/src/views/LoginView.vue | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/client/src/views/HomeView.vue b/client/src/views/HomeView.vue index 12bbd477..c3aabbe6 100644 --- a/client/src/views/HomeView.vue +++ b/client/src/views/HomeView.vue @@ -1,3 +1,9 @@ + + \ No newline at end of file +

Home

+ diff --git a/client/src/views/LoginView.vue b/client/src/views/LoginView.vue index 2139a43d..5f1915dd 100644 --- a/client/src/views/LoginView.vue +++ b/client/src/views/LoginView.vue @@ -1,3 +1,9 @@ + + \ No newline at end of file +

Login

+ From 413b15e8625df9b97d49fd9cdc7b6f3f7693b660 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:07:22 +0200 Subject: [PATCH 030/247] ref --- client/src/router/index.ts | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 client/src/router/index.ts diff --git a/client/src/router/index.ts b/client/src/router/index.ts new file mode 100644 index 00000000..9e7cab87 --- /dev/null +++ b/client/src/router/index.ts @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2026 OpenAdmin +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { createRouter, createWebHashHistory } from "vue-router" +import { openAdminSpecQueryOptions } from "@/composables/openadmin-spec" +import { queryClient } from "@/lib/query-client" +import { ApiError } from "@/schemas/error" + +export const router = createRouter({ + history: createWebHashHistory(), + routes: [ + { + path: "/login", + name: "login", + component: () => import("@/views/LoginView.vue"), + meta: { public: true }, + }, + { + path: "/", + name: "home", + component: () => import("@/views/HomeView.vue"), + }, + ], +}) + +router.beforeEach(async (to) => { + if (to.meta.public) return true + + try { + await queryClient.ensureQueryData(openAdminSpecQueryOptions) + return true + } catch (error) { + if (error instanceof ApiError && error.status === 401) { + return { name: "login", query: { redirect: to.fullPath } } + } + throw error + } +}) From c56a815be5a6579eb15be56c7c537b3b71db5c2b Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:09:51 +0200 Subject: [PATCH 031/247] ref --- client/src/composables/openadmin-spec.ts | 30 +++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/client/src/composables/openadmin-spec.ts b/client/src/composables/openadmin-spec.ts index 1d563c30..0e751e7f 100644 --- a/client/src/composables/openadmin-spec.ts +++ b/client/src/composables/openadmin-spec.ts @@ -2,27 +2,29 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -import { useQuery } from "@tanstack/vue-query" +import { queryOptions, useQuery } from "@tanstack/vue-query" import { computed } from "vue" import type { Error as ApiError } from "@/schemas/error" import { errorSchema } from "@/schemas/error" import type { Spec } from "@/schemas/spec" import { specSchema } from "@/schemas/spec" +export const useOpenAdminSpecOptions = queryOptions({ + queryKey: ["openadmin-spec"], + queryFn: async () => { + const response = await fetch("api/openadmin.json") + const data = await response.json() + + if (!response.ok) { + throw errorSchema.parse(data) + } + + return specSchema.parse(data) + }, +}) + export const useOpenAdminSpec = () => { - return useQuery({ - queryKey: ["openadmin-spec"], - queryFn: async () => { - const response = await fetch("api/openadmin.json") - const data = await response.json() - - if (!response.ok) { - throw errorSchema.parse(data) - } - - return specSchema.parse(data) - }, - }) + return useQuery(useOpenAdminSpecOptions) } export const useOpenAdminPageSpec = ({ id }: { id: string }) => { From 9e67a459396ae6952de55e9c68f062b546f423c6 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:10:44 +0200 Subject: [PATCH 032/247] ref --- client/src/schemas/error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/schemas/error.ts b/client/src/schemas/error.ts index 97181d5b..0706bd18 100644 --- a/client/src/schemas/error.ts +++ b/client/src/schemas/error.ts @@ -8,4 +8,4 @@ export const errorSchema = z.object({ message: z.string(), }) -export type Error = z.infer +export type Error = z.infer & { status: string } From 8381a0384ed0bb8e28a4aee62f0b9fcc94b09364 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:13:25 +0200 Subject: [PATCH 033/247] ref --- client/src/composables/openadmin-spec.ts | 2 +- client/src/schemas/error.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/composables/openadmin-spec.ts b/client/src/composables/openadmin-spec.ts index 0e751e7f..b0f25ddf 100644 --- a/client/src/composables/openadmin-spec.ts +++ b/client/src/composables/openadmin-spec.ts @@ -4,7 +4,7 @@ import { queryOptions, useQuery } from "@tanstack/vue-query" import { computed } from "vue" -import type { Error as ApiError } from "@/schemas/error" +import type { ApiError } from "@/schemas/error" import { errorSchema } from "@/schemas/error" import type { Spec } from "@/schemas/spec" import { specSchema } from "@/schemas/spec" diff --git a/client/src/schemas/error.ts b/client/src/schemas/error.ts index 0706bd18..75a03c03 100644 --- a/client/src/schemas/error.ts +++ b/client/src/schemas/error.ts @@ -8,4 +8,4 @@ export const errorSchema = z.object({ message: z.string(), }) -export type Error = z.infer & { status: string } +export type ApiError = z.infer & { status: string } From bd3f766347fcfb4529b7078589ced14cb47a0222 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:14:21 +0200 Subject: [PATCH 034/247] ref --- client/src/router/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 9e7cab87..db30d2b9 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -3,9 +3,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import { createRouter, createWebHashHistory } from "vue-router" -import { openAdminSpecQueryOptions } from "@/composables/openadmin-spec" -import { queryClient } from "@/lib/query-client" -import { ApiError } from "@/schemas/error" +import { useOpenAdminSpecOptions } from "@/composables/openadmin-spec" +import { type ApiError } from "@/schemas/error" +import { useQueryClient } from "@tanstack/vue-query" export const router = createRouter({ history: createWebHashHistory(), @@ -27,8 +27,10 @@ export const router = createRouter({ router.beforeEach(async (to) => { if (to.meta.public) return true + const queryClient = useQueryClient() + try { - await queryClient.ensureQueryData(openAdminSpecQueryOptions) + await queryClient.ensureQueryData(useOpenAdminSpecOptions) return true } catch (error) { if (error instanceof ApiError && error.status === 401) { From a09bbef7af8426ee15d3df481e15630616938226 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:16:28 +0200 Subject: [PATCH 035/247] ref --- examples/admin/auth.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/admin/auth.py b/examples/admin/auth.py index a31dea0d..91bc741b 100644 --- a/examples/admin/auth.py +++ b/examples/admin/auth.py @@ -13,6 +13,8 @@ def login(req: Request, login_req: LoginReq) -> None: if login_req.username == "admin" and login_req.password == "admin": req.session.update({"token": "admin-token"}) + else: + raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid username or password") @auth.authenticate() From cfcf7b7b89afe98f835b8867fff051b3c80a9568 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:18:35 +0200 Subject: [PATCH 036/247] ref --- client/src/main.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/main.ts b/client/src/main.ts index ef5bcf7e..483ba952 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -6,5 +6,7 @@ import { createApp } from "vue" import { VueQueryPlugin } from "@tanstack/vue-query" import "./style.css" import App from "./App.vue" +import { router } from "./router" -createApp(App).use(VueQueryPlugin).mount("#app") + +createApp(App).use(router).use(VueQueryPlugin).mount("#app") From 2526ed46716f0bfbc8442870e0cf5e29982f76da Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:22:26 +0200 Subject: [PATCH 037/247] ref --- examples/admin/auth.py | 4 +++- openadmin/fastapi/admin_auth.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/examples/admin/auth.py b/examples/admin/auth.py index 91bc741b..beb14579 100644 --- a/examples/admin/auth.py +++ b/examples/admin/auth.py @@ -14,7 +14,9 @@ def login(req: Request, login_req: LoginReq) -> None: if login_req.username == "admin" and login_req.password == "admin": req.session.update({"token": "admin-token"}) else: - raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid username or password") + raise HTTPException( + status.HTTP_401_UNAUTHORIZED, "Invalid username or password" + ) @auth.authenticate() diff --git a/openadmin/fastapi/admin_auth.py b/openadmin/fastapi/admin_auth.py index 6f306c6c..12b3879f 100644 --- a/openadmin/fastapi/admin_auth.py +++ b/openadmin/fastapi/admin_auth.py @@ -18,6 +18,9 @@ def __init__(self) -> None: self.login_func: Callable[[Request, LoginReq], None | Awaitable[None]] = ( self.__create_default_login() ) + self.logout_func: Callable[[Request], None | Awaitable[None]] = ( + self.__create_default_logout() + ) def login(self): return self.__create_login_decorator() @@ -25,6 +28,9 @@ def login(self): def authenticate(self): return self.__create_authenticate_decorator() + def logout(self): + return self.__create_logout_decorator() + def __create_login_decorator( self, ): @@ -51,6 +57,19 @@ def _( return _ + def __create_logout_decorator( + self, + ): + def _( + func: Callable[[Request], None | Awaitable[None]], + ) -> Callable: + + self.logout_func = func + + return func + + return _ + def __create_default_login( self, ) -> Callable[[Request, LoginReq], None | Awaitable[None]]: ... @@ -58,3 +77,7 @@ def __create_default_login( def __create_default_authenticate( self, ) -> Callable[[Request], None | Awaitable[None]]: ... + + def __create_default_logout( + self, + ) -> Callable[[Request], None | Awaitable[None]]: ... From 04bf60e328c89331ca5342e672bf57cf935e78c5 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:23:50 +0200 Subject: [PATCH 038/247] ref --- examples/admin/auth.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/admin/auth.py b/examples/admin/auth.py index beb14579..36c4a315 100644 --- a/examples/admin/auth.py +++ b/examples/admin/auth.py @@ -25,3 +25,7 @@ def authenticate(req: Request) -> None: if not token == "admin-token": raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Unauthorized") + +@auth.logout() +def logout(req: Request) -> None: + req.session.clear() From 83f06074ea02f352001f3b9d0254beb1cf0fffa6 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Thu, 13 Aug 2026 23:25:19 +0200 Subject: [PATCH 039/247] ref --- client/src/main.ts | 1 - client/src/router/index.ts | 3 +-- examples/admin/auth.py | 1 + openadmin/fastapi/admin_panel.py | 9 +++++++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/client/src/main.ts b/client/src/main.ts index 483ba952..d0f82ffb 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -8,5 +8,4 @@ import "./style.css" import App from "./App.vue" import { router } from "./router" - createApp(App).use(router).use(VueQueryPlugin).mount("#app") diff --git a/client/src/router/index.ts b/client/src/router/index.ts index db30d2b9..b1751185 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -4,7 +4,6 @@ import { createRouter, createWebHashHistory } from "vue-router" import { useOpenAdminSpecOptions } from "@/composables/openadmin-spec" -import { type ApiError } from "@/schemas/error" import { useQueryClient } from "@tanstack/vue-query" export const router = createRouter({ @@ -27,7 +26,7 @@ export const router = createRouter({ router.beforeEach(async (to) => { if (to.meta.public) return true - const queryClient = useQueryClient() + const queryClient = useQueryClient() try { await queryClient.ensureQueryData(useOpenAdminSpecOptions) diff --git a/examples/admin/auth.py b/examples/admin/auth.py index 36c4a315..fab2147f 100644 --- a/examples/admin/auth.py +++ b/examples/admin/auth.py @@ -26,6 +26,7 @@ def authenticate(req: Request) -> None: if not token == "admin-token": raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Unauthorized") + @auth.logout() def logout(req: Request) -> None: req.session.clear() diff --git a/openadmin/fastapi/admin_panel.py b/openadmin/fastapi/admin_panel.py index d96ab1cd..730e8da1 100644 --- a/openadmin/fastapi/admin_panel.py +++ b/openadmin/fastapi/admin_panel.py @@ -101,6 +101,15 @@ def __mount_initial_routes(self): summary="Log in", description="Log in user route", )(self.auth.login_func) + self.auth_router.post( + "/logout", + status_code=status.HTTP_204_NO_CONTENT, + summary="Log out", + description="Log out user route", + dependencies=[ + deps.create_authenticate_dep(self.auth.authenticate_func) + ], + )(self.auth.logout_func) self.app.include_router( prefix="/auth", From 65107911544eee72826ea0acc82d4829599c275b Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 08:48:48 +0200 Subject: [PATCH 040/247] ref --- client/src/main.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/main.ts b/client/src/main.ts index d0f82ffb..09235b48 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -4,8 +4,8 @@ import { createApp } from "vue" import { VueQueryPlugin } from "@tanstack/vue-query" -import "./style.css" -import App from "./App.vue" -import { router } from "./router" +import "@/style.css" +import App from "@/App.vue" +import { router } from "@/router" createApp(App).use(router).use(VueQueryPlugin).mount("#app") From ff5a72589d0d164b696e6f02c93297c39e4d1756 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 08:55:51 +0200 Subject: [PATCH 041/247] ref --- client/src/lib/status-codes.ts | 8 ++++++++ client/src/schemas/error.ts | 2 +- client/src/types/errors.ts | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 client/src/lib/status-codes.ts create mode 100644 client/src/types/errors.ts diff --git a/client/src/lib/status-codes.ts b/client/src/lib/status-codes.ts new file mode 100644 index 00000000..6c8663ac --- /dev/null +++ b/client/src/lib/status-codes.ts @@ -0,0 +1,8 @@ +export const statusCodes = { + NOT_FOUND: 404, + UNAUTHORIZED: 401, + FORBIDDEN: 403, + BAD_REQUEST: 400, + UNPROCESSABLE_ENTITY: 422, + INTERNAL_ERROR: 500, +} \ No newline at end of file diff --git a/client/src/schemas/error.ts b/client/src/schemas/error.ts index 75a03c03..2631a3d4 100644 --- a/client/src/schemas/error.ts +++ b/client/src/schemas/error.ts @@ -8,4 +8,4 @@ export const errorSchema = z.object({ message: z.string(), }) -export type ApiError = z.infer & { status: string } +export type ApiError = z.infer diff --git a/client/src/types/errors.ts b/client/src/types/errors.ts new file mode 100644 index 00000000..12471107 --- /dev/null +++ b/client/src/types/errors.ts @@ -0,0 +1,4 @@ +export type AppError = { + message: string, + status: number +} \ No newline at end of file From 6d8c9994650743bac9be3299700053779fea699c Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 09:05:26 +0200 Subject: [PATCH 042/247] ref --- client/src/composables/openadmin-spec.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/composables/openadmin-spec.ts b/client/src/composables/openadmin-spec.ts index b0f25ddf..6f8a9266 100644 --- a/client/src/composables/openadmin-spec.ts +++ b/client/src/composables/openadmin-spec.ts @@ -4,19 +4,20 @@ import { queryOptions, useQuery } from "@tanstack/vue-query" import { computed } from "vue" -import type { ApiError } from "@/schemas/error" +import type { AppError } from "@/types/errors" import { errorSchema } from "@/schemas/error" import type { Spec } from "@/schemas/spec" import { specSchema } from "@/schemas/spec" -export const useOpenAdminSpecOptions = queryOptions({ +export const useOpenAdminSpecOptions = queryOptions({ queryKey: ["openadmin-spec"], queryFn: async () => { const response = await fetch("api/openadmin.json") const data = await response.json() if (!response.ok) { - throw errorSchema.parse(data) + const error = errorSchema.parse(data) + throw { ...error, status: response.status } satisfies AppError } return specSchema.parse(data) @@ -24,7 +25,7 @@ export const useOpenAdminSpecOptions = queryOptions({ }) export const useOpenAdminSpec = () => { - return useQuery(useOpenAdminSpecOptions) + return useQuery(useOpenAdminSpecOptions) } export const useOpenAdminPageSpec = ({ id }: { id: string }) => { From cbaf23d3a0bbea93fd568148da88349a9c4e36ba Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 09:07:18 +0200 Subject: [PATCH 043/247] ref --- client/src/lib/status-codes.ts | 6 +++++- client/src/router/index.ts | 5 +++-- client/src/types/errors.ts | 10 +++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/client/src/lib/status-codes.ts b/client/src/lib/status-codes.ts index 6c8663ac..28310cd6 100644 --- a/client/src/lib/status-codes.ts +++ b/client/src/lib/status-codes.ts @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026 OpenAdmin +// +// SPDX-License-Identifier: AGPL-3.0-or-later + export const statusCodes = { NOT_FOUND: 404, UNAUTHORIZED: 401, @@ -5,4 +9,4 @@ export const statusCodes = { BAD_REQUEST: 400, UNPROCESSABLE_ENTITY: 422, INTERNAL_ERROR: 500, -} \ No newline at end of file +} diff --git a/client/src/router/index.ts b/client/src/router/index.ts index b1751185..c8f46e6a 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -5,6 +5,7 @@ import { createRouter, createWebHashHistory } from "vue-router" import { useOpenAdminSpecOptions } from "@/composables/openadmin-spec" import { useQueryClient } from "@tanstack/vue-query" +import { statusCodes } from "@/lib/status-codes" export const router = createRouter({ history: createWebHashHistory(), @@ -31,8 +32,8 @@ router.beforeEach(async (to) => { try { await queryClient.ensureQueryData(useOpenAdminSpecOptions) return true - } catch (error) { - if (error instanceof ApiError && error.status === 401) { + } catch (error: any) { + if (error.status === statusCodes.UNAUTHORIZED) { return { name: "login", query: { redirect: to.fullPath } } } throw error diff --git a/client/src/types/errors.ts b/client/src/types/errors.ts index 12471107..a17dd07f 100644 --- a/client/src/types/errors.ts +++ b/client/src/types/errors.ts @@ -1,4 +1,8 @@ +// SPDX-FileCopyrightText: 2026 OpenAdmin +// +// SPDX-License-Identifier: AGPL-3.0-or-later + export type AppError = { - message: string, - status: number -} \ No newline at end of file + message: string + status: number +} From b9fd956e8dd28d7503f7c625e08afaa0a664c70e Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 09:07:45 +0200 Subject: [PATCH 044/247] ref --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 610768ff..17c15a22 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,6 @@ check/test: check: check/format check/lint check/typing check/cves check/security check/unused check/spell check/license check/test -dev/run: +dev: @ cd client && bun run build @ PYTHONPATH=. uv run fastapi dev examples/main.py --host 0.0.0.0 --port $${PORT:-8000} --reload \ No newline at end of file From 5ef0d1fe264eb0672cd4d144a68214f1a44e9f0c Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 09:14:17 +0200 Subject: [PATCH 045/247] ref --- client/src/App.vue | 13 +++---------- examples/main.py | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/client/src/App.vue b/client/src/App.vue index 7164a998..556cab01 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -4,15 +4,8 @@ SPDX-FileCopyrightText: 2026 OpenAdmin SPDX-License-Identifier: AGPL-3.0-or-later --> - - diff --git a/examples/main.py b/examples/main.py index 3e8fac49..3864fc7d 100644 --- a/examples/main.py +++ b/examples/main.py @@ -35,7 +35,7 @@ allow_headers=["*"], ) -app.add_middleware(SessionMiddleware, secret_key="test") +app.add_middleware(SessionMiddleware, secret_key="testa") admin_panel = AdminPanel( "Book Library Admin", From edc1edf01b5d175234816452448743f0dbd6032a Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 09:24:13 +0200 Subject: [PATCH 046/247] feat: login form --- .claude/launch.json | 11 ++++++ client/src/views/LoginView.vue | 68 +++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 .claude/launch.json diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 00000000..699905e2 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "client-dev", + "runtimeExecutable": "bun", + "runtimeArgs": ["run", "--cwd", "client", "dev"], + "port": 5173 + } + ] +} diff --git a/client/src/views/LoginView.vue b/client/src/views/LoginView.vue index 5f1915dd..f81c6400 100644 --- a/client/src/views/LoginView.vue +++ b/client/src/views/LoginView.vue @@ -4,6 +4,72 @@ SPDX-FileCopyrightText: 2026 OpenAdmin SPDX-License-Identifier: AGPL-3.0-or-later --> + + From de2531d99248eb6c80e7882eab628d697802b5d7 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 09:26:04 +0200 Subject: [PATCH 047/247] ref --- client/tsconfig.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/client/tsconfig.json b/client/tsconfig.json index 6c4e12ba..88a659fb 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,10 +1,4 @@ { "files": [], - "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }], - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - } + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] } From 4b2ab8f55026e2b38cab27a99bda1a828b8c4ee0 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 09:43:12 +0200 Subject: [PATCH 048/247] ref --- client/src/composables/auth.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client/src/composables/auth.ts b/client/src/composables/auth.ts index e1e36f17..28b41f99 100644 --- a/client/src/composables/auth.ts +++ b/client/src/composables/auth.ts @@ -8,8 +8,8 @@ import { useMutation, useQueryClient } from "@tanstack/vue-query" import { toast } from "vue-sonner" import { useForm } from "@tanstack/vue-form" -export const useLoginForm = () => { - const { mutate } = useLogin() +export const useLoginForm = ({ onSuccess }: { onSuccess?: () => void } = {}) => { + const { mutate } = useLogin({ onSuccess }) return useForm({ defaultValues: { @@ -25,7 +25,7 @@ export const useLoginForm = () => { }) } -const useLogin = () => { +const useLogin = ({ onSuccess }: { onSuccess?: () => void } = {}) => { const queryClient = useQueryClient() return useMutation({ @@ -43,6 +43,9 @@ const useLogin = () => { throw error } }, - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["openadmin-spec"] }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["openadmin-spec"] }) + onSuccess?.() + }, }) } From 73ccef7522fc893676b509decc99ab8b1baeff44 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 09:43:36 +0200 Subject: [PATCH 049/247] ref --- client/src/composables/auth.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/composables/auth.ts b/client/src/composables/auth.ts index 28b41f99..ecb5f667 100644 --- a/client/src/composables/auth.ts +++ b/client/src/composables/auth.ts @@ -2,11 +2,11 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -import { errorSchema } from "@/schemas/error" -import { type Login, loginSchema } from "@/schemas/login" +import { useForm } from "@tanstack/vue-form" import { useMutation, useQueryClient } from "@tanstack/vue-query" import { toast } from "vue-sonner" -import { useForm } from "@tanstack/vue-form" +import { errorSchema } from "@/schemas/error" +import { type Login, loginSchema } from "@/schemas/login" export const useLoginForm = ({ onSuccess }: { onSuccess?: () => void } = {}) => { const { mutate } = useLogin({ onSuccess }) From 7da6d143d7a3ad93108d15111eb2d1563fb85a97 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 09:44:03 +0200 Subject: [PATCH 050/247] ref --- client/src/views/LoginView.vue | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/src/views/LoginView.vue b/client/src/views/LoginView.vue index f81c6400..c01f8578 100644 --- a/client/src/views/LoginView.vue +++ b/client/src/views/LoginView.vue @@ -6,13 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-or-later From a740b475998c21b6df3081f9369d27f74773d0b9 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 09:54:37 +0200 Subject: [PATCH 052/247] ref --- client/src/layouts/Dashboard.vue | 70 ++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 client/src/layouts/Dashboard.vue diff --git a/client/src/layouts/Dashboard.vue b/client/src/layouts/Dashboard.vue new file mode 100644 index 00000000..ca8b70ec --- /dev/null +++ b/client/src/layouts/Dashboard.vue @@ -0,0 +1,70 @@ + + + + + From 6b45d35e85f1622b697af2804c65714ad8e5321a Mon Sep 17 00:00:00 2001 From: Mykyta Date: Fri, 14 Aug 2026 20:32:26 +0200 Subject: [PATCH 053/247] ref --- client/src/layouts/Dashboard.vue | 70 -------------------------------- 1 file changed, 70 deletions(-) delete mode 100644 client/src/layouts/Dashboard.vue diff --git a/client/src/layouts/Dashboard.vue b/client/src/layouts/Dashboard.vue deleted file mode 100644 index ca8b70ec..00000000 --- a/client/src/layouts/Dashboard.vue +++ /dev/null @@ -1,70 +0,0 @@ - - - - - From f142cad649a3a3274f573cf4092b4fb2cdc1204f Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 10:45:02 +0200 Subject: [PATCH 054/247] feat: added refresh --- openadmin/fastapi/admin_page.py | 3 +++ openadmin/spec/area_chart.py | 1 + openadmin/spec/bar_chart.py | 2 ++ openadmin/spec/line_chart.py | 1 + openadmin/spec/markdown.py | 2 ++ openadmin/spec/pie_chart.py | 2 ++ openadmin/spec/stat.py | 2 ++ openadmin/spec/table.py | 2 ++ 8 files changed, 15 insertions(+) diff --git a/openadmin/fastapi/admin_page.py b/openadmin/fastapi/admin_page.py index b843074e..c2ffc4ae 100644 --- a/openadmin/fastapi/admin_page.py +++ b/openadmin/fastapi/admin_page.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later from collections.abc import Awaitable, Callable +from datetime import timedelta from fastapi import APIRouter from openadmin import spec @@ -38,6 +39,7 @@ def table( columns: dict[str, spec.ColumnConfigValue] | None = None, icon: spec.Icon | None = None, color: spec.Color | None = None, + refresh: timedelta | None = None ): table_id = utils.get_id(name) @@ -54,6 +56,7 @@ def table( "query": None, "body": None, "form": None, + 'refresh': refresh // timedelta(milliseconds=1) if refresh is not None else None, } self.components.append(item) diff --git a/openadmin/spec/area_chart.py b/openadmin/spec/area_chart.py index a5ad28c5..cfc8708b 100644 --- a/openadmin/spec/area_chart.py +++ b/openadmin/spec/area_chart.py @@ -13,6 +13,7 @@ class AreaChartComponent(TypedDict): id: str name: str description: str | None + refresh: int | None method: HttpMethod form: JsonSchema | None body: JsonSchema | None diff --git a/openadmin/spec/bar_chart.py b/openadmin/spec/bar_chart.py index 42c283bd..224449f8 100644 --- a/openadmin/spec/bar_chart.py +++ b/openadmin/spec/bar_chart.py @@ -28,6 +28,7 @@ class BarChartComponent(TypedDict): caption: str | None caption_description: str | None caption_icon: Icon | None + refresh: int | None method: HttpMethod form: JsonSchema | None body: JsonSchema | None @@ -45,6 +46,7 @@ class BarChartResponce(TypedDict): config: NotRequired[dict[str, BarChartConfigValue]] icon: NotRequired[Icon] color: NotRequired[Color] + refresh: int | None data: BarChartData diff --git a/openadmin/spec/line_chart.py b/openadmin/spec/line_chart.py index 80b88a5c..b2a2c6ba 100644 --- a/openadmin/spec/line_chart.py +++ b/openadmin/spec/line_chart.py @@ -13,6 +13,7 @@ class LineChartComponent(TypedDict): id: str name: str description: str | None + refresh: int | None method: HttpMethod form: JsonSchema | None body: JsonSchema | None diff --git a/openadmin/spec/markdown.py b/openadmin/spec/markdown.py index 8b596a8d..241b4014 100644 --- a/openadmin/spec/markdown.py +++ b/openadmin/spec/markdown.py @@ -17,6 +17,7 @@ class MarkdownComponent(TypedDict): description: str | None color: Color | None icon: Icon | None + refresh: int | None method: HttpMethod form: JsonSchema | None body: JsonSchema | None @@ -29,6 +30,7 @@ class MarkdownComponent(TypedDict): class MarkdownResponse(TypedDict): icon: NotRequired[Icon] color: NotRequired[Color] + refresh: int | None content: MarkdownContent diff --git a/openadmin/spec/pie_chart.py b/openadmin/spec/pie_chart.py index 9a15f629..ecde0cd7 100644 --- a/openadmin/spec/pie_chart.py +++ b/openadmin/spec/pie_chart.py @@ -29,6 +29,7 @@ class PieChartComponent(TypedDict): caption: str | None caption_description: str | None caption_icon: Icon | None + refresh: int | None method: HttpMethod form: JsonSchema | None body: JsonSchema | None @@ -47,6 +48,7 @@ class PieChartResponce(TypedDict): icon: NotRequired[Icon] color: NotRequired[Color] data: PieChartData + refresh: int | None type PieChart = PieChartData | PieChartResponce diff --git a/openadmin/spec/stat.py b/openadmin/spec/stat.py index bdc46870..e573a102 100644 --- a/openadmin/spec/stat.py +++ b/openadmin/spec/stat.py @@ -16,6 +16,7 @@ class StatComponent(TypedDict): icon: Icon | None color: Color | None name: str + refresh: int | None description: str | None method: HttpMethod form: JsonSchema | None @@ -28,6 +29,7 @@ class StatComponent(TypedDict): class StatResponse(TypedDict): value: StatValue + refresh: int | None icon: NotRequired[Icon] color: NotRequired[Color] diff --git a/openadmin/spec/table.py b/openadmin/spec/table.py index fa97ca7c..e3d647a8 100644 --- a/openadmin/spec/table.py +++ b/openadmin/spec/table.py @@ -43,6 +43,7 @@ class TableComponent(TypedDict): color: Color | None method: HttpMethod is_hidden: bool + refresh: int | None form: JsonSchema | None body: JsonSchema | None query: JsonSchema | None @@ -65,6 +66,7 @@ class TableResponse(TypedDict): data: TableData icon: NotRequired[Icon] color: NotRequired[Color] + refresh: int | None type Table = TableData | TableResponse From 05a5c923a37ef699503e0d85ec0e648fbe0546a4 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 10:47:30 +0200 Subject: [PATCH 055/247] feat: added refresh to all components --- openadmin/fastapi/admin_page.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openadmin/fastapi/admin_page.py b/openadmin/fastapi/admin_page.py index c2ffc4ae..6918c85b 100644 --- a/openadmin/fastapi/admin_page.py +++ b/openadmin/fastapi/admin_page.py @@ -76,6 +76,7 @@ def stat( icon: spec.Icon | None = None, color: spec.Color | None = None, description: str | None = None, + refresh: timedelta | None = None, ): stat_id = utils.get_id(name) @@ -90,6 +91,7 @@ def stat( "query": None, "body": None, "form": None, + "refresh": refresh // timedelta(milliseconds=1) if refresh is not None else None, } self.components.append(item) @@ -109,6 +111,7 @@ def markdown( description: str | None = None, color: spec.Color | None = None, icon: spec.Icon | None = None, + refresh: timedelta | None = None, ): markdown_id = utils.get_id(name) @@ -123,6 +126,7 @@ def markdown( "query": None, "body": None, "form": None, + "refresh": refresh // timedelta(milliseconds=1) if refresh is not None else None, } self.components.append(item) @@ -323,6 +327,7 @@ def bar_chart( caption_icon: spec.Icon | None = None, config: dict[str, spec.BarChartConfigValue] | None = None, data_key: str | None = None, + refresh: timedelta | None = None, ): bar_chart_id = utils.get_id(name) @@ -342,6 +347,7 @@ def bar_chart( "query": None, "body": None, "form": None, + "refresh": refresh // timedelta(milliseconds=1) if refresh is not None else None, } self.components.append(item) @@ -396,6 +402,7 @@ def pie_chart( caption: str | None = None, caption_description: str | None = None, caption_icon: spec.Icon | None = None, + refresh: timedelta | None = None, ): pie_chart_id = utils.get_id(name) @@ -416,6 +423,7 @@ def pie_chart( "query": None, "body": None, "form": None, + "refresh": refresh // timedelta(milliseconds=1) if refresh is not None else None, } self.components.append(item) From ba04a702893b08ea714eba0999fe639e7540513b Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 10:47:46 +0200 Subject: [PATCH 056/247] ref --- openadmin/fastapi/admin_page.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openadmin/fastapi/admin_page.py b/openadmin/fastapi/admin_page.py index 6918c85b..789f21db 100644 --- a/openadmin/fastapi/admin_page.py +++ b/openadmin/fastapi/admin_page.py @@ -39,7 +39,7 @@ def table( columns: dict[str, spec.ColumnConfigValue] | None = None, icon: spec.Icon | None = None, color: spec.Color | None = None, - refresh: timedelta | None = None + refresh: timedelta | None = None, ): table_id = utils.get_id(name) @@ -56,7 +56,9 @@ def table( "query": None, "body": None, "form": None, - 'refresh': refresh // timedelta(milliseconds=1) if refresh is not None else None, + "refresh": refresh // timedelta(milliseconds=1) + if refresh is not None + else None, } self.components.append(item) @@ -91,7 +93,9 @@ def stat( "query": None, "body": None, "form": None, - "refresh": refresh // timedelta(milliseconds=1) if refresh is not None else None, + "refresh": refresh // timedelta(milliseconds=1) + if refresh is not None + else None, } self.components.append(item) @@ -126,7 +130,9 @@ def markdown( "query": None, "body": None, "form": None, - "refresh": refresh // timedelta(milliseconds=1) if refresh is not None else None, + "refresh": refresh // timedelta(milliseconds=1) + if refresh is not None + else None, } self.components.append(item) @@ -347,7 +353,9 @@ def bar_chart( "query": None, "body": None, "form": None, - "refresh": refresh // timedelta(milliseconds=1) if refresh is not None else None, + "refresh": refresh // timedelta(milliseconds=1) + if refresh is not None + else None, } self.components.append(item) @@ -423,7 +431,9 @@ def pie_chart( "query": None, "body": None, "form": None, - "refresh": refresh // timedelta(milliseconds=1) if refresh is not None else None, + "refresh": refresh // timedelta(milliseconds=1) + if refresh is not None + else None, } self.components.append(item) From 861a5e01ce5a3d30e4dfe8a2fb38af845f74a03e Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 10:50:15 +0200 Subject: [PATCH 057/247] feat: added style to table responce --- openadmin/spec/table.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openadmin/spec/table.py b/openadmin/spec/table.py index e3d647a8..3604bfda 100644 --- a/openadmin/spec/table.py +++ b/openadmin/spec/table.py @@ -55,6 +55,7 @@ class TableComponent(TypedDict): { "__view__": str | int | float | bool | None, "__actions__": list[ActionConfig], + "__style__": ColumnStyle | None }, extra_items=str | int | float | bool | None, ) From 7e5d622f25022709be4d5ccc0b251ed886ba233b Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 11:59:00 +0200 Subject: [PATCH 058/247] ref --- Makefile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Makefile b/Makefile index 17c15a22..8cedad1e 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ +# +# Fix +# + fix/license: @ uv run reuse download --all @ uv run reuse annotate --license AGPL-3.0-or-later --copyright "OpenAdmin" --recursive --skip-unrecognised openadmin/ @@ -15,6 +19,10 @@ fix/lint: fix: fix/license fix/format fix/lint +# +# Check +# + check/format: @ cd client && bun run check:format @ uv run ruff format --check . @@ -46,6 +54,10 @@ check/test: check: check/format check/lint check/typing check/cves check/security check/unused check/spell check/license check/test +# +# Dev +# + dev: @ cd client && bun run build @ PYTHONPATH=. uv run fastapi dev examples/main.py --host 0.0.0.0 --port $${PORT:-8000} --reload \ No newline at end of file From 5ddd608d31c70ce43f9f1274de32f042b8696c14 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 12:09:24 +0200 Subject: [PATCH 059/247] ref --- Makefile | 7 +++++-- client/vite.config.ts | 12 ++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8cedad1e..b655fc1e 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,9 @@ check: check/format check/lint check/typing check/cves check/security check/unus # Dev # -dev: +dev/client: + @ cd client && bun run dev + +dev/example: @ cd client && bun run build - @ PYTHONPATH=. uv run fastapi dev examples/main.py --host 0.0.0.0 --port $${PORT:-8000} --reload \ No newline at end of file + @ PYTHONPATH=. uv run fastapi dev examples/main.py --host 0.0.0.0 --port $${PORT:-8000} --reload diff --git a/client/vite.config.ts b/client/vite.config.ts index b50af815..e9bece78 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -15,4 +15,16 @@ export default defineConfig({ outDir: path.resolve(import.meta.dirname, "../openadmin/__client__"), emptyOutDir: true, }, + server: { + proxy: { + "/api": { + target: "http://localhost:8000/admin", + changeOrigin: true, + }, + "/auth": { + target: "http://localhost:8000/admin", + changeOrigin: true, + }, + }, + }, }) From fe64dfb4dca1d4f7412a1ef01676c8729cb8d901 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 12:15:13 +0200 Subject: [PATCH 060/247] ref --- client/src/components/dashboard/Sidebar.vue | 70 +++++++++++++++++++++ openadmin/spec/table.py | 2 +- 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 client/src/components/dashboard/Sidebar.vue diff --git a/client/src/components/dashboard/Sidebar.vue b/client/src/components/dashboard/Sidebar.vue new file mode 100644 index 00000000..ca8b70ec --- /dev/null +++ b/client/src/components/dashboard/Sidebar.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/openadmin/spec/table.py b/openadmin/spec/table.py index 3604bfda..f2651c54 100644 --- a/openadmin/spec/table.py +++ b/openadmin/spec/table.py @@ -55,7 +55,7 @@ class TableComponent(TypedDict): { "__view__": str | int | float | bool | None, "__actions__": list[ActionConfig], - "__style__": ColumnStyle | None + "__style__": ColumnStyle | None, }, extra_items=str | int | float | bool | None, ) From 8737752d5391dca05f7ffe20d565a64d8fd3916a Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 12:17:53 +0200 Subject: [PATCH 061/247] ref --- .../src/components/dashboard/ThemeToggle.vue | 24 +++++++++++++++++++ client/src/composables/theme.ts | 8 +++++++ 2 files changed, 32 insertions(+) create mode 100644 client/src/components/dashboard/ThemeToggle.vue create mode 100644 client/src/composables/theme.ts diff --git a/client/src/components/dashboard/ThemeToggle.vue b/client/src/components/dashboard/ThemeToggle.vue new file mode 100644 index 00000000..14d15518 --- /dev/null +++ b/client/src/components/dashboard/ThemeToggle.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/client/src/composables/theme.ts b/client/src/composables/theme.ts new file mode 100644 index 00000000..99ea3eb5 --- /dev/null +++ b/client/src/composables/theme.ts @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2026 OpenAdmin +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { useDark, useToggle } from "@vueuse/core" + +export const isDark = useDark({ storageKey: "theme" }) +export const toggleDark = useToggle(isDark) From eb7f0525f5a4f09e7c7f6ed0944b4e21325e4a42 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 12:22:15 +0200 Subject: [PATCH 062/247] ref --- client/src/layout/Dashboard.vue | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 client/src/layout/Dashboard.vue diff --git a/client/src/layout/Dashboard.vue b/client/src/layout/Dashboard.vue new file mode 100644 index 00000000..9ff07205 --- /dev/null +++ b/client/src/layout/Dashboard.vue @@ -0,0 +1,22 @@ + + + + + From e5a66529222bed834cdbe3a922c718628ce44664 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 12:48:24 +0200 Subject: [PATCH 063/247] ref --- client/src/{layout => layouts}/Dashboard.vue | 2 +- client/src/router/index.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) rename client/src/{layout => layouts}/Dashboard.vue (95%) diff --git a/client/src/layout/Dashboard.vue b/client/src/layouts/Dashboard.vue similarity index 95% rename from client/src/layout/Dashboard.vue rename to client/src/layouts/Dashboard.vue index 9ff07205..ae80b47d 100644 --- a/client/src/layout/Dashboard.vue +++ b/client/src/layouts/Dashboard.vue @@ -15,7 +15,7 @@ import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
- +
diff --git a/client/src/router/index.ts b/client/src/router/index.ts index c8f46e6a..7a372b75 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -18,8 +18,14 @@ export const router = createRouter({ }, { path: "/", - name: "home", - component: () => import("@/views/HomeView.vue"), + component: () => import("@/layouts/Dashboard.vue"), + children: [ + { + path: "", + name: "home", + component: () => import("@/views/HomeView.vue"), + }, + ], }, ], }) From cdab93ca3f98f7f79ffb7a0f09c35f65ca4f9c58 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 12:54:55 +0200 Subject: [PATCH 064/247] feat: page view --- client/src/router/index.ts | 5 +++++ client/src/views/PageView.vue | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 client/src/views/PageView.vue diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 7a372b75..6d758b9c 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -25,6 +25,11 @@ export const router = createRouter({ name: "home", component: () => import("@/views/HomeView.vue"), }, + { + path: ":sectionId/:pageId", + name: "page", + component: () => import("@/views/PageView.vue"), + }, ], }, ], diff --git a/client/src/views/PageView.vue b/client/src/views/PageView.vue new file mode 100644 index 00000000..828d6356 --- /dev/null +++ b/client/src/views/PageView.vue @@ -0,0 +1,22 @@ + + + + + From d3afba23fa3b6f977951251d371ffd7c9207a270 Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 12:56:24 +0200 Subject: [PATCH 065/247] feat: add iconify --- client/bun.lock | 5 +++++ client/package.json | 1 + 2 files changed, 6 insertions(+) diff --git a/client/bun.lock b/client/bun.lock index e2f3207c..21084240 100644 --- a/client/bun.lock +++ b/client/bun.lock @@ -23,6 +23,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.5.7", + "@iconify/vue": "^5.0.1", "@types/node": "^26.2.0", "@vitejs/plugin-vue": "^6.0.8", "@vue/tsconfig": "^0.9.1", @@ -102,6 +103,10 @@ "@floating-ui/vue": ["@floating-ui/vue@1.1.11", "", { "dependencies": { "@floating-ui/dom": "^1.7.6", "@floating-ui/utils": "^0.2.11", "vue-demi": ">=0.13.0" } }, "sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw=="], + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/vue": ["@iconify/vue@5.0.1", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "vue": ">=3.0.0" } }, "sha512-aumwwooJlFJ5H5qYWB6ZTAyM0C8hpfcSVLB9/a3qnH1GGvIJ+FEbpEs4s/HfErYe/M5qZeLjwmESR5fFm3lXEw=="], + "@internationalized/date": ["@internationalized/date@3.12.3", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q=="], "@internationalized/number": ["@internationalized/number@3.6.7", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg=="], diff --git a/client/package.json b/client/package.json index 800f1def..d2dd1e33 100644 --- a/client/package.json +++ b/client/package.json @@ -33,6 +33,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.5.7", + "@iconify/vue": "^5.0.1", "@types/node": "^26.2.0", "@vitejs/plugin-vue": "^6.0.8", "@vue/tsconfig": "^0.9.1", From 7b8c284d98e6d96ca627868f1fc13994a236e6fb Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 12:58:06 +0200 Subject: [PATCH 066/247] feat: add iconify --- client/src/components/dashboard/Sidebar.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/dashboard/Sidebar.vue b/client/src/components/dashboard/Sidebar.vue index ca8b70ec..d91e7ecf 100644 --- a/client/src/components/dashboard/Sidebar.vue +++ b/client/src/components/dashboard/Sidebar.vue @@ -20,6 +20,7 @@ import { SidebarRail, } from "@/components/ui/sidebar" import { useOpenAdminSpec } from "@/composables/openadmin-spec" +import { Icon } from "@iconify/vue"; const { data: spec } = useOpenAdminSpec() const route = useRoute() @@ -56,7 +57,7 @@ const route = useRoute() - + {{ page.name }} From 21ce80c6176256c04e774f9bc4de40eac71b550c Mon Sep 17 00:00:00 2001 From: Mykyta Date: Sat, 15 Aug 2026 13:02:31 +0200 Subject: [PATCH 067/247] ref --- client/src/layouts/Dashboard.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/layouts/Dashboard.vue b/client/src/layouts/Dashboard.vue index ae80b47d..4358d371 100644 --- a/client/src/layouts/Dashboard.vue +++ b/client/src/layouts/Dashboard.vue @@ -5,9 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-or-later -->