[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 \
openssl \
tzdata \
py3-psutil \
&& pip3 install --upgrade pip \
docker \
flask \

View File

@ -6,6 +6,7 @@ from flask import jsonify
from flask import Response
from flask import request
from threading import Thread
from datetime import datetime
import docker
import uuid
import signal
@ -17,6 +18,7 @@ import ssl
import socket
import subprocess
import traceback
import psutil
docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
app = Flask(__name__)
@ -326,6 +328,48 @@ class container_post(Resource):
else:
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 recv_socket_data(c_socket, timeout):
@ -406,6 +450,7 @@ def startFlaskAPI():
api.add_resource(containers_get, '/containers/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(host_stats_get, '/host/stats')
if __name__ == '__main__':
api_thread = Thread(target=startFlaskAPI)

View File

@ -44,15 +44,22 @@ foreach ($containers as $container => $container_info) {
$containers[$container]['State']['StartedAtHR'] = $started;
}
// get mailconf data
$hostname = getenv('MAILCOW_HOSTNAME');
$timezone = getenv('TZ');
$template = 'debug.twig';
$template_data = [
'log_lines' => getenv('LOG_LINES'),
'vmail_df' => $vmail_df,
'hostname' => $hostname,
'timezone' => $timezone,
'solr_status' => $solr_status,
'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60),
'clamd_status' => $clamd_status,
'containers' => $containers,
'lang_admin' => json_encode($lang['admin']),
'lang_debug' => json_encode($lang['debug']),
'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;
break;
case 'containers':
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
@ -146,5 +147,23 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
}
}
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);
}
});
// 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($){
if (localStorage.getItem("current_page") === null) {
@ -998,3 +1007,233 @@ jQuery(function($){
onVisible("[id^=rspamd_history]", () => draw_rspamd_history());
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]
);
echo json_encode($temp, JSON_UNESCAPED_SLASHES);
break;
case "solr":
$solr_status = solr_status();
$solr_size = ($solr_status['status']['dovecot-fts']['index']['size']);
$solr_documents = ($solr_status['status']['dovecot-fts']['index']['numDocs']);
if (strtolower(getenv('SKIP_SOLR')) != 'n') {
$solr_enabled = false;
}
else {
$solr_enabled = true;
}
echo json_encode(array(
'type' => 'info',
'solr_enabled' => $solr_enabled,
'solr_size' => $solr_size,
'solr_documents' => $solr_documents
));
break;
case "version":
echo json_encode(array(
'version' => $GLOBALS['MAILCOW_GIT_VERSION']
));
break;
break;
case "solr":
$solr_status = solr_status();
$solr_size = ($solr_status['status']['dovecot-fts']['index']['size']);
$solr_documents = ($solr_status['status']['dovecot-fts']['index']['numDocs']);
if (strtolower(getenv('SKIP_SOLR')) != 'n') {
$solr_enabled = false;
}
else {
$solr_enabled = true;
}
echo json_encode(array(
'type' => 'info',
'solr_enabled' => $solr_enabled,
'solr_size' => $solr_size,
'solr_documents' => $solr_documents
));
break;
case "host":
$stats = docker("host_stats");
echo json_encode($stats);
break;
case "version":
echo json_encode(array(
'version' => $GLOBALS['MAILCOW_GIT_VERSION']
));
break;
}
}
break;

View File

