diff --git a/main.js b/main.js index d4841cec2..c1831379f 100644 --- a/main.js +++ b/main.js @@ -1,17 +1,22 @@ /* eslint-disable no-undef */ /* eslint-disable @typescript-eslint/no-var-requires */ const { app, BrowserWindow, ipcMain } = require("electron"); -const url = require("url"); -const path = require("path"); +const electronStore = require("electron-store"); const fs = require("fs"); -const Store = require("electron-store"); +const got = require('got'); +const path = require("path"); +const url = require("url"); +const stream = require('stream'); +const {promisify} = require('util'); +const progress = require('progress-stream'); -const store = new Store(); const exec = require("child_process").exec; +const store = new electronStore(); + const args = process.argv.slice(1); -const dev = args.some((val) => val === "--serve"); const big = args.some((val) => val === "--big"); +const dev = args.some((val) => val === "--serve"); app.commandLine.appendSwitch("touch-events", "enabled"); app.allowRendererProcessReuse = true; @@ -68,10 +73,10 @@ function createWindow() { window.setFullScreen(true); } - // setTimeout(sendVersionInfo, 30 * 1000); activateAppInfoListener(); activateScreenSleepListener(); activateReloadListener(); + activateUpdateListener(); window.on("closed", () => { window = null; @@ -109,6 +114,12 @@ function activateAppInfoListener() { }); } +function activateUpdateListener() { + ipcMain.on("update", (_, updateInfo) => { + downloadUpdate(updateInfo); + }); +} + function sendCustomStyles() { fs.readFile(path.join(app.getPath("userData"), "custom-styles.css"), "utf-8", (err, data) => { if (err) { @@ -135,6 +146,92 @@ function sendVersionInfo() { }); } +function downloadUpdate(updateInfo) { + const downloadPath = "/tmp/octodash.deb"; + + exec("arch", (err, stdout, stderr) => { + if (err || stderr) { + window.webContents.send("updateError", { + error: err ? err : { message: stderr }, + }); + } + got(updateInfo.assetsURL) + .then((releaseFiles) => { + const reducer = (accumulator, currentValue) => accumulator + currentValue; + let averageETA = []; + let downloadURL; + let packageSize; + for (let package of JSON.parse(releaseFiles.body)) { + if (package.name.includes(stdout.trim())) { + downloadURL = package.browser_download_url; + packageSize = package.size; + } + } + if (downloadURL) { + const downloadPipeline = promisify(stream.pipeline); + let downloadProgress = progress({ + length: packageSize, + time: 300, + }); + + downloadProgress.on('progress', (progress) => { + averageETA.push(progress.eta); + if (averageETA.length > 4) averageETA.shift(); + window.webContents.send("updateDownloadProgress", { + percentage: progress.percentage, + transferred: (progress.transferred / 100000).toFixed(1), + total: (progress.length / 1000000).toFixed(1), + remaining: (progress.remaining / 100000).toFixed(1), + eta: new Date(averageETA.reduce(reducer) * 1000).toISOString().substr(14, 5), + runtime: new Date(progress.runtime * 1000).toISOString().substr(14, 5), + delta: (progress.delta / 100000).toFixed(1), + speed: (progress.speed / 1000000).toFixed(2), + }) + }) + + try { + if (fs.existsSync(downloadPath)) fs.unlinkSync(downloadPath) + } catch { + // no need to handle this properly + } + + downloadPipeline( + got.stream(downloadURL), + downloadProgress, + fs.createWriteStream(downloadPath) + ).catch((error) => { + window.webContents.send("updateError", { + error: { + message: `Can't download package! ${error.message}.` + } + }) + }).then(() => { + window.webContents.send("updateDownloadFinished"); + exec('sudo ~/scripts/update-octodash', (err, _, stderr) => { + if (err || stderr) { + window.webContents.send("updateError", { + error: err ? err : { message: stderr }, + }); + } else { + window.webContents.send("updateInstalled"); + } + }) + }) + } else { + window.webContents.send("updateError", { + error: { + message: `Can't find matching package for architecture ${stdout}.` + } + }) + } + }) + .catch((error) => { + error.message = `Can't load releases. ${error.message}`; + window.webContents.send("updateError", {error}); + }) + }); +} + app.on("ready", createWindow); app.on("window-all-closed", () => { diff --git a/package-lock.json b/package-lock.json index 2d60b70be..dd2468ae9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1953,6 +1953,68 @@ "sumchecker": "^3.0.1" }, "dependencies": { + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -1963,6 +2025,73 @@ "jsonfile": "^4.0.0", "universalify": "^0.1.0" } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } } } }, @@ -2226,18 +2355,16 @@ } }, "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.0.0.tgz", + "integrity": "sha512-kqA5I6Yun7PBHk8WN9BBP1c7FfN2SrD05GuVSEYPqDb4nerv7HqYfgBfMIKmT/EuejURkJKLZuLyGKGs6WEG9w==" }, "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", "requires": { - "defer-to-connect": "^1.0.1" + "defer-to-connect": "^2.0.0" } }, "@types/ajv": { @@ -2249,6 +2376,17 @@ "ajv": "*" } }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -2286,6 +2424,11 @@ "@types/node": "*" } }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + }, "@types/json-schema": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", @@ -2298,6 +2441,14 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "requires": { + "@types/node": "*" + } + }, "@types/lodash": { "version": "4.14.158", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.158.tgz", @@ -2311,10 +2462,9 @@ "dev": true }, "@types/node": { - "version": "14.0.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", - "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==", - "dev": true + "version": "14.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.14.tgz", + "integrity": "sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==" }, "@types/q": { "version": "1.5.4", @@ -2322,6 +2472,14 @@ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", "dev": true }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -4064,26 +4222,29 @@ "unset-value": "^1.0.0" } }, + "cacheable-lookup": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz", + "integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==" + }, "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", "requires": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", + "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^4.1.0", - "responselike": "^1.0.2" + "responselike": "^2.0.0" }, "dependencies": { "get-stream": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -4091,20 +4252,12 @@ "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "normalize-url": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", - "dev": true + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" } } }, @@ -4368,7 +4521,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, "requires": { "mimic-response": "^1.0.0" } @@ -4860,8 +5012,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "5.2.1", @@ -5317,12 +5468,18 @@ "dev": true }, "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "requires": { - "mimic-response": "^1.0.0" + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } } }, "deep-equal": { @@ -5379,10 +5536,9 @@ } }, "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", + "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==" }, "define-properties": { "version": "1.1.3", @@ -6217,7 +6373,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -7710,22 +7865,21 @@ } }, "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/got/-/got-11.5.0.tgz", + "integrity": "sha512-vOZEcEaK0b6x11uniY0HcblZObKPRO75Jvz53VKuqGSaKCM/zEt0sj2LGYVdqDYJzO3wYdG+FPQQ1hsgoXy7vQ==", + "requires": { + "@sindresorhus/is": "^3.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.4.8", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" } }, "graceful-fs": { @@ -8136,6 +8290,15 @@ "sshpk": "^1.7.0" } }, + "http2-wrapper": { + "version": "1.0.0-beta.5.2", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz", + "integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -8305,8 +8468,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -8774,8 +8936,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isbinaryfile": { "version": "4.0.6", @@ -8895,10 +9056,9 @@ "dev": true }, "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "json-parse-better-errors": { "version": "1.0.2", @@ -8986,12 +9146,11 @@ } }, "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.1.tgz", + "integrity": "sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw==", "requires": { - "json-buffer": "3.0.0" + "json-buffer": "3.0.1" } }, "killable": { @@ -9226,10 +9385,9 @@ } }, "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" }, "lru-cache": { "version": "5.1.1", @@ -9505,8 +9663,7 @@ "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, "mini-css-extract-plugin": { "version": "0.9.0", @@ -10333,7 +10490,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -10508,10 +10664,9 @@ } }, "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", + "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==" }, "p-finally": { "version": "1.0.0", @@ -10568,6 +10723,137 @@ "registry-auth-token": "^4.0.0", "registry-url": "^5.0.0", "semver": "^6.2.0" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + } } }, "pacote": { @@ -11633,8 +11919,7 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", @@ -11642,6 +11927,25 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "progress-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-2.0.0.tgz", + "integrity": "sha1-+sY6Cz0R3qy7CWmrzJOyFLzhntU=", + "requires": { + "speedometer": "~1.0.0", + "through2": "~2.0.3" + } + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "optional": true, + "requires": { + "asap": "~2.0.3" + } + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -11730,7 +12034,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -11813,6 +12116,11 @@ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -12021,7 +12329,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -12249,6 +12556,11 @@ "path-parse": "^1.0.6" } }, + "resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==" + }, "resolve-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", @@ -12343,12 +12655,11 @@ } }, "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", "requires": { - "lowercase-keys": "^1.0.0" + "lowercase-keys": "^2.0.0" } }, "restore-cursor": { @@ -12506,8 +12817,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -13313,6 +13623,11 @@ "chalk": "^2.0.1" } }, + "speedometer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.0.0.tgz", + "integrity": "sha1-zWccsGdSwivKM3Di8zREC+T8YuI=" + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -13506,7 +13821,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -13884,7 +14198,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" @@ -14491,8 +14804,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util-promisify": { "version": "2.1.0", @@ -15685,8 +15997,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", @@ -15727,8 +16038,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "4.0.0", diff --git a/package.json b/package.json index 9fb7bbf18..9e408d121 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "lint": "eslint ./src/ -c .eslintrc.js --ext .ts", "lint:fix": "eslint ./src/ -c .eslintrc.js --ext .ts --fix", "electron": "electron .", + "electron:dev": "electron . --serve --big", "electron:serve": "wait-on http-get://localhost:4200/ && electron . --serve", "electron:serve:big": "wait-on http-get://localhost:4200/ && electron . --serve --big", "pack": "npm run ng:build && electron-builder build -l", @@ -81,6 +82,7 @@ "electron-store": "^6.0.0", "lodash": "^4.17.19", "ngx-spinner": "^10.0.1", + "progress-stream": "^2.0.0", "rxjs": "~6.6.2", "tslib": "^2.0.0", "zone.js": "~0.10.3" diff --git a/scripts/install.sh b/scripts/install.sh index cd9277472..464abd05e 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -757,6 +757,25 @@ EOF echo "OctoDash will start automatically on next reboot. Please ensure that auto-login is enabled!" fi +list_input "Should I setup the update script? This will allow installing '~/tmp/octodash.deb' without sudo or root access. For more info visit the Update section of the wiki. " yes_no update +if [ $update == 'yes' ]; then + mkdir -p ~/scripts + echo "Setting up update script ..." + cat <<EOF > ~/scripts/update-octodash +#!/bin/bash + +dpkg -i /tmp/octodash.deb +rm /tmp/octodash.deb +EOF + + sudo chmod +x ~/scripts/update-octodash + + sudo bash -c 'cat >> /etc/sudoers.d/update-octodash' <<EOF +pi ALL=NOPASSWD: /home/pi/scripts/update-octodash +EOF +fi + + list_input "Shall I reboot your Pi now?" yes_no reboot echo "OctoDash has been successfully installed! :)" if [ $reboot == 'yes' ]; then diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ca944e8e6..cdefd7777 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -5,6 +5,15 @@ import _ from "lodash"; import { AppService } from "./app.service"; import { ConfigService } from "./config/config.service"; +declare global { + interface Window { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + require: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + process: any; + } +} + @Component({ selector: "app-root", templateUrl: "./app.component.html", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 25a333234..cd8c126bd 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -32,6 +32,7 @@ import { PrinterStatusComponent } from "./printer-status/printer-status.componen import { PrinterService } from "./printer.service"; import { SettingsComponent } from "./settings/settings.component"; import { StandbyComponent } from "./standby/standby.component"; +import { UpdateComponent } from "./update/update.component"; import { URLSafePipe } from "./url.pipe"; @NgModule({ @@ -54,6 +55,7 @@ import { URLSafePipe } from "./url.pipe"; SettingsComponent, URLSafePipe, StandbyComponent, + UpdateComponent, ], imports: [ BrowserModule, diff --git a/src/app/app.service.ts b/src/app/app.service.ts index 14a70c27f..3a622db51 100644 --- a/src/app/app.service.ts +++ b/src/app/app.service.ts @@ -12,28 +12,28 @@ export class AppService { private loadedFile = false; // eslint-disable-next-line @typescript-eslint/no-explicit-any private ipc: any; - private version: string; - private latestVersion: string; + public version: string; + public latestVersion: string; + private latestVersionAssetsURL: string; + public updateAvailable = false; public constructor( private configService: ConfigService, private notificationService: NotificationService, private http: HttpClient ) { - if (window.require) { - try { - this.ipc = window.require("electron").ipcRenderer; - this.enableVersionListener(); - this.enableCustomCSSListener(); - setTimeout(() => { - this.ipc.send("appInfo"); - }, 0); - } catch (e) { - this.notificationService.setError( - "Can't retrieve version information", - "Please open an issue for GitHub as this shouldn't happen." - ); - } + try { + this.ipc = window.require("electron").ipcRenderer; + this.enableVersionListener(); + this.enableCustomCSSListener(); + setTimeout(() => { + this.ipc.send("appInfo"); + }, 0); + } catch (e) { + this.notificationService.setError( + "Can't connect to backend", + "Please restart your system. If the issue persists open an issue on GitHub." + ); } this.updateError = []; } @@ -62,24 +62,23 @@ export class AppService { }); this.ipc.on("customStylesError", (_, customCSSError: string): void => { - this.notificationService.setError("Can't get custom styles!", customCSSError); + this.notificationService.setError("Can't load custom styles!", customCSSError); }); } private checkUpdate(): void { this.http.get("https://api.github.com/repos/UnchartedBull/OctoDash/releases/latest").subscribe( (data: GitHubReleaseInformation): void => { - if (this.version !== data.name.replace("v", "")) { - this.notificationService.setUpdate( - "It's time for an update", - `Version ${data.name} is available now, while you're on v${this.version}. Consider updating :)` - ); + //FIXME + if (this.version !== data.name.replace("va", "")) { + this.updateAvailable = true; } this.latestVersion = data.name.replace("v", ""); + this.latestVersionAssetsURL = data.assets_url; }, (): void => null ); - setTimeout(this.checkUpdate.bind(this), 21.6 * 1000000); + setTimeout(this.checkUpdate.bind(this), 3600000); } public getVersion(): string { @@ -114,6 +113,10 @@ export class AppService { return this.loadedFile; } + public getLatestVersionAssetsURL(): string { + return this.latestVersionAssetsURL; + } + public convertByteToMegabyte(byte: number): string { return (byte / 1000000).toFixed(1); } @@ -136,16 +139,8 @@ export class AppService { return roundedHours + ":" + ("0" + roundedMinutes).slice(-2); } - public convertFilamentLengthToAmount(filamentLength: number): number { - return ( - Math.round( - (Math.PI * - (this.configService.getFilamentThickness() / 2) * - filamentLength * - this.configService.getFilamentDensity()) / - 100 - ) / 10 - ); + public convertFilamentVolumeToWeight(filamentVolume: number): number { + return Math.round(filamentVolume * this.configService.getFilamentDensity() * 10) / 10; } } @@ -155,5 +150,7 @@ interface VersionInformation { interface GitHubReleaseInformation { name: string; + // eslint-disable-next-line camelcase + assets_url: string; [key: string]: string; } diff --git a/src/app/config/config.service.ts b/src/app/config/config.service.ts index 2a4d8ce0f..a43153e70 100644 --- a/src/app/config/config.service.ts +++ b/src/app/config/config.service.ts @@ -1,18 +1,9 @@ -import { HttpClient, HttpHeaders } from "@angular/common/http"; +import { HttpHeaders } from "@angular/common/http"; import { Injectable } from "@angular/core"; import Ajv from "ajv"; import _ from "lodash"; -import { environment } from "../../environments/environment"; - -declare global { - interface Window { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - require: any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - process: any; - } -} +import { NotificationService } from "../notification/notification.service"; @Injectable({ providedIn: "root", @@ -29,20 +20,18 @@ export class ConfigService { private httpHeaders: HttpHeader; - public constructor(private http: HttpClient) { + public constructor(private notificationService: NotificationService) { const ajv = new Ajv({ allErrors: true }); this.validator = ajv.compile(schema); - if (window && window.process && window.process.type) { + try { const Store = window.require("electron-store"); this.store = new Store(); this.initialize(this.store.get("config")); - } else { - console.warn( - "Detected non-electron environment. Fallback to assets/config.json. Any changes are non-persistent!" + } catch (e) { + this.notificationService.setError( + "Can't read config file!", + "Please restart your system. If the issue persists open an issue on GitHub." ); - this.http.get(environment.config).subscribe((config: Config): void => { - this.initialize(config); - }); } } diff --git a/src/app/files.service.ts b/src/app/files.service.ts index ad9c1bb35..a032dcade 100644 --- a/src/app/files.service.ts +++ b/src/app/files.service.ts @@ -67,7 +67,7 @@ export class FilesService { let filamentLength = 0; if (fileOrFolder.gcodeAnalysis) { _.forEach(fileOrFolder.gcodeAnalysis.filament, (tool): void => { - filamentLength += tool.length; + filamentLength += tool.volume; }); } @@ -88,7 +88,7 @@ export class FilesService { ...(fileOrFolder.gcodeAnalysis ? { printTime: this.service.convertSecondsToHours(fileOrFolder.gcodeAnalysis.estimatedPrintTime), - filamentWeight: this.service.convertFilamentLengthToAmount(filamentLength), + filamentWeight: this.service.convertFilamentVolumeToWeight(filamentLength), } : {}), } as unknown) as File); @@ -148,7 +148,7 @@ export class FilesService { let filamentLength = 0; if (data.gcodeAnalysis) { _.forEach(data.gcodeAnalysis.filament, (tool): void => { - filamentLength += tool.length; + filamentLength += tool.volume; }); } const file = ({ @@ -160,7 +160,7 @@ export class FilesService { ? { date: this.service.convertDateToString(new Date(data.date * 1000)), printTime: this.service.convertSecondsToHours(data.gcodeAnalysis.estimatedPrintTime), - filamentWeight: this.service.convertFilamentLengthToAmount(filamentLength), + filamentWeight: this.service.convertFilamentVolumeToWeight(filamentLength), } : {}), thumbnail: data.thumbnail diff --git a/src/app/job.service.ts b/src/app/job.service.ts index b083bca5a..50cea574f 100644 --- a/src/app/job.service.ts +++ b/src/app/job.service.ts @@ -53,7 +53,7 @@ export class JobService { progress: Math.round((data.progress.filepos / data.job.file.size) * 100), ...(data.job.filament !== null ? { - filamentAmount: this.service.convertFilamentLengthToAmount( + filamentAmount: this.service.convertFilamentVolumeToWeight( this.getTotalAmountOfFilament(data.job.filament) ), } @@ -102,10 +102,10 @@ export class JobService { let filamentLength = 0; for (const property in filamentAmount) { if ( - Object.prototype.hasOwnProperty.call(filamentAmount, "property") && - Object.prototype.hasOwnProperty.call(filamentAmount[property], "length") + Object.prototype.hasOwnProperty.call(filamentAmount, property) && + Object.prototype.hasOwnProperty.call(filamentAmount[property], "volume") ) { - filamentLength += filamentAmount[property].length; + filamentLength += filamentAmount[property].volume; } } return filamentLength; diff --git a/src/app/main-menu/main-menu.component.html b/src/app/main-menu/main-menu.component.html index b232f5dae..6482e15b9 100644 --- a/src/app/main-menu/main-menu.component.html +++ b/src/app/main-menu/main-menu.component.html @@ -3,6 +3,7 @@ >OctoDash<span class="main-menu__heading" style="color: #1ec02c; display: inline;">.</span></span > <img src="assets/settings.svg" class="main-menu__settings-icon" (click)="showSettings()" /> + <div class="main-menu__update-notifier" *ngIf="service.updateAvailable"></div> <table class="main-menu__options"> <tr> <td routerLink="/files" matRipple [matRippleUnbounded]="false" [matRippleCentered]="true"> @@ -20,4 +21,4 @@ </tr> </table> </div> -<app-settings *ngIf="settings" (closeFunction)="hideSettings($event)"></app-settings> +<app-settings *ngIf="settings" (closeFunction)="hideSettings()"></app-settings> diff --git a/src/app/main-menu/main-menu.component.scss b/src/app/main-menu/main-menu.component.scss index d2e79c192..cf8658a0e 100644 --- a/src/app/main-menu/main-menu.component.scss +++ b/src/app/main-menu/main-menu.component.scss @@ -51,4 +51,14 @@ margin-bottom: 1.7vw; } } + + &__update-notifier { + width: 2.2vw; + height: 2.2vw; + border-radius: 4vw; + background-color: #e84118; + position: absolute; + right: 1.9vw; + top: 13vh; + } } diff --git a/src/app/main-menu/main-menu.component.ts b/src/app/main-menu/main-menu.component.ts index 546c54f10..bb3e87465 100644 --- a/src/app/main-menu/main-menu.component.ts +++ b/src/app/main-menu/main-menu.component.ts @@ -1,11 +1,15 @@ import { Component } from "@angular/core"; +import { AppService } from "../app.service"; + @Component({ selector: "app-main-menu", templateUrl: "./main-menu.component.html", styleUrls: ["./main-menu.component.scss"], }) export class MainMenuComponent { + public constructor(public service: AppService) {} + public settings = false; public showSettings(): void { diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index e506c93c9..7d3b277f2 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -5,17 +5,23 @@ <div class="settings__content" #settingsMain> <span class="settings__heading">settings</span> <ul class="settings__list"> - <li (click)="changePage(1, 0, 'forward')">General <fa-icon [icon]="['fas', 'chevron-right']"></fa-icon></li> - <li (click)="changePage(2, 0, 'forward')">OctoDash <fa-icon [icon]="['fas', 'chevron-right']"></fa-icon></li> - <li (click)="changePage(3, 0, 'forward')">Plugins <fa-icon [icon]="['fas', 'chevron-right']"></fa-icon></li> - <li (click)="changePage(4, 0, 'forward')">Credits <fa-icon [icon]="['fas', 'chevron-right']"></fa-icon></li> + <li (click)="changePage(1, 0, 'forward')">general <fa-icon [icon]="['fas', 'chevron-right']"></fa-icon></li> + <li (click)="changePage(2, 0, 'forward')">octodash <fa-icon [icon]="['fas', 'chevron-right']"></fa-icon></li> + <li (click)="changePage(3, 0, 'forward')">plugins <fa-icon [icon]="['fas', 'chevron-right']"></fa-icon></li> + <li (click)="changePage(4, 0, 'forward')">about <fa-icon [icon]="['fas', 'chevron-right']"></fa-icon></li> </ul> + <span + class="settings__update-available" + (click)="changePage(4, 0, 'forward')" + [ngStyle]="{ opacity: service.updateAvailable ? '0.7' : '0' }" + >update available!</span + > <span class="settings__save" (click)="updateConfig()">save</span> <span class="settings__made" - >Made with <fa-icon [icon]="['fas', 'heart']" class="settings__made-heart"> </fa-icon> by + >made with <fa-icon [icon]="['fas', 'heart']" class="settings__made-heart"> </fa-icon> by /u/UnchartedBull</span > - <span class="settings__version">OctoDash v{{ version }}</span> + <!-- <span class="settings__version">OctoDash v{{ version }}</span> --> </div> <div class="settings__content settings__content-inactive" #settingsGeneral> <span class="settings__heading" (click)="changePage(0, 1, 'backward')"> @@ -537,9 +543,22 @@ </div> <div class="settings__content settings__content-inactive" #settingsCredits> <span class="settings__heading" (click)="changePage(0, 4, 'backward')"> - <fa-icon [icon]="['fas', 'chevron-left']" class="settings__heading-back"> </fa-icon>credits + <fa-icon [icon]="['fas', 'chevron-left']" class="settings__heading-back"> </fa-icon>about </span> <div class="settings__scroll"> + <img src="assets/icon/icon-main-title.svg" class="settings__about-icon" /> + <span class="settings__about-version">v{{ service.version }}</span> + <button class="settings__about-update settings__about-update-not-available" *ngIf="!service.updateAvailable"> + no update available + </button> + <button + class="settings__about-update settings__about-update-available" + *ngIf="service.updateAvailable" + (click)="showUpdate()" + > + install v{{ service.latestVersion }} + </button> + <span class="settings__about-copyright">© 2019-2020 UnchartedBull</span> <span class="settings__heading-2">License</span> <p class="settings__text"> Licensed under the Apache 2.0 License. <br /> @@ -587,3 +606,4 @@ </div> </div> </div> +<app-update *ngIf="update" (closeFunction)="hideUpdate()"></app-update> diff --git a/src/app/settings/settings.component.scss b/src/app/settings/settings.component.scss index c4582d02a..f35c85338 100644 --- a/src/app/settings/settings.component.scss +++ b/src/app/settings/settings.component.scss @@ -18,7 +18,7 @@ position: absolute; top: 10vh; left: 20vw; - height: 80vh; + height: 81vh; width: 60vw; background-color: #353b48; border-radius: 2vw; @@ -271,6 +271,56 @@ padding-left: 4vw; } } + + &__update-available { + font-size: 0.4rem; + display: block; + margin-top: -1vh; + margin-bottom: -2.5vh; + } + + &__about { + &-icon { + height: 35vh; + margin: auto; + display: block; + } + + &-version { + display: block; + text-align: center; + font-size: 0.45em; + margin-top: 1.5vh; + opacity: 0.7; + } + + &-update { + display: block; + padding: 1.4vh 2vw; + border-radius: 1vw; + box-shadow: 0 10px 19px -8px rgba(0, 0, 0, 0.75); + font-size: 0.7rem; + outline: 0; + border: 0; + margin: 3vh auto; + + &-not-available { + background-color: #718093; + opacity: 0.8; + } + + &-available { + background-color: #44bd32; + } + } + + &-copyright { + display: block; + text-align: center; + font-size: 0.4rem; + margin-top: 5vh; + } + } } .filament-feed-speed-info { diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index b2a361879..01e4f07d6 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -10,7 +10,7 @@ import { NotificationService } from "../notification/notification.service"; styleUrls: ["./settings.component.scss"], }) export class SettingsComponent implements OnInit { - @Output() private closeFunction = new EventEmitter<string>(); + @Output() closeFunction = new EventEmitter<void>(); @ViewChild("settingsMain") private settingsMain: ElementRef; @ViewChild("settingsGeneral") private settingsGeneral: ElementRef; @ViewChild("settingsOctoDash") private settingsOctoDash: ElementRef; @@ -27,36 +27,26 @@ export class SettingsComponent implements OnInit { "Bottom Left", "Bottom Right", ]; - public version: string; private overwriteNoSave = false; private pages = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any private ipc: any; + public update = false; public constructor( private configService: ConfigService, private notificationService: NotificationService, - private service: AppService + public service: AppService ) { this.config = this.configService.getCurrentConfig(); this.config = this.configService.revertConfigForInput(this.config); - this.getVersion(); - if (window.require) { - try { - this.ipc = window.require("electron").ipcRenderer; - } catch (e) { - this.notificationService.setError( - "Can't connect to backend", - "Please open an issue for GitHub as this shouldn't happen." - ); - } - } - } - - private getVersion(): void { - this.version = this.service.getVersion(); - if (this.version === undefined) { - setTimeout(this.getVersion.bind(this), 3500); + try { + this.ipc = window.require("electron").ipcRenderer; + } catch (e) { + this.notificationService.setError( + "Can't connect to backend", + "Please restart your system. If the issue persists open an issue on GitHub." + ); } } @@ -114,4 +104,12 @@ export class SettingsComponent implements OnInit { this.configService.updateConfig(); this.ipc.send("reload", ""); } + + public showUpdate(): void { + this.update = true; + } + + public hideUpdate(): void { + this.update = false; + } } diff --git a/src/app/update/update.component.html b/src/app/update/update.component.html new file mode 100644 index 000000000..290c9087d --- /dev/null +++ b/src/app/update/update.component.html @@ -0,0 +1,31 @@ +<div class="update-container"> + <div *ngIf="page === 1"> + <span class="update-heading">downloading update ...</span> + <div class="update-progress-bar__wrapper"> + <div + class="update-progress-bar" + [ngStyle]="{ width: updateProgress.percentage / 2 + 'vw' }" + id="updateDownloadProgress" + > + {{ updateProgress.speed }} MB/s + </div> + </div> + <span class="update-size__total">{{ updateProgress.total }} MB</span> + <span class="update-time__remaining">{{ updateProgress.eta }} minutes left</span> + </div> + <div *ngIf="page === 2"> + <span class="update-heading">installing update ...</span> + <div class="update-progress-bar__wrapper"> + <div class="update-progress-bar update-progress-bar-no-percentage" id="installUpdateProgress"></div> + </div> + <span class="update-notice">this might take a while</span> + </div> + <div *ngIf="page === 3"> + <span class="update-heading">v{{ service.version }} installed</span> + <span class="update-restart">would you like to reboot now to activate the latest version?</span> + <div class="update-restart-button__wrapper"> + <button class="update-restart-button update-restart-button__no" (click)="closeUpdateWindow()">no</button> + <button class="update-restart-button update-restart-button__yes" (click)="reboot()">yes</button> + </div> + </div> +</div> diff --git a/src/app/update/update.component.scss b/src/app/update/update.component.scss new file mode 100644 index 000000000..bc4a39535 --- /dev/null +++ b/src/app/update/update.component.scss @@ -0,0 +1,101 @@ +.update { + &-container { + position: absolute; + z-index: 20; + top: 10vh; + left: 20vw; + height: 81vh; + width: 60vw; + background-color: #353b48; + border-radius: 2vw; + } + + &-progress-bar { + height: 5.5vh; + border-radius: 2vh; + background-color: #44bd32; + width: 25vw; + transition: width 0.7s ease-in-out; + font-size: 0.5rem; + text-align: right; + padding-top: 1.5vh; + overflow: visible; + white-space: nowrap; + + &-no-percentage { + width: 10vw; + transition: margin-left 2s ease-in-out; + margin-left: 0; + } + + &__wrapper { + width: 50vw; + margin: 14vh auto 3vh; + display: block; + height: 7vh; + background-color: transparent; + border: 3px solid #f5f6fa; + border-radius: 3vh; + } + } + + &-heading { + display: block; + text-align: center; + margin-top: 12vh; + } + + &-size__total { + display: block; + text-align: right; + font-size: 0.6rem; + margin-right: 5vw; + margin-top: -2vh; + opacity: 0.6; + } + + &-time__remaining { + display: block; + text-align: center; + margin-top: 10vh; + } + + &-notice { + display: block; + text-align: center; + font-size: 0.5rem; + margin-top: 20vh; + } + + &-restart { + display: block; + text-align: center; + font-size: 0.65rem; + margin-top: 10vh; + margin-bottom: 8vw; + + &-button { + padding: 1.4vh 2vw; + border-radius: 1vw; + box-shadow: 0 10px 19px -8px rgba(0, 0, 0, 0.75); + font-size: 0.7rem; + outline: 0; + border: 0; + margin: 7vh 2vw; + + &__no { + background-color: #718093; + opacity: 0.8; + } + + &__yes { + background-color: #44bd32; + } + + &__wrapper { + display: block; + text-align: center; + } + } + } +} diff --git a/src/app/update/update.component.ts b/src/app/update/update.component.ts new file mode 100644 index 000000000..50bf316f1 --- /dev/null +++ b/src/app/update/update.component.ts @@ -0,0 +1,121 @@ +import { ChangeDetectorRef, Component, EventEmitter, OnInit, Output } from "@angular/core"; + +import { AppService } from "../app.service"; +import { NotificationService } from "../notification/notification.service"; +import { OctoprintService } from "../octoprint.service"; + +@Component({ + selector: "app-update", + templateUrl: "./update.component.html", + styleUrls: ["./update.component.scss"], +}) +export class UpdateComponent implements OnInit { + @Output() closeFunction = new EventEmitter<void>(true); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private ipc: any; + private installationAnimationInterval: number; + public updateProgress: UpdateDownloadProgress = { + percentage: 0, + transferred: 0, + total: "--.-", + remaining: 0, + eta: "--:--", + runtime: "--:--", + delta: 0, + speed: "--.-", + }; + public page = 1; + + constructor( + public service: AppService, + private notificationService: NotificationService, + private octoprintService: OctoprintService, + private changeDetector: ChangeDetectorRef + ) { + try { + this.ipc = window.require("electron").ipcRenderer; + } catch (e) { + this.notificationService.setError( + "Can't connect to backend", + "Please restart your system. If the issue persists open an issue on GitHub." + ); + } + } + + ngOnInit(): void { + if (!this.service.latestVersion || !this.service.getLatestVersionAssetsURL()) { + this.notificationService.setWarning( + "Can't initiate update!", + "Some information is missing, please try again in an hour or update manually." + ); + this.closeUpdateWindow(); + } else { + this.setupListeners(); + this.update(this.service.getLatestVersionAssetsURL()); + } + } + + private setupListeners(): void { + this.ipc.on("updateError", (_, updateError: UpdateError): void => { + this.notificationService.setError("Can't install update!", updateError.error.message); + this.closeUpdateWindow(); + }); + + this.ipc.on("updateDownloadProgress", (_, updateDownloadProgress: UpdateDownloadProgress): void => { + this.updateProgress = updateDownloadProgress; + this.changeDetector.detectChanges(); + }); + + this.ipc.on("updateDownloadFinished", (): void => { + this.page = 2; + this.changeDetector.detectChanges(); + setTimeout(() => { + const updateProgressBar = document.getElementById("installUpdateProgress"); + updateProgressBar.style.marginLeft = "40vw"; + this.installationAnimationInterval = setInterval(() => { + updateProgressBar.style.marginLeft = updateProgressBar.style.marginLeft === "0vw" ? "40vw" : "0vw"; + }, 2050); + }, 250); + }); + + this.ipc.on("updateInstalled", (): void => { + this.page = 3; + this.changeDetector.detectChanges(); + }); + } + + private closeUpdateWindow(): void { + this.page = 1; + clearInterval(this.installationAnimationInterval); + this.closeFunction.emit(); + } + + private update(assetsURL: string): void { + this.ipc.send("update", { + assetsURL: assetsURL, + }); + } + + public reboot(): void { + this.octoprintService.sendSystemCommand("reboot"); + } +} + +interface UpdateError { + error: { + message: string; + stack?: string; + }; +} + +interface UpdateDownloadProgress { + percentage: number; + transferred: number; + total: number | string; + remaining: number; + eta: string; + runtime: string; + delta: number; + speed: number | string; +} diff --git a/src/assets/made-in-berlin.png b/src/assets/made-in-berlin.png index e3dd57868..d901c07db 100644 Binary files a/src/assets/made-in-berlin.png and b/src/assets/made-in-berlin.png differ