diff --git a/package-lock.json b/package-lock.json index a1afcae1..eaba4ec9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,13 @@ { - "name": "cachet-core", + "name": "cachethq-core", "lockfileVersion": 3, "requires": true, "packages": { "": { + "dependencies": { + "chart.js": "^4.4.7", + "chartjs-adapter-moment": "^1.0.1" + }, "devDependencies": { "@alpinejs/anchor": "^3.13.3", "@alpinejs/collapse": "^3.13.3", @@ -530,6 +534,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1065,6 +1075,28 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chart.js": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz", + "integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chartjs-adapter-moment": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz", + "integrity": "sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==", + "license": "MIT", + "peerDependencies": { + "chart.js": ">=3.0.0", + "moment": "^2.10.2" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -1893,6 +1925,16 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", diff --git a/package.json b/package.json index b046fb37..58670969 100644 --- a/package.json +++ b/package.json @@ -23,5 +23,9 @@ "prettier-plugin-tailwindcss": "^0.5.11", "tailwindcss": "^3.4.13", "vite": "^5.4" + }, + "dependencies": { + "chart.js": "^4.4.7", + "chartjs-adapter-moment": "^1.0.1" } } diff --git a/resources/js/cachet.js b/resources/js/cachet.js index 50005015..d286c497 100644 --- a/resources/js/cachet.js +++ b/resources/js/cachet.js @@ -1,3 +1,6 @@ +import Chart from 'chart.js/auto' +import 'chartjs-adapter-moment' + import Alpine from 'alpinejs' import Anchor from '@alpinejs/anchor' @@ -5,9 +8,13 @@ import Collapse from '@alpinejs/collapse' import Focus from '@alpinejs/focus' import Ui from '@alpinejs/ui' +Chart.defaults.color = '#fff' +window.Chart = Chart + Alpine.plugin(Anchor) Alpine.plugin(Collapse) Alpine.plugin(Focus) Alpine.plugin(Ui) +window.Alpine = Alpine Alpine.start() diff --git a/resources/views/components/component-group.blade.php b/resources/views/components/component-group.blade.php index b714c40f..b20f93db 100644 --- a/resources/views/components/component-group.blade.php +++ b/resources/views/components/component-group.blade.php @@ -1,12 +1,15 @@ @props(['componentGroup' => null]) {{ \Cachet\Facades\CachetView::renderHook(\Cachet\View\RenderHook::STATUS_PAGE_COMPONENT_GROUPS_BEFORE) }} -
merge(array_filter([ - 'default-open' => $componentGroup->isExpanded(), - ])) - ->class(['overflow-hidden rounded-lg border dark:border-zinc-700']) - }}> +
merge( + array_filter([ + 'default-open' => $componentGroup->isExpanded(), + ]), + ) + ->class(['overflow-hidden rounded-lg border dark:border-zinc-700']) +}}>
- @if(($incidentCount = $componentGroup->components->sum('incidents_count')) > 0) - - {{ trans_choice('cachet::component_group.incident_count', $incidentCount) }} - + @if (($incidentCount = $componentGroup->components->sum('incidents_count')) > 0) + + {{ trans_choice('cachet::component_group.incident_count', $incidentCount) }} + @endif
    - @foreach($componentGroup->components as $component) - + @foreach ($componentGroup->components as $component) + @endforeach
diff --git a/resources/views/components/metric.blade.php b/resources/views/components/metric.blade.php new file mode 100644 index 00000000..6bc8b6c5 --- /dev/null +++ b/resources/views/components/metric.blade.php @@ -0,0 +1,43 @@ +@props([ + 'metric', +]) + +@use('\Cachet\Enums\MetricViewEnum') + +
+
+
+
{{ $metric->name }}
+ +
+ +
+ +

{{ $metric->description }}

+
+
+ + + +
+ +
+
+ + diff --git a/resources/views/components/metrics.blade.php b/resources/views/components/metrics.blade.php new file mode 100644 index 00000000..52c3616f --- /dev/null +++ b/resources/views/components/metrics.blade.php @@ -0,0 +1,125 @@ + + +
+ @foreach ($metrics as $metric) + + @endforeach +
diff --git a/resources/views/status-page/index.blade.php b/resources/views/status-page/index.blade.php index 5fef4e98..bb9092e9 100644 --- a/resources/views/status-page/index.blade.php +++ b/resources/views/status-page/index.blade.php @@ -1,21 +1,22 @@ -
+
- - @foreach($componentGroups as $componentGroup) - + @foreach ($componentGroups as $componentGroup) + @endforeach - @foreach($ungroupedComponents as $component) - + @foreach ($ungroupedComponents as $component) + @endforeach - @if($schedules->isNotEmpty()) - + + + @if ($schedules->isNotEmpty()) + @endif diff --git a/src/View/Components/Metrics.php b/src/View/Components/Metrics.php new file mode 100644 index 00000000..a500e050 --- /dev/null +++ b/src/View/Components/Metrics.php @@ -0,0 +1,55 @@ +subDays(30); + + $metrics = $this->metrics($startDate); + + // Convert each metric point to Chart.js format (x, y) + $metrics->each(function ($metric) { + $metric->metricPoints->transform(fn ($point) => [ + 'x' => $point->created_at->utc(), + 'y' => $point->value, + ]); + }); + + return view('cachet::components.metrics', [ + 'metrics' => $metrics + ]); + } + + /** + * Fetch the available metrics and their points. + */ + private function metrics(Carbon $startDate): Collection + { + return Metric::query() + ->with([ + 'metricPoints' => fn ($query) => $query->orderBy('created_at'), + ]) + ->where('display_chart', '=', 1) + ->where('visible', '=', 1) + ->where('visible', '>=', !auth()->check()) + ->whereHas('metricPoints', fn (Builder $query) => $query->where('created_at', '>=', $startDate)) + ->orderBy('places', 'asc') + ->get(); + } +}