fastapi-lite-admin 0.1.2__tar.gz → 0.1.4__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.
Files changed (28) hide show
  1. fastapi_lite_admin-0.1.4/PKG-INFO +209 -0
  2. fastapi_lite_admin-0.1.4/README.md +190 -0
  3. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/ui/templates/dashboard.html +12 -11
  4. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/ui/templates/layout.html +100 -27
  5. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/ui/templates/model_detail.html +25 -16
  6. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/ui/templates/model_form.html +30 -20
  7. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/ui/templates/model_list.html +56 -40
  8. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/ui/views.py +13 -3
  9. fastapi_lite_admin-0.1.4/fastapi_lite_admin.egg-info/PKG-INFO +209 -0
  10. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/pyproject.toml +1 -1
  11. fastapi_lite_admin-0.1.2/PKG-INFO +0 -43
  12. fastapi_lite_admin-0.1.2/README.md +0 -24
  13. fastapi_lite_admin-0.1.2/fastapi_lite_admin.egg-info/PKG-INFO +0 -43
  14. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/LICENSE +0 -0
  15. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/__init__.py +0 -0
  16. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/core/config.py +0 -0
  17. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/core/crud.py +0 -0
  18. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/core/registry.py +0 -0
  19. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/core/schema.py +0 -0
  20. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/dependencies/db.py +0 -0
  21. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/integrations/sqlalchemy.py +0 -0
  22. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/main.py +0 -0
  23. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_admin_lite/routers/admin.py +0 -0
  24. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_lite_admin.egg-info/SOURCES.txt +0 -0
  25. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_lite_admin.egg-info/dependency_links.txt +0 -0
  26. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_lite_admin.egg-info/requires.txt +0 -0
  27. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/fastapi_lite_admin.egg-info/top_level.txt +0 -0
  28. {fastapi_lite_admin-0.1.2 → fastapi_lite_admin-0.1.4}/setup.cfg +0 -0
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-lite-admin
3
+ Version: 0.1.4
4
+ Summary: A lightweight, pluggable admin panel for FastAPI
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: fastapi>=0.100.0
10
+ Requires-Dist: sqlalchemy>=2.0.0
11
+ Requires-Dist: pydantic>=2.0.0
12
+ Requires-Dist: jinja2>=3.1.0
13
+ Requires-Dist: python-multipart>=0.0.6
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest; extra == "dev"
16
+ Requires-Dist: httpx; extra == "dev"
17
+ Requires-Dist: uvicorn; extra == "dev"
18
+ Dynamic: license-file
19
+
20
+ # FastAPI Lite Admin
21
+
22
+ A premium, lightweight, pluggable admin panel for FastAPI and SQLAlchemy.
23
+
24
+ ## Features
25
+
26
+ - **Zero-config CRUD**: Automatically generate admin interfaces for your models.
27
+ - **ORM Agnostic**: Initial support for SQLAlchemy, designed to support others.
28
+ - **API First**: All admin actions are available via a clean, RESTful API.
29
+ - **Lightweight UI**: Fast, responsive, Jinja2 templates with server-side pagination, sorting, and search.
30
+ - **Attention Filters**: Highlight critical records (e.g. low stock, inactive users) requiring moderation attention.
31
+ - **Live Activity Badges**: "24h Activity" badges in headers and footers for real-time monitoring.
32
+ - **Custom System Logs**: Display a customizable activity feed on the main dashboard.
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ Install the package directly into your project:
39
+
40
+ ```bash
41
+ pip install fastapi-lite-admin
42
+ ```
43
+
44
+ For development/local setup, clone the repository and run:
45
+
46
+ ```bash
47
+ pip install -e ".[dev]"
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Quick Start & Usage
53
+
54
+ Using `FastAPI Lite Admin` is designed to be highly explicit, simple, and require minimal boilerplate. Here is how you can set it up in your application.
55
+
56
+ ### 1. Initialize the Admin Panel
57
+
58
+ Instantiate the `Admin` class and mount it to your `FastAPI` instance.
59
+
60
+ ```python
61
+ from fastapi import FastAPI
62
+ from fastapi_admin_lite import Admin
63
+
64
+ app = FastAPI()
65
+
66
+ admin = Admin(
67
+ title="Secure Control Panel", # Dashboard title
68
+ base_url="/admin", # URL prefix for the admin panel
69
+ enable_ui=True # Enable/disable Jinja-based UI
70
+ )
71
+
72
+ # Mount the admin router and views to your FastAPI app
73
+ admin.mount(app)
74
+ ```
75
+
76
+ This will automatically create and serve:
77
+ - The UI dashboard at `http://localhost:8000/admin`
78
+
79
+ ---
80
+
81
+ ## Registering Models (Adding Tables)
82
+
83
+ To add database tables to your admin panel, register your models using `admin.register()`.
84
+
85
+ ```python
86
+ from database import get_db # Your SQLAlchemy session dependency
87
+ from models import User
88
+
89
+ admin.register(
90
+ model=User,
91
+ get_db=get_db,
92
+ list_display=["id", "email", "is_active", "created_at"],
93
+ date_field="created_at",
94
+ attention_filter=(User.is_active == False),
95
+ readonly_fields=["created_at"],
96
+ config={"display_name": "System Users"}
97
+ )
98
+ ```
99
+
100
+ ### Registration Configuration Parameters
101
+
102
+ When calling `admin.register()`, you can configure how each model is represented:
103
+
104
+ | Parameter | Type | Required | Description |
105
+ | :--- | :--- | :--- | :--- |
106
+ | **`model`** | `Type[Any]` | **Yes** | The SQLAlchemy Declarative model class to generate CRUD operations for. |
107
+ | **`get_db`** | `Callable` | **Yes** | An async/sync generator yielding an active SQLAlchemy database session (`AsyncSession` or `Session`). |
108
+ | **`list_display`** | `List[str]` | No | List of field/column names to display as columns in the UI model list view. Defaults to all fields. |
109
+ | **`date_field`** | `str` | No | Name of the datetime field (e.g. `created_at`). Required to show the "24h Activity" count cards on the dashboard and lists. |
110
+ | **`attention_filter`** | `SQLAlchemy Expression` | No | A SQLAlchemy binary filter expression (e.g., `User.is_active == False` or `Product.stock < 10`) used to calculate and flag rows that require moderator attention. |
111
+ | **`readonly_fields`** | `List[str]` | No | List of columns that cannot be modified or set via creation or updates (e.g., auto-generated columns or timestamps like `id`, `created_at`). |
112
+ | **`config`** | `Dict[str, Any]` | No | Dictionary containing extra settings. Supports `"display_name"` to override the sidebar label. |
113
+
114
+ ---
115
+
116
+ ## Initialization Configurations
117
+
118
+ The `Admin` class constructor supports the following parameters for customization:
119
+
120
+ | Parameter | Type | Default | Description |
121
+ | :--- | :--- | :--- | :--- |
122
+ | **`title`** | `str` | `"FastAPI Admin Lite"` | Customized title displayed in the UI header and dashboard. |
123
+ | **`base_url`** | `str` | `"/admin"` | URL prefix where the admin UI is served. |
124
+ | **`enable_ui`** | `bool` | `True` | Whether to serve the Jinja2 templates UI. If `False`, only the API routes are registered. |
125
+ | **`dependencies`** | `List[Any]` | `[]` | General list of FastAPI dependencies to apply to all admin routes. |
126
+ | **`auth_dependency`** | `Callable` | `None` | Dependency function for custom security gating (e.g., checking tokens or cookies). |
127
+ | **`permission_checker`**| `Callable` | `allow all` | Custom callable to restrict user roles. |
128
+ | **`dashboard_models`** | `List[str]` | `None` | List of registered model names to display on the dashboard (if you want to restrict which registered models show on the home dashboard). |
129
+ | **`get_logs`** | `Callable` | `None` | An optional callable (async or sync) returning system logs to display on the dashboard activity log feed. |
130
+ | **`logs_config`** | `Dict[str, Any]` | `{"title": "System Activity", "columns": ["level", "timestamp", "message"]}` | Config dictionary to customize dashboard log columns and activity title. |
131
+
132
+ ---
133
+
134
+ ## Gating Access & Security
135
+
136
+ By default, the admin panel warns if no authentication is configured. You can gate the admin panel and its APIs using standard FastAPI dependencies:
137
+
138
+ ### 1. `auth_dependency`
139
+
140
+ Pass a dependency to `Admin` constructor that handles authentication.
141
+
142
+ ```python
143
+ from fastapi import Header, HTTPException
144
+
145
+ async def verify_admin_token(x_admin_token: str = Header(None)):
146
+ if x_admin_token != "super-secret-admin-token":
147
+ raise HTTPException(status_code=401, detail="Unauthorized")
148
+ return x_admin_token
149
+
150
+ admin = Admin(
151
+ title="Secure Admin",
152
+ auth_dependency=verify_admin_token
153
+ )
154
+ ```
155
+
156
+ ### 2. `permission_checker`
157
+
158
+ Restrict granular user roles with a custom callable:
159
+
160
+ ```python
161
+ async def check_permissions(user: Any = Depends(get_current_user)) -> bool:
162
+ return user.is_superuser
163
+
164
+ admin = Admin(
165
+ title="Staff Portal",
166
+ permission_checker=check_permissions
167
+ )
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Custom Dashboard Activity Logs
173
+
174
+ Configure a live activity log feed on the dashboard homepage:
175
+
176
+ ```python
177
+ async def fetch_system_logs():
178
+ # Fetch logs from a database table, file, or third-party service
179
+ return [
180
+ {"level": "info", "timestamp": "10:45 AM", "event": "User signup", "user": "alice@example.com"},
181
+ {"level": "error", "timestamp": "11:20 AM", "event": "Payment failed", "user": "bob@example.com"}
182
+ ]
183
+
184
+ admin = Admin(
185
+ title="Command Center",
186
+ get_logs=fetch_system_logs,
187
+ logs_config={
188
+ "title": "Recent Activity Feed",
189
+ "columns": ["level", "timestamp", "event", "user"]
190
+ }
191
+ )
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Running the Example Application
197
+
198
+ We package a complete working example inside the `/example` directory. To run it:
199
+
200
+ ```bash
201
+ # 1. Install dependencies with dev options
202
+ pip install -e ".[dev]"
203
+
204
+ # 2. Run the example application
205
+ python -m example.main
206
+ ```
207
+
208
+ Then visit `http://localhost:8001/admin` in your web browser. You'll be able to view logs, add/update users, search database entries, and filter elements dynamically.
209
+
@@ -0,0 +1,190 @@
1
+ # FastAPI Lite Admin
2
+
3
+ A premium, lightweight, pluggable admin panel for FastAPI and SQLAlchemy.
4
+
5
+ ## Features
6
+
7
+ - **Zero-config CRUD**: Automatically generate admin interfaces for your models.
8
+ - **ORM Agnostic**: Initial support for SQLAlchemy, designed to support others.
9
+ - **API First**: All admin actions are available via a clean, RESTful API.
10
+ - **Lightweight UI**: Fast, responsive, Jinja2 templates with server-side pagination, sorting, and search.
11
+ - **Attention Filters**: Highlight critical records (e.g. low stock, inactive users) requiring moderation attention.
12
+ - **Live Activity Badges**: "24h Activity" badges in headers and footers for real-time monitoring.
13
+ - **Custom System Logs**: Display a customizable activity feed on the main dashboard.
14
+
15
+ ---
16
+
17
+ ## Installation
18
+
19
+ Install the package directly into your project:
20
+
21
+ ```bash
22
+ pip install fastapi-lite-admin
23
+ ```
24
+
25
+ For development/local setup, clone the repository and run:
26
+
27
+ ```bash
28
+ pip install -e ".[dev]"
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Quick Start & Usage
34
+
35
+ Using `FastAPI Lite Admin` is designed to be highly explicit, simple, and require minimal boilerplate. Here is how you can set it up in your application.
36
+
37
+ ### 1. Initialize the Admin Panel
38
+
39
+ Instantiate the `Admin` class and mount it to your `FastAPI` instance.
40
+
41
+ ```python
42
+ from fastapi import FastAPI
43
+ from fastapi_admin_lite import Admin
44
+
45
+ app = FastAPI()
46
+
47
+ admin = Admin(
48
+ title="Secure Control Panel", # Dashboard title
49
+ base_url="/admin", # URL prefix for the admin panel
50
+ enable_ui=True # Enable/disable Jinja-based UI
51
+ )
52
+
53
+ # Mount the admin router and views to your FastAPI app
54
+ admin.mount(app)
55
+ ```
56
+
57
+ This will automatically create and serve:
58
+ - The UI dashboard at `http://localhost:8000/admin`
59
+
60
+ ---
61
+
62
+ ## Registering Models (Adding Tables)
63
+
64
+ To add database tables to your admin panel, register your models using `admin.register()`.
65
+
66
+ ```python
67
+ from database import get_db # Your SQLAlchemy session dependency
68
+ from models import User
69
+
70
+ admin.register(
71
+ model=User,
72
+ get_db=get_db,
73
+ list_display=["id", "email", "is_active", "created_at"],
74
+ date_field="created_at",
75
+ attention_filter=(User.is_active == False),
76
+ readonly_fields=["created_at"],
77
+ config={"display_name": "System Users"}
78
+ )
79
+ ```
80
+
81
+ ### Registration Configuration Parameters
82
+
83
+ When calling `admin.register()`, you can configure how each model is represented:
84
+
85
+ | Parameter | Type | Required | Description |
86
+ | :--- | :--- | :--- | :--- |
87
+ | **`model`** | `Type[Any]` | **Yes** | The SQLAlchemy Declarative model class to generate CRUD operations for. |
88
+ | **`get_db`** | `Callable` | **Yes** | An async/sync generator yielding an active SQLAlchemy database session (`AsyncSession` or `Session`). |
89
+ | **`list_display`** | `List[str]` | No | List of field/column names to display as columns in the UI model list view. Defaults to all fields. |
90
+ | **`date_field`** | `str` | No | Name of the datetime field (e.g. `created_at`). Required to show the "24h Activity" count cards on the dashboard and lists. |
91
+ | **`attention_filter`** | `SQLAlchemy Expression` | No | A SQLAlchemy binary filter expression (e.g., `User.is_active == False` or `Product.stock < 10`) used to calculate and flag rows that require moderator attention. |
92
+ | **`readonly_fields`** | `List[str]` | No | List of columns that cannot be modified or set via creation or updates (e.g., auto-generated columns or timestamps like `id`, `created_at`). |
93
+ | **`config`** | `Dict[str, Any]` | No | Dictionary containing extra settings. Supports `"display_name"` to override the sidebar label. |
94
+
95
+ ---
96
+
97
+ ## Initialization Configurations
98
+
99
+ The `Admin` class constructor supports the following parameters for customization:
100
+
101
+ | Parameter | Type | Default | Description |
102
+ | :--- | :--- | :--- | :--- |
103
+ | **`title`** | `str` | `"FastAPI Admin Lite"` | Customized title displayed in the UI header and dashboard. |
104
+ | **`base_url`** | `str` | `"/admin"` | URL prefix where the admin UI is served. |
105
+ | **`enable_ui`** | `bool` | `True` | Whether to serve the Jinja2 templates UI. If `False`, only the API routes are registered. |
106
+ | **`dependencies`** | `List[Any]` | `[]` | General list of FastAPI dependencies to apply to all admin routes. |
107
+ | **`auth_dependency`** | `Callable` | `None` | Dependency function for custom security gating (e.g., checking tokens or cookies). |
108
+ | **`permission_checker`**| `Callable` | `allow all` | Custom callable to restrict user roles. |
109
+ | **`dashboard_models`** | `List[str]` | `None` | List of registered model names to display on the dashboard (if you want to restrict which registered models show on the home dashboard). |
110
+ | **`get_logs`** | `Callable` | `None` | An optional callable (async or sync) returning system logs to display on the dashboard activity log feed. |
111
+ | **`logs_config`** | `Dict[str, Any]` | `{"title": "System Activity", "columns": ["level", "timestamp", "message"]}` | Config dictionary to customize dashboard log columns and activity title. |
112
+
113
+ ---
114
+
115
+ ## Gating Access & Security
116
+
117
+ By default, the admin panel warns if no authentication is configured. You can gate the admin panel and its APIs using standard FastAPI dependencies:
118
+
119
+ ### 1. `auth_dependency`
120
+
121
+ Pass a dependency to `Admin` constructor that handles authentication.
122
+
123
+ ```python
124
+ from fastapi import Header, HTTPException
125
+
126
+ async def verify_admin_token(x_admin_token: str = Header(None)):
127
+ if x_admin_token != "super-secret-admin-token":
128
+ raise HTTPException(status_code=401, detail="Unauthorized")
129
+ return x_admin_token
130
+
131
+ admin = Admin(
132
+ title="Secure Admin",
133
+ auth_dependency=verify_admin_token
134
+ )
135
+ ```
136
+
137
+ ### 2. `permission_checker`
138
+
139
+ Restrict granular user roles with a custom callable:
140
+
141
+ ```python
142
+ async def check_permissions(user: Any = Depends(get_current_user)) -> bool:
143
+ return user.is_superuser
144
+
145
+ admin = Admin(
146
+ title="Staff Portal",
147
+ permission_checker=check_permissions
148
+ )
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Custom Dashboard Activity Logs
154
+
155
+ Configure a live activity log feed on the dashboard homepage:
156
+
157
+ ```python
158
+ async def fetch_system_logs():
159
+ # Fetch logs from a database table, file, or third-party service
160
+ return [
161
+ {"level": "info", "timestamp": "10:45 AM", "event": "User signup", "user": "alice@example.com"},
162
+ {"level": "error", "timestamp": "11:20 AM", "event": "Payment failed", "user": "bob@example.com"}
163
+ ]
164
+
165
+ admin = Admin(
166
+ title="Command Center",
167
+ get_logs=fetch_system_logs,
168
+ logs_config={
169
+ "title": "Recent Activity Feed",
170
+ "columns": ["level", "timestamp", "event", "user"]
171
+ }
172
+ )
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Running the Example Application
178
+
179
+ We package a complete working example inside the `/example` directory. To run it:
180
+
181
+ ```bash
182
+ # 1. Install dependencies with dev options
183
+ pip install -e ".[dev]"
184
+
185
+ # 2. Run the example application
186
+ python -m example.main
187
+ ```
188
+
189
+ Then visit `http://localhost:8001/admin` in your web browser. You'll be able to view logs, add/update users, search database entries, and filter elements dynamically.
190
+
@@ -90,26 +90,27 @@
90
90
  }
