new: [crud:auditlog] Added auditlogs for entity being viewed

develop-unstable
Sami Mokaddem 2023-02-16 14:52:05 +01:00
parent adad45baf6
commit 62b2a1b264
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
9 changed files with 103 additions and 50 deletions

View File

@ -33,6 +33,10 @@ class CRUDComponent extends Component
public function index(array $options): void
{
$embedInModal = !empty($this->request->getQuery('embedInModal', false));
$excludeStats = !empty($this->request->getQuery('excludeStats', false));
$skipTableToolbar = !empty($this->request->getQuery('skipTableToolbar', false));
if (!empty($options['quickFilters'])) {
if (empty($options['filters'])) {
$options['filters'] = [];
@ -150,7 +154,7 @@ class CRUDComponent extends Component
return $template['enabled'];
}));
}
if (true) { // check if stats are requested
if (empty($excludeStats)) { // check if stats are requested
$modelStatistics = [];
if ($this->Table->hasBehavior('Timestamp')) {
$modelStatistics = $this->Table->getActivityStatisticsForModel(
@ -191,6 +195,8 @@ class CRUDComponent extends Component
}
$this->Controller->set('model', $this->Table);
$this->Controller->set('data', $data);
$this->Controller->set('embedInModal', $embedInModal);
$this->Controller->set('skipTableToolbar', $skipTableToolbar);
}
}

View File

@ -14,12 +14,14 @@ class InboxNavigation extends BaseNavigation
'icon' => 'trash',
'url' => '/inbox/discard/{{id}}',
'url_vars' => ['id' => 'id'],
'isPOST' => true,
]);
$this->bcf->addRoute('Inbox', 'process', [
'label' => __('Process message'),
'icon' => 'cogs',
'url' => '/inbox/process/{{id}}',
'url_vars' => ['id' => 'id'],
'isPOST' => true,
]);
}

View File

@ -15,12 +15,14 @@ class MetaTemplatesNavigation extends BaseNavigation
'icon' => 'check-square',
'url' => '/metaTemplates/toggle/{{id}}/enabled',
'url_vars' => ['id' => 'id'],
'isPOST' => true,
]);
$this->bcf->addRoute('MetaTemplates', 'set_default', [
'label' => __('Set as default'),
'icon' => 'check-square',
'url' => '/metaTemplates/toggle/{{id}}/default',
'url_vars' => ['id' => 'id'],
'isPOST' => true,
]);
$totalUpdateCount = 0;
@ -46,11 +48,13 @@ class MetaTemplatesNavigation extends BaseNavigation
'label' => __('Update template'),
'icon' => 'download',
'url' => '/metaTemplates/update',
'isPOST' => true,
]);
$this->bcf->addRoute('MetaTemplates', 'prune_outdated_template', [
'label' => __('Prune outdated template'),
'icon' => 'trash',
'url' => '/metaTemplates/prune_outdated_template',
'isPOST' => true,
]);
}

View File

@ -14,12 +14,14 @@ class OutboxNavigation extends BaseNavigation
'icon' => 'trash',
'url' => '/outbox/discard/{{id}}',
'url_vars' => ['id' => 'id'],
'isPOST' => true,
]);
$this->bcf->addRoute('Outbox', 'process', [
'label' => __('Process message'),
'icon' => 'cogs',
'url' => '/outbox/process/{{id}}',
'url_vars' => ['id' => 'id'],
'isPOST' => true,
]);
}

View File

