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)) {
|
||||
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)
|
||||
|
|
|
@ -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.'));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1,16 +1,36 @@
|
|||
<?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', [
|
||||
'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 '</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',
|
||||
'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)
|
||||
}
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue