[BS5] add host statistics

This commit is contained in:
FreddleSpl0it 2022-08-09 20:29:33 +02:00
parent 9747995510
commit a3c0737ba8
11 changed files with 469 additions and 65 deletions

View File

@ -8,6 +8,7 @@ RUN apk add --update --no-cache python3 \
py3-pip \ py3-pip \
openssl \ openssl \
tzdata \ tzdata \
py3-psutil \
&& pip3 install --upgrade pip \ && pip3 install --upgrade pip \
docker \ docker \
flask \ flask \

View File

@ -6,6 +6,7 @@ from flask import jsonify
from flask import Response from flask import Response
from flask import request from flask import request
from threading import Thread from threading import Thread
from datetime import datetime
import docker import docker
import uuid import uuid
import signal import signal
@ -17,6 +18,7 @@ import ssl
import socket import socket
import subprocess import subprocess
import traceback import traceback
import psutil
docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto') docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
app = Flask(__name__) app = Flask(__name__)
@ -326,6 +328,48 @@ class container_post(Resource):
else: else:
return jsonify(type='danger', msg='command did not complete') return jsonify(type='danger', msg='command did not complete')
class host_stats_get(Resource):
def get(self):
try:
system_time = datetime.now()
disk_io_before = psutil.disk_io_counters(perdisk=False)
net_io_before = psutil.net_io_counters(pernic=False)
time.sleep(1)
disk_io_after = psutil.disk_io_counters(perdisk=False)
net_io_after = psutil.net_io_counters(pernic=False)
disks_read_per_sec = disk_io_after.read_bytes - disk_io_before.read_bytes
disks_write_per_sec = disk_io_after.write_bytes - disk_io_before.write_bytes
net_recv_per_sec = net_io_after.bytes_recv - net_io_before.bytes_recv
net_sent_per_sec = net_io_after.bytes_sent - net_io_before.bytes_sent
host_stats = {
"cpu": {
"cores": psutil.cpu_count(),
"usage": psutil.cpu_percent()
},
"memory": {
"total": psutil.virtual_memory().total,
"usage": psutil.virtual_memory().percent,
"swap": psutil.swap_memory()
},
"disk": {
"read_bytes": disks_read_per_sec,
"write_bytes": disks_write_per_sec
},
"network": {
"bytes_recv": net_recv_per_sec,
"bytes_sent": net_sent_per_sec
},
"uptime": time.time() - psutil.boot_time(),
"system_time": system_time.strftime("%d.%m.%Y %H:%M:%S")
}
return host_stats
except Exception as e:
return jsonify(type='danger', msg=str(e))
def exec_cmd_container(container, cmd, user, timeout=2, shell_cmd="/bin/bash"): def exec_cmd_container(container, cmd, user, timeout=2, shell_cmd="/bin/bash"):
def recv_socket_data(c_socket, timeout): def recv_socket_data(c_socket, timeout):
@ -406,6 +450,7 @@ def startFlaskAPI():
api.add_resource(containers_get, '/containers/json') api.add_resource(containers_get, '/containers/json')
api.add_resource(container_get, '/containers/<string:container_id>/json') api.add_resource(container_get, '/containers/<string:container_id>/json')
api.add_resource(container_post, '/containers/<string:container_id>/<string:post_action>') api.add_resource(container_post, '/containers/<string:container_id>/<string:post_action>')
api.add_resource(host_stats_get, '/host/stats')
if __name__ == '__main__': if __name__ == '__main__':
api_thread = Thread(target=startFlaskAPI) api_thread = Thread(target=startFlaskAPI)

View File

@ -44,15 +44,22 @@ foreach ($containers as $container => $container_info) {
$containers[$container]['State']['StartedAtHR'] = $started; $containers[$container]['State']['StartedAtHR'] = $started;
} }
// get mailconf data
$hostname = getenv('MAILCOW_HOSTNAME');
$timezone = getenv('TZ');
$template = 'debug.twig'; $template = 'debug.twig';
$template_data = [ $template_data = [
'log_lines' => getenv('LOG_LINES'), 'log_lines' => getenv('LOG_LINES'),
'vmail_df' => $vmail_df, 'vmail_df' => $vmail_df,
'hostname' => $hostname,
'timezone' => $timezone,
'solr_status' => $solr_status, 'solr_status' => $solr_status,
'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60), 'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60),
'clamd_status' => $clamd_status, 'clamd_status' => $clamd_status,
'containers' => $containers, 'containers' => $containers,
'lang_admin' => json_encode($lang['admin']), 'lang_admin' => json_encode($lang['admin']),
'lang_debug' => json_encode($lang['debug']),
'lang_datatables' => json_encode($lang['datatables']), 'lang_datatables' => json_encode($lang['datatables']),
]; ];

View File

@ -32,6 +32,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
} }
} }
return false; return false;
break;
case 'containers': case 'containers':
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json'); curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
@ -146,5 +147,23 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
} }
} }
break; break;
case 'host_stats':
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/host/stats');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT);
$response = curl_exec($curl);
if ($response === false) {
$err = curl_error($curl);
curl_close($curl);
return $err;
}
else {
curl_close($curl);
$stats = json_decode($response, true);
if (!empty($stats)) return $stats;
}
return false;
break;
} }
} }

View File

