From 207bb590314775632fe5f1e02a380706aaf725d9 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 14 May 2024 19:25:14 +0300 Subject: [PATCH] feature-cfm291-bulkActions (#198) * Add first-pass at Bulk Actions box. Turn on bulk actions for Groups and Group Members. * Support bulk delete * Use Modal for bulk actions * css tweaks * Modal vue module * Style the bulk select actions; provide "dismissable" configuration on alert component; add language strings (CFM-291) * add datatables * improvements * mvea use common modal element * remove obsolete dependency * fix forgotten form field * use api/ajax/v2 endpoint * take into account restful and not only ajax requests.Description as a subject value on the report datatable. * fix typo * Stylistic and textual updates for Bulk Edit (CFM-291) --------- Co-authored-by: Arlen Johnson --- app/resources/locales/en_US/information.po | 6 + app/resources/locales/en_US/operation.po | 18 +- app/resources/locales/en_US/result.po | 9 + .../Component/RegistryAuthComponent.php | 11 +- app/src/Lib/Enum/BulkActionEnum.php | 76 +++ app/src/Lib/Enum/StandardEnum.php | 39 ++ app/src/View/Helper/VueHelper.php | 10 +- app/templates/GroupMembers/columns.inc | 14 +- app/templates/Groups/columns.inc | 13 +- app/templates/Notifications/fields.inc | 5 +- app/templates/Standard/index.php | 34 +- app/templates/element/bulk/bulk.php | 131 +++++ app/templates/element/bulk/checkbox.php | 56 ++ app/templates/element/javascript.php | 2 + app/templates/element/menuAction.php | 2 +- app/templates/element/mveaJs.php | 4 +- app/templates/element/mveaModal.php | 30 +- app/templates/layout/default.php | 7 +- app/templates/layout/iframe.php | 5 +- app/webroot/css/co-base.css | 86 +++- app/webroot/css/co-responsive.css | 19 + .../css/datatables/dataTables.bootstrap5.css | 487 ++++++++++++++++++ .../datatables/datatables-2.0.7.net.min.css | 15 + .../comanage/components/bulk/bulk-actions.js | 229 ++++++++ .../js/comanage/components/bulk/datatable.js | 97 ++++ .../js/comanage/components/common/alert.js | 104 ++++ .../{mvea/mvea-modal.js => common/modal.js} | 36 +- .../js/comanage/components/utils/helpers.js | 15 +- .../js/datatables/datatables-2.0.7.net.min.js | 4 + app/webroot/js/jquery/jquery.min.js | 4 +- 30 files changed, 1491 insertions(+), 77 deletions(-) create mode 100644 app/src/Lib/Enum/BulkActionEnum.php create mode 100644 app/templates/element/bulk/bulk.php create mode 100644 app/templates/element/bulk/checkbox.php create mode 100644 app/webroot/css/datatables/dataTables.bootstrap5.css create mode 100644 app/webroot/css/datatables/datatables-2.0.7.net.min.css create mode 100644 app/webroot/js/comanage/components/bulk/bulk-actions.js create mode 100644 app/webroot/js/comanage/components/bulk/datatable.js create mode 100644 app/webroot/js/comanage/components/common/alert.js rename app/webroot/js/comanage/components/{mvea/mvea-modal.js => common/modal.js} (62%) create mode 100644 app/webroot/js/datatables/datatables-2.0.7.net.min.js diff --git a/app/resources/locales/en_US/information.po b/app/resources/locales/en_US/information.po index 933cd91f0..85a1b293f 100644 --- a/app/resources/locales/en_US/information.po +++ b/app/resources/locales/en_US/information.po @@ -108,5 +108,11 @@ msgstr "Active, Cannot Be Disabled" msgid "plugin.inactive" msgstr "Inactive" +msgid "record" +msgstr "Record" + +msgid "report.for" +msgstr "Report for " + msgid "table.list" msgstr "{0} List" \ No newline at end of file diff --git a/app/resources/locales/en_US/operation.po b/app/resources/locales/en_US/operation.po index 473c81ebf..a80337754 100644 --- a/app/resources/locales/en_US/operation.po +++ b/app/resources/locales/en_US/operation.po @@ -36,12 +36,12 @@ msgstr "Add" msgid "add.a" msgstr "Add a New {0}" -msgid "add.bulk" -msgstr "Bulk add" - msgid "add.member" msgstr "Add member: " +msgid "apply" +msgstr "Apply" + msgid "autocomplete.pager.show.more" msgstr "show more" @@ -69,6 +69,12 @@ msgstr "Assign" msgid "any" msgstr "Any" +msgid "bulk.actions" +msgstr "Bulk Actions" + +msgid "bulk.add" +msgstr "Bulk add" + msgid "cancel" msgstr "Cancel" @@ -171,6 +177,9 @@ msgstr "Display records" msgid "page.goto" msgstr "Go to page" +msgid "pick" +msgstr "Pick" + msgid "previous" msgstr "Previous" @@ -210,6 +219,9 @@ msgstr "Search" msgid "search.global" msgstr "Global Search" +msgid "select.prompt" +msgstr "Please select..." + msgid "skip_to_content" msgstr "Skip to main content" diff --git a/app/resources/locales/en_US/result.po b/app/resources/locales/en_US/result.po index ec5506dec..fd77789d5 100644 --- a/app/resources/locales/en_US/result.po +++ b/app/resources/locales/en_US/result.po @@ -57,6 +57,9 @@ msgstr "External Identity status recalculated from {0} to {1}" msgid "ExternalIdentitySources.synced" msgstr "External Identity Source sync complete" +msgid "failed" +msgstr "failed" + msgid "Groups.added" msgstr "Group {0} created" @@ -154,6 +157,9 @@ msgstr "Created new External Identity via Pipeline {0} ({1}) using Source {2} ({ msgid "Pipelines.started" msgstr "Pipeline {0} ({1}) started for EIS {2} ({3}) source key {4}" +msgid "removed" +msgstr "removed" + msgid "saved" msgstr "Saved" @@ -190,3 +196,6 @@ msgstr "Database connection established" msgid "test.mail.ok" msgstr "Test message sent" + +msgid "updated" +msgstr "updated" diff --git a/app/src/Controller/Component/RegistryAuthComponent.php b/app/src/Controller/Component/RegistryAuthComponent.php index bc00de262..a1646e817 100644 --- a/app/src/Controller/Component/RegistryAuthComponent.php +++ b/app/src/Controller/Component/RegistryAuthComponent.php @@ -252,12 +252,21 @@ public function beforeFilter(EventInterface $event) { } if(Configure::read('debug')) { - // For testing purposes throw an error, but in production we want to + // For testing purposes, throw an error, but in production we want to // redirect to /login + if($request->is('ajax') || $request->is('restful')) { + // Permission denied + throw new ForbiddenException(__d('error', 'perm')); + } $controller->Flash->error("Authorization Failed (RegistryAuthComponent)"); return $controller->redirect("/"); } } + + if($request->is('ajax') || $request->is('restful')) { + // Permission denied + throw new ForbiddenException(__d('error', 'perm')); + } // No authentication, redirect to login diff --git a/app/src/Lib/Enum/BulkActionEnum.php b/app/src/Lib/Enum/BulkActionEnum.php new file mode 100644 index 000000000..d0169dde7 --- /dev/null +++ b/app/src/Lib/Enum/BulkActionEnum.php @@ -0,0 +1,76 @@ + 'delete', + 'owner' => 'post', + default => 'get' + }; + } + + /** + * Return actions to request http methods + * + * @param array $actions + * + * @return array + * @since COmanage Registry v5.0.0 + */ + + public static function actionsToMethods() : array + { + $ret = []; + foreach (self::getConstValues() as $act) { + $ret[$act] = match($act) { + 'delete' => 'delete', + 'owner' => 'post', + default => 'get' + }; + } + + return $ret; + } +} \ No newline at end of file diff --git a/app/src/Lib/Enum/StandardEnum.php b/app/src/Lib/Enum/StandardEnum.php index 80c20fb63..5cf23ea16 100644 --- a/app/src/Lib/Enum/StandardEnum.php +++ b/app/src/Lib/Enum/StandardEnum.php @@ -82,4 +82,43 @@ public static function getConstValues() : array { return array_values($consts); } + + /** + * Get the Keys for the constants in the Enumeration in Humanized form. + * + * @since COmanage Registry v5.0.0 + * @return array Array of enumeration keys + */ + + public static function getConstHumanized() : array { + // Get the keys for this enum + $reflect = new ReflectionClass(get_called_class()); + + $consts = $reflect->getConstants(); + + return collection(array_keys($consts))->map( + fn($key) => Inflector::humanize(Inflector::underscore($key)) + )->toList(); + } + + /** + * Reverse the Const. The key is now the value is the humanized form + * of the key. Ideal for Select elements + * + * @since COmanage Registry v5.0.0 + * @return array + */ + + public static function getHumanized() : array { + // Get the keys for this enum + $reflect = new ReflectionClass(get_called_class()); + + $consts = $reflect->getConstants(); + + $humanized = collection(array_keys($consts))->map( + fn($key) => Inflector::humanize(Inflector::underscore($key)) + )->toList(); + + return array_combine(array_values($consts), $humanized); + } } \ No newline at end of file diff --git a/app/src/View/Helper/VueHelper.php b/app/src/View/Helper/VueHelper.php index 54adaa1a3..3175e6732 100644 --- a/app/src/View/Helper/VueHelper.php +++ b/app/src/View/Helper/VueHelper.php @@ -51,11 +51,14 @@ class VueHelper extends Helper { 'login', 'primary', 'datepicker.hour', + 'status', 'unverified' ], 'information' => [ 'global.value.none', - 'datepicker.hour' + 'datepicker.hour', + 'record', + 'report.for' ], 'operation' => [ 'add', @@ -65,6 +68,11 @@ class VueHelper extends Helper { 'autocomplete.people.label', 'autocomplete.people.placeholder', 'close' + ], + 'result' => [ + 'failed', + 'removed', + 'updated' ] ]; diff --git a/app/templates/GroupMembers/columns.inc b/app/templates/GroupMembers/columns.inc index 3007190e8..8cef9bbc8 100644 --- a/app/templates/GroupMembers/columns.inc +++ b/app/templates/GroupMembers/columns.inc @@ -25,6 +25,8 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ +use App\Lib\Enum\BulkActionEnum; + $indexColumns = [ 'name' => [ 'type' => 'link', @@ -83,13 +85,11 @@ $topLinks = [ */ ]; -/* -// When the $bulkActions variable exists in a columns.inc config, the "Bulk edit" switch will appear in the index. -$bulkActions = [ - // TODO: develop bulk actions. For now, use a placeholder. - 'delete' => true -]; -*/ + +// When the $bulkActions variable exists in a columns.inc config, the "Bulk edit" switch will appear in the index. +// The array should contain a list of actions to be made available. +$bulkActions = [BulkActionEnum::Delete]; + $subnav = [ 'name' => 'group', diff --git a/app/templates/Groups/columns.inc b/app/templates/Groups/columns.inc index 2167470fd..75abf054e 100644 --- a/app/templates/Groups/columns.inc +++ b/app/templates/Groups/columns.inc @@ -25,6 +25,8 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ +use App\Lib\Enum\BulkActionEnum; + $indexColumns = [ 'name' => [ 'type' => 'link' @@ -73,10 +75,7 @@ $rowActions = [ ] ]; -/* -// When the $bulkActions variable exists in a columns.inc config, the "Bulk edit" switch will appear in the index. -$bulkActions = [ - // TODO: develop bulk actions. For now, use a placeholder. - 'delete' => true -]; -*/ \ No newline at end of file + +// When the $bulkActions variable exists in a columns.inc config, the "Bulk edit" switch will appear in the index. +// The array should contain a list of actions to be made available. +$bulkActions = [BulkActionEnum::Delete]; diff --git a/app/templates/Notifications/fields.inc b/app/templates/Notifications/fields.inc index c6a5819bd..449c5da13 100644 --- a/app/templates/Notifications/fields.inc +++ b/app/templates/Notifications/fields.inc @@ -58,7 +58,10 @@ if($vv_action == 'view') { ] ]); } else { - print $this->Field->control('status'); + print $this->element('form/listItem', [ + 'arguments' => [ + 'fieldName' => 'status' + ]]); } print $this->element('form/listItem', [ diff --git a/app/templates/Standard/index.php b/app/templates/Standard/index.php index d29a64b71..1e12d4e9d 100644 --- a/app/templates/Standard/index.php +++ b/app/templates/Standard/index.php @@ -169,14 +169,21 @@ - + + element('flash', $flashArgs); ?> + + + + + element('bulk/bulk', ['bulkActions' => $bulkActions]); ?> + - + '; } ?> - -
- - -
- + element('bulk/checkbox', compact('label')); + } ?> '; print '
'; print $this->element('menuAction', $action_args); print '
'; @@ -498,12 +501,7 @@ // Output the bulk-action checkbox and label if present if($isFirstLink && !empty($bulkActions)) { - print '
'; - print ''; - print ''; - print '
'; + print $this->element('bulk/checkbox', compact('label', 'entity')); } // $linkActions can be overridden in columns.inc to apply to all @@ -603,6 +601,10 @@ } break; } + + if($isFirstLink && !empty($rowActions)) { + print ''; // field-actions-container + } ?> diff --git a/app/templates/element/bulk/bulk.php b/app/templates/element/bulk/bulk.php new file mode 100644 index 000000000..08633b227 --- /dev/null +++ b/app/templates/element/bulk/bulk.php @@ -0,0 +1,131 @@ +name = Models +$modelsName = $this->name; +// $tablename = models +// XXX backport to match? +$tableName = Inflector::tableize($this->name); +$controllersName = Inflector::underscore($this->name); +$tableFK = Inflector::singularize($tableName) . "_id"; + +// Load my helper functions +$vueHelper = $this->loadHelper('Vue'); +// Get the CSRF Token in JavaScript +$token = $this->request->getAttribute('csrfToken'); + +$label = __d('operation','apply'); + +?> + + + +
diff --git a/app/templates/element/bulk/checkbox.php b/app/templates/element/bulk/checkbox.php new file mode 100644 index 000000000..eb14fa93a --- /dev/null +++ b/app/templates/element/bulk/checkbox.php @@ -0,0 +1,56 @@ +id}"; +} + +?> + +
+ + data-entity="" + data-entity-id="id ?>"> + + +
\ No newline at end of file diff --git a/app/templates/element/javascript.php b/app/templates/element/javascript.php index 23c171e21..de77d2194 100644 --- a/app/templates/element/javascript.php +++ b/app/templates/element/javascript.php @@ -271,8 +271,10 @@ // Bulk edit switch $('#bulk-edit-switch').click(function() { if($("#bulk-edit-switch").is(':checked')) { + $("body").addClass('bulk-mode'); $("table.index-table").removeClass('list-mode').addClass('bulk-edit-mode'); } else { + $("body").removeClass('bulk-mode'); $("table.index-table").removeClass('bulk-edit-mode').addClass('list-mode'); } }); diff --git a/app/templates/element/menuAction.php b/app/templates/element/menuAction.php index 723141a15..1832e89d1 100644 --- a/app/templates/element/menuAction.php +++ b/app/templates/element/menuAction.php @@ -149,7 +149,7 @@ class="">
  • -
    +
    diff --git a/app/templates/element/mveaJs.php b/app/templates/element/mveaJs.php index 7360b44e1..7b1d286bb 100644 --- a/app/templates/element/mveaJs.php +++ b/app/templates/element/mveaJs.php @@ -35,7 +35,7 @@ $mveaController = Cake\Utility\Inflector::camelize($mveaType); $title = __d('controller', $mveaController, [99]); -// Get the CSRF Token in JavaScript +// Get the CSRF Token in JavaScript $token = $this->request->getAttribute('csrfToken'); // Load my helper functions $vueHelper = $this->loadHelper('Vue'); @@ -47,10 +47,8 @@ -
    - - -
    +
    diff --git a/app/templates/layout/default.php b/app/templates/layout/default.php index b46685aad..fe70da0bf 100644 --- a/app/templates/layout/default.php +++ b/app/templates/layout/default.php @@ -59,9 +59,11 @@ Html->css([ 'bootstrap/bootstrap.min', + 'datatables/datatables-2.0.7.net.min', + 'datatables/dataTables.bootstrap5', 'co-color', 'co-base', - 'co-responsive' + 'co-responsive', ]) . PHP_EOL ?> @@ -71,7 +73,8 @@ 'jquery/jquery.min.js', 'vue/vue-3.2.31.global.prod.js', 'vue/primevue-3.52.0.core.min.js', - 'vue/primevue-3.52.0.autocomplete.min.js' + 'vue/primevue-3.52.0.autocomplete.min.js', + 'datatables/datatables-2.0.7.net.min.js', ]) . PHP_EOL ?> diff --git a/app/templates/layout/iframe.php b/app/templates/layout/iframe.php index 8dcf36fbc..01967245c 100644 --- a/app/templates/layout/iframe.php +++ b/app/templates/layout/iframe.php @@ -59,6 +59,8 @@ Html->css([ 'bootstrap/bootstrap.min', + 'datatables/datatables-2.0.7.net.min', + 'datatables/dataTables.bootstrap5', 'co-color', 'co-base', 'co-responsive' @@ -71,7 +73,8 @@ 'jquery/jquery.min.js', 'vue/vue-3.2.31.global.prod.js', 'vue/primevue-3.52.0.core.min.js', - 'vue/primevue-3.52.0.autocomplete.min.js' + 'vue/primevue-3.52.0.autocomplete.min.js', + 'datatables/datatables-2.0.7.net.min.js', ]) . PHP_EOL ?> diff --git a/app/webroot/css/co-base.css b/app/webroot/css/co-base.css index 006b8be8d..f66b87746 100644 --- a/app/webroot/css/co-base.css +++ b/app/webroot/css/co-base.css @@ -256,6 +256,18 @@ body.cos.select #top-bar { font-size: 1.1rem; margin-right: 0.5rem; } +.material-icons.danger, +.material-icons-outlined.danger { + color: var(--cmg-color-btn-004); +} +.material-icons.success, +.material-icons-outlined.success { + color: var(--cmg-color-highlight-007); +} +.material-icons.warning, +.material-icons-outlined.warning { + color: var(--cmg-color-highlight-018); +} .menu-grouping { padding: 1em; font-size: 0.9em; @@ -639,7 +651,7 @@ ul.form-list li.alert-banner .co-alert { .page-title-container { display: flex; justify-content: space-between; - margin: 1em 0 0.5em; + margin: 1em 0 0; align-items: baseline; flex-wrap: wrap; } @@ -1026,12 +1038,14 @@ h2.config-subtitle { } /* INDEX ACTION COMMAND MENUS */ th.with-field-actions { - padding-left: 2.75em; + padding-left: 3em; +} +table.list-mode td.with-field-actions { + padding: 0; } -td.with-field-actions { +table.list-mode .field-actions-container { display: flex; align-items: center; - padding: 0; } .field-actions .action-menu-toggle { display: inline-block; @@ -1065,7 +1079,29 @@ a.dropdown-item.deletebutton { } /* INDEX ACTION BULK EDIT */ .field-actions.top-links #bulk-edit-switch-container { - padding: 0.5em 1em; + padding: 0.5em 1em 1.5em; +} +#bulk-actions { + display: none; +} +body.bulk-mode #bulk-actions { + display: block; + width: 100%; + padding: 1em; + margin: 0 0 1em; + background-color: var(--cmg-color-bg-003); + border: 1px solid var(--cmg-color-bg-005); +} +body.bulk-mode #bulk-actions legend { + width: auto; + font-size: 1.2em; +} +#bulk-action-select { + margin-bottom: 0.75em; + font-size: 0.9rem; +} +#bulk-actions button { + float: right; } table.bulk-edit-mode a.row-link, table.bulk-edit-mode .read-only-link-container, @@ -1073,8 +1109,8 @@ table.bulk-edit-mode .row-link-heading, table.bulk-edit-mode .field-actions { display: none; } -table.bulk-edit-mode th.with-field-actions { - padding-left: 0.75em; +table.bulk-edit-mode .with-field-actions { + padding-left: 1em; } table.list-mode a.row-link, table.list-mode .field-actions { @@ -1084,11 +1120,37 @@ table.list-mode .bulk-action-checkbox-container { display: none; } table.index-table .form-check { - margin-bottom: 0; + margin: 0 0 0 -0.5em; min-height: unset; } +table.index-table .with-field-actions .form-check { + margin: 0; +} +.bulk-action-checkbox-container { + display: flex; + align-items: center; +} .bulk-action-checkbox-container .form-check-input { margin-right: 1em; + flex-shrink: 0; +} +#bulk-actions-modal .modal-body { + padding: 1rem; + overflow-y: auto; +} +#bulk-actions-modal .modal-body h3.data-table-modal-header { + margin-bottom: -1.75em; +} +#bulk-actions .progress { + width: 100%; + height: 3rem; + margin-bottom: 1rem; + background-color: var(--cmg-color-bg-006); + border: 1px solid var(--cmg-color-btn-bg-001); + border-radius: 0; +} +#bulk-actions .progress-bar { + background-color: var(--cmg-color-btn-bg-001); } /* PAGINATION */ #pagination { @@ -1998,10 +2060,13 @@ button, .btn, .btn:hover { border-color: var(--cmg-color-btn-bg-001); } .btn-primary, -.btn-primary:active { +.btn-primary:active, +.btn-secondary, +.btn-secondary:active { box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); } -.btn-primary:hover { +.btn-primary:hover, +.btn-secondary:hover { background-color: var(--cmg-color-btn-bg-002); color: var(--cmg-color-txt-inverse) !important; border-color: var(--cmg-color-btn-bg-002); @@ -2031,6 +2096,7 @@ html.dark-mode .btn-default:active { .btn-secondary { background-color: var(--cmg-color-btn-004); color: var(--cmg-color-txt-inverse); + border-color: var(--cmg-color-btn-004); } .btn-link { font-size: 1em; diff --git a/app/webroot/css/co-responsive.css b/app/webroot/css/co-responsive.css index 7a3e97b81..8549e72c5 100644 --- a/app/webroot/css/co-responsive.css +++ b/app/webroot/css/co-responsive.css @@ -375,6 +375,24 @@ margin-top: -3.5em; float: right; } + /* BULK EDIT */ + body.bulk-mode #bulk-actions { + display: flex; + flex-wrap: wrap; + gap: 1em; + align-items: center; + } + #bulk-actions legend { + margin: 0; + } + #bulk-action-select { + flex-grow: 1; + width: auto; + margin: 0; + } + .field-actions.top-links #bulk-edit-switch-container { + padding: 0 1em; + } /* CO CONFIGURATION DASHBOARD */ .page-title-features { display: flex; @@ -412,6 +430,7 @@ border: none; background-color: transparent; white-space: nowrap; + padding: 1.5em 0; } .field-actions.top-links .actions-expanded .action-list-item { display: inline-block; diff --git a/app/webroot/css/datatables/dataTables.bootstrap5.css b/app/webroot/css/datatables/dataTables.bootstrap5.css new file mode 100644 index 000000000..1d21cd0e1 --- /dev/null +++ b/app/webroot/css/datatables/dataTables.bootstrap5.css @@ -0,0 +1,487 @@ +@charset "UTF-8"; +:root { + --dt-row-selected: 13, 110, 253; + --dt-row-selected-text: 255, 255, 255; + --dt-row-selected-link: 9, 10, 11; + --dt-row-stripe: 0, 0, 0; + --dt-row-hover: 0, 0, 0; + --dt-column-ordering: 0, 0, 0; + --dt-html-background: white; +} +:root.dark { + --dt-html-background: rgb(33, 37, 41); +} + +table.dataTable td.dt-control { + text-align: center; + cursor: pointer; +} +table.dataTable td.dt-control:before { + display: inline-block; + box-sizing: border-box; + content: ""; + border-top: 5px solid transparent; + border-left: 10px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid transparent; + border-right: 0px solid transparent; +} +table.dataTable tr.dt-hasChild td.dt-control:before { + border-top: 10px solid rgba(0, 0, 0, 0.5); + border-left: 5px solid transparent; + border-bottom: 0px solid transparent; + border-right: 5px solid transparent; +} + +html.dark table.dataTable td.dt-control:before, +:root[data-bs-theme=dark] table.dataTable td.dt-control:before { + border-left-color: rgba(255, 255, 255, 0.5); +} +html.dark table.dataTable tr.dt-hasChild td.dt-control:before, +:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before { + border-top-color: rgba(255, 255, 255, 0.5); + border-left-color: transparent; +} + +div.dt-scroll-body thead tr, +div.dt-scroll-body tfoot tr { + height: 0; +} +div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td, +div.dt-scroll-body tfoot tr th, +div.dt-scroll-body tfoot tr td { + height: 0 !important; + padding-top: 0px !important; + padding-bottom: 0px !important; + border-top-width: 0px !important; + border-bottom-width: 0px !important; +} +div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing, +div.dt-scroll-body tfoot tr th div.dt-scroll-sizing, +div.dt-scroll-body tfoot tr td div.dt-scroll-sizing { + height: 0 !important; + overflow: hidden !important; +} + +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before { + position: absolute; + display: block; + bottom: 50%; + content: "▲"; + content: "▲"/""; +} +table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + position: absolute; + display: block; + top: 50%; + content: "▼"; + content: "▼"/""; +} +table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc, +table.dataTable thead > tr > td.dt-orderable-asc, +table.dataTable thead > tr > td.dt-orderable-desc, +table.dataTable thead > tr > td.dt-ordering-asc, +table.dataTable thead > tr > td.dt-ordering-desc { + position: relative; + padding-right: 30px; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order { + position: absolute; + right: 12px; + top: 0; + bottom: 0; + width: 12px; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + left: 0; + opacity: 0.125; + line-height: 9px; + font-size: 0.8em; +} +table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, +table.dataTable thead > tr > td.dt-orderable-asc, +table.dataTable thead > tr > td.dt-orderable-desc { + cursor: pointer; +} +table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover, +table.dataTable thead > tr > td.dt-orderable-asc:hover, +table.dataTable thead > tr > td.dt-orderable-desc:hover { + outline: 2px solid rgba(0, 0, 0, 0.05); + outline-offset: -2px; +} +table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + opacity: 0.6; +} +table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before, +table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after, +table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before { + display: none; +} +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} + +div.dt-scroll-body > table.dataTable > thead > tr > th, +div.dt-scroll-body > table.dataTable > thead > tr > td { + overflow: hidden; +} + +:root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover, +:root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover, +:root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover { + outline: 2px solid rgba(255, 255, 255, 0.05); +} + +div.dt-processing { + position: absolute; + top: 50%; + left: 50%; + width: 200px; + margin-left: -100px; + margin-top: -22px; + text-align: center; + padding: 2px; + z-index: 10; +} +div.dt-processing > div:last-child { + position: relative; + width: 80px; + height: 15px; + margin: 1em auto; +} +div.dt-processing > div:last-child > div { + position: absolute; + top: 0; + width: 13px; + height: 13px; + border-radius: 50%; + background: rgb(13, 110, 253); + background: rgb(var(--dt-row-selected)); + animation-timing-function: cubic-bezier(0, 1, 1, 0); +} +div.dt-processing > div:last-child > div:nth-child(1) { + left: 8px; + animation: datatables-loader-1 0.6s infinite; +} +div.dt-processing > div:last-child > div:nth-child(2) { + left: 8px; + animation: datatables-loader-2 0.6s infinite; +} +div.dt-processing > div:last-child > div:nth-child(3) { + left: 32px; + animation: datatables-loader-2 0.6s infinite; +} +div.dt-processing > div:last-child > div:nth-child(4) { + left: 56px; + animation: datatables-loader-3 0.6s infinite; +} + +@keyframes datatables-loader-1 { + 0% { + transform: scale(0); + } + 100% { + transform: scale(1); + } +} +@keyframes datatables-loader-3 { + 0% { + transform: scale(1); + } + 100% { + transform: scale(0); + } +} +@keyframes datatables-loader-2 { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(24px, 0); + } +} +table.dataTable.nowrap th, table.dataTable.nowrap td { + white-space: nowrap; +} +table.dataTable th, +table.dataTable td { + box-sizing: border-box; +} +table.dataTable th.dt-left, +table.dataTable td.dt-left { + text-align: left; +} +table.dataTable th.dt-center, +table.dataTable td.dt-center { + text-align: center; +} +table.dataTable th.dt-right, +table.dataTable td.dt-right { + text-align: right; +} +table.dataTable th.dt-justify, +table.dataTable td.dt-justify { + text-align: justify; +} +table.dataTable th.dt-nowrap, +table.dataTable td.dt-nowrap { + white-space: nowrap; +} +table.dataTable th.dt-empty, +table.dataTable td.dt-empty { + text-align: center; + vertical-align: top; +} +table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date, +table.dataTable td.dt-type-numeric, +table.dataTable td.dt-type-date { + text-align: right; +} +table.dataTable thead th, +table.dataTable thead td, +table.dataTable tfoot th, +table.dataTable tfoot td { + text-align: left; +} +table.dataTable thead th.dt-head-left, +table.dataTable thead td.dt-head-left, +table.dataTable tfoot th.dt-head-left, +table.dataTable tfoot td.dt-head-left { + text-align: left; +} +table.dataTable thead th.dt-head-center, +table.dataTable thead td.dt-head-center, +table.dataTable tfoot th.dt-head-center, +table.dataTable tfoot td.dt-head-center { + text-align: center; +} +table.dataTable thead th.dt-head-right, +table.dataTable thead td.dt-head-right, +table.dataTable tfoot th.dt-head-right, +table.dataTable tfoot td.dt-head-right { + text-align: right; +} +table.dataTable thead th.dt-head-justify, +table.dataTable thead td.dt-head-justify, +table.dataTable tfoot th.dt-head-justify, +table.dataTable tfoot td.dt-head-justify { + text-align: justify; +} +table.dataTable thead th.dt-head-nowrap, +table.dataTable thead td.dt-head-nowrap, +table.dataTable tfoot th.dt-head-nowrap, +table.dataTable tfoot td.dt-head-nowrap { + white-space: nowrap; +} +table.dataTable tbody th.dt-body-left, +table.dataTable tbody td.dt-body-left { + text-align: left; +} +table.dataTable tbody th.dt-body-center, +table.dataTable tbody td.dt-body-center { + text-align: center; +} +table.dataTable tbody th.dt-body-right, +table.dataTable tbody td.dt-body-right { + text-align: right; +} +table.dataTable tbody th.dt-body-justify, +table.dataTable tbody td.dt-body-justify { + text-align: justify; +} +table.dataTable tbody th.dt-body-nowrap, +table.dataTable tbody td.dt-body-nowrap { + white-space: nowrap; +} + +/*! Bootstrap 5 integration for DataTables + * + * ©2020 SpryMedia Ltd, all rights reserved. + * License: MIT datatables.net/license/mit + */ +table.table.dataTable { + clear: both; + margin-bottom: 0; + max-width: none; + border-spacing: 0; +} +table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * { + box-shadow: none; +} +table.table.dataTable > :not(caption) > * > * { + background-color: var(--bs-table-bg); +} +table.table.dataTable > tbody > tr { + background-color: transparent; +} +table.table.dataTable > tbody > tr.selected > * { + box-shadow: inset 0 0 0 9999px rgb(13, 110, 253); + box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected)); + color: rgb(255, 255, 255); + color: rgb(var(--dt-row-selected-text)); +} +table.table.dataTable > tbody > tr.selected a { + color: rgb(9, 10, 11); + color: rgb(var(--dt-row-selected-link)); +} +table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * { + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05); +} +table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95); +} +table.table.dataTable.table-hover > tbody > tr:hover > * { + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075); +} +table.table.dataTable.table-hover > tbody > tr.selected:hover > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975); +} + +div.dt-container div.dt-length label { + font-weight: normal; + text-align: left; + white-space: nowrap; +} +div.dt-container div.dt-length select { + width: auto; + display: inline-block; + margin-right: 0.5em; +} +div.dt-container div.dt-search { + text-align: right; +} +div.dt-container div.dt-search label { + font-weight: normal; + white-space: nowrap; + text-align: left; +} +div.dt-container div.dt-search input { + margin-left: 0.5em; + display: inline-block; + width: auto; +} +div.dt-container div.dt-info { + padding-top: 0.85em; +} +div.dt-container div.dt-paging { + margin: 0; +} +div.dt-container div.dt-paging ul.pagination { + margin: 2px 0; + flex-wrap: wrap; +} +div.dt-container div.dt-row { + position: relative; +} + +div.dt-scroll-head table.dataTable { + margin-bottom: 0 !important; +} + +div.dt-scroll-body { + border-bottom-color: var(--bs-border-color); + border-bottom-width: var(--bs-border-width); + border-bottom-style: solid; +} +div.dt-scroll-body > table { + border-top: none; + margin-top: 0 !important; + margin-bottom: 0 !important; +} +div.dt-scroll-body > table > tbody > tr:first-child { + border-top-width: 0; +} +div.dt-scroll-body > table > thead > tr { + border-width: 0 !important; +} +div.dt-scroll-body > table > tbody > tr:last-child > * { + border-bottom: none; +} + +div.dt-scroll-foot > .dt-scroll-footInner { + box-sizing: content-box; +} +div.dt-scroll-foot > .dt-scroll-footInner > table { + margin-top: 0 !important; + border-top: none; +} +div.dt-scroll-foot > .dt-scroll-footInner > table > tfoot > tr:first-child { + border-top-width: 0 !important; +} + +@media screen and (max-width: 767px) { + div.dt-container div.dt-length, + div.dt-container div.dt-search, + div.dt-container div.dt-info, + div.dt-container div.dt-paging { + text-align: center; + } + div.dt-container .row { + --bs-gutter-y: 0.5rem; + } + div.dt-container div.dt-paging ul.pagination { + justify-content: center !important; + } +} +table.dataTable.table-sm > thead > tr th.dt-orderable-asc, table.dataTable.table-sm > thead > tr th.dt-orderable-desc, table.dataTable.table-sm > thead > tr th.dt-ordering-asc, table.dataTable.table-sm > thead > tr th.dt-ordering-desc, +table.dataTable.table-sm > thead > tr td.dt-orderable-asc, +table.dataTable.table-sm > thead > tr td.dt-orderable-desc, +table.dataTable.table-sm > thead > tr td.dt-ordering-asc, +table.dataTable.table-sm > thead > tr td.dt-ordering-desc { + padding-right: 20px; +} +table.dataTable.table-sm > thead > tr th.dt-orderable-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-orderable-desc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-desc span.dt-column-order, +table.dataTable.table-sm > thead > tr td.dt-orderable-asc span.dt-column-order, +table.dataTable.table-sm > thead > tr td.dt-orderable-desc span.dt-column-order, +table.dataTable.table-sm > thead > tr td.dt-ordering-asc span.dt-column-order, +table.dataTable.table-sm > thead > tr td.dt-ordering-desc span.dt-column-order { + right: 5px; +} + +div.dt-scroll-head table.table-bordered { + border-bottom-width: 0; +} + +div.table-responsive > div.dt-container > div.row { + margin: 0; +} +div.table-responsive > div.dt-container > div.row > div[class^=col-]:first-child { + padding-left: 0; +} +div.table-responsive > div.dt-container > div.row > div[class^=col-]:last-child { + padding-right: 0; +} + +:root[data-bs-theme=dark] { + --dt-row-hover: 255, 255, 255; + --dt-row-stripe: 255, 255, 255; + --dt-column-ordering: 255, 255, 255; +} diff --git a/app/webroot/css/datatables/datatables-2.0.7.net.min.css b/app/webroot/css/datatables/datatables-2.0.7.net.min.css new file mode 100644 index 000000000..578881446 --- /dev/null +++ b/app/webroot/css/datatables/datatables-2.0.7.net.min.css @@ -0,0 +1,15 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#dt/dt-2.0.7 + * + * Included libraries: + * DataTables 2.0.7 + */ + +:root{--dt-row-selected: 13, 110, 253;--dt-row-selected-text: 255, 255, 255;--dt-row-selected-link: 9, 10, 11;--dt-row-stripe: 0, 0, 0;--dt-row-hover: 0, 0, 0;--dt-column-ordering: 0, 0, 0;--dt-html-background: white}:root.dark{--dt-html-background: rgb(33, 37, 41)}table.dataTable td.dt-control{text-align:center;cursor:pointer}table.dataTable td.dt-control:before{display:inline-block;box-sizing:border-box;content:"";border-top:5px solid transparent;border-left:10px solid rgba(0, 0, 0, 0.5);border-bottom:5px solid transparent;border-right:0px solid transparent}table.dataTable tr.dt-hasChild td.dt-control:before{border-top:10px solid rgba(0, 0, 0, 0.5);border-left:5px solid transparent;border-bottom:0px solid transparent;border-right:5px solid transparent}html.dark table.dataTable td.dt-control:before,:root[data-bs-theme=dark] table.dataTable td.dt-control:before{border-left-color:rgba(255, 255, 255, 0.5)}html.dark table.dataTable tr.dt-hasChild td.dt-control:before,:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before{border-top-color:rgba(255, 255, 255, 0.5);border-left-color:transparent}div.dt-scroll-body thead tr,div.dt-scroll-body tfoot tr{height:0}div.dt-scroll-body thead tr th,div.dt-scroll-body thead tr td,div.dt-scroll-body tfoot tr th,div.dt-scroll-body tfoot tr td{height:0 !important;padding-top:0px !important;padding-bottom:0px !important;border-top-width:0px !important;border-bottom-width:0px !important}div.dt-scroll-body thead tr th div.dt-scroll-sizing,div.dt-scroll-body thead tr td div.dt-scroll-sizing,div.dt-scroll-body tfoot tr th div.dt-scroll-sizing,div.dt-scroll-body tfoot tr td div.dt-scroll-sizing{height:0 !important;overflow:hidden !important}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead>tr>th.dt-orderable-asc span.dt-column-order:before,table.dataTable thead>tr>th.dt-ordering-asc span.dt-column-order:before,table.dataTable thead>tr>td.dt-orderable-asc span.dt-column-order:before,table.dataTable thead>tr>td.dt-ordering-asc span.dt-column-order:before{position:absolute;display:block;bottom:50%;content:"▲";content:"▲"/""}table.dataTable thead>tr>th.dt-orderable-desc span.dt-column-order:after,table.dataTable thead>tr>th.dt-ordering-desc span.dt-column-order:after,table.dataTable thead>tr>td.dt-orderable-desc span.dt-column-order:after,table.dataTable thead>tr>td.dt-ordering-desc span.dt-column-order:after{position:absolute;display:block;top:50%;content:"▼";content:"▼"/""}table.dataTable thead>tr>th.dt-orderable-asc,table.dataTable thead>tr>th.dt-orderable-desc,table.dataTable thead>tr>th.dt-ordering-asc,table.dataTable thead>tr>th.dt-ordering-desc,table.dataTable thead>tr>td.dt-orderable-asc,table.dataTable thead>tr>td.dt-orderable-desc,table.dataTable thead>tr>td.dt-ordering-asc,table.dataTable thead>tr>td.dt-ordering-desc{position:relative;padding-right:30px}table.dataTable thead>tr>th.dt-orderable-asc span.dt-column-order,table.dataTable thead>tr>th.dt-orderable-desc span.dt-column-order,table.dataTable thead>tr>th.dt-ordering-asc span.dt-column-order,table.dataTable thead>tr>th.dt-ordering-desc span.dt-column-order,table.dataTable thead>tr>td.dt-orderable-asc span.dt-column-order,table.dataTable thead>tr>td.dt-orderable-desc span.dt-column-order,table.dataTable thead>tr>td.dt-ordering-asc span.dt-column-order,table.dataTable thead>tr>td.dt-ordering-desc span.dt-column-order{position:absolute;right:12px;top:0;bottom:0;width:12px}table.dataTable thead>tr>th.dt-orderable-asc span.dt-column-order:before,table.dataTable thead>tr>th.dt-orderable-asc span.dt-column-order:after,table.dataTable thead>tr>th.dt-orderable-desc span.dt-column-order:before,table.dataTable thead>tr>th.dt-orderable-desc span.dt-column-order:after,table.dataTable thead>tr>th.dt-ordering-asc span.dt-column-order:before,table.dataTable thead>tr>th.dt-ordering-asc span.dt-column-order:after,table.dataTable thead>tr>th.dt-ordering-desc span.dt-column-order:before,table.dataTable thead>tr>th.dt-ordering-desc span.dt-column-order:after,table.dataTable thead>tr>td.dt-orderable-asc span.dt-column-order:before,table.dataTable thead>tr>td.dt-orderable-asc span.dt-column-order:after,table.dataTable thead>tr>td.dt-orderable-desc span.dt-column-order:before,table.dataTable thead>tr>td.dt-orderable-desc span.dt-column-order:after,table.dataTable thead>tr>td.dt-ordering-asc span.dt-column-order:before,table.dataTable thead>tr>td.dt-ordering-asc span.dt-column-order:after,table.dataTable thead>tr>td.dt-ordering-desc span.dt-column-order:before,table.dataTable thead>tr>td.dt-ordering-desc span.dt-column-order:after{left:0;opacity:.125;line-height:9px;font-size:.8em}table.dataTable thead>tr>th.dt-orderable-asc,table.dataTable thead>tr>th.dt-orderable-desc,table.dataTable thead>tr>td.dt-orderable-asc,table.dataTable thead>tr>td.dt-orderable-desc{cursor:pointer}table.dataTable thead>tr>th.dt-orderable-asc:hover,table.dataTable thead>tr>th.dt-orderable-desc:hover,table.dataTable thead>tr>td.dt-orderable-asc:hover,table.dataTable thead>tr>td.dt-orderable-desc:hover{outline:2px solid rgba(0, 0, 0, 0.05);outline-offset:-2px}table.dataTable thead>tr>th.dt-ordering-asc span.dt-column-order:before,table.dataTable thead>tr>th.dt-ordering-desc span.dt-column-order:after,table.dataTable thead>tr>td.dt-ordering-asc span.dt-column-order:before,table.dataTable thead>tr>td.dt-ordering-desc span.dt-column-order:after{opacity:.6}table.dataTable thead>tr>th.sorting_desc_disabled span.dt-column-order:after,table.dataTable thead>tr>th.sorting_asc_disabled span.dt-column-order:before,table.dataTable thead>tr>td.sorting_desc_disabled span.dt-column-order:after,table.dataTable thead>tr>td.sorting_asc_disabled span.dt-column-order:before{display:none}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}div.dt-scroll-body>table.dataTable>thead>tr>th,div.dt-scroll-body>table.dataTable>thead>tr>td{overflow:hidden}:root.dark table.dataTable thead>tr>th.dt-orderable-asc:hover,:root.dark table.dataTable thead>tr>th.dt-orderable-desc:hover,:root.dark table.dataTable thead>tr>td.dt-orderable-asc:hover,:root.dark table.dataTable thead>tr>td.dt-orderable-desc:hover,:root[data-bs-theme=dark] table.dataTable thead>tr>th.dt-orderable-asc:hover,:root[data-bs-theme=dark] table.dataTable thead>tr>th.dt-orderable-desc:hover,:root[data-bs-theme=dark] table.dataTable thead>tr>td.dt-orderable-asc:hover,:root[data-bs-theme=dark] table.dataTable thead>tr>td.dt-orderable-desc:hover{outline:2px solid rgba(255, 255, 255, 0.05)}div.dt-processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-22px;text-align:center;padding:2px;z-index:10}div.dt-processing>div:last-child{position:relative;width:80px;height:15px;margin:1em auto}div.dt-processing>div:last-child>div{position:absolute;top:0;width:13px;height:13px;border-radius:50%;background:rgb(13, 110, 253);background:rgb(var(--dt-row-selected));animation-timing-function:cubic-bezier(0, 1, 1, 0)}div.dt-processing>div:last-child>div:nth-child(1){left:8px;animation:datatables-loader-1 .6s infinite}div.dt-processing>div:last-child>div:nth-child(2){left:8px;animation:datatables-loader-2 .6s infinite}div.dt-processing>div:last-child>div:nth-child(3){left:32px;animation:datatables-loader-2 .6s infinite}div.dt-processing>div:last-child>div:nth-child(4){left:56px;animation:datatables-loader-3 .6s infinite}@keyframes datatables-loader-1{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes datatables-loader-3{0%{transform:scale(1)}100%{transform:scale(0)}}@keyframes datatables-loader-2{0%{transform:translate(0, 0)}100%{transform:translate(24px, 0)}}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable th,table.dataTable td{box-sizing:border-box}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable th.dt-empty,table.dataTable td.dt-empty{text-align:center;vertical-align:top}table.dataTable th.dt-type-numeric,table.dataTable th.dt-type-date,table.dataTable td.dt-type-numeric,table.dataTable td.dt-type-date{text-align:right}table.dataTable thead th,table.dataTable thead td,table.dataTable tfoot th,table.dataTable tfoot td{text-align:left}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable{width:100%;margin:0 auto;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable>thead>tr>th,table.dataTable>thead>tr>td{padding:10px;border-bottom:1px solid rgba(0, 0, 0, 0.3)}table.dataTable>thead>tr>th:active,table.dataTable>thead>tr>td:active{outline:none}table.dataTable>tfoot>tr>th,table.dataTable>tfoot>tr>td{border-top:1px solid rgba(0, 0, 0, 0.3);padding:10px 10px 6px 10px}table.dataTable>tbody>tr{background-color:transparent}table.dataTable>tbody>tr:first-child>*{border-top:none}table.dataTable>tbody>tr:last-child>*{border-bottom:none}table.dataTable>tbody>tr.selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.9);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.9);color:rgb(255, 255, 255);color:rgb(var(--dt-row-selected-text))}table.dataTable>tbody>tr.selected a{color:rgb(9, 10, 11);color:rgb(var(--dt-row-selected-link))}table.dataTable>tbody>tr>th,table.dataTable>tbody>tr>td{padding:8px 10px}table.dataTable.row-border>tbody>tr>*,table.dataTable.display>tbody>tr>*{border-top:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.row-border>tbody>tr:first-child>*,table.dataTable.display>tbody>tr:first-child>*{border-top:none}table.dataTable.row-border>tbody>tr.selected+tr.selected>td,table.dataTable.display>tbody>tr.selected+tr.selected>td{border-top-color:rgba(13, 110, 253, 0.65);border-top-color:rgba(var(--dt-row-selected), 0.65)}table.dataTable.cell-border>tbody>tr>*{border-top:1px solid rgba(0, 0, 0, 0.15);border-right:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.cell-border>tbody>tr>*:first-child{border-left:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.cell-border>tbody>tr:first-child>*{border-top:1px solid rgba(0, 0, 0, 0.3)}table.dataTable.stripe>tbody>tr:nth-child(odd)>*,table.dataTable.display>tbody>tr:nth-child(odd)>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.023);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.023)}table.dataTable.stripe>tbody>tr:nth-child(odd).selected>*,table.dataTable.display>tbody>tr:nth-child(odd).selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.923);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.923)}table.dataTable.hover>tbody>tr:hover>*,table.dataTable.display>tbody>tr:hover>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.035);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.035)}table.dataTable.hover>tbody>tr.selected:hover>*,table.dataTable.display>tbody>tr.selected:hover>*{box-shadow:inset 0 0 0 9999px #0d6efd !important;box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 1) !important}table.dataTable.order-column>tbody tr>.sorting_1,table.dataTable.order-column>tbody tr>.sorting_2,table.dataTable.order-column>tbody tr>.sorting_3,table.dataTable.display>tbody tr>.sorting_1,table.dataTable.display>tbody tr>.sorting_2,table.dataTable.display>tbody tr>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.019);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019)}table.dataTable.order-column>tbody tr.selected>.sorting_1,table.dataTable.order-column>tbody tr.selected>.sorting_2,table.dataTable.order-column>tbody tr.selected>.sorting_3,table.dataTable.display>tbody tr.selected>.sorting_1,table.dataTable.display>tbody tr.selected>.sorting_2,table.dataTable.display>tbody tr.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.919);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919)}table.dataTable.display>tbody>tr:nth-child(odd)>.sorting_1,table.dataTable.order-column.stripe>tbody>tr:nth-child(odd)>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.054);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.054)}table.dataTable.display>tbody>tr:nth-child(odd)>.sorting_2,table.dataTable.order-column.stripe>tbody>tr:nth-child(odd)>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.047);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.047)}table.dataTable.display>tbody>tr:nth-child(odd)>.sorting_3,table.dataTable.order-column.stripe>tbody>tr:nth-child(odd)>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.039);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.039)}table.dataTable.display>tbody>tr:nth-child(odd).selected>.sorting_1,table.dataTable.order-column.stripe>tbody>tr:nth-child(odd).selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.954);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.954)}table.dataTable.display>tbody>tr:nth-child(odd).selected>.sorting_2,table.dataTable.order-column.stripe>tbody>tr:nth-child(odd).selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.947);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.947)}table.dataTable.display>tbody>tr:nth-child(odd).selected>.sorting_3,table.dataTable.order-column.stripe>tbody>tr:nth-child(odd).selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.939);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.939)}table.dataTable.display>tbody>tr.even>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.019);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019)}table.dataTable.display>tbody>tr.even>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.011);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.011)}table.dataTable.display>tbody>tr.even>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.003);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.003)}table.dataTable.display>tbody>tr.even.selected>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.919);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919)}table.dataTable.display>tbody>tr.even.selected>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.911);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.911)}table.dataTable.display>tbody>tr.even.selected>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.903);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.903)}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.082);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.082)}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.074);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.074)}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.062);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.062)}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.982);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.982)}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.974);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.974)}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.962);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.962)}table.dataTable.compact thead th,table.dataTable.compact thead td,table.dataTable.compact tfoot th,table.dataTable.compact tfoot td,table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}div.dt-container{position:relative;clear:both}div.dt-container div.dt-layout-row{display:table;clear:both;width:100%}div.dt-container div.dt-layout-row.dt-layout-table{display:block}div.dt-container div.dt-layout-row.dt-layout-table div.dt-layout-cell{display:block}div.dt-container div.dt-layout-cell{display:table-cell;vertical-align:middle;padding:5px 0}div.dt-container div.dt-layout-cell.dt-full{text-align:center}div.dt-container div.dt-layout-cell.dt-start{text-align:left}div.dt-container div.dt-layout-cell.dt-end{text-align:right}div.dt-container div.dt-layout-cell:empty{display:none}div.dt-container .dt-search input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;color:inherit;margin-left:3px}div.dt-container .dt-input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;color:inherit}div.dt-container select.dt-input{padding:4px}div.dt-container .dt-paging .dt-paging-button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;color:inherit !important;border:1px solid transparent;border-radius:2px;background:transparent}div.dt-container .dt-paging .dt-paging-button.current,div.dt-container .dt-paging .dt-paging-button.current:hover{color:inherit !important;border:1px solid rgba(0, 0, 0, 0.3);background-color:rgba(0, 0, 0, 0.05);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.05)), color-stop(100%, rgba(0, 0, 0, 0.05)));background:-webkit-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%);background:-moz-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%);background:-ms-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%);background:-o-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%);background:linear-gradient(to bottom, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%)}div.dt-container .dt-paging .dt-paging-button.disabled,div.dt-container .dt-paging .dt-paging-button.disabled:hover,div.dt-container .dt-paging .dt-paging-button.disabled:active{cursor:default;color:rgba(0, 0, 0, 0.5) !important;border:1px solid transparent;background:transparent;box-shadow:none}div.dt-container .dt-paging .dt-paging-button:hover{color:white !important;border:1px solid #111;background-color:#111;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}div.dt-container .dt-paging .dt-paging-button:active{outline:none;background-color:#0c0c0c;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}div.dt-container .dt-paging .ellipsis{padding:0 1em}div.dt-container .dt-length,div.dt-container .dt-search,div.dt-container .dt-info,div.dt-container .dt-processing,div.dt-container .dt-paging{color:inherit}div.dt-container .dataTables_scroll{clear:both}div.dt-container .dataTables_scroll div.dt-scroll-body{-webkit-overflow-scrolling:touch}div.dt-container .dataTables_scroll div.dt-scroll-body>table>thead>tr>th,div.dt-container .dataTables_scroll div.dt-scroll-body>table>thead>tr>td,div.dt-container .dataTables_scroll div.dt-scroll-body>table>tbody>tr>th,div.dt-container .dataTables_scroll div.dt-scroll-body>table>tbody>tr>td{vertical-align:middle}div.dt-container .dataTables_scroll div.dt-scroll-body>table>thead>tr>th>div.dataTables_sizing,div.dt-container .dataTables_scroll div.dt-scroll-body>table>thead>tr>td>div.dataTables_sizing,div.dt-container .dataTables_scroll div.dt-scroll-body>table>tbody>tr>th>div.dataTables_sizing,div.dt-container .dataTables_scroll div.dt-scroll-body>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}div.dt-container.dt-empty-footer tbody>tr:last-child>*{border-bottom:1px solid rgba(0, 0, 0, 0.3)}div.dt-container.dt-empty-footer .dt-scroll-body{border-bottom:1px solid rgba(0, 0, 0, 0.3)}div.dt-container.dt-empty-footer .dt-scroll-body tbody>tr:last-child>*{border-bottom:none}@media screen and (max-width: 767px){div.dt-container div.dt-layout-row{display:block}div.dt-container div.dt-layout-cell{display:block}div.dt-container div.dt-layout-cell.dt-full,div.dt-container div.dt-layout-cell.dt-start,div.dt-container div.dt-layout-cell.dt-end{text-align:center}}@media screen and (max-width: 640px){.dt-container .dt-length,.dt-container .dt-search{float:none;text-align:center}.dt-container .dt-search{margin-top:.5em}}html.dark{--dt-row-hover: 255, 255, 255;--dt-row-stripe: 255, 255, 255;--dt-column-ordering: 255, 255, 255}html.dark table.dataTable>thead>tr>th,html.dark table.dataTable>thead>tr>td{border-bottom:1px solid rgb(89, 91, 94)}html.dark table.dataTable>thead>tr>th:active,html.dark table.dataTable>thead>tr>td:active{outline:none}html.dark table.dataTable>tfoot>tr>th,html.dark table.dataTable>tfoot>tr>td{border-top:1px solid rgb(89, 91, 94)}html.dark table.dataTable.row-border>tbody>tr>*,html.dark table.dataTable.display>tbody>tr>*{border-top:1px solid rgb(64, 67, 70)}html.dark table.dataTable.row-border>tbody>tr:first-child>*,html.dark table.dataTable.display>tbody>tr:first-child>*{border-top:none}html.dark table.dataTable.row-border>tbody>tr.selected+tr.selected>td,html.dark table.dataTable.display>tbody>tr.selected+tr.selected>td{border-top-color:rgba(13, 110, 253, 0.65);border-top-color:rgba(var(--dt-row-selected), 0.65)}html.dark table.dataTable.cell-border>tbody>tr>th,html.dark table.dataTable.cell-border>tbody>tr>td{border-top:1px solid rgb(64, 67, 70);border-right:1px solid rgb(64, 67, 70)}html.dark table.dataTable.cell-border>tbody>tr>th:first-child,html.dark table.dataTable.cell-border>tbody>tr>td:first-child{border-left:1px solid rgb(64, 67, 70)}html.dark .dt-container.dt-empty-footer table.dataTable{border-bottom:1px solid rgb(89, 91, 94)}html.dark .dt-container .dt-search input,html.dark .dt-container .dt-length select{border:1px solid rgba(255, 255, 255, 0.2);background-color:var(--dt-html-background)}html.dark .dt-container .dt-paging .dt-paging-button.current,html.dark .dt-container .dt-paging .dt-paging-button.current:hover{border:1px solid rgb(89, 91, 94);background:rgba(255, 255, 255, 0.15)}html.dark .dt-container .dt-paging .dt-paging-button.disabled,html.dark .dt-container .dt-paging .dt-paging-button.disabled:hover,html.dark .dt-container .dt-paging .dt-paging-button.disabled:active{color:#666 !important}html.dark .dt-container .dt-paging .dt-paging-button:hover{border:1px solid rgb(53, 53, 53);background:rgb(53, 53, 53)}html.dark .dt-container .dt-paging .dt-paging-button:active{background:#3a3a3a}*[dir=rtl] table.dataTable thead th,*[dir=rtl] table.dataTable thead td,*[dir=rtl] table.dataTable tfoot th,*[dir=rtl] table.dataTable tfoot td{text-align:right}*[dir=rtl] table.dataTable th.dt-type-numeric,*[dir=rtl] table.dataTable th.dt-type-date,*[dir=rtl] table.dataTable td.dt-type-numeric,*[dir=rtl] table.dataTable td.dt-type-date{text-align:left}*[dir=rtl] div.dt-container div.dt-layout-cell.dt-start{text-align:right}*[dir=rtl] div.dt-container div.dt-layout-cell.dt-end{text-align:left}*[dir=rtl] div.dt-container div.dt-search input{margin:0 3px 0 0} + + diff --git a/app/webroot/js/comanage/components/bulk/bulk-actions.js b/app/webroot/js/comanage/components/bulk/bulk-actions.js new file mode 100644 index 000000000..950d9b191 --- /dev/null +++ b/app/webroot/js/comanage/components/bulk/bulk-actions.js @@ -0,0 +1,229 @@ +/** + * COmanage Registry Bulk Actions Vue.js Component + * + * Portions licensed to the University Corporation for Advanced Internet + * Development, Inc. ("UCAID") under one or more contributor license agreements. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * UCAID licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @link https://www.internet2.edu/comanage COmanage Project + * @package registry + * @since COmanage Registry v5.0.0 + * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +import Alert from '../common/alert.js' +import Modal from '../common/modal.js' +import Datatable from './datatable.js'; +import {capitalize} from "../utils/helpers.js" + +export default { + data() { + return { + loading: false, + num: 0, + progress: 0, + ids: [], + info: {}, + selected: '', + failed: 0, + succeeded: 0, + responses: [], + responses_json: [] + } + }, + components: { + Alert, + Modal, + Datatable + }, + props: { + label: { + type: String, + default: "Apply" + }, + legend: { + type: String, + default: "Not provided" + }, + selectprompt: { + type: String, + default: "Please select..." + }, + bulkactions: { + type: Array, + default: [] + } + }, + inject: ['txt', 'api', 'app'], + mounted: function() { + this.reload() + }, + methods: { + getActionDescription(action) { + return JSON.parse(this.app.bulkactionfull)[action] + }, + reload() { + // Reload the view to fetch the latest changes + if($('#bulk-actions-modal').length) { + $('#bulk-actions-modal').on('hidden.bs.modal', function() { + location.reload() + }) + } + }, + capitalize, + async allProgress(fetches, progress_cb) { + let d = 0; + progress_cb(0); + for (const p of fetches) { + p.then(()=> { + d ++; + progress_cb( (d * 100) / fetches.length ); + }); + } + return Promise.all(fetches) + }, + async run() { + this.loading = true; + // Construct the url + const urlString = window.location.protocol + + "//" + window.location.host + + this.api.webroot + // We have to send the request to the ajax end point + + 'api/ajax/v2/' + + this.app.controller + + this.allProgress( + this.ids.map((idx, id) => { + let finalUrl = urlString + "/" + id + let method = JSON.parse(this.app.bulkactionmethods)[this.selected] + + let request_init = { + headers: new Headers({ + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'application/json', + 'X-CSRF-Token': this.api.token + }), + method: method.toUpperCase() + } + + // AJAX Request + let request = new Request(finalUrl, request_init); + return fetch(request) + }), + (prog) => this.progress = prog + ).then((responses) => { + Promise.all(responses.map(resp => resp.json().catch(err => { + console.error(`${err} while parsing response`) + return {} + }))).then(dataList => { + this.responses_json = dataList + return dataList + }).then(dataList => { + this.responses = responses + this.failed = responses.filter((resp) => !resp.ok).length + this.succeeded = responses.filter((resp) => resp.ok).length + this.loading = false + }) + }).catch((error) => { + this.loading = false + console.error(error.message); + }); + }, + rowChecked() { + let checked = $('.bulk-action-checkbox-container input.form-check-input[type=checkbox]:checked'); + this.ids = checked.map((idx, item) => item.getAttribute('data-entity-id')) + this.info = checked.map((idx, item) => { + let row = JSON.parse(item.getAttribute('data-entity')) + if(row?.name) { + return {id: row.id, label: row?.name} + } else if(row?.person?.primary_name?.full_name) { + return {id: row.id, label: row?.person?.primary_name?.full_name} + } else if(row?.description) { + return {id: row.id, label: row?.description} + } + }) + this.num = this.ids.length; + } + }, + computed: { + roundedProgress() { + return Math.trunc(this.progress) + }, + calculateStyle() { + return "width: " + this.roundedProgress + "%;" + }, + disable() { + return this.ids.length == 0 || this.selected == undefined || this.selected == '' + }, + failedMessage() { + return `${this.failed} ${this.txt['failed']}` + }, + succeededMessage() { + if(this.selected == 'delete') { + console.log(JSON.parse(JSON.stringify(this.txt))); + return `${this.succeeded} ${this.txt['removed']}` + } + return `${this.succeeded} ${this.txt['updated']}` + } + }, + template: ` + {{ this.legend }} + + + + + + ` +} \ No newline at end of file diff --git a/app/webroot/js/comanage/components/bulk/datatable.js b/app/webroot/js/comanage/components/bulk/datatable.js new file mode 100644 index 000000000..b15065032 --- /dev/null +++ b/app/webroot/js/comanage/components/bulk/datatable.js @@ -0,0 +1,97 @@ +/** + * COmanage Registry Bulk Actions Vue.js Component + * + * Portions licensed to the University Corporation for Advanced Internet + * Development, Inc. ("UCAID") under one or more contributor license agreements. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * UCAID licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @link https://www.internet2.edu/comanage COmanage Project + * @package registry + * @since COmanage Registry v5.0.0 + * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +export default { + data() { + return { + body: [], + columns: [ + {'data': 'record'}, + {'data': 'status'} + ] + } + }, + props: { + raw: { + type: Array + }, + jsn: { + type: Array + }, + labels: { + type: Object + }, + action: { + type: String, + default: 'Action' + } + }, + methods: { + parseRawData() { + return this.raw.map((resp, idx) => { + let url = new URL(resp.url) + let path = url.pathname + let id = path.split('/').pop() + let info = ` ${this.action} ${resp.statusText} (Code: ${resp.status})` + if(!resp.ok) { + info = `

    ${this.action} ${resp.statusText} (Code: ${resp.status})

    ` + info += `

    ${this.jsn[idx].message}

    ` + info += `

    ${this.jsn[idx].url}

    ` + } + return { + 'record': this.labels.filter((idx, lbl) => lbl.id == parseInt(id))[0].label, + 'status': info + } + }) + } + }, + mounted() { + this.body = this.parseRawData() + let parsed = JSON.parse(JSON.stringify(this.body)) + $("#error-datatable").DataTable({ + data: parsed, + columns: this.columns, + paging: false, + scrollCollapse: true, + scrollY: '250px', + }); + }, + inject: ['txt'], + components: { + DataTable + }, + template: ` +

    {{ this.txt['report.for'] }} {{ action }}

    + + + + + + + +
    {{ this.txt['record'] }}{{ this.txt['status'] }}
    + ` +} \ No newline at end of file diff --git a/app/webroot/js/comanage/components/common/alert.js b/app/webroot/js/comanage/components/common/alert.js new file mode 100644 index 000000000..ae46ae58b --- /dev/null +++ b/app/webroot/js/comanage/components/common/alert.js @@ -0,0 +1,104 @@ +/** + * COmanage Registry Alert Vue.js Component + * + * Portions licensed to the University Corporation for Advanced Internet + * Development, Inc. ("UCAID") under one or more contributor license agreements. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * UCAID licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @link https://www.internet2.edu/comanage COmanage Project + * @package registry + * @since COmanage Registry v5.0.0 + * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +import {capitalize} from "../utils/helpers.js"; + +export default { + data() { + return { + msg: null + } + }, + props: { + message: { + type: String + }, + failed: { + type: Number + }, + succeed: { + type: Number + }, + action: { + type: String + }, + title: { + type: String, + default: null + }, + dismissable: { + type: Boolean, + default: false + } + }, + computed: { + getClass() { + let state = 'success' + if(this.hasFailed) { + state = 'danger' + } + + return "w-100 alert co-alert alert-" + state + }, + titleExists() { + return this.title != undefined && this.title != '' + }, + hasSucceeded() { + return this.succeed > 0 + }, + hasFailed() { + return this.failed > 0 + }, + getMessage() { + if(this.message != undefined && this.message != '') { + return this.message + } else if(this.hasFailed) { + return capitalize(this.action) + ' Failed (#' + this.failed + ')' + } else if(this.hasSucceeded) { + return capitalize(this.action) + ' Succeeded (#' + this.succeed + ')' + } + }, + display() { + return this.hasSucceeded || this.hasFailed + } + }, + template: ` +
    +
    + + + report_problem + {{ this.title }} + + {{ this.getMessage }} + + + + +
    +
    + ` +} \ No newline at end of file diff --git a/app/webroot/js/comanage/components/mvea/mvea-modal.js b/app/webroot/js/comanage/components/common/modal.js similarity index 62% rename from app/webroot/js/comanage/components/mvea/mvea-modal.js rename to app/webroot/js/comanage/components/common/modal.js index 72730530b..86545eb01 100644 --- a/app/webroot/js/comanage/components/mvea/mvea-modal.js +++ b/app/webroot/js/comanage/components/common/modal.js @@ -1,5 +1,5 @@ /** - * COmanage Registry MVEA Modal JavaScript + * COmanage Registry Modal Vue Element * * Portions licensed to the University Corporation for Advanced Internet * Development, Inc. ("UCAID") under one or more contributor license agreements. @@ -26,21 +26,39 @@ export default { props: { - modal: Object, - core: Object, - txt: Object + id: { + type: String + }, + title: { + type: String, + default: 'Title' + }, + buttonLabel: { + type: String + } }, + inject: ['txt'], template: ` -