wip: initial connectors

connector
iglocska 2021-04-30 23:59:53 +02:00
parent dd30519e73
commit 2d4727770c
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
25 changed files with 923 additions and 10 deletions

View File

@ -186,6 +186,21 @@ CREATE TABLE `individuals` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `local_tools`
--
DROP TABLE IF EXISTS `local_tools`;
CREATE TABLE `local_tools` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`connector` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`settings` text COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`id`),
KEY `name` (`name`),
KEY `connector` (`connector`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- Table structure for table `organisation_encryption_keys`
--

View File

@ -135,7 +135,7 @@ class Application extends BaseApplication implements AuthenticationServiceProvid
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'fields' => $fields,
'loginUrl' => '/users/login'
'loginUrl' => \Cake\Routing\Router::url('/users/login')
]);
// Load identifiers

View File

@ -99,7 +99,7 @@ class AppController extends Controller
if (!empty($user['disabled'])) {
$this->Authentication->logout();
$this->Flash->error(__('The user account is disabled.'));
return $this->redirect(['controller' => 'Users', 'action' => 'login']);
return $this->redirect(\Cake\Routing\Router::url('/users/login'));
}
unset($user['password']);
$this->ACL->setUser($user);
@ -112,11 +112,6 @@ class AppController extends Controller
$this->set('ajax', $this->request->is('ajax'));
$this->request->getParam('prefix');
$this->set('darkMode', !empty(Configure::read('Cerebrate.dark')));
if (!empty(Configure::read('baseurl'))) {
Configure::write('App.fullBaseUrl', Configure::read('baseurl'));
} else if (!empty(env('CEREBRATE_BASEURL'))) {
Configure::write('App.fullBaseUrl', env('CEREBRATE_BASEURL'));
}
$this->set('baseurl', Configure::read('App.fullBaseUrl'));
}

View File

