adonisjs-pulse 0.0.1
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.
- package/LICENSE.md +9 -0
- package/README.md +119 -0
- package/build/configure.d.ts +2 -0
- package/build/configure.js +47 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +5 -0
- package/build/providers/pulse_provider.d.ts +16 -0
- package/build/providers/pulse_provider.js +64 -0
- package/build/resources/views/components/card-header.edge +30 -0
- package/build/resources/views/components/card.edge +8 -0
- package/build/resources/views/components/no-results.edge +3 -0
- package/build/resources/views/components/pulse.edge +79 -0
- package/build/resources/views/components/scroll.edge +3 -0
- package/build/resources/views/components/table.edge +3 -0
- package/build/resources/views/components/td.edge +4 -0
- package/build/resources/views/components/th.edge +4 -0
- package/build/resources/views/components/thead.edge +3 -0
- package/build/resources/views/components/theme-switcher.edge +48 -0
- package/build/resources/views/dashboard.edge +7 -0
- package/build/resources/views/livewire/cache.edge +111 -0
- package/build/resources/views/livewire/exceptions.edge +48 -0
- package/build/resources/views/livewire/period-selector.edge +11 -0
- package/build/resources/views/livewire/servers.edge +86 -0
- package/build/resources/views/livewire/slow-queries.edge +50 -0
- package/build/resources/views/livewire/slow-requests.edge +62 -0
- package/build/resources/views/livewire/usage.edge +63 -0
- package/build/services/pulse.d.ts +3 -0
- package/build/services/pulse.js +6 -0
- package/build/src/entry.d.ts +24 -0
- package/build/src/entry.js +67 -0
- package/build/src/livewire/cache.d.ts +4 -0
- package/build/src/livewire/cache.js +33 -0
- package/build/src/livewire/card.d.ts +22 -0
- package/build/src/livewire/card.js +96 -0
- package/build/src/livewire/exceptions.d.ts +4 -0
- package/build/src/livewire/exceptions.js +29 -0
- package/build/src/livewire/period_selector.d.ts +5 -0
- package/build/src/livewire/period_selector.js +16 -0
- package/build/src/livewire/servers.d.ts +4 -0
- package/build/src/livewire/servers.js +36 -0
- package/build/src/livewire/slow_queries.d.ts +5 -0
- package/build/src/livewire/slow_queries.js +37 -0
- package/build/src/livewire/slow_requests.d.ts +5 -0
- package/build/src/livewire/slow_requests.js +37 -0
- package/build/src/pulse.d.ts +44 -0
- package/build/src/pulse.js +224 -0
- package/build/src/recorders/cache_interactions.d.ts +34 -0
- package/build/src/recorders/cache_interactions.js +80 -0
- package/build/src/recorders/exceptions.d.ts +8 -0
- package/build/src/recorders/exceptions.js +81 -0
- package/build/src/recorders/index.d.ts +7 -0
- package/build/src/recorders/index.js +6 -0
- package/build/src/recorders/recorder.d.ts +24 -0
- package/build/src/recorders/recorder.js +45 -0
- package/build/src/recorders/servers.d.ts +24 -0
- package/build/src/recorders/servers.js +133 -0
- package/build/src/recorders/slow_queries.d.ts +8 -0
- package/build/src/recorders/slow_queries.js +27 -0
- package/build/src/recorders/slow_requests.d.ts +11 -0
- package/build/src/recorders/slow_requests.js +80 -0
- package/build/src/storage/database_storage.d.ts +20 -0
- package/build/src/storage/database_storage.js +357 -0
- package/build/src/types.d.ts +91 -0
- package/build/src/types.js +3 -0
- package/build/src/value.d.ts +9 -0
- package/build/src/value.js +20 -0
- package/build/stubs/config.stub +58 -0
- package/build/stubs/main.d.ts +5 -0
- package/build/stubs/main.js +7 -0
- package/build/stubs/migration.stub +58 -0
- package/package.json +100 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div class="flex items-center gap-2">
|
|
2
|
+
<select
|
|
3
|
+
wire:model.live="period"
|
|
4
|
+
class="block w-full rounded-md border-0 bg-white dark:bg-gray-800 py-1.5 pl-3 pr-10 text-gray-900 dark:text-gray-100 ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
|
5
|
+
>
|
|
6
|
+
<option value="1_hour">Last hour</option>
|
|
7
|
+
<option value="6_hours">Last 6 hours</option>
|
|
8
|
+
<option value="24_hours">Last 24 hours</option>
|
|
9
|
+
<option value="7_days">Last 7 days</option>
|
|
10
|
+
</select>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<x-pulse::card :cols="cols ?? 'full'" :rows="rows ?? 1">
|
|
2
|
+
<x-pulse::scroll>
|
|
3
|
+
@if(servers && servers.length > 0)
|
|
4
|
+
<div class="grid grid-cols-[max-content,minmax(max-content,1fr),max-content,minmax(min-content,2fr),max-content,minmax(min-content,2fr),minmax(max-content,1fr)] gap-x-1">
|
|
5
|
+
<div></div>
|
|
6
|
+
<div></div>
|
|
7
|
+
<div class="text-xs uppercase text-left text-gray-500 dark:text-gray-400 font-bold py-2">CPU</div>
|
|
8
|
+
<div></div>
|
|
9
|
+
<div class="text-xs uppercase text-left text-gray-500 dark:text-gray-400 font-bold py-2">Memory</div>
|
|
10
|
+
<div></div>
|
|
11
|
+
<div class="text-xs uppercase text-left text-gray-500 dark:text-gray-400 font-bold py-2">Storage</div>
|
|
12
|
+
@each(server in servers)
|
|
13
|
+
<div class="flex items-center {{ servers.length > 1 ? 'py-2' : '' }}" title="{{ server.recently_reported ? 'Online' : 'Offline' }}">
|
|
14
|
+
@if(server.recently_reported)
|
|
15
|
+
<div class="w-5 flex justify-center mr-1">
|
|
16
|
+
<div class="h-1 w-1 bg-green-500 rounded-full animate-pulse"></div>
|
|
17
|
+
</div>
|
|
18
|
+
@else
|
|
19
|
+
<svg xmlns="http://
|
|
20
|
+
www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" class="w-5 h-5 stroke-red-500 mr-1">
|
|
21
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3 3l8.735 8.735m0 0a.374.374 0 11.53.53m-.53-.53l.53.53m0 0L21 21M14.652 9.348a3.75 3.75 0 010 5.304m2.121-7.425a6.75 6.75 0 010 9.546m2.121-11.667c3.808 3.807 3.808 9.98 0 13.788m-9.546-4.242a3.733 3.733 0 01-1.06-2.122m-1.061 4.243a6.75 6.75 0 01-1.625-6.929m-.496 9.05c-3.068-3.067-3.664-7.67-1.79-11.334M12 12h.008v.008H12V12z" />
|
|
22
|
+
</svg>
|
|
23
|
+
@end
|
|
24
|
+
</div>
|
|
25
|
+
<div class="flex items-center pr-8 xl:pr-12 {{ servers.length > 1 ? 'py-2' : '' }} {{ !server.recently_reported ? 'opacity-25 animate-pulse' : '' }}">
|
|
26
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" class="w-6 h-6 mr-2 stroke-gray-500 dark:stroke-gray-400">
|
|
27
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 17.25v-.228a4.5 4.5 0 0 0-.12-1.03l-2.268-9.64a3.375 3.375 0 0 0-3.285-2.602H7.923a3.375 3.375 0 0 0-3.285 2.602l-2.268 9.64a4.5 4.5 0 0 0-.12 1.03v.228m19.5 0a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3m19.5 0a3 3 0 0 0-3-3H5.25a3 3 0 0 0-3 3m16.5 0h.008v.008h-.008v-.008Zm-3 0h.008v.008h-.008v-.008Z" />
|
|
28
|
+
</svg>
|
|
29
|
+
<span class="text-base font-bold text-gray-600 dark:text-gray-300">{{ server.name }}</span>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="flex items-center {{ servers.length > 1 ? 'py-2' : '' }} {{ !server.recently_reported ? 'opacity-25 animate-pulse' : '' }}">
|
|
32
|
+
<div class="text-xl font-bold text-gray-700 dark:text-gray-200 w-14 whitespace-nowrap tabular-nums">
|
|
33
|
+
{{ server.cpu }}%
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="flex items-center pr-8 xl:pr-12 {{ servers.length > 1 ? 'py-2' : '' }} {{ !server.recently_reported ? 'opacity-25 animate-pulse' : '' }}">
|
|
37
|
+
<div class="w-full min-w-[5rem] max-w-xs h-9 relative">
|
|
38
|
+
<div class="w-full h-full ring-1 ring-gray-900/5 bg-gray-100 dark:bg-gray-800 rounded-md shadow-sm overflow-hidden">
|
|
39
|
+
<div class="h-full bg-purple-500 transition-all duration-300" style="width: {{ server.cpu }}%"></div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="flex items-center {{ servers.length > 1 ? 'py-2' : '' }} {{ !server.recently_reported ? 'opacity-25 animate-pulse' : '' }}">
|
|
44
|
+
<div class="w-36 flex-shrink-0 whitespace-nowrap tabular-nums">
|
|
45
|
+
<span class="text-xl font-bold text-gray-700 dark:text-gray-200">
|
|
46
|
+
{{ formatBytes(server.memory_used) }}
|
|
47
|
+
</span>
|
|
48
|
+
<span class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
49
|
+
/ {{ formatBytes(server.memory_total) }}
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="flex items-center pr-8 xl:pr-12 {{ servers.length > 1 ? 'py-2' : '' }} {{ !server.recently_reported ? 'opacity-25 animate-pulse' : '' }}">
|
|
54
|
+
@let(memoryPercent = server.memory_total > 0 ? Math.round((server.memory_used / server.memory_total) * 100) : 0)
|
|
55
|
+
<div class="w-full min-w-[5rem] max-w-xs h-9 relative">
|
|
56
|
+
<div class="w-full h-full ring-1 ring-gray-900/5 bg-gray-100 dark:bg-gray-800 rounded-md shadow-sm overflow-hidden">
|
|
57
|
+
<div class="h-full bg-purple-500 transition-all duration-300" style="width: {{ memoryPercent }}%"></div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="flex items-center gap-8 {{ servers.length > 1 ? 'py-2' : '' }} {{ !server.recently_reported ? 'opacity-25 animate-pulse' : '' }}">
|
|
62
|
+
@if(server.storage && server.storage.length > 0)
|
|
63
|
+
@each(disk in server.storage)
|
|
64
|
+
<div class="flex items-center gap-4" title="Directory: {{ disk.directory }}">
|
|
65
|
+
<div class="whitespace-nowrap tabular-nums">
|
|
66
|
+
<span class="text-xl font-bold text-gray-700 dark:text-gray-200">{{ formatBytes(disk.used) }}</span>
|
|
67
|
+
<span class="text-sm font-medium text-gray-500 dark:text-gray-400">/ {{ formatBytes(disk.total) }}</span>
|
|
68
|
+
</div>
|
|
69
|
+
@let(diskPercent = disk.total > 0 ? Math.round((disk.used / disk.total) * 100) : 0)
|
|
70
|
+
<div class="relative h-8 w-8">
|
|
71
|
+
<svg class="h-8 w-8 -rotate-90" viewBox="0 0 36 36">
|
|
72
|
+
<circle cx="18" cy="18" r="14" fill="none" stroke-width="4" class="stroke-purple-500/20"></circle>
|
|
73
|
+
<circle cx="18" cy="18" r="14" fill="none" stroke-width="4" stroke-dasharray="{{ diskPercent * 0.88 }}, 100" stroke-linecap="round" class="stroke-purple-500"></circle>
|
|
74
|
+
</svg>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
@end
|
|
78
|
+
@end
|
|
79
|
+
</div>
|
|
80
|
+
@end
|
|
81
|
+
</div>
|
|
82
|
+
@else
|
|
83
|
+
<x-pulse::no-results message="No servers reporting" />
|
|
84
|
+
@end
|
|
85
|
+
</x-pulse::scroll>
|
|
86
|
+
</x-pulse::card>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<x-pulse::card :cols="cols ?? 8" :rows="rows ?? 1">
|
|
2
|
+
<x-pulse::card-header title="Slow Queries" :details="'past ' + period">
|
|
3
|
+
<x-slot name="icon">
|
|
4
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5">
|
|
5
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" />
|
|
6
|
+
</svg>
|
|
7
|
+
</x-slot>
|
|
8
|
+
</x-pulse::card-header>
|
|
9
|
+
|
|
10
|
+
<x-pulse::scroll>
|
|
11
|
+
@if(queries && queries.length > 0)
|
|
12
|
+
<x-pulse::table>
|
|
13
|
+
<colgroup>
|
|
14
|
+
<col width="100%" />
|
|
15
|
+
<col width="0%" />
|
|
16
|
+
<col width="0%" />
|
|
17
|
+
</colgroup>
|
|
18
|
+
<x-pulse::thead>
|
|
19
|
+
<tr>
|
|
20
|
+
<x-pulse::th>Query</x-pulse::th>
|
|
21
|
+
<x-pulse::th class="text-right">Count</x-pulse::th>
|
|
22
|
+
<x-pulse::th class="text-right">Slowest</x-pulse::th>
|
|
23
|
+
</tr>
|
|
24
|
+
</x-pulse::thead>
|
|
25
|
+
<tbody>
|
|
26
|
+
@each(query in queries)
|
|
27
|
+
<tr class="h-2 first:h-0"></tr>
|
|
28
|
+
<tr>
|
|
29
|
+
<x-pulse::td class="max-w-[1px]">
|
|
30
|
+
<code class="block truncate text-xs text-gray-900 dark:text-gray-100" title="{{ query.sql }}">
|
|
31
|
+
{{ query.sql }}
|
|
32
|
+
</code>
|
|
33
|
+
</x-pulse::td>
|
|
34
|
+
<x-pulse::td class="text-right tabular-nums text-gray-700 dark:text-gray-300 font-bold whitespace-nowrap">
|
|
35
|
+
{{ formatNumber(query.count) }}
|
|
36
|
+
</x-pulse::td>
|
|
37
|
+
<x-pulse::td class="text-right tabular-nums whitespace-nowrap">
|
|
38
|
+
<span class="{{ query.slowest >= 1000 ? 'text-red-600 dark:text-red-400' : query.slowest >= 500 ? 'text-yellow-600 dark:text-yellow-400' : 'text-gray-700 dark:text-gray-300' }} font-bold">
|
|
39
|
+
{{ formatDuration(query.slowest) }}
|
|
40
|
+
</span>
|
|
41
|
+
</x-pulse::td>
|
|
42
|
+
</tr>
|
|
43
|
+
@end
|
|
44
|
+
</tbody>
|
|
45
|
+
</x-pulse::table>
|
|
46
|
+
@else
|
|
47
|
+
<x-pulse::no-results message="No slow queries" />
|
|
48
|
+
@end
|
|
49
|
+
</x-pulse::scroll>
|
|
50
|
+
</x-pulse::card>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<x-pulse::card :cols="cols ?? 6" :rows="rows ?? 1">
|
|
2
|
+
<x-pulse::card-header title="Slow Requests" :details="'past ' + period">
|
|
3
|
+
<x-slot name="icon">
|
|
4
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5">
|
|
5
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
|
6
|
+
</svg>
|
|
7
|
+
</x-slot>
|
|
8
|
+
</x-pulse::card-header>
|
|
9
|
+
|
|
10
|
+
<x-pulse::scroll>
|
|
11
|
+
@if(requests && requests.length > 0)
|
|
12
|
+
<x-pulse::table>
|
|
13
|
+
<colgroup>
|
|
14
|
+
<col width="0%" />
|
|
15
|
+
<col width="100%" />
|
|
16
|
+
<col width="0%" />
|
|
17
|
+
<col width="0%" />
|
|
18
|
+
</colgroup>
|
|
19
|
+
<x-pulse::thead>
|
|
20
|
+
<tr>
|
|
21
|
+
<x-pulse::th>Method</x-pulse::th>
|
|
22
|
+
<x-pulse::th>Route</x-pulse::th>
|
|
23
|
+
<x-pulse::th class="text-right">Count</x-pulse::th>
|
|
24
|
+
<x-pulse::th class="text-right">Slowest</x-pulse::th>
|
|
25
|
+
</tr>
|
|
26
|
+
</x-pulse::thead>
|
|
27
|
+
<tbody>
|
|
28
|
+
@each(request in requests)
|
|
29
|
+
<tr class="h-2 first:h-0"></tr>
|
|
30
|
+
<tr>
|
|
31
|
+
<x-pulse::td class="whitespace-nowrap">
|
|
32
|
+
<span class="inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium {{ request.method === 'GET' ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300' : request.method === 'POST' ? 'bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300' : request.method === 'PUT' || request.method === 'PATCH' ? 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300' : request.method === 'DELETE' ? 'bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300' : 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300' }}">
|
|
33
|
+
{{ request.method }}
|
|
34
|
+
</span>
|
|
35
|
+
</x-pulse::td>
|
|
36
|
+
<x-pulse::td class="max-w-[1px]">
|
|
37
|
+
<div class="truncate text-gray-900 dark:text-gray-100" title="{{ request.uri }}">
|
|
38
|
+
{{ request.uri }}
|
|
39
|
+
</div>
|
|
40
|
+
@if(request.action)
|
|
41
|
+
<div class="mt-0.5 truncate text-xs text-gray-500 dark:text-gray-400" title="{{ request.action }}">
|
|
42
|
+
{{ request.action }}
|
|
43
|
+
</div>
|
|
44
|
+
@end
|
|
45
|
+
</x-pulse::td>
|
|
46
|
+
<x-pulse::td class="text-right tabular-nums text-gray-700 dark:text-gray-300 font-bold whitespace-nowrap">
|
|
47
|
+
{{ formatNumber(request.count) }}
|
|
48
|
+
</x-pulse::td>
|
|
49
|
+
<x-pulse::td class="text-right tabular-nums whitespace-nowrap">
|
|
50
|
+
<span class="{{ request.slowest >= 1000 ? 'text-red-600 dark:text-red-400' : request.slowest >= 500 ? 'text-yellow-600 dark:text-yellow-400' : 'text-gray-700 dark:text-gray-300' }} font-bold">
|
|
51
|
+
{{ formatDuration(request.slowest) }}
|
|
52
|
+
</span>
|
|
53
|
+
</x-pulse::td>
|
|
54
|
+
</tr>
|
|
55
|
+
@end
|
|
56
|
+
</tbody>
|
|
57
|
+
</x-pulse::table>
|
|
58
|
+
@else
|
|
59
|
+
<x-pulse::no-results message="No slow requests" />
|
|
60
|
+
@end
|
|
61
|
+
</x-pulse::scroll>
|
|
62
|
+
</x-pulse::card>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<x-pulse::card :cols="cols ?? 4" :rows="rows ?? 2">
|
|
2
|
+
<x-pulse::card-header title="Application Usage">
|
|
3
|
+
<x-slot name="actions">
|
|
4
|
+
<select
|
|
5
|
+
wire:model.live="type"
|
|
6
|
+
class="block rounded border-0 bg-transparent py-0.5 pl-2 pr-7 text-xs text-gray-500 dark:text-gray-400 ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-indigo-600"
|
|
7
|
+
>
|
|
8
|
+
<option value="requests">Requests</option>
|
|
9
|
+
<option value="slow_requests">Slow Requests</option>
|
|
10
|
+
<option value="jobs">Jobs</option>
|
|
11
|
+
</select>
|
|
12
|
+
</x-slot>
|
|
13
|
+
</x-pulse::card-header>
|
|
14
|
+
|
|
15
|
+
<x-pulse::scroll>
|
|
16
|
+
@if(users && users.length > 0)
|
|
17
|
+
<div class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
18
|
+
@each(user in users)
|
|
19
|
+
<div class="flex items-center gap-3 px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
20
|
+
<div class="flex-shrink-0">
|
|
21
|
+
@if(user.avatar)
|
|
22
|
+
<img
|
|
23
|
+
src="{{ user.avatar }}"
|
|
24
|
+
alt="{{ user.name }}"
|
|
25
|
+
class="h-8 w-8 rounded-full object-cover"
|
|
26
|
+
/>
|
|
27
|
+
@else
|
|
28
|
+
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200 dark:bg-gray-700 text-sm font-medium text-gray-600 dark:text-gray-300">
|
|
29
|
+
{{ user.name ? user.name.charAt(0).toUpperCase() : '?' }}
|
|
30
|
+
</div>
|
|
31
|
+
@end
|
|
32
|
+
</div>
|
|
33
|
+
<div class="min-w-0 flex-1">
|
|
34
|
+
<div class="truncate text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
35
|
+
{{ user.name || 'Unknown' }}
|
|
36
|
+
</div>
|
|
37
|
+
@if(user.email)
|
|
38
|
+
<div class="truncate text-xs text-gray-500 dark:text-gray-400">
|
|
39
|
+
{{ user.email }}
|
|
40
|
+
</div>
|
|
41
|
+
@end
|
|
42
|
+
</div>
|
|
43
|
+
<div class="flex-shrink-0 text-right">
|
|
44
|
+
<div class="text-sm font-medium tabular-nums text-gray-900 dark:text-gray-100">
|
|
45
|
+
{{ formatNumber(user.count) }}
|
|
46
|
+
</div>
|
|
47
|
+
@if(user.percentage)
|
|
48
|
+
<div class="mt-0.5 h-1 w-16 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
|
|
49
|
+
<div
|
|
50
|
+
class="h-full rounded-full bg-indigo-500"
|
|
51
|
+
style="width: {{ user.percentage }}%"
|
|
52
|
+
></div>
|
|
53
|
+
</div>
|
|
54
|
+
@end
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
@end
|
|
58
|
+
</div>
|
|
59
|
+
@else
|
|
60
|
+
<x-pulse::no-results message="No usage data" />
|
|
61
|
+
@end
|
|
62
|
+
</x-pulse::scroll>
|
|
63
|
+
</x-pulse::card>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AggregationType, EntryAttributes } from './types.js';
|
|
2
|
+
export declare class Entry {
|
|
3
|
+
timestamp: number;
|
|
4
|
+
type: string;
|
|
5
|
+
key: string;
|
|
6
|
+
value: number | null;
|
|
7
|
+
protected aggregations: AggregationType[];
|
|
8
|
+
protected onlyBucketsFlag: boolean;
|
|
9
|
+
constructor(timestamp: number, type: string, key: string, value?: number | null);
|
|
10
|
+
count(): this;
|
|
11
|
+
min(): this;
|
|
12
|
+
max(): this;
|
|
13
|
+
sum(): this;
|
|
14
|
+
avg(): this;
|
|
15
|
+
onlyBuckets(): this;
|
|
16
|
+
getAggregations(): AggregationType[];
|
|
17
|
+
isCount(): boolean;
|
|
18
|
+
isMin(): boolean;
|
|
19
|
+
isMax(): boolean;
|
|
20
|
+
isSum(): boolean;
|
|
21
|
+
isAvg(): boolean;
|
|
22
|
+
isOnlyBuckets(): boolean;
|
|
23
|
+
attributes(): EntryAttributes;
|
|
24
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export class Entry {
|
|
2
|
+
timestamp;
|
|
3
|
+
type;
|
|
4
|
+
key;
|
|
5
|
+
value;
|
|
6
|
+
aggregations = [];
|
|
7
|
+
onlyBucketsFlag = false;
|
|
8
|
+
constructor(timestamp, type, key, value = null) {
|
|
9
|
+
this.timestamp = timestamp;
|
|
10
|
+
this.type = type;
|
|
11
|
+
this.key = key;
|
|
12
|
+
this.value = value;
|
|
13
|
+
}
|
|
14
|
+
count() {
|
|
15
|
+
this.aggregations.push('count');
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
min() {
|
|
19
|
+
this.aggregations.push('min');
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
max() {
|
|
23
|
+
this.aggregations.push('max');
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
sum() {
|
|
27
|
+
this.aggregations.push('sum');
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
avg() {
|
|
31
|
+
this.aggregations.push('avg');
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
onlyBuckets() {
|
|
35
|
+
this.onlyBucketsFlag = true;
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
getAggregations() {
|
|
39
|
+
return this.aggregations;
|
|
40
|
+
}
|
|
41
|
+
isCount() {
|
|
42
|
+
return this.aggregations.includes('count');
|
|
43
|
+
}
|
|
44
|
+
isMin() {
|
|
45
|
+
return this.aggregations.includes('min');
|
|
46
|
+
}
|
|
47
|
+
isMax() {
|
|
48
|
+
return this.aggregations.includes('max');
|
|
49
|
+
}
|
|
50
|
+
isSum() {
|
|
51
|
+
return this.aggregations.includes('sum');
|
|
52
|
+
}
|
|
53
|
+
isAvg() {
|
|
54
|
+
return this.aggregations.includes('avg');
|
|
55
|
+
}
|
|
56
|
+
isOnlyBuckets() {
|
|
57
|
+
return this.onlyBucketsFlag;
|
|
58
|
+
}
|
|
59
|
+
attributes() {
|
|
60
|
+
return {
|
|
61
|
+
timestamp: this.timestamp,
|
|
62
|
+
type: this.type,
|
|
63
|
+
key: this.key,
|
|
64
|
+
value: this.value,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { lazy } from 'adonisjs-livewire';
|
|
8
|
+
import { Card } from './card.js';
|
|
9
|
+
let Cache = class Cache extends Card {
|
|
10
|
+
async render() {
|
|
11
|
+
const totals = await this.aggregateTotal(['cache_hit', 'cache_miss'], 'count');
|
|
12
|
+
const allCacheInteractions = {
|
|
13
|
+
hits: totals['cache_hit'] ?? 0,
|
|
14
|
+
misses: totals['cache_miss'] ?? 0,
|
|
15
|
+
};
|
|
16
|
+
const cacheKeyInteractions = await this.aggregateTypes(['cache_hit', 'cache_miss'], 'count', 'cache_hit', 'desc', 101);
|
|
17
|
+
const keys = cacheKeyInteractions.map((row) => ({
|
|
18
|
+
key: row.key,
|
|
19
|
+
hits: row['cache_hit'] ?? 0,
|
|
20
|
+
misses: row['cache_miss'] ?? 0,
|
|
21
|
+
}));
|
|
22
|
+
return await this.view.render('pulse::livewire/cache', {
|
|
23
|
+
period: this.periodForHumans(),
|
|
24
|
+
config: { sampleRate: 1, groups: {} },
|
|
25
|
+
allCacheInteractions,
|
|
26
|
+
cacheKeyInteractions: keys,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
Cache = __decorate([
|
|
31
|
+
lazy()
|
|
32
|
+
], Cache);
|
|
33
|
+
export { Cache };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Component } from 'adonisjs-livewire';
|
|
2
|
+
import type { AggregationType, AggregateResult, AggregateTypesResult, StoredValue } from '../types.js';
|
|
3
|
+
export declare abstract class Card extends Component {
|
|
4
|
+
cols: number | 'full' | null;
|
|
5
|
+
rows: number | 'full' | null;
|
|
6
|
+
expand: boolean;
|
|
7
|
+
class: string;
|
|
8
|
+
period: string;
|
|
9
|
+
placeholder(): string;
|
|
10
|
+
placeholderData(): Record<string, any>;
|
|
11
|
+
protected periodAsInterval(): number;
|
|
12
|
+
protected periodForHumans(): string;
|
|
13
|
+
protected values(type: string, keys?: string[]): Promise<StoredValue[]>;
|
|
14
|
+
protected graph(types: string[], aggregate: AggregationType): Promise<Map<number, Map<string, number>>>;
|
|
15
|
+
protected aggregate(type: string, aggregates: AggregationType | AggregationType[], orderBy?: AggregationType, direction?: 'asc' | 'desc', limit?: number): Promise<Map<string, AggregateResult>>;
|
|
16
|
+
protected aggregateTypes(types: string[], aggregate: AggregationType, orderBy?: string, direction?: 'asc' | 'desc', limit?: number): Promise<AggregateTypesResult[]>;
|
|
17
|
+
protected aggregateTotal(types: string[], aggregate: AggregationType): Promise<Record<string, number>>;
|
|
18
|
+
protected formatBytes(bytes: number): string;
|
|
19
|
+
protected formatNumber(num: number): string;
|
|
20
|
+
protected formatDuration(ms: number): string;
|
|
21
|
+
protected timeAgo(timestamp: number): string;
|
|
22
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Component, url } from 'adonisjs-livewire';
|
|
8
|
+
import pulse from 'adonisjs-pulse/services/pulse';
|
|
9
|
+
export class Card extends Component {
|
|
10
|
+
cols = null;
|
|
11
|
+
rows = null;
|
|
12
|
+
expand = false;
|
|
13
|
+
class = '';
|
|
14
|
+
period = '1_hour';
|
|
15
|
+
placeholder() {
|
|
16
|
+
return '<div>Loading...</div>';
|
|
17
|
+
}
|
|
18
|
+
placeholderData() {
|
|
19
|
+
return {
|
|
20
|
+
cols: this.cols,
|
|
21
|
+
rows: this.rows,
|
|
22
|
+
class: this.class,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
periodAsInterval() {
|
|
26
|
+
switch (this.period) {
|
|
27
|
+
case '1_hour':
|
|
28
|
+
return 3600;
|
|
29
|
+
case '6_hours':
|
|
30
|
+
return 21600;
|
|
31
|
+
case '24_hours':
|
|
32
|
+
return 86400;
|
|
33
|
+
case '7_days':
|
|
34
|
+
return 604800;
|
|
35
|
+
default:
|
|
36
|
+
return 3600;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
periodForHumans() {
|
|
40
|
+
switch (this.period) {
|
|
41
|
+
case '1_hour':
|
|
42
|
+
return '1 hour';
|
|
43
|
+
case '6_hours':
|
|
44
|
+
return '6 hours';
|
|
45
|
+
case '24_hours':
|
|
46
|
+
return '24 hours';
|
|
47
|
+
case '7_days':
|
|
48
|
+
return '7 days';
|
|
49
|
+
default:
|
|
50
|
+
return '1 hour';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async values(type, keys) {
|
|
54
|
+
return pulse.getValues(type, keys);
|
|
55
|
+
}
|
|
56
|
+
async graph(types, aggregate) {
|
|
57
|
+
return pulse.graph(types, aggregate, this.periodAsInterval());
|
|
58
|
+
}
|
|
59
|
+
async aggregate(type, aggregates, orderBy, direction = 'desc', limit = 101) {
|
|
60
|
+
const aggregateArray = Array.isArray(aggregates) ? aggregates : [aggregates];
|
|
61
|
+
return pulse.aggregate(type, aggregateArray, this.periodAsInterval(), orderBy, direction, limit);
|
|
62
|
+
}
|
|
63
|
+
async aggregateTypes(types, aggregate, orderBy, direction = 'desc', limit = 101) {
|
|
64
|
+
return pulse.aggregateTypes(types, aggregate, this.periodAsInterval(), orderBy, direction, limit);
|
|
65
|
+
}
|
|
66
|
+
async aggregateTotal(types, aggregate) {
|
|
67
|
+
return pulse.aggregateTotal(types, aggregate, this.periodAsInterval());
|
|
68
|
+
}
|
|
69
|
+
formatBytes(bytes) {
|
|
70
|
+
if (bytes === 0)
|
|
71
|
+
return '0 B';
|
|
72
|
+
const k = 1024;
|
|
73
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
74
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
75
|
+
return `${Number.parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
76
|
+
}
|
|
77
|
+
formatNumber(num) {
|
|
78
|
+
return num.toLocaleString();
|
|
79
|
+
}
|
|
80
|
+
formatDuration(ms) {
|
|
81
|
+
return ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${ms}ms`;
|
|
82
|
+
}
|
|
83
|
+
timeAgo(timestamp) {
|
|
84
|
+
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
|
85
|
+
if (seconds < 60)
|
|
86
|
+
return `${seconds}s ago`;
|
|
87
|
+
if (seconds < 3600)
|
|
88
|
+
return `${Math.floor(seconds / 60)}m ago`;
|
|
89
|
+
if (seconds < 86400)
|
|
90
|
+
return `${Math.floor(seconds / 3600)}h ago`;
|
|
91
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
__decorate([
|
|
95
|
+
url()
|
|
96
|
+
], Card.prototype, "period", void 0);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { lazy } from 'adonisjs-livewire';
|
|
8
|
+
import { Card } from './card.js';
|
|
9
|
+
let Exceptions = class Exceptions extends Card {
|
|
10
|
+
async render() {
|
|
11
|
+
const aggregateData = await this.aggregate('exception', ['count'], 'count');
|
|
12
|
+
const exceptions = Array.from(aggregateData.entries()).map(([key, row]) => {
|
|
13
|
+
const [className, location] = JSON.parse(key);
|
|
14
|
+
return {
|
|
15
|
+
class: className,
|
|
16
|
+
location,
|
|
17
|
+
count: row.count ?? 0,
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
return await this.view.render('pulse::livewire/exceptions', {
|
|
21
|
+
period: this.periodForHumans(),
|
|
22
|
+
exceptions,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
Exceptions = __decorate([
|
|
27
|
+
lazy()
|
|
28
|
+
], Exceptions);
|
|
29
|
+
export { Exceptions };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Component, url } from 'adonisjs-livewire';
|
|
8
|
+
export class PeriodSelector extends Component {
|
|
9
|
+
period = '1_hour';
|
|
10
|
+
async render() {
|
|
11
|
+
return await this.view.render('pulse::livewire/period-selector');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
__decorate([
|
|
15
|
+
url()
|
|
16
|
+
], PeriodSelector.prototype, "period", void 0);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { lazy } from 'adonisjs-livewire';
|
|
8
|
+
import { Card } from './card.js';
|
|
9
|
+
let Servers = class Servers extends Card {
|
|
10
|
+
async render() {
|
|
11
|
+
const values = await this.values('system');
|
|
12
|
+
const servers = values
|
|
13
|
+
.map((s) => {
|
|
14
|
+
try {
|
|
15
|
+
const data = JSON.parse(s.value);
|
|
16
|
+
return {
|
|
17
|
+
...data,
|
|
18
|
+
recently_reported: Date.now() / 1000 - s.timestamp < 60,
|
|
19
|
+
updated_at: s.timestamp * 1000,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
return await this.view.render('pulse::livewire/servers', {
|
|
28
|
+
period: this.periodForHumans(),
|
|
29
|
+
servers,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
Servers = __decorate([
|
|
34
|
+
lazy()
|
|
35
|
+
], Servers);
|
|
36
|
+
export { Servers };
|