91
91
 
92
92
  .badge {
93
- padding: 4px 8px;
94
- border-radius: 4px;
93
+ padding: 4px 10px;
94
+ border-radius: 12px;
95
95
  font-size: 0.7rem;
96
96
  font-weight: 700;
97
97
  text-transform: uppercase;
98
+ letter-spacing: 0.025em;
98
99
  }
99
100
 
100
- .badge-info { background-color: #e0f2fe; color: #0369a1; }
101
- .badge-warn { background-color: #fef3c7; color: #b45309; }
102
- .badge-error { background-color: #fee2e2; color: #b91c1c; }
103
- .badge-success { background-color: #dcfce7; color: #15803d; }
101
+ .badge-info { background-color: var(--info-bg); color: var(--info-text); }
102
+ .badge-warn { background-color: var(--warning-bg); color: var(--warning-text); }
103
+ .badge-error { background-color: var(--error-bg); color: var(--error-text); }
104
+ .badge-success { background-color: var(--success-bg); color: var(--success-text); }
104
105
 
105
106
  .status-text {
106
107
  font-weight: 600;
107
108
  font-family: monospace;
108
109
  }
109
110
 
110
- .status-200 { color: var(--success); }
111
- .status-422 { color: var(--error); }
112
- .status-latency { color: var(--warning); }
111
+ .status-200 { color: var(--success-text); }
112
+ .status-422 { color: var(--error-text); }
113
+ .status-latency { color: var(--warning-text); }
113
114
 
114
115
  /* Quick Actions */
115
116
  .quick-actions {
@@ -134,12 +135,12 @@
134
135
  .action-icon {
135
136
  width: 48px;
136
137
  height: 48px;
137
- background-color: #f1f5f9;
138
+ background-color: var(--bg-surface);
138
139
  border-radius: 12px;
139
140
  display: flex;
140
141
  align-items: center;
141
142
  justify-content: center;
142
- color: var(--text-muted);
143
+ color: var(--primary);
143
144
  font-size: 1.25rem;
144
145
  }
145
146
 
@@ -8,28 +8,68 @@
8
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
9
  <style>
10
10
  :root {
11
- --primary: #0089a7;
12
- --primary-hover: #00768f;
11
+ --primary: #6366f1;
12
+ --primary-hover: #4f46e5;
13
13
  --bg-main: #f8fafc;
14
- --bg-sidebar: #ffffff;
14
+ --bg-card: #ffffff;
15
+ --bg-surface: #f1f5f9;
16
+ --bg-header: #f8fafc;
17
+ --bg-sidebar: #0f172a;
15
18
  --text-main: #1e293b;
16
19
  --text-muted: #64748b;
20
+ --sidebar-text: #94a3b8;
21
+ --sidebar-text-active: #ffffff;
17
22
  --border: #e2e8f0;
18
- --surface: #ffffff;
19
- --success: #10b981;
20
- --warning: #f59e0b;
21
- --error: #ef4444;
22
- --sidebar-width: 260px;
23
+ --sidebar-hover: #1e293b;
24
+ --sidebar-active: #334155;
25
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
26
+ --sidebar-width: 240px;
27
+ --topbar-height: 48px;
28
+
29
+ /* Status Colors */
30
+ --success-bg: #dcfce7;
31
+ --success-text: #166534;
32
+ --warning-bg: #fef9c3;
33
+ --warning-text: #854d0e;
34
+ --error-bg: #fee2e2;
35
+ --error-text: #991b1b;
36
+ --info-bg: #e0f2fe;
37
+ --info-text: #075985;
38
+ }
39
+
40
+ [data-theme="dark"] {
41
+ --bg-main: #020617;
42
+ --bg-card: #0f172a;
43
+ --bg-surface: #1e293b;
44
+ --bg-header: #1e293b;
45
+ --bg-sidebar: #020617;
46
+ --text-main: #f1f5f9;
47
+ --text-muted: #94a3b8;
48
+ --border: #1e293b;
49
+ --sidebar-hover: #0f172a;
50
+ --sidebar-active: #1e293b;
51
+ --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.3);
52
+
53
+ /* Status Colors - Dark Mode */
54
+ --success-bg: rgba(22, 101, 52, 0.2);
55
+ --success-text: #4ade80;
56
+ --warning-bg: rgba(133, 77, 14, 0.2);
57
+ --warning-text: #facc15;
58
+ --error-bg: rgba(153, 27, 27, 0.2);
59
+ --error-text: #f87171;
60
+ --info-bg: rgba(7, 89, 133, 0.2);
61
+ --info-text: #38bdf8;
23
62
  }
24
63
 
25
64
  * {
26
65
  margin: 0;
27
66
  padding: 0;
28
67
  box-sizing: border-box;
68
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
69
+ transition: background-color 0.2s, border-color 0.2s, color 0.2s;
29
70
  }
30
71
 
31
72
  body {
32
- font-family: 'Inter', sans-serif;
33
73
  background-color: var(--bg-main);
34
74
  color: var(--text-main);
35
75
  display: flex;
@@ -69,14 +109,14 @@
69
109
  .logo-text {
70
110
  font-weight: 700;
71
111
  font-size: 1.1rem;
72
- color: var(--text-main);
112
+ color: var(--sidebar-text-active);
73
113
  }
74
114
 
75
115
  .logo-subtext {
76
116
  font-size: 0.7rem;
77
117
  text-transform: uppercase;
78
118
  letter-spacing: 0.05em;
79
- color: var(--text-muted);
119
+ color: var(--sidebar-text);
80
120
  margin-top: -2px;
81
121
  }
82
122
 
@@ -91,7 +131,7 @@
91
131
  gap: 12px;
92
132
  padding: 12px 16px;
93
133
  text-decoration: none;
94
- color: var(--text-muted);
134
+ color: var(--sidebar-text);
95
135
  border-radius: 8px;
96
136
  font-weight: 500;
97
137
  transition: all 0.2s;
@@ -99,13 +139,13 @@
99
139
  }
100
140
 
101
141
  .nav-item:hover {
102
- background-color: #f1f5f9;
103
- color: var(--text-main);
142
+ background-color: var(--sidebar-hover);
143
+ color: var(--sidebar-text-active);
104
144
  }
105
145
 
106
146
  .nav-item.active {
107
- background-color: #ecfeff;
108
- color: var(--primary);
147
+ background-color: var(--sidebar-active);
148
+ color: var(--sidebar-text-active);
109
149
  }
110
150
 
111
151
  .sidebar-footer {
@@ -134,11 +174,12 @@
134
174
  .user-info .name {
135
175
  font-size: 0.9rem;
136
176
  font-weight: 600;
177
+ color: var(--sidebar-text-active);
137
178
  }
138
179
 
139
180
  .user-info .email {
140
181
  font-size: 0.75rem;
141
- color: var(--text-muted);
182
+ color: var(--sidebar-text);
142
183
  }
143
184
 
144
185
  /* Main Content */
@@ -151,8 +192,8 @@
151
192
 
152
193
  /* Topbar */
153
194
  .topbar {
154
- height: 48px;
155
- background-color: var(--surface);
195
+ height: var(--topbar-height);
196
+ background-color: var(--bg-card);
156
197
  border-bottom: 1px solid var(--border);
157
198
  display: flex;
158
199
  align-items: center;
@@ -167,13 +208,14 @@
167
208
 
168
209
  .search-box input {
169
210
  width: 100%;
170
- padding: 6px 12px 6px 32px;
171
- background-color: #f1f5f9;
172
- border: 1px solid transparent;
173
- border-radius: 6px;
211
+ padding: 8px 12px 8px 36px;
212
+ background-color: var(--bg-surface);
213
+ border: 1px solid var(--border);
214
+ border-radius: 8px;
174
215
  outline: none;
175
216
  font-size: 0.85rem;
176
217
  transition: all 0.2s;
218
+ color: var(--text-main);
177
219
  }
178
220
 
179
221
  .search-box i {
@@ -217,11 +259,11 @@
217
259
 
218
260
  /* Shared Components */
219
261
  .card {
220
- background-color: var(--surface);
262
+ background-color: var(--bg-card);
221
263
  border: 1px solid var(--border);
222
264
  border-radius: 12px;
223
265
  padding: 24px;
224
- box-shadow: 0 1px 3px rgba(0,0,0,0.05);
266
+ box-shadow: var(--shadow);
225
267
  }
226
268
 
227
269
  .btn {
@@ -244,16 +286,17 @@
244
286
 
245
287
  .btn-primary:hover {
246
288
  background-color: var(--primary-hover);
289
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2);
247
290
  }
248
291
 
249
292
  .btn-secondary {
250
- background-color: white;
293
+ background-color: var(--bg-card);
251
294
  border-color: var(--border);
252
295
  color: var(--text-main);
253
296
  }
254
297
 
255
298
  .btn-secondary:hover {
256
- background-color: #f8fafc;
299
+ background-color: var(--bg-surface);
257
300
  }
258
301
 
259
302
  /* Extra CSS from child templates */
@@ -311,6 +354,9 @@
311
354
  <input type="text" placeholder="Search resources...">
312
355
  </div>
313
356
  <div class="topbar-actions">
357
+ <button id="theme-toggle" class="btn-icon" style="background: none; border: none; cursor: pointer; color: var(--text-muted);">
358
+ <i class="fas fa-moon"></i>
359
+ </button>
314
360
  <i class="far fa-bell"></i>
315
361
  <i class="far fa-question-circle"></i>
316
362
  <div class="avatar" style="width: 32px; height: 32px;">
@@ -324,6 +370,33 @@
324
370
  </div>
325
371
  </div>
326
372
 
373
+ <script>
374
+ // Theme Toggle Logic
375
+ const themeToggle = document.getElementById('theme-toggle');
376
+ const icon = themeToggle.querySelector('i');
377
+
378
+ // Check for saved theme or default to light
379
+ const savedTheme = localStorage.getItem('theme') || 'light';
380
+ document.documentElement.setAttribute('data-theme', savedTheme);
381
+ updateIcon(savedTheme);
382
+
383
+ themeToggle.addEventListener('click', () => {
384
+ const currentTheme = document.documentElement.getAttribute('data-theme');
385
+ const newTheme = currentTheme === 'light' ? 'dark' : 'light';
386
+
387
+ document.documentElement.setAttribute('data-theme', newTheme);
388
+ localStorage.setItem('theme', newTheme);
389
+ updateIcon(newTheme);
390
+ });
391
+
392
+ function updateIcon(theme) {
393
+ if (theme === 'dark') {
394
+ icon.className = 'fas fa-sun';
395
+ } else {
396
+ icon.className = 'fas fa-moon';
397
+ }
398
+ }
399
+ </script>
327
400
  {% block extra_js %}{% endblock %}
328
401
  </body>
329
402
  </html>