Content Security Policy (CSP) compatibility beta.
This commit is contained in:
@@ -33,12 +33,41 @@ class API_office_stompjes extends API
|
|||||||
$this->apiOutput(200, ['success' => 'Stomp removed.']);
|
$this->apiOutput(200, ['success' => 'Stomp removed.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStomp()
|
public function getStomp($frontend = false)
|
||||||
{
|
{
|
||||||
|
if (!$frontend) {
|
||||||
list($query, $types, $params) = $this->buildDynamicQuery('office_stompjes');
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
24
pub/api/v1/frontend/breadcrumbs/index.php
Normal file
24
pub/api/v1/frontend/breadcrumbs/index.php
Normal 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;
|
||||||
|
}
|
||||||
21
pub/api/v1/frontend/locale/index.php
Normal file
21
pub/api/v1/frontend/locale/index.php
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
18
pub/api/v1/frontend/response/index.php
Normal file
18
pub/api/v1/frontend/response/index.php
Normal 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 '[]';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,12 @@ if (!$GLOBALS['modules_enabled']['office']) {
|
|||||||
if ($API_office_stompjes->request_method === 'GET') {
|
if ($API_office_stompjes->request_method === 'GET') {
|
||||||
$API_office_stompjes->checkPermissions('office-stompjes', 'RO');
|
$API_office_stompjes->checkPermissions('office-stompjes', 'RO');
|
||||||
|
|
||||||
|
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();
|
$stompjes = $API_office_stompjes->getStomp();
|
||||||
|
}
|
||||||
|
|
||||||
$API_office_stompjes->apiOutput($code = 200, ['success' => $stompjes]);
|
$API_office_stompjes->apiOutput($code = 200, ['success' => $stompjes]);
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ if ($_GET['add'] == 'phone' || $_GET['add'] == 'base') {
|
|||||||
$formInputs[] = 'device_extra';
|
$formInputs[] = 'device_extra';
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_device'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('add_device'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -56,8 +56,7 @@ $stmt->bind_param("s", $_GET['edit']);
|
|||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$device_data_result = $stmt->get_result();
|
$device_data_result = $stmt->get_result();
|
||||||
$device_data = $device_data_result->fetch_assoc();
|
$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) {
|
if ($device_data_result->num_rows > 0) {
|
||||||
$device_found = true;
|
$device_found = true;
|
||||||
}
|
}
|
||||||
@@ -70,7 +69,9 @@ if ($device_data['device_type'] == 'phone' || $device_data['device_type'] == 'ba
|
|||||||
$formInputs[] = 'device_extra';
|
$formInputs[] = 'device_extra';
|
||||||
|
|
||||||
# Set breadcrumb data
|
# 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
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -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");
|
$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
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('devices'), 'href' => '/devices/'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('devices'), 'href' => '/devices/'];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ $formBuilder->submitButtonIcon = '<i class="fas fa-save"></i>';
|
|||||||
# Retrieve Information for the page
|
# Retrieve Information for the page
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('platforms'), 'href' => '/platforms/'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('platforms'), 'href' => '/platforms/'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_platform'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('add_platform'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ $API_platforms->validateData($requiredFields);
|
|||||||
$platform_data = $API_platforms->getPlatforms()[0];
|
$platform_data = $API_platforms->getPlatforms()[0];
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('platforms'), 'href' => '/platforms/'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('platforms'), 'href' => '/platforms/'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_platform'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_platform'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
if ($platform_data) {
|
if ($platform_data) {
|
||||||
|
|||||||
@@ -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/');
|
$formBuilder = new formBuilder('add_vendor', '<i class="fas fa-plus"></i>', '/vendors/');
|
||||||
|
|
||||||
# Set breadcrumb data
|
# 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
|
# Retrieve Information for the page
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ $API_vendors->validateData($requiredFields);
|
|||||||
$vendor_data = $API_vendors->getVendors()[0];
|
$vendor_data = $API_vendors->getVendors()[0];
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_vendor'), 'href' => '/vendors/'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_vendor'), 'href' => '/vendors/'];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ $result = $stmt->get_result();
|
|||||||
$company_data = $result->fetch_assoc();
|
$company_data = $result->fetch_assoc();
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('companies'), 'href' => '/companies/'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('companies'), 'href' => '/companies/'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => $company_data['company_name'], 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => $company_data['company_name'], 'href' => ''];
|
||||||
|
|
||||||
# Retrieve Information for the page
|
# Retrieve Information for the page
|
||||||
$company_uuid = htmlspecialchars($_GET['view'], ENT_QUOTES, 'UTF-8');
|
$company_uuid = htmlspecialchars($_GET['view'], ENT_QUOTES, 'UTF-8');
|
||||||
|
|||||||
@@ -57,23 +57,14 @@ if (!isset($_GET['td'])) {
|
|||||||
$SelectTillDate = strtotime($date . ' 23:59:59');
|
$SelectTillDate = strtotime($date . ' 23:59:59');
|
||||||
}
|
}
|
||||||
|
|
||||||
$stompjes = array();
|
$stompjes = $API->getStomp(true);
|
||||||
$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
|
foreach ($stompjes as $stompje) {
|
||||||
WHERE stomp_timestamp BETWEEN '$SelectFromDate' AND '$SelectTillDate'
|
$administrators[$stompje['user_uuid']]['amount']++;
|
||||||
AND user_stompable = '1'
|
|
||||||
ORDER BY stomp_timestamp DESC");
|
|
||||||
while ($row = $stmt->fetch_assoc()) {
|
|
||||||
array_push($stompjes, $row);
|
|
||||||
$administrators[$row['user_uuid']]['amount']++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
?>
|
?>
|
||||||
<script>
|
|
||||||
const stompData = <?php echo json_encode($stompjes); ?>;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="form-group form-show-validation row mb-3">
|
<div class="form-group form-show-validation row mb-3">
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<h2>
|
<h2>
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ if ($result->num_rows == 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_profile'), 'href' => '/userprofile/'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('user_profile'), 'href' => '/userprofile/'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('edit'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ while ($user_group = $user_groups_data->fetch_assoc()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('administrators'), 'href' => '/accesscontrol/#administrators'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('administrators'), 'href' => '/accesscontrol/#administrators'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_admin'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('add_admin'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ if ($result->num_rows == 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('administrators'), 'href' => '/accesscontrol/#administrators'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('administrators'), 'href' => '/accesscontrol/#administrators'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_admin'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_admin'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -71,9 +71,8 @@ $API_token->validateData($requiredFields);
|
|||||||
$apitokens = $API_token->getTokens();
|
$apitokens = $API_token->getTokens();
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('administrators'), 'href' => '/accesscontrol/#administrators'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('administrators'), 'href' => '/accesscontrol/#administrators'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('view'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('view'), 'href' => ''];
|
||||||
|
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
@@ -223,18 +222,18 @@ if ($admin_data) { ?>
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php if ($API->checkPermissions('admin-access-admins-mfa', 'RW', true)) { ?>
|
<?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">
|
<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"}'>
|
<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> <?php echo __('reset_mfa') ?>
|
<i class="fa-solid fa-lock"></i> <?= __('reset_mfa') ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="col-auto">
|
||||||
<button class="btn btn-danger delete-btn" href="#" disabled>
|
<button class="btn btn-danger delete-btn" disabled>
|
||||||
<i class="fa-solid fa-lock"></i> <?php echo __('reset_mfa') ?>
|
<i class="fa-solid fa-lock"></i> <?= __('reset_mfa') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -288,9 +287,9 @@ if ($admin_data) { ?>
|
|||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($apitokens as $token_data) { ?>
|
<?php foreach ($apitokens as $token_data) { ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-nowrap" style="max-width: 100%;">
|
<td class="text-nowrap">
|
||||||
<div class="d-flex align-items-center gap-2" style="max-width: 100%;">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<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']; ?>">
|
<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']; ?>
|
<?php echo $token_data['api_token_uuid']; ?>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-copy-target="<?php echo $token_data['api_token_uuid'] ?>" title="Copy ID">
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-copy-target="<?php echo $token_data['api_token_uuid'] ?>" title="Copy ID">
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ while ($module = $system_modules_data->fetch_assoc()) {
|
|||||||
|
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_permission'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('add_permission'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ while ($module = $system_modules_data->fetch_assoc()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_permission'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_permission'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ $API_permissions->validateData($requiredFields);
|
|||||||
$permission_data = $API_permissions->getPermissionRights();
|
$permission_data = $API_permissions->getPermissionRights();
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('view'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('view'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ while ($user_group = $user_groups_data->fetch_assoc()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_user_groups'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('add_user_groups'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ $API_usergroups->validateData($requiredFields);
|
|||||||
$user_group = $API_usergroups->getUsergroup()[0];
|
$user_group = $API_usergroups->getUsergroup()[0];
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => $user_group['user_group_name'], 'href' => '/accesscontrol/#user-groups'));
|
$_SESSION['breadCrumbArray'][] = ['display' => $user_group['user_group_name'], 'href' => '/accesscontrol/#user-groups'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('edit'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ if (!$API->checkPermissions('admin-access-control-permissions', 'RW', true)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => $user_group['user_group_name'], 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => $user_group['user_group_name'], 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ if (!defined('APP_INIT')) {
|
|||||||
$jsScriptLoadData['activeTabOnRefresh'] = true;
|
$jsScriptLoadData['activeTabOnRefresh'] = true;
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<div class="card-body activeTabOnRefresh" style="opacity: 0; transition: opacity 10ms;">
|
<div class="card-body activeTabOnRefresh">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-1">
|
<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">
|
<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">
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ while ($module = $system_modules_data->fetch_assoc()) {
|
|||||||
<div class="col-lg-9 col-md-12 col-sm-10">
|
<div class="col-lg-9 col-md-12 col-sm-10">
|
||||||
<div class="input-group">
|
<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/>
|
<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>
|
<i class="fas fa-copy"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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="col-lg-9 col-md-12 col-sm-10">
|
||||||
<div class="input-group">
|
<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/>
|
<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>
|
<i class="fas fa-copy"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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) : "{}");
|
$inserve_custom_data = ($inserve_settings['source_custom_data'] ? json_encode(json_decode($inserve_settings['source_custom_data']), JSON_PRETTY_PRINT) : "{}");
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('portal_management'), 'href' => '/systemconfig'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('portal_management'), 'href' => '/systemconfig'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('sources'), 'href' => '/systemconfig#sources'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('sources'), 'href' => '/systemconfig#sources'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => 'Inserve', 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => 'Inserve', 'href' => ''];
|
||||||
|
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ if (!$API->checkPermissions('servers', 'RO', true)) {
|
|||||||
$jsScriptLoadData['delete_confirmation'] = true;
|
$jsScriptLoadData['delete_confirmation'] = true;
|
||||||
$jsScriptLoadData['breadCrumbs'] = true;
|
$jsScriptLoadData['breadCrumbs'] = true;
|
||||||
$jsScriptLoadData['codeblocks'] = true;
|
$jsScriptLoadData['codeblocks'] = true;
|
||||||
|
$jsScriptLoadData['serverView'] = true;
|
||||||
|
|
||||||
# PageClasses Setup
|
# PageClasses Setup
|
||||||
$pageNavbar = new pageNavbar(true);
|
$pageNavbar = new pageNavbar(true);
|
||||||
@@ -129,8 +130,8 @@ if ($API->checkPermissions('servers', 'RW', true)) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('server_overview'), 'href' => '/servers/'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('server_overview'), 'href' => '/servers/'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => $server_data['server_vm_host_name'], 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => $server_data['server_vm_host_name'], 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$pageNavbar->outPutNavbar();
|
$pageNavbar->outPutNavbar();
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ if (!$GLOBALS['modules_enabled']['customers']) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Set breadcrumb data
|
# Set breadcrumb data
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
|
||||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('view'), 'href' => ''));
|
$_SESSION['breadCrumbArray'][] = ['display' => __('view'), 'href' => ''];
|
||||||
|
|
||||||
# Start page output
|
# Start page output
|
||||||
$serverOverview->serverOverviewOutPut();
|
$serverOverview->serverOverviewOutPut();
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class pageBuilder extends API
|
|||||||
$requestUri = rtrim($requestUri, '/');
|
$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']['pageName'] = '404';
|
||||||
$GLOBALS['pageContentToShow']['pageFile'] = 'pageNotFound.php';
|
$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 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">
|
<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 -->
|
<!-- CSS Files -->
|
||||||
<link rel="stylesheet" href="/src/css/bootstrap.gruvbox.min.css"/> <!-- Basic bootstrap and theming -->
|
<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/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/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"/> -->
|
<!--<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="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="apple-touch-icon" sizes="180x180" href="/src/images/favicon/apple-touch-icon.png"/>
|
||||||
<link rel="manifest" href="/src/images/favicon/site.webmanifest"/>
|
<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>
|
</head>
|
||||||
<?php }
|
<?php }
|
||||||
|
|
||||||
@@ -383,6 +329,9 @@ class pageBuilder extends API
|
|||||||
<script src="/src/js/core/popper.min.js"></script>
|
<script src="/src/js/core/popper.min.js"></script>
|
||||||
<script src="/src/js/core/bootstrap.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 -->
|
<!-- Bootstrap Notify -->
|
||||||
<script src="/src/js/plugin/bootstrap-notify/bootstrap-notify.min.js"></script>
|
<script src="/src/js/plugin/bootstrap-notify/bootstrap-notify.min.js"></script>
|
||||||
|
|
||||||
@@ -418,37 +367,11 @@ class pageBuilder extends API
|
|||||||
setTimeZoneCookie();
|
setTimeZoneCookie();
|
||||||
|
|
||||||
if (isset($this->jsScriptLoadData)) {
|
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
|
# process response from actions
|
||||||
if (isset($_SESSION['response'])) {
|
if (isset($_SESSION['response'])) { ?>
|
||||||
$ar = json_decode($_SESSION['response']);
|
<script src="/src/js/sentri/response.js"></script>
|
||||||
$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>
|
|
||||||
<?php }
|
<?php }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class pageNavbar
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col d-flex justify-content-start">
|
<div class="col d-flex justify-content-start">
|
||||||
<?php if ($this->breadCrumb) { ?>
|
<?php if ($this->breadCrumb) { ?>
|
||||||
<div class="pb-2" id="breadCrumb"></div>
|
<div class="pb-2" id="breadCrumb"> </div>
|
||||||
<?php } elseif ($this->title) { ?>
|
<?php } elseif ($this->title) { ?>
|
||||||
<h2 class="px-4 mb-0"><?php echo $this->title ?></h2>
|
<h2 class="px-4 mb-0"><?php echo $this->title ?></h2>
|
||||||
<?php } else { ?>
|
<?php } else { ?>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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="apple-touch-icon" sizes="180x180" href="/src/images/favicon/apple-touch-icon.png">
|
||||||
<link rel="manifest" href="/src/images/favicon/site.webmanifest">
|
<link rel="manifest" href="/src/images/favicon/site.webmanifest">
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="css/style.full.css" as="style">
|
<link rel="stylesheet" href="css/style.full.css" as="style">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -24,13 +24,9 @@ function addLoginAttempts()
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setTimeZoneCookie()
|
function setTimeZoneCookie()
|
||||||
{
|
{ ?>
|
||||||
?>
|
<script src="/src/js/timeZoneCookie.js"></script>
|
||||||
<script>
|
<?php }
|
||||||
document.cookie = "user_timezone=" + Intl.DateTimeFormat().resolvedOptions().timeZone + "; path=/";
|
|
||||||
</script>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUserTimezone()
|
function getUserTimezone()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ if (checkLoginAttempts() > 20) {
|
|||||||
<div class="card col-md-5 text-white bg-transparent py-5">
|
<div class="card col-md-5 text-white bg-transparent py-5">
|
||||||
<div class="card-body text-center pt-5">
|
<div class="card-body text-center pt-5">
|
||||||
<div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9314,3 +9314,59 @@ body .jvm-zoom-btn {
|
|||||||
font-size: 18px !important;
|
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;
|
||||||
|
}
|
||||||
2
pub/src/css/sentri.min.css
vendored
2
pub/src/css/sentri.min.css
vendored
File diff suppressed because one or more lines are too long
53
pub/src/js/i18n-loader.js
Normal file
53
pub/src/js/i18n-loader.js
Normal 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;
|
||||||
|
}
|
||||||
30
pub/src/js/plugin/chart.js/chart.min.js
vendored
30
pub/src/js/plugin/chart.js/chart.min.js
vendored
File diff suppressed because one or more lines are too long
1
pub/src/js/plugin/chart.js/chart.min.js.map
Normal file
1
pub/src/js/plugin/chart.js/chart.min.js.map
Normal file
File diff suppressed because one or more lines are too long
1
pub/src/js/plugin/sweetalert2/sweetalert2.min.css
vendored
Normal file
1
pub/src/js/plugin/sweetalert2/sweetalert2.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
34
pub/src/js/sentri/CopyTargetData.js
Normal file
34
pub/src/js/sentri/CopyTargetData.js
Normal 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'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
9
pub/src/js/sentri/Generatepassword.js
Normal file
9
pub/src/js/sentri/Generatepassword.js
Normal 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;
|
||||||
|
}
|
||||||
53
pub/src/js/sentri/activateCompany.js
Normal file
53
pub/src/js/sentri/activateCompany.js
Normal 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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
28
pub/src/js/sentri/activeTabOnRefresh.js
Normal file
28
pub/src/js/sentri/activeTabOnRefresh.js
Normal 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";
|
||||||
|
});
|
||||||
65
pub/src/js/sentri/breadCrumbs.js
Normal file
65
pub/src/js/sentri/breadCrumbs.js
Normal 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);
|
||||||
|
});
|
||||||
43
pub/src/js/sentri/copyInputValue.js
Normal file
43
pub/src/js/sentri/copyInputValue.js
Normal 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"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
8
pub/src/js/sentri/datepicker.js
Normal file
8
pub/src/js/sentri/datepicker.js
Normal 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"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
153
pub/src/js/sentri/delete_confirmation.js
Normal file
153
pub/src/js/sentri/delete_confirmation.js
Normal 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);
|
||||||
|
});
|
||||||
20
pub/src/js/sentri/enableButtonOnImageUpload.js
Normal file
20
pub/src/js/sentri/enableButtonOnImageUpload.js
Normal 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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
8
pub/src/js/sentri/form.js
Normal file
8
pub/src/js/sentri/form.js
Normal 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");
|
||||||
|
},
|
||||||
|
});
|
||||||
40
pub/src/js/sentri/inserve_source.js
Normal file
40
pub/src/js/sentri/inserve_source.js
Normal 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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
45
pub/src/js/sentri/load_dropzone.js
Normal file
45
pub/src/js/sentri/load_dropzone.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
64
pub/src/js/sentri/multiFilterSelect.js
Normal file
64
pub/src/js/sentri/multiFilterSelect.js
Normal 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_'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
135
pub/src/js/sentri/multiFilterSelectServers.js
Normal file
135
pub/src/js/sentri/multiFilterSelectServers.js
Normal 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"));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
8
pub/src/js/sentri/multiple_select.js
Normal file
8
pub/src/js/sentri/multiple_select.js
Normal 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"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
5
pub/src/js/sentri/passwordGenOnLoad.js
Normal file
5
pub/src/js/sentri/passwordGenOnLoad.js
Normal 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);
|
||||||
|
});
|
||||||
9
pub/src/js/sentri/passwordRegen.js
Normal file
9
pub/src/js/sentri/passwordRegen.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
15
pub/src/js/sentri/passwordShowHide.js
Normal file
15
pub/src/js/sentri/passwordShowHide.js
Normal 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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
40
pub/src/js/sentri/response.js
Normal file
40
pub/src/js/sentri/response.js
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
9
pub/src/js/sentri/serverView.js
Normal file
9
pub/src/js/sentri/serverView.js
Normal 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" : "";
|
||||||
|
});
|
||||||
|
});
|
||||||
20
pub/src/js/sentri/slugify.js
Normal file
20
pub/src/js/sentri/slugify.js
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
358
pub/src/js/sentri/stompjes.js
Normal file
358
pub/src/js/sentri/stompjes.js
Normal 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();
|
||||||
|
});
|
||||||
50
pub/src/js/sentri/updatePermissions.js
Normal file
50
pub/src/js/sentri/updatePermissions.js
Normal 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
53
pub/src/js/sentri/updateToggle.js
Normal file
53
pub/src/js/sentri/updateToggle.js
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
30
pub/src/js/sentri/validateJson.js
Normal file
30
pub/src/js/sentri/validateJson.js
Normal 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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
3
pub/src/js/timeZoneCookie.js
Normal file
3
pub/src/js/timeZoneCookie.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
|
||||||
|
document.cookie = `user_timezone=${tz}; path=/; SameSite=Lax`;
|
||||||
Reference in New Issue
Block a user