sidekiq_queue_manager 1.0.1 → 1.1.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.
@@ -236,10 +236,48 @@
236
236
 
237
237
  .sqm-stats-grid {
238
238
  display: grid;
239
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
239
+ grid-template-columns: repeat(7, 1fr);
240
240
  gap: 1rem;
241
241
  }
242
242
 
243
+ @media (max-width: 1200px) {
244
+ .sqm-stats-grid {
245
+ grid-template-columns: repeat(4, 1fr);
246
+ }
247
+ }
248
+
249
+ @media (max-width: 900px) {
250
+ .sqm-stats-grid {
251
+ grid-template-columns: repeat(2, 1fr);
252
+ }
253
+ }
254
+
255
+ @media (max-width: 600px) {
256
+ .sqm-stats-grid {
257
+ grid-template-columns: 1fr;
258
+ }
259
+
260
+ .sqm-stat-card {
261
+ padding: 0.75rem;
262
+ text-align: center;
263
+ }
264
+
265
+ .sqm-stat-value {
266
+ font-size: 1.5rem !important;
267
+ }
268
+
269
+ .sqm-stat-value[data-length="8"],
270
+ .sqm-stat-value[data-length="9"],
271
+ .sqm-stat-value[data-length="10"],
272
+ .sqm-stat-value[data-length="11"],
273
+ .sqm-stat-value[data-length="12"],
274
+ .sqm-stat-value[data-length="13"],
275
+ .sqm-stat-value[data-length="14"],
276
+ .sqm-stat-value[data-length="long"] {
277
+ font-size: 1.25rem !important;
278
+ }
279
+ }
280
+
243
281
  .sqm-stat-card {
244
282
  background: var(--sqm-card);
245
283
  border: 1px solid var(--sqm-border);
@@ -248,6 +286,7 @@
248
286
  transition: var(--sqm-transition);
249
287
  position: relative;
250
288
  overflow: hidden;
289
+ min-width: 0; /* Allow flex shrinking */
251
290
  }
252
291
 
253
292
  .sqm-stat-card:hover {
@@ -268,7 +307,28 @@
268
307
  font-weight: 700;
269
308
  color: var(--sqm-foreground);
270
309
  font-family: var(--sqm-font-mono);
271
- line-height: 1;
310
+ line-height: 1.1;
311
+ word-break: break-all;
312
+ overflow-wrap: break-word;
313
+ min-width: 0;
314
+ }
315
+
316
+ /* Responsive font sizing for large numbers */
317
+ .sqm-stat-value[data-length="8"],
318
+ .sqm-stat-value[data-length="9"] {
319
+ font-size: 1.5rem;
320
+ }
321
+
322
+ .sqm-stat-value[data-length="10"],
323
+ .sqm-stat-value[data-length="11"] {
324
+ font-size: 1.25rem;
325
+ }
326
+
327
+ .sqm-stat-value[data-length="12"],
328
+ .sqm-stat-value[data-length="13"],
329
+ .sqm-stat-value[data-length="14"],
330
+ .sqm-stat-value[data-length="long"] {
331
+ font-size: 1rem;
272
332
  }
273
333
 
