From f7e3f4a8285fc8f2f8505cd21babf641e316bdd5 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 2 Dec 2018 10:26:32 +0100 Subject: [PATCH] Hass.io: Show ANSI color codes in logs (#2155) * Hass.io: Show ANSI color codes in logs * Use innerHTML and color classes * Refactor ANSI function * Readability * Make green text black in supervisor logs * Use assigning while loop --- hassio/src/addon-view/hassio-addon-logs.js | 14 +- hassio/src/ansi-to-html.js | 203 +++++++++++++++++++++ hassio/src/system/hassio-supervisor-log.js | 24 ++- 3 files changed, 230 insertions(+), 11 deletions(-) create mode 100644 hassio/src/ansi-to-html.js diff --git a/hassio/src/addon-view/hassio-addon-logs.js b/hassio/src/addon-view/hassio-addon-logs.js index 8784689e05..d93be1750e 100644 --- a/hassio/src/addon-view/hassio-addon-logs.js +++ b/hassio/src/addon-view/hassio-addon-logs.js @@ -2,6 +2,7 @@ import "@polymer/paper-button/paper-button"; import "@polymer/paper-card/paper-card"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html"; import "../../../src/resources/ha-style"; @@ -15,10 +16,13 @@ class HassioAddonLogs extends PolymerElement { } pre { overflow-x: auto; + white-space: pre-wrap; + overflow-wrap: break-word; } + ${ANSI_HTML_STYLE} -
[[log]]
+
Refresh
@@ -33,7 +37,6 @@ class HassioAddonLogs extends PolymerElement { type: String, observer: "addonSlugChanged", }, - log: String, }; } @@ -51,8 +54,11 @@ class HassioAddonLogs extends PolymerElement { refresh() { this.hass .callApi("get", `hassio/addons/${this.addonSlug}/logs`) - .then((info) => { - this.log = info; + .then((text) => { + while (this.$.content.lastChild) { + this.$.content.removeChild(this.$.content.lastChild); + } + this.$.content.appendChild(parseTextToColoredPre(text)); }); } } diff --git a/hassio/src/ansi-to-html.js b/hassio/src/ansi-to-html.js new file mode 100644 index 0000000000..d9ab04a690 --- /dev/null +++ b/hassio/src/ansi-to-html.js @@ -0,0 +1,203 @@ +import { html } from "@polymer/polymer/lib/utils/html-tag"; + +export const ANSI_HTML_STYLE = html` + +`; + +export function parseTextToColoredPre(text) { + const pre = document.createElement("pre"); + const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g; + let i = 0; + + const state = { + bold: false, + italic: false, + underline: false, + strikethrough: false, + foregroundColor: null, + backgroundColor: null, + }; + + const addSpan = (content) => { + const span = document.createElement("span"); + if (state.bold) span.classList.add("bold"); + if (state.italic) span.classList.add("italic"); + if (state.underline) span.classList.add("underline"); + if (state.strikethrough) span.classList.add("strikethrough"); + if (state.foregroundColor !== null) + span.classList.add(`fg-${state.foregroundColor}`); + if (state.backgroundColor !== null) + span.classList.add(`bg-${state.backgroundColor}`); + span.appendChild(document.createTextNode(content)); + pre.appendChild(span); + }; + + /* eslint-disable no-cond-assign */ + let match; + while ((match = re.exec(text)) !== null) { + const j = match.index; + addSpan(text.substring(i, j)); + i = j + match[0].length; + + if (match[1] === undefined) continue; + + for (const colorCode of match[1].split(";")) { + switch (parseInt(colorCode)) { + case 0: + // reset + state.bold = false; + state.italic = false; + state.underline = false; + state.strikethrough = false; + state.foregroundColor = null; + state.backgroundColor = null; + break; + case 1: + state.bold = true; + break; + case 3: + state.italic = true; + break; + case 4: + state.underline = true; + break; + case 9: + state.strikethrough = true; + break; + case 22: + state.bold = false; + break; + case 23: + state.italic = false; + break; + case 24: + state.underline = false; + break; + case 29: + state.strikethrough = false; + break; + case 30: + // foreground black + state.foregroundColor = null; + break; + case 31: + state.foregroundColor = "red"; + break; + case 32: + state.foregroundColor = "green"; + break; + case 33: + state.foregroundColor = "yellow"; + break; + case 34: + state.foregroundColor = "blue"; + break; + case 35: + state.foregroundColor = "magenta"; + break; + case 36: + state.foregroundColor = "cyan"; + break; + case 37: + state.foregroundColor = "white"; + break; + case 39: + // foreground reset + state.foregroundColor = null; + break; + case 40: + state.backgroundColor = "black"; + break; + case 41: + state.backgroundColor = "red"; + break; + case 42: + state.backgroundColor = "green"; + break; + case 43: + state.backgroundColor = "yellow"; + break; + case 44: + state.backgroundColor = "blue"; + break; + case 45: + state.backgroundColor = "magenta"; + break; + case 46: + state.backgroundColor = "cyan"; + break; + case 47: + state.backgroundColor = "white"; + break; + case 49: + // background reset + state.backgroundColor = null; + break; + } + } + } + addSpan(text.substring(i)); + + return pre; +} diff --git a/hassio/src/system/hassio-supervisor-log.js b/hassio/src/system/hassio-supervisor-log.js index 26a3a6a078..985cc2372c 100644 --- a/hassio/src/system/hassio-supervisor-log.js +++ b/hassio/src/system/hassio-supervisor-log.js @@ -2,6 +2,7 @@ import "@polymer/paper-button/paper-button"; import "@polymer/paper-card/paper-card"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html"; class HassioSupervisorLog extends PolymerElement { static get template() { @@ -12,12 +13,18 @@ class HassioSupervisorLog extends PolymerElement { } pre { overflow-x: auto; + white-space: pre-wrap; + overflow-wrap: break-word; + } + .fg-green { + color: var(--primary-text-color) !important; } + ${ANSI_HTML_STYLE} -
[[log]]
+
- Refresh + Refresh
`; @@ -26,7 +33,6 @@ class HassioSupervisorLog extends PolymerElement { static get properties() { return { hass: Object, - log: String, }; } @@ -37,16 +43,20 @@ class HassioSupervisorLog extends PolymerElement { loadData() { this.hass.callApi("get", "hassio/supervisor/logs").then( - (info) => { - this.log = info; + (text) => { + while (this.$.content.lastChild) { + this.$.content.removeChild(this.$.content.lastChild); + } + this.$.content.appendChild(parseTextToColoredPre(text)); }, () => { - this.log = "Error fetching logs"; + this.$.content.innerHTML = + 'Error fetching logs'; } ); } - refreshTapped() { + refresh() { this.loadData(); } }