From 64341026359244b961daea844018fe629cd4a1fa Mon Sep 17 00:00:00 2001 From: Danny Morabito Date: Thu, 3 Apr 2025 13:47:43 +0200 Subject: [PATCH] - Convert ANSI-formatted relay logs to HTML for cleaner visualization in initial setup screen - Add relay logs section to settings interface for improved accessibility --- src/components/InitialSetup.ts | 4 +- src/components/RelayLogs.ts | 39 ++++++++++++++++ src/routes/Settings.ts | 20 ++++++++ src/style.css | 4 ++ src/utils/ansiToLitHtml.ts | 84 ++++++++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/components/RelayLogs.ts create mode 100644 src/utils/ansiToLitHtml.ts diff --git a/src/components/InitialSetup.ts b/src/components/InitialSetup.ts index 76dfd1e..708e4e8 100644 --- a/src/components/InitialSetup.ts +++ b/src/components/InitialSetup.ts @@ -17,6 +17,7 @@ import '@components/General/Fieldset'; import '@components/General/Input'; import '@components/LoadingView'; import '@components/ProgressSteps'; +import '@components/RelayLogs'; @customElement('arx-initial-setup') export class InitialSetup extends LitElement { @@ -395,8 +396,7 @@ export class InitialSetup extends LitElement { Relay is running with PID: ${this.relayStatus.pid}

-
${this.relayStatus.logs.slice(-5).join('\n')}
- `, + `, )}

Having trouble? Our team is here to help if you encounter any diff --git a/src/components/RelayLogs.ts b/src/components/RelayLogs.ts new file mode 100644 index 0000000..d3939cc --- /dev/null +++ b/src/components/RelayLogs.ts @@ -0,0 +1,39 @@ +import { ansiToLitHtml } from '@/utils/ansiToLitHtml'; +import { LitElement, css, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { map } from 'lit/directives/map.js'; + +@customElement('arx-relay-logs') +export class RelayLogs extends LitElement { + static override styles = css` + :host { + display: block; + font-family: var(--font-family, "Inter", system-ui, sans-serif); + margin: 0 auto; + line-height: 1.6; + color: var(--color-base-content); + } + + .relay-logs { + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: break-word; + overflow-x: auto; + max-height: 200px; + padding: 1rem; + border: 2px solid var(--color-base-300); + border-radius: var(--radius-box); + border-radius: var(--radius-md); + background-color: var(--color-code-block); + color: var(--color-code-block-content); + } + `; + + @property({ type: Array }) logs: string[] = []; + + override render() { + return html` +

${map(this.logs, (log) => ansiToLitHtml(log))}
+ `; + } +} diff --git a/src/routes/Settings.ts b/src/routes/Settings.ts index 4886845..32edf75 100644 --- a/src/routes/Settings.ts +++ b/src/routes/Settings.ts @@ -12,6 +12,7 @@ import '@components/General/Button'; import '@components/General/Card'; import '@components/General/Fieldset'; import '@components/General/Input'; +import '@components/RelayLogs'; @customElement('arx-settings') export class EveSettings extends LitElement { @@ -61,11 +62,22 @@ export class EveSettings extends LitElement { @state() private profile: NDKUserProfile | undefined; @state() private error: string | undefined; @state() private darkMode = false; + @state() private relayStatus: { running: boolean; pid: number | null; logs: string[] } = { + running: false, + pid: null, + logs: [], + }; + + private async updateRelayStatus() { + this.relayStatus = await window.relay.getStatus(); + if (this.relayStatus.running) setTimeout(() => this.updateRelayStatus(), 2000); + } protected override async firstUpdated() { try { this.profile = await getUserProfile(); this.darkMode = localStorage.getItem('darkMode') === 'true'; + this.updateRelayStatus(); this.loading = false; } catch (err) { this.error = 'Failed to load profile'; @@ -176,6 +188,14 @@ export class EveSettings extends LitElement { > + + +

+ ${this.relayStatus.running ? `Relay is running. PID: ${this.relayStatus.pid}` : 'Relay is not running'} +

+ +
+
`; } } diff --git a/src/style.css b/src/style.css index bb02569..cee2999 100644 --- a/src/style.css +++ b/src/style.css @@ -34,6 +34,8 @@ --color-warning-content: oklch(41% 0.112 45.904); --color-error: oklch(70% 0.191 22.216); --color-error-content: oklch(39% 0.141 25.723); + --color-code-block: oklch(95% 0.038 75.164); + --color-code-block-content: oklch(40% 0.123 38.172); --radius-selector: 1rem; --radius-field: 0.5rem; @@ -68,6 +70,8 @@ body.dark { --color-warning-content: oklch(19.106% 0.026 112.757); --color-error: oklch(68.22% 0.206 24.43); --color-error-content: oklch(13.644% 0.041 24.43); + --color-code-block: oklch(20% 0.05 270); + --color-code-block-content: oklch(90% 0.076 70.697); --radius-selector: 1rem; --radius-field: 0.5rem; --radius-box: 1rem; diff --git a/src/utils/ansiToLitHtml.ts b/src/utils/ansiToLitHtml.ts new file mode 100644 index 0000000..e1d2aa9 --- /dev/null +++ b/src/utils/ansiToLitHtml.ts @@ -0,0 +1,84 @@ +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; + +export function ansiToLitHtml(text: string) { + const textColors: Record = { + '30': 'black', + '31': 'red', + '32': 'green', + '33': 'yellow', + '34': 'blue', + '35': 'magenta', + '36': 'cyan', + '37': 'white', + '90': 'gray', + '91': 'lightred', + '92': 'lightgreen', + '93': 'lightyellow', + '94': 'lightblue', + '95': 'lightmagenta', + '96': 'lightcyan', + '97': 'white', + }; + + const bgColors: Record = { + '40': 'black', + '41': 'red', + '42': 'green', + '43': 'yellow', + '44': 'blue', + '45': 'magenta', + '46': 'cyan', + '47': 'white', + '100': 'gray', + '101': 'lightred', + '102': 'lightgreen', + '103': 'lightyellow', + '104': 'lightblue', + '105': 'lightmagenta', + '106': 'lightcyan', + '107': 'white', + }; + + const styles: Record = { + '1': 'font-weight: bold;', + '3': 'font-style: italic;', + '4': 'text-decoration: underline;', + }; + + const ESC = '\u001b'; + const ansiPattern = new RegExp(`${ESC}\\[(\\d+(?:;\\d+)*)m`, 'g'); + const openTags: string[] = []; + + let result = text.replace(ansiPattern, (_, codes) => { + const codeArray = codes.split(';'); + + if (codeArray.includes('0')) { + const closeTags = openTags.length > 0 ? ''.repeat(openTags.length) : ''; + openTags.length = 0; + return closeTags; + } + + let style = ''; + + for (const code of codeArray) { + if (textColors[code]) { + style += `color: ${textColors[code]};`; + } else if (bgColors[code]) { + style += `background-color: ${bgColors[code]};`; + } else if (styles[code]) { + style += styles[code]; + } + } + + if (style) { + openTags.push('span'); + return ``; + } + + return ''; + }); + + if (openTags.length > 0) result += ''.repeat(openTags.length); + + return unsafeHTML(`${result}\n`); +}