Content Security Policy (CSP) compatibility beta.

This commit is contained in:
2026-06-20 00:21:19 +02:00
parent 0b76255cfa
commit 8b742d997c
68 changed files with 1632 additions and 1421 deletions

View File

@@ -33,12 +33,41 @@ class API_office_stompjes extends API
$this->apiOutput(200, ['success' => 'Stomp removed.']);
}
public function getStomp()
public function getStomp($frontend = false)
{
list($query, $types, $params) = $this->buildDynamicQuery('office_stompjes');
if (!$frontend) {
list($query, $types, $params) = $this->buildDynamicQuery('office_stompjes');
$items = $this->generalGetFunction($query, $types, $params, false, 'Stompjes');
$items = $this->generalGetFunction($query, $types, $params, false, 'Stompjes');
return ($items);
return ($items);
} else {
if (!isset($_GET['fd'])) {
$SelectFromDate = strtotime(date('Y-m-01'));
} else {
$date = str_replace('/', '-', htmlspecialchars($_GET['fd'], ENT_QUOTES, 'UTF-8'));
$SelectFromDate = strtotime($date . ' 00:00:00');
}
if (!isset($_GET['td'])) {
$SelectTillDate = time();
} else {
$date = str_replace('/', '-', htmlspecialchars($_GET['td'], ENT_QUOTES, 'UTF-8'));
$SelectTillDate = strtotime($date . ' 23:59:59');
}
$stompjes = [];
$stmt = $this->conn->query("SELECT stomp_uuid, office_stompjes.user_uuid, user_full_name, user_first_name, stomp_timestamp
FROM office_stompjes
INNER JOIN system_users ON office_stompjes.user_uuid = system_users.user_uuid
WHERE stomp_timestamp BETWEEN '$SelectFromDate' AND '$SelectTillDate'
AND user_stompable = '1'
ORDER BY stomp_timestamp DESC");
while ($row = $stmt->fetch_assoc()) {
$stompjes[] = $row;
}
return $stompjes;
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
use api\classes\API;
session_start();
require_once "{$_SERVER['DOCUMENT_ROOT']}/api/classes/API.php";
$API = new API();
if ($API->request_method === 'GET') {
$breadcrumbJson = '[]';
if (isset($_SESSION['breadCrumbArray'])) {
try {
$breadcrumbJson = json_encode($_SESSION['breadCrumbArray'] ?? [], JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
error_log($e->getMessage());
$breadcrumbJson = '[]';
}
$_SESSION['breadCrumbArray'] = [];
}
echo $breadcrumbJson;
}

View File

@@ -0,0 +1,21 @@
<?php
use api\classes\API;
session_start();
require_once "{$_SERVER['DOCUMENT_ROOT']}/api/classes/API.php";
$API = new API();
if ($API->request_method === 'GET') {
# this is used by js scripts to get the locale of the user
$locale = getPreferredLocale();
$translations = require $_SERVER['DOCUMENT_ROOT'] . "/bin/locales/{$locale}.php";
header("Cache-Control: public, max-age=3600");
header('Content-Type: application/json; charset=utf-8');
echo json_encode($translations, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

View File

@@ -0,0 +1,18 @@
<?php
use api\classes\API;
session_start();
require_once "{$_SERVER['DOCUMENT_ROOT']}/api/classes/API.php";
$API = new API();
if ($API->request_method === 'GET') {
ini_set('display_errors', 1);
if (isset($_SESSION['response'])) {
echo $_SESSION['response'];
unset($_SESSION['response']);
} else {
echo '[]';
}
}

View File

@@ -15,7 +15,12 @@ if (!$GLOBALS['modules_enabled']['office']) {
if ($API_office_stompjes->request_method === 'GET') {
$API_office_stompjes->checkPermissions('office-stompjes', 'RO');
$stompjes = $API_office_stompjes->getStomp();
if (isset($_GET['frontend'])) {
# This one is used by the frontend and is filtered by date.
$stompjes = $API_office_stompjes->getStomp(true);
} else {
$stompjes = $API_office_stompjes->getStomp();
}
$API_office_stompjes->apiOutput($code = 200, ['success' => $stompjes]);

View File

@@ -58,7 +58,7 @@ if ($_GET['add'] == 'phone' || $_GET['add'] == 'base') {
$formInputs[] = 'device_extra';
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_device'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('add_device'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -56,8 +56,7 @@ $stmt->bind_param("s", $_GET['edit']);
$stmt->execute();
$device_data_result = $stmt->get_result();
$device_data = $device_data_result->fetch_assoc();
array_push($GLOBALS['breadCrumbArray'], array('display' => $device_data['vendor_name'] . ' ' . $device_data['device_name'], 'href' => '?view=' . $device_data['device_uuid']));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit'), 'href' => '?view=' . $device_data['device_uuid']));
if ($device_data_result->num_rows > 0) {
$device_found = true;
}
@@ -70,7 +69,9 @@ if ($device_data['device_type'] == 'phone' || $device_data['device_type'] == 'ba
$formInputs[] = 'device_extra';
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('devices'), 'href' => '/devices/'));
$_SESSION['breadCrumbArray'][] = ['display' => $device_data['vendor_name'] . ' ' . $device_data['device_name'], 'href' => '?view=' . $device_data['device_uuid']];
$_SESSION['breadCrumbArray'][] = ['display' => __('edit'), 'href' => '?view=' . $device_data['device_uuid']];
$_SESSION['breadCrumbArray'][] = ['display' => __('devices'), 'href' => '/devices/'];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -48,7 +48,7 @@ if ($API->checkPermissions('admin-devices', 'RW', true)) {
$data = $GLOBALS['conn']->query("SELECT * FROM autop_devices INNER JOIN autop_vendors ON autop_devices.device_vendor_uuid = autop_vendors.vendor_uuid");
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('devices'), 'href' => '/devices/'));
$_SESSION['breadCrumbArray'][] = ['display' => __('devices'), 'href' => '/devices/'];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -34,8 +34,8 @@ $formBuilder->submitButtonIcon = '<i class="fas fa-save"></i>';
# Retrieve Information for the page
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('platforms'), 'href' => '/platforms/'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_platform'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('platforms'), 'href' => '/platforms/'];
$_SESSION['breadCrumbArray'][] = ['display' => __('add_platform'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -47,8 +47,8 @@ $API_platforms->validateData($requiredFields);
$platform_data = $API_platforms->getPlatforms()[0];
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('platforms'), 'href' => '/platforms/'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_platform'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('platforms'), 'href' => '/platforms/'];
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_platform'), 'href' => ''];
# Start page output
if ($platform_data) {

View File

@@ -34,7 +34,7 @@ $pageNavbar->AddHTMLButton('<a href="?add="><button class="btn btn-primary"><i c
$formBuilder = new formBuilder('add_vendor', '<i class="fas fa-plus"></i>', '/vendors/');
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_vendor'), 'href' => '/vendors/'));
$_SESSION['breadCrumbArray'][] = ['display' => __('add_vendor'), 'href' => '/vendors/'];
# Retrieve Information for the page

View File

@@ -47,7 +47,7 @@ $API_vendors->validateData($requiredFields);
$vendor_data = $API_vendors->getVendors()[0];
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_vendor'), 'href' => '/vendors/'));
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_vendor'), 'href' => '/vendors/'];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -49,8 +49,8 @@ $result = $stmt->get_result();
$company_data = $result->fetch_assoc();
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('companies'), 'href' => '/companies/'));
array_push($GLOBALS['breadCrumbArray'], array('display' => $company_data['company_name'], 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('companies'), 'href' => '/companies/'];
$_SESSION['breadCrumbArray'][] = ['display' => $company_data['company_name'], 'href' => ''];
# Retrieve Information for the page
$company_uuid = htmlspecialchars($_GET['view'], ENT_QUOTES, 'UTF-8');

View File

@@ -57,23 +57,14 @@ if (!isset($_GET['td'])) {
$SelectTillDate = strtotime($date . ' 23:59:59');
}
$stompjes = array();
$stmt = $GLOBALS['conn']->query("SELECT stomp_uuid, office_stompjes.user_uuid, user_full_name, user_first_name, stomp_timestamp FROM office_stompjes
INNER JOIN system_users ON office_stompjes.user_uuid = system_users.user_uuid
WHERE stomp_timestamp BETWEEN '$SelectFromDate' AND '$SelectTillDate'
AND user_stompable = '1'
ORDER BY stomp_timestamp DESC");
while ($row = $stmt->fetch_assoc()) {
array_push($stompjes, $row);
$administrators[$row['user_uuid']]['amount']++;
$stompjes = $API->getStomp(true);
foreach ($stompjes as $stompje) {
$administrators[$stompje['user_uuid']]['amount']++;
}
# Start page output
?>
<script>
const stompData = <?php echo json_encode($stompjes); ?>;
</script>
<div class="form-group form-show-validation row mb-3">
<div class="col-5">
<h2>

View File

@@ -43,8 +43,8 @@ if ($result->num_rows == 1) {
}
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_profile'), 'href' => '/userprofile/'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('user_profile'), 'href' => '/userprofile/'];
$_SESSION['breadCrumbArray'][] = ['display' => __('edit'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -46,8 +46,8 @@ while ($user_group = $user_groups_data->fetch_assoc()) {
}
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('administrators'), 'href' => '/accesscontrol/#administrators'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_admin'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('administrators'), 'href' => '/accesscontrol/#administrators'];
$_SESSION['breadCrumbArray'][] = ['display' => __('add_admin'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -52,8 +52,8 @@ if ($result->num_rows == 1) {
}
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('administrators'), 'href' => '/accesscontrol/#administrators'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_admin'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('administrators'), 'href' => '/accesscontrol/#administrators'];
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_admin'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -71,9 +71,8 @@ $API_token->validateData($requiredFields);
$apitokens = $API_token->getTokens();
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('administrators'), 'href' => '/accesscontrol/#administrators'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('view'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('administrators'), 'href' => '/accesscontrol/#administrators'];
$_SESSION['breadCrumbArray'][] = ['display' => __('view'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();
@@ -223,18 +222,18 @@ if ($admin_data) { ?>
</div>
<div class="card-body">
<?php if ($API->checkPermissions('admin-access-admins-mfa', 'RW', true)) { ?>
<div id="mfa-enabled-row" class="row" style="display: <?php echo(($admin_data['user_two_factor_enabled']) ? '' : 'none') ?>">
<div id="mfa-enabled-row" class="row <?= ($admin_data['user_two_factor_enabled'] ? '' : 'd-none') ?>">
<div class="col-auto">
<a href="#" class="btn btn-danger delete-btn" data-item-uuid="<?php echo $admin_data['user_uuid'] ?>" data-api-url="/api/v1/user/mfa/" data-item-name='user_uuid' data-delete-action='{"mfa-enabled-row":"hide", "mfa-disabled-row":"show"}'>
<i class="fa-solid fa-lock"></i> <?php echo __('reset_mfa') ?>
<a href="#" class="btn btn-danger delete-btn" data-item-uuid="<?= $admin_data['user_uuid'] ?>" data-api-url="/api/v1/user/mfa/" data-item-name="user_uuid" data-delete-action='{"mfa-enabled-row":"hide", "mfa-disabled-row":"show"}'>
<i class="fa-solid fa-lock"></i> <?= __('reset_mfa') ?>
</a>
</div>
</div>
<div id="mfa-disabled-row" class="row" style="display: <?php echo(($admin_data['user_two_factor_enabled'] == 1) ? 'none' : '') ?>">
<div id="mfa-disabled-row" class="row <?= ($admin_data['user_two_factor_enabled'] ? 'd-none' : '') ?>">
<div class="col-auto">
<button class="btn btn-danger delete-btn" href="#" disabled>
<i class="fa-solid fa-lock"></i> <?php echo __('reset_mfa') ?>
<button class="btn btn-danger delete-btn" disabled>
<i class="fa-solid fa-lock"></i> <?= __('reset_mfa') ?>
</button>
</div>
</div>
@@ -288,9 +287,9 @@ if ($admin_data) { ?>
<tbody>
<?php foreach ($apitokens as $token_data) { ?>
<tr>
<td class="text-nowrap" style="max-width: 100%;">
<div class="d-flex align-items-center gap-2" style="max-width: 100%;">
<div class="text-truncate" style="max-width: 200px;" id="<?php echo $token_data['api_token_uuid'] ?>" data-copy-data="<?php echo $token_data['api_token_uuid']; ?>">
<td class="text-nowrap">
<div class="d-flex align-items-center gap-2">
<div class="text-truncate" id="<?php echo $token_data['api_token_uuid'] ?>" data-copy-data="<?php echo $token_data['api_token_uuid']; ?>">
<?php echo $token_data['api_token_uuid']; ?>
</div>
<button type="button" class="btn btn-sm btn-outline-secondary" data-copy-target="<?php echo $token_data['api_token_uuid'] ?>" title="Copy ID">

View File

@@ -41,8 +41,8 @@ while ($module = $system_modules_data->fetch_assoc()) {
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_permission'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
$_SESSION['breadCrumbArray'][] = ['display' => __('add_permission'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -51,8 +51,8 @@ while ($module = $system_modules_data->fetch_assoc()) {
}
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_permission'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_permission'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -52,8 +52,8 @@ $API_permissions->validateData($requiredFields);
$permission_data = $API_permissions->getPermissionRights();
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('view'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
$_SESSION['breadCrumbArray'][] = ['display' => __('view'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -41,8 +41,8 @@ while ($user_group = $user_groups_data->fetch_assoc()) {
}
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_user_groups'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'];
$_SESSION['breadCrumbArray'][] = ['display' => __('add_user_groups'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -44,9 +44,9 @@ $API_usergroups->validateData($requiredFields);
$user_group = $API_usergroups->getUsergroup()[0];
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'));
array_push($GLOBALS['breadCrumbArray'], array('display' => $user_group['user_group_name'], 'href' => '/accesscontrol/#user-groups'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'];
$_SESSION['breadCrumbArray'][] = ['display' => $user_group['user_group_name'], 'href' => '/accesscontrol/#user-groups'];
$_SESSION['breadCrumbArray'][] = ['display' => __('edit'), 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -56,8 +56,8 @@ if (!$API->checkPermissions('admin-access-control-permissions', 'RW', true)) {
}
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'));
array_push($GLOBALS['breadCrumbArray'], array('display' => $user_group['user_group_name'], 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'];
$_SESSION['breadCrumbArray'][] = ['display' => $user_group['user_group_name'], 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -6,7 +6,7 @@ if (!defined('APP_INIT')) {
$jsScriptLoadData['activeTabOnRefresh'] = true;
?>
<div class="card-body activeTabOnRefresh" style="opacity: 0; transition: opacity 10ms;">
<div class="card-body activeTabOnRefresh">
<div class="row">
<div class="col-md-1">
<div class="nav flex-column nav-pills nav-secondary nav-pills-no-bd nav-pills-icons" id="v-pills-tab-with-icon" role="tablist" aria-orientation="vertical">

View File

@@ -128,7 +128,7 @@ while ($module = $system_modules_data->fetch_assoc()) {
<div class="col-lg-9 col-md-12 col-sm-10">
<div class="input-group">
<input type="text" class="form-control" id="cacert_url" name="cacert_url" value="<?php echo $portal_settings['cacert_url'] ?>" placeholder="" disabled/>
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('cacert_url')">
<button class="btn btn-outline-secondary" type="button" data-copy-target="cacert_url">
<i class="fas fa-copy"></i>
</button>
</div>
@@ -141,7 +141,7 @@ while ($module = $system_modules_data->fetch_assoc()) {
<div class="col-lg-9 col-md-12 col-sm-10">
<div class="input-group">
<input type="text" class="form-control" id="autop_url" name="autop_url" value="<?php echo $portal_settings['autop_url'] ?>" placeholder="" disabled/>
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('autop_url')">
<button class="btn btn-outline-secondary" type="button" data-copy-target="autop_url">
<i class="fas fa-copy"></i>
</button>
</div>

View File

@@ -35,10 +35,9 @@ $inserve_settings = $GLOBALS['conn']->query("SELECT * FROM system_sources WHERE
$inserve_custom_data = ($inserve_settings['source_custom_data'] ? json_encode(json_decode($inserve_settings['source_custom_data']), JSON_PRETTY_PRINT) : "{}");
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('portal_management'), 'href' => '/systemconfig'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('sources'), 'href' => '/systemconfig#sources'));
array_push($GLOBALS['breadCrumbArray'], array('display' => 'Inserve', 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('portal_management'), 'href' => '/systemconfig'];
$_SESSION['breadCrumbArray'][] = ['display' => __('sources'), 'href' => '/systemconfig#sources'];
$_SESSION['breadCrumbArray'][] = ['display' => 'Inserve', 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -30,6 +30,7 @@ if (!$API->checkPermissions('servers', 'RO', true)) {
$jsScriptLoadData['delete_confirmation'] = true;
$jsScriptLoadData['breadCrumbs'] = true;
$jsScriptLoadData['codeblocks'] = true;
$jsScriptLoadData['serverView'] = true;
# PageClasses Setup
$pageNavbar = new pageNavbar(true);
@@ -129,8 +130,8 @@ if ($API->checkPermissions('servers', 'RW', true)) {
};
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('server_overview'), 'href' => '/servers/'));
array_push($GLOBALS['breadCrumbArray'], array('display' => $server_data['server_vm_host_name'], 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('server_overview'), 'href' => '/servers/'];
$_SESSION['breadCrumbArray'][] = ['display' => $server_data['server_vm_host_name'], 'href' => ''];
# Start page output
$pageNavbar->outPutNavbar();

View File

@@ -59,8 +59,8 @@ if (!$GLOBALS['modules_enabled']['customers']) {
}
# Set breadcrumb data
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
array_push($GLOBALS['breadCrumbArray'], array('display' => __('view'), 'href' => ''));
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
$_SESSION['breadCrumbArray'][] = ['display' => __('view'), 'href' => ''];
# Start page output
$serverOverview->serverOverviewOutPut();

View File

@@ -47,7 +47,7 @@ class pageBuilder extends API
$requestUri = rtrim($requestUri, '/');
}
$GLOBALS['breadCrumbArray'] = array(array('display' => '<i class="fas fa-home"></i>', 'href' => '/'));
$_SESSION['breadCrumbArray'] = array(array('display' => '<i class="fas fa-home"></i>', 'href' => '/'));
$GLOBALS['pageContentToShow']['pageName'] = '404';
$GLOBALS['pageContentToShow']['pageFile'] = 'pageNotFound.php';
@@ -109,49 +109,11 @@ class pageBuilder extends API
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link href="https://fonts.googleapis.com/css?family=Public+Sans:300,400,500,600,700|Quicksand:300,400,500,600,700&display=swap" rel="stylesheet">
<style>
.dataTables_filter label {
width: 100%;
height: 40px;
display: flex;
}
.dataTables_filter input {
height: 40px;
width: 100% !important; /* Make the input field full width */
box-sizing: border-box; /* Ensure padding doesn't exceed the width */
}
.dataTables_filter .fa-search {
position: absolute;
top: 12px;
left: auto;
right: 10px;
}
.dataTables_length label {
display: flex;
align-items: center;
height: 40px;
}
.dataTables_length label::before {
display: inline-block;
height: 40px;
padding-top: 10px;
}
.dataTables_length select {
flex: 1;
max-width: 100%;
margin-left: 10px;
}
</style>
<!-- CSS Files -->
<link rel="stylesheet" href="/src/css/bootstrap.gruvbox.min.css"/> <!-- Basic bootstrap and theming -->
<link rel="stylesheet" href="/src/css/sentri.min.css"/> <!-- Special css tricks for Sentri -->
<link rel="stylesheet" href="/src/css/plugins.min.css"/> <!-- need this for scroll bars and other small things -->
<link rel="stylesheet" href="/src/js/plugin/sweetalert2/sweetalert2.min.css"/> <!-- need this for scroll bars and other small things -->
<!--<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css"/> -->
@@ -162,22 +124,6 @@ class pageBuilder extends API
<link rel="shortcut icon" href="/src/images/favicon/favicon.ico"/>
<link rel="apple-touch-icon" sizes="180x180" href="/src/images/favicon/apple-touch-icon.png"/>
<link rel="manifest" href="/src/images/favicon/site.webmanifest"/>
<style>
.fade-in {
opacity: 0;
transition: opacity 0.3s ease, transform 0.3s ease;
}
.fade-in.show {
opacity: 1;
transform: scale(1);
}
.transition-opacity {
transition: opacity 0.3s ease;
}
</style>
</head>
<?php }
@@ -383,6 +329,9 @@ class pageBuilder extends API
<script src="/src/js/core/popper.min.js"></script>
<script src="/src/js/core/bootstrap.min.js"></script>
<!-- Load the locales in js -->
<script src="/src/js/i18n-loader.js"></script>
<!-- Bootstrap Notify -->
<script src="/src/js/plugin/bootstrap-notify/bootstrap-notify.min.js"></script>
@@ -418,37 +367,11 @@ class pageBuilder extends API
setTimeZoneCookie();
if (isset($this->jsScriptLoadData)) {
include_once $_SERVER['DOCUMENT_ROOT'] . '/bin/php/jsScripts.php';
require_once "{$_SERVER['DOCUMENT_ROOT']}/bin/php/jsScripts.php";
}
# process response from actions
if (isset($_SESSION['response'])) {
$ar = json_decode($_SESSION['response']);
$type = key($ar);
$text = reset($ar);
$title = $type;
if ($type == 'error') {
$type = 'danger';
}
unset($_SESSION['response']) ?>
<script>
var content = {};
content.message = '<?php echo __($text) ?>';
content.title = '<?php echo ucfirst($title) ?>';
content.icon = "fa fa-bell";
$.notify(content, {
type: '<?php echo $type ?>',
placement: {
from: 'top',
align: 'right',
},
time: 10,
delay: 3,
});
$('#multiple').select2({
theme: "bootstrap"
});
</script>
if (isset($_SESSION['response'])) { ?>
<script src="/src/js/sentri/response.js"></script>
<?php }
}
}

View File

@@ -27,7 +27,7 @@ class pageNavbar
<div class="row">
<div class="col d-flex justify-content-start">
<?php if ($this->breadCrumb) { ?>
<div class="pb-2" id="breadCrumb"></div>
<div class="pb-2" id="breadCrumb">&nbsp;</div>
<?php } elseif ($this->title) { ?>
<h2 class="px-4 mb-0"><?php echo $this->title ?></h2>
<?php } else { ?>

File diff suppressed because it is too large Load Diff

View File

@@ -54,9 +54,6 @@ $_SESSION['mfasetup']['secret'] = $secret;
<link rel="apple-touch-icon" sizes="180x180" href="/src/images/favicon/apple-touch-icon.png">
<link rel="manifest" href="/src/images/favicon/site.webmanifest">
<style>
</style>
<link rel="stylesheet" href="css/style.full.css" as="style">
</head>
<body>

View File

@@ -24,13 +24,9 @@ function addLoginAttempts()
}
function setTimeZoneCookie()
{
?>
<script>
document.cookie = "user_timezone=" + Intl.DateTimeFormat().resolvedOptions().timeZone + "; path=/";
</script>
<?php
}
{ ?>
<script src="/src/js/timeZoneCookie.js"></script>
<?php }
function getUserTimezone()
{

View File

@@ -143,7 +143,7 @@ if (checkLoginAttempts() > 20) {
<div class="card col-md-5 text-white bg-transparent py-5">
<div class="card-body text-center pt-5">
<div>
<img class="pt-3 w-100 portal-image" alt="portal-image" src="/src/images/logo-login-dark.webp" width="500" height="265" style="height: auto;">
<img class="pt-3 w-100 portal-image" alt="portal-image" src="" width="500" height="265" style="height: auto;">
</div>
</div>
</div>

View File

@@ -9313,4 +9313,60 @@ body .jvm-zoom-btn {
.wrapper.not-found .desc {
font-size: 18px !important;
}
}
.dataTables_filter label {
width: 100%;
height: 40px;
display: flex;
}
.dataTables_filter input {
height: 40px;
width: 100% !important; /* Make the input field full width */
box-sizing: border-box; /* Ensure padding doesn't exceed the width */
}
.dataTables_filter .fa-search {
position: absolute;
top: 12px;
left: auto;
right: 10px;
}
.dataTables_length label {
display: flex;
align-items: center;
height: 40px;
}
.dataTables_length label::before {
display: inline-block;
height: 40px;
padding-top: 10px;
}
.dataTables_length select {
flex: 1;
max-width: 100%;
margin-left: 10px;
}
.fade-in {
opacity: 0;
transition: opacity 0.3s ease, transform 0.3s ease;
}
.fade-in.show {
opacity: 1;
transform: scale(1);
}
.transition-opacity {
transition: opacity 0.3s ease;
}
.activeTabOnRefresh {
opacity: 0;
transition: opacity 10ms;
}

File diff suppressed because one or more lines are too long

53
pub/src/js/i18n-loader.js Normal file
View File

@@ -0,0 +1,53 @@
window.__i18n = {};
// Cache translations for 1 hour
const I18N_CACHE_KEY = 'i18n';
const I18N_CACHE_TIME = 60 * 60 * 1000; // 1 hour
async function loadTranslations() {
try {
const cached = JSON.parse(localStorage.getItem(I18N_CACHE_KEY) || '{}');
// Use cached translations if they are still valid
if (
cached.timestamp &&
cached.data &&
(Date.now() - cached.timestamp) < I18N_CACHE_TIME
) {
window.__i18n = cached.data;
return;
}
// Otherwise fetch fresh translations
const response = await fetch('/api/v1/frontend/locale/');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
window.__i18n = data;
// Save to localStorage with timestamp
localStorage.setItem(I18N_CACHE_KEY, JSON.stringify({
timestamp: Date.now(),
data: data
}));
} catch (err) {
console.error('Failed to load translations:', err);
// If the fetch failed, try to use any cached translations,
// even if they are older than 24 hours.
const cached = JSON.parse(localStorage.getItem(I18N_CACHE_KEY) || '{}');
if (cached.data) {
window.__i18n = cached.data;
}
}
}
window.i18nReady = loadTranslations();
function __(key) {
return window.__i18n[key] ?? key;
}

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

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,34 @@
window.i18nReady.then(() => {
document.querySelectorAll('[data-copy-target]').forEach(button => {
button.addEventListener('click', function () {
const targetId = this.getAttribute('data-copy-target');
const target = document.getElementById(targetId);
if (target) {
const copyData = target.getAttribute('data-copy-data');
if (copyData) {
navigator.clipboard.writeText(copyData).then(() => {
Swal.fire({
background: background,
color: color,
title: __('copied'),
text: __("copy_text_success"),
icon: 'success',
confirmButtonText: __("ok")
});
}).catch(err => {
console.error('Clipboard error:', err);
Swal.fire({
background: background,
color: color,
title: 'Error!',
text: 'Failed to copy the content.',
icon: 'error',
confirmButtonText: 'OK'
});
});
}
}
});
});
});

View File

@@ -0,0 +1,9 @@
function generateRandomPassword(length) {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_-+=<>?";
let password = "";
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charset.length);
password += charset.charAt(randomIndex);
}
return password;
}

View File

@@ -0,0 +1,53 @@
$(document).on('click', '.btn[data-item-company-state]', function (e) {
e.preventDefault();
const btn = $(this);
const companyUuid = btn.data('item-uuid');
const currentState = btn.data('item-state'); // "active" or "imported"
const newState = (currentState === "active") ? "imported" : "active";
const url = "/api/v1/customers/companies/activate/";
const params = {
company_uuid: companyUuid,
company_state: newState,
"X-HTTP-Method-Override": "PUT"
};
const rootStyles = getComputedStyle(document.documentElement);
const background = rootStyles.getPropertyValue('--swal-bg')?.trim();
const color = rootStyles.getPropertyValue('--swal-text-color')?.trim();
$.post(url, params)
.done(function (response, status, jqXHR) {
if (jqXHR.status === 200) {
btn.data('item-state', newState);
btn.removeClass('btn-success btn-danger');
btn.addClass(newState === "active" ? "btn-danger" : "btn-success");
btn.find('i')
.removeClass('fa-plus fa-xmark')
.addClass(newState === "active" ? "fa-xmark" : "fa-plus");
const row = btn.closest("tr");
const stateCell = row.find("td").eq(3);
stateCell.text(newState);
}
})
.fail(function (jqXHR) {
let response = jqXHR.responseJSON || {};
Swal.fire({
background: background,
color: color,
icon: "error",
title: __('connection_error_title'),
text: response.message || __('connection_error_text') + jqXHR.status,
confirmButtonText: __('close'),
customClass: {confirmButton: "btn btn-danger"},
buttonsStyling: false
});
});
});

View File

@@ -0,0 +1,28 @@
document.addEventListener("DOMContentLoaded", function () {
const cardBody = document.querySelector('.card-body.activeTabOnRefresh');
const hash = window.location.hash;
let selectedTab = hash ? cardBody.querySelector(`[href="${hash}"]`) : null;
let selectedContent = hash ? cardBody.querySelector(hash) : null;
if (!selectedTab || !selectedContent) {
selectedTab = cardBody.querySelector(".nav-link");
selectedContent = cardBody.querySelector(".tab-pane");
}
if (selectedTab && selectedContent) {
cardBody.querySelectorAll('.nav-link').forEach(tab => tab.classList.remove('active'));
cardBody.querySelectorAll('.tab-pane').forEach(content => content.classList.remove('show', 'active'));
selectedTab.classList.add('active');
selectedContent.classList.add('show', 'active');
}
cardBody.querySelectorAll('.nav-link').forEach(tab => {
tab.addEventListener("click", function () {
let targetTab = this.getAttribute("href");
history.replaceState(null, null, targetTab);
});
});
cardBody.style.opacity = "1";
});

View File

@@ -0,0 +1,65 @@
document.addEventListener("DOMContentLoaded", async function () {
let breadcrumbArray = [];
try {
const response = await fetch("/api/v1/frontend/breadcrumbs/");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
breadcrumbArray = await response.json();
} catch (err) {
console.error("Failed to load breadcrumbs:", err);
return;
}
// Get the breadcrumb container
const breadcrumbContainer = document.getElementById("breadCrumb");
if (!breadcrumbContainer) {
return;
}
// Create the ul element
const ul = document.createElement("ul");
ul.classList.add("breadcrumbs", "mb-3");
// Loop through the array to generate breadcrumb items
breadcrumbArray.forEach((item, index) => {
// Create the li for each item
const li = document.createElement("li");
li.classList.add("nav-item");
// Create the anchor tag
const a = document.createElement("a");
if (index === breadcrumbArray.length - 1) {
// Active page
a.classList.add("active");
} else {
a.classList.add("text-info");
a.href = item.href;
}
// Includes icons
a.innerHTML = item.display;
li.appendChild(a);
ul.appendChild(li);
// Add separator
if (index < breadcrumbArray.length - 1) {
const separator = document.createElement("li");
separator.classList.add("separator");
const arrowIcon = document.createElement("i");
arrowIcon.classList.add("fa-solid", "fa-chevron-right");
separator.appendChild(arrowIcon);
ul.appendChild(separator);
}
});
breadcrumbContainer.appendChild(ul);
});

View File

@@ -0,0 +1,43 @@
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll("[data-copy-target]").forEach(button => {
button.addEventListener("click", function () {
const inputId = this.getAttribute("data-copy-target");
copyToClipboard(inputId);
});
});
});
function copyToClipboard(inputId) {
const input = document.getElementById(inputId);
const rootStyles = getComputedStyle(document.documentElement);
const background = rootStyles.getPropertyValue('--swal-bg')?.trim();
const color = rootStyles.getPropertyValue('--swal-text-color')?.trim();
if (!input) return;
navigator.clipboard.writeText(input.value)
.then(() => {
Swal.fire({
background: background,
color: color,
title: __("copied"),
text: __("copy_text_success"),
icon: "success",
confirmButtonText: __("ok")
});
})
.catch(err => {
console.error("Clipboard error:", err);
Swal.fire({
background: background,
color: color,
title: "Error!",
text: "Failed to copy the content.",
icon: "error",
confirmButtonText: "OK"
});
});
}

View File

@@ -0,0 +1,8 @@
document.addEventListener("DOMContentLoaded", function () {
// Initialize datepicker for all elements with data-datepicker
document.querySelectorAll("[data-datepicker]").forEach(input => {
$(input).datetimepicker({
format: "DD/MM/YYYY"
});
});
});

View File

@@ -0,0 +1,153 @@
const rootStyles = getComputedStyle(document.documentElement);
const background = rootStyles.getPropertyValue('--swal-bg')?.trim();
const color = rootStyles.getPropertyValue('--swal-text-color')?.trim();
function showDeleteConfirmation({
itemUuid,
itemName,
apiUrl,
row,
deleteAction,
title = "Are you sure?",
text = "You won't be able to revert this!",
icon = "question",
confirmButtonText = "Yes, delete",
cancelButtonText = "Cancel!",
background = getComputedStyle(document.documentElement).getPropertyValue('--swal-bg')?.trim(),
color = getComputedStyle(document.documentElement).getPropertyValue('--swal-text-color')?.trim(),
customClass = {
confirmButton: "btn btn-danger mx-2",
cancelButton: "btn btn-secondary"
},
responseSuccessTitle = '',
responseSuccessText = '',
responseSuccessBtnText = '',
responseErrorTitle = '',
responseErrorText = '',
cancelConfirmTitle = '',
cancelConfirmText = '',
cancelConfirmBtnText = ''
}) {
Swal.fire({
title: title,
text: text,
icon: icon,
background: background,
color: color,
showCancelButton: true,
confirmButtonText: confirmButtonText,
cancelButtonText: cancelButtonText,
customClass: customClass,
buttonsStyling: false,
}).then((result) => {
if (result.isConfirmed) {
// Make the POST request to delete the item
$.post(apiUrl, {
[itemName]: itemUuid,
"X-HTTP-Method-Override": "DELETE"
})
.done(function (response) {
if (!(typeof deleteAction === "string" && deleteAction.startsWith("url:"))) { // do not fire the swal if there will be a redirect
Swal.fire({
background: background,
color: color,
title: responseSuccessTitle || "Deleted!",
text: response.message || responseSuccessText || "Your item has been deleted.",
confirmButtonText: responseSuccessBtnText,
icon: "success",
customClass: {
confirmButton: "btn btn-success",
},
buttonsStyling: true,
});
}
if (typeof deleteAction === "string" && deleteAction.startsWith("url:")) {
window.location.href = deleteAction.replace("url:", "").trim();
} else if (!deleteAction || deleteAction === "false") {
row.fadeOut(500, function () {
$(this).remove();
});
} else {
try {
let actions = typeof deleteAction === "string" ? JSON.parse(deleteAction) : deleteAction;
for (const [elementId, action] of Object.entries(actions)) {
let $element = $("#" + elementId);
if (action === "show") {
$element.fadeIn(500); // Fade in element smoothly
} else if (action === "hide") {
$element.fadeOut(0); // Fade out element smoothly
}
}
} catch (error) {
console.error("Error parsing deleteAction JSON:", error);
}
}
})
.fail(function (jqXHR) {
// Parse the error response
let response = jqXHR.responseJSON || {};
Swal.fire({
background: background,
color: color,
title: responseErrorTitle || "Error",
text: response.message || responseErrorText || "An error occurred. Please contact support.",
icon: "error",
customClass: {
confirmButton: "btn btn-danger",
},
buttonsStyling: false,
});
});
} else if (result.dismiss === Swal.DismissReason.cancel) {
Swal.fire({
background: background,
color: color,
title: cancelConfirmTitle,
text: cancelConfirmText,
confirmButtonText: cancelConfirmBtnText,
icon: "info",
customClass: {
confirmButton: "btn btn-black",
},
buttonsStyling: false,
});
}
});
}
// Attach event listener
$(document).on('click', '.delete-btn', function (e) {
e.preventDefault(); // Prevent default link behavior
const itemUuid = $(this).data('item-uuid');
const itemName = $(this).data('item-name')
const apiUrl = $(this).data('api-url');
const deleteAction = $(this).data('delete-action') || false;
const row = $(this).closest('tr');
const deleteOptions = {
itemUuid: itemUuid,
itemName: itemName,
apiUrl: apiUrl,
row: row,
deleteAction: deleteAction,
title: __('action_confirm'),
text: __('action_confirm_text'),
icon: "question",
confirmButtonText: __('action_confirm_button'),
cancelButtonText: __('action_cancel_button'),
responseSuccessTitle: __('action_success_title'),
responseSuccessText: __('action_success_text'),
responseSuccessBtnText: __('close'),
responseErrorTitle: __('action_error_title'),
responseErrorText: __('action_error_text'),
cancelConfirmTitle: __('action_cancel_confirm_title'),
cancelConfirmText: __('action_cancel_confirm_text'),
cancelConfirmBtnText: __('close'),
};
showDeleteConfirmation(deleteOptions);
});

View File

@@ -0,0 +1,20 @@
document.addEventListener("DOMContentLoaded", function () {
const fileInputs = document.querySelectorAll('input[type="file"][data-enable-button]');
fileInputs.forEach(function (input) {
input.addEventListener("change", function () {
const buttonId = input.getAttribute("data-enable-button");
const button = document.getElementById(buttonId);
if (input.files.length > 0) {
button.removeAttribute("disabled");
button.classList.remove("opacity-0");
button.classList.add("opacity-100");
} else {
button.setAttribute("disabled", "disabled");
button.classList.remove("opacity-100");
button.classList.add("opacity-0");
}
});
});
});

View File

@@ -0,0 +1,8 @@
$("#FormValidation").validate({
highlight: function (element) {
$(element).closest(".form-group").removeClass("has-success").addClass("has-error");
},
success: function (element) {
$(element).closest(".form-group").removeClass("has-error").addClass("has-success");
},
});

View File

@@ -0,0 +1,40 @@
$(document).on('click', '.test-inserve-connection-btn', function (e) {
e.preventDefault();
const url = "/api/v1/sources/inserve/";
const params = {action: "auth/me"};
const rootStyles = getComputedStyle(document.documentElement);
const background = rootStyles.getPropertyValue('--swal-bg')?.trim();
const color = rootStyles.getPropertyValue('--swal-text-color')?.trim();
$.get(url, params)
.done(function (response, status, jqXHR) {
// Check if HTTP code is 200
if (jqXHR.status === 200) {
Swal.fire({
background: background,
color: color,
icon: "success",
title: __('connection_success_title'),
text: __('connection_success_text'),
confirmButtonText: __('close'),
customClass: {confirmButton: "btn btn-success"},
buttonsStyling: true
});
}
})
.fail(function (jqXHR) {
let response = jqXHR.responseJSON || {};
Swal.fire({
background: background,
color: color,
icon: "error",
title: __('connection_error_title'),
text: response.message || __('connection_error_text') + jqXHR.status,
confirmButtonText: __('close'),
customClass: {confirmButton: "btn btn-danger"},
buttonsStyling: false
});
});
});

View File

@@ -0,0 +1,45 @@
// Disable Dropzone's auto-discover feature
Dropzone.autoDiscover = false;
document.addEventListener('DOMContentLoaded', function () {
// Select all forms with the Dropzone class
const dropzones = document.querySelectorAll('.dropzone');
dropzones.forEach((form) => {
// Check if Dropzone is already attached
if (form.dropzone) return;
// Get the form type from the data attribute
const formType = form.getAttribute('data-form-type');
const associatedTable = document.querySelector(`table[data-table-type="${formType}"] tbody`);
const dz = new Dropzone(form, {
url: form.getAttribute('action'),
paramName: 'file',
autoProcessQueue: true,
maxFilesize: 600,
acceptedFiles: '.pdf,.rom'
});
dz.on('success', function (file, response) {
const parsedResponse = typeof response === 'string'
? JSON.parse(response)
: response;
if (parsedResponse.status === 'success') {
if (associatedTable) {
const newRow = document.createElement('tr');
const newCell = document.createElement('td');
newCell.textContent = parsedResponse.file_name;
newRow.appendChild(newCell);
associatedTable.appendChild(newRow);
}
}
});
dz.on('error', function (file, errorMessage) {
console.error('Error uploading file:', errorMessage);
});
});
});

View File

@@ -0,0 +1,64 @@
document.addEventListener("DOMContentLoaded", function () {
$(".multi-filter-select").each(function () {
const $tableEl = $(this);
const skipColumnsAttr = $tableEl.data("skip-columns");
const skipColumns = skipColumnsAttr
? skipColumnsAttr.toString().split(',').map(Number)
: [];
const orderAttr = $tableEl.attr("data-datatables-order");
const orderConfig = orderAttr ? JSON.parse(orderAttr) : [[0, 'asc']];
const pageLengthAttr = $tableEl.attr("data-page-length");
const pageLength = pageLengthAttr ? parseInt(pageLengthAttr, 10) : 10;
$tableEl.DataTable({
pageLength: pageLength,
dom: "<'row'<'col-sm-12 col-md-9'f><'pl-3 col-sm-12 col-md-3'l>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: {
search: "",
lengthMenu: "Entries _MENU_"
},
order: orderConfig,
initComplete: function () {
this.api().columns().every(function (index) {
if (skipColumns.includes(index)) {
return;
}
const column = this;
const select = $('<select class="form-select"><option value=""></option></select>')
.appendTo($(column.footer()).empty())
.on('change', function () {
const val = $.fn.dataTable.util.escapeRegex($(this).val());
column.search(val ? '^' + val + '$' : '', true, false).draw();
});
// Collect unique filter values
let uniqueValues = new Set();
column.nodes().each(function (cell) {
const cleanText = $(cell).data("filter") || $(cell).text().trim();
if (cleanText !== "") {
uniqueValues.add(cleanText);
}
});
// Sort + append unique values
Array.from(uniqueValues).sort().forEach(function (val) {
select.append('<option value="' + val + '">' + val + '</option>');
});
});
}
});
// Customize search input
$tableEl.closest('.dataTables_wrapper').find('[type=search]').each(function () {
$(this).attr("placeholder", __('search_'));
});
});
});

View File

@@ -0,0 +1,135 @@
document.addEventListener("DOMContentLoaded", function () {
function setCookie(name, value, days = 365) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = name + "=" + encodeURIComponent(value) + "; expires=" + expires + "; path=/";
}
$(".multi-filter-select").each(function () {
const $tableEl = $(this);
//
// AUTO-BUILD columnDefs from <th data-column="">
//
const columnDefs = [];
$tableEl.find("thead th").each(function (index) {
const colName = $(this).data("column") || "";
columnDefs.push({
targets: index,
name: colName
});
});
//
// INITIALIZE DATATABLE
//
let table = $tableEl.DataTable({
autoWidth: false,
pageLength: $tableEl.attr("data-page-length") ? parseInt($tableEl.attr("data-page-length"), 10) : 10,
dom: "<'row'<'col-sm-12 col-md-9'f><'pl-3 col-sm-12 col-md-3'l>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: {
search: "",
lengthMenu: "Entries _MENU_"
},
order: (function () {
let idx = 0;
$tableEl.find("thead th").each(function (i) {
if ($(this).data("column") === "server_hostname") {
idx = i;
}
});
return [[idx, 'asc']];
})(),
columnDefs: columnDefs,
initComplete: function () {
const dtApi = this.api();
dtApi.columns().every(function () {
const colIndex = this.index();
const th = $tableEl.find("thead th").eq(colIndex);
const colName = th.data("column");
if (!colName) return;
const footerCell = $(this.footer());
if (!footerCell.length) return;
footerCell.empty();
const select = $('<select class="form-select"><option value=""></option></select>')
.appendTo(footerCell)
.on('change', function () {
const val = $.fn.dataTable.util.escapeRegex($(this).val());
dtApi.column(colName + ":name")
.search(val ? '^' + val + '$' : '', true, false)
.draw();
});
const uniqueValues = new Set();
this.nodes().each(function (cell) {
const cleanText = $(cell).data("filter") || $(cell).text().trim();
if (cleanText !== "") uniqueValues.add(cleanText);
});
Array.from(uniqueValues).sort().forEach(v => {
select.append('<option value="' + v + '">' + v + '</option>');
});
});
//
// APPLY CHECKBOX VISIBILITY DEFAULTS
//
// Set initial visibility based on checkboxes
const $checkboxes = $(".selectgroup-input");
$checkboxes.each(function () {
const baseName = $(this).val();
const visible = $(this).is(":checked");
dtApi.columns().every(function () {
const name = this.settings()[0].aoColumns[this.index()].name || '';
if (name === baseName || name.startsWith(baseName + '_')) {
this.visible(visible);
}
});
});
$checkboxes.on("change", function () {
const selected = [];
$checkboxes.filter(":checked").each(function () {
selected.push($(this).val());
});
setCookie("serverTableColumns", JSON.stringify(selected), 365);
const baseName = $(this).val();
const visible = $(this).is(":checked");
table.columns(`[data-column^='${baseName}']`).visible(visible);
});
}
});
//
// SEARCH FIELD ICON
//
$tableEl.closest('.dataTables_wrapper').find('[type=search]').each(function () {
$(this).attr("placeholder", __('search'));
});
//
// HANDLE CHECKBOX TOGGLE
//
$(".selectgroup-input").on("change", function () {
const colName = $(this).val();
table.column(colName + ":name").visible($(this).is(":checked"));
});
});
});

View File

@@ -0,0 +1,8 @@
document.addEventListener("DOMContentLoaded", function () {
// Initialize select2 for all elements with data-multiple-select
document.querySelectorAll("[data-multiple-select]").forEach(select => {
$(select).select2({
theme: "bootstrap"
});
});
});

View File

@@ -0,0 +1,5 @@
document.querySelectorAll("[data-password-target]").forEach(input => {
const passwordLength = Math.floor(Math.random() * (25 - 13 + 1)) + 13;
const generatedPassword = generateRandomPassword(passwordLength);
input.setAttribute("value", generatedPassword);
});

View File

@@ -0,0 +1,9 @@
document.querySelectorAll(".password-generate").forEach(button => {
button.addEventListener("click", function () {
const inputId = this.getAttribute("data-generate-target");
const input = document.getElementById(inputId);
const passwordLength = Math.floor(Math.random() * (25 - 13 + 1)) + 13;
const newPassword = generateRandomPassword(passwordLength);
input.setAttribute("value", newPassword);
});
});

View File

@@ -0,0 +1,15 @@
document.querySelectorAll(".password-toggle").forEach(button => {
button.addEventListener("click", function () {
const inputId = this.getAttribute("data-toggle-target");
const input = document.getElementById(inputId);
const icon = this.querySelector("i");
if (input.type === "password") {
input.type = "text";
icon.classList.replace("fa-eye", "fa-eye-slash");
} else {
input.type = "password";
icon.classList.replace("fa-eye-slash", "fa-eye");
}
});
});

View File

@@ -0,0 +1,40 @@
document.addEventListener("DOMContentLoaded", async function () {
try {
const response = await fetch("/api/v1/frontend/response/");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (!Object.keys(data).length) {
return;
}
const title = Object.keys(data)[0];
const messageKey = data[title];
$.notify({
icon: "fa fa-bell",
title: title.charAt(0).toUpperCase() + title.slice(1),
message: __(messageKey)
}, {
type: title === "error" ? "danger" : title,
placement: {
from: "top",
align: "right"
},
delay: 3000
});
if ($.fn.select2) {
$("#multiple").select2({
theme: "bootstrap"
});
}
} catch (err) {
console.error("Failed to retrieve response message:", err);
}
});

View File

@@ -0,0 +1,9 @@
document.addEventListener("DOMContentLoaded", function () {
const select = document.getElementById("server_state");
if (!select) return;
select.addEventListener("change", function () {
this.name = this.value ? "server_state" : "";
});
});

View File

@@ -0,0 +1,20 @@
function slugify(str) {
return str
.trim()
.toLowerCase()
.replace(/[^a-z0-9 -]/g, '') // Remove non-alphanumeric characters
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/-+/g, '-'); // Remove consecutive hyphens
}
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll("[data-slugify]").forEach(input => {
input.addEventListener("input", function () {
const targetId = this.getAttribute("data-slugify");
const targetInput = document.getElementById(targetId);
if (targetInput) {
targetInput.value = slugify(this.value);
}
});
});
});

View File

@@ -0,0 +1,358 @@
// Attach event listener
$(document).on('click', '.stomp-btn', function (e) {
e.preventDefault(); // Prevent default link behavior
console.log(typeof Swal);
const itemUuid = $(this).data('item-uuid');
const itemName = $(this).data('item-name')
const apiUrl = $(this).data('api-url');
const deleteAction = $(this).data('delete-action') || false;
const row = $(this).closest('tr');
const StompOptions = {
itemUuid: itemUuid,
itemName: itemName,
apiUrl: apiUrl,
row: row,
deleteAction: deleteAction,
title: __('action_confirm'),
text: __('action_confirm_text'),
icon: "question",
responseSuccessTitle: __('stomped'),
responseSuccessText: __('stomped_success'),
responseSuccessBtnText: __('close'),
responseErrorTitle: __('action_error_title'),
responseErrorText: __('action_error_text'),
cancelConfirmTitle: __('action_cancel_confirm_title'),
cancelConfirmText: __('action_cancel_confirm_text'),
cancelConfirmBtnText: __('close'),
};
// Make the POST request to delete the device
$.post(apiUrl, {
[itemName]: itemUuid,
"X-HTTP-Method-Override": "POST"
})
.done(function (response) {
// Parse the response if necessary
Swal.fire({
background: background,
color: color,
title: StompOptions.responseSuccessTitle || "Added!",
text: response.message || StompOptions.responseSuccessText || "Your stomp has been added.",
confirmButtonText: StompOptions.responseSuccessBtnText,
icon: "success",
customClass: {
confirmButton: "btn btn-success",
},
buttonsStyling: true,
}).then(() => {
const counterEl = $("#count-" + itemUuid);
if (counterEl.length) {
const currentCount = parseInt(counterEl.text(), 10);
if (!isNaN(currentCount)) {
counterEl.text(currentCount + 1);
}
}
});
})
.fail(function (jqXHR) {
let response = jqXHR.responseJSON || {};
Swal.fire({
background: background,
color: color,
title: StompOptions.responseErrorTitle,
text: response.message || StompOptions.responseErrorText,
icon: "error",
customClass: {confirmButton: "btn btn-danger"},
buttonsStyling: false,
});
});
});
// Attach event listener
$(document).on('click', '.stomp-delete-btn', function (e) {
e.preventDefault(); // Prevent default link behavior
const row = $(this).closest('tr');
const deleteStompOptions = {
itemUuid: $(this).data('item-uuid'),
itemName: $(this).data('item-name'),
apiUrl: $(this).data('api-url'),
row: row,
deleteAction: $(this).data('delete-action') || false,
title: __('action_confirm'),
text: __('action_confirm_text'),
confirmButtonText: __('action_confirm_button'),
cancelButtonText: __('action_cancel_button'),
responseSuccessTitle: __('deleted'),
responseSuccessText: __('action_success_text'),
responseSuccessBtnText: __('close'),
responseErrorTitle: __('action_error_title'),
responseErrorText: __('action_error_text'),
cancelConfirmBtnText: __('close'),
};
// Make the POST request to delete the device
$.post(deleteStompOptions.apiUrl, {
[deleteStompOptions.itemName]: deleteStompOptions.itemUuid,
"X-HTTP-Method-Override": "DELETE"
})
.done(function (response) {
// Parse the response if necessary
Swal.fire({
background: background,
color: color,
title: deleteStompOptions.responseSuccessTitle || "Deleted!",
text: response.message || deleteStompOptions.responseSuccessText || "Your item has been deleted.",
confirmButtonText: deleteStompOptions.responseSuccessBtnText,
icon: "error",
customClass: {
confirmButton: "btn btn-danger",
},
buttonsStyling: true,
}).then(() => {
// If deleteAction is false, remove the row; otherwise, process show and hide actions
const userUUID = deleteStompOptions.row.data('user-uuid');
const counterEl = $("#count-" + userUUID);
if (counterEl.length) {
const currentCount = parseInt(counterEl.text(), 10);
if (!isNaN(currentCount) && currentCount > 0) {
counterEl.text(currentCount - 1);
}
}
if (!deleteStompOptions.deleteAction || deleteStompOptions.deleteAction === "false") {
row.fadeOut(500, function () {
$(this).remove();
});
}
});
})
.fail(function (jqXHR) {
let response = jqXHR.responseJSON || {};
Swal.fire({
background: background,
color: color,
title: deleteStompOptions.responseErrorTitle,
text: response.message || deleteStompOptions.responseErrorText,
icon: "error",
customClass: {confirmButton: "btn btn-danger"},
buttonsStyling: false,
});
});
});
const gruvboxColors = [
"#fabd2f", // yellow
"#b8bb26", // green
"#fb4934", // red
"#8ec07c", // aqua
"#83a598", // blue
"#fe8019", // orange
];
const assignedColors = {};
const usedColors = new Set();
function hexToRGBA(hex, alpha) {
hex = hex.replace('#', '');
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return `rgba(${r},${g},${b},${alpha})`;
}
function getColorForUser(name) {
if (assignedColors[name]) return assignedColors[name];
let hash = 0;
for (let i = 0; i < name.length; i++) {
hash = name.charCodeAt(i) + ((hash << 5) - hash);
}
let preferredIndex = Math.abs(hash) % gruvboxColors.length;
let preferredColor = gruvboxColors[preferredIndex];
if (!usedColors.has(preferredColor)) {
assignedColors[name] = preferredColor;
usedColors.add(preferredColor);
return preferredColor;
}
const unused = gruvboxColors.filter(c => !usedColors.has(c));
let finalColor = unused.length > 0
? unused[Math.floor(Math.random() * unused.length)]
: gruvboxColors[Math.floor(Math.random() * gruvboxColors.length)];
assignedColors[name] = finalColor;
usedColors.add(finalColor);
return finalColor;
}
// Helper: get all dates between two dates (inclusive)
function getDatesBetween(startDate, endDate) {
const dates = [];
let current = new Date(startDate);
const end = new Date(endDate);
while (current <= end) {
dates.push(current.toISOString().slice(0, 10));
current.setDate(current.getDate() + 1);
}
return dates;
}
let stompChart = null;
// Convert dd/mm/yyyy → Date safely
function parseInputDate(value) {
if (!value) return null;
const parts = value.split("/");
if (parts.length !== 3) return null;
return new Date(`${parts[2]}-${parts[1]}-${parts[0]}`);
}
// Generate date range (expects existing helper)
function getDatesBetween(startDate, endDate) {
const dates = [];
const current = new Date(startDate);
while (current <= endDate) {
dates.push(current.toISOString().slice(0, 10));
current.setDate(current.getDate() + 1);
}
return dates;
}
async function loadChart() {
try {
// Read inputs safely
const fromDate = parseInputDate(document.getElementById("fd").value);
const toDate = parseInputDate(document.getElementById("td").value);
if (!fromDate || !toDate) {
console.warn("Invalid date range");
return;
}
const sortedDates = getDatesBetween(fromDate, toDate);
// Fetch data
const res = await fetch('/api/v1/office/stompjes/?frontend=1');
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const data = await res.json();
const stompData = data.success || [];
// Build user dataset
const users = {};
stompData.forEach(item => {
const user = item.user_first_name;
const date = new Date(item.stomp_timestamp * 1000)
.toISOString()
.slice(0, 10);
if (!users[user]) users[user] = {};
if (!users[user][date]) users[user][date] = 0;
users[user][date] += 1;
});
// Fill missing dates with 0
Object.keys(users).forEach(user => {
sortedDates.forEach(date => {
if (!users[user][date]) {
users[user][date] = 0;
}
});
});
// Build datasets
const datasets = Object.keys(users).map(user => {
const color = getColorForUser(user);
return {
label: user,
data: sortedDates.map(date => users[user][date]),
borderColor: color,
backgroundColor: hexToRGBA(color, 0.1),
tension: 0.3
};
});
// Chart config (Chart.js 4.5.1)
const config = {
type: 'line',
data: {
labels: sortedDates,
datasets: datasets
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top'
},
title: {
display: true,
text: 'Stomps per User per Day'
}
},
scales: {
y: {
beginAtZero: true
}
}
}
};
// Render / replace chart safely
const ctx = document.getElementById('stompjesChart');
if (stompChart instanceof Chart) {
stompChart.destroy();
}
stompChart = new Chart(ctx, config);
} catch (err) {
console.error('Failed to load stomp data:', err);
}
}
$(document).ready(function () {
$("#datePicker").on("click", function () {
const fd = $("#fd").val();
const td = $("#td").val();
const params = new URLSearchParams(window.location.search);
if (fd) params.set("fd", fd);
else params.delete("fd");
if (td) params.set("td", td);
else params.delete("td");
window.location.href = window.location.pathname + "?" + params.toString();
});
// Initial chart load
loadChart();
});

View File

@@ -0,0 +1,50 @@
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.checkbox').forEach(checkbox => {
checkbox.addEventListener('change', async (e) => {
const input = e.target;
const permissionUUID = input.dataset.permissionUuid;
const userGroupUUID = input.dataset.userGroupUuid;
const permissionValue = input.dataset.value;
const apiUrl = input.dataset.apiUrl;
// Revert the checkbox immediately; only mark it again on success
input.checked = !input.checked;
input.disabled = true;
// Build URL-encoded form data
const formData = new URLSearchParams();
formData.append('permission_uuid', permissionUUID);
formData.append('user_group_uuid', userGroupUUID);
formData.append('permission_value', permissionValue);
formData.append('X-HTTP-Method-Override', 'PUT')
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData.toString()
});
if (response.ok) {
// Uncheck all switches in the same group
const groupSelector = `input[data-permission-uuid="${permissionUUID}"][data-user-group-uuid="${userGroupUUID}"]`;
document.querySelectorAll(groupSelector).forEach(cb => cb.checked = false);
// Then check the selected one
input.checked = true;
} else {
console.error('API error:', response.status);
// Leave checkbox state unchanged (reverted above)
}
} catch (err) {
console.error('Network error:', err);
// Leave checkbox state unchanged (reverted above)
} finally {
input.disabled = false;
}
});
});
});

View File

@@ -0,0 +1,53 @@
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.checkbox').forEach(function (checkbox) {
checkbox.addEventListener('change', async function (e) {
const input = e.target;
const apiUrl = input.getAttribute('data-api-url');
const changeKey = input.getAttribute('data-api-changevalue');
const dataRaw = input.getAttribute('data-api-data');
let dataObj;
try {
dataObj = JSON.parse(dataRaw);
} catch (err) {
console.error('Invalid JSON in data-api-data:', err);
return;
}
// Toggle value FIRST
if (changeKey in dataObj) {
const currentValue = dataObj[changeKey];
dataObj[changeKey] = currentValue === '1' || currentValue === 1 ? 0 : 1;
}
// Add method override
dataObj['X-HTTP-Method-Override'] = 'PUT';
// Convert to URL-encoded string
const formBody = new URLSearchParams(dataObj).toString();
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formBody
});
if (response.ok) {
// Remove X-HTTP-Method-Override before saving back
delete dataObj['X-HTTP-Method-Override']
input.setAttribute('data-api-data', JSON.stringify(dataObj));
} else {
input.checked = !input.checked; // revert toggle
console.error('Request failed:', await response.text());
}
} catch (err) {
input.checked = !input.checked;
console.error('Request error:', err);
}
});
});
});

View File

@@ -0,0 +1,30 @@
function isValidJSON(value) {
try {
JSON.parse(value);
return true;
} catch (e) {
return false;
}
}
document.addEventListener("DOMContentLoaded", function () {
// Attach JSON validation to all textareas with data-validate-json
document.querySelectorAll("[data-validate-json]").forEach(textarea => {
textarea.addEventListener("input", function () {
const value = this.value.trim();
const feedback = this.nextElementSibling; // Assuming <small> is right after <textarea>
if (value === "" || isValidJSON(value)) {
feedback.textContent = __('json_valid');
feedback.style.color = "green";
this.classList.remove("is-invalid");
this.classList.add("is-valid");
} else {
feedback.textContent = __('json_invalid');
feedback.style.color = "red";
this.classList.remove("is-valid");
this.classList.add("is-invalid");
}
});
});
});

View File

@@ -0,0 +1,3 @@
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
document.cookie = `user_timezone=${tz}; path=/; SameSite=Lax`;