Skip to content

Commit 60d2fdf

Browse files
Ionys320jbrooksuk
authored andcommitted
feat(metrics): Implementing metrics into the status page
1 parent a34c060 commit 60d2fdf

File tree

7 files changed

+236
-3
lines changed

7 files changed

+236
-3
lines changed

package-lock.json

Lines changed: 61 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
"@alpinejs/collapse": "^3.13.3",
3131
"@alpinejs/focus": "^3.13.3",
3232
"@alpinejs/ui": "^3.13.3-beta.4",
33-
"alpinejs": "^3.13.3"
33+
"alpinejs": "^3.13.3",
34+
"chart.js": "^4.4.2",
35+
"chartjs-adapter-moment": "^1.0.1",
36+
"moment": "^2.30.1"
3437
}
3538
}

resources/js/cachet.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
import 'moment';
2+
import Chart from 'chart.js/auto'
3+
import 'chartjs-adapter-moment';
4+
15
import Alpine from 'alpinejs'
26

37
import Anchor from '@alpinejs/anchor'
48
import Collapse from '@alpinejs/collapse'
59
import Focus from '@alpinejs/focus'
610
import Ui from '@alpinejs/ui'
711

12+
Chart.defaults.color = '#fff';
13+
window.Chart = Chart
14+
815
Alpine.plugin(Anchor)
916
Alpine.plugin(Collapse)
1017
Alpine.plugin(Focus)
1118
Alpine.plugin(Ui)
1219

13-
Alpine.start()
20+
window.Alpine = Alpine
21+
Alpine.start()
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
@props([
2+
'metric',
3+
])
4+
5+
<div x-data="chart">
6+
<div class="flex flex-col gap-2">
7+
<div class="flex items-center gap-1.5">
8+
<div class="font-semibold leading-6">{{ $metric->name }}</div>
9+
10+
<div x-data x-popover class="flex items-center">
11+
<button x-ref="anchor" x-popover:button>
12+
<x-heroicon-o-question-mark-circle class="size-4 text-zinc-500 dark:text-zinc-300" />
13+
</button>
14+
<div x-popover:panel x-cloak x-transition.opacity x-anchor.right.offset.8="$refs.anchor" class="rounded bg-white px-2 py-1 text-xs font-medium text-zinc-800 drop-shadow dark:text-zinc-800">
15+
<span class="pointer-events-none absolute -left-1 top-1.5 size-4 rotate-45 bg-white"></span>
16+
<p class="relative">{{ $metric->description }}</p>
17+
</div>
18+
</div>
19+
20+
<!-- Period Selector -->
21+
<select x-model="period" class="ml-auto rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm font-medium">
22+
<option value="0">Last Hour</option>
23+
<option value="1">Today</option>
24+
<option value="2">Week</option>
25+
<option value="3">Month</option>
26+
</select>
27+
</div>
28+
<canvas x-ref="canvas" height="300" class="ring-1 ring-gray-900/5 dark:ring-gray-100/10 bg-gray-50 dark:bg-gray-800 rounded-md shadow-sm text-white"></canvas>
29+
</div>
30+
</div>
31+
32+
<script>
33+
document.addEventListener('alpine:init', () => {
34+
Alpine.data('chart', () => ({
35+
metric: <?php echo json_encode($metric); ?>,
36+
period: <?php echo json_encode($metric->defaultView); ?>,
37+
points: [
38+
[],
39+
[],
40+
[],
41+
[]
42+
],
43+
chart: null,
44+
init,
45+
}))
46+
})
47+
</script>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<script>
2+
const now = new Date();
3+
const previousHour = new Date(now - 60 * 60 * 1000);
4+
const previous24Hours = new Date(now - 24 * 60 * 60 * 1000);
5+
const previous7Days = new Date(now - 7 * 24 * 60 * 60 * 1000);
6+
const previous30Days = new Date(now - 30 * 24 * 60 * 60 * 1000);
7+
8+
function init() {
9+
// Parse metric points
10+
const metricPoints = this.metric.metric_points.map((point) => {
11+
return {
12+
x: new Date(point.x),
13+
y: point.y
14+
}
15+
});
16+
17+
// Filter points based on the selected period
18+
this.points[0] = metricPoints.filter((point) => point.x >= previousHour);
19+
this.points[1] = metricPoints.filter((point) => point.x >= previous24Hours);
20+
this.points[2] = metricPoints.filter((point) => point.x >= previous7Days);
21+
this.points[3] = metricPoints.filter((point) => point.x >= previous30Days);
22+
23+
// Initialize chart
24+
const chart = new Chart(this.$refs.canvas, {
25+
type: 'line',
26+
data: {
27+
datasets: [{
28+
label: this.metric.name,
29+
data: this.points[this.period],
30+
fill: false,
31+
borderColor: 'rgb(75, 192, 192)',
32+
tension: 0.1
33+
}],
34+
},
35+
options: {
36+
scales: {
37+
x: {
38+
type: 'timeseries',
39+
}
40+
},
41+
}
42+
});
43+
44+
this.$watch('period', () => {
45+
chart.data.datasets[0].data = this.points[this.period];
46+
chart.update();
47+
});
48+
}
49+
</script>
50+
51+
<div class="flex flex-col gap-8">
52+
@foreach($metrics as $metric)
53+
<x-cachet::metric :metric="$metric" />
54+
<x-cachet::metric :metric="$metric" />
55+
@endforeach
56+
</div>

resources/views/status-page/index.blade.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
<x-cachet::component-ungrouped :component="$component" />
1515
@endforeach
1616

17+
<x-cachet::metrics />
18+
1719
@if($schedules->isNotEmpty())
1820
<x-cachet::schedules :schedules="$schedules" />
1921
@endif

src/View/Components/Metrics.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Cachet\View\Components;
4+
5+
use Cachet\Models\Metric;
6+
use Cachet\Settings\AppSettings;
7+
use Illuminate\Contracts\View\View;
8+
use Illuminate\Database\Eloquent\Builder;
9+
use Illuminate\Support\Carbon;
10+
use Illuminate\Support\Collection;
11+
use Illuminate\View\Component;
12+
13+
class Metrics extends Component
14+
{
15+
public function __construct(private AppSettings $appSettings)
16+
{
17+
//
18+
}
19+
20+
public function render(): View
21+
{
22+
$startDate = Carbon::now()->subDays(30);
23+
24+
$metrics = $this->metrics($startDate);
25+
26+
// Convert each metric point to Chart.js format (x, y)
27+
$metrics->each(function ($metric) {
28+
$metric->metricPoints->transform(function ($point) {
29+
return [
30+
'x' => $point->created_at->toIso8601String(),
31+
'y' => $point->value,
32+
];
33+
});
34+
});
35+
36+
return view('cachet::components.metrics', [
37+
'metrics' => $metrics
38+
]);
39+
}
40+
41+
/**
42+
* Fetch the available metrics and their points.
43+
*/
44+
private function metrics(Carbon $startDate): Collection
45+
{
46+
return Metric::query()
47+
->with([
48+
'metricPoints' => fn ($query) => $query->orderBy('created_at'),
49+
])
50+
->where('visible', '>=', !auth()->check())
51+
->whereHas('metricPoints', function (Builder $query) use ($startDate) {
52+
$query->where('created_at', '>=', $startDate);
53+
})
54+
->orderBy('places', 'asc')
55+
->get();
56+
}
57+
}

0 commit comments

Comments
 (0)