Compare commits

...

32 Commits

Author SHA1 Message Date
d7a3568506 reject policy dmarc 2025-09-29 19:06:10 +02:00
10c13bc5b1 not generating disabled zones 2025-09-26 13:01:26 +02:00
dcb3f6f568 DKIM stuff with our own selector 2025-09-25 11:16:48 +02:00
7566def0d1 TODO: This is a dkim hack 2025-09-25 09:40:40 +02:00
3630f82817 greylisting 2.0 2025-09-24 16:45:43 +02:00
9ddd2e9154 styles 2025-09-03 12:10:46 +02:00
53afe4ebd1 new files
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-30 17:12:26 +01:00
4f69e8ee0e new css 2024-01-30 17:12:16 +01:00
32f5b0d5e9 new Theme
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-30 16:52:34 +01:00
53a6485a6e Maketank Theme migration
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-30 13:52:59 +01:00
f2643ac887 env test 3
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:46:20 +01:00
e37687a85d env test 3
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:44:29 +01:00
ccbc3286a5 env test 2
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:39:58 +01:00
929a562324 env test 2
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:36:50 +01:00
3704cf6621 env test 2
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-13 12:35:09 +01:00
10238a1466 env test
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-12 14:04:03 +01:00
9002ddf4a2 env test
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-12-12 14:03:10 +01:00
8a2de5a44a env test
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-12-12 13:59:34 +01:00
96c0af18dd npm and compose
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-12 13:50:23 +01:00
5bb228ce78 npm and compose
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-12 13:48:08 +01:00
804128280c npm and compose 2023-12-12 13:47:32 +01:00
5b8e918f75 ssh test
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-12 13:45:23 +01:00
0e3e83d184 ssh test
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-12 10:51:21 +01:00
8ced61c6aa bogus edit for pipeline trigger
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-11 15:31:39 +01:00
29a2ab7567 2.0 upgrade test first
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-12-07 12:39:20 +01:00
Michael Kaufmann
166ec0575b set version to 2.0.24 for upcoming maintenance release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-06 11:18:18 +02:00
Andreu Trepat Rubirola
215e749ba8 added ca language (#1184) 2023-09-24 15:22:33 +02:00
Michael Kaufmann
506cccd7c8 fix vhost-cleaning regex for nginx-location directives; fixes #1185
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-12 15:20:56 +02:00
Michael Kaufmann
6d9014c29b fix API permission error in navigation when customer-hide-options include 'domains'; fixes #1183
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-07 15:34:06 +02:00
Michael Kaufmann
10555bff76 set version to 2.0.23 for upcoming bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-03 20:16:18 +02:00
Michael Kaufmann
37aa7af4da check for existing userinfo if settings are being imported via cli
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 17:11:06 +02:00
Michael Kaufmann
4b75369597 only check non-admin resources if user is not an admin in navigation
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-09-02 15:53:15 +02:00
154 changed files with 12099 additions and 6318 deletions

53
.drone.yml Normal file
View File

@@ -0,0 +1,53 @@
kind: pipeline
name: deploy-froxlor
type: docker
platform:
os: linux
arch: arm64
trigger:
branch:
- upgrade-2.0
event:
include:
- push
environment:
DEPLOY_HOST: rechner.maketank.net
DEPLOY_DIR: ~/froxlor-test
steps:
- name: deploy
image: cr.wks/drone/drone-rsync:latest
settings:
hosts: ["rechner02.maketank.net"]
source: ./
target: ~/froxlor-test
user: www-data
exclude: ['vendor', '.git*', '*drone.yml', '.settings', '.buildpath', '.editorconfig', '.project', '.travis.yml', 'node_modules']
args: '-v --delete'
log_level: quiet
key:
from_secret: ssh-www-data-maketank-rsa
command_timeout: 10m
- name: compose
image: appleboy/drone-ssh
settings:
host:
- rechner02.maketank.net
username: www-data
key:
from_secret: ssh-www-data-maketank-rsa
script:
- cd ~/froxlor-test && composer install --no-dev
- name: npm
image: appleboy/drone-ssh
settings:
host:
- rechner02.maketank.net
username: www-data
key:
from_secret: ssh-www-data-maketank-rsa
script:
- cd ~/froxlor-test && npm install && npm run build

1
.gitignore vendored
View File

@@ -18,7 +18,6 @@ img/
vendor/
node_modules/
fonts/
templates/*
!templates/index.html
!templates/Froxlor/
templates/Froxlor/assets/mix-manifest.json

View File

@@ -299,6 +299,30 @@ if ($page == 'email_domain') {
'action' => 'edit',
'id' => $id,
]);
} elseif ($action == 'togglegreylist' && $id != 0) {
try {
$json_result = Emails::getLocal($userinfo, [
'id' => $id
])->get();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
$result = json_decode($json_result, true)['data'];
try {
Emails::getLocal($userinfo, [
'id' => $id,
'disablegreylist' => ($result['disablegreylist'] == '1' ? 0 : 1)
])->updateGreylist();
} catch (Exception $e) {
Response::dynamicError($e->getMessage());
}
Response::redirectTo($filename, [
'page' => $page,
'domainid' => $email_domainid,
'action' => 'edit',
'id' => $id,
]);
}
} elseif ($page == 'accounts') {
$email_domainid = Request::any('domainid', 0);

View File

@@ -697,7 +697,7 @@ opcache.validate_timestamps'),
('system', 'distribution', ''),
('system', 'update_channel', 'stable'),
('system', 'updatecheck_data', ''),
('system', 'update_notify_last', '2.0.22'),
('system', 'update_notify_last', '2.0.24'),
('system', 'traffictool', 'goaccess'),
('system', 'req_limit_per_interval', 60),
('system', 'req_limit_interval', 60),
@@ -744,7 +744,7 @@ opcache.validate_timestamps'),
('panel', 'logo_overridetheme', '0'),
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'version', '2.0.22'),
('panel', 'version', '2.0.24'),
('panel', 'db_version', '202304260');

View File

@@ -507,3 +507,13 @@ if (Froxlor::isFroxlorVersion('2.0.21')) {
Update::showUpdateStep("Updating from 2.0.21 to 2.0.22", false);
Froxlor::updateToVersion('2.0.22');
}
if (Froxlor::isFroxlorVersion('2.0.22')) {
Update::showUpdateStep("Updating from 2.0.22 to 2.0.23", false);
Froxlor::updateToVersion('2.0.23');
}
if (Froxlor::isFroxlorVersion('2.0.23')) {
Update::showUpdateStep("Updating from 2.0.23 to 2.0.24", false);
Froxlor::updateToVersion('2.0.24');
}

View File

@@ -75,6 +75,7 @@ class Emails extends ApiCommand implements ResourceEntity
// parameters
$iscatchall = $this->getBoolParam('iscatchall', true, 0);
$disablegreylist = $this->getBoolParam('disablegreylist', true, 0);
$description = $this->getParam('description', true, '');
// validation
@@ -118,7 +119,7 @@ class Emails extends ApiCommand implements ResourceEntity
// duplicate check
$stmt = Database::prepare("
SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid` FROM `" . TABLE_MAIL_VIRTUAL . "`
SELECT `id`, `email`, `email_full`, `iscatchall`, `destination`, `customerid`, `disablegreylist` FROM `" . TABLE_MAIL_VIRTUAL . "`
WHERE (`email` = :email OR `email_full` = :emailfull )
AND `customerid`= :cid
");
@@ -144,7 +145,8 @@ class Emails extends ApiCommand implements ResourceEntity
`email_full` = :email_full,
`iscatchall` = :iscatchall,
`domainid` = :domainid,
`description` = :description
`description` = :description,
`disablegreylist` = :disablegreylist
");
$params = [
"cid" => $customer['customerid'],
@@ -152,7 +154,8 @@ class Emails extends ApiCommand implements ResourceEntity
"email_full" => $email_full,
"iscatchall" => $iscatchall,
"domainid" => $domain_check['id'],
"description" => $description
"description" => $description,
"disablegreylist" => $disablegreylist
];
Database::pexecute($stmt, $params, true, true);
@@ -191,7 +194,7 @@ class Emails extends ApiCommand implements ResourceEntity
$customer_ids = $this->getAllowedCustomerIds('email');
$params['idea'] = ($id <= 0 ? $emailaddr : $id);
$result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`destination`, v.`customerid`, v.`popaccountid`, v.`domainid`, v.`description`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
$result_stmt = Database::prepare("SELECT v.`id`, v.`email`, v.`email_full`, v.`iscatchall`, v.`disablegreylist`, v.`destination`, v.`customerid`, v.`popaccountid`, v.`domainid`, v.`description`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` v
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON v.`popaccountid` = u.`id`
WHERE v.`customerid` IN (" . implode(", ", $customer_ids) . ")
@@ -302,6 +305,81 @@ class Emails extends ApiCommand implements ResourceEntity
return $this->response($result);
}
/**
* toggle greylist flag of given email address either by id or email-address
*
* @param int $id
* optional, the email-address-id
* @param string $emailaddr
* optional, the email-address
* @param int $customerid
* optional, required when called as admin (if $loginname is not specified)
* @param string $loginname
* optional, required when called as admin (if $customerid is not specified)
* @param boolean $greylist
* optional
* @param string $description
* optional custom description (currently not used/shown in the frontend), default empty
*
* @access admin, customer
* @return string json-encoded array
* @throws Exception
*/
public function updateGreylist()
{
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'email')) {
throw new Exception("You cannot access this resource", 405);
}
// if enabling catchall is not allowed by settings, we do not need
// to run update()
/** if (Settings::Get('catchall.catchall_enabled') != '1') {
Response::standardError([
'operationnotpermitted',
'featureisdisabled'
], 'catchall', true);
} */
$id = $this->getParam('id', true, 0);
$ea_optional = $id > 0;
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
$result = $this->apiCall('Emails.get', [
'id' => $id,
'emailaddr' => $emailaddr
]);
$id = $result['id'];
$email = $result['email'];
// parameters
$disablegreylist = $this->getBoolParam('disablegreylist', true, $result['disablegreylist']);
$description = $this->getParam('description', true, $result['description']);
// get needed customer info to reduce the email-address-counter by one
$customer = $this->getCustomerData();
// check for catchall-flag
$stmt = Database::prepare("
UPDATE `" . TABLE_MAIL_VIRTUAL . "`
SET `email` = :email , `disablegreylist` = :grflag, `description` = :description
WHERE `customerid`= :cid AND `id`= :id
");
$params = [
"email" => $email,
"grflag" => $disablegreylist,
"description" => $description,
"cid" => $customer['customerid'],
"id" => $id
];
Database::pexecute($stmt, $params, true, true);
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] toggled greylist-flag for email address '" . $result['email_full'] . "'");
$result = $this->apiCall('Emails.get', [
'emailaddr' => $result['email_full']
]);
return $this->response($result);
}
/**
* list all email addresses, if called from an admin, list all email addresses of all customers you are allowed to
* view, or specify id or loginname for one specific customer
@@ -331,7 +409,7 @@ class Emails extends ApiCommand implements ResourceEntity
$result = [];
$query_fields = [];
$result_stmt = Database::prepare("
SELECT m.`id`, m.`domainid`, m.`email`, m.`email_full`, m.`iscatchall`, m.`destination`, m.`popaccountid`, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
SELECT m.`id`, m.`domainid`, m.`email`, m.`email_full`, m.`iscatchall`, m.`disablegreylist`, m.`destination`, m.`popaccountid`, d.`domain`, u.`quota`, u.`imap`, u.`pop3`, u.`postfix`, u.`mboxsize`
FROM `" . TABLE_MAIL_VIRTUAL . "` m
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` d ON (m.`domainid` = d.`id`)
LEFT JOIN `" . TABLE_MAIL_USERS . "` u ON (m.`popaccountid` = u.`id`)

View File

@@ -132,18 +132,16 @@ abstract class DnsBase
");
while ($domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) {
$privkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . Settings::Get('dkim.privkeysuffix'));
$pubkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . '.public');
$privkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/mx.' . $domain['domain'] . '.' . Settings::Get('dkim.privkeysuffix'));
$pubkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/mx.' . $domain['domain'] . '.public');
if ($domain['dkim_privkey'] == '' || $domain['dkim_pubkey'] == '') {
$max_dkim_id_stmt = Database::query("SELECT MAX(`dkim_id`) as `max_dkim_id` FROM `" . TABLE_PANEL_DOMAINS . "`");
$max_dkim_id = $max_dkim_id_stmt->fetch(PDO::FETCH_ASSOC);
$domain['dkim_id'] = (int)$max_dkim_id['max_dkim_id'] + 1;
$privkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . Settings::Get('dkim.privkeysuffix'));
FileDir::safe_exec('openssl genrsa -out ' . escapeshellarg($privkey_filename) . ' ' . Settings::Get('dkim.dkim_keylength'));
$domain['dkim_privkey'] = file_get_contents($privkey_filename);
FileDir::safe_exec("chmod 0640 " . escapeshellarg($privkey_filename));
$pubkey_filename = FileDir::makeCorrectFile(Settings::Get('dkim.dkim_prefix') . '/dkim' . $domain['dkim_id'] . '.public');
FileDir::safe_exec('openssl rsa -in ' . escapeshellarg($privkey_filename) . ' -pubout -outform pem -out ' . escapeshellarg($pubkey_filename));
$domain['dkim_pubkey'] = file_get_contents($pubkey_filename);
FileDir::safe_exec("chmod 0664 " . escapeshellarg($pubkey_filename));
@@ -217,7 +215,7 @@ abstract class DnsBase
`" . TABLE_PANEL_DOMAINS . "` `d`
LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`)
WHERE
`d`.`isbinddomain` = '1'
`d`.`isbinddomain` = '1' aND `d`.`deactivated` = '0'
ORDER BY
`d`.`domain` ASC
");

View File

@@ -883,7 +883,7 @@ class Nginx extends HttpConfigBase
// remove comments
$vhost = implode("\n", preg_replace('/^(\s+)?#(.*)$/', '', explode("\n", $vhost)));
// Break blocks into lines
$vhost = preg_replace("/^(\s+)location(.+)\{(.+)\}$/misU", "location $2 {\n $3 \n}", $vhost);
$vhost = preg_replace("/^(\s+)?location(.+)\{(.+)\}$/misU", "location $2 {\n $3 \n}", $vhost);
// Break into array items
$vhost = explode("\n", preg_replace('/[ \t]+/', ' ', trim(preg_replace('/\t+/', '', $vhost))));
// Remove empty lines

View File

@@ -151,9 +151,13 @@ class CurrentUser
]);
$addition = $result['emaildomains'] != 0;
} elseif ($resource == 'subdomains') {
$parentDomainCollection = (new Collection(SubDomains::class, $_SESSION['userinfo'],
['sql_search' => ['d.parentdomainid' => 0]]));
$addition = $parentDomainCollection->count() != 0;
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
$addition = false;
} else {
$parentDomainCollection = (new Collection(SubDomains::class, $_SESSION['userinfo'],
['sql_search' => ['d.parentdomainid' => 0]]));
$addition = $parentDomainCollection->count() != 0;
}
} elseif ($resource == 'domains') {
$customerCollection = (new Collection(Customers::class, $_SESSION['userinfo']));
$addition = $customerCollection != 0;

View File

@@ -22,7 +22,6 @@
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Dns;
use Froxlor\Database\Database;
@@ -183,7 +182,10 @@ class Dns
}
if (Settings::Get('dkim.use_dkim') == '1') {
// check for DKIM content later
self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey.' . $sub_record, 'TXT', $required_entries);
//self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey.' . $sub_record, 'TXT', $required_entries);
self::addRequiredEntry('mx._domainkey.' . $sub_record, 'TXT', $required_entries);
//Also add dmarc
self::addRequiredEntry('_dmarc' . $sub_record, 'TXT', $required_entries);
}
}
}
@@ -220,7 +222,10 @@ class Dns
}
if (Settings::Get('dkim.use_dkim') == '1') {
// check for DKIM content later
self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey', 'TXT', $required_entries);
//self::addRequiredEntry('dkim' . $domain['dkim_id'] . '._domainkey', 'TXT', $required_entries);
self::addRequiredEntry('mx._domainkey', 'TXT', $required_entries);
//Also add dmarc
self::addRequiredEntry('_dmarc', 'TXT', $required_entries);
}
}
@@ -378,10 +383,13 @@ class Dns
if (array_key_exists("TXT", $required_entries)) {
if (Settings::Get('dkim.use_dkim') == '1') {
$dkim_entries = self::generateDkimEntries($domain);
$dmarc_entries = self::generateDmarcEntries($domain);
}
foreach ($required_entries as $type => $records) {
if ($type == 'TXT') {
//$dkim_record = 'dkim' . $domain['dkim_id'] . '._domainkey';
$dkim_record = 'mx._domainkey';
foreach ($records as $record) {
if ($record == '@SPF@') {
// spf for main-domain
@@ -392,9 +400,8 @@ class Dns
$txt_content = Settings::Get('spf.spf_entry');
$sub_record = substr($record, 6);
$zonerecords[] = new DnsEntry($sub_record, 'TXT', self::encloseTXTContent($txt_content));
} elseif (!empty($dkim_entries)) {
} elseif (!empty($dkim_entries) && $record == $dkim_record ) {
// DKIM entries
$dkim_record = 'dkim' . $domain['dkim_id'] . '._domainkey';
if ($record == $dkim_record) {
// dkim for main-domain
// check for multiline entry
@@ -412,7 +419,10 @@ class Dns
}
$zonerecords[] = new DnsEntry($record, 'TXT', self::encloseTXTContent($dkim_entries[0], $multiline));
}
} elseif ($record == '_dmarc' && !empty($dmarc_entries) && $domain['isemaildomain'] == '1') {
$zonerecords[] = new DnsEntry($record, 'TXT', self::encloseTXTContent($dmarc_entries[0]));
}
}
}
}
@@ -523,7 +533,7 @@ class Dns
* @param array $domain
* @return array
*/
private static function generateDkimEntries(array $domain): array
/** private static function generateDkimEntries(array $domain): array
{
$zone_dkim = [];
@@ -569,43 +579,61 @@ class Dns
}
return $zone_dkim;
}
} */
private static function generateDkimEntries(array $domain): array
{
$zone_dkim = [];
if (Settings::Get('dkim.use_dkim') == '1' && $domain['dkim'] == '1' && $domain['dkim_pubkey'] != '') {
// start
$dkim_txt = 'v=DKIM1;k=rsa;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAosq0CmLqEzJJxIHkQwG1Xwk6CSyHHWSDXL9BHCKzY9lJXH7a23PogVlLvUBYaAgBtFOpsKuUCBl+/g6rOqgVXKg0OpYdpgTxZyz1i4NcubGFLifQGnF8ZKpIEDqIzmLI6SbH+9DKwYA319sXAR6feZI4g5bWqF07t/kzA5LN+2V5QnDQ3th++GPRl5rmWF6uoidIRD85UZVEX4s3J1hce0k6tRb2aEozCJaSXHUwyarmbbX/5rky467QQ+45Uy0q9CNaMMu1IX5eybhLRxYXK1k0TfIRJv4FH1UFLlq2QoGC7d+KvLrUabhzQ5wbdZkWuVgLFZ7CL2NegfzO6YeEcQIDAQAB';
$zone_dkim[] = $dkim_txt;
}
return $zone_dkim;
}
private static function generateDmarcEntries(array $domain): array
{
$zone_dmarc = [];
if (Settings::Get('dkim.use_dkim') == '1' && $domain['dkim'] == '1' ){
$dmarc_txt = 'v=DMARC1; p=reject; ruf=mailto:dmarc@'. $domain['domain'] . '; rua=mailto:dmarc@'. $domain['domain'] . '; fo=1; adkim=r; aspf=r; pct=100; rf=afrf; ri=345600;';
$zone_dmarc[] = $dmarc_txt;
}
return $zone_dmarc;
}
/**
* @param string $txt_content
* @param bool $isMultiLine
* @return string
*/
public static function encloseTXTContent(string $txt_content, bool $isMultiLine = false): string
{
// check that TXT content is enclosed in " "
if (!$isMultiLine && Settings::Get('system.dns_server') != 'PowerDNS') {
if (substr($txt_content, 0, 1) != '"') {
$txt_content = '"' . $txt_content;
}
if (substr($txt_content, -1) != '"') {
$txt_content .= '"';
}
}
if (Settings::Get('system.dns_server') == 'PowerDNS') {
// no quotation for PowerDNS
if (substr($txt_content, 0, 1) == '"') {
$txt_content = substr($txt_content, 1);
}
if (substr($txt_content, -1) == '"') {
$txt_content = substr($txt_content, 0, -1);
}
}
return $txt_content;
}
{
// check that TXT content is enclosed in " "
if (! $isMultiLine && Settings::Get('system.dns_server') != 'PowerDNS') {
if (substr($txt_content, 0, 1) != '"') {
$txt_content = '"' . $txt_content;
}
if (substr($txt_content, - 1) != '"') {
$txt_content .= '"';
}
}
if (Settings::Get('system.dns_server') == 'PowerDNS') {
// no quotation for PowerDNS
if (substr($txt_content, 0, 1) == '"') {
$txt_content = substr($txt_content, 1);
}
if (substr($txt_content, - 1) == '"') {
$txt_content = substr($txt_content, 0, - 1);
}
}
return $txt_content;
}
/**
* @param string $email
* @return string
*/
private static function escapeSoaAdminMail(string $email): string
{
$mail_parts = explode("@", $email);
return str_replace(".", "\.", $mail_parts[0]) . "." . $mail_parts[1] . ".";
}
{
$mail_parts = explode("@", $email);
return str_replace(".", "\.", $mail_parts[0]) . "." . $mail_parts[1] . ".";
}
}

