Merge pull request #793 from Pax1601/manager-wizard

Manager wizard
This commit is contained in:
Pax1601 2024-01-01 22:03:07 +01:00 committed by GitHub
commit 31999ec00c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 254 additions and 78 deletions

View File

@ -1,5 +1,7 @@
@echo OFF
SET PATH=%PATH%;%WINDIR%\System32;%WINDIR%\System32\WindowsPowerShell\v1.0;
WHERE /q powershell
if %ERRORLEVEL% NEQ 0 (
.\scripts\install.bat

View File

@ -0,0 +1,3 @@
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.

35
manager/DCS.openbeta.log Normal file
View File

@ -0,0 +1,35 @@
^CTerminare il processo batch (S/N)?
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.
C:\Users\Davide Passoni\Documents\DCSOlympus\client\client.vbs(1, 1) Errore di run-time di Microsoft VBScript: Indice non incluso nell'intervallo
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.
C:\Users\Davide Passoni\Documents\DCSOlympus\client\client.vbs(1, 1) Errore di run-time di Microsoft VBScript: Indice non incluso nell'intervallo
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.
Microsoft (R) Windows Script Host Versione 5.812
Copyright (C) Microsoft Corporation. Tutti i diritti riservati.

View File

@ -29,7 +29,7 @@
</style>
<div id="manager-connections">
<div class="step-summary">
<div class="blue <%= !install? 'hide': '' %>">User path</div>
<div class="blue <%= !install || simplified? 'hide': '' %>">User path</div>
<div class="white">Ports and address</div>
<div class="empty">Passwords</div>
<div class="empty"> <%= install? 'Install': 'Update' %></div>
@ -75,15 +75,19 @@
<input type="text" value="<%= instance["backendAddress"] %>">
</div>
<div class="buttons-footer">
<% if (!simplified) { %>
<div class="button back">
Back
</div>
<% } %>
<div class="button next">
Next
</div>
</div>
<% if (!simplified) { %>
<div class="button cancel">
Cancel installation
</div>
<% } %>
</div>
</div>

View File

@ -104,6 +104,10 @@
font-weight: normal;
}
#manager-instances .start-stop-client {
margin-right: auto;
}
</style>
<div id="manager-instances">
<div class="content">
@ -163,7 +167,7 @@
<div class="button uninstall">Uninstall</div>
<% } else { %>
<div class="button start-stop-server">Start server</div>
<!--<div class="button start-client">Start client</div>-->
<div class="button start-stop-client">Start client</div>
<% } %>
</div>
</div>

View File

@ -55,6 +55,8 @@
align-items: center;
border-radius: 5px;
cursor: pointer;
background-color: transparent;
color: var(--offwhite);
}
#manager-menu .option:hover {
@ -62,6 +64,12 @@
background-color: var(--offwhite);
}
#manager-menu .option.disabled {
pointer-events: none;
color: var(--darkgray);
border-color: var(--darkgray);
}
#manager-menu .option * {
pointer-events: none;
}
@ -74,14 +82,14 @@
<div>Using this manager, you can install Olympus, update settings, and view and manage instances</div>
</div>
<div id="menu">
<div class="option install">
<div class="option install <%= installEnabled? '': 'disabled' %>">
Install Olympus
</div>
<div class="option update">
<div class="option update <%= updateEnabled? '': 'disabled' %>">
Update/remove Olympus
</div>
<div class="option divider"></div>
<div class="option manage">
<div class="option manage <%= manageEnabled? '': 'disabled' %>">
View and manage instances
</div>
</div>

View File

@ -3,7 +3,7 @@
</style>
<div id="manager-passwords">
<div class="step-summary">
<div class="blue <%= !install? 'hide': '' %>">User path</div>
<div class="blue <%= !install || simplified? 'hide': '' %>">User path</div>
<div class="blue">Ports and address</div>
<div class="white">Passwords</div>
<div class="empty"> <%= install? 'Install': 'Update' %></div>
@ -41,8 +41,10 @@
Next
</div>
</div>
<% if (!simplified) { %>
<div class="button cancel">
Cancel installation
</div>
<% } %>
</div>
</div>

