diff --git a/html/js/observium.js b/html/js/observium.js
index efec922..deddf19 100644
--- a/html/js/observium.js
+++ b/html/js/observium.js
@@ -1074,3 +1074,115 @@ $.fn.tinyToggle = function (action) {
}
});
};
+
+/**
+ * Copy text to the clipboard.
+ * Uses the async Clipboard API when available (secure context only),
+ * otherwise falls back to a hidden textarea and document.execCommand('copy'),
+ * which also works on plain http and with self-signed certificates.
+ *
+ * @param {string} text Text to copy
+ * @param {function} callback Called with true on success, false on failure
+ */
+function copy_to_clipboard(text, callback) {
+ callback = typeof callback === 'function' ? callback : function () {};
+
+ var fallback = function () {
+ var textarea = document.createElement('textarea');
+ textarea.value = text;
+ textarea.setAttribute('readonly', '');
+ textarea.style.position = 'fixed';
+ textarea.style.top = '-1000px';
+ textarea.style.opacity = '0';
+ document.body.appendChild(textarea);
+ textarea.select();
+ var success = false;
+ try {
+ success = document.execCommand('copy');
+ } catch (e) {
+ success = false;
+ }
+ document.body.removeChild(textarea);
+ callback(success);
+ };
+
+ if (navigator.clipboard && window.isSecureContext) {
+ navigator.clipboard.writeText(text).then(function () {
+ callback(true);
+ }, fallback);
+ } else {
+ fallback();
+ }
+}
+
+/**
+ * Build plain text from a two-column (label/value) table, one "Label: Value" per line.
+ * Optional first line from the table attribute data-copy-title.
+ * Hidden rows and rows with empty label or value are skipped.
+ * Label is taken from td.entity or from a cell with data-copy-label attribute.
+ *
+ * @param {Element} table Table element
+ * @returns {string}
+ */
+function copy_table_text(table) {
+ var clean = function (str) {
+ return $.trim(String(str).replace(/\s+/g, ' '));
+ };
+ var lines = [];
+ var title = $(table).attr('data-copy-title');
+ if (title) {
+ lines.push(clean(title));
+ }
+
+ $(table).find('tr').filter(':visible').each(function () {
+ var cells = $(this).children('td');
+ var label = '', value = '';
+
+ if (cells.first().is('[data-copy-label]')) {
+ label = cells.first().attr('data-copy-label');
+ value = cells.first().text();
+ } else if (cells.length >= 2 && cells.first().hasClass('entity')) {
+ label = cells.eq(0).text();
+ value = cells.eq(1).text();
+ }
+
+ label = clean(label);
+ value = clean(value);
+ if (label !== '' && value !== '') {
+ lines.push(label + ': ' + value);
+ }
+ });
+
+ return lines.join('\n');
+}
+
+// Box header "Copy" button: copy the box table (marked with data-copy-title) as plain text
+$(document).on('click', '.copy-table-text', function (e) {
+ e.preventDefault();
+
+ var btn = $(this);
+ var table = btn.closest('.box').find('table[data-copy-title]').first();
+ if (!table.length) {
+ return;
+ }
+
+ copy_to_clipboard(copy_table_text(table[0]), function (success) {
+ var icon = btn.children('i').first();
+ var icon_class = btn.data('icon-class') || icon.attr('class');
+ btn.data('icon-class', icon_class);
+ icon.attr('class', success ? 'icon-ok' : 'icon-remove');
+
+ var api = btn.data('qtip');
+ if (api) {
+ api.set('content.text', success ? 'Copied' : 'Copy failed');
+ }
+
+ clearTimeout(btn.data('copy-timer'));
+ btn.data('copy-timer', setTimeout(function () {
+ icon.attr('class', icon_class);
+ if (api) {
+ api.set('content.text', btn.attr('data-tooltip'));
+ }
+ }, 2000));
+ });
+});
diff --git a/html/pages/device/overview/information.inc.php b/html/pages/device/overview/information.inc.php
index e65c998..37ab5d2 100644
--- a/html/pages/device/overview/information.inc.php
+++ b/html/pages/device/overview/information.inc.php
@@ -10,17 +10,26 @@
*
*/
+// Box header with "Copy" button, copies device information as plain text (see copy_table_text() in observium.js)
+$box_args = [ 'title' => 'Device Information',
+ 'header-controls' => [ 'controls' => [ 'copy' => [ 'icon' => $config['icon']['copy'],
+ 'class' => 'copy-table-text',
+ 'data' => [ 'aria-label' => 'Copy',
+ 'data-rel' => 'tooltip',
+ 'data-tooltip' => 'Copy device information to the clipboard' ] ] ] ] ];
+$table_attribs = 'class="table table-condensed table-striped table-hover" data-copy-title="' . escape_html('Hostname: ' . $device['hostname']) . '"';
+
if (isset($overview_extended) && $overview_extended) {
// From old information_extended.inc.php
// FIXME. Not sure if it's required
- echo generate_box_open();
+ echo generate_box_open($box_args);
- echo('
');
+ echo('');
if ($config['web_show_overview_extra']) {
echo '';
- echo '| ';
+ echo ' | ';
/*
if (is_file($config['html_dir'] . '/images/hardware/' . trim($device['sysObjectID'], ".") . '.png')) {
@@ -31,15 +40,16 @@ if (isset($overview_extended) && $overview_extended) {
}
} else {
- echo generate_box_open([ 'box-class' => 'hidden-xl' ]);
+ $box_args['box-class'] = 'hidden-xl';
+ echo generate_box_open($box_args);
- echo('');
+ echo('');
if ($config['web_show_overview_extra']) {
- echo('| ' . escape_html($device['sysDescr']) . " | ");
+ echo('| ' . escape_html($device['sysDescr']) . " | ");
}
}
-unset($overview_extended);
+unset($overview_extended, $box_args, $table_attribs);
// Groups
if (OBSERVIUM_EDITION !== 'community' && $config['web_show_overview_extra'] &&
|