Changed heartbeats component and simplified HTML structure

This commit is contained in:
Filip Znachor 2023-11-02 15:19:52 +01:00
parent a338deefae
commit 1523136573
15 changed files with 163 additions and 132 deletions

View file

@ -2,16 +2,32 @@
require_once("../vendor/autoload.php");
function timediffmin() {
return new \Twig\TwigFilter('timediffmin', function ($ago, $now) {
return round((strtotime($now) - strtotime($ago)) / 60);
});
function globalstatus() {
return new \Twig\TwigFilter('globalstatus', function (int $online, int $total) {
if ($online == $total) return 1;
elseif ($online == 0) return 0;
else return 2;
});
}
function isof() {
return new \Twig\TwigFilter('isof', function ($online, $total) {
if($online == $total) return "all";
elseif($online == 0) return "none";
else return "some";
function globalstatustext() {
return new \Twig\TwigFilter('globalstatustext', function (int $status) {
$msgs = [
"none",
"all",
"some"
];
return "header.title.$msgs[$status]";
});
}
function statusicon() {
return new \Twig\TwigFilter('statusicon', function (int $status) {
$icons = [
0 => "error",
1 => "success",
2 => "warning"
];
return "/icon/{$icons[$status]}.svg";
});
}

View file

@ -34,8 +34,9 @@ class UptimeStatus {
$loader = new \Twig\Loader\FilesystemLoader("../view/");
$twig = new \Twig\Environment($loader, $twig_config);
$twig->addFilter(\Filters\timediffmin());
$twig->addFilter(\Filters\isof());
$twig->addFilter(\Filters\globalstatus());
$twig->addFilter(\Filters\globalstatustext());
$twig->addFilter(\Filters\statusicon());
$locale = new \Locale\Locale($this->cfg("default_language"));
$twig->addFilter($locale->t());

View file

@ -3,9 +3,10 @@
"header.title.some": "Některé služby neběží",
"header.title.none": "Všechny služby jsou offline",
"header.updated": "Poslední aktualizace {{date}}.",
"heartbeats.now": "nyní",
"heartbeats.ago": "před {{minutes}} minutami",
"monitor.uptime": "<b>{{pct}}%</b> uptime",
"status.1": "V pořádku",
"status.0": "Výpadek",
"status.2": "Problém",
"dateformat": "j. m. Y G:i",
"footer.poweredby": "Poháněno projektem <b><a href=\"{{link}}\" target=\"_blank\">Uptime Status</a></b>"
}

View file

@ -3,9 +3,10 @@
"header.title.some": "Some services are down",
"header.title.none": "All services are down",
"header.updated": "Last updated on {{date}}.",
"heartbeats.now": "now",
"heartbeats.ago": "{{minutes}} minutes ago",
"monitor.uptime": "<b>{{pct}}%</b> uptime",
"status.1": "Operational",
"status.0": "Downtime",
"status.2": "Problem",
"dateformat": "M j, Y, g:i a",
"footer.poweredby": "Powered by <b><a href=\"{{link}}\" target=\"_blank\">Uptime Status</a></b>"
}

1
public/icon/error.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1.75 1.75 20.5 20.5"><path fill="#F87171" d="m8.4 17l3.6-3.6l3.6 3.6l1.4-1.4l-3.6-3.6L17 8.4L15.6 7L12 10.6L8.4 7L7 8.4l3.6 3.6L7 15.6L8.4 17Zm3.6 5q-2.075 0-3.9-.788t-3.175-2.137q-1.35-1.35-2.137-3.175T2 12q0-2.075.788-3.9t2.137-3.175q1.35-1.35 3.175-2.137T12 2q2.075 0 3.9.788t3.175 2.137q1.35 1.35 2.138 3.175T22 12q0 2.075-.788 3.9t-2.137 3.175q-1.35 1.35-3.175 2.138T12 22Z"/></svg>

After

Width:  |  Height:  |  Size: 437 B

1
public/icon/success.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1.75 1.75 20.5 20.5"><path fill="#10B981" d="m10.6 16.6l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4l4.25 4.25ZM12 22q-2.075 0-3.9-.788t-3.175-2.137q-1.35-1.35-2.137-3.175T2 12q0-2.075.788-3.9t2.137-3.175q1.35-1.35 3.175-2.137T12 2q2.075 0 3.9.788t3.175 2.137q1.35 1.35 2.138 3.175T22 12q0 2.075-.788 3.9t-2.137 3.175q-1.35 1.35-3.175 2.138T12 22Z"/></svg>

After

Width:  |  Height:  |  Size: 410 B

1
public/icon/warning.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1.75 1.75 20.5 20.5"><path fill="#FFBB6D" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10Zm-1-7v2h2v-2h-2Zm0-8v6h2V7h-2Z"/></svg>

After

Width:  |  Height:  |  Size: 207 B

View file

@ -2,12 +2,12 @@
--border-radius: .35rem;
--title-color: #fff;
--text-color: #fff9;
--bg-color: #0f121a;
--title-color: 255, 255, 255;
--text-color: 177, 177, 177;
--bg-color: 15, 18, 26;
--card-bg-color: #23273191;
--card-border-color: #21242d;
--card-bg-color: 35, 39, 49;
--card-border-color: 33, 36, 45;
--green-color: 16, 185, 129;
--red-color: 248, 113, 113;
@ -16,11 +16,12 @@
}
body {
color: var(--text-color);
background-color: var(--bg-color);
color: rgb(var(--text-color));
background-color: rgb(var(--bg-color));
font-family: Cantarell, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
margin: 0;
overflow-x: hidden;
line-height: 1.15;
margin: 0;
}
* {
@ -34,7 +35,7 @@ body {
}
h1, h2, h3, h4 {
color: var(--title-color);
color: rgb(var(--title-color));
}
h3, h4 {
@ -42,11 +43,11 @@ h3, h4 {
}
a {
color: var(--text-color);
color: rgb(var(--text-color));
}
a:hover {
color: var(--title-color);
color: rgb(var(--title-color));
}
@ -71,18 +72,6 @@ header::after {
z-index: -1;
}
header.all {
--color: var(--green-color);
}
header.some {
--color: var(--yellow-color);
}
header.none {
--color: var(--red-color);
}
header .icon {
color: rgb(var(--color));
border-radius: 50%;
@ -92,10 +81,7 @@ header .icon {
position: relative;
display: flex;
background-image: linear-gradient(-45deg, rgb(var(--color), .1) 0%, rgb(var(--color), .5) 100%);
}
header .icon svg {
filter: drop-shadow(0 0 10px var(--bg-color));
filter: drop-shadow(0 0 10px rgb(var(--bg-color)));
}
header p {
@ -113,27 +99,23 @@ footer {
/* GROUP */
.group {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
background: rgb(var(--card-bg-color), .5);
border: 1px solid rgb(var(--card-border-color));
margin: 10px 0;
padding: 10px;
border-radius: var(--border-radius);
}
.group .item {
overflow: hidden;
padding: 10px;
border-radius: var(--border-radius);
}
.group .item > .inner {
margin: 10px;
}
.group .item:hover {
background-color: #fff1;
}
.group > .header > .inner {
.group > .header {
display: flex;
align-items: center;
justify-content: space-between;
@ -148,9 +130,6 @@ footer {
.monitor {
--color: 150, 150, 150;
}
.monitor > .inner {
display: flex;
gap: 7px;
flex-direction: column;
@ -178,50 +157,97 @@ footer {
.heartbeats {
display: flex;
gap: 5px;
flex-direction: column;
}
.heartbeats .items {
display: flex;
gap: 2px;
border-radius: var(--border-radius);
overflow: hidden;
gap: 0px;
height: 5px;
transition: all .3s .5s;
opacity: .4;
transition: all .3s;
}
.heartbeats .items > * {
.heartbeats .tooltip {
position: absolute;
z-index: 10;
background-color: rgb(var(--card-bg-color));
border-radius: var(--border-radius);
border: 1px solid rgb(var(--card-border-color));
top: 100%;
left: 50%;
margin-top: 5px;
margin-left: -90px;
width: 180px;
box-shadow: 0 0 10px 0 #0005;
opacity: 0;
pointer-events: none;
}
.heartbeats .tooltip > * {
padding: 15px;
}
.heartbeats .tooltip .status {
display: flex;
flex-direction: column;
gap: 8px;
}
.heartbeats .tooltip .status :nth-child(1) {
display: flex;
flex-direction: row;
gap: 7px;
font-weight: 700;
color: rgb(var(--title-color));
}
.heartbeats .tooltip img {
max-width: 19px;
}
.heartbeats .tooltip .date {
font-size: .9rem;
text-align: center;
font-weight: 700;
background-color: #fff1;
padding: 8px 15px;
}
.heartbeats .beat {
cursor: pointer;
position: relative;
flex: 1;
background-color: rgb(var(--color));
border-radius: var(--border-radius);
}
.heartbeats .time {
display: flex;
justify-content: space-between;
font-size: .9rem;
opacity: .7;
line-height: 1;
.heartbeats .beat:last-child {
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
.monitor:hover .heartbeats .items {
height: 25px;
.heartbeats .beat:first-child {
border-radius: var(--border-radius) 0 0 var(--border-radius);
}
.heartbeats .beat:hover {
box-shadow: inset 0 0 0 2px rgb(var(--title-color));
}
.heartbeats .beat:hover .tooltip {
opacity: 1;
}
.monitor:hover .heartbeats {
opacity: 1;
height: 25px;
}
/* STATUS COLORS */
.monitor.status-1, .monitor .heartbeats .status-1 {
.status-1 {
--color: var(--green-color);
}
.monitor.status-0, .monitor .heartbeats .status-0 {
.status-0 {
--color: var(--red-color);
}
.monitor.status-2, .monitor .heartbeats .status-2 {
.status-2 {
--color: var(--yellow-color);
}

View file

@ -1,9 +1,7 @@
<div class="group">
<div class="header item">
<div class="inner">
<h3>{{ group.name }}</h3>
<div class="status">{{ group.online }} / {{ group.monitors | length }}</div>
</div>
<h3>{{ group.name }}</h3>
<div class="status">{{ group.online }} / {{ group.monitors | length }}</div>
</div>
{% for monitor in group.monitors %}
{{ include("./monitor.twig") }}

View file

@ -1,23 +1,8 @@
<header class="{{ online | isof(total) }}">
{% set gs = online | globalstatus(total) %}
<header class="status-{{ gs }}">
<div class="inner">
{% if online == total %}
<div class="icon">{{ include("./icon/success.svg") }}</div>
<h1>{{ "header.title.all" | t }}</h1>
{% elseif online == 0 %}
<div class="icon">{{ include("./icon/error.svg") }}</div>
<h1>{{ "header.title.none" | t }}</h1>
{% else %}
<div class="icon">{{ include("./icon/warning.svg") }}</div>
<h1>{{ "header.title.some" | t }}</h1>
{% endif %}
<p>
{{ "header.updated" | t({date: now | date}) }}
</p>
<img class="icon" src="{{ gs | statusicon }}" />
<h1>{{ gs | globalstatustext | t }}</h1>
<p>{{ "header.updated" | t({date: now | date}) }}</p>
</div>
</header>

View file

@ -1,11 +1,24 @@
<div class="heartbeats">
<div class="items">
{% for heartbeat in monitor.heartbeats %}
<div class="status-{{ heartbeat.status }}"></div>
{% endfor %}
</div>
<div class="time">
<div>{{ "heartbeats.ago" | t({minutes: (monitor.heartbeats | first).time | timediffmin((monitor.heartbeats | last).time)}) }}</div>
<div>{{ "heartbeats.now" | t }}</div>
</div>
{% for heartbeat in monitor.heartbeats %}
<div class="beat status-{{ heartbeat.status }}">
<div class="tooltip">
<div class="status">
<div>
<img src="{{ heartbeat.status | statusicon }}" />
<span>
{{ ("status." ~ heartbeat.status) | t }}
</span>
</div>
{% if heartbeat.ping %}
<div>
Ping: {{ heartbeat.ping }}ms
</div>
{% endif %}
</div>
<div class="date">
{{ heartbeat.time | date }}
</div>
</div>
</div>
{% endfor %}
</div>

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1.75 1.75 20.5 20.5"><path fill="currentColor" d="m8.4 17l3.6-3.6l3.6 3.6l1.4-1.4l-3.6-3.6L17 8.4L15.6 7L12 10.6L8.4 7L7 8.4l3.6 3.6L7 15.6L8.4 17Zm3.6 5q-2.075 0-3.9-.788t-3.175-2.137q-1.35-1.35-2.137-3.175T2 12q0-2.075.788-3.9t2.137-3.175q1.35-1.35 3.175-2.137T12 2q2.075 0 3.9.788t3.175 2.137q1.35 1.35 2.138 3.175T22 12q0 2.075-.788 3.9t-2.137 3.175q-1.35 1.35-3.175 2.138T12 22Z"/></svg>

Before

Width:  |  Height:  |  Size: 442 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1.75 1.75 20.5 20.5"><path fill="currentColor" d="m10.6 16.6l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4l4.25 4.25ZM12 22q-2.075 0-3.9-.788t-3.175-2.137q-1.35-1.35-2.137-3.175T2 12q0-2.075.788-3.9t2.137-3.175q1.35-1.35 3.175-2.137T12 2q2.075 0 3.9.788t3.175 2.137q1.35 1.35 2.138 3.175T22 12q0 2.075-.788 3.9t-2.137 3.175q-1.35 1.35-3.175 2.138T12 22Z"/></svg>

Before

Width:  |  Height:  |  Size: 415 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1.75 1.75 20.5 20.5"><path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10Zm-1-7v2h2v-2h-2Zm0-8v6h2V7h-2Z"/></svg>

Before

Width:  |  Height:  |  Size: 212 B

View file

@ -1,25 +1,15 @@
{% set status = monitor.last.status %}
<div class="item monitor status-{{ status }}">
<div class="inner">
<div class="header">
<div class="icon">
{% if status == 0 %}
{{ include("./icon/error.svg") }}
{% elseif status == 1 %}
{{ include("./icon/success.svg") }}
{% elseif status == 2 %}
{{ include("./icon/warning.svg") }}
{% endif %}
</div>
<h4>
{{ monitor.name }}
</h4>
<div class="uptime">
{{ "monitor.uptime" | t({pct: (monitor.uptime * 100) | number_format(1, '.')}) | raw }}
</div>
<div class="header">
<img class="icon" src="{{ status | statusicon }}" />
<h4>
{{ monitor.name }}
</h4>
<div class="uptime">
{{ "monitor.uptime" | t({pct: (monitor.uptime * 100) | number_format(1, '.')}) | raw }}
</div>
{% if monitor.opt.rich %}
{{ include("./heartbeats.twig") }}
{% endif %}
</div>
{% if monitor.opt.rich %}
{{ include("./heartbeats.twig") }}
{% endif %}
</div>