Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions hassio/src/addon-view/hassio-addon-logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -15,10 +16,13 @@ class HassioAddonLogs extends PolymerElement {
}
pre {
overflow-x: auto;
white-space: pre-wrap;
overflow-wrap: break-word;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This enables wrapping in the logs. I personally find it easier to read but I can also remove it again

screen shot 2018-11-30 at 11 07 51

(before)

screen shot 2018-11-30 at 11 07 39

(after)

}
</style>
${ANSI_HTML_STYLE}
<paper-card heading="Log">
<div class="card-content"><pre>[[log]]</pre></div>
<div class="card-content" id="content"></div>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<div class="card-content" id="content"></div>
<div class="card-content">[[_computeLog(log)]]</div>

With:

  _computeLog(log) {
    return parseTextToColoredPre(log);
  }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I misunderstand how the html function works, I believe this way would be quite inefficient.

parseTextToColoredPre parses the log and returns a DOM element. With the method that is implemented here, that generated DOM element must then just be appended - done. So API response -> DOM element (parseTextToColoredPre) -> append

With _computeLog however, the generated DOM tree would need to be serialized to HTML after parseTextToColoredPre and then be de-serialized again when it is inserted into the <template>. So API response -> DOM element (parseTextToColoredPre) -> Serialize -> Insert into <template> -> De-serialize by browser. Also, _computeLog would need to look something like this:

_computeLog(log) {
  return parseTextToColoredPre(log).outerHTML;
}

In the application where this code is taken from, this really became a performance problem with longer logs (I used innerHTML, but the speed would be similar). Especially once the Hass.io logs are streamed using websockets re-computing the entire log every time a new line appears would be quite inefficient IMHO.

Copy link
Copy Markdown
Member

@bramkragten bramkragten Nov 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be right... let's use lit, that will fix it all :-P

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, Polymer databinding doesn't allow setting HTML content, just text.

<div class="card-actions">
<paper-button on-click="refresh">Refresh</paper-button>
</div>
Expand All @@ -33,7 +37,6 @@ class HassioAddonLogs extends PolymerElement {
type: String,
observer: "addonSlugChanged",
},
log: String,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep this

};
}

Expand All @@ -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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
while (this.$.content.lastChild) {
this.log = text;

this.$.content.removeChild(this.$.content.lastChild);
}
this.$.content.appendChild(parseTextToColoredPre(text));
});
}
}
Expand Down
203 changes: 203 additions & 0 deletions hassio/src/ansi-to-html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";

export const ANSI_HTML_STYLE = html`
<style>
.bold {
font-weight: bold;
}
.italic {
font-style: italic;
}
.underline {
text-decoration: underline;
}
.strikethrough {
text-decoration: line-through;
}
.underline.strikethrough {
text-decoration: underline line-through;
}
.fg-red {
color: rgb(222, 56, 43);
}
.fg-green {
color: rgb(57, 181, 74);
}
.fg-yellow {
color: rgb(255, 199, 6);
}
.fg-blue {
color: rgb(0, 111, 184);
}
.fg-magenta {
color: rgb(118, 38, 113);
}
.fg-cyan {
color: rgb(44, 181, 233);
}
.fg-white {
color: rgb(204, 204, 204);
}
.bg-black {
background-color: rgb(0, 0, 0);
}
.bg-red {
background-color: rgb(222, 56, 43);
}
.bg-green {
background-color: rgb(57, 181, 74);
}
.bg-yellow {
background-color: rgb(255, 199, 6);
}
.bg-blue {
background-color: rgb(0, 111, 184);
}
.bg-magenta {
background-color: rgb(118, 38, 113);
}
.bg-cyan {
background-color: rgb(44, 181, 233);
}
.bg-white {
background-color: rgb(204, 204, 204);
}
</style>
`;

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;
}
24 changes: 17 additions & 7 deletions hassio/src/system/hassio-supervisor-log.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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;
}
</style>
${ANSI_HTML_STYLE}
<paper-card>
<div class="card-content"><pre>[[log]]</pre></div>
<div class="card-content" id="content"></div>
<div class="card-actions">
<paper-button on-click="refreshTapped">Refresh</paper-button>
<paper-button on-click="refresh">Refresh</paper-button>
</div>
</paper-card>
`;
Expand All @@ -26,7 +33,6 @@ class HassioSupervisorLog extends PolymerElement {
static get properties() {
return {
hass: Object,
log: String,
};
}

Expand All @@ -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 =
'<span class="fg-red bold">Error fetching logs</span>';
}
);
}

refreshTapped() {
refresh() {
this.loadData();
}
}
Expand Down