274
334
  /* ========================================
@@ -933,11 +993,6 @@
933
993
  gap: 0.75rem;
934
994
  }
935
995
 
936
- .sqm-stats-grid {
937
- grid-template-columns: 1fr;
938
- gap: 0.75rem;
939
- }
940
-
941
996
  .sqm-controls {
942
997
  flex-direction: column;
943
998
  align-items: stretch;
@@ -1015,4 +1070,409 @@
1015
1070
  transition-duration: 0.01ms !important;
1016
1071
  scroll-behavior: auto !important;
1017
1072
  }
1073
+ }
1074
+
1075
+ /* ========================================
1076
+ Tab Navigation System
1077
+ ======================================== */
1078
+
1079
+ .sqm-tabs-nav {
1080
+ display: flex;
1081
+ align-items: center;
1082
+ gap: 0.125rem;
1083
+ margin-bottom: 1.5rem;
1084
+ padding: 0.25rem;
1085
+ background: var(--sqm-muted);
1086
+ border-radius: var(--sqm-radius);
1087
+ border: 1px solid var(--sqm-border);
1088
+ }
1089
+
1090
+ .sqm-tab {
1091
+ display: flex;
1092
+ align-items: center;
1093
+ gap: 0.5rem;
1094
+ padding: 0.75rem 1rem;
1095
+ font-size: 0.875rem;
1096
+ font-weight: 500;
1097
+ line-height: 1;
1098
+ color: var(--sqm-muted-foreground);
1099
+ background: transparent;
1100
+ border: none;
1101
+ border-radius: var(--sqm-radius-sm);
1102
+ cursor: pointer;
1103
+ transition: var(--sqm-transition);
1104
+ white-space: nowrap;
1105
+ min-height: 2.5rem;
1106
+ }
1107
+
1108
+ .sqm-tab:hover {
1109
+ color: var(--sqm-foreground);
1110
+ background: var(--sqm-accent);
1111
+ }
1112
+
1113
+ .sqm-tab:focus {
1114
+ outline: 2px solid var(--sqm-ring);
1115
+ outline-offset: 2px;
1116
+ }
1117
+
1118
+ .sqm-tab-active {
1119
+ color: var(--sqm-foreground);
1120
+ background: var(--sqm-background);
1121
+ border: 1px solid var(--sqm-border);
1122
+ box-shadow: var(--sqm-shadow-sm);
1123
+ }
1124
+
1125
+ .sqm-tab-active:hover {
1126
+ background: var(--sqm-background);
1127
+ }
1128
+
1129
+ .sqm-tab svg {
1130
+ flex-shrink: 0;
1131
+ width: 1rem;
1132
+ height: 1rem;
1133
+ }
1134
+
1135
+ .sqm-tab-count {
1136
+ display: inline-flex;
1137
+ align-items: center;
1138
+ justify-content: center;
1139
+ min-width: 1.25rem;
1140
+ height: 1.25rem;
1141
+ padding: 0 0.375rem;
1142
+ font-size: 0.75rem;
1143
+ font-weight: 600;
1144
+ line-height: 1;
1145
+ color: var(--sqm-muted-foreground);
1146
+ background: var(--sqm-muted);
1147
+ border-radius: 0.625rem;
1148
+ transition: var(--sqm-transition);
1149
+ }
1150
+
1151
+ .sqm-tab-active .sqm-tab-count {
1152
+ color: var(--sqm-primary-foreground);
1153
+ background: var(--sqm-primary);
1154
+ }
1155
+
1156
+ /* ========================================
1157
+ Tab Content System
1158
+ ======================================== */
1159
+
1160
+ .sqm-tab-content {
1161
+ position: relative;
1162
+ }
1163
+
1164
+ .sqm-tab-panel {
1165
+ display: block;
1166
+ outline: none;
1167
+ }
1168
+
1169
+ .sqm-tab-panel.sqm-hidden {
1170
+ display: none;
1171
+ }
1172
+
1173
+ .sqm-tab-panel-active {
1174
+ display: block;
1175
+ }
1176
+
1177
+ /* ========================================
1178
+ Section Controls
1179
+ ======================================== */
1180
+
1181
+ .sqm-section-controls {
1182
+ display: flex;
1183
+ align-items: center;
1184
+ justify-content: space-between;
1185
+ gap: 1rem;
1186
+ margin-bottom: 1.5rem;
1187
+ padding: 1rem;
1188
+ background: var(--sqm-card);
1189
+ border: 1px solid var(--sqm-border);
1190
+ border-radius: var(--sqm-radius);
1191
+ }
1192
+
1193
+ .sqm-controls-left,
1194
+ .sqm-controls-right {
1195
+ display: flex;
1196
+ align-items: center;
1197
+ gap: 0.75rem;
1198
+ }
1199
+
1200
+ .sqm-filter-input {
1201
+ width: 240px;
1202
+ height: 2.5rem;
1203
+ padding: 0 0.75rem;
1204
+ font-size: 0.875rem;
1205
+ color: var(--sqm-foreground);
1206
+ background: var(--sqm-background);
1207
+ border: 1px solid var(--sqm-border);
1208
+ border-radius: var(--sqm-radius);
1209
+ transition: var(--sqm-transition);
1210
+ }
1211
+
1212
+ .sqm-filter-input:focus {
1213
+ outline: none;
1214
+ border-color: var(--sqm-ring);
1215
+ box-shadow: 0 0 0 2px rgba(var(--sqm-ring), 0.2);
1216
+ }
1217
+
1218
+ .sqm-filter-input::placeholder {
1219
+ color: var(--sqm-muted-foreground);
1220
+ }
1221
+
1222
+ /* ========================================
1223
+ Pagination System
1224
+ ======================================== */
1225
+
1226
+ .sqm-pagination {
1227
+ display: flex;
1228
+ align-items: center;
1229
+ justify-content: space-between;
1230
+ margin-top: 1.5rem;
1231
+ padding: 1rem;
1232
+ background: var(--sqm-card);
1233
+ border: 1px solid var(--sqm-border);
1234
+ border-radius: var(--sqm-radius);
1235
+ }
1236
+
1237
+ .sqm-pagination-info {
1238
+ font-size: 0.875rem;
1239
+ color: var(--sqm-muted-foreground);
1240
+ }
1241
+
1242
+ .sqm-pagination-controls {
1243
+ display: flex;
1244
+ align-items: center;
1245
+ gap: 0.5rem;
1246
+ }
1247
+
1248
+ .sqm-pagination-btn {
1249
+ display: flex;
1250
+ align-items: center;
1251
+ justify-content: center;
1252
+ width: 2.5rem;
1253
+ height: 2.5rem;
1254
+ font-size: 0.875rem;
1255
+ font-weight: 500;
1256
+ color: var(--sqm-foreground);
1257
+ background: var(--sqm-background);
1258
+ border: 1px solid var(--sqm-border);
1259
+ border-radius: var(--sqm-radius);
1260
+ cursor: pointer;
1261
+ transition: var(--sqm-transition);
1262
+ }
1263
+
1264
+ .sqm-pagination-btn:hover:not(:disabled) {
1265
+ background: var(--sqm-accent);
1266
+ }
1267
+
1268
+ .sqm-pagination-btn:disabled {
1269
+ color: var(--sqm-muted-foreground);
1270
+ cursor: not-allowed;
1271
+ opacity: 0.5;
1272
+ }
1273
+
1274
+ .sqm-pagination-btn svg {
1275
+ width: 1rem;
1276
+ height: 1rem;
1277
+ }
1278
+
1279
+ .sqm-pagination-current {
1280
+ background: var(--sqm-primary);
1281
+ color: var(--sqm-primary-foreground);
1282
+ border-color: var(--sqm-primary);
1283
+ }
1284
+
1285
+ .sqm-pagination-current:hover {
1286
+ background: var(--sqm-primary);
1287
+ }
1288
+
1289
+ /* ========================================
1290
+ Enhanced Table Styles for New Tabs
1291
+ ======================================== */
1292
+
1293
+ .sqm-scheduled-section .sqm-table,
1294
+ .sqm-retries-section .sqm-table,
1295
+ .sqm-dead-section .sqm-table {
1296
+ margin-top: 0;
1297
+ }
1298
+
1299
+ .sqm-table .sqm-error-preview {
1300
+ max-width: 200px;
1301
+ font-family: var(--sqm-font-mono);
1302
+ font-size: 0.75rem;
1303
+ color: var(--sqm-destructive);
1304
+ white-space: nowrap;
1305
+ overflow: hidden;
1306
+ text-overflow: ellipsis;
1307
+ }
1308
+
1309
+ .sqm-table .sqm-time-relative {
1310
+ font-size: 0.75rem;
1311
+ color: var(--sqm-muted-foreground);
1312
+ white-space: nowrap;
1313
+ }
1314
+
1315
+ .sqm-table .sqm-job-args {
1316
+ max-width: 150px;
1317
+ font-family: var(--sqm-font-mono);
1318
+ font-size: 0.75rem;
1319
+ color: var(--sqm-muted-foreground);
1320
+ white-space: nowrap;
1321
+ overflow: hidden;
1322
+ text-overflow: ellipsis;
1323
+ }
1324
+
1325
+ .sqm-table .sqm-retry-count {
1326
+ display: inline-flex;
1327
+ align-items: center;
1328
+ gap: 0.25rem;
1329
+ font-size: 0.75rem;
1330
+ font-weight: 500;
1331
+ }
1332
+
1333
+ .sqm-table .sqm-retry-progress {
1334
+ display: inline-block;
1335
+ width: 60px;
1336
+ height: 4px;
1337
+ background: var(--sqm-muted);
1338
+ border-radius: 2px;
1339
+ overflow: hidden;
1340
+ }
1341
+
1342
+ .sqm-table .sqm-retry-progress-bar {
1343
+ height: 100%;
1344
+ background: var(--sqm-warning);
1345
+ transition: width 0.3s ease;
1346
+ }
1347
+
1348
+ /* ========================================
1349
+ Job Status Indicators
1350
+ ======================================== */
1351
+
1352
+ .sqm-job-status {
1353
+ display: inline-flex;
1354
+ align-items: center;
1355
+ gap: 0.375rem;
1356
+ padding: 0.25rem 0.5rem;
1357
+ font-size: 0.75rem;
1358
+ font-weight: 500;
1359
+ border-radius: var(--sqm-radius-sm);
1360
+ }
1361
+
1362
+ .sqm-job-status-scheduled {
1363
+ color: var(--sqm-info-foreground);
1364
+ background: var(--sqm-info);
1365
+ }
1366
+
1367
+ .sqm-job-status-retrying {
1368
+ color: var(--sqm-warning-foreground);
1369
+ background: var(--sqm-warning);
1370
+ }
1371
+
1372
+ .sqm-job-status-dead {
1373
+ color: var(--sqm-destructive-foreground);
1374
+ background: var(--sqm-destructive);
1375
+ }
1376
+
1377
+ .sqm-job-status-dot {
1378
+ width: 0.375rem;
1379
+ height: 0.375rem;
1380
+ border-radius: 50%;
1381
+ background: currentColor;
1382
+ }
1383
+
1384
+ /* ========================================
1385
+ Action Button Variants for Job Management
1386
+ ======================================== */
1387
+
1388
+ .sqm-btn-enqueue {
1389
+ color: var(--sqm-info-foreground);
1390
+ background: var(--sqm-info);
1391
+ border-color: var(--sqm-info);
1392
+ }
1393
+
1394
+ .sqm-btn-enqueue:hover {
1395
+ background: hsl(199.1 89.1% 42%);
1396
+ }
1397
+
1398
+ .sqm-btn-resurrect {
1399
+ color: var(--sqm-success-foreground);
1400
+ background: var(--sqm-success);
1401
+ border-color: var(--sqm-success);
1402
+ }
1403
+
1404
+ .sqm-btn-resurrect:hover {
1405
+ background: hsl(142.1 76.2% 32%);
1406
+ }
1407
+
1408
+ .sqm-btn-kill {
1409
+ color: var(--sqm-destructive-foreground);
1410
+ background: var(--sqm-destructive);
1411
+ border-color: var(--sqm-destructive);
1412
+ }
1413
+
1414
+ .sqm-btn-kill:hover {
1415
+ background: hsl(0 84.2% 55%);
1416
+ }
1417
+
1418
+ /* ========================================
1419
+ Responsive Design for Tabs
1420
+ ======================================== */
1421
+
1422
+ @media (max-width: 768px) {
1423
+ .sqm-tabs-nav {
1424
+ flex-wrap: wrap;
1425
+ gap: 0.25rem;
1426
+ }
1427
+
1428
+ .sqm-tab {
1429
+ flex: 1;
1430
+ min-width: 0;
1431
+ justify-content: center;
1432
+ padding: 0.625rem 0.75rem;
1433
+ }
1434
+
1435
+ .sqm-tab span:not(.sqm-tab-count) {
1436
+ display: none;
1437
+ }
1438
+
1439
+ .sqm-section-controls {
1440
+ flex-direction: column;
1441
+ align-items: stretch;
1442
+ gap: 1rem;
1443
+ }
1444
+
1445
+ .sqm-controls-left,
1446
+ .sqm-controls-right {
1447
+ justify-content: center;
1448
+ }
1449
+
1450
+ .sqm-filter-input {
1451
+ width: 100%;
1452
+ max-width: 300px;
1453
+ }
1454
+
1455
+ .sqm-pagination {
1456
+ flex-direction: column;
1457
+ gap: 1rem;
1458
+ text-align: center;
1459
+ }
1460
+ }
1461
+
1462
+ @media (max-width: 480px) {
1463
+ .sqm-tab-count {
1464
+ min-width: 1rem;
1465
+ height: 1rem;
1466
+ font-size: 0.625rem;
1467
+ }
1468
+
1469
+ .sqm-section-controls {
1470
+ padding: 0.75rem;
1471
+ }
1472
+
1473
+ .sqm-controls-left,
1474
+ .sqm-controls-right {
1475
+ flex-wrap: wrap;
1476
+ gap: 0.5rem;
1477
+ }
1018
1478
  }
