This is an automated email from the ASF dual-hosted git repository.
dlmarion pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/main by this push:
new 0390490580 Adds a Column selector button to the tables with dynamic
content (#6392)
0390490580 is described below
commit 0390490580a31e0b9311dedba90e88fc373f58db
Author: Dave Marion <[email protected]>
AuthorDate: Thu May 28 12:47:35 2026 -0400
Adds a Column selector button to the tables with dynamic content (#6392)
This change installs the DataTables Buttons and ColVis extensions,
and includes that button on the DataTables that are created by
the server_process_common.js file. These tables typically have
a lot of columns and this button will allow the user to select
which columns they want to see. The new button is to the right
of the search bar with a gear icon. Column visibility is retained
locally so new browser sessions will only show the columns
selected by the user.
---
LICENSE | 8 +
assemble/src/main/resources/LICENSE | 16 +
.../src/main/appended-resources/META-INF/LICENSE | 16 +
.../external/datatables/css/buttons.bootstrap5.css | 387 +++
.../external/datatables/js/buttons.bootstrap5.js | 117 +
.../external/datatables/js/buttons.colVis.js | 260 ++
.../external/datatables/js/dataTables.buttons.js | 2592 ++++++++++++++++++++
.../monitor/resources/js/server_process_common.js | 8 +
.../accumulo/monitor/templates/compactors.ftl | 8 +-
.../accumulo/monitor/templates/coordinator.ftl | 16 +-
.../apache/accumulo/monitor/templates/default.ftl | 4 +
.../org/apache/accumulo/monitor/templates/gc.ftl | 11 +-
.../apache/accumulo/monitor/templates/manager.ftl | 6 +-
.../apache/accumulo/monitor/templates/sservers.ftl | 8 +-
.../apache/accumulo/monitor/templates/tservers.ftl | 8 +-
15 files changed, 3444 insertions(+), 21 deletions(-)
diff --git a/LICENSE b/LICENSE
index c71ceabecb..d84d8e37e1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -309,6 +309,14 @@ Files (in server/monitor/src/main/resources/):
Copyright (c) 2008-2024 SpryMedia Ltd
Licensed under the MIT license (see above)
+## DataTables Buttons 2.4.2 (https://datatables.net)
+
+Files (in server/monitor/src/main/resources/):
+* org/apache/accumulo/monitor/resources/external/datatables/**/*
+
+ Copyright (c) 2010-2015 SpryMedia Limited
+ Licensed under the MIT license (see above)
+
## DataTables ColReorder 1.7.0 (https://datatables.net)
Files (in server/monitor/src/main/resources/):
diff --git a/assemble/src/main/resources/LICENSE
b/assemble/src/main/resources/LICENSE
index caf46e5357..d6bd358baf 100644
--- a/assemble/src/main/resources/LICENSE
+++ b/assemble/src/main/resources/LICENSE
@@ -309,6 +309,22 @@ Files (in lib/accumulo-monitor-*.jar):
Copyright (c) 2008-2024 SpryMedia Ltd
Licensed under the MIT license (see above)
+## DataTables Buttons 2.4.2 (https://datatables.net)
+
+Files (in server/monitor/src/main/resources/):
+* org/apache/accumulo/monitor/resources/external/datatables/**/*
+
+ Copyright (c) 2010-2015 SpryMedia Limited
+ Licensed under the MIT license (see above)
+
+## DataTables ColReorder 1.7.0 (https://datatables.net)
+
+Files (in server/monitor/src/main/resources/):
+* org/apache/accumulo/monitor/resources/external/datatables/**/*
+
+ Copyright (c) 2010-2015 SpryMedia Limited
+ Licensed under the MIT license (see above)
+
**********
# BUNDLED DEPENDENCIES
diff --git a/server/monitor/src/main/appended-resources/META-INF/LICENSE
b/server/monitor/src/main/appended-resources/META-INF/LICENSE
index 39f0ba8551..11f0d3ced7 100644
--- a/server/monitor/src/main/appended-resources/META-INF/LICENSE
+++ b/server/monitor/src/main/appended-resources/META-INF/LICENSE
@@ -71,4 +71,20 @@ Files (in lib/accumulo-monitor-*.jar):
Copyright (c) 2008-2024 SpryMedia Ltd
Licensed under the MIT license (see above)
+## DataTables Buttons 2.4.2 (https://datatables.net)
+
+Files (in server/monitor/src/main/resources/):
+* org/apache/accumulo/monitor/resources/external/datatables/**/*
+
+ Copyright (c) 2010-2015 SpryMedia Limited
+ Licensed under the MIT license (see above)
+
+## DataTables ColReorder 1.7.0 (https://datatables.net)
+
+Files (in server/monitor/src/main/resources/):
+* org/apache/accumulo/monitor/resources/external/datatables/**/*
+
+ Copyright (c) 2010-2015 SpryMedia Limited
+ Licensed under the MIT license (see above)
+
**********
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/css/buttons.bootstrap5.css
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/css/buttons.bootstrap5.css
new file mode 100644
index 0000000000..df249f01a1
--- /dev/null
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/css/buttons.bootstrap5.css
@@ -0,0 +1,387 @@
+@keyframes dtb-spinner {
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+@-o-keyframes dtb-spinner {
+ 100% {
+ -o-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@-ms-keyframes dtb-spinner {
+ 100% {
+ -ms-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@-webkit-keyframes dtb-spinner {
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@-moz-keyframes dtb-spinner {
+ 100% {
+ -moz-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+div.dataTables_wrapper {
+ position: relative;
+}
+
+div.dt-buttons {
+ position: initial;
+}
+div.dt-buttons .dt-button {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+div.dt-button-info {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ width: 400px;
+ margin-top: -100px;
+ margin-left: -200px;
+ background-color: white;
+ border-radius: 0.75em;
+ box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8);
+ text-align: center;
+ z-index: 2003;
+ overflow: hidden;
+}
+div.dt-button-info h2 {
+ padding: 2rem 2rem 1rem 2rem;
+ margin: 0;
+ font-weight: normal;
+}
+div.dt-button-info > div {
+ padding: 1em 2em 2em 2em;
+}
+
+div.dtb-popover-close {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ width: 22px;
+ height: 22px;
+ text-align: center;
+ border-radius: 3px;
+ cursor: pointer;
+ z-index: 2003;
+}
+
+button.dtb-hide-drop {
+ display: none !important;
+}
+
+div.dt-button-collection-title {
+ text-align: center;
+ padding: 0.3em 0 0.5em;
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ font-size: 0.9em;
+}
+
+div.dt-button-collection-title:empty {
+ display: none;
+}
+
+span.dt-button-spacer {
+ display: inline-block;
+ margin: 0.5em;
+ white-space: nowrap;
+}
+span.dt-button-spacer.bar {
+ border-left: 1px solid rgba(0, 0, 0, 0.3);
+ vertical-align: middle;
+ padding-left: 0.5em;
+}
+span.dt-button-spacer.bar:empty {
+ height: 1em;
+ width: 1px;
+ padding-left: 0;
+}
+
+div.dt-button-collection .dt-button-active {
+ padding-right: 3em;
+}
+div.dt-button-collection .dt-button-active:after {
+ position: absolute;
+ top: 50%;
+ margin-top: -10px;
+ right: 1em;
+ display: inline-block;
+ content: "✓";
+ color: inherit;
+}
+div.dt-button-collection .dt-button-active.dt-button-split {
+ padding-right: 0;
+}
+div.dt-button-collection .dt-button-active.dt-button-split:after {
+ display: none;
+}
+div.dt-button-collection .dt-button-active.dt-button-split > *:first-child {
+ padding-right: 3em;
+}
+div.dt-button-collection .dt-button-active.dt-button-split >
*:first-child:after {
+ position: absolute;
+ top: 50%;
+ margin-top: -10px;
+ right: 1em;
+ display: inline-block;
+ content: "✓";
+ color: inherit;
+}
+div.dt-button-collection .dt-button-active-a a {
+ padding-right: 3em;
+}
+div.dt-button-collection .dt-button-active-a a:after {
+ position: absolute;
+ right: 1em;
+ display: inline-block;
+ content: "✓";
+ color: inherit;
+}
+div.dt-button-collection span.dt-button-spacer {
+ width: 100%;
+ font-size: 0.9em;
+ text-align: center;
+ margin: 0.5em 0;
+}
+div.dt-button-collection span.dt-button-spacer:empty {
+ height: 0;
+ width: 100%;
+}
+div.dt-button-collection span.dt-button-spacer.bar {
+ border-left: none;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ padding-left: 0;
+}
+
+html.dark div.dt-button-info {
+ background-color: var(--dt-html-background);
+ border: 1px solid rgba(255, 255, 255, 0.15);
+}
+
+div.dt-buttons div.btn-group {
+ position: initial;
+}
+div.dt-buttons div.dropdown-menu {
+ margin-top: 4px;
+}
+div.dt-buttons div.dropdown-menu .dt-button {
+ position: relative;
+}
+div.dt-buttons div.dropdown-menu div.dt-button-split {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+ align-content: flex-start;
+ align-items: stretch;
+}
+div.dt-buttons div.dropdown-menu div.dt-button-split a:first-child {
+ min-width: auto;
+ flex: 1 0 50px;
+ padding-right: 0;
+}
+div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child {
+ min-width: 33px;
+ flex: 0;
+ background: transparent;
+ border: none;
+ line-height: 1rem;
+ color: var(--bs-dropdown-link-color);
+ padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);
+}
+div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child:hover {
+ color: var(--bs-dropdown-link-hover-color);
+ background-color: var(--bs-dropdown-link-hover-bg);
+}
+div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child:after {
+ position: relative;
+ left: -3px;
+}
+div.dt-buttons div.dropdown-menu.fixed {
+ position: fixed;
+ display: block;
+ top: 50%;
+ left: 50%;
+ margin-left: -75px;
+ border-radius: 5px;
+ background-color: white;
+ padding: 0.5em;
+}
+div.dt-buttons div.dropdown-menu.fixed.two-column {
+ margin-left: -200px;
+}
+div.dt-buttons div.dropdown-menu.fixed.three-column {
+ margin-left: -225px;
+}
+div.dt-buttons div.dropdown-menu.fixed.four-column {
+ margin-left: -300px;
+}
+div.dt-buttons div.dropdown-menu.fixed.columns {
+ margin-left: -409px;
+}
+@media screen and (max-width: 1024px) {
+ div.dt-buttons div.dropdown-menu.fixed.columns {
+ margin-left: -308px;
+ }
+}
+@media screen and (max-width: 640px) {
+ div.dt-buttons div.dropdown-menu.fixed.columns {
+ margin-left: -203px;
+ }
+}
+@media screen and (max-width: 460px) {
+ div.dt-buttons div.dropdown-menu.fixed.columns {
+ margin-left: -100px;
+ }
+}
+div.dt-buttons div.dropdown-menu.fixed > :last-child {
+ max-height: 100vh;
+ overflow: auto;
+}
+div.dt-buttons div.dropdown-menu.two-column > :last-child, div.dt-buttons
div.dropdown-menu.three-column > :last-child, div.dt-buttons
div.dropdown-menu.four-column > :last-child {
+ display: block !important;
+ -webkit-column-gap: 8px;
+ -moz-column-gap: 8px;
+ -ms-column-gap: 8px;
+ -o-column-gap: 8px;
+ column-gap: 8px;
+}
+div.dt-buttons div.dropdown-menu.two-column > :last-child > *, div.dt-buttons
div.dropdown-menu.three-column > :last-child > *, div.dt-buttons
div.dropdown-menu.four-column > :last-child > * {
+ -webkit-column-break-inside: avoid;
+ break-inside: avoid;
+}
+div.dt-buttons div.dropdown-menu.two-column {
+ width: 400px;
+}
+div.dt-buttons div.dropdown-menu.two-column > :last-child {
+ padding-bottom: 1px;
+ column-count: 2;
+}
+div.dt-buttons div.dropdown-menu.three-column {
+ width: 450px;
+}
+div.dt-buttons div.dropdown-menu.three-column > :last-child {
+ padding-bottom: 1px;
+ column-count: 3;
+}
+div.dt-buttons div.dropdown-menu.four-column {
+ width: 600px;
+}
+div.dt-buttons div.dropdown-menu.four-column > :last-child {
+ padding-bottom: 1px;
+ column-count: 4;
+}
+div.dt-buttons div.dropdown-menu .dt-button {
+ border-radius: 0;
+}
+div.dt-buttons div.dropdown-menu.columns {
+ width: auto;
+}
+div.dt-buttons div.dropdown-menu.columns > :last-child {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+ align-items: center;
+ gap: 6px;
+ width: 818px;
+ padding-bottom: 1px;
+}
+div.dt-buttons div.dropdown-menu.columns > :last-child .dt-button {
+ min-width: 200px;
+ flex: 0 1;
+ margin: 0;
+}
+div.dt-buttons div.dropdown-menu.columns.dtb-b3 > :last-child, div.dt-buttons
div.dropdown-menu.columns.dtb-b2 > :last-child, div.dt-buttons
div.dropdown-menu.columns.dtb-b1 > :last-child {
+ justify-content: space-between;
+}
+div.dt-buttons div.dropdown-menu.columns.dtb-b3 .dt-button {
+ flex: 1 1 32%;
+}
+div.dt-buttons div.dropdown-menu.columns.dtb-b2 .dt-button {
+ flex: 1 1 48%;
+}
+div.dt-buttons div.dropdown-menu.columns.dtb-b1 .dt-button {
+ flex: 1 1 100%;
+}
+@media screen and (max-width: 1024px) {
+ div.dt-buttons div.dropdown-menu.columns > :last-child {
+ width: 612px;
+ }
+}
+@media screen and (max-width: 640px) {
+ div.dt-buttons div.dropdown-menu.columns > :last-child {
+ width: 406px;
+ }
+ div.dt-buttons div.dropdown-menu.columns.dtb-b3 .dt-button {
+ flex: 0 1 32%;
+ }
+}
+@media screen and (max-width: 460px) {
+ div.dt-buttons div.dropdown-menu.columns > :last-child {
+ width: 200px;
+ }
+}
+div.dt-buttons span.dt-button-spacer.empty {
+ margin: 1px;
+}
+div.dt-buttons span.dt-button-spacer.bar:empty {
+ height: inherit;
+}
+div.dt-buttons .btn.processing {
+ color: rgba(0, 0, 0, 0.2);
+}
+div.dt-buttons .btn.processing:after {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 16px;
+ height: 16px;
+ margin: -8px 0 0 -8px;
+ box-sizing: border-box;
+ display: block;
+ content: " ";
+ border: 2px solid rgb(40, 40, 40);
+ border-radius: 50%;
+ border-left-color: transparent;
+ border-right-color: transparent;
+ animation: dtb-spinner 1500ms infinite linear;
+ -o-animation: dtb-spinner 1500ms infinite linear;
+ -ms-animation: dtb-spinner 1500ms infinite linear;
+ -webkit-animation: dtb-spinner 1500ms infinite linear;
+ -moz-animation: dtb-spinner 1500ms infinite linear;
+}
+
+div.dt-button-background {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 999;
+}
+
+@media screen and (max-width: 767px) {
+ div.dt-buttons {
+ float: none;
+ width: 100%;
+ text-align: center;
+ margin-bottom: 0.5em;
+ }
+ div.dt-buttons a.btn {
+ float: none;
+ }
+}
+:root[data-bs-theme=dark] div.dropdown-menu.dt-button-collection.fixed {
+ background-color: rgb(33, 37, 41);
+ border: 1px solid rgba(255, 255, 255, 0.15);
+ border-radius: 8px;
+}
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/buttons.bootstrap5.js
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/buttons.bootstrap5.js
new file mode 100644
index 0000000000..e3f41e8c0e
--- /dev/null
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/buttons.bootstrap5.js
@@ -0,0 +1,117 @@
+/*! Bootstrap integration for DataTables' Buttons
+ * © SpryMedia Ltd - datatables.net/license
+ */
+
+(function( factory ){
+ if ( typeof define === 'function' && define.amd ) {
+ // AMD
+ define( ['jquery', 'datatables.net-bs5',
'datatables.net-buttons'], function ( $ ) {
+ return factory( $, window, document );
+ } );
+ }
+ else if ( typeof exports === 'object' ) {
+ // CommonJS
+ var jq = require('jquery');
+ var cjsRequires = function (root, $) {
+ if ( ! $.fn.dataTable ) {
+ require('datatables.net-bs5')(root, $);
+ }
+
+ if ( ! $.fn.dataTable.Buttons ) {
+ require('datatables.net-buttons')(root, $);
+ }
+ };
+
+ if (typeof window === 'undefined') {
+ module.exports = function (root, $) {
+ if ( ! root ) {
+ // CommonJS environments without a
window global must pass a
+ // root. This will give an error
otherwise
+ root = window;
+ }
+
+ if ( ! $ ) {
+ $ = jq( root );
+ }
+
+ cjsRequires( root, $ );
+ return factory( $, root, root.document );
+ };
+ }
+ else {
+ cjsRequires( window, jq );
+ module.exports = factory( jq, window, window.document );
+ }
+ }
+ else {
+ // Browser
+ factory( jQuery, window, document );
+ }
+}(function( $, window, document, undefined ) {
+'use strict';
+var DataTable = $.fn.dataTable;
+
+
+
+$.extend(true, DataTable.Buttons.defaults, {
+ dom: {
+ container: {
+ className: 'dt-buttons btn-group flex-wrap'
+ },
+ button: {
+ className: 'btn btn-secondary',
+ active: 'active'
+ },
+ collection: {
+ action: {
+ dropHtml: ''
+ },
+ container: {
+ tag: 'div',
+ className: 'dropdown-menu dt-button-collection'
+ },
+ closeButton: false,
+ button: {
+ tag: 'a',
+ className: 'dt-button dropdown-item',
+ active: 'dt-button-active',
+ disabled: 'disabled',
+ spacer: {
+ className: 'dropdown-divider',
+ tag: 'hr'
+ }
+ }
+ },
+ split: {
+ action: {
+ tag: 'a',
+ className: 'btn btn-secondary
dt-button-split-drop-button',
+ closeButton: false
+ },
+ dropdown: {
+ tag: 'button',
+ dropHtml: '',
+ className:
+ 'btn btn-secondary dt-button-split-drop
dropdown-toggle dropdown-toggle-split',
+ closeButton: false,
+ align: 'split-left',
+ splitAlignClass: 'dt-button-split-left'
+ },
+ wrapper: {
+ tag: 'div',
+ className: 'dt-button-split btn-group',
+ closeButton: false
+ }
+ }
+ },
+ buttonCreated: function (config, button) {
+ return config.buttons ? $('<div
class="btn-group"/>').append(button) : button;
+ }
+});
+
+DataTable.ext.buttons.collection.className += ' dropdown-toggle';
+DataTable.ext.buttons.collection.rightAlignClassName = 'dropdown-menu-right';
+
+
+return DataTable;
+}));
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/buttons.colVis.js
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/buttons.colVis.js
new file mode 100644
index 0000000000..7e8491bd1e
--- /dev/null
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/buttons.colVis.js
@@ -0,0 +1,260 @@
+/*!
+ * Column visibility buttons for Buttons and DataTables.
+ * © SpryMedia Ltd - datatables.net/license
+ */
+
+(function( factory ){
+ if ( typeof define === 'function' && define.amd ) {
+ // AMD
+ define( ['jquery', 'datatables.net', 'datatables.net-buttons'],
function ( $ ) {
+ return factory( $, window, document );
+ } );
+ }
+ else if ( typeof exports === 'object' ) {
+ // CommonJS
+ var jq = require('jquery');
+ var cjsRequires = function (root, $) {
+ if ( ! $.fn.dataTable ) {
+ require('datatables.net')(root, $);
+ }
+
+ if ( ! $.fn.dataTable.Buttons ) {
+ require('datatables.net-buttons')(root, $);
+ }
+ };
+
+ if (typeof window === 'undefined') {
+ module.exports = function (root, $) {
+ if ( ! root ) {
+ // CommonJS environments without a
window global must pass a
+ // root. This will give an error
otherwise
+ root = window;
+ }
+
+ if ( ! $ ) {
+ $ = jq( root );
+ }
+
+ cjsRequires( root, $ );
+ return factory( $, root, root.document );
+ };
+ }
+ else {
+ cjsRequires( window, jq );
+ module.exports = factory( jq, window, window.document );
+ }
+ }
+ else {
+ // Browser
+ factory( jQuery, window, document );
+ }
+}(function( $, window, document, undefined ) {
+'use strict';
+var DataTable = $.fn.dataTable;
+
+
+
+$.extend(DataTable.ext.buttons, {
+ // A collection of column visibility buttons
+ colvis: function (dt, conf) {
+ var node = null;
+ var buttonConf = {
+ extend: 'collection',
+ init: function (dt, n) {
+ node = n;
+ },
+ text: function (dt) {
+ return dt.i18n('buttons.colvis', 'Column
visibility');
+ },
+ className: 'buttons-colvis',
+ closeButton: false,
+ buttons: [
+ {
+ extend: 'columnsToggle',
+ columns: conf.columns,
+ columnText: conf.columnText
+ }
+ ]
+ };
+
+ // Rebuild the collection with the new column structure if
columns are reordered
+ dt.on('column-reorder.dt' + conf.namespace, function (e,
settings, details) {
+ dt.button(null, dt.button(null,
node).node()).collectionRebuild([
+ {
+ extend: 'columnsToggle',
+ columns: conf.columns,
+ columnText: conf.columnText
+ }
+ ]);
+ });
+
+ return buttonConf;
+ },
+
+ // Selected columns with individual buttons - toggle column visibility
+ columnsToggle: function (dt, conf) {
+ var columns = dt
+ .columns(conf.columns)
+ .indexes()
+ .map(function (idx) {
+ return {
+ extend: 'columnToggle',
+ columns: idx,
+ columnText: conf.columnText
+ };
+ })
+ .toArray();
+
+ return columns;
+ },
+
+ // Single button to toggle column visibility
+ columnToggle: function (dt, conf) {
+ return {
+ extend: 'columnVisibility',
+ columns: conf.columns,
+ columnText: conf.columnText
+ };
+ },
+
+ // Selected columns with individual buttons - set column visibility
+ columnsVisibility: function (dt, conf) {
+ var columns = dt
+ .columns(conf.columns)
+ .indexes()
+ .map(function (idx) {
+ return {
+ extend: 'columnVisibility',
+ columns: idx,
+ visibility: conf.visibility,
+ columnText: conf.columnText
+ };
+ })
+ .toArray();
+
+ return columns;
+ },
+
+ // Single button to set column visibility
+ columnVisibility: {
+ columns: undefined, // column selector
+ text: function (dt, button, conf) {
+ return conf._columnText(dt, conf);
+ },
+ className: 'buttons-columnVisibility',
+ action: function (e, dt, button, conf) {
+ var col = dt.columns(conf.columns);
+ var curr = col.visible();
+
+ col.visible(
+ conf.visibility !== undefined ? conf.visibility
: !(curr.length ? curr[0] : false)
+ );
+ },
+ init: function (dt, button, conf) {
+ var that = this;
+ button.attr('data-cv-idx', conf.columns);
+
+ dt.on('column-visibility.dt' + conf.namespace, function
(e, settings) {
+ if (!settings.bDestroying && settings.nTable ==
dt.settings()[0].nTable) {
+
that.active(dt.column(conf.columns).visible());
+ }
+ }).on('column-reorder.dt' + conf.namespace, function
(e, settings, details) {
+ // Button has been removed from the DOM
+ if (conf.destroying) {
+ return;
+ }
+
+ if (dt.columns(conf.columns).count() !== 1) {
+ return;
+ }
+
+ // This button controls the same column index
but the text for the column has
+ // changed
+ that.text(conf._columnText(dt, conf));
+
+ // Since its a different column, we need to
check its visibility
+ that.active(dt.column(conf.columns).visible());
+ });
+
+ this.active(dt.column(conf.columns).visible());
+ },
+ destroy: function (dt, button, conf) {
+ dt.off('column-visibility.dt' + conf.namespace).off(
+ 'column-reorder.dt' + conf.namespace
+ );
+ },
+
+ _columnText: function (dt, conf) {
+ // Use DataTables' internal data structure until this
is presented
+ // is a public API. The other option is to use
+ // `$( column(col).node() ).text()` but the node might
not have been
+ // populated when Buttons is constructed.
+ var idx = dt.column(conf.columns).index();
+ var title = dt.settings()[0].aoColumns[idx].sTitle;
+
+ if (!title) {
+ title = dt.column(idx).header().innerHTML;
+ }
+
+ title = title
+ .replace(/\n/g, ' ') // remove new lines
+ .replace(/<br\s*\/?>/gi, ' ') // replace line
breaks with spaces
+ .replace(/<select(.*?)<\/select>/g, '') //
remove select tags, including options text
+ .replace(/<!\-\-.*?\-\->/g, '') // strip HTML
comments
+ .replace(/<.*?>/g, '') // strip HTML
+ .replace(/^\s+|\s+$/g, ''); // trim
+
+ return conf.columnText ? conf.columnText(dt, idx,
title) : title;
+ }
+ },
+
+ colvisRestore: {
+ className: 'buttons-colvisRestore',
+
+ text: function (dt) {
+ return dt.i18n('buttons.colvisRestore', 'Restore
visibility');
+ },
+
+ init: function (dt, button, conf) {
+ conf._visOriginal = dt
+ .columns()
+ .indexes()
+ .map(function (idx) {
+ return dt.column(idx).visible();
+ })
+ .toArray();
+ },
+
+ action: function (e, dt, button, conf) {
+ dt.columns().every(function (i) {
+ // Take into account that ColReorder might have
disrupted our
+ // indexes
+ var idx =
+ dt.colReorder && dt.colReorder.transpose
+ ? dt.colReorder.transpose(i,
'toOriginal')
+ : i;
+
+ this.visible(conf._visOriginal[idx]);
+ });
+ }
+ },
+
+ colvisGroup: {
+ className: 'buttons-colvisGroup',
+
+ action: function (e, dt, button, conf) {
+ dt.columns(conf.show).visible(true, false);
+ dt.columns(conf.hide).visible(false, false);
+
+ dt.columns.adjust();
+ },
+
+ show: [],
+
+ hide: []
+ }
+});
+
+
+return DataTable;
+}));
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/dataTables.buttons.js
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/dataTables.buttons.js
new file mode 100644
index 0000000000..d4fd8dc7ba
--- /dev/null
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/external/datatables/js/dataTables.buttons.js
@@ -0,0 +1,2592 @@
+/*! Buttons for DataTables 2.4.2
+ * © SpryMedia Ltd - datatables.net/license
+ */
+
+(function( factory ){
+ if ( typeof define === 'function' && define.amd ) {
+ // AMD
+ define( ['jquery', 'datatables.net'], function ( $ ) {
+ return factory( $, window, document );
+ } );
+ }
+ else if ( typeof exports === 'object' ) {
+ // CommonJS
+ var jq = require('jquery');
+ var cjsRequires = function (root, $) {
+ if ( ! $.fn.dataTable ) {
+ require('datatables.net')(root, $);
+ }
+ };
+
+ if (typeof window === 'undefined') {
+ module.exports = function (root, $) {
+ if ( ! root ) {
+ // CommonJS environments without a
window global must pass a
+ // root. This will give an error
otherwise
+ root = window;
+ }
+
+ if ( ! $ ) {
+ $ = jq( root );
+ }
+
+ cjsRequires( root, $ );
+ return factory( $, root, root.document );
+ };
+ }
+ else {
+ cjsRequires( window, jq );
+ module.exports = factory( jq, window, window.document );
+ }
+ }
+ else {
+ // Browser
+ factory( jQuery, window, document );
+ }
+}(function( $, window, document, undefined ) {
+'use strict';
+var DataTable = $.fn.dataTable;
+
+
+
+// Used for namespacing events added to the document by each instance, so they
+// can be removed on destroy
+var _instCounter = 0;
+
+// Button namespacing counter for namespacing events on individual buttons
+var _buttonCounter = 0;
+
+var _dtButtons = DataTable.ext.buttons;
+
+// Custom entity decoder for data export
+var _entityDecoder = null;
+
+// Allow for jQuery slim
+function _fadeIn(el, duration, fn) {
+ if ($.fn.animate) {
+ el.stop().fadeIn(duration, fn);
+ }
+ else {
+ el.css('display', 'block');
+
+ if (fn) {
+ fn.call(el);
+ }
+ }
+}
+
+function _fadeOut(el, duration, fn) {
+ if ($.fn.animate) {
+ el.stop().fadeOut(duration, fn);
+ }
+ else {
+ el.css('display', 'none');
+
+ if (fn) {
+ fn.call(el);
+ }
+ }
+}
+
+/**
+ * [Buttons description]
+ * @param {[type]}
+ * @param {[type]}
+ */
+var Buttons = function (dt, config) {
+ // If not created with a `new` keyword then we return a wrapper
function that
+ // will take the settings object for a DT. This allows easy use of new
instances
+ // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons(
... )`.
+ if (!(this instanceof Buttons)) {
+ return function (settings) {
+ return new Buttons(settings, dt).container();
+ };
+ }
+
+ // If there is no config set it to an empty object
+ if (typeof config === 'undefined') {
+ config = {};
+ }
+
+ // Allow a boolean true for defaults
+ if (config === true) {
+ config = {};
+ }
+
+ // For easy configuration of buttons an array can be given
+ if (Array.isArray(config)) {
+ config = { buttons: config };
+ }
+
+ this.c = $.extend(true, {}, Buttons.defaults, config);
+
+ // Don't want a deep copy for the buttons
+ if (config.buttons) {
+ this.c.buttons = config.buttons;
+ }
+
+ this.s = {
+ dt: new DataTable.Api(dt),
+ buttons: [],
+ listenKeys: '',
+ namespace: 'dtb' + _instCounter++
+ };
+
+ this.dom = {
+ container: $('<' + this.c.dom.container.tag +
'/>').addClass(this.c.dom.container.className)
+ };
+
+ this._constructor();
+};
+
+$.extend(Buttons.prototype, {
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * *
+ * Public methods
+ */
+
+ /**
+ * Get the action of a button
+ * @param {int|string} Button index
+ * @return {function}
+ */ /**
+ * Set the action of a button
+ * @param {node} node Button element
+ * @param {function} action Function to set
+ * @return {Buttons} Self for chaining
+ */
+ action: function (node, action) {
+ var button = this._nodeToButton(node);
+
+ if (action === undefined) {
+ return button.conf.action;
+ }
+
+ button.conf.action = action;
+
+ return this;
+ },
+
+ /**
+ * Add an active class to the button to make to look active or get
current
+ * active state.
+ * @param {node} node Button element
+ * @param {boolean} [flag] Enable / disable flag
+ * @return {Buttons} Self for chaining or boolean for getter
+ */
+ active: function (node, flag) {
+ var button = this._nodeToButton(node);
+ var klass = this.c.dom.button.active;
+ var jqNode = $(button.node);
+
+ if (
+ button.inCollection &&
+ this.c.dom.collection.button &&
+ this.c.dom.collection.button.active !== undefined
+ ) {
+ klass = this.c.dom.collection.button.active;
+ }
+
+ if (flag === undefined) {
+ return jqNode.hasClass(klass);
+ }
+
+ jqNode.toggleClass(klass, flag === undefined ? true : flag);
+
+ return this;
+ },
+
+ /**
+ * Add a new button
+ * @param {object} config Button configuration object, base string name
or function
+ * @param {int|string} [idx] Button index for where to insert the button
+ * @param {boolean} [draw=true] Trigger a draw. Set a false when adding
+ * lots of buttons, until the last button.
+ * @return {Buttons} Self for chaining
+ */
+ add: function (config, idx, draw) {
+ var buttons = this.s.buttons;
+
+ if (typeof idx === 'string') {
+ var split = idx.split('-');
+ var base = this.s;
+
+ for (var i = 0, ien = split.length - 1; i < ien; i++) {
+ base = base.buttons[split[i] * 1];
+ }
+
+ buttons = base.buttons;
+ idx = split[split.length - 1] * 1;
+ }
+
+ this._expandButton(
+ buttons,
+ config,
+ config !== undefined ? config.split : undefined,
+ (config === undefined || config.split === undefined ||
config.split.length === 0) &&
+ base !== undefined,
+ false,
+ idx
+ );
+
+ if (draw === undefined || draw === true) {
+ this._draw();
+ }
+
+ return this;
+ },
+
+ /**
+ * Clear buttons from a collection and then insert new buttons
+ */
+ collectionRebuild: function (node, newButtons) {
+ var button = this._nodeToButton(node);
+
+ if (newButtons !== undefined) {
+ var i;
+ // Need to reverse the array
+ for (i = button.buttons.length - 1; i >= 0; i--) {
+ this.remove(button.buttons[i].node);
+ }
+
+ // If the collection has prefix and / or postfix
buttons we need to add them in
+ if (button.conf.prefixButtons) {
+ newButtons.unshift.apply(newButtons,
button.conf.prefixButtons);
+ }
+
+ if (button.conf.postfixButtons) {
+ newButtons.push.apply(newButtons,
button.conf.postfixButtons);
+ }
+
+ for (i = 0; i < newButtons.length; i++) {
+ var newBtn = newButtons[i];
+
+ this._expandButton(
+ button.buttons,
+ newBtn,
+ newBtn !== undefined &&
+ newBtn.config !== undefined &&
+ newBtn.config.split !==
undefined,
+ true,
+ newBtn.parentConf !== undefined &&
newBtn.parentConf.split !== undefined,
+ null,
+ newBtn.parentConf
+ );
+ }
+ }
+
+ this._draw(button.collection, button.buttons);
+ },
+
+ /**
+ * Get the container node for the buttons
+ * @return {jQuery} Buttons node
+ */
+ container: function () {
+ return this.dom.container;
+ },
+
+ /**
+ * Disable a button
+ * @param {node} node Button node
+ * @return {Buttons} Self for chaining
+ */
+ disable: function (node) {
+ var button = this._nodeToButton(node);
+
+
$(button.node).addClass(this.c.dom.button.disabled).prop('disabled', true);
+
+ return this;
+ },
+
+ /**
+ * Destroy the instance, cleaning up event handlers and removing DOM
+ * elements
+ * @return {Buttons} Self for chaining
+ */
+ destroy: function () {
+ // Key event listener
+ $('body').off('keyup.' + this.s.namespace);
+
+ // Individual button destroy (so they can remove their own
events if
+ // needed). Take a copy as the array is modified by `remove`
+ var buttons = this.s.buttons.slice();
+ var i, ien;
+
+ for (i = 0, ien = buttons.length; i < ien; i++) {
+ this.remove(buttons[i].node);
+ }
+
+ // Container
+ this.dom.container.remove();
+
+ // Remove from the settings object collection
+ var buttonInsts = this.s.dt.settings()[0];
+
+ for (i = 0, ien = buttonInsts.length; i < ien; i++) {
+ if (buttonInsts.inst === this) {
+ buttonInsts.splice(i, 1);
+ break;
+ }
+ }
+
+ return this;
+ },
+
+ /**
+ * Enable / disable a button
+ * @param {node} node Button node
+ * @param {boolean} [flag=true] Enable / disable flag
+ * @return {Buttons} Self for chaining
+ */
+ enable: function (node, flag) {
+ if (flag === false) {
+ return this.disable(node);
+ }
+
+ var button = this._nodeToButton(node);
+
$(button.node).removeClass(this.c.dom.button.disabled).prop('disabled', false);
+
+ return this;
+ },
+
+ /**
+ * Get a button's index
+ *
+ * This is internally recursive
+ * @param {element} node Button to get the index of
+ * @return {string} Button index
+ */
+ index: function (node, nested, buttons) {
+ if (!nested) {
+ nested = '';
+ buttons = this.s.buttons;
+ }
+
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
+ var inner = buttons[i].buttons;
+
+ if (buttons[i].node === node) {
+ return nested + i;
+ }
+
+ if (inner && inner.length) {
+ var match = this.index(node, i + '-', inner);
+
+ if (match !== null) {
+ return match;
+ }
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Get the instance name for the button set selector
+ * @return {string} Instance name
+ */
+ name: function () {
+ return this.c.name;
+ },
+
+ /**
+ * Get a button's node of the buttons container if no button is given
+ * @param {node} [node] Button node
+ * @return {jQuery} Button element, or container
+ */
+ node: function (node) {
+ if (!node) {
+ return this.dom.container;
+ }
+
+ var button = this._nodeToButton(node);
+ return $(button.node);
+ },
+
+ /**
+ * Set / get a processing class on the selected button
+ * @param {element} node Triggering button node
+ * @param {boolean} flag true to add, false to remove, undefined to get
+ * @return {boolean|Buttons} Getter value or this if a setter.
+ */
+ processing: function (node, flag) {
+ var dt = this.s.dt;
+ var button = this._nodeToButton(node);
+
+ if (flag === undefined) {
+ return $(button.node).hasClass('processing');
+ }
+
+ $(button.node).toggleClass('processing', flag);
+
+ $(dt.table().node()).triggerHandler('buttons-processing.dt', [
+ flag,
+ dt.button(node),
+ dt,
+ $(node),
+ button.conf
+ ]);
+
+ return this;
+ },
+
+ /**
+ * Remove a button.
+ * @param {node} node Button node
+ * @return {Buttons} Self for chaining
+ */
+ remove: function (node) {
+ var button = this._nodeToButton(node);
+ var host = this._nodeToHost(node);
+ var dt = this.s.dt;
+
+ // Remove any child buttons first
+ if (button.buttons.length) {
+ for (var i = button.buttons.length - 1; i >= 0; i--) {
+ this.remove(button.buttons[i].node);
+ }
+ }
+
+ button.conf.destroying = true;
+
+ // Allow the button to remove event handlers, etc
+ if (button.conf.destroy) {
+ button.conf.destroy.call(dt.button(node), dt, $(node),
button.conf);
+ }
+
+ this._removeKey(button.conf);
+
+ $(button.node).remove();
+
+ var idx = $.inArray(button, host);
+ host.splice(idx, 1);
+
+ return this;
+ },
+
+ /**
+ * Get the text for a button
+ * @param {int|string} node Button index
+ * @return {string} Button text
+ */ /**
+ * Set the text for a button
+ * @param {int|string|function} node Button index
+ * @param {string} label Text
+ * @return {Buttons} Self for chaining
+ */
+ text: function (node, label) {
+ var button = this._nodeToButton(node);
+ var textNode = button.textNode;
+ var dt = this.s.dt;
+ var jqNode = $(button.node);
+ var text = function (opt) {
+ return typeof opt === 'function' ? opt(dt, jqNode,
button.conf) : opt;
+ };
+
+ if (label === undefined) {
+ return text(button.conf.text);
+ }
+
+ button.conf.text = label;
+ textNode.html(text(label));
+
+ return this;
+ },
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * *
+ * Constructor
+ */
+
+ /**
+ * Buttons constructor
+ * @private
+ */
+ _constructor: function () {
+ var that = this;
+ var dt = this.s.dt;
+ var dtSettings = dt.settings()[0];
+ var buttons = this.c.buttons;
+
+ if (!dtSettings._buttons) {
+ dtSettings._buttons = [];
+ }
+
+ dtSettings._buttons.push({
+ inst: this,
+ name: this.c.name
+ });
+
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
+ this.add(buttons[i]);
+ }
+
+ dt.on('destroy', function (e, settings) {
+ if (settings === dtSettings) {
+ that.destroy();
+ }
+ });
+
+ // Global key event binding to listen for button keys
+ $('body').on('keyup.' + this.s.namespace, function (e) {
+ if (!document.activeElement || document.activeElement
=== document.body) {
+ // SUse a string of characters for fast lookup
of if we need to
+ // handle this
+ var character =
String.fromCharCode(e.keyCode).toLowerCase();
+
+ if
(that.s.listenKeys.toLowerCase().indexOf(character) !== -1) {
+ that._keypress(character, e);
+ }
+ }
+ });
+ },
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * *
+ * Private methods
+ */
+
+ /**
+ * Add a new button to the key press listener
+ * @param {object} conf Resolved button configuration object
+ * @private
+ */
+ _addKey: function (conf) {
+ if (conf.key) {
+ this.s.listenKeys += $.isPlainObject(conf.key) ?
conf.key.key : conf.key;
+ }
+ },
+
+ /**
+ * Insert the buttons into the container. Call without parameters!
+ * @param {node} [container] Recursive only - Insert point
+ * @param {array} [buttons] Recursive only - Buttons array
+ * @private
+ */
+ _draw: function (container, buttons) {
+ if (!container) {
+ container = this.dom.container;
+ buttons = this.s.buttons;
+ }
+
+ container.children().detach();
+
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
+ container.append(buttons[i].inserter);
+ container.append(' ');
+
+ if (buttons[i].buttons && buttons[i].buttons.length) {
+ this._draw(buttons[i].collection,
buttons[i].buttons);
+ }
+ }
+ },
+
+ /**
+ * Create buttons from an array of buttons
+ * @param {array} attachTo Buttons array to attach to
+ * @param {object} button Button definition
+ * @param {boolean} inCollection true if the button is in a collection
+ * @private
+ */
+ _expandButton: function (
+ attachTo,
+ button,
+ split,
+ inCollection,
+ inSplit,
+ attachPoint,
+ parentConf
+ ) {
+ var dt = this.s.dt;
+ var isSplit = false;
+ var domCollection = this.c.dom.collection;
+ var buttons = !Array.isArray(button) ? [button] : button;
+
+ if (button === undefined) {
+ buttons = !Array.isArray(split) ? [split] : split;
+ }
+
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
+ var conf = this._resolveExtends(buttons[i]);
+
+ if (!conf) {
+ continue;
+ }
+
+ isSplit = conf.config && conf.config.split ? true :
false;
+
+ // If the configuration is an array, then expand the
buttons at this
+ // point
+ if (Array.isArray(conf)) {
+ this._expandButton(
+ attachTo,
+ conf,
+ built !== undefined && built.conf !==
undefined ? built.conf.split : undefined,
+ inCollection,
+ parentConf !== undefined &&
parentConf.split !== undefined,
+ attachPoint,
+ parentConf
+ );
+ continue;
+ }
+
+ var built = this._buildButton(
+ conf,
+ inCollection,
+ conf.split !== undefined ||
+ (conf.config !== undefined &&
conf.config.split !== undefined),
+ inSplit
+ );
+ if (!built) {
+ continue;
+ }
+
+ if (attachPoint !== undefined && attachPoint !== null) {
+ attachTo.splice(attachPoint, 0, built);
+ attachPoint++;
+ }
+ else {
+ attachTo.push(built);
+ }
+
+ // Create the dropdown for a collection
+ if (built.conf.buttons) {
+ built.collection = $('<' +
domCollection.container.content.tag + '/>');
+ built.conf._collection = built.collection;
+
+
$(built.node).append(domCollection.action.dropHtml);
+
+ this._expandButton(
+ built.buttons,
+ built.conf.buttons,
+ built.conf.split,
+ !isSplit,
+ isSplit,
+ attachPoint,
+ built.conf
+ );
+ }
+
+ // And the split collection
+ if (built.conf.split) {
+ built.collection = $('<' +
domCollection.container.tag + '/>');
+ built.conf._collection = built.collection;
+
+ for (var j = 0; j < built.conf.split.length;
j++) {
+ var item = built.conf.split[j];
+
+ if (typeof item === 'object') {
+ item.parent = parentConf;
+
+ if (item.collectionLayout ===
undefined) {
+ item.collectionLayout =
built.conf.collectionLayout;
+ }
+
+ if (item.dropup === undefined) {
+ item.dropup =
built.conf.dropup;
+ }
+
+ if (item.fade === undefined) {
+ item.fade =
built.conf.fade;
+ }
+ }
+ }
+
+ this._expandButton(
+ built.buttons,
+ built.conf.buttons,
+ built.conf.split,
+ !isSplit,
+ isSplit,
+ attachPoint,
+ built.conf
+ );
+ }
+
+ built.conf.parent = parentConf;
+
+ // init call is made here, rather than buildButton as
it needs to
+ // be selectable, and for that it needs to be in the
buttons array
+ if (conf.init) {
+ conf.init.call(dt.button(built.node), dt,
$(built.node), conf);
+ }
+ }
+ },
+
+ /**
+ * Create an individual button
+ * @param {object} config Resolved button configuration
+ * @param {boolean} inCollection `true` if a collection button
+ * @return {object} Completed button description object
+ * @private
+ */
+ _buildButton: function (config, inCollection, isSplit, inSplit) {
+ var configDom = this.c.dom;
+ var textNode;
+ var dt = this.s.dt;
+ var text = function (opt) {
+ return typeof opt === 'function' ? opt(dt, button,
config) : opt;
+ };
+
+ // Create an object that describes the button which can be in
`dom.button`, or
+ // `dom.collection.button` or `dom.split.button` or
`dom.collection.split.button`!
+ // Each should extend from `dom.button`.
+ var dom = $.extend(true, {}, configDom.button);
+
+ if (inCollection && isSplit && configDom.collection.split) {
+ $.extend(true, dom, configDom.collection.split.action);
+ }
+ else if (inSplit || inCollection) {
+ $.extend(true, dom, configDom.collection.button);
+ }
+ else if (isSplit) {
+ $.extend(true, dom, configDom.split.button);
+ }
+
+ // Spacers don't do much other than insert an element into the
DOM
+ if (config.spacer) {
+ var spacer = $('<' + dom.spacer.tag + '/>')
+ .addClass('dt-button-spacer ' + config.style +
' ' + dom.spacer.className)
+ .html(text(config.text));
+
+ return {
+ conf: config,
+ node: spacer,
+ inserter: spacer,
+ buttons: [],
+ inCollection: inCollection,
+ isSplit: isSplit,
+ collection: null,
+ textNode: spacer
+ };
+ }
+
+ // Make sure that the button is available based on whatever
requirements
+ // it has. For example, PDF button require pdfmake
+ if (config.available && !config.available(dt, config) &&
!config.hasOwnProperty('html')) {
+ return false;
+ }
+
+ var button;
+
+ if (!config.hasOwnProperty('html')) {
+ var action = function (e, dt, button, config) {
+ config.action.call(dt.button(button), e, dt,
button, config);
+
+
$(dt.table().node()).triggerHandler('buttons-action.dt', [
+ dt.button(button),
+ dt,
+ button,
+ config
+ ]);
+ };
+
+ var tag = config.tag || dom.tag;
+ var clickBlurs = config.clickBlurs === undefined ? true
: config.clickBlurs;
+
+ button = $('<' + tag + '/>')
+ .addClass(dom.className)
+ .attr('tabindex',
this.s.dt.settings()[0].iTabIndex)
+ .attr('aria-controls',
this.s.dt.table().node().id)
+ .on('click.dtb', function (e) {
+ e.preventDefault();
+
+ if (!button.hasClass(dom.disabled) &&
config.action) {
+ action(e, dt, button, config);
+ }
+
+ if (clickBlurs) {
+ button.trigger('blur');
+ }
+ })
+ .on('keypress.dtb', function (e) {
+ if (e.keyCode === 13) {
+ e.preventDefault();
+
+ if
(!button.hasClass(dom.disabled) && config.action) {
+ action(e, dt, button,
config);
+ }
+ }
+ });
+
+ // Make `a` tags act like a link
+ if (tag.toLowerCase() === 'a') {
+ button.attr('href', '#');
+ }
+
+ // Button tags should have `type=button` so they don't
have any default behaviour
+ if (tag.toLowerCase() === 'button') {
+ button.attr('type', 'button');
+ }
+
+ if (dom.liner.tag) {
+ var liner = $('<' + dom.liner.tag + '/>')
+ .html(text(config.text))
+ .addClass(dom.liner.className);
+
+ if (dom.liner.tag.toLowerCase() === 'a') {
+ liner.attr('href', '#');
+ }
+
+ button.append(liner);
+ textNode = liner;
+ }
+ else {
+ button.html(text(config.text));
+ textNode = button;
+ }
+
+ if (config.enabled === false) {
+ button.addClass(dom.disabled);
+ }
+
+ if (config.className) {
+ button.addClass(config.className);
+ }
+
+ if (config.titleAttr) {
+ button.attr('title', text(config.titleAttr));
+ }
+
+ if (config.attr) {
+ button.attr(config.attr);
+ }
+
+ if (!config.namespace) {
+ config.namespace = '.dt-button-' +
_buttonCounter++;
+ }
+
+ if (config.config !== undefined && config.config.split)
{
+ config.split = config.config.split;
+ }
+ }
+ else {
+ button = $(config.html);
+ }
+
+ var buttonContainer = this.c.dom.buttonContainer;
+ var inserter;
+ if (buttonContainer && buttonContainer.tag) {
+ inserter = $('<' + buttonContainer.tag + '/>')
+ .addClass(buttonContainer.className)
+ .append(button);
+ }
+ else {
+ inserter = button;
+ }
+
+ this._addKey(config);
+
+ // Style integration callback for DOM manipulation
+ // Note that this is _not_ documented. It is currently
+ // for style integration only
+ if (this.c.buttonCreated) {
+ inserter = this.c.buttonCreated(config, inserter);
+ }
+
+ var splitDiv;
+
+ if (isSplit) {
+ var dropdownConf = inCollection
+ ? $.extend(true, this.c.dom.split,
this.c.dom.collection.split)
+ : this.c.dom.split;
+ var wrapperConf = dropdownConf.wrapper;
+
+ splitDiv = $('<' + wrapperConf.tag + '/>')
+ .addClass(wrapperConf.className)
+ .append(button);
+
+ var dropButtonConfig = $.extend(config, {
+ align: dropdownConf.dropdown.align,
+ attr: {
+ 'aria-haspopup': 'dialog',
+ 'aria-expanded': false
+ },
+ className: dropdownConf.dropdown.className,
+ closeButton: false,
+ splitAlignClass:
dropdownConf.dropdown.splitAlignClass,
+ text: dropdownConf.dropdown.text
+ });
+
+ this._addKey(dropButtonConfig);
+
+ var splitAction = function (e, dt, button, config) {
+
_dtButtons.split.action.call(dt.button(splitDiv), e, dt, button, config);
+
+
$(dt.table().node()).triggerHandler('buttons-action.dt', [
+ dt.button(button),
+ dt,
+ button,
+ config
+ ]);
+ button.attr('aria-expanded', true);
+ };
+
+ var dropButton = $(
+ '<button class="' +
dropdownConf.dropdown.className + ' dt-button"></button>'
+ )
+ .html(dropdownConf.dropdown.dropHtml)
+ .on('click.dtb', function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (!dropButton.hasClass(dom.disabled))
{
+ splitAction(e, dt, dropButton,
dropButtonConfig);
+ }
+ if (clickBlurs) {
+ dropButton.trigger('blur');
+ }
+ })
+ .on('keypress.dtb', function (e) {
+ if (e.keyCode === 13) {
+ e.preventDefault();
+
+ if
(!dropButton.hasClass(dom.disabled)) {
+ splitAction(e, dt,
dropButton, dropButtonConfig);
+ }
+ }
+ });
+
+ if (config.split.length === 0) {
+ dropButton.addClass('dtb-hide-drop');
+ }
+
+ splitDiv.append(dropButton).attr(dropButtonConfig.attr);
+ }
+
+ return {
+ conf: config,
+ node: isSplit ? splitDiv.get(0) : button.get(0),
+ inserter: isSplit ? splitDiv : inserter,
+ buttons: [],
+ inCollection: inCollection,
+ isSplit: isSplit,
+ inSplit: inSplit,
+ collection: null,
+ textNode: textNode
+ };
+ },
+
+ /**
+ * Get the button object from a node (recursive)
+ * @param {node} node Button node
+ * @param {array} [buttons] Button array, uses base if not defined
+ * @return {object} Button object
+ * @private
+ */
+ _nodeToButton: function (node, buttons) {
+ if (!buttons) {
+ buttons = this.s.buttons;
+ }
+
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
+ if (buttons[i].node === node) {
+ return buttons[i];
+ }
+
+ if (buttons[i].buttons.length) {
+ var ret = this._nodeToButton(node,
buttons[i].buttons);
+
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+ },
+
+ /**
+ * Get container array for a button from a button node (recursive)
+ * @param {node} node Button node
+ * @param {array} [buttons] Button array, uses base if not defined
+ * @return {array} Button's host array
+ * @private
+ */
+ _nodeToHost: function (node, buttons) {
+ if (!buttons) {
+ buttons = this.s.buttons;
+ }
+
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
+ if (buttons[i].node === node) {
+ return buttons;
+ }
+
+ if (buttons[i].buttons.length) {
+ var ret = this._nodeToHost(node,
buttons[i].buttons);
+
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+ },
+
+ /**
+ * Handle a key press - determine if any button's key configured matches
+ * what was typed and trigger the action if so.
+ * @param {string} character The character pressed
+ * @param {object} e Key event that triggered this call
+ * @private
+ */
+ _keypress: function (character, e) {
+ // Check if this button press already activated on another
instance of Buttons
+ if (e._buttonsHandled) {
+ return;
+ }
+
+ var run = function (conf, node) {
+ if (!conf.key) {
+ return;
+ }
+
+ if (conf.key === character) {
+ e._buttonsHandled = true;
+ $(node).click();
+ }
+ else if ($.isPlainObject(conf.key)) {
+ if (conf.key.key !== character) {
+ return;
+ }
+
+ if (conf.key.shiftKey && !e.shiftKey) {
+ return;
+ }
+
+ if (conf.key.altKey && !e.altKey) {
+ return;
+ }
+
+ if (conf.key.ctrlKey && !e.ctrlKey) {
+ return;
+ }
+
+ if (conf.key.metaKey && !e.metaKey) {
+ return;
+ }
+
+ // Made it this far - it is good
+ e._buttonsHandled = true;
+ $(node).click();
+ }
+ };
+
+ var recurse = function (a) {
+ for (var i = 0, ien = a.length; i < ien; i++) {
+ run(a[i].conf, a[i].node);
+
+ if (a[i].buttons.length) {
+ recurse(a[i].buttons);
+ }
+ }
+ };
+
+ recurse(this.s.buttons);
+ },
+
+ /**
+ * Remove a key from the key listener for this instance (to be used
when a
+ * button is removed)
+ * @param {object} conf Button configuration
+ * @private
+ */
+ _removeKey: function (conf) {
+ if (conf.key) {
+ var character = $.isPlainObject(conf.key) ?
conf.key.key : conf.key;
+
+ // Remove only one character, as multiple buttons could
have the
+ // same listening key
+ var a = this.s.listenKeys.split('');
+ var idx = $.inArray(character, a);
+ a.splice(idx, 1);
+ this.s.listenKeys = a.join('');
+ }
+ },
+
+ /**
+ * Resolve a button configuration
+ * @param {string|function|object} conf Button config to resolve
+ * @return {object} Button configuration
+ * @private
+ */
+ _resolveExtends: function (conf) {
+ var that = this;
+ var dt = this.s.dt;
+ var i, ien;
+ var toConfObject = function (base) {
+ var loop = 0;
+
+ // Loop until we have resolved to a button
configuration, or an
+ // array of button configurations (which will be
iterated
+ // separately)
+ while (!$.isPlainObject(base) && !Array.isArray(base)) {
+ if (base === undefined) {
+ return;
+ }
+
+ if (typeof base === 'function') {
+ base = base.call(that, dt, conf);
+
+ if (!base) {
+ return false;
+ }
+ }
+ else if (typeof base === 'string') {
+ if (!_dtButtons[base]) {
+ return { html: base };
+ }
+
+ base = _dtButtons[base];
+ }
+
+ loop++;
+ if (loop > 30) {
+ // Protect against misconfiguration
killing the browser
+ throw 'Buttons: Too many iterations';
+ }
+ }
+
+ return Array.isArray(base) ? base : $.extend({}, base);
+ };
+
+ conf = toConfObject(conf);
+
+ while (conf && conf.extend) {
+ // Use `toConfObject` in case the button definition
being extended
+ // is itself a string or a function
+ if (!_dtButtons[conf.extend]) {
+ throw 'Cannot extend unknown button type: ' +
conf.extend;
+ }
+
+ var objArray = toConfObject(_dtButtons[conf.extend]);
+ if (Array.isArray(objArray)) {
+ return objArray;
+ }
+ else if (!objArray) {
+ // This is a little brutal as it might be
possible to have a
+ // valid button without the extend, but if
there is no extend
+ // then the host button would be acting in an
undefined state
+ return false;
+ }
+
+ // Stash the current class name
+ var originalClassName = objArray.className;
+
+ if (conf.config !== undefined && objArray.config !==
undefined) {
+ conf.config = $.extend({}, objArray.config,
conf.config);
+ }
+
+ conf = $.extend({}, objArray, conf);
+
+ // The extend will have overwritten the original class
name if the
+ // `conf` object also assigned a class, but we want to
concatenate
+ // them so they are list that is combined from all
extended buttons
+ if (originalClassName && conf.className !==
originalClassName) {
+ conf.className = originalClassName + ' ' +
conf.className;
+ }
+
+ // Although we want the `conf` object to overwrite
almost all of
+ // the properties of the object being extended, the
`extend`
+ // property should come from the object being extended
+ conf.extend = objArray.extend;
+ }
+
+ // Buttons to be added to a collection -gives the ability to
define
+ // if buttons should be added to the start or end of a
collection
+ var postfixButtons = conf.postfixButtons;
+ if (postfixButtons) {
+ if (!conf.buttons) {
+ conf.buttons = [];
+ }
+
+ for (i = 0, ien = postfixButtons.length; i < ien; i++) {
+ conf.buttons.push(postfixButtons[i]);
+ }
+ }
+
+ var prefixButtons = conf.prefixButtons;
+ if (prefixButtons) {
+ if (!conf.buttons) {
+ conf.buttons = [];
+ }
+
+ for (i = 0, ien = prefixButtons.length; i < ien; i++) {
+ conf.buttons.splice(i, 0, prefixButtons[i]);
+ }
+ }
+
+ return conf;
+ },
+
+ /**
+ * Display (and replace if there is an existing one) a popover attached
to a button
+ * @param {string|node} content Content to show
+ * @param {DataTable.Api} hostButton DT API instance of the button
+ * @param {object} inOpts Options (see object below for all options)
+ */
+ _popover: function (content, hostButton, inOpts, e) {
+ var dt = hostButton;
+ var c = this.c;
+ var closed = false;
+ var options = $.extend(
+ {
+ align: 'button-left', // button-right,
dt-container, split-left, split-right
+ autoClose: false,
+ background: true,
+ backgroundClassName: 'dt-button-background',
+ closeButton: true,
+ containerClassName:
c.dom.collection.container.className,
+ contentClassName:
c.dom.collection.container.content.className,
+ collectionLayout: '',
+ collectionTitle: '',
+ dropup: false,
+ fade: 400,
+ popoverTitle: '',
+ rightAlignClassName: 'dt-button-right',
+ tag: c.dom.collection.container.tag
+ },
+ inOpts
+ );
+
+ var containerSelector = options.tag + '.' +
options.containerClassName.replace(/ /g, '.');
+ var hostNode = hostButton.node();
+
+ var close = function () {
+ closed = true;
+
+ _fadeOut($(containerSelector), options.fade, function
() {
+ $(this).detach();
+ });
+
+
$(dt.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes()).attr(
+ 'aria-expanded',
+ 'false'
+ );
+
+
$('div.dt-button-background').off('click.dtb-collection');
+ Buttons.background(false, options.backgroundClassName,
options.fade, hostNode);
+
+ $(window).off('resize.resize.dtb-collection');
+ $('body').off('.dtb-collection');
+ dt.off('buttons-action.b-internal');
+ dt.off('destroy');
+ };
+
+ if (content === false) {
+ close();
+ return;
+ }
+
+ var existingExpanded = $(
+
dt.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes()
+ );
+ if (existingExpanded.length) {
+ // Reuse the current position if the button that was
triggered is inside an existing collection
+ if (hostNode.closest(containerSelector).length) {
+ hostNode = existingExpanded.eq(0);
+ }
+
+ close();
+ }
+
+ // Try to be smart about the layout
+ var cnt = $('.dt-button', content).length;
+ var mod = '';
+
+ if (cnt === 3) {
+ mod = 'dtb-b3';
+ }
+ else if (cnt === 2) {
+ mod = 'dtb-b2';
+ }
+ else if (cnt === 1) {
+ mod = 'dtb-b1';
+ }
+
+ var display = $('<' + options.tag + '/>')
+ .addClass(options.containerClassName)
+ .addClass(options.collectionLayout)
+ .addClass(options.splitAlignClass)
+ .addClass(mod)
+ .css('display', 'none')
+ .attr({
+ 'aria-modal': true,
+ role: 'dialog'
+ });
+
+ content = $(content)
+ .addClass(options.contentClassName)
+ .attr('role', 'menu')
+ .appendTo(display);
+
+ hostNode.attr('aria-expanded', 'true');
+
+ if (hostNode.parents('body')[0] !== document.body) {
+ hostNode = document.body.lastChild;
+ }
+
+ if (options.popoverTitle) {
+ display.prepend(
+ '<div class="dt-button-collection-title">' +
options.popoverTitle + '</div>'
+ );
+ }
+ else if (options.collectionTitle) {
+ display.prepend(
+ '<div class="dt-button-collection-title">' +
options.collectionTitle + '</div>'
+ );
+ }
+
+ if (options.closeButton) {
+ display
+ .prepend('<div
class="dtb-popover-close">×</div>')
+ .addClass('dtb-collection-closeable');
+ }
+
+ _fadeIn(display.insertAfter(hostNode), options.fade);
+
+ var tableContainer = $(hostButton.table().container());
+ var position = display.css('position');
+
+ if (options.span === 'container' || options.align ===
'dt-container') {
+ hostNode = hostNode.parent();
+ display.css('width', tableContainer.width());
+ }
+
+ // Align the popover relative to the DataTables container
+ // Useful for wide popovers such as SearchPanes
+ if (position === 'absolute') {
+ // Align relative to the host button
+ var offsetParent = $(hostNode[0].offsetParent);
+ var buttonPosition = hostNode.position();
+ var buttonOffset = hostNode.offset();
+ var tableSizes = offsetParent.offset();
+ var containerPosition = offsetParent.position();
+ var computed = window.getComputedStyle(offsetParent[0]);
+
+ tableSizes.height = offsetParent.outerHeight();
+ tableSizes.width = offsetParent.width() +
parseFloat(computed.paddingLeft);
+ tableSizes.right = tableSizes.left + tableSizes.width;
+ tableSizes.bottom = tableSizes.top + tableSizes.height;
+
+ // Set the initial position so we can read height /
width
+ var top = buttonPosition.top + hostNode.outerHeight();
+ var left = buttonPosition.left;
+
+ display.css({
+ top: top,
+ left: left
+ });
+
+ // Get the popover position
+ computed = window.getComputedStyle(display[0]);
+ var popoverSizes = display.offset();
+
+ popoverSizes.height = display.outerHeight();
+ popoverSizes.width = display.outerWidth();
+ popoverSizes.right = popoverSizes.left +
popoverSizes.width;
+ popoverSizes.bottom = popoverSizes.top +
popoverSizes.height;
+ popoverSizes.marginTop = parseFloat(computed.marginTop);
+ popoverSizes.marginBottom =
parseFloat(computed.marginBottom);
+
+ // First position per the class requirements - pop up
and right align
+ if (options.dropup) {
+ top =
+ buttonPosition.top -
+ popoverSizes.height -
+ popoverSizes.marginTop -
+ popoverSizes.marginBottom;
+ }
+
+ if (options.align === 'button-right' ||
display.hasClass(options.rightAlignClassName)) {
+ left = buttonPosition.left - popoverSizes.width
+ hostNode.outerWidth();
+ }
+
+ // Container alignment - make sure it doesn't overflow
the table container
+ if (options.align === 'dt-container' || options.align
=== 'container') {
+ if (left < buttonPosition.left) {
+ left = -buttonPosition.left;
+ }
+
+ if (left + popoverSizes.width >
tableSizes.width) {
+ left = tableSizes.width -
popoverSizes.width;
+ }
+ }
+
+ // Window adjustment
+ if (containerPosition.left + left + popoverSizes.width
> $(window).width()) {
+ // Overflowing the document to the right
+ left = $(window).width() - popoverSizes.width -
containerPosition.left;
+ }
+
+ if (buttonOffset.left + left < 0) {
+ // Off to the left of the document
+ left = -buttonOffset.left;
+ }
+
+ if (
+ containerPosition.top + top +
popoverSizes.height >
+ $(window).height() + $(window).scrollTop()
+ ) {
+ // Pop up if otherwise we'd need the user to
scroll down
+ top =
+ buttonPosition.top -
+ popoverSizes.height -
+ popoverSizes.marginTop -
+ popoverSizes.marginBottom;
+ }
+
+ if (containerPosition.top + top <
$(window).scrollTop()) {
+ // Correction for when the top is beyond the
top of the page
+ top = buttonPosition.top +
hostNode.outerHeight();
+ }
+
+ // Calculations all done - now set it
+ display.css({
+ top: top,
+ left: left
+ });
+ }
+ else {
+ // Fix position - centre on screen
+ var position = function () {
+ var half = $(window).height() / 2;
+
+ var top = display.height() / 2;
+ if (top > half) {
+ top = half;
+ }
+
+ display.css('marginTop', top * -1);
+ };
+
+ position();
+
+ $(window).on('resize.dtb-collection', function () {
+ position();
+ });
+ }
+
+ if (options.background) {
+ Buttons.background(
+ true,
+ options.backgroundClassName,
+ options.fade,
+ options.backgroundHost || hostNode
+ );
+ }
+
+ // This is bonkers, but if we don't have a click listener on the
+ // background element, iOS Safari will ignore the body click
+ // listener below. An empty function here is all that is
+ // required to make it work...
+ $('div.dt-button-background').on('click.dtb-collection',
function () {});
+
+ if (options.autoClose) {
+ setTimeout(function () {
+ dt.on('buttons-action.b-internal', function (e,
btn, dt, node) {
+ if (node[0] === hostNode[0]) {
+ return;
+ }
+ close();
+ });
+ }, 0);
+ }
+
+ $(display).trigger('buttons-popover.dt');
+
+ dt.on('destroy', close);
+
+ setTimeout(function () {
+ closed = false;
+ $('body')
+ .on('click.dtb-collection', function (e) {
+ if (closed) {
+ return;
+ }
+
+ // andSelf is deprecated in jQ1.8, but
we want 1.7 compat
+ var back = $.fn.addBack ? 'addBack' :
'andSelf';
+ var parent = $(e.target).parent()[0];
+
+ if (
+
(!$(e.target).parents()[back]().filter(content).length &&
+
!$(parent).hasClass('dt-buttons')) ||
+
$(e.target).hasClass('dt-button-background')
+ ) {
+ close();
+ }
+ })
+ .on('keyup.dtb-collection', function (e) {
+ if (e.keyCode === 27) {
+ close();
+ }
+ })
+ .on('keydown.dtb-collection', function (e) {
+ // Focus trap for tab key
+ var elements = $('a, button', content);
+ var active = document.activeElement;
+
+ if (e.keyCode !== 9) {
+ // tab
+ return;
+ }
+
+ if (elements.index(active) === -1) {
+ // If current focus is not
inside the popover
+ elements.first().focus();
+ e.preventDefault();
+ }
+ else if (e.shiftKey) {
+ // Reverse tabbing order when
shift key is pressed
+ if (active === elements[0]) {
+ elements.last().focus();
+ e.preventDefault();
+ }
+ }
+ else {
+ if (active ===
elements.last()[0]) {
+
elements.first().focus();
+ e.preventDefault();
+ }
+ }
+ });
+ }, 0);
+ }
+});
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Statics
+ */
+
+/**
+ * Show / hide a background layer behind a collection
+ * @param {boolean} Flag to indicate if the background should be shown or
+ * hidden
+ * @param {string} Class to assign to the background
+ * @static
+ */
+Buttons.background = function (show, className, fade, insertPoint) {
+ if (fade === undefined) {
+ fade = 400;
+ }
+ if (!insertPoint) {
+ insertPoint = document.body;
+ }
+
+ if (show) {
+ _fadeIn(
+ $('<div/>').addClass(className).css('display',
'none').insertAfter(insertPoint),
+ fade
+ );
+ }
+ else {
+ _fadeOut($('div.' + className), fade, function () {
+ $(this).removeClass(className).remove();
+ });
+ }
+};
+
+/**
+ * Instance selector - select Buttons instances based on an instance selector
+ * value from the buttons assigned to a DataTable. This is only useful if
+ * multiple instances are attached to a DataTable.
+ * @param {string|int|array} Instance selector - see `instance-selector`
+ * documentation on the DataTables site
+ * @param {array} Button instance array that was attached to the DataTables
+ * settings object
+ * @return {array} Buttons instances
+ * @static
+ */
+Buttons.instanceSelector = function (group, buttons) {
+ if (group === undefined || group === null) {
+ return $.map(buttons, function (v) {
+ return v.inst;
+ });
+ }
+
+ var ret = [];
+ var names = $.map(buttons, function (v) {
+ return v.name;
+ });
+
+ // Flatten the group selector into an array of single options
+ var process = function (input) {
+ if (Array.isArray(input)) {
+ for (var i = 0, ien = input.length; i < ien; i++) {
+ process(input[i]);
+ }
+ return;
+ }
+
+ if (typeof input === 'string') {
+ if (input.indexOf(',') !== -1) {
+ // String selector, list of names
+ process(input.split(','));
+ }
+ else {
+ // String selector individual name
+ var idx = $.inArray(input.trim(), names);
+
+ if (idx !== -1) {
+ ret.push(buttons[idx].inst);
+ }
+ }
+ }
+ else if (typeof input === 'number') {
+ // Index selector
+ ret.push(buttons[input].inst);
+ }
+ else if (typeof input === 'object') {
+ // Actual instance selector
+ ret.push(input);
+ }
+ };
+
+ process(group);
+
+ return ret;
+};
+
+/**
+ * Button selector - select one or more buttons from a selector input so some
+ * operation can be performed on them.
+ * @param {array} Button instances array that the selector should operate on
+ * @param {string|int|node|jQuery|array} Button selector - see
+ * `button-selector` documentation on the DataTables site
+ * @return {array} Array of objects containing `inst` and `idx` properties of
+ * the selected buttons so you know which instance each button belongs to.
+ * @static
+ */
+Buttons.buttonSelector = function (insts, selector) {
+ var ret = [];
+ var nodeBuilder = function (a, buttons, baseIdx) {
+ var button;
+ var idx;
+
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
+ button = buttons[i];
+
+ if (button) {
+ idx = baseIdx !== undefined ? baseIdx + i : i +
'';
+
+ a.push({
+ node: button.node,
+ name: button.conf.name,
+ idx: idx
+ });
+
+ if (button.buttons) {
+ nodeBuilder(a, button.buttons, idx +
'-');
+ }
+ }
+ }
+ };
+
+ var run = function (selector, inst) {
+ var i, ien;
+ var buttons = [];
+ nodeBuilder(buttons, inst.s.buttons);
+
+ var nodes = $.map(buttons, function (v) {
+ return v.node;
+ });
+
+ if (Array.isArray(selector) || selector instanceof $) {
+ for (i = 0, ien = selector.length; i < ien; i++) {
+ run(selector[i], inst);
+ }
+ return;
+ }
+
+ if (selector === null || selector === undefined || selector ===
'*') {
+ // Select all
+ for (i = 0, ien = buttons.length; i < ien; i++) {
+ ret.push({
+ inst: inst,
+ node: buttons[i].node
+ });
+ }
+ }
+ else if (typeof selector === 'number') {
+ // Main button index selector
+ if (inst.s.buttons[selector]) {
+ ret.push({
+ inst: inst,
+ node: inst.s.buttons[selector].node
+ });
+ }
+ }
+ else if (typeof selector === 'string') {
+ if (selector.indexOf(',') !== -1) {
+ // Split
+ var a = selector.split(',');
+
+ for (i = 0, ien = a.length; i < ien; i++) {
+ run(a[i].trim(), inst);
+ }
+ }
+ else if (selector.match(/^\d+(\-\d+)*$/)) {
+ // Sub-button index selector
+ var indexes = $.map(buttons, function (v) {
+ return v.idx;
+ });
+
+ ret.push({
+ inst: inst,
+ node: buttons[$.inArray(selector,
indexes)].node
+ });
+ }
+ else if (selector.indexOf(':name') !== -1) {
+ // Button name selector
+ var name = selector.replace(':name', '');
+
+ for (i = 0, ien = buttons.length; i < ien; i++)
{
+ if (buttons[i].name === name) {
+ ret.push({
+ inst: inst,
+ node: buttons[i].node
+ });
+ }
+ }
+ }
+ else {
+ // jQuery selector on the nodes
+ $(nodes)
+ .filter(selector)
+ .each(function () {
+ ret.push({
+ inst: inst,
+ node: this
+ });
+ });
+ }
+ }
+ else if (typeof selector === 'object' && selector.nodeName) {
+ // Node selector
+ var idx = $.inArray(selector, nodes);
+
+ if (idx !== -1) {
+ ret.push({
+ inst: inst,
+ node: nodes[idx]
+ });
+ }
+ }
+ };
+
+ for (var i = 0, ien = insts.length; i < ien; i++) {
+ var inst = insts[i];
+
+ run(selector, inst);
+ }
+
+ return ret;
+};
+
+/**
+ * Default function used for formatting output data.
+ * @param {*} str Data to strip
+ */
+Buttons.stripData = function (str, config) {
+ if (typeof str !== 'string') {
+ return str;
+ }
+
+ // Always remove script tags
+ str =
str.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
+
+ // Always remove comments
+ str = str.replace(/<!\-\-.*?\-\->/g, '');
+
+ if (!config || config.stripHtml) {
+ str = str.replace(/<[^>]*>/g, '');
+ }
+
+ if (!config || config.trim) {
+ str = str.replace(/^\s+|\s+$/g, '');
+ }
+
+ if (!config || config.stripNewlines) {
+ str = str.replace(/\n/g, ' ');
+ }
+
+ if (!config || config.decodeEntities) {
+ if (_entityDecoder) {
+ str = _entityDecoder(str);
+ }
+ else {
+ _exportTextarea.innerHTML = str;
+ str = _exportTextarea.value;
+ }
+ }
+
+ return str;
+};
+
+/**
+ * Provide a custom entity decoding function - e.g. a regex one, which can be
+ * much faster than the built in DOM option, but also larger code size.
+ * @param {function} fn
+ */
+Buttons.entityDecoder = function (fn) {
+ _entityDecoder = fn;
+}
+
+/**
+ * Buttons defaults. For full documentation, please refer to the docs/option
+ * directory or the DataTables site.
+ * @type {Object}
+ * @static
+ */
+Buttons.defaults = {
+ buttons: ['copy', 'excel', 'csv', 'pdf', 'print'],
+ name: 'main',
+ tabIndex: 0,
+ dom: {
+ container: {
+ tag: 'div',
+ className: 'dt-buttons'
+ },
+ collection: {
+ action: {
+ // action button
+ dropHtml: '<span
class="dt-button-down-arrow">▼</span>'
+ },
+ container: {
+ // The element used for the dropdown
+ className: 'dt-button-collection',
+ content: {
+ className: '',
+ tag: 'div'
+ },
+ tag: 'div'
+ }
+ // optionally
+ // , button: IButton - buttons inside the collection
container
+ // , split: ISplit - splits inside the collection
container
+ },
+ button: {
+ tag: 'button',
+ className: 'dt-button',
+ active: 'dt-button-active', // class name
+ disabled: 'disabled', // class name
+ spacer: {
+ className: 'dt-button-spacer',
+ tag: 'span'
+ },
+ liner: {
+ tag: 'span',
+ className: ''
+ }
+ },
+ split: {
+ action: {
+ // action button
+ className: 'dt-button-split-drop-button
dt-button',
+ tag: 'button'
+ },
+ dropdown: {
+ // button to trigger the dropdown
+ align: 'split-right',
+ className: 'dt-button-split-drop',
+ dropHtml: '<span
class="dt-button-down-arrow">▼</span>',
+ splitAlignClass: 'dt-button-split-left',
+ tag: 'button'
+ },
+ wrapper: {
+ // wrap around both
+ className: 'dt-button-split',
+ tag: 'div'
+ }
+ }
+ }
+};
+
+/**
+ * Version information
+ * @type {string}
+ * @static
+ */
+Buttons.version = '2.4.2';
+
+$.extend(_dtButtons, {
+ collection: {
+ text: function (dt) {
+ return dt.i18n('buttons.collection', 'Collection');
+ },
+ className: 'buttons-collection',
+ closeButton: false,
+ init: function (dt, button, config) {
+ button.attr('aria-expanded', false);
+ },
+ action: function (e, dt, button, config) {
+ if (config._collection.parents('body').length) {
+ this.popover(false, config);
+ }
+ else {
+ this.popover(config._collection, config);
+ }
+
+ // When activated using a key - auto focus on the
+ // first item in the popover
+ if (e.type === 'keypress') {
+ $('a, button',
config._collection).eq(0).focus();
+ }
+ },
+ attr: {
+ 'aria-haspopup': 'dialog'
+ }
+ // Also the popover options, defined in Buttons.popover
+ },
+ split: {
+ text: function (dt) {
+ return dt.i18n('buttons.split', 'Split');
+ },
+ className: 'buttons-split',
+ closeButton: false,
+ init: function (dt, button, config) {
+ return button.attr('aria-expanded', false);
+ },
+ action: function (e, dt, button, config) {
+ this.popover(config._collection, config);
+ },
+ attr: {
+ 'aria-haspopup': 'dialog'
+ }
+ // Also the popover options, defined in Buttons.popover
+ },
+ copy: function (dt, conf) {
+ if (_dtButtons.copyHtml5) {
+ return 'copyHtml5';
+ }
+ },
+ csv: function (dt, conf) {
+ if (_dtButtons.csvHtml5 && _dtButtons.csvHtml5.available(dt,
conf)) {
+ return 'csvHtml5';
+ }
+ },
+ excel: function (dt, conf) {
+ if (_dtButtons.excelHtml5 &&
_dtButtons.excelHtml5.available(dt, conf)) {
+ return 'excelHtml5';
+ }
+ },
+ pdf: function (dt, conf) {
+ if (_dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available(dt,
conf)) {
+ return 'pdfHtml5';
+ }
+ },
+ pageLength: function (dt) {
+ var lengthMenu = dt.settings()[0].aLengthMenu;
+ var vals = [];
+ var lang = [];
+ var text = function (dt) {
+ return dt.i18n(
+ 'buttons.pageLength',
+ {
+ '-1': 'Show all rows',
+ _: 'Show %d rows'
+ },
+ dt.page.len()
+ );
+ };
+
+ // Support for DataTables 1.x 2D array
+ if (Array.isArray(lengthMenu[0])) {
+ vals = lengthMenu[0];
+ lang = lengthMenu[1];
+ }
+ else {
+ for (var i = 0; i < lengthMenu.length; i++) {
+ var option = lengthMenu[i];
+
+ // Support for DataTables 2 object in the array
+ if ($.isPlainObject(option)) {
+ vals.push(option.value);
+ lang.push(option.label);
+ }
+ else {
+ vals.push(option);
+ lang.push(option);
+ }
+ }
+ }
+
+ return {
+ extend: 'collection',
+ text: text,
+ className: 'buttons-page-length',
+ autoClose: true,
+ buttons: $.map(vals, function (val, i) {
+ return {
+ text: lang[i],
+ className: 'button-page-length',
+ action: function (e, dt) {
+ dt.page.len(val).draw();
+ },
+ init: function (dt, node, conf) {
+ var that = this;
+ var fn = function () {
+
that.active(dt.page.len() === val);
+ };
+
+ dt.on('length.dt' +
conf.namespace, fn);
+ fn();
+ },
+ destroy: function (dt, node, conf) {
+ dt.off('length.dt' +
conf.namespace);
+ }
+ };
+ }),
+ init: function (dt, node, conf) {
+ var that = this;
+ dt.on('length.dt' + conf.namespace, function ()
{
+ that.text(conf.text);
+ });
+ },
+ destroy: function (dt, node, conf) {
+ dt.off('length.dt' + conf.namespace);
+ }
+ };
+ },
+ spacer: {
+ style: 'empty',
+ spacer: true,
+ text: function (dt) {
+ return dt.i18n('buttons.spacer', '');
+ }
+ }
+});
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
+ * DataTables API
+ *
+ * For complete documentation, please refer to the docs/api directory or the
+ * DataTables site
+ */
+
+// Buttons group and individual button selector
+DataTable.Api.register('buttons()', function (group, selector) {
+ // Argument shifting
+ if (selector === undefined) {
+ selector = group;
+ group = undefined;
+ }
+
+ this.selector.buttonGroup = group;
+
+ var res = this.iterator(
+ true,
+ 'table',
+ function (ctx) {
+ if (ctx._buttons) {
+ return Buttons.buttonSelector(
+ Buttons.instanceSelector(group,
ctx._buttons),
+ selector
+ );
+ }
+ },
+ true
+ );
+
+ res._groupSelector = group;
+ return res;
+});
+
+// Individual button selector
+DataTable.Api.register('button()', function (group, selector) {
+ // just run buttons() and truncate
+ var buttons = this.buttons(group, selector);
+
+ if (buttons.length > 1) {
+ buttons.splice(1, buttons.length);
+ }
+
+ return buttons;
+});
+
+// Active buttons
+DataTable.Api.registerPlural('buttons().active()', 'button().active()',
function (flag) {
+ if (flag === undefined) {
+ return this.map(function (set) {
+ return set.inst.active(set.node);
+ });
+ }
+
+ return this.each(function (set) {
+ set.inst.active(set.node, flag);
+ });
+});
+
+// Get / set button action
+DataTable.Api.registerPlural('buttons().action()', 'button().action()',
function (action) {
+ if (action === undefined) {
+ return this.map(function (set) {
+ return set.inst.action(set.node);
+ });
+ }
+
+ return this.each(function (set) {
+ set.inst.action(set.node, action);
+ });
+});
+
+// Collection control
+DataTable.Api.registerPlural(
+ 'buttons().collectionRebuild()',
+ 'button().collectionRebuild()',
+ function (buttons) {
+ return this.each(function (set) {
+ for (var i = 0; i < buttons.length; i++) {
+ if (typeof buttons[i] === 'object') {
+ buttons[i].parentConf = set;
+ }
+ }
+ set.inst.collectionRebuild(set.node, buttons);
+ });
+ }
+);
+
+// Enable / disable buttons
+DataTable.Api.register(['buttons().enable()', 'button().enable()'], function
(flag) {
+ return this.each(function (set) {
+ set.inst.enable(set.node, flag);
+ });
+});
+
+// Disable buttons
+DataTable.Api.register(['buttons().disable()', 'button().disable()'], function
() {
+ return this.each(function (set) {
+ set.inst.disable(set.node);
+ });
+});
+
+// Button index
+DataTable.Api.register('button().index()', function () {
+ var idx = null;
+
+ this.each(function (set) {
+ var res = set.inst.index(set.node);
+
+ if (res !== null) {
+ idx = res;
+ }
+ });
+
+ return idx;
+});
+
+// Get button nodes
+DataTable.Api.registerPlural('buttons().nodes()', 'button().node()', function
() {
+ var jq = $();
+
+ // jQuery will automatically reduce duplicates to a single entry
+ $(
+ this.each(function (set) {
+ jq = jq.add(set.inst.node(set.node));
+ })
+ );
+
+ return jq;
+});
+
+// Get / set button processing state
+DataTable.Api.registerPlural('buttons().processing()',
'button().processing()', function (flag) {
+ if (flag === undefined) {
+ return this.map(function (set) {
+ return set.inst.processing(set.node);
+ });
+ }
+
+ return this.each(function (set) {
+ set.inst.processing(set.node, flag);
+ });
+});
+
+// Get / set button text (i.e. the button labels)
+DataTable.Api.registerPlural('buttons().text()', 'button().text()', function
(label) {
+ if (label === undefined) {
+ return this.map(function (set) {
+ return set.inst.text(set.node);
+ });
+ }
+
+ return this.each(function (set) {
+ set.inst.text(set.node, label);
+ });
+});
+
+// Trigger a button's action
+DataTable.Api.registerPlural('buttons().trigger()', 'button().trigger()',
function () {
+ return this.each(function (set) {
+ set.inst.node(set.node).trigger('click');
+ });
+});
+
+// Button resolver to the popover
+DataTable.Api.register('button().popover()', function (content, options) {
+ return this.map(function (set) {
+ return set.inst._popover(content, this.button(this[0].node),
options);
+ });
+});
+
+// Get the container elements
+DataTable.Api.register('buttons().containers()', function () {
+ var jq = $();
+ var groupSelector = this._groupSelector;
+
+ // We need to use the group selector directly, since if there are no
buttons
+ // the result set will be empty
+ this.iterator(true, 'table', function (ctx) {
+ if (ctx._buttons) {
+ var insts = Buttons.instanceSelector(groupSelector,
ctx._buttons);
+
+ for (var i = 0, ien = insts.length; i < ien; i++) {
+ jq = jq.add(insts[i].container());
+ }
+ }
+ });
+
+ return jq;
+});
+
+DataTable.Api.register('buttons().container()', function () {
+ // API level of nesting is `buttons()` so we can zip into the
containers method
+ return this.containers().eq(0);
+});
+
+// Add a new button
+DataTable.Api.register('button().add()', function (idx, conf, draw) {
+ var ctx = this.context;
+
+ // Don't use `this` as it could be empty - select the instances directly
+ if (ctx.length) {
+ var inst = Buttons.instanceSelector(this._groupSelector,
ctx[0]._buttons);
+
+ if (inst.length) {
+ inst[0].add(conf, idx, draw);
+ }
+ }
+
+ return this.button(this._groupSelector, idx);
+});
+
+// Destroy the button sets selected
+DataTable.Api.register('buttons().destroy()', function () {
+ this.pluck('inst')
+ .unique()
+ .each(function (inst) {
+ inst.destroy();
+ });
+
+ return this;
+});
+
+// Remove a button
+DataTable.Api.registerPlural('buttons().remove()', 'buttons().remove()',
function () {
+ this.each(function (set) {
+ set.inst.remove(set.node);
+ });
+
+ return this;
+});
+
+// Information box that can be used by buttons
+var _infoTimer;
+DataTable.Api.register('buttons.info()', function (title, message, time) {
+ var that = this;
+
+ if (title === false) {
+ this.off('destroy.btn-info');
+ _fadeOut($('#datatables_buttons_info'), 400, function () {
+ $(this).remove();
+ });
+ clearTimeout(_infoTimer);
+ _infoTimer = null;
+
+ return this;
+ }
+
+ if (_infoTimer) {
+ clearTimeout(_infoTimer);
+ }
+
+ if ($('#datatables_buttons_info').length) {
+ $('#datatables_buttons_info').remove();
+ }
+
+ title = title ? '<h2>' + title + '</h2>' : '';
+
+ _fadeIn(
+ $('<div id="datatables_buttons_info" class="dt-button-info"/>')
+ .html(title)
+ .append($('<div/>')[typeof message === 'string' ?
'html' : 'append'](message))
+ .css('display', 'none')
+ .appendTo('body')
+ );
+
+ if (time !== undefined && time !== 0) {
+ _infoTimer = setTimeout(function () {
+ that.buttons.info(false);
+ }, time);
+ }
+
+ this.on('destroy.btn-info', function () {
+ that.buttons.info(false);
+ });
+
+ return this;
+});
+
+// Get data from the table for export - this is common to a number of plug-in
+// buttons so it is included in the Buttons core library
+DataTable.Api.register('buttons.exportData()', function (options) {
+ if (this.context.length) {
+ return _exportData(new DataTable.Api(this.context[0]), options);
+ }
+});
+
+// Get information about the export that is common to many of the export data
+// types (DRY)
+DataTable.Api.register('buttons.exportInfo()', function (conf) {
+ if (!conf) {
+ conf = {};
+ }
+
+ return {
+ filename: _filename(conf),
+ title: _title(conf),
+ messageTop: _message(this, conf.message || conf.messageTop,
'top'),
+ messageBottom: _message(this, conf.messageBottom, 'bottom')
+ };
+});
+
+/**
+ * Get the file name for an exported file.
+ *
+ * @param {object} config Button configuration
+ * @param {boolean} incExtension Include the file name extension
+ */
+var _filename = function (config) {
+ // Backwards compatibility
+ var filename =
+ config.filename === '*' &&
+ config.title !== '*' &&
+ config.title !== undefined &&
+ config.title !== null &&
+ config.title !== ''
+ ? config.title
+ : config.filename;
+
+ if (typeof filename === 'function') {
+ filename = filename();
+ }
+
+ if (filename === undefined || filename === null) {
+ return null;
+ }
+
+ if (filename.indexOf('*') !== -1) {
+ filename = filename.replace('*', $('head >
title').text()).trim();
+ }
+
+ // Strip characters which the OS will object to
+ filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g,
'');
+
+ var extension = _stringOrFunction(config.extension);
+ if (!extension) {
+ extension = '';
+ }
+
+ return filename + extension;
+};
+
+/**
+ * Simply utility method to allow parameters to be given as a function
+ *
+ * @param {undefined|string|function} option Option
+ * @return {null|string} Resolved value
+ */
+var _stringOrFunction = function (option) {
+ if (option === null || option === undefined) {
+ return null;
+ }
+ else if (typeof option === 'function') {
+ return option();
+ }
+ return option;
+};
+
+/**
+ * Get the title for an exported file.
+ *
+ * @param {object} config Button configuration
+ */
+var _title = function (config) {
+ var title = _stringOrFunction(config.title);
+
+ return title === null
+ ? null
+ : title.indexOf('*') !== -1
+ ? title.replace('*', $('head > title').text() || 'Exported
data')
+ : title;
+};
+
+var _message = function (dt, option, position) {
+ var message = _stringOrFunction(option);
+ if (message === null) {
+ return null;
+ }
+
+ var caption = $('caption', dt.table().container()).eq(0);
+ if (message === '*') {
+ var side = caption.css('caption-side');
+ if (side !== position) {
+ return null;
+ }
+
+ return caption.length ? caption.text() : '';
+ }
+
+ return message;
+};
+
+var _exportTextarea = $('<textarea/>')[0];
+var _exportData = function (dt, inOpts) {
+ var config = $.extend(
+ true,
+ {},
+ {
+ rows: null,
+ columns: '',
+ modifier: {
+ search: 'applied',
+ order: 'applied'
+ },
+ orthogonal: 'display',
+ stripHtml: true,
+ stripNewlines: true,
+ decodeEntities: true,
+ trim: true,
+ format: {
+ header: function (d) {
+ return Buttons.stripData(d, config);
+ },
+ footer: function (d) {
+ return Buttons.stripData(d, config);
+ },
+ body: function (d) {
+ return Buttons.stripData(d, config);
+ }
+ },
+ customizeData: null
+ },
+ inOpts
+ );
+
+ var header = dt
+ .columns(config.columns)
+ .indexes()
+ .map(function (idx) {
+ var el = dt.column(idx).header();
+ return config.format.header(el.innerHTML, idx, el);
+ })
+ .toArray();
+
+ var footer = dt.table().footer()
+ ? dt
+ .columns(config.columns)
+ .indexes()
+ .map(function (idx) {
+ var el = dt.column(idx).footer();
+ return config.format.footer(el ?
el.innerHTML : '', idx, el);
+ })
+ .toArray()
+ : null;
+
+ // If Select is available on this table, and any rows are selected,
limit the export
+ // to the selected rows. If no rows are selected, all rows will be
exported. Specify
+ // a `selected` modifier to control directly.
+ var modifier = $.extend({}, config.modifier);
+ if (dt.select && typeof dt.select.info === 'function' &&
modifier.selected === undefined) {
+ if (dt.rows(config.rows, $.extend({ selected: true },
modifier)).any()) {
+ $.extend(modifier, { selected: true });
+ }
+ }
+
+ var rowIndexes = dt.rows(config.rows, modifier).indexes().toArray();
+ var selectedCells = dt.cells(rowIndexes, config.columns);
+ var cells = selectedCells.render(config.orthogonal).toArray();
+ var cellNodes = selectedCells.nodes().toArray();
+
+ var columns = header.length;
+ var rows = columns > 0 ? cells.length / columns : 0;
+ var body = [];
+ var cellCounter = 0;
+
+ for (var i = 0, ien = rows; i < ien; i++) {
+ var row = [columns];
+
+ for (var j = 0; j < columns; j++) {
+ row[j] = config.format.body(cells[cellCounter], i, j,
cellNodes[cellCounter]);
+ cellCounter++;
+ }
+
+ body[i] = row;
+ }
+
+ var data = {
+ header: header,
+ footer: footer,
+ body: body
+ };
+
+ if (config.customizeData) {
+ config.customizeData(data);
+ }
+
+ return data;
+};
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
+ * DataTables interface
+ */
+
+// Attach to DataTables objects for global access
+$.fn.dataTable.Buttons = Buttons;
+$.fn.DataTable.Buttons = Buttons;
+
+// DataTables creation - check if the buttons have been defined for this table,
+// they will have been if the `B` option was used in `dom`, otherwise we should
+// create the buttons instance here so they can be inserted into the document
+// using the API. Listen for `init` for compatibility with pre 1.10.10, but to
+// be removed in future.
+$(document).on('init.dt plugin-init.dt', function (e, settings) {
+ if (e.namespace !== 'dt') {
+ return;
+ }
+
+ var opts = settings.oInit.buttons || DataTable.defaults.buttons;
+
+ if (opts && !settings._buttons) {
+ new Buttons(settings, opts).container();
+ }
+});
+
+function _init(settings, options) {
+ var api = new DataTable.Api(settings);
+ var opts = options ? options : api.init().buttons ||
DataTable.defaults.buttons;
+
+ return new Buttons(api, opts).container();
+}
+
+// DataTables `dom` feature option
+DataTable.ext.feature.push({
+ fnInit: _init,
+ cFeature: 'B'
+});
+
+// DataTables 2 layout feature
+if (DataTable.ext.features) {
+ DataTable.ext.features.register('buttons', _init);
+}
+
+
+return DataTable;
+}));
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js
index c711f6e8db..bdb9361145 100644
---
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js
@@ -252,6 +252,14 @@ function createDataTable(table, storageKey) {
data: getStoredRows(storageKey)
});
},
+ "buttons": [{
+ "extend": 'colvis',
+ "text": '<i class="bi bi-gear"></i>',
+ "titleAttr": 'Columns'
+ }],
+ "dom": '<"row"<"col-sm-12 col-md-4"l><"col-sm-12 col-md-6"f><"col-sm-12
col-md-2"B>>' +
+ '<"row dt-row"<"col-sm-12"rt>>' +
+ '<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
"stateSave": true,
"colReorder": true,
"columnDefs": [{
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/compactors.ftl
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/compactors.ftl
index 71944f81e7..bed7793e2e 100644
---
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/compactors.ftl
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/compactors.ftl
@@ -23,10 +23,12 @@
</div>
<div class="row">
<div class="col-xs-12">
+ <span class="table-caption">Compactors</span>
+ <br />
+ <span class="table-subcaption">The following Compactors reported
status.</span>
+ <br />
+ <br />
<table id="compactorsTable" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">Compactors</span><br />
- <span class="table-subcaption">The following Compactors reported
status.</span><br />
- </caption>
<#include "table_loading.ftl" >
</table>
</div>
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/coordinator.ftl
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/coordinator.ftl
index 11d700f30f..70564e5d11 100644
---
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/coordinator.ftl
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/coordinator.ftl
@@ -18,19 +18,22 @@
under the License.
-->
+ <br />
+ <span class="table-caption">Compaction Coordinator Activity</span>
<br />
<table id="coordinators" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">Compaction Coordinator
Activity</span><br /></caption>
- <#include "table_loading.ftl" >
+ <#include "table_loading.ftl" >
</table>
<br />
+ <span class="table-caption">Compaction Coordinator Queues</span>
+ <br />
<table id="coordinator_queues" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">Compaction Coordinator
Queues</span><br /></caption>
- <#include "table_loading.ftl" >
+ <#include "table_loading.ftl" >
</table>
<br />
+ <span class="table-caption">Running Compactions By Table</span>
+ <br />
<table id="table_running" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">Running Compactions By
Table</span><br /></caption>
<thead>
<tr>
<th>Table Name</th>
@@ -40,8 +43,9 @@
<tbody></tbody>
</table>
<br />
+ <span class="table-caption">Running Compactions By Queue</span>
+ <br />
<table id="queue_running" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">Running Compactions By
Queue</span><br /></caption>
<thead>
<tr>
<th>Queue Name</th>
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl
index 1e2ad1e5ef..163ebe8202 100644
---
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl
@@ -42,12 +42,16 @@
<script src="resources/external/jquery/jquery-3.7.1.js"></script>
<script
src="resources/external/bootstrap/js/bootstrap.bundle.js"></script>
<script
src="resources/external/datatables/js/jquery.dataTables.js"></script>
+ <script
src="resources/external/datatables/js/dataTables.buttons.js"></script>
<script
src="resources/external/datatables/js/dataTables.colReorder.js"></script>
<script
src="resources/external/datatables/js/dataTables.bootstrap5.js"></script>
+ <script
src="resources/external/datatables/js/buttons.bootstrap5.js"></script>
<script
src="resources/external/datatables/js/colReorder.bootstrap5.js"></script>
+ <script
src="resources/external/datatables/js/buttons.colVis.js"></script>
<link rel="stylesheet"
href="resources/external/bootstrap/css/bootstrap.css" />
<link rel="stylesheet"
href="resources/external/bootstrap/css/bootstrap-icons.css" />
<link rel="stylesheet"
href="resources/external/datatables/css/dataTables.bootstrap5.css" />
+ <link rel="stylesheet"
href="resources/external/datatables/css/buttons.bootstrap5.css" />
<link rel="stylesheet"
href="resources/external/datatables/css/colReorder.bootstrap5.css" />
</#if>
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/gc.ftl
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/gc.ftl
index 19e6d317bb..93bb069fa6 100644
---
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/gc.ftl
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/gc.ftl
@@ -22,20 +22,23 @@
<div id="gc-banner-message" class="alert" role="alert"></div>
</div>
<div>
+ <span class="table-caption">Garbage Collector</span>
+ <br />
<table id="gc-server" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">Garbage Collector</span><br
/></caption>
<#include "table_loading.ftl" >
</table>
</div>
<div>
+ <span class="table-caption">File Collection</span>
+ <br />
<table id="gc-file" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">File Collection</span><br
/></caption>
<#include "table_loading.ftl" >
</table>
</div>
- <div>
+ <div>
+ <span class="table-caption">Wal Collection</span>
+ <br />
<table id="gc-wal" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">Wal Collection</span><br
/></caption>
<#include "table_loading.ftl" >
</table>
</div>
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/manager.ftl
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/manager.ftl
index 503ade72d5..f011fd46fd 100644
---
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/manager.ftl
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/manager.ftl
@@ -27,13 +27,15 @@
<div id="managerStateBanner" style="display: none;">
<div id="manager-state-message" class="alert alert-warning"
role="alert"></div>
</div>
+ <span class="table-caption">Managers</span>
+ <br />
<table id="managers" class="table caption-top table-bordered table-striped
table-condensed">
- <caption><span class="table-caption">Managers</span><br /></caption>
<#include "table_loading.ftl" >
</table>
<br />
+ <span class="table-caption">Manager Fate Activity</span>
+ <br />
<table id="managers_fate" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">Manager Fate Activity</span><br
/></caption>
<#include "table_loading.ftl" >
</table>
<br />
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/sservers.ftl
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/sservers.ftl
index 3ac0891e50..b506687468 100644
---
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/sservers.ftl
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/sservers.ftl
@@ -23,10 +23,12 @@
</div>
<div class="row">
<div class="col-xs-12">
+ <span class="table-caption">Scan Servers</span>
+ <br />
+ <span class="table-subcaption">The following Scan Servers reported
status.</span>
+ <br />
+ <br />
<table id="sservers" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">Scan Servers</span><br />
- <span class="table-subcaption">The following Scan Servers reported
status.</span><br />
- </caption>
<#include "table_loading.ftl" >
</table>
</div>
diff --git
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/tservers.ftl
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/tservers.ftl
index 2973391603..1978759908 100644
---
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/tservers.ftl
+++
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/tservers.ftl
@@ -26,10 +26,12 @@
</div>
<div class="row">
<div class="col-xs-12">
+ <span class="table-caption">Tablet Servers</span>
+ <br />
+ <span class="table-subcaption">The following Tablet Servers reported
status.</span>
+ <br />
+ <br />
<table id="tservers" class="table caption-top table-bordered
table-striped table-condensed">
- <caption><span class="table-caption">Tablet Servers</span><br />
- <span class="table-subcaption">The following Tablet Servers
reported status.</span><br />
- </caption>
<#include "table_loading.ftl" >
</table>
</div>