openadmin-dev 0.7.18__tar.gz → 0.7.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.gitignore +2 -1
- openadmin_dev-0.7.20/PKG-INFO +108 -0
- openadmin_dev-0.7.20/README.md +91 -0
- openadmin_dev-0.7.20/docs/assets/dashboard-screenshot.png +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/index.md +1 -1
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/introduction/getting-started.md +2 -2
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/authors.py +1 -5
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/books.py +15 -12
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/control_panel.py +17 -28
- openadmin_dev-0.7.20/examples/admin/form_test.py +73 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/health.py +4 -1
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/overview.py +1 -1
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/publishers.py +1 -4
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/tags.py +7 -6
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/weekdays.py +1 -4
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/main.py +4 -1
- openadmin_dev-0.7.20/openadmin/__client__/assets/CardTitle-g-eR1YB7.js +1 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/Dashboard-CH62YEWP.js +1 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/DialogPortal-sTp649jA.js +1 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/FormView-DPqNEejF.js +193 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/LoginView-3isjvo_Q.js +1 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/PageView-Dq8SJOqN.js +67 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/SelectValue-WzbxPrgA.js +1 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/error-C_Ap5sRv.js +66 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/field-BSJU6_no.js +1 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/index-B5AJQb5X.css +2 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/index-BOBko9Fy.js +2 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/openadmin-form-Dp1fg3ME.js +3 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/openadmin-spec-DMXwLYN0.js +1 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/theme-CEh0Zsbj.js +1 -0
- openadmin_dev-0.7.20/openadmin/__client__/assets/useForm-DH6J4tEc.js +1 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/index.html +4 -6
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/admin_page.py +3 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/__init__.py +2 -1
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/action.py +0 -4
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/form.py +3 -4
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/page.py +2 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/pyproject.toml +1 -1
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/uv.lock +1 -1
- openadmin_dev-0.7.18/PKG-INFO +0 -332
- openadmin_dev-0.7.18/README.md +0 -315
- openadmin_dev-0.7.18/openadmin/__client__/assets/CardTitle-_-VQGAs4.js +0 -1
- openadmin_dev-0.7.18/openadmin/__client__/assets/Dashboard-Cbu4jQ6o.js +0 -1
- openadmin_dev-0.7.18/openadmin/__client__/assets/LoginView-DftiEGqx.js +0 -1
- openadmin_dev-0.7.18/openadmin/__client__/assets/PageView-3k326RhX.js +0 -67
- openadmin_dev-0.7.18/openadmin/__client__/assets/button-BahsXozd.js +0 -1
- openadmin_dev-0.7.18/openadmin/__client__/assets/error-BjWDzPTy.js +0 -66
- openadmin_dev-0.7.18/openadmin/__client__/assets/index-B6cWCMDE.js +0 -2
- openadmin_dev-0.7.18/openadmin/__client__/assets/index-DHDnMjXv.css +0 -1
- openadmin_dev-0.7.18/openadmin/__client__/assets/lib-DCDnSbDN.js +0 -1
- openadmin_dev-0.7.18/openadmin/__client__/assets/openadmin-page-BBinRZu9.js +0 -3
- openadmin_dev-0.7.18/openadmin/__client__/assets/openadmin-spec-B5RYk8O-.js +0 -1
- openadmin_dev-0.7.18/openadmin/__client__/assets/theme-BbaAAQ0K.js +0 -1
- openadmin_dev-0.7.18/openadmin/__client__/assets/vue-router-CAoMHVFh.js +0 -1
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.claude/launch.json +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/ISSUE_TEMPLATE/bug.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/ISSUE_TEMPLATE/chore.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/ISSUE_TEMPLATE/feature.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/branch-name/action.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/dependency-cves/action.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/format/action.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/license/action.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/lint/action.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/security/action.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/spell-check/action.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/test/action.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/typecheck/action.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/unused-code/action.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/workflows/ci.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/workflows/docs.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/workflows/publish.yml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.python-version +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/LICENSE +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/LICENSES/AGPL-3.0-or-later.txt +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/Makefile +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/REUSE.toml +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/.gitignore +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/assets/logo.png +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/bun.lock +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/config/.vitepress/config.mts +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/config/.vitepress/theme/index.ts +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/config/.vitepress/theme/style.css +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/auth/index.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/action.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/area-chart.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/bar-chart.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/form.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/index.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/line-chart.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/markdown.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/pie-chart.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/stat.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/table.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/cookbook/implementing-auth.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/cookbook/implementing-form-with-reference.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/cookbook/implementing-table.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/introduction/what-is-openadmin.md +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/package.json +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/__init__.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/__init__.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/analytics.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/auth.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/docs.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/genres.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/reports.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/lib/__init__.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/lib/database.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/lib/lifespan.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/lib/models.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/lib/seed.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/HomeView-BY92UWl4.js +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-cyrillic-400-normal-DXusLSnH.woff2 +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-cyrillic-400-normal-DkrqoNl2.woff +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-cyrillic-ext-400-normal-ChfpGzr5.woff +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-latin-400-normal-B40WzpMT.woff2 +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-latin-400-normal-akEymXtG.woff +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-latin-ext-400-normal-Bz1pQMyt.woff +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-latin-ext-400-normal-CND6cjiG.woff2 +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-cyrillic-400-normal-C51Di1Mf.woff2 +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-cyrillic-400-normal-CPJFfJgk.woff +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-latin-400-normal-B2yvC1Cq.woff +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-latin-400-normal-DTRLJnHl.woff2 +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-latin-ext-400-normal-CfzLURNc.woff +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-latin-ext-400-normal-DI-rJ0UV.woff2 +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-symbols2-400-normal-DjbiTEEA.woff +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-vietnamese-400-normal-CZEPLOgu.woff +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-vietnamese-400-normal-C8xY9-dI.woff +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/logo-D9vyMJZl.png +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/rolldown-runtime-DK3Fl9T5.js +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/runtime-core.esm-bundler-B4b-2GOK.js +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/favicon.svg +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/icons.svg +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/__init__.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/admin_auth.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/admin_panel.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/counter.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/deps.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/exc_handler.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/refs.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/req.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/utils.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/area_chart.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/bar_chart.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/colors.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/component.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/error.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/http_methods.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/icons.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/json_schema.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/line_chart.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/markdown.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/pie_chart.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/section.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/spec.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/stat.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/table.py +0 -0
- {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/tests/test_hello.py +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: openadmin-dev
|
|
3
|
+
Version: 0.7.20
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Project-URL: Homepage, https://github.com/openadmin-team/openadmin-py
|
|
6
|
+
Project-URL: Issues, https://github.com/openadmin-team/openadmin-py/issues
|
|
7
|
+
Author-email: Mykyta Kasianenko <mykytakasianenko@gmail.com>
|
|
8
|
+
License-Expression: AGPL-3.0-or-later
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.14
|
|
13
|
+
Requires-Dist: fastapi[standard]>=0.136.3
|
|
14
|
+
Requires-Dist: itsdangerous>=2.2.0
|
|
15
|
+
Requires-Dist: typing-extensions>=4.12.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
<div align="center">
|
|
19
|
+
<img src="docs/assets/logo.png" alt="OpenAdmin" width="120" />
|
|
20
|
+
|
|
21
|
+
<h1>OpenAdmin</h1>
|
|
22
|
+
|
|
23
|
+
<p>Admin dashboards as FastAPI routes. No templates, no frontend project, no config files.</p>
|
|
24
|
+
|
|
25
|
+
<a href="https://pypi.org/project/openadmin/"><img src="https://img.shields.io/pypi/v/openadmin?style=flat-square&label=pypi&color=blue" alt="PyPI" /></a>
|
|
26
|
+
<a href="https://python.org"><img src="https://img.shields.io/badge/python-3.14%2B-blue?style=flat-square" alt="Python" /></a>
|
|
27
|
+
<a href="https://fastapi.tiangolo.com"><img src="https://img.shields.io/badge/built%20on-FastAPI-009688?style=flat-square" alt="FastAPI" /></a>
|
|
28
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0--or--later-green?style=flat-square" alt="License" /></a>
|
|
29
|
+
|
|
30
|
+
<p><a href="https://openadmin-team.github.io/openadmin-py/">Documentation</a></p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<img src="docs/assets/dashboard-screenshot.png" alt="OpenAdmin dashboard" width="100%" />
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
OpenAdmin is a FastAPI-native library for building admin panels. A page is a router, a widget is an endpoint — stats, tables, forms, actions, and charts, defined with typed decorators in plain Python.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from openadmin import spec
|
|
41
|
+
from openadmin.fastapi import AdminPage, AdminPanel
|
|
42
|
+
|
|
43
|
+
page = AdminPage("Dashboard", icon="layout-dashboard")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@page.stat("Total Users")
|
|
47
|
+
async def total_users() -> spec.Stat:
|
|
48
|
+
return 1_024
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@page.table("Recent Users")
|
|
52
|
+
async def recent_users() -> spec.Table:
|
|
53
|
+
return {
|
|
54
|
+
"data": [
|
|
55
|
+
{"id": 1, "name": "Alice", "role": "admin"},
|
|
56
|
+
{"id": 2, "name": "Bob", "role": "viewer"},
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
admin = AdminPanel("My Admin")
|
|
62
|
+
admin.section("General", pages=[page])
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from fastapi import FastAPI
|
|
67
|
+
|
|
68
|
+
app = FastAPI()
|
|
69
|
+
app.mount("/admin", admin.app)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Each decorated function is a normal FastAPI handler — dependency injection, `Query`/`Body` parameters, and OpenAPI docs all work as they would on any route.
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install openadmin
|
|
78
|
+
# or
|
|
79
|
+
uv add openadmin
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Documentation
|
|
83
|
+
|
|
84
|
+
Everything else — the full component reference, authentication, and worked cookbook recipes — lives at:
|
|
85
|
+
|
|
86
|
+
**[openadmin-team.github.io/openadmin-py](https://openadmin-team.github.io/openadmin-py/)**
|
|
87
|
+
|
|
88
|
+
- [Getting Started](https://openadmin-team.github.io/openadmin-py/introduction/getting-started) — build a minimal panel end to end
|
|
89
|
+
- [Components](https://openadmin-team.github.io/openadmin-py/components/) — stats, tables, forms, actions, and charts
|
|
90
|
+
- [Authentication](https://openadmin-team.github.io/openadmin-py/auth/) — gate the panel behind a login screen
|
|
91
|
+
- [Cookbook](https://openadmin-team.github.io/openadmin-py/cookbook/implementing-auth) — recipes for row actions, reference fields, and auth
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Run the example app
|
|
97
|
+
make dev/example
|
|
98
|
+
|
|
99
|
+
# Run all checks (format, lint, types, tests, security)
|
|
100
|
+
make check
|
|
101
|
+
|
|
102
|
+
# Auto-fix formatting and lint issues
|
|
103
|
+
make fix
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
[AGPL-3.0-or-later](LICENSE) — © 2026 OpenAdmin
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="docs/assets/logo.png" alt="OpenAdmin" width="120" />
|
|
3
|
+
|
|
4
|
+
<h1>OpenAdmin</h1>
|
|
5
|
+
|
|
6
|
+
<p>Admin dashboards as FastAPI routes. No templates, no frontend project, no config files.</p>
|
|
7
|
+
|
|
8
|
+
<a href="https://pypi.org/project/openadmin/"><img src="https://img.shields.io/pypi/v/openadmin?style=flat-square&label=pypi&color=blue" alt="PyPI" /></a>
|
|
9
|
+
<a href="https://python.org"><img src="https://img.shields.io/badge/python-3.14%2B-blue?style=flat-square" alt="Python" /></a>
|
|
10
|
+
<a href="https://fastapi.tiangolo.com"><img src="https://img.shields.io/badge/built%20on-FastAPI-009688?style=flat-square" alt="FastAPI" /></a>
|
|
11
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0--or--later-green?style=flat-square" alt="License" /></a>
|
|
12
|
+
|
|
13
|
+
<p><a href="https://openadmin-team.github.io/openadmin-py/">Documentation</a></p>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<img src="docs/assets/dashboard-screenshot.png" alt="OpenAdmin dashboard" width="100%" />
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
OpenAdmin is a FastAPI-native library for building admin panels. A page is a router, a widget is an endpoint — stats, tables, forms, actions, and charts, defined with typed decorators in plain Python.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from openadmin import spec
|
|
24
|
+
from openadmin.fastapi import AdminPage, AdminPanel
|
|
25
|
+
|
|
26
|
+
page = AdminPage("Dashboard", icon="layout-dashboard")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@page.stat("Total Users")
|
|
30
|
+
async def total_users() -> spec.Stat:
|
|
31
|
+
return 1_024
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@page.table("Recent Users")
|
|
35
|
+
async def recent_users() -> spec.Table:
|
|
36
|
+
return {
|
|
37
|
+
"data": [
|
|
38
|
+
{"id": 1, "name": "Alice", "role": "admin"},
|
|
39
|
+
{"id": 2, "name": "Bob", "role": "viewer"},
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
admin = AdminPanel("My Admin")
|
|
45
|
+
admin.section("General", pages=[page])
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from fastapi import FastAPI
|
|
50
|
+
|
|
51
|
+
app = FastAPI()
|
|
52
|
+
app.mount("/admin", admin.app)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Each decorated function is a normal FastAPI handler — dependency injection, `Query`/`Body` parameters, and OpenAPI docs all work as they would on any route.
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install openadmin
|
|
61
|
+
# or
|
|
62
|
+
uv add openadmin
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Documentation
|
|
66
|
+
|
|
67
|
+
Everything else — the full component reference, authentication, and worked cookbook recipes — lives at:
|
|
68
|
+
|
|
69
|
+
**[openadmin-team.github.io/openadmin-py](https://openadmin-team.github.io/openadmin-py/)**
|
|
70
|
+
|
|
71
|
+
- [Getting Started](https://openadmin-team.github.io/openadmin-py/introduction/getting-started) — build a minimal panel end to end
|
|
72
|
+
- [Components](https://openadmin-team.github.io/openadmin-py/components/) — stats, tables, forms, actions, and charts
|
|
73
|
+
- [Authentication](https://openadmin-team.github.io/openadmin-py/auth/) — gate the panel behind a login screen
|
|
74
|
+
- [Cookbook](https://openadmin-team.github.io/openadmin-py/cookbook/implementing-auth) — recipes for row actions, reference fields, and auth
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Run the example app
|
|
80
|
+
make dev/example
|
|
81
|
+
|
|
82
|
+
# Run all checks (format, lint, types, tests, security)
|
|
83
|
+
make check
|
|
84
|
+
|
|
85
|
+
# Auto-fix formatting and lint issues
|
|
86
|
+
make fix
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
[AGPL-3.0-or-later](LICENSE) — © 2026 OpenAdmin
|
|
Binary file
|
|
@@ -5,7 +5,7 @@ layout: home
|
|
|
5
5
|
hero:
|
|
6
6
|
name: "OpenAdmin"
|
|
7
7
|
text: "Admin panel for python"
|
|
8
|
-
tagline: Build admin dashboards as FastAPI routes — stats, tables, charts, and actions, all in pure Python.
|
|
8
|
+
tagline: Build admin dashboards as FastAPI routes — stats, tables, charts, and actions, all in pure Python using any orm and api.
|
|
9
9
|
actions:
|
|
10
10
|
- theme: brand
|
|
11
11
|
text: What is OpenAdmin?
|
|
@@ -10,13 +10,13 @@ This walks through building a minimal admin panel from scratch: one page, one st
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
pip install openadmin
|
|
13
|
+
pip install openadmin
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
or with `uv`:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
uv add openadmin
|
|
19
|
+
uv add openadmin
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
## Build a page
|
|
@@ -160,7 +160,6 @@ class AddAuthorBody(BaseModel):
|
|
|
160
160
|
first_name: str
|
|
161
161
|
last_name: str
|
|
162
162
|
bio: str | None = None
|
|
163
|
-
friend: int
|
|
164
163
|
|
|
165
164
|
|
|
166
165
|
@page.form(
|
|
@@ -181,7 +180,4 @@ async def add_author(body: AddAuthorBody, session: AsyncSessionDep) -> spec.Form
|
|
|
181
180
|
await session.commit()
|
|
182
181
|
await session.refresh(author)
|
|
183
182
|
name = f"{author.first_name} {author.last_name}"
|
|
184
|
-
return {
|
|
185
|
-
"message": f"Added author '{name}'",
|
|
186
|
-
"table": {"id": author.id, "name": name},
|
|
187
|
-
}
|
|
183
|
+
return {"toast": f"Added author '{name}'"}
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
import asyncio
|
|
6
6
|
from urllib.parse import quote
|
|
7
7
|
|
|
8
|
-
from fastapi import Query
|
|
8
|
+
from fastapi import Query, UploadFile
|
|
9
9
|
from pydantic import BaseModel
|
|
10
10
|
from sqlalchemy import func, select
|
|
11
11
|
|
|
12
12
|
from openadmin import spec
|
|
13
|
-
from openadmin.fastapi import AdminPage
|
|
13
|
+
from openadmin.fastapi import AdminPage, reference_action
|
|
14
14
|
from openadmin.fastapi.deps import PageDep, SearchDep
|
|
15
15
|
|
|
16
16
|
from ..lib import models
|
|
@@ -44,6 +44,14 @@ async def get_books_without_publisher(session: AsyncSessionDep) -> int:
|
|
|
44
44
|
BOOK_COVER_URL = "https://img.magnific.com/free-photo/beautiful-tropical-beach-sea-ocean-with-white-cloud-blue-sky-copyspace_74190-8663.jpg?semt=ais_hybrid&w=740&q=80"
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
@page.action(
|
|
48
|
+
"Upload cover",
|
|
49
|
+
is_hidden=True,
|
|
50
|
+
)
|
|
51
|
+
async def upload_cover(id: int, cover: UploadFile) -> spec.Action:
|
|
52
|
+
return f"File uploaded {cover.filename} {id=}"
|
|
53
|
+
|
|
54
|
+
|
|
47
55
|
@page.table(
|
|
48
56
|
"All Books",
|
|
49
57
|
description="Browse all books with search and pagination",
|
|
@@ -112,6 +120,9 @@ async def get_all_books(
|
|
|
112
120
|
"reference": {"label": "Link to the book"},
|
|
113
121
|
"attachment": {"label": "Book in pdf"},
|
|
114
122
|
},
|
|
123
|
+
"__actions__": [
|
|
124
|
+
{"action": reference_action(upload_cover), "form": {"id": book.id}}
|
|
125
|
+
],
|
|
115
126
|
}
|
|
116
127
|
for book, author in result.all()
|
|
117
128
|
],
|
|
@@ -161,10 +172,7 @@ async def add_book(body: AddBookBody, session: AsyncSessionDep) -> spec.Form:
|
|
|
161
172
|
session.add(book)
|
|
162
173
|
await session.commit()
|
|
163
174
|
await session.refresh(book)
|
|
164
|
-
return {
|
|
165
|
-
"message": f"Added book '{book.title}'",
|
|
166
|
-
"table": {"id": book.id, "title": book.title},
|
|
167
|
-
}
|
|
175
|
+
return {"toast": f"Added book '{book.title}'"}
|
|
168
176
|
|
|
169
177
|
|
|
170
178
|
@page.action("Delete Book", method="delete", description="Remove a book by ID")
|
|
@@ -177,9 +185,4 @@ async def delete_book(
|
|
|
177
185
|
await session.delete(book)
|
|
178
186
|
await session.commit()
|
|
179
187
|
found = book is not None
|
|
180
|
-
return {
|
|
181
|
-
"message": f"Deleted book #{book_id}"
|
|
182
|
-
if found
|
|
183
|
-
else f"Book #{book_id} not found",
|
|
184
|
-
"table": {"deleted": book_id, "found": found},
|
|
185
|
-
}
|
|
188
|
+
return f"Deleted book #{book_id}" if found else f"Book #{book_id} not found"
|
|
@@ -48,10 +48,8 @@ async def ping_service(
|
|
|
48
48
|
target: str = Query("api", description="Service name to ping"),
|
|
49
49
|
) -> spec.Action:
|
|
50
50
|
return {
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"toast": f"{target} responded in 12ms",
|
|
54
|
-
"message": f"Pinged '{target}' — reachable (example response, nothing was contacted)",
|
|
51
|
+
"toast": f"Pinged '{target}' — reachable in 12ms "
|
|
52
|
+
"(example response, nothing was contacted)"
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
|
|
@@ -93,11 +91,7 @@ async def update_feature_flag(
|
|
|
93
91
|
flag_name: str = Query(..., description="Flag key, e.g. new-dashboard"),
|
|
94
92
|
enabled: bool = Query(..., description="New flag state"),
|
|
95
93
|
) -> spec.Action:
|
|
96
|
-
return {
|
|
97
|
-
"toast": f"{flag_name} -> {enabled}",
|
|
98
|
-
"message": "Preview of the change below (example data, flag was not modified)",
|
|
99
|
-
"table": [{"flag": flag_name, "previous": not enabled, "new": enabled}],
|
|
100
|
-
}
|
|
94
|
+
return {"toast": f"{flag_name} -> {enabled} (example only, flag was not modified)"}
|
|
101
95
|
|
|
102
96
|
|
|
103
97
|
@page.action(
|
|
@@ -124,12 +118,10 @@ async def rotate_api_key(
|
|
|
124
118
|
async def purge_temp_files(
|
|
125
119
|
older_than_days: int = Query(7, ge=0, description="Age threshold in days"),
|
|
126
120
|
) -> spec.Action:
|
|
127
|
-
return
|
|
128
|
-
"
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
)
|
|
132
|
-
}
|
|
121
|
+
return (
|
|
122
|
+
f"Would delete files older than {older_than_days} days "
|
|
123
|
+
"(example — nothing was purged)"
|
|
124
|
+
)
|
|
133
125
|
|
|
134
126
|
|
|
135
127
|
# ---------------------------------------------------------------------------
|
|
@@ -447,17 +439,7 @@ class WebhookBody(BaseModel):
|
|
|
447
439
|
color="teal",
|
|
448
440
|
)
|
|
449
441
|
async def create_webhook(body: WebhookBody) -> spec.Form:
|
|
450
|
-
return {
|
|
451
|
-
"icon": "webhook",
|
|
452
|
-
"color": "teal",
|
|
453
|
-
"toast": "Webhook created",
|
|
454
|
-
"message": f"Would create a webhook for '{body.event}' -> {body.url}",
|
|
455
|
-
"table": {
|
|
456
|
-
"data": [{"id": 1, "url": body.url, "event": body.event}],
|
|
457
|
-
"icon": "webhook",
|
|
458
|
-
"color": "teal",
|
|
459
|
-
},
|
|
460
|
-
}
|
|
442
|
+
return {"toast": f"Would create a webhook for '{body.event}' -> {body.url}"}
|
|
461
443
|
|
|
462
444
|
|
|
463
445
|
class RateLimitBody(BaseModel):
|
|
@@ -475,8 +457,6 @@ class RateLimitBody(BaseModel):
|
|
|
475
457
|
)
|
|
476
458
|
async def update_rate_limit(body: RateLimitBody) -> spec.Form:
|
|
477
459
|
return {
|
|
478
|
-
"icon": "gauge",
|
|
479
|
-
"color": "cyan",
|
|
480
460
|
"toast": f"Rate limit set to {body.requests_per_minute}/min "
|
|
481
461
|
f"(burst {body.burst_size}) — example only, nothing was saved",
|
|
482
462
|
}
|
|
@@ -509,6 +489,15 @@ async def rename_environment(
|
|
|
509
489
|
)
|
|
510
490
|
async def schedule_config_reset(
|
|
511
491
|
config_id: int = Query(..., description="Config entry to reset"),
|
|
492
|
+
environment: str = Query("production", description="Environment to target"),
|
|
493
|
+
reason: str = Query(..., description="Why the reset is needed"),
|
|
494
|
+
scheduled_by: str = Query(..., description="Who is requesting the reset"),
|
|
495
|
+
maintenance_window: str = Query(
|
|
496
|
+
..., description="Maintenance window label, e.g. Sat 02:00 UTC"
|
|
497
|
+
),
|
|
498
|
+
notify_owner: bool = Query(True, description="Notify the config owner beforehand"),
|
|
499
|
+
dry_run: bool = Query(False, description="Preview the reset without scheduling it"),
|
|
500
|
+
retry_count: int = Query(0, description="Number of retries if the reset fails"),
|
|
512
501
|
) -> spec.Form:
|
|
513
502
|
return None
|
|
514
503
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 OpenAdmin
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
from datetime import date, datetime
|
|
6
|
+
from enum import Enum, StrEnum
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
from fastapi import UploadFile
|
|
10
|
+
|
|
11
|
+
from openadmin import spec
|
|
12
|
+
from openadmin.fastapi import AdminPage
|
|
13
|
+
|
|
14
|
+
page = AdminPage("Test forms", icon="test-tube")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Methods(Enum):
|
|
18
|
+
GET = "GET"
|
|
19
|
+
POST = "POST"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MethodsStr(StrEnum):
|
|
23
|
+
GET = "GET"
|
|
24
|
+
POST = "POST"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@page.form(
|
|
28
|
+
"Test all fields",
|
|
29
|
+
method="post",
|
|
30
|
+
icon="university",
|
|
31
|
+
color="green",
|
|
32
|
+
fields={
|
|
33
|
+
"string": {"icon": "type", "color": "blue"},
|
|
34
|
+
"int": {"icon": "hash", "color": "indigo"},
|
|
35
|
+
"float": {"icon": "binary", "color": "violet"},
|
|
36
|
+
"date": {"icon": "calendar-days", "color": "purple"},
|
|
37
|
+
"dates": {"icon": "calendar", "color": "fuchsia"},
|
|
38
|
+
"bool": {"icon": "toggle-left", "color": "pink"},
|
|
39
|
+
"list_of_strings": {"icon": "list", "color": "rose"},
|
|
40
|
+
"list_of_ints": {"icon": "list-ordered", "color": "red"},
|
|
41
|
+
"file": {"icon": "file-up", "color": "orange"},
|
|
42
|
+
"files": {"icon": "files", "color": "amber"},
|
|
43
|
+
"rich_text": {"icon": "pilcrow", "color": "yellow", "style": "rich-text"},
|
|
44
|
+
"literals": {"icon": "list-filter", "color": "lime"},
|
|
45
|
+
"enum": {"icon": "tag", "color": "green"},
|
|
46
|
+
"str_enum": {"icon": "tags", "color": "emerald"},
|
|
47
|
+
"list_literals": {"icon": "list-checks", "color": "teal"},
|
|
48
|
+
"list_enum": {"icon": "layers", "color": "cyan"},
|
|
49
|
+
"list_str_enum": {"icon": "list-tree", "color": "sky"},
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
def get_info(
|
|
53
|
+
string: str,
|
|
54
|
+
int: int,
|
|
55
|
+
float: float,
|
|
56
|
+
date: list[datetime],
|
|
57
|
+
dates: list[date],
|
|
58
|
+
bool: bool,
|
|
59
|
+
list_of_strings: list[str],
|
|
60
|
+
list_of_ints: list[int],
|
|
61
|
+
file: UploadFile,
|
|
62
|
+
files: list[UploadFile],
|
|
63
|
+
rich_text: str,
|
|
64
|
+
literals: Literal["get", "post"],
|
|
65
|
+
enum: Methods,
|
|
66
|
+
str_enum: MethodsStr,
|
|
67
|
+
list_literals: list[Literal["get", "post"]],
|
|
68
|
+
list_enum: list[Methods],
|
|
69
|
+
list_str_enum: list[MethodsStr],
|
|
70
|
+
) -> spec.Form:
|
|
71
|
+
return {
|
|
72
|
+
"toast": f"All good ! {rich_text=} {list_literals=} {list_str_enum=} {list_enum=} {enum=} {literals=} {str_enum} {string=} {int=} {float=} {date=} {dates=} {bool=} {list_of_ints=} {list_of_strings=} {file.filename=} {len(files)=}"
|
|
73
|
+
}
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
from openadmin.fastapi import AdminPage
|
|
6
6
|
|
|
7
7
|
page = AdminPage(
|
|
8
|
-
"Health",
|
|
8
|
+
"Health",
|
|
9
|
+
icon="heart-pulse",
|
|
10
|
+
description="System health and infrastructure metrics",
|
|
11
|
+
color="blue",
|
|
9
12
|
)
|
|
10
13
|
|
|
11
14
|
|
|
@@ -86,7 +86,7 @@ Foreign keys are enforced at the application layer.
|
|
|
86
86
|
)
|
|
87
87
|
async def ping_database(session: AsyncSessionDep) -> spec.Action:
|
|
88
88
|
await session.execute(text("SELECT 1"))
|
|
89
|
-
return {"
|
|
89
|
+
return {"toast": "Database is reachable"}
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
async def _all_table_counts(session: AsyncSessionDep) -> list[tuple[str, int]]:
|
|
@@ -99,7 +99,4 @@ async def add_publisher(body: AddPublisherBody, session: AsyncSessionDep) -> spe
|
|
|
99
99
|
session.add(publisher)
|
|
100
100
|
await session.commit()
|
|
101
101
|
await session.refresh(publisher)
|
|
102
|
-
return {
|
|
103
|
-
"message": f"Added publisher '{publisher.name}'",
|
|
104
|
-
"table": {"id": publisher.id, "name": publisher.name},
|
|
105
|
-
}
|
|
102
|
+
return {"toast": f"Added publisher '{publisher.name}'"}
|
|
@@ -12,7 +12,9 @@ from openadmin.fastapi.deps import PageDep
|
|
|
12
12
|
from ..lib import models
|
|
13
13
|
from ..lib.database import AsyncSessionDep
|
|
14
14
|
|
|
15
|
-
page = AdminPage(
|
|
15
|
+
page = AdminPage(
|
|
16
|
+
"Tags", icon="tag", description="Manage and analyze book tags", color="red"
|
|
17
|
+
)
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
@page.stat("Total Tags")
|
|
@@ -97,9 +99,8 @@ async def remove_tag_from_book(
|
|
|
97
99
|
await session.delete(link)
|
|
98
100
|
await session.commit()
|
|
99
101
|
removed = link is not None
|
|
100
|
-
return
|
|
101
|
-
|
|
102
|
+
return (
|
|
103
|
+
f"Removed tag #{tag_id} from book #{book_id}"
|
|
102
104
|
if removed
|
|
103
|
-
else f"No such tag assignment (book #{book_id}, tag #{tag_id})"
|
|
104
|
-
|
|
105
|
-
}
|
|
105
|
+
else f"No such tag assignment (book #{book_id}, tag #{tag_id})"
|
|
106
|
+
)
|
|
@@ -15,10 +15,7 @@ WEEKDAYS: list[dict[str, Any]] = [
|
|
|
15
15
|
{"day": "Friday", "books": 10, "coffe": 9},
|
|
16
16
|
]
|
|
17
17
|
|
|
18
|
-
page = fastapi.AdminPage(
|
|
19
|
-
"Week Days",
|
|
20
|
-
icon="calendar",
|
|
21
|
-
)
|
|
18
|
+
page = fastapi.AdminPage("Week Days", icon="calendar", color="green")
|
|
22
19
|
|
|
23
20
|
|
|
24
21
|
@page.pie_chart(
|
|
@@ -15,6 +15,7 @@ from .admin import (
|
|
|
15
15
|
books,
|
|
16
16
|
control_panel,
|
|
17
17
|
docs,
|
|
18
|
+
form_test,
|
|
18
19
|
genres,
|
|
19
20
|
health,
|
|
20
21
|
overview,
|
|
@@ -62,6 +63,8 @@ admin_panel.section(
|
|
|
62
63
|
icon="server",
|
|
63
64
|
pages=[health.page, reports.page, overview.page, control_panel.page],
|
|
64
65
|
)
|
|
65
|
-
admin_panel.section(
|
|
66
|
+
admin_panel.section(
|
|
67
|
+
"Weekdays", icon="calendar-days", pages=[weekdays.page, form_test.page]
|
|
68
|
+
)
|
|
66
69
|
|
|
67
70
|
app.mount("/admin", admin_panel.app, name="admin_panel")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{On as e,Zn as t,b as n,k as r,mt as i,xt as a}from"./runtime-core.esm-bundler-B4b-2GOK.js";import{Wc as o}from"./error-C_Ap5sRv.js";var s=r({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card`,class:t(e(o)(`bg-card text-card-foreground flex flex-col gap-6 rounded-lg border py-6`,s.class))},[a(r.$slots,`default`)],2))}}),c=r({__name:`CardDescription`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`p`,{"data-slot":`card-description`,class:t(e(o)(`text-muted-foreground text-sm`,s.class))},[a(r.$slots,`default`)],2))}}),l=r({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card-header`,class:t(e(o)(`@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6`,s.class))},[a(r.$slots,`default`)],2))}}),u=r({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`h3`,{"data-slot":`card-title`,class:t(e(o)(`leading-none font-semibold`,s.class))},[a(r.$slots,`default`)],2))}});export{s as i,l as n,c as r,u as t};
|