Maketank Theme migration
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2024-01-30 13:52:59 +01:00
parent f2643ac887
commit 53a6485a6e
413 changed files with 6836 additions and 1985 deletions

View File

@@ -0,0 +1,62 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
{% if userinfo.type_2fa == 0 %}
{% set linkeraction = 'preadd' %}
{% elseif userinfo['2fa_unsaved'] is defined and userinfo['2fa_unsaved'] %}
{% set linkeraction = 'add' %}
{% else %}
{% set linkeraction = 'delete' %}
{% endif %}
<form action="{{ linker({'section':'index','action':linkeraction}) }}" class="col-12 max-w-420 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{ lng('login.2fa') }}</h5>
<div>
{% if userinfo.type_2fa == 0 %}
<label for="type_2fa" class="col-form-label">{{ lng('2fa.2fa_overview_desc')|raw }}</label>
<select class="form-select" name="type_2fa" id="type_2fa" required>
{% for val,opt in type_select_values %}
<option value="{{ val }}">{{ opt }}</option>
{% endfor %}
</select>
{% elseif userinfo.type_2fa == 2 %}
<label for="qrcode" class="col-form-label">{{ lng('2fa.2fa_ga_desc')|raw }}</label>
<img src="{{ ga_qrcode }}" class="img-fluid" alt="QRCode" id="qrcode"/><br>
<span>Code: <code>{{ userinfo.data_2fa }}</code></span>
{% endif %}
{% if userinfo['2fa_unsaved'] is defined and userinfo['2fa_unsaved'] %}
<br>
<label for="codevalidation" class="col-form-label">{{ lng('login.2facode') }}</label>
<input type="text" name="codevalidation" id="codevalidation" class="form-control" required/>
<input type="hidden" name="type_2fa" id="type_2fa" value="{{ userinfo.type_2fa }}"/>
<input type="hidden" name="data_2fa" id="data_2fa" value="{{ userinfo.data_2fa }}"/>
{% endif %}
</div>
</div>
<div class="card-body d-grid gap-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="page" value="{{ page }}"/>
<input type="hidden" name="send" value="send"/>
{% if userinfo.type_2fa == 0 %}
<button class="btn btn-primary rounded-top-0" type="submit" name="preadd">
{{ lng('2fa.2fa_add') }}</button>
{% elseif userinfo['2fa_unsaved'] is defined and userinfo['2fa_unsaved'] %}
<button class="btn btn-primary rounded-top-0" type="submit" name="add">
{{ lng('2fa.2fa_add') }}</button>
{% else %}
<button class="btn btn-warning rounded-top-0" type="submit" name="delete">
{{ lng('2fa.2fa_delete') }}</button>
{% endif %}
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,34 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<form action="{{ linker({'section':'index'}) }}" class="col-12 max-w-420 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{ lng('menue.main.changelanguage') }}</h5>
<div>
<label for="def_language" class="col-form-label">{{ lng('login.language') }}</label>
<select class="form-select" name="def_language" id="def_language" required>
<option value="profile">{{ lng('login.profile_lng') }}</option>
{% for val,lang in languages %}
<option value="{{ val }}" {% if default_lang == val %} selected="selected" {% endif %}>{{ lang|raw }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="card-body d-grid gap-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="page" value="{{ page }}"/>
<input type="hidden" name="send" value="send"/>
<button class="btn btn-primary rounded-top-0" type="submit" name="dosave">
<i class="fa-regular fa-floppy-disk"></i>
{{ lng('menue.main.changelanguage') }}</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,57 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<form action="{{ linker({'section':'index'}) }}" class="col-12 max-w-420 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{ lng('menue.main.changepassword') }}</h5>
<div class="mb-3">
<label for="old_password" class="col-form-label">{{ lng('changepassword.old_password') }}</label>
<input class="form-control" type="password" name="old_password" id="old_password" value="" required/>
</div>
<div class="mb-3">
<label for="new_password" class="col-form-label">{{ lng('changepassword.new_password') }}</label>
<input class="form-control" type="password" name="new_password" id="new_password" value="" required/>
</div>
<div class="mb-3">
<label for="new_password_confirm" class="col-form-label">{{ lng('changepassword.new_password_confirm') }}</label>
<input class="form-control" type="password" name="new_password_confirm" id="new_password_confirm" value="" required/>
</div>
{% if userinfo.adminsession == 0 %}
<div class="mb-3">
<label for="change_main_ftp" class="col-form-label">{{ lng('changepassword.also_change_ftp') }}</label>
<div class="form-check form-switch">
<input type="hidden" name="change_main_ftp" value="false">
<input class="form-check-input" type="checkbox" name="change_main_ftp" id="change_main_ftp" value="true" checked>
</div>
</div>
<div class="mb-3">
<label for="change_stats" class="col-form-label">{{ lng('changepassword.also_change_stats') }}</label>
<div class="form-check form-switch">
<input type="hidden" name="change_stats" value="false">
<input class="form-check-input" type="checkbox" name="change_stats" id="change_stats" value="true" checked>
</div>
</div>
{% endif %}
</div>
<div class="card-body d-grid gap-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="page" value="{{ page }}"/>
<input type="hidden" name="send" value="send"/>
<button class="btn btn-primary rounded-top-0" type="submit" name="dosave">
<i class="fa-regular fa-floppy-disk"></i>
{{ lng('menue.main.changepassword') }}</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,34 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<form action="{{ linker({'section':'index'}) }}" class="col-12 max-w-420 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{ lng('menue.main.changetheme') }}</h5>
<div>
<label for="theme" class="col-form-label">{{ lng('panel.theme') }}</label>
<select class="form-select" name="theme" id="theme" required>
{% for val,t in themes %}
<option value="{{ val }}" {% if default_theme == val %} selected="selected" {% endif %}>{{ t|raw }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="card-body d-grid gap-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="page" value="{{ page }}"/>
<input type="hidden" name="send" value="send"/>
<button class="btn btn-primary rounded-top-0" type="submit" name="dosave">
<i class="fa-regular fa-floppy-disk"></i>
{{ lng('menue.main.changetheme') }}
</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,31 @@
{% macro ditem(lngstr, available, used, assigned = null, formatbytes = false, byte_usage = null) %}
<div class="col border-end border-bottom p-3">
<div class="row mb-1">
<div class="col text-truncate">{{ lng(lngstr) }}{% if byte_usage %} <small>({{ byte_usage|formatBytes }})</small>{% endif %}</div>
<div class="col-auto">
<small>{% if formatbytes %}{{ used|formatBytes }}{% else %}{{ used }}{% endif %}/{% if available < 0 %}{{ lng('panel.unlimited') }}{% else %}{% if formatbytes %}{{ available|formatBytes }}{% else %}{{ available }}{% endif %}{% endif %}</small>
</div>
</div>
<div class="progress progress-thin my-auto">
{% set usageperc = 1 %}
{% if available > 0 %}
{% set usageperc = (used / available) * 100|round %}
{% endif %}
<div
class="progress-bar{% if available < 0 %} bg-secondary{% elseif usageperc >= 75 and usageperc < 90 %} bg-warning{% elseif usageperc >= 90 %} bg-danger{% endif %}"
role="progressbar"
style="width: {% if available < 0 %}100{% else %}{{ usageperc }}{% endif %}%;"
aria-valuenow="{% if available < 0 %}100{% else %}{{ used }}{% endif %}"
aria-valuemin="0"
aria-valuemax="{% if available < 0 %}100{% else %}{{ available }}{% endif %}"></div>
</div>
{% if assigned is not empty %}
<div class="progress progress-thin mt-2 my-auto">
<div class="progress-bar bg-primary" role="progressbar"
style="width:{% if available > 0 %}{{ (assigned / available) * 100|round }}{% else %}1{% endif %}%;"
aria-valuenow="{{ assigned }}" aria-valuemin="0"
aria-valuemax="{{ available }}"></div>
</div>
{% endif %}
</div>
{% endmacro %}

View File

@@ -0,0 +1,34 @@
{% extends "Froxlor/user/table.html.twig" %}
{% block content %}
<div class="pb-2">
{% include 'Froxlor/misc/alertbox.html.twig' %}
</div>
<ul class="nav nav-tabs mb-3" id="ex1" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="ex1-tab-1" data-bs-toggle="tab" href="#ex1-tabs-1" role="tab" aria-controls="ex1-tabs-1" aria-selected="true">DNS Editor</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="ex1-tab-2" data-bs-toggle="tab" href="#ex1-tabs-2" role="tab" aria-controls="ex1-tabs-2" aria-selected="false">DNS Zone</a>
</li>
</ul>
<div class="tab-content" id="ex1-content">
<div class="tab-pane fade show active" id="ex1-tabs-1" role="tabpanel" aria-labelledby="ex1-tab-1">
{{ parent() }}
{% import "Froxlor/form/form.html.twig" as form %}
{{ form.form(formdata, formaction|default('#'), formdata.title, editid|default('')) }}
</div>
<div class="tab-pane fade" id="ex1-tabs-2" role="tabpanel" aria-labelledby="ex1-tab-2">
{% if zonefile is not empty %}
<div class="row">
<div class="col-12">
<textarea class="logcontent form-control" rows="25" cols="60" readonly>{{ zonefile }}</textarea>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
<div>
<h5 class="mb-1">
<i class="fa-solid fa-bug me-1"></i>
{{ lng('error.send_report_title') }}
</h5>
</div>
{% endblock %}
{% block content %}
<form action="{{ linker({'section':'index','page':'send_error_report','errorid':errorid}) }}" class="col-12 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<div class="alert alert-primary" role="alert">{{ lng('error.send_report_desc')|raw }}</div>
<code class="border rounded bg-white p-2 mb-3">{{ mail_html|nl2br }}</code>
<div>
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="send" value="send"/>
<div class="col-12 text-end">
<button type="submit" class="btn btn-primary">{{ lng('error.send_report') }}</button>
</div>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% extends "Froxlor/user/form.html.twig" %}
{% block content %}
{% if tabledata.table.tr is not empty and tabledata.table.tr is iterable %}
<div class="row mb-2">
{% set type = 'warning' %}
{% set alert_msg = lng('error.customerhasongoingbackupjob') %}
{% include 'Froxlor/misc/alertbox.html.twig' %}
</div>
{% import "Froxlor/table/table.html.twig" as table %}
{{ table.table(tabledata) }}
{% endif %}
{{ parent() }}
{% endblock %}

View File

@@ -0,0 +1,10 @@
{% extends "Froxlor/user/form.html.twig" %}
{% block content %}
{% if alert_msg is defined and alert_msg is not empty %}
<div class="mb-4">
{% include 'Froxlor/misc/alertbox.html.twig' %}
</div>
{% endif %}
{{ parent() }}
{% endblock %}

View File

@@ -0,0 +1,23 @@
{% extends "Froxlor/user/form.html.twig" %}
{% block content %}
{{ parent() }}
{% if replacers is not empty and replacers is iterable %}
<div class="card my-3">
<div class="card-body">
<h5 class="card-title">{{ lng('admin.templates.template_replace_vars') }}</h5>
<dl class="row">
{% for replacer in replacers.replacers %}
{% if (replacer.visible is defined and replacer.visible) or replacer.visible is not defined %}
<dt class="col-sm-3">{{ '{' }}{{ replacer.var }}{{ '}' }}</dt>
<dd class="col-sm-9">{{ replacer.description }}</dd>
{% endif %}
{% endfor %}
</dl>
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,57 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
{% if formdata.title is not empty %}
<div>
<h5 class="mb-1">
{% if formdata.image is not empty %}
<i class="{{ formdata.image }} me-1"></i>
{% endif %}
{{ formdata.title }}
</h5>
{% if formdata.description is not empty %}
<span class="text-muted">{{ formdata.description }}</span>
{% endif %}
</div>
{% endif %}
{% endblock %}
{% block actions %}
{% if formdata.self_overview is defined and formdata.self_overview is iterable %}
{% if actions_links is not defined %}{% set actions_links = [] %}{% endif %}
{% set actions_tmp = actions_links %}
{% set actions_links = {'href': linker(formdata.self_overview), 'icon': 'fa-solid fa-reply','label': lng('panel.backtooverview')} %}
{% set actions_links = {actions_links}|merge(actions_tmp) %}
{% endif %}
{% if actions_links is iterable or (entity_info is defined and entity_info is not empty) %}
<div>
{% if actions_links is iterable %}
{% for link in actions_links %}
<a class="btn {{ link.class|default('btn-outline-primary') }}" href="{{ link.href|raw }}">
<i class="{{ link.icon|default('fa-solid fa-plus-circle') }}"></i><span class="d-none d-lg-inline ms-lg-1">{{ link.label }}</span>
</a>
{% endfor %}
{% endif %}
{# TODO: eventually not used anymore because of using a documentation link
{% if entity_info is defined and entity_info is not empty %}
<div class="alert alert-info" role="alert">
{{ entity_info|raw }}
</div>
{% endif %}
#}
</div>
{% endif %}
{% endblock %}
{% block content %}
{% import "Froxlor/form/form.html.twig" as form %}
{{ form.form(formdata, formaction|default('#'), formdata.title, editid|default(''), false, idprefix|default('')) }}
{% endblock %}

View File

View File

@@ -0,0 +1,329 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block content %}
<h3 class="page-header">{{ lng('panel.dashboard') }}</h3>
{% if get_setting('panel.is_configured') == 0 and userinfo.adminsession == 1 and userinfo.change_serversettings == 1 %}
<div class="alert alert-info position-relative p-5">
<h3>{{ lng('welcome.title') }}</h3>
<p class="lead mb-5">{{ lng('welcome.config_note') }}</p>
<a class="btn btn-lg btn-light text-info" href="{{ linker({'section': 'configfiles', 'page': 'configfiles'}) }}">{{ lng('welcome.config_now') }}</a>
<aside class="position-absolute bottom-0 end-0 p-5 d-none d-md-block">
<i class="fa-solid fa-hat-wizard fa-5x"></i>
</aside>
</div>
{% endif %}
<div class="card">
{% import "Froxlor/user/dashboard-item.html.twig" as dashboard %}
{% if userinfo.adminsession == 1 %}
{# admin-resources #}
<div class="row row-cols-1 row-cols-sm-2 row-cols-xl-5 g-0">
{{ dashboard.ditem('admin.customers', userinfo.customers, overview.number_customers) }}
{{ dashboard.ditem('admin.domains', userinfo.domains, overview.number_domains) }}
{{ dashboard.ditem('customer.diskspace', userinfo.diskspace_bytes, overview.diskspace_bytes_used, overview.diskspace_bytes, true) }}
{{ dashboard.ditem('customer.traffic', userinfo.traffic_bytes, overview.traffic_bytes_used, overview.traffic_bytes, true) }}
{{ dashboard.ditem('customer.subdomains', userinfo.subdomains, overview.subdomains_used, overview.subdomains_assigned) }}
{{ dashboard.ditem('customer.mysqls', userinfo.mysqls, overview.mysqls_used, overview.mysqls_assigned) }}
{{ dashboard.ditem('customer.emails', userinfo.emails, overview.emails_used, overview.emails_assigned) }}
{{ dashboard.ditem('customer.accounts', userinfo.email_accounts, overview.email_accounts_used, overview.email_accounts_assigned) }}
{{ dashboard.ditem('customer.forwarders', userinfo.email_forwarders, overview.email_forwarders_used, overview.email_forwarders_assigned) }}
{{ dashboard.ditem('customer.ftps', userinfo.ftps, overview.ftps_used, overview.ftps_assigned) }}
</div>
{% else %}
{# customer-resources #}
<div class="row row-cols-1 row-cols-sm-2 row-cols-xl-4 g-0">
{{ dashboard.ditem('customer.total_diskspace', userinfo.diskspace_bytes, userinfo.total_bytes_used, null, true) }}
{{ dashboard.ditem('customer.diskspace', userinfo.diskspace_bytes, userinfo.diskspace_bytes_used, null, true) }}
{{ dashboard.ditem('customer.traffic', userinfo.traffic_bytes, userinfo.traffic_bytes_used, null, true) }}
{{ dashboard.ditem('customer.subdomains', userinfo.subdomains, userinfo.subdomains_used) }}
{{ dashboard.ditem('customer.mysqls', userinfo.mysqls, userinfo.mysqls_used, null, false, userinfo.dbspace_used) }}
{{ dashboard.ditem('customer.emails', userinfo.emails, userinfo.emails_used) }}
{{ dashboard.ditem('customer.accounts', userinfo.email_accounts, userinfo.email_accounts_used, null, false, userinfo.mailspace_used) }}
{{ dashboard.ditem('customer.forwarders', userinfo.email_forwarders, userinfo.email_forwarders_used) }}
{{ dashboard.ditem('customer.ftps', userinfo.ftps, userinfo.ftps_used) }}
</div>
{% endif %}
</div>
<div class="row">
{% if userinfo.adminsession == 1 %}
<div
class="col-12 col-lg-6">
{# system infos #}
<div class="card mb-3">
<div class="card-header">
<i class="fa-solid fa-gears me-1"></i>
{{ lng('admin.systemdetails') }}
<div class="float-end">
<button id="copySysInfo" class="btn btn-outline-dark" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .5rem;" title="Copy to clipboard"><i class="fa-solid fa-copy"></i></button>
</div>
<div id="ccSysInfo" class="d-none">
- Froxlor: {{ call_static('\\Froxlor\\Froxlor', 'getVersionString') }}
- {{ lng('serversettings.update_channel.title') }}: {{ get_setting('system.update_channel') }}
- {{ lng('admin.serversoftware') }}: {{ sysinfo.webserver }}
- {{ lng('admin.phpversion') }}: {{ sysinfo.phpversion }}
- {{ lng('admin.mysqlserverversion') }}: {{ sysinfo.mysqlserverversion }}
- {{ lng('admin.webserverinterface') }}: {{ sysinfo.phpsapi }}
- Kernel: {{ sysinfo.kernel }}
- OS: {{ get_setting('system.distribution') }}
</div>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('admin.hostname') }}</div>
{{ sysinfo.hostname }}
</div>
</li>
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('admin.serversoftware') }}</div>
{{ sysinfo.webserver }}
</div>
</li>
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('admin.phpversion') }}</div>
<a href="{{ linker({'section':'settings','page':'phpinfo'}) }}">{{ sysinfo.phpversion }}</a>
</div>
</li>
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('admin.mysqlserverversion') }}</div>
{{ sysinfo.mysqlserverversion }}
</div>
</li>
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('admin.webserverinterface') }}</div>
{{ sysinfo.phpsapi }}
</div>
</li>
{% if sysinfo.memory is not empty %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('admin.memory') }}</div>
<pre>{{ sysinfo.memory }}</pre>
</div>
</li>
{% endif %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('admin.sysload') }}</div>
{{ sysinfo.load }}
</div>
</li>
{% if sysinfo.kernel is not empty %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">Kernel</div>
{{ sysinfo.kernel }}
</div>
</li>
{% endif %}
{% if sysinfo.uptime is not empty %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">Uptime</div>
{{ sysinfo.uptime }}
</div>
</li>
{% endif %}
</ul>
</div>
{% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %}
<div class="card mb-3">
<ul class="list-group list-group-flush">
<li class="list-group-item list-group-item-info d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
{{ userinfo.custom_notes|nl2br|raw }}
</div>
</li>
</ul>
</div>
{% endif %}
</div>
{% else %}
<div
class="col-12 col-md-6 col-lg-4">
{# account info #}
<div class="card mb-3">
<div class="card-header">
<i class="fa-solid fa-user me-1"></i>
{{ lng('index.accountdetails') }}
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('login.username') }}</div>
{{ userinfo.loginname }}
</div>
</li>
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('customer.email') }}</div>
{{ userinfo.email }}
</div>
</li>
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('customer.services') }}</div>
{% if userinfo.imap == 1 %}
<span class="badge bg-success">IMAP</span>
{% endif %}
{% if userinfo.pop3 == 1 %}
<span class="badge bg-success">POP3</span>
{% endif %}
{% if userinfo.phpenabled == 1 %}
<span class="badge bg-success">PHP</span>
{% endif %}
{% if userinfo.perlenabled == 1 %}
<span class="badge bg-success">Perl/CGI</span>
{% endif %}
{% if userinfo.api_allowed == 1 %}
<a href="{{ linker({'section':'index','page':'apikeys'}) }}">
<span class="badge bg-success">API</span>
</a>
{% endif %}
</div>
</li>
{% if stdsubdomain is not empty %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('admin.stdsubdomain') }}</div>
{{ stdsubdomain }}
</div>
</li>
{% endif %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('customer.domains') }}</div>
{% for domain in domains %}
{{ domain }}<br>
{% endfor %}
</div>
<span class="badge bg-primary rounded-pill">{{ domains|length }}</span>
</li>
</ul>
</div>
</div>
<div
class="col-12 col-md-6 col-lg-4">
{# customer details #}
<div class="card">
<div class="card-header">
<i class="fa-solid fa-id-card me-1"></i>
{{ lng('index.customerdetails') }}
</div>
<ul class="list-group list-group-flush">
{% if userinfo.customernumber is not empty %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('customer.customernumber') }}</div>
{{ userinfo.customernumber }}
</div>
</li>
{% endif %}
{% if userinfo.company is not empty %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('customer.company') }}</div>
{{ userinfo.company }}
</div>
</li>
{% endif %}
{% if userinfo.name is not empty %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('customer.name') }}</div>
{{ userinfo.firstname }}
{{ userinfo.name }}
</div>
</li>
{% endif %}
{% if userinfo.street is not empty %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('customer.street') }}</div>
{{ userinfo.street }}
</div>
</li>
{% endif %}
{% if userinfo.city is not empty %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('customer.zipcode') }}/{{ lng('customer.city') }}</div>
{{ userinfo.zipcode }}
{{ userinfo.city }}
</div>
</li>
{% endif %}
{% if userinfo.custom_notes is not empty and userinfo.custom_notes_show == 1 %}
<li class="list-group-item list-group-item-info d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
{{ userinfo.custom_notes|nl2br|raw }}
</div>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
<div class="col-12 {% if userinfo.adminsession == 1 %}col-lg-6{% else %}col-lg-4{% endif %}">
{% if userinfo.adminsession == 1 %}
{# froxlor-details #}
<div class="card mb-3">
<div class="card-header">
<i class="fa-solid fa-wrench me-1"></i>
{{ lng('admin.froxlordetails') }}
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">{{ lng('tasks.outstanding_tasks') }}</div>
{% for task in outstanding_tasks %}
<i class="fa-regular fa-clock"></i>
{{ task.desc }}<br>
{% endfor %}
</div>
</li>
{% for cronrun in cron_last_runs %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">{{ cronrun.title }}</div>
<span class="badge bg-primary">
{% if cronrun.lastrun > 0 %}
{{ cronrun.lastrun|date('d.m.Y H:i') }}
{% else %}
{{ lng('cronjobs.notyetrun') }}
{% endif %}
</span>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if (get_setting('admin.show_news_feed') == 1 and userinfo.adminsession == 1) or (get_setting('customer.show_news_feed') == 1 and userinfo.adminsession == 0) %}
<div id="newsfeed" class="card" {% if userinfo.adminsession == 0 %} data-role="customer" {% endif %}>
<div class="card-header">
<i class="fa-solid fa-info-circle me-1"></i>
{% if get_setting('customer.news_feed_url') is empty %}Froxlor
{% endif %}Newsfeed
</div>
<div class="list-group list-group-flush" id="newsfeeditems">
<div class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
Loading newsfeed...
</div>
<span>
<i class="fa-solid fa-spinner fa-spin"></i>
</span>
</div>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,2 @@
{% import "Froxlor/form/form.html.twig" as form %}
{{ form.form(formdata, formaction|default('#'), formdata.title, editid|default(''), true, idprefix|default('')) }}

View File

@@ -0,0 +1,47 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
<div>
<h5 class="mb-1">
<i class="fa-solid fa-magnifying-glass me-1"></i>
{{ lng('panel.viewlogs') }}
</h5>
</div>
{% endblock %}
{% block actions %}
{% if actions_links is iterable or (entity_info is defined and entity_info is not empty) %}
<div>
{% if actions_links is iterable %}
{% for link in actions_links %}
<a class="btn {{ link.class|default('btn-outline-primary') }}" href="{{ link.href|raw }}">
<i class="{{ link.icon|default('fa-solid fa-plus-circle') }}"></i>
<span class="d-none d-lg-inline ms-lg-1">{{ link.label }}</span>
</a>
{% endfor %}
{% endif %}
</div>
{% endif %}
{% endblock %}
{% block content %}
<ul class="nav nav-tabs mb-3" id="ex1" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="ex1-tab-1" data-bs-toggle="tab" href="#ex1-tabs-1" role="tab" aria-controls="ex1-tabs-1" aria-selected="true">Error log</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="ex1-tab-2" data-bs-toggle="tab" href="#ex1-tabs-2" role="tab" aria-controls="ex1-tabs-2" aria-selected="false">Access log</a>
</li>
</ul>
<div class="tab-content" id="ex1-content">
<div class="tab-pane fade show active" id="ex1-tabs-1" role="tabpanel" aria-labelledby="ex1-tab-1">
<textarea rows="16" class="logcontent" readonly>{{ error_log_content }}</textarea>
</div>
<div class="tab-pane fade" id="ex1-tabs-2" role="tabpanel" aria-labelledby="ex1-tab-2">
<textarea rows="16" class="logcontent" readonly>{{ access_log_content }}</textarea>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,9 @@
<a href="{{ link|default('#')|raw }}" class="list-group-item list-group-item-action" target="_blank">
<div class="d-flex w-100 justify-content-between">
<b class="mb-1">{{ title }}</b>
{% if date is not empty %}
<small>{{ date }}</small>
{% endif %}
</div>
<p class="small mb-1">{{ content|raw }}</p>
</a>

View File

@@ -0,0 +1,163 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
<h5 class="mb-1">
<i class="fa-solid fa-calculator me-1"></i>
{{ lng('admin.updatecounters') }}
</h5>
{% endblock %}
{% block content %}
{% for usersection,data in counters|reverse(true) %}
<div class="card table-responsive">
<table class="table table-borderless table-striped align-middle mb-0 px-3">
<thead>
<tr>
<th colspan="3">{{ lng('admin.' ~ usersection) }}</th>
</tr>
</thead>
<tbody>
{% for user in data %}
<tr>
{% if usersection == 'customers' %}
<th class="px-3 w-50" scope="row">
{{ call_static('\\Froxlor\\User', 'getCorrectUserSalutation', [user]) }}
(<a href="{{ linker({'section': 'customers', 'page':'customers','action': 'su','id':user.customerid}) }}">{{ user.loginname }}</a>)
</th>
<td class="px-3 w-25">
{{ lng('customer.subdomains') }}:
<span class="{% if user.subdomains_used == user.subdomains_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.subdomains_used }}
-&gt;
{{ user.subdomains_used_new }}</b>
</span><br/>
{{ lng('customer.mysqls') }}:
<span class="{% if user.mysqls_used == user.mysqls_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.mysqls_used }}
-&gt;
{{ user.mysqls_used_new }}</b>
</span><br/>
{{ lng('customer.ftps') }}:
<span class="{% if user.ftps_used == user.ftps_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.ftps_used }}
-&gt;
{{ user.ftps_used_new }}</b>
</span>
</td>
<td class="px-3 w-25">
{{ lng('customer.emails') }}:
<span class="{% if user.emails_used == user.emails_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.emails_used }}
-&gt;
{{ user.emails_used_new }}</b>
</span><br/>
{{ lng('customer.accounts') }}:
<span class="{% if user.email_accounts_used == user.email_accounts_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.email_accounts_used }}
-&gt;
{{ user.email_accounts_used_new }}</b>
</span><br/>
{{ lng('customer.forwarders') }}:
<span class="{% if user.email_forwarders_used == user.email_forwarders_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.email_forwarders_used }}
-&gt;
{{ user.email_forwarders_used_new }}</b>
</span>
{% if get_setting('system.mail_quota_enabled') == 1 %}
<br/>{{ lng('customer.email_quota') }}:
<span class="{% if user.email_quota_used == user.email_quota_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.email_quota_used }}
-&gt;
{{ user.email_quota_used_new }}</b>
</span>
{% endif %}
</td>
{% else %}
<th class="px-3 w-50" scope="row">
{{ user.name }}
{% if user.adminid != call_static('\\Froxlor\\CurrentUser', 'getField', ['adminid']) %}
(<a href="{{ linker({'section': 'admins', 'page':'admins','action': 'su','id':user.adminid}) }}">{{ user.loginname }}</a>)
{% endif %}
</th>
<td class="px-3 w-25">
{{ lng('admin.customers') }}:
<span class="{% if user.customers_used == user.customers_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.customers_used }}
-&gt;
{{ user.customers_used_new }}</b>
</span><br/>
{{ lng('customer.domains') }}:
<span class="{% if user.domains_used == user.domains_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.domains_used }}
-&gt;
{{ user.domains_used_new }}</b>
</span><br/>
{{ lng('customer.subdomains') }}:
<span class="{% if user.subdomains_used == user.subdomains_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.subdomains_used }}
-&gt;
{{ user.subdomains_used_new }}</b>
</span><br/>
{{ lng('customer.diskspace') }}:
<span class="{% if user.diskspace_used == user.diskspace_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ (user.diskspace_used * 1024)|formatBytes }}
-&gt;
{{ (user.diskspace_used_new * 1024)|formatBytes }}</b>
</span><br/>
{{ lng('customer.traffic') }}:
<span class="{% if user.traffic_used == user.traffic_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ (user.traffic_used * 1024)|formatBytes }}
-&gt;
{{ (user.traffic_used_new * 1024)|formatBytes }}</b>
</span>
</td>
<td class="px-3 w-25">
{{ lng('customer.mysqls') }}:
<span class="{% if user.mysqls_used == user.mysqls_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.mysqls_used }}
-&gt;
{{ user.mysqls_used_new }}</b>
</span><br/>
{{ lng('customer.ftps') }}:
<span class="{% if user.ftps_used == user.ftps_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.ftps_used }}
-&gt;
{{ user.ftps_used_new }}</b>
</span><br/>
{{ lng('customer.emails') }}:
<span class="{% if user.emails_used == user.emails_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.emails_used }}
-&gt;
{{ user.emails_used_new }}</b>
</span><br/>
{{ lng('customer.accounts') }}:
<span class="{% if user.email_accounts_used == user.email_accounts_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.email_accounts_used }}
-&gt;
{{ user.email_accounts_used_new }}</b>
</span><br/>
{{ lng('customer.forwarders') }}:
<span class="{% if user.email_forwarders_used == user.email_forwarders_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.email_forwarders_used }}
-&gt;
{{ user.email_forwarders_used_new }}</b>
</span>
{% if get_setting('system.mail_quota_enabled') == 1 %}
<br/>{{ lng('customer.email_quota') }}:
<span class="{% if user.email_quota_used == user.email_quota_used_new %}text-success{% else %}warning{% endif %}">
<b>{{ user.email_quota_used }}
-&gt;
{{ user.email_quota_used_new }}</b>
</span>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,8 @@
{% extends "Froxlor/user/table.html.twig" %}
{% block content %}
<div class="pb-2">
{% include 'Froxlor/misc/alertbox.html.twig' %}
</div>
{{ parent() }}
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends "Froxlor/user/table.html.twig" %}
{% block heading %}
{% set listing = maillisting %}
{{ parent() }}
{% set listing = filelisting %}
{{ parent() }}
{% endblock %}
{% block content %}
{% set listing = maillisting %}
{{ parent() }}
{% set listing = filelisting %}
{{ parent() }}
{% endblock %}