@ -494,6 +494,7 @@
"containers_info": "Container-Information",
"container_running": "Läuft",
"container_stopped": "Angehalten",
"current_time": "Systemzeit",
"disk_usage": "Festplattennutzung",
"docs": "Dokumente",
"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>",
"login_time": "Zeit",
"logs": "Protokolle",
"memory": "Arbeitsspeicher",
"online_users": "Benutzer online",
"restart_container": "Neustart",
"service": "Dienst",
@ -515,7 +517,10 @@
"static_logs": "Statische Logs",
"success": "Erfolg",
"system_containers": "System & Container",
"timezone": "Zeitzone",
"uptime": "Uptime",
"update_available": "Es ist ein Update verfügbar",
"no_update_available": "Das System ist auf aktuellem Stand",
"username": "Benutzername"
},
"diagnostics": {

View File

@ -494,6 +494,7 @@
"containers_info": "Container information",
"container_running": "Running",
"container_stopped": "Stopped",
"current_time": "System Time",
"disk_usage": "Disk usage",
"docs": "Docs",
"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>",
"login_time": "Time",
"logs": "Logs",
"memory": "Memory",
"online_users": "Users online",
"restart_container": "Restart",
"service": "Service",
@ -515,7 +517,10 @@
"static_logs": "Static logs",
"success": "Success",
"system_containers": "System & Containers",
"timezone": "Timezone",
"uptime": "Uptime",
"update_available": "There is an update available",
"no_update_available": "The System is on the latest version",
"username": "Username"
},
"diagnostics": {

View File

@ -89,39 +89,6 @@
<br>
</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">
<i style="font-size:10pt;" class="bi bi-plus-square"></i> API
</legend>

View File

@ -28,26 +28,125 @@
<div class="row">
<div class="col-md-12">
<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 class="card mb-4">
<div class="card-header">
<h3 class="card-title">{{ lang.debug.disk_usage }}</h3>
<h3 class="card-title">mailcow</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-sm-3">
<p><i class="bi bi-hdd-fill"></i> {{ vmail_df[0] }}</p>
<p>{{ vmail_df[2] }} / {{ vmail_df[1] }} ({{ vmail_df[4] }})</p>
<div class="col-sm-12 col-md-4 d-flex flex-column">
<img class="img-responsive" alt="mailcow-logo" src="{{ logo|default('/img/cow_mailcow.svg') }}" style="max-height: 200px;">
<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 class="col-sm-9">
<div class="progress">
<div class="progress-bar bg-info" role="progressbar" style="width:{{ vmail_df[4] }}"></div>
<div class="col-sm-12 col-md-8">
<div class="table-responsive" style="margin-top: 10px;">
<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 class="card mb-4">
<div class="card-header">
<h3 class="card-title">{{ lang.debug.solr_status }}</h3>
@ -124,6 +223,7 @@
</div>
<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-header d-flex">Postfix
<div class="btn-group ms-auto">
@ -139,6 +239,7 @@
</div>
<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-header d-flex"> Mailcow UI
<div class="btn-group ms-auto">
@ -154,6 +255,7 @@
</div>
<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-header d-flex">SASL
<div class="btn-group ms-auto">
@ -169,6 +271,7 @@
</div>
<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-header d-flex">Dovecot
<div class="btn-group ms-auto">
@ -184,6 +287,7 @@
</div>
<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-header d-flex">SOGo
<div class="btn-group ms-auto">
@ -199,6 +303,7 @@
</div>
<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-header d-flex">Netfilter
<div class="btn-group ms-auto">
@ -214,6 +319,7 @@
</div>
<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-header d-flex">Rspamd history
<div class="btn-group ms-auto">
@ -234,6 +340,7 @@
</div>
<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-header d-flex">Autodiscover
<div class="btn-group ms-auto">
@ -249,6 +356,7 @@
</div>
<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-header d-flex">Watchdog
<div class="btn-group ms-auto">
@ -264,6 +372,7 @@
</div>
<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-header d-flex">ACME
<div class="btn-group ms-auto">
@ -279,6 +388,7 @@
</div>
<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-header d-flex">API
<div class="btn-group ms-auto">
@ -294,6 +404,7 @@
</div>
<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-header d-flex">Ratelimits
<div class="btn-group ms-auto">
@ -315,6 +426,7 @@
<script type='text/javascript'>
var lang = {{ lang_admin|raw }};
var lang_debug = {{ lang_debug|raw }};
var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}';
var log_pagination_size = '{{ log_pagination_size }}';

View File

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