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('"); + echo('"); } } -unset($overview_extended); +unset($overview_extended, $box_args, $table_attribs); // Groups if (OBSERVIUM_EDITION !== 'community' && $config['web_show_overview_extra'] &&
' . escape_html($device['sysDescr']) . "
' . escape_html($device['sysDescr']) . "