new: [localTool:batchActions] Added framework to execute batch actions on list of connections
parent
ce9fc762bc
commit
41e9666224
|
@ -44,8 +44,56 @@ class LocalToolsController extends AppController
|
||||||
if (!empty($responsePayload)) {
|
if (!empty($responsePayload)) {
|
||||||
return $responsePayload;
|
return $responsePayload;
|
||||||
}
|
}
|
||||||
|
$connector = $this->LocalTools->getConnectors($connectorName)[$connectorName];
|
||||||
$this->set('metaGroup', 'Administration');
|
$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)
|
public function action($connectionId, $actionName)
|
||||||
|
|
|
@ -25,6 +25,13 @@ class CommonConnectorTools
|
||||||
$this->exposedFunctions[] = $functionName;
|
$this->exposedFunctions[] = $functionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getBatchActionFunctions(): array
|
||||||
|
{
|
||||||
|
return array_filter($this->exposedFunctions, function($function) {
|
||||||
|
return $function['type'] == 'batchAction';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function runAction($action, $params) {
|
public function runAction($action, $params) {
|
||||||
if (!in_array($action, $exposedFunctions)) {
|
if (!in_array($action, $exposedFunctions)) {
|
||||||
throw new MethodNotAllowedException(__('Invalid connector function called.'));
|
throw new MethodNotAllowedException(__('Invalid connector function called.'));
|
||||||
|
|
|
@ -94,6 +94,20 @@ class MispConnector extends CommonConnectorTools
|
||||||
'sort',
|
'sort',
|
||||||
'direction'
|
'direction'
|
||||||
]
|
]
|
||||||
|
],
|
||||||
|
'batchAPIAction' => [
|
||||||
|
'type' => 'batchAction',
|
||||||
|
'scope' => 'childAction',
|
||||||
|
'params' => [
|
||||||
|
'method',
|
||||||
|
'url',
|
||||||
|
'body',
|
||||||
|
],
|
||||||
|
'ui' => [
|
||||||
|
'text' => 'Batch API',
|
||||||
|
'icon' => 'terminal',
|
||||||
|
'variant' => 'primary',
|
||||||
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
public $version = '0.1';
|
public $version = '0.1';
|
||||||
|
@ -230,7 +244,10 @@ class MispConnector extends CommonConnectorTools
|
||||||
throw new NotFoundException(__('No connection object received.'));
|
throw new NotFoundException(__('No connection object received.'));
|
||||||
}
|
}
|
||||||
$url = $this->urlAppendParams($url, $params);
|
$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()) {
|
if ($response->isOk()) {
|
||||||
return $response;
|
return $response;
|
||||||
} else {
|
} else {
|
||||||
|
@ -791,6 +808,57 @@ class MispConnector extends CommonConnectorTools
|
||||||
throw new MethodNotAllowedException(__('Invalid http request type for the given action.'));
|
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
|
public function initiateConnection(array $params): array
|
||||||
{
|
{
|
||||||
$params['connection_settings'] = json_decode($params['connection']['settings'], true);
|
$params['connection_settings'] = json_decode($params['connection']['settings'], true);
|
||||||
|
|
|
@ -310,7 +310,7 @@ class LocalToolsTable extends AppTable
|
||||||
public function isValidSettings($settings, array $context)
|
public function isValidSettings($settings, array $context)
|
||||||
{
|
{
|
||||||
$settings = json_decode($settings, true);
|
$settings = json_decode($settings, true);
|
||||||
$validationErrors = $this->getLocalToolsSettingValidationErrors($context['data']['id'], $settings);
|
$validationErrors = $this->getLocalToolsSettingValidationErrors($context['data']['connector'], $settings);
|
||||||
return $this->getValidationMessage($validationErrors);
|
return $this->getValidationMessage($validationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,9 +323,9 @@ class LocalToolsTable extends AppTable
|
||||||
return empty($messages) ? true : implode('; ', $messages);
|
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 = [];
|
$errors = [];
|
||||||
if (method_exists($connector, 'addSettingValidatorRules')) {
|
if (method_exists($connector, 'addSettingValidatorRules')) {
|
||||||
$validator = new Validator();
|
$validator = new Validator();
|
||||||
|
|
|
@ -1,16 +1,36 @@
|
||||||
<?php
|
<?php
|
||||||
|
$multiSelectActions = [];
|
||||||
|
foreach ($connector->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', [
|
echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
'data' => [
|
'data' => [
|
||||||
'data' => $data,
|
'data' => $data,
|
||||||
'top_bar' => [
|
'top_bar' => [
|
||||||
'children' => [
|
'children' => [
|
||||||
|
[
|
||||||
|
'type' => 'multi_select_actions',
|
||||||
|
'children' => $multiSelectActions,
|
||||||
|
'data' => [
|
||||||
|
'id' => [
|
||||||
|
'value_path' => 'id'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'type' => 'simple',
|
'type' => 'simple',
|
||||||
'children' => [
|
'children' => [
|
||||||
'data' => [
|
'data' => [
|
||||||
'type' => 'simple',
|
'type' => 'simple',
|
||||||
'text' => __('Add connection'),
|
'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]',
|
'open_modal' => '/localTools/connectLocal/[onclick_params_data_path]',
|
||||||
'modal_params_data_path' => 'id',
|
'modal_params_data_path' => 'id',
|
||||||
'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connector)),
|
'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connectorName)),
|
||||||
'icon' => 'plug'
|
'icon' => 'plug'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'open_modal' => '/localTools/edit/[onclick_params_data_path]',
|
'open_modal' => '/localTools/edit/[onclick_params_data_path]',
|
||||||
'modal_params_data_path' => 'id',
|
'modal_params_data_path' => 'id',
|
||||||
'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connector)),
|
'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connectorName)),
|
||||||
'icon' => 'edit'
|
'icon' => 'edit'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'open_modal' => '/localTools/delete/[onclick_params_data_path]',
|
'open_modal' => '/localTools/delete/[onclick_params_data_path]',
|
||||||
'modal_params_data_path' => 'id',
|
'modal_params_data_path' => 'id',
|
||||||
'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connector)),
|
'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connectorName)),
|
||||||
'icon' => 'trash'
|
'icon' => 'trash'
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
echo '</div>';
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function handleMultiSelectAction(idList, selectedRows, $table, $clicked) {
|
||||||
|
const url = `/localTools/batchAction/${$clicked.data('actionname')}?connection_ids=${encodeURIComponent(idList)}`
|
||||||
|
const reloadUrl = '/localTools/connectorIndex/<?= $connectorName ?>'
|
||||||
|
const successCallback = function([requestData, modalObject]) {
|
||||||
|
includeResultInModal(requestData, modalObject)
|
||||||
|
UI.reload(reloadUrl, UI.getContainerForTable($table), $table)
|
||||||
|
}
|
||||||
|
const failCallback = function([requestData, modalObject]) {
|
||||||
|
includeResultInModal(requestData, modalObject)
|
||||||
|
}
|
||||||
|
UI.submissionModal(url, successCallback, failCallback, {closeOnSuccess: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
function includeResultInModal(requestData, modalObject) {
|
||||||
|
const resultsHaveErrors = checkResultsHaveErrors(requestData.data)
|
||||||
|
let tableData = []
|
||||||
|
let tableHeader = []
|
||||||
|
if (resultsHaveErrors) {
|
||||||
|
tableHeader = ['<?= __('Connection ID') ?>', '<?= __('Connection Name') ?>', '<?= __('Message') ?>', '<?= __('Error') ?>', '<?= __('Success') ?>', '<?= __('Result') ?>']
|
||||||
|
} else {
|
||||||
|
tableHeader = ['<?= __('Connection ID') ?>', '<?= __('Connection Name') ?>', '<?= __('Message') ?>', '<?= __('Success') ?>', '<?= __('Result') ?>']
|
||||||
|
}
|
||||||
|
for (const key in requestData.data) {
|
||||||
|
if (Object.hasOwnProperty.call(requestData.data, key)) {
|
||||||
|
const singleResult = requestData.data[key];
|
||||||
|
$faIcon = $('<i class="fa"></i>').addClass(singleResult.success ? 'fa-check text-success' : 'fa-times text-danger')
|
||||||
|
$jsonResult = $('<pre class="p-2 rounded mb-0" style="max-width: 400px; max-height: 300px;background: #eeeeee55;"></pre>').append(
|
||||||
|
$('<code></code>').text(JSON.stringify(singleResult.data, null, 4))
|
||||||
|
)
|
||||||
|
if (resultsHaveErrors) {
|
||||||
|
tableData.push([singleResult.connection.id, singleResult.connection.name, singleResult.message, JSON.stringify(singleResult.errors, null, 4), $faIcon, $jsonResult])
|
||||||
|
} else {
|
||||||
|
tableData.push([singleResult.connection.id, singleResult.connection.name, singleResult.message, $faIcon, $jsonResult])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleMessageTable(
|
||||||
|
modalObject.$modal,
|
||||||
|
tableHeader,
|
||||||
|
tableData
|
||||||
|
)
|
||||||
|
const $footer = $(modalObject.ajaxApi.statusNode).parent()
|
||||||
|
modalObject.ajaxApi.statusNode.remove()
|
||||||
|
const $cancelButton = $footer.find('button[data-dismiss="modal"]')
|
||||||
|
$cancelButton.text('<?= __('OK') ?>').removeClass('btn-secondary').addClass('btn-primary')
|
||||||
|
}
|
||||||
|
|
||||||
|
function constructMessageTable(header, data) {
|
||||||
|
return HtmlHelper.table(
|
||||||
|
header,
|
||||||
|
data,
|
||||||
|
{
|
||||||
|
small: true,
|
||||||
|
borderless: true,
|
||||||
|
tableClass: ['message-table', 'mt-4 mb-0'],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMessageTable($modal, header, data) {
|
||||||
|
const $modalBody = $modal.find('.modal-body')
|
||||||
|
const $messageTable = $modalBody.find('table.message-table')
|
||||||
|
const messageTableHTML = constructMessageTable(header, data)[0].outerHTML
|
||||||
|
if ($messageTable.length) {
|
||||||
|
$messageTable.html(messageTableHTML)
|
||||||
|
} else {
|
||||||
|
$modalBody.append(messageTableHTML)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkResultsHaveErrors(result) {
|
||||||
|
for (const key in result) {
|
||||||
|
if (Object.hasOwnProperty.call(result, key)) {
|
||||||
|
const singleResult = result[key];
|
||||||
|
if(!singleResult.success) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -6,11 +6,12 @@
|
||||||
'variant' => $child['variant'] ?? 'primary',
|
'variant' => $child['variant'] ?? 'primary',
|
||||||
'text' => $child['text'],
|
'text' => $child['text'],
|
||||||
'outline' => !empty($child['outline']),
|
'outline' => !empty($child['outline']),
|
||||||
'params' => [
|
'icon' => $child['icon'] ?? null,
|
||||||
|
'params' => array_merge([
|
||||||
'data-onclick-function' => $child['onclick'] ?? '',
|
'data-onclick-function' => $child['onclick'] ?? '',
|
||||||
'data-table-random-value' => $tableRandomValue,
|
'data-table-random-value' => $tableRandomValue,
|
||||||
'onclick' => 'multiActionClickHandler(this)'
|
'onclick' => 'multiActionClickHandler(this)'
|
||||||
]
|
], $child['params'] ?? [])
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
echo sprintf(
|
echo sprintf(
|
||||||
|
@ -68,7 +69,7 @@
|
||||||
})
|
})
|
||||||
const functionName = $clicked.data('onclick-function')
|
const functionName = $clicked.data('onclick-function')
|
||||||
if (functionName && typeof window[functionName] === 'function') {
|
if (functionName && typeof window[functionName] === 'function') {
|
||||||
window[functionName](selectedIDs, selectedData, $table)
|
window[functionName](selectedIDs, selectedData, $table, $clicked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
Loading…
Reference in New Issue