@ -36,6 +36,15 @@ $(document).ready(function() {
$(this).text(started_local_date); $(this).text(started_local_date);
} }
}); });
// set default ChartJs Font Color
Chart.defaults.color = '#999';
// create net and disk charts
createNetAndDiskChart();
// check for new version
check_update(mailcow_info.version_tag, mailcow_info.project_url);
// update system stats
update_stats();
}); });
jQuery(function($){ jQuery(function($){
if (localStorage.getItem("current_page") === null) { if (localStorage.getItem("current_page") === null) {
@ -998,3 +1007,233 @@ jQuery(function($){
onVisible("[id^=rspamd_history]", () => draw_rspamd_history()); onVisible("[id^=rspamd_history]", () => draw_rspamd_history());
onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph()); onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph());
}); });
// update system stats - every 5 seconds if system & container tab is active
function update_stats(){
if (!$('#tab-containers').hasClass('active')) {
// tab not active - dont fetch stats - run again in n seconds
setTimeout(update_stats, 5000);
return;
}
window.fetch("/api/v1/get/status/host", {method:'GET',cache:'no-cache'}).then(function(response) {
return response.json();
}).then(function(data) {
$("#host_date").text(data.system_time);
$("#host_uptime").text(formatUptime(data.uptime));
$("#host_cpu_cores").text(data.cpu.cores);
$("#host_cpu_usage").text(parseInt(data.cpu.usage).toString() + "%");
$("#host_memory_total").text((data.memory.total / (1024 ** 3)).toFixed(2).toString() + "GB");
$("#host_memory_usage").text(parseInt(data.memory.usage).toString() + "%");
var net_io_chart = Chart.getChart("net_io_chart");
var disk_io_chart = Chart.getChart("disk_io_chart");
net_io_chart.data.labels.push(data.system_time.split(" ")[1]);
if (net_io_chart.data.labels.length > 20) {
net_io_chart.data.labels.shift();
}
net_io_chart.data.datasets[0].data.push((data.network.bytes_recv / 1024).toFixed(4));
net_io_chart.data.datasets[1].data.push((data.network.bytes_sent / 1024).toFixed(4));
if (net_io_chart.data.datasets[0].data.length > 20) {
net_io_chart.data.datasets[0].data.shift();
}
if (net_io_chart.data.datasets[1].data.length > 20) {
net_io_chart.data.datasets[1].data.shift();
}
disk_io_chart.data.labels.push(data.system_time.split(" ")[1]);
if (disk_io_chart.data.labels.length > 20) {
disk_io_chart.data.labels.shift();
}
disk_io_chart.data.datasets[0].data.push((data.disk.read_bytes / 1024).toFixed(4));
disk_io_chart.data.datasets[1].data.push((data.disk.write_bytes / 1024).toFixed(4));
if (disk_io_chart.data.datasets[0].data.length > 20) {
disk_io_chart.data.datasets[0].data.shift();
}
if (disk_io_chart.data.datasets[1].data.length > 20) {
disk_io_chart.data.datasets[1].data.shift();
}
net_io_chart.update();
disk_io_chart.update();
// run again in n seconds
setTimeout(update_stats, 5000);
});
}
// format hosts uptime seconds to readable string
function formatUptime(seconds){
seconds = Number(seconds);
var d = Math.floor(seconds / (3600*24));
var h = Math.floor(seconds % (3600*24) / 3600);
var m = Math.floor(seconds % 3600 / 60);
var s = Math.floor(seconds % 60);
var dFormat = d > 0 ? d + "D " : "";
var hFormat = h > 0 ? h + "H " : "";
var mFormat = m > 0 ? m + "M " : "";
var sFormat = s > 0 ? s + "S" : "";
return dFormat + hFormat + mFormat + sFormat;
}
// create network and disk chart
function createNetAndDiskChart(){
var net_io_ctx = document.getElementById("net_io_chart");
var disk_io_ctx = document.getElementById("disk_io_chart");
var dataNet = {
labels: [],
datasets: [{
label: "Recieve",
backgroundColor: "rgba(41, 187, 239, 0.3)",
borderColor: "rgba(41, 187, 239, 0.6)",
color: "#ff0000",
borderWidth: 2,
fill: true,
tension: 0.2,
data: []
}, {
label: "Sent",
backgroundColor: "rgba(239, 60, 41, 0.3)",
borderColor: "rgba(239, 60, 41, 0.6)",
borderWidth: 2,
fill: true,
tension: 0.2,
data: []
}]
};
var optionsNet = {
scales: {
yAxis: {
min: 0,
grid: {
display: false
},
ticks: {
callback: function(i, index, ticks) {
// b
if (i < 1000) return i.toFixed(2).toString()+' B/s';
// b to kb
i = i / 1024;
if (i < 1000) return i.toFixed(2).toString()+' KB/s';
// kb to mb
i = i / 1024;
if (i < 1000) return i.toFixed(2).toString()+' MB/s';
// final mb to gb
return (i / 1024).toFixed(2).toString()+' GB/s';
}
}
},
xAxis: {
grid: {
display: false
}
}
}
};
var dataDisk = {
labels: [],
datasets: [{
label: "Read",
backgroundColor: "rgba(41, 187, 239, 0.3)",
borderColor: "rgba(41, 187, 239, 0.6)",
color: "#ff0000",
borderWidth: 2,
fill: true,
tension: 0.2,
data: []
}, {
label: "Write",
backgroundColor: "rgba(239, 60, 41, 0.3)",
borderColor: "rgba(239, 60, 41, 0.6)",
borderWidth: 2,
fill: true,
tension: 0.2,
data: []
}]
};
var optionsDisk = {
scales: {
yAxis: {
min: 0,
grid: {
display: false
},
ticks: {
callback: function(i, index, ticks) {
// b
if (i < 1000) return i.toFixed(2).toString()+' B/s';
// b to kb
i = i / 1024;
if (i < 1000) return i.toFixed(2).toString()+' KB/s';
// kb to mb
i = i / 1024;
if (i < 1000) return i.toFixed(2).toString()+' MB/s';
// final mb to gb
return (i / 1024).toFixed(2).toString()+' GB/s';
}
}
},
xAxis: {
grid: {
display: false
}
}
}
};
var net_io_chart = new Chart(net_io_ctx, {
type: 'line',
data: dataNet,
options: optionsNet
});
var disk_io_chart = new Chart(disk_io_ctx, {
type: 'line',
data: dataDisk,
options: optionsDisk
});
}
// check for mailcow updates
function check_update(current_version, github_repo_url){
var github_account = github_repo_url.split("/")[3];
var github_repo_name = github_repo_url.split("/")[4];
// get details about latest release
window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/latest", {method:'GET',cache:'no-cache'}).then(function(response) {
return response.json();
}).then(function(latest_data) {
// get details about current release
window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/tags/"+current_version, {method:'GET',cache:'no-cache'}).then(function(response) {
return response.json();
}).then(function(current_data) {
// compare releases
var date_current = new Date(current_data.created_at);
var date_latest = new Date(latest_data.created_at);
if (date_latest.getTime() <= date_current.getTime()){
// no update available
$("#mailcow_update").removeClass("text-warning text-danger").addClass("text-success");
$("#mailcow_update").html("<b>" + lang_debug.no_update_available + "</b>");
} else {
// update available
$("#mailcow_update").removeClass("text-danger text-success").addClass("text-warning");
$("#mailcow_update").html(
`<b>` + lang_debug.update_available + `
<a target="_blank" href="https://github.com/`+github_account+`/`+github_repo_name+`/releases/tag/`+latest_data.tag_name+`">`+latest_data.tag_name+`</a></b>`
);
}
}).catch(err => {
// err
console.log(err);
$("#mailcow_update").removeClass("text-success text-warning").addClass("text-danger");
$("#mailcow_update").html("<b>Could not check for an Update</b>");
});
}).catch(err => {
// err
console.log(err);
$("#mailcow_update").removeClass("text-success text-warning").addClass("text-danger");
$("#mailcow_update").html("<b>Could not check for an Update</b>");
});
}

