first refactor of config-files
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
@@ -20,6 +20,8 @@ const AREA = 'admin';
|
||||
require __DIR__ . '/lib/init.php';
|
||||
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\UI\Request;
|
||||
|
||||
if ($userinfo['change_serversettings'] == '1') {
|
||||
|
||||
@@ -28,98 +30,17 @@ if ($userinfo['change_serversettings'] == '1') {
|
||||
\Froxlor\UI\Response::redirectTo('admin_configfiles.php');
|
||||
}
|
||||
|
||||
$customer_tmpdir = '/tmp/';
|
||||
if (Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_tmpdir') != '') {
|
||||
$customer_tmpdir = Settings::Get('system.mod_fcgid_tmpdir');
|
||||
} elseif (Settings::Get('phpfpm.enabled') == '1' && Settings::Get('phpfpm.tmpdir') != '') {
|
||||
$customer_tmpdir = Settings::Get('phpfpm.tmpdir');
|
||||
}
|
||||
|
||||
// try to convert namserver hosts to ip's
|
||||
$ns_ips = "";
|
||||
$known_ns_ips = [];
|
||||
if (Settings::Get('system.nameservers') != '') {
|
||||
$nameservers = explode(',', Settings::Get('system.nameservers'));
|
||||
foreach ($nameservers as $nameserver) {
|
||||
$nameserver = trim($nameserver);
|
||||
// DNS servers might be multi homed; allow transfer from all ip
|
||||
// addresses of the DNS server
|
||||
$nameserver_ips = \Froxlor\PhpHelper::gethostbynamel6($nameserver);
|
||||
// append dot to hostname
|
||||
if (substr($nameserver, - 1, 1) != '.') {
|
||||
$nameserver .= '.';
|
||||
}
|
||||
// ignore invalid responses
|
||||
if (! is_array($nameserver_ips)) {
|
||||
// act like \Froxlor\PhpHelper::gethostbynamel6() and return unmodified hostname on error
|
||||
$nameserver_ips = array(
|
||||
$nameserver
|
||||
);
|
||||
} else {
|
||||
$known_ns_ips = array_merge($known_ns_ips, $nameserver_ips);
|
||||
}
|
||||
if (!empty($ns_ips)) {
|
||||
$ns_ips .= ',';
|
||||
}
|
||||
$ns_ips .= implode(",", $nameserver_ips);
|
||||
}
|
||||
}
|
||||
|
||||
// AXFR server
|
||||
if (Settings::Get('system.axfrservers') != '') {
|
||||
$axfrservers = explode(',', Settings::Get('system.axfrservers'));
|
||||
foreach ($axfrservers as $axfrserver) {
|
||||
if (!in_array(trim($axfrserver), $known_ns_ips)) {
|
||||
if (!empty($ns_ips)) {
|
||||
$ns_ips .= ',';
|
||||
}
|
||||
$ns_ips .= trim($axfrserver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$replace_arr = Array(
|
||||
'<SQL_UNPRIVILEGED_USER>' => $sql['user'],
|
||||
'<SQL_UNPRIVILEGED_PASSWORD>' => 'FROXLOR_MYSQL_PASSWORD',
|
||||
'<SQL_DB>' => $sql['db'],
|
||||
'<SQL_HOST>' => $sql['host'],
|
||||
'<SQL_SOCKET>' => isset($sql['socket']) ? $sql['socket'] : null,
|
||||
'<SERVERNAME>' => Settings::Get('system.hostname'),
|
||||
'<SERVERIP>' => Settings::Get('system.ipaddress'),
|
||||
'<NAMESERVERS>' => Settings::Get('system.nameservers'),
|
||||
'<NAMESERVERS_IP>' => $ns_ips,
|
||||
'<VIRTUAL_MAILBOX_BASE>' => Settings::Get('system.vmail_homedir'),
|
||||
'<VIRTUAL_UID_MAPS>' => Settings::Get('system.vmail_uid'),
|
||||
'<VIRTUAL_GID_MAPS>' => Settings::Get('system.vmail_gid'),
|
||||
'<SSLPROTOCOLS>' => (Settings::Get('system.use_ssl') == '1') ? 'imaps pop3s' : '',
|
||||
'<CUSTOMER_TMP>' => \Froxlor\FileDir::makeCorrectDir($customer_tmpdir),
|
||||
'<BASE_PATH>' => \Froxlor\FileDir::makeCorrectDir(\Froxlor\Froxlor::getInstallDir()),
|
||||
'<BIND_CONFIG_PATH>' => \Froxlor\FileDir::makeCorrectDir(Settings::Get('system.bindconf_directory')),
|
||||
'<WEBSERVER_RELOAD_CMD>' => Settings::Get('system.apachereload_command'),
|
||||
'<CUSTOMER_LOGS>' => \Froxlor\FileDir::makeCorrectDir(Settings::Get('system.logfiles_directory')),
|
||||
'<FPM_IPCDIR>' => \Froxlor\FileDir::makeCorrectDir(Settings::Get('phpfpm.fastcgi_ipcdir')),
|
||||
'<WEBSERVER_GROUP>' => Settings::Get('system.httpgroup')
|
||||
);
|
||||
|
||||
// get distro from URL param
|
||||
$distribution = (isset($_GET['distribution']) && $_GET['distribution'] != 'choose') ? $_GET['distribution'] : "";
|
||||
$service = (isset($_GET['service']) && $_GET['service'] != 'choose') ? $_GET['service'] : "";
|
||||
$daemon = (isset($_GET['daemon']) && $_GET['daemon'] != 'choose') ? $_GET['daemon'] : "";
|
||||
$distributions_select = "";
|
||||
$services_select = "";
|
||||
$daemons_select = "";
|
||||
$distribution = Request::get('distribution');
|
||||
|
||||
$configfiles = "";
|
||||
$services = "";
|
||||
$daemons = "";
|
||||
$distributions_select = [];
|
||||
|
||||
$services = [];
|
||||
$config_dir = \Froxlor\FileDir::makeCorrectDir(\Froxlor\Froxlor::getInstallDir() . '/lib/configfiles/');
|
||||
|
||||
if ($distribution != "") {
|
||||
|
||||
if (! file_exists($config_dir . '/' . $distribution . ".xml")) {
|
||||
trigger_error("Unknown distribution, are you playing around with the URL?");
|
||||
exit();
|
||||
if (!empty($distribution)) {
|
||||
if (!file_exists($config_dir . '/' . $distribution . ".xml")) {
|
||||
\Froxlor\UI\Response::dynamic_error("Unknown distribution");
|
||||
}
|
||||
|
||||
// create configparser object
|
||||
@@ -130,36 +51,10 @@ if ($userinfo['change_serversettings'] == '1') {
|
||||
|
||||
// get all the services from the distro
|
||||
$services = $configfiles->getServices();
|
||||
|
||||
if ($service != "") {
|
||||
|
||||
if (! isset($services[$service])) {
|
||||
trigger_error("Unknown service, are you playing around with the URL?");
|
||||
exit();
|
||||
}
|
||||
|
||||
$daemons = $services[$service]->getDaemons();
|
||||
|
||||
if ($daemon == "") {
|
||||
foreach ($daemons as $di => $dd) {
|
||||
$title = $dd->title;
|
||||
if ($dd->default) {
|
||||
$title = $title . " (" . strtolower($lng['panel']['default']) . ")";
|
||||
}
|
||||
$daemons_select .= \Froxlor\UI\HTML::makeoption($title, $di);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($services as $si => $sd) {
|
||||
$services_select .= \Froxlor\UI\HTML::makeoption($sd->title, $si);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// show list of available distro's
|
||||
$distros = glob($config_dir . '*.xml');
|
||||
// tmp array
|
||||
$distributions_select_data = array();
|
||||
// read in all the distros
|
||||
foreach ($distros as $_distribution) {
|
||||
// get configparser object
|
||||
@@ -167,114 +62,87 @@ if ($userinfo['change_serversettings'] == '1') {
|
||||
// get distro-info
|
||||
$dist_display = getCompleteDistroName($dist);
|
||||
// store in tmp array
|
||||
$distributions_select_data[$dist_display] = str_replace(".xml", "", strtolower(basename($_distribution)));
|
||||
$distributions_select[str_replace(".xml", "", strtolower(basename($_distribution)))] = $dist_display;
|
||||
}
|
||||
|
||||
// sort by distribution name
|
||||
ksort($distributions_select_data);
|
||||
|
||||
foreach ($distributions_select_data as $dist_display => $dist_index) {
|
||||
// create select-box-option
|
||||
$distributions_select .= \Froxlor\UI\HTML::makeoption($dist_display, $dist_index);
|
||||
}
|
||||
asort($distributions_select);
|
||||
}
|
||||
|
||||
if ($distribution != "" && $service != "" && $daemon != "") {
|
||||
if ($distribution != "" && isset($_POST['finish'])) {
|
||||
|
||||
if (! isset($daemons[$daemon])) {
|
||||
trigger_error("Unknown daemon, are you playing around with the URL?");
|
||||
exit();
|
||||
unset($_POST['finish']);
|
||||
$params = $_POST;
|
||||
$params['distro'] = $distribution;
|
||||
$params['system'] = [];
|
||||
foreach ($_POST['system'] as $sysdaemon) {
|
||||
$params['system'][] = $sysdaemon;
|
||||
}
|
||||
$params_content = json_encode($params);
|
||||
$params_filename = \Froxlor\FileDir::makeCorrectFile(\Froxlor\Froxlor::getInstallDir() . 'install/' . \Froxlor\Froxlor::genSessionId() . '.json');
|
||||
file_put_contents($params_filename, $params_content);
|
||||
|
||||
$confarr = $daemons[$daemon]->getConfig();
|
||||
|
||||
$configpage = '';
|
||||
|
||||
$distro_editor = $configfiles->distributionEditor;
|
||||
|
||||
$commands_pre = "";
|
||||
$commands_file = "";
|
||||
$commands_post = "";
|
||||
|
||||
$lasttype = '';
|
||||
$commands = '';
|
||||
foreach ($confarr as $_action) {
|
||||
if ($lasttype != '' && $lasttype != $_action['type']) {
|
||||
$commands = trim($commands);
|
||||
$numbrows = count(explode("\n", $commands));
|
||||
eval("\$configpage.=\"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles_commands") . "\";");
|
||||
$lasttype = '';
|
||||
$commands = '';
|
||||
}
|
||||
switch ($_action['type']) {
|
||||
case "install":
|
||||
$commands .= strtr($_action['content'], $replace_arr) . "\n";
|
||||
$lasttype = "install";
|
||||
break;
|
||||
case "command":
|
||||
$commands .= strtr($_action['content'], $replace_arr) . "\n";
|
||||
$lasttype = "command";
|
||||
break;
|
||||
case "file":
|
||||
if (array_key_exists('content', $_action)) {
|
||||
$commands_file = getFileContentContainer($_action['content'], $replace_arr, $_action['name'], $distro_editor);
|
||||
} elseif (array_key_exists('subcommands', $_action)) {
|
||||
foreach ($_action['subcommands'] as $fileaction) {
|
||||
if (array_key_exists('execute', $fileaction) && $fileaction['execute'] == "pre") {
|
||||
$commands_pre .= $fileaction['content'] . "\n";
|
||||
} elseif (array_key_exists('execute', $fileaction) && $fileaction['execute'] == "post") {
|
||||
$commands_post .= $fileaction['content'] . "\n";
|
||||
} elseif ($fileaction['type'] == 'file') {
|
||||
$commands_file = getFileContentContainer($fileaction['content'], $replace_arr, $_action['name'], $distro_editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
$realname = $_action['name'];
|
||||
$commands = trim($commands_pre);
|
||||
if ($commands != "") {
|
||||
$numbrows = count(explode("\n", $commands));
|
||||
eval("\$commands_pre=\"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles_commands") . "\";");
|
||||
}
|
||||
$commands = trim($commands_post);
|
||||
if ($commands != "") {
|
||||
$numbrows = count(explode("\n", $commands));
|
||||
eval("\$commands_post=\"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles_commands") . "\";");
|
||||
}
|
||||
eval("\$configpage.=\"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles_subfileblock") . "\";");
|
||||
$commands = '';
|
||||
$commands_pre = '';
|
||||
$commands_post = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
$commands = trim($commands);
|
||||
if ($commands != '') {
|
||||
$numbrows = count(explode("\n", $commands));
|
||||
eval("\$configpage.=\"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles_commands") . "\";");
|
||||
}
|
||||
eval("echo \"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles") . "\";");
|
||||
UI::twigBuffer('settings/configuration-final.html.twig', [
|
||||
'distribution' => $distribution,
|
||||
// alert
|
||||
'type' => 'info',
|
||||
'alert_msg' => 'Parameter file generated successfully. Now run the following command as root:',
|
||||
'basedir' => \Froxlor\Froxlor::getInstallDir(),
|
||||
'params_filename' => $params_filename
|
||||
]);
|
||||
} else {
|
||||
$basedir = \Froxlor\Froxlor::getInstallDir();
|
||||
eval("echo \"" . \Froxlor\UI\Template::getTemplate("configfiles/wizard") . "\";");
|
||||
|
||||
if (!empty($distribution)) {
|
||||
// show available services to configure
|
||||
$fields = $services;
|
||||
$link_params = ['section' => 'configfiles', 'distribution' => $distribution];
|
||||
UI::twigBuffer('settings/configuration.html.twig', [
|
||||
'action' => $linker->getLink($link_params),
|
||||
'fields' => $fields,
|
||||
'distribution' => $distribution
|
||||
]);
|
||||
} else {
|
||||
// @fixme check set distribution from settings
|
||||
|
||||
$cfg_formfield = [
|
||||
'config' => [
|
||||
'title' => $lng['admin']['configfiles']['serverconfiguration'],
|
||||
'image' => 'fa-solid fa-wrench',
|
||||
'sections' => [
|
||||
'section_config' => [
|
||||
'fields' => [
|
||||
'distribution' => ['type' => 'select', 'select_var' => $distributions_select, 'label' => $lng['admin']['configfiles']['distribution']]
|
||||
]
|
||||
]
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'class' => 'btn-outline-secondary',
|
||||
'label' => $lng['panel']['cancel'],
|
||||
'type' => 'reset'
|
||||
],
|
||||
[
|
||||
'label' => $lng['update']['proceed']
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
UI::twigBuffer('user/form-note.html.twig', [
|
||||
'formaction' => $linker->getLink(array('section' => 'configfiles')),
|
||||
'formdata' => $cfg_formfield['config'],
|
||||
// alert
|
||||
'type' => 'warning',
|
||||
'alert_msg' => $lng['panel']['settings_before_configuration']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
UI::twigOutputBuffer();
|
||||
} else {
|
||||
\Froxlor\UI\Response::redirectTo('admin_index.php');
|
||||
}
|
||||
|
||||
// helper functions
|
||||
function getFileContentContainer($file_content, &$replace_arr, $realname, $distro_editor)
|
||||
{
|
||||
$files = "";
|
||||
$file_content = trim($file_content);
|
||||
if ($file_content != '') {
|
||||
$file_content = strtr($file_content, $replace_arr);
|
||||
$file_content = htmlspecialchars($file_content);
|
||||
$numbrows = count(explode("\n", $file_content));
|
||||
eval("\$files=\"" . \Froxlor\UI\Template::getTemplate("configfiles/configfiles_file") . "\";");
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
function getCompleteDistroName($cparser)
|
||||
{
|
||||
// get distro-info
|
||||
|
||||
@@ -136,7 +136,7 @@ class PhpHelper
|
||||
$err_display .= '<br><p><pre>';
|
||||
$debug = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
foreach ($debug as $dline) {
|
||||
$err_display .= $dline['function'] . '() called at [' . str_replace(\Froxlor\Froxlor::getInstallDir(), '', $dline['file']) . ':' . $dline['line'] . ']<br>';
|
||||
$err_display .= $dline['function'] . '() called at [' . str_replace(\Froxlor\Froxlor::getInstallDir(), '', ($dline['file'] ?? 'unknown')) . ':' . ($dline['line'] ?? 0) . ']<br>';
|
||||
}
|
||||
$err_display .= '</pre></p>';
|
||||
// end later
|
||||
|
||||
File diff suppressed because one or more lines are too long
12
templates/Froxlor/settings/configuration-final.html.twig
Normal file
12
templates/Froxlor/settings/configuration-final.html.twig
Normal file
@@ -0,0 +1,12 @@
|
||||
{% extends "Froxlor/settings/configuration.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-2">
|
||||
{% include 'Froxlor/misc/alertbox.html.twig' %}
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<textarea cols="12" rows="4" readonly class="form-control w-100">`which php` {{ basedir }}install/scripts/config-services.php --froxlor-dir={{ basedir }} --apply={{ params_filename }}
|
||||
rm {{ params_filename }}</textarea>
|
||||
</div>
|
||||
{% endblock %}
|
||||
103
templates/Froxlor/settings/configuration.html.twig
Normal file
103
templates/Froxlor/settings/configuration.html.twig
Normal file
@@ -0,0 +1,103 @@
|
||||
{% extends "Froxlor/userarea.html.twig" %}
|
||||
|
||||
{% block heading %}
|
||||
<h5>
|
||||
<i class="fa-solid fa-wrench"></i>
|
||||
{{ lng('admin.configfiles.serverconfiguration') }}
|
||||
</h5>
|
||||
<span class="text-muted">Configure the system services</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block actions %}
|
||||
<a class="btn btn-outline-primary" href="{{ linker({'section':'configfiles'}) }}">
|
||||
<i class="fa-solid fa-grip me-1"></i>
|
||||
{{ lng('admin.configfiles.distribution') }}:
|
||||
{{ distribution }}
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form action="{{ action|default(filename) }}" method="post" enctype="application/x-www-form-urlencoded" class="form">
|
||||
{% block settings %}
|
||||
<div class="row row-cols-2 row-cols-md-2 row-cols-xl-4 g-3">
|
||||
{% for stype,field in fields %}
|
||||
<div class="col">
|
||||
<div class="card h-100 position-relative">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ stype|upper }}</h5>
|
||||
{% if stype != 'system' %}
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="{{ stype }}" id="{{ stype }}none" value="x" checked>
|
||||
<label class="form-check-label" for="{{ stype }}none">
|
||||
Nicht (erneut) konfigurieren
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% set daemons = field.getDaemons %}
|
||||
{% for dtype,daemon in daemons %}
|
||||
{% if stype == 'system' %}
|
||||
<div class="form-check">
|
||||
{% set recommended = false %}
|
||||
{% if
|
||||
(dtype == 'awstats' and get_setting('system.awstats_enabled') == '1') or
|
||||
(dtype == 'libnssextrausers' and (get_setting('system.mod_fcgid') == '1' or get_setting('phpfpm.enabled') == '1' or get_setting('system.apacheitksupport') == '1')) or
|
||||
(dtype == 'logrotate') or
|
||||
(dtype == 'fcgid' and get_setting('system.mod_fcgid') == '1') or
|
||||
(dtype == 'php-fpm' and get_setting('phpfpm.enabled') == '1') or
|
||||
(dtype == 'cron')
|
||||
%}
|
||||
{% set recommended = true %}
|
||||
{% endif %}
|
||||
<input class="form-check-input" type="checkbox" name="system[{{ dtype }}]" id="{{ dtype }}" value="{{ dtype }}" data-recommended="{{ recommended }}">
|
||||
<label class="form-check-label" for="{{ dtype }}">
|
||||
{% if recommended %}
|
||||
<strong>{{ daemon.title }}<span class="text-danger">*</span></strong>
|
||||
{% else %}
|
||||
{{ daemon.title }}
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="form-check">
|
||||
{% set recommended = false %}
|
||||
{% if
|
||||
(dtype == 'apache22' and get_setting('system.webserver') == 'apache2' and get_setting('system.apache24') == '0') or
|
||||
(dtype == 'apache24' and get_setting('system.webserver') == 'apache2' and get_setting('system.apache24') == '1') or
|
||||
(dtype == 'lighttpd' and get_setting('system.webserver') == 'lighttpd') or
|
||||
(dtype == 'nginx' and get_setting('system.webserver') == 'nginx') or
|
||||
(dtype == 'bind' and get_setting('system.bind_enable') == '1' and get_setting('system.dns_server') == 'Bind') or
|
||||
(dtype == 'powerdns' and get_setting('system.bind_enable') == '1' and get_setting('system.dns_server') == 'PowerDNS') or
|
||||
(dtype == 'proftpd' and get_setting('system.ftpserver') == 'proftpd') or
|
||||
(dtype == 'pureftpd' and get_setting('system.ftpserver') == 'pureftpd')
|
||||
%}
|
||||
{% set recommended = true %}
|
||||
{% endif %}
|
||||
<input class="form-check-input" type="radio" name="{{ stype }}" id="{{ dtype }}" value="{{ dtype }}" data-recommended="{{ recommended }}">
|
||||
<label class="form-check-label" for="{{ dtype }}">
|
||||
{% if recommended %}
|
||||
<strong>{{ daemon.title }}<span class="text-danger">*</span></strong>
|
||||
{% else %}
|
||||
{{ daemon.title }}
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
<div class="row mt-3">
|
||||
<input type="hidden" name="finish" value="1"/>
|
||||
<div class="col-12 col-md-6">
|
||||
<span class="text-danger">*</span> Empfohlene/benötigte Dienste ahand der aktuellen Systemeinstellungen
|
||||
</div>
|
||||
<div class="col-12 col-md-6 text-end">
|
||||
<button type="button" class="btn btn-outline-secondary" id="selectRecommendedConfig">Empfohlene auswählen</button>
|
||||
<button type="submit" class="btn btn-primary">{{ lng('update.proceed') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
14
templates/Froxlor/src/js/components/configfiles.js
Normal file
14
templates/Froxlor/src/js/components/configfiles.js
Normal file
@@ -0,0 +1,14 @@
|
||||
$(document).ready(function () {
|
||||
/*
|
||||
* config files - select all recommended
|
||||
*/
|
||||
$('#selectRecommendedConfig').click(function () {
|
||||
$('input[data-recommended]').each(function () {
|
||||
if ($(this).data('recommended') == 1) {
|
||||
$(this).prop('checked', true);
|
||||
} else {
|
||||
$(this).prop('checked', false);
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -15,3 +15,4 @@ require('./components/updatecheck')
|
||||
require('./components/customer')
|
||||
require('./components/ipsandports')
|
||||
require('./components/domains')
|
||||
require('./components/configfiles')
|
||||
|
||||
Reference in New Issue
Block a user