@ -45,10 +45,24 @@ class CRUDComponent extends Component
}
if ($this->Controller->ParamHandler->isRest()) {
$data = $query->all();
if (isset($options['afterFind'])) {
if (is_callable($options['afterFind'])) {
$data = $options['afterFind']($data);
} else {
$data = $this->Table->{$options['afterFind']}($data);
}
}
$this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json');
} else {
$this->Controller->loadComponent('Paginator');
$data = $this->Controller->Paginator->paginate($query);
if (isset($options['afterFind'])) {
if (is_callable($options['afterFind'])) {
$data = $options['afterFind']($data);
} else {
$data = $this->Table->{$options['afterFind']}($data);
}
}
if (!empty($options['contextFilters'])) {
$this->setFilteringContext($options['contextFilters'], $params);
}
@ -63,7 +77,7 @@ class CRUDComponent extends Component
$this->Controller->viewBuilder()->setLayout('ajax');
$this->Controller->render('/genericTemplates/filters');
}
/**
* getResponsePayload Returns the adaquate response payload based on the request context
*

View File

@ -0,0 +1,122 @@
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Utility\Hash;
use Cake\Utility\Text;
use \Cake\Database\Expression\QueryExpression;
class LocalToolsController extends AppController
{
public function index()
{
$data = $this->LocalTools->extractMeta($this->LocalTools->getConnectors(), true);
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->viewData($data, 'json');
}
$data = $this->CustomPagination->paginate($data);
$this->set('data', $data);
if ($this->request->is('ajax')) {
$this->viewBuilder()->disableAutoLayout();
}
$this->set('metaGroup', 'LocalTools');
}
public function connectorIndex()
{
$this->CRUD->index([
'filters' => ['name', 'connector'],
'quickFilters' => ['name', 'connector'],
'afterFind' => function($data) {
foreach ($data as $connector) {
$connector['health'] = [$this->LocalTools->healthCheckIndividual($connector)];
}
return $data;
}
]);
if ($this->ParamHandler->isRest()) {
return $this->restResponsePayload;
}
$this->set('metaGroup', 'LocalTools');
}
public function action()
{
$params = [];
$results = $this->LocalTools->runAction();
$this->render('add');
}
public function add()
{
$this->CRUD->add();
if ($this->ParamHandler->isRest()) {
return $this->restResponsePayload;
}
$connectors = $this->LocalTools->extractMeta($this->LocalTools->getConnectors());
$dropdownData = ['connectors' => []];
foreach ($connectors as $connector) {
$dropdownData['connectors'][$connector['connector']] = $connector['name'];
}
$this->set(compact('dropdownData'));
$this->set('metaGroup', 'LocalTools');
}
public function viewConnector($connector_name)
{
$connectors = $this->LocalTools->extractMeta($this->LocalTools->getConnectors());
$connector = false;
foreach ($connectors as $c) {
if ($connector === false || version_compare($c['version'], $connectors['version']) > 0) {
$connector = $c;
}
}
if ($this->ParamHandler->isRest()) {
$this->restResponsePayload = $this->Controller->RestResponse->viewData($connector, 'json');
}
$this->set('entity', $connector);
$this->set('metaGroup', 'LocalTools');
}
public function edit($id)
{
$this->CRUD->edit($id);
if ($this->ParamHandler->isRest()) {
return $this->restResponsePayload;
}
$connectors = $this->LocalTools->extractMeta($this->LocalTools->getConnectors());
$dropdownData = ['connectors' => []];
foreach ($connectors as $connector) {
$dropdownData['connectors'][$connector['connector']] = $connector['name'];
}
$this->set(compact('dropdownData'));
$this->set('metaGroup', 'LocalTools');
$this->render('add');
}
public function delete($id)
{
$this->CRUD->delete($id);
if ($this->ParamHandler->isRest()) {
return $this->restResponsePayload;
}
$this->set('metaGroup', 'LocalTools');
}
public function view($id)
{
$this->CRUD->view($id);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
$this->set('metaGroup', 'LocalTools');
}
public function test()
{
$connectors = $this->LocalTools->getConnectors();
$connectors['MispConnector']->test();
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Controller\Open;
use App\Controller\AppController;
use Cake\Utility\Hash;
use Cake\Utility\Text;
use \Cake\Database\Expression\QueryExpression;
use Cake\Http\Exception\NotFoundException;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Http\Exception\ForbiddenException;
use Cake\Event\EventInterface;
class IndividualsController extends AppController
{
public function beforeFilter(EventInterface $event)
{
parent::beforeFilter($event);
$this->Authentication->allowUnauthenticated(['index']);
}
public function index()
{
$this->CRUD->index([
'filters' => ['uuid', 'email', 'first_name', 'last_name', 'position', 'Organisations.id'],
'quickFilters' => ['uuid', 'email', 'first_name', 'last_name', 'position'],
'contain' => ['Alignments' => 'Organisations']
]);
if ($this->ParamHandler->isRest()) {
return $this->restResponsePayload;
}
$this->set('alignmentScope', 'organisations');
$this->set('metaGroup', 'Public');
}
}

View File

@ -131,7 +131,7 @@ class UsersController extends AppController
if ($result->isValid()) {
$this->Authentication->logout();
$this->Flash->success(__('Goodbye.'));
return $this->redirect(['controller' => 'Users', 'action' => 'login']);
return $this->redirect(\Cake\Routing\Router::url('/users/login'));
}
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace CommonConnectorTools;
class CommonConnectorTools
{
public $description = '';
public $name = '';
public $exposedFunctions = [
'diagnostics'
];
public $version = '???';
public function addExposedFunction(string $functionName): void
{
$this->exposedFunctions[] = $functionName;
}
public function runAction($action, $params) {
if (!in_array($action, $exposedFunctions)) {
throw new MethodNotAllowedException(__('Invalid connector function called.'));
}
return $this->{$action}($params);
}
public function health(Object $connection): array
{
return 0;
}
}
?>

View File

@ -0,0 +1,127 @@
<?php
namespace MispConnector;
require_once(ROOT . '/src/Lib/default/local_tool_connectors/CommonConnectorTools.php');
use CommonConnectorTools\CommonConnectorTools;
use Cake\Http\Client;
class MispConnector extends CommonConnectorTools
{
public $description = 'MISP connector, handling diagnostics, organisation and sharing group management of your instance. Synchronisation requests can also be managed through the connector.';
public $name = 'MISP';
public $exposedFunctions = [
'diagnostics',
'viewOrgAction'
];
public $version = '0.1';
public function addExposedFunction(string $functionName): void
{
$this->exposedFunctions[] = $functionName;
}
public function health(Object $connection): array
{
$settings = json_decode($connection->settings, true);
$http = new Client();
$response = $http->post($settings['url'] . '/users/view/me.json', '{}', [
'headers' => [
'AUTHORIZATION' => $settings['authkey'],
'Accept' => 'Application/json',
'Content-type' => 'Application/json'
]
]);
$responseCode = $response->getStatusCode();
if ($response->isOk()) {
$status = 1;
$message = __('OK');
} else if ($responseCode == 403){
$status = 3;
$message = __('Unauthorized');
} else {
$status = 0;
$message = __('Something went wrong.');
}
return [
'status' => $status,
'message' => $message
];
}
private function viewOrgAction(array $params): array
{
if (empty($params['connection'])) {
throw new InvalidArgumentException(__('No connection object received.'));
}
$settings = json_decode($params['connection']->settings, true);
$http = new Client();
$url = '/users/organisations/index/scope:all';
if (!empty($params['page'])) {
$url .= '/page:' . $params['page'];
}
if (!empty($params['limit'])) {
$url .= '/limit:' . $params['limit'];
}
if (!empty($params['quickFilter'])) {
$url .= '/searchall:' . $params['quickFilter'];
}
$response = $http->post($settings['url'] . '/users/organisations/index/scope:all', '{}', [
'headers' => [
'AUTHORIZATION' => $settings['authkey'],
'Accept' => 'Application/json',
'Content-type' => 'Application/json'
]
]);
$responseCode = $response->getStatusCode();
if ($response->isOk()) {
return [
'type' => 'index',
'data' => [
'data' => json_decode($response->getBody(), true),
'top_bar' => [
'children' => [
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value'
]
]
],
'fields' => [
[
'name' => '#',
'sort' => 'id',
'data_path' => 'Organisation.id',
],
[
'name' => __('Name'),
'sort' => 'name',
'data_path' => 'Organisation.name',
],
[
'name' => __('UUID'),
'sort' => 'uuid',
'data_path' => 'Organisation.uuid',
]
],
'title' => false,
'description' => false,
'pull' => 'right',
'actions' => [
[
'url' => '/localTools/action/fetchOrg',
'url_params_data_paths' => ['id'],
'icon' => 'download'
]
]
]
];
} else {
return __('Could not fetch the organisations, error code: {0}', $response->getStatusCode());
}
}
}
?>

View File

@ -0,0 +1,172 @@
<?php
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
use Cake\Event\EventInterface;
use Cake\Datasource\EntityInterface;
use ArrayObject;
use Cake\Utility\Text;
use Cake\Http\Client;
class SyncTool extends Behavior
{
// take a server as parameter and return a HttpSocket object using the ssl options defined in the server settings
public function setupHttpSocket($server = null, $timeout = false)
{
$params = array();
if (!empty($server)) {
if ($server['cert_file']) {
$params['ssl_cafile'] = APP . "files" . DS . "certs" . DS . $server['id'] . '.pem';
}
if ($server['client_cert_file']) {
$params['ssl_local_cert'] = APP . "files" . DS . "certs" . DS . $server['id'] . '_client.pem';
}
if ($server['self_signed']) {
$params['ssl_allow_self_signed'] = true;
$params['ssl_verify_peer_name'] = false;
if (!isset($server['cert_file'])) {
$params['ssl_verify_peer'] = false;
}
}
if (!empty($server['skip_proxy'])) {
$params['skip_proxy'] = 1;
}
if (!empty($timeout)) {
$params['timeout'] = $timeout;
}
}
return $this->createHttpSocket($params);
}
public function setupHttpSocketFeed($feed = null)
{
return $this->setupHttpSocket();
}
/**
* @param array $params
* @return HttpSocket
* @throws Exception
*/
public function createHttpSocket($params = array())
{
App::uses('HttpSocket', 'Network/Http');
$HttpSocket = new HttpSocket($params);
$proxy = Configure::read('Proxy');
if (empty($params['skip_proxy']) && isset($proxy['host']) && !empty($proxy['host'])) {
$HttpSocket->configProxy($proxy['host'], $proxy['port'], $proxy['method'], $proxy['user'], $proxy['password']);
}
return $HttpSocket;
}
/**
* @param array $server
* @return array|void
* @throws Exception
*/
public static function getServerClientCertificateInfo(array $server)
{
if (!$server['client_cert_file']) {
return;
}
$clientCertificate = new File(APP . "files" . DS . "certs" . DS . $server['id'] . '_client.pem');
if (!$clientCertificate->exists()) {
throw new Exception("Certificate file '{$clientCertificate->pwd()}' doesn't exists.");
}
$certificateContent = $clientCertificate->read();
if ($certificateContent === false) {
throw new Exception("Could not read '{$clientCertificate->pwd()}' file with client certificate.");
}
return self::getClientCertificateInfo($certificateContent);
}
/**
* @param string $certificateContent PEM encoded certificate and private key.
* @return array
* @throws Exception
*/
private static function getClientCertificateInfo($certificateContent)
{
$certificate = openssl_x509_read($certificateContent);
if (!$certificate) {
throw new Exception("Could't parse certificate: " . openssl_error_string());
}
$privateKey = openssl_pkey_get_private($certificateContent);
if (!$privateKey) {
throw new Exception("Could't get private key from certificate: " . openssl_error_string());
}
$verify = openssl_x509_check_private_key($certificate, $privateKey);
if (!$verify) {
throw new Exception('Public and private key do not match.');
}
return self::parseCertificate($certificate);
}
/**
* @param mixed $certificate
* @return array
* @throws Exception
*/
private static function parseCertificate($certificate)
{
$parsed = openssl_x509_parse($certificate);
if (!$parsed) {
throw new Exception("Could't get parse X.509 certificate: " . openssl_error_string());
}
$currentTime = new DateTime();
$output = [
'serial_number' => $parsed['serialNumberHex'],
'signature_type' => $parsed['signatureTypeSN'],
'valid_from' => isset($parsed['validFrom_time_t']) ? new DateTime("@{$parsed['validFrom_time_t']}") : null,
'valid_to' => isset($parsed['validTo_time_t']) ? new DateTime("@{$parsed['validTo_time_t']}") : null,
'public_key_size' => null,
'public_key_type' => null,
'public_key_size_ok' => null,
];
$output['valid_from_ok'] = $output['valid_from'] ? ($output['valid_from'] <= $currentTime) : null;
$output['valid_to_ok'] = $output['valid_to'] ? ($output['valid_to'] >= $currentTime) : null;
$subject = [];
foreach ($parsed['subject'] as $type => $value) {
$subject[] = "$type=$value";
}
$output['subject'] = implode(', ', $subject);
$issuer = [];
foreach ($parsed['issuer'] as $type => $value) {
$issuer[] = "$type=$value";
}
$output['issuer'] = implode(', ', $issuer);
$publicKey = openssl_pkey_get_public($certificate);
if ($publicKey) {
$publicKeyDetails = openssl_pkey_get_details($publicKey);
if ($publicKeyDetails) {
$output['public_key_size'] = $publicKeyDetails['bits'];
switch ($publicKeyDetails['type']) {
case OPENSSL_KEYTYPE_RSA:
$output['public_key_type'] = 'RSA';
$output['public_key_size_ok'] = $output['public_key_size'] >= 2048;
break;
case OPENSSL_KEYTYPE_DSA:
$output['public_key_type'] = 'DSA';
$output['public_key_size_ok'] = $output['public_key_size'] >= 2048;
break;
case OPENSSL_KEYTYPE_DH:
$output['public_key_type'] = 'DH';
break;
case OPENSSL_KEYTYPE_EC:
$output['public_key_type'] = "EC ({$publicKeyDetails['ec']['curve_name']})";
$output['public_key_size_ok'] = $output['public_key_size'] >= 224;
break;
}
}
}
return $output;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Model\Entity;
use App\Model\Entity\AppModel;
use Cake\ORM\Entity;
class LocalTool extends AppModel
{
}

View File

@ -0,0 +1,103 @@
<?php
namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Migrations\Migrations;
use Cake\Filesystem\Folder;
use Cake\Filesystem\File;
class LocalToolsTable extends AppTable
{
const HEALTH_CODES = [
0 => 'UNKNOWN',
1 => 'OK',
2 => 'ISSUES',
3 => 'ERROR',
];
private $connectors = null;
public function initialize(array $config): void
{
parent::initialize($config);
}
public function validationDefault(Validator $validator): Validator
{
return $validator;
}
public function getConnectors(string $name = null): array
{
$connectors = [];
$dirs = [
ROOT . '/src/Lib/default/local_tool_connectors',
ROOT . '/src/Lib/custom/local_tool_connectors'
];
foreach ($dirs as $dir) {
$dir = new Folder($dir);
$files = $dir->find('.*Connector\.php');
foreach ($files as $file) {
require_once($dir->pwd() . '/'. $file);
$className = substr($file, 0, -4);
$classNamespace = '\\' . $className . '\\' . $className;
if (empty($name) || $name === $className) {
$connectors[$className] = new $classNamespace;
}
}
}
return $connectors;
}
public function extractMeta(array $connector_classes, bool $includeConnections = false): array
{
$connectors = [];
foreach ($connector_classes as $connector_type => $connector_class) {
$connector = [
'name' => $connector_class->name,
'connector' => $connector_type,
'connector_version' => $connector_class->version,
'connector_description' => $connector_class->description
];
if ($includeConnections) {
$connector['connections'] = $this->healthCheck($connector_type, $connector_class);
}
$connectors[] = $connector;
}
return $connectors;
}
public function healthCheck(string $connector_type, Object $connector_class): array
{
$query = $this->find();
$query->where([
'connector' => $connector_type
]);
$connections = $query->all()->toList();
foreach ($connections as &$connection) {
$connection = $this->healthCheckIndividual($connector_class, $connection);
}
return $connections;
}
public function healthCheckIndividual(Object $connector): array
{
$connector_class = $this->getConnectors($connector['connector']);
if (empty($connector_class[$connector['connector']])) {
return [];
}
$connector_class = $connector_class[$connector['connector']];
$health = $connector_class->health($connector);
return $connection = [
'name' => $connector->name,
'health' => $health['status'],
'message' => $health['message'],
'url' => '/localTools/viewConnection/' . $connector['id']
];
}
}

View File

@ -40,6 +40,7 @@ class AppView extends View
{
parent::initialize();
$this->loadHelper('Hash');
$this->loadHelper('PrettyPrint');
$this->loadHelper('FormFieldMassage');
$this->loadHelper('Paginator', ['templates' => 'cerebrate-pagination-templates']);
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\View\Helper;
use Cake\View\Helper;
class PrettyPrintHelper extends Helper
{
public function ppArray($array, $depth = 0)
{
$text = '';
foreach ($array as $key => $value) {
if (is_array($value)) {
$value = $this->ppArray($value, $depth+1);
} else {
$value = h($value);
}
$text .= sprintf(
'<div><span class="text-primary">%s</span>: %s</div>',
h($key),
$value
);
}
return $text;
}
}
?>

View File

@ -0,0 +1,4 @@
<?php
echo $this->element('genericElements/IndexTable/index_table', $index_table);
echo '</div>';
?>

View File

@ -0,0 +1,26 @@
<?php
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'description' => __('Add connections to local tools via any of the available connectors below.'),
'model' => 'LocalTools',
'fields' => [
[
'field' => 'name'
],
[
'field' => 'connector',
'options' => $dropdownData['connectors'],
'type' => 'dropdown'
],
[
'field' => 'settings',
'type' => 'textarea'
]
],
'submit' => [
'action' => $this->request->getParam('action')
]
]
]);
?>
</div>

