From cc89fd92d73c780a796c9c56f1ae985011bb37d3 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Thu, 21 Jan 2021 12:44:53 -0800 Subject: [PATCH] New simple implementation for YAML serialization Tiny in size so we don't need to fetch the 80KB yaml package for simple YAML serializaiton needs in various pages. It's also 8x faster than yaml package so that's a big plus for debug logging. --- lib/common/yaml.ts | 198 ++++++++ lib/debug.ts | 14 +- npm-shrinkwrap.json | 3 +- package.json | 6 +- test/yaml-tests.json | 861 +++++++++++++++++++++++++++++++++ test/yaml.ts | 14 + ui/components/device-faults.ts | 6 +- ui/faults-page.ts | 15 +- 8 files changed, 1096 insertions(+), 21 deletions(-) create mode 100644 lib/common/yaml.ts create mode 100644 test/yaml-tests.json create mode 100644 test/yaml.ts diff --git a/lib/common/yaml.ts b/lib/common/yaml.ts new file mode 100644 index 0000000..549812c --- /dev/null +++ b/lib/common/yaml.ts @@ -0,0 +1,198 @@ +const LINE_WIDTH = 80; +const INDENTATION = " "; + +const STRING_RESERVED = new Set([ + "true", + "True", + "TRUE", + "false", + "False", + "FALSE", + "null", + "Null", + "NULL", +]); + +function isPrintable(str: string): boolean { + return !/[^\t\n\x20-\x7e\x85\u{a0}-\u{d7ff}\u{e000}-\u{fffd}\u{10000}-\u{10ffff}]/u.test( + str + ); +} + +function stringifyKey(str: string): string { + if (!str || !isPrintable(str)) return JSON.stringify(str); + if (/^[\s-?:,[\]{}#&$!|>'"%@`]|: | #|[\n,[\]{}]|\s$/.test(str)) + return JSON.stringify(str); + return str; +} + +function foldString(str: string): string[] { + if (str.length <= LINE_WIDTH) return [str]; + if (str.startsWith(" ")) return [str]; + const words = str.split(/(?<=[^ ]) (?=[^ ])/); + const lines: string[] = []; + + let idx = 0; + let len = 0; + for (const [i, word] of words.entries()) { + len += word.length + 1; + if (len >= LINE_WIDTH && len !== word.length) { + lines.push(words.slice(idx, i).join(" ")); + idx = i; + len = 0; + } + } + + if (idx < words.length) lines.push(words.slice(idx).join(" ")); + + return lines; +} + +function stringifyString(str: string, res: string[], prefix1, prefix2): void { + if (/^\s*$/.test(str) || STRING_RESERVED.has(str) || !isPrintable(str)) { + res.push(prefix1 + JSON.stringify(str)); + return; + } + + if (!prefix2) prefix2 = INDENTATION; + + const lines = str.split("\n"); + if (lines.length > 1) { + let idt = ""; + let chmp = "-"; + if ((lines.find((l) => l) || "").startsWith(" ")) + idt = `${INDENTATION.length}`; + + if (!lines[lines.length - 1]) { + lines.pop(); + if (lines[lines.length - 1]) chmp = ""; + else chmp = "+"; + } + + if (/^\s+$/.test(lines[lines.length - 1])) { + res.push(prefix1 + JSON.stringify(str)); + return; + } + + let isFolded = false; + const folded = lines.map((l) => { + const ls = foldString(l); + if (ls.length > 1) isFolded = true; + return ls; + }); + + if (!isFolded) { + res.push( + `${prefix1}|${idt}${chmp}`, + ...lines.map((l) => (l ? prefix2 + l : l)) + ); + return; + } + + res.push(`${prefix1}>${idt}${chmp}`); + res.push(...folded[0].map((f) => prefix2 + f)); + for (let i = 1; i < folded.length; ++i) { + const prevLine = folded[i - 1][0]; + if (prevLine && !folded[i - 1][0].startsWith(" ")) res.push(""); + res.push(...folded[i].map((f) => prefix2 + f)); + } + return; + } + + if ( + /^[\s-?:,[\]{}#&$!|>'"%@`]|: | #|\s$/.test(str) || + parseFloat(str) === +str + ) { + res.push(prefix1 + JSON.stringify(str)); + return; + } + + res.push(prefix1 + str); +} + +function stringifyAny( + obj: unknown, + res: string[], + prefix1 = "", + prefix2 = "" +): void { + if (obj == null || typeof obj === "number" || typeof obj === "boolean") { + res.push(`${prefix1}${obj}`); + return; + } + if (obj instanceof Date) { + res.push(`${prefix1}${obj.toJSON()}`); + return; + } + + if (typeof obj === "string") { + stringifyString(obj, res, prefix1, prefix2); + return; + } + + if (Array.isArray(obj)) { + if (!obj.length) { + res.push(prefix1 + "[]"); + return; + } + + if (!prefix1 || prefix1.endsWith("- ")) { + stringifyAny(obj[0], res, prefix1 + "- ", prefix2 + INDENTATION); + prefix1 = prefix2 + "- "; + prefix2 = prefix2 + INDENTATION; + for (let i = 1; i < obj.length; ++i) + stringifyAny(obj[i], res, prefix1, prefix2); + } else { + res.push(prefix1); + prefix1 = prefix2 + "- "; + prefix2 = prefix2 + INDENTATION; + for (let i = 0; i < obj.length; ++i) + stringifyAny(obj[i], res, prefix1, prefix2); + } + return; + } + + const entries = Object.entries(obj); + + if (!entries.length) { + res.push(prefix1 + "{}"); + return; + } + + if (!prefix1 || prefix1.endsWith("- ")) { + stringifyAny( + entries[0][1], + res, + prefix1 + `${stringifyKey(entries[0][0])}: `, + prefix2 + INDENTATION + ); + prefix1 = prefix2; + prefix2 = prefix2 + INDENTATION; + for (let i = 1; i < entries.length; ++i) { + stringifyAny( + entries[i][1], + res, + prefix1 + `${stringifyKey(entries[i][0])}: `, + prefix2 + ); + } + } else { + res.push(prefix1); + prefix1 = prefix2; + prefix2 = prefix2 + INDENTATION; + for (let i = 0; i < entries.length; ++i) { + stringifyAny( + entries[i][1], + res, + prefix1 + `${stringifyKey(entries[i][0])}: `, + prefix2 + ); + } + } +} + +export function stringify(obj: unknown): string { + const lines: string[] = []; + stringifyAny(obj, lines); + return lines.join("\n") + "\n"; +} diff --git a/lib/debug.ts b/lib/debug.ts index 1801000..d2a8de1 100644 --- a/lib/debug.ts +++ b/lib/debug.ts @@ -25,7 +25,7 @@ import { } from "http"; import { Socket } from "net"; import { appendFileSync } from "fs"; -import * as yaml from "yaml"; +import { stringify } from "./common/yaml"; import * as config from "./config"; const DEBUG_FILE = "" + config.get("DEBUG_FILE"); @@ -64,7 +64,7 @@ export function incomingHttpRequest( }; if (DEBUG_FORMAT === "yaml") - appendFileSync(DEBUG_FILE, "---\n" + yaml.stringify(msg)); + appendFileSync(DEBUG_FILE, "---\n" + stringify(msg)); else if (DEBUG_FORMAT === "json") appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n"); else throw new Error(`Unrecognized DEBUG_FORMAT option`); @@ -90,7 +90,7 @@ export function outgoingHttpResponse( }; if (DEBUG_FORMAT === "yaml") - appendFileSync(DEBUG_FILE, "---\n" + yaml.stringify(msg)); + appendFileSync(DEBUG_FILE, "---\n" + stringify(msg)); else if (DEBUG_FORMAT === "json") appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n"); else throw new Error(`Unrecognized DEBUG_FORMAT option`); @@ -119,7 +119,7 @@ export function outgoingHttpRequest( }; if (DEBUG_FORMAT === "yaml") - appendFileSync(DEBUG_FILE, "---\n" + yaml.stringify(msg)); + appendFileSync(DEBUG_FILE, "---\n" + stringify(msg)); else if (DEBUG_FORMAT === "json") appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n"); else throw new Error(`Unrecognized DEBUG_FORMAT option`); @@ -147,7 +147,7 @@ export function outgoingHttpRequestError( }; if (DEBUG_FORMAT === "yaml") - appendFileSync(DEBUG_FILE, "---\n" + yaml.stringify(msg)); + appendFileSync(DEBUG_FILE, "---\n" + stringify(msg)); else if (DEBUG_FORMAT === "json") appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n"); else throw new Error(`Unrecognized DEBUG_FORMAT option`); @@ -173,7 +173,7 @@ export function incomingHttpResponse( }; if (DEBUG_FORMAT === "yaml") - appendFileSync(DEBUG_FILE, "---\n" + yaml.stringify(msg)); + appendFileSync(DEBUG_FILE, "---\n" + stringify(msg)); else if (DEBUG_FORMAT === "json") appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n"); else throw new Error(`Unrecognized DEBUG_FORMAT option`); @@ -197,7 +197,7 @@ export function outgoingUdpMessage( }; if (DEBUG_FORMAT === "yaml") - appendFileSync(DEBUG_FILE, "---\n" + yaml.stringify(msg)); + appendFileSync(DEBUG_FILE, "---\n" + stringify(msg)); else if (DEBUG_FORMAT === "json") appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n"); else throw new Error(`Unrecognized DEBUG_FORMAT option`); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e16738d..4cfe31a 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -8167,7 +8167,8 @@ "yaml": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true }, "yargs": { "version": "16.0.3", diff --git a/package.json b/package.json index c8fa757..3aca7d4 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,7 @@ "later": "^1.2.0", "mongodb": "^3.6.2", "parsimmon": "^1.16.0", - "seedrandom": "^3.0.5", - "yaml": "^1.10.0" + "seedrandom": "^3.0.5" }, "devDependencies": { "@rollup/plugin-commonjs": "^16.0.0", @@ -72,7 +71,8 @@ "svgo": "^1.3.2", "ts-node": "^9.0.0", "typescript": "^4.0.5", - "webpack": "^5.2.0" + "webpack": "^5.2.0", + "yaml": "^1.10.0" }, "engines": { "node": ">=12.13.0" diff --git a/test/yaml-tests.json b/test/yaml-tests.json new file mode 100644 index 0000000..d4f97ab --- /dev/null +++ b/test/yaml-tests.json @@ -0,0 +1,861 @@ +[ + [ + { + "name": "Mark McGwire", + "hr": 65, + "avg": 0.278 + }, + { + "name": "Sammy Sosa", + "hr": 63, + "avg": 0.288 + } + ], + { + "top1": { + "key1": "scalar1" + }, + "top2": { + "key2": "scalar2" + }, + "top3": { + "scalar1": "scalar3" + }, + "top4": { + "scalar2": "scalar4" + }, + "top5": "scalar5", + "top6": { + "key6": "scalar6" + } + }, + "text", + ["a", "b", 42, "d"], + { + "a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~": "safe", + "?foo": "safe question mark", + ":foo": "safe colon", + "-foo": "safe dash", + "this is#not": "a comment" + }, + { + "": "a" + }, + "foo", + { + "key": "value", + "foo": "key" + }, + [1, -2, 33], + { + "plain": "a b\nc" + }, + [["s1_i1", "s1_i2"], "s2"], + { + "First occurrence": "Foo", + "Second occurrence": "Foo", + "Override anchor": "Bar", + "Reuse anchor": "Bar" + }, + "k:#foo &a !t s", + ["a"], + { + "escaped slash": "a/b" + }, + { + "plain": "This unquoted scalar spans many lines.", + "quoted": "So does this quoted scalar.\n" + }, + "here's to \"quotes\"", + { + "foo": "bar" + }, + "ab cd\nef\n\ngh\n", + ["detected\n", "\n\n# detected\n", " explicit\n", "detected\n"], + "foo: bar\": baz", + "plain\\value\\with\\backslashes", + { + "plain": "text lines", + "quoted": "text lines", + "block": "text\n \tlines\n" + }, + "a", + { + "foo": "you", + "bar": "far" + }, + { + "sequence": ["entry", ["nested"]], + "mapping": { + "foo": "bar" + } + }, + { + "literal": "some\ntext\n", + "folded": "some text\n" + }, + [ + { + "one": "two", + "three": "four" + }, + { + "five": "six", + "seven": "eight" + } + ], + { + "Folding": "Empty line\nas a line feed", + "Chomping": "Clipped empty lines\n" + }, + [ + ["one", "two"], + ["three", "four"] + ], + { + "foo": "bar" + }, + { + "key": "value" + }, + { + "explicit key": null, + "block key\n": ["one", "two"] + }, + ["foo"], + [ + { + "foo": "bar" + }, + ["baz", "baz"] + ], + "ab\n\n \n", + { + "foo: bar\\": "baz'" + }, + { + "Not indented": { + "By one space": "By four\n spaces\n", + "Flow style": ["By two", "Also by two", "Still by two"] + } + }, + "\\//||\\/||\n// || ||__\n", + { + "foo": [ + "a", + { + "key": "value" + } + ] + }, + { + "a": null, + "b": null + }, + "foo", + { + "a": "b", + "": "a" + }, + { + "foo\nbar:baz\tx \\$%^&*()x": 23, + "x\\ny:z\\tx $%^&*()x": 24 + }, + "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n", + " foo\nbar\nbaz ", + { "matches %": 20 }, + [ + "flow in block", + "Block scalar\n", + { + "foo": "bar" + } + ], + { + "a": "b", + "c": 42, + "e": "f", + "g": "h", + "23": false + }, + "ab", + " 1st non-empty\n2nd non-empty 3rd non-empty ", + { + "top1": { + "key1": "one" + }, + "top2": { + "key2": "two" + }, + "top3": { + "key3": "three" + }, + "top4": { + "key4": "four" + }, + "top5": { + "key5": "five" + }, + "top6": "six", + "top7": "seven" + }, + { + "hr": ["Mark McGwire", "Sammy Sosa"], + "rbi": ["Sammy Sosa", "Ken Griffey"] + }, + "\nfolded line\nnext line\n * bullet\n\n * list\n * lines\n\nlast line\n", + ["word1", "word2"], + { + "a": null, + "b": null, + "c": null + }, + { + "nested sequences": [[[[]]], [[{}]]], + "key1": [], + "key2": {} + }, + "---word1 word2", + { + "implicit block key": [ + { + "implicit flow key": "value" + } + ] + }, + { + "key ends with two colons::": "value" + }, + [ + { + "single line": null, + "a": "b" + }, + { + "multi line": null, + "a": "b" + } + ], + "a", + { + "key": ["item1", "item2"] + }, + [ + "double quoted", + "single quoted", + "plain text", + ["nested"], + { + "single": "pair" + } + ], + ["unicode anchor"], + [ + { + "key": "value", + "key2": "value2" + }, + { + "key3": "value3" + } + ], + "trimmed\n\n\nas space", + "Mark McGwire's year was crippled by a knee injury.\n", + [ + { + "single line": null, + "a": "b" + }, + { + "multi line": null, + "a": "b" + } + ], + { + "a": { + "b": { + "c": "d" + }, + "e": { + "f": "g" + } + }, + "h": "i" + }, + { + "foo": { + "bar": "baz" + } + }, + [ + { + "single line": "value" + }, + { + "multi line": "value" + } + ], + { + "single": "text", + "double": "text" + }, + " 1st non-empty\n2nd non-empty 3rd non-empty ", + [ + { + "item": "Super Hoop", + "quantity": 1 + }, + { + "item": "Basketball", + "quantity": 4 + }, + { + "item": "Big Shoes", + "quantity": 1 + } + ], + "a b c d\ne", + { + "a": ["b", ["c", "d"]] + }, + { + "strip": "text", + "clip": "text\n", + "keep": "text\n" + }, + { + "a": "b c", + "d": "e f" + }, + ["single multiline - sequence entry"], + { + "1": [2, 3], + "4": 5 + }, + [ + { + "bla\"keks": "foo" + }, + { + "bla]keks": "foo" + } + ], + "folded text\n", + "foo", + { + "key": { + "a": "b" + } + }, + { + "adjacent": "value", + "readable": "value", + "empty": null + }, + [ + { + "a": "b" + }, + { + "c": "d" + }, + { + "e": "f" + }, + { + "g": "h" + } + ], + { + "tab": "\tstring" + }, + [ + { + "foo bar": "baz" + } + ], + { + "anchored": "value", + "alias": "value" + }, + ["explicit indent and chomp", "chomp and explicit indent"], + { + "a": ["b", "c"] + }, + { + "foo": "bar" + }, + [ + "::vector", + ": - ()", + "Up, up, and away!", + -123, + "http://example.com/foo#bar", + [ + "::vector", + ": - ()", + "Up, up and away!", + -123, + "http://example.com/foo#bar" + ] + ], + { + "a": "b", + "seq": ["a"], + "c": "d" + }, + ["foo", "bar", 42], + "line1 # no comment line3\n", + "\n\nliteral\n \n\ntext\n", + { + "a": "b" + }, + { + "k": ["a", "b"] + }, + "a b c d\ne", + "---word1 word2", + ["a", 2, 4, "d"], + { + "a": [ + "b", + "c", + { + "d": ["e", "f"] + } + ] + }, + { + "a": " more indented\nregular\n", + "b": "\n\n more indented\nregular\n" + }, + { + "strip": "# text", + "clip": "# text\n", + "keep": "# text\n\n" + }, + { + "safe": "a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~ !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~", + "safe question mark": "?foo", + "safe colon": ":foo", + "safe dash": "-foo" + }, + "line1 line2 line3\n", + ["Mark McGwire", "Sammy Sosa", "Ken Griffey"], + ["a"], + ["a", ["b", "c"]], + { + "unicode": "Sosa did fine.☺", + "control": "\b1998\t1999\t2000\n", + "hex esc": "\r\n is \r\n", + "single": "\"Howdy!\" he cried.", + "quoted": " # Not a 'comment'.", + "tie-fighter": "|\\-*-/|" + }, + [["-", "-"]], + "folded text\n", + { + "a": 13, + "1.5": "d" + }, + { + "foo": 1, + "bar": 2, + "text": "a\n \nb\n\nc\n\nd\n" + }, + { + "wanted": "love ♥ and peace ☮" + }, + { + "name": "Mark McGwire", + "accomplishment": "Mark set a major league home run record in 1998.\n", + "stats": "65 Home Runs\n0.278 Batting Average\n" + }, + { + "foo": "bar", + "baz": "foo" + }, + "1st non-empty\n2nd non-empty 3rd non-empty", + { + "quoted": "Quoted \t", + "block": "void main() {\n\tprintf(\"Hello, world!\\n\");\n}\n" + }, + { + "foo": "blue", + "bar": "arrr", + "baz": "jazz" + }, + [ + { + "Mark McGwire": 65 + }, + { + "Sammy Sosa": 63 + }, + { + "Ken Griffy": 58 + } + ], + { + "1": 2, + "3": 4 + }, + { + "hr": ["Mark McGwire", "Sammy Sosa"], + "rbi": ["Sammy Sosa", "Ken Griffey"] + }, + "k:#foo &a !t s", + { + "block sequence": [ + "one", + { + "two": "three" + } + ] + }, + { + "First occurrence": "Value", + "Second occurrence": "Value" + }, + { + "a true": "null d", + "e 42": null + }, + { + "foo": "bar" + }, + ["foo", "bar", 42], + "trimmed\n\n\nas space", + "scalar", + { + "strip": "", + "clip": "", + "keep": "\n" + }, + { + "foo": { + "bar": 1 + }, + "baz": 2 + }, + { + "a": 47, + "c": "d" + }, + { + "implicit block key": [ + { + "implicit flow key": "value" + } + ] + }, + ["a", "b", "c", "c", ""], + [ + ["a", "b", "c"], + { + "a": "b", + "c": "d", + "e": "f" + }, + [] + ], + { + "implicit block key": [ + { + "implicit flow key": "value" + } + ] + }, + { + "a": "ab\n\ncd\nef\n" + }, + { + "literal": "value\n", + "folded": "value\n" + }, + { + "a": [ + "b", + "c", + { + "d": ["e", "f"] + } + ] + }, + "literal\n\ttext\n", + "foo \n\n\t bar\n\nbaz\n", + [ + { + "a": "b" + } + ], + "ab", + ["plain", "double quoted", "single quoted", "block\n", "plain again"], + { + "a": " ", + "b": " ", + "c": " ", + "d": " ", + "e": "\n", + "f": "\n", + "g": "\n\n", + "h": "\n\n" + }, + { + "key": "value with\ntabs" + }, + { + "": null + }, + [ + { + "single line": "value" + }, + { + "multi line": "value" + } + ], + "folded to a space,\nto a line feed, or \t \tnon-content", + ["literal\n", " folded\n", "keep\n\n", " strip"], + { + "key": "value" + }, + { + "american": ["Boston Red Sox", "Detroit Tigers", "New York Yankees"], + "national": ["New York Mets", "Chicago Cubs", "Atlanta Braves"] + }, + " 1st non-empty\n2nd non-empty 3rd non-empty ", + {}, + [ + ["a", "b"], + { + "a": "b" + }, + "a", + "b", + "c" + ], + "folded to a space,\nto a line feed, or \t \tnon-content", + [ + { + "foo": "bar" + } + ], + ["detected\n", "\n\n# detected\n", " explicit\n", "\t\ndetected\n"], + { + "top1": [ + "item1", + { + "key2": "value2" + }, + "item3" + ], + "top2": "value2" + }, + { + "foo": [42], + "bar": [44] + }, + { + "23": "d", + "a": 4.2 + }, + "Document", + { + "Date": "2001-11-23 15:03:17 -5", + "User": "ed", + "Fatal": "Unknown variable \"bar\"", + "Stack": [ + { + "file": "TopClass.py", + "line": 23, + "code": "x = MoreObject(\"345\\n\")\n" + }, + { "file": "MoreClass.py", "line": 58, "code": "foo = bar" } + ] + }, + { + "plain key": "in-line value", + "": null, + "quoted key": ["entry"] + }, + ["12", 12, "12"], + { + "aaa": "bbb" + }, + [":,"], + { + "sequence": ["one", "two"], + "mapping": { + "sky": "blue", + "sea": "green" + } + }, + { + "seq": ["a", "b"] + }, + "here's to \"quotes\"", + { + "hr": 65, + "avg": 0.278, + "rbi": 147 + }, + "\n\nliteral\n \n\ntext\n", + " 1st non-empty\n2nd non-empty 3rd non-empty ", + "literal\n\ttext\n", + { + "block mapping": { + "key": "value" + } + }, + " foo\nbar\nbaz ", + "ab cd\nef\n\ngh\n", + "foo", + { + "top1": { + "key1": "one" + }, + "top2": { + "key2": "two" + }, + "top3": { + "key3": "three" + }, + "top4": { + "key4": "four" + }, + "top5": { + "key5": "five" + }, + "top6": "six", + "top7": "seven" + }, + [ + { + "url": "http://example.org" + } + ], + { + "sequence": ["one", "two"], + "mapping": { + "sky": "blue", + "sea": "green" + } + }, + { + "invoice": 34843, + "date": "2001-01-23", + "bill-to": { + "given": "Chris", + "family": "Dumars", + "address": { + "lines": "458 Walkman Dr.\nSuite #292\n", + "city": "Royal Oak", + "state": "MI", + "postal": 48046 + } + }, + "ship-to": { + "given": "Chris", + "family": "Dumars", + "address": { + "lines": "458 Walkman Dr.\nSuite #292\n", + "city": "Royal Oak", + "state": "MI", + "postal": 48046 + } + }, + "product": [ + { + "sku": "BL394D", + "quantity": 4, + "description": "Basketball", + "price": 450 + }, + { + "sku": "BL4438H", + "quantity": 1, + "description": "Super Hoop", + "price": 2392 + } + ], + "tax": 251.42, + "total": 4443.52, + "comments": "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338." + }, + ["a", "b", "a", "b"], + [ + null, + "block node\n", + ["one", "two"], + { + "one": "two" + } + ], + { + "a": "scalar a", + "b": "scalar a" + }, + { + "foo": "", + "": "bar" + }, + { + "key": "value" + }, + "scalar %YAML 1.2", + { + "Folding": "Empty line\nas a line feed", + "Chomping": "Clipped empty lines\n" + }, + { + "key": "value" + }, + [ + ["name", "hr", "avg"], + ["Mark McGwire", 65, 0.278], + ["Sammy Sosa", 63, 0.288] + ], + { + "literal": "value\n", + "folded": "value\n" + }, + { + "Mark McGwire": { + "hr": 65, + "avg": 0.278 + }, + "Sammy Sosa": { + "hr": 63, + "avg": 0.288 + } + }, + { + "a": "b", + "c": "d" + }, + { + "key": [[["value"]]] + }, + { + "a": 1, + "b": null, + "c": 3 + }, + { + "event": "outgoing HTTP response", + "timestamp": "2021-01-21T18:44:57.116Z", + "remoteAddress": "127.0.0.1", + "deviceId": "202BC1-BM632w-000000", + "connection": "2021-01-21T18:44:57.105Z", + "statusCode": 200, + "headers": { + "content-length": 528, + "server": "GenieACS/1.2.3+20210121000910", + "soapserver": "GenieACS/1.2.3+20210121000910", + "content-type": "text/xml; charset=\"utf-8\"", + "set-cookie": "session=ea31d97ccf6832fb" + }, + "body": "\nwdsx50vq1" + } +] diff --git a/test/yaml.ts b/test/yaml.ts new file mode 100644 index 0000000..6983efe --- /dev/null +++ b/test/yaml.ts @@ -0,0 +1,14 @@ +import ava from "ava"; +import * as yaml from "yaml"; +import { stringify } from "../lib/common/yaml"; + +import testCases from "./yaml-tests.json"; + +ava("stringify", (t) => { + for (const testCase of testCases) { + let str = stringify(testCase); + if (str.startsWith(">2")) str = ">3" + str.slice(2); + if (str.startsWith("|2")) str = "|3" + str.slice(2); + t.deepEqual(yaml.parse(yaml.stringify(testCase)), yaml.parse(str)); + } +}); diff --git a/ui/components/device-faults.ts b/ui/components/device-faults.ts index b38abf2..6acb5e0 100644 --- a/ui/components/device-faults.ts +++ b/ui/components/device-faults.ts @@ -22,6 +22,7 @@ import { m } from "../components"; import * as store from "../store"; import * as notifications from "../notifications"; import { getIcon } from "../icons"; +import { stringify } from "../../lib/common/yaml"; const component: ClosureComponent = (): Component => { return { @@ -38,6 +39,7 @@ const component: ClosureComponent = (): Component => { "Channel", "Code", "Message", + "Detail", "Retries", "Timestamp", ].map((l) => m("th", l)); @@ -48,7 +50,9 @@ const component: ClosureComponent = (): Component => { rows.push([ m("td", f["channel"]), m("td", f["code"]), - m("td", f["message"]), + m("td", m("long-text", { text: f["message"] })), + m("td", m("long-text", { text: stringify(f["detail"]) })), + m("td", f["retries"]), m("td", new Date(f["timestamp"]).toLocaleString()), m( diff --git a/ui/faults-page.ts b/ui/faults-page.ts index 4d46d4b..c2bd803 100644 --- a/ui/faults-page.ts +++ b/ui/faults-page.ts @@ -27,7 +27,7 @@ import * as notifications from "./notifications"; import memoize from "../lib/common/memoize"; import * as smartQuery from "./smart-query"; import { map, parse, stringify } from "../lib/common/expression-parser"; -import { loadYaml, yaml } from "./dynamic-loader"; +import { stringify as yamlStringify } from "../lib/common/yaml"; const PAGE_SIZE = config.ui.pageSize || 10; @@ -76,13 +76,7 @@ export function init( const sort = args.hasOwnProperty("sort") ? "" + args["sort"] : ""; const filter = args.hasOwnProperty("filter") ? "" + args["filter"] : ""; - return new Promise((resolve, reject) => { - loadYaml() - .then(() => { - resolve({ filter, sort }); - }) - .catch(reject); - }); + return Promise.resolve({ filter, sort }); } export const component: ClosureComponent = (): Component => { @@ -147,8 +141,11 @@ export const component: ClosureComponent = (): Component => { return m("a", { href: deviceHref }, fault["device"]); } + if (attr.id === "message") + return m("long-text", { text: fault["message"] }); + if (attr.id === "detail") - return m("long-text", { text: yaml.stringify(fault["detail"]) }); + return m("long-text", { text: yamlStringify(fault["detail"]) }); if (attr.id === "timestamp") return new Date(fault["timestamp"]).toLocaleString();