2021-07-19 15:00:09 +02:00
|
|
|
<?php
|
2021-07-30 08:18:30 +02:00
|
|
|
|
2021-07-21 11:18:06 +02:00
|
|
|
$variantFromSeverity = [
|
|
|
|
'critical' => 'danger',
|
|
|
|
'warning' => 'warning',
|
|
|
|
'info' => 'info',
|
|
|
|
];
|
|
|
|
$this->set('variantFromSeverity', $variantFromSeverity);
|
2021-07-26 11:16:52 +02:00
|
|
|
$settingTable = genNavcard($settingsProvider, $this);
|
2021-07-19 15:00:09 +02:00
|
|
|
?>
|
2021-07-26 11:16:52 +02:00
|
|
|
|
|
|
|
<script>
|
|
|
|
const variantFromSeverity = <?= json_encode($variantFromSeverity) ?>;
|
|
|
|
const settingsFlattened = <?= json_encode($settingsFlattened) ?>;
|
|
|
|
</script>
|
|
|
|
|
2021-07-19 15:00:09 +02:00
|
|
|
<div class="px-5">
|
|
|
|
<div class="mb-3">
|
2021-07-26 11:16:52 +02:00
|
|
|
<?=
|
|
|
|
$this->element('Settings/search', [
|
|
|
|
]);
|
|
|
|
?>
|
2021-07-19 15:00:09 +02:00
|
|
|
</div>
|
|
|
|
<?= $settingTable; ?>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<?php
|
2021-07-26 11:16:52 +02:00
|
|
|
function genNavcard($settingsProvider, $appView)
|
2021-07-19 15:00:09 +02:00
|
|
|
{
|
2021-07-26 11:16:52 +02:00
|
|
|
$cardContent = [];
|
|
|
|
$cardNavs = array_keys($settingsProvider);
|
|
|
|
foreach ($settingsProvider as $navName => $sectionSettings) {
|
|
|
|
if (!empty($sectionSettings)) {
|
|
|
|
$cardContent[] = genContentForNav($sectionSettings, $appView);
|
2021-07-19 15:00:09 +02:00
|
|
|
} else {
|
2021-07-26 11:16:52 +02:00
|
|
|
$cardContent[] = __('No Settings available yet');
|
2021-07-19 15:00:09 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-26 11:16:52 +02:00
|
|
|
array_unshift($cardNavs, __('Settings Diagnostic'));
|
|
|
|
$notice = $appView->element('Settings/notice', [
|
|
|
|
'variantFromSeverity' => $appView->get('variantFromSeverity'),
|
|
|
|
]);
|
|
|
|
array_unshift($cardContent, $notice);
|
2021-07-19 15:00:09 +02:00
|
|
|
$tabsOptions0 = [
|
|
|
|
// 'vertical' => true,
|
|
|
|
// 'vertical-size' => 2,
|
|
|
|
'card' => false,
|
|
|
|
'pills' => false,
|
|
|
|
'justify' => 'center',
|
2021-07-21 11:18:06 +02:00
|
|
|
'nav-class' => ['settings-tabs'],
|
2021-07-19 15:00:09 +02:00
|
|
|
'data' => [
|
2021-07-26 11:16:52 +02:00
|
|
|
'navs' => $cardNavs,
|
|
|
|
'content' => $cardContent
|
2021-07-19 15:00:09 +02:00
|
|
|
]
|
|
|
|
];
|
|
|
|
$table0 = $appView->Bootstrap->tabs($tabsOptions0);
|
|
|
|
return $table0;
|
|
|
|
}
|
|
|
|
|
2021-07-26 11:16:52 +02:00
|
|
|
function genContentForNav($sectionSettings, $appView)
|
2021-07-19 15:00:09 +02:00
|
|
|
{
|
2021-07-26 11:16:52 +02:00
|
|
|
$groupedContent = [];
|
|
|
|
$groupedSetting = [];
|
|
|
|
foreach ($sectionSettings as $sectionName => $subSectionSettings) {
|
|
|
|
if (!empty($subSectionSettings)) {
|
|
|
|
$groupedContent[] = genSection($sectionName, $subSectionSettings, $appView);
|
2021-07-19 15:00:09 +02:00
|
|
|
} else {
|
2021-07-26 11:16:52 +02:00
|
|
|
$groupedContent[] = '';
|
2021-07-19 15:00:09 +02:00
|
|
|
}
|
2021-07-27 13:40:24 +02:00
|
|
|
if (!isLeaf($subSectionSettings)) {
|
|
|
|
$groupedSetting[$sectionName] = array_filter( // only show grouped settings
|
|
|
|
array_keys($subSectionSettings),
|
|
|
|
function ($settingGroupName) use ($subSectionSettings) {
|
|
|
|
return !isLeaf($subSectionSettings[$settingGroupName]) && !empty($subSectionSettings[$settingGroupName]);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2021-07-19 15:00:09 +02:00
|
|
|
}
|
2021-07-26 11:16:52 +02:00
|
|
|
$contentHtml = implode('', $groupedContent);
|
|
|
|
$scrollspyNav = $appView->element('Settings/scrollspyNav', [
|
|
|
|
'groupedSetting' => $groupedSetting
|
|
|
|
]);
|
2021-07-22 15:51:06 +02:00
|
|
|
$mainPanelHeight = 'calc(100vh - 42px - 1rem - 56px - 38px - 1rem)';
|
2021-07-19 15:00:09 +02:00
|
|
|
$container = '<div class="d-flex">';
|
|
|
|
$container .= "<div class=\"\" style=\"flex: 0 0 10em;\">{$scrollspyNav}</div>";
|
2021-07-30 09:23:19 +02:00
|
|
|
$container .= "<div data-spy=\"scroll\" data-target=\"#navbar-scrollspy-setting\" data-offset=\"25\" style=\"height: {$mainPanelHeight}\" class=\"p-3 overflow-auto position-relative flex-grow-1\">{$contentHtml}</div>";
|
2021-07-19 15:00:09 +02:00
|
|
|
$container .= '</div>';
|
|
|
|
return $container;
|
|
|
|
}
|
|
|
|
|
2021-07-26 11:16:52 +02:00
|
|
|
function genSection($sectionName, $subSectionSettings, $appView)
|
2021-07-19 15:00:09 +02:00
|
|
|
{
|
2021-07-26 11:16:52 +02:00
|
|
|
$sectionContent = [];
|
2021-07-30 08:18:30 +02:00
|
|
|
$sectionContent[] = '<div>';
|
2021-07-27 13:40:24 +02:00
|
|
|
if (isLeaf($subSectionSettings)) {
|
|
|
|
$panelHTML = $appView->element('Settings/panel', [
|
|
|
|
'sectionName' => $sectionName,
|
|
|
|
'panelName' => $sectionName,
|
|
|
|
'panelSettings' => $subSectionSettings,
|
|
|
|
]);
|
|
|
|
$sectionContent[] = $panelHTML;
|
|
|
|
} else {
|
2021-07-30 14:50:10 +02:00
|
|
|
if (count($subSectionSettings) > 1) {
|
|
|
|
$sectionContent[] = sprintf('<h2 id="%s">%s</h2>', getResolvableID($sectionName), h($sectionName));
|
|
|
|
}
|
2021-07-27 13:40:24 +02:00
|
|
|
foreach ($subSectionSettings as $panelName => $panelSettings) {
|
|
|
|
if (!empty($panelSettings)) {
|
|
|
|
$panelHTML = $appView->element('Settings/panel', [
|
|
|
|
'sectionName' => $sectionName,
|
|
|
|
'panelName' => $panelName,
|
|
|
|
'panelSettings' => $panelSettings,
|
|
|
|
]);
|
|
|
|
$sectionContent[] = $panelHTML;
|
|
|
|
} else {
|
|
|
|
$sectionContent[] = '';
|
|
|
|
}
|
2021-07-19 15:00:09 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-26 11:16:52 +02:00
|
|
|
$sectionContent[] = '</div>';
|
|
|
|
return implode('', $sectionContent);
|
2021-07-19 15:00:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function isLeaf($setting)
|
|
|
|
{
|
|
|
|
return !empty($setting['name']) && !empty($setting['type']);
|
|
|
|
}
|
2021-07-30 08:18:30 +02:00
|
|
|
|
|
|
|
function getResolvableID($sectionName, $panelName=false)
|
|
|
|
{
|
2021-07-30 14:50:10 +02:00
|
|
|
$id = sprintf('sp-%s', preg_replace('/(\.|\W)/', '_', h($sectionName)));
|
2021-07-30 08:18:30 +02:00
|
|
|
if (!empty($panelName)) {
|
2021-07-30 14:50:10 +02:00
|
|
|
$id .= '-' . preg_replace('/(\.|\W)/', '_', h($panelName));
|
2021-07-30 08:18:30 +02:00
|
|
|
}
|
|
|
|
return $id;
|
|
|
|
}
|
2021-07-19 15:00:09 +02:00
|
|
|
?>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
$(document).ready(function() {
|
2021-07-26 11:16:52 +02:00
|
|
|
$('.depends-on-icon').tooltip({
|
|
|
|
placement: 'right',
|
2021-07-19 15:00:09 +02:00
|
|
|
})
|
2021-07-30 14:50:40 +02:00
|
|
|
$('select.custom-select[multiple]').select2()
|
2021-07-21 11:18:06 +02:00
|
|
|
|
|
|
|
$('.settings-tabs a[data-toggle="tab"]').on('shown.bs.tab', function (event) {
|
|
|
|
$('[data-spy="scroll"]').trigger('scroll.bs.scrollspy')
|
|
|
|
})
|
|
|
|
|
2021-07-22 16:59:26 +02:00
|
|
|
$('.tab-content input, .tab-content select').on('input', function() {
|
2021-07-22 15:51:06 +02:00
|
|
|
if ($(this).attr('type') == 'checkbox') {
|
|
|
|
const $input = $(this)
|
|
|
|
const $inputGroup = $(this).closest('.form-group')
|
|
|
|
const settingName = $(this).data('setting-name')
|
2021-07-27 10:58:34 +02:00
|
|
|
const settingValue = $(this).is(':checked') ? 1 : 0
|
2021-07-22 15:51:06 +02:00
|
|
|
saveSetting($inputGroup[0], $input, settingName, settingValue)
|
|
|
|
} else {
|
|
|
|
handleSettingValueChange($(this))
|
|
|
|
}
|
2021-07-21 16:07:30 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
$('.tab-content .input-group-actions .btn-save-setting').click(function() {
|
2021-07-22 16:59:26 +02:00
|
|
|
const $input = $(this).closest('.input-group').find('input, select')
|
2021-07-21 16:07:30 +02:00
|
|
|
const settingName = $input.data('setting-name')
|
|
|
|
const settingValue = $input.val()
|
2021-07-22 15:51:06 +02:00
|
|
|
saveSetting(this, $input, settingName, settingValue)
|
2021-07-21 16:07:30 +02:00
|
|
|
})
|
|
|
|
$('.tab-content .input-group-actions .btn-reset-setting').click(function() {
|
|
|
|
const $btn = $(this)
|
2021-07-22 16:59:26 +02:00
|
|
|
const $input = $btn.closest('.input-group').find('input, select')
|
|
|
|
let oldValue = settingsFlattened[$input.data('setting-name')].value
|
|
|
|
if ($input.is('select')) {
|
|
|
|
oldValue = oldValue !== undefined ? oldValue : -1
|
2021-07-23 14:51:48 +02:00
|
|
|
} else {
|
|
|
|
oldValue = oldValue !== undefined ? oldValue : ''
|
2021-07-22 16:59:26 +02:00
|
|
|
}
|
2021-07-21 16:07:30 +02:00
|
|
|
$input.val(oldValue)
|
|
|
|
handleSettingValueChange($input)
|
|
|
|
})
|
2021-07-30 09:23:19 +02:00
|
|
|
|
2021-07-30 11:44:53 +02:00
|
|
|
const referencedID = window.location.hash
|
|
|
|
redirectToSetting(referencedID)
|
2021-07-19 15:00:09 +02:00
|
|
|
})
|
|
|
|
|
2021-07-22 15:51:06 +02:00
|
|
|
function saveSetting(statusNode, $input, settingName, settingValue) {
|
|
|
|
const url = '/instance/saveSetting/'
|
|
|
|
const data = {
|
|
|
|
name: settingName,
|
|
|
|
value: settingValue,
|
|
|
|
}
|
|
|
|
const APIOptions = {
|
|
|
|
statusNode: statusNode,
|
|
|
|
}
|
|
|
|
AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((result) => {
|
|
|
|
settingsFlattened[settingName] = result.data
|
|
|
|
if ($input.attr('type') == 'checkbox') {
|
|
|
|
$input.prop('checked', result.data.value)
|
|
|
|
} else {
|
|
|
|
$input.val(result.data.value)
|
|
|
|
}
|
|
|
|
handleSettingValueChange($input)
|
2021-07-27 10:40:58 +02:00
|
|
|
}).catch((e) => {})
|
2021-07-22 15:51:06 +02:00
|
|
|
}
|
|
|
|
|
2021-07-21 16:07:30 +02:00
|
|
|
function handleSettingValueChange($input) {
|
|
|
|
const oldValue = settingsFlattened[$input.data('setting-name')].value
|
2021-07-22 15:51:06 +02:00
|
|
|
const newValue = ($input.attr('type') == 'checkbox' ? $input.is(':checked') : $input.val())
|
2021-07-23 14:51:48 +02:00
|
|
|
if (newValue == oldValue || (newValue == '' && oldValue == undefined)) {
|
2021-07-21 16:07:30 +02:00
|
|
|
restoreWarnings($input)
|
|
|
|
} else {
|
|
|
|
removeWarnings($input)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeWarnings($input) {
|
|
|
|
const $inputGroup = $input.closest('.input-group')
|
|
|
|
const $inputGroupAppend = $inputGroup.find('.input-group-append')
|
|
|
|
const $saveButton = $inputGroup.find('button.btn-save-setting')
|
2021-07-27 10:40:58 +02:00
|
|
|
$input.removeClass(['is-invalid', 'border-warning', 'border-danger', 'border-info', 'warning', 'info'])
|
2021-07-21 16:07:30 +02:00
|
|
|
$inputGroupAppend.removeClass('d-none')
|
2021-07-22 16:59:26 +02:00
|
|
|
if ($input.is('select') && $input.find('option:selected').data('is-empty-option') == 1) {
|
|
|
|
$inputGroupAppend.addClass('d-none') // hide save button if empty selection picked
|
|
|
|
}
|
2021-07-21 16:07:30 +02:00
|
|
|
$inputGroup.parent().find('.invalid-feedback').removeClass('d-block')
|
|
|
|
}
|
|
|
|
|
|
|
|
function restoreWarnings($input) {
|
|
|
|
const $inputGroup = $input.closest('.input-group')
|
|
|
|
const $inputGroupAppend = $inputGroup.find('.input-group-append')
|
|
|
|
const $saveButton = $inputGroup.find('button.btn-save-setting')
|
|
|
|
const setting = settingsFlattened[$input.data('setting-name')]
|
|
|
|
if (setting.error) {
|
|
|
|
borderVariant = setting.severity !== undefined ? variantFromSeverity[setting.severity] : 'warning'
|
2021-07-27 10:40:58 +02:00
|
|
|
$input.addClass(['is-invalid', `border-${borderVariant}`, borderVariant])
|
2021-07-21 16:07:30 +02:00
|
|
|
if (setting.severity == 'warning') {
|
|
|
|
$input.addClass('warning')
|
|
|
|
}
|
|
|
|
$inputGroup.parent().find('.invalid-feedback').addClass('d-block').text(setting.errorMessage)
|
2021-07-22 15:51:06 +02:00
|
|
|
} else {
|
|
|
|
removeWarnings($input)
|
2021-07-21 16:07:30 +02:00
|
|
|
}
|
|
|
|
const $callout = $input.closest('.settings-group')
|
|
|
|
updateCalloutColors($callout)
|
|
|
|
$inputGroupAppend.addClass('d-none')
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateCalloutColors($callout) {
|
2021-07-22 15:51:06 +02:00
|
|
|
if ($callout.length == 0) {
|
|
|
|
return
|
|
|
|
}
|
2021-07-23 12:03:03 +02:00
|
|
|
const $settings = $callout.find('input, select')
|
2021-07-21 16:07:30 +02:00
|
|
|
const settingNames = Array.from($settings).map((i) => {
|
|
|
|
return $(i).data('setting-name')
|
|
|
|
})
|
|
|
|
const severityMapping = {null: 0, info: 1, warning: 2, critical: 3}
|
|
|
|
const severityMappingInverted = Object.assign({}, ...Object.entries(severityMapping).map(([k, v]) => ({[v]: k})))
|
|
|
|
let highestSeverity = severityMapping[null]
|
|
|
|
settingNames.forEach(name => {
|
|
|
|
if (settingsFlattened[name].error) {
|
|
|
|
highestSeverity = severityMapping[settingsFlattened[name].severity] > highestSeverity ? severityMapping[settingsFlattened[name].severity] : highestSeverity
|
|
|
|
}
|
|
|
|
});
|
|
|
|
highestSeverity = severityMappingInverted[highestSeverity]
|
|
|
|
$callout.removeClass(['callout', 'callout-danger', 'callout-warning', 'callout-info'])
|
|
|
|
if (highestSeverity !== null) {
|
|
|
|
$callout.addClass(['callout', `callout-${variantFromSeverity[highestSeverity]}`])
|
|
|
|
}
|
|
|
|
}
|
2021-07-30 09:23:19 +02:00
|
|
|
|
2021-07-30 11:44:53 +02:00
|
|
|
function redirectToSetting(referencedID) {
|
2021-07-30 09:23:19 +02:00
|
|
|
const $settingToFocus = $(referencedID)
|
|
|
|
const pageNavID = $(referencedID).closest('.tab-pane').attr('aria-labelledby')
|
|
|
|
const $navController = $(`#${pageNavID}`)
|
|
|
|
$navController
|
|
|
|
.on('shown.bs.tab.after-redirect', () => {
|
|
|
|
$settingToFocus[0].scrollIntoView()
|
|
|
|
const inputID = $settingToFocus.parent().attr('for')
|
|
|
|
$settingToFocus.closest('.form-group').find(`#${inputID}`).focus()
|
|
|
|
$navController.off('shown.bs.tab.after-redirect')
|
|
|
|
})
|
|
|
|
.tab('show')
|
|
|
|
}
|
2021-07-19 15:00:09 +02:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<style>
|
2021-07-22 16:59:26 +02:00
|
|
|
.input-group-actions {
|
|
|
|
z-index: 5;
|
|
|
|
}
|
|
|
|
a.btn-reset-setting {
|
|
|
|
left: -1.25em;
|
|
|
|
}
|
|
|
|
.custom-select ~ div > a.btn-reset-setting {
|
|
|
|
left: -2.5em;
|
|
|
|
}
|
2021-07-26 11:16:52 +02:00
|
|
|
.form-control[type="number"] ~ div > a.btn-reset-setting {
|
2021-07-23 14:51:48 +02:00
|
|
|
left: -3em;
|
|
|
|
}
|
2021-07-30 14:50:40 +02:00
|
|
|
select.custom-select[multiple][data-setting-name] ~ span.select2-container{
|
|
|
|
min-width: unset;
|
|
|
|
}
|
|
|
|
span.select2-container--open {
|
|
|
|
min-width: unset;
|
|
|
|
}
|
2021-07-19 15:00:09 +02:00
|
|
|
</style>
|