Merge pull request #6657 from ShadowArcanist/shadow/metrics-visual-improvements

chore(ui): improve sentinel metrics
This commit is contained in:
Andras Bacsai
2025-09-22 09:46:26 +02:00
committed by GitHub
5 changed files with 320 additions and 223 deletions

View File

@@ -8,7 +8,7 @@ class Metrics extends Component
{ {
public $resource; public $resource;
public $chartId = 'container-cpu'; public $chartId = 'metrics';
public $data; public $data;

View File

@@ -6,10 +6,31 @@
@apply hidden!; @apply hidden!;
} }
@utility apexcharts-grid-borders {
@apply dark:hidden!;
}
@utility apexcharts-xaxistooltip { @utility apexcharts-xaxistooltip {
@apply hidden!; @apply hidden!;
} }
@utility apexcharts-tooltip-custom {
@apply bg-white dark:bg-coolgray-100 border border-neutral-200 dark:border-coolgray-300 rounded-lg shadow-lg p-3 text-sm;
min-width: 160px;
}
@utility apexcharts-tooltip-custom-value {
@apply text-neutral-700 dark:text-neutral-300 mb-1;
}
@utility apexcharts-tooltip-value-bold {
@apply font-bold text-black dark:text-white;
}
@utility apexcharts-tooltip-custom-title {
@apply text-xs text-neutral-500 dark:text-neutral-400 font-medium;
}
@utility input-sticky { @utility input-sticky {
@apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-1 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 focus:ring-2 focus:ring-neutral-400 dark:focus:ring-coolgray-300; @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-1 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 focus:ring-2 focus:ring-neutral-400 dark:focus:ring-coolgray-300;
} }

View File

