solid_queue_dashboard 0.1.0 â 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/Procfile.dev +2 -1
- data/README.md +13 -9
- data/app/assets/javascripts/solid_queue_dashboard/application.js +17 -3
- data/app/assets/stylesheets/solid_queue_dashboard/application.css +96 -24
- data/app/assets/stylesheets/solid_queue_dashboard/tailwind.css +5 -5
- data/app/controllers/solid_queue_dashboard/appearance_controller.rb +1 -1
- data/app/controllers/solid_queue_dashboard/dashboard_controller.rb +61 -1
- data/app/controllers/solid_queue_dashboard/jobs_controller.rb +1 -1
- data/app/controllers/solid_queue_dashboard/stats_controller.rb +8 -0
- data/app/helpers/solid_queue_dashboard/icons_helper.rb +23 -0
- data/app/helpers/solid_queue_dashboard/jobs_helper.rb +8 -2
- data/app/helpers/solid_queue_dashboard/pagination_helper.rb +2 -2
- data/app/helpers/solid_queue_dashboard/processes_helper.rb +4 -2
- data/app/views/layouts/solid_queue_dashboard/application.html.erb +5 -3
- data/app/views/solid_queue_dashboard/application/_navbar.html.erb +28 -5
- data/app/views/solid_queue_dashboard/dashboard/index.html.erb +16 -28
- data/app/views/solid_queue_dashboard/jobs/_table.html.erb +1 -1
- data/app/views/solid_queue_dashboard/jobs/_table_row.html.erb +19 -3
- data/app/views/solid_queue_dashboard/jobs/index.html.erb +5 -5
- data/app/views/solid_queue_dashboard/jobs/show.html.erb +5 -3
- data/app/views/solid_queue_dashboard/processes/_table_row.html.erb +6 -0
- data/app/views/solid_queue_dashboard/processes/show.html.erb +14 -1
- data/app/views/solid_queue_dashboard/recurring_tasks/show.html.erb +6 -1
- data/app/views/solid_queue_dashboard/stats/index.html.erb +25 -0
- data/config/routes.rb +2 -0
- data/lib/solid_queue_dashboard/decorators/job_decorator.rb +9 -2
- data/lib/solid_queue_dashboard/decorators/jobs_decorator.rb +7 -0
- data/lib/solid_queue_dashboard/job.rb +5 -3
- data/lib/solid_queue_dashboard/process.rb +3 -3
- data/lib/solid_queue_dashboard/version.rb +1 -1
- data/lib/solid_queue_dashboard.rb +2 -0
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6d60665de93e100be378a8e607aaf251e62bf7c991051633dd1aba8d9b2a84f
|
4
|
+
data.tar.gz: 47a0d627dbf19a6be53260c846ca3baa35038f64bb7d6e844d77a1de68e62c83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0dc95cf029ba6e32480b9a10c07d2f50106f0630b66f0385134181d84b1af1bfd4e0dff016f1d140a696aae180eaae83f173c6575e51a04a68bf7574f9177307
|
7
|
+
data.tar.gz: 5fdb3e8c5b8f08f3afb4769855595ccf2a8426ac429428f19bd32d82cec932d7882c892b60eb5f2e2ae8ae65822a91fc1ef86cc7701b6fa994fffe31912ddabe
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.2.0] - October 17, 2024
|
4
|
+
|
5
|
+
- Show running jobs
|
6
|
+
- Add charts
|
7
|
+
- Add auto-refresh
|
8
|
+
- Add number of current processes and recurring tasks to navbar
|
9
|
+
- Fix dark mode not switching for the first time
|
10
|
+
|
11
|
+
## [0.1.1] - October 7, 2024
|
12
|
+
|
13
|
+
- Replace OpenStruct with a Hash to avoid including the `ostruct` gem
|
14
|
+
|
3
15
|
## [0.1.0] - October 7, 2024
|
4
16
|
|
5
17
|
- Initial release
|
data/Procfile.dev
CHANGED
data/README.md
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
# Solid Queue Dashboard
|
1
|
+
# Solid Queue Dashboard <sup>BETA</sup>
|
2
2
|
|
3
3
|
<p align="center">
|
4
4
|
<a href="https://github.com/akodkod/solid-queue-dashboard#gh-light-mode-only">
|
5
|
-
<img src="https://github.com/user-attachments/assets/
|
5
|
+
<img src="https://github.com/user-attachments/assets/55aa4a3c-da51-471b-8f58-0cf1f9a1f8da" alt="Solid Queue Dashboard Light Mode">
|
6
6
|
</a>
|
7
7
|
<a href="https://github.com/akodkod/solid-queue-dashboard#gh-dark-mode-only">
|
8
|
-
<img src="https://github.com/user-attachments/assets/
|
8
|
+
<img src="https://github.com/user-attachments/assets/645558cb-c20f-4d4b-9697-55282710ea6c" alt="Solid Queue Dashboard Dark Mode">
|
9
9
|
</a>
|
10
10
|
|
11
|
-
_đ I'm
|
11
|
+
_đ I'm Available for Hire â [kodkod.me](https://kodkod.me)_
|
12
12
|
</p>
|
13
13
|
|
14
14
|
## Features
|
@@ -19,12 +19,12 @@
|
|
19
19
|
- đ View execution history
|
20
20
|
- đ Filter options
|
21
21
|
- đ Retry jobs from the UI
|
22
|
-
-
|
22
|
+
- đĨŦ Auto-refresh
|
23
|
+
- đ Add charts
|
23
24
|
- đ No monkey patching
|
25
|
+
- đ TailwindCSS
|
24
26
|
|
25
27
|
## Roadmap
|
26
|
-
- đââī¸ Auto-pooling
|
27
|
-
- đ Add charts
|
28
28
|
- đ Manually trigger jobs
|
29
29
|
- âšī¸ Cancel long jobs (if possible)
|
30
30
|
- đ More statistics and insights
|
@@ -43,7 +43,7 @@ bundle add solid_queue_dashboard
|
|
43
43
|
Or add this line to your `Gemfile`:
|
44
44
|
|
45
45
|
```bash
|
46
|
-
gem "solid_queue_dashboard", "~> 0.
|
46
|
+
gem "solid_queue_dashboard", "~> 0.2.0"
|
47
47
|
```
|
48
48
|
|
49
49
|
Add this line to `routes.rb`:
|
@@ -85,10 +85,14 @@ To generate dummy data:
|
|
85
85
|
```
|
86
86
|
cd test_app
|
87
87
|
rails jobs:generate_dummy_data
|
88
|
-
./bin/jobs
|
89
88
|
```
|
90
89
|
|
91
90
|
## License
|
92
91
|
|
93
92
|
This gem is open source under the [MIT License](http://opensource.org/licenses/MIT).
|
94
93
|
|
94
|
+
---
|
95
|
+
|
96
|
+
_Made with love by Ukrainians đđ_
|
97
|
+
_[Help Ukraine](https://u24.gov.ua/)_
|
98
|
+
|
@@ -16,7 +16,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
16
16
|
});
|
17
17
|
|
18
18
|
// Format dates
|
19
|
-
document.querySelectorAll('[data-date]').forEach(function(element) {
|
19
|
+
document.querySelectorAll('[data-date]').forEach(function (element) {
|
20
20
|
const dateString = element.textContent.trim();
|
21
21
|
const date = new Date(dateString);
|
22
22
|
|
@@ -37,10 +37,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
37
37
|
});
|
38
38
|
|
39
39
|
// Handle auto-submit elements
|
40
|
-
document.querySelectorAll('[data-auto-submit]').forEach(function(element) {
|
41
|
-
element.addEventListener('change', function() {
|
40
|
+
document.querySelectorAll('[data-auto-submit]').forEach(function (element) {
|
41
|
+
element.addEventListener('change', function () {
|
42
42
|
const form = element.closest('form');
|
43
43
|
if (form) form.submit();
|
44
44
|
});
|
45
45
|
});
|
46
|
+
|
47
|
+
|
48
|
+
// Auto-refresh functionality for dashboard page
|
49
|
+
const searchParams = new URLSearchParams(window.location.search);
|
50
|
+
const refreshInterval = parseInt(searchParams.get('auto_refresh_period'));
|
51
|
+
|
52
|
+
if (refreshInterval) {
|
53
|
+
setInterval(refreshHomePage, refreshInterval * 1000);
|
54
|
+
}
|
55
|
+
|
56
|
+
function refreshHomePage() {
|
57
|
+
// TODO: Implement a smart refresh strategy using Fetch or Turbo
|
58
|
+
window.location.reload();
|
59
|
+
}
|
46
60
|
});
|
@@ -584,12 +584,12 @@ video {
|
|
584
584
|
|
585
585
|
.dark {
|
586
586
|
--background: 240 10% 5.4%;
|
587
|
-
--foreground: 0 0%
|
587
|
+
--foreground: 0 0% 92%;
|
588
588
|
--card: 240 10% 3.9%;
|
589
|
-
--card-foreground: 0 0%
|
589
|
+
--card-foreground: 0 0% 92%;
|
590
590
|
--popover: 240 10% 3.9%;
|
591
591
|
--popover-foreground: 0 0% 98%;
|
592
|
-
--primary: 0 0%
|
592
|
+
--primary: 0 0% 95%;
|
593
593
|
--primary-foreground: 240 5.9% 10%;
|
594
594
|
--secondary: 240 3.7% 15.9%;
|
595
595
|
--secondary-foreground: 0 0% 98%;
|
@@ -796,6 +796,16 @@ body {
|
|
796
796
|
background-color: hsl(var(--primary) / 0.8);
|
797
797
|
}
|
798
798
|
|
799
|
+
.badge-secondary {
|
800
|
+
border-color: transparent;
|
801
|
+
background-color: hsl(var(--secondary));
|
802
|
+
color: hsl(var(--secondary-foreground));
|
803
|
+
}
|
804
|
+
|
805
|
+
.badge-secondary:hover {
|
806
|
+
background-color: hsl(var(--secondary) / 0.8);
|
807
|
+
}
|
808
|
+
|
799
809
|
.badge-destructive {
|
800
810
|
border-color: transparent;
|
801
811
|
background-color: hsl(var(--destructive) / 0.15);
|
@@ -901,6 +911,18 @@ body {
|
|
901
911
|
color: rgb(96 165 250 / var(--tw-text-opacity));
|
902
912
|
}
|
903
913
|
|
914
|
+
.badge-indigo {
|
915
|
+
border-color: transparent;
|
916
|
+
background-color: rgb(99 102 241 / 0.15);
|
917
|
+
--tw-text-opacity: 1;
|
918
|
+
color: rgb(67 56 202 / var(--tw-text-opacity));
|
919
|
+
}
|
920
|
+
|
921
|
+
.badge-indigo:is(.dark *) {
|
922
|
+
--tw-text-opacity: 1;
|
923
|
+
color: rgb(129 140 248 / var(--tw-text-opacity));
|
924
|
+
}
|
925
|
+
|
904
926
|
.badge-purple {
|
905
927
|
border-color: transparent;
|
906
928
|
background-color: rgb(168 85 247 / 0.15);
|
@@ -915,7 +937,7 @@ body {
|
|
915
937
|
|
916
938
|
.badge-zinc {
|
917
939
|
border-color: transparent;
|
918
|
-
background-color: rgb(82 82 91 / 0.
|
940
|
+
background-color: rgb(82 82 91 / 0.075);
|
919
941
|
--tw-text-opacity: 1;
|
920
942
|
color: rgb(63 63 70 / var(--tw-text-opacity));
|
921
943
|
}
|
@@ -969,6 +991,11 @@ body {
|
|
969
991
|
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
|
970
992
|
}
|
971
993
|
|
994
|
+
.circle-indigo {
|
995
|
+
--tw-bg-opacity: 1;
|
996
|
+
background-color: rgb(99 102 241 / var(--tw-bg-opacity));
|
997
|
+
}
|
998
|
+
|
972
999
|
.circle-purple {
|
973
1000
|
--tw-bg-opacity: 1;
|
974
1001
|
background-color: rgb(168 85 247 / var(--tw-bg-opacity));
|
@@ -976,7 +1003,7 @@ body {
|
|
976
1003
|
|
977
1004
|
.circle-zinc {
|
978
1005
|
--tw-bg-opacity: 1;
|
979
|
-
background-color: rgb(
|
1006
|
+
background-color: rgb(161 161 170 / var(--tw-bg-opacity));
|
980
1007
|
}
|
981
1008
|
|
982
1009
|
/*
|
@@ -1504,6 +1531,22 @@ body {
|
|
1504
1531
|
position: static;
|
1505
1532
|
}
|
1506
1533
|
|
1534
|
+
.absolute {
|
1535
|
+
position: absolute;
|
1536
|
+
}
|
1537
|
+
|
1538
|
+
.relative {
|
1539
|
+
position: relative;
|
1540
|
+
}
|
1541
|
+
|
1542
|
+
.right-6 {
|
1543
|
+
right: 1.5rem;
|
1544
|
+
}
|
1545
|
+
|
1546
|
+
.top-6 {
|
1547
|
+
top: 1.5rem;
|
1548
|
+
}
|
1549
|
+
|
1507
1550
|
.mx-auto {
|
1508
1551
|
margin-left: auto;
|
1509
1552
|
margin-right: auto;
|
@@ -1529,6 +1572,10 @@ body {
|
|
1529
1572
|
margin-left: 0.125rem;
|
1530
1573
|
}
|
1531
1574
|
|
1575
|
+
.ml-1 {
|
1576
|
+
margin-left: 0.25rem;
|
1577
|
+
}
|
1578
|
+
|
1532
1579
|
.ml-1\.5 {
|
1533
1580
|
margin-left: 0.375rem;
|
1534
1581
|
}
|
@@ -1561,6 +1608,10 @@ body {
|
|
1561
1608
|
margin-right: 0.375rem;
|
1562
1609
|
}
|
1563
1610
|
|
1611
|
+
.mt-0\.5 {
|
1612
|
+
margin-top: 0.125rem;
|
1613
|
+
}
|
1614
|
+
|
1564
1615
|
.mt-1 {
|
1565
1616
|
margin-top: 0.25rem;
|
1566
1617
|
}
|
@@ -1581,10 +1632,6 @@ body {
|
|
1581
1632
|
margin-top: 2rem;
|
1582
1633
|
}
|
1583
1634
|
|
1584
|
-
.mt-0\.5 {
|
1585
|
-
margin-top: 0.125rem;
|
1586
|
-
}
|
1587
|
-
|
1588
1635
|
.inline-block {
|
1589
1636
|
display: inline-block;
|
1590
1637
|
}
|
@@ -1641,6 +1688,10 @@ body {
|
|
1641
1688
|
width: 6rem;
|
1642
1689
|
}
|
1643
1690
|
|
1691
|
+
.w-40 {
|
1692
|
+
width: 10rem;
|
1693
|
+
}
|
1694
|
+
|
1644
1695
|
.w-5 {
|
1645
1696
|
width: 1.25rem;
|
1646
1697
|
}
|
@@ -1667,8 +1718,8 @@ body {
|
|
1667
1718
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
1668
1719
|
}
|
1669
1720
|
|
1670
|
-
.grid-cols-
|
1671
|
-
grid-template-columns: repeat(
|
1721
|
+
.grid-cols-6 {
|
1722
|
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
1672
1723
|
}
|
1673
1724
|
|
1674
1725
|
.\!flex-row {
|
@@ -1683,6 +1734,10 @@ body {
|
|
1683
1734
|
flex-wrap: wrap;
|
1684
1735
|
}
|
1685
1736
|
|
1737
|
+
.items-start {
|
1738
|
+
align-items: flex-start;
|
1739
|
+
}
|
1740
|
+
|
1686
1741
|
.items-center {
|
1687
1742
|
align-items: center;
|
1688
1743
|
}
|
@@ -1703,6 +1758,10 @@ body {
|
|
1703
1758
|
gap: 0.25rem;
|
1704
1759
|
}
|
1705
1760
|
|
1761
|
+
.gap-1\.5 {
|
1762
|
+
gap: 0.375rem;
|
1763
|
+
}
|
1764
|
+
|
1706
1765
|
.gap-2 {
|
1707
1766
|
gap: 0.5rem;
|
1708
1767
|
}
|
@@ -1785,6 +1844,11 @@ body {
|
|
1785
1844
|
border-top-color: rgb(239 68 68 / var(--tw-border-opacity));
|
1786
1845
|
}
|
1787
1846
|
|
1847
|
+
.border-t-sky-500 {
|
1848
|
+
--tw-border-opacity: 1;
|
1849
|
+
border-top-color: rgb(14 165 233 / var(--tw-border-opacity));
|
1850
|
+
}
|
1851
|
+
|
1788
1852
|
.border-t-zinc-500 {
|
1789
1853
|
--tw-border-opacity: 1;
|
1790
1854
|
border-top-color: rgb(113 113 122 / var(--tw-border-opacity));
|
@@ -1798,6 +1862,14 @@ body {
|
|
1798
1862
|
padding: 0px !important;
|
1799
1863
|
}
|
1800
1864
|
|
1865
|
+
.p-0 {
|
1866
|
+
padding: 0px;
|
1867
|
+
}
|
1868
|
+
|
1869
|
+
.p-6 {
|
1870
|
+
padding: 1.5rem;
|
1871
|
+
}
|
1872
|
+
|
1801
1873
|
.px-2 {
|
1802
1874
|
padding-left: 0.5rem;
|
1803
1875
|
padding-right: 0.5rem;
|
@@ -1822,6 +1894,10 @@ body {
|
|
1822
1894
|
padding-bottom: 15vh;
|
1823
1895
|
}
|
1824
1896
|
|
1897
|
+
.pl-1 {
|
1898
|
+
padding-left: 0.25rem;
|
1899
|
+
}
|
1900
|
+
|
1825
1901
|
.pl-6 {
|
1826
1902
|
padding-left: 1.5rem;
|
1827
1903
|
}
|
@@ -1867,19 +1943,6 @@ body {
|
|
1867
1943
|
line-height: 1rem;
|
1868
1944
|
}
|
1869
1945
|
|
1870
|
-
.text-base {
|
1871
|
-
font-size: 1rem;
|
1872
|
-
line-height: 1.5rem;
|
1873
|
-
}
|
1874
|
-
|
1875
|
-
.text-\[15px\] {
|
1876
|
-
font-size: 15px;
|
1877
|
-
}
|
1878
|
-
|
1879
|
-
.text-\[14px\] {
|
1880
|
-
font-size: 14px;
|
1881
|
-
}
|
1882
|
-
|
1883
1946
|
.font-bold {
|
1884
1947
|
font-weight: 700;
|
1885
1948
|
}
|
@@ -1964,6 +2027,10 @@ body {
|
|
1964
2027
|
}
|
1965
2028
|
}
|
1966
2029
|
|
2030
|
+
.running {
|
2031
|
+
animation-play-state: running;
|
2032
|
+
}
|
2033
|
+
|
1967
2034
|
.marker\:text-sm *::marker {
|
1968
2035
|
font-size: 0.875rem;
|
1969
2036
|
line-height: 1.25rem;
|
@@ -2009,6 +2076,11 @@ body {
|
|
2009
2076
|
border-top-color: rgb(147 51 234 / var(--tw-border-opacity));
|
2010
2077
|
}
|
2011
2078
|
|
2079
|
+
.dark\:border-t-sky-600:is(.dark *) {
|
2080
|
+
--tw-border-opacity: 1;
|
2081
|
+
border-top-color: rgb(2 132 199 / var(--tw-border-opacity));
|
2082
|
+
}
|
2083
|
+
|
2012
2084
|
.dark\:border-t-zinc-600:is(.dark *) {
|
2013
2085
|
--tw-border-opacity: 1;
|
2014
2086
|
border-top-color: rgb(82 82 91 / var(--tw-border-opacity));
|
@@ -33,12 +33,12 @@
|
|
33
33
|
|
34
34
|
.dark {
|
35
35
|
--background: 240 10% 5.4%;
|
36
|
-
--foreground: 0 0%
|
36
|
+
--foreground: 0 0% 92%;
|
37
37
|
--card: 240 10% 3.9%;
|
38
|
-
--card-foreground: 0 0%
|
38
|
+
--card-foreground: 0 0% 92%;
|
39
39
|
--popover: 240 10% 3.9%;
|
40
40
|
--popover-foreground: 0 0% 98%;
|
41
|
-
--primary: 0 0%
|
41
|
+
--primary: 0 0% 95%;
|
42
42
|
--primary-foreground: 240 5.9% 10%;
|
43
43
|
--secondary: 240 3.7% 15.9%;
|
44
44
|
--secondary-foreground: 0 0% 98%;
|
@@ -188,7 +188,7 @@
|
|
188
188
|
}
|
189
189
|
|
190
190
|
.badge-zinc {
|
191
|
-
@apply border-transparent bg-zinc-600/
|
191
|
+
@apply border-transparent bg-zinc-600/7.5 text-zinc-700 dark:bg-white/5 dark:text-zinc-400;
|
192
192
|
}
|
193
193
|
|
194
194
|
/*
|
@@ -284,7 +284,7 @@
|
|
284
284
|
}
|
285
285
|
|
286
286
|
.circle-zinc {
|
287
|
-
@apply bg-zinc-
|
287
|
+
@apply bg-zinc-400;
|
288
288
|
}
|
289
289
|
|
290
290
|
/*
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module SolidQueueDashboard
|
2
2
|
class AppearanceController < ApplicationController
|
3
3
|
def toggle
|
4
|
-
cookies[:dark_mode] = cookies[:dark_mode] == "
|
4
|
+
cookies[:dark_mode] = cookies[:dark_mode] == "true" ? "false" : "true"
|
5
5
|
redirect_to request.referer
|
6
6
|
end
|
7
7
|
end
|
@@ -2,7 +2,67 @@ module SolidQueueDashboard
|
|
2
2
|
class DashboardController < ApplicationController
|
3
3
|
def index
|
4
4
|
@jobs = SolidQueueDashboard.decorate(SolidQueue::Job.all)
|
5
|
-
|
5
|
+
load_charts
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def load_charts
|
11
|
+
case params[:chart_period] || "30m"
|
12
|
+
when "15m"
|
13
|
+
n = 1
|
14
|
+
last = 15
|
15
|
+
when "30m"
|
16
|
+
n = 1
|
17
|
+
last = 30
|
18
|
+
when "1h"
|
19
|
+
n = 2
|
20
|
+
last = 30
|
21
|
+
when "3h"
|
22
|
+
n = 6
|
23
|
+
last = 30
|
24
|
+
when "6h"
|
25
|
+
n = 12
|
26
|
+
last = 30
|
27
|
+
when "12h"
|
28
|
+
n = 20
|
29
|
+
last = 36
|
30
|
+
when "1d"
|
31
|
+
n = 30
|
32
|
+
last = 48
|
33
|
+
when "3d"
|
34
|
+
n = 90 # 1.5 hours
|
35
|
+
last = 48
|
36
|
+
when "1w"
|
37
|
+
n = 180 # 3 hours
|
38
|
+
last = 56
|
39
|
+
else
|
40
|
+
n = 1
|
41
|
+
last = 30
|
42
|
+
end
|
43
|
+
|
44
|
+
@charts = [
|
45
|
+
{
|
46
|
+
name: "Enqueued",
|
47
|
+
data: SolidQueue::Job.group_by_minute(:created_at, last: last, n: n).count,
|
48
|
+
color: "#A1A1AB"
|
49
|
+
},
|
50
|
+
{
|
51
|
+
name: "Finished",
|
52
|
+
data: @jobs.finished.group_by_minute(:finished_at, last: last, n: n).count,
|
53
|
+
color: "#23C55E"
|
54
|
+
},
|
55
|
+
{
|
56
|
+
name: "Retried",
|
57
|
+
data: @jobs.retried.group_by_minute(:finished_at, last: last, n: n).count,
|
58
|
+
color: "#FBBF26"
|
59
|
+
},
|
60
|
+
{
|
61
|
+
name: "Failed",
|
62
|
+
data: SolidQueue::FailedExecution.group_by_minute(:created_at, last: last, n: n).count,
|
63
|
+
color: "#F04444"
|
64
|
+
}
|
65
|
+
]
|
6
66
|
end
|
7
67
|
end
|
8
68
|
end
|
@@ -32,7 +32,7 @@ module SolidQueueDashboard
|
|
32
32
|
jobs = jobs.where(queue_name: params[:queue_name]) if params[:queue_name].present?
|
33
33
|
|
34
34
|
@pagination = paginate(jobs, page: params[:page].to_i, per_page: params[:per_page].to_i)
|
35
|
-
@jobs = SolidQueueDashboard.decorate(@pagination
|
35
|
+
@jobs = SolidQueueDashboard.decorate(@pagination[:records])
|
36
36
|
end
|
37
37
|
|
38
38
|
def set_job
|
@@ -227,5 +227,28 @@ module SolidQueueDashboard
|
|
227
227
|
concat tag.polygon(points: "6 3 20 12 6 21 6 3")
|
228
228
|
end
|
229
229
|
end
|
230
|
+
|
231
|
+
def icon_chart_scatter(options = {})
|
232
|
+
svg_options = {
|
233
|
+
xmlns: "http://www.w3.org/2000/svg",
|
234
|
+
width: "24",
|
235
|
+
height: "24",
|
236
|
+
viewBox: "0 0 24 24",
|
237
|
+
fill: "none",
|
238
|
+
stroke: "currentColor",
|
239
|
+
"stroke-width": "2",
|
240
|
+
"stroke-linecap": "round",
|
241
|
+
"stroke-linejoin": "round"
|
242
|
+
}.merge(options)
|
243
|
+
|
244
|
+
tag.svg(**svg_options) do
|
245
|
+
concat tag.circle(cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor")
|
246
|
+
concat tag.circle(cx: "18.5", cy: "5.5", r: ".5", fill: "currentColor")
|
247
|
+
concat tag.circle(cx: "11.5", cy: "11.5", r: ".5", fill: "currentColor")
|
248
|
+
concat tag.circle(cx: "7.5", cy: "16.5", r: ".5", fill: "currentColor")
|
249
|
+
concat tag.circle(cx: "17.5", cy: "14.5", r: ".5", fill: "currentColor")
|
250
|
+
concat tag.path(d: "M3 3v16a2 2 0 0 0 2 2h16")
|
251
|
+
end
|
252
|
+
end
|
230
253
|
end
|
231
254
|
end
|
@@ -11,7 +11,10 @@ module SolidQueueDashboard
|
|
11
11
|
"amber": "circle-amber",
|
12
12
|
"red": "circle-red",
|
13
13
|
"blue": "circle-blue",
|
14
|
-
"
|
14
|
+
"sky": "circle-sky",
|
15
|
+
"zinc": "circle-zinc",
|
16
|
+
"indigo": "circle-indigo",
|
17
|
+
"purple": "circle-purple"
|
15
18
|
}[Job::STATUS_COLORS[status]&.to_sym || :zinc]
|
16
19
|
end
|
17
20
|
|
@@ -26,7 +29,10 @@ module SolidQueueDashboard
|
|
26
29
|
"amber": "badge-amber",
|
27
30
|
"red": "badge-red",
|
28
31
|
"blue": "badge-blue",
|
29
|
-
"
|
32
|
+
"sky": "badge-sky",
|
33
|
+
"zinc": "badge-zinc",
|
34
|
+
"indigo": "badge-indigo",
|
35
|
+
"purple": "badge-purple"
|
30
36
|
}[Job::STATUS_COLORS[status]&.to_sym || :zinc]
|
31
37
|
end
|
32
38
|
|
@@ -10,13 +10,13 @@ module SolidQueueDashboard
|
|
10
10
|
total_count = scope.count
|
11
11
|
total_pages = (total_count.to_f / per_page).ceil
|
12
12
|
|
13
|
-
|
13
|
+
{
|
14
14
|
records: records,
|
15
15
|
current_page: page,
|
16
16
|
per_page: per_page,
|
17
17
|
total_pages: total_pages,
|
18
18
|
total_count: total_count
|
19
|
-
|
19
|
+
}
|
20
20
|
end
|
21
21
|
|
22
22
|
def page_range(current_page, total_pages, window: 2)
|
@@ -10,7 +10,8 @@ module SolidQueueDashboard
|
|
10
10
|
"blue": "circle-blue",
|
11
11
|
"green": "circle-green",
|
12
12
|
"yellow": "circle-yellow",
|
13
|
-
"purple": "circle-purple"
|
13
|
+
"purple": "circle-purple",
|
14
|
+
"sky": "circle-sky",
|
14
15
|
}[Process::KIND_COLORS[kind]&.to_sym || :zinc]
|
15
16
|
end
|
16
17
|
|
@@ -24,7 +25,8 @@ module SolidQueueDashboard
|
|
24
25
|
"blue": "badge-blue",
|
25
26
|
"green": "badge-green",
|
26
27
|
"yellow": "badge-yellow",
|
27
|
-
"purple": "badge-purple"
|
28
|
+
"purple": "badge-purple",
|
29
|
+
"sky": "badge-sky",
|
28
30
|
}[Process::KIND_COLORS[kind]&.to_sym || :zinc]
|
29
31
|
end
|
30
32
|
|
@@ -8,14 +8,16 @@
|
|
8
8
|
|
9
9
|
<%= stylesheet_link_tag "solid_queue_dashboard/application", media: "all" %>
|
10
10
|
<%= javascript_include_tag "solid_queue_dashboard/application" %>
|
11
|
+
<%= javascript_include_tag "chartkick" %>
|
12
|
+
<%= javascript_include_tag "Chart.bundle" %>
|
11
13
|
<%= javascript_include_tag "solid_queue_dashboard/alpine", defer: true %>
|
12
14
|
</head>
|
13
15
|
<body class="pb-[15vh] sm:pb-[25vh]">
|
14
16
|
<div class="max-w-[1920px] mx-auto px-4 sm:px-6 lg:px-8">
|
15
|
-
<%= render
|
16
|
-
<%= render
|
17
|
+
<%= render "navbar" %>
|
18
|
+
<%= render "flash_messages" %>
|
17
19
|
<%= yield %>
|
18
|
-
<%= render
|
20
|
+
<%= render "footer" %>
|
19
21
|
</div>
|
20
22
|
</body>
|
21
23
|
</html>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<nav class="navbar mb-6">
|
2
|
-
<%= link_to root_path, class: "inline-flex items-center gap-0.5 text-xl font-bold tracking-tight translate-y-px" do %>
|
2
|
+
<%= link_to root_path, class: "inline-flex items-center gap-0.5 text-xl font-bold tracking-tight translate-y-px pl-1" do %>
|
3
3
|
<span class="circle circle-blue"></span>
|
4
4
|
<span class="circle circle-green"></span>
|
5
5
|
<span class="circle circle-yellow"></span>
|
@@ -21,18 +21,41 @@
|
|
21
21
|
<%= link_to processes_path, class: "navbar-item #{current_page?(processes_path) ? 'navbar-item-current' : 'navbar-item-default'}" do %>
|
22
22
|
<%= icon_server class: "size-4 mr-1.5" %>
|
23
23
|
Processes
|
24
|
+
|
25
|
+
<span class="badge <%= current_page?(processes_path) ? 'badge-secondary' : 'badge-primary' %> ml-1 text-xs">
|
26
|
+
<%= SolidQueue::Process.count %>
|
27
|
+
</span>
|
24
28
|
<% end %>
|
25
29
|
|
26
30
|
<%= link_to recurring_tasks_path, class: "navbar-item #{current_page?(recurring_tasks_path) ? 'navbar-item-current' : 'navbar-item-default'}" do %>
|
27
31
|
<%= icon_clock class: "size-4 mr-1.5" %>
|
28
32
|
Recurring Tasks
|
33
|
+
|
34
|
+
<span class="badge <%= current_page?(recurring_tasks_path) ? 'badge-secondary' : 'badge-primary' %> ml-1 text-xs">
|
35
|
+
<%= SolidQueue::RecurringTask.count %>
|
36
|
+
</span>
|
37
|
+
<% end %>
|
38
|
+
|
39
|
+
<%= link_to stats_path, class: "navbar-item #{current_page?(stats_path) ? 'navbar-item-current' : 'navbar-item-default'}" do %>
|
40
|
+
<%= icon_chart_scatter class: "size-4 mr-1.5" %>
|
41
|
+
Stats
|
29
42
|
<% end %>
|
30
43
|
</div>
|
31
44
|
|
32
|
-
<div class="ml-auto">
|
33
|
-
|
34
|
-
|
35
|
-
|
45
|
+
<div class="ml-auto flex gap-4">
|
46
|
+
<% if current_page?(controller: 'dashboard', action: 'index') %>
|
47
|
+
<%= form_with url: root_path, method: :get, class: "flex gap-1.5 items-center" do |form| %>
|
48
|
+
<%= form.hidden_field :chart_period %>
|
49
|
+
|
50
|
+
<%= icon_refresh_cw class: "size-4 text-muted-foreground" %>
|
51
|
+
<%= form.select :auto_refresh_period,
|
52
|
+
[["Auto-Refresh Off", "off"], ["15 seconds", "15"], ["30 seconds", "30"], ["1 minute", "60"], ["3 minutes", "180"], ["5 minutes", "300"], ["10 minutes", "600"], ["15 minutes", "900"], ["30 minutes", "1800"], ["1 hour", "3600"]],
|
53
|
+
{ selected: params[:auto_refresh_period].presence || "off" },
|
54
|
+
class: "select w-40",
|
55
|
+
data: { auto_submit: true }
|
56
|
+
%>
|
57
|
+
<% end %>
|
58
|
+
<% end %>
|
36
59
|
|
37
60
|
<%= form_with url: toggle_appearance_path, method: :post do |f| %>
|
38
61
|
<button
|
@@ -1,4 +1,18 @@
|
|
1
|
-
<div class="
|
1
|
+
<div class="card card-content p-6 relative">
|
2
|
+
<%= form_with url: root_path, method: :get, class: "absolute top-6 right-6" do |form| %>
|
3
|
+
<%= form.hidden_field :auto_refresh_period %>
|
4
|
+
<%= form.select :chart_period,
|
5
|
+
[["15 minutes", "15m"], ["30 minutes", "30m"], ["1 hour", "1h"], ["3 hours", "3h"], ["6 hours", "6h"], ["12 hours", "12h"], ["1 day", "1d"], ["3 days", "3d"], ["1 week", "1w"]],
|
6
|
+
{ selected: params[:chart_period].presence || "30m" },
|
7
|
+
class: "select",
|
8
|
+
data: { auto_submit: true }
|
9
|
+
%>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<%= line_chart @charts, points: false, thousands: "," %>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<div class="grid grid-cols-6 gap-4 mt-4">
|
2
16
|
<% SolidQueueDashboard::Job::STATUSES.each do |status| %>
|
3
17
|
<div class="card" data-href="<%= jobs_path(status:) %>">
|
4
18
|
<div class="card-content pt-5">
|
@@ -7,35 +21,9 @@
|
|
7
21
|
<span class="ml-0.5 -translate-y-px circle <%= job_status_circle_class(status) %>"></span>
|
8
22
|
</h4>
|
9
23
|
<p class="text-4xl font-bold mt-1 text-black dark:text-white">
|
10
|
-
<%= @jobs.with_status(status).count %>
|
24
|
+
<%= number_with_delimiter(@jobs.with_status(status).count) %>
|
11
25
|
</p>
|
12
26
|
</div>
|
13
27
|
</div>
|
14
28
|
<% end %>
|
15
29
|
</div>
|
16
|
-
|
17
|
-
<div class="card mt-4">
|
18
|
-
<div class="card-header border-b">
|
19
|
-
<h3 class="card-title">Failure Rate</h3>
|
20
|
-
</div>
|
21
|
-
<div class="card-content !p-0">
|
22
|
-
<div class="table-wrapper">
|
23
|
-
<table class="table">
|
24
|
-
<thead class="table-header">
|
25
|
-
<tr class="table-row">
|
26
|
-
<th class="table-head">Job</th>
|
27
|
-
<th class="table-head">Failure Rate</th>
|
28
|
-
</tr>
|
29
|
-
</thead>
|
30
|
-
<tbody class="table-body">
|
31
|
-
<% @job_class_names.each do |class_name| %>
|
32
|
-
<tr class="table-row" data-href="<%= jobs_path(class_name:, status: :failed) %>">
|
33
|
-
<td class="table-cell font-medium"><%= class_name.titleize %></td>
|
34
|
-
<td class="table-cell font-medium"><%= format_failure_rate(SolidQueueDashboard.decorate(SolidQueue::Job.where(class_name:)).failure_rate) %></td>
|
35
|
-
</tr>
|
36
|
-
<% end %>
|
37
|
-
</tbody>
|
38
|
-
</table>
|
39
|
-
</div>
|
40
|
-
</div>
|
41
|
-
</div>
|
@@ -13,7 +13,7 @@
|
|
13
13
|
</tr>
|
14
14
|
</thead>
|
15
15
|
<tbody class="table-body">
|
16
|
-
<%= render partial:
|
16
|
+
<%= render partial: "solid_queue_dashboard/jobs/table_row", collection: jobs, as: :job, locals: { highlight_ids: highlight_ids } %>
|
17
17
|
</tbody>
|
18
18
|
</table>
|
19
19
|
</div>
|
@@ -14,6 +14,12 @@
|
|
14
14
|
|
15
15
|
<td class="table-cell">
|
16
16
|
<%= job_status_badge(job.status) %>
|
17
|
+
<% if job.running? %>
|
18
|
+
<br />
|
19
|
+
<span class="text-xs text-muted-foreground">
|
20
|
+
<%= time_ago_in_words(job.claimed_execution.created_at, include_seconds: true) %>
|
21
|
+
</span>
|
22
|
+
<% end %>
|
17
23
|
</td>
|
18
24
|
|
19
25
|
<td class="table-cell">
|
@@ -29,9 +35,9 @@
|
|
29
35
|
<p class="font-medium"><%= job.class_name %></p>
|
30
36
|
|
31
37
|
<% if job.arguments["arguments"].present? %>
|
32
|
-
<p class="mt-0.5">
|
38
|
+
<p class="inline-flex flex-wrap gap-0.5 items-start mt-0.5">
|
33
39
|
<% job.arguments["arguments"].each do |argument| %>
|
34
|
-
<span class="badge badge-zinc"><%= truncate(JSON.pretty_generate(argument), length:
|
40
|
+
<span class="badge badge-zinc text-xs"><%= truncate(JSON.pretty_generate(argument), length: 60) %></span>
|
35
41
|
<% end %>
|
36
42
|
</p>
|
37
43
|
<% end %>
|
@@ -51,7 +57,17 @@
|
|
51
57
|
</td>
|
52
58
|
|
53
59
|
<td class="table-cell">
|
54
|
-
<% if job.
|
60
|
+
<% if job.running? %>
|
61
|
+
<p class="text-sm text-muted-foreground">
|
62
|
+
Running for <strong class="font-medium text-foreground"><%= time_ago_in_words(job.claimed_execution.created_at, include_seconds: true) %></strong>
|
63
|
+
<% if job.claimed_execution.process %>
|
64
|
+
by <strong class="font-medium text-foreground">
|
65
|
+
<%= process_kind_badge(job.claimed_execution.process.kind) %>
|
66
|
+
<%= link_to "##{job.claimed_execution.process.id}", process_path(job.claimed_execution.process), class: "link" %>
|
67
|
+
</strong>
|
68
|
+
<% end %>
|
69
|
+
</p>
|
70
|
+
<% elsif job.success? || job.retried? %>
|
55
71
|
<p class="text-sm text-muted-foreground">
|
56
72
|
<%= job.retried? ? "Failed" : "Finished" %> at <strong class="font-medium text-foreground" data-date="<%= job.finished_at.to_fs(:database) %>"><%= job.finished_at %></strong><br />
|
57
73
|
<span class="text-xs"><%= time_ago_in_words(job.scheduled_at, include_seconds: true) %> ago</span>
|
@@ -25,12 +25,12 @@
|
|
25
25
|
<% end %>
|
26
26
|
</div>
|
27
27
|
|
28
|
-
<% if @pagination
|
28
|
+
<% if @pagination[:total_pages] > 1 %>
|
29
29
|
<div class="card-footer pt-6 border-t flex justify-between">
|
30
30
|
<%= render partial: 'solid_queue_dashboard/application/pagination', locals: {
|
31
|
-
current_page: @pagination
|
32
|
-
total_pages: @pagination
|
33
|
-
per_page: @pagination
|
31
|
+
current_page: @pagination[:current_page],
|
32
|
+
total_pages: @pagination[:total_pages],
|
33
|
+
per_page: @pagination[:per_page]
|
34
34
|
} %>
|
35
35
|
|
36
36
|
<div class="flex items-center gap-2">
|
@@ -42,7 +42,7 @@
|
|
42
42
|
<%= f.hidden_field :page, value: 1 %>
|
43
43
|
<%= f.select :per_page,
|
44
44
|
[10, 25, 50, 100],
|
45
|
-
{ selected: @pagination
|
45
|
+
{ selected: @pagination[:per_page] },
|
46
46
|
class: "select w-24",
|
47
47
|
data: { auto_submit: true }
|
48
48
|
%>
|
@@ -126,9 +126,11 @@
|
|
126
126
|
<% if @job.class_name === SolidQueueDashboard::Job::COMMAND_CLASS_NAME %>
|
127
127
|
<pre x-show="!showAll"><%= @job.arguments["arguments"][0] %></pre>
|
128
128
|
<% else %>
|
129
|
-
|
130
|
-
|
131
|
-
|
129
|
+
<div x-show="!showAll">
|
130
|
+
<% @job.arguments["arguments"].each do |argument| %>
|
131
|
+
<span class="badge badge-zinc text-sm"><%= JSON.pretty_generate(argument) %></span>
|
132
|
+
<% end %>
|
133
|
+
</div>
|
132
134
|
<% end %>
|
133
135
|
|
134
136
|
<pre x-show="showAll"><%= JSON.pretty_generate(@job.arguments) %></pre>
|
@@ -5,6 +5,12 @@
|
|
5
5
|
|
6
6
|
<td class="table-cell">
|
7
7
|
<%= process_kind_badge(process.kind) %>
|
8
|
+
|
9
|
+
<% if process.claimed_executions.any? %>
|
10
|
+
<span class="ml-0.5 text-xs text-muted-foreground">
|
11
|
+
Running <%= pluralize(process.claimed_executions.count, "job") %>
|
12
|
+
</span>
|
13
|
+
<% end %>
|
8
14
|
</td>
|
9
15
|
|
10
16
|
<td class="table-cell"><%= process.hostname %></td>
|
@@ -66,9 +66,22 @@
|
|
66
66
|
</div>
|
67
67
|
</div>
|
68
68
|
|
69
|
+
<% if @process.claimed_executions.any? %>
|
70
|
+
<div class="card mt-8 overflow-hidden">
|
71
|
+
<div class="card-header border-b border-t-4 border-t-sky-500 dark:border-t-sky-600">
|
72
|
+
<h2 class="card-title">Running Jobs</h2>
|
73
|
+
<p class="card-description">Jobs that this process is currently running</p>
|
74
|
+
</div>
|
75
|
+
|
76
|
+
<div class="card-content p-0">
|
77
|
+
<%= render partial: 'solid_queue_dashboard/jobs/table', locals: { jobs: @process.claimed_executions.map { |execution| SolidQueueDashboard::Decorators::JobDecorator.new(execution.job) } } %>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
<% end %>
|
81
|
+
|
69
82
|
<% if @process.metadata.present? %>
|
70
83
|
<div class="card mt-8 overflow-hidden">
|
71
|
-
<div class="card-header border-b border-t-4 border-t-
|
84
|
+
<div class="card-header border-b border-t-4 border-t-amber-500 dark:border-t-amber-600">
|
72
85
|
<h2 class="card-title">Metadata</h2>
|
73
86
|
<p class="card-description">Additional information about this process</p>
|
74
87
|
</div>
|
@@ -122,7 +122,12 @@
|
|
122
122
|
<div class="card-content pt-5">
|
123
123
|
<ul class="list-decimal marker:text-sm marker:text-muted-foreground ml-4 space-y-1">
|
124
124
|
<% @recurring_task.next_runs.each do |run_time| %>
|
125
|
-
<li
|
125
|
+
<li>
|
126
|
+
<span data-date><%= run_time.strftime("%Y-%m-%d %H:%M:%S %Z") %></span>
|
127
|
+
<span class="text-muted-foreground">
|
128
|
+
in <%= distance_of_time_in_words(Time.current, run_time) %>
|
129
|
+
</span>
|
130
|
+
</li>
|
126
131
|
<% end %>
|
127
132
|
</ul>
|
128
133
|
</div>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<div class="card mt-4">
|
2
|
+
<div class="card-header border-b">
|
3
|
+
<h3 class="card-title">Failure Rate</h3>
|
4
|
+
</div>
|
5
|
+
<div class="card-content !p-0">
|
6
|
+
<div class="table-wrapper">
|
7
|
+
<table class="table">
|
8
|
+
<thead class="table-header">
|
9
|
+
<tr class="table-row">
|
10
|
+
<th class="table-head">Job</th>
|
11
|
+
<th class="table-head">Failure Rate</th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
<tbody class="table-body">
|
15
|
+
<% @job_class_names.each do |class_name| %>
|
16
|
+
<tr class="table-row" data-href="<%= jobs_path(class_name:, status: :failed) %>">
|
17
|
+
<td class="table-cell font-medium"><%= class_name.titleize %></td>
|
18
|
+
<td class="table-cell font-medium"><%= format_failure_rate(SolidQueueDashboard.decorate(SolidQueue::Job.where(class_name:)).failure_rate) %></td>
|
19
|
+
</tr>
|
20
|
+
<% end %>
|
21
|
+
</tbody>
|
22
|
+
</table>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
</div>
|
data/config/routes.rb
CHANGED
@@ -8,7 +8,9 @@ module SolidQueueDashboard
|
|
8
8
|
def status
|
9
9
|
return @status if defined?(@status)
|
10
10
|
|
11
|
-
@status = if
|
11
|
+
@status = if running?
|
12
|
+
Job::RUNNING
|
13
|
+
elsif retried?
|
12
14
|
Job::RETRIED
|
13
15
|
elsif failed?
|
14
16
|
Job::FAILED
|
@@ -21,6 +23,11 @@ module SolidQueueDashboard
|
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
26
|
+
def running?
|
27
|
+
return @running if defined?(@running)
|
28
|
+
@running = claimed_execution.present?
|
29
|
+
end
|
30
|
+
|
24
31
|
def success?
|
25
32
|
return @success if defined?(@success)
|
26
33
|
@success = finished_at.present? && !failed? && !retried?
|
@@ -44,7 +51,7 @@ module SolidQueueDashboard
|
|
44
51
|
|
45
52
|
def pending?
|
46
53
|
return @pending if defined?(@pending)
|
47
|
-
@pending = !finished_at.present?
|
54
|
+
@pending = !finished_at.present? && !running?
|
48
55
|
end
|
49
56
|
|
50
57
|
def execution_history
|
@@ -3,6 +3,8 @@ module SolidQueueDashboard
|
|
3
3
|
class JobsDecorator < SimpleDelegator
|
4
4
|
def with_status(status)
|
5
5
|
case status.to_sym
|
6
|
+
when Job::RUNNING
|
7
|
+
running
|
6
8
|
when Job::SUCCESS
|
7
9
|
success
|
8
10
|
when Job::FAILED
|
@@ -18,6 +20,10 @@ module SolidQueueDashboard
|
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
23
|
+
def running
|
24
|
+
where.associated(:claimed_execution)
|
25
|
+
end
|
26
|
+
|
21
27
|
def success
|
22
28
|
where.not(finished_at: nil)
|
23
29
|
.where.not(id: failed)
|
@@ -31,6 +37,7 @@ module SolidQueueDashboard
|
|
31
37
|
def pending
|
32
38
|
where(finished_at: nil, scheduled_at: ..Time.current)
|
33
39
|
.where.not(id: failed)
|
40
|
+
.where.not(id: running)
|
34
41
|
end
|
35
42
|
|
36
43
|
def retried
|
@@ -1,20 +1,22 @@
|
|
1
1
|
module SolidQueueDashboard
|
2
2
|
module Job
|
3
3
|
# Constants
|
4
|
+
RUNNING = :running
|
4
5
|
SUCCESS = :success
|
5
6
|
RETRIED = :retried
|
6
7
|
FAILED = :failed
|
7
8
|
PENDING = :pending
|
8
9
|
SCHEDULED = :scheduled
|
9
10
|
|
10
|
-
STATUSES = [ SUCCESS, RETRIED, FAILED, SCHEDULED, PENDING ]
|
11
|
+
STATUSES = [ RUNNING, SUCCESS, RETRIED, FAILED, SCHEDULED, PENDING ]
|
11
12
|
|
12
13
|
STATUS_COLORS = {
|
13
14
|
SUCCESS => "green",
|
14
15
|
RETRIED => "amber",
|
15
16
|
FAILED => "red",
|
16
|
-
SCHEDULED => "
|
17
|
-
PENDING => "zinc"
|
17
|
+
SCHEDULED => "purple",
|
18
|
+
PENDING => "zinc",
|
19
|
+
RUNNING => "sky"
|
18
20
|
}
|
19
21
|
|
20
22
|
COMMAND_CLASS_NAME = "SolidQueue::RecurringJob"
|
@@ -9,13 +9,13 @@ module SolidQueueDashboard
|
|
9
9
|
KINDS = [ SUPERVISOR, DISPATCHER, WORKER, SCHEDULER ]
|
10
10
|
|
11
11
|
KIND_COLORS = {
|
12
|
-
SUPERVISOR => "
|
12
|
+
SUPERVISOR => "yellow",
|
13
13
|
DISPATCHER => "green",
|
14
|
-
WORKER => "
|
14
|
+
WORKER => "sky",
|
15
15
|
SCHEDULER => "purple"
|
16
16
|
}
|
17
17
|
|
18
|
-
HEARTBEAT_DEAD_THRESHOLD =
|
18
|
+
HEARTBEAT_DEAD_THRESHOLD = 3.minutes
|
19
19
|
|
20
20
|
def self.kind_color(kind)
|
21
21
|
KIND_COLORS[kind] || "zinc"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_queue_dashboard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kodkod
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: solid_queue
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: groupdate
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '6.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: chartkick
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
27
55
|
description: Dashboard for Solid Queue
|
28
56
|
email:
|
29
57
|
- andrew@kodkod.me
|
@@ -48,6 +76,7 @@ files:
|
|
48
76
|
- app/controllers/solid_queue_dashboard/jobs_controller.rb
|
49
77
|
- app/controllers/solid_queue_dashboard/processes_controller.rb
|
50
78
|
- app/controllers/solid_queue_dashboard/recurring_tasks_controller.rb
|
79
|
+
- app/controllers/solid_queue_dashboard/stats_controller.rb
|
51
80
|
- app/helpers/solid_queue_dashboard/appearance_helper.rb
|
52
81
|
- app/helpers/solid_queue_dashboard/application_helper.rb
|
53
82
|
- app/helpers/solid_queue_dashboard/icons_helper.rb
|
@@ -76,6 +105,7 @@ files:
|
|
76
105
|
- app/views/solid_queue_dashboard/recurring_tasks/_table_row.html.erb
|
77
106
|
- app/views/solid_queue_dashboard/recurring_tasks/index.html.erb
|
78
107
|
- app/views/solid_queue_dashboard/recurring_tasks/show.html.erb
|
108
|
+
- app/views/solid_queue_dashboard/stats/index.html.erb
|
79
109
|
- bun.lockb
|
80
110
|
- config/routes.rb
|
81
111
|
- lib/solid_queue_dashboard.rb
|