new: [workflowPart] Started integration of workflow parts

pull/8530/head
Sami Mokaddem 2022-06-22 09:46:57 +02:00
parent d1c617a287
commit 4e47782a04
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
11 changed files with 471 additions and 7 deletions

View File

@ -0,0 +1,93 @@
<?php
App::uses('AppController', 'Controller');
class WorkflowPartsController extends AppController
{
public $components = array(
'RequestHandler'
);
public function index()
{
$params = [
'filters' => ['name', 'uuid', 'timestamp'],
'quickFilters' => ['name', 'uuid'],
];
$this->CRUD->index($params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('menuData', ['menuList' => 'workflowParts', 'menuItem' => 'index']);
}
public function add($fromEditor = false)
{
$params = [];
$this->CRUD->add($params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('fromEditor', !empty($fromEditor));
$this->set('menuData', ['menuList' => 'workflowParts', 'menuItem' => 'add']);
}
public function edit($id)
{
$params = [];
$this->CRUD->edit($id, $params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('menuData', ['menuList' => 'workflowParts', 'menuItem' => 'edit']);
$this->set('id', $id);
$this->render('add');
}
public function delete($id)
{
$params = [
];
$this->CRUD->delete($id, $params);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('menuData', ['menuList' => 'workflowParts', 'menuItem' => 'delete']);
}
public function view($id)
{
$this->CRUD->view($id, [
]);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('id', $id);
$this->set('menuData', ['menuList' => 'workflowParts', 'menuItem' => 'view']);
}
public function import()
{
if ($this->request->is('post') || $this->request->is('put')) {
$workflowPartData = JsonTool::decode($this->request->data['WorkflowPart']['data']);
if ($workflowPartData === null) {
throw new MethodNotAllowedException(__('Error while decoding JSON'));
}
$this->request->data['WorkflowPart']['data'] = JsonTool::encode($workflowPartData);
$this->add();
}
}
public function export($id)
{
$workflowPart = $this->WorkflowPart->find('first', [
'conditions' => [
'id' => $id,
]
]);
$content = JsonTool::encode($workflowPart, JSON_PRETTY_PRINT);
$this->response->body($content);
$this->response->type('json');
$this->response->download(sprintf('workflowpart_%s_%s.json', $workflowPart['WorkflowPart']['name'], time()));
return $this->response;
}
}

View File

@ -1704,6 +1704,18 @@ class AppModel extends Model
INDEX `user_id` (`user_id`),
INDEX `org_id` (`org_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
$sqlArray[] = "CREATE TABLE IF NOT EXISTS `workflow_parts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uuid` varchar(40) COLLATE utf8_bin NOT NULL ,
`name` varchar(191) NOT NULL,
`description` varchar(191) NOT NULL,
`timestamp` int(11) NOT NULL DEFAULT 0,
`data` text,
PRIMARY KEY (`id`),
INDEX `uuid` (`uuid`),
INDEX `name` (`name`),
INDEX `timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
break;
case 'fixNonEmptySharingGroupID':
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';

View File

@ -0,0 +1,96 @@
<?php
App::uses('AppModel', 'Model');
class WorkflowPart extends AppModel
{
public $recursive = -1;
public $actsAs = [
'AuditLog',
'Containable',
];
public $belongsTo = [
];
public $validate = [
'value' => [
'stringNotEmpty' => [
'rule' => ['stringNotEmpty']
]
],
'uuid' => [
'uuid' => [
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID'
],
'unique' => [
'rule' => 'isUnique',
'message' => 'The UUID provided is not unique',
'required' => 'create'
]
],
];
const CAPTURE_FIELDS = ['name', 'description', 'timestamp', 'data'];
public function beforeValidate($options = array())
{
parent::beforeValidate();
if (empty($this->data['WorkflowPart']['uuid'])) {
$this->data['WorkflowPart']['uuid'] = CakeText::uuid();
} else {
$this->data['WorkflowPart']['uuid'] = strtolower($this->data['WorkflowPart']['uuid']);
}
if (empty($this->data['WorkflowPart']['data'])) {
$this->data['WorkflowPart']['data'] = [];
}
if (empty($this->data['WorkflowPart']['timestamp'])) {
$this->data['WorkflowPart']['timestamp'] = time();
}
return true;
}
public function afterFind($results, $primary = false)
{
foreach ($results as $k => $result) {
if (empty($result['WorkflowPart']['data'])) {
$result['WorkflowPart']['data'] = '{}';
}
$results[$k]['WorkflowPart']['data'] = JsonTool::decode($result['WorkflowPart']['data']);
}
return $results;
}
public function beforeSave($options = [])
{
$this->data['WorkflowPart']['data'] = JsonTool::encode($this->data['WorkflowPart']['data']);
return true;
}
public function logExecutionError($workflow, $message)
{
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry('SYSTEM', 'execute_workflow', 'Workflow', $workflow['Workflow']['id'], $message);
}
private function __logError($id, $message)
{
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry('SYSTEM', 'load_module', 'Workflow', $id, $message);
return false;
}
private function __saveAndReturnErrors($data, $saveOptions = [], $errors = [])
{
$saveSuccess = $this->save($data, $saveOptions);
if (!$saveSuccess) {
foreach ($this->validationErrors as $validationError) {
$errors[] = $validationError[0];
}
}
return $errors;
}
}

View File

@ -1612,6 +1612,37 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
}
break;
case 'workflowParts':
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'index',
'url' => '/workflowParts/index',
'text' => __('List Workflow Parts')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => $baseurl . '/workflowParts/import',
'text' => __('Import Workflow Parts')
));
if ($menuItem === 'view' || $menuItem === 'edit') {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'view',
'url' => '/workflowParts/view/' . h($id),
'text' => __('View Workflow Part')
));
if ($isSiteAdmin) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'edit',
'url' => '/workflows/edit/' . h($id),
'text' => __('Edit Workflow Part')
));
}
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => '/admin/audit_logs/index/model:WorkflowParts/model_id:' . h($id),
'text' => __('View worflow part history'),
'requirement' => Configure::read('MISP.log_new_audit') && $canAccess('auditLogs', 'admin_index'),
));
}
break;
case 'workflows':
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'index_trigger',
@ -1623,6 +1654,11 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
'url' => '/workflows/moduleIndex',
'text' => __('List Modules')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'index',
'url' => '/workflowParts/index',
'text' => __('List Workflow Parts')
));
if ($menuItem === 'view' || $menuItem === 'edit') {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'view',

View File

@ -0,0 +1,37 @@
<?php
$edit = $this->request->params['action'] === 'edit' ? true : false;
$fields = [
[
'field' => 'name',
'class' => 'span6',
'placeholder' => 'Name of the workflow part',
],
[
'field' => 'description',
'type' => 'textarea',
'class' => 'input span6',
'placeholder' => 'Concise description of the workflow part',
],
[
'field' => 'data',
'type' => 'textarea',
'class' => 'input span6',
'div' => (!empty($fromEditor) ? 'hidden' : '')
]
];
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'description' => false,
'model' => 'WorkflowPart',
'title' => $edit ? __('Edit Workflow Part') : __('Add Workflow Part'),
'fields' => $fields,
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
}

