diff --git a/INSTALL/mysql.sql b/INSTALL/mysql.sql index 9f888dc..c2f8f64 100644 --- a/INSTALL/mysql.sql +++ b/INSTALL/mysql.sql @@ -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` -- diff --git a/src/Application.php b/src/Application.php index 90e3865..8eb1704 100644 --- a/src/Application.php +++ b/src/Application.php @@ -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 diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index e713f50..87f968d 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -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')); } diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index e64470d..ee373a1 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -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 * diff --git a/src/Controller/LocalToolsController.php b/src/Controller/LocalToolsController.php new file mode 100644 index 0000000..5546051 --- /dev/null +++ b/src/Controller/LocalToolsController.php @@ -0,0 +1,122 @@ +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(); + } +} diff --git a/src/Controller/Open/IndividualsController.php b/src/Controller/Open/IndividualsController.php new file mode 100644 index 0000000..097489b --- /dev/null +++ b/src/Controller/Open/IndividualsController.php @@ -0,0 +1,35 @@ +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'); + } +} diff --git a/src/Controller/UsersController.php b/src/Controller/UsersController.php index db11453..dadca75 100644 --- a/src/Controller/UsersController.php +++ b/src/Controller/UsersController.php @@ -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')); } } } diff --git a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php new file mode 100644 index 0000000..1a04acb --- /dev/null +++ b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php @@ -0,0 +1,32 @@ +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; + } +} + +?> diff --git a/src/Lib/default/local_tool_connectors/MispConnector.php b/src/Lib/default/local_tool_connectors/MispConnector.php new file mode 100644 index 0000000..838d620 --- /dev/null +++ b/src/Lib/default/local_tool_connectors/MispConnector.php @@ -0,0 +1,127 @@ +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()); + } + } +} + + ?> diff --git a/src/Model/Behavior/SyncTool.php b/src/Model/Behavior/SyncTool.php new file mode 100644 index 0000000..ad3f756 --- /dev/null +++ b/src/Model/Behavior/SyncTool.php @@ -0,0 +1,172 @@ +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; + } +} diff --git a/src/Model/Entity/LocalTool.php b/src/Model/Entity/LocalTool.php new file mode 100644 index 0000000..f996d77 --- /dev/null +++ b/src/Model/Entity/LocalTool.php @@ -0,0 +1,11 @@ + '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'] + ]; + } +} diff --git a/src/View/AppView.php b/src/View/AppView.php index e4b82b6..87b775b 100644 --- a/src/View/AppView.php +++ b/src/View/AppView.php @@ -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']); } diff --git a/src/View/Helper/PrettyPrintHelper.php b/src/View/Helper/PrettyPrintHelper.php new file mode 100644 index 0000000..c288cb3 --- /dev/null +++ b/src/View/Helper/PrettyPrintHelper.php @@ -0,0 +1,29 @@ + $value) { + if (is_array($value)) { + $value = $this->ppArray($value, $depth+1); + } else { + $value = h($value); + } + $text .= sprintf( + '
%s: %s
', + h($key), + $value + ); + } + return $text; + } +} + + +?> diff --git a/templates/Common/index.php b/templates/Common/index.php new file mode 100644 index 0000000..969eb89 --- /dev/null +++ b/templates/Common/index.php @@ -0,0 +1,4 @@ +element('genericElements/IndexTable/index_table', $index_table); + echo ''; +?> diff --git a/templates/LocalTools/add.php b/templates/LocalTools/add.php new file mode 100644 index 0000000..10cefb6 --- /dev/null +++ b/templates/LocalTools/add.php @@ -0,0 +1,26 @@ +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') + ] + ] + ]); +?> + diff --git a/templates/LocalTools/connector_index.php b/templates/LocalTools/connector_index.php new file mode 100644 index 0000000..06c8560 --- /dev/null +++ b/templates/LocalTools/connector_index.php @@ -0,0 +1,68 @@ +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 ''; +?> diff --git a/templates/LocalTools/index.php b/templates/LocalTools/index.php new file mode 100644 index 0000000..91903a2 --- /dev/null +++ b/templates/LocalTools/index.php @@ -0,0 +1,54 @@ +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 ''; +?> diff --git a/templates/LocalTools/view.php b/templates/LocalTools/view.php new file mode 100644 index 0000000..e69de29 diff --git a/templates/LocalTools/view_connector.php b/templates/LocalTools/view_connector.php new file mode 100644 index 0000000..d6c4665 --- /dev/null +++ b/templates/LocalTools/view_connector.php @@ -0,0 +1,33 @@ +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') + ] + ] + ] +); diff --git a/templates/element/genericElements/Configuration/Fields.php/scaffold.php b/templates/element/genericElements/Configuration/Fields.php/scaffold.php new file mode 100644 index 0000000..6a9187d --- /dev/null +++ b/templates/element/genericElements/Configuration/Fields.php/scaffold.php @@ -0,0 +1,4 @@ +element('genericElements/Configuration/Fields/' . $type . 'Field.php', ['data' => $field]); diff --git a/templates/element/genericElements/Configuration/scaffold.php b/templates/element/genericElements/Configuration/scaffold.php new file mode 100644 index 0000000..ea424fc --- /dev/null +++ b/templates/element/genericElements/Configuration/scaffold.php @@ -0,0 +1,33 @@ +'; +} +$fields = ''; +if (!empty($data['fields'])) { + foreach ($data['fields'] as $field) { + $fields .= $this->element('genericElements/Configuration/Fields/scaffold.php', ['data' => $field]); + } +} +echo sprintf( + '

%s

%s
<%s/div>
', + empty($ajax) ? 'col-8' : '', + h($data['title']), + $diagnostics, + $fields +); +?> diff --git a/templates/element/genericElements/IndexTable/Fields/array.php b/templates/element/genericElements/IndexTable/Fields/array.php new file mode 100644 index 0000000..b1cc9bf --- /dev/null +++ b/templates/element/genericElements/IndexTable/Fields/array.php @@ -0,0 +1,7 @@ +Hash->extract($row, $field['data_path']);; + if (!empty($field['isJson'])) { + $data = json_decode($data[0], true); + } + echo $this->PrettyPrint->ppArray($data); +?> diff --git a/templates/element/genericElements/IndexTable/Fields/health.php b/templates/element/genericElements/IndexTable/Fields/health.php new file mode 100644 index 0000000..ad14ac4 --- /dev/null +++ b/templates/element/genericElements/IndexTable/Fields/health.php @@ -0,0 +1,28 @@ +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( + '%s', + $baseurl, + $healthElement['url'], + $name + ); + } + $lines .= sprintf( + '

%s: %s

', + $status_colours[$healthElement['health']], + $name, + h($healthElement['message']) + ); + } + echo $lines; +?> diff --git a/templates/element/genericElements/header_scaffold.php b/templates/element/genericElements/header_scaffold.php index 1fe6a2f..ac999d8 100644 --- a/templates/element/genericElements/header_scaffold.php +++ b/templates/element/genericElements/header_scaffold.php @@ -53,7 +53,7 @@ foreach ($data['menu'] as $name => $menuElement) { } } $logoutButton = sprintf( - '%s', + '%s', $baseurl, __('Logout') );