appmanager-server 0.2.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.
Files changed (89) hide show
  1. appmanager/__init__.py +52 -0
  2. appmanager/admin/__init__.py +3 -0
  3. appmanager/admin/app_installer.py +739 -0
  4. appmanager/admin/members.py +95 -0
  5. appmanager/admin/registry.py +372 -0
  6. appmanager/admin/routes.py +900 -0
  7. appmanager/api/__init__.py +5 -0
  8. appmanager/api/routes.py +373 -0
  9. appmanager/app_config.py +101 -0
  10. appmanager/auth/__init__.py +3 -0
  11. appmanager/auth/oauth.py +77 -0
  12. appmanager/auth/routes.py +224 -0
  13. appmanager/auth/utils.py +175 -0
  14. appmanager/bridge.py +90 -0
  15. appmanager/cli.py +1171 -0
  16. appmanager/config.py +58 -0
  17. appmanager/database.py +120 -0
  18. appmanager/extension.py +198 -0
  19. appmanager/extensions.py +225 -0
  20. appmanager/health.py +133 -0
  21. appmanager/hooks.py +187 -0
  22. appmanager/middleware.py +256 -0
  23. appmanager/models.py +307 -0
  24. appmanager/py.typed +1 -0
  25. appmanager/sdk.py +30 -0
  26. appmanager/security.py +227 -0
  27. appmanager/security_scanner.py +582 -0
  28. appmanager/signals.py +21 -0
  29. appmanager/static/css/app.css +808 -0
  30. appmanager/static/vendor/fonts/fonts.css +288 -0
  31. appmanager/static/vendor/fonts/jetbrainsmono-0.woff2 +0 -0
  32. appmanager/static/vendor/fonts/jetbrainsmono-1.woff2 +0 -0
  33. appmanager/static/vendor/fonts/jetbrainsmono-10.woff2 +0 -0
  34. appmanager/static/vendor/fonts/jetbrainsmono-11.woff2 +0 -0
  35. appmanager/static/vendor/fonts/jetbrainsmono-12.woff2 +0 -0
  36. appmanager/static/vendor/fonts/jetbrainsmono-13.woff2 +0 -0
  37. appmanager/static/vendor/fonts/jetbrainsmono-14.woff2 +0 -0
  38. appmanager/static/vendor/fonts/jetbrainsmono-15.woff2 +0 -0
  39. appmanager/static/vendor/fonts/jetbrainsmono-16.woff2 +0 -0
  40. appmanager/static/vendor/fonts/jetbrainsmono-17.woff2 +0 -0
  41. appmanager/static/vendor/fonts/jetbrainsmono-18.woff2 +0 -0
  42. appmanager/static/vendor/fonts/jetbrainsmono-19.woff2 +0 -0
  43. appmanager/static/vendor/fonts/jetbrainsmono-2.woff2 +0 -0
  44. appmanager/static/vendor/fonts/jetbrainsmono-20.woff2 +0 -0
  45. appmanager/static/vendor/fonts/jetbrainsmono-21.woff2 +0 -0
  46. appmanager/static/vendor/fonts/jetbrainsmono-22.woff2 +0 -0
  47. appmanager/static/vendor/fonts/jetbrainsmono-23.woff2 +0 -0
  48. appmanager/static/vendor/fonts/jetbrainsmono-3.woff2 +0 -0
  49. appmanager/static/vendor/fonts/jetbrainsmono-4.woff2 +0 -0
  50. appmanager/static/vendor/fonts/jetbrainsmono-5.woff2 +0 -0
  51. appmanager/static/vendor/fonts/jetbrainsmono-6.woff2 +0 -0
  52. appmanager/static/vendor/fonts/jetbrainsmono-7.woff2 +0 -0
  53. appmanager/static/vendor/fonts/jetbrainsmono-8.woff2 +0 -0
  54. appmanager/static/vendor/fonts/jetbrainsmono-9.woff2 +0 -0
  55. appmanager/static/vendor/fonts/spacegrotesk-0.woff2 +0 -0
  56. appmanager/static/vendor/fonts/spacegrotesk-1.woff2 +0 -0
  57. appmanager/static/vendor/fonts/spacegrotesk-10.woff2 +0 -0
  58. appmanager/static/vendor/fonts/spacegrotesk-11.woff2 +0 -0
  59. appmanager/static/vendor/fonts/spacegrotesk-2.woff2 +0 -0
  60. appmanager/static/vendor/fonts/spacegrotesk-3.woff2 +0 -0
  61. appmanager/static/vendor/fonts/spacegrotesk-4.woff2 +0 -0
  62. appmanager/static/vendor/fonts/spacegrotesk-5.woff2 +0 -0
  63. appmanager/static/vendor/fonts/spacegrotesk-6.woff2 +0 -0
  64. appmanager/static/vendor/fonts/spacegrotesk-7.woff2 +0 -0
  65. appmanager/static/vendor/fonts/spacegrotesk-8.woff2 +0 -0
  66. appmanager/static/vendor/fonts/spacegrotesk-9.woff2 +0 -0
  67. appmanager/static/vendor/htmx/htmx.min.js +1 -0
  68. appmanager/templates/admin/_member_drawer.html +98 -0
  69. appmanager/templates/admin/_members_table.html +108 -0
  70. appmanager/templates/admin/app_detail.html +136 -0
  71. appmanager/templates/admin/dashboard.html +902 -0
  72. appmanager/templates/admin/flairs.html +90 -0
  73. appmanager/templates/admin/permissions.html +88 -0
  74. appmanager/templates/admin/roles.html +119 -0
  75. appmanager/templates/admin/users.html +156 -0
  76. appmanager/templates/auth/login.html +58 -0
  77. appmanager/templates/auth/profile.html +44 -0
  78. appmanager/templates/base.html +76 -0
  79. appmanager/templates/errors/401.html +19 -0
  80. appmanager/templates/errors/403.html +19 -0
  81. appmanager/templates/errors/404.html +18 -0
  82. appmanager/templates/errors/500.html +19 -0
  83. appmanager/templates/index.html +54 -0
  84. appmanager_server-0.2.0.dist-info/METADATA +320 -0
  85. appmanager_server-0.2.0.dist-info/RECORD +89 -0
  86. appmanager_server-0.2.0.dist-info/WHEEL +5 -0
  87. appmanager_server-0.2.0.dist-info/entry_points.txt +5 -0
  88. appmanager_server-0.2.0.dist-info/licenses/LICENSE +21 -0
  89. appmanager_server-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,808 @@