View File

@@ -55,6 +55,7 @@ class IpAddr
/**
* @return array
* @throws \Exception
*/
public static function getSslIpPortCombinations(): array
{
@@ -75,7 +76,7 @@ class IpAddr
$additional_conditions_params = [];
$additional_conditions_array = [];
if ($userinfo['ip'] != '-1') {
if (!empty($userinfo) && $userinfo['ip'] != '-1') {
$admin_ip_stmt = Database::prepare("
SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = IN (:ipid)
");

View File

@@ -31,7 +31,7 @@ final class Froxlor
{
// Main version variable
const VERSION = '2.0.22';
const VERSION = '2.0.24';
// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202304260';

View File

@@ -52,7 +52,13 @@ return [
'type' => 'checkbox',
'value' => '1',
'checked' => false
]
],
'disablegreylist' => [
'label' => lng('emails.disablegreylist'),
'type' => 'checkbox',
'value' => '1',
'checked' => false
]
]
]
]

View File

@@ -102,6 +102,19 @@ return [
]
]
],
'mail_disablegreylist' => [
'label' => lng('emails.greylist'),
'type' => 'label',
'value' => ((int)$result['disablegreylist'] == 0 ? lng('panel.no') : lng('panel.yes')),
'next_to' => [
'add_link' => [
'type' => 'link',
'href' => $filename . '?page=' . $page . '&amp;domainid=' . $result['domainid'] . '&amp;action=togglegreylist&amp;id=' . $result['id'],
'label' => '<i class="fa-solid fa-arrow-right-arrow-left"></i> ' . lng('panel.toggle'),
'classes' => 'btn btn-sm btn-secondary'
]
]
],
'mail_fwds' => [
'label' => lng('emails.forwarders') . ' (' . $forwarders_count . ')',
'type' => 'itemlist',

View File

@@ -38,7 +38,7 @@ return [
'url' => 'customer_email.php?page=emails',
'label' => lng('menue.email.emails'),
'required_resources' => 'emails',
'add_shortlink' => CurrentUser::canAddResource('emails') ? 'customer_email.php?page=email_domain&action=add' : null,
'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('emails') ? 'customer_email.php?page=email_domain&action=add' : null,
],
[
'url' => Settings::Get('panel.webmail_url'),
@@ -60,7 +60,7 @@ return [
'url' => 'customer_mysql.php?page=mysqls',
'label' => lng('menue.mysql.databases'),
'required_resources' => 'mysqls',
'add_shortlink' => CurrentUser::canAddResource('mysqls')? 'customer_mysql.php?page=mysqls&action=add' : null,
'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('mysqls')? 'customer_mysql.php?page=mysqls&action=add' : null,
],
[
'url' => Settings::Get('panel.phpmyadmin_url'),
@@ -81,7 +81,7 @@ return [
[
'url' => 'customer_domains.php?page=domains',
'label' => lng('menue.domains.settings'),
'add_shortlink' => CurrentUser::canAddResource('subdomains') ? 'customer_domains.php?page=domains&action=add' : null,
'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('subdomains') ? 'customer_domains.php?page=domains&action=add' : null,
],
[
'url' => 'customer_domains.php?page=sslcertificates',
@@ -98,7 +98,7 @@ return [
[
'url' => 'customer_ftp.php?page=accounts',
'label' => lng('menue.ftp.accounts'),
'add_shortlink' => CurrentUser::canAddResource('ftps') ? 'customer_ftp.php?page=accounts&action=add' : null,
'add_shortlink' => !CurrentUser::isAdmin() && CurrentUser::canAddResource('ftps') ? 'customer_ftp.php?page=accounts&action=add' : null,
],
[
'url' => Settings::Get('panel.webftp_url'),

View File

@@ -55,6 +55,12 @@ return [
'callback' => [Text::class, 'boolean'],
'visible' => Settings::Get('catchall.catchall_enabled') == '1'
],
'm.disablegreylist' => [
'label' => lng('emails.greylist'),
'field' => 'disablegreylist',
'callback' => [Text::class, 'boolean'],
'#visible' => Settings::Get('greylist.greylist_enabled') == '1'
],
'u.quota' => [
'label' => lng('emails.quota'),
'field' => 'quota',
@@ -66,6 +72,7 @@ return [
'm.destination',
'm.popaccountid',
'm.iscatchall',
'm.disablegreylist',
'u.quota'
]),
'actions' => [

2406
lng/ca.lng.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -724,6 +724,8 @@ return [
'back_to_overview' => 'Zurück zur Domain-Übersicht',
'accounts' => 'Konten',
'emails' => 'Adressen',
'disablegreylist' => 'Greylisting deaktivieren?',
'greylist' => 'Greylisting aus?'
],
'error' => [
'error' => 'Fehlermeldung',

View File

@@ -34,6 +34,7 @@ return [
'pt' => 'Portuguese',
'se' => 'Swedish',
'es' => 'Spanish',
'ca' => 'Catalan',
],
'2fa' => [
'2fa' => '2FA options',

55
notice.html Normal file
View File

@@ -0,0 +1,55 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>froxlor - Domain not configured</title>
<style>
:root{--primary:#1872a2;--fonts:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}
body{display:flex;flex-direction:column;background:#f8f9fa;color:#4b5563;align-items:center;justify-content:center;font-family:var(--fonts)}
main{background:#fff;margin:10% auto 12px;max-width:540px;padding:2rem;box-shadow:4px 8px 16px 0 rgba(0,0,0,.07);border-radius:.375rem}
main h2{margin:0}
main p{margin-bottom:1.5rem}
main p:last-child{margin-bottom:0}
main ul{list-style:none;margin-left:-40px}
main li{display:flex;align-items:center;margin-bottom:1rem}
main .icon{min-width:24px;width:24px;stroke:var(--primary);margin-right:.75rem}
code{background:#eee;padding:.1rem .25rem;border-radius:4px;color:rgba(0,0,0,.75)}
hr{margin:2rem 0;border:none;border-bottom:solid 1px rgba(0,0,0,.1)}
a,a:active,a:visited{color:var(--primary);text-decoration:none}
a:hover{text-decoration:underline}
footer{display:flex;align-items:center;margin-top:.5rem}
footer .logo{margin-right:.35rem}
@media (prefers-color-scheme: dark) {
:root{--primary:#29a2d6}
body{background:#212529;color:#f8f9fa}
main{background:#343a40}
hr{border-color:rgba(0,0,0,.2)}
}
</style>
</head>
<body>
<main>
<h2>Domain not configured</h2>
<p>
This domain requires configuration via the froxlor server management panel, as it is currently not assigned to any customer.
</p>
<ul>
<li>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
</svg>
<span>Please ask your provider/hoster if you have any questions.</span>
</li>
</ul>
</main>
<footer>
<img class="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAAAQCAYAAAC1MDndAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAy1JREFUeNrs1muIVlUUBuBnvqZkoswudKErZFOG0kWt6WpN0wWCDMOsgX4UmVS/KovKhIKYLhRBJvV1o/5VlJCmYEmUFYijaBYhSRQVBVE0ZnRBJ/vzfnA4nDN+wTh/pgWHc/baa6+z97v3et/d0Ww24SQ8ij5MNL7tN6zBvdjWiZOxDpP8b3JA5qAXPQ0MtAnOm9jZBvqbsQHbsGsMFrQLv+KPUc47CQONlFXZPsNfJd/buBrDFfHDWIDDcAZmohsPjwFAD+EQnL8Xcl/WqOGc+/FJyXcxVuHJivjX8Hx2cwGeweO4fIxOkL10Wg9o1HSsxUcl30V5P1JxnFuxp+M53I67cQ5mYwZexj2YlnarJBdl3BE4FfPxdfofSOwNhX+uwgXxf1Uz/024DpNxFHrwRIEiPs74XnyIq3A8Xion6qxIvj0TX1vyb8x7CJ/jrELfjrx/zgmS0p2bCWzHzejAQTgmO35J+OpInIcv8GLKeROuzcI24lAsRH/yzcGJFfMfDIB/B/ipeD8bNojX8W1hPR+gCxPwTzsAdaGRgTeGrAZLJXd4zc59l1Jr2dzC91S8g+PSXhFwGgGxO4s6Bd9gaQRkAHdhSQHso/FCzRweS56ezLmB1bgCb+DBCg67rwaLSud+uDAAvVLRfw1OqCO18JEKbusvgNMqA/F153sCpgegT+O7Iwt8NwrZwKsh5irbUuDMFoXMquhv2eKRSKiOg54OUFXSt3SEfPvi4Dz77IEAW/8uH+vdpf6dkXGF+F9GyNtRkXe4or8tqwNoWrigbDeFTEfDZhbKckuBy9blu0XkC1Pi++PK+Objy5q80/NeXSDl9wrgnDkaAIlyzCj5zh1FCb00JL07Jd2LKfgBx+LWXE6XJP6pcMiUiMg8/FmRd3GEYHNiZ+H6wgZP/i+T7MzPJo5wkjYU2t01cWfjp6hH2WZn0adVbM7KALAS3weYftwZIVgTIHtxS8YtD6kOYX2Uqi8iIKCsD6CDhXnNi+iI+vXhwD3g83tHs9l8K5JZZbfh2UL7x0jyeLFljVzUhtoc0DWOwBnCoga25s6wrHDhG8+2I1j0YOu/AwBUU7aBHvM/ZwAAAABJRU5ErkJggg==" alt="froxlor"/>
<small>&copy; 2009-<span id="year"></span> by <a href="https://froxlor.org" rel="external">the froxlor team</a></small>
</footer>
<script>
document.getElementById("year").innerHTML = new Date().getFullYear();
</script>
</body>
</html>

10455
package-lock.json generated

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,45 @@
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* @kurkle/color v0.2.1
* https://github.com/kurkle/color#readme
* (c) 2022 Jukka Kurkela
* Released under the MIT License
*/
/*!
* Chart.js v3.9.1
* https://www.chartjs.org
* (c) 2022 Chart.js Contributors
* Released under the MIT License
*/
/*!
* Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2023 Fonticons, Inc.
*/
/*!
* jQuery JavaScript Library v3.7.1
* https://jquery.com/
*
* Copyright OpenJS Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2023-08-28T13:37Z
*/
/*!
* jQuery Validation Plugin v1.20.0
*
* https://jqueryvalidation.org/
*
* Copyright (c) 2023 Jörn Zaefferer
* Released under the MIT license
*/

View File

@@ -0,0 +1,13 @@
{
"/js/main.js": "/js/main.js?id=29451cc889431e973473738b93e6a626",
"/css/dark.css": "/css/dark.css?id=f965da97d900604705c3762bb9cd645a",
"/css/main.css": "/css/main.css?id=cbda89c88526530a66fe1e0d46a63a22",
"/webfonts/fa-brands-400.ttf": "/webfonts/fa-brands-400.ttf?id=69e5d8e4e818f05fd882cceb758d1eba",
"/webfonts/fa-brands-400.woff2": "/webfonts/fa-brands-400.woff2?id=189b85e9c72c6f75e464c3f58a6707cf",
"/webfonts/fa-regular-400.ttf": "/webfonts/fa-regular-400.ttf?id=ed4c23399d1013809882e90bfe396d1b",
"/webfonts/fa-regular-400.woff2": "/webfonts/fa-regular-400.woff2?id=be75b1958ae0da55e1eed562d9b7713d",
"/webfonts/fa-solid-900.ttf": "/webfonts/fa-solid-900.ttf?id=dfdc7801582dd0d20ea75faa3b96c296",
"/webfonts/fa-solid-900.woff2": "/webfonts/fa-solid-900.woff2?id=a0feb384c3c6071947a49708f2b0bc85",
"/webfonts/fa-v4compatibility.ttf": "/webfonts/fa-v4compatibility.ttf?id=e24ec0b8661f7fa333b29444df39e399",
"/webfonts/fa-v4compatibility.woff2": "/webfonts/fa-v4compatibility.woff2?id=e11465c0eff0549edd4e8ea6bbcf242f"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,213 @@
:root,[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
/* --bs-gray-dark:#343a40; */
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #1872a2;
--bs-secondary: #6c757d;
--bs-success: #059669;
--bs-info: #0e5380;
--bs-warning: #fbbf24;
--bs-danger: #be123c;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 180,170,160;
--bs-secondary-rgb: 218,212,208;
--bs-success-rgb: 5,150,105;
--bs-info-rgb: 14,83,128;
--bs-warning-rgb: 251,191,36;
--bs-danger-rgb: 190,18,60;
--bs-light-rgb: 248,249,250;
--bs-dark-rgb: 218,212,208;
--bs-primary-text-emphasis: #0a2e41;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #023c2a;
--bs-info-text-emphasis: #062133;
--bs-warning-text-emphasis: #644c0e;
--bs-danger-text-emphasis: #4c0718;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #d1e3ec;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #cdeae1;
--bs-info-bg-subtle: #cfdde6;
--bs-warning-bg-subtle: #fef2d3;
--bs-danger-bg-subtle: #f2d0d8;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #a3c7da;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #9bd5c3;
--bs-info-border-subtle: #9fbacc;
--bs-warning-border-subtle: #fde5a7;
--bs-danger-border-subtle: #e5a0b1;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255,255,255;
--bs-black-rgb: 0,0,0;
--bs-font-sans-serif: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
--bs-font-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
--bs-gradient: linear-gradient(180deg,hsla(0,0%,100%,.15),hsla(0,0%,100%,0));
--bs-root-font-size: 16px;
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #343a40;
--bs-body-color-rgb: 52,58,64;
--bs-body-bg: #f8f9fa;
--bs-body-bg-rgb: 248,249,250;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0,0,0;
--bs-secondary-color: rgba(52,58,64,.75);
--bs-secondary-color-rgb: 52,58,64;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233,236,239;
--bs-tertiary-color: rgba(52,58,64,.5);
--bs-tertiary-color-rgb: 52,58,64;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248,249,250;
--bs-heading-color: inherit;
--bs-link-color: #1872a2;
--bs-link-color-rgb: 24,114,162;
--bs-link-decoration: underline;
--bs-link-hover-color: #135b82;
--bs-link-hover-color-rgb: 19,91,130;
--bs-code-color: #d63384;
--bs-highlight-color: #343a40;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #A08C78;
--bs-border-color-translucent: rgba(0,0,0,.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0,0,0,.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0,0,0,.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0,0,0,.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0,0,0,.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(24,114,162,.25);
--bs-form-valid-color: #059669;
--bs-form-valid-border-color: #059669;
--bs-form-invalid-color: #be123c;
--bs-form-invalid-border-color: #be123c
}
.navbar {
background: rgb(var(--bs-primary-rgb));
}
.form-control:focus {
background-color: #fff;
border-color: rgb(var(--bs-primary-rgb));
box-shadow: 0 0 0 .25rem rgba(24,114,162,.25);
color: var(--bs-body-color);
outline: 0;
}
.navbar .navbar-brand {
background: rgb(180,170,160);
flex-shrink: 0;
margin-right: 0;
width: 256px;
}
.sidebar>.nav>.nav-item>.nav-link:not(.collapsed) {
background: rgb(var(--bs-primaryu-rgb));
border-left: 3px solid #1872a2;
padding-left: calc(1rem - 3px);
}
.text-light {
--bs-text-opacity: 1;
color: rgba(var(--bs-gray-900),var(--bs-text-opacity))!important;
}
.sidebar>.nav>.nav-item>.collapse, .sidebar>.nav>.nav-item>.collapsing {
background: rgb(160,140,120);
color: rgba(var(--bs-light),var(--bs-text-opacity))!important;
}
img.header-logo {
width: 80%;
height: 80%;
}
.btn-primary {
--bs-btn-color: #fff;
--bs-btn-bg: rgb(180,170,160);
--bs-btn-border-color: rgb(160,140,120);
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #fff;
--bs-btn-hover-border-color: rgb(160,140,120);
--bs-btn-focus-shadow-rgb: 59,135,176;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #135b82;
--bs-btn-active-border-color: #12567a;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0,0,0,.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #1872a2;
--bs-btn-disabled-border-color: #1872a2;
}
.btn-outline-primary {
--bs-btn-color: #000;
--bs-btn-border-color: rgb(160,140,120);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: rgb(180,170,160);
--bs-btn-hover-border-color: rgb(160,140,120);
--bs-btn-focus-shadow-rgb: 24,114,162;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #1872a2;
--bs-btn-active-border-color: #1872a2;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0,0,0,.125);
--bs-btn-disabled-color: #1872a2;
--bs-btn-disabled-bg: transparent;
--bs-btn-disabled-border-color: #1872a2;
--bs-gradient: none;
}
.alert, .card, .shadow-sm, .sub-sidebar {
box-shadow: var(--bs-box-shadow)!important;
}
body.d-flex {
display: flex!important;
background-color: rgb(var(--bs-secondary-rgb))!important;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,45 @@
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* @kurkle/color v0.2.1
* https://github.com/kurkle/color#readme
* (c) 2022 Jukka Kurkela
* Released under the MIT License
*/
/*!
* Chart.js v3.9.1
* https://www.chartjs.org
* (c) 2022 Chart.js Contributors
* Released under the MIT License
*/
/*!
* Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2023 Fonticons, Inc.
*/
/*!
* jQuery JavaScript Library v3.7.1
* https://jquery.com/
*
* Copyright OpenJS Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2023-08-28T13:37Z
*/
/*!
* jQuery Validation Plugin v1.20.0
*
* https://jqueryvalidation.org/
*
* Copyright (c) 2023 Jörn Zaefferer
* Released under the MIT license
*/

View File

@@ -0,0 +1,13 @@
{
"/js/main.js": "/js/main.js?id=29451cc889431e973473738b93e6a626",
"/css/dark.css": "/css/dark.css?id=f965da97d900604705c3762bb9cd645a",
"/css/main.css": "/css/main.css?id=cbda89c88526530a66fe1e0d46a63a22",
"/webfonts/fa-brands-400.ttf": "/webfonts/fa-brands-400.ttf?id=69e5d8e4e818f05fd882cceb758d1eba",
"/webfonts/fa-brands-400.woff2": "/webfonts/fa-brands-400.woff2?id=189b85e9c72c6f75e464c3f58a6707cf",
"/webfonts/fa-regular-400.ttf": "/webfonts/fa-regular-400.ttf?id=ed4c23399d1013809882e90bfe396d1b",
"/webfonts/fa-regular-400.woff2": "/webfonts/fa-regular-400.woff2?id=be75b1958ae0da55e1eed562d9b7713d",
"/webfonts/fa-solid-900.ttf": "/webfonts/fa-solid-900.ttf?id=dfdc7801582dd0d20ea75faa3b96c296",
"/webfonts/fa-solid-900.woff2": "/webfonts/fa-solid-900.woff2?id=a0feb384c3c6071947a49708f2b0bc85",
"/webfonts/fa-v4compatibility.ttf": "/webfonts/fa-v4compatibility.ttf?id=e24ec0b8661f7fa333b29444df39e399",
"/webfonts/fa-v4compatibility.woff2": "/webfonts/fa-v4compatibility.woff2?id=e11465c0eff0549edd4e8ea6bbcf242f"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex, nofollow, noarchive"/>
<meta name="googlebot" content="nosnippet"/>
<link rel="icon" type="image/x-icon" href="{{ basehref|default('') }}templates/Froxlor/assets/img/icon.png">
{% if csrf_token %}<meta name="csrf-token" content="{{ csrf_token }}" />{% endif %}
<!-- CSS -->
{% if theme_css is empty %}
<link href="{{ basehref|default('') }}{{ mix('templates/Froxlor/assets/css/main.css') }}" rel="stylesheet" type="text/css" />
{% else %}
{{ theme_css|raw }}
{% endif %}
{% block custom_css %}{% endblock %}
<!-- Scripts -->
{% if theme_js is empty %}
<script type="text/javascript" src="{{ basehref|default('') }}{{ mix('templates/Froxlor/assets/js/main.js') }}"></script>
{% else %}
{{ theme_js|raw }}
{% endif %}
{% block custom_js %}{% endblock %}
<title>Froxlor{% if page_title %} | {{ page_title }}{% endif %}</title>
</head>
<body class="min-vh-100 d-flex flex-column">
{% block navigation %}{% endblock %}
{% block body %}
<div class="container-fluid">
{% block content %}{% endblock %}
{{ include('Froxlor/footer.html.twig') }}
</div>
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,35 @@
{
"variants": {
"default": {
"img": {
"login": "logo.svg",
"ui": "logo_white.png"
},
"css": [
"main.css",
"custom.css"
],
"js": [
"main.js",
"apikey.js"
],
"description": "Default"
},
"dark": {
"img": {
"login": "logo.svg",
"ui": "logo_white.png"
},
"css": [
"dark.css",
"custom.css"
],
"js": [
"main.js",
"apikey.js"
],
"description": "Darkmode"
}
},
"author": "Maketank"
}

View File

@@ -0,0 +1,23 @@
<footer class="text-center mb-3">
<span>
<img src="{{ basehref|default("") }}templates/Froxlor/assets/img/logo_grey.png" alt="Froxlor"/>
{% if install_mode is not defined %}
{% if (get_setting('admin.show_version_login') == '1'
and area == 'login') or (area != 'login'
and get_setting('admin.show_version_footer') == '1') %}
{{ call_static('\\Froxlor\\Froxlor', 'getFullVersion') }}
{% endif %}
{% endif %}
&copy; 2009-{{ "now"|date("Y") }} by <a href="https://www.froxlor.org/" rel="external" target="_blank">the Froxlor Team</a><br>
{% if install_mode is not defined %}
{% if (get_setting('panel.imprint_url') != '') %}<a href="{{ get_setting('panel.imprint_url') }}" target="_blank" class="footer-link">{{ lng('imprint') }}</a>{% endif %}
{% if (get_setting('panel.terms_url') != '') %}<a href="{{ get_setting('panel.terms_url') }}" target="_blank" class="footer-link">{{ lng('terms') }}</a>{% endif %}
{% if (get_setting('panel.privacy_url') != '') %}<a href="{{ get_setting('panel.privacy_url') }}" target="_blank" class="footer-link">{{ lng('privacy') }}</a>{% endif %}
{% endif %}
</span>
{% if lng('translator') %}
<br/>
<small class="mt-3">{{ lng('panel.translator') }}: {{ lng('translator') }}</small>
{% endif %}
</footer>

View File

@@ -0,0 +1,56 @@
{% macro form(form_data, formaction, title = "", hiddenid = "", nosubmit = false, idprefix = "") %}
{% import "Froxlor/form/formfields.html.twig" as formfields %}
<form action="{{ formaction|default("") }}" {% if form_data.id is defined %}id="{{ form_data.id }}"{% endif %} method="post" enctype="multipart/form-data" class="form">
{% for sid,section in form_data.sections %}
{% if section.visible is not defined or (section.visible is defined and section.visible == true) %}
<div class="card mb-3" id="{{ idprefix }}{{ sid }}">
{% if section.title is not empty %}
<div class="card-header">
{% if section.image is not empty %}
<i class="{{ section.image }}"></i>
{% endif %}
{{ section.title }}
</div>
{% endif %}
<div class="formfields">
{% for id,field in section.fields %}
{{ formfields.fieldrow(id, field) }}
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
{% if nosubmit == false %}
<!-- submit buttons -->
<div>
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
{% if hiddenid is not empty %}
<input type="hidden" name="id" value="{{ hiddenid }}"/>
{% endif %}
<input type="hidden" name="page" value="{{ page }}"/>
<input type="hidden" name="action" value="{{ action }}"/>
<input type="hidden" name="send" value="send"/>
<div class="col-12 text-center mb-2 d-grid gap-2 d-md-block">
{% if form_data.buttons is defined and form_data.buttons is iterable %}
{% for btn in form_data.buttons %}
<button type="{{ btn.type|default("submit") }}" class="btn btn-lg {{ btn.class|default(" btn-primary") }}">{{ btn.label }}</button>
{% endfor %}
{% else %}
<button type="reset" class="btn btn-lg btn-outline-secondary me-md-3">{{ lng('panel.reset') }}</button>
<button type="submit" class="btn btn-lg btn-primary">{{ lng('panel.save') }}</button>
{% endif %}
</div>
</div>
{% endif %}
<span class="text-danger">*</span> {{ lng('panel.mandatoryfield') }}
</form>
{# add translation for custom validations #}
{% if form_data.id is defined and form_data.id in ['customer_add', 'customer_edit', 'domain_add', 'domain_edit'] %}
<script>$(function() { $.extend($.validator.messages, {required: "{{ lng('error.requiredfield') }}"}) });</script>
{% endif %}
{% endmacro %}

View File

@@ -0,0 +1,250 @@
{% macro fieldrow(id, field, norow = false, nohide = false, em = false) %}
{% if field.visible is not defined or (field.visible is defined and field.visible) or nohide == true %}
{% if norow == false and (field.type != 'hidden' or (field.type == 'hidden' and field.display is defined and field.display is not empty)) %}
<div class="row g-0 formfield d-flex align-items-center">
{% if field.prior_infotext is defined and field.prior_infotext|length > 0 %}
<h5>{{ field.prior_infotext }}</h5>
{% endif %}
{% if field.label is iterable %}
<label for="{{ id }}" class="col-sm-6 col-form-label pe-3">
{% if em %}
<mark>
{% endif %}
{{ field.label.title|raw }}
{% if field.mandatory is defined and field.mandatory %}
<span class="text-danger">*</span>
{% endif %}
{% if em %}
</mark>
{% endif %}
{% if field.label.description is defined and field.label.description is not empty %}<br><small>{{ field.label.description|raw }}</small>
{% endif %}
{% if field.requires_reconf is defined and field.requires_reconf is not empty %}
<div class="bg-info bg-opacity-25 rounded p-2 mt-2 d-flex align-items-center" role="alert">
<i class="fa-solid fa-circle-exclamation me-2"></i><p class="mb-0">{{ lng('serversettings.requires_reconfiguration', [field.requires_reconf|join(', ')])|raw }}</p>
</div>
{% endif %}
</label>
{% else %}
<label for="{{ id }}" class="col-sm-6 col-form-label pe-3">
{% if em %}
<mark>
{% endif %}
{{ field.label|raw }}
{% if field.mandatory is defined and field.mandatory %}
<span class="text-danger">*</span>
{% endif %}
{% if em %}
</mark>
{% endif %}
{% if field.desc is defined and field.desc is not empty %}<br><small>{{ field.desc|raw }}</small>
{% endif %}
{% if field.requires_reconf is defined and field.requires_reconf is not empty %}
<div class="bg-info bg-opacity-25 rounded p-2 mt-2 d-flex align-items-center" role="alert">
<i class="fa-solid fa-circle-exclamation me-2"></i><p class="mb-0">{{ lng('serversettings.requires_reconfiguration', [field.requires_reconf|join(', ')])|raw }}</p>
</div>
{% endif %}
</label>
{% endif %}
<div class="col-sm-6">
{% endif %}
{% if field.type == 'text' or field.type == 'password' or field.type == 'number' or field.type == 'file' or field.type == 'email' or field.type == 'url' or field.type == 'hidden' or field.type == 'date' or field.type == 'datetime-local' %}
{{ _self.input(id, field) }}
{% elseif field.type == 'textul' %}
{{ _self.input_ul(id, field) }}
{% elseif field.type == 'checkbox' %}
{{ _self.bool(id, field) }}
{% elseif field.type == 'checkrequired' %}
{{ _self.chk_required(id, field) }}
{% elseif field.type == 'select' %}
{{ _self.select(id, field) }}
{% elseif field.type == 'textarea' %}
{{ _self.textarea(id, field) }}
{% elseif field.type == 'label' %}
{{ _self.plain(id, field) }}
{% elseif field.type == 'link' %}
{{ _self.link(id, field) }}
{% elseif field.type == 'itemlist' %}
{{ _self.itemlist(id, field) }}
{% elseif field.type == 'infotext' %}
{{ _self.infotext(id, field) }}
{% elseif field.type == 'image' %}
{{ _self.image(id, field) }}
{% else %}
<div class="alert alert-warning" role="alert">Unknown field-type
{{ field.type }}</div>
{% endif %}
{% if field.note is defined and field.note is not empty %}
<small class="text-info">{{ field.note|raw }}</small>
{% endif %}
{% if norow == false and (field.type != 'hidden' or (field.type == 'hidden' and field.display is defined and field.display is not empty)) %}
</div>
</div>
{% endif %}
{% endif %}
{% endmacro %}
{# installation specific format #}
{% macro field(id, field, norow = true, nohide = false, em = false) %}
{% if field.type == 'checkbox' %}
<div class="form-check form-switch mb-3">
<input type="hidden" value="0" name="{{ id }}"/>
<input type="checkbox" {% if (field.visible is defined and field.visible == false) or (field.disabled is defined and field.disabled == true) %} disabled {% endif %} value="{{ field.value }}" id="{{ id }}" name="{{ id }}" class="form-check-input {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.checked is defined and field.checked == 1 %} checked="checked" {% endif %}>
<label for="{{ id }}" class="form-check-label">{{ field.label|raw }}</label>
</div>
{% elseif field.type == 'hidden' %}
{{ _self.fieldrow(id, field, norow, nohide, em) }}
{% else %}
<div class="form-floating mb-3">
{{ _self.fieldrow(id, field, norow, nohide, em) }}
<label for="{{ id }}" class="form-label">{{ field.label|raw }}</label>
</div>
{% endif %}
{% endmacro %}
{% macro bool(id, field) %}
{% if field.is_array is defined and field.is_array == 1 and field.values is not empty %}
{% for subfield in field.values %}
<div class="form-check form-switch">
<input type="checkbox" {% if (field.visible is defined and field.visible == false) or (field.disabled is defined and field.disabled == true) %} disabled {% endif %} value="{{ subfield.value }}" name="{{ id }}[]" class="form-check-input" {% if field.value is defined and subfield.value in field.value %} checked="checked" {% endif %} {% if field.mandatory is defined and field.mandatory %} required {% endif %}>
<label class="form-check-label">
{{ subfield.label|raw }}
</label>
</div>
{% endfor %}
{% else %}
<div class="form-check form-switch">
<input type="hidden" value="0" name="{{ id }}"/>
<input type="checkbox" {% if (field.visible is defined and field.visible == false) or (field.disabled is defined and field.disabled == true) %} disabled {% endif %} value="{{ field.value }}" id="{{ id }}" name="{{ id }}" class="form-check-input {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.checked is defined and field.checked == 1 %} checked="checked" {% endif %}>
</div>
{% endif %}
{% endmacro %}
{% macro chk_required(id, field) %}
<div class="form-check form-switch">
<input type="checkbox" value="{{ field.value }}" id="{{ id }}" name="{{ id }}" class="form-check-input" {% if field.mandatory is defined and field.mandatory == 1 %} required {% endif %} />
</div>
{% endmacro %}
{% macro infotext(id, field) %}
{% if field.next_to is defined %}
<div class="input-group">
{% endif %}
<span {% if field.classes is defined %} class="{{ field.classes }}" {% endif %}>{{ field.value|raw }}</span>
{% if field.next_to is defined %}
{% for nid, nfield in field.next_to %}
{% if nfield.next_to_prefix is defined %}
<span class="input-group-text">{{ nfield.next_to_prefix }}</span>
{% endif %}
{{ _self.fieldrow(nid, nfield, true) }}
{% endfor %}
</div>
{% endif %}
{% endmacro %}
{% macro plain(id, field) %}
<input type="text" readonly class="form-control-plaintext" id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}">
{% if field.next_to is defined %}
{% for nid, nfield in field.next_to %}
{% if nfield.next_to_prefix is defined %}
<span class="input-group-text">{{ nfield.next_to_prefix }}</span>
{% endif %}
{{ _self.fieldrow(nid, nfield, true) }}
{% endfor %}
{% endif %}
{% endmacro %}
{% macro input(id, field) %}
{% if field.next_to is defined %}
<div class="input-group">
{% endif %}
<input type="{{ field.type }}" {% if field.visible is defined and field.visible == false %} disabled {% endif %} {% if field.type == 'number' and field.min is defined %} min="{{ field.min }}" {% endif %} {% if field.type == 'number' and field.max is defined %} max="{{ field.max }}" {% endif %} {% if field.type != 'number' and field.maxlength is defined %} maxlength="{{ field.maxlength }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %} {% if field.type == 'file' and field.accept is defined %} accept="{{ field.accept }}" {% endif %} {% if field.pattern is defined %} pattern="{{ field.pattern }}" {% endif %}/>
{% if field.type == 'hidden' and field.display is defined %}
<input type="text" readonly class="form-control-plaintext" value="{{ field.display|raw }}">
{% endif %}
{% if field.next_to is defined %}
{% for nid, nfield in field.next_to %}
{% if nfield.next_to_prefix is defined %}
<span class="input-group-text">{{ nfield.next_to_prefix }}</span>
{% endif %}
{{ _self.fieldrow(nid, nfield, true) }}
{% endfor %}
</div>
{% endif %}
{% endmacro %}
{% macro image(id, field) %}
{% if field.value is not empty %}
<img src="{{ field.value }}" alt="Current Image" class="field-image-preview"><br>
<div class="form-check form-switch mb-2">
<input type="checkbox" value="1" name="{{ id }}_delete" class="form-check-input">
<label class="form-check-label">
{{ lng('panel.image_field_delete') }}
</label>
</div>
{% endif %}
{% set field = field|merge({'type':'file'}) %}
{{ _self.input(id, field) }}
{% endmacro %}
{% macro input_ul(id, field) %}
{% set max = "" %}
{% if field.maxlength is defined %}
{% for i in 1..field.maxlength %}
{% set max = max ~ "9" %}
{% endfor %}
{% endif %}
<div class="input-group">
<input type="number" min="0" {% if max is not empty %} max="{{ max }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{% if field.value >= 0 %}{{ field.value }}{% endif %}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %}/>
<div class="input-group-text">
<input class="form-check-input mt-0" type="checkbox" name="{{ id }}_ul" value="1" {% if field.value == -1 %} checked="checked" {% endif %}>
</div>
</div>
{% endmacro %}
{% macro select(id, field) %}
{% if field.next_to is defined %}
<div class="input-group">
{% endif %}
<select {% if field.visible is defined and field.visible == false %} disabled {% endif %} class="form-select {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" name="{{ id }}{% if field.select_mode is defined and field.select_mode == 'multiple' %}[]{% endif %}" id="{{ id }}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.select_mode is defined and field.select_mode == 'multiple' %} multiple="multiple" {% endif %}{% if field.readonly is defined and field.readonly %} readonly {% endif %}>
{% for val,txt in field.select_var %}
<option value="{{ val }}" {% if field.selected is defined and ((field.selected is not iterable and field.selected == val) or (field.selected is iterable and val in field.selected|keys)) %} selected="selected" {% endif %}>{{ txt|raw }}</option>
{% endfor %}
</select>
{% if field.next_to is defined %}
{% for nid, nfield in field.next_to %}
{% if nfield.next_to_prefix is defined %}
<span class="input-group-text">{{ nfield.next_to_prefix }}</span>
{% endif %}
{{ _self.fieldrow(nid, nfield, true) }}
{% endfor %}
</div>
{% endif %}
{% endmacro %}
{% macro textarea(id, field) %}
<textarea {% if field.visible is defined and field.visible == false %} disabled {% endif %} rows="{{ field.rows|default('12') }}" cols="{{ field.cols|default('60') }}" id="{{ id }}" name="{{ id }}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %} {% if field.style is defined %} style="{{ field.style }}" {% endif %}>{{ field.value|raw }}</textarea>
{% endmacro %}
{% macro link(id, field) %}
<a href="{{ field.href|raw }}" class="{{ field.classes }}">{{ field.label|raw }}</a>
{% endmacro %}
{% macro itemlist(id, field) %}
{% if field.values is not empty %}
{% for value in field.values %}
<p>{{ value.item|raw }}
{% if value.href is defined and value.href is not empty %}
{{ _self.link(id, value) }}
{% endif %}
</p>
{% endfor %}
{% endif %}
{% if field.next_to is defined %}
{% for nid, nfield in field.next_to %}
{{ _self.fieldrow(nid, nfield, true) }}
{% endfor %}
{% endif %}
{% endmacro %}

View File

View File

@@ -0,0 +1,37 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block content %}
<form action="{{ action|default("") }}" method="post" enctype="application/x-www-form-urlencoded" class="form">
<div class="alert alert-warning" role="alert">
<h4 class="alert-heading">{{ lng('panel.security_question') }}</h4>
<p>{{ question|raw }}</p>
{% if with_checkbox is defined and with_checkbox is iterable %}
{% if with_checkbox.show %}
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="delete_userfiles" name="delete_userfiles" value="1">
<label class="form-check-label" for="delete_userfiles">{{ with_checkbox.chk_text|raw }}</label>
</div>
{% else %}
<input type="hidden" name="delete_userfiles" value="0"/>
{% endif %}
{% endif %}
<p>
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="send" value="send"/>
{% for id,field in url_params %}
<input type="hidden" name="{{ id }}" value="{{ field }}"/>
{% endfor %}
<button class="btn btn-danger" type="submit" name="submitbutton">{{ lng('panel.yes') }}</button>&nbsp;
{% if back_link is defined and back_link is iterable and back_link|length > 0 %}
<a href="{{ linker(back_link) }}" class="btn btn-secondary">{{ lng('panel.no') }}</a>
{% else %}
<a href="javascript:history.back(-1)" class="btn btn-secondary">{{ lng('panel.no') }}</a>
{% endif %}
</p>
</div>
</form>
{% endblock %}

View File

View File

@@ -0,0 +1,89 @@
<!-- language select -->
<form action="{{ pagecontent.form.formaction }}" method="get">
<div class="row mb-3">
<label for="language" class="col-sm-4 col-form-label">{{ lng('install.language') }}</label>
<div class="col-sm-8">
<select class="form-select" id="language" name="language">
{% for lngfile,lngname in pagecontent.form.languages %}
<option value="{{ lngfile }}" {% if lngfile == pagecontent.form.activelang %} selected="selected" {% endif %}>{{ lngname }}</option>
{% endfor %}
</select>
</div>
</div>
<aside class="text-end">
<input type="hidden" name="check" value="1"/>
<button class="btn btn-sm btn-primary" type="submit" name="chooselang">{{ lng('install.lngbtn_go') }}</button>
</aside>
</form>
<!-- main install form -->
<div class="alert alert-primary mt-md-3" role="alert">{{ lng('install.welcometext')|raw }}</div>
{% if pagecontent.form.result is not empty %}
<div class="alert alert-warning" role="alert">
{% for emsg in pagecontent.form.result %}
<p>{{ emsg }}</p>
{% endfor %}
</div>
{% endif %}
<form action="{{ pagecontent.form.formaction }}" method="post">
{% for fdata in pagecontent.form.data %}
<fieldset>
<legend>{{ fdata.title }}</legend>
{% for field in fdata.fields %}
{% if field is iterable %}
{% if field.type is defined %}
{% if field.type == 'text' or field.type == 'password' %}
<div class="row mb-3">
<label for="{{ field.id }}" class="col-sm-4 col-form-label">{{ field.label|raw }}</label>
<div class="col-sm-8">
<input type="{{ field.type }}" class="form-control {% if field.style == 'red' %}is-invalid{% endif %}" id="{{ field.id }}" name="{{ field.name }}" value="{{ field.value }}" {% if field.required %} required {% endif %}/>
</div>
</div>
{% elseif field.type == 'select' %}
<div class="row mb-3">
<label for="{{ field.id }}" class="col-sm-4 col-form-label">{{ field.label|raw }}</label>
<div class="col-sm-8">
<select class="form-select {% if field.style == 'red' %}is-invalid{% endif %}" id="{{ field.id }}" name="{{ field.name }}" {% if field.required %} required {% endif %}>
{% for opts in field.options %}
<option value="{{ opts.value }}" {% if opts.selected %} selected="selected" {% endif %}>{{ opts.label }}</option>
{% endfor %}
</select>
</div>
</div>
{% elseif field.type == 'checkbox' %}
<div class="row mb-3">
<label for="{{ field.id }}" class="col-sm-4 col-form-label">{{ field.label|raw }}</label>
<div class="col-sm-8">
<div class="form-check form-switch">
<input class="form-check-input {% if field.style == 'red' %}is-invalid{% endif %}" type="checkbox" value="{{ field.value }}" id="{{ field.id }}" name="{{ field.name }}" {% if field.checked %} checked="checked" {% endif %}>
</div>
</div>
</div>
{% endif %}
{% else %}
<div class="row mb-3">
<label class="col-sm-4 col-form-label">{{ field.label|raw }}</label>
<div class="col-sm-8">
{% for radios in field.fields %}
<div class="form-check">
<input class="form-check-input {% if field.style == 'red' %}is-invalid{% endif %}" type="radio" name="{{ radios.name }}" id="{{ radios.id }}" value="{{ radios.value }}" {% if radios.checked %}checked="checked"{% endif %}>
<label class="form-check-label" for="{{ radios.id }}">
{{ radios.label }}
</label>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% endif %}
{% endfor %}
</fieldset>
{% endfor %}
<aside class="text-end mt-3">
<input type="hidden" name="check" value="1"/>
<input type="hidden" name="language" value="{{ pagecontent.form.activelang }}"/>
<input type="hidden" name="installstep" value="1"/>
<button class="btn btn-lg btn-success" type="submit" name="submitbutton">
{{ lng('click_here_to_continue') }} &raquo;
</button>
</aside>
</form>

View File

View File

@@ -0,0 +1,126 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
<div class="container max-w-lg flex align-content-center mt-5">
<img src="{{ basehref|default('') }}templates/Froxlor/assets/img/logo.png" class="mb-5" alt="{{ lng('install.slogan') }}"/>
{% if error is not null %}
<div class="alert alert-danger mb-4">{{ error }}</div>
{% endif %}
<div class="row text-center gx-0">
<div class="col p-3{{ setup.step == 0 ? ' bg-white shadow rounded-top' : '' }}">
<i class="far fa-circle{{ setup.step > 0 ? '-check' : '' }}"></i>
{% if setup.step > 0 %}<a href="?step=0" class="text-decoration-none">{{ lng('install.preflight') }}</a>{% else %}{{ lng('install.preflight') }}{% endif %}
</div>
<div class="col p-3{{ setup.step == 1 ? ' bg-white shadow rounded-top' : '' }}">
<i class="far fa-circle{{ setup.step > 1 ? '-check' : '' }}"></i>
{% if setup.step > 1 %}<a href="?step=1" class="text-decoration-none">{{ lng('install.database.top') }}</a>{% else %}{{ lng('install.database.top') }}{% endif %}
</div>
<div class="col p-3{{ setup.step == 2 ? ' bg-white shadow rounded-top' : '' }}">
<i class="far fa-circle{{ setup.step > 2 ? '-check' : '' }}"></i>
{% if setup.step > 2 %}<a href="?step=2" class="text-decoration-none">{{ lng('install.admin.top') }}</a>{% else %}{{ lng('install.admin.top') }}{% endif %}
</div>
<div class="col p-3{{ setup.step == 3 ? ' bg-white shadow rounded-top' : '' }}">
<i class="far fa-circle{{ setup.step > 3 ? '-check' : '' }}"></i>
{% if setup.step > 3 %}<a href="?step=3" class="text-decoration-none">{{ lng('install.system.top') }}</a>{% else %}{{ lng('install.system.top') }}{% endif %}
</div>
<div class="col p-3{{ setup.step == 4 ? ' bg-white shadow rounded-top' : '' }}">
<i class="far fa-circle{{ setup.step > 4 ? '-check' : '' }}"></i>
{% if setup.step > 4 %}<a href="?step=4" class="text-decoration-none">{{ lng('install.install.top') }}</a>{% else %}{{ lng('install.install.top') }}{% endif %}
</div>
</div>
<div class="card border-0 shadow">
<div class="card-body p-5">
<form method="post" action="?step={{ setup.step }}">
{% if setup.step > 0 %}
<div class="d-block d-lg-flex justify-content-between align-items-center mb-3">
<h4 class="mb-3 mb-lg-0">{{ section.title }}</h4>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="switchInstallMode" {% if extended is defined and extended %}checked{% endif %}>
<label class="form-check-label" for="switchInstallMode">{% if extended is defined and extended %}{{ lng('install.switchmode_basic') }}{% else %}{{ lng('install.switchmode_advanced') }}{% endif %}</label>
</div>
</div>
<p class="lead">{{ section.description|raw }}</p>
<hr />
{% import "Froxlor/form/formfields.html.twig" as formfields %}
{% for id, field in section.fields %}
{% if field.advanced is defined and field.advanced == true and extended == false %}
{# hide advanced fields #}
{% set field = field|merge({'type': 'hidden'}) %}
{% endif %}
{{ formfields.field(id, field) }}
{% endfor %}
<div class="d-flex {% if setup.step < setup.max_steps %}justify-content-between{% else %}justify-content-end{% endif %} mt-4">
{% if setup.step < setup.max_steps %}
<a href="?step={{ setup.step - 1 }}" class="btn btn-secondary">&laquo; {{ lng('panel.back') }}</a>
<button type="submit" name="submit" class="btn btn-primary">{{ lng('panel.next') }} &raquo;</button>
{% else %}
<span id="submitAuto"><i class="fas fa-spinner fa-pulse"></i> {{ lng('install.install.waitforconfig') }}</span>
<button id="submitManual" type="submit" name="submit" class="btn btn-success d-none">{{ lng('install.install.top') }} &raquo;</button>
{% endif %}
</div>
{% else %}
<h4 class="mb-3">{{ lng('install.dependency_check.title') }}</h4>
<p class="lead">{{ lng('install.dependency_check.description') }}</p>
<p class="lead {{ preflight.criticals ? 'text-danger' : preflight.suggestions ? 'text-warning' : 'text-success'}}">
<i class="{{ preflight.criticals ? 'fa-solid fa-triangle-exclamation' : preflight.suggestions ? 'fa-solid fa-circle-info' : 'far fa-circle-check' }}"></i>
{{ preflight.text }}
</p>
{% if preflight.criticals %}
<p class="text-muted">{{ lng('install.critical_error') }}</p>
<ul>
{% for ctype, critical in preflight.criticals %}
{% if ctype == 'wrong_ownership' %}
<li>{{ lng('install.errors.' ~ ctype, [critical.user, critical.group]) }}</li>
{% elseif ctype == 'missing_extensions' %}
<li>{{ lng('install.errors.' ~ ctype) }}<ul>
{% for missext in critical %}
<li>{{ missext }}</li>
{% endfor %}
</ul>
</li>
{% else %}
<li>{{ critical|raw }}</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% if preflight.suggestions %}
<p class="text-muted">{{ lng('install.suggestions') }}</p>
<ul>
{% for ctype, suggestion in preflight.suggestions %}
{% if ctype == 'missing_extensions' %}
<li>{{ lng('install.errors.suggestedextensions') }}<ul>
{% for missext in suggestion %}
<li>{{ missext }}</li>
{% endfor %}
</ul>
</li>
{% else %}
<li>{{ suggestion|raw }}</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
<div class="d-flex justify-content-end mt-4">
{% if preflight.criticals %}
<a href="" class="btn btn-secondary"><i class="fa-solid fa-arrow-rotate-left"></i> {{ lng('install.check_again') }}</a>
{% else %}
<a href="?step=1" class="btn btn-primary">{{ lng('install.start_installation') }}</a>
{% endif %}
</div>
{% endif %}
</form>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,48 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
<div>
<h5 class="mb-1">
<i class="fa-solid fa-download me-1"></i>
{{ lng('update.update') }}
</h5>
<span class="text-muted">{{ lng('update.description') }}</span>
</div>
{% endblock %}
{% block content %}
<div class="card table-responsive">
<div class="card-body">
<table class="table table-borderless align-middle mb-0 px-3">
<tbody>
{% for check in checks %}
<tr class="{% if check.result == 1 %}table-danger{% elseif check.result == 2 %}table-warning{% endif %}">
<td class="w-75" scope="row">{{ check.title }}</td>
<td class="col-auto text-end{% if check.result == 0 %} text-success{% endif %}">
<span class="d-none d-md-inline">{{ check.result_txt }}</span>
{% if check.result == 0 %}&nbsp;<i class="fa-solid fa-check-circle" {% elseif check.result == 2 %}<span class="d-md-none">&nbsp;???</span>{% elseif check.result == 1 %}<span class="d-md-none">&nbsp;!!!</span>
{% endif %}
</td>
</tr>
{% if check.result_desc is not empty %}
<tr>
<td colspan="2">
<span>{{ check.result_desc|raw }}</span>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
<div class="row pt-md-3">
<div class="col-12 text-end mt-4 mt-md-0">
<a class="btn btn-lg btn-block btn-primary" href="admin_index.php">
{{ lng('success.clickheretocontinue') }}
&raquo;
</a>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,36 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<form action="index.php" class="col-12 max-w-420 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<img class="align-self-center my-5" src="{{ header_logo_login }}" alt="Froxlor Server Management Panel"/>
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{ pagetitle }}</h5>
{% if message is not empty %}
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">{{ lng('error.error') }}</h4>
<p>{{ message|raw }}</p>
</div>
{% endif %}
<div class="mb-3">
<label for="2fa_code" class="col-form-label">{{ lng('login.2facode') }}</label>
<input class="form-control" type="text" name="2fa_code" id="2fa_code" value="" autocomplete="off" autofocus required/>
</div>
</div>
<div class="card-body d-grid gap-2">
<input type="hidden" name="action" value="2fa_verify"/>
<input type="hidden" name="send" value="send"/>
<button class="btn btn-primary rounded-top-0" type="submit" name="2faverify">{{ lng('2fa.2fa_verify') }}</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,53 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<form action="{{ formaction }}" class="col-12 max-w-420 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<img class="align-self-center my-5" src="{{ header_logo_login }}" alt="Froxlor Server Management Panel"/>
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{ pagetitle }}</h5>
{% if upd_in_progress %}
<div class="alert alert-warning" role="alert">
{{ lng('update.updateinprogress_onlyadmincanlogin')|raw }}
</div>
{% elseif successmsg is not empty %}
<div class="alert alert-success" role="alert">
<h4 class="alert-heading">{{ lng('success.success') }}</h4>
<p>{{ successmsg|raw }}</p>
</div>
{% elseif message is not empty %}
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">{{ lng('error.error') }}</h4>
<p>{{ message|raw }}</p>
</div>
{% endif %}
<div class="mb-3">
<label for="loginname" class="col-form-label">{{ lng('login.username') }}</label>
<input class="form-control" type="text" name="loginname" id="loginname" value="" required autofocus/>
</div>
<div class="mb-3">
<label for="loginemail" class="col-form-label">{{ lng('login.email') }}</label>
<input class="form-control" type="email" name="loginemail" id="loginemail" value="" required/>
</div>
</div>
<div class="card-body d-grid gap-2">
<button class="btn btn-primary rounded-top-0" type="submit" name="doremind">{{ lng('login.remind') }}</button>
</div>
<div class="card-footer">
<a class="card-link text-muted" href="index.php">
<i class="fa-solid fa-angles-left"></i>
{{ lng('login.backtologin') }}</a>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

View File

@@ -0,0 +1,54 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<form class="col-12 max-w-420 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<img class="align-self-center my-5" src="{{ header_logo_login }}" alt="Froxlor Server Management Panel"/>
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{ pagetitle }}</h5>
<p>{{ lng('login.welcomemsg') }}</p>
{% if upd_in_progress %}
<div class="alert alert-warning" role="alert">
{{ lng('update.updateinprogress_onlyadmincanlogin')|raw }}
</div>
{% elseif successmsg is not empty %}
<div class="alert alert-success" role="alert">
<h4 class="alert-heading">{{ lng('success.success') }}</h4>
<p>{{ successmsg|raw }}</p>
</div>
{% elseif message is not empty %}
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">{{ lng('error.error') }}</h4>
<p>{{ message|raw }}</p>
</div>
{% endif %}
<div class="mb-3">
<label for="loginname" class="col-form-label">{{ lng('login.username') }}</label>
<input class="form-control" type="text" name="loginname" id="loginname" value="" required autofocus/>
</div>
<div class="mb-3">
<label for="password" class="col-form-label">{{ lng('login.password') }}</label>
<input class="form-control" type="password" name="password" id="password" value="" required/>
</div>
</div>
<div class="card-body d-grid gap-2">
<button class="btn btn-primary rounded-top-0" type="submit" name="dologin">{{ lng('login.login') }}</button>
</div>
{% if get_setting('panel.allow_preset') == '1' %}
<div class="card-footer">
<a class="card-link text-muted" href="index.php?action=forgotpwd">{{ lng('login.forgotpwd') }}</a>
</div>
{% endif %}
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<form action="{{ formaction }}" class="col-12 max-w-420 d-flex flex-column" method="post" enctype="application/x-www-form-urlencoded">
<img class="align-self-center my-5" src="{{ header_logo_login }}" alt="Froxlor Server Management Panel"/>
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{ pagetitle }}</h5>
<p>{{ lng('login.presend') }}</p>
{% if message is not empty %}
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">{{ lng('error.error') }}</h4>
<p>{{ message|raw }}</p>
</div>
{% endif %}
<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 autofocus/>
</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>
</div>
<div class="card-body d-grid gap-2">
<button class="btn btn-primary rounded-top-0" type="submit" name="doremind">{{ lng('login.remind') }}</button>
</div>
<div class="card-footer">
<a class="card-link text-muted" href="index.php">
<i class="fa-solid fa-angles-left"></i>
{{ lng('login.backtologin') }}</a>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,5 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block content %}
{% include 'Froxlor/misc/alertbox.html.twig' %}
{% endblock %}

View File

@@ -0,0 +1,5 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
{% include 'Froxlor/misc/alertbox.html.twig' %}
{% endblock %}

View File

@@ -0,0 +1,27 @@
<div class="alert alert-{{ type|default("info") }} fade show" role="alert">
{% if heading is defined and heading is not empty %}
<h4 class="alert-heading">
{{ heading }}
</h4>
{% endif %}
<p>
{{ alert_msg|raw }}
</p>
{% if alert_info %}
<hr>
<p class="mb-0">
<pre>{{ alert_info|raw }}</pre>
</p>
{% endif %}
{% if redirect_link %}
<p>
<a href="{{ redirect_link|raw }}" class="btn btn-{{ btntype }}">
{% if type == 'danger' %}
{{ lng('panel.back') }}
{% else %}
{{ lng('success.clickheretocontinue') }}
{% endif %}
</a>
</p>
{% endif %}
</div>

View File

@@ -0,0 +1,22 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
<div class="container max-w-lg flex align-content-center mt-5">
<img src="templates/Froxlor/assets/img/logo.png" alt="Froxlor Server Management Panel" />
<div class="row gx-0 rounded shadow bg-primary text-white mt-5">
<div class="col p-5 rounded-start">
<h2 class="card-title">Welcome to Froxlor</h2>
<p class="lead mt-5">It seems that Froxlor has not been installed yet.</p>
<p class="lead">Click on the button below to start the installation.</p>
</div>
<div class="col text-white position-relative">
<img class="h-75 position-absolute bottom-0 end-0 rounded-tl-bl" src="{{ basehref }}templates/Froxlor/assets/img/preview.jpg">
</div>
</div>
<div class="mt-5 text-end">
<a class="btn btn-lg btn-primary" href="./install/install.php" title="Click to start the install process">Start install</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
<div class="container my-auto">
<div class="alert alert-danger fade show" role="alert">
<h4 class="alert-heading">
A database error occurred
</h4>
<p>
{{ message }}
</p>
{% if debug is not empty %}
<hr>
<p class="mb-0">
<pre>{{ debug }}</pre>
</p>
{% endif %}
<p class="mt-1 text-center">
<a href="#" class="btn btn-primary" title="Click here to go back" id="historyback">Go back</a>
{% if report is not empty %}
<a href="{{ report|raw }}" class="btn btn-warning" title="Click here to report error">Report error</a>
{% endif %}
</p>
</div>
</div>
{% endblock %}

View File

View File

@@ -0,0 +1,18 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
<div class="container my-auto">
<div class="alert alert-warning fade show" role="alert">
<h4 class="alert-heading">
Whoops!
</h4>
<p>The configuration file <b>lib/userdata.inc.php</b> cannot be read from the webserver.</p>
<p>This mostly happens due to wrong ownership.<br />Try the following command to correct the ownership:</p>
<pre>chown -R {{ user }}:{{ group }} {{ installdir }}</pre>
<hr>
<p class="mt-1 text-center">
<a href="" class="btn btn-primary" title="Reload page">Reload</a>
</p>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex, nofollow, noarchive" />
<meta name="googlebot" content="nosnippet" />
<!-- CSS -->
<link href="{{ basehref }}templates/Froxlor/assets/css/main.css" rel="stylesheet">
<!-- Scripts -->
<script src="{{ basehref }}templates/Froxlor/assets/js/main.js"></script>
<title>Froxlor - Error</title>
</head>
<body class="min-vh-100 d-flex align-items-center">
<div class="container-fluid">
<div class="container max-w-lg">
<div class="card bg-danger text-white">
<div class="card-body">
<h4 class="card-title">
Whoops!
</h4>
<p>It seems you are using an older version of PHP</p>
<p>Froxlor requires at least PHP version {{ froxlor_min_version }}</p>
<p>The installed version is: {{ current_version }}</p>
</div>
<div class="card-footer text-end">
<a href="" class="btn btn-primary" title="Click to refresh">Refresh</a>
</div>
</div>
</div>
<footer class="pý-5 text-center">
<span>
<img src="{{ basehref }}templates/Froxlor/assets/img/logo_grey.png" alt="Froxlor" />
&copy; 2009-{{ current_year }} by <a href="https://www.froxlor.org/" rel="external">the Froxlor Team</a>
</span>
</footer>
</div>
</body>
</html>

View File

@@ -0,0 +1,17 @@
{% extends "Froxlor/base.html.twig" %}
{% block content %}
<div class="container my-auto">
<div class="alert alert-warning fade show" role="alert">
<h4 class="alert-heading">
Whoops!
</h4>
<p>It seems like you've hit the rate limit.</p>
<p>Please slow down your requests and retry after {{ retry|date('d.m.Y H:i:s') }}</p>
<hr>
<p class="mt-1 text-center">
<a href="" class="btn btn-primary" title="Reload page">Reload</a>
</p>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex, nofollow, noarchive" />
<meta name="googlebot" content="nosnippet" />
<!-- CSS -->
<link href="{{ basehref }}templates/Froxlor/assets/css/main.css" rel="stylesheet">
<!-- Scripts -->
<script src="{{ basehref }}templates/Froxlor/assets/js/main.js"></script>
<title>Froxlor - Error</title>
</head>
<body class="min-vh-100 d-flex align-items-center">
<div class="container-fluid">
<div class="container max-w-lg">
<div class="card bg-danger text-white">
<div class="card-body">
<h4 class="card-title">
Whoops!
</h4>
<p>It seems you are missing some required files.</p>
<p>Froxlor uses composer for its external requirements. Try the following command to install them:</p>
<pre>cd {{ froxlor_install_dir }} && composer install --no-dev</pre>
</div>
<div class="card-footer text-end">
<a href="" class="btn btn-primary" title="Click to refresh">Refresh</a>
</div>
</div>
</div>
<footer class="py-5 text-center">
<span>
<img src="{{ basehref }}templates/Froxlor/assets/img/logo_grey.png" alt="Froxlor" />
&copy; 2009-{{ current_year }} by <a href="https://www.froxlor.org/" rel="external">the Froxlor Team</a>
</span>
</footer>
</div>
</body>
</html>

View File

@@ -0,0 +1,26 @@
{% macro vpopover(isnewerversion, additional_info, full_version, dbversion, channel, last_update_check, message) %}
{% if isnewerversion == 0 %}
<p>{{ additional_info }}</p>
<div class='d-flex justify-content-between'>
<div class='fw-bold'>Version:</div>
<div>{{ full_version }}</div>
</div>
<div class='d-flex justify-content-between'>
<div class='fw-bold'>Database version:</div>
<div>{{ dbversion }}</div>
</div>
<div class='d-flex justify-content-between'>
<div class='fw-bold'>Channel:</div>
<div>{{ channel }}</div>
</div>
<div class='d-flex justify-content-between'>
<div class='fw-bold'>Last checked:</div>
<div>{{ last_update_check|date('d.m.Y H:i') }}</div>
</div>
{% else %}
<p>{{ message }}</p>
{% if get_config('enable_webupdate') %}
<a class='btn d-block btn-outline-warning' href='admin_autoupdate.php?page=overview'>Open updater</a>
{% endif %}
{% endif %}
{% endmacro %}

View File

@@ -0,0 +1,12 @@
{% import "Froxlor/misc/version_popover.html.twig" as vc %}
<span id="ucheck" class="nav-link {% if isnewerversion == 0 and aucheck < 0 %}text-muted{% elseif isnewerversion == 0 %}text-success{% else %}text-warning{% endif %}"
data-bs-container="body" data-bs-toggle="popover" data-bs-placement="bottom" data-bs-trigger="hover focus click" data-bs-html="true"
data-bs-content="{{ vc.vpopover(isnewerversion, additional_info, full_version, dbversion, channel, last_update_check, message) }}"
>
{% if isnewerversion == 0 and aucheck == 0 %}
<i class="fa-solid fa-circle-check me-1"></i>
{% else %}
<i class="fa-solid fa-circle-exclamation me-1"></i>
{% endif %}
<span class="d-none d-xl-inline">{{ version }}</span>
</span>

View File

@@ -0,0 +1,176 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
<h5>
<i class="fa-solid fa-hard-drive me-1"></i>
{{ lng('admin.apcuinfo') }}
</h5>
{% endblock %}
{% block actions %}
<a class="btn btn-warning" href="{{ linker({'section':'apcuinfo','page':'showinfo','action':'delete'}) }}">
<i class="fa-solid fa-trash-can me-1"></i>
{{ lng('apcuinfo.clearcache') }}
</a>
{% endblock %}
{% block content %}
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-4 mb-4">
<div class="col">
<div class="card h-100 mb-3">
<div class="card-body">
<h5 class="card-title">{{ lng('apcuinfo.memnote') }}</h5>
<div class="progress position-relative">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ apcuinfo.mem_used_percentage }}%" aria-valuenow="{{ apcuinfo.mem_used }}" aria-valuemin="0" aria-valuemax="{{ apcuinfo.mem_avail }}"></div>
<small class="justify-content-center d-flex position-absolute w-100 text-dark">{{ apcuinfo.mem_used_percentage }}%</small>
</div>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.total') }}
<span class="badge bg-secondary">{{ apcuinfo.readable.mem_size }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.used') }}
<span class="badge bg-secondary">{{ apcuinfo.readable.mem_used }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.free') }}
<span class="badge bg-secondary">{{ apcuinfo.readable.mem_avail }}</span>
</li>
</ul>
</div>
</div>
<div class="col">
<div class="card h-100 mb-3">
<div class="card-body">
<h5 class="card-title">{{ lng('apcuinfo.hitmiss') }}</h5>
<div class="progress position-relative">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ apcuinfo.num_hits_percentage }}%" aria-valuenow="{{ apcuinfo.num_hits }}" aria-valuemin="0" aria-valuemax="{{ apcuinfo.num_hits_and_misses }}"></div>
<div class="progress-bar bg-danger" role="progressbar" style="width: {{ 100 - apcuinfo.num_misses_percentage }}%" aria-valuenow="{{ apcuinfo.num_misses }}" aria-valuemin="0" aria-valuemax="{{ apcuinfo.num_hits_and_misses }}"></div>
<small class="justify-content-center d-flex position-absolute w-100 text-dark">{{ apcuinfo.num_hits_percentage }}%</small>
</div>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.hit') }}
<span class="badge bg-secondary">{{ apcuinfo.readable.num_hits }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.miss') }}
<span class="badge bg-secondary">{{ apcuinfo.readable.num_misses }}</span>
</li>
</ul>
</div>
</div>
<div class="col">
<div class="card h-100 mb-3">
<div class="card-body">
<h5 class="card-title">{{ lng('apcuinfo.cachetitle') }}</h5>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.cvar') }}
<span class="badge bg-secondary">{{ apcuinfo.readable.number_vars }}
({{ apcuinfo.size_vars }})</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.reqrate') }}
<span class="badge bg-secondary">{{ apcuinfo.req_rate_user }}
{{ lng('apcuinfo.creqsec') }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.hitrate') }}
<span class="badge bg-secondary">{{ apcuinfo.hit_rate_user }}
{{ lng('apcuinfo.creqsec') }}</span>
</li>
</ul>
</div>
</div>
<div class="col">
<div class="card h-100 mb-3">
<div class="card-body">
<h5 class="card-title">{{ lng('apcuinfo.detailmem') }}</h5>
{% if apcuinfo.fragmentation is not iterable %}
{{ lng('apcuinfo.nofragment') }}
{% endif %}
</div>
{% if apcuinfo.fragmentation is iterable %}
<div class="progress position-relative">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ apcuinfo.fragmentation.used_percentage }}%" aria-valuenow="{{ apcuinfo.fragmentation.used_bytes }}" aria-valuemin="0" aria-valuemax="{{ apcuinfo.fragmentation.total_bytes }}"></div>
<small class="justify-content-center d-flex position-absolute w-100 text-dark">{{ apcuinfo.fragmentation.used_percentage }}%</small>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.total') }}
<span class="badge bg-secondary">{{ apcuinfo.fragmentation.readable.total_bytes }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.used') }}
<span class="badge bg-secondary">{{ apcuinfo.fragmentation.readable.used_bytes }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('apcuinfo.fragments') }}
<span class="badge bg-secondary">{{ apcuinfo.fragmentation.readable.num_frags }}</span>
</li>
</ul>
{% endif %}
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<div class="card table-responsive mb-3">
<table class="table table-borderless table-striped align-middle mb-0 px-3">
<tbody>
<tr>
<th class="text-center" colspan="2" scope="row">{{ lng('apcuinfo.generaltitle') }}</th>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('apcuinfo.version') }}</th>
<td class="text-end">{{ apcuinfo.apcversion }}</td>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('apcuinfo.phpversion') }}</th>
<td class="text-end">{{ apcuinfo.phpversion }}</td>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('admin.hostname') }}</th>
<td class="text-end">{{ apcuinfo.host }}</td>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('admin.serversoftware') }}</th>
<td class="text-end">{{ apcuinfo.server }}</td>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('apcuinfo.start') }}</th>
<td class="text-end">{{ apcuinfo.start_time|date('d.m.Y H:i:s') }}</td>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('apcuinfo.uptime') }}</th>
<td class="text-end">{{ apcuinfo.uptime }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col">
<div class="card table-responsive">
<table class="table table-borderless table-striped align-middle mb-0 px-3">
<tbody>
<tr>
<th class="text-center" colspan="2" scope="row">{{ lng('apcuinfo.runtime') }}</th>
</tr>
{% for k,v in apcuinfo.runtimelines %}
<tr>
<th class="fw-bold" scope="row">{{ k|raw }}</th>
<td class="text-end">{{ v|raw }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1 @@
<textarea class="form-control bg-secondary text-light mb-2" rows="{{ numbrows }}" readonly>{{ commands|raw }}</textarea>

View File

@@ -0,0 +1,2 @@
<textarea class="form-control bg-secondary text-light mb-2" rows="1" readonly>{{ distro_editor }} {{ realname }}</textarea>
<textarea class="form-control mb-2" rows="{% if numbrows <= 20 %}{{ numbrows }}{% else %}21{% endif %}" readonly>{{ file_content|raw }}</textarea>

View File

@@ -0,0 +1,6 @@
<fieldset class="file">
{# <legend>{{ realname }}</legend> #}
{{ commands_pre|raw }}
{{ commands_file|raw }}
{{ commands_post|raw }}
</fieldset>

View 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">{{ basedir }}bin/froxlor-cli froxlor:config-services --apply={{ params_filename }}
rm {{ params_filename }}</textarea>
</div>
{% endblock %}

View File

@@ -0,0 +1,138 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
<h5>
<i class="fa-solid fa-wrench"></i>
{{ lng('admin.configfiles.serverconfiguration') }}
</h5>
<span class="text-muted">{{ lng('admin.configfiles.description') }}</span>
{% endblock %}
{% block actions %}
<a class="btn btn-outline-primary" href="{{ linker({'section':'configfiles','reselect':1}) }}">
<i class="fa-solid fa-grip me-1"></i>
{{ lng('admin.configfiles.distribution') }}:
{{ distribution }}
</a>
{% endblock %}
{% block content %}
<div class="pb-2">
<div class="alert alert-info fade show" role="alert">
<p>{{ lng('admin.configfiles.minihowto')|raw }}</p>
</div>
</div>
<form action="{{ action|default(filename) }}" method="post" enctype="application/x-www-form-urlencoded" class="form">
{% block settings %}
<div class="row row-cols-1 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">
{{ lng('admin.configfiles.skipconfig') }}
</label>
</div>
{% endif %}
{% set daemons = field.getDaemons %}
{% for dtype,daemon in daemons %}
{% if stype == 'system' %}
<div class="form-check">
{% set recommended = false %}
{% if
(dtype == get_setting('system.traffictool')) 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>
<a class="show-config text-secondary opacity-50 float-end" role="button" data-dist="{{ distribution }}" data-section="{{ stype }}" data-daemon="{{ dtype }}" title="show config">
<i class="fa-regular fa-file-code"></i>
</a>
</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>
<a class="show-config text-secondary opacity-50 float-end" role="button" data-dist="{{ distribution }}" data-section="{{ stype }}" data-daemon="{{ dtype }}" title="show config">
<i class="fa-regular fa-file-code"></i>
</a>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
<div class="row mt-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="finish" value="1"/>
<div class="col-12 col-md-6">
<span class="text-danger">*</span>
{{ lng('admin.configfiles.recommendednote') }}
</div>
<div class="col-12 col-md-6 text-end">
<button type="button" class="btn btn-outline-secondary" id="selectRecommendedConfig">{{ lng('admin.configfiles.selectrecommended') }}</button>
<button type="button" class="btn btn-outline-secondary" id="downloadSelectionAsJson">
<i class="fa-solid fa-download"></i>
{{ lng('admin.configfiles.downloadselected') }}</button>
<button type="submit" class="btn btn-primary">{{ lng('update.proceed') }}</button>
</div>
</div>
</form>
<div class="modal fade" id="configTplShow" aria-hidden="true" aria-labelledby="configTplShowLabel" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="configTplShowLabel"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ lng('panel.modalclose') }}"></button>
</div>
<div class="modal-body text-start"></div>
<div class="modal-footer">
<button class="btn btn-primary" data-bs-dismiss="modal">{{ lng('panel.modalclose') }}</button>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,40 @@
{% extends "Froxlor/settings/index.html.twig" %}
{% block actions %}
<a class="btn btn-outline-primary" href="{{ linker({'section':'settings','page':'overview','part':'all'}) }}">
<i class="fa-solid fa-grip me-1"></i>
{{ lng('admin.configfiles.overview') }}
</a>
<a class="btn btn-outline-secondary" href="{{ linker({'section':'settings','page':'importexport'}) }}">
<i class="fa-solid fa-file-import me-1"></i>
{{ lng('admin.configfiles.importexport') }}
</a>
{% endblock %}
{% block settings %}
{% import "Froxlor/form/formfields.html.twig" as formfields %}
<div class="card mb-3">
<div class="formfields">
{% for id,setting in fields %}
{% if id != '_group' %}
{% set isEm = em is defined and em == id %}
{{ formfields.fieldrow(id, setting, false, (get_setting('system.hide_incompatible_settings') == '0'), isEm) }}
{% endif %}
{% endfor %}
</div>
</div>
<div>
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="page" value="{{ page }}"/>
<input type="hidden" name="action" value="{{ action }}"/>
<input type="hidden" name="send" value="send"/>
<div class="col-12 text-center mb-2 d-grid gap-2 d-md-block">
<button type="reset" class="btn btn-lg btn-outline-secondary me-md-3">{{ lng('panel.reset') }}</button>
<button type="submit" class="btn btn-lg btn-primary">{{ lng('panel.save') }}</button>
</div>
</div>
{% endblock %}

View File

View File

@@ -0,0 +1,60 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
<h5>
<i class="fa-solid fa-gears"></i>
{{ lng('admin.serversettings') }}
{% if fields._group is defined %}&nbsp;&raquo;&nbsp;{{ fields._group.title|raw }}
{% endif %}
</h5>
<span class="text-muted">{{ lng('admin.serversettings_desc') }}</span>
{% endblock %}
{% block actions %}
<a class="btn btn-outline-secondary" href="{{ linker({'section':'settings','page':'toggleSettingsMode'}) }}" title="{{ lng('panel.settingsmodetoggle') }}">
{% if get_setting('panel.settings_mode') == 0 %}
<i class="fa-solid fa-maximize me-1"></i>
{{ lng('panel.settingsmode') }}: {{ lng('panel.settingsmodebasic') }}
{% else %}
<i class="fa-solid fa-minimize me-1"></i>
{{ lng('panel.settingsmode') }}: {{ lng('panel.settingsmodeadvanced') }}
{% endif %}
</a>
<a class="btn btn-outline-secondary" href="{{ linker({'section':'settings','page':'importexport'}) }}">
<i class="fa-solid fa-file-import me-1"></i>
{{ lng('admin.configfiles.importexport') }}
</a>
{% endblock %}
{% block content %}
<form action="{{ action|default(filename) }}" method="post" enctype="multipart/form-data" class="form">
{% block settings %}
<div class="row row-cols-2 row-cols-md-2 row-cols-xl-4 g-3">
{% for field in fields %}
{% if get_setting('system.hide_incompatible_settings') == 0 or (get_setting('system.hide_incompatible_settings') == 1 and (field.visible is not defined or (field.visible is defined and field.visible))) %}
<div class="col">
<div class="card h-100 position-relative {% if not field.activated %}{% endif %}">
<div class="card-body d-flex overflow-hidden align-items-center">
<a href="{{ linker({'section':'settings','page':'overview','part':field.part}) }}" class="stretched-link">
<i class="{{ field.icon }} fa-2x me-4" style="width: 1em;"></i>
</a>
<div>
{{ field.title|raw }}
{% if field.info is defined and field.info is not empty %}
{{ field.info|raw }}
{% endif %}
</div>
</div>
{% if not field.activated %}
<div class="position-absolute top-0 end-0 p-1">
<span class="badge text-muted" style="background: #eee">{{ lng('panel.not_activated') }}</span>
</div>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endblock %}
</form>
{% endblock %}

View File

@@ -0,0 +1,214 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
<h5>
<i class="fa-solid fa-hard-drive me-1"></i>
{{ lng('admin.opcacheinfo') }}
</h5>
{% endblock %}
{% block actions %}
<a class="btn btn-warning" href="{{ linker({'section':'opcacheinfo','page':'showinfo','action':'reset'}) }}">
<i class="fa-solid fa-trash-can me-1"></i>
{{ lng('opcacheinfo.resetcache') }}
</a>
{% endblock %}
{% block content %}
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-4 mb-4">
<div class="col">
<div class="card h-100 mb-3">
<div class="card-body">
<h5 class="card-title">{{ lng('opcacheinfo.memusage') }}</h5>
<div class="progress position-relative">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ opcacheinfo.overview.used_memory_percentage }}%" aria-valuenow="{{ opcacheinfo.overview.used_memory }}" aria-valuemin="0" aria-valuemax="{{ opcacheinfo.overview.total_memory }}"></div>
<small class="justify-content-center d-flex position-absolute w-100 text-dark">{{ opcacheinfo.overview.used_memory_percentage }}%</small>
</div>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.totalmem') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.total_memory }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.used') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.used_memory }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.free') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.free_memory }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.wastedmem') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.wasted_memory }}</span>
</li>
</ul>
</div>
</div>
<div class="col">
<div class="card h-100 mb-3">
<div class="card-body">
<h5 class="card-title">{{ lng('opcacheinfo.hitsc') }}</h5>
<div class="progress position-relative">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ opcacheinfo.overview.hit_rate_percentage }}%" aria-valuenow="{{ opcacheinfo.overview.hits }}" aria-valuemin="0" aria-valuemax="{{ opcacheinfo.overview.hits + opcacheinfo.overview.misses }}"></div>
<div class="progress-bar bg-danger" role="progressbar" style="width: {{ 100 - opcacheinfo.overview.hit_rate_percentage }}%" aria-valuenow="{{ opcacheinfo.overview.misses }}" aria-valuemin="0" aria-valuemax="{{ opcacheinfo.overview.hits + opcacheinfo.overview.misses }}"></div>
<small class="justify-content-center d-flex position-absolute w-100 text-dark">{{ opcacheinfo.overview.hit_rate_percentage }}%</small>
</div>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.cachedscripts') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.num_cached_scripts }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.hitsc') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.hits }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.missc') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.misses }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.blmissc') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.blacklist_miss }}</span>
</li>
</ul>
</div>
</div>
<div class="col">
<div class="card h-100 mb-3">
<div class="card-body">
<h5 class="card-title">{{ lng('opcacheinfo.usedkey') }}</h5>
<div class="progress position-relative">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ opcacheinfo.overview.used_key_percentage }}%" aria-valuenow="{{ opcacheinfo.overview.num_cached_keys }}" aria-valuemin="0" aria-valuemax="{{ opcacheinfo.overview.max_cached_keys }}"></div>
<small class="justify-content-center d-flex position-absolute w-100 text-dark">{{ opcacheinfo.overview.used_key_percentage }}%</small>
</div>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.maxkey') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.max_cached_keys }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.usedkey') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.num_cached_keys }}</span>
</li>
</ul>
</div>
</div>
<div class="col">
<div class="card h-100 mb-3">
<div class="card-body">
<h5 class="card-title">{{ lng('opcacheinfo.strinterning') }}</h5>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.totalmem') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.interned.buffer_size }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.used') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.interned.strings_used_memory }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.free') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.interned.strings_free_memory }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ lng('opcacheinfo.strcount') }}
<span class="badge bg-secondary">{{ opcacheinfo.overview.readable.interned.number_of_strings }}</span>
</li>
</ul>
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<div class="card table-responsive mb-3">
<table class="table table-borderless table-striped align-middle mb-0 px-3">
<tbody>
<tr>
<th class="text-center" colspan="2" scope="row">{{ lng('opcacheinfo.generaltitle') }}</th>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('opcacheinfo.version') }}</th>
<td class="text-end">{{ opcacheinfo.version.version }}</td>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('opcacheinfo.phpversion') }}</th>
<td class="text-end">{{ opcacheinfo.version.php }}</td>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('admin.hostname') }}</th>
<td class="text-end">{{ opcacheinfo.version.host }}</td>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('admin.serversoftware') }}</th>
<td class="text-end">{{ opcacheinfo.version.server }}</td>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('opcacheinfo.start') }}</th>
<td class="text-end">{{ opcacheinfo.overview.start_time|date('d.m.Y H:i:s') }}</td>
</tr>
<tr>
<th class="fw-bold" scope="row">{{ lng('opcacheinfo.lastreset') }}</th>
<td class="text-end">
{% if opcacheinfo.overview.last_restart_time > 0 %}
{{ opcacheinfo.overview.last_restart_time|date('d.m.Y H:i:s') }}
{% else %}
{{ lng('panel.never') }}
{% endif %}
</td>
</tr>
</tbody>
</table>
</div>
<div class="card table-responsive">
<table class="table table-borderless table-striped align-middle mb-0 px-3">
<tbody>
<tr>
<th class="text-center" scope="row">{{ lng('opcacheinfo.funcsavail') }}</th>
</tr>
{% for funcs in opcacheinfo.functions %}
<tr>
<td>{{ funcs }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col">
<div class="card table-responsive">
<table class="table table-borderless table-striped align-middle mb-0 px-3">
<tbody>
<tr>
<th class="text-center" colspan="2" scope="row">{{ lng('opcacheinfo.runtimeconf') }}</th>
</tr>
{% for directive in opcacheinfo.directives %}
<tr>
<th class="fw-bold" scope="row">{{ directive.k }}</th>
<td class="text-end">
{% if directive.v is iterable %}
{% for vval in directive.v %}
{% if vval is iterable %}
{% for val2 in vval %}
{{ val2|raw }}<br>
{% endfor %}
{% else %}
{{ vval|raw }}<br>
{% endif %}
{% endfor %}
{% else %}
{{ directive.v|raw }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,42 @@
{% extends "Froxlor/userarea.html.twig" %}
{% block heading %}
<h5>
<i class="fa-solid fa-gears me-1"></i>
{{ lng('admin.phpinfo') }}
</h5>
{% endblock %}
{% block content %}
<div class="card table-responsive">
<table class="table table-borderless table-striped align-middle mb-0 px-3" id="phpinfotable">
<tbody>
{% for name,section in phpinfo %}
{% if name|lower == 'phpinfo' %}
{% set name = 'PHP ' ~ phpversion %}
{% endif %}
<tr>
<th colspan="3">{{ name|raw }}</th>
</tr>
{% for key,val in section %}
{% if key != 'Directive' %}
<tr>
{% if val is iterable %}
<td width="180">{{ key|raw }}</td>
<td colspan="2">{{ val[0]|raw }}<br/><small>(Master:
{{ val[1]|raw }})</small>
</td>
{% elseif key matches '/^\\d+$/' %}
<td colspan="3" align="center">{{ val|raw }}</td>
{% else %}
<td width="180">{{ key|raw }}</td>
<td colspan="2">{{ val|raw }}</td>
{% endif %}
</tr>
{% endif %}
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@@ -0,0 +1,42 @@
<nav id="sidebar" class="sidebar collapse d-md-flex flex-shrink-0 flex-column bg-dark overflow-auto max-h-before-header">
<ul class="nav d-flex flex-fill flex-column py-3">
{% for idx,mitems in nav_entries %}
{% if mitems.items is not empty %}
<li class="nav-item" {% if mitems.active == 1 %}aria-current="page"{% endif %}>
<a class="nav-link text-light {% if mitems.active == 0 %}collapsed{% endif %}" href="#sub{{ idx }}" data-bs-toggle="collapse" data-bs-target="#sub{{ idx }}">
{% if mitems.icon is not empty %}
<i class="{{ mitems.icon }}"></i>
{% endif %}
{{ mitems.label }}
</a>
<div class="collapse {% if mitems.active == 1 %}show{% endif %}" id="sub{{ idx }}" aria-expanded="{% if mitems.active == 1 %}true{% else %}false{% endif %}">
<ul class="flex-column ps-3 nav">
{% for item in mitems.items %}
<li class="nav-item d-flex justify-content-between align-items-center" {% if item.active == 1 %}aria-current="page"{% endif %}>
<div class="me-auto">
<a class="nav-link text-light {% if item.active == 1 %}active fw-bold{% endif %}" href="{{ item.url|raw }}" {% if item.is_external is defined and item.is_external %}target="_blank"{% endif %}>{{ item.label|raw }}</a>
</div>
{% if item.add_shortlink is defined and item.add_shortlink is not empty %}
<a href="{{ item.add_shortlink|raw }}" class="text-secondary me-2"><i class="fa-solid fa-plus-circle"></i></a>
{% endif %}
{% if item.is_external is defined and item.is_external %}
<span class="me-2"><i class="fa-solid fa-arrow-up-right-from-square"></i></span>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</li>
{% else %}
<li class="nav-item" {% if mitems.active == 1 %}aria-current="page"{% endif %}>
<a class="nav-link text-light {% if mitems.active == 1 %}active{% endif %}" href="{% if mitems.url is not empty %}{{ mitems.url|raw }}{% else %}#{% endif %}" {% if mitems.target is not empty %} target="{{ mitems.target }}" {% endif %}>
{% if mitems.icon is not empty %}
<i class="{{ mitems.icon }}"></i>
{% endif %}
{{ mitems.label|upper }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>

View File

View File

@@ -0,0 +1,60 @@
$(function () {
var timer, delay = 500;
$('div[data-action="apikeys"] #allowed_from').on('keyup change', function () {
var _this = $(this);
clearTimeout(timer);
timer = setTimeout(function () {
var akid = _this.closest('div[data-action="apikeys"]').data('entry');
$.ajax({
url: "lib/ajax.php?action=editapikey",
type: "POST",
dataType: "json",
data: { id: akid, allowed_from: _this.val(), valid_until: $('div[data-entry="' + akid + '"] #valid_until').val() },
success: function (data) {
if (data.message) {
_this.removeClass('is-valid');
_this.addClass('is-invalid');
} else {
_this.removeClass('is-invalid');
_this.addClass('is-valid');
_this.val(data.allowed_from);
}
},
error: function (request, status, error) {
_this.removeClass('is-valid');
_this.addClass('is-invalid');
}
});
}, delay);
});
$('div[data-action="apikeys"] #valid_until').on('keyup change', function () {
var _this = $(this);
clearTimeout(timer);
timer = setTimeout(function () {
var akid = _this.closest('div[data-action="apikeys"]').data('entry');
$.ajax({
url: "lib/ajax.php?action=editapikey",
type: "POST",
dataType: "json",
data: { id: akid, valid_until: _this.val(), allowed_from: $('div[data-entry="' + akid + '"] #allowed_from').val() },
success: function (data) {
if (data.message) {
_this.removeClass('is-valid');
_this.addClass('is-invalid');
} else {
_this.removeClass('is-invalid');
_this.addClass('is-valid');
_this.val(data.valid_until);
}
},
error: function (request, status, error) {
_this.removeClass('is-valid');
_this.addClass('is-invalid');
}
});
}, delay);
});
});

View File

@@ -0,0 +1,52 @@
$(function () {
/*
* config files - select all recommended
*/
$('#selectRecommendedConfig').on('click', function () {
$('input[data-recommended]').each(function () {
if ($(this).data('recommended') == 1) {
$(this).prop('checked', true);
} else {
$(this).prop('checked', false);
}
})
});
/*
* export/download JSON file (e.g. for usage with config-services)
*/
$('#downloadSelectionAsJson').on('click', function () {
var formData = $(this).closest('form').serialize();
window.location = "lib/ajax.php?action=getConfigJsonExport&" + formData;
});
/*
* open modal window to show selected config-commands/files
* for selected daemon
*/
$('.show-config').on('click', function () {
var distro = $(this).data('dist');
var section = $(this).data('section');
var daemon = $(this).data('daemon');
$.ajax({
url: "lib/ajax.php?action=getConfigDetails",
type: "POST",
dataType: "json",
data: { distro: distro, section: section, daemon: daemon },
success: function (data) {
$('#configTplShowLabel').html(data.title);
$('#configTplShow .modal-body').html(data.content);
var myModal = new bootstrap.Modal(document.getElementById('configTplShow'));
myModal.show();
},
error: function (request, status, error) {
$('#configTplShowLabel').html('Error');
$('#configTplShow .modal-body').html('<div class="alert alert-danger" role="alert">' + request.responseJSON.message + '</div>');
var myModal = new bootstrap.Modal(document.getElementById('configTplShow'));
myModal.show();
}
});
});
});

View File

@@ -0,0 +1,76 @@
$(function() {
// Make inputs with enabled unlimited checked disabled
$("input[name$='_ul']").each(function () {
var fieldname = $(this).attr("name").substring(0, $(this).attr("name").length - 3);
$("input[name='" + fieldname + "']").prop({
readonly: $(this).is(":checked"),
required: !$(this).is(":checked")
});
});
// change state when unlimited checkboxes are clicked
$("input[name$='_ul']").on('change', function () {
var fieldname = $(this).attr("name").substring(0, $(this).attr("name").length - 3);
$("input[name='" + fieldname + "']").prop({
readonly: $(this).is(":checked"),
required: !$(this).is(":checked")
});
if (!$(this).is(":checked")) {
$("input[name='" + fieldname + "']").focus()
}
});
// set values from hosting plan when adding/editing a customer according to the plan's values
$('#use_plan').on('change', function () {
var pid = $(this).val();
if (pid > 0) {
$.ajax({
url: "admin_plans.php?page=overview&action=jqGetPlanValues",
type: "POST",
data: {
planid: pid
},
dataType: "json",
success: function (json) {
for (var i in json) {
if (i == 'email_imap' || i == 'email_pop3' || i == 'perlenabled' || i == 'phpenabled' || i == 'dnsenabled' || i == 'logviewenabled') {
/** handle checkboxes **/
if (json[i] == 1) {
$("input[name='" + i + "']").prop('checked', true);
} else {
$("input[name='" + i + "']").prop('checked', false);
}
} else if (i == 'allowed_phpconfigs') {
/** handle array of values **/
$("input[name='allowed_phpconfigs[]']").each(function (index) {
$(this).prop('checked', false);
for (var j in json[i]) {
if ($(this).val() == json[i][j]) {
$(this).prop('checked', true);
break;
}
}
});
} else if (json[i] == -1) {
/** handle unlimited checkboxes **/
$("input[name='" + i + "_ul']").attr('checked', 'checked');
$("input[name='" + i + "']").prop({
readonly: true
});
} else {
/** handle normal value **/
$("input[name='" + i + "']").val(json[i]);
$("input[name='" + i + "']").prop({
readonly: false
});
$("input[name='" + i + "_ul']").prop('checked', false);
}
}
},
error: function (a, b) {
console.log(a, b);
}
});
}
});
});

View File

@@ -0,0 +1,19 @@
$(function () {
// Display helptext to content box according to dns-record type selected
$("select[name='dns_type']").on('change', function () {
var selVal = $(this).val();
$.ajax({
url: "lib/ajax.php?action=loadLanguageString",
type: "POST",
dataType: "json",
data: { langid: 'dnseditor.notes.' + selVal },
success: function (data) {
$("#dns_content").next().html(data);
},
error: function (request, status, error) {
console.log(request, status, error)
}
});
});
});

View File

@@ -0,0 +1,87 @@
$(function() {
// disable unusable php-configuration by customer settings
$('#customerid').on('change', function () {
var cid = $(this).val();
$.ajax({
url: "admin_domains.php?page=domains&action=jqGetCustomerPHPConfigs",
type: "POST",
data: {
customerid: cid
},
dataType: "json",
success: function (json) {
if (json.length > 0) {
$('#phpsettingid option').each(function () {
var pid = $(this).val();
$(this).attr("disabled", "disabled");
for (var i in json) {
if (pid == json[i]) {
$(this).removeAttr("disabled");
}
}
});
}
},
error: function (a, b) {
console.log(a, b);
}
});
});
// show warning if speciallogfile option is toggled
if ($('input[name=speciallogverified]')) {
$('input[name=speciallogfile]').on('click', function () {
$('#speciallogfilenote').remove();
$('#speciallogfile').removeClass('is-invalid');
$('#speciallogverified').val(0);
$.ajax({
url: "admin_domains.php?page=overview&action=jqSpeciallogfileNote",
type: "POST",
data: {
id: $('input[name=id]').val(), newval: +$('#speciallogfile').is(':checked')
},
dataType: "json",
success: function (json) {
if (json.changed) {
$('#speciallogfile').addClass('is-invalid');
$('#speciallogfile').parent().append(json.info);
$('#speciallogverified').val(1);
}
},
error: function (a, b) {
console.log(a, b);
}
});
});
}
/**
* email only domain - hide unnecessary/unused sections
*/
if ($('#id') && $('#email_only').is(':checked')) {
$('#section_b').hide();
$('#section_bssl').hide();
$('#section_c').hide();
$('#section_d').hide();
}
/**
* toggle show/hide of sections in case of email only flag
*/
$('#email_only').on('click', function () {
if ($(this).is(':checked')) {
// hide unnecessary sections
$('#section_b').hide();
$('#section_bssl').hide();
$('#section_c').hide();
$('#section_d').hide();
} else {
// show sections
$('#section_b').show();
$('#section_bssl').show();
$('#section_c').show();
$('#section_d').show();
}
})
});

View File

@@ -0,0 +1,12 @@
$(function () {
$('#historyback').on('click', function (e) {
e.preventDefault();
history.back(1);
})
$('#copySysInfo').on('click', function (e) {
e.preventDefault();
navigator.clipboard.writeText($('#ccSysInfo').text().trim());
})
});

Some files were not shown because too many files have changed in this diff Show More