diff --git a/hyperglass/ui/components/Result.js b/hyperglass/ui/components/Result.js index 8feae76..0a99669 100644 --- a/hyperglass/ui/components/Result.js +++ b/hyperglass/ui/components/Result.js @@ -29,6 +29,7 @@ import ResultHeader from "~/components/ResultHeader"; import CacheTimeout from "~/components/CacheTimeout"; import BGPTable from "~/components/BGPTable"; import TextOutput from "~/components/TextOutput"; +import { tableToString } from "~/util"; format.extend(String.prototype, {}); @@ -141,8 +142,10 @@ const Result = forwardRef( "error"; let Output = TextOutput; + let copyValue = data?.output; if (data?.format === "application/json") { Output = structuredDataComponent[queryType]; + copyValue = tableToString(queryTarget, data, config); } useEffect(() => { @@ -183,7 +186,7 @@ const Result = forwardRef( diff --git a/hyperglass/ui/util.js b/hyperglass/ui/util.js index ac98ce6..b4bdd6c 100644 --- a/hyperglass/ui/util.js +++ b/hyperglass/ui/util.js @@ -1,4 +1,10 @@ import chroma from "chroma-js"; +import dayjs from "dayjs"; +import relativeTimePlugin from "dayjs/plugin/relativeTime"; +import utcPlugin from "dayjs/plugin/utc"; + +dayjs.extend(relativeTimePlugin); +dayjs.extend(utcPlugin); const isDark = color => { // YIQ equation from http://24ways.org/2010/calculating-color-contrast @@ -33,4 +39,78 @@ const googleFontUrl = (fontFamily, weights = [300, 400, 700]) => { return urlBase; }; -export { isDark, isLight, opposingColor, googleFontUrl }; +const formatAsPath = path => { + return path.join(" → "); +}; + +const formatCommunities = comms => { + const commsStr = comms.map(c => ` - ${c}`); + return "\n" + commsStr.join("\n"); +}; + +const formatBool = val => { + let fmt = ""; + if (val === true) { + fmt = "yes"; + } else if (val === false) { + fmt = "no"; + } + return fmt; +}; + +const formatTime = val => { + const now = dayjs.utc(); + const then = now.subtract(val, "seconds"); + const timestamp = then.toString().replace("GMT", "UTC"); + const relative = now.to(then, true); + return `${relative} (${timestamp})`; +}; + +const tableToString = (target, data, config) => { + try { + const formatRpkiState = val => { + const rpkiStateNames = [ + config.web.text.rpki_invalid, + config.web.text.rpki_valid, + config.web.text.rpki_unknown, + config.web.text.rpki_unverified + ]; + return rpkiStateNames[val]; + }; + + const tableFormatMap = { + age: formatTime, + active: formatBool, + as_path: formatAsPath, + communities: formatCommunities, + rpki_state: formatRpkiState + }; + + let tableStringParts = [ + `Routes For: ${target}`, + `Timestamp: ${data.timestamp} UTC` + ]; + + data.output.routes.map(route => { + config.parsed_data_fields.map(field => { + const [header, accessor, align] = field; + if (align !== null) { + let value = route[accessor]; + const fmtFunc = tableFormatMap[accessor] ?? String; + value = fmtFunc(value); + if (accessor === "prefix") { + tableStringParts.push(` - ${header}: ${value}`); + } else { + tableStringParts.push(` - ${header}: ${value}`); + } + } + }); + }); + return tableStringParts.join("\n"); + } catch (err) { + console.error(err); + return `An error occurred while parsing the output: '${err.message}'`; + } +}; + +export { isDark, isLight, opposingColor, googleFontUrl, tableToString };