View File

View File

@ -7,7 +7,7 @@ echo $this->element('genericElements/Form/genericForm', [
'data' => [
'model' => 'Workflow',
'title' => __('Import Workflow'),
'description' => __('Paste a JSON of a Workflow to import or provide a JSON file below.'),
'description' => __('Paste a JSON of a Workflow part to import it or provide a JSON file below.'),
'fields' => [
[
'field' => 'json',
@ -15,7 +15,7 @@ echo $this->element('genericElements/Form/genericForm', [
'class' => 'input span6',
'div' => 'input clear',
'label' => __('JSON'),
'placeholder' => __('Workflow JSON'),
'placeholder' => __('Workflow Part JSON'),
'rows' => 18
],
[
@ -30,4 +30,6 @@ echo $this->element('genericElements/Form/genericForm', [
]
]);
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'workflows', 'menuItem' => 'import'));
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', ['menuList' => 'workflowParts', 'menuItem' => 'import']);
}

View File

@ -0,0 +1,92 @@
<?php
$fields = [
[
'name' => __('ID'),
'sort' => 'WorkflowPart.id',
'data_path' => 'WorkflowPart.id',
],
[
'name' => __('UUID'),
'sort' => 'WorkflowPart.uuid',
'data_path' => 'WorkflowPart.uuid',
],
[
'name' => __('Name'),
'sort' => 'WorkflowPart.name',
'data_path' => 'WorkflowPart.name',
],
[
'name' => __('Description'),
'sort' => 'WorkflowPart.description',
'data_path' => 'WorkflowPart.description',
],
[
'name' => __('Timestamp'),
'sort' => 'WorkflowPart.timestamp',
'data_path' => 'WorkflowPart.timestamp',
],
];
echo $this->element('genericElements/IndexTable/scaffold', [
'scaffold_data' => [
'data' => [
'data' => $data,
'top_bar' => [
'pull' => 'right',
'children' => [
[
'type' => 'simple',
'children' => [
'data' => [
'type' => 'simple',
'fa-icon' => 'plus',
'text' => __('Import Workflow Part'),
'class' => 'btn-primary modal-open',
'url' => "$baseurl/workflowParts/import",
]
]
],
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'quickFilter'
]
]
],
'fields' => $fields,
'title' => __('Workflow Parts'),
'description' => __('You can create re-use workflow parts in your workflows'),
'actions' => [
[
'url' => $baseurl . '/workflowParts/view',
'url_params_data_paths' => ['WorkflowPart.id'],
'icon' => 'eye',
'dbclickAction' => true,
],
[
'url' => $baseurl . '/workflowParts/edit',
'url_params_data_paths' => ['WorkflowPart.id'],
'icon' => 'edit',
],
[
'url' => $baseurl . '/workflowParts/export',
'url_params_data_paths' => ['WorkflowPart.id'],
'title' => __('Export Workflow Part'),
'icon' => 'download',
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/workflowParts/delete/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => 'WorkflowPart.id',
'icon' => 'trash'
]
]
]
]
]);

View File

@ -0,0 +1,46 @@
<?php
echo $this->element(
'genericElements/SingleViews/single_view',
[
'title' => 'Workflow view',
'data' => $data,
'fields' => [
[
'key' => __('Name'),
'path' => 'WorkflowPart.name'
],
[
'key' => __('ID'),
'path' => 'WorkflowPart.id'
],
[
'key' => __('UUID'),
'path' => 'WorkflowPart.uuid'
],
[
'key' => __('Timestamp'),
'path' => 'WorkflowPart.timestamp',
],
[
'key' => __('Description'),
'path' => 'WorkflowPart.description'
],
[
'key' => __('Data'),
'class' => 'restrict-height',
'path' => 'WorkflowPart.data',
'type' => 'json',
],
],
]
);
?>
<style>
.restrict-height > div {
height: 200px;
overflow: auto;
resize: both;
}
</style>

View File

@ -100,7 +100,7 @@ $triggerModules = $modules['blocks_trigger'];
</a>
<ul class="dropdown-menu pull-right">
<li id="control-import-blocks" class=""><a href="#"><i class="fa-fw <?= $this->FontAwesome->getClass('file-import') ?>"></i> <?= __('Import workflow parts') ?></a></li>
<li id="control-export-blocks" class="disabled"><a href="#"><i class="fa-fw <?= $this->FontAwesome->getClass('file-export') ?>"></i> <?= __('Export workflow parts') ?></a></li>
<li id="control-save-blocks" class="disabled"><a href="<?= $baseurl . '/workflowParts/add/1' ?>"><i class=" fa-fw <?= $this->FontAwesome->getClass('save') ?>"></i> <?= __('Save workflow parts') ?></a></li>
</ul>
</div>
</div>
@ -179,6 +179,7 @@ echo $this->element('genericElements/assetLoader', [
var $controlDuplicateButton = $('.control-buttons #control-duplicate')
var $controlDeleteButton = $('.control-buttons #control-delete')
var $controlExportBlocksLi = $('.control-buttons #control-export-blocks')
var $controlSaveBlocksLi = $('.control-buttons #control-save-blocks')
var $importWorkflowButton = $('#importWorkflow')
var $exportWorkflowButton = $('#exportWorkflow')
var $saveWorkflowButton = $('#saveWorkflow')
@ -200,4 +201,10 @@ echo $this->element('genericElements/assetLoader', [
$(document).ready(function() {
initDrawflow()
})
</script>
</script>
<style>
.dropdown-menu li.disabled a {
pointer-events: none;
}
</style>

View File

@ -336,7 +336,7 @@ function initDrawflow() {
editor.on('nodeSelected', function(node_id) {
$controlDuplicateButton.removeClass('disabled')
$controlDeleteButton.removeClass('disabled')
$controlExportBlocksLi.removeClass('disabled')
$controlSaveBlocksLi.removeClass('disabled')
selection.select([getNodeHtmlByID(node_id)])
})
editor.on('nodeUnselected', function() {
@ -346,7 +346,7 @@ function initDrawflow() {
selection.clearSelection()
$controlDuplicateButton.addClass('disabled')
$controlDeleteButton.addClass('disabled')
$controlExportBlocksLi.addClass('disabled')
$controlSaveBlocksLi.addClass('disabled')
})
var selection = new SelectionArea({
selectables: ['#drawflow .drawflow-node'],
@ -398,6 +398,49 @@ function initDrawflow() {
editor.removeNodeId(node.id)
})
})
$controlSaveBlocksLi.click(function(evt) {
var $link = $(this).find('a')
evt.preventDefault()
var selectedNodes = selection.getSelection()
var editorData = getEditorData()
openGenericModal($link.attr('href'), undefined, function() {
var nodes = selectedNodes.map(function (nodeHtml) { return editorData[nodeHtml.id.slice(5)] })
var $modal = $('#genericModal')
var $graphData = $modal.find('form #WorkflowPartData')
$graphData.val(JSON.stringify(nodes))
$modal.find('.modal-body').append(
$('<h3></h3>').append(
$('<span></span').text('Workflow Part Content '),
$('<a class="fas fa-copy" href="#"></a>')
.attr('title', 'Copy Workflow Part to clipboard')
.click(function() {
var $clicked = $(this)
navigator.clipboard.writeText(JSON.stringify(nodes)).then(function () {
$clicked.removeClass('fa-copy').addClass('fa-check').addClass('text-success')
setTimeout(function () {
$clicked.removeClass('fa-check').addClass('fa-copy').removeClass('text-success')
}, 2000);
}, function (err) {
console.error('Async: Could not copy text: ', err);
});
}),
)
)
var $ul = $('<ul></ul>')
nodes.forEach(function(node) {
$ul.append(
$('<li></li>').append(
$('<strong></strong>').text(node.data.name),
$('<ul></ul>').append(
node.data.saved_filters.length > 0 ? $('<li></li>').text('Has filter') : null,
node.data.params.length > 0 ? $('<li></li>').text('Has parameters') : null
)
)
)
})
$modal.find('.modal-body').append($ul)
})
})
$(window).bind('beforeunload', function() {
if (contentChanged) {