View File

@@ -0,0 +1,60 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
{% if listing.title is not empty %}
<h5 class="mb-1">
{% if listing.icon is not empty %}
<i class="{{ listing.icon }} me-1"></i>
{% endif %}
{{ listing.title }}
{% if listing.total_entries > 0 %}
<small>
<span class="badge rounded-pill bg-secondary">{{ listing.total_entries }}</span>
</small>
{% endif %}
</h5>
{% if listing.description is not empty %}
<span class="text-muted mt-2">{{ listing.description }}</span>
{% endif %}
{% endif %}
{% endblock %}
{% block actions %}
{% if listing.is_search and listing.self_overview is defined and listing.self_overview is iterable %}
{% set actions_tmp = actions_links|default([]) %}
{% set actions_links = {'href': linker(listing.self_overview), 'icon': 'fa-solid fa-reply','label': lng('panel.backtooverview')} %}
{% set actions_links = {actions_links}|merge(actions_tmp) %}
{% endif %}
{% if actions_links is iterable or (entity_info is defined and entity_info is not empty) %}
<div>
{% if actions_links is iterable %}
{% for link in actions_links %}
<a class="btn {{ link.class|default('btn-outline-primary') }}" href="{{ link.href|raw }}">
<i class="{{ link.icon|default('fa-solid fa-plus-circle') }}"></i>
<span class="d-none d-lg-inline ms-lg-1">{{ link.label }}</span>
</a>
{% endfor %}
{% endif %}
{# TODO: eventually not used anymore because of using a documentation link
{% if entity_info is defined and entity_info is not empty %}
<div class="alert alert-info" role="alert">
{{ entity_info|raw }}
</div>
{% endif %}
#}
</div>
{% endif %}
{% endblock %}
{% block content %}
{% import "Froxlor/table/table.html.twig" as table %}
{{ table.table(listing) }}
{% endblock %}

View File

@@ -0,0 +1,305 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
<div>
<h5 class="mb-1">
<i class="fa-solid fa-chart-area me-1"></i>
{{ lng('admin.traffic') }}
</h5>
<span class="text-muted">{{ lng('admin.traffic_sub') }}</span>
</div>
{% endblock %}
{% block content %}
<!-- Range -->
<!-- TODO: set url on change. e.g.: ?param=days:7 -->
<div class="d-flex justify-content-center justify-content-md-end">
<select class="form-select mb-3 mb-md-4 w-auto mt-md-n4" aria-label="select the traffic range" name="range" data-baseref="{{ linker({'section':'traffic'}) }}">
<option value="hours:24" {% if range == 'hours:24' %}selected{% endif %}>{{ lng('traffic.ranges.last24h') }}</option>
<option value="days:7" {% if range == 'days:7' %}selected{% endif %}>{{ lng('traffic.ranges.last7d') }}</option>
<option value="days:30" {% if range == 'days:30' %}selected{% endif %}>{{ lng('traffic.ranges.last30d') }}</option>
<option value="currentmonth" {% if range == 'currentmonth' %}selected{% endif %}>{{ lng('traffic.ranges.cm') }}</option>
<option value="months:3" {% if range == 'months:3' %}selected{% endif %}>{{ lng('traffic.ranges.last3m') }}</option>
<option value="months:6" {% if range == 'months:6' %}selected{% endif %}>{{ lng('traffic.ranges.last6m') }}</option>
<option value="months:12" {% if range == 'months:12' %}selected{% endif %}>{{ lng('traffic.ranges.last12m') }}</option>
<option value="currentyear" {% if range == 'currentyear' %}selected{% endif %}>{{ lng('traffic.ranges.cy') }}</option>
{% for yd in years_avail %}
{% if yd.year != "now"|date('Y') %}
<option value="year:{{ yd.year }}" {% if range == 'year:' ~ yd.year %}selected{% endif %}>{{ yd.year }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="card">
<div class="row row-cols-1 row-cols-md-2 g-0">
<div class="col p-3 border-end">
<canvas id="trafficsummary" style="max-height:30vh;"></canvas>
</div>
<div class="col p-3 border-end">
<canvas id="customersummary" style="max-height:30vh;"></canvas>
</div>
</div>
</div>
<!-- Overview for given range -->
<div class="card">
<div class="row row-cols-2 row-cols-md-4 g-0">
<div class="col p-3 border-end">
<h3>{{ metrics.total|formatBytes }}</h3>
<span>{{ lng('traffic.months.total') }}</span>
</div>
<div class="col p-3 border-end">
<h3>{{ metrics.http|formatBytes }}</h3>
<span>{{ lng('traffic.http') }}</span>
</div>
<div class="col p-3 border-end">
<h3>{{ metrics.ftp|formatBytes }}</h3>
<span>{{ lng('traffic.ftp') }}</span>
</div>
<div class="col p-3 border-end">
<h3>{{ metrics.mail|formatBytes }}</h3>
<span>{{ lng('traffic.mail') }}</span>
</div>
</div>
</div>
{% if userinfo.adminsession == 1 %}
<!-- Overview for given range by user -->
<h4 class="page-header">{{ lng('traffic.bycustomers') }}</h4>
{% if users is not empty %}
<div class="card table-responsive">
<table class="table table-borderless table-striped align-middle mb-0 px-3">
<thead>
<tr>
<th scope="col">{{ lng('login.username') }}</th>
<th scope="col">{{ lng('traffic.months.total') }}</th>
<th scope="col">{{ lng('traffic.http') }}</th>
<th scope="col">{{ lng('traffic.ftp') }}</th>
<th scope="col">{{ lng('traffic.mail') }}</th>
</tr>
</thead>
<tbody>
{% for uid,user in users %}
<tr>
<td>
<a href="{{ linker({'section':'customers','page':'customers','action':'su','id':uid}) }}">{{ user.loginname }}</a>
</td>
<td>{{ user.total|formatBytes }}</td>
<td>{{ user.http|formatBytes }}</td>
<td>{{ user.ftp|formatBytes }}</td>
<td>{{ user.mail|formatBytes }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card">
<div class="card-body">
<p>{{ lng('traffic.nodata') }}</p>
</div>
</div>
{% endif %}
{% endif %}
<script>
const labelsS = ['{{ lng('traffic.http') }}', '{{ lng('traffic.ftp') }}', '{{ lng('traffic.mail') }}'];
const dataS = {
labels: labelsS,
datasets: [{
label: '{{ lng('traffic.trafficoverview') }}',
backgroundColor: ['rgb(255, 99, 132)', 'rgb(200, 199, 132)', 'rgb(255, 99, 0)'],
data: [{value: '{{ metrics.http|default(0) }}', formatted: '{{ metrics.http|formatBytes }}'}, {value: '{{ metrics.ftp|default(0) }}', formatted: '{{ metrics.ftp|formatBytes }}'}, {value: '{{ metrics.mail|default(0) }}', formatted: '{{ metrics.mail|formatBytes }}'}]
}]
};
const configS = {
type: 'pie',
data: dataS,
options: {
parsing: {
key: 'value'
},
responsive: true,
plugins: {
title: {
display: true,
text: '{{ lng('traffic.sumtotal') }}'
},
legend: {
position: 'right'
},
tooltip: {
enabled: true,
usePointStyle: true,
callbacks: {
label: (data) => {
return data.label + ' ' + data.raw.formatted
},
},
},
}
}
};
const sChart = new Chart(document.getElementById('trafficsummary'), configS);
{% if userinfo.adminsession == 1 %}
const labelsC = [];
const dataValues = [];
{% for user in users|sort((a, b) => a.total <=> b.total)|slice(0, 5) %}
labelsC.push('{{ user.loginname }}');
dataValues.push({value: '{{ user.total|default(0) }}', formatted: '{{ user.total|formatBytes }}'});
{% endfor %}
const dataC = {
labels: labelsC,
datasets: [{
label: '{{ lng('traffic.top5customers') }}',
backgroundColor: ['rgb(255, 99, 132)', 'rgb(200, 199, 132)', 'rgb(255, 99, 0)', 'rgb(100, 100, 132)', 'rgb(240, 150, 232)'],
data: dataValues
}]
};
const configC = {
type: 'pie',
data: dataC,
options: {
parsing: {
key: 'value'
},
responsive: true,
plugins: {
title: {
display: true,
text: '{{ lng('traffic.top5customers') }}'
},
legend: {
position: 'right'
},
tooltip: {
enabled: true,
usePointStyle: true,
callbacks: {
label: (data) => {
return data.label + ' ' + data.raw.formatted
},
},
},
}
}
};
const cChart = new Chart(document.getElementById('customersummary'), configC);
{% elseif not (range starts with 'hours') %}
const labelsC = [];
const dataValues = [];
{% if range starts with 'days' or range == 'currentmonth' %}
{% for d,dd in days %}
labelsC.push('{{ d }}');
{% endfor %}
{% elseif range starts with 'months' or range starts with 'year' or range == 'currentyear' %}
{% for m,md in months %}
labelsC.push('{{ m }}');
{% endfor %}
{% else %}
{% for yr,year in years %}
labelsC.push('{{ yr }}');
{% endfor %}
{% endif %}
const dataC = {
labels: labelsC,
datasets: [
{
label: '{{ lng('traffic.sumhttp') }}',
backgroundColor: 'rgb(255, 99, 132)',
{% if range starts with 'days' or range == 'currentmonth' %}
data: [{% for d,dd in days %}{value: '{{ dd.http|default(0) }}', formatted: '{{ dd.http|formatBytes }}', axisv: '{{ d }}'},{% endfor %}],
{% elseif range starts with 'months' or range starts with 'year' or range == 'currentyear' %}
data: [{% for m,md in months %}{value: '{{ md.http|default(0) }}', formatted: '{{ md.http|formatBytes }}', axisv: '{{ m }}'},{% endfor %}],
{% else %}
data: [{% for yr,year in years %}{value: '{{ year.http|default(0) }}', formatted: '{{ year.http|formatBytes }}', axisv: '{{ yr }}'},{% endfor %}],
{% endif %}
parsing: {
xAxisKey: 'axisv'
}
},
{
label: '{{ lng('traffic.sumftp') }}',
backgroundColor: 'rgb(200, 199, 132)',
{% if range starts with 'days' or range == 'currentmonth' %}
data: [{% for d,dd in days %}{value: '{{ dd.ftp|default(0) }}', formatted: '{{ dd.ftp|formatBytes }}', axisv: '{{ d }}'},{% endfor %}],
{% elseif range starts with 'months' or range starts with 'year' or range == 'currentyear' %}
data: [{% for m,md in months %}{value: '{{ md.ftp|default(0) }}', formatted: '{{ md.ftp|formatBytes }}', axisv: '{{ m }}'},{% endfor %}],
{% else %}
data: [{% for yr,year in years %}{value: '{{ year.ftp|default(0) }}', formatted: '{{ year.ftp|formatBytes }}', axisv: '{{ yr }}'},{% endfor %}],
{% endif %}
parsing: {
xAxisKey: 'axisv'
}
},
{
label: '{{ lng('traffic.summail') }}',
backgroundColor: 'rgb(255, 99, 0)',
{% if range starts with 'days' or range == 'currentmonth' %}
data: [{% for d,dd in days %}{value: '{{ dd.mail|default(0) }}', formatted: '{{ dd.mail|formatBytes }}', axisv: '{{ d }}'},{% endfor %}],
{% elseif range starts with 'months' or range starts with 'year' or range == 'currentyear' %}
data: [{% for m,md in months %}{value: '{{ md.ftp|default(0) }}', formatted: '{{ md.ftp|formatBytes }}', axisv: '{{ m }}'},{% endfor %}],
{% else %}
data: [{% for yr,year in years %}{value: '{{ year.mail|default(0) }}', formatted: '{{ year.mail|formatBytes }}', axisv: '{{ yr }}'},{% endfor %}],
{% endif %}
parsing: {
xAxisKey: 'axisv'
}
},
]
};
const configC = {
type: 'line',
data: dataC,
options: {
parsing: {
yAxisKey: 'value'
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
},
plugins: {
title: {
display: true,
text: '{{ lng('traffic.byrange') }}'
},
tooltip: {
enabled: true,
usePointStyle: true,
callbacks: {
label: (data) => {
return data.raw.formatted
},
},
},
}
}
};
const cChart = new Chart(document.getElementById('customersummary'), configC);
{% endif %}
</script>
{% endblock %}