Sfoglia il codice sorgente

Add log viewer tool

Lennart Grahl 6 anni fa
parent
commit
64bc598f7e
3 ha cambiato i file con 395 aggiunte e 2 eliminazioni
  1. 2 2
      dist/package.sh
  2. 196 0
      troubleshoot/log.html
  3. 197 0
      troubleshoot/log.js

+ 2 - 2
dist/package.sh

@@ -100,8 +100,8 @@ for target in "${targets[@]}"; do
 done
 
 echo "+ Update version number..."
-sed -i.bak -e "s/\[\[VERSION\]\]/${VERSION}/g" $DIR/index.html $DIR/troubleshoot/index.html $DIR/*.bundle.js $DIR/manifest.webmanifest $DIR/browserconfig.xml $DIR/version.txt
-rm $DIR/*.bak $DIR/troubleshoot/index.html.bak
+sed -i.bak -e "s/\[\[VERSION\]\]/${VERSION}/g" $DIR/index.html $DIR/troubleshoot/*.html $DIR/*.bundle.js $DIR/manifest.webmanifest $DIR/browserconfig.xml $DIR/version.txt
+rm $DIR/*.bak $DIR/troubleshoot/*.html.bak
 
 echo "+ Update permissions..."
 find $DIR/ -type f -exec chmod 644 {} \;

+ 196 - 0
troubleshoot/log.html

@@ -0,0 +1,196 @@
+<!DOCTYPE html>
+<!--
+
+    Copyright © 2017-2019 Threema GmbH (https://threema.ch/).
+
+    This file is part of Threema Web.
+
+    Threema Web is free software: you can redistribute it and/or modify it
+    under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or (at
+    your option) any later version.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
+    General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
+
+-->
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="referrer" content="no-referrer">
+    <meta name="robots" content="noindex">
+
+    <title>Threema Web Log Viewer</title>
+
+    <!-- Favicon -->
+    <link rel="icon" href="../img/favicon/favicon.ico?v=[[VERSION]]" type="image/x-icon">
+    <link rel="shortcut icon" href="../img/favicon/favicon.ico?v=[[VERSION]]" type="image/x-icon">
+
+    <!-- Fonts -->
+    <link rel="stylesheet" href="../fonts/roboto.css?v=[[VERSION]]" type="text/css">
+    <link rel="stylesheet" href="../fonts/material.css?v=[[VERSION]]" type="text/css">
+
+    <!-- Styling -->
+    <style>
+        body {
+            padding: 16px;
+            font-family: 'Roboto';
+            background: url('../img/bg.jpg?v=[[VERSION]]') no-repeat fixed center;
+            background-size: cover;
+        }
+
+        #wrapper {
+            background-color: white;
+            margin: 0 auto;
+            padding: 16px 32px 32px;
+            text-align: center;
+        }
+
+        .drag-over {
+            background-color: #a5d6a7 !important;
+        }
+
+        #logo {
+            width: 300px;
+            color: white;
+            margin: 0 auto 16px;
+        }
+
+        h1 {
+            margin-top: 0;
+            font-size: 30px;
+            font-weight: 500;
+        }
+        h2 {
+            font-weight: 300;
+            font-size: 22px;
+        }
+        p {
+            font-weight: 300;
+        }
+        summary h1, summary h2 {
+            display: inline-block;
+        }
+
+        #config {
+            background-color: #fafafa;
+            border: 1px solid #e0e0e0;
+            padding: 8px;
+            text-align: left;
+            overflow: auto;
+        }
+
+        #log {
+            width: 100%;
+            border: 1px solid #e0e0e0;
+            padding: 8px;
+            text-align: left;
+        }
+
+        #log {
+            border-collapse: collapse;
+            font-family: monospace;
+        }
+
+        #log td {
+            border-bottom: 1px solid #e0e0e0;
+            padding: 2px 6px;
+        }
+
+        .record .message > details {
+            display: inline-block;
+        }
+        .record ol, .record ul {
+            margin: 0 0 0 3px;
+            padding: 0 10px 0;
+            border-left: 1px solid #1565c0;
+            list-style-type: none;
+        }
+
+        .record.debug, .record.trace {
+            background-color: #fafafa;
+        }
+        .record.info {
+            background-color: #e3f2fd;
+        }
+        .record.warn {
+            background-color: #fff59d;
+        }
+        .record.error {
+            background-color: #ef9a9a;
+        }
+
+        .record .date {
+            color: #757575;
+        }
+        .record .tag {
+            text-align: right;
+        }
+
+        .record .null {
+            color: #616161;
+        }
+        .record .boolean {
+            color: #ab47bc;
+        }
+        .record .number {
+            color: #388e3c;
+        }
+        .record .type {
+            color: #1565c0;
+        }
+        .record .converted {
+            color: #00796b;
+        }
+        .record .error {
+            color: #c62828;
+        }
+
+        footer {
+            color: white;
+            font-weight: 300;
+            text-align: center;
+            padding-top: 16px;
+        }
+    </style>
+</head>
+<body>
+
+<header>
+    <div id="title">
+        <div id="logo">
+            <img src="../img/logo.svg?v=[[VERSION]]" alt="Logo">
+        </div>
+    </div>
+</header>
+
+<div id="wrapper">
+    <h1>Log Viewer</h1>
+
+    <p id="prompt">Paste or drag the log to be displayed here.</p>
+
+    <div id="container">
+        <h2>Browser: <span id="browser"></span></h2>
+
+        <details>
+            <summary><h2>Config</h2></summary>
+            <pre id="config" class="log-data"></pre>
+        </details>
+
+        <h2>Log</h2>
+        <table id="log"></table>
+    </div>
+</div>
+
+<footer>&copy; 2017&ndash;2019 Threema GmbH</footer>
+
+<!-- JS -->
+<script src="log.js?v=[[VERSION]]"></script>
+</body>
+</html>

+ 197 - 0
troubleshoot/log.js

@@ -0,0 +1,197 @@
+// Constants
+const regex = {
+    error: new RegExp('^[a-zA-Z]*Error:'),
+};
+
+// DOM elements
+const elements = {
+    wrapper: document.querySelector('#wrapper'),
+    prompt: document.querySelector('#prompt'),
+    container: document.querySelector('#container'),
+    browser: document.querySelector('#browser'),
+    config: document.querySelector('#config'),
+    log: document.querySelector('#log'),
+};
+
+// Show prompt and hide log container
+elements.prompt.hidden = false;
+elements.container.hidden = true;
+
+/**
+ * Escape HTML.
+ */
+function escapeHTML(text) {
+    const template = document.createElement('span');
+    template.innerText = text;
+    return template.innerHTML;
+}
+
+/**
+ * Create an element from HTML.
+ */
+function createElementFromHTML(html) {
+    const template = document.createElement('template');
+    template.innerHTML = html.trim();
+    return template.content.firstChild;
+}
+
+/**
+ * Format a record (message) value.
+ */
+function formatRecordValue(value) {
+    // Handle null
+    if (value === null) {
+        return `<span class="null">${escapeHTML(`${value}`)}</span>`;
+    }
+
+    // Handle boolean
+    if (value.constructor === Boolean) {
+        return `<span class="boolean">${escapeHTML(value)}</span>`;
+    }
+
+    // Handle number
+    if (value.constructor === Number) {
+        return `<span class="number">${escapeHTML(value)}</span>`;
+    }
+
+    // Handle string, converted types (e.g. ArrayBuffer, Blob, ...)
+    // and errors (exceptions).
+    if (value.constructor === String) {
+        if (value.startsWith('[') && value.endsWith(']')) {
+            return `<span class="converted">${escapeHTML(value)}</span>`;
+        }
+        if (regex.error.test(value)) {
+            return `<span class="error">${escapeHTML(value)}</span>`;
+        }
+        return `<span class="string">${escapeHTML(value)}</span>`;
+    }
+
+    // Handle object
+    if (value.constructor === Object) {
+        const entries = Object.entries(value);
+        return `
+            <details>
+                <summary class="type">Object(${entries.length})</summary>
+                <ul>
+                    ${entries.map(([key, value]) => {
+                        return `<li><span class="type">${escapeHTML(key)}:</span> ${formatRecordValue(value)}</li>`;
+                    }).join('\n')}
+                </ul>
+            </details>`;
+    }
+
+    // Handle array
+    if (value instanceof Array) {
+        return `
+            <details>
+                <summary class="type">Array(${value.length})</summary>
+                <ol>
+                    ${value.map((item, index) => {
+                        return `<li><span class="type">${index}:</span> ${formatRecordValue(item)}</li>`;
+                    }).join('\n')}
+                </ol>
+            </details>`;
+    }
+
+    // Unknown
+    return `[${value.constructor}]`;
+}
+
+/**
+ * Show the log in the UI.
+ * @param data A log report in JSON notation.
+ */
+function showLog(data) {
+    // Decode as JSON
+    let container;
+    try {
+        container = JSON.parse(data);
+    } catch (error) {
+        return console.error('Could not parse pasted text to object:', error);
+    }
+
+    // Required keys to be available
+    if (!(container.config instanceof Object) ||
+        container.browser.constructor !== String ||
+        !(container.log instanceof Array)) {
+        return console.error('Not a valid container object');
+    }
+
+    // Hide prompt and show log container
+    elements.prompt.hidden = true;
+    elements.container.hidden = false;
+
+    // Display meta data
+    elements.browser.textContent = container.browser;
+    elements.config.textContent = JSON.stringify(container.config, null, 2);
+
+    // Display log records
+    elements.log.innerHTML = '';
+    let startTimestampMs;
+    for (let [timestampMs, type, tag, ...values] of container.log) {
+        // Determine start timestamp so we can display the offset in seconds
+        if (startTimestampMs === undefined) {
+            startTimestampMs = timestampMs;
+        }
+
+        // Get CSS style from tag (if any)
+        if (tag.startsWith('%c')) {
+            const style = escapeHTML(values.shift());
+            tag = `<span style="${style}">${escapeHTML(tag.substring(2))}</span>`;
+        } else {
+            tag = escapeHTML(tag);
+        }
+
+        // Add element to log container
+        elements.log.appendChild(createElementFromHTML(`
+            <tbody>
+                <tr class="record ${escapeHTML(type)}">
+                    <td class="date">${((timestampMs - startTimestampMs) / 1000).toFixed(3)}</td>
+                    <td class="tag">${tag}</td>
+                    <td class="message">${values.map((value) => formatRecordValue(value)).join('\n')}</td>  
+                </tr>
+            </tbody>`));
+    }
+}
+
+/**
+ * Listen for *paste* events.
+ */
+document.addEventListener('paste', (event) => {
+    // If no clipboard data is available, do nothing.
+    let text;
+    try {
+        text = event.clipboardData.getData('text');
+    } catch (error) {
+        return console.error('Could not retrieve pasted data as text:', error);
+    }
+
+    // Show log
+    showLog(text);
+});
+
+/**
+ * Listen for *drag* events.
+ */
+document.addEventListener('dragover', (event) => {
+    event.preventDefault();
+});
+document.addEventListener('dragenter', () => {
+    elements.wrapper.className = 'drag-over';
+});
+document.addEventListener('drop', (event) => {
+    event.preventDefault();
+    elements.wrapper.className = '';
+
+    // Read first file (if any)
+    const files = event.dataTransfer.files;
+    if (files.length === 0) {
+        console.error('No files in drop event');
+        return;
+    }
+    const reader = new FileReader();
+    reader.addEventListener('load', (event) => {
+        showLog(event.target.result);
+    });
+    reader.readAsText(files[0]);
+});