@ -217,6 +217,7 @@ class BreadcrumbFactory
'label' => __('[new {0}]', $controller),
'icon' => 'plus',
'url' => "/{$controller}/add",
'isPOST' => true,
]);
} else if ($action === 'edit') {
$item = $this->genRouteConfig($controller, $action, [
@ -224,6 +225,7 @@ class BreadcrumbFactory
'icon' => 'edit',
'url' => "/{$controller}/edit/{{id}}",
'url_vars' => ['id' => 'id'],
'isPOST' => true,
'textGetter' => !empty($table->getDisplayField()) ? $table->getDisplayField() : 'id',
]);
} else if ($action === 'delete') {
@ -232,6 +234,15 @@ class BreadcrumbFactory
'icon' => 'trash',
'url' => "/{$controller}/delete/{{id}}",
'url_vars' => ['id' => 'id'],
'isPOST' => true,
'textGetter' => !empty($table->getDisplayField()) ? $table->getDisplayField() : 'id',
]);
} else if ($action === 'audit') {
$item = $this->genRouteConfig($controller, $action, [
'label' => __('Audit changes'),
'icon' => 'history',
'url' => "/audit-logs?model={{model}}&model_id={{id}}&sort=created&direction=desc&embedInModal=1&excludeStats=1&skipTableToolbar=1",
'url_vars' => ['id' => 'id', 'model' => ['raw' => $table->getAlias()]],
'textGetter' => !empty($table->getDisplayField()) ? $table->getDisplayField() : 'id',
]);
}
@ -253,6 +264,7 @@ class BreadcrumbFactory
$routeConfig = $this->addIfNotEmpty($routeConfig, $config, 'label');
$routeConfig = $this->addIfNotEmpty($routeConfig, $config, 'textGetter');
$routeConfig = $this->addIfNotEmpty($routeConfig, $config, 'badge');
$routeConfig = $this->addIfNotEmpty($routeConfig, $config, 'isPOST');
return $routeConfig;
}
@ -279,6 +291,7 @@ class BreadcrumbFactory
$this->addRoute($controller, 'add', $this->defaultCRUD($controller, 'add'));
$this->addRoute($controller, 'edit', $this->defaultCRUD($controller, 'edit'));
$this->addRoute($controller, 'delete', $this->defaultCRUD($controller, 'delete'));
$this->addRoute($controller, 'audit', $this->defaultCRUD($controller, 'audit'));
$this->addParent($controller, 'view', $controller, 'index');
$this->addParent($controller, 'add', $controller, 'index');
@ -292,8 +305,10 @@ class BreadcrumbFactory
$this->addAction($controller, 'view', $controller, 'add');
$this->addAction($controller, 'view', $controller, 'delete');
$this->addAction($controller, 'view', $controller, 'audit');
$this->addAction($controller, 'edit', $controller, 'add');
$this->addAction($controller, 'edit', $controller, 'delete');
$this->addAction($controller, 'edit', $controller, 'audit');
}
public function get($controller, $action)

View File