View File

@ -0,0 +1,68 @@
<?php
echo $this->element('genericElements/IndexTable/index_table', [
'data' => [
'data' => $data,
'top_bar' => [
'children' => [
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value'
]
]
],
'fields' => [
[
'name' => '#',
'sort' => 'id',
'data_path' => 'id',
],
[
'name' => __('Name'),
'sort' => 'name',
'data_path' => 'name',
],
[
'name' => __('Connector'),
'sort' => 'connector',
'data_path' => 'connector',
],
[
'name' => 'settings',
'data_path' => 'settings',
'isJson' => 1,
'element' => 'array'
],
[
'name' => 'health',
'data_path' => 'health',
'element' => 'health',
'class' => 'text-nowrap'
]
],
'title' => false,
'description' => false,
'pull' => 'right',
'actions' => [
[
'url' => '/localTools/view',
'url_params_data_paths' => ['id'],
'icon' => 'eye'
],
[
'open_modal' => '/localTools/edit/[onclick_params_data_path]',
'modal_params_data_path' => 'id',
'icon' => 'edit'
],
[
'open_modal' => '/localTools/delete/[onclick_params_data_path]',
'modal_params_data_path' => 'id',
'icon' => 'trash'
],
]
]
]);
echo '</div>';
?>

View File

@ -0,0 +1,54 @@
<?php
echo $this->element('genericElements/IndexTable/index_table', [
'data' => [
'data' => $data,
'top_bar' => [
'children' => [
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value'
]
]
],
'fields' => [
[
'name' => __('Name'),
'sort' => 'name',
'data_path' => 'name',
],
[
'name' => 'Connector',
'data_path' => 'connector'
],
[
'name' => 'Version',
'data_path' => 'connector_version'
],
[
'name' => 'Description',
'data_path' => 'connector_description'
],
[
'name' => 'Connections',
'data_path' => 'connections',
'element' => 'health',
'class' => 'text-nowrap'
]
],
'title' => __('Local tool connector index'),
'description' => __('Cerebrate can connect to local tools via individual connectors, built to expose the various functionalities of the given tool via Cerebrate. Simply view the connectors\' details and the accompanying instance list to manage the connections using the given connector. '),
'pull' => 'right',
'actions' => [
[
'url' => '/localTools/viewConnector',
'url_params_data_paths' => ['connector'],
'icon' => 'eye'
]
]
]
]);
echo '</div>';
?>