@@ -19,6 +19,10 @@ module SidekiqQueueManager
19
19
  set_limit remove_limit set_process_limit remove_process_limit
20
20
  block unblock clear delete_queue
21
21
  ]
22
+ before_action :set_job_id, only: %i[
23
+ delete_scheduled_job enqueue_scheduled_job retry_job_now delete_retry_job
24
+ kill_retry_job resurrect_dead_job delete_dead_job
25
+ ]
22
26
 
23
27
  # Comprehensive error handling for all controller actions
24
28
  rescue_from StandardError, with: :handle_api_error
@@ -73,6 +77,193 @@ module SidekiqQueueManager
73
77
  end
74
78
  end
75
79
 
80
+ # ========================================
81
+ # Scheduled Jobs Management
82
+ # ========================================
83
+
84
+ # Get scheduled jobs with pagination and filtering
85
+ # GET /scheduled
86
+ def scheduled_jobs
87
+ page = params[:page] || 1
88
+ per_page = params[:per_page] || 25
89
+ filter = params[:filter]
90
+
91
+ result = QueueService.scheduled_jobs(page: page, per_page: per_page, filter: filter)
92
+ render json: api_response(
93
+ success: result[:success],
94
+ message: result[:message],
95
+ data: result
96
+ ), status: result[:success] ? :ok : :unprocessable_entity
97
+ end
98
+
99
+ # Delete a scheduled job
100
+ # DELETE /scheduled/:id
101
+ def delete_scheduled_job
102
+ result = QueueService.delete_scheduled_job(@job_id)
103
+ render json: api_response(
104
+ success: result[:success],
105
+ message: result[:message]
106
+ ), status: result[:success] ? :ok : :unprocessable_entity
107
+ end
108
+
109
+ # Enqueue a scheduled job immediately
110
+ # POST /scheduled/:id/enqueue
111
+ def enqueue_scheduled_job
112
+ result = QueueService.enqueue_scheduled_job(@job_id)
113
+ render json: api_response(
114
+ success: result[:success],
115
+ message: result[:message]
116
+ ), status: result[:success] ? :ok : :unprocessable_entity
117
+ end
118
+
119
+ # Clear all scheduled jobs
120
+ # POST /scheduled/clear
121
+ def clear_scheduled_jobs
122
+ filter = params[:filter]
123
+ result = QueueService.clear_scheduled_jobs(filter: filter)
124
+ render json: api_response(
125
+ success: result[:success],
126
+ message: result[:message],
127
+ data: result
128
+ ), status: result[:success] ? :ok : :unprocessable_entity
129
+ end
130
+
131
+ # ========================================
132
+ # Retry Jobs Management
133
+ # ========================================
134
+
135
+ # Get retry jobs with pagination and filtering
136
+ # GET /retries
137
+ def retry_jobs
138
+ page = params[:page] || 1
139
+ per_page = params[:per_page] || 25
140
+ filter = params[:filter]
141
+
142
+ result = QueueService.retry_jobs(page: page, per_page: per_page, filter: filter)
143
+ render json: api_response(
144
+ success: result[:success],
145
+ message: result[:message],
146
+ data: result
147
+ ), status: result[:success] ? :ok : :unprocessable_entity
148
+ end
149
+
150
+ # Retry a job immediately
151
+ # POST /retries/:id/retry
152
+ def retry_job_now
153
+ result = QueueService.retry_job_now(@job_id)
154
+ render json: api_response(
155
+ success: result[:success],
156
+ message: result[:message]
157
+ ), status: result[:success] ? :ok : :unprocessable_entity
158
+ end
159
+
160
+ # Delete a retry job
161
+ # DELETE /retries/:id
162
+ def delete_retry_job
163
+ result = QueueService.delete_retry_job(@job_id)
164
+ render json: api_response(
165
+ success: result[:success],
166
+ message: result[:message]
167
+ ), status: result[:success] ? :ok : :unprocessable_entity
168
+ end
169
+
170
+ # Kill a retry job (move to dead queue)
171
+ # POST /retries/:id/kill
172
+ def kill_retry_job
173
+ result = QueueService.kill_retry_job(@job_id)
174
+ render json: api_response(
175
+ success: result[:success],
176
+ message: result[:message]
177
+ ), status: result[:success] ? :ok : :unprocessable_entity
178
+ end
179
+
180
+ # Clear all retry jobs
181
+ # POST /retries/clear
182
+ def clear_retry_jobs
183
+ filter = params[:filter]
184
+ result = QueueService.clear_retry_jobs(filter: filter)
185
+ render json: api_response(
186
+ success: result[:success],
187
+ message: result[:message],
188
+ data: result
189
+ ), status: result[:success] ? :ok : :unprocessable_entity
190
+ end
191
+
192
+ # Retry all jobs in retry queue
193
+ # POST /retries/retry_all
194
+ def retry_all_jobs
195
+ filter = params[:filter]
196
+ result = QueueService.retry_all_jobs(filter: filter)
197
+ render json: api_response(
198
+ success: result[:success],
199
+ message: result[:message],
200
+ data: result
201
+ ), status: result[:success] ? :ok : :unprocessable_entity
202
+ end
203
+
204
+ # ========================================
205
+ # Dead Jobs Management
206
+ # ========================================
207
+
208
+ # Get dead jobs with pagination and filtering
209
+ # GET /dead
210
+ def dead_jobs
211
+ page = params[:page] || 1
212
+ per_page = params[:per_page] || 25
213
+ filter = params[:filter]
214
+
215
+ result = QueueService.dead_jobs(page: page, per_page: per_page, filter: filter)
216
+ render json: api_response(
217
+ success: result[:success],
218
+ message: result[:message],
219
+ data: result
220
+ ), status: result[:success] ? :ok : :unprocessable_entity
221
+ end
222
+
223
+ # Resurrect a dead job
224
+ # POST /dead/:id/resurrect
225
+ def resurrect_dead_job
226
+ result = QueueService.resurrect_dead_job(@job_id)
227
+ render json: api_response(
228
+ success: result[:success],
229
+ message: result[:message]
230
+ ), status: result[:success] ? :ok : :unprocessable_entity
231
+ end
232
+
233
+ # Delete a dead job permanently
234
+ # DELETE /dead/:id
235
+ def delete_dead_job
236
+ result = QueueService.delete_dead_job(@job_id)
237
+ render json: api_response(
238
+ success: result[:success],
239
+ message: result[:message]
240
+ ), status: result[:success] ? :ok : :unprocessable_entity
241
+ end
242
+
243
+ # Clear all dead jobs
244
+ # POST /dead/clear
245
+ def clear_dead_jobs
246
+ filter = params[:filter]
247
+ result = QueueService.clear_dead_jobs(filter: filter)
248
+ render json: api_response(
249
+ success: result[:success],
250
+ message: result[:message],
251
+ data: result
252
+ ), status: result[:success] ? :ok : :unprocessable_entity
253
+ end
254
+
255
+ # Resurrect all dead jobs
256
+ # POST /dead/resurrect_all
257
+ def resurrect_all_dead_jobs
258
+ filter = params[:filter]
259
+ result = QueueService.resurrect_all_dead_jobs(filter: filter)
260
+ render json: api_response(
261
+ success: result[:success],
262
+ message: result[:message],
263
+ data: result
264
+ ), status: result[:success] ? :ok : :unprocessable_entity
265
+ end
266
+
76
267
  # ========================================
77
268
  # Queue Control Actions (JSON API)
78
269
  # ========================================
@@ -293,6 +484,17 @@ module SidekiqQueueManager
293
484
  nil
294
485
  end
295
486
 
487
+ def set_job_id
488
+ @job_id = params[:id] || params[:job_id]
489
+
490
+ if @job_id.blank?
491
+ render json: api_error_response('Job ID is required'), status: :bad_request
492
+ return
493
+ end
494
+
495
+ @job_id
496
+ end
497
+
296
498
  def available_queue_names
297
499
  @available_queue_names ||= SidekiqQueueManager::QueueService.send(:available_queues)
298
500
  end