View File

@ -1474,29 +1474,33 @@ if (isset($_GET['query'])) {
'used_percent' => $vmail_df[4] 'used_percent' => $vmail_df[4]
); );
echo json_encode($temp, JSON_UNESCAPED_SLASHES); echo json_encode($temp, JSON_UNESCAPED_SLASHES);
break; break;
case "solr": case "solr":
$solr_status = solr_status(); $solr_status = solr_status();
$solr_size = ($solr_status['status']['dovecot-fts']['index']['size']); $solr_size = ($solr_status['status']['dovecot-fts']['index']['size']);
$solr_documents = ($solr_status['status']['dovecot-fts']['index']['numDocs']); $solr_documents = ($solr_status['status']['dovecot-fts']['index']['numDocs']);
if (strtolower(getenv('SKIP_SOLR')) != 'n') { if (strtolower(getenv('SKIP_SOLR')) != 'n') {
$solr_enabled = false; $solr_enabled = false;
} }
else { else {
$solr_enabled = true; $solr_enabled = true;
} }
echo json_encode(array( echo json_encode(array(
'type' => 'info', 'type' => 'info',
'solr_enabled' => $solr_enabled, 'solr_enabled' => $solr_enabled,
'solr_size' => $solr_size, 'solr_size' => $solr_size,
'solr_documents' => $solr_documents 'solr_documents' => $solr_documents
)); ));
break; break;
case "version": case "host":
echo json_encode(array( $stats = docker("host_stats");
'version' => $GLOBALS['MAILCOW_GIT_VERSION'] echo json_encode($stats);
)); break;
break; case "version":
echo json_encode(array(
'version' => $GLOBALS['MAILCOW_GIT_VERSION']
));
break;
} }
} }
break; break;

View File

