Skip to content

Commit 8b44e42

Browse files
authored
Merge pull request #14 from houdaslassi/logging-toggle
This PR introduces several enhancements and fixes to improve the Vantage dashboard experience, including new configuration options, UI improvements, and bug fixes.
2 parents 3d57600 + 99b0060 commit 8b44e42

File tree

14 files changed

+252
-63
lines changed

14 files changed

+252
-63
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ php artisan vendor:publish --tag=vantage-config
164164
- `retention_days` - How long to keep job history
165165
- `routes` - Master switch to register dashboard routes
166166
- `route_prefix` - Base URI segment for all dashboard routes (default: `vantage`)
167+
- `logging.enabled` - Toggle Vantage's own log output
167168
- `notify.email` - Email to notify on failures
168169
- `notify.slack_webhook` - Slack webhook URL for failures
169170
- `telemetry.enabled` - Enable performance telemetry (memory/CPU)
@@ -291,15 +292,19 @@ VANTAGE_SLACK_WEBHOOK=https://hooks.slack.com/services/...
291292
292293
# Routes (default: true)
293294
VANTAGE_ROUTES=true
295+
294296
# Change the base URL path for the dashboard (default: vantage)
295297
VANTAGE_ROUTE_PREFIX=vantage
298+
299+
# Logging (default: true)
300+
VANTAGE_LOGGING_ENABLED=true
296301
```
297302

298303
## Demo
299304

300305
Watch a brief demo of the Vantage dashboard in action:
301306

302-
[![Vantage Demo](https://img.youtube.com/vi/IZAjYTtzL7I/0.jpg)](https://www.youtube.com/watch?v=IZAjYTtzL7I)
307+
[![Vantage Demo](https://img.youtube.com/vi/IZAjYTtzL7I/0.jpg)](https://www.youtube.com/watch?v=ZPea5E3o_2w)
303308

304309
> Click the thumbnail above to watch the demo video on YouTube.
305310

config/vantage.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,17 @@
156156
'auth' => [
157157
'enabled' => env('VANTAGE_AUTH_ENABLED', true),
158158
],
159+
160+
/*
161+
|--------------------------------------------------------------------------
162+
| Logging
163+
|--------------------------------------------------------------------------
164+
|
165+
| Toggle package-originated log messages. When disabled, Vantage will refrain
166+
| from writing informational/debug/warning logs to your application log.
167+
|
168+
*/
169+
'logging' => [
170+
'enabled' => env('VANTAGE_LOGGING_ENABLED', true),
171+
],
159172
];

resources/vantage-views/failed.blade.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
</div>
1010

1111
<!-- Failed Jobs Table -->
12-
<div class="bg-white shadow rounded-lg overflow-hidden">
13-
<table class="min-w-full divide-y divide-gray-200">
12+
<div class="bg-white shadow rounded-lg">
13+
<div class="overflow-x-auto">
14+
<table class="min-w-full w-full divide-y divide-gray-200">
1415
<thead class="bg-gray-50">
1516
<tr>
1617
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
@@ -65,6 +66,7 @@ class="text-indigo-600 hover:text-indigo-900">
6566
@endforelse
6667
</tbody>
6768
</table>
69+
</div>
6870
</div>
6971

7072
<!-- Pagination -->

resources/vantage-views/jobs.blade.php

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<p class="mt-1 text-sm text-gray-500">Narrow down your job list</p>
3030
</div>
3131
<div class="p-6">
32-
<form method="GET" action="{{ route('vantage.jobs') }}" class="space-y-6">
32+
<form id="jobs-filter-form" method="GET" action="{{ route('vantage.jobs') }}" class="space-y-6">
3333
<!-- First row of filters -->
3434
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
3535
<div>
@@ -106,6 +106,53 @@ class="block w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:
106106

107107
<!-- Tag Cloud -->
108108
@if($allTags->isNotEmpty())
109+
<script>
110+
// Define function globally so it's accessible from onclick
111+
window.addTagToFilter = function(tag) {
112+
if (!tag) {
113+
return false;
114+
}
115+
116+
// Find the tags input in the main filters form
117+
const form = document.getElementById('jobs-filter-form');
118+
if (!form) {
119+
return false;
120+
}
121+
122+
const tagsInput = form.querySelector('input[name="tags"]');
123+
if (!tagsInput) {
124+
return false;
125+
}
126+
127+
const currentTags = tagsInput.value.trim();
128+
129+
// Normalize tag (already lowercased from PHP, but ensure it's trimmed)
130+
const normalizedTag = String(tag).toLowerCase().trim();
131+
132+
let newTagsValue;
133+
if (currentTags === '') {
134+
newTagsValue = normalizedTag;
135+
} else {
136+
const tags = currentTags.split(',').map(t => t.trim().toLowerCase()).filter(t => t);
137+
if (!tags.includes(normalizedTag)) {
138+
// Add the tag if it doesn't exist
139+
newTagsValue = currentTags + ', ' + normalizedTag;
140+
} else {
141+
// Tag already exists - remove it (toggle behavior)
142+
const newTags = tags.filter(t => t !== normalizedTag);
143+
newTagsValue = newTags.join(', ');
144+
}
145+
}
146+
147+
// Set the value
148+
tagsInput.value = newTagsValue;
149+
150+
// Submit the main form
151+
form.submit();
152+
153+
return false; // Prevent default
154+
};
155+
</script>
109156
<div class="bg-white shadow-sm border border-gray-200 rounded-lg mb-6">
110157
<div class="px-6 py-4 border-b border-gray-200">
111158
<h3 class="text-lg font-medium text-gray-900">Popular Tags</h3>
@@ -114,8 +161,17 @@ class="block w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:
114161
<div class="p-6">
115162
<div class="flex flex-wrap gap-2">
116163
@foreach($allTags as $tagData)
117-
<button onclick="addTagToFilter('{{ $tagData['tag'] }}')"
118-
class="group inline-flex items-center px-3 py-2 rounded-lg text-sm font-medium transition-all duration-200 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500
164+
@php
165+
$tagValue = strtolower($tagData['tag']);
166+
$currentTags = request('tags', '');
167+
$tagsArray = !empty($currentTags) ? array_map('trim', explode(',', $currentTags)) : [];
168+
$isActive = in_array($tagValue, array_map('strtolower', $tagsArray));
169+
@endphp
170+
<button type="button"
171+
data-tag="{{ $tagValue }}"
172+
onclick="addTagToFilter('{{ addslashes($tagValue) }}')"
173+
class="tag-filter-button group inline-flex items-center px-3 py-2 rounded-lg text-sm font-medium transition-all duration-200 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500
174+
{{ $isActive ? 'ring-2 ring-indigo-500 ' : '' }}
119175
{{ $tagData['failed'] > 0 ? 'bg-red-50 text-red-700 border border-red-200 hover:bg-red-100' :
120176
($tagData['processed'] > 0 ? 'bg-green-50 text-green-700 border border-green-200 hover:bg-green-100' :
121177
'bg-blue-50 text-blue-700 border border-blue-200 hover:bg-blue-100') }}">
@@ -140,8 +196,9 @@ class="group inline-flex items-center px-3 py-2 rounded-lg text-sm font-medium t
140196
@endif
141197

142198
<!-- Jobs Table -->
143-
<div class="bg-white shadow rounded-lg overflow-hidden">
144-
<table class="min-w-full divide-y divide-gray-200">
199+
<div class="bg-white shadow rounded-lg">
200+
<div class="overflow-x-auto">
201+
<table class="min-w-full w-full divide-y divide-gray-200">
145202
<thead class="bg-gray-50">
146203
<tr>
147204
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
@@ -236,6 +293,7 @@ class="group inline-flex items-center px-3 py-2 rounded-lg text-sm font-medium t
236293
@endforelse
237294
</tbody>
238295
</table>
296+
</div>
239297
</div>
240298

241299
<!-- Pagination -->
@@ -248,31 +306,21 @@ class="group inline-flex items-center px-3 py-2 rounded-lg text-sm font-medium t
248306

249307
@section('scripts')
250308
<script>
251-
function addTagToFilter(tag) {
252-
const tagsInput = document.querySelector('input[name="tags"]');
253-
const currentTags = tagsInput.value.trim();
254-
255-
if (currentTags === '') {
256-
tagsInput.value = tag;
257-
} else {
258-
const tags = currentTags.split(',').map(t => t.trim());
259-
if (!tags.includes(tag)) {
260-
tagsInput.value = currentTags + ', ' + tag;
261-
}
262-
}
263-
264-
// Auto-submit the form
265-
document.querySelector('form').submit();
266-
}
309+
// Function is defined inline before the Popular Tags section
310+
// This section is kept for any additional scripts
267311
268312
// Add keyboard shortcuts
269313
document.addEventListener('keydown', function(e) {
270314
// Ctrl/Cmd + K to focus on tags input
271315
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
272316
e.preventDefault();
273-
document.querySelector('input[name="tags"]').focus();
317+
const tagsInput = document.querySelector('input[name="tags"]');
318+
if (tagsInput) {
319+
tagsInput.focus();
320+
}
274321
}
275322
});
276323
</script>
277324
@endsection
278325

326+

resources/vantage-views/layout.blade.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<body class="bg-gray-50">
1313
<!-- Navigation -->
1414
<nav class="bg-white shadow-sm border-b border-gray-200">
15-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
15+
<div class="w-full px-4 sm:px-6 lg:px-10">
1616
<div class="flex justify-between h-16">
1717
<div class="flex">
1818
<div class="flex-shrink-0 flex items-center">
@@ -44,7 +44,7 @@ class="@if(request()->routeIs('vantage.tags')) border-indigo-500 text-gray-900 @
4444
</nav>
4545

4646
<!-- Content -->
47-
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
47+
<main class="w-full px-4 sm:px-6 lg:px-10 py-8">
4848
@if(session('success'))
4949
<div class="mb-4 bg-green-50 border border-green-200 text-green-800 px-4 py-3 rounded relative" role="alert">
5050
<span class="block sm:inline">{{ session('success') }}</span>

resources/vantage-views/tags.blade.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ class="w-full max-w-md px-4 py-2 border border-gray-300 rounded-md focus:ring-in
2424
</div>
2525

2626
<!-- Tags Table -->
27-
<div class="bg-white shadow rounded-lg overflow-hidden">
28-
<table id="tagsTable" class="min-w-full divide-y divide-gray-200">
27+
<div class="bg-white shadow rounded-lg">
28+
<div class="overflow-x-auto">
29+
<table id="tagsTable" class="min-w-full w-full divide-y divide-gray-200">
2930
<thead class="bg-gray-50">
3031
<tr>
3132
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 sortable" data-column="0">
@@ -102,6 +103,7 @@ class="inline-flex items-center px-2.5 py-0.5 rounded-full text-sm font-medium b
102103
@endforelse
103104
</tbody>
104105
</table>
106+
</div>
105107
</div>
106108

107109
@if(!empty($tagStats))

src/Http/Controllers/QueueMonitorController.php

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use HoudaSlassi\Vantage\Models\VantageJob;
66
use HoudaSlassi\Vantage\Support\QueueDepthChecker;
7+
use HoudaSlassi\Vantage\Support\VantageLogger;
78
use Illuminate\Http\Request;
89
use Illuminate\Routing\Controller;
910
use Illuminate\Support\Facades\DB;
@@ -145,7 +146,7 @@ public function index(Request $request)
145146
try {
146147
$queueDepths = QueueDepthChecker::getQueueDepthWithMetadataAlways();
147148
} catch (\Throwable $e) {
148-
Log::warning('Failed to get queue depths', ['error' => $e->getMessage()]);
149+
VantageLogger::warning('Failed to get queue depths', ['error' => $e->getMessage()]);
149150
// Always show at least one queue entry even on error
150151
$queueDepths = [
151152
'default' => [
@@ -252,27 +253,69 @@ public function jobs(Request $request)
252253
}
253254

254255
// Advanced tag filtering
255-
if ($request->filled('tags')) {
256-
$tags = is_array($request->tags) ? $request->tags : explode(',', $request->tags);
256+
$tagsParam = $request->get('tags');
257+
258+
// Check if tags parameter exists and is not empty
259+
if (!empty($tagsParam) && trim($tagsParam) !== '') {
260+
$tags = is_array($tagsParam) ? $tagsParam : explode(',', $tagsParam);
257261
$tags = array_map('trim', $tags);
258262
$tags = array_map('strtolower', $tags);
259-
260-
if ($request->filled('tag_mode') && $request->tag_mode === 'any') {
261-
// Jobs that have ANY of the specified tags
262-
$query->where(function($q) use ($tags) {
263+
$tags = array_filter($tags); // Remove empty tags
264+
265+
if (!empty($tags)) {
266+
// Get database driver for database-specific JSON queries
267+
$connectionName = (new VantageJob)->getConnectionName();
268+
$connection = DB::connection($connectionName);
269+
$driver = $connection->getDriverName();
270+
271+
if ($request->filled('tag_mode') && $request->tag_mode === 'any') {
272+
// Jobs that have ANY of the specified tags
273+
$query->where(function($q) use ($tags, $driver) {
274+
foreach ($tags as $tag) {
275+
if ($driver === 'sqlite') {
276+
// SQLite: Use JSON functions if available, otherwise fallback to LIKE
277+
// Try json_each first (SQLite 3.38+), fallback to LIKE pattern
278+
$q->orWhereRaw("EXISTS (
279+
SELECT 1 FROM json_each(job_tags)
280+
WHERE json_each.value = ?
281+
)", [json_encode($tag)]);
282+
} else {
283+
// MySQL and PostgreSQL support whereJsonContains
284+
$q->orWhereJsonContains('job_tags', $tag);
285+
}
286+
}
287+
});
288+
} else {
289+
// Jobs that have ALL of the specified tags (default)
263290
foreach ($tags as $tag) {
264-
$q->orWhereJsonContains('job_tags', $tag);
291+
if ($driver === 'sqlite') {
292+
// SQLite: Use JSON functions if available
293+
$query->whereRaw("EXISTS (
294+
SELECT 1 FROM json_each(job_tags)
295+
WHERE json_each.value = ?
296+
)", [json_encode($tag)]);
297+
} else {
298+
// MySQL and PostgreSQL
299+
$query->whereJsonContains('job_tags', $tag);
300+
}
265301
}
266-
});
267-
} else {
268-
// Jobs that have ALL of the specified tags (default)
269-
foreach ($tags as $tag) {
270-
$query->whereJsonContains('job_tags', $tag);
271302
}
272303
}
273304
} elseif ($request->filled('tag')) {
274305
// Single tag filter (backward compatibility)
275-
$query->whereJsonContains('job_tags', strtolower($request->tag));
306+
$tag = strtolower(trim($request->tag));
307+
$connectionName = (new VantageJob)->getConnectionName();
308+
$connection = DB::connection($connectionName);
309+
$driver = $connection->getDriverName();
310+
311+
if ($driver === 'sqlite') {
312+
$query->whereRaw("EXISTS (
313+
SELECT 1 FROM json_each(job_tags)
314+
WHERE json_each.value = ?
315+
)", [json_encode($tag)]);
316+
} else {
317+
$query->whereJsonContains('job_tags', $tag);
318+
}
276319
}
277320

278321
if ($request->filled('since')) {
@@ -285,7 +328,16 @@ public function jobs(Request $request)
285328
->withQueryString();
286329

287330
// Get filter options
288-
$queues = VantageJob::distinct()->pluck('queue')->filter();
331+
// Only show queues that actually have jobs in vantage_jobs table
332+
// This ensures filtering by a queue will return results
333+
$queues = VantageJob::distinct()
334+
->whereNotNull('queue')
335+
->where('queue', '!=', '')
336+
->pluck('queue')
337+
->filter()
338+
->sort()
339+
->values();
340+
289341
$jobClasses = VantageJob::distinct()->pluck('job_class')->map(fn($c) => class_basename($c))->filter();
290342

291343
// Get all available tags with counts
@@ -434,7 +486,7 @@ public function retry($id)
434486
return back()->with('success', "✓ Job queued for retry!");
435487

436488
} catch (\Throwable $e) {
437-
\Log::error('Vantage: Retry failed', [
489+
VantageLogger::error('Vantage: Retry failed', [
438490
'job_id' => $id,
439491
'error' => $e->getMessage()
440492
]);
@@ -460,22 +512,22 @@ protected function restoreJobFromPayload(VantageJob $run): ?object
460512
$serialized = $payload['raw_payload']['data']['command'] ?? null;
461513

462514
if (!$serialized) {
463-
\Log::warning('Vantage: No serialized command in payload', ['run_id' => $run->id]);
515+
VantageLogger::warning('Vantage: No serialized command in payload', ['run_id' => $run->id]);
464516
return null;
465517
}
466518

467519
// Unserialize it - Laravel stored it this way originally
468520
$job = unserialize($serialized, ['allowed_classes' => true]);
469521

470522
if (!is_object($job)) {
471-
\Log::warning('Vantage: Unserialize did not return object', [
523+
VantageLogger::warning('Vantage: Unserialize did not return object', [
472524
'run_id' => $run->id,
473525
'result_type' => gettype($job)
474526
]);
475527
return null;
476528
}
477529

478-
\Log::info('Vantage: Successfully restored job', [
530+
VantageLogger::info('Vantage: Successfully restored job', [
479531
'run_id' => $run->id,
480532
'job_class' => get_class($job)
481533
]);

0 commit comments

Comments
 (0)