mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
removed updater, auto start server
This commit is contained in:
parent
100c871cd9
commit
84504f734f
5 changed files with 34 additions and 345 deletions
|
@ -1,7 +1,6 @@
|
||||||
import { app, BrowserWindow, shell, ipcMain } from "electron";
|
import { app, BrowserWindow, shell, ipcMain } from "electron";
|
||||||
import { release, platform } from "node:os";
|
import { release, platform } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { autoUpdater } from "electron-updater";
|
|
||||||
import { existsSync } from "fs";
|
import { existsSync } from "fs";
|
||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ if (!app.requestSingleInstanceLock()) {
|
||||||
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
||||||
|
|
||||||
// set daemon process var
|
// set daemon process var
|
||||||
var daemonProcess = null;
|
var serverProcess = null;
|
||||||
let win: BrowserWindow | null = null;
|
let win: BrowserWindow | null = null;
|
||||||
// Here, you can also use other preload
|
// Here, you can also use other preload
|
||||||
const preload = join(__dirname, "../preload/index.js");
|
const preload = join(__dirname, "../preload/index.js");
|
||||||
|
@ -87,12 +86,7 @@ async function createWindow() {
|
||||||
// win.webContents.on('will-navigate', (event, url) => { }) #344
|
// win.webContents.on('will-navigate', (event, url) => { }) #344
|
||||||
|
|
||||||
win.once("ready-to-show", () => {
|
win.once("ready-to-show", () => {
|
||||||
//autoUpdater.logger = log.scope("updater");
|
//
|
||||||
//autoUpdater.channel = config.update_channel;
|
|
||||||
autoUpdater.autoInstallOnAppQuit = false;
|
|
||||||
autoUpdater.autoDownload = true;
|
|
||||||
autoUpdater.checkForUpdatesAndNotify();
|
|
||||||
//autoUpdater.quitAndInstall();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,18 +96,16 @@ app.whenReady().then(() => {
|
||||||
|
|
||||||
console.log(platform());
|
console.log(platform());
|
||||||
//Generate daemon binary path
|
//Generate daemon binary path
|
||||||
var daemonPath = "";
|
var serverPath = "";
|
||||||
switch (platform().toLowerCase()) {
|
switch (platform().toLowerCase()) {
|
||||||
case "darwin":
|
//case "darwin":
|
||||||
daemonPath = join(process.resourcesPath, "modem", "freedata-server");
|
// serverPath = join(process.resourcesPath, "modem", "freedata-server");
|
||||||
case "linux":
|
//case "linux":
|
||||||
daemonPath = join(process.resourcesPath, "modem", "freedata-server");
|
// serverPath = join(process.resourcesPath, "modem", "freedata-server");
|
||||||
break;
|
// break;
|
||||||
case "win32":
|
case "win32":
|
||||||
daemonPath = join(process.resourcesPath, "modem", "freedata-server.exe");
|
|
||||||
break;
|
|
||||||
case "win64":
|
case "win64":
|
||||||
daemonPath = join(process.resourcesPath, "modem", "freedata-server.exe");
|
serverPath = join(process.env.LOCALAPPDATA, "FreeDATA", "freedata-server", "freedata-server.exe");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log("Unhandled OS Platform: ", platform());
|
console.log("Unhandled OS Platform: ", platform());
|
||||||
|
@ -121,45 +113,34 @@ app.whenReady().then(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Start daemon binary if it exists
|
//Start daemon binary if it exists
|
||||||
if (existsSync(daemonPath)) {
|
if (existsSync(serverPath)) {
|
||||||
console.log("Starting freedata-server binary");
|
console.log("Starting freedata-server binary");
|
||||||
console.log("daemonPath:", daemonPath);
|
console.log("serverPath:", serverPath);
|
||||||
console.log("CWD:", join(daemonPath, ".."));
|
console.log("CWD:", join(serverPath, ".."));
|
||||||
/*
|
|
||||||
var daemonProcess = spawn("freedata-server", [], {
|
serverProcess = spawn(serverPath, [], { shell: true });
|
||||||
cwd: join(process.env.DIST, "modem"),
|
|
||||||
shell: true
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
daemonProcess = spawn(daemonPath, [], {
|
|
||||||
shell: true
|
|
||||||
});
|
|
||||||
console.log(daemonProcess)
|
|
||||||
*/
|
|
||||||
daemonProcess = spawn(daemonPath, [], {});
|
|
||||||
|
|
||||||
// return process messages
|
// return process messages
|
||||||
daemonProcess.on("error", (err) => {
|
serverProcess.on("error", (err) => {
|
||||||
//daemonProcessLog.error(`error when starting daemon: ${err}`);
|
//serverProcessLog.error(`error when starting daemon: ${err}`);
|
||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
daemonProcess.on("message", () => {
|
serverProcess.on("message", () => {
|
||||||
// daemonProcessLog.info(`${data}`);
|
// serverProcessLog.info(`${data}`);
|
||||||
});
|
});
|
||||||
daemonProcess.stdout.on("data", () => {
|
serverProcess.stdout.on("data", () => {
|
||||||
// daemonProcessLog.info(`${data}`);
|
// serverProcessLog.info(`${data}`);
|
||||||
});
|
});
|
||||||
daemonProcess.stderr.on("data", (data) => {
|
serverProcess.stderr.on("data", (data) => {
|
||||||
// daemonProcessLog.info(`${data}`);
|
// serverProcessLog.info(`${data}`);
|
||||||
console.log(data);
|
console.log(data);
|
||||||
});
|
});
|
||||||
daemonProcess.on("close", (code) => {
|
serverProcess.on("close", (code) => {
|
||||||
// daemonProcessLog.warn(`daemonProcess exited with code ${code}`);
|
// serverProcessLog.warn(`serverProcess exited with code ${code}`);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
daemonProcess = null;
|
serverProcess = null;
|
||||||
daemonPath = null;
|
serverPath = null;
|
||||||
console.log("Daemon binary doesn't exist--normal for dev environments.");
|
console.log("Daemon binary doesn't exist--normal for dev environments.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,101 +186,31 @@ ipcMain.handle("open-win", (_, arg) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//restart and install udpate
|
|
||||||
ipcMain.on("request-restart-and-install-update", (event, data) => {
|
|
||||||
close_sub_processes();
|
|
||||||
autoUpdater.quitAndInstall();
|
|
||||||
});
|
|
||||||
|
|
||||||
// LISTENER FOR UPDATER EVENTS
|
|
||||||
autoUpdater.on("update-available", (info) => {
|
|
||||||
process.env.FDUpdateAvail = "1";
|
|
||||||
console.log("update available");
|
|
||||||
|
|
||||||
let arg = {
|
|
||||||
status: "update-available",
|
|
||||||
info: info,
|
|
||||||
};
|
|
||||||
win.webContents.send("action-updater", arg);
|
|
||||||
});
|
|
||||||
|
|
||||||
autoUpdater.on("update-not-available", (info) => {
|
|
||||||
console.log("update not available");
|
|
||||||
let arg = {
|
|
||||||
status: "update-not-available",
|
|
||||||
info: info,
|
|
||||||
};
|
|
||||||
win.webContents.send("action-updater", arg);
|
|
||||||
});
|
|
||||||
|
|
||||||
autoUpdater.on("update-downloaded", (info) => {
|
|
||||||
process.env.FDUpdateAvail = "1";
|
|
||||||
console.log("update downloaded");
|
|
||||||
let arg = {
|
|
||||||
status: "update-downloaded",
|
|
||||||
info: info,
|
|
||||||
};
|
|
||||||
win.webContents.send("action-updater", arg);
|
|
||||||
// we need to call this at this point.
|
|
||||||
// if an update is available and we are force closing the app
|
|
||||||
// the entire screen crashes...
|
|
||||||
//console.log('quit application and install update');
|
|
||||||
//autoUpdater.quitAndInstall();
|
|
||||||
});
|
|
||||||
|
|
||||||
autoUpdater.on("checking-for-update", () => {
|
|
||||||
console.log("checking for update");
|
|
||||||
let arg = {
|
|
||||||
status: "checking-for-update",
|
|
||||||
version: app.getVersion(),
|
|
||||||
};
|
|
||||||
win.webContents.send("action-updater", arg);
|
|
||||||
});
|
|
||||||
|
|
||||||
autoUpdater.on("download-progress", (progress) => {
|
|
||||||
let arg = {
|
|
||||||
status: "download-progress",
|
|
||||||
progress: progress,
|
|
||||||
};
|
|
||||||
win.webContents.send("action-updater", arg);
|
|
||||||
});
|
|
||||||
|
|
||||||
autoUpdater.on("error", (error) => {
|
|
||||||
console.log("update error");
|
|
||||||
let arg = {
|
|
||||||
status: "error",
|
|
||||||
progress: error,
|
|
||||||
};
|
|
||||||
win.webContents.send("action-updater", arg);
|
|
||||||
console.log("AUTO UPDATER : " + error);
|
|
||||||
});
|
|
||||||
|
|
||||||
function close_sub_processes() {
|
function close_sub_processes() {
|
||||||
console.log("closing sub processes");
|
console.log("closing sub processes");
|
||||||
|
|
||||||
// closing the modem binary if not closed when closing application and also our daemon which has been started by the gui
|
// closing the modem binary if not closed when closing application and also our daemon which has been started by the gui
|
||||||
try {
|
try {
|
||||||
if (daemonProcess != null) {
|
if (serverProcess != null) {
|
||||||
daemonProcess.kill();
|
serverProcess.kill();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("closing modem and daemon");
|
console.log("closing freedata server process");
|
||||||
try {
|
try {
|
||||||
if (platform() == "win32") {
|
if (platform() == "win32") {
|
||||||
spawn("Taskkill", ["/IM", "freedata-modem.exe", "/F"]);
|
|
||||||
spawn("Taskkill", ["/IM", "freedata-server.exe", "/F"]);
|
spawn("Taskkill", ["/IM", "freedata-server.exe", "/F"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform() == "linux") {
|
if (platform() == "linux") {
|
||||||
spawn("pkill", ["-9", "freedata-modem"]);
|
|
||||||
spawn("pkill", ["-9", "freedata-server"]);
|
spawn("pkill", ["-9", "freedata-server"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform() == "darwin") {
|
if (platform() == "darwin") {
|
||||||
spawn("pkill", ["-9", "freedata-modem"]);
|
|
||||||
spawn("pkill", ["-9", "freedata-server"]);
|
spawn("pkill", ["-9", "freedata-server"]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -111,83 +111,4 @@ window.onmessage = (ev) => {
|
||||||
ev.data.payload === "removeLoading" && removeLoading();
|
ev.data.payload === "removeLoading" && removeLoading();
|
||||||
};
|
};
|
||||||
|
|
||||||
setTimeout(removeLoading, 4999);
|
setTimeout(removeLoading, 3999);
|
||||||
|
|
||||||
// IPC ACTION FOR AUTO UPDATER
|
|
||||||
ipcRenderer.on("action-updater", (event, arg) => {
|
|
||||||
if (arg.status == "download-progress") {
|
|
||||||
var progressinfo =
|
|
||||||
"(" +
|
|
||||||
Math.round(arg.progress.transferred / 1024) +
|
|
||||||
"kB /" +
|
|
||||||
Math.round(arg.progress.total / 1024) +
|
|
||||||
"kB)" +
|
|
||||||
" @ " +
|
|
||||||
Math.round(arg.progress.bytesPerSecond / 1024) +
|
|
||||||
"kByte/s";
|
|
||||||
document.getElementById("UpdateProgressInfo").innerHTML = progressinfo;
|
|
||||||
|
|
||||||
document
|
|
||||||
.getElementById("UpdateProgressBar")
|
|
||||||
.setAttribute("aria-valuenow", arg.progress.percent);
|
|
||||||
document
|
|
||||||
.getElementById("UpdateProgressBar")
|
|
||||||
.setAttribute("style", "width:" + arg.progress.percent + "%;");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg.status == "checking-for-update") {
|
|
||||||
//document.title = document.title + ' - v' + arg.version;
|
|
||||||
//updateTitle(
|
|
||||||
// config.myCall,
|
|
||||||
// config.tnc_host,
|
|
||||||
// config.tnc_port,
|
|
||||||
// " -v " + arg.version,
|
|
||||||
//);
|
|
||||||
document.getElementById("updater_status").innerHTML =
|
|
||||||
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>';
|
|
||||||
|
|
||||||
document.getElementById("updater_status").className =
|
|
||||||
"btn btn-secondary btn-sm";
|
|
||||||
document.getElementById("update_and_install").style.display = "none";
|
|
||||||
}
|
|
||||||
if (arg.status == "update-downloaded") {
|
|
||||||
document.getElementById("update_and_install").removeAttribute("style");
|
|
||||||
document.getElementById("updater_status").innerHTML =
|
|
||||||
'<i class="bi bi-cloud-download ms-1 me-1" style="color: white;"></i>';
|
|
||||||
document.getElementById("updater_status").className =
|
|
||||||
"btn btn-success btn-sm";
|
|
||||||
|
|
||||||
// HERE WE NEED TO RUN THIS SOMEHOW...
|
|
||||||
//mainLog.info('quit application and install update');
|
|
||||||
//autoUpdater.quitAndInstall();
|
|
||||||
}
|
|
||||||
if (arg.status == "update-not-available") {
|
|
||||||
document.getElementById("updater_last_version").innerHTML =
|
|
||||||
arg.info.releaseName;
|
|
||||||
document.getElementById("updater_last_update").innerHTML =
|
|
||||||
arg.info.releaseDate;
|
|
||||||
document.getElementById("updater_release_notes").innerHTML =
|
|
||||||
arg.info.releaseNotes;
|
|
||||||
|
|
||||||
document.getElementById("updater_status").innerHTML =
|
|
||||||
'<i class="bi bi-check2-square ms-1 me-1" style="color: white;"></i>';
|
|
||||||
document.getElementById("updater_status").className =
|
|
||||||
"btn btn-success btn-sm";
|
|
||||||
document.getElementById("update_and_install").style.display = "none";
|
|
||||||
}
|
|
||||||
if (arg.status == "update-available") {
|
|
||||||
document.getElementById("updater_status").innerHTML =
|
|
||||||
'<i class="bi bi-hourglass-split ms-1 me-1" style="color: white;"></i>';
|
|
||||||
document.getElementById("updater_status").className =
|
|
||||||
"btn btn-warning btn-sm";
|
|
||||||
document.getElementById("update_and_install").style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg.status == "error") {
|
|
||||||
document.getElementById("updater_status").innerHTML =
|
|
||||||
'<i class="bi bi-exclamation-square ms-1 me-1" style="color: white;"></i>';
|
|
||||||
document.getElementById("updater_status").className =
|
|
||||||
"btn btn-danger btn-sm";
|
|
||||||
document.getElementById("update_and_install").style.display = "none";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import settings_updater from "./settings_updater.vue";
|
|
||||||
import settings_station from "./settings_station.vue";
|
import settings_station from "./settings_station.vue";
|
||||||
import settings_gui from "./settings_gui.vue";
|
import settings_gui from "./settings_gui.vue";
|
||||||
import settings_chat from "./settings_chat.vue";
|
import settings_chat from "./settings_chat.vue";
|
||||||
|
@ -20,23 +19,10 @@ import settings_exp from "./settings_exp.vue";
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<!-- SETTINGS Nav tabs -->
|
<!-- SETTINGS Nav tabs -->
|
||||||
<ul class="nav nav-tabs card-header-tabs" id="myTab" role="tablist">
|
<ul class="nav nav-tabs card-header-tabs" id="myTab" role="tablist">
|
||||||
|
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button
|
<button
|
||||||
class="nav-link active"
|
class="nav-link active"
|
||||||
id="updater-tab"
|
|
||||||
data-bs-toggle="tab"
|
|
||||||
data-bs-target="#updater"
|
|
||||||
type="button"
|
|
||||||
role="tab"
|
|
||||||
aria-controls="home"
|
|
||||||
aria-selected="true"
|
|
||||||
>
|
|
||||||
Updater
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button
|
|
||||||
class="nav-link"
|
|
||||||
id="station-tab"
|
id="station-tab"
|
||||||
data-bs-toggle="tab"
|
data-bs-toggle="tab"
|
||||||
data-bs-target="#station"
|
data-bs-target="#station"
|
||||||
|
@ -141,23 +127,12 @@ import settings_exp from "./settings_exp.vue";
|
||||||
>
|
>
|
||||||
<!-- SETTINGS Nav Tab panes -->
|
<!-- SETTINGS Nav Tab panes -->
|
||||||
|
|
||||||
<!-- Updater tab contents-->
|
|
||||||
<div class="tab-content">
|
|
||||||
<div
|
|
||||||
class="tab-pane active"
|
|
||||||
id="updater"
|
|
||||||
role="tabpanel"
|
|
||||||
aria-labelledby="updater-tab"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<settings_updater />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Station tab contents-->
|
<!-- Station tab contents-->
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div
|
<div
|
||||||
class="tab-pane"
|
class="tab-pane active"
|
||||||
id="station"
|
id="station"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
aria-labelledby="station-tab"
|
aria-labelledby="station-tab"
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import settings_updater_core from "./settings_updater_core.vue";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
The updater might not working, yet! Please update manually if you are running into problems!
|
|
||||||
</div>
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
The updater doesnt contain the server related parts, yet! We are discussing this topic actually, feel free contributing with your opinion on Discord!
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<settings_updater_core />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -1,102 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { setActivePinia } from "pinia";
|
|
||||||
import pinia from "../store/index";
|
|
||||||
setActivePinia(pinia);
|
|
||||||
|
|
||||||
import { useStateStore } from "../store/stateStore.js";
|
|
||||||
import { settingsStore } from "../store/settingsStore";
|
|
||||||
import { onMounted } from "vue";
|
|
||||||
import { ipcRenderer } from "electron";
|
|
||||||
const state = useStateStore(pinia);
|
|
||||||
onMounted(() => {
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
|
||||||
// we are using this area for implementing the electron runUpdater
|
|
||||||
// we need access to DOM for displaying updater results in GUI
|
|
||||||
// close app, update and restart
|
|
||||||
document
|
|
||||||
.getElementById("update_and_install")
|
|
||||||
.addEventListener("click", () => {
|
|
||||||
ipcRenderer.send("request-restart-and-install-update");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="card m-2">
|
|
||||||
<div class="card-header p-1 d-flex">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-1">
|
|
||||||
<i class="bi bi-cloud-download" style="font-size: 1.2rem"></i>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<strong class="fs-5">Updater</strong>
|
|
||||||
</div>
|
|
||||||
<div class="col-7">
|
|
||||||
<div class="progress w-100 ms-1 m-1">
|
|
||||||
<div
|
|
||||||
class="progress-bar"
|
|
||||||
style="width: 0%"
|
|
||||||
role="progressbar"
|
|
||||||
id="UpdateProgressBar"
|
|
||||||
aria-valuenow="0"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuemax="100"
|
|
||||||
>
|
|
||||||
<span id="UpdateProgressInfo"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-1 text-end">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
id="openHelpModalUpdater"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#updaterHelpModal"
|
|
||||||
class="btn m-0 p-0 border-0"
|
|
||||||
>
|
|
||||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-2 mb-1">
|
|
||||||
<button
|
|
||||||
class="btn btn-secondary btn-sm ms-1 me-1"
|
|
||||||
id="updater_channel"
|
|
||||||
type="button"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
Update channel: {{ settingsStore.local.update_channel }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-secondary btn-sm ms-1"
|
|
||||||
id="updater_status"
|
|
||||||
type="button"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
...
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-secondary btn-sm ms-1"
|
|
||||||
id="updater_changelog"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#updaterReleaseNotes"
|
|
||||||
>
|
|
||||||
Changelog
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-primary btn-sm ms-1"
|
|
||||||
id="update_and_install"
|
|
||||||
type="button"
|
|
||||||
style="display: none"
|
|
||||||
>
|
|
||||||
Install & Restart
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
Loading…
Reference in a new issue