@ -494,6 +494,7 @@
"containers_info": "Container-Information", "containers_info": "Container-Information",
"container_running": "Läuft", "container_running": "Läuft",
"container_stopped": "Angehalten", "container_stopped": "Angehalten",
"current_time": "Systemzeit",
"disk_usage": "Festplattennutzung", "disk_usage": "Festplattennutzung",
"docs": "Dokumente", "docs": "Dokumente",
"external_logs": "Externe Logs", "external_logs": "Externe Logs",
@ -504,6 +505,7 @@
"log_info": "<p>mailcow <b>in-memory Logs</b> werden in Redis Listen gespeichert, die maximale Anzahl der Einträge pro Anwendung richtet sich nach LOG_LINES (%d).\r\n <br>In-memory Logs sind vergänglich und nicht zur ständigen Aufbewahrung bestimmt. Alle Anwendungen, die in-memory protokollieren, schreiben ebenso in den Docker Daemon.\r\n <br>Das in-memory Protokoll versteht sich als schnelle Übersicht zum Debugging eines Containers, für komplexere Protokolle sollte der Docker Daemon konsultiert werden.</p>\r\n <p><b>Externe Logs</b> werden via API externer Applikationen bezogen.</p>\r\n <p><b>Statische Logs</b> sind weitestgehend Aktivitätsprotokolle, die nicht in den Docker Daemon geschrieben werden, jedoch permanent verfügbar sein müssen (ausgeschlossen API Logs).</p>", "log_info": "<p>mailcow <b>in-memory Logs</b> werden in Redis Listen gespeichert, die maximale Anzahl der Einträge pro Anwendung richtet sich nach LOG_LINES (%d).\r\n <br>In-memory Logs sind vergänglich und nicht zur ständigen Aufbewahrung bestimmt. Alle Anwendungen, die in-memory protokollieren, schreiben ebenso in den Docker Daemon.\r\n <br>Das in-memory Protokoll versteht sich als schnelle Übersicht zum Debugging eines Containers, für komplexere Protokolle sollte der Docker Daemon konsultiert werden.</p>\r\n <p><b>Externe Logs</b> werden via API externer Applikationen bezogen.</p>\r\n <p><b>Statische Logs</b> sind weitestgehend Aktivitätsprotokolle, die nicht in den Docker Daemon geschrieben werden, jedoch permanent verfügbar sein müssen (ausgeschlossen API Logs).</p>",
"login_time": "Zeit", "login_time": "Zeit",
"logs": "Protokolle", "logs": "Protokolle",
"memory": "Arbeitsspeicher",
"online_users": "Benutzer online", "online_users": "Benutzer online",
"restart_container": "Neustart", "restart_container": "Neustart",
"service": "Dienst", "service": "Dienst",
@ -515,7 +517,10 @@
"static_logs": "Statische Logs", "static_logs": "Statische Logs",
"success": "Erfolg", "success": "Erfolg",
"system_containers": "System & Container", "system_containers": "System & Container",
"timezone": "Zeitzone",
"uptime": "Uptime", "uptime": "Uptime",
"update_available": "Es ist ein Update verfügbar",
"no_update_available": "Das System ist auf aktuellem Stand",
"username": "Benutzername" "username": "Benutzername"
}, },
"diagnostics": { "diagnostics": {

View File

@ -494,6 +494,7 @@
"containers_info": "Container information", "containers_info": "Container information",
"container_running": "Running", "container_running": "Running",
"container_stopped": "Stopped", "container_stopped": "Stopped",
"current_time": "System Time",
"disk_usage": "Disk usage", "disk_usage": "Disk usage",
"docs": "Docs", "docs": "Docs",
"external_logs": "External logs", "external_logs": "External logs",
@ -504,6 +505,7 @@
"log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>", "log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>",
"login_time": "Time", "login_time": "Time",
"logs": "Logs", "logs": "Logs",
"memory": "Memory",
"online_users": "Users online", "online_users": "Users online",
"restart_container": "Restart", "restart_container": "Restart",
"service": "Service", "service": "Service",
@ -515,7 +517,10 @@
"static_logs": "Static logs", "static_logs": "Static logs",
"success": "Success", "success": "Success",
"system_containers": "System & Containers", "system_containers": "System & Containers",
"timezone": "Timezone",
"uptime": "Uptime", "uptime": "Uptime",
"update_available": "There is an update available",
"no_update_available": "The System is on the latest version",
"username": "Username" "username": "Username"
}, },
"diagnostics": { "diagnostics": {

View File

@ -89,39 +89,6 @@
<br> <br>
</div> </div>
<legend style="cursor:pointer;margin-top:40px" data-bs-target="#license" unselectable="on" data-bs-toggle="collapse">
<i style="font-size:10pt;" class="bi bi-plus-square"></i> {{ lang.admin.guid_and_license }}
</legend>
<hr />
<div id="license" class="collapse">
<form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
<div class="row">
<label class="control-label col-sm-3" for="guid">{{ lang.admin.guid }}:</label>
<div class="col-sm-9">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-suit-heart{% if gal.valid == true %}-fill text-danger{% endif %}"></i>
</span>
<input type="text" id="guid" class="form-control" value="{{ license_guid }}" readonly>
</div>
<p class="text-muted">
{{ lang.admin.customer_id }}: {{ gal.c|default('?')|raw }} -
{{ lang.admin.service_id }}: {{ gal.s|default('?')|raw }} -
{{ lang.admin.sal_level }}: {{ gal.m|default('?')|raw }}
</p>
</div>
</div>
<div class="row">
<div class="offset-sm-3 col-sm-9">
<p class="text-muted">{{ lang.admin.license_info|raw }}</p>
<div class="btn-group">
<button class="btn btn-sm d-block d-sm-inline btn-success" name="license_validate_now" type="submit" href="#">{{ lang.admin.validate_license_now }}</button>
</div>
</div>
</div>
</form>
</div>
<legend style="cursor:pointer;margin-top:20px" data-bs-target="#admin_api" unselectable="on" data-bs-toggle="collapse"> <legend style="cursor:pointer;margin-top:20px" data-bs-target="#admin_api" unselectable="on" data-bs-toggle="collapse">
<i style="font-size:10pt;" class="bi bi-plus-square"></i> API <i style="font-size:10pt;" class="bi bi-plus-square"></i> API
</legend> </legend>

View File

@ -28,26 +28,125 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="tab-content" style="padding-top:20px"> <div class="tab-content" style="padding-top:20px">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div role="tabpanel" class="tab-pane active" id="tab-containers"> <div role="tabpanel" class="tab-pane active" id="tab-containers">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">{{ lang.debug.disk_usage }}</h3> <h3 class="card-title">mailcow</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-sm-3"> <div class="col-sm-12 col-md-4 d-flex flex-column">
<p><i class="bi bi-hdd-fill"></i> {{ vmail_df[0] }}</p> <img class="img-responsive" alt="mailcow-logo" src="{{ logo|default('/img/cow_mailcow.svg') }}" style="max-height: 200px;">
<p>{{ vmail_df[2] }} / {{ vmail_df[1] }} ({{ vmail_df[4] }})</p> <div style="margin-top: 60px;">
<span class="d-block"><i class="bi bi-hdd-fill"></i> {{ vmail_df[0] }}</span>
<span class="d-block">{{ vmail_df[2] }} / {{ vmail_df[1] }} ({{ vmail_df[4] }})</span>
</div>
<div class="mt-2 mb-4">
<div class="progress">
<div class="progress-bar bg-info" role="progressbar" style="width:{{ vmail_df[4] }}"></div>
</div>
</div>
</div> </div>
<div class="col-sm-9"> <div class="col-sm-12 col-md-8">
<div class="progress"> <div class="table-responsive" style="margin-top: 10px;">
<div class="progress-bar bg-info" role="progressbar" style="width:{{ vmail_df[4] }}"></div> <table class="table table-striped table-condensed">
<tbody>
<tr>
<td>Hostname</td>
<td><div>
<p><b>{{ hostname }}</b></p>
</div></td>
</tr>
<tr>
<td>Version</td>
<td><div>
<p><b>{{ mailcow_info.version_tag }}</b></p>
<p id="mailcow_update"></p>
</div></td>
</tr>
<tr>
<td>Changelog</td>
<td><a href="{{ mailcow_info.project_url }}/releases/tag/{{ mailcow_info.version_tag }}" target="_blank">
{{ mailcow_info.project_url }}/releases/tag/{{ mailcow_info.version_tag }}
</a></td>
</tr>
<tr>
<td>{{ lang.debug.current_time }}</td>
<td id="host_date">-</td>
</tr>
<tr>
<td>{{ lang.debug.timezone }}</td>
<td>{{ timezone }}</td>
</tr>
<tr>
<td>{{ lang.debug.uptime }}</td>
<td id="host_uptime">-</td>
</tr>
<tr>
<td>CPU</td>
<td>
Cores <span id="host_cpu_cores">-</span><br />
Usage <span id="host_cpu_usage"></span>
</td>
</tr>
<tr>
<td>{{ lang.debug.memory }}</td>
<td>
Total <span id="host_memory_total">-</span><br />
Usage <span id="host_memory_usage"></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col-sm-6">
<canvas id="net_io_chart" width="400" height="200"></canvas>
</div>
<div class="col-sm-6">
<canvas id="disk_io_chart" width="400" height="200"></canvas>
</div>
<div class="col-sm-12">
<legend class="mt-4">
{{ lang.admin.guid_and_license }}
</legend>
<hr />
<div id="license">
<form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
<div class="row">
<label class="control-label col-sm-3" for="guid">{{ lang.admin.guid }}:</label>
<div class="col-sm-9">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-suit-heart{% if gal.valid == true %}-fill text-danger{% endif %}"></i>
</span>
<input type="text" id="guid" class="form-control" value="{{ license_guid }}" readonly>
</div>
<p class="text-muted">
{{ lang.admin.customer_id }}: {{ gal.c|default('?')|raw }} -
{{ lang.admin.service_id }}: {{ gal.s|default('?')|raw }} -
{{ lang.admin.sal_level }}: {{ gal.m|default('?')|raw }}
</p>
</div>
</div>
<div class="row">
<div class="offset-sm-3 col-sm-9">
<p class="text-muted">{{ lang.admin.license_info|raw }}</p>
<div class="btn-group">
<button class="btn btn-sm d-block d-sm-inline btn-success" name="license_validate_now" type="submit" href="#">{{ lang.admin.validate_license_now }}</button>
</div>
</div>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">{{ lang.debug.solr_status }}</h3> <h3 class="card-title">{{ lang.debug.solr_status }}</h3>
@ -124,6 +223,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-postfix-logs"> <div role="tabpanel" class="tab-pane" id="tab-postfix-logs">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">Postfix <div class="card-header d-flex">Postfix
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -139,6 +239,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-ui"> <div role="tabpanel" class="tab-pane" id="tab-ui">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex"> Mailcow UI <div class="card-header d-flex"> Mailcow UI
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -154,6 +255,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-sasl"> <div role="tabpanel" class="tab-pane" id="tab-sasl">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">SASL <div class="card-header d-flex">SASL
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -169,6 +271,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-dovecot-logs"> <div role="tabpanel" class="tab-pane" id="tab-dovecot-logs">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">Dovecot <div class="card-header d-flex">Dovecot
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -184,6 +287,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-sogo-logs"> <div role="tabpanel" class="tab-pane" id="tab-sogo-logs">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">SOGo <div class="card-header d-flex">SOGo
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -199,6 +303,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-netfilter-logs"> <div role="tabpanel" class="tab-pane" id="tab-netfilter-logs">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">Netfilter <div class="card-header d-flex">Netfilter
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -214,6 +319,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-rspamd-history"> <div role="tabpanel" class="tab-pane" id="tab-rspamd-history">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">Rspamd history <div class="card-header d-flex">Rspamd history
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -234,6 +340,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-autodiscover-logs"> <div role="tabpanel" class="tab-pane" id="tab-autodiscover-logs">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">Autodiscover <div class="card-header d-flex">Autodiscover
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -249,6 +356,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-watchdog-logs"> <div role="tabpanel" class="tab-pane" id="tab-watchdog-logs">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">Watchdog <div class="card-header d-flex">Watchdog
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -264,6 +372,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-acme-logs"> <div role="tabpanel" class="tab-pane" id="tab-acme-logs">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">ACME <div class="card-header d-flex">ACME
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -279,6 +388,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-api-logs"> <div role="tabpanel" class="tab-pane" id="tab-api-logs">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">API <div class="card-header d-flex">API
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -294,6 +404,7 @@
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-api-rl"> <div role="tabpanel" class="tab-pane" id="tab-api-rl">
<div class="debug-log-info">{{ lang.debug.log_info|format(log_lines+1)|raw }}</div>
<div class="card panel-xs-lg"> <div class="card panel-xs-lg">
<div class="card-header d-flex">Ratelimits <div class="card-header d-flex">Ratelimits
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
@ -315,6 +426,7 @@
<script type='text/javascript'> <script type='text/javascript'>
var lang = {{ lang_admin|raw }}; var lang = {{ lang_admin|raw }};
var lang_debug = {{ lang_debug|raw }};
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
var log_pagination_size = '{{ log_pagination_size }}'; var log_pagination_size = '{{ log_pagination_size }}';

View File

@ -509,7 +509,7 @@ services:
- watchdog - watchdog
dockerapi-mailcow: dockerapi-mailcow:
image: mailcow/dockerapi:1.42 image: mailcow/dockerapi:1.43
security_opt: security_opt:
- label=disable - label=disable
restart: always restart: always