From 493530f52d3e85f5f53be231dd81a7e2fbe14655 Mon Sep 17 00:00:00 2001 From: mokaddem Date: Wed, 7 Jul 2021 15:02:48 +0200 Subject: [PATCH 1/4] fix: [genericElement:codemirror] Catch if no data for codemirror are passed --- templates/element/genericElements/codemirror.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/element/genericElements/codemirror.php b/templates/element/genericElements/codemirror.php index dd2ad65..337989e 100644 --- a/templates/element/genericElements/codemirror.php +++ b/templates/element/genericElements/codemirror.php @@ -40,7 +40,7 @@ }, hintData: {} } - const passedOptions = ; + const passedOptions = ; const cmOptions = Object.assign({}, cmDefaultOptions, passedOptions) let cm init() From ce9fc762bcb9a3c450d3f1030d2d2e30aadbe27b Mon Sep 17 00:00:00 2001 From: mokaddem Date: Wed, 7 Jul 2021 15:03:54 +0200 Subject: [PATCH 2/4] chg: [helpers:submissionModal] Allow passing modalFactory options --- webroot/js/bootstrap-helper.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/webroot/js/bootstrap-helper.js b/webroot/js/bootstrap-helper.js index 6c4176b..80af3e8 100644 --- a/webroot/js/bootstrap-helper.js +++ b/webroot/js/bootstrap-helper.js @@ -29,15 +29,18 @@ class UIFactory { * @param {string} url - The URL from which the modal's content should be fetched * @param {ModalFactory~POSTSuccessCallback} POSTSuccessCallback - The callback that handles successful form submission * @param {ModalFactory~POSTFailCallback} POSTFailCallback - The callback that handles form submissions errors and validation errors. + * @param {Object=[]} modalOptions - Additional options to be passed to the modal constructor * @return {Promise} Promise object resolving to the ModalFactory object */ - async submissionModal(url, POSTSuccessCallback, POSTFailCallback) { + async submissionModal(url, POSTSuccessCallback, POSTFailCallback, modalOptions={}) { return AJAXApi.quickFetchURL(url).then((modalHTML) => { - const theModal = new ModalFactory({ + const defaultOptions = { rawHtml: modalHTML, POSTSuccessCallback: POSTSuccessCallback !== undefined ? POSTSuccessCallback : () => {}, POSTFailCallback: POSTFailCallback !== undefined ? POSTFailCallback : (errorMessage) => {}, - }); + } + const options = Object.assign({}, defaultOptions, modalOptions) + const theModal = new ModalFactory(options); theModal.makeModal() theModal.show() theModal.$modal.data('modalObject', theModal) From 41e966622494077bb92a075f8750e0a7599fb5f6 Mon Sep 17 00:00:00 2001 From: mokaddem Date: Wed, 7 Jul 2021 15:05:32 +0200 Subject: [PATCH 3/4] new: [localTool:batchActions] Added framework to execute batch actions on list of connections --- src/Controller/LocalToolsController.php | 50 +++++++- .../CommonConnectorTools.php | 7 ++ .../local_tool_connectors/MispConnector.php | 70 ++++++++++- src/Model/Table/LocalToolsTable.php | 6 +- templates/LocalTools/connector_index.php | 113 +++++++++++++++++- .../ListTopBar/group_multi_select_actions.php | 7 +- 6 files changed, 240 insertions(+), 13 deletions(-) diff --git a/src/Controller/LocalToolsController.php b/src/Controller/LocalToolsController.php index 73e3f83..901407a 100644 --- a/src/Controller/LocalToolsController.php +++ b/src/Controller/LocalToolsController.php @@ -44,8 +44,56 @@ class LocalToolsController extends AppController if (!empty($responsePayload)) { return $responsePayload; } + $connector = $this->LocalTools->getConnectors($connectorName)[$connectorName]; $this->set('metaGroup', 'Administration'); - $this->set('connector', $connectorName); + $this->set('connectorName', $connectorName); + $this->set('connector', $connector); + } + + public function batchAction($actionName) + { + $params = $this->ParamHandler->harvestParams(['connection_ids']); + $params['connection_ids'] = explode(',', $params['connection_ids']); + $connections = $this->LocalTools->query()->where(['id IN' => $params['connection_ids']])->all(); + if (empty($connections)) { + throw new NotFoundException(__('Invalid connector.')); + } + $connection = $connections->first(); + if ($this->request->is(['post', 'put'])) { + $actionParams = $this->LocalTools->getActionFilterOptions($connection->connector, $actionName); + $params = array_merge($params, $this->ParamHandler->harvestParams($actionParams)); + $results = []; + $successes = 0; + $this->LocalTools->loadConnector($connection->connector); + foreach ($connections as $connection) { + $actionDetails = $this->LocalTools->getActionDetails($actionName); + $params['connection'] = $connection; + $tmpResult = $this->LocalTools->action($this->ACL->getUser()['id'], $connection->connector, $actionName, $params, $this->request); + $tmpResult['connection'] = $connection; + $results[$connection->id] = $tmpResult; + $successes += $tmpResult['success'] ? 1 : 0; + } + $success = $successes > 0; + $message = __('{0} / {1} operations were successful', $successes, count($results)); + $this->CRUD->setResponseForController('batchAction', $success, $message, $results, $results); + $responsePayload = $this->CRUD->getResponsePayload(); + if (!empty($responsePayload)) { + return $responsePayload; + } + if (!empty($success)) { + $this->Flash->success($message); + $this->redirect(['controller' => 'localTools', 'action' => 'connectorIndex', $actionName]); + } else { + $this->Flash->error($message); + $this->redirect(['controller' => 'localTools', 'action' => 'connectorIndex', $actionName]); + } + } else { + $params['connection'] = $connection; + $results = $this->LocalTools->action($this->ACL->getUser()['id'], $connection->connector, $actionName, $params, $this->request); + $this->set('data', $results); + $this->set('metaGroup', 'Administration'); + $this->render('/Common/getForm'); + } } public function action($connectionId, $actionName) diff --git a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php index 7e865c0..179a238 100644 --- a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php +++ b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php @@ -25,6 +25,13 @@ class CommonConnectorTools $this->exposedFunctions[] = $functionName; } + public function getBatchActionFunctions(): array + { + return array_filter($this->exposedFunctions, function($function) { + return $function['type'] == 'batchAction'; + }); + } + public function runAction($action, $params) { if (!in_array($action, $exposedFunctions)) { throw new MethodNotAllowedException(__('Invalid connector function called.')); diff --git a/src/Lib/default/local_tool_connectors/MispConnector.php b/src/Lib/default/local_tool_connectors/MispConnector.php index 940db8b..e84f082 100644 --- a/src/Lib/default/local_tool_connectors/MispConnector.php +++ b/src/Lib/default/local_tool_connectors/MispConnector.php @@ -94,6 +94,20 @@ class MispConnector extends CommonConnectorTools 'sort', 'direction' ] + ], + 'batchAPIAction' => [ + 'type' => 'batchAction', + 'scope' => 'childAction', + 'params' => [ + 'method', + 'url', + 'body', + ], + 'ui' => [ + 'text' => 'Batch API', + 'icon' => 'terminal', + 'variant' => 'primary', + ] ] ]; public $version = '0.1'; @@ -230,7 +244,10 @@ class MispConnector extends CommonConnectorTools throw new NotFoundException(__('No connection object received.')); } $url = $this->urlAppendParams($url, $params); - $response = $this->HTTPClientPOST($url, $params['connection'], json_encode($params['body'])); + if (!is_string($params['body'])) { + $params['body'] = json_encode($params['body']); + } + $response = $this->HTTPClientPOST($url, $params['connection'], $params['body']); if ($response->isOk()) { return $response; } else { @@ -791,6 +808,57 @@ class MispConnector extends CommonConnectorTools throw new MethodNotAllowedException(__('Invalid http request type for the given action.')); } + public function batchAPIAction(array $params): array + { + if ($params['request']->is(['get'])) { + return [ + 'data' => [ + 'title' => __('Execute API Request'), + 'description' => __('Perform an API Request on the list of selected connections'), + 'fields' => [ + [ + 'field' => 'connection_ids', + 'type' => 'hidden', + 'value' => $params['connection_ids'] + ], + [ + 'field' => 'method', + 'label' => __('Method'), + 'type' => 'dropdown', + 'options' => ['GET' => 'GET', 'POST' => 'POST'] + ], + [ + 'field' => 'url', + 'label' => __('Relative URL'), + 'type' => 'text', + ], + [ + 'field' => 'body', + 'label' => __('POST Body'), + 'type' => 'codemirror', + ], + ], + 'submit' => [ + 'action' => $params['request']->getParam('action') + ], + 'url' => ['controller' => 'localTools', 'action' => 'batchAction', 'batchAPIAction'] + ] + ]; + } else if ($params['request']->is(['post'])) { + if ($params['method'] == 'GET') { + $response = $this->getData($params['url'], $params); + } else { + $response = $this->postData($params['url'], $params); + } + if ($response->getStatusCode() == 200) { + return ['success' => 1, 'message' => __('API query successful'), 'data' => $response->getJson()]; + } else { + return ['success' => 0, 'message' => __('API query failed'), 'data' => $response->getJson()]; + } + } + throw new MethodNotAllowedException(__('Invalid http request type for the given action.')); + } + public function initiateConnection(array $params): array { $params['connection_settings'] = json_decode($params['connection']['settings'], true); diff --git a/src/Model/Table/LocalToolsTable.php b/src/Model/Table/LocalToolsTable.php index d061ba3..cb64588 100644 --- a/src/Model/Table/LocalToolsTable.php +++ b/src/Model/Table/LocalToolsTable.php @@ -310,7 +310,7 @@ class LocalToolsTable extends AppTable public function isValidSettings($settings, array $context) { $settings = json_decode($settings, true); - $validationErrors = $this->getLocalToolsSettingValidationErrors($context['data']['id'], $settings); + $validationErrors = $this->getLocalToolsSettingValidationErrors($context['data']['connector'], $settings); return $this->getValidationMessage($validationErrors); } @@ -323,9 +323,9 @@ class LocalToolsTable extends AppTable return empty($messages) ? true : implode('; ', $messages); } - public function getLocalToolsSettingValidationErrors($connectionId, array $settings): array + public function getLocalToolsSettingValidationErrors($connectorName, array $settings): array { - $connector = array_values($this->getConnectorByConnectionId($connectionId))[0]; + $connector = array_values($this->getConnectors($connectorName))[0]; $errors = []; if (method_exists($connector, 'addSettingValidatorRules')) { $validator = new Validator(); diff --git a/templates/LocalTools/connector_index.php b/templates/LocalTools/connector_index.php index 83508c9..e14be3b 100644 --- a/templates/LocalTools/connector_index.php +++ b/templates/LocalTools/connector_index.php @@ -1,16 +1,36 @@ getBatchActionFunctions() as $actionName => $actionData) { + $multiSelectActions[] = [ + 'text' => $actionData['ui']['text'], + 'icon' => $actionData['ui']['icon'], + 'variant' => $actionData['ui']['variant'], + 'params' => ['data-actionname' => $actionName], + 'onclick' => 'handleMultiSelectAction' + ]; +} + echo $this->element('genericElements/IndexTable/index_table', [ 'data' => [ 'data' => $data, 'top_bar' => [ 'children' => [ + [ + 'type' => 'multi_select_actions', + 'children' => $multiSelectActions, + 'data' => [ + 'id' => [ + 'value_path' => 'id' + ] + ] + ], [ 'type' => 'simple', 'children' => [ 'data' => [ 'type' => 'simple', 'text' => __('Add connection'), - 'popover_url' => sprintf('/localTools/add/%s', h($connector)) + 'popover_url' => sprintf('/localTools/add/%s', h($connectorName)) ] ] ], @@ -72,23 +92,106 @@ echo $this->element('genericElements/IndexTable/index_table', [ [ 'open_modal' => '/localTools/connectLocal/[onclick_params_data_path]', 'modal_params_data_path' => 'id', - 'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connector)), + 'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connectorName)), 'icon' => 'plug' ], [ 'open_modal' => '/localTools/edit/[onclick_params_data_path]', 'modal_params_data_path' => 'id', - 'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connector)), + 'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connectorName)), 'icon' => 'edit' ], [ 'open_modal' => '/localTools/delete/[onclick_params_data_path]', 'modal_params_data_path' => 'id', - 'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connector)), + 'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connectorName)), 'icon' => 'trash' ], ] ] ]); -echo ''; ?> + + diff --git a/templates/element/genericElements/ListTopBar/group_multi_select_actions.php b/templates/element/genericElements/ListTopBar/group_multi_select_actions.php index 44616ec..f591cfa 100644 --- a/templates/element/genericElements/ListTopBar/group_multi_select_actions.php +++ b/templates/element/genericElements/ListTopBar/group_multi_select_actions.php @@ -6,11 +6,12 @@ 'variant' => $child['variant'] ?? 'primary', 'text' => $child['text'], 'outline' => !empty($child['outline']), - 'params' => [ + 'icon' => $child['icon'] ?? null, + 'params' => array_merge([ 'data-onclick-function' => $child['onclick'] ?? '', 'data-table-random-value' => $tableRandomValue, 'onclick' => 'multiActionClickHandler(this)' - ] + ], $child['params'] ?? []) ]); } echo sprintf( @@ -68,7 +69,7 @@ }) const functionName = $clicked.data('onclick-function') if (functionName && typeof window[functionName] === 'function') { - window[functionName](selectedIDs, selectedData, $table) + window[functionName](selectedIDs, selectedData, $table, $clicked) } } \ No newline at end of file From aa66b4fbf5ad3351f40e88917df7c669360532a3 Mon Sep 17 00:00:00 2001 From: mokaddem Date: Wed, 7 Jul 2021 15:11:52 +0200 Subject: [PATCH 4/4] fix: [genericElement:singleView] Improved display of json field --- .../element/genericElements/SingleViews/Fields/jsonField.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/element/genericElements/SingleViews/Fields/jsonField.php b/templates/element/genericElements/SingleViews/Fields/jsonField.php index ef14c58..fabc6ce 100644 --- a/templates/element/genericElements/SingleViews/Fields/jsonField.php +++ b/templates/element/genericElements/SingleViews/Fields/jsonField.php @@ -3,8 +3,8 @@ if (isset($field['raw'])) { $string = $field['raw']; } else { - $value = Cake\Utility\Hash::extract($data, $field['path']); - $string = count($value) == 0 ? '' : $value[0]; + $value = Cake\Utility\Hash::get($data, $field['path']); + $string = is_null($value) ? '' : $value; } echo sprintf( '
',