View File

View File

@ -0,0 +1,33 @@
<?php
echo $this->element(
'/genericElements/SingleViews/single_view',
[
'title' => __('{0} connector view', $entity['name']),
'data' => $entity,
'fields' => [
[
'key' => __('Name'),
'path' => 'name'
],
[
'key' => __('Connector name'),
'path' => 'connector'
],
[
'key' => __('version'),
'path' => 'connector_version'
],
[
'key' => __('Description'),
'path' => 'connector_description'
]
],
'children' => [
[
'url' => '/localTools/connectorIndex/',
'url_params' => ['connector'],
'title' => __('Connections')
]
]
]
);

View File

@ -0,0 +1,4 @@
<?php
$random = Cake\Utility\Security::randomString(8);
$type = empty($data['type']) ? 'generic' : $data['type'];
echo $this->element('genericElements/Configuration/Fields/' . $type . 'Field.php', ['data' => $field]);

View File

@ -0,0 +1,33 @@
<?php
/*
* Generic configuration panel builder
*
* Simply pass a JSON with the following keys set:
* - title: The title for the configuration interface
* - diagnostic-view (optional): url of the diagnostic page to be shown and refreshed on any setting change
* - fields: an array with each element generating an input field
* - field: the unique field name for the context
* - description: a brief description of the field
* - type (optional): the type of form element to use
* - options (optional): for select style elements
* - validation (optional): regex to validate input
* - requirements (optional): boolean, if false is passed the field is skipped
*/
$diagnostics = '';
if (!empty($data['diagnostics'])) {
$diagnostics = '<div data-url="' . h($data['diagnostics']) . '"></div>';
}
$fields = '';
if (!empty($data['fields'])) {
foreach ($data['fields'] as $field) {
$fields .= $this->element('genericElements/Configuration/Fields/scaffold.php', ['data' => $field]);
}
}
echo sprintf(
'<div class="%s"><h2>%s</h2><div>%s</div><div><%s/div></div>',
empty($ajax) ? 'col-8' : '',
h($data['title']),
$diagnostics,
$fields
);
?>

View File

@ -0,0 +1,7 @@
<?php
$data = $this->Hash->extract($row, $field['data_path']);;
if (!empty($field['isJson'])) {
$data = json_decode($data[0], true);
}
echo $this->PrettyPrint->ppArray($data);
?>

View File

@ -0,0 +1,28 @@
<?php
$data = $this->Hash->extract($row, $field['data_path']);
$lines = '';
$status_colours = [
0 => 'secondary',
1 => 'success',
2 => 'warning',
3 => 'danger'
];
foreach ($data as $healthElement) {
$name = h($healthElement['name']);
if (!empty($healthElement['url'])) {
$name = sprintf(
'<a href="%s/%s">%s</a>',
$baseurl,
$healthElement['url'],
$name
);
}
$lines .= sprintf(
'<p><span class="text-%s"><i class="fas fa-circle" ></i></span> %s: %s</p>',
$status_colours[$healthElement['health']],
$name,
h($healthElement['message'])
);
}
echo $lines;
?>

View File

@ -53,7 +53,7 @@ foreach ($data['menu'] as $name => $menuElement) {
}
}
$logoutButton = sprintf(
'<span class="nav-item"><a href="%susers/logout" class="nav-link">%s</a></span>',
'<span class="nav-item"><a href="%s/users/logout" class="nav-link">%s</a></span>',
$baseurl,
__('Logout')
);