@@ -138,7 +138,8 @@
} }
} }
let theme = localStorage.theme let theme = localStorage.theme
let baseColor = '#FCD452' let cpuColor = '#1e90ff'
let ramColor = '#00ced1'
let textColor = '#ffffff' let textColor = '#ffffff'
let editorBackground = '#181818' let editorBackground = '#181818'
let editorTheme = 'blackboard' let editorTheme = 'blackboard'
@@ -149,12 +150,14 @@
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
} }
if (theme == 'dark') { if (theme == 'dark') {
baseColor = '#FCD452' cpuColor = '#1e90ff'
ramColor = '#00ced1'
textColor = '#ffffff' textColor = '#ffffff'
editorBackground = '#181818' editorBackground = '#181818'
editorTheme = 'blackboard' editorTheme = 'blackboard'
} else { } else {
baseColor = 'black' cpuColor = '#1e90ff'
ramColor = '#00ced1'
textColor = '#000000' textColor = '#000000'
editorBackground = '#ffffff' editorBackground = '#ffffff'
editorTheme = null editorTheme = null

View File

@@ -1,21 +1,20 @@
<div> <div>
<div class="flex items-center gap-2 "> <div class="flex items-center gap-2">
<h2>Metrics</h2> <h2>Metrics</h2>
</div> </div>
<div class="pb-4">Basic metrics for your container.</div> <div class="pb-4">Basic metrics for your application container.</div>
@if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') <div>
<div class="alert alert-warning">Metrics are not available for Docker Compose applications yet!</div> @if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose')
@elseif(!$resource->destination->server->isMetricsEnabled()) <div class="alert alert-warning">Metrics are not available for Docker Compose applications yet!</div>
<div class="alert alert-warning">Metrics are only available for servers with Sentinel & Metrics enabled!</div> @elseif(!$resource->destination->server->isMetricsEnabled())
<div> Go to <a class="underline dark:text-white" <div class="alert alert-warning">Metrics are only available for servers with Sentinel & Metrics enabled!</div>
href="{{ route('server.show', $resource->destination->server->uuid) }}">Server settings</a> to <div>Go to <a class="underline dark:text-white" href="{{ route('server.show', $resource->destination->server->uuid) }}">Server settings</a> to enable it.</div>
enable
it.</div>
@else
@if (!str($resource->status)->contains('running'))
<div class="alert alert-warning">Metrics are only available when this resource is running!</div>
@else @else
<x-forms.select label="Interval" wire:change="setInterval" id="interval"> @if (!str($resource->status)->contains('running'))
<div class="alert alert-warning">Metrics are only available when the application container is running!</div>
@else
<div>
<x-forms.select label="Interval" wire:change="setInterval" id="interval">
<option value="5">5 minutes (live)</option> <option value="5">5 minutes (live)</option>
<option value="10">10 minutes (live)</option> <option value="10">10 minutes (live)</option>
<option value="30">30 minutes</option> <option value="30">30 minutes</option>
@@ -26,7 +25,7 @@
</x-forms.select> </x-forms.select>
<div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()" <div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()"
class="pt-5"> class="pt-5">
<h4>CPU (%)</h4> <h4>CPU Usage</h4>
<div wire:ignore id="{!! $chartId !!}-cpu"></div> <div wire:ignore id="{!! $chartId !!}-cpu"></div>
<script> <script>
@@ -34,6 +33,7 @@
const optionsServerCpu = { const optionsServerCpu = {
stroke: { stroke: {
curve: 'straight', curve: 'straight',
width: 2,
}, },
chart: { chart: {
height: '150px', height: '150px',
@@ -52,7 +52,7 @@
}, },
}, },
animations: { animations: {
enabled: false, enabled: true,
}, },
}, },
fill: { fill: {
@@ -68,74 +68,90 @@
enabled: false, enabled: false,
} }
}, },
grid: { grid: {
show: true, show: true,
borderColor: '', borderColor: '',
}, },
colors: [baseColor], colors: [cpuColor],
xaxis: { xaxis: {
type: 'datetime', type: 'datetime',
}, },
series: [{ series: [{
name: "CPU %", name: "CPU %",
data: [] data: []
}], }],
noData: { noData: {
text: 'Loading...', text: 'Loading...',
style: { style: {
color: textColor, color: textColor,
} }
}, },
tooltip: { tooltip: {
enabled: true, enabled: true,
marker: { marker: {
show: false, show: false,
} },
}, custom: function({ series, seriesIndex, dataPointIndex, w }) {
legend: { const value = series[seriesIndex][dataPointIndex];
show: false const timestamp = w.globals.seriesX[seriesIndex][dataPointIndex];
} const date = new Date(timestamp);
const timeString = String(date.getUTCHours()).padStart(2, '0') + ':' +
String(date.getUTCMinutes()).padStart(2, '0') + ':' +
String(date.getUTCSeconds()).padStart(2, '0') + ', ' +
date.getUTCFullYear() + '-' +
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
String(date.getUTCDate()).padStart(2, '0');
return '<div class="apexcharts-tooltip-custom">' +
'<div class="apexcharts-tooltip-custom-value">CPU: <span class="apexcharts-tooltip-value-bold">' + value + '%</span></div>' +
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
'</div>';
}
},
legend: {
show: false
}
} }
const serverCpuChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-cpu`), optionsServerCpu); const serverCpuChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-cpu`), optionsServerCpu);
serverCpuChart.render(); serverCpuChart.render();
document.addEventListener('livewire:init', () => { Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => {
Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => { checkTheme();
checkTheme(); serverCpuChart.updateOptions({
serverCpuChart.updateOptions({ series: [{
series: [{ data: chartData[0].seriesData,
data: chartData[0].seriesData, }],
}], colors: [cpuColor],
colors: [baseColor], xaxis: {
xaxis: { type: 'datetime',
type: 'datetime', labels: {
labels: { show: true,
show: true, style: {
style: { colors: textColor,
colors: textColor, }
} }
} },
}, yaxis: {
yaxis: { show: true,
show: true, labels: {
labels: { show: true,
show: true, style: {
style: { colors: textColor,
colors: textColor, },
} formatter: function(value) {
} return Math.round(value) + ' %';
}, }
noData: { }
text: 'Loading...', },
style: { noData: {
color: textColor, text: 'Loading...',
} style: {
} color: textColor,
}); }
}); }
}); });
});
</script> </script>
<h3>Memory (MB)</h3> <h4>Memory Usage</h4>
<div wire:ignore id="{!! $chartId !!}-memory"></div> <div wire:ignore id="{!! $chartId !!}-memory"></div>
<script> <script>
@@ -143,6 +159,7 @@
const optionsServerMemory = { const optionsServerMemory = {
stroke: { stroke: {
curve: 'straight', curve: 'straight',
width: 2,
}, },
chart: { chart: {
height: '150px', height: '150px',
@@ -161,7 +178,7 @@
}, },
}, },
animations: { animations: {
enabled: false, enabled: true,
}, },
}, },
fill: { fill: {
@@ -177,81 +194,99 @@
enabled: false, enabled: false,
} }
}, },
grid: { grid: {
show: true, show: true,
borderColor: '', borderColor: '',
}, },
colors: [baseColor], colors: [ramColor],
xaxis: { xaxis: {
type: 'datetime', type: 'datetime',
labels: { labels: {
show: true, show: true,
style: { style: {
colors: textColor, colors: textColor,
} }
} }
}, },
series: [{ series: [{
name: "Memory (MB)", name: "Memory (MB)",
data: [] data: []
}], }],
noData: { noData: {
text: 'Loading...', text: 'Loading...',
style: { style: {
color: textColor, color: textColor,
} }
}, },
tooltip: { tooltip: {
enabled: true, enabled: true,
marker: { marker: {
show: false, show: false,
} },
}, custom: function({ series, seriesIndex, dataPointIndex, w }) {
legend: { const value = series[seriesIndex][dataPointIndex];
show: false const timestamp = w.globals.seriesX[seriesIndex][dataPointIndex];
} const date = new Date(timestamp);
const timeString = String(date.getUTCHours()).padStart(2, '0') + ':' +
String(date.getUTCMinutes()).padStart(2, '0') + ':' +
String(date.getUTCSeconds()).padStart(2, '0') + ', ' +
date.getUTCFullYear() + '-' +
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
String(date.getUTCDate()).padStart(2, '0');
return '<div class="apexcharts-tooltip-custom">' +
'<div class="apexcharts-tooltip-custom-value">Memory: <span class="apexcharts-tooltip-value-bold">' + value + ' MB</span></div>' +
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
'</div>';
}
},
legend: {
show: false
}
} }
const serverMemoryChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-memory`), const serverMemoryChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-memory`),
optionsServerMemory); optionsServerMemory);
serverMemoryChart.render(); serverMemoryChart.render();
document.addEventListener('livewire:init', () => { Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => {
Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => { checkTheme();
checkTheme(); serverMemoryChart.updateOptions({
serverMemoryChart.updateOptions({ series: [{
series: [{ data: chartData[0].seriesData,
data: chartData[0].seriesData, }],
}], colors: [ramColor],
colors: [baseColor], xaxis: {
xaxis: { type: 'datetime',
type: 'datetime', labels: {
labels: { show: true,
show: true, style: {
style: { colors: textColor,
colors: textColor, }
} }
} },
}, yaxis: {
yaxis: { min: 0,
min: 0, show: true,
show: true, labels: {
labels: { show: true,
show: true, style: {
style: { colors: textColor,
colors: textColor, },
} formatter: function(value) {
} return Math.round(value) + ' MB';
}, }
noData: { }
text: 'Loading...', },
style: { noData: {
color: textColor, text: 'Loading...',
} style: {
} color: textColor,
}); }
}); }
}); });
});
</script> </script>
</div> </div>
</div>
@endif @endif
@endif @endif
</div>
</div> </div>

