new: [settings] Added setting and settingProvider functionality - WiP

pull/70/head
mokaddem 2021-07-19 15:00:09 +02:00
parent 51dd0434cd
commit dc5d54c30e
4 changed files with 493 additions and 0 deletions

View File

@ -101,4 +101,12 @@ class InstanceController extends AppController
$this->set('path', ['controller' => 'instance', 'action' => 'rollback']);
$this->render('/genericTemplates/confirm');
}
public function settings()
{
$this->Settings = $this->getTableLocator()->get('Settings');
$all = $this->Settings->getSettings();
$this->set('settingsProvider', $all['settingsProvider']);
$this->set('settings', $all['settings']);
}
}

View File

@ -0,0 +1,189 @@
<?php
namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\Validation\Validator;
class SettingsProviderTable extends AppTable
{
private $settingsConfiguration = [];
public function initialize(array $config): void
{
parent::initialize($config);
$this->settingsConfiguration = $this->generateSettingsConfiguration();
$this->setTable(false);
}
/**
* getSettingsConfiguration Return the setting configuration and merge existing settings into it if provided
*
* @param null|array $settings - Settings to be merged in the provided setting configuration
* @return array
*/
public function getSettingsConfiguration($settings = null) {
$settingConf = $this->settingsConfiguration;
if (!is_null($settings)) {
$settingConf = $this->mergeSettingsIntoSettingConfiguration($settingConf, $settings);
}
return $settingConf;
}
private function mergeSettingsIntoSettingConfiguration($settingConf, $settings)
{
foreach ($settingConf as $key => $value) {
if ($this->isLeaf($value)) {
if (isset($settings[$key])) {
$settingConf[$key]['value'] = $settings[$key];
}
} else {
$settingConf[$key] = $this->mergeSettingsIntoSettingConfiguration($value, $settings);
}
}
return $settingConf;
}
private function isLeaf($setting)
{
return !empty($setting['name']) && !empty($setting['type']);
}
/**
* Support up to 3 level:
* Application -> Network -> Proxy -> Proxy.URL
*
* Leave errorMessage empty to let the validator generate the error message
*/
private function generateSettingsConfiguration()
{
return [
'Application' => [
'General' => [
'Essentials' => [
'baseurl' => [
'description' => __('The base url of the application (in the format https://www.mymispinstance.com or https://myserver.com/misp). Several features depend on this setting being correctly set to function.'),
'errorMessage' => __('The currently set baseurl does not match the URL through which you have accessed the page. Disregard this if you are accessing the page via an alternate URL (for example via IP address).'),
'default' => '',
'name' => __('Base URL'),
'test' => 'testBaseURL',
'type' => 'string',
],
'uuid' => [
'description' => __('The Cerebrate instance UUID. This UUID is used to identify this instance.'),
'errorMessage' => __('No valid UUID set'),
'default' => '',
'name' => 'UUID',
'test' => 'testUuid',
'type' => 'string'
],
],
'Miscellaneous' => [
'to-del' => [
'description' => 'to del',
'errorMessage' => 'to del',
'default' => '',
'name' => 'To DEL',
'type' => 'string'
],
'to-del2' => [
'description' => 'to del',
'errorMessage' => 'to del',
'default' => '',
'name' => 'To DEL',
'type' => 'string'
],
'to-del3' => [
'description' => 'to del',
'errorMessage' => 'to del',
'default' => '',
'name' => 'To DEL',
'type' => 'string'
],
],
'floating-setting' => [
'description' => 'floaringSetting',
'errorMessage' => 'floaringSetting',
'default' => '',
'name' => 'Uncategorized Setting',
'type' => 'string'
],
],
'Network' => [
'Proxy' => [
'host' => [
'description' => __('The hostname of an HTTP proxy for outgoing sync requests. Leave empty to not use a proxy.'),
'default' => '',
'name' => __('Host'),
'test' => 'testHostname',
'type' => 'string',
],
'port' => [
'description' => __('The TCP port for the HTTP proxy.'),
'default' => '',
'name' => __('Port'),
'test' => 'testForRangeXY',
'type' => 'integer',
],
'user' => [
'description' => __('The authentication username for the HTTP proxy.'),
'default' => '',
'name' => __('User'),
'test' => 'testForEmpty',
'type' => 'string',
],
'password' => [
'description' => __('The authentication password for the HTTP proxy.'),
'default' => '',
'name' => __('Password'),
'test' => 'testForEmpty',
'type' => 'string',
],
],
'Proxy2' => [
'host' => [
'description' => __('The hostname of an HTTP proxy for outgoing sync requests. Leave empty to not use a proxy.'),
'default' => '',
'name' => __('Host'),
'test' => 'testHostname',
'type' => 'string',
],
'port' => [
'description' => __('The TCP port for the HTTP proxy.'),
'default' => '',
'name' => __('Port'),
'test' => 'testForRangeXY',
'type' => 'integer',
],
'user' => [
'description' => __('The authentication username for the HTTP proxy.'),
'default' => '',
'name' => __('User'),
'test' => 'testForEmpty',
'type' => 'string',
],
'password' => [
'description' => __('The authentication password for the HTTP proxy.'),
'default' => '',
'name' => __('Password'),
'test' => 'testForEmpty',
'type' => 'string',
],
],
],
'UI' => [
'dark' => [
'description' => __('Enable the dark theme of the application'),
'default' => false,
'name' => __('Dark theme'),
'type' => 'boolean',
],
],
],
'Features' => [
],
'Security' => [
],
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\Core\Configure;
use Cake\ORM\TableRegistry;
class SettingsTable extends AppTable
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable(false);
$this->SettingsProvider = TableRegistry::getTableLocator()->get('SettingsProvider');
}
public function getSettings(): array
{
$settings = Configure::read()['Cerebrate'];
$settingsProvider = $this->SettingsProvider->getSettingsConfiguration($settings);
return [
'settings' => $settings,
'settingsProvider' => $settingsProvider
];
}
}

View File

@ -0,0 +1,268 @@
<?php
// debug($settings);
// debug($settingsProvider);
$settingTable = genLevel0($settingsProvider, $this);
?>
<div class="px-5">
<div class="mb-3">
<input class="form-control" type="text" id="search" placeholder="<?= __('Search settings') ?>" aria-describedby="<?= __('Search setting input') ?>">
</div>
<?= $settingTable; ?>
</div>
<?php
function genLevel0($settingsProvider, $appView)
{
$content0 = [];
$level0 = array_keys($settingsProvider);
foreach ($settingsProvider as $level1Name => $level1Setting) {
if (!empty($level1Setting)) {
$content0[] = genLevel1($level1Setting, $appView);
} else {
$content0[] = __('No Settings available yet');
}
}
$tabsOptions0 = [
// 'vertical' => true,
// 'vertical-size' => 2,
'card' => false,
'pills' => false,
'justify' => 'center',
'content-class' => ['mt-2'],
'data' => [
'navs' => $level0,
'content' => $content0
]
];
$table0 = $appView->Bootstrap->tabs($tabsOptions0);
return $table0;
}
function genLevel1($level1Setting, $appView)
{
$content1 = [];
$nav1 = [];
foreach ($level1Setting as $level2Name => $level2Setting) {
if (!empty($level2Setting)) {
$content1[] = genLevel2($level2Name, $level2Setting, $appView);
} else {
$content1[] = '';
}
$nav1[$level2Name] = array_filter( // only show grouped settings
array_keys($level2Setting),
function ($settingGroupName) use ($level2Setting) {
return !isLeaf($level2Setting[$settingGroupName]);
}
);
}
$contentHtml = implode('', $content1);
$scrollspyNav = genScrollspyNav($nav1);
$mainPanelHeight = 'calc(100vh - 8px - 42px - 1rem - 56px - 38px - 1rem)';
$container = '<div class="d-flex">';
$container .= "<div class=\"\" style=\"flex: 0 0 10em;\">{$scrollspyNav}</div>";
$container .= "<div data-spy=\"scroll\" data-target=\"#navbar-scrollspy-setting\" data-offset=\"24\" style=\"height: {$mainPanelHeight}\" class=\"p-3 overflow-auto position-relative flex-grow-1\">{$contentHtml}</div>";
$container .= '</div>';
return $container;
}
function genLevel2($level2Name, $level2Setting, $appView)
{
foreach ($level2Setting as $level3Name => $level3Setting) {
if (!empty($level3Setting)) {
$level3 = genLevel3($level2Name, $level3Name, $level3Setting, $appView);
$content2[] = sprintf('<div id="%s">%s</div>', sprintf('sp-%s', h($level2Name)), $level3);
} else {
$content2[] = '';
}
}
return implode('', $content2);
}
function genLevel3($level2Name, $settingGroupName, $setting, $appView)
{
$settingGroup = '';
if (isLeaf($setting)) {
$tmp = genSingleSetting($settingGroupName, $setting, $appView);
$settingGroup = "<div>{$tmp}</div>";
} else {
$tmpID = sprintf('sp-%s-%s', h($level2Name), h($settingGroupName));
$settingGroup .= sprintf('<h4 id="%s"><a class="text-reset text-decoration-none" href="#%s">%s</a></h4>', $tmpID, $tmpID, h($settingGroupName));
foreach ($setting as $singleSettingName => $singleSetting) {
$tmp = genSingleSetting($singleSettingName, $singleSetting, $appView);
$settingGroup .= sprintf('<div class="ml-3">%s</div>', $tmp);
}
$settingGroup = $appView->Bootstrap->genNode('div', [
'class' => ['shadow', 'p-2', 'mb-4', 'rounded', ($appView->get('darkMode') ? 'bg-dark' : 'bg-light')],
], $settingGroup);
}
return $settingGroup;
}
function genSingleSetting($settingName, $setting, $appView)
{
$label = $appView->Bootstrap->genNode('label', [
'class' => ['font-weight-bolder', 'mb-0'],
'for' => $settingName
], h($setting['name']));
$description = '';
if (!empty($setting['description'])) {
$description = $appView->Bootstrap->genNode('small', [
'class' => ['form-text', 'text-muted', 'mt-0'],
'id' => "{$settingName}Help"
], h($setting['description']));
}
$inputGroup = '';
if (empty($setting['type'])) {
$setting['type'] = 'string';
}
if ($setting['type'] == 'string') {
$input = genInputString($settingName, $setting, $appView);
} elseif ($setting['type'] == 'boolean') {
$input = genInputCheckbox($settingName, $setting, $appView);
$description = '';
} elseif ($setting['type'] == 'integer') {
$input = genInputInteger($settingName, $setting, $appView);
} elseif ($setting['type'] == 'select') {
$input = genInputSelect($settingName, $setting, $appView);
} elseif ($setting['type'] == 'multi-select') {
$input = genInputMultiSelect($settingName, $setting, $appView);
} else {
$input = genInputString($settingName, $setting, $appView);
}
$container = $appView->Bootstrap->genNode('div', [
'class' => ['form-group', 'mb-2']
], implode('', [$label, $input, $description]));
return $container;
}
function genInputString($settingName, $setting, $appView)
{
return $appView->Bootstrap->genNode('input', [
'class' => [
'form-control'
],
'type' => 'text',
'id' => $settingName,
'value' => isset($setting['value']) ? $setting['value'] : "",
'aria-describedby' => "{$settingName}Help"
]);
}
function genInputCheckbox($settingName, $setting, $appView)
{
$switch = $appView->Bootstrap->genNode('input', [
'class' => [
'custom-control-input'
],
'type' => 'checkbox',
'value' => !empty($setting['value']) ? 1 : 0,
'checked' => !empty($setting['value']) ? 'checked' : '',
'id' => $settingName,
]);
$label = $appView->Bootstrap->genNode('label', [
'class' => [
'custom-control-label'
],
'for' => $settingName,
], h($setting['description']));
$container = $appView->Bootstrap->genNode('div', [
'class' => [
'custom-control',
'custom-switch',
],
], implode('', [$switch, $label]));
return $container;
}
function genInputInteger($settingName, $setting, $appView)
{
return $appView->Bootstrap->genNode('input', [
'class' => [
'form-control'
],
'params' => [
'type' => 'integer',
'id' => $settingName,
'aria-describedby' => "{$settingName}Help"
]
]);
}
function genInputSelect($settingName, $setting, $appView)
{
}
function genInputMultiSelect($settingName, $setting, $appView)
{
}
function genScrollspyNav($nav1)
{
$nav = '<nav id="navbar-scrollspy-setting" class="navbar">';
$nav .= '<nav class="nav nav-pills flex-column">';
foreach ($nav1 as $group => $sections) {
$nav .= sprintf('<a class="nav-link main-group text-reset p-1" href="#%s">%s</a>', sprintf('sp-%s', h($group)), h($group));
$nav .= sprintf('<nav class="nav nav-pills sub-group collapse flex-column" data-maingroup="%s">', sprintf('sp-%s', h($group)));
foreach ($sections as $section) {
$nav .= sprintf('<a class="nav-link nav-link-group text-reset ml-3 my-1 p-1" href="#%s">%s</a>', sprintf('sp-%s-%s', h($group), h($section)), h($section));
}
$nav .= '</nav>';
}
$nav .= '</nav>';
$nav .= '</nav>';
return $nav;
}
function isLeaf($setting)
{
return !empty($setting['name']) && !empty($setting['type']);
}
?>
<script>
$(document).ready(function() {
$('[data-spy="scroll"]').on('activate.bs.scrollspy', function(evt, {relatedTarget}) {
const $associatedLink = $(`#navbar-scrollspy-setting nav.nav-pills .nav-link[href="${relatedTarget}"]`)
let $associatedNav
if ($associatedLink.hasClass('main-group')) {
$associatedNav = $associatedLink.next()
} else {
$associatedNav = $associatedLink.parent()
}
const $allNavs = $('#navbar-scrollspy-setting nav.nav-pills.sub-group')
$allNavs.removeClass('group-active').hide()
$associatedNav.addClass('group-active').show()
})
})
</script>
<style>
#navbar-scrollspy-setting nav.nav-pills .nav-link {
background-color: unset !important;
color: black;
display: block;
}
#navbar-scrollspy-setting nav.nav-pills .nav-link:not(.main-group).active {
color: #007bff !important;
font-weight: bold;
}
#navbar-scrollspy-setting nav.nav-pills .nav-link.main-group:before {
margin-right: 0.25em;
font-family: 'Font Awesome 5 Free';
font-weight: 900;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
}
#navbar-scrollspy-setting nav.nav-pills .nav-link.main-group.active:before {
content: "\f0d7";
}
#navbar-scrollspy-setting nav.nav-pills .nav-link.main-group:before {
content: "\f0da";
}
</style>