@ -2,24 +2,24 @@
use Cake\Utility\Text;
/*
* echo $this->element('/genericElements/IndexTable/index_table', [
* 'top_bar' => (
* // search/filter bar information compliant with ListTopBar
* ),
* 'data' => [
// the actual data to be used
* ),
* 'fields' => [
* // field list with information for the paginator, the elements used for the individual cells, etc
* ),
* 'title' => optional title,
* 'description' => optional description,
* 'notice' => optional alert to be placed at the top,
* 'index_statistics' => optional statistics to be displayed for the index,
* 'primary_id_path' => path to each primary ID (extracted and passed as $primary to fields)
* ));
*
*/
* echo $this->element('/genericElements/IndexTable/index_table', [
* 'top_bar' => (
* // search/filter bar information compliant with ListTopBar
* ),
* 'data' => [
// the actual data to be used
* ),
* 'fields' => [
* // field list with information for the paginator, the elements used for the individual cells, etc
* ),
* 'title' => optional title,
* 'description' => optional description,
* 'notice' => optional alert to be placed at the top,
* 'index_statistics' => optional statistics to be displayed for the index,
* 'primary_id_path' => path to each primary ID (extracted and passed as $primary to fields)
* ));
*
*/
$newMetaFields = [];
if (!empty($requestedMetaFields)) { // Create mapping for new index table fields on the fly
@ -40,49 +40,56 @@ if (!empty($requestedMetaFields)) { // Create mapping for new index table fields
$data['fields'] = array_merge($data['fields'], $newMetaFields);
$tableRandomValue = Cake\Utility\Security::randomString(8);
echo '<div id="table-container-' . h($tableRandomValue) . '">';
$html = '<div id="table-container-' . h($tableRandomValue) . '">';
if (!empty($data['title'])) {
echo Text::insert(
'<h2 class="fw-light">:title :help</h2>',
[
'title' => $this->ValueGetter->get($data['title']),
'help' => $this->Bootstrap->icon('info', [
'class' => ['fs-6', 'align-text-top',],
'title' => empty($data['description']) ? '' : h($data['description']),
'attrs' => [
'data-bs-toggle' => 'tooltip',
]
]),
]
);
if (empty($embedInModal)) {
$html .= Text::insert(
'<h2 class="fw-light">:title :help</h2>',
[
'title' => h($this->ValueGetter->get($data['title'])),
'help' => $this->Bootstrap->icon('info', [
'class' => ['fs-6', 'align-text-top',],
'title' => empty($data['description']) ? '' : h($data['description']),
'attrs' => [
'data-bs-toggle' => 'tooltip',
]
]),
]
);
} else {
$pageTitle = $this->Bootstrap->node('h5', [], h($this->ValueGetter->get($data['title'])));
}
}
if(!empty($notice)) {
echo $this->Bootstrap->alert($notice);
$html .= $this->Bootstrap->alert($notice);
}
if (!empty($modelStatistics)) {
echo $this->element('genericElements/IndexTable/Statistics/index_statistic_scaffold', [
$html .= $this->element('genericElements/IndexTable/Statistics/index_statistic_scaffold', [
'statistics' => $modelStatistics,
]);
}
echo '<div class="panel">';
$html .= '<div class="panel">';
if (!empty($data['html'])) {
echo sprintf('<div>%s</div>', $data['html']);
$html .= sprintf('<div>%s</div>', $data['html']);
}
$skipPagination = isset($data['skip_pagination']) ? $data['skip_pagination'] : 0;
if (!$skipPagination) {
$paginationData = !empty($data['paginatorOptions']) ? $data['paginatorOptions'] : [];
echo $this->element(
if (!empty($embedInModal)) {
$paginationData['update'] = ".modal-main-{$tableRandomValue}";
}
$html .= $this->element(
'/genericElements/IndexTable/pagination',
[
'paginationOptions' => $paginationData,
'tableRandomValue' => $tableRandomValue
]
);
echo $this->element(
$html .= $this->element(
'/genericElements/IndexTable/pagination_links'
);
}
@ -95,8 +102,8 @@ if (!empty($multiSelectData)) {
];
array_unshift($data['fields'], $multiSelectField);
}
if (!empty($data['top_bar'])) {
echo $this->element(
if (!empty($data['top_bar']) && empty($skipTableToolbar)) {
$html .= $this->element(
'/genericElements/ListTopBar/scaffold',
[
'data' => $data['top_bar'],
@ -144,7 +151,7 @@ foreach ($data['data'] as $k => $data_row) {
);
}
$tbody = '<tbody>' . $rows . '</tbody>';
echo sprintf(
$html .= sprintf(
'<table class="table table-hover" id="index-table-%s" data-table-random-value="%s" data-reload-url="%s">%s%s</table>',
$tableRandomValue,
$tableRandomValue,
@ -161,11 +168,23 @@ echo sprintf(
$tbody
);
if (!$skipPagination) {
echo $this->element('/genericElements/IndexTable/pagination_counter', $paginationData);
echo $this->element('/genericElements/IndexTable/pagination_links');
$html .= $this->element('/genericElements/IndexTable/pagination_counter', $paginationData);
$html .= $this->element('/genericElements/IndexTable/pagination_links');
}
$html .= '</div>';
$html .= '</div>';
if (!empty($embedInModal)) {
echo $this->Bootstrap->modal([
'titleHtml' => $pageTitle ?? '',
'bodyHtml' => $html,
'size' => 'xl',
'type' => 'ok-only',
'modalClass' => "modal-main-{$tableRandomValue}"
]);
} else {
echo $html;
}
echo '</div>';
echo '</div>';
?>
<script type="text/javascript">
$(document).ready(function() {

View File

@ -8,7 +8,7 @@
}
$onClick = sprintf(
'onClick="executePagination(%s, %s);"',
"'" . h($tableRandomValue) . "'",
"'" . h($options['update']) . "'",
"'{{url}}'"
);

View File

@ -79,10 +79,15 @@ if (!empty($breadcrumb)) {
if (!empty($actionEntry['badge'])) {
$badgeNumber += 1;
}
if (!empty($actionEntry['isPOST'])) {
$onclickFunction = sprintf('UI.overlayUntilResolve(this, UI.submissionModalAutoGuess(\'%s\'))', h(Router::url($actionEntry['url'])));
} else {
$onclickFunction = sprintf('UI.overlayUntilResolve(this, UI.modalFromUrl(\'%s\'))', h(Router::url($actionEntry['url'])));
}
$breadcrumbAction .= sprintf(
'<a class="dropdown-item %s" href="#" onclick="%s"><i class="me-1 %s"></i>%s%s</a>',
!empty($actionEntry['variant']) ? sprintf('dropdown-item-%s', $actionEntry['variant']) : '',
sprintf('UI.overlayUntilResolve(this, UI.submissionModalAutoGuess(\'%s\'))', h(Router::url($actionEntry['url']))),
$onclickFunction,
!empty($actionEntry['icon']) ? $this->FontAwesome->getClass(h($actionEntry['icon'])) : '',
h($actionEntry['label']),
!empty($actionEntry['badge']) ? $this->Bootstrap->badge($actionEntry['badge']) : ''

View File

@ -1,5 +1,5 @@
function executePagination(randomValue, url) {
UI.reload(url, $(`#table-container-${randomValue}`), $(`#table-container-${randomValue} table.table`))
function executePagination(selector, url) {
UI.reload(url, $(selector), $(selector).find('table.table'))
}
function executeStateDependencyChecks(dependenceSourceSelector) {