shared css classes for memory-dash and scheduler

3clyp50 committed Dec 26, 2025 at 14:47 UTC 0d92bb793bf0ed2b3ca4e5468b5c49a3cf21dc74
4 files changed +612 -698
webui/components/modals/scheduler/scheduler-task-list.html
+36 -285
@@ -11,7 +11,7 @@
11 x-show="!$store.schedulerStore.isCreating && !$store.schedulerStore.isEditing">
12
13 <!-- Filter Header -->
14 - <div class="scheduler-filters-header">
14 + <div class="filters-header">
15 <div class="filter-group-inline filter-type">
16 <label>Type</label>
17 <select x-model="$store.schedulerStore.filterType">
@@ -40,7 +40,7 @@
40 </div>
41
42 <!-- Status Bar -->
43 - <div class="scheduler-status-bar" x-show="!$store.schedulerStore.isLoading">
43 + <div class="data-status-bar" x-show="!$store.schedulerStore.isLoading">
44 <div class="status-info">
45 <span class="status-item">
46 <span class="material-symbols-outlined">schedule</span>
@@ -77,9 +77,9 @@
77 </div>
78
79 <!-- Empty State -->
80 - <div class="scheduler-empty"
80 + <div class="data-empty-state"
81 x-show="!$store.schedulerStore.isLoading && $store.schedulerStore.filteredTasks.length === 0">
82 - <div class="scheduler-empty-text">No tasks found</div>
82 + <div>No tasks found</div>
83 <button class="btn btn-ok" @click="$store.schedulerStore.startCreateTask()">
84 <span class="material-symbols-outlined">add</span>
85 Create your first task
@@ -87,29 +87,29 @@
87 </div>
88
89 <!-- Task Table -->
90 - <div class="scheduler-table-container"
90 + <div class="data-table-container"
91 x-show="!$store.schedulerStore.isLoading && $store.schedulerStore.paginatedTasks.length > 0">
92 - <table class="scheduler-task-table">
92 + <table class="data-table scheduler-task-table">
93 <thead>
94 <tr>
95 - <th class="col-name" @click="$store.schedulerStore.changeSort('name')">
95 + <th class="col-name sortable" @click="$store.schedulerStore.changeSort('name')">
96 Name
97 - <span class="scheduler-sort-indicator"
97 + <span class="sort-indicator"
98 x-show="$store.schedulerStore.sortField === 'name'"
99 - :class="{'scheduler-sort-desc': $store.schedulerStore.sortDirection === 'desc'}">↑</span>
99 + :class="{'desc': $store.schedulerStore.sortDirection === 'desc'}">↑</span>
100 </th>
101 - <th class="col-state" @click="$store.schedulerStore.changeSort('state')">
101 + <th class="col-state sortable" @click="$store.schedulerStore.changeSort('state')">
102 State
103 - <span class="scheduler-sort-indicator"
103 + <span class="sort-indicator"
104 x-show="$store.schedulerStore.sortField === 'state'"
105 - :class="{'scheduler-sort-desc': $store.schedulerStore.sortDirection === 'desc'}">↑</span>
105 + :class="{'desc': $store.schedulerStore.sortDirection === 'desc'}">↑</span>
106 </th>
107 <th class="col-project">Project</th>
108 - <th class="col-lastrun" @click="$store.schedulerStore.changeSort('last_run')">
108 + <th class="col-lastrun sortable" @click="$store.schedulerStore.changeSort('last_run')">
109 Last Run
110 - <span class="scheduler-sort-indicator"
110 + <span class="sort-indicator"
111 x-show="$store.schedulerStore.sortField === 'last_run'"
112 - :class="{'scheduler-sort-desc': $store.schedulerStore.sortDirection === 'desc'}">↑</span>
112 + :class="{'desc': $store.schedulerStore.sortDirection === 'desc'}">↑</span>
113 </th>
114 <th class="col-actions">Actions</th>
115 </tr>
@@ -158,7 +158,7 @@
158 </div>
159
160 <!-- Bottom Status Bar (for pagination when table is long) -->
161 - <div class="scheduler-status-bar scheduler-status-bar-bottom"
161 + <div class="data-status-bar data-status-bar-bottom"
162 x-show="!$store.schedulerStore.isLoading && $store.schedulerStore.paginatedTasks.length > 5 && $store.schedulerStore.totalPages > 1">
163 <div class="status-info">
164 <span class="status-item">
@@ -198,50 +198,14 @@
198 </div>
199
200 <style>
201 + /* Scheduler-specific: Container layout */
202 .scheduler-list-container {
203 display: flex;
204 flex-direction: column;
205 gap: 0;
206 }
207
207 - /* Filter Header - matches memory-dashboard pattern */
208 - .scheduler-filters-header {
209 - display: flex;
210 - flex-wrap: wrap;
211 - gap: 0.75rem;
212 - align-items: center;
213 - padding: 1rem;
214 - background: var(--color-panel);
215 - border: 1px solid var(--color-border);
216 - border-radius: 8px;
217 - margin-bottom: 0;
218 - }
219 -
220 - .filter-group-inline {
221 - display: flex;
222 - align-items: center;
223 - gap: 0.5rem;
224 - }
225 -
226 - .filter-group-inline label {
227 - font-size: 0.85rem;
228 - font-weight: 600;
229 - color: var(--color-text);
230 - opacity: 0.8;
231 - white-space: nowrap;
232 - }
233 -
234 - .filter-group-inline select {
235 - border: 1px solid var(--color-border);
236 - min-width: 120px;
237 - }
238 -
239 - .filter-group-inline select:focus {
240 - outline: none;
241 - border-color: var(--color-primary);
242 - background: var(--color-input-focus);
243 - }
244 -
208 + /* Scheduler-specific: New task button positioning */
209 .btn-new-task {
210 margin-left: auto;
211 display: inline-flex;
@@ -253,204 +217,19 @@
217 font-size: 20px;
218 }
219
256 - /* Status Bar - matches memory-dashboard pattern */
257 - .scheduler-status-bar {
258 - display: flex;
259 - justify-content: space-between;
260 - align-items: center;
261 - padding: 0.75rem 1rem;
262 - background: var(--color-panel);
263 - border: 1px solid var(--color-border);
264 - border-top: none;
265 - font-size: 0.9rem;
266 - }
267 -
268 - .scheduler-status-bar-bottom {
269 - border-top: 1px solid var(--color-border);
270 - border-radius: 0 0 8px 8px;
271 - }
272 -
273 - .status-info {
274 - display: flex;
275 - align-items: center;
276 - gap: 0.5rem;
277 - flex-wrap: wrap;
278 - }
279 -
280 - .status-item {
281 - display: flex;
282 - align-items: center;
283 - gap: 0.25rem;
284 - }
285 -
286 - .status-item strong {
287 - color: var(--color-primary);
288 - font-weight: 600;
289 - }
290 -
291 - .status-item .material-symbols-outlined {
292 - font-size: 18px;
293 - opacity: 0.6;
294 - }
295 -
296 - .status-separator {
297 - color: var(--color-border);
298 - margin: 0 0.25rem;
299 - }
300 -
301 - .status-pagination {
302 - display: flex;
303 - align-items: center;
304 - gap: 0.5rem;
305 - }
306 -
307 - .page-input-group {
308 - display: flex;
309 - align-items: center;
310 - gap: 0.5rem;
311 - }
312 -
313 - .page-input-group label {
314 - opacity: 0.8;
315 - }
316 -
317 - .page-total {
318 - color: var(--color-text);
319 - opacity: 0.8;
320 - font-size: 0.85rem;
321 - white-space: nowrap;
322 - }
323 -
324 - .page-total strong {
325 - color: var(--color-primary);
326 - font-weight: 600;
327 - }
328 -
329 - .page-input {
330 - width: 60px;
331 - padding: 0.25rem 0.5rem;
332 - text-align: center;
333 - border: 1px solid var(--color-border);
334 - background: var(--color-input);
335 - color: var(--color-text);
336 - border-radius: 4px;
337 - font-size: 0.85rem;
338 - height: 28px;
339 - }
340 -
341 - .page-input:focus {
342 - outline: none;
343 - border-color: var(--color-primary);
344 - background: var(--color-input-focus);
345 - }
346 -
347 - .page-input::-webkit-inner-spin-button,
348 - .page-input::-webkit-outer-spin-button {
349 - -webkit-appearance: none;
350 - margin: 0;
351 - }
352 -
353 - .page-input[type=number] {
354 - -moz-appearance: textfield;
355 - appearance: textfield;
356 - }
357 -
358 - /* Table Container */
359 - .scheduler-table-container {
360 - border: 1px solid var(--color-border);
361 - border-top: none;
362 - border-radius: 0 0 8px 8px;
363 - overflow: hidden;
364 - }
365 -
366 - .scheduler-task-table {
367 - width: 100%;
368 - border-collapse: collapse;
369 - table-layout: auto;
370 - font-size: 0.9rem;
371 - }
372 -
373 - .scheduler-task-table th,
374 - .scheduler-task-table td {
375 - padding: 0.75rem;
376 - text-align: left;
377 - border-bottom: 1px solid var(--color-border);
378 - vertical-align: middle;
379 - overflow: hidden;
380 - text-overflow: ellipsis;
381 - white-space: nowrap;
382 - }
383 -
384 - .scheduler-task-table th {
385 - background: var(--color-panel);
386 - font-weight: 600;
387 - cursor: pointer;
388 - user-select: none;
389 - }
390 -
391 - .scheduler-task-table th:hover {
392 - background: var(--color-bg-tertiary);
393 - }
394 -
395 - .scheduler-task-table tbody tr {
396 - cursor: pointer;
397 - transition: background-color 0.2s ease;
398 - }
399 -
400 - .scheduler-task-table tbody tr:hover {
401 - background-color: rgba(255, 255, 255, 0.03);
402 - }
403 -
404 - .light-mode .scheduler-task-table tbody tr:hover {
405 - background-color: rgba(0, 0, 0, 0.02);
406 - }
407 -
408 - .scheduler-task-table tbody tr:last-child td {
409 - border-bottom: none;
410 - }
411 -
412 - /* Column widths */
413 - .col-name { width: 25%; }
414 - .col-state { width: 12%; }
415 - .col-project { width: 20%; }
416 - .col-lastrun { width: 23%; }
417 - .col-actions { width: 20%; }
418 -
419 - /* Empty state */
420 - .scheduler-empty {
421 - text-align: center;
422 - padding: 3rem 1rem;
423 - color: var(--color-text);
424 - opacity: 0.8;
425 - background: var(--color-panel);
426 - border: 1px solid var(--color-border);
427 - border-top: none;
428 - border-radius: 0 0 8px 8px;
429 - }
430 -
431 - .scheduler-empty-text {
432 - margin-bottom: 1rem;
433 - font-size: 1rem;
434 - }
435 -
436 - .scheduler-empty .btn {
437 - display: inline-flex;
438 - align-items: center;
439 - gap: 0.5rem;
440 - }
441 -
442 - /* Sort indicator */
443 - .scheduler-sort-indicator {
444 - display: inline-block;
445 - margin-left: 4px;
446 - transition: transform 0.2s ease;
220 + /* Scheduler-specific: Filter select min-width */
221 + .scheduler-list-container .filter-group-inline select {
222 + min-width: 120px;
223 }
224
449 - .scheduler-sort-desc {
450 - transform: rotate(180deg);
451 - }
225 + /* Scheduler-specific: Column widths */
226 + .scheduler-task-table .col-name { width: 25%; }
227 + .scheduler-task-table .col-state { width: 12%; }
228 + .scheduler-task-table .col-project { width: 20%; }
229 + .scheduler-task-table .col-lastrun { width: 23%; }
230 + .scheduler-task-table .col-actions { width: 20%; }
231
453 - /* Project color ball */
232 + /* Scheduler-specific: Project color ball */
233 .project-color-ball {
234 display: inline-block;
235 width: 10px;
@@ -460,51 +239,23 @@
239 vertical-align: middle;
240 }
241
463 - /* Responsive */
242 + /* Responsive - Scheduler-specific */
243 @media (max-width: 870px) {
465 - .scheduler-filters-header {
466 - flex-direction: column;
467 - align-items: stretch;
468 - }
469 -
470 - .filter-group-inline {
471 - width: 100%;
472 - }
473 -
474 - .filter-group-inline select {
475 - flex: 1;
476 - }
477 -
244 .btn-new-task {
245 margin-left: 0;
246 justify-content: center;
247 }
248
483 - .scheduler-status-bar {
484 - flex-direction: column;
485 - gap: 0.75rem;
486 - }
487 -
488 - .status-info {
489 - width: 100%;
490 - justify-content: center;
491 - }
492 -
493 - .status-pagination {
494 - width: 100%;
495 - justify-content: center;
496 - padding-top: 0.5rem;
497 - border-top: 1px solid var(--color-border);
498 - }
499 -
500 - .col-project,
501 - .col-lastrun {
249 + /* Hide less important columns on mobile */
250 + .scheduler-task-table .col-project,
251 + .scheduler-task-table .col-lastrun {
252 display: none;
253 }
254
505 - .col-name { width: 45%; }
506 - .col-state { width: 20%; }
507 - .col-actions { width: 35%; }
255 + /* Adjust remaining column widths */
256 + .scheduler-task-table .col-name { width: 45%; }
257 + .scheduler-task-table .col-state { width: 20%; }
258 + .scheduler-task-table .col-actions { width: 35%; }
259 }
260 </style>
261 </body>
webui/components/settings/memory/memory-dashboard.html
+106 -413
@@ -14,7 +14,7 @@
14 class="memory-dashboard">
15
16 <!-- Search and Filters Section -->
17 - <div class="memory-filters-header">
17 + <div class="filters-header memory-filters-header">
18 <div class="filter-group-inline filter-memory-dir">
19 <label for="memory-subdir-select">Memory Directory</label>
20 <select id="memory-subdir-select" x-model="$store.memoryDashboardStore.selectedMemorySubdir"
@@ -81,10 +81,10 @@
81
82 <!-- Memory table -->
83 <div x-show="!$store.memoryDashboardStore.loading && !$store.memoryDashboardStore.error"
84 - class="memory-table-container">
84 + class="data-table-container data-table-container-standalone memory-table-container">
85
86 <!-- Status Bar -->
87 - <div class="memory-status-bar" x-show="!$store.memoryDashboardStore.loading">
87 + <div class="data-status-bar" x-show="!$store.memoryDashboardStore.loading">
88 <div class="status-info">
89 <span class="status-item">
90 <span class="material-symbols-outlined">database</span>
@@ -164,7 +164,7 @@
164 </div>
165
166 <div class="table-wrapper">
167 - <table class="memory-table">
167 + <table class="data-table memory-table">
168 <thead>
169 <tr>
170 <th class="col-select">
@@ -288,7 +288,7 @@
288
289
290 <!-- Status Bar -->
291 - <div class="memory-status-bar" x-show="!$store.memoryDashboardStore.loading">
291 + <div class="data-status-bar data-status-bar-bottom" x-show="!$store.memoryDashboardStore.loading">
292 <div class="status-info">
293 <span class="status-item">
294 <span class="material-symbols-outlined">database</span>
@@ -335,7 +335,7 @@
335
336 <!-- No memories message -->
337 <div x-show="$store.memoryDashboardStore.memories.length === 0 && !$store.memoryDashboardStore.message"
338 - class="no-memories">
338 + class="data-empty-state">
339 No memories found matching the current filters.
340 </div>
341
@@ -355,6 +355,7 @@
355 </div>
356
357 <style>
358 + /* Modal footer layout */
359 .modal-inner.modal-with-footer {
360 display: flex;
361 flex-direction: column;
@@ -366,17 +367,13 @@
367 .modal-inner.modal-with-footer .modal-footer {
368 flex-shrink: 0;
369 }
370 +
371 + /* Memory-specific: Grid-based filter header layout */
372 .memory-filters-header {
373 display: grid;
374 grid-template-columns: 35% 25% 10% auto;
375 grid-template-rows: auto auto;
373 - gap: 0.75rem;
374 - align-items: center;
376 margin: 1rem 0;
376 - padding: 1rem;
377 - background: var(--color-panel);
378 - border: 1px solid var(--color-border);
379 - border-radius: 8px;
377 }
378
379 /* Grid positioning for desktop - 2 rows */
@@ -399,6 +396,15 @@
396 #search-input {
397 grid-column: 1 / 3;
398 grid-row: 2;
399 + flex: 1;
400 + border: 1px solid var(--color-border);
401 + font-size: 0.9rem;
402 + height: 38px;
403 + }
404 + #search-input:focus {
405 + outline: none;
406 + border-color: var(--color-primary);
407 + background: var(--color-input-focus);
408 }
409 .btn-search {
410 grid-column: 3;
@@ -411,223 +417,45 @@
417 justify-self: end;
418 }
419
414 - .filter-group-inline {
415 - display: flex;
420 + /* Memory-specific filter overrides */
421 + .memory-filters-header .filter-group-inline {
422 flex: 1;
417 - align-items: center;
418 - gap: 0.5rem;
419 - }
420 - .filter-group-inline label {
421 - font-size: 0.85rem;
422 - font-weight: 600;
423 - color: var(--color-text);
424 - opacity: 0.8;
425 - white-space: nowrap;
423 }
427 - .filter-group-inline select,
428 - .filter-group-inline input {
429 - border: 1px solid var(--color-border);
424 + .memory-filters-header .filter-group-inline select,
425 + .memory-filters-header .filter-group-inline input {
426 flex: 0 auto;
431 - min-width: 0;
432 - }
433 - .filter-group-inline select:focus,
434 - .filter-group-inline input:focus {
435 - outline: none;
436 - border-color: var(--color-primary);
437 - background: var(--color-input-focus);
427 }
439 - .filter-group-inline.filter-limit {
428 + .memory-filters-header .filter-group-inline.filter-limit {
429 flex: 0 0 auto;
430 }
431 .filter-limit input {
432 flex: 0 0 150%;
433 }
445 - #search-input {
446 - flex: 1;
447 - border: 1px solid var(--color-border);
448 - font-size: 0.9rem;
449 - height: 38px;
450 - }
451 - #search-input:focus {
452 - outline: none;
453 - border-color: var(--color-primary);
454 - background: var(--color-input-focus);
455 - }
456 - .filter-threshold label {
457 - font-size: 0.85rem;
458 - opacity: 0.8;
459 - }
460 -
461 - .memory-status-bar {
462 - display: flex;
463 - justify-content: space-between;
464 - align-items: center;
465 - padding: 0.75rem 1rem;
466 - background: var(--color-panel);
467 - border-bottom: 1px solid var(--color-border);
468 - border-radius: 8px;
469 - font-size: 0.9rem;
470 - }
471 - .status-info {
472 - display: flex;
473 - align-items: center;
474 - gap: 0.5rem;
475 - flex-wrap: wrap;
476 - }
477 - .status-item {
478 - display: flex;
479 - align-items: center;
480 - gap: 0.25rem;
481 - }
482 - .status-item strong {
483 - color: var(--color-primary);
484 - font-weight: 600;
485 - }
486 - .status-item .material-symbols-outlined {
487 - font-size: 18px;
488 - opacity: 0.6;
489 - }
490 - .status-separator {
491 - color: var(--color-border);
492 - margin: 0 0.25rem;
493 - }
494 - .status-pagination {
495 - display: flex;
496 - align-items: center;
497 - gap: 0.5rem;
498 - }
499 -
500 - .btn-icon {
501 - background: transparent;
502 - border: 1px solid var(--color-border);
503 - color: var(--color-text);
504 - padding: 0.25rem;
505 - display: flex;
506 - align-items: center;
507 - font-size: 0;
508 - }
509 - .btn-icon:hover:not(:disabled) {
510 - background: var(--color-panel);
511 - border-color: var(--color-primary);
512 - color: var(--color-text);
513 - }
514 - .btn-icon:disabled {
515 - opacity: 0.4;
516 - cursor: not-allowed;
517 - }
518 - .btn-icon .material-symbols-outlined {
519 - font-size: 20px;
520 - }
521 -
522 - .page-input-group {
523 - display: flex;
524 - align-items: center;
525 - gap: 0.5rem;
526 - }
527 - .page-input-group label {
528 - opacity: 0.8;
529 - }
530 - .page-total {
531 - color: var(--color-text);
532 - opacity: 0.8;
533 - font-size: 0.85rem;
534 - white-space: nowrap;
535 - }
536 - .page-total strong {
537 - color: var(--color-primary);
538 - font-weight: 600;
539 - }
540 - .page-input {
541 - width: 60px;
542 - padding: 0.25rem 0.5rem;
543 - text-align: center;
544 - border: 1px solid var(--color-border);
545 - background: var(--color-input);
546 - color: var(--color-text);
547 - border-radius: 4px;
548 - font-size: 0.85rem;
549 - height: 28px;
550 - }
551 - .page-input:focus {
552 - outline: none;
553 - border-color: var(--color-primary);
554 - background: var(--color-input-focus);
555 - }
556 -
557 - /* Remove spinner arrows */
558 - .page-input::-webkit-inner-spin-button,
559 - .page-input::-webkit-outer-spin-button {
560 - -webkit-appearance: none;
561 - margin: 0;
562 - }
563 - .page-input[type=number] {
564 - -moz-appearance: textfield;
565 - appearance: textfield;
566 - }
434
568 - .loading-state,
569 - .error-state,
570 - .no-memories,
571 - .init-message {
572 - text-align: center;
573 - padding: 2rem;
574 - color: var(--color-text);
575 - opacity: 0.8;
576 - background: var(--color-panel);
577 - border: 1px solid var(--color-border);
578 - border-radius: 8px;
579 - margin: 1rem 0;
580 - }
581 - .loading-state {
582 - display: grid;
583 - align-items: center;
435 + /* Memory-specific: Table layout overrides */
436 + .memory-table {
437 + table-layout: fixed;
438 }
585 - .init-message {
586 - color: var(--color-primary);
587 - border-color: var(--color-primary);
439 + .memory-table th,
440 + .memory-table td {
441 + padding: 1rem 0.75rem;
442 + vertical-align: top;
443 }
589 - .error-state {
590 - color: var(--color-accent);
591 - border-color: var(--color-accent);
592 - }
593 -
594 - .loading-spinner {
595 - width: 24px;
596 - height: 24px;
597 - min-width: 24px;
598 - min-height: 24px;
599 - max-width: 24px;
600 - max-height: 24px;
601 - border: 3px solid var(--color-border);
602 - border-top-color: var(--color-primary);
603 - border-radius: 50%;
604 - animation: spin 1s linear infinite;
605 - margin: 0 auto 0.5rem;
606 - flex-shrink: 0;
607 - flex-grow: 0;
608 - display: inline-block;
609 - position: relative;
610 - box-sizing: border-box;
444 + .memory-table th {
445 + position: sticky;
446 + top: 0;
447 + z-index: 10;
448 + border-bottom: 2px solid var(--color-border);
449 }
612 -
613 - @keyframes spin {
614 - to {
615 - transform: rotate(360deg);
616 - }
450 + .memory-table tbody tr.memory-row > td {
451 + height: 5em;
452 }
453
619 - .memory-table-container {
620 - border: 1px solid var(--color-border);
621 - border-radius: 8px;
622 - }
623 - .memory-table {
624 - width: 100%;
625 - border-collapse: collapse;
626 - table-layout: fixed;
627 - }
628 -
454 + /* Memory-specific column widths */
455 .col-select {
456 width: 5%;
457 + text-align: center;
458 + padding: 0.65rem 0.5rem !important;
459 }
460 .col-metadata {
461 width: 10em;
@@ -635,16 +463,12 @@
463 .col-actions {
464 width: 4em;
465 }
638 -
639 - .memory-table th,
640 - .memory-table td {
641 - padding: 1rem 0.75rem;
642 - text-align: left;
643 - vertical-align: top;
644 - border-bottom: 1px solid var(--color-border);
645 - overflow: hidden;
466 + .col-select input[type="checkbox"] {
467 + cursor: pointer;
468 + transform: scale(1.2);
469 }
647 -
470 +
471 + /* Memory-specific: Metadata display */
472 .metadata-cell {
473 overflow: visible;
474 white-space: normal;
@@ -654,6 +478,8 @@
478 display: flex;
479 flex-direction: column;
480 gap: 0.5rem;
481 + height: 5.5em;
482 + overflow: hidden;
483 }
484 .metadata-row {
485 display: flex;
@@ -666,6 +492,7 @@
492 font-family: monospace;
493 }
494
495 + /* Memory-specific: Area badge */
496 .area-badge {
497 color: white;
498 padding: 0.35rem 0.75rem;
@@ -674,95 +501,8 @@
501 font-weight: bold;
502 text-transform: uppercase;
503 }
677 - .select-cell,
678 - .col-select {
679 - text-align: center;
680 - padding: 0.65rem 0.5rem !important;
681 - }
682 - .select-cell input[type="checkbox"],
683 - .col-select input[type="checkbox"] {
684 - cursor: pointer;
685 - transform: scale(1.2);
686 - }
687 - .memory-row.selected {
688 - background: var(--color-panel);
689 - border-left: 3px solid var(--color-primary);
690 - }
691 - .mass-action-toolbar {
692 - display: flex;
693 - justify-content: space-between;
694 - align-items: center;
695 - padding: 1rem;
696 - background: var(--color-panel);
697 - margin: 0;
698 - animation: slideDown 0.3s ease;
699 - }
700 - .btn-mass {
701 - align-items: center;
702 - border: 1px solid var(--color-border);
703 - gap: 0.25rem;
704 - display: flex;
705 - background: var(--color-background);
706 - color: var(--color-text);
707 - }
708 - .btn-mass:hover {
709 - border-color: var(--color-primary);
710 - color: var(--color-primary);
711 - }
712 - .btn-mass.delete:hover {
713 - border-color: var(--color-accent);
714 - color: var(--color-accent);
715 - }
716 - .selection-info {
717 - font-weight: 600;
718 - }
719 - .mass-actions {
720 - display: flex;
721 - gap: 0.5rem;
722 - }
723 - .btn-mass .material-symbols-outlined {
724 - font-size: 18px;
725 - }
726 - .btn-mass:hover {
727 - border-color: var(--color-primary);
728 - background: var(--color-panel);
729 - }
730 - .btn-mass.delete:hover {
731 - border-color: var(--color-accent);
732 - color: var(--color-accent);
733 - }
734 -
735 - @keyframes slideDown {
736 - from {
737 - opacity: 0;
738 - transform: translateY(-10px);
739 - }
740 -
741 - to {
742 - opacity: 1;
743 - transform: translateY(0);
744 - }
745 - }
746 -
747 - .memory-table th {
748 - background: var(--color-panel);
749 - font-weight: 600;
750 - color: var(--color-text);
751 - position: sticky;
752 - top: 0;
753 - z-index: 10;
754 - border-bottom: 2px solid var(--color-border);
755 - }
756 - .memory-table tbody tr {
757 - border-bottom: 1px solid var(--color-border);
758 - }
759 - .memory-table tbody tr:last-child {
760 - border-bottom: none;
761 - }
762 - .memory-table tbody tr.memory-row>td {
763 - height: 5em;
764 - }
504
505 + /* Memory-specific: Source type badge */
506 .source-type {
507 padding: 0.25rem 0.5rem;
508 border-radius: var(--border-radius);
@@ -773,13 +513,16 @@
513 white-space: nowrap;
514 flex-shrink: 0;
515 }
776 - .content-preview,
777 - .metadata-info {
516 +
517 + /* Memory-specific: Content preview */
518 + .content-preview {
519 height: 5.5em;
520 overflow: hidden;
521 word-wrap: break-word;
522 overflow-wrap: break-word;
523 }
524 +
525 + /* Memory-specific: Tags */
526 .tags {
527 display: flex;
528 gap: 0.25rem;
@@ -795,70 +538,47 @@
538 color: var(--color-text);
539 opacity: 0.8;
540 }
541 +
542 + /* Memory-specific: Selection state */
543 + .memory-row.selected {
544 + background: var(--color-panel);
545 + border-left: 3px solid var(--color-primary);
546 + }
547 + .select-cell {
548 + text-align: center;
549 + padding: 0.65rem 0.5rem !important;
550 + }
551 + .select-cell input[type="checkbox"] {
552 + cursor: pointer;
553 + transform: scale(1.2);
554 + }
555 +
556 + /* Memory-specific: Actions cell vertical layout */
557 .actions-cell {
558 padding: 0 !important;
559 position: relative;
560 }
802 - .actions-wrapper {
561 + .memory-table .actions-wrapper {
562 position: absolute;
563 top: 0;
564 left: 0;
565 right: 0;
566 bottom: 0;
808 - display: flex;
567 flex-direction: column;
810 - align-items: center;
568 justify-content: center;
569 }
570
814 - /* Table action buttons - extends .btn */
815 - .btn-action {
816 - background: none;
817 - border: 1px solid var(--color-border);
818 - padding: 0.25rem;
819 - margin: 0 0.1rem;
820 - color: var(--color-text);
821 - opacity: 0.7;
822 - display: inline-flex;
823 - }
824 - .btn-action .material-symbols-outlined {
825 - font-size: 18px;
826 - }
827 - .btn-action:hover {
828 - opacity: 1;
829 - background: var(--color-panel);
830 - border-color: var(--color-primary);
831 - color: var(--color-primary);
832 - transform: translateY(-1px);
833 - }
834 - .btn-action.view:hover {
835 - background: var(--color-panel);
836 - border-color: var(--color-primary);
837 - color: var(--color-primary);
838 - }
839 - .btn-action.copy:hover {
840 - background: var(--color-panel);
841 - border-color: var(--color-primary);
842 - color: var(--color-primary);
843 - }
844 - .btn-action.delete:hover {
845 - background: var(--color-panel);
846 - border-color: var(--color-accent);
847 - color: var(--color-accent);
848 - }
849 -
850 - /* Responsive design */
571 + /* Responsive design - Memory-specific */
572 @media (max-width: 870px) {
573 .memory-dashboard {
574 padding: 0.5rem;
575 }
855 - .memory-filters-header {
856 - padding: 0.75rem;
857 - }
576 +
577 + /* Switch to flex layout on mobile */
578 .memory-filters-header {
579 display: flex;
580 flex-wrap: wrap;
861 - gap: 0.75rem;
581 + padding: 0.75rem;
582 }
583 .content-preview,
584 .metadata-info {
@@ -876,29 +596,8 @@
596 grid-column: auto;
597 grid-row: auto;
598 }
879 - .filter-group-inline {
880 - width: 100%;
881 - flex-direction: row;
882 - align-items: center;
883 - gap: 0.5rem;
884 - }
885 - .filter-group-inline label {
886 - font-size: 0.9rem;
887 - margin-bottom: 0.25rem;
888 - }
889 - .filter-group-inline select,
890 - .filter-group-inline input {
891 - height: 42px;
892 - font-size: 1rem;
893 - }
894 - .filter-group-inline.filter-limit {
895 - flex: 33%;
896 - }
897 - .filter-limit input {
898 - flex: 1;
899 - }
599
901 - /* Mobile order and layout - force items to specific widths */
600 + /* Mobile order and layout */
601 .filter-memory-dir {
602 order: 1;
603 flex: 1 1 100%;
@@ -911,29 +610,6 @@
610 order: 3;
611 flex: 1 1 100%;
612 }
914 - .btn-clear {
915 - order: 6;
916 - flex: 40%;
917 - min-width: 80px;
918 - padding: 0;
919 - height: 42px;
920 - justify-content: center;
921 - }
922 - #search-input {
923 - order: 5;
924 - flex: auto;
925 - width: 100%;
926 - height: 42px;
927 - }
928 -
929 - /* Search button and threshold on same row */
930 - .btn-search {
931 - order: 7;
932 - flex: 40%;
933 - height: 42px;
934 - padding: 0 1rem;
935 - min-width: 80px;
936 - }
613 .filter-threshold {
614 order: 4;
615 flex: 40%;
@@ -951,24 +627,41 @@
627 .filter-threshold input[type="range"] {
628 width: 100%;
629 }
954 - .memory-status-bar {
955 - flex-direction: column;
956 - gap: 0.75rem;
957 - }
958 - .status-info {
630 + #search-input {
631 + order: 5;
632 + flex: auto;
633 width: 100%;
634 + height: 42px;
635 + }
636 + .btn-clear {
637 + order: 6;
638 + flex: 40%;
639 + min-width: 80px;
640 + padding: 0;
641 + height: 42px;
642 justify-content: center;
961 - flex-wrap: wrap;
962 - gap: 0.5rem 1rem;
643 }
964 - .status-info .material-symbols-outlined {
965 - font-size: 18px;
644 + .btn-search {
645 + order: 7;
646 + flex: 40%;
647 + height: 42px;
648 + padding: 0 1rem;
649 + min-width: 80px;
650 }
967 - .status-pagination {
651 +
652 + .memory-filters-header .filter-group-inline {
653 width: 100%;
969 - justify-content: center;
970 - padding-top: 0.5rem;
971 - border-top: 1px solid var(--color-border);
654 + }
655 + .memory-filters-header .filter-group-inline select,
656 + .memory-filters-header .filter-group-inline input {
657 + height: 42px;
658 + font-size: 1rem;
659 + }
660 + .memory-filters-header .filter-group-inline.filter-limit {
661 + flex: 33%;
662 + }
663 + .filter-limit input {
664 + flex: 1;
665 }
666
667 .memory-table th,
webui/css/tables.css new
+469
@@ -0,0 +1,469 @@
1 +/* ==========================================================================
2 + Tables CSS - Shared styles for data tables and list views
3 + Used by: Memory Dashboard, Task Scheduler, and similar components
4 + ========================================================================== */
5 +
6 +/* ==========================================================================
7 + FILTER HEADER
8 + Flex-based filter layout for search/filter controls
9 + ========================================================================== */
10 +.filters-header {
11 + display: flex;
12 + flex-wrap: wrap;
13 + gap: 0.75rem;
14 + align-items: center;
15 + padding: 1rem;
16 + background: var(--color-panel);
17 + border: 1px solid var(--color-border);
18 + border-radius: 8px;
19 + margin-bottom: 0;
20 +}
21 +
22 +.filter-group-inline {
23 + display: flex;
24 + align-items: center;
25 + gap: 0.5rem;
26 +}
27 +
28 +.filter-group-inline label {
29 + font-size: 0.85rem;
30 + font-weight: 600;
31 + color: var(--color-text);
32 + opacity: 0.8;
33 + white-space: nowrap;
34 +}
35 +
36 +.filter-group-inline select,
37 +.filter-group-inline input {
38 + border: 1px solid var(--color-border);
39 + min-width: 0;
40 +}
41 +
42 +.filter-group-inline select:focus,
43 +.filter-group-inline input:focus {
44 + outline: none;
45 + border-color: var(--color-primary);
46 + background: var(--color-input-focus);
47 +}
48 +
49 +/* Filter threshold (range slider with label) */
50 +.filter-threshold {
51 + display: flex;
52 + flex-direction: column;
53 + gap: 0.25rem;
54 +}
55 +
56 +.filter-threshold label {
57 + font-size: 0.85rem;
58 + opacity: 0.8;
59 +}
60 +
61 +/* ==========================================================================
62 + STATUS BAR
63 + Status info and pagination controls
64 + ========================================================================== */
65 +.data-status-bar {
66 + display: flex;
67 + justify-content: space-between;
68 + align-items: center;
69 + padding: 0.75rem 1rem;
70 + background: var(--color-panel);
71 + border: 1px solid var(--color-border);
72 + border-top: none;
73 + font-size: 0.9rem;
74 +}
75 +
76 +.data-status-bar-top {
77 + border-top: 1px solid var(--color-border);
78 + border-bottom: none;
79 + border-radius: 8px 8px 0 0;
80 +}
81 +
82 +.data-status-bar-bottom {
83 + border-top: 1px solid var(--color-border);
84 + border-radius: 0 0 8px 8px;
85 +}
86 +
87 +.data-status-bar-standalone {
88 + border: 1px solid var(--color-border);
89 + border-radius: 8px;
90 +}
91 +
92 +.status-info {
93 + display: flex;
94 + align-items: center;
95 + gap: 0.5rem;
96 + flex-wrap: wrap;
97 +}
98 +
99 +.status-item {
100 + display: flex;
101 + align-items: center;
102 + gap: 0.25rem;
103 +}
104 +
105 +.status-item strong {
106 + color: var(--color-primary);
107 + font-weight: 600;
108 +}
109 +
110 +.status-item .material-symbols-outlined {
111 + font-size: 18px;
112 + opacity: 0.6;
113 +}
114 +
115 +.status-separator {
116 + color: var(--color-border);
117 + margin: 0 0.25rem;
118 +}
119 +
120 +/* ==========================================================================
121 + PAGINATION
122 + Page navigation controls
123 + ========================================================================== */
124 +.status-pagination {
125 + display: flex;
126 + align-items: center;
127 + gap: 0.5rem;
128 +}
129 +
130 +.page-input-group {
131 + display: flex;
132 + align-items: center;
133 + gap: 0.5rem;
134 +}
135 +
136 +.page-input-group label {
137 + opacity: 0.8;
138 +}
139 +
140 +.page-total {
141 + color: var(--color-text);
142 + opacity: 0.8;
143 + font-size: 0.85rem;
144 + white-space: nowrap;
145 +}
146 +
147 +.page-total strong {
148 + color: var(--color-primary);
149 + font-weight: 600;
150 +}
151 +
152 +.page-input {
153 + width: 60px;
154 + padding: 0.25rem 0.5rem;
155 + text-align: center;
156 + border: 1px solid var(--color-border);
157 + background: var(--color-input);
158 + color: var(--color-text);
159 + border-radius: 4px;
160 + font-size: 0.85rem;
161 + height: 28px;
162 +}
163 +
164 +.page-input:focus {
165 + outline: none;
166 + border-color: var(--color-primary);
167 + background: var(--color-input-focus);
168 +}
169 +
170 +/* Remove spinner arrows from number input */
171 +.page-input::-webkit-inner-spin-button,
172 +.page-input::-webkit-outer-spin-button {
173 + -webkit-appearance: none;
174 + margin: 0;
175 +}
176 +
177 +.page-input[type=number] {
178 + -moz-appearance: textfield;
179 + appearance: textfield;
180 +}
181 +
182 +/* ==========================================================================
183 + TABLE CONTAINER & BASE STYLES
184 + Wrapper and base table styling
185 + ========================================================================== */
186 +.data-table-container {
187 + border: 1px solid var(--color-border);
188 + border-top: none;
189 + border-radius: 0 0 8px 8px;
190 + overflow: hidden;
191 +}
192 +
193 +.data-table-container-standalone {
194 + border-top: 1px solid var(--color-border);
195 + border-radius: 8px;
196 +}
197 +
198 +.table-wrapper {
199 + overflow-x: auto;
200 +}
201 +
202 +.data-table {
203 + width: 100%;
204 + border-collapse: collapse;
205 + table-layout: auto;
206 + font-size: 0.9rem;
207 +}
208 +
209 +.data-table th,
210 +.data-table td {
211 + padding: 0.75rem;
212 + text-align: left;
213 + border-bottom: 1px solid var(--color-border);
214 + vertical-align: middle;
215 + overflow: hidden;
216 + text-overflow: ellipsis;
217 + white-space: nowrap;
218 +}
219 +
220 +.data-table th {
221 + background: var(--color-panel);
222 + font-weight: 600;
223 + color: var(--color-text);
224 +}
225 +
226 +.data-table th.sortable {
227 + cursor: pointer;
228 + user-select: none;
229 +}
230 +
231 +.data-table th.sortable:hover {
232 + background: var(--color-bg-tertiary);
233 +}
234 +
235 +.data-table tbody tr {
236 + cursor: pointer;
237 + transition: background-color 0.2s ease;
238 +}
239 +
240 +.data-table tbody tr:hover {
241 + background-color: rgba(255, 255, 255, 0.03);
242 +}
243 +
244 +.light-mode .data-table tbody tr:hover {
245 + background-color: rgba(0, 0, 0, 0.02);
246 +}
247 +
248 +.data-table tbody tr:last-child td {
249 + border-bottom: none;
250 +}
251 +
252 +/* Sticky header for scrollable tables */
253 +.data-table th.sticky {
254 + position: sticky;
255 + top: 0;
256 + z-index: 10;
257 + border-bottom: 2px solid var(--color-border);
258 +}
259 +
260 +/* ==========================================================================
261 + SORT INDICATOR
262 + Arrow indicator for sortable columns
263 + ========================================================================== */
264 +.sort-indicator {
265 + display: inline-block;
266 + margin-left: 4px;
267 + transition: transform 0.2s ease;
268 +}
269 +
270 +.sort-indicator.desc {
271 + transform: rotate(180deg);
272 +}
273 +
274 +/* ==========================================================================
275 + EMPTY & LOADING STATES
276 + Empty results and loading indicators
277 + ========================================================================== */
278 +.data-empty-state {
279 + text-align: center;
280 + padding: 3rem 1rem;
281 + color: var(--color-text);
282 + opacity: 0.8;
283 + background: var(--color-panel);
284 + border: 1px solid var(--color-border);
285 + border-top: none;
286 + border-radius: 0 0 8px 8px;
287 +}
288 +
289 +.data-empty-state-standalone {
290 + border-top: 1px solid var(--color-border);
291 + border-radius: 8px;
292 +}
293 +
294 +.data-empty-state .btn {
295 + display: inline-flex;
296 + align-items: center;
297 + gap: 0.5rem;
298 + margin-top: 1rem;
299 +}
300 +
301 +.loading-state {
302 + text-align: center;
303 + padding: 2rem;
304 + color: var(--color-text);
305 + opacity: 0.8;
306 + background: var(--color-panel);
307 + border: 1px solid var(--color-border);
308 + border-radius: 8px;
309 + margin: 1rem 0;
310 + display: grid;
311 + align-items: center;
312 +}
313 +
314 +.error-state {
315 + text-align: center;
316 + padding: 2rem;
317 + color: var(--color-accent);
318 + border: 1px solid var(--color-accent);
319 + background: var(--color-panel);
320 + border-radius: 8px;
321 + margin: 1rem 0;
322 +}
323 +
324 +.init-message {
325 + text-align: center;
326 + padding: 2rem;
327 + color: var(--color-primary);
328 + border: 1px solid var(--color-primary);
329 + background: var(--color-panel);
330 + border-radius: 8px;
331 + margin: 1rem 0;
332 +}
333 +
334 +.loading-spinner {
335 + width: 24px;
336 + height: 24px;
337 + min-width: 24px;
338 + min-height: 24px;
339 + max-width: 24px;
340 + max-height: 24px;
341 + border: 3px solid var(--color-border);
342 + border-top-color: var(--color-primary);
343 + border-radius: 50%;
344 + animation: spin 1s linear infinite;
345 + margin: 0 auto 0.5rem;
346 + flex-shrink: 0;
347 + flex-grow: 0;
348 + display: inline-block;
349 + position: relative;
350 + box-sizing: border-box;
351 +}
352 +
353 +@keyframes spin {
354 + to {
355 + transform: rotate(360deg);
356 + }
357 +}
358 +
359 +/* ==========================================================================
360 + MASS ACTION TOOLBAR
361 + Bulk action controls for selected items
362 + ========================================================================== */
363 +.mass-action-toolbar {
364 + display: flex;
365 + justify-content: space-between;
366 + align-items: center;
367 + padding: 1rem;
368 + background: var(--color-panel);
369 + margin: 0;
370 + animation: slideDown 0.3s ease;
371 +}
372 +
373 +.selection-info {
374 + font-weight: 600;
375 +}
376 +
377 +.mass-actions {
378 + display: flex;
379 + gap: 0.5rem;
380 +}
381 +
382 +.btn-mass {
383 + align-items: center;
384 + border: 1px solid var(--color-border);
385 + gap: 0.25rem;
386 + display: flex;
387 + background: var(--color-background);
388 + color: var(--color-text);
389 +}
390 +
391 +.btn-mass .material-symbols-outlined {
392 + font-size: 18px;
393 +}
394 +
395 +.btn-mass:hover {
396 + border-color: var(--color-primary);
397 + color: var(--color-primary);
398 + background: var(--color-panel);
399 +}
400 +
401 +.btn-mass.delete:hover {
402 + border-color: var(--color-accent);
403 + color: var(--color-accent);
404 +}
405 +
406 +@keyframes slideDown {
407 + from {
408 + opacity: 0;
409 + transform: translateY(-10px);
410 + }
411 + to {
412 + opacity: 1;
413 + transform: translateY(0);
414 + }
415 +}
416 +
417 +/* ==========================================================================
418 + RESPONSIVE STYLES
419 + Mobile breakpoints for tables and status bars
420 + ========================================================================== */
421 +@media (max-width: 870px) {
422 + .filters-header {
423 + flex-direction: column;
424 + align-items: stretch;
425 + }
426 +
427 + .filter-group-inline {
428 + width: 100%;
429 + }
430 +
431 + .filter-group-inline select,
432 + .filter-group-inline input {
433 + flex: 1;
434 + }
435 +
436 + .data-status-bar {
437 + flex-direction: column;
438 + gap: 0.75rem;
439 + }
440 +
441 + .status-info {
442 + width: 100%;
443 + justify-content: center;
444 + }
445 +
446 + .status-pagination {
447 + width: 100%;
448 + justify-content: center;
449 + padding-top: 0.5rem;
450 + border-top: 1px solid var(--color-border);
451 + }
452 +
453 + .data-table th,
454 + .data-table td {
455 + padding: 0.5rem 0.25rem;
456 + font-size: 0.85rem;
457 + }
458 +
459 + .mass-action-toolbar {
460 + flex-direction: column;
461 + gap: 0.75rem;
462 + }
463 +
464 + .mass-actions {
465 + flex-wrap: wrap;
466 + justify-content: center;
467 + }
468 +}
469 +
webui/index.html
+1
@@ -17,6 +17,7 @@
17 <link rel="stylesheet" href="css/scheduler.css">
18 <link rel="stylesheet" href="css/notification.css">
19 <link rel="stylesheet" href="css/buttons.css">
20 + <link rel="stylesheet" href="css/tables.css">
21
22 <!-- Flatpickr for datetime picker -->
23 <link rel="stylesheet" href="vendor/flatpickr/flatpickr.min.css">