View File

@@ -7,7 +7,7 @@
<x-server.sidebar :server="$server" activeMenu="metrics" /> <x-server.sidebar :server="$server" activeMenu="metrics" />
<div class="w-full"> <div class="w-full">
<h2>Metrics</h2> <h2>Metrics</h2>
<div class="pb-4">Basic metrics for your container.</div> <div class="pb-4">Basic metrics for your server.</div>
@if ($server->isMetricsEnabled()) @if ($server->isMetricsEnabled())
<div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()"> <div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()">
<x-forms.select label="Interval" wire:change="setInterval" id="interval"> <x-forms.select label="Interval" wire:change="setInterval" id="interval">
@@ -19,7 +19,7 @@
<option value="10080">1 week</option> <option value="10080">1 week</option>
<option value="43200">30 days</option> <option value="43200">30 days</option>
</x-forms.select> </x-forms.select>
<h4 class="pt-4">CPU (%)</h4> <h4 class="pt-4">CPU Usage</h4>
<div wire:ignore id="{!! $chartId !!}-cpu"></div> <div wire:ignore id="{!! $chartId !!}-cpu"></div>
<script> <script>
@@ -27,6 +27,7 @@
const optionsServerCpu = { const optionsServerCpu = {
stroke: { stroke: {
curve: 'straight', curve: 'straight',
width: 2,
}, },
chart: { chart: {
height: '150px', height: '150px',
@@ -45,7 +46,7 @@
}, },
}, },
animations: { animations: {
enabled: false, enabled: true,
}, },
}, },
fill: { fill: {
@@ -61,16 +62,16 @@
enabled: false, enabled: false,
} }
}, },
grid: { grid: {
show: true, show: true,
borderColor: '', borderColor: '',
}, },
colors: [baseColor], colors: [cpuColor],
xaxis: { xaxis: {
type: 'datetime', type: 'datetime',
}, },
series: [{ series: [{
name: 'CPU %', name: 'CPU %',
data: [] data: []
}], }],
noData: { noData: {
@@ -79,12 +80,27 @@
color: textColor, color: textColor,
} }
}, },
tooltip: { tooltip: {
enabled: true, enabled: true,
marker: { marker: {
show: false, show: false,
} },
}, custom: function({ series, seriesIndex, dataPointIndex, w }) {
const value = series[seriesIndex][dataPointIndex];
const timestamp = w.globals.seriesX[seriesIndex][dataPointIndex];
const date = new Date(timestamp);
const timeString = String(date.getUTCHours()).padStart(2, '0') + ':' +
String(date.getUTCMinutes()).padStart(2, '0') + ':' +
String(date.getUTCSeconds()).padStart(2, '0') + ', ' +
date.getUTCFullYear() + '-' +
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
String(date.getUTCDate()).padStart(2, '0');
return '<div class="apexcharts-tooltip-custom">' +
'<div class="apexcharts-tooltip-custom-value">CPU: <span class="apexcharts-tooltip-value-bold">' + value + '%</span></div>' +
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
'</div>';
}
},
legend: { legend: {
show: false show: false
} }
@@ -95,11 +111,11 @@
document.addEventListener('livewire:init', () => { document.addEventListener('livewire:init', () => {
Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => { Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => {
checkTheme(); checkTheme();
serverCpuChart.updateOptions({ serverCpuChart.updateOptions({
series: [{ series: [{
data: chartData[0].seriesData, data: chartData[0].seriesData,
}], }],
colors: [baseColor], colors: [cpuColor],
xaxis: { xaxis: {
type: 'datetime', type: 'datetime',
labels: { labels: {
@@ -109,15 +125,18 @@
} }
} }
}, },
yaxis: { yaxis: {
show: true, show: true,
labels: { labels: {
show: true, show: true,
style: { style: {
colors: textColor, colors: textColor,
} },
} formatter: function(value) {
}, return Math.round(value) + ' %';
}
}
},
noData: { noData: {
text: 'Loading...', text: 'Loading...',
style: { style: {
@@ -130,7 +149,7 @@
</script> </script>
<div> <div>
<h4>Memory (%)</h4> <h4>Memory Usage</h4>
<div wire:ignore id="{!! $chartId !!}-memory"></div> <div wire:ignore id="{!! $chartId !!}-memory"></div>
<script> <script>
@@ -138,6 +157,7 @@
const optionsServerMemory = { const optionsServerMemory = {
stroke: { stroke: {
curve: 'straight', curve: 'straight',
width: 2,
}, },
chart: { chart: {
height: '150px', height: '150px',
@@ -156,7 +176,7 @@
}, },
}, },
animations: { animations: {
enabled: false, enabled: true,
}, },
}, },
fill: { fill: {
@@ -172,15 +192,15 @@
enabled: false, enabled: false,
} }
}, },
grid: { grid: {
show: true, show: true,
borderColor: '', borderColor: '',
}, },
colors: [baseColor], colors: [ramColor],
xaxis: { xaxis: {
type: 'datetime', type: 'datetime',
labels: { labels: {
show: true, show: true,
style: { style: {
colors: textColor, colors: textColor,
} }
@@ -196,12 +216,27 @@
color: textColor, color: textColor,
} }
}, },
tooltip: { tooltip: {
enabled: true, enabled: true,
marker: { marker: {
show: false, show: false,
} },
}, custom: function({ series, seriesIndex, dataPointIndex, w }) {
const value = series[seriesIndex][dataPointIndex];
const timestamp = w.globals.seriesX[seriesIndex][dataPointIndex];
const date = new Date(timestamp);
const timeString = String(date.getUTCHours()).padStart(2, '0') + ':' +
String(date.getUTCMinutes()).padStart(2, '0') + ':' +
String(date.getUTCSeconds()).padStart(2, '0') + ', ' +
date.getUTCFullYear() + '-' +
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
String(date.getUTCDate()).padStart(2, '0');
return '<div class="apexcharts-tooltip-custom">' +
'<div class="apexcharts-tooltip-custom-value">Memory: <span class="apexcharts-tooltip-value-bold">' + value + '%</span></div>' +
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
'</div>';
}
},
legend: { legend: {
show: false show: false
} }
@@ -212,11 +247,11 @@
document.addEventListener('livewire:init', () => { document.addEventListener('livewire:init', () => {
Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => { Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => {
checkTheme(); checkTheme();
serverMemoryChart.updateOptions({ serverMemoryChart.updateOptions({
series: [{ series: [{
data: chartData[0].seriesData, data: chartData[0].seriesData,
}], }],
colors: [baseColor], colors: [ramColor],
xaxis: { xaxis: {
type: 'datetime', type: 'datetime',
labels: { labels: {
@@ -226,16 +261,19 @@
} }
} }
}, },
yaxis: { yaxis: {
min: 0, min: 0,
show: true, show: true,
labels: { labels: {
show: true, show: true,
style: { style: {
colors: textColor, colors: textColor,
} },
} formatter: function(value) {
}, return Math.round(value) + ' %';
}
}
},
noData: { noData: {
text: 'Loading...', text: 'Loading...',
style: { style: {