fastapi-lite-admin 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,329 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ title|default("FastAPI Admin") }}</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ :root {
11
+ --primary: #0089a7;
12
+ --primary-hover: #00768f;
13
+ --bg-main: #f8fafc;
14
+ --bg-sidebar: #ffffff;
15
+ --text-main: #1e293b;
16
+ --text-muted: #64748b;
17
+ --border: #e2e8f0;
18
+ --surface: #ffffff;
19
+ --success: #10b981;
20
+ --warning: #f59e0b;
21
+ --error: #ef4444;
22
+ --sidebar-width: 260px;
23
+ }
24
+
25
+ * {
26
+ margin: 0;
27
+ padding: 0;
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ body {
32
+ font-family: 'Inter', sans-serif;
33
+ background-color: var(--bg-main);
34
+ color: var(--text-main);
35
+ display: flex;
36
+ min-height: 100vh;
37
+ }
38
+
39
+ /* Sidebar */
40
+ .sidebar {
41
+ width: var(--sidebar-width);
42
+ background-color: var(--bg-sidebar);
43
+ border-right: 1px solid var(--border);
44
+ display: flex;
45
+ flex-direction: column;
46
+ position: fixed;
47
+ height: 100vh;
48
+ z-index: 100;
49
+ }
50
+
51
+ .sidebar-header {
52
+ padding: 24px;
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 12px;
56
+ }
57
+
58
+ .logo-icon {
59
+ width: 32px;
60
+ height: 32px;
61
+ background-color: var(--primary);
62
+ border-radius: 8px;
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ color: white;
67
+ }
68
+
69
+ .logo-text {
70
+ font-weight: 700;
71
+ font-size: 1.1rem;
72
+ color: var(--text-main);
73
+ }
74
+
75
+ .logo-subtext {
76
+ font-size: 0.7rem;
77
+ text-transform: uppercase;
78
+ letter-spacing: 0.05em;
79
+ color: var(--text-muted);
80
+ margin-top: -2px;
81
+ }
82
+
83
+ .nav-links {
84
+ padding: 12px;
85
+ flex: 1;
86
+ }
87
+
88
+ .nav-item {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 12px;
92
+ padding: 12px 16px;
93
+ text-decoration: none;
94
+ color: var(--text-muted);
95
+ border-radius: 8px;
96
+ font-weight: 500;
97
+ transition: all 0.2s;
98
+ margin-bottom: 4px;
99
+ }
100
+
101
+ .nav-item:hover {
102
+ background-color: #f1f5f9;
103
+ color: var(--text-main);
104
+ }
105
+
106
+ .nav-item.active {
107
+ background-color: #ecfeff;
108
+ color: var(--primary);
109
+ }
110
+
111
+ .sidebar-footer {
112
+ padding: 24px;
113
+ border-top: 1px solid var(--border);
114
+ margin-top: auto;
115
+ }
116
+
117
+ .user-profile {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 12px;
121
+ }
122
+
123
+ .avatar {
124
+ width: 36px;
125
+ height: 36px;
126
+ background-color: #e2e8f0;
127
+ border-radius: 50%;
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ overflow: hidden;
132
+ }
133
+
134
+ .user-info .name {
135
+ font-size: 0.9rem;
136
+ font-weight: 600;
137
+ }
138
+
139
+ .user-info .email {
140
+ font-size: 0.75rem;
141
+ color: var(--text-muted);
142
+ }
143
+
144
+ /* Main Content */
145
+ .main-content {
146
+ flex: 1;
147
+ margin-left: var(--sidebar-width);
148
+ display: flex;
149
+ flex-direction: column;
150
+ }
151
+
152
+ /* Topbar */
153
+ .topbar {
154
+ height: 48px;
155
+ background-color: var(--surface);
156
+ border-bottom: 1px solid var(--border);
157
+ display: flex;
158
+ align-items: center;
159
+ padding: 0 24px;
160
+ justify-content: space-between;
161
+ }
162
+
163
+ .search-box {
164
+ position: relative;
165
+ width: 300px;
166
+ }
167
+
168
+ .search-box input {
169
+ width: 100%;
170
+ padding: 6px 12px 6px 32px;
171
+ background-color: #f1f5f9;
172
+ border: 1px solid transparent;
173
+ border-radius: 6px;
174
+ outline: none;
175
+ font-size: 0.85rem;
176
+ transition: all 0.2s;
177
+ }
178
+
179
+ .search-box i {
180
+ position: absolute;
181
+ left: 10px;
182
+ top: 50%;
183
+ transform: translateY(-50%);
184
+ color: var(--text-muted);
185
+ font-size: 0.8rem;
186
+ }
187
+
188
+ .topbar-actions {
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 16px;
192
+ color: var(--text-muted);
193
+ font-size: 0.9rem;
194
+ }
195
+
196
+ /* Page Content */
197
+ .page-container {
198
+ padding: 16px 24px;
199
+ max-width: 100%;
200
+ width: 100%;
201
+ }
202
+
203
+ .page-header {
204
+ margin-bottom: 32px;
205
+ }
206
+
207
+ .page-title {
208
+ font-size: 1.8rem;
209
+ font-weight: 700;
210
+ margin-bottom: 4px;
211
+ }
212
+
213
+ .page-subtitle {
214
+ color: var(--text-muted);
215
+ font-size: 0.95rem;
216
+ }
217
+
218
+ /* Shared Components */
219
+ .card {
220
+ background-color: var(--surface);
221
+ border: 1px solid var(--border);
222
+ border-radius: 12px;
223
+ padding: 24px;
224
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
225
+ }
226
+
227
+ .btn {
228
+ padding: 10px 20px;
229
+ border-radius: 8px;
230
+ font-weight: 600;
231
+ font-size: 0.9rem;
232
+ cursor: pointer;
233
+ transition: all 0.2s;
234
+ border: 1px solid transparent;
235
+ display: inline-flex;
236
+ align-items: center;
237
+ gap: 8px;
238
+ }
239
+
240
+ .btn-primary {
241
+ background-color: var(--primary);
242
+ color: white;
243
+ }
244
+
245
+ .btn-primary:hover {
246
+ background-color: var(--primary-hover);
247
+ }
248
+
249
+ .btn-secondary {
250
+ background-color: white;
251
+ border-color: var(--border);
252
+ color: var(--text-main);
253
+ }
254
+
255
+ .btn-secondary:hover {
256
+ background-color: #f8fafc;
257
+ }
258
+
259
+ /* Extra CSS from child templates */
260
+ {% block extra_css %}{% endblock %}
261
+ </style>
262
+ </head>
263
+ <body>
264
+ <div class="sidebar">
265
+ <div class="sidebar-header">
266
+ <div class="logo-icon">
267
+ <i class="fas fa-database"></i>
268
+ </div>
269
+ <div>
270
+ <div class="logo-text">FastAPI Admin</div>
271
+ <div class="logo-subtext">Management Console</div>
272
+ </div>
273
+ </div>
274
+
275
+ <div class="nav-links">
276
+ <a href="/admin" class="nav-item {% if active_nav == 'dashboard' %}active{% endif %}">
277
+ <i class="fas fa-th-large"></i>
278
+ Dashboard
279
+ </a>
280
+
281
+ <div style="margin: 16px 16px 8px; font-size: 0.65rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); letter-spacing: 0.05em;">
282
+ Resources
283
+ </div>
284
+
285
+ {% for m in models %}
286
+ <a href="/admin/{{ m }}" class="nav-item {% if active_nav == m %}active{% endif %}">
287
+ <i class="fas {% if m == 'user' or m == 'users' %}fa-user-group{% elif m == 'product' or m == 'products' %}fa-box{% else %}fa-database{% endif %}"></i>
288
+ {{ m|capitalize }}
289
+ </a>
290
+ {% endfor %}
291
+
292
+ </div>
293
+
294
+ <div class="sidebar-footer">
295
+ <div class="user-profile">
296
+ <div class="avatar">
297
+ <img src="https://ui-avatars.com/api/?name=Admin+User&background=0D8ABC&color=fff" alt="Avatar">
298
+ </div>
299
+ <div class="user-info">
300
+ <div class="name">Admin User</div>
301
+ <div class="email">admin@fastapi.local</div>
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+
307
+ <div class="main-content">
308
+ <div class="topbar">
309
+ <div class="search-box">
310
+ <i class="fas fa-search"></i>
311
+ <input type="text" placeholder="Search resources...">
312
+ </div>
313
+ <div class="topbar-actions">
314
+ <i class="far fa-bell"></i>
315
+ <i class="far fa-question-circle"></i>
316
+ <div class="avatar" style="width: 32px; height: 32px;">
317
+ <img src="https://ui-avatars.com/api/?name=AU&background=0D8ABC&color=fff" alt="AU">
318
+ </div>
319
+ </div>
320
+ </div>
321
+
322
+ <div class="page-container">
323
+ {% block content %}{% endblock %}
324
+ </div>
325
+ </div>
326
+
327
+ {% block extra_js %}{% endblock %}
328
+ </body>
329
+ </html>
@@ -0,0 +1,170 @@
1
+ {% extends "layout.html" %}
2
+
3
+ {% set active_nav = model_name %}
4
+
5
+ {% block extra_css %}
6
+ .detail-header {
7
+ display: flex;
8
+ justify-content: space-between;
9
+ align-items: center;
10
+ margin-bottom: 32px;
11
+ }
12
+
13
+ .detail-grid {
14
+ display: grid;
15
+ grid-template-columns: repeat(2, 1fr);
16
+ gap: 0;
17
+ border-radius: 12px;
18
+ overflow: hidden;
19
+ border: 1px solid var(--border);
20
+ }
21
+
22
+ .detail-item {
23
+ padding: 24px;
24
+ border-bottom: 1px solid var(--border);
25
+ background-color: white;
26
+ }
27
+
28
+ .detail-item:nth-child(odd) {
29
+ border-right: 1px solid var(--border);
30
+ }
31
+
32
+ .detail-item.full-width {
33
+ grid-column: span 2;
34
+ border-right: none;
35
+ }
36
+
37
+ .detail-item:last-child, .detail-item:nth-last-child(2):not(.full-width) {
38
+ border-bottom: none;
39
+ }
40
+
41
+ .detail-label {
42
+ font-size: 0.75rem;
43
+ font-weight: 700;
44
+ text-transform: uppercase;
45
+ color: var(--text-muted);
46
+ margin-bottom: 8px;
47
+ letter-spacing: 0.05em;
48
+ }
49
+
50
+ .detail-value {
51
+ font-size: 1rem;
52
+ font-weight: 500;
53
+ word-break: break-all;
54
+ }
55
+
56
+ .status-pill {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ gap: 6px;
60
+ padding: 4px 12px;
61
+ border-radius: 20px;
62
+ font-size: 0.8rem;
63
+ font-weight: 600;
64
+ }
65
+
66
+ .status-pill.active { background-color: #dcfce7; color: #15803d; }
67
+ .status-pill.inactive { background-color: #f1f5f9; color: #64748b; }
68
+ {% endblock %}
69
+
70
+ {% block content %}
71
+ <div class="breadcrumb">
72
+ <a href="/admin">Dashboard</a>
73
+ <i class="fas fa-chevron-right" style="font-size: 0.6rem;"></i>
74
+ <a href="/admin/{{ model_name }}">{{ model_name|capitalize }}</a>
75
+ <i class="fas fa-chevron-right" style="font-size: 0.6rem;"></i>
76
+ <span>{{ model_name|capitalize }} Details</span>
77
+ </div>
78
+
79
+ <div class="detail-header">
80
+ <div>
81
+ <h1 class="page-title">{{ model_name|capitalize }} Details</h1>
82
+ <p class="page-subtitle">Full record information and audit metadata.</p>
83
+ </div>
84
+ <div style="display: flex; gap: 12px;">
85
+ <button class="btn btn-secondary" onclick="location.href='/admin/{{ model_name }}'">
86
+ <i class="fas fa-arrow-left"></i> Back
87
+ </button>
88
+ <button class="btn btn-primary" onclick="location.href='/admin/{{ model_name }}/{{ id }}'">
89
+ <i class="fas fa-pencil-alt"></i> Edit Record
90
+ </button>
91
+ </div>
92
+ </div>
93
+
94
+ <div class="card" style="padding: 0; overflow: hidden; margin-bottom: 32px;">
95
+ <div id="detail-container" class="detail-grid">
96
+ <div style="grid-column: span 2; padding: 48px; text-align: center; color: var(--text-muted);">
97
+ <i class="fas fa-circle-notch fa-spin"></i> Loading details...
98
+ </div>
99
+ </div>
100
+ </div>
101
+
102
+ <div class="section-header" style="margin-top: 32px;">
103
+ <h2 class="section-title">System Information</h2>
104
+ </div>
105
+
106
+ <div class="meta-cards">
107
+ <div class="card meta-card">
108
+ <div class="meta-icon">
109
+ <i class="far fa-clock"></i>
110
+ </div>
111
+ <div>
112
+ <div class="meta-label">Created At</div>
113
+ <div class="meta-value">Oct 24, 2023 - 14:32 PM</div>
114
+ </div>
115
+ </div>
116
+ <div class="card meta-card">
117
+ <div class="meta-icon">
118
+ <i class="far fa-database"></i>
119
+ </div>
120
+ <div>
121
+ <div class="meta-label">Record Source</div>
122
+ <div class="meta-value">SQLAlchemy Production DB</div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ {% endblock %}
127
+
128
+ {% block extra_js %}
129
+ <script>
130
+ const modelName = "{{ model_name }}";
131
+ const recordId = "{{ id }}";
132
+ const apiBaseUrl = "/admin/api";
133
+
134
+ async function loadDetail() {
135
+ try {
136
+ const response = await fetch(`${apiBaseUrl}/${modelName}/${recordId}`);
137
+ const data = await response.json();
138
+
139
+ const container = document.getElementById('detail-container');
140
+ const keys = Object.keys(data);
141
+
142
+ container.innerHTML = keys.map(key => {
143
+ const val = data[key];
144
+ const isFullWidth = typeof val === 'string' && val.length > 50;
145
+
146
+ let displayVal = val;
147
+ if (typeof val === 'boolean' || key === 'is_active') {
148
+ const isActive = val === true || val === 'true';
149
+ displayVal = `<span class="status-pill ${isActive ? 'active' : 'inactive'}">
150
+ <i class="fas ${isActive ? 'fa-check-circle' : 'fa-times-circle'}"></i>
151
+ ${isActive ? 'Active' : 'Inactive'}
152
+ </span>`;
153
+ }
154
+
155
+ return `
156
+ <div class="detail-item ${isFullWidth ? 'full-width' : ''}">
157
+ <div class="detail-label">${key.replace('_', ' ')}</div>
158
+ <div class="detail-value">${displayVal}</div>
159
+ </div>
160
+ `;
161
+ }).join('');
162
+ } catch (error) {
163
+ console.error('Error loading detail:', error);
164
+ document.getElementById('detail-container').innerHTML = '<div style="grid-column: span 2; padding: 48px; color: var(--error);">Error loading record details</div>';
165
+ }
166
+ }
167
+
168
+ loadDetail();
169
+ </script>
170
+ {% endblock %}