1
+ /* ============================================================
2
+ AppManager Design System — "Terminal/Ops" aesthetic
3
+ Neutral surfaces · single amber accent · mono for ops data
4
+ ============================================================ */
5
+
6
+ /* ---------- Design Tokens ---------- */
7
+ :root {
8
+ /* Neutral 3-step background ladder (Linear/Vercel-style) */
9
+ --bg: #0B0C0E;
10
+ --bg-elevated: #101113;
11
+ --surface: #131417;
12
+ --surface-2: #1A1C1F;
13
+ --surface-hover: #202327;
14
+ --border: rgba(255, 255, 255, 0.07);
15
+ --border-strong: rgba(255, 255, 255, 0.14);
16
+
17
+ /* Signature accent — amber (ops/terminal warmth) */
18
+ --accent: #F5A524;
19
+ --accent-hover: #FFB84D;
20
+ --accent-soft: rgba(245, 165, 36, 0.12);
21
+ --accent-contrast: #0D0D0F; /* text on amber */
22
+
23
+ /* Text */
24
+ --text: #E8EAED;
25
+ --text-muted: #9BA1A6;
26
+ --text-faint: #6B7075;
27
+
28
+ /* Status — reserved for actual state, never decorative */
29
+ --success: #3ECF8E;
30
+ --success-soft: rgba(62, 207, 142, 0.12);
31
+ --warning: #F5A524;
32
+ --warning-soft: rgba(245, 165, 36, 0.12);
33
+ --danger: #F87171;
34
+ --danger-soft: rgba(248, 113, 113, 0.12);
35
+ --info: #60A5FA;
36
+ --info-soft: rgba(96, 165, 250, 0.12);
37
+
38
+ /* Effects — hairline borders, minimal shadow, 4px radius (no pills) */
39
+ --radius: 4px;
40
+ --radius-sm: 3px;
41
+ --radius-lg: 6px;
42
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
43
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
44
+ --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.5);
45
+ --ring: 0 0 0 2px rgba(245, 165, 36, 0.35);
46
+
47
+ /* Typography */
48
+ --font: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
49
+ --mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
50
+
51
+ /* Spacing grid (8px base) */
52
+ --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
53
+ --sp-5: 24px; --sp-6: 32px; --sp-7: 48px;
54
+ }
55
+
56
+ /* ---------- Reset & Base ---------- */
57
+ * { box-sizing: border-box; margin: 0; padding: 0; }
58
+ html { scroll-behavior: smooth; }
59
+
60
+ body {
61
+ font-family: var(--font);
62
+ font-size: 14px;
63
+ line-height: 1.55;
64
+ background-color: var(--bg);
65
+ color: var(--text);
66
+ -webkit-font-smoothing: antialiased;
67
+ letter-spacing: -0.011em;
68
+ /* Faint dot-grid background (Supabase-style) */
69
+ background-image: radial-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px);
70
+ background-size: 24px 24px;
71
+ background-attachment: fixed;
72
+ }
73
+
74
+ /* Subtle noise overlay for texture */
75
+ body::before {
76
+ content: "";
77
+ position: fixed;
78
+ inset: 0;
79
+ z-index: 0;
80
+ pointer-events: none;
81
+ opacity: 0.025;
82
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
83
+ }
84
+
85
+ /* ---------- Typography ---------- */
86
+ h1, h2, h3, h4 { font-weight: 600; letter-spacing: -0.02em; line-height: 1.2; }
87
+ code, pre, .mono, .tabular { font-family: var(--mono); font-variant-numeric: tabular-nums; }
88
+ code {
89
+ background: var(--surface-2);
90
+ border: 1px solid var(--border);
91
+ border-radius: var(--radius-sm);
92
+ padding: 0.1em 0.35em;
93
+ font-size: 0.85em;
94
+ color: var(--accent);
95
+ }
96
+ .text-muted { color: var(--text-muted); }
97
+ .text-faint { color: var(--text-faint); }
98
+ .text-success { color: var(--success); }
99
+ .text-danger { color: var(--danger); }
100
+ .text-warning { color: var(--warning); }
101
+ .text-info { color: var(--info); }
102
+ .text-accent { color: var(--accent); }
103
+
104
+ /* ---------- Layout ---------- */
105
+ .container { max-width: 1200px; margin: 0 auto; padding: var(--sp-5) var(--sp-4); position: relative; z-index: 1; }
106
+ .grid { display: grid; gap: var(--sp-4); }
107
+ .grid-2 { grid-template-columns: repeat(2, 1fr); }
108
+ .grid-3 { grid-template-columns: repeat(3, 1fr); }
109
+ .grid-4 { grid-template-columns: repeat(4, 1fr); }
110
+ .grid-apps { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--sp-4); }
111
+ .flex { display: flex; }
112
+ .flex-between { display: flex; align-items: center; justify-content: space-between; }
113
+ .flex-center { display: flex; align-items: center; }
114
+ .flex-wrap { flex-wrap: wrap; }
115
+ .gap-1 { gap: var(--sp-1); } .gap-2 { gap: var(--sp-2); } .gap-3 { gap: var(--sp-3); } .gap-4 { gap: var(--sp-4); }
116
+ .mt-1 { margin-top: var(--sp-1); } .mt-2 { margin-top: var(--sp-2); } .mt-3 { margin-top: var(--sp-3); } .mt-4 { margin-top: var(--sp-4); }
117
+ .mb-1 { margin-bottom: var(--sp-1); } .mb-2 { margin-bottom: var(--sp-2); } .mb-3 { margin-bottom: var(--sp-3); } .mb-4 { margin-bottom: var(--sp-4); }
118
+ .mb-0 { margin-bottom: 0; }
119
+
120
+ /* ---------- Page header ---------- */
121
+ .page-header {
122
+ display: flex;
123
+ align-items: flex-end;
124
+ justify-content: space-between;
125
+ gap: var(--sp-4);
126
+ margin-bottom: var(--sp-5);
127
+ padding-bottom: var(--sp-4);
128
+ border-bottom: 1px solid var(--border);
129
+ }
130
+ .page-title { font-size: 1.6rem; font-weight: 700; }
131
+ .page-subtitle { color: var(--text-muted); font-size: 0.9rem; margin-top: var(--sp-1); }
132
+
133
+ /* ---------- Cards ---------- */
134
+ .card {
135
+ background: var(--surface);
136
+ border: 1px solid var(--border);
137
+ border-radius: var(--radius);
138
+ padding: var(--sp-4);
139
+ box-shadow: var(--shadow-sm);
140
+ }
141
+ .card-hover { transition: border-color 120ms ease-out, transform 120ms ease-out, box-shadow 120ms ease-out; }
142
+ .card-hover:hover {
143
+ border-color: var(--border-strong);
144
+ transform: translateY(-1px);
145
+ box-shadow: var(--shadow-md);
146
+ }
147
+ .card-title { font-size: 1.05rem; font-weight: 600; }
148
+ .card-header {
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: space-between;
152
+ gap: var(--sp-3);
153
+ padding-bottom: var(--sp-3);
154
+ margin-bottom: var(--sp-3);
155
+ border-bottom: 1px solid var(--border);
156
+ }
157
+
158
+ /* ---------- Buttons ---------- */
159
+ .btn {
160
+ display: inline-flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ gap: var(--sp-2);
164
+ font-family: var(--font);
165
+ font-size: 0.85rem;
166
+ font-weight: 500;
167
+ letter-spacing: -0.01em;
168
+ padding: 0.5rem 0.9rem;
169
+ border-radius: var(--radius);
170
+ border: 1px solid var(--border-strong);
171
+ background: var(--surface-2);
172
+ color: var(--text);
173
+ cursor: pointer;
174
+ text-decoration: none;
175
+ transition: background 120ms ease-out, border-color 120ms ease-out, color 120ms ease-out;
176
+ white-space: nowrap;
177
+ }
178
+ .btn:hover { background: var(--surface-hover); border-color: var(--border-strong); }
179
+ .btn:active { transform: scale(0.98); }
180
+ .btn:focus-visible { outline: none; box-shadow: var(--ring); }
181
+ .btn:disabled, .btn.disabled { opacity: 0.5; cursor: not-allowed; }
182
+
183
+ .btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-contrast); font-weight: 600; }
184
+ .btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-contrast); }
185
+
186
+ .btn-secondary { background: transparent; border-color: var(--border-strong); color: var(--text); }
187
+ .btn-secondary:hover { background: var(--surface-2); }
188
+
189
+ .btn-ghost { background: transparent; border-color: transparent; color: var(--text-muted); }
190
+ .btn-ghost:hover { background: var(--surface-2); color: var(--text); }
191
+
192
+ .btn-success { background: transparent; border-color: rgba(62, 207, 142, 0.4); color: var(--success); }
193
+ .btn-success:hover { background: var(--success-soft); }
194
+ .btn-warning { background: transparent; border-color: rgba(245, 165, 36, 0.4); color: var(--warning); }
195
+ .btn-warning:hover { background: var(--warning-soft); }
196
+ .btn-danger { background: transparent; border-color: rgba(248, 113, 113, 0.4); color: var(--danger); }
197
+ .btn-danger:hover { background: var(--danger-soft); }
198
+
199
+ .btn-sm { padding: 0.3rem 0.6rem; font-size: 0.78rem; }
200
+ .btn-lg { padding: 0.7rem 1.2rem; font-size: 0.95rem; }
201
+ .btn-block { width: 100%; }
202
+
203
+ /* ---------- Badges (outline only, mono, no fills) ---------- */
204
+ .badge {
205
+ display: inline-flex;
206
+ align-items: center;
207
+ gap: 0.35em;
208
+ font-family: var(--mono);
209
+ font-size: 0.7rem;
210
+ font-weight: 500;
211
+ letter-spacing: 0.02em;
212
+ text-transform: uppercase;
213
+ padding: 0.15em 0.5em;
214
+ border: 1px solid var(--border-strong);
215
+ border-radius: var(--radius-sm);
216
+ color: var(--text-muted);
217
+ background: transparent;
218
+ line-height: 1.4;
219
+ }
220
+ .badge-primary { border-color: rgba(245, 165, 36, 0.4); color: var(--accent); }
221
+ .badge-success { border-color: rgba(62, 207, 142, 0.4); color: var(--success); }
222
+ .badge-warning { border-color: rgba(245, 165, 36, 0.4); color: var(--warning); }
223
+ .badge-danger { border-color: rgba(248, 113, 113, 0.4); color: var(--danger); }
224
+ .badge-info { border-color: rgba(96, 165, 250, 0.4); color: var(--info); }
225
+ .badge-accent { border-color: rgba(245, 165, 36, 0.5); color: var(--accent); }
226
+ .badge-muted { border-color: var(--border-strong); color: var(--text-faint); }
227
+
228
+ /* Pulsing status dot */
229
+ .badge-dot::before {
230
+ content: "";
231
+ width: 6px; height: 6px;
232
+ border-radius: 50%;
233
+ background: currentColor;
234
+ flex-shrink: 0;
235
+ }
236
+ .badge-success.badge-dot::before { animation: pulse 2s ease-in-out infinite; }
237
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
238
+
239
+ /* ---------- Alerts ---------- */
240
+ .alert {
241
+ padding: var(--sp-3) var(--sp-4);
242
+ border: 1px solid var(--border-strong);
243
+ border-left: 3px solid var(--text-muted);
244
+ border-radius: var(--radius);
245
+ margin-bottom: var(--sp-3);
246
+ font-size: 0.9rem;
247
+ background: var(--surface);
248
+ }
249
+ .alert-success { border-left-color: var(--success); color: var(--success); }
250
+ .alert-warning { border-left-color: var(--warning); color: var(--warning); }
251
+ .alert-danger, .alert-error { border-left-color: var(--danger); color: var(--danger); }
252
+ .alert-info { border-left-color: var(--info); color: var(--info); }
253
+
254
+ /* ---------- Forms ---------- */
255
+ .form-group { margin-bottom: var(--sp-4); }
256
+ .form-label {
257
+ display: block;
258
+ font-family: var(--mono);
259
+ font-size: 0.72rem;
260
+ font-weight: 500;
261
+ letter-spacing: 0.04em;
262
+ text-transform: uppercase;
263
+ color: var(--text-muted);
264
+ margin-bottom: var(--sp-2);
265
+ }
266
+ .form-input, .form-select, .form-textarea {
267
+ width: 100%;
268
+ font-family: var(--font);
269
+ font-size: 0.9rem;
270
+ color: var(--text);
271
+ background: var(--bg-elevated);
272
+ border: 1px solid var(--border-strong);
273
+ border-radius: var(--radius);
274
+ padding: 0.5rem 0.7rem;
275
+ transition: border-color 120ms ease-out, box-shadow 120ms ease-out;
276
+ }
277
+ .form-input:focus, .form-select:focus, .form-textarea:focus {
278
+ outline: none;
279
+ border-color: var(--accent);
280
+ box-shadow: var(--ring);
281
+ }
282
+ .form-input::placeholder { color: var(--text-faint); }
283
+ input[type="file"].form-input {
284
+ padding: 0.35rem 0.5rem;
285
+ font-family: var(--mono);
286
+ font-size: 0.82rem;
287
+ color: var(--text-muted);
288
+ cursor: pointer;
289
+ display: flex;
290
+ align-items: center;
291
+ }
292
+ input[type="file"].form-input::file-selector-button,
293
+ input[type="file"].form-input::-webkit-file-upload-button {
294
+ font-family: var(--mono);
295
+ font-size: 0.76rem;
296
+ font-weight: 600;
297
+ letter-spacing: 0.03em;
298
+ text-transform: uppercase;
299
+ padding: 0.35rem 0.75rem;
300
+ margin-right: 0.75rem;
301
+ border-radius: var(--radius-sm);
302
+ border: 1px solid var(--border-strong);
303
+ background: var(--surface-2);
304
+ color: var(--accent);
305
+ cursor: pointer;
306
+ transition: all 120ms ease-out;
307
+ }
308
+ input[type="file"].form-input:hover::file-selector-button,
309
+ input[type="file"].form-input:hover::-webkit-file-upload-button {
310
+ background: var(--accent-soft);
311
+ border-color: var(--accent);
312
+ color: var(--accent-hover);
313
+ }
314
+ .form-textarea { resize: vertical; min-height: 80px; font-family: var(--mono); font-size: 0.8rem; }
315
+ .form-select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%239BA1A6' fill='none' stroke-width='1.5'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 0.7rem center; padding-right: 2rem; }
316
+
317
+ /* ---------- Tables ---------- */
318
+ .table-wrap { overflow-x: auto; }
319
+ .table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
320
+ .table thead th {
321
+ font-family: var(--mono);
322
+ font-size: 0.68rem;
323
+ font-weight: 500;
324
+ letter-spacing: 0.05em;
325
+ text-transform: uppercase;
326
+ color: var(--text-faint);
327
+ text-align: left;
328
+ padding: var(--sp-2) var(--sp-3);
329
+ border-bottom: 1px solid var(--border-strong);
330
+ white-space: nowrap;
331
+ }
332
+ .table tbody td {
333
+ padding: var(--sp-3);
334
+ border-bottom: 1px solid var(--border);
335
+ vertical-align: middle;
336
+ }
337
+ .table tbody tr { transition: background 100ms ease-out; }
338
+ .table tbody tr:hover { background: rgba(255, 255, 255, 0.02); }
339
+ .table tbody tr:hover td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
340
+ .table tbody tr:last-child td { border-bottom: none; }
341
+ .table .num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
342
+
343
+ /* ---------- Tabs ---------- */
344
+ .tab-panel { display: none; }
345
+ .tab-panel.active { display: block; animation: fadeIn 200ms ease-out; }
346
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
347
+
348
+ /* ---------- Admin layout ---------- */
349
+ .admin-layout { display: grid; grid-template-columns: 200px 1fr; gap: var(--sp-5); align-items: start; }
350
+ .admin-sidebar { position: sticky; top: var(--sp-5); }
351
+ .admin-nav { display: flex; flex-direction: column; gap: 2px; }
352
+ .admin-nav a {
353
+ display: flex;
354
+ align-items: center;
355
+ gap: var(--sp-2);
356
+ padding: 0.5rem 0.75rem;
357
+ font-size: 0.85rem;
358
+ color: var(--text-muted);
359
+ text-decoration: none;
360
+ border-radius: var(--radius);
361
+ border-left: 2px solid transparent;
362
+ transition: color 100ms ease-out, background 100ms ease-out;
363
+ }
364
+ .admin-nav a:hover { color: var(--text); background: var(--surface); }
365
+ .admin-nav a.active { color: var(--text); background: var(--surface); border-left-color: var(--accent); }
366
+ .admin-nav a.active .nav-icon { color: var(--accent); }
367
+
368
+ /* App detail page */
369
+ .breadcrumb { font-size: 0.72rem; color: var(--text-faint); letter-spacing: 0.04em; margin-bottom: var(--sp-2); }
370
+ .card-body { padding: 1.25rem 1.5rem; }
371
+ .admin-nav-group { margin-top: var(--sp-4); }
372
+ .admin-nav-group-title {
373
+ font-family: var(--mono); font-size: 0.65rem; letter-spacing: 0.12em;
374
+ color: var(--text-faint); text-transform: uppercase;
375
+ padding: 0 0.75rem 0.4rem;
376
+ }
377
+ .app-panel { display: none; }
378
+ .app-panel.active { display: block; }
379
+ .panel-link-card { padding: 0.5rem 0; }
380
+ .schema-form .form-group { margin-bottom: var(--sp-3); }
381
+
382
+ /* Flairs admin panel (extension-flairs) */
383
+ .flair-presets { display: flex; flex-wrap: wrap; gap: 0.5rem; }
384
+ .preset-chip {
385
+ font-family: var(--mono); font-size: 0.78rem; cursor: pointer;
386
+ padding: 0.4rem 0.8rem; border-radius: 9999px;
387
+ background: color-mix(in srgb, var(--fc) 12%, transparent);
388
+ color: var(--fc); border: 1px solid color-mix(in srgb, var(--fc) 40%, transparent);
389
+ transition: transform 0.1s ease, filter 0.1s ease;
390
+ }
391
+ .preset-chip:hover { transform: translateY(-1px); filter: brightness(1.1); }
392
+ .flair-badge {
393
+ display: inline-block; padding: 2px 8px; border-radius: 9999px;
394
+ font-size: 0.75rem; font-weight: 600;
395
+ background: color-mix(in srgb, var(--fc) 13%, transparent);
396
+ color: var(--fc); border: 1px solid color-mix(in srgb, var(--fc) 27%, transparent);
397
+ }
398
+ .nav-icon { width: 18px; text-align: center; font-size: 0.9rem; }
399
+
400
+ /* ---------- Stat readouts (label-over-number, no boxes) ---------- */
401
+ .stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-4); margin-bottom: var(--sp-5); }
402
+ .stat-readout { padding: var(--sp-3) 0; border-bottom: 1px solid var(--border); }
403
+ .stat-readout:last-child { border-bottom: none; }
404
+ .stat-label {
405
+ font-family: var(--mono);
406
+ font-size: 0.68rem;
407
+ letter-spacing: 0.06em;
408
+ text-transform: uppercase;
409
+ color: var(--text-faint);
410
+ margin-bottom: var(--sp-1);
411
+ }
412
+ .stat-value { font-family: var(--mono); font-size: 1.9rem; font-weight: 600; color: var(--text); line-height: 1.1; font-variant-numeric: tabular-nums; }
413
+ .stat-sub { font-family: var(--mono); font-size: 0.72rem; color: var(--text-muted); margin-top: var(--sp-1); }
414
+ .stat-delta { font-family: var(--mono); font-size: 0.75rem; }
415
+
416
+ /* ---------- App tiles (glass + glow on hover) ---------- */
417
+ .app-card {
418
+ display: flex;
419
+ flex-direction: column;
420
+ justify-content: space-between;
421
+ gap: var(--sp-4);
422
+ background: rgba(255, 255, 255, 0.03);
423
+ backdrop-filter: blur(8px);
424
+ -webkit-backdrop-filter: blur(8px);
425
+ border: 1px solid var(--border);
426
+ border-radius: var(--radius);
427
+ padding: var(--sp-4);
428
+ position: relative;
429
+ overflow: hidden;
430
+ transition: border-color 150ms ease-out, box-shadow 150ms ease-out, transform 150ms ease-out;
431
+ }
432
+ .app-card:hover {
433
+ border-color: rgba(255, 255, 255, 0.15);
434
+ box-shadow: 0 0 24px -8px var(--accent);
435
+ transform: translateY(-1px);
436
+ }
437
+ .app-title { font-size: 1.1rem; font-weight: 600; margin-bottom: var(--sp-1); }
438
+ .app-desc { color: var(--text-muted); font-size: 0.85rem; line-height: 1.5; }
439
+ .app-meta { font-family: var(--mono); font-size: 0.72rem; color: var(--text-faint); }
440
+ .app-footer { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); padding-top: var(--sp-3); border-top: 1px solid var(--border); }
441
+
442
+ /* Scanline sweep on hover */
443
+ .app-card::after {
444
+ content: "";
445
+ position: absolute;
446
+ top: 0; left: 0; right: 0;
447
+ height: 1px;
448
+ background: linear-gradient(90deg, transparent, var(--accent), transparent);
449
+ opacity: 0;
450
+ transform: translateY(-100%);
451
+ transition: none;
452
+ pointer-events: none;
453
+ }
454
+ .app-card:hover::after { animation: scanline 300ms ease-out forwards; }
455
+ @keyframes scanline { 0% { opacity: 0; transform: translateY(-100%); } 20% { opacity: 1; } 100% { opacity: 0; transform: translateY(1000%); } }
456
+
457
+ /* ---------- Braille sparkline ---------- */
458
+ .sparkline { font-family: var(--mono); font-size: 0.8rem; color: var(--accent); letter-spacing: 0; white-space: nowrap; }
459
+
460
+ /* ---------- Installed apps table (cleaner) ---------- */
461
+ .table-apps th,
462
+ .table-apps td { vertical-align: middle; }
463
+ .app-name-cell { min-width: 220px; }
464
+ .app-name-row { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
465
+ .app-name-row strong { font-size: 0.92rem; }
466
+ .app-path { font-size: 0.75rem; color: var(--text-muted); margin-top: 2px; }
467
+ .state-cell { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
468
+ .state-toggle {
469
+ display: inline-flex; align-items: center; gap: 0.35rem;
470
+ padding: 0.25rem 0.55rem; font-size: 0.75rem; font-family: var(--mono);
471
+ border: 1px solid var(--border); border-radius: var(--radius);
472
+ background: transparent; color: var(--text-muted); cursor: pointer;
473
+ transition: border-color 120ms ease, color 120ms ease;
474
+ }
475
+ .state-toggle:hover { border-color: var(--border-strong); color: var(--text); }
476
+ .state-on { color: var(--success); border-color: var(--success); }
477
+ .state-on:hover { background: var(--success); color: var(--accent-contrast); }
478
+ .state-off { color: var(--danger); border-color: var(--danger); }
479
+ .state-off:hover { background: var(--danger); color: var(--accent-contrast); }
480
+ .state-lock { color: var(--text-muted); }
481
+ .state-open { color: var(--success); border-color: var(--success); }
482
+ .health-cell { display: flex; align-items: center; gap: 0.6rem; white-space: nowrap; }
483
+ .actions-col { text-align: right; }
484
+ .row-actions { display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem; flex-wrap: nowrap; }
485
+ .icon-btn { padding: 0.3rem 0.5rem; line-height: 1; }
486
+ .settings-row td { background: var(--bg-elevated); }
487
+
488
+ /* ---------- Member management (pagination + drawer) ---------- */
489
+ .members-toolbar {
490
+ display: flex; align-items: center; justify-content: space-between;
491
+ gap: var(--sp-3); padding: 0.75rem 1.25rem;
492
+ border-bottom: 1px solid var(--border); flex-wrap: wrap;
493
+ }
494
+ .members-search { position: relative; flex: 1; min-width: 220px; max-width: 360px; }
495
+ .members-search .search-icon {
496
+ position: absolute; left: 0.6rem; top: 50%; transform: translateY(-50%);
497
+ color: var(--text-faint); font-size: 0.9rem; pointer-events: none;
498
+ }
499
+ .members-search .form-input { padding-left: 1.9rem; }
500
+ .members-toolbar-right { display: flex; align-items: center; gap: var(--sp-3); }
501
+ .result-count { font-size: 0.72rem; color: var(--text-faint); letter-spacing: 0.04em; }
502
+ .table-members th, .table-members td { vertical-align: middle; }
503
+ .col-check { width: 2.2rem; text-align: center; }
504
+ .col-check input { cursor: pointer; accent-color: var(--accent); }
505
+ .member-cell { min-width: 220px; }
506
+ .member-name-row { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
507
+ .member-name-row strong { font-size: 0.92rem; }
508
+ .member-email { font-size: 0.75rem; color: var(--text-muted); margin-top: 2px; }
509
+ .members-footer {
510
+ display: flex; align-items: center; justify-content: space-between;
511
+ gap: var(--sp-3); padding: 0.75rem 1.25rem;
512
+ border-top: 1px solid var(--border); flex-wrap: wrap;
513
+ }
514
+ .pagination { display: flex; align-items: center; gap: var(--sp-3); font-size: 0.8rem; }
515
+ .pagination a { color: var(--accent); text-decoration: none; }
516
+ .pagination a:hover { text-decoration: underline; }
517
+ .page-indicator { color: var(--text-muted); }
518
+ .page-size { width: auto; padding: 0.2rem 0.4rem; font-size: 0.75rem; }
519
+ .bulk-bar {
520
+ display: none; align-items: center; gap: var(--sp-3);
521
+ padding: 0.5rem 0.9rem; border: 1px solid var(--accent); border-radius: var(--radius);
522
+ background: var(--accent-soft);
523
+ }
524
+ .bulk-bar.active { display: flex; }
525
+ .bulk-count { color: var(--accent); font-size: 0.78rem; }
526
+ .bulk-form { display: flex; align-items: center; gap: var(--sp-2); }
527
+ .bulk-form .form-select { width: auto; padding: 0.25rem 0.5rem; font-size: 0.78rem; }
528
+
529
+ /* Member drawer (slide-over) */
530
+ .drawer-overlay {
531
+ position: fixed; inset: 0; z-index: 1000;
532
+ background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(2px);
533
+ display: none; align-items: stretch; justify-content: flex-end;
534
+ }
535
+ .drawer-overlay.open { display: flex; }
536
+ .member-drawer {
537
+ width: 480px; max-width: 92vw; height: 100%;
538
+ background: var(--bg-elevated); border-left: 1px solid var(--border-strong);
539
+ box-shadow: -12px 0 40px rgba(0, 0, 0, 0.4);
540
+ overflow-y: auto; transform: translateX(100%);
541
+ transition: transform 150ms ease-out;
542
+ }
543
+ .drawer-overlay.open .member-drawer { transform: translateX(0); }
544
+ body.drawer-open { overflow: hidden; }
545
+ .drawer-loading, .drawer-error { padding: 2rem; color: var(--text-faint); font-size: 0.8rem; }
546
+ .drawer-header {
547
+ display: flex; align-items: flex-start; justify-content: space-between;
548
+ gap: var(--sp-3); padding: 1.25rem 1.5rem;
549
+ border-bottom: 1px solid var(--border);
550
+ }
551
+ .drawer-title h3 { margin: 0; font-size: 1.1rem; }
552
+ .drawer-sub { font-size: 0.78rem; color: var(--text-muted); margin-top: 2px; }
553
+ .drawer-form { padding: 0 1.5rem 1.5rem; }
554
+ .drawer-section { padding: 1.25rem 0; border-bottom: 1px solid var(--border); }
555
+ .drawer-section:last-of-type { border-bottom: none; }
556
+ .drawer-section-title {
557
+ font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.12em;
558
+ color: var(--text-faint); text-transform: uppercase; margin: 0 0 0.75rem;
559
+ }
560
+ .drawer-section-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem; }
561
+ .drawer-section-head .drawer-section-title { margin: 0; }
562
+ .perm-list { display: flex; flex-direction: column; gap: 2px; }
563
+ .perm-row {
564
+ display: flex; align-items: center; justify-content: space-between;
565
+ padding: 0.45rem 0.6rem; border-radius: var(--radius); cursor: pointer;
566
+ transition: background 100ms ease;
567
+ }
568
+ .perm-row:hover { background: rgba(255, 255, 255, 0.03); }
569
+ .perm-name { display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; }
570
+ .perm-dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }
571
+ .perm-toggle { accent-color: var(--accent); cursor: pointer; }
572
+ .session-stats { display: flex; flex-direction: column; gap: 0.4rem; }
573
+ .session-stat { display: flex; align-items: baseline; gap: 0.6rem; font-size: 0.85rem; }
574
+ .session-stat .mono { color: var(--text); }
575
+ .drawer-footer {
576
+ display: flex; align-items: center; gap: var(--sp-2);
577
+ padding-top: 1rem; border-top: 1px solid var(--border);
578
+ }
579
+
580
+ /* ---------- Empty state ---------- */
581
+ .empty-state {
582
+ text-align: center;
583
+ padding: var(--sp-7) var(--sp-4);
584
+ border: 1px dashed var(--border-strong);
585
+ border-radius: var(--radius);
586
+ color: var(--text-muted);
587
+ }
588
+ .empty-state h2 { font-size: 1.2rem; margin-bottom: var(--sp-2); color: var(--text); }
589
+ .empty-state p { margin-bottom: var(--sp-4); }
590
+
591
+ /* ---------- Header / topbar ---------- */
592
+ .site-header {
593
+ position: sticky;
594
+ top: 0;
595
+ z-index: 20;
596
+ background: rgba(11, 12, 14, 0.85);
597
+ backdrop-filter: blur(12px);
598
+ -webkit-backdrop-filter: blur(12px);
599
+ border-bottom: 1px solid var(--border);
600
+ }
601
+ .nav-container {
602
+ max-width: 1200px;
603
+ margin: 0 auto;
604
+ padding: 0 var(--sp-4);
605
+ height: 56px;
606
+ display: flex;
607
+ align-items: center;
608
+ justify-content: space-between;
609
+ gap: var(--sp-4);
610
+ }
611
+ .brand { display: flex; align-items: center; gap: var(--sp-2); text-decoration: none; color: var(--text); font-weight: 600; font-size: 1rem; }
612
+ .brand-icon {
613
+ width: 28px; height: 28px;
614
+ display: flex; align-items: center; justify-content: center;
615
+ background: var(--accent);
616
+ color: var(--accent-contrast);
617
+ font-family: var(--mono);
618
+ font-size: 0.7rem;
619
+ font-weight: 700;
620
+ border-radius: var(--radius-sm);
621
+ }
622
+ .nav-links { display: flex; align-items: center; gap: var(--sp-1); list-style: none; }
623
+ .nav-link {
624
+ padding: 0.4rem 0.7rem;
625
+ font-size: 0.85rem;
626
+ color: var(--text-muted);
627
+ text-decoration: none;
628
+ border-radius: var(--radius);
629
+ transition: color 100ms ease-out, background 100ms ease-out;
630
+ }
631
+ .nav-link:hover { color: var(--text); background: var(--surface); }
632
+
633
+ /* Topbar instruments (live clock) */
634
+ .topbar-instruments {
635
+ display: flex;
636
+ align-items: center;
637
+ gap: var(--sp-4);
638
+ font-family: var(--mono);
639
+ font-size: 0.72rem;
640
+ color: var(--text-faint);
641
+ }
642
+ .instrument { display: flex; align-items: center; gap: var(--sp-1); }
643
+ .instrument .blink { animation: blink 1s step-start infinite; color: var(--accent); }
644
+ @keyframes blink { 50% { opacity: 0; } }
645
+
646
+ /* ---------- Footer ---------- */
647
+ .site-footer {
648
+ border-top: 1px solid var(--border);
649
+ padding: var(--sp-5) var(--sp-4);
650
+ text-align: center;
651
+ color: var(--text-faint);
652
+ font-size: 0.8rem;
653
+ position: relative;
654
+ z-index: 1;
655
+ }
656
+
657
+ /* ---------- Boot sequence load ---------- */
658
+ .boot-item { opacity: 0; animation: bootIn 300ms ease-out forwards; }
659
+ @keyframes bootIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
660
+ .boot-ok { font-family: var(--mono); font-size: 0.7rem; color: var(--success); }
661
+
662
+ /* ---------- Inline forms & misc ---------- */
663
+ .inline-form { display: inline; }
664
+ .action-group { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
665
+ .divider { display: flex; align-items: center; gap: var(--sp-3); color: var(--text-faint); font-size: 0.75rem; }
666
+ .checkbox-cell { accent-color: var(--accent); }
667
+
668
+ /* ---------- Modal (type-to-confirm) ---------- */
669
+ .modal-overlay {
670
+ position: fixed;
671
+ inset: 0;
672
+ z-index: 100;
673
+ background: rgba(0, 0, 0, 0.6);
674
+ backdrop-filter: blur(4px);
675
+ -webkit-backdrop-filter: blur(4px);
676
+ display: flex;
677
+ align-items: center;
678
+ justify-content: center;
679
+ padding: var(--sp-4);
680
+ }
681
+ .modal {
682
+ background: var(--surface);
683
+ border: 1px solid var(--border-strong);
684
+ border-radius: var(--radius);
685
+ box-shadow: var(--shadow-lg);
686
+ padding: var(--sp-5);
687
+ max-width: 420px;
688
+ width: 100%;
689
+ }
690
+ .modal.modal-lg {
691
+ max-width: 680px;
692
+ max-height: 85vh;
693
+ display: flex;
694
+ flex-direction: column;
695
+ }
696
+ .modal-scroll-body {
697
+ overflow-y: auto;
698
+ max-height: calc(85vh - 160px);
699
+ padding-right: var(--sp-2);
700
+ margin-bottom: var(--sp-4);
701
+ }
702
+ .security-header {
703
+ display: flex;
704
+ align-items: center;
705
+ justify-content: space-between;
706
+ border-bottom: 1px solid var(--border);
707
+ padding-bottom: var(--sp-3);
708
+ margin-bottom: var(--sp-4);
709
+ }
710
+ .security-stats {
711
+ display: grid;
712
+ grid-template-columns: repeat(3, 1fr);
713
+ gap: var(--sp-3);
714
+ margin-bottom: var(--sp-4);
715
+ }
716
+ .security-stat-box {
717
+ background: var(--surface-2);
718
+ border: 1px solid var(--border);
719
+ border-radius: var(--radius-sm);
720
+ padding: var(--sp-3);
721
+ text-align: center;
722
+ }
723
+ .security-stat-val {
724
+ font-size: 1.25rem;
725
+ font-weight: 700;
726
+ font-family: var(--mono);
727
+ }
728
+ .security-stat-lbl {
729
+ font-size: 0.75rem;
730
+ color: var(--text-muted);
731
+ text-transform: uppercase;
732
+ letter-spacing: 0.5px;
733
+ }
734
+ .finding-card {
735
+ background: var(--surface-2);
736
+ border: 1px solid var(--border);
737
+ border-left: 3px solid var(--border-strong);
738
+ border-radius: var(--radius-sm);
739
+ padding: var(--sp-3);
740
+ margin-bottom: var(--sp-3);
741
+ }
742
+ .finding-card.severity-CRITICAL { border-left-color: var(--danger); }
743
+ .finding-card.severity-HIGH { border-left-color: #f97316; }
744
+ .finding-card.severity-MEDIUM { border-left-color: var(--warning); }
745
+ .finding-card.severity-LOW { border-left-color: var(--info); }
746
+ .finding-header {
747
+ display: flex;
748
+ align-items: center;
749
+ justify-content: space-between;
750
+ gap: var(--sp-2);
751
+ margin-bottom: var(--sp-1);
752
+ }
753
+ .finding-title {
754
+ font-weight: 600;
755
+ font-size: 0.9rem;
756
+ color: var(--text);
757
+ }
758
+ .finding-meta {
759
+ font-size: 0.8rem;
760
+ color: var(--text-muted);
761
+ font-family: var(--mono);
762
+ margin-bottom: var(--sp-2);
763
+ }
764
+ .finding-snippet {
765
+ background: var(--bg);
766
+ border: 1px solid var(--border);
767
+ border-radius: var(--radius-sm);
768
+ padding: var(--sp-2) var(--sp-3);
769
+ font-family: var(--mono);
770
+ font-size: 0.8rem;
771
+ color: var(--accent);
772
+ overflow-x: auto;
773
+ white-space: pre;
774
+ }
775
+ .scanner-loader {
776
+ display: flex;
777
+ flex-direction: column;
778
+ align-items: center;
779
+ justify-content: center;
780
+ padding: var(--sp-6) var(--sp-4);
781
+ text-align: center;
782
+ }
783
+ .scanner-spinner {
784
+ width: 44px;
785
+ height: 44px;
786
+ border: 3px solid var(--border);
787
+ border-top-color: var(--accent);
788
+ border-radius: 50%;
789
+ animation: spin 0.8s linear infinite;
790
+ margin-bottom: var(--sp-4);
791
+ }
792
+ @keyframes spin {
793
+ to { transform: rotate(360deg); }
794
+ }
795
+
796
+ /* ---------- Responsive ---------- */
797
+ @media (max-width: 900px) {
798
+ .admin-layout { grid-template-columns: 1fr; }
799
+ .admin-sidebar { position: static; }
800
+ .admin-nav { flex-direction: row; flex-wrap: wrap; }
801
+ .stat-grid { grid-template-columns: repeat(2, 1fr); }
802
+ .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
803
+ .topbar-instruments { display: none; }
804
+ }
805
+ @media (max-width: 600px) {
806
+ .stat-grid { grid-template-columns: 1fr; }
807
+ .page-header { flex-direction: column; align-items: flex-start; }
808
+ }