solid_queue_web 0.6.0 → 0.7.0
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.
- checksums.yaml +4 -4
- data/README.md +20 -2
- data/Rakefile +2 -2
- data/app/assets/stylesheets/solid_queue_web/_01_base.css +41 -0
- data/app/assets/stylesheets/solid_queue_web/_02_layout.css +105 -0
- data/app/assets/stylesheets/solid_queue_web/_03_stats.css +49 -0
- data/app/assets/stylesheets/solid_queue_web/_04_table.css +52 -0
- data/app/assets/stylesheets/solid_queue_web/_05_badges.css +27 -0
- data/app/assets/stylesheets/solid_queue_web/_06_buttons.css +38 -0
- data/app/assets/stylesheets/solid_queue_web/_07_forms.css +103 -0
- data/app/assets/stylesheets/solid_queue_web/_08_detail.css +84 -0
- data/app/assets/stylesheets/solid_queue_web/_09_pagination.css +27 -0
- data/app/assets/stylesheets/solid_queue_web/_10_responsive.css +73 -0
- data/app/assets/stylesheets/solid_queue_web/_11_throughput.css +68 -0
- data/app/assets/stylesheets/solid_queue_web/application.css +1 -617
- data/app/controllers/solid_queue_web/dashboard_controller.rb +12 -0
- data/app/controllers/solid_queue_web/failed_jobs_controller.rb +1 -1
- data/app/controllers/solid_queue_web/history_controller.rb +16 -0
- data/app/controllers/solid_queue_web/jobs_controller.rb +1 -1
- data/app/controllers/solid_queue_web/queues/jobs_controller.rb +1 -1
- data/app/controllers/solid_queue_web/queues_controller.rb +15 -0
- data/app/helpers/solid_queue_web/application_helper.rb +15 -1
- data/app/javascript/solid_queue_web/refresh_controller.js +3 -2
- data/app/views/layouts/solid_queue_web/application.html.erb +1 -0
- data/app/views/solid_queue_web/dashboard/index.html.erb +38 -0
- data/app/views/solid_queue_web/failed_jobs/index.html.erb +0 -1
- data/app/views/solid_queue_web/history/index.html.erb +67 -0
- data/app/views/solid_queue_web/jobs/index.html.erb +0 -1
- data/app/views/solid_queue_web/queues/index.html.erb +15 -1
- data/config/routes.rb +9 -8
- data/lib/solid_queue_web/version.rb +1 -1
- metadata +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 770d3981147f5b4b76fb23bdf00c65064534f286bad9c13d6331f00df500109d
|
|
4
|
+
data.tar.gz: 8bdd99286a28795b050dede93a70f69cb627d1e11dab7111ac4aa7c8f617430e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae1d0e53e8710cdaa66a84da64a5eed60fff93734c3c5e56715ed95dfa03ec3115731b1f30c5203db666c4d0abc17870f38b5faae3c8fbdd651d333457de9ea8
|
|
7
|
+
data.tar.gz: a9f4a6fc3ff1c183bd538a5df7b5c45128ffea0291c194bd3241b5bcf7a9cab3114e761a2874e6c9665a3e491f8d2369450bce657492a954c2dda287dd9464ba
|
data/README.md
CHANGED
|
@@ -33,8 +33,8 @@ SolidQueueWeb surfaces all of this in a browser UI available at any route you ch
|
|
|
33
33
|
|
|
34
34
|
## Features
|
|
35
35
|
|
|
36
|
-
- **Dashboard** — stat cards showing counts for ready, scheduled, running, blocked, and failed jobs, plus queues, recurring tasks, and processes; auto-refreshes every 5 seconds
|
|
37
|
-
- **Queues** — all queues sorted by name with size
|
|
36
|
+
- **Dashboard** — stat cards showing counts for ready, scheduled, running, blocked, and failed jobs, plus queues, recurring tasks, and processes; "Done (1h)" and "Done (24h)" throughput cards; a "Throughput — Last 12 Hours" bar chart showing completed-job counts per hour (pure CSS, no charting library); auto-refreshes every 5 seconds
|
|
37
|
+
- **Queues** — all queues sorted by name with size; oldest ready job latency (color-coded, with UTC timestamp tooltip); Done (24h) and Failed (24h) throughput counts; pause/resume controls
|
|
38
38
|
- **Jobs** — filterable by status (ready, scheduled, claimed, blocked, failed) and by queue; search by job class name with dynamic auto-submit; time-based period filter (1 h / 24 h / 7 d); discard individual or all jobs; Turbo Frame navigation so only the table updates on filter or search; auto-refreshes every 10 seconds
|
|
39
39
|
- **Failed jobs** — list of failed executions with error details; search by class name; filter by queue; time-based period filter; retry or discard individually or in bulk
|
|
40
40
|
- **Job detail** — full arguments, timestamps, blocked-until date, and error backtrace; action buttons based on job status
|
|
@@ -43,6 +43,7 @@ SolidQueueWeb surfaces all of this in a browser UI available at any route you ch
|
|
|
43
43
|
- **Processes** — workers, dispatchers, and supervisors with heartbeat health status; auto-refreshes every 10 seconds
|
|
44
44
|
- **Global search** — search across all job statuses at once by class name substring; results grouped by status with match count and direct links to filtered views; native datalist autocomplete pre-populated from all known job classes; auto-submits on selection
|
|
45
45
|
- **Targeted bulk actions** — checkboxes on the jobs and failed jobs lists for selecting individual rows; selection bar shows count and action buttons ("Discard Selected" for jobs, "Retry Selected" / "Discard Selected" for failed jobs); select-all checkbox in the table header
|
|
46
|
+
- **Job history** — browsable list of all finished jobs with class name, queue, duration, and finished timestamp; filterable by period (1h / 24h / 7d), queue, and class name search; Done (1h) / Done (24h) dashboard cards link directly to the filtered history view; auto-refreshes every 10 seconds
|
|
46
47
|
|
|
47
48
|
## Screenshots
|
|
48
49
|
|
|
@@ -96,6 +97,23 @@ end
|
|
|
96
97
|
|
|
97
98
|
HTTP Basic authentication is used as a fallback when the block returns falsy.
|
|
98
99
|
|
|
100
|
+
## Roadmap
|
|
101
|
+
|
|
102
|
+
Planned features, roughly ordered by priority:
|
|
103
|
+
|
|
104
|
+
**Near-term**
|
|
105
|
+
- Dark mode — CSS custom properties are already structured for it; toggle persists to `localStorage`
|
|
106
|
+
|
|
107
|
+
**Medium-term**
|
|
108
|
+
- Dashboard quick actions — Retry All Failed / Clear All Blocked directly from the dashboard
|
|
109
|
+
- Configurable page size — `?per=25|50|100` via Pagy's built-in support
|
|
110
|
+
|
|
111
|
+
**Larger scope**
|
|
112
|
+
- CSV export of any filtered view (jobs, failed jobs, history)
|
|
113
|
+
- Webhook / alert config — POST to a URL when the failure count exceeds a threshold
|
|
114
|
+
|
|
115
|
+
Pull requests for any of these are welcome. See [Contributing](#contributing) below.
|
|
116
|
+
|
|
99
117
|
## Contributing
|
|
100
118
|
|
|
101
119
|
Bug reports and pull requests are welcome on [GitHub](https://github.com/eclectic-coding/solid_queue_web).
|
data/Rakefile
CHANGED
|
@@ -7,7 +7,7 @@ require "rspec/core/rake_task"
|
|
|
7
7
|
RuboCop::RakeTask.new
|
|
8
8
|
RSpec::Core::RakeTask.new(:spec)
|
|
9
9
|
|
|
10
|
-
task default: [
|
|
10
|
+
task default: [:rubocop, :spec]
|
|
11
11
|
|
|
12
12
|
namespace :dev do
|
|
13
13
|
def dummy_env
|
|
@@ -32,5 +32,5 @@ namespace :dev do
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
desc "Reset and reseed the dummy app development database"
|
|
35
|
-
task reset: [
|
|
35
|
+
task reset: [:setup, :seed]
|
|
36
36
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2
|
+
|
|
3
|
+
.sqd-sr-only {
|
|
4
|
+
position: absolute;
|
|
5
|
+
width: 1px;
|
|
6
|
+
height: 1px;
|
|
7
|
+
padding: 0;
|
|
8
|
+
margin: -1px;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
clip: rect(0, 0, 0, 0);
|
|
11
|
+
white-space: nowrap;
|
|
12
|
+
border: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:focus-visible {
|
|
16
|
+
outline: 2px solid var(--primary);
|
|
17
|
+
outline-offset: 2px;
|
|
18
|
+
border-radius: 2px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
:root {
|
|
22
|
+
--bg: #f8f9fa;
|
|
23
|
+
--surface: #ffffff;
|
|
24
|
+
--border: #dee2e6;
|
|
25
|
+
--text: #212529;
|
|
26
|
+
--muted: #6c757d;
|
|
27
|
+
--primary: #0d6efd;
|
|
28
|
+
--danger: #dc3545;
|
|
29
|
+
--warning: #fd7e14;
|
|
30
|
+
--success: #198754;
|
|
31
|
+
--info: #0dcaf0;
|
|
32
|
+
--purple: #6f42c1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
body {
|
|
36
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
37
|
+
font-size: 14px;
|
|
38
|
+
background: var(--bg);
|
|
39
|
+
color: var(--text);
|
|
40
|
+
line-height: 1.5;
|
|
41
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
.sqd-header {
|
|
2
|
+
background: var(--surface);
|
|
3
|
+
border-bottom: 1px solid var(--border);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.sqd-header__inner {
|
|
7
|
+
max-width: 1200px;
|
|
8
|
+
margin: 0 auto;
|
|
9
|
+
padding: 0 1.5rem;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: 2rem;
|
|
13
|
+
height: 56px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.sqd-header__title {
|
|
17
|
+
font-size: 16px;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
color: var(--text);
|
|
20
|
+
text-decoration: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.sqd-nav {
|
|
24
|
+
display: flex;
|
|
25
|
+
gap: 0.25rem;
|
|
26
|
+
list-style: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.sqd-nav a {
|
|
30
|
+
display: block;
|
|
31
|
+
padding: 0.35rem 0.75rem;
|
|
32
|
+
border-radius: 6px;
|
|
33
|
+
color: var(--muted);
|
|
34
|
+
text-decoration: none;
|
|
35
|
+
font-size: 13px;
|
|
36
|
+
font-weight: 500;
|
|
37
|
+
transition: background 0.1s, color 0.1s;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sqd-nav a:hover,
|
|
41
|
+
.sqd-nav a.active {
|
|
42
|
+
background: var(--bg);
|
|
43
|
+
color: var(--text);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.sqd-nav-toggle {
|
|
47
|
+
display: none;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
gap: 5px;
|
|
51
|
+
width: 36px;
|
|
52
|
+
height: 36px;
|
|
53
|
+
padding: 6px;
|
|
54
|
+
margin-left: auto;
|
|
55
|
+
background: none;
|
|
56
|
+
border: 1px solid var(--border);
|
|
57
|
+
border-radius: 5px;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.sqd-nav-toggle span {
|
|
62
|
+
display: block;
|
|
63
|
+
height: 2px;
|
|
64
|
+
background: var(--text);
|
|
65
|
+
border-radius: 1px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.sqd-main {
|
|
69
|
+
max-width: 1200px;
|
|
70
|
+
margin: 0 auto;
|
|
71
|
+
padding: 2rem 1.5rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.sqd-page-title {
|
|
75
|
+
font-size: 20px;
|
|
76
|
+
font-weight: 600;
|
|
77
|
+
margin-bottom: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.sqd-page-header {
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: space-between;
|
|
84
|
+
margin-bottom: 1.5rem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.sqd-actions {
|
|
88
|
+
display: flex;
|
|
89
|
+
gap: 0.5rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@keyframes sqd-flash-dismiss {
|
|
93
|
+
0%, 75% { opacity: 1; max-height: 120px; padding: 0.75rem 1rem; margin-bottom: 1rem; }
|
|
94
|
+
100% { opacity: 0; max-height: 0; padding: 0; margin-bottom: 0; overflow: hidden; }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.sqd-flash {
|
|
98
|
+
padding: 0.75rem 1rem;
|
|
99
|
+
border-radius: 6px;
|
|
100
|
+
margin-bottom: 1rem;
|
|
101
|
+
font-size: 13px;
|
|
102
|
+
animation: sqd-flash-dismiss 6s ease-in forwards;
|
|
103
|
+
}
|
|
104
|
+
.sqd-flash--notice { background: #d1e7dd; color: #0f5132; border: 1px solid #badbcc; }
|
|
105
|
+
.sqd-flash--alert { background: #f8d7da; color: #842029; border: 1px solid #f5c2c7; }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
.sqd-stats {
|
|
2
|
+
display: grid;
|
|
3
|
+
grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
|
|
4
|
+
gap: 1rem;
|
|
5
|
+
margin-bottom: 2rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.sqd-stat {
|
|
9
|
+
background: var(--surface);
|
|
10
|
+
border: 1px solid var(--border);
|
|
11
|
+
border-radius: 8px;
|
|
12
|
+
padding: 1.25rem 1rem;
|
|
13
|
+
text-align: center;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.sqd-stat__value {
|
|
17
|
+
font-size: 28px;
|
|
18
|
+
font-weight: 700;
|
|
19
|
+
line-height: 1;
|
|
20
|
+
margin-bottom: 0.25rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.sqd-stat__label {
|
|
24
|
+
font-size: 12px;
|
|
25
|
+
color: var(--muted);
|
|
26
|
+
text-transform: uppercase;
|
|
27
|
+
letter-spacing: 0.05em;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sqd-stat--ready .sqd-stat__value { color: var(--success); }
|
|
31
|
+
.sqd-stat--scheduled .sqd-stat__value { color: var(--info); }
|
|
32
|
+
.sqd-stat--claimed .sqd-stat__value { color: var(--primary); }
|
|
33
|
+
.sqd-stat--failed .sqd-stat__value { color: var(--danger); }
|
|
34
|
+
.sqd-stat--blocked .sqd-stat__value { color: var(--warning); }
|
|
35
|
+
.sqd-stat--queues .sqd-stat__value { color: var(--purple); }
|
|
36
|
+
.sqd-stat--processes .sqd-stat__value { color: var(--muted); }
|
|
37
|
+
.sqd-stat--recurring .sqd-stat__value { color: var(--info); }
|
|
38
|
+
|
|
39
|
+
.sqd-stat--link {
|
|
40
|
+
display: block;
|
|
41
|
+
text-decoration: none;
|
|
42
|
+
color: inherit;
|
|
43
|
+
transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
|
|
44
|
+
}
|
|
45
|
+
.sqd-stat--link:hover {
|
|
46
|
+
border-color: var(--primary);
|
|
47
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
48
|
+
transform: translateY(-2px);
|
|
49
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.sqd-card {
|
|
2
|
+
background: var(--surface);
|
|
3
|
+
border: 1px solid var(--border);
|
|
4
|
+
border-radius: 8px;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.sqd-card__header {
|
|
9
|
+
padding: 0.875rem 1rem;
|
|
10
|
+
border-bottom: 1px solid var(--border);
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: space-between;
|
|
14
|
+
gap: 1rem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.sqd-card__title {
|
|
18
|
+
font-size: 14px;
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
table {
|
|
23
|
+
width: 100%;
|
|
24
|
+
border-collapse: collapse;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
th {
|
|
28
|
+
padding: 0.625rem 1rem;
|
|
29
|
+
text-align: left;
|
|
30
|
+
font-size: 12px;
|
|
31
|
+
font-weight: 600;
|
|
32
|
+
color: var(--muted);
|
|
33
|
+
text-transform: uppercase;
|
|
34
|
+
letter-spacing: 0.05em;
|
|
35
|
+
border-bottom: 1px solid var(--border);
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
td {
|
|
40
|
+
padding: 0.75rem 1rem;
|
|
41
|
+
border-bottom: 1px solid var(--border);
|
|
42
|
+
vertical-align: middle;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
tr:last-child td { border-bottom: none; }
|
|
46
|
+
tbody tr:hover { background: var(--bg); }
|
|
47
|
+
|
|
48
|
+
.sqd-empty {
|
|
49
|
+
text-align: center;
|
|
50
|
+
padding: 3rem 1rem;
|
|
51
|
+
color: var(--muted);
|
|
52
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.sqd-badge {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
padding: 0.2em 0.55em;
|
|
4
|
+
border-radius: 4px;
|
|
5
|
+
font-size: 11px;
|
|
6
|
+
font-weight: 600;
|
|
7
|
+
line-height: 1;
|
|
8
|
+
text-transform: uppercase;
|
|
9
|
+
letter-spacing: 0.04em;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.sqd-badge--ready { background: #d1e7dd; color: #0f5132; }
|
|
13
|
+
.sqd-badge--scheduled { background: #cff4fc; color: #055160; }
|
|
14
|
+
.sqd-badge--claimed { background: #cfe2ff; color: #084298; }
|
|
15
|
+
.sqd-badge--failed { background: #f8d7da; color: #842029; }
|
|
16
|
+
.sqd-badge--blocked { background: #fff3cd; color: #664d03; }
|
|
17
|
+
.sqd-badge--static { background: #d1e7dd; color: #0f5132; }
|
|
18
|
+
.sqd-badge--dynamic { background: #e0d7f5; color: #4a2c8a; }
|
|
19
|
+
.sqd-badge--paused { background: #e2e3e5; color: #41464b; }
|
|
20
|
+
.sqd-badge--running { background: #d1e7dd; color: #0f5132; }
|
|
21
|
+
.sqd-badge--supervisor { background: #e0d7f5; color: #4a2c8a; }
|
|
22
|
+
.sqd-badge--worker { background: #d1e7dd; color: #0f5132; }
|
|
23
|
+
.sqd-badge--dispatcher { background: #cff4fc; color: #055160; }
|
|
24
|
+
|
|
25
|
+
.sqd-process-meta { font-size: 12px; color: var(--muted); }
|
|
26
|
+
.sqd-process-meta span + span::before { content: " · "; }
|
|
27
|
+
.sqd-muted-text { color: var(--muted); font-size: 13px; }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
.sqd-btn {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
padding: 0.35rem 0.75rem;
|
|
5
|
+
border-radius: 5px;
|
|
6
|
+
font-size: 12px;
|
|
7
|
+
font-weight: 500;
|
|
8
|
+
text-decoration: none;
|
|
9
|
+
border: 1px solid transparent;
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
transition: opacity 0.15s;
|
|
12
|
+
}
|
|
13
|
+
.sqd-btn:hover { opacity: 0.85; }
|
|
14
|
+
.sqd-btn--primary { background: var(--primary); color: #fff; border-color: var(--primary); }
|
|
15
|
+
.sqd-btn--danger { background: var(--danger); color: #fff; border-color: var(--danger); }
|
|
16
|
+
.sqd-btn--muted { background: var(--surface); color: var(--text); border-color: var(--border); }
|
|
17
|
+
.sqd-btn--sm { padding: 0.2rem 0.55rem; font-size: 11px; }
|
|
18
|
+
|
|
19
|
+
.sqd-row-actions { white-space: nowrap; text-align: right; width: 1%; }
|
|
20
|
+
.sqd-row-actions form { display: inline; margin-left: 0.25rem; }
|
|
21
|
+
|
|
22
|
+
.sqd-selection-bar {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 0.75rem;
|
|
26
|
+
padding: 0.5rem 1rem;
|
|
27
|
+
background: var(--bg);
|
|
28
|
+
border-bottom: 1px solid var(--border);
|
|
29
|
+
font-size: 13px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
table th input[type="checkbox"],
|
|
33
|
+
table td input[type="checkbox"] {
|
|
34
|
+
width: 15px;
|
|
35
|
+
height: 15px;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
accent-color: var(--primary);
|
|
38
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
.sqd-search {
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: 0.5rem;
|
|
4
|
+
align-items: center;
|
|
5
|
+
margin-bottom: 1rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.sqd-search__input {
|
|
9
|
+
width: 280px;
|
|
10
|
+
padding: 0.35rem 0.75rem;
|
|
11
|
+
border: 1px solid var(--border);
|
|
12
|
+
border-radius: 5px;
|
|
13
|
+
font-size: 13px;
|
|
14
|
+
background: var(--surface);
|
|
15
|
+
color: var(--text);
|
|
16
|
+
line-height: 1.5;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sqd-search__input:focus {
|
|
20
|
+
outline: 2px solid var(--primary);
|
|
21
|
+
outline-offset: -1px;
|
|
22
|
+
border-color: var(--primary);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@media (max-width: 640px) {
|
|
26
|
+
.sqd-search { flex-wrap: wrap; }
|
|
27
|
+
.sqd-search__input { width: 100%; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sqd-search--global { margin-bottom: 2rem; }
|
|
31
|
+
|
|
32
|
+
.sqd-search__input--lg {
|
|
33
|
+
width: 420px;
|
|
34
|
+
font-size: 15px;
|
|
35
|
+
padding: 0.5rem 1rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@media (max-width: 640px) {
|
|
39
|
+
.sqd-search__input--lg { width: 100%; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.sqd-search-group {
|
|
43
|
+
margin-bottom: 2rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.sqd-search-group__header {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 0.75rem;
|
|
50
|
+
margin-bottom: 0.75rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.sqd-filters {
|
|
54
|
+
display: flex;
|
|
55
|
+
gap: 0.5rem;
|
|
56
|
+
flex-wrap: wrap;
|
|
57
|
+
margin-bottom: 1rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.sqd-filters a {
|
|
61
|
+
padding: 0.35rem 0.875rem;
|
|
62
|
+
border-radius: 20px;
|
|
63
|
+
font-size: 12px;
|
|
64
|
+
font-weight: 500;
|
|
65
|
+
text-decoration: none;
|
|
66
|
+
border: 1px solid var(--border);
|
|
67
|
+
color: var(--muted);
|
|
68
|
+
background: var(--surface);
|
|
69
|
+
transition: all 0.1s;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.sqd-filters a:hover,
|
|
73
|
+
.sqd-filters a.active {
|
|
74
|
+
background: var(--primary);
|
|
75
|
+
border-color: var(--primary);
|
|
76
|
+
color: #fff;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.sqd-period-filter {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: 0.25rem;
|
|
83
|
+
margin-left: auto;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.sqd-period-filter a {
|
|
87
|
+
padding: 0.2rem 0.55rem;
|
|
88
|
+
border-radius: 4px;
|
|
89
|
+
font-size: 11px;
|
|
90
|
+
font-weight: 500;
|
|
91
|
+
text-decoration: none;
|
|
92
|
+
border: 1px solid var(--border);
|
|
93
|
+
color: var(--muted);
|
|
94
|
+
background: var(--surface);
|
|
95
|
+
transition: all 0.1s;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.sqd-period-filter a:hover,
|
|
99
|
+
.sqd-period-filter a.active {
|
|
100
|
+
background: var(--muted);
|
|
101
|
+
border-color: var(--muted);
|
|
102
|
+
color: #fff;
|
|
103
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
.sqd-mono {
|
|
2
|
+
font-family: "SFMono-Regular", Menlo, Monaco, Consolas, monospace;
|
|
3
|
+
font-size: 12px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.sqd-error-msg {
|
|
7
|
+
color: var(--danger);
|
|
8
|
+
font-size: 12px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.sqd-truncate {
|
|
12
|
+
max-width: 320px;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
text-overflow: ellipsis;
|
|
15
|
+
white-space: nowrap;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.sqd-detail-grid {
|
|
19
|
+
display: grid;
|
|
20
|
+
grid-template-columns: 1fr 1fr;
|
|
21
|
+
gap: 1.5rem;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.sqd-grid-2 {
|
|
25
|
+
display: grid;
|
|
26
|
+
grid-template-columns: 1fr 1fr;
|
|
27
|
+
gap: 1rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sqd-breadcrumb {
|
|
31
|
+
font-size: 12px;
|
|
32
|
+
color: var(--muted);
|
|
33
|
+
margin-bottom: 0.25rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sqd-breadcrumb a { color: var(--muted); text-decoration: none; }
|
|
37
|
+
.sqd-breadcrumb a:hover { color: var(--text); }
|
|
38
|
+
|
|
39
|
+
.sqd-detail-section { padding: 1.25rem; }
|
|
40
|
+
|
|
41
|
+
.sqd-section-title {
|
|
42
|
+
font-size: 13px;
|
|
43
|
+
font-weight: 600;
|
|
44
|
+
text-transform: uppercase;
|
|
45
|
+
letter-spacing: 0.05em;
|
|
46
|
+
color: var(--muted);
|
|
47
|
+
margin-bottom: 1rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.sqd-section-title--danger { color: var(--danger); }
|
|
51
|
+
|
|
52
|
+
.sqd-dl {
|
|
53
|
+
display: grid;
|
|
54
|
+
grid-template-columns: auto 1fr;
|
|
55
|
+
gap: 0.5rem 1.5rem;
|
|
56
|
+
font-size: 13px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.sqd-dl dt { color: var(--muted); white-space: nowrap; }
|
|
60
|
+
.sqd-dl dd { word-break: break-all; }
|
|
61
|
+
|
|
62
|
+
.sqd-pre {
|
|
63
|
+
font-family: monospace;
|
|
64
|
+
font-size: 12px;
|
|
65
|
+
background: var(--bg);
|
|
66
|
+
border: 1px solid var(--border);
|
|
67
|
+
border-radius: 5px;
|
|
68
|
+
padding: 0.75rem;
|
|
69
|
+
overflow-x: auto;
|
|
70
|
+
white-space: pre-wrap;
|
|
71
|
+
word-break: break-word;
|
|
72
|
+
max-height: 400px;
|
|
73
|
+
overflow-y: auto;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.sqd-pre--muted { color: var(--muted); }
|
|
77
|
+
|
|
78
|
+
.sqd-error-header {
|
|
79
|
+
font-size: 13px;
|
|
80
|
+
padding: 0.5rem 0.75rem;
|
|
81
|
+
background: #f8d7da;
|
|
82
|
+
color: #842029;
|
|
83
|
+
border-radius: 5px;
|
|
84
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
nav.pagy {
|
|
2
|
+
display: flex;
|
|
3
|
+
justify-content: center;
|
|
4
|
+
gap: 0.25rem;
|
|
5
|
+
padding: 1rem;
|
|
6
|
+
list-style: none;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
nav.pagy a {
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
min-width: 32px;
|
|
14
|
+
height: 32px;
|
|
15
|
+
padding: 0 0.5rem;
|
|
16
|
+
border-radius: 5px;
|
|
17
|
+
font-size: 13px;
|
|
18
|
+
text-decoration: none;
|
|
19
|
+
border: 1px solid var(--border);
|
|
20
|
+
color: var(--text);
|
|
21
|
+
background: var(--surface);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
nav.pagy a:hover:not([aria-disabled="true"]) { background: var(--bg); }
|
|
25
|
+
nav.pagy a[aria-current="page"] { background: var(--primary); color: #fff; border-color: var(--primary); }
|
|
26
|
+
nav.pagy a[role="separator"],
|
|
27
|
+
nav.pagy a[aria-disabled="true"]:not([aria-current="page"]) { color: var(--muted); cursor: default; }
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
@media (max-width: 768px) {
|
|
2
|
+
.sqd-detail-grid { grid-template-columns: 1fr; }
|
|
3
|
+
.sqd-grid-2 { grid-template-columns: 1fr; }
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
@media (max-width: 640px) {
|
|
7
|
+
.sqd-main {
|
|
8
|
+
padding: 1.5rem 1rem;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.sqd-page-header {
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
align-items: flex-start;
|
|
14
|
+
gap: 0.75rem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.sqd-card {
|
|
18
|
+
overflow-x: auto;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.sqd-card__header {
|
|
22
|
+
flex-wrap: wrap;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.sqd-stats {
|
|
26
|
+
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.sqd-truncate {
|
|
30
|
+
max-width: 160px;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@media (max-width: 576px) {
|
|
35
|
+
.sqd-header {
|
|
36
|
+
position: relative;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.sqd-header__inner {
|
|
40
|
+
padding: 0 1rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sqd-nav-toggle {
|
|
44
|
+
display: flex;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sqd-nav-wrapper {
|
|
48
|
+
display: none;
|
|
49
|
+
position: absolute;
|
|
50
|
+
top: 100%;
|
|
51
|
+
left: 0;
|
|
52
|
+
right: 0;
|
|
53
|
+
background: var(--surface);
|
|
54
|
+
border-bottom: 1px solid var(--border);
|
|
55
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
56
|
+
z-index: 50;
|
|
57
|
+
padding: 0.5rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.sqd-nav-wrapper.sqd-nav--open {
|
|
61
|
+
display: block;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.sqd-nav {
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
gap: 0.25rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.sqd-nav a {
|
|
70
|
+
padding: 0.5rem 0.75rem;
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
}
|
|
73
|
+
}
|