View File

@ -15,9 +15,14 @@ class ConnectionsPage extends ManagerPage {
const element = this.getElement();
element.innerHTML = str;
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
this.element.querySelector(".next").addEventListener("click", (e) => this.onNextClicked(e));
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
if (this.element.querySelector(".back"))
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
if (this.element.querySelector(".next"))
this.element.querySelector(".next").addEventListener("click", (e) => this.onNextClicked(e));
if (this.element.querySelector(".cancel"))
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
this.element.querySelector(".client-port").querySelector("input").addEventListener("change", async (e) => { this.setClientPort(Number(e.target.value)); })
this.element.querySelector(".backend-port").querySelector("input").addEventListener("change", async (e) => { this.setBackendPort(Number(e.target.value)); })

View File

@ -85,7 +85,10 @@ class DCSInstance {
}
this.installed = true;
const options = { compareContent: true };
const options = {
compareContent: true,
excludeFilter: "databases"
};
var err1 = true;
var err2 = true;
var res1;
@ -124,12 +127,12 @@ class DCSInstance {
instanceDiv.querySelector(".load .data").innerTexr = this.load;
}
instanceDiv.querySelector(".start-stop-server").innerText = this.webserverOnline ? "Stop server" : "Start server";
instanceDiv.querySelector(".start-stop-server").innerText = this.webserverOnline ? "Stop" : "Start server";
instanceDiv.querySelector(".start-stop-client").innerText = this.webserverOnline ? "Open in browser" : "Start client";
}
}
}
}
}, 1000);
}
@ -262,7 +265,7 @@ class DCSInstance {
console.log(`Starting server for instance at ${this.folder}`)
const out = fs.openSync(`./${this.name}.log`, 'a');
const err = fs.openSync(`./${this.name}.log`, 'a');
const sub = spawn('npm.cmd', ['run', 'start', '--', '--config', path.join(this.folder, "Config", "olympus.json")], {
const sub = spawn('cscript.exe', ['server.vbs', path.join(this.folder, "Config", "olympus.json")], {
detached: true,
cwd: "../client",
stdio: ['ignore', out, err]
@ -271,7 +274,20 @@ class DCSInstance {
sub.unref();
}
stopServer() {
startClient() {
console.log(`Starting client for instance at ${this.folder}`)
const out = fs.openSync(`./${this.name}.log`, 'a');
const err = fs.openSync(`./${this.name}.log`, 'a');
const sub = spawn('cscript.exe', ['client.vbs', path.join(this.folder, "Config", "olympus.json")], {
detached: true,
cwd: "../client",
stdio: ['ignore', out, err]
});
sub.unref();
}
stop() {
find('port', this.clientPort)
.then((list) => {
if (list.length !== 1) {
@ -279,13 +295,17 @@ class DCSInstance {
} else {
if (list[0].name.includes("node.exe")) {
console.log(`Killing process ${list[0].name}`)
process.kill(list[0].pid);
try {
process.kill(list[0].pid);
process.kill(list[0].ppid);
} catch (e) {
process.kill(list[0].pid);
}
}
else {
console.log(list[0])
console.error(`The process listening on the specified port has an incorrect name: ${list[0].name}`)
}
}
}, () => {
console.error("Error retrieving list of processes")
@ -294,7 +314,7 @@ class DCSInstance {
uninstall() {
showConfirmPopup("Are you sure you want to completely remove this Olympus installation?", () =>
uninstallInstance(this.folder).then(
uninstallInstance(this.folder, this.name).then(
() => {
location.reload();
},

View File

@ -2,7 +2,30 @@ const sha256 = require('sha256')
const createShortcut = require('create-desktop-shortcuts');
const fs = require('fs');
const path = require('path');
const { showErrorPopup, showWaitPopup } = require('./popup');
const { showWaitPopup } = require('./popup');
const homeDir = require('os').homedir();
async function deleteFile(filePath) {
console.log(`Deleting ${filePath}`);
var promise = new Promise((res, rej) => {
if (fs.existsSync(filePath)) {
fs.rm(filePath, (err) => {
if (err) {
console.error(`Error removing ${filePath}: ${err}`)
rej(err);
}
else {
console.log(`Removed ${filePath}`)
res(true);
}
});
}
else {
res(true);
}
})
return promise;
}
async function fixInstances(instances) {
var promise = new Promise((res, rej) => {
@ -23,13 +46,14 @@ async function fixInstances(instances) {
return promise;
}
async function uninstallInstance(folder) {
async function uninstallInstance(folder, name) {
console.log(`Uninstalling Olympus from ${folder}`)
showWaitPopup("Please wait while the Olympus installation is being uninstalled.")
var promise = new Promise((res, rej) => {
deleteMod(folder)
.then(() => deleteHooks(folder), (err) => { return Promise.reject(err); })
.then(() => deleteJSON(folder), (err) => { return Promise.reject(err); })
.then(() => deleteShortCuts(folder, name), (err) => { return Promise.reject(err); })
.then(() => res(true), (err) => { rej(err) });
})
return promise;
@ -143,7 +167,27 @@ async function installShortCuts(folder, name) {
}
});
if (res1 && res2) {
var res3 = createShortcut({
windows: {
filePath: path.resolve(__dirname, '..', '..', 'client', 'client.vbs'),
name: `DCS Olympus Client (${name})`,
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus.ico'),
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
}
});
var res4 = createShortcut({
windows: {
filePath: path.resolve(__dirname, '..', '..', 'client', 'server.vbs'),
name: `DCS Olympus Server (${name})`,
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus_server.ico'),
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
}
});
if (res1 && res2 && res3 && res4) {
res(true);
} else {
rej("An error occurred while creating the shortcuts")
@ -154,23 +198,7 @@ async function installShortCuts(folder, name) {
async function deleteHooks(folder) {
console.log(`Deleting hooks from ${folder}`);
var promise = new Promise((res, rej) => {
if (fs.existsSync(path.join(folder, "Scripts", "Hooks", "OlympusHook.lua"))) {
fs.rm(path.join(folder, "Scripts", "Hooks", "OlympusHook.lua"), (err) => {
if (err) {
console.log(`Error removing hooks from ${folder}: ${err}`)
rej(err);
}
else {
console.log(`Hooks succesfully removed from ${folder}`)
res(true);
}
});
} else {
res(true);
}
})
return promise;
return deleteFile(path.join(folder, "Scripts", "Hooks", "OlympusHook.lua"));
}
async function deleteMod(folder) {
@ -196,28 +224,19 @@ async function deleteMod(folder) {
async function deleteJSON(folder) {
console.log(`Deleting JSON from ${folder}`);
var promise = new Promise((res, rej) => {
if (fs.existsSync(path.join(folder, "Config", "olympus.json"))) {
fs.rm(path.join(folder, "Config", "olympus.json"), (err) => {
if (err) {
console.log(`Error removing JSON from ${folder}: ${err}`)
rej(err);
}
else {
console.log(`JSON succesfully removed from ${folder}`)
res(true);
}
});
}
else {
res(true);
}
})
return promise;
return deleteFile(path.join(folder, "Config", "olympus.json"));
}
async function deleteShortCuts() {
async function deleteShortCuts(folder, name) {
console.log(`Deleting ShortCuts from ${folder}`);
var promise = new Promise((res, rej) => {
deleteFile(path.join(folder, `DCS Olympus Server (${name}).lnk`))
.then(deleteFile(path.join(folder, `DCS Olympus Client (${name}).lnk`)), (err) => { return Promise.reject(err); })
.then(deleteFile(path.join(homeDir, "Desktop", `DCS Olympus Server (${name}).lnk`)), (err) => { return Promise.reject(err); })
.then(deleteFile(path.join(homeDir, "Desktop", `DCS Olympus Client (${name}).lnk`)), (err) => { return Promise.reject(err); })
.then(() => {res(true)}, (err) => { rej(err) })
});
return promise;
}
module.exports = {
@ -230,5 +249,6 @@ module.exports = {
deleteHooks: deleteHooks,
deleteJSON: deleteJSON,
deleteMod: deleteMod,
deleteShortCuts: deleteShortCuts,
uninstallInstance: uninstallInstance
}

View File

@ -2,6 +2,7 @@ const DCSInstance = require("./dcsinstance");
const ManagerPage = require("./managerpage");
const ejs = require('ejs');
const { showErrorPopup } = require("./popup");
const { exec } = require("child_process");
class InstancesPage extends ManagerPage {
onCancelClicked;
@ -25,9 +26,14 @@ class InstancesPage extends ManagerPage {
uninstallButtons[i].onclick = (e) => {this.onUninstallClicked(e);}
}
var startButtons = this.element.querySelectorAll(".start-stop-server");
for (let i = 0; i < startButtons.length; i++) {
startButtons[i].onclick = (e) => {this.onStartStopClicked(e);}
var startStopServerButtons = this.element.querySelectorAll(".start-stop-server");
for (let i = 0; i < startStopServerButtons.length; i++) {
startStopServerButtons[i].onclick = (e) => {this.onStartStopServerClicked(e);}
}
var startStopClientButtons = this.element.querySelectorAll(".start-stop-client");
for (let i = 0; i < startStopClientButtons.length; i++) {
startStopClientButtons[i].onclick = (e) => {this.onStartStopClientClicked(e);}
}
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
@ -38,9 +44,14 @@ class InstancesPage extends ManagerPage {
this.setSelectedInstance((await DCSInstance.getInstances()).find((instance) => {return instance.folder === e.target.closest('.option').dataset.folder}));
}
async onStartStopClicked(e) {
async onStartStopServerClicked(e) {
var instance = (await DCSInstance.getInstances()).find((instance) => {return instance.folder === e.target.closest('.option').dataset.folder});
instance.webserverOnline? instance.stopServer(): instance.startServer();
instance.webserverOnline? instance.stop(): instance.startServer();
}
async onStartStopClientClicked(e) {
var instance = (await DCSInstance.getInstances()).find((instance) => {return instance.folder === e.target.closest('.option').dataset.folder});
instance.webserverOnline? exec(`start http://localhost:${instance.clientPort}`): instance.startClient();
}
async onUninstallClicked(e) {

View File

@ -6,23 +6,28 @@ const ResultPage = require('./result');
const InstancesPage = require('./instances');
const DCSInstance = require('./dcsinstance');
const { showErrorPopup } = require('./popup');
const { showErrorPopup, showWaitPopup } = require('./popup');
const { fixInstances } = require('./filesystem');
class Manager {
simplified = true;
constructor() {
}
async start() {
var instances = await DCSInstance.getInstances();
this.simplified = instances.length === 1 && !instances[0].installed;
document.getElementById("loader").classList.add("hide");
if (instances.some((instance) => {
return instance.installed && instance.error;
})) {
showErrorPopup("One or more Olympus instances are corrupted or need updating. Press Close to fix this.", async () => {
showWaitPopup("Please wait while your instances are being fixed.")
fixInstances(instances.filter((instance) => {
return instance.installed && instance.error;
})).then(
@ -32,8 +37,18 @@ class Manager {
})
}
const installEnabled = instances.some((instance) => { return !instance.installed; });
const updateEnabled = instances.some((instance) => { return instance.installed; });
const manageEnabled = instances.some((instance) => { return instance.installed; });
/* Menu */
var menuPage = new MenuPage();
menuPage.options = {
...menuPage.options,
installEnabled: installEnabled,
updateEnabled: updateEnabled,
manageEnabled: manageEnabled
}
menuPage.onInstallClicked = (e) => {
menuPage.hide();
installationsPage.show();
@ -65,16 +80,19 @@ class Manager {
connectionsPage.options = {
...connectionsPage.options,
instance: activeInstance,
simplified: this.simplified,
install: true
}
passwordsPage.options = {
...passwordsPage.options,
instance: activeInstance,
simplified: this.simplified,
install: true
}
resultPage.options = {
...resultPage.options,
instance: activeInstance,
simplified: this.simplified,
install: true
}
installationsPage.hide();
@ -100,16 +118,19 @@ class Manager {
connectionsPage.options = {
...connectionsPage.options,
instance: activeInstance,
simplified: this.simplified,
install: false
}
passwordsPage.options = {
...passwordsPage.options,
instance: activeInstance,
simplified: this.simplified,
install: false
}
resultPage.options = {
...resultPage.options,
instance: activeInstance,
simplified: this.simplified,
install: false
}
instancesPage.hide();
@ -198,7 +219,30 @@ class Manager {
document.body.appendChild(passwordsPage.getElement());
document.body.appendChild(resultPage.getElement());
menuPage.show();
if (this.simplified) {
connectionsPage.options = {
...connectionsPage.options,
instance: instances[0],
simplified: this.simplified,
install: true
}
passwordsPage.options = {
...passwordsPage.options,
instance: instances[0],
simplified: this.simplified,
install: true
}
resultPage.options = {
...resultPage.options,
instance: instances[0],
simplified: this.simplified,
install: true
}
instancesPage.hide();
connectionsPage.show();
} else {
menuPage.show();
}
}
}

View File

@ -8,14 +8,6 @@ class MenuPage extends ManagerPage {
constructor(options) {
super(options);
ejs.renderFile("./ejs/menu.ejs", options, {}, (err, str) => {
if (!err) {
this.render(str);
} else {
console.error(err);
}
});
}
render(str) {
@ -26,6 +18,20 @@ class MenuPage extends ManagerPage {
element.querySelector(".update").addEventListener("click", (e) => this.onUpdateClicked(e))
element.querySelector(".manage").addEventListener("click", (e) => this.onManageClicked(e))
}
show() {
this.instance = this.options.instance;
ejs.renderFile("./ejs/menu.ejs", this.options, {}, (err, str) => {
if (!err) {
this.render(str);
} else {
console.error(err);
}
});
super.show();
}
}
module.exports = MenuPage;

View File

@ -14,9 +14,14 @@ class PasswordsPage extends ManagerPage {
const element = this.getElement();
element.innerHTML = str;
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
this.element.querySelector(".next").addEventListener("click", (e) => this.onNextClicked(e));
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
if (this.element.querySelector(".back"))
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
if (this.element.querySelector(".next"))
this.element.querySelector(".next").addEventListener("click", (e) => this.onNextClicked(e));
if (this.element.querySelector(".cancel"))
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
this.element.querySelector(".game-master").querySelector("input").addEventListener("change", async (e) => { this.instance.setGameMasterPassword(e.target.value); })
this.element.querySelector(".blue-commander").querySelector("input").addEventListener("change", async (e) => { this.instance.setBlueCommanderPassword(e.target.value); })

View File

@ -5,6 +5,8 @@ const path = require('path');
let window;
process.env['PATH'] = process.env['PATH'] + "%WINDIR%\\System32;"
function createWindow() {
const window = new electronBrowserWindow({
width: 1500,

View File

@ -10,7 +10,6 @@
"license": "ISC",
"dependencies": {
"create-desktop-shortcuts": "^1.10.1",
"create-windowless-app": "^11.0.0",
"dir-compare": "^4.2.0",
"ejs": "^3.1.9",
"electron": "^28.0.0",

View File

@ -74,6 +74,10 @@ body {
background-image: url("../icons/xmark-solid.svg");
}
.close:hover {
background-color: darkred;
}
.title-bar-button {
-webkit-app-region: no-drag;
}
@ -180,6 +184,7 @@ body {
display: flex;
flex-direction: column;
row-gap: 10px;
width: 50%;
}
.instructions>span {
@ -221,7 +226,7 @@ body {
border: 1px solid var(--offwhite);
}
.edit, .start-stop-server, .start-client {
.edit, .start-stop-server, .start-stop-client {
color: var(--offwhite);
background-color: var(--blue);
}
@ -485,6 +490,7 @@ input {
flex-direction: row;
width: 100%;
justify-content: space-between;
column-gap: 10px;
}
.instance-info .info {