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.']);
|
||||
}
|
||||
|
||||
public function getStomp()
|
||||
public function getStomp($frontend = false)
|
||||
{
|
||||
if (!$frontend) {
|
||||
list($query, $types, $params) = $this->buildDynamicQuery('office_stompjes');
|
||||
|
||||
$items = $this->generalGetFunction($query, $types, $params, false, 'Stompjes');
|
||||
|
||||
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') {
|
||||
$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();
|
||||
}
|
||||
|
||||
$API_office_stompjes->apiOutput($code = 200, ['success' => $stompjes]);
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ if ($_GET['add'] == 'phone' || $_GET['add'] == 'base') {
|
||||
$formInputs[] = 'device_extra';
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_device'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('add_device'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -56,8 +56,7 @@ $stmt->bind_param("s", $_GET['edit']);
|
||||
$stmt->execute();
|
||||
$device_data_result = $stmt->get_result();
|
||||
$device_data = $device_data_result->fetch_assoc();
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => $device_data['vendor_name'] . ' ' . $device_data['device_name'], 'href' => '?view=' . $device_data['device_uuid']));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit'), 'href' => '?view=' . $device_data['device_uuid']));
|
||||
|
||||
if ($device_data_result->num_rows > 0) {
|
||||
$device_found = true;
|
||||
}
|
||||
@@ -70,7 +69,9 @@ if ($device_data['device_type'] == 'phone' || $device_data['device_type'] == 'ba
|
||||
$formInputs[] = 'device_extra';
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('devices'), 'href' => '/devices/'));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => $device_data['vendor_name'] . ' ' . $device_data['device_name'], 'href' => '?view=' . $device_data['device_uuid']];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('edit'), 'href' => '?view=' . $device_data['device_uuid']];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('devices'), 'href' => '/devices/'];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -48,7 +48,7 @@ if ($API->checkPermissions('admin-devices', 'RW', true)) {
|
||||
$data = $GLOBALS['conn']->query("SELECT * FROM autop_devices INNER JOIN autop_vendors ON autop_devices.device_vendor_uuid = autop_vendors.vendor_uuid");
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('devices'), 'href' => '/devices/'));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('devices'), 'href' => '/devices/'];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -34,8 +34,8 @@ $formBuilder->submitButtonIcon = '<i class="fas fa-save"></i>';
|
||||
# Retrieve Information for the page
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('platforms'), 'href' => '/platforms/'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_platform'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('platforms'), 'href' => '/platforms/'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('add_platform'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -47,8 +47,8 @@ $API_platforms->validateData($requiredFields);
|
||||
$platform_data = $API_platforms->getPlatforms()[0];
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('platforms'), 'href' => '/platforms/'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_platform'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('platforms'), 'href' => '/platforms/'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_platform'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
if ($platform_data) {
|
||||
|
||||
@@ -34,7 +34,7 @@ $pageNavbar->AddHTMLButton('<a href="?add="><button class="btn btn-primary"><i c
|
||||
$formBuilder = new formBuilder('add_vendor', '<i class="fas fa-plus"></i>', '/vendors/');
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_vendor'), 'href' => '/vendors/'));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('add_vendor'), 'href' => '/vendors/'];
|
||||
|
||||
# Retrieve Information for the page
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ $API_vendors->validateData($requiredFields);
|
||||
$vendor_data = $API_vendors->getVendors()[0];
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_vendor'), 'href' => '/vendors/'));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_vendor'), 'href' => '/vendors/'];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -49,8 +49,8 @@ $result = $stmt->get_result();
|
||||
$company_data = $result->fetch_assoc();
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('companies'), 'href' => '/companies/'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => $company_data['company_name'], 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('companies'), 'href' => '/companies/'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => $company_data['company_name'], 'href' => ''];
|
||||
|
||||
# Retrieve Information for the page
|
||||
$company_uuid = htmlspecialchars($_GET['view'], ENT_QUOTES, 'UTF-8');
|
||||
|
||||
@@ -57,23 +57,14 @@ if (!isset($_GET['td'])) {
|
||||
$SelectTillDate = strtotime($date . ' 23:59:59');
|
||||
}
|
||||
|
||||
$stompjes = array();
|
||||
$stmt = $GLOBALS['conn']->query("SELECT stomp_uuid, office_stompjes.user_uuid, user_full_name, user_first_name, stomp_timestamp FROM office_stompjes
|
||||
INNER JOIN system_users ON office_stompjes.user_uuid = system_users.user_uuid
|
||||
WHERE stomp_timestamp BETWEEN '$SelectFromDate' AND '$SelectTillDate'
|
||||
AND user_stompable = '1'
|
||||
ORDER BY stomp_timestamp DESC");
|
||||
while ($row = $stmt->fetch_assoc()) {
|
||||
array_push($stompjes, $row);
|
||||
$administrators[$row['user_uuid']]['amount']++;
|
||||
$stompjes = $API->getStomp(true);
|
||||
|
||||
foreach ($stompjes as $stompje) {
|
||||
$administrators[$stompje['user_uuid']]['amount']++;
|
||||
}
|
||||
|
||||
# Start page output
|
||||
?>
|
||||
<script>
|
||||
const stompData = <?php echo json_encode($stompjes); ?>;
|
||||
</script>
|
||||
|
||||
<div class="form-group form-show-validation row mb-3">
|
||||
<div class="col-5">
|
||||
<h2>
|
||||
|
||||
@@ -43,8 +43,8 @@ if ($result->num_rows == 1) {
|
||||
}
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_profile'), 'href' => '/userprofile/'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('user_profile'), 'href' => '/userprofile/'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('edit'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -46,8 +46,8 @@ while ($user_group = $user_groups_data->fetch_assoc()) {
|
||||
}
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('administrators'), 'href' => '/accesscontrol/#administrators'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_admin'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('administrators'), 'href' => '/accesscontrol/#administrators'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('add_admin'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -52,8 +52,8 @@ if ($result->num_rows == 1) {
|
||||
}
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('administrators'), 'href' => '/accesscontrol/#administrators'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_admin'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('administrators'), 'href' => '/accesscontrol/#administrators'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_admin'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -71,9 +71,8 @@ $API_token->validateData($requiredFields);
|
||||
$apitokens = $API_token->getTokens();
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('administrators'), 'href' => '/accesscontrol/#administrators'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('view'), 'href' => ''));
|
||||
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('administrators'), 'href' => '/accesscontrol/#administrators'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('view'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
@@ -223,18 +222,18 @@ if ($admin_data) { ?>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if ($API->checkPermissions('admin-access-admins-mfa', 'RW', true)) { ?>
|
||||
<div id="mfa-enabled-row" class="row" style="display: <?php echo(($admin_data['user_two_factor_enabled']) ? '' : 'none') ?>">
|
||||
<div id="mfa-enabled-row" class="row <?= ($admin_data['user_two_factor_enabled'] ? '' : 'd-none') ?>">
|
||||
<div class="col-auto">
|
||||
<a href="#" class="btn btn-danger delete-btn" data-item-uuid="<?php echo $admin_data['user_uuid'] ?>" data-api-url="/api/v1/user/mfa/" data-item-name='user_uuid' data-delete-action='{"mfa-enabled-row":"hide", "mfa-disabled-row":"show"}'>
|
||||
<i class="fa-solid fa-lock"></i> <?php echo __('reset_mfa') ?>
|
||||
<a href="#" class="btn btn-danger delete-btn" data-item-uuid="<?= $admin_data['user_uuid'] ?>" data-api-url="/api/v1/user/mfa/" data-item-name="user_uuid" data-delete-action='{"mfa-enabled-row":"hide", "mfa-disabled-row":"show"}'>
|
||||
<i class="fa-solid fa-lock"></i> <?= __('reset_mfa') ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mfa-disabled-row" class="row" style="display: <?php echo(($admin_data['user_two_factor_enabled'] == 1) ? 'none' : '') ?>">
|
||||
<div id="mfa-disabled-row" class="row <?= ($admin_data['user_two_factor_enabled'] ? 'd-none' : '') ?>">
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-danger delete-btn" href="#" disabled>
|
||||
<i class="fa-solid fa-lock"></i> <?php echo __('reset_mfa') ?>
|
||||
<button class="btn btn-danger delete-btn" disabled>
|
||||
<i class="fa-solid fa-lock"></i> <?= __('reset_mfa') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -288,9 +287,9 @@ if ($admin_data) { ?>
|
||||
<tbody>
|
||||
<?php foreach ($apitokens as $token_data) { ?>
|
||||
<tr>
|
||||
<td class="text-nowrap" style="max-width: 100%;">
|
||||
<div class="d-flex align-items-center gap-2" style="max-width: 100%;">
|
||||
<div class="text-truncate" style="max-width: 200px;" id="<?php echo $token_data['api_token_uuid'] ?>" data-copy-data="<?php echo $token_data['api_token_uuid']; ?>">
|
||||
<td class="text-nowrap">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="text-truncate" id="<?php echo $token_data['api_token_uuid'] ?>" data-copy-data="<?php echo $token_data['api_token_uuid']; ?>">
|
||||
<?php echo $token_data['api_token_uuid']; ?>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-copy-target="<?php echo $token_data['api_token_uuid'] ?>" title="Copy ID">
|
||||
|
||||
@@ -41,8 +41,8 @@ while ($module = $system_modules_data->fetch_assoc()) {
|
||||
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_permission'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('add_permission'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -51,8 +51,8 @@ while ($module = $system_modules_data->fetch_assoc()) {
|
||||
}
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit_permission'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('edit_permission'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -52,8 +52,8 @@ $API_permissions->validateData($requiredFields);
|
||||
$permission_data = $API_permissions->getPermissionRights();
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('view'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('view'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -41,8 +41,8 @@ while ($user_group = $user_groups_data->fetch_assoc()) {
|
||||
}
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('add_user_groups'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('add_user_groups'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -44,9 +44,9 @@ $API_usergroups->validateData($requiredFields);
|
||||
$user_group = $API_usergroups->getUsergroup()[0];
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => $user_group['user_group_name'], 'href' => '/accesscontrol/#user-groups'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('edit'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => $user_group['user_group_name'], 'href' => '/accesscontrol/#user-groups'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('edit'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -56,8 +56,8 @@ if (!$API->checkPermissions('admin-access-control-permissions', 'RW', true)) {
|
||||
}
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => $user_group['user_group_name'], 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('user_groups'), 'href' => '/accesscontrol/#user-groups'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => $user_group['user_group_name'], 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -6,7 +6,7 @@ if (!defined('APP_INIT')) {
|
||||
$jsScriptLoadData['activeTabOnRefresh'] = true;
|
||||
|
||||
?>
|
||||
<div class="card-body activeTabOnRefresh" style="opacity: 0; transition: opacity 10ms;">
|
||||
<div class="card-body activeTabOnRefresh">
|
||||
<div class="row">
|
||||
<div class="col-md-1">
|
||||
<div class="nav flex-column nav-pills nav-secondary nav-pills-no-bd nav-pills-icons" id="v-pills-tab-with-icon" role="tablist" aria-orientation="vertical">
|
||||
|
||||
@@ -128,7 +128,7 @@ while ($module = $system_modules_data->fetch_assoc()) {
|
||||
<div class="col-lg-9 col-md-12 col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="cacert_url" name="cacert_url" value="<?php echo $portal_settings['cacert_url'] ?>" placeholder="" disabled/>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('cacert_url')">
|
||||
<button class="btn btn-outline-secondary" type="button" data-copy-target="cacert_url">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -141,7 +141,7 @@ while ($module = $system_modules_data->fetch_assoc()) {
|
||||
<div class="col-lg-9 col-md-12 col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="autop_url" name="autop_url" value="<?php echo $portal_settings['autop_url'] ?>" placeholder="" disabled/>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('autop_url')">
|
||||
<button class="btn btn-outline-secondary" type="button" data-copy-target="autop_url">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -35,10 +35,9 @@ $inserve_settings = $GLOBALS['conn']->query("SELECT * FROM system_sources WHERE
|
||||
$inserve_custom_data = ($inserve_settings['source_custom_data'] ? json_encode(json_decode($inserve_settings['source_custom_data']), JSON_PRETTY_PRINT) : "{}");
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('portal_management'), 'href' => '/systemconfig'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('sources'), 'href' => '/systemconfig#sources'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => 'Inserve', 'href' => ''));
|
||||
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('portal_management'), 'href' => '/systemconfig'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('sources'), 'href' => '/systemconfig#sources'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => 'Inserve', 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -30,6 +30,7 @@ if (!$API->checkPermissions('servers', 'RO', true)) {
|
||||
$jsScriptLoadData['delete_confirmation'] = true;
|
||||
$jsScriptLoadData['breadCrumbs'] = true;
|
||||
$jsScriptLoadData['codeblocks'] = true;
|
||||
$jsScriptLoadData['serverView'] = true;
|
||||
|
||||
# PageClasses Setup
|
||||
$pageNavbar = new pageNavbar(true);
|
||||
@@ -129,8 +130,8 @@ if ($API->checkPermissions('servers', 'RW', true)) {
|
||||
};
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('server_overview'), 'href' => '/servers/'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => $server_data['server_vm_host_name'], 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('server_overview'), 'href' => '/servers/'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => $server_data['server_vm_host_name'], 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$pageNavbar->outPutNavbar();
|
||||
|
||||
@@ -59,8 +59,8 @@ if (!$GLOBALS['modules_enabled']['customers']) {
|
||||
}
|
||||
|
||||
# Set breadcrumb data
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('permission'), 'href' => '/accesscontrol/#permissions'));
|
||||
array_push($GLOBALS['breadCrumbArray'], array('display' => __('view'), 'href' => ''));
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('permission'), 'href' => '/accesscontrol/#permissions'];
|
||||
$_SESSION['breadCrumbArray'][] = ['display' => __('view'), 'href' => ''];
|
||||
|
||||
# Start page output
|
||||
$serverOverview->serverOverviewOutPut();
|
||||
|
||||
@@ -47,7 +47,7 @@ class pageBuilder extends API
|
||||
$requestUri = rtrim($requestUri, '/');
|
||||
}
|
||||
|
||||
$GLOBALS['breadCrumbArray'] = array(array('display' => '<i class="fas fa-home"></i>', 'href' => '/'));
|
||||
$_SESSION['breadCrumbArray'] = array(array('display' => '<i class="fas fa-home"></i>', 'href' => '/'));
|
||||
|
||||
$GLOBALS['pageContentToShow']['pageName'] = '404';
|
||||
$GLOBALS['pageContentToShow']['pageFile'] = 'pageNotFound.php';
|
||||
@@ -109,49 +109,11 @@ class pageBuilder extends API
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Public+Sans:300,400,500,600,700|Quicksand:300,400,500,600,700&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
.dataTables_filter label {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dataTables_filter input {
|
||||
height: 40px;
|
||||
width: 100% !important; /* Make the input field full width */
|
||||
box-sizing: border-box; /* Ensure padding doesn't exceed the width */
|
||||
}
|
||||
|
||||
.dataTables_filter .fa-search {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: auto;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.dataTables_length label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.dataTables_length label::before {
|
||||
display: inline-block;
|
||||
height: 40px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.dataTables_length select {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<!-- CSS Files -->
|
||||
<link rel="stylesheet" href="/src/css/bootstrap.gruvbox.min.css"/> <!-- Basic bootstrap and theming -->
|
||||
<link rel="stylesheet" href="/src/css/sentri.min.css"/> <!-- Special css tricks for Sentri -->
|
||||
<link rel="stylesheet" href="/src/css/plugins.min.css"/> <!-- need this for scroll bars and other small things -->
|
||||
<link rel="stylesheet" href="/src/js/plugin/sweetalert2/sweetalert2.min.css"/> <!-- need this for scroll bars and other small things -->
|
||||
|
||||
|
||||
<!--<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css"/> -->
|
||||
@@ -162,22 +124,6 @@ class pageBuilder extends API
|
||||
<link rel="shortcut icon" href="/src/images/favicon/favicon.ico"/>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/src/images/favicon/apple-touch-icon.png"/>
|
||||
<link rel="manifest" href="/src/images/favicon/site.webmanifest"/>
|
||||
|
||||
<style>
|
||||
.fade-in {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-in.show {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.transition-opacity {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<?php }
|
||||
|
||||
@@ -383,6 +329,9 @@ class pageBuilder extends API
|
||||
<script src="/src/js/core/popper.min.js"></script>
|
||||
<script src="/src/js/core/bootstrap.min.js"></script>
|
||||
|
||||
<!-- Load the locales in js -->
|
||||
<script src="/src/js/i18n-loader.js"></script>
|
||||
|
||||
<!-- Bootstrap Notify -->
|
||||
<script src="/src/js/plugin/bootstrap-notify/bootstrap-notify.min.js"></script>
|
||||
|
||||
@@ -418,37 +367,11 @@ class pageBuilder extends API
|
||||
setTimeZoneCookie();
|
||||
|
||||
if (isset($this->jsScriptLoadData)) {
|
||||
include_once $_SERVER['DOCUMENT_ROOT'] . '/bin/php/jsScripts.php';
|
||||
require_once "{$_SERVER['DOCUMENT_ROOT']}/bin/php/jsScripts.php";
|
||||
}
|
||||
# process response from actions
|
||||
if (isset($_SESSION['response'])) {
|
||||
$ar = json_decode($_SESSION['response']);
|
||||
$type = key($ar);
|
||||
$text = reset($ar);
|
||||
$title = $type;
|
||||
if ($type == 'error') {
|
||||
$type = 'danger';
|
||||
}
|
||||
unset($_SESSION['response']) ?>
|
||||
<script>
|
||||
var content = {};
|
||||
content.message = '<?php echo __($text) ?>';
|
||||
content.title = '<?php echo ucfirst($title) ?>';
|
||||
content.icon = "fa fa-bell";
|
||||
|
||||
$.notify(content, {
|
||||
type: '<?php echo $type ?>',
|
||||
placement: {
|
||||
from: 'top',
|
||||
align: 'right',
|
||||
},
|
||||
time: 10,
|
||||
delay: 3,
|
||||
});
|
||||
$('#multiple').select2({
|
||||
theme: "bootstrap"
|
||||
});
|
||||
</script>
|
||||
if (isset($_SESSION['response'])) { ?>
|
||||
<script src="/src/js/sentri/response.js"></script>
|
||||
<?php }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class pageNavbar
|
||||
<div class="row">
|
||||
<div class="col d-flex justify-content-start">
|
||||
<?php if ($this->breadCrumb) { ?>
|
||||
<div class="pb-2" id="breadCrumb"></div>
|
||||
<div class="pb-2" id="breadCrumb"> </div>
|
||||
<?php } elseif ($this->title) { ?>
|
||||
<h2 class="px-4 mb-0"><?php echo $this->title ?></h2>
|
||||
<?php } else { ?>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,9 +54,6 @@ $_SESSION['mfasetup']['secret'] = $secret;
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/src/images/favicon/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/src/images/favicon/site.webmanifest">
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="css/style.full.css" as="style">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -24,13 +24,9 @@ function addLoginAttempts()
|
||||
}
|
||||
|
||||
function setTimeZoneCookie()
|
||||
{
|
||||
?>
|
||||
<script>
|
||||
document.cookie = "user_timezone=" + Intl.DateTimeFormat().resolvedOptions().timeZone + "; path=/";
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
{ ?>
|
||||
<script src="/src/js/timeZoneCookie.js"></script>
|
||||
<?php }
|
||||
|
||||
function getUserTimezone()
|
||||
{
|
||||
|
||||
@@ -143,7 +143,7 @@ if (checkLoginAttempts() > 20) {
|
||||
<div class="card col-md-5 text-white bg-transparent py-5">
|
||||
<div class="card-body text-center pt-5">
|
||||
<div>
|
||||
<img class="pt-3 w-100 portal-image" alt="portal-image" src="/src/images/logo-login-dark.webp" width="500" height="265" style="height: auto;">
|
||||
<img class="pt-3 w-100 portal-image" alt="portal-image" src="" width="500" height="265" style="height: auto;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9314,3 +9314,59 @@ body .jvm-zoom-btn {
|
||||
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