Update - This is an automated commit

mane
Mia Raindrops 3 months ago
parent 9389fc46aa
commit a80166d68c
Signed by: Mia Raindrops
GPG Key ID: EFBDC68435A574B7

2
.gitignore vendored

@ -1,3 +1,5 @@
servers.json
output.json
config.yaml
pings.json
history.json

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" autoUpload="Always" serverName="Bridlewood" remoteFilesAllowedToDisappearOnAutoupload="false">
<component name="PublishConfigData" serverName="Bridlewood" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="Bridlewood">
<serverdata>
@ -10,6 +10,5 @@
</serverdata>
</paths>
</serverData>
<option name="myAutoUpload" value="ALWAYS" />
</component>
</project>

@ -37,10 +37,16 @@ try {
totalMem = require('os').totalmem();
freeMem = require('os').freemem();
} catch (e) {
disk = require('child_process').execSync(`df -c`, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim().replace(/( )+/gm, " ").split("\n").map((i) => i.trim());
memParts = require('child_process').execSync("top -d1 | grep Mem:").toString().trim().split(" ").filter(i => i.endsWith("M")).map(i => ponyReadableToBytes(i));
totalMem = memParts[0] + memParts[1] + memParts[2] + memParts[3] + memParts[5];
freeMem = memParts[1] + memParts[2] + memParts[5]
try {
disk = require('child_process').execSync(`gdf -x tmpfs -x squashfs -x devtmpfs --total --output=source,fstype,size,used,avail,pcent,ipcent,iused,itotal,target ${parts}`, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim().replace(/( )+/gm, " ").split("\n").map((i) => i.trim());
totalMem = require('os').totalmem();
freeMem = require('os').freemem();
} catch (e) {
disk = require('child_process').execSync(`df -c`, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim().replace(/( )+/gm, " ").split("\n").map((i) => i.trim());
memParts = require('child_process').execSync("top -d1 | grep Mem:").toString().trim().split(" ").filter(i => i.endsWith("M")).map(i => ponyReadableToBytes(i));
totalMem = memParts[0] + memParts[1] + memParts[2] + memParts[3] + memParts[5];
freeMem = memParts[1] + memParts[2] + memParts[5]
}
}
let top;

File diff suppressed because one or more lines are too long

@ -5,6 +5,7 @@ const axios = require('axios').default;
const fs = require('fs');
const net = require('net');
const ejs = require('ejs');
const history = require("./history.json");
if (!fs.existsSync("./pings.json")) fs.writeFileSync("./pings.json", "{}");
@ -41,12 +42,15 @@ async function check() {
global.pingHistory = JSON.parse(fs.readFileSync("./pings.json").toString());
global.config = YAML.parse(fs.readFileSync("./config.yaml").toString());
global.output = {};
global.groups = [];
for (let item of config['services']) {
console.log(`[${item.id}] ${item.name}`);
let result, start, ping;
global.groups = [...new Set([...global.groups, item.group])];
switch (item.type) {
case "http":
case "https":
@ -74,6 +78,7 @@ async function check() {
output[item.id] = {
id: item.id,
name: item.name,
group: item.group ?? "Default",
ping,
status: "notWorking",
details: "The service is reachable from an off-site network, but it is running with degraded performance."
@ -83,6 +88,7 @@ async function check() {
output[item.id] = {
id: item.id,
name: item.name,
group: item.group ?? "Default",
ping,
status: "online",
details: "The service is entirely operational and responds within a reasonable amount of time."
@ -93,6 +99,7 @@ async function check() {
output[item.id] = {
id: item.id,
name: item.name,
group: item.group ?? "Default",
ping,
status: "notWorking",
details: "The service is reachable from an off-site network, but does not behave like it should (warning code: " + result.status + ")."
@ -102,6 +109,7 @@ async function check() {
output[item.id] = {
id: item.id,
name: item.name,
group: item.group ?? "Default",
ping,
status: "offline",
details: "The service returns a server error upon connection (error code: " + result.status + ")."
@ -111,6 +119,7 @@ async function check() {
output[item.id] = {
id: item.id,
name: item.name,
group: item.group ?? "Default",
ping,
status: "offline",
details: "The service is currently unreachable from an off-site network (error message: " + result.message + ")."
@ -133,6 +142,7 @@ async function check() {
output[item.id] = {
id: item.id,
name: item.name,
group: item.group ?? "Default",
ping,
status: "notWorking",
details: "The service is reachable from an off-site network, but it is running with degraded performance."
@ -142,6 +152,7 @@ async function check() {
output[item.id] = {
id: item.id,
name: item.name,
group: item.group ?? "Default",
ping,
status: "online",
details: "The service is entirely operational and responds within a reasonable amount of time."
@ -156,6 +167,7 @@ async function check() {
output[item.id] = {
id: item.id,
name: item.name,
group: item.group ?? "Default",
ping,
status: "notWorking",
details: "The service is potentially reachable from an off-site network, but the attempt to connect took longer than the maximum allowed time."
@ -166,6 +178,7 @@ async function check() {
output[item.id] = {
id: item.id,
name: item.name,
group: item.group ?? "Default",
ping,
status: "offline",
details: "The service is currently unreachable from an off-site network (error code: " + e.code + ")."
@ -180,6 +193,7 @@ async function check() {
output[item.id] = {
id: item.id,
name: item.name,
group: item.group ?? "Default",
ping: -1,
status: null,
details: "An error occurred while processing status for this service."
@ -207,8 +221,50 @@ async function check() {
return a['name'].localeCompare(b['name']);
}),
ping: pings.reduce((a, b) => a + b) / pings.length,
date: new Date()
date: new Date(),
total: 0,
groups: groups
}
let history = require('./history.json');
for (let service of output.services) {
if (!history[service.id]) history[service.id] = {};
if (!history[service.id][new Date().toISOString().split("T")[0]]) history[service.id][new Date().toISOString().split("T")[0]] = [];
let code = 0;
if (service.status === "offline") code = 2;
if (service.status === "notWorking") code = 1;
if (service.status === "maintenance") code = 3;
history[service.id][new Date().toISOString().split("T")[0]].push(code);
let newHistory = {};
let days = Object.keys(history[service.id]);
let keepDays = [];
for (let day of days) {
if (new Date(new Date().toISOString().split("T")[0]).getTime() - new Date(day).getTime() >= 7776000000) {} else {
keepDays.push(day);
}
}
for (let day of keepDays) {
newHistory[day] = history[service.id][day];
}
history[service.id] = newHistory;
}
fs.writeFileSync("./history.json", JSON.stringify(history));
if (global.output.services.map(i => i.status).includes("offline")) {
global.output.total = 2;
} else if (global.output.services.map(i => i.status).includes("notWorking")) {
global.output.total = 1;
}
fs.writeFileSync("output.json", JSON.stringify(output, null, 4));
pingHistory[new Date().toISOString()] = pings.reduce((a, b) => a + b) / pings.length;
@ -225,7 +281,7 @@ async function web() {
console.log("Generating webpage...");
let pings = JSON.parse(fs.readFileSync("./pings.json").toString());
let rendered = ejs.render(fs.readFileSync("./web/page.ejs").toString(), { config, services: output["servers"], ping: output["ping"], pings: [Object.keys(pings), Object.values(pings)], date: new Date(output["date"]), servers: JSON.parse(fs.readFileSync("./servers.json").toString()) });
let rendered = ejs.render(fs.readFileSync("./web/page.ejs").toString(), { config, services: output["servers"], ping: output["ping"], pings: [Object.keys(pings), Object.values(pings)], outage: config['outage'], maintenances: config['maintenances'], history: JSON.parse(fs.readFileSync("./history.json").toString()), date: new Date(output["date"]), servers: JSON.parse(fs.readFileSync("./servers.json").toString()) });
fs.writeFileSync("./web/public/index.html", rendered);
for (let asset of fs.readdirSync("./web/static")) {
@ -245,7 +301,7 @@ async function servers() {
let stats = null;
try {
stats = JSON.parse(require('child_process').execSync(server['command'], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim());
stats = JSON.parse(require('child_process').execSync(server['command'], { timeout: 30000, stdio: ["ignore", "pipe", "ignore"] }).toString().trim());
stats["color"] = "hsl(" + Math.round(rgb2hue(...require('crypto').createHash("sha1").update(server.id).digest("hex").substring(0, 6).split(" ").map(i => [parseInt(i.substring(0, 2), 16), parseInt(i.substring(2, 4), 16), parseInt(i.substring(4, 6), 16)])[0])) + "deg 75% 60%)";
} catch (e) {
stats = null;
@ -268,15 +324,15 @@ async function servers() {
total[new Date().toISOString()] = {
ram: {
used: Object.values(list).map((i) => Object.values(i)[Object.values(i).length - 1]).map((i) => i.ram.used).reduce((a, b) => a + b),
total: Object.values(list).map((i) => Object.values(i)[Object.values(i).length - 1]).map((i) => i.ram.total).reduce((a, b) => a + b)
used: Object.values(list).map((i) => Object.values(i)[Object.values(i).length - 1]).filter(i => i).map((i) => i.ram.used).reduce((a, b) => a + b),
total: Object.values(list).map((i) => Object.values(i)[Object.values(i).length - 1]).filter(i => i).map((i) => i.ram.total).reduce((a, b) => a + b)
},
cpu: {
usage: Object.values(list).map((i) => Object.values(i)[Object.values(i).length - 1]).map((i) => i.cpu.usage).reduce((a, b) => a + b) / Object.values(list).length,
usage: Object.values(list).map((i) => Object.values(i)[Object.values(i).length - 1]).filter(i => i).map((i) => i.cpu.usage).reduce((a, b) => a + b) / Object.values(list).length,
},
disk: {
used: Object.values(list).map((i) => Object.values(i)[Object.values(i).length - 1]).map((i) => i.disk.used).reduce((a, b) => a + b),
total: Object.values(list).map((i) => Object.values(i)[Object.values(i).length - 1]).map((i) => i.disk.total).reduce((a, b) => a + b)
used: Object.values(list).map((i) => Object.values(i)[Object.values(i).length - 1]).filter(i => i).map((i) => i.disk.used).reduce((a, b) => a + b),
total: Object.values(list).map((i) => Object.values(i)[Object.values(i).length - 1]).filter(i => i).map((i) => i.disk.total).reduce((a, b) => a + b)
}
};

@ -4,598 +4,391 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Equestria.dev - Infrastructure status</title>
<title>Equestria.dev Status</title>
<link rel="stylesheet" href="/bootstrap.min.css">
<script src="/bootstrap.min.js"></script>
<script src="/chart.js"></script>
<link rel="icon" href="/logo.svg" type="image/svg+xml">
<style>
.tooltip {
--bs-tooltip-max-width: 20vw;
}
.status-service:hover {
background-color: rgba(0, 0, 0, .1);
}
tr:nth-last-child(1) .status-service {
border-bottom: none !important;
}
@media (min-width: 100px) {
tr:nth-child(1) .status-service {
border-top: none !important;
}
}
@media (max-width: 1000px) {
.status-service {
display: block;
width: 100% !important;
border-top: none !important;
border-left: none !important;
border-right: none !important;
}
.status-filler {
display: none !important;
}
.tooltip {
--bs-tooltip-max-width: 80vw;
}
}
</style>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="container">
<br><br>
<div style="text-align: center;">
<img src="/banner.svg" style="width: 320px; max-width: 100%;" alt="Equestria.dev">
<h1>Infrastructure status</h1>
<div class="pt-5 pb-5 bg-dark text-white" style="text-align: center;">
<img src="/banner.svg" style="width: 320px; max-width: 100%;" alt="Equestria.dev">
</div>
<div class="container">
<br><br>
<% if (outage && outage['enabled']) { %>
<div class="custom-outage alert alert-<%= outage['code'] %>">
<p><b style="font-size: 1.5rem;"><%= outage['title'] %></b></p>
<% for (let step of outage['steps']) { let date = new Date(step['date']).toUTCString().split(" ") %>
<p><b><%= step['type'] %></b> - <%= step['message'] %><br><small style="opacity:.5;"><%= date[2] + " " + date[1] + ", " + date[4].substring(0, date[4].length - 3) + " UTC" %></small></p>
<% } %>
</div>
<% } else { %>
<div class="alert <%= output["total"] === 2 ? "alert-danger" : (output["total"] === 1 ? "alert-warning" : "alert-success") %>">
<b style="font-size: 1.5rem;"><%= output["total"] === 0 ? "All Systems Operational" : (output["total"] === 1 ? "Running with Degraded Performance" : "Systems Outage") %></b>
</div>
<% } %>
<div>
<div class="list-group">
<% for (let group of output["groups"]) { %>
<details class="list-group-item service list-group-item-action">
<summary style="list-style: none;">
<b><img src="/open.svg" class="icon-open" style="width: 16px;height: 16px;margin-top: -3px;"><img src="/close.svg" class="icon-close" style="display: none;width: 16px;height: 16px;margin-top: -3px;"> <%= group %></b>
<span style="float: right;"></span>
<div class="history-90">
<div style="margin-top: 5px; display: grid; grid-template-columns: repeat(90, 1fr);">
<% let uptimes = []; for (let i = 89; i > -1; i--) {
let date = new Date(new Date(new Date().toISOString().split("T")[0]).getTime() - 86400000 * i).toISOString().split("T")[0];
let parts = new Date(new Date(new Date().toISOString().split("T")[0]).getTime() - 86400000 * i).toString().split(" ");
let pretty = parts[0] + " " + parts[1] + " " + parts[2];
let services = Object.values(output["services"]).filter(i => i.group === group).map(i => i.id);
let list = [];
for (let service of services) {
if (history[service]) {
if (history[service][date]) {
list.push(history[service][date].reduce((a, b) => a + b) / history[service][date].length);
}
}
}
<div style="font-size: 14px;">
<div style="display: grid; grid-template-columns: 1fr max-content;">
<div style="display:flex; align-items: center;">
<div>
<span style="vertical-align: middle; background: <% if (ping < 256) { %>#05b705<% } else if (ping < 512) { %>#f4c424<% } else { %>#f43224<% } %>; border-radius: 999px; width: 12px; height: 12px; display: inline-block;"></span>
<span style="vertical-align: middle;"><%= ping.toFixed(2) %>ms</span>
</div>
</div>
<div>
<img alt="Available" src="/online.svg" style="width: 28px; vertical-align: middle;"><span style="opacity: .65; margin-left: 2px; display:inline-block; vertical-align: middle;">Available</span>
<img alt="Maintenance" src="/maintenance.svg" style="width: 28px; vertical-align: middle;"><span style="opacity: .65; margin-left: 2px; display:inline-block; vertical-align: middle;">Maintenance</span>
<img alt="Issue" src="/misbehaving.svg" style="width: 28px; vertical-align: middle;"><span style="opacity: .65; margin-left: 2px; display:inline-block; vertical-align: middle;">Issue</span>
<img alt="Outage" src="/offline.svg" style="width: 28px; vertical-align: middle;"><span style="opacity: .65; margin-left: 2px; display:inline-block; vertical-align: middle;">Outage</span>
</div>
</div>
<hr>
<h3>Average ping history</h3>
<canvas id="history" style="width: 100%; height: 300px; max-height: 100%;"></canvas>
let avg = -1;
<hr>
<h3>Services status</h3>
if (list.length > 0) {
avg = list.reduce((a, b) => a + b) / list.length;
uptimes.push((Math.abs(3 - avg) / 3) * 100);
}
<table style="border-collapse: collapse; width: 100%; table-layout: fixed; border-left: 1px solid white; border-right: 1px solid white; margin-top: 10px;">
<tr style="width:100%;">
<% for (let index in Object.keys(output["services"])) { index = parseInt(index); let service = output["services"][Object.keys(output["services"])[index]]; %>
<td class="status-service" data-bs-toggle="tooltip" title="<p style='text-align:left !important;margin-bottom:<% if (service['details'] && service['details'].trim() !== "") { %>0.5<% } else { %>0<% } %>rem;'><b><%= service['name'] %> - <% if (service['status'] === "online") { %>Available<% } %><% if (service['status'] === "maintenance") { %>Maintenance<% } %><% if (service['status'] === "notWorking") { %>Issue<% } %><% if (service['status'] === "offline") { %>Outage<% } %></b><br><% if (service['ping'] > 0) { %>Latency: <%= service['ping'] %>ms<% } %></p><div style='text-align:left !important;'><% if (service['details'] && service['details'].trim() !== "") { %><%= service['details'] %><% } else { %><% } %></div></span>" style="width: calc(100% / 3); border: 1px solid rgba(0, 0, 0, .25); padding: 5px 10px; <% if ((index + 1) % 3 === 1) { %>border-left: none;<% } %><% if ((index + 1) % 3 === 0) { %>border-right: none;<% } %>">
<span>
let lower = Math.floor(avg);
let higher = Math.ceil(avg);
let diff = Math.abs(1 - (higher - avg));
%>
<div title="<%= pretty %>" data-bs-toggle="tooltip" style="display: flex; align-items: center; justify-content: center;">
<% if (list.length === 0) { %>
<div class="bg-secondary" style="height: 34px; width: 5px;"></div>
<% } else { %>
<div class="bg-<%= lower === 0 ? "success" : (lower === 1 ? "warning" : (lower === 2 ? "danger" : "info")) %>" style="height: 34px; width: 5px;">
<div class="bg-<%= higher === 0 ? "success" : (higher === 1 ? "warning" : (higher === 2 ? "danger" : "info")) %>" style="height: 34px; width: 5px; opacity: <%= diff * 100 %>%;"></div>
</div>
<% } %>
</div>
<% } %>
</div>
<div style="margin-top: 5px; display: grid; grid-template-columns: max-content 1fr max-content;">
<small style="opacity: .5;">90 days ago</small>
<small style="opacity: .75; text-align: center;"><% if (uptimes.length > 0) { %><%= (uptimes.reduce((a, b) => a + b) / uptimes.length).toFixed(1) %> % uptime<% } %></small>
<small style="opacity: .5;">Today </small>
</div>
</div>
<div class="history-60">
<div style="margin-top: 5px; display: grid; grid-template-columns: repeat(60, 1fr);">
<% uptimes = []; for (let i = 59; i > -1; i--) {
let date = new Date(new Date(new Date().toISOString().split("T")[0]).getTime() - 86400000 * i).toISOString().split("T")[0];
let parts = new Date(new Date(new Date().toISOString().split("T")[0]).getTime() - 86400000 * i).toString().split(" ");
let pretty = parts[0] + " " + parts[1] + " " + parts[2];
let services = Object.values(output["services"]).filter(i => i.group === group).map(i => i.id);
let list = [];
for (let service of services) {
if (history[service]) {
if (history[service][date]) {
list.push(history[service][date].reduce((a, b) => a + b) / history[service][date].length);
}
}
}
let avg = -1;
if (list.length > 0) {
avg = list.reduce((a, b) => a + b) / list.length;
uptimes.push((Math.abs(3 - avg) / 3) * 100);
}
let lower = Math.floor(avg);
let higher = Math.ceil(avg);
let diff = Math.abs(1 - (higher - avg));
%>
<div title="<%= pretty %>" data-bs-toggle="tooltip" style="display: flex; align-items: center; justify-content: center;">
<% if (list.length === 0) { %>
<div class="bg-secondary" style="height: 34px; width: 8px;"></div>
<% } else { %>
<div class="bg-<%= lower === 0 ? "success" : (lower === 1 ? "warning" : (lower === 2 ? "danger" : "info")) %>" style="height: 34px; width: 8px;">
<div class="bg-<%= higher === 0 ? "success" : (higher === 1 ? "warning" : (higher === 2 ? "danger" : "info")) %>" style="height: 34px; width: 8px; opacity: <%= diff * 100 %>%;"></div>
</div>
<% } %>
</div>
<% } %>
</div>
<div style="margin-top: 5px; display: grid; grid-template-columns: max-content 1fr max-content;">
<small style="opacity: .5;">60 days ago</small>
<small style="opacity: .75; text-align: center;"><% if (uptimes.length > 0) { %><%= (uptimes.reduce((a, b) => a + b) / uptimes.length).toFixed(1) %> % uptime<% } %></small>
<small style="opacity: .5;">Today </small>
</div>
</div>
<div class="history-30">
<div style="margin-top: 5px; display: grid; grid-template-columns: repeat(30, 1fr);">
<% uptimes = []; for (let i = 29; i > -1; i--) {
let date = new Date(new Date(new Date().toISOString().split("T")[0]).getTime() - 86400000 * i).toISOString().split("T")[0];
let parts = new Date(new Date(new Date().toISOString().split("T")[0]).getTime() - 86400000 * i).toString().split(" ");
let pretty = parts[0] + " " + parts[1] + " " + parts[2];
let services = Object.values(output["services"]).filter(i => i.group === group).map(i => i.id);
let list = [];
for (let service of services) {
if (history[service]) {
if (history[service][date]) {
list.push(history[service][date].reduce((a, b) => a + b) / history[service][date].length);
}
}
}
let avg = -1;
if (list.length > 0) {
avg = list.reduce((a, b) => a + b) / list.length;
uptimes.push((Math.abs(3 - avg) / 3) * 100);
}
let lower = Math.floor(avg);
let higher = Math.ceil(avg);
let diff = Math.abs(1 - (higher - avg));
%>
<div title="<%= pretty %>" data-bs-toggle="tooltip" style="display: flex; align-items: center; justify-content: center;">
<% if (list.length === 0) { %>
<div class="bg-secondary" style="height: 34px; width: 10px;"></div>
<% } else { %>
<div class="bg-<%= lower === 0 ? "success" : (lower === 1 ? "warning" : (lower === 2 ? "danger" : "info")) %>" style="height: 34px; width: 10px;">
<div class="bg-<%= higher === 0 ? "success" : (higher === 1 ? "warning" : (higher === 2 ? "danger" : "info")) %>" style="height: 34px; width: 10px; opacity: <%= diff * 100 %>%;"></div>
</div>
<% } %>
</div>
<% } %>
</div>
<div style="margin-top: 5px; display: grid; grid-template-columns: max-content 1fr max-content;">
<small style="opacity: .5;">30 days ago</small>
<small style="opacity: .75; text-align: center;"><% if (uptimes.length > 0) { %><%= (uptimes.reduce((a, b) => a + b) / uptimes.length).toFixed(1) %> % uptime<% } %></small>
<small style="opacity: .5;">Today </small>
</div>
</div>
</summary>
<% for (let index in Object.keys(output["services"])) { index = parseInt(index); let service = output["services"][Object.keys(output["services"])[index]]; if (service.group === group) { %>
<div style="margin: 10px 0 10px 20px;">
<span><%= service['name'] %></span>
<% if (service['status'] === "online") { %>
<img alt="Available" src="/online.svg" style="width: 24px; vertical-align: middle;">
<span class="text-success" style="float: right;" data-status-code="0">Operational</span>
<% } %>
<% if (service['status'] === "maintenance") { %>
<img alt="Maintenance" src="/maintenance.svg" style="width: 24px; vertical-align: middle;">
<span class="text-info" style="float: right;" data-status-code="3">Under Maintenance</span>
<% } %>
<% if (service['status'] === "notWorking") { %>
<img alt="Issue" src="/misbehaving.svg" style="width: 24px; vertical-align: middle;">
<span class="text-warning" style="float: right;" data-status-code="1">Degraded Performance</span>
<% } %>
<% if (service['status'] === "offline") { %>
<img alt="Outage" src="/offline.svg" style="width: 24px; vertical-align: middle;">
<span class="text-danger" style="float: right;" data-status-code="2">Outage</span>
<% } %>
<span style="margin-left: 2px; display:inline-block; vertical-align: middle;"><%= service['name'] %></span>
</span>
</td>
<% if ((index + 1) % 3 === 0) { %></tr><tr><% } %>
<% } %>
</tr>
</table>
<hr>
<h3>Servers memory usage (global)</h3>
<canvas id="ram" style="width: 100%; height: 300px; max-height: 100%;"></canvas>
<hr>
<h3>Servers memory usage (per server)</h3>
<canvas id="ram2" style="width: 100%; height: 300px; max-height: 100%;"></canvas>
<hr>
<h3>Servers storage usage (global)</h3>
<canvas id="disk" style="width: 100%; height: 300px; max-height: 100%;"></canvas>
<hr>
<h3>Servers storage usage (per server)</h3>
<canvas id="disk2" style="width: 100%; height: 300px; max-height: 100%;"></canvas>
<hr>
<h3>Servers CPU usage (global)</h3>
<canvas id="cpu" style="width: 100%; height: 300px; max-height: 100%;"></canvas>
<hr>
<h3>Servers CPU usage (per server)</h3>
<canvas id="cpu2" style="width: 100%; height: 300px; max-height: 100%;"></canvas>
<hr>
<p class="text-muted" style="margin-top: 10px;">Last updated <span id="update-date" data-update-date="<%= date.toISOString() %>"></span><span id="update-date-full"><%= date.toString() %></span></p>
</div>
<% }} %>
</details>
<% } %>
</div>
</div>
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl, { html: true, sanitize: false, animation: false }));
<script>
function timeAgo(time) {
if (!isNaN(parseInt(time))) {
time = new Date(time).getTime();
}
Array.from(document.getElementsByClassName("service")).forEach((i) => {
let code = 0;
let children = Array.from(i.children).filter(i => i.tagName === "DIV").map(i => Array.from(i.children).filter(i => i.className.startsWith("text-"))[0].getAttribute("data-status-code")).map(i => parseInt(i));
let periods = ["sec.", "mn.", "hr.", "d.", "wk.", "mo.", "y.", "ages"];
if (children.includes(1)) code = 1;
if (children.includes(2)) code = 2;
if (children.includes(3)) code = 3;
let lengths = ["60", "60", "24", "7", "4.35", "12", "100"];
console.log(code);
let now = new Date().getTime();
switch (code) {
case 0:
i.children[0].children[1].innerText = "Operational";
i.children[0].children[1].className = "text-success";
break;
let difference = Math.round((now - time) / 1000);
let tense;
let period;
case 1:
i.children[0].children[1].innerText = "Degraded Performance";
i.children[0].children[1].className = "text-warning";
break;
if (difference <= 10 && difference >= 0) {
return "now";
} else if (difference > 0) {
tense = "ago";
} else {
tense = "later";
}
case 2:
i.children[0].children[1].innerText = "Outage";
i.children[0].children[1].className = "text-danger";
break;
let j;
case 3:
i.children[0].children[1].innerText = "Under Maintenance";
i.children[0].children[1].className = "text-info";
break;
}
});
for (j = 0; difference >= lengths[j] && j < lengths.length - 1; j++) {
difference /= lengths[j];
}
</script>
difference = Math.round(difference);
<% if (maintenances.length > 0) { %>
<h3 style="margin-top: 50px;">Scheduled Maintenance<%= maintenances.length > 1 ? "s" : "" %></h3>
<div class="maintenances">
<% for (let maintenance of maintenances) { if (new Date(maintenance["end"]).getTime() >= new Date(new Date().toISOString().split("T")[0]).getTime()) %>
<h5><%= maintenance["title"] %></h5>
<p><%= maintenance["description"] %><br><small class="text-muted">Scheduled from <%= new Date(maintenance["start"]).toDateString().split(" ").slice(-3).join(" ") %> to <%= new Date(maintenance["end"]).toDateString().split(" ").slice(-3).join(" ") %> (estimated)</small></p>
<% } %>
</div>
<% } %>
period = periods[j];
<h3 style="margin-top: 50px;">Systems Response Time (<%= Math.round(ping) %> ms)</h3>
<canvas id="history" style="width: 100%; height: 200px; max-height: 100%;"></canvas>
return `${difference} ${period} ${tense}`;
<hr>
<p class="text-muted" style="margin-top: 10px;">Last updated <span data-bs-toggle="tooltip" title="<%= date.toString() %>" id="update-date" data-update-date="<%= date.toISOString() %>"><%= date.toISOString() %></span><span id="link-refresh" style="display:none;">, <a href="/">refresh</a></span></p>
</div>
</div>
<script>
function timeAgo(time) {
if (!isNaN(parseInt(time))) {
time = new Date(time).getTime();
}
window.onload = () => {
let date = new Date(document.getElementById("update-date").getAttribute("data-update-date"));
document.getElementById("update-date").innerText = timeAgo(date.getTime()) + ", ";
document.getElementById("update-date-full").innerText = date.toString();
setInterval(() => {
let date = new Date(document.getElementById("update-date").getAttribute("data-update-date"));
document.getElementById("update-date").innerText = timeAgo(date.getTime()) + ", ";
}, 500);
const ctx = document.getElementById('history').getContext('2d');
const graph = new Chart(ctx, {
type: 'line',
data: {
labels: JSON.parse(`<%- JSON.stringify(pings[0]) %>`).map(i => new Date(i).toDateString().split(" ")[2] + " " + new Date(i).toDateString().split(" ")[1] + " " + new Date(i).toTimeString().split(" ")[0].substring(0, 5)),
datasets: [{
label: 'Ping',
data: JSON.parse(`<%= JSON.stringify(pings[1]) %>`),
borderColor: '<% if (ping < 256) { %>#05b705<% } else if (ping < 512) { %>#f4c424<% } else { %>#f43224<% } %>'
}]
},
options: {
animation: {
duration: 0
},
scales: {
y: {
ticks: {
callback: function(label) {
return label + "ms";
}
}
},
x: {
gridLines: {
display: false
},
grid: {
display: false
},
gridLineWidth: 0
}
},
elements: {
point:{
radius: 0
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(tooltipItem) {
return tooltipItem.raw.toFixed(2) + 'ms';
}
},
intersect: false
}
}
}
});
let periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"];
function bytesToSize(bytes) {
if (bytes > 1024) {
if (bytes > 1024**2) {
if (bytes > 1024**3) {
if (bytes > 1024**4) {
return (bytes / 1024**4).toFixed(3) + " TiB";
} else {
return (bytes / 1024**3).toFixed(2) + " GiB";
}
} else {
return (bytes / 1024**2).toFixed(1) + " MiB";
}
} else {
return (bytes / 1024).toFixed(0) + " KiB";
}
} else {
return bytes.toFixed(0) + " bytes";
}
}
let lengths = ["60", "60", "24", "7", "4.35", "12", "100"];
window.serverData = JSON.parse(atob(`<%= Buffer.from(JSON.stringify(servers)).toString("base64") %>`));
const ctx2 = document.getElementById('ram').getContext('2d');
const graph2 = new Chart(ctx2, {
type: 'line',
data: {
labels: Object.keys(window.serverData._total).map(i => new Date(i).toDateString().split(" ")[2] + " " + new Date(i).toDateString().split(" ")[1] + " " + new Date(i).toTimeString().split(" ")[0].substring(0, 5)),
datasets: [
{
label: 'Memory usage',
data: Object.values(window.serverData._total).map(i => i.ram.used),
borderColor: '#a033f4'
}
]
},
options: {
animation: {
duration: 0
},
scales: {
y: {
ticks: {
callback: function(label) {
return bytesToSize(label);
}
}
},
x: {
gridLines: {
display: false
},
grid: {
display: false
},
gridLineWidth: 0
}
},
elements: {
point:{
radius: 0
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label: function(tooltipItem) {
return bytesToSize(tooltipItem.raw);
}
},
intersect: false
}
}
}
});
let now = new Date().getTime();
const ctx2a = document.getElementById('ram2').getContext('2d');
const graph2a = new Chart(ctx2a, {
type: 'line',
data: {
labels: Object.keys(window.serverData._total).map(i => new Date(i).toDateString().split(" ")[2] + " " + new Date(i).toDateString().split(" ")[1] + " " + new Date(i).toTimeString().split(" ")[0].substring(0, 5)),
datasets: Object.keys(window.serverData).filter(i => i !== "_total").map(i => {
return {
label: i,
data: Object.values(window.serverData[i]).map(i => (i.ram.used / i.ram.total) * 100),
borderColor: Object.values(window.serverData[i])[Object.values(window.serverData[i]).length - 1].color
}
})
},
options: {
animation: {
duration: 0
},
scales: {
y: {
ticks: {
callback: function(label) {
return label.toFixed(2) + "%";
}
}
},
x: {
gridLines: {
display: false
},
grid: {
display: false
},
gridLineWidth: 0
}
},
elements: {
point:{
radius: 0
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label: function(tooltipItem) {
return tooltipItem.dataset.label + ": " + tooltipItem.raw.toFixed(2) + "%";
}
},
intersect: false
}
}
}
});
let difference = Math.round((now - time) / 1000);
let tense;
let period;
const ctx3 = document.getElementById('disk').getContext('2d');
const graph3 = new Chart(ctx3, {
type: 'line',
data: {
labels: Object.keys(window.serverData._total).map(i => new Date(i).toDateString().split(" ")[2] + " " + new Date(i).toDateString().split(" ")[1] + " " + new Date(i).toTimeString().split(" ")[0].substring(0, 5)),
datasets: [
{
label: 'Storage usage',
data: Object.values(window.serverData._total).map(i => i.disk.used * 1024),
borderColor: '#25bda2'
}
]
if (difference <= 10 && difference >= 0) {
return "now";
} else if (difference > 0) {
tense = "ago";
} else {
tense = "later";
}
let j;
for (j = 0; difference >= lengths[j] && j < lengths.length - 1; j++) {
difference /= lengths[j];
}
difference = Math.round(difference);
period = periods[j];
return `${difference} ${period}${difference > 1 ? "s" : ""} ${tense}`;
}
window.onload = () => {
let date = new Date(document.getElementById("update-date").getAttribute("data-update-date"));
document.getElementById("update-date").innerText = timeAgo(date.getTime());
document.getElementById("update-date").title = date.toString();
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl, { html: true, sanitize: false, animation: false }));
setInterval(() => {
let date = new Date(document.getElementById("update-date").getAttribute("data-update-date"));
document.getElementById("update-date").innerText = timeAgo(date.getTime());
if (new Date() - date.getTime() >= 360000) {
document.getElementById("link-refresh").style.display = "inline";
}
}, 500);
const ctx = document.getElementById('history').getContext('2d');
const graph = new Chart(ctx, {
type: 'line',
data: {
labels: JSON.parse(`<%- JSON.stringify(pings[0]) %>`).map(i => new Date(i).toDateString().split(" ")[2] + " " + new Date(i).toDateString().split(" ")[1] + " " + new Date(i).toTimeString().split(" ")[0].substring(0, 5)),
datasets: [{
label: 'Ping',
data: JSON.parse(`<%= JSON.stringify(pings[1]) %>`),
borderColor: '#05b7b1',
backgroundColor: '#05b7b177'
}]
},
options: {
animation: {
duration: 0
},
options: {
animation: {
duration: 0
},
scales: {
y: {
ticks: {
callback: function(label) {
return bytesToSize(label);
}
scales: {
y: {
ticks: {
callback: function(label) {
return Math.round(label) + " ms";
}
},
x: {
gridLines: {
display: false
},
grid: {
display: false
},
gridLineWidth: 0
}
},
elements: {
point:{
radius: 0
}
},
plugins: {
legend: {
display: false,
x: {
gridLines: {
display: false
},
tooltip: {
callbacks: {
label: function(tooltipItem) {
return bytesToSize(tooltipItem.raw);
}
},
intersect: false
}
grid: {
display: false
},
gridLineWidth: 0
}
}
});
const ctx3a = document.getElementById('disk2').getContext('2d');
const graph3a = new Chart(ctx3a, {
type: 'line',
data: {
labels: Object.keys(window.serverData._total).map(i => new Date(i).toDateString().split(" ")[2] + " " + new Date(i).toDateString().split(" ")[1] + " " + new Date(i).toTimeString().split(" ")[0].substring(0, 5)),
datasets: Object.keys(window.serverData).filter(i => i !== "_total").map(i => {
return {
label: i,
data: Object.values(window.serverData[i]).map(i => (i.disk.used / i.disk.total) * 100),
borderColor: Object.values(window.serverData[i])[Object.values(window.serverData[i]).length - 1].color
}
})
},
options: {
animation: {
duration: 0
},
scales: {
y: {
ticks: {
callback: function(label) {
return label.toFixed(2) + "%";
}
}
},
x: {
gridLines: {
display: false
},
grid: {
display: false
},
gridLineWidth: 0
}
},
elements: {
point:{
radius: 0
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label: function(tooltipItem) {
return tooltipItem.dataset.label + ": " + tooltipItem.raw.toFixed(2) + "%";
}
},
intersect: false
}
elements: {
point:{
radius: 0
}
}
});
const ctx4 = document.getElementById('cpu').getContext('2d');
const graph4 = new Chart(ctx4, {
type: 'line',
data: {
labels: Object.keys(window.serverData._total).map(i => new Date(i).toDateString().split(" ")[2] + " " + new Date(i).toDateString().split(" ")[1] + " " + new Date(i).toTimeString().split(" ")[0].substring(0, 5)),
datasets: [
{
label: 'CPU usage',
data: Object.values(window.serverData._total).map(i => i.cpu.usage),
borderColor: '#4346ee'
}
]
},
options: {
animation: {
duration: 0
plugins: {
legend: {
display: false
},
scales: {
y: {
ticks: {
callback: function(label) {
return label.toFixed(2) + "%";
}
tooltip: {
callbacks: {
label: function(tooltipItem) {
return tooltipItem.raw.toFixed(2) + ' ms';
}
},
x: {
gridLines: {
display: false
},
grid: {
display: false
},
gridLineWidth: 0
}
},
elements: {
point:{
radius: 0
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label: function(tooltipItem) {
return tooltipItem.raw.toFixed(2) + "%";
}
},
intersect: false
}
intersect: false
}
}
});
const ctx4a = document.getElementById('cpu2').getContext('2d');
const graph4a = new Chart(ctx4a, {
type: 'line',
data: {
labels: Object.keys(window.serverData._total).map(i => new Date(i).toDateString().split(" ")[2] + " " + new Date(i).toDateString().split(" ")[1] + " " + new Date(i).toTimeString().split(" ")[0].substring(0, 5)),
datasets: Object.keys(window.serverData).filter(i => i !== "_total").map(i => {
return {
label: i,
data: Object.values(window.serverData[i]).map(i => i.cpu.usage),
borderColor: Object.values(window.serverData[i])[Object.values(window.serverData[i]).length - 1].color
}
})
},
options: {
animation: {
duration: 0
},
scales: {
y: {
ticks: {
callback: function(label) {
return label.toFixed(2) + "%";
}
}
},
x: {
gridLines: {
display: false
},
grid: {
display: false
},
gridLineWidth: 0
}
},
elements: {
point:{
radius: 0
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label: function(tooltipItem) {
return tooltipItem.dataset.label + ": " + tooltipItem.raw.toFixed(2) + "%";
}
},
intersect: false
}
});
function bytesToSize(bytes) {
if (bytes > 1024) {
if (bytes > 1024**2) {
if (bytes > 1024**3) {
if (bytes > 1024**4) {
return (bytes / 1024**4).toFixed(3) + " TiB";
} else {
return (bytes / 1024**3).toFixed(2) + " GiB";
}
} else {
return (bytes / 1024**2).toFixed(1) + " MiB";
}
} else {
return (bytes / 1024).toFixed(0) + " KiB";
}
});
} else {
return bytes.toFixed(0) + " bytes";
}
}
</script>
}
</script>
</body>
</html>

@ -24,7 +24,8 @@
.st16{fill:#FFFAFF;stroke:#000000;stroke-width:0.6851;stroke-miterlimit:10;}
.st17{fill:#68C0B1;stroke:#000000;stroke-width:0.6851;stroke-miterlimit:10;}
.st18{fill:#F4FAFB;stroke:#000000;stroke-width:0.6851;stroke-miterlimit:10;}
.st19{fill:url(#SVGID_1_);}
.st19{fill:#F2F2F2;}
.st20{fill:url(#SVGID_1_);}
</style>
<sodipodi:namedview bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview243" inkscape:current-layer="svg241" inkscape:cx="-413.24164" inkscape:cy="369.67422" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="986" inkscape:window-maximized="1" inkscape:window-width="1920" inkscape:window-x="-11" inkscape:window-y="-11" inkscape:zoom="0.2979798" objecttolerance="10" pagecolor="#ffffff" showgrid="false">
</sodipodi:namedview>
@ -354,46 +355,47 @@
<polygon id="polygon652" class="st8" points="128.3,181.3 131.2,172.3 123.6,166.7 133,166.7 136,157.8 138.9,166.7 148.3,166.7
140.7,172.3 143.6,181.3 136,175.7 "/>
<g>
<path d="M624.8,133.2h138.5v38.6h-98.5v43.3H752v38.6h-87.2v43.9h102.4v38.6H624.8V133.2z"/>
<path d="M850.2,339.6c-10.3,0-19.8-2.5-28.6-7.4c-8.7-5-15.7-12.2-21-21.7c-5.2-9.5-7.9-21-7.9-34.6c0-13.8,2.9-25.6,8.8-35.5
c5.9-9.8,13.6-17.4,23-22.8c9.5-5.3,19.7-8,30.8-8c10.3,0,18.9,1.7,25.7,5.1c6.8,3.4,12.8,7.9,17.9,13.4l-3,8.3V213h38.6v181.3
h-38.4v-81.4l0.6,12.4c-4.8,4-11.2,7.4-19.2,10.2C869.7,338.2,860.5,339.6,850.2,339.6z M864,309.3c5.1,0,9.9-0.8,14.2-2.3
c4.3-1.6,8-3.8,11-6.8c3-2.9,5.3-6.3,6.8-10.2v-29.5c-2.2-4-4.8-7.4-7.9-10.1c-3-2.7-6.5-4.7-10.5-6.1c-4-1.4-8.5-2.1-13.7-2.1
c-5.7,0-11,1.5-15.9,4.4c-4.9,2.9-8.8,7-11.7,12.1c-2.9,5.2-4.4,10.9-4.4,17.4c0,6.4,1.5,12.1,4.4,17.1c2.9,5,6.8,8.9,11.7,11.7
C853,307.8,858.3,309.3,864,309.3z"/>
<path d="M1013.3,342.1c-10.9,0-20-3.4-27.3-10.1c-7.4-6.7-11.2-15.4-11.6-26.1v-90.2h38.6v75.9c0.4,5.3,1.7,9.6,4.1,12.8
c2.4,3.2,6.4,4.8,12.1,4.8c5.7,0,10.6-1.9,14.8-5.8c4.1-3.9,7.4-9.1,9.7-15.9c2.3-6.7,3.4-14.3,3.4-22.8v-49.1h38.6v120.6h-35
l-3-22.1l0.6,2.5c-2.8,5-6.3,9.4-10.5,13.2c-4.2,3.9-9.2,6.8-15,9C1026.9,341,1020.4,342.1,1013.3,342.1z"/>
<path d="M1194.8,339.6c-14.7,0-27.2-2.8-37.4-8.3c-10.2-5.5-18-13.1-23.3-22.9c-5.3-9.7-8-21-8-33.7c0-12.1,3.1-23.1,9.4-32.8
c6.3-9.7,14.6-17.5,25.1-23.3s22.2-8.7,35-8.7c17.3,0,31.5,5,42.6,15c11.1,10,18.3,24.5,21.7,43.5l-93.8,29.8l-8.6-21l67.9-22.9
l-8,3.6c-1.5-4.8-4.1-9-7.9-12.6c-3.8-3.6-9.4-5.4-17-5.4c-5.7,0-10.7,1.3-15,4c-4.3,2.7-7.6,6.4-9.9,11.3
c-2.3,4.9-3.4,10.6-3.4,17.2c0,7.5,1.4,13.8,4.1,18.9c2.8,5.1,6.5,8.9,11.3,11.5c4.8,2.6,10.1,3.9,16,3.9c4.2,0,8.3-0.7,12.3-2.2
c4-1.5,7.9-3.4,11.7-5.8l17.1,28.7c-6.4,3.7-13.4,6.6-20.8,8.8S1201.4,339.6,1194.8,339.6z"/>
<path d="M1327.2,338.8c-9.9,0-19.2-1.8-27.7-5.2c-8.6-3.5-15.8-8.4-21.7-14.6l16-21.2c5.9,5.3,11.4,9.2,16.4,11.4
<path class="st19" d="M624.8,133.2h138.5v38.6h-98.5v43.3H752v38.6h-87.2v43.9h102.4v38.6H624.8V133.2z"/>
<path class="st19" d="M850.2,339.6c-10.3,0-19.8-2.5-28.6-7.4c-8.7-5-15.7-12.2-21-21.7c-5.2-9.5-7.9-21-7.9-34.6
c0-13.8,2.9-25.6,8.8-35.5c5.9-9.8,13.6-17.4,23-22.8c9.5-5.3,19.7-8,30.8-8c10.3,0,18.9,1.7,25.7,5.1c6.8,3.4,12.8,7.9,17.9,13.4
l-3,8.3V213h38.6v181.3h-38.4v-81.4l0.6,12.4c-4.8,4-11.2,7.4-19.2,10.2C869.7,338.2,860.5,339.6,850.2,339.6z M864,309.3
c5.1,0,9.9-0.8,14.2-2.3c4.3-1.6,8-3.8,11-6.8c3-2.9,5.3-6.3,6.8-10.2v-29.5c-2.2-4-4.8-7.4-7.9-10.1c-3-2.7-6.5-4.7-10.5-6.1
c-4-1.4-8.5-2.1-13.7-2.1c-5.7,0-11,1.5-15.9,4.4c-4.9,2.9-8.8,7-11.7,12.1c-2.9,5.2-4.4,10.9-4.4,17.4c0,6.4,1.5,12.1,4.4,17.1
c2.9,5,6.8,8.9,11.7,11.7C853,307.8,858.3,309.3,864,309.3z"/>
<path class="st19" d="M1013.3,342.1c-10.9,0-20-3.4-27.3-10.1c-7.4-6.7-11.2-15.4-11.6-26.1v-90.2h38.6v75.9
c0.4,5.3,1.7,9.6,4.1,12.8c2.4,3.2,6.4,4.8,12.1,4.8c5.7,0,10.6-1.9,14.8-5.8c4.1-3.9,7.4-9.1,9.7-15.9c2.3-6.7,3.4-14.3,3.4-22.8
v-49.1h38.6v120.6h-35l-3-22.1l0.6,2.5c-2.8,5-6.3,9.4-10.5,13.2c-4.2,3.9-9.2,6.8-15,9C1026.9,341,1020.4,342.1,1013.3,342.1z"/>
<path class="st19" d="M1194.8,339.6c-14.7,0-27.2-2.8-37.4-8.3c-10.2-5.5-18-13.1-23.3-22.9c-5.3-9.7-8-21-8-33.7
c0-12.1,3.1-23.1,9.4-32.8c6.3-9.7,14.6-17.5,25.1-23.3s22.2-8.7,35-8.7c17.3,0,31.5,5,42.6,15c11.1,10,18.3,24.5,21.7,43.5
l-93.8,29.8l-8.6-21l67.9-22.9l-8,3.6c-1.5-4.8-4.1-9-7.9-12.6c-3.8-3.6-9.4-5.4-17-5.4c-5.7,0-10.7,1.3-15,4
c-4.3,2.7-7.6,6.4-9.9,11.3c-2.3,4.9-3.4,10.6-3.4,17.2c0,7.5,1.4,13.8,4.1,18.9c2.8,5.1,6.5,8.9,11.3,11.5
c4.8,2.6,10.1,3.9,16,3.9c4.2,0,8.3-0.7,12.3-2.2c4-1.5,7.9-3.4,11.7-5.8l17.1,28.7c-6.4,3.7-13.4,6.6-20.8,8.8
S1201.4,339.6,1194.8,339.6z"/>
<path class="st19" d="M1327.2,338.8c-9.9,0-19.2-1.8-27.7-5.2c-8.6-3.5-15.8-8.4-21.7-14.6l16-21.2c5.9,5.3,11.4,9.2,16.4,11.4
c5.1,2.3,9.6,3.4,13.7,3.4c3.1,0,5.9-0.3,8.4-1c2.5-0.6,4.5-1.7,5.9-3c1.5-1.4,2.2-3.2,2.2-5.4c0-3.1-1.2-5.6-3.7-7.4
c-2.5-1.8-5.7-3.4-9.5-4.6c-3.9-1.2-8-2.4-12.4-3.7c-11-3.5-19-8.6-23.9-15.3c-4.9-6.7-7.3-14-7.3-21.9c0-6.1,1.6-12.2,4.8-18.3
c3.2-6.2,8.4-11.3,15.5-15.5c7.1-4.1,16.2-6.2,27.5-6.2c10.1,0,18.7,1,25.7,3c7,2,13.6,5.3,19.9,9.9l-14.6,22.6
c-3.5-2.8-7.4-5.1-11.7-7c-4.3-1.9-8.3-3-12-3.2c-3.3-0.2-6.1,0.2-8.4,1.1c-2.3,0.9-4.1,2.1-5.4,3.6c-1.3,1.5-1.9,3-1.9,4.7
c-0.2,3.5,1.1,6.3,4,8.3c2.8,2,6.5,3.6,10.9,4.7s8.7,2.4,13,3.9c5.9,1.8,10.9,4.3,15.2,7.4c4.2,3.1,7.5,6.9,9.8,11.2
c2.3,4.3,3.4,9.5,3.4,15.6c0,7.4-1.9,14.3-5.7,20.8c-3.8,6.5-9.5,11.8-17.1,15.9C1348.7,336.8,1339,338.8,1327.2,338.8z"/>
<path d="M1422.7,162.8h38.6v52.4h29.2v30.1h-29.2v91h-38.6v-91h-18.8v-30.1h18.8V162.8z"/>
<path d="M1554.9,215.7l3.6,33.1l-0.8-5c3.5-7.2,8.3-13.3,14.3-18.3c6.1-5.1,12.1-9,18.1-11.7c6-2.8,10.5-4.1,13.7-4.1l-1.9,38.6
c-9-1.1-16.7,0.4-23.2,4.4c-6.4,4-11.4,9.4-14.9,16c-3.5,6.6-5.2,13.4-5.2,20.4v47.2h-38.3V215.7H1554.9z"/>
<path d="M1632.4,173c0-5.7,2.2-10.4,6.8-14.1c4.5-3.7,9.3-5.5,14.5-5.5c5.1,0,9.9,1.8,14.2,5.5c4.3,3.7,6.5,8.4,6.5,14.1
c0,5.7-2.2,10.3-6.5,13.9c-4.3,3.6-9.1,5.4-14.2,5.4c-5.2,0-10-1.8-14.5-5.4C1634.6,183.3,1632.4,178.7,1632.4,173z M1633.2,215.7
h38.6v120.6h-38.6V215.7z"/>
<path d="M1762.3,339.6c-11.2,0-21.4-2.3-30.5-6.8c-9.1-4.5-16.3-11.4-21.7-20.8c-5.3-9.4-8-21.3-8-35.9c0-13.6,2.8-25.4,8.3-35.3
c5.5-9.9,12.8-17.6,21.8-23c9-5.4,18.6-8.1,28.7-8.1c12,0,21,2,27.2,5.9c6.2,4,11.3,8.3,15.3,13.1l-1.7,4.7l3.6-17.7h35.9v120.6
h-38.6v-26.2l3,8.3c-0.4,0-1.5,1.1-3.3,3.2c-1.8,2.1-4.6,4.6-8.1,7.4c-3.6,2.9-8,5.3-13.2,7.4S1769.5,339.6,1762.3,339.6z
M1773.4,308.2c4.6,0,8.7-0.7,12.4-2.1c3.7-1.4,6.9-3.4,9.7-6.1c2.8-2.7,5.1-6,7.2-10.1v-29.5c-1.5-4-3.7-7.5-6.6-10.3
c-2.9-2.8-6.4-5.1-10.5-6.6c-4-1.6-8.6-2.3-13.5-2.3c-5.5,0-10.6,1.4-15.3,4.3c-4.7,2.9-8.4,6.8-11.2,11.7
<path class="st19" d="M1422.7,162.8h38.6v52.4h29.2v30.1h-29.2v91h-38.6v-91h-18.8v-30.1h18.8V162.8z"/>
<path class="st19" d="M1554.9,215.7l3.6,33.1l-0.8-5c3.5-7.2,8.3-13.3,14.3-18.3c6.1-5.1,12.1-9,18.1-11.7c6-2.8,10.5-4.1,13.7-4.1
l-1.9,38.6c-9-1.1-16.7,0.4-23.2,4.4c-6.4,4-11.4,9.4-14.9,16c-3.5,6.6-5.2,13.4-5.2,20.4v47.2h-38.3V215.7H1554.9z"/>
<path class="st19" d="M1632.4,173c0-5.7,2.2-10.4,6.8-14.1c4.5-3.7,9.3-5.5,14.5-5.5c5.1,0,9.9,1.8,14.2,5.5
c4.3,3.7,6.5,8.4,6.5,14.1c0,5.7-2.2,10.3-6.5,13.9c-4.3,3.6-9.1,5.4-14.2,5.4c-5.2,0-10-1.8-14.5-5.4
C1634.6,183.3,1632.4,178.7,1632.4,173z M1633.2,215.7h38.6v120.6h-38.6V215.7z"/>
<path class="st19" d="M1762.3,339.6c-11.2,0-21.4-2.3-30.5-6.8c-9.1-4.5-16.3-11.4-21.7-20.8c-5.3-9.4-8-21.3-8-35.9
c0-13.6,2.8-25.4,8.3-35.3c5.5-9.9,12.8-17.6,21.8-23c9-5.4,18.6-8.1,28.7-8.1c12,0,21,2,27.2,5.9c6.2,4,11.3,8.3,15.3,13.1
l-1.7,4.7l3.6-17.7h35.9v120.6h-38.6v-26.2l3,8.3c-0.4,0-1.5,1.1-3.3,3.2c-1.8,2.1-4.6,4.6-8.1,7.4c-3.6,2.9-8,5.3-13.2,7.4
S1769.5,339.6,1762.3,339.6z M1773.4,308.2c4.6,0,8.7-0.7,12.4-2.1c3.7-1.4,6.9-3.4,9.7-6.1c2.8-2.7,5.1-6,7.2-10.1v-29.5
c-1.5-4-3.7-7.5-6.6-10.3c-2.9-2.8-6.4-5.1-10.5-6.6c-4-1.6-8.6-2.3-13.5-2.3c-5.5,0-10.6,1.4-15.3,4.3c-4.7,2.9-8.4,6.8-11.2,11.7
c-2.8,5-4.1,10.7-4.1,17.1c0,6.4,1.5,12.2,4.4,17.4c2.9,5.2,6.8,9.2,11.7,12.1C1762.4,306.7,1767.7,308.2,1773.4,308.2z"/>
</g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="2062.6323" y1="449.9906" x2="2062.6323" y2="286.1863">
<stop offset="0" style="stop-color:#965FA6"/>
<stop offset="1" style="stop-color:#4C3054"/>
<stop offset="0" style="stop-color:#DB8BF3"/>
<stop offset="1" style="stop-color:#915CA0"/>
</linearGradient>
<path class="st19" d="M1844.9,450c-6.3,0-11.4-1.8-15.2-5.5c-3.8-3.7-5.6-8.7-5.6-15.1c0-6.3,1.9-11.4,5.6-15.2
<path class="st20" d="M1844.9,450c-6.3,0-11.4-1.8-15.2-5.5c-3.8-3.7-5.6-8.7-5.6-15.1c0-6.3,1.9-11.4,5.6-15.2
c3.8-3.8,8.8-5.6,15.2-5.6c6.3,0,11.4,1.9,15.2,5.6c3.8,3.8,5.6,8.8,5.6,15.2c0,6.3-1.9,11.4-5.6,15.1
C1856.3,448.1,1851.3,450,1844.9,450z M1967.3,450c-12.1,0-21.9-4.2-29.3-12.6c-7.5-8.4-11.2-19.7-11.2-33.9v-33
c0-14.3,3.7-25.7,11.1-34.1c7.4-8.4,17.2-12.6,29.4-12.6c10,0,18,2.8,23.9,8.5c5.9,5.7,8.9,13.5,8.9,23.4l-6.2-6.4h6.4l-0.9-29

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M13.4 25.05h21.25V22.8H13.4ZM9.9 41q-1.2 0-2.05-.85Q7 39.3 7 38.1V9.9q0-1.2.85-2.05Q8.7 7 9.9 7h28.2q1.2 0 2.05.85.85.85.85 2.05v28.2q0 1.2-.85 2.05-.85.85-2.05.85Zm0-2.25h28.2q.25 0 .45-.2t.2-.45V9.9q0-.25-.2-.45t-.45-.2H9.9q-.25 0-.45.2t-.2.45v28.2q0 .25.2.45t.45.2Zm-.65-29.5v29.5-29.5Z"/></svg>

After

Width:  |  Height:  |  Size: 370 B

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M22.85 33.5h2.3v-8.35h8.35v-2.3h-8.35V14.5h-2.3v8.35H14.5v2.3h8.35ZM9.9 41q-1.2 0-2.05-.85Q7 39.3 7 38.1V9.9q0-1.2.85-2.05Q8.7 7 9.9 7h28.2q1.2 0 2.05.85.85.85.85 2.05v28.2q0 1.2-.85 2.05-.85.85-2.05.85Zm0-2.25h28.2q.25 0 .45-.2t.2-.45V9.9q0-.25-.2-.45t-.45-.2H9.9q-.25 0-.45.2t-.2.45v28.2q0 .25.2.45t.45.2Zm-.65-29.5v29.5-29.5Z"/></svg>

After

Width:  |  Height:  |  Size: 409 B

@ -24,7 +24,8 @@
.st16{fill:#FFFAFF;stroke:#000000;stroke-width:0.6851;stroke-miterlimit:10;}
.st17{fill:#68C0B1;stroke:#000000;stroke-width:0.6851;stroke-miterlimit:10;}
.st18{fill:#F4FAFB;stroke:#000000;stroke-width:0.6851;stroke-miterlimit:10;}
.st19{fill:url(#SVGID_1_);}
.st19{fill:#F2F2F2;}
.st20{fill:url(#SVGID_1_);}
</style>
<sodipodi:namedview bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview243" inkscape:current-layer="svg241" inkscape:cx="-413.24164" inkscape:cy="369.67422" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="986" inkscape:window-maximized="1" inkscape:window-width="1920" inkscape:window-x="-11" inkscape:window-y="-11" inkscape:zoom="0.2979798" objecttolerance="10" pagecolor="#ffffff" showgrid="false">
</sodipodi:namedview>
@ -354,46 +355,47 @@
<polygon id="polygon652" class="st8" points="128.3,181.3 131.2,172.3 123.6,166.7 133,166.7 136,157.8 138.9,166.7 148.3,166.7
140.7,172.3 143.6,181.3 136,175.7 "/>
<g>
<path d="M624.8,133.2h138.5v38.6h-98.5v43.3H752v38.6h-87.2v43.9h102.4v38.6H624.8V133.2z"/>
<path d="M850.2,339.6c-10.3,0-19.8-2.5-28.6-7.4c-8.7-5-15.7-12.2-21-21.7c-5.2-9.5-7.9-21-7.9-34.6c0-13.8,2.9-25.6,8.8-35.5
c5.9-9.8,13.6-17.4,23-22.8c9.5-5.3,19.7-8,30.8-8c10.3,0,18.9,1.7,25.7,5.1c6.8,3.4,12.8,7.9,17.9,13.4l-3,8.3V213h38.6v181.3
h-38.4v-81.4l0.6,12.4c-4.8,4-11.2,7.4-19.2,10.2C869.7,338.2,860.5,339.6,850.2,339.6z M864,309.3c5.1,0,9.9-0.8,14.2-2.3
c4.3-1.6,8-3.8,11-6.8c3-2.9,5.3-6.3,6.8-10.2v-29.5c-2.2-4-4.8-7.4-7.9-10.1c-3-2.7-6.5-4.7-10.5-6.1c-4-1.4-8.5-2.1-13.7-2.1
c-5.7,0-11,1.5-15.9,4.4c-4.9,2.9-8.8,7-11.7,12.1c-2.9,5.2-4.4,10.9-4.4,17.4c0,6.4,1.5,12.1,4.4,17.1c2.9,5,6.8,8.9,11.7,11.7
C853,307.8,858.3,309.3,864,309.3z"/>
<path d="M1013.3,342.1c-10.9,0-20-3.4-27.3-10.1c-7.4-6.7-11.2-15.4-11.6-26.1v-90.2h38.6v75.9c0.4,5.3,1.7,9.6,4.1,12.8
c2.4,3.2,6.4,4.8,12.1,4.8c5.7,0,10.6-1.9,14.8-5.8c4.1-3.9,7.4-9.1,9.7-15.9c2.3-6.7,3.4-14.3,3.4-22.8v-49.1h38.6v120.6h-35
l-3-22.1l0.6,2.5c-2.8,5-6.3,9.4-10.5,13.2c-4.2,3.9-9.2,6.8-15,9C1026.9,341,1020.4,342.1,1013.3,342.1z"/>
<path d="M1194.8,339.6c-14.7,0-27.2-2.8-37.4-8.3c-10.2-5.5-18-13.1-23.3-22.9c-5.3-9.7-8-21-8-33.7c0-12.1,3.1-23.1,9.4-32.8
c6.3-9.7,14.6-17.5,25.1-23.3s22.2-8.7,35-8.7c17.3,0,31.5,5,42.6,15c11.1,10,18.3,24.5,21.7,43.5l-93.8,29.8l-8.6-21l67.9-22.9
l-8,3.6c-1.5-4.8-4.1-9-7.9-12.6c-3.8-3.6-9.4-5.4-17-5.4c-5.7,0-10.7,1.3-15,4c-4.3,2.7-7.6,6.4-9.9,11.3
c-2.3,4.9-3.4,10.6-3.4,17.2c0,7.5,1.4,13.8,4.1,18.9c2.8,5.1,6.5,8.9,11.3,11.5c4.8,2.6,10.1,3.9,16,3.9c4.2,0,8.3-0.7,12.3-2.2
c4-1.5,7.9-3.4,11.7-5.8l17.1,28.7c-6.4,3.7-13.4,6.6-20.8,8.8S1201.4,339.6,1194.8,339.6z"/>
<path d="M1327.2,338.8c-9.9,0-19.2-1.8-27.7-5.2c-8.6-3.5-15.8-8.4-21.7-14.6l16-21.2c5.9,5.3,11.4,9.2,16.4,11.4
<path class="st19" d="M624.8,133.2h138.5v38.6h-98.5v43.3H752v38.6h-87.2v43.9h102.4v38.6H624.8V133.2z"/>
<path class="st19" d="M850.2,339.6c-10.3,0-19.8-2.5-28.6-7.4c-8.7-5-15.7-12.2-21-21.7c-5.2-9.5-7.9-21-7.9-34.6
c0-13.8,2.9-25.6,8.8-35.5c5.9-9.8,13.6-17.4,23-22.8c9.5-5.3,19.7-8,30.8-8c10.3,0,18.9,1.7,25.7,5.1c6.8,3.4,12.8,7.9,17.9,13.4
l-3,8.3V213h38.6v181.3h-38.4v-81.4l0.6,12.4c-4.8,4-11.2,7.4-19.2,10.2C869.7,338.2,860.5,339.6,850.2,339.6z M864,309.3
c5.1,0,9.9-0.8,14.2-2.3c4.3-1.6,8-3.8,11-6.8c3-2.9,5.3-6.3,6.8-10.2v-29.5c-2.2-4-4.8-7.4-7.9-10.1c-3-2.7-6.5-4.7-10.5-6.1
c-4-1.4-8.5-2.1-13.7-2.1c-5.7,0-11,1.5-15.9,4.4c-4.9,2.9-8.8,7-11.7,12.1c-2.9,5.2-4.4,10.9-4.4,17.4c0,6.4,1.5,12.1,4.4,17.1
c2.9,5,6.8,8.9,11.7,11.7C853,307.8,858.3,309.3,864,309.3z"/>
<path class="st19" d="M1013.3,342.1c-10.9,0-20-3.4-27.3-10.1c-7.4-6.7-11.2-15.4-11.6-26.1v-90.2h38.6v75.9
c0.4,5.3,1.7,9.6,4.1,12.8c2.4,3.2,6.4,4.8,12.1,4.8c5.7,0,10.6-1.9,14.8-5.8c4.1-3.9,7.4-9.1,9.7-15.9c2.3-6.7,3.4-14.3,3.4-22.8
v-49.1h38.6v120.6h-35l-3-22.1l0.6,2.5c-2.8,5-6.3,9.4-10.5,13.2c-4.2,3.9-9.2,6.8-15,9C1026.9,341,1020.4,342.1,1013.3,342.1z"/>
<path class="st19" d="M1194.8,339.6c-14.7,0-27.2-2.8-37.4-8.3c-10.2-5.5-18-13.1-23.3-22.9c-5.3-9.7-8-21-8-33.7
c0-12.1,3.1-23.1,9.4-32.8c6.3-9.7,14.6-17.5,25.1-23.3s22.2-8.7,35-8.7c17.3,0,31.5,5,42.6,15c11.1,10,18.3,24.5,21.7,43.5
l-93.8,29.8l-8.6-21l67.9-22.9l-8,3.6c-1.5-4.8-4.1-9-7.9-12.6c-3.8-3.6-9.4-5.4-17-5.4c-5.7,0-10.7,1.3-15,4
c-4.3,2.7-7.6,6.4-9.9,11.3c-2.3,4.9-3.4,10.6-3.4,17.2c0,7.5,1.4,13.8,4.1,18.9c2.8,5.1,6.5,8.9,11.3,11.5
c4.8,2.6,10.1,3.9,16,3.9c4.2,0,8.3-0.7,12.3-2.2c4-1.5,7.9-3.4,11.7-5.8l17.1,28.7c-6.4,3.7-13.4,6.6-20.8,8.8
S1201.4,339.6,1194.8,339.6z"/>
<path class="st19" d="M1327.2,338.8c-9.9,0-19.2-1.8-27.7-5.2c-8.6-3.5-15.8-8.4-21.7-14.6l16-21.2c5.9,5.3,11.4,9.2,16.4,11.4
c5.1,2.3,9.6,3.4,13.7,3.4c3.1,0,5.9-0.3,8.4-1c2.5-0.6,4.5-1.7,5.9-3c1.5-1.4,2.2-3.2,2.2-5.4c0-3.1-1.2-5.6-3.7-7.4
c-2.5-1.8-5.7-3.4-9.5-4.6c-3.9-1.2-8-2.4-12.4-3.7c-11-3.5-19-8.6-23.9-15.3c-4.9-6.7-7.3-14-7.3-21.9c0-6.1,1.6-12.2,4.8-18.3
c3.2-6.2,8.4-11.3,15.5-15.5c7.1-4.1,16.2-6.2,27.5-6.2c10.1,0,18.7,1,25.7,3c7,2,13.6,5.3,19.9,9.9l-14.6,22.6
c-3.5-2.8-7.4-5.1-11.7-7c-4.3-1.9-8.3-3-12-3.2c-3.3-0.2-6.1,0.2-8.4,1.1c-2.3,0.9-4.1,2.1-5.4,3.6c-1.3,1.5-1.9,3-1.9,4.7
c-0.2,3.5,1.1,6.3,4,8.3c2.8,2,6.5,3.6,10.9,4.7s8.7,2.4,13,3.9c5.9,1.8,10.9,4.3,15.2,7.4c4.2,3.1,7.5,6.9,9.8,11.2
c2.3,4.3,3.4,9.5,3.4,15.6c0,7.4-1.9,14.3-5.7,20.8c-3.8,6.5-9.5,11.8-17.1,15.9C1348.7,336.8,1339,338.8,1327.2,338.8z"/>
<path d="M1422.7,162.8h38.6v52.4h29.2v30.1h-29.2v91h-38.6v-91h-18.8v-30.1h18.8V162.8z"/>
<path d="M1554.9,215.7l3.6,33.1l-0.8-5c3.5-7.2,8.3-13.3,14.3-18.3c6.1-5.1,12.1-9,18.1-11.7c6-2.8,10.5-4.1,13.7-4.1l-1.9,38.6
c-9-1.1-16.7,0.4-23.2,4.4c-6.4,4-11.4,9.4-14.9,16c-3.5,6.6-5.2,13.4-5.2,20.4v47.2h-38.3V215.7H1554.9z"/>
<path d="M1632.4,173c0-5.7,2.2-10.4,6.8-14.1c4.5-3.7,9.3-5.5,14.5-5.5c5.1,0,9.9,1.8,14.2,5.5c4.3,3.7,6.5,8.4,6.5,14.1
c0,5.7-2.2,10.3-6.5,13.9c-4.3,3.6-9.1,5.4-14.2,5.4c-5.2,0-10-1.8-14.5-5.4C1634.6,183.3,1632.4,178.7,1632.4,173z M1633.2,215.7
h38.6v120.6h-38.6V215.7z"/>
<path d="M1762.3,339.6c-11.2,0-21.4-2.3-30.5-6.8c-9.1-4.5-16.3-11.4-21.7-20.8c-5.3-9.4-8-21.3-8-35.9c0-13.6,2.8-25.4,8.3-35.3
c5.5-9.9,12.8-17.6,21.8-23c9-5.4,18.6-8.1,28.7-8.1c12,0,21,2,27.2,5.9c6.2,4,11.3,8.3,15.3,13.1l-1.7,4.7l3.6-17.7h35.9v120.6
h-38.6v-26.2l3,8.3c-0.4,0-1.5,1.1-3.3,3.2c-1.8,2.1-4.6,4.6-8.1,7.4c-3.6,2.9-8,5.3-13.2,7.4S1769.5,339.6,1762.3,339.6z
M1773.4,308.2c4.6,0,8.7-0.7,12.4-2.1c3.7-1.4,6.9-3.4,9.7-6.1c2.8-2.7,5.1-6,7.2-10.1v-29.5c-1.5-4-3.7-7.5-6.6-10.3
c-2.9-2.8-6.4-5.1-10.5-6.6c-4-1.6-8.6-2.3-13.5-2.3c-5.5,0-10.6,1.4-15.3,4.3c-4.7,2.9-8.4,6.8-11.2,11.7
<path class="st19" d="M1422.7,162.8h38.6v52.4h29.2v30.1h-29.2v91h-38.6v-91h-18.8v-30.1h18.8V162.8z"/>
<path class="st19" d="M1554.9,215.7l3.6,33.1l-0.8-5c3.5-7.2,8.3-13.3,14.3-18.3c6.1-5.1,12.1-9,18.1-11.7c6-2.8,10.5-4.1,13.7-4.1
l-1.9,38.6c-9-1.1-16.7,0.4-23.2,4.4c-6.4,4-11.4,9.4-14.9,16c-3.5,6.6-5.2,13.4-5.2,20.4v47.2h-38.3V215.7H1554.9z"/>
<path class="st19" d="M1632.4,173c0-5.7,2.2-10.4,6.8-14.1c4.5-3.7,9.3-5.5,14.5-5.5c5.1,0,9.9,1.8,14.2,5.5
c4.3,3.7,6.5,8.4,6.5,14.1c0,5.7-2.2,10.3-6.5,13.9c-4.3,3.6-9.1,5.4-14.2,5.4c-5.2,0-10-1.8-14.5-5.4
C1634.6,183.3,1632.4,178.7,1632.4,173z M1633.2,215.7h38.6v120.6h-38.6V215.7z"/>
<path class="st19" d="M1762.3,339.6c-11.2,0-21.4-2.3-30.5-6.8c-9.1-4.5-16.3-11.4-21.7-20.8c-5.3-9.4-8-21.3-8-35.9
c0-13.6,2.8-25.4,8.3-35.3c5.5-9.9,12.8-17.6,21.8-23c9-5.4,18.6-8.1,28.7-8.1c12,0,21,2,27.2,5.9c6.2,4,11.3,8.3,15.3,13.1
l-1.7,4.7l3.6-17.7h35.9v120.6h-38.6v-26.2l3,8.3c-0.4,0-1.5,1.1-3.3,3.2c-1.8,2.1-4.6,4.6-8.1,7.4c-3.6,2.9-8,5.3-13.2,7.4
S1769.5,339.6,1762.3,339.6z M1773.4,308.2c4.6,0,8.7-0.7,12.4-2.1c3.7-1.4,6.9-3.4,9.7-6.1c2.8-2.7,5.1-6,7.2-10.1v-29.5
c-1.5-4-3.7-7.5-6.6-10.3c-2.9-2.8-6.4-5.1-10.5-6.6c-4-1.6-8.6-2.3-13.5-2.3c-5.5,0-10.6,1.4-15.3,4.3c-4.7,2.9-8.4,6.8-11.2,11.7
c-2.8,5-4.1,10.7-4.1,17.1c0,6.4,1.5,12.2,4.4,17.4c2.9,5.2,6.8,9.2,11.7,12.1C1762.4,306.7,1767.7,308.2,1773.4,308.2z"/>
</g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="2062.6323" y1="449.9906" x2="2062.6323" y2="286.1863">
<stop offset="0" style="stop-color:#965FA6"/>
<stop offset="1" style="stop-color:#4C3054"/>
<stop offset="0" style="stop-color:#DB8BF3"/>
<stop offset="1" style="stop-color:#915CA0"/>
</linearGradient>
<path class="st19" d="M1844.9,450c-6.3,0-11.4-1.8-15.2-5.5c-3.8-3.7-5.6-8.7-5.6-15.1c0-6.3,1.9-11.4,5.6-15.2
<path class="st20" d="M1844.9,450c-6.3,0-11.4-1.8-15.2-5.5c-3.8-3.7-5.6-8.7-5.6-15.1c0-6.3,1.9-11.4,5.6-15.2
c3.8-3.8,8.8-5.6,15.2-5.6c6.3,0,11.4,1.9,15.2,5.6c3.8,3.8,5.6,8.8,5.6,15.2c0,6.3-1.9,11.4-5.6,15.1
C1856.3,448.1,1851.3,450,1844.9,450z M1967.3,450c-12.1,0-21.9-4.2-29.3-12.6c-7.5-8.4-11.2-19.7-11.2-33.9v-33
c0-14.3,3.7-25.7,11.1-34.1c7.4-8.4,17.2-12.6,29.4-12.6c10,0,18,2.8,23.9,8.5c5.9,5.7,8.9,13.5,8.9,23.4l-6.2-6.4h6.4l-0.9-29

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M13.4 25.05h21.25V22.8H13.4ZM9.9 41q-1.2 0-2.05-.85Q7 39.3 7 38.1V9.9q0-1.2.85-2.05Q8.7 7 9.9 7h28.2q1.2 0 2.05.85.85.85.85 2.05v28.2q0 1.2-.85 2.05-.85.85-2.05.85Zm0-2.25h28.2q.25 0 .45-.2t.2-.45V9.9q0-.25-.2-.45t-.45-.2H9.9q-.25 0-.45.2t-.2.45v28.2q0 .25.2.45t.45.2Zm-.65-29.5v29.5-29.5Z"/></svg>

After

Width:  |  Height:  |  Size: 370 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M22.85 33.5h2.3v-8.35h8.35v-2.3h-8.35V14.5h-2.3v8.35H14.5v2.3h8.35ZM9.9 41q-1.2 0-2.05-.85Q7 39.3 7 38.1V9.9q0-1.2.85-2.05Q8.7 7 9.9 7h28.2q1.2 0 2.05.85.85.85.85 2.05v28.2q0 1.2-.85 2.05-.85.85-2.05.85Zm0-2.25h28.2q.25 0 .45-.2t.2-.45V9.9q0-.25-.2-.45t-.45-.2H9.9q-.25 0-.45.2t-.2.45v28.2q0 .25.2.45t.45.2Zm-.65-29.5v29.5-29.5Z"/></svg>

After

Width:  |  Height:  |  Size: 409 B

@ -0,0 +1,99 @@
.container {
max-width: 960px;
}
.service:nth-child(1) {
border-top-left-radius: 0.375rem;
border-top-right-radius: 0.375rem;
}
.service:nth-last-child(1) {
border-bottom-left-radius: 0.375rem;
border-bottom-right-radius: 0.375rem;
}
.service[open] > summary > span {
display: none;
}
.service[open] .icon-open {
display: none;
}
.service[open] .icon-close {
display: inline !important;
}
.tooltip {
--bs-tooltip-max-width: 20vw;
}
.status-service:hover {
background-color: rgba(0, 0, 0, .1);
}
tr:nth-last-child(1) .status-service {
border-bottom: none !important;
}
@media (min-width: 100px) {
tr:nth-child(1) .status-service {
border-top: none !important;
}
}
@media (max-width: 1000px) {
.status-service {
display: block;
width: 100% !important;
border-top: none !important;
border-left: none !important;
border-right: none !important;
}
.status-filler {
display: none !important;
}
.tooltip {
--bs-tooltip-max-width: 80vw;
}
}
@media (min-width: 800px) {
.history-60, .history-30 {
display: none;
}
.history-90 {
display: block;
}
}
@media (max-width: 800px) {
.history-30, .history-90 {
display: none;
}
.history-60 {
display: block;
}
}
@media (max-width: 670px) {
.history-60, .history-90 {
display: none;
}
.history-30 {
display: block;
}
}
.custom-outage *:nth-last-child(1) {
margin-bottom: 0 !important;
}
.maintenances *:nth-last-child(1) {
margin-bottom: 0 !important;
}
Loading…
Cancel
Save