mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Completed basic functionality development
This commit is contained in:
parent
613aed2d2b
commit
f2161da162
2
manager/.vscode/tasks.json
vendored
2
manager/.vscode/tasks.json
vendored
@ -6,7 +6,7 @@
|
||||
{
|
||||
"label": "mirror-package",
|
||||
"type": "shell",
|
||||
"command": "./scripts/mirror-package.bat",
|
||||
"command": "call ./scripts/mirror-package.bat",
|
||||
"isBackground": true
|
||||
}
|
||||
]
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="wizard-inputs">
|
||||
<% for (var i = 0; i < instances.length; i++) { %>
|
||||
<div class="button radio" onclick="signal('onFolderClicked', '<%= instances[i].name %>')" data-folder="<%= instances[i].folder %>">
|
||||
<div class="button radio <%= (i === 0)? 'selected': '' %>" onclick="signal('onFolderClicked', '<%= instances[i].name %>')" data-folder="<%= instances[i].folder %>">
|
||||
<%= instances[i].name %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
@ -57,6 +57,10 @@
|
||||
background-color: var(--offwhite);
|
||||
}
|
||||
|
||||
#manager-menu .option:hover::after {
|
||||
filter: invert();
|
||||
}
|
||||
|
||||
#manager-menu .option.disabled {
|
||||
pointer-events: none;
|
||||
color: var(--darkgray);
|
||||
|
||||
@ -88,14 +88,14 @@
|
||||
</div>
|
||||
<div class="result-summary error hide">
|
||||
<div class="title"><img src="./icons/triangle-exclamation-solid-background.svg">An error occurred while adding Olympus to <i><%= activeInstance["name"] %></i></div>
|
||||
<div class="description">See the manager log located in TODO for more information.</div>
|
||||
<div class="description">See the manager log located in <i><%= logLocation %></i> for more information.</div>
|
||||
</div>
|
||||
<div class="instructions-group hide">
|
||||
<div style="font-size: 18px; font-weight: bold; color: var(--offwhite);">
|
||||
How to launch Olympus
|
||||
</div>
|
||||
<div style="font-size: 13px; color: var(--offwhite);">
|
||||
To launch Olympus, there are shortcuts available in the <i><b><%= activeInstance["name"] %></b></i> folder under <i><b>Saved Games</b></i>.
|
||||
To launch Olympus, there are shortcuts available on the desktop and in the <i><b><%= activeInstance["name"] %></b></i> folder under <i><b>Saved Games</b></i>.
|
||||
</div>
|
||||
<% if (activeInstance["installationType"] === "singleplayer") { %>
|
||||
<div class="usage-instructions" style="width: 600px;">
|
||||
|
||||
@ -216,7 +216,10 @@
|
||||
}
|
||||
|
||||
</style>
|
||||
<div id="manager-settings" style="margin-bottom: 10px;">
|
||||
<div id="manager-settings" style="padding: 40px;">
|
||||
<div class="cancel" style="font-size: 14px; font-weight: 600; color: var(--offwhite); display: flex; align-items: center; column-gap: 10px; cursor: pointer; text-decoration: underline; " onclick="signal('onBackClicked')">
|
||||
<img src="./icons/chevron-left-solid.svg" style=" height: 14px;">Back to menu
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="instructions" style="display: flex; flex-direction: column; row-gap: 10px; align-items: center; padding: 20px;">
|
||||
<span style="color: var(--offwhite); font-size: 18px; font-weight: 600;">
|
||||
|
||||
@ -27,14 +27,12 @@
|
||||
<div class="accent-green">{{OLYMPUS_VERSION_NUMBER}}</div>
|
||||
</div>
|
||||
<div class="link first" data-link="https://github.com/Pax1601/DCSOlympus/wiki/2.-User-Guide">User Guide</div>
|
||||
<div class="link" data-link="https://github.com/Pax1601/DCSOlympus/wiki/Setup-Troubleshooting">Troubleshooting
|
||||
Guide</div>
|
||||
<div class="link" data-link="https://github.com/Pax1601/DCSOlympus/wiki/Setup-Troubleshooting">Troubleshooting Guide</div>
|
||||
<div id="switch-mode" class="link"> </div>
|
||||
<div style="width: 15px;"></div>
|
||||
<img class="link" data-link="https://github.com/Pax1601/DCSOlympus" src="./icons/github.svg" />
|
||||
<img class="link" data-link="https://discord.gg/pCfCykAdrw" src="./icons/discord.svg" />
|
||||
<img class="link" data-link="https://www.youtube.com/@DCSOlympus" src="./icons/youtube.svg" />
|
||||
<div class="link" onclick="signal('reload')">Debug reload</div>
|
||||
</div>
|
||||
<div id="loader" class="manager-page hide">
|
||||
<div style="font-weight: bold;">Loading, please wait...</div>
|
||||
|
||||
@ -1,36 +1,36 @@
|
||||
const { getManager } = require('./managerfactory')
|
||||
var regedit = require('regedit').promisified;
|
||||
const shellFoldersKey = 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders'
|
||||
const saveGamesKey = '{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}'
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
const { checkPort, fetchWithTimeout } = require('./net')
|
||||
const { checkPort, fetchWithTimeout, getFreePort } = require('./net')
|
||||
const dircompare = require('dir-compare');
|
||||
const { spawn } = require('child_process');
|
||||
const find = require('find-process');
|
||||
const { uninstallInstance, installHooks, installMod, installJSON, applyConfiguration, installShortCuts } = require('./filesystem')
|
||||
const { showErrorPopup, showConfirmPopup } = require('./popup')
|
||||
const { installHooks, installMod, installJSON, applyConfiguration, installShortCuts, deleteMod, deleteHooks, deleteJSON, deleteShortCuts } = require('./filesystem')
|
||||
const { showErrorPopup, showConfirmPopup, showWaitLoadingPopup, setPopupLoadingProgress } = require('./popup')
|
||||
const { logger } = require("./filesystem")
|
||||
const { hidePopup } = require('./popup')
|
||||
const { hidePopup } = require('./popup');
|
||||
const { sleep } = require('./utils');
|
||||
|
||||
const shellFoldersKey = 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders'
|
||||
const saveGamesKey = '{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}'
|
||||
|
||||
class DCSInstance {
|
||||
static instances = null;
|
||||
|
||||
/** Static asynchronous method to retrieve all DCS instances. Only runs at startup
|
||||
/** Static asynchronous method to retrieve all DCS instances. Only runs at startup, later calls will serve the cached result
|
||||
*
|
||||
* @returns The list of DCS instances
|
||||
*/
|
||||
static async getInstances() {
|
||||
if (this.instances === null) {
|
||||
var ans = this.findInstances();
|
||||
console.log(ans)
|
||||
return this.findInstances();
|
||||
}
|
||||
else
|
||||
return DCSInstance.instances;
|
||||
if (this.instances === null)
|
||||
DCSInstance.instances = this.findInstances();
|
||||
return DCSInstance.instances;
|
||||
}
|
||||
|
||||
/** Static asynchronous method to find all existing DCS instances
|
||||
*
|
||||
* @returns The list of found DCS instances
|
||||
*/
|
||||
static async findInstances() {
|
||||
/* Get the Saved Games folder from the registry */
|
||||
@ -47,26 +47,58 @@ class DCSInstance {
|
||||
/* A DCS Instance is created if either the appsettings.lua or serversettings.lua file is detected */
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
const folder = folders[i];
|
||||
if (fs.existsSync(path.join(searchpath, folder, "Config", "appsettings.lua")) ||
|
||||
fs.existsSync(path.join(searchpath, folder, "Config", "serversettings.lua"))) {
|
||||
if (fs.existsSync(path.join(searchpath, folder, "Config", "appsettings.lua")) ||fs.existsSync(path.join(searchpath, folder, "Config", "serversettings.lua"))) {
|
||||
logger.log(`Found instance in ${folder}, checking for Olympus`)
|
||||
getManager().setLoadingProgress(`Found instance in ${folder}, checking for Olympus...`, (i + 1) / folders.length * 100);
|
||||
var newInstance = new DCSInstance(path.join(searchpath, folder));
|
||||
|
||||
/* Check if Olympus is already installed */
|
||||
getManager().setLoadingProgress(`Found instance in ${folder}, checking for Olympus...`, (i + 1) / folders.length * 100);
|
||||
await newInstance.checkInstallation();
|
||||
instances.push(newInstance);
|
||||
}
|
||||
}
|
||||
|
||||
DCSInstance.instances = instances;
|
||||
} else {
|
||||
logger.error("An error occured while trying to fetch the location of the DCS instances.")
|
||||
Promise.reject("An error occured while trying to fetch the location of the DCS instances.");
|
||||
throw "An error occured while trying to fetch the location of the DCS instances.";
|
||||
}
|
||||
getManager().setLoadingProgress(`All DCS instances found!`, 100);
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
/** Asynchronously fixes/updates all the instances by deleting the existing installation and the copying over the clean files
|
||||
*
|
||||
*/
|
||||
static async fixInstances() {
|
||||
showWaitLoadingPopup("Please wait while your instances are being fixed.")
|
||||
const instancesToFix = (await DCSInstance.getInstances()).filter((instance) => { return instance.installed && instance.error; });
|
||||
setPopupLoadingProgress(`Fixing Olympus instances`, 0);
|
||||
|
||||
for (let i = 0; i < instancesToFix.length; i++) {
|
||||
const instance = instancesToFix[i];
|
||||
logger.log(`Fixing Olympus in ${instance.folder}`)
|
||||
|
||||
setPopupLoadingProgress(`Deleting mod folder in ${instance.folder}...`, (i * 4 + 1) / (instancesToFix.length * 4) * 100);
|
||||
await sleep(100);
|
||||
await deleteMod(instance.folder, instance.name);
|
||||
|
||||
setPopupLoadingProgress(`Deleting hook scripts in ${instance.folder}...`, (i * 4 + 2) / (instancesToFix.length * 4) * 100);
|
||||
await sleep(100);
|
||||
await deleteHooks(instance.folder);
|
||||
|
||||
setPopupLoadingProgress(`Installing mod folder in ${instance.folder}...`, (i * 4 + 3) / (instancesToFix.length * 4) * 100);
|
||||
await sleep(100);
|
||||
await installMod(instance.folder, instance.name);
|
||||
|
||||
setPopupLoadingProgress(`Installing hook scripts in ${instance.folder}...`, (i * 4 + 4) / (instancesToFix.length * 4) * 100);
|
||||
await sleep(100);
|
||||
await installHooks(instance.folder);
|
||||
}
|
||||
|
||||
setPopupLoadingProgress(`All instances fixed!`, 100);
|
||||
await sleep(100);
|
||||
}
|
||||
|
||||
folder = "";
|
||||
name = "";
|
||||
clientPort = 3000;
|
||||
@ -96,10 +128,14 @@ class DCSInstance {
|
||||
/* Periodically "ping" Olympus to check if either the client or the backend are active */
|
||||
window.setInterval(async () => {
|
||||
await this.getData();
|
||||
getManager().updateInstances();
|
||||
getManager().updateInstances();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/** Asynchronously checks if Olympus is installed in a DCS instance and compares the contents of package with the installation
|
||||
*
|
||||
* @returns true if the instance has any error or is outdated
|
||||
*/
|
||||
async checkInstallation() {
|
||||
/* Check if the olympus.json file is detected. If true, Olympus is considered to be installed */
|
||||
if (fs.existsSync(path.join(this.folder, "Config", "olympus.json"))) {
|
||||
@ -112,16 +148,17 @@ class DCSInstance {
|
||||
this.backendAddress = config["server"]["address"];
|
||||
this.gameMasterPasswordHash = config["authentication"]["gameMasterPassword"];
|
||||
} catch (err) {
|
||||
showErrorPopup(`A critical error has occurred while reading your Olympus configuration file. Please, manually reinstall olympus in ${this.folder}.`)
|
||||
logger.error(err)
|
||||
}
|
||||
|
||||
/* Compare the contents of the installed Olympus instance and the one in the root folder. Exclude the databases folder, which users can edit.
|
||||
If there is any difference, the instance is flagged as either corrupted or outdated */
|
||||
this.installed = true;
|
||||
const options = {
|
||||
const options = {
|
||||
compareContent: true,
|
||||
excludeFilter: "databases, mods.lua"
|
||||
};
|
||||
};
|
||||
var err1 = true;
|
||||
var err2 = true;
|
||||
var res1;
|
||||
@ -150,24 +187,28 @@ class DCSInstance {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
/** Asynchronously set the client port
|
||||
/** Set the client port
|
||||
*
|
||||
* @param {Number} newPort The new client port to set
|
||||
*/
|
||||
async setClientPort(newPort) {
|
||||
|
||||
setClientPort(newPort) {
|
||||
logger.log(`Instance ${this.folder} client port set to ${newPort}`)
|
||||
this.clientPort = newPort;
|
||||
}
|
||||
|
||||
/** Asynchronously set the backend port
|
||||
/** Set the backend port
|
||||
*
|
||||
* @param {Number} newPort The new backend port to set
|
||||
*/
|
||||
async setBackendPort(newPort) {
|
||||
setBackendPort(newPort) {
|
||||
logger.log(`Instance ${this.folder} backend port set to ${newPort}`)
|
||||
this.backendPort = newPort;
|
||||
}
|
||||
|
||||
/** Set backend address
|
||||
*
|
||||
* @param {String} newAddress The new backend address to set
|
||||
*/
|
||||
setBackendAddress(newAddress) {
|
||||
this.backendAddress = newAddress;
|
||||
@ -175,6 +216,7 @@ class DCSInstance {
|
||||
|
||||
/** Set Game Master password
|
||||
*
|
||||
* @param {String} newPassword The new Game Master password to set
|
||||
*/
|
||||
setGameMasterPassword(newPassword) {
|
||||
this.gameMasterPassword = newPassword;
|
||||
@ -183,6 +225,7 @@ class DCSInstance {
|
||||
|
||||
/** Set Blue Commander password
|
||||
*
|
||||
* @param {String} newAddress The new Blue Commander password to set
|
||||
*/
|
||||
setBlueCommanderPassword(newPassword) {
|
||||
this.blueCommanderPassword = newPassword;
|
||||
@ -191,85 +234,126 @@ class DCSInstance {
|
||||
|
||||
/** Set Red Commander password
|
||||
*
|
||||
* @param {String} newAddress The new Red Commander password to set
|
||||
*/
|
||||
setRedCommanderPassword(newPassword) {
|
||||
this.redCommanderPassword = newPassword;
|
||||
this.redCommanderPasswordEdited = true;
|
||||
}
|
||||
|
||||
/** Checks if any password has been edited by the user
|
||||
*
|
||||
* @returns true if any password was edited
|
||||
*/
|
||||
arePasswordsEdited() {
|
||||
return (getManager().getActiveInstance().gameMasterPasswordEdited || getManager().getActiveInstance().blueCommanderPasswordEdited || getManager().getActiveInstance().redCommanderPasswordEdited );
|
||||
return (getManager().getActiveInstance().gameMasterPasswordEdited || getManager().getActiveInstance().blueCommanderPasswordEdited || getManager().getActiveInstance().redCommanderPasswordEdited);
|
||||
}
|
||||
|
||||
/** Checks if all the passwords have been set by the user
|
||||
*
|
||||
* @returns true if all the password have been set
|
||||
*/
|
||||
arePasswordsSet() {
|
||||
return !(getManager().getActiveInstance().gameMasterPassword === '' || getManager().getActiveInstance().blueCommanderPassword === '' || getManager().getActiveInstance().redCommanderPassword === '');
|
||||
}
|
||||
|
||||
/** Checks if all the passwords are different
|
||||
*
|
||||
* @returns true if all the passwords are different
|
||||
*/
|
||||
arePasswordsDifferent() {
|
||||
return !(getManager().getActiveInstance().gameMasterPassword === getManager().getActiveInstance().blueCommanderPassword || getManager().getActiveInstance().gameMasterPassword === getManager().getActiveInstance().redCommanderPassword || getManager().getActiveInstance().blueCommanderPassword === getManager().getActiveInstance().redCommanderPassword);
|
||||
}
|
||||
|
||||
/** Check if the client port is free
|
||||
/** Asynchronously check if the client port is free
|
||||
*
|
||||
* @param {Number | undefined} port The port to check. If not set, the current clientPort will be checked
|
||||
* @returns true if the client port is free
|
||||
*/
|
||||
checkClientPort(port) {
|
||||
var promise = new Promise((res, rej) => {
|
||||
checkPort(port, async (portFree) => {
|
||||
if (portFree) {
|
||||
portFree = !(await DCSInstance.getInstances()).some((instance) => {
|
||||
if (instance !== this && instance.installed) {
|
||||
if (instance.clientPort === port || instance.backendPort === port) {
|
||||
logger.log(`Port ${port} already selected by other instance`);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (instance.backendPort === port) {
|
||||
logger.log(`Port ${port} equal to backend port`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
async checkClientPort(port) {
|
||||
port = port ?? this.clientPort;
|
||||
|
||||
logger.log(`Checking client port ${port}`);
|
||||
var portFree = await checkPort(port);
|
||||
if (portFree) {
|
||||
portFree = !(await DCSInstance.getInstances()).some((instance) => {
|
||||
if (instance !== this && instance.installed) {
|
||||
if (instance.clientPort === port || instance.backendPort === port) {
|
||||
logger.log(`Client port ${port} already selected by other instance`);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (instance.backendPort === port) {
|
||||
logger.log(`Client port ${port} equal to backend port`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.log(`Port ${port} currently in use`);
|
||||
}
|
||||
logger.log(`Port ${port} is free`);
|
||||
res(portFree);
|
||||
return false;
|
||||
})
|
||||
})
|
||||
return promise;
|
||||
}
|
||||
else {
|
||||
logger.log(`Client port ${port} currently in use`);
|
||||
}
|
||||
return portFree;
|
||||
}
|
||||
|
||||
/** Check if the backend port is free
|
||||
/** Asynchronously check if the backend port is free
|
||||
*
|
||||
* @param {Number | undefined} port The port to check. If not set, the current backendPort will be checked
|
||||
* @returns true if the backend port is free
|
||||
*/
|
||||
async checkBackendPort(port) {
|
||||
port = port ?? this.backendPort;
|
||||
|
||||
logger.log(`Checking backend port ${port}`);
|
||||
var portFree = await checkPort(port);
|
||||
if (portFree) {
|
||||
portFree = !(await DCSInstance.getInstances()).some((instance) => {
|
||||
if (instance !== this && instance.installed) {
|
||||
if (instance.clientPort === port || instance.backendPort === port) {
|
||||
logger.log(`Backend port ${port} already selected by other instance`);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (instance.clientPort === port) {
|
||||
logger.log(`Backend port ${port} equal to client port`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
} else {
|
||||
logger.log(`Backend port ${port} currently in use`);
|
||||
}
|
||||
return portFree;
|
||||
}
|
||||
|
||||
/** Asynchronously find free client and backend ports. If the old ports are free, it will keep them.
|
||||
*
|
||||
*/
|
||||
checkBackendPort(port) {
|
||||
var promise = new Promise((res, rej) => {
|
||||
checkPort(port, async (portFree) => {
|
||||
if (portFree) {
|
||||
portFree = !(await DCSInstance.getInstances()).some((instance) => {
|
||||
if (instance !== this && instance.installed) {
|
||||
if (instance.clientPort === port || instance.backendPort === port) {
|
||||
logger.log(`Port ${port} already selected by other instance`);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (instance.clientPort === port) {
|
||||
logger.log(`Port ${port} equal to client port`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
} else {
|
||||
logger.log(`Port ${port} currently in use`);
|
||||
}
|
||||
logger.log(`Port ${port} is free`);
|
||||
res(portFree);
|
||||
})
|
||||
})
|
||||
return promise;
|
||||
async findFreePorts() {
|
||||
logger.log(`Looking for free ports`);
|
||||
if (await this.checkClientPort() && await this.checkBackendPort()) {
|
||||
logger.log("Old ports are free, keeping them")
|
||||
} else {
|
||||
logger.log(`Finding new free ports`);
|
||||
|
||||
const instances = await DCSInstance.getInstances();
|
||||
const firstPort = instances.map((instance) => { return instance.clientPort; }).concat(instances.map((instance) => { return instance.backendPort; })).sort().at(-1) + 1;
|
||||
|
||||
var clientPort = await getFreePort(firstPort);
|
||||
if (clientPort === false)
|
||||
rej("Unable to find a free client port");
|
||||
logger.log(`Found free client port ${clientPort}`);
|
||||
|
||||
var backendPort = await getFreePort(clientPort + 1);
|
||||
if (backendPort === false)
|
||||
rej("Unable to find a free backend port");
|
||||
logger.log(`Found free backend port ${backendPort}`);
|
||||
|
||||
this.clientPort = clientPort;
|
||||
this.backendPort = backendPort;
|
||||
}
|
||||
}
|
||||
|
||||
/** Asynchronously interrogate the webserver and the backend to check if they are active and to retrieve data.
|
||||
@ -345,7 +429,9 @@ class DCSInstance {
|
||||
sub.unref();
|
||||
}
|
||||
|
||||
/* Stop any node process running on the server port. This will stop either the server or the client depending on what is running */
|
||||
/** Stop any node process running on the server port. This will stop either the server or the client depending on what is running
|
||||
*
|
||||
*/
|
||||
stop() {
|
||||
find('port', this.clientPort)
|
||||
.then((list) => {
|
||||
@ -370,66 +456,109 @@ class DCSInstance {
|
||||
})
|
||||
}
|
||||
|
||||
/* Install this instance */
|
||||
install() {
|
||||
getManager().setPopupLoadingProgress("Installing hook scripts...", 0)
|
||||
installHooks(getManager().getActiveInstance().folder).then(
|
||||
() => {getManager().setPopupLoadingProgress("Installing mod folder...", 20) },
|
||||
(err) => {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(() => installMod(getManager().getActiveInstance().folder, getManager().getActiveInstance().name)).then(
|
||||
() => {getManager().setPopupLoadingProgress("Installing JSON file...", 40) },
|
||||
(err) => {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(() => installJSON(getManager().getActiveInstance().folder)).then(
|
||||
() => {getManager().setPopupLoadingProgress("Applying configuration...", 60) },
|
||||
(err) => {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(() => applyConfiguration(getManager().getActiveInstance().folder, getManager().getActiveInstance())).then(
|
||||
() => {getManager().setPopupLoadingProgress("Creating shortcuts...", 80) },
|
||||
(err) => {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(() => installShortCuts(getManager().getActiveInstance().folder, getManager().getActiveInstance().name)).then(
|
||||
() => {getManager().setPopupLoadingProgress("Installation completed!", 100) },
|
||||
(err) => {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(
|
||||
() => {
|
||||
hidePopup();
|
||||
getManager().resultPage.show();
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.success").classList.remove("hide");
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.error").classList.add("hide");
|
||||
getManager().resultPage.getElement().querySelector(".instructions-group").classList.remove("hide");
|
||||
},
|
||||
() => {
|
||||
hidePopup();
|
||||
getManager().resultPage.show();
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.success").classList.add("hide");
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.error").classList.remove("hide");
|
||||
}
|
||||
);
|
||||
/** Edit this instance
|
||||
*
|
||||
*/
|
||||
async edit() {
|
||||
showWaitLoadingPopup(`<span>Please wait while Olympus is being edited in <i>${this.name}</i></span>`);
|
||||
try {
|
||||
setPopupLoadingProgress("Applying configuration...", 0);
|
||||
await sleep(100);
|
||||
await applyConfiguration(getManager().getActiveInstance().folder, getManager().getActiveInstance());
|
||||
|
||||
setPopupLoadingProgress("Editing completed!", 100);
|
||||
await sleep(500);
|
||||
logger.log(`Editing completed successfully`);
|
||||
hidePopup();
|
||||
|
||||
this.options.mode === "basic"? getManager().menuPage.show(): getManager().instancesPage.show();
|
||||
} catch (err) {
|
||||
logger.log(`An error occurred during editing: ${err}`);
|
||||
hidePopup();
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
}
|
||||
|
||||
/* Uninstall this instance */
|
||||
uninstall() {
|
||||
showConfirmPopup("<div style='font-size: 18px; max-width: 100%; margin-bottom: 15px;'> Are you sure you want to remove Olympus? </div> If you click Accept, the Olympus mod will be removed from your DCS installation.", () =>
|
||||
uninstallInstance(this.folder, this.name).then(
|
||||
() => {
|
||||
/** Install this instance
|
||||
*
|
||||
*/
|
||||
async install() {
|
||||
showWaitLoadingPopup(`<span>Please wait while Olympus is being installed in <i>${this.name}</i></span>`);
|
||||
try {
|
||||
setPopupLoadingProgress("Installing hook scripts...", 0);
|
||||
await sleep(100);
|
||||
await installHooks(getManager().getActiveInstance().folder);
|
||||
|
||||
setPopupLoadingProgress("Installing mod folder...", 20);
|
||||
await sleep(100);
|
||||
await installMod(getManager().getActiveInstance().folder, getManager().getActiveInstance().name);
|
||||
|
||||
setPopupLoadingProgress("Installing JSON file...", 40);
|
||||
await sleep(100);
|
||||
await installJSON(getManager().getActiveInstance().folder);
|
||||
|
||||
setPopupLoadingProgress("Applying configuration...", 60);
|
||||
await sleep(100);
|
||||
await applyConfiguration(getManager().getActiveInstance().folder, getManager().getActiveInstance());
|
||||
|
||||
setPopupLoadingProgress("Creating shortcuts...", 80);
|
||||
await sleep(100);
|
||||
await installShortCuts(getManager().getActiveInstance().folder, getManager().getActiveInstance().name);
|
||||
|
||||
setPopupLoadingProgress("Installation completed!", 100);
|
||||
await sleep(500);
|
||||
logger.log(`Installation completed successfully`);
|
||||
hidePopup();
|
||||
getManager().resultPage.show();
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.success").classList.remove("hide");
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.error").classList.add("hide");
|
||||
getManager().resultPage.getElement().querySelector(".instructions-group").classList.remove("hide");
|
||||
} catch (err) {
|
||||
logger.log(`An error occurred during installation: ${err}`);
|
||||
hidePopup();
|
||||
getManager().resultPage.show();
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.success").classList.add("hide");
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.error").classList.remove("hide");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Uninstall this instance
|
||||
*
|
||||
*/
|
||||
async uninstall() {
|
||||
showConfirmPopup("<div style='font-size: 18px; max-width: 100%; margin-bottom: 15px;'> Are you sure you want to remove Olympus? </div> If you click Accept, the Olympus mod will be removed from your DCS installation.", async () => {
|
||||
try {
|
||||
logger.log(`Uninstalling Olympus from ${this.folder}`)
|
||||
showWaitLoadingPopup(`<span>Please wait while Olympus is being removed from <i>${this.name}</i></span>`);
|
||||
setPopupLoadingProgress("Deleting mod folder...", 0);
|
||||
await sleep(100);
|
||||
await deleteMod(this.folder, this.name);
|
||||
|
||||
setPopupLoadingProgress("Deleting hook scripts...", 25);
|
||||
await sleep(100);
|
||||
await deleteHooks(this.folder);
|
||||
|
||||
setPopupLoadingProgress("Deleting JSON...", 50);
|
||||
await sleep(100);
|
||||
await deleteJSON(this.folder);
|
||||
|
||||
setPopupLoadingProgress("Deleting shortcuts...", 75);
|
||||
await sleep(100);
|
||||
await deleteShortCuts(this.folder, this.name);
|
||||
|
||||
await sleep(500);
|
||||
setPopupLoadingProgress("Instance removed!", 100);
|
||||
logger.log(`Olympus removed from ${this.folder}`)
|
||||
location.reload();
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
showErrorPopup(`An error has occurred while uninstalling the Olympus instance. Make sure Olympus and DCS are not running. <br><br> You can find more info in ${path.join(__dirname, "..", "manager.log")}`, () => {
|
||||
location.reload();
|
||||
},
|
||||
(err) => {
|
||||
logger.error(err)
|
||||
showErrorPopup(`An error has occurred while uninstalling the Olympus instance. Make sure Olympus and DCS are not running. <br><br> You can find more info in ${path.join(__dirname, "..", "manager.log")}`, () => {
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,315 +1,234 @@
|
||||
const sha256 = require('sha256')
|
||||
const createShortcut = require('create-desktop-shortcuts');
|
||||
const fs = require('fs');
|
||||
const fsp = require('fs').promises;
|
||||
const path = require('path');
|
||||
const { showWaitPopup } = require('./popup');
|
||||
const { Console } = require('console');
|
||||
const homeDir = require('os').homedir();
|
||||
|
||||
var output = fs.createWriteStream('./manager.log', {flags: 'a'});
|
||||
var output = fs.createWriteStream('./manager.log', { flags: 'a' });
|
||||
var logger = new Console(output, output);
|
||||
const date = new Date();
|
||||
output.write(` ======================= New log starting at ${date.toString()} =======================\n`);
|
||||
|
||||
/** Conveniency function to asynchronously delete a single file, with error catching
|
||||
*
|
||||
* @param {String} filePath The path to the file to delete
|
||||
*/
|
||||
async function deleteFile(filePath) {
|
||||
logger.log(`Deleting ${filePath}`);
|
||||
var promise = new Promise((res, rej) => {
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.rm(filePath, (err) => {
|
||||
if (err) {
|
||||
logger.error(`Error removing ${filePath}: ${err}`)
|
||||
rej(err);
|
||||
}
|
||||
else {
|
||||
logger.log(`Removed ${filePath}`)
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
res(true);
|
||||
}
|
||||
})
|
||||
return promise;
|
||||
if (await exists(filePath) && await fsp.rm(filePath))
|
||||
logger.log(`Removed ${filePath}`);
|
||||
else
|
||||
logger.log(`${filePath} does not exist, nothing to do`);
|
||||
}
|
||||
|
||||
/** Given a list of Olympus instances, it fixes/updates them by deleting the existing installation and the copying over the clean files
|
||||
/** Conveniency function to asynchronously check if a file or folder exists
|
||||
*
|
||||
* @param {*} location Path to the folder or file to check for existance
|
||||
* @returns true if file exists, false if it doesn't
|
||||
*/
|
||||
async function fixInstances(instances) {
|
||||
var promise = new Promise((res, rej) => {
|
||||
var instancePromises = instances.map((instance) => {
|
||||
var instancePromise = new Promise((instanceRes, instanceErr) => {
|
||||
logger.log(`Fixing Olympus in ${instance.folder}`)
|
||||
deleteMod(instance.folder, instance.name)
|
||||
.then(() => deleteHooks(instance.folder), (err) => { return Promise.reject(err); })
|
||||
.then(() => installMod(instance.folder, instance.name), (err) => { return Promise.reject(err); })
|
||||
.then(() => installHooks(instance.folder), (err) => { return Promise.reject(err); })
|
||||
.then(() => installShortCuts(instance.folder, instance.name), (err) => { return Promise.reject(err); })
|
||||
.then(() => instanceRes(true), (err) => { instanceErr(err) })
|
||||
})
|
||||
return instancePromise;
|
||||
});
|
||||
Promise.all(instancePromises).then(() => res(true), (err) => { rej(err) });
|
||||
})
|
||||
return promise;
|
||||
async function exists(location) {
|
||||
try {
|
||||
await fsp.stat(location);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return false
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Uninstalls a specific instance given its folder
|
||||
*
|
||||
*/
|
||||
async function uninstallInstance(folder, name) {
|
||||
logger.log(`Uninstalling Olympus from ${folder}`)
|
||||
showWaitPopup("Please wait while the Olympus installation is being removed.")
|
||||
var promise = new Promise((res, rej) => {
|
||||
deleteMod(folder, name)
|
||||
.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;
|
||||
}
|
||||
|
||||
/** Installs the Hooks script
|
||||
/** Asynchronously installs the Hooks script
|
||||
*
|
||||
* @param {String} folder The base Saved Games folder where the hooks scripts should be installed
|
||||
*/
|
||||
async function installHooks(folder) {
|
||||
logger.log(`Installing hooks in ${folder}`)
|
||||
var promise = new Promise((res, rej) => {
|
||||
fs.cp(path.join("..", "scripts", "OlympusHook.lua"), path.join(folder, "Scripts", "Hooks", "OlympusHook.lua"), (err) => {
|
||||
if (err) {
|
||||
logger.log(`Error installing hooks in ${folder}: ${err}`)
|
||||
rej(err);
|
||||
}
|
||||
else {
|
||||
logger.log(`Hooks succesfully installed in ${folder}`)
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
})
|
||||
return promise;
|
||||
await fsp.cp(path.join("..", "scripts", "OlympusHook.lua"), path.join(folder, "Scripts", "Hooks", "OlympusHook.lua"));
|
||||
logger.log(`Hooks succesfully installed in ${folder}`)
|
||||
}
|
||||
|
||||
/** Installs the Mod folder
|
||||
/** Asynchronously installs the Mod folder
|
||||
*
|
||||
* @param {String} folder The base Saved Games folder where the mod folder should be installed
|
||||
* @param {String} name The name of the current DCS Instance, used to create backups of user created files
|
||||
*/
|
||||
|
||||
async function installMod(folder, name) {
|
||||
logger.log(`Installing mod in ${folder}`)
|
||||
var promise = new Promise((res, rej) => {
|
||||
fs.cp(path.join("..", "mod"), path.join(folder, "Mods", "Services", "Olympus"), { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
logger.log(`Error installing mod in ${folder}: ${err}`)
|
||||
rej(err);
|
||||
}
|
||||
else {
|
||||
logger.log(`Mod succesfully installed in ${folder}`)
|
||||
|
||||
/* Check if backup user-editable files exist. If true copy them over */
|
||||
try {
|
||||
logger.log(__dirname)
|
||||
logger.log(path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "databases"));
|
||||
if (fs.existsSync(path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "databases"))) {
|
||||
logger.log("Backup databases found, copying over");
|
||||
fs.cpSync(path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "databases"), path.join(folder, "Mods", "Services", "Olympus", "databases"), {recursive: true});
|
||||
}
|
||||
await fsp.cp(path.join("..", "mod"), path.join(folder, "Mods", "Services", "Olympus"), { recursive: true });
|
||||
logger.log(`Mod succesfully installed in ${folder}`)
|
||||
|
||||
if (fs.existsSync(path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "scripts", "mods.lua"))) {
|
||||
logger.log("Backup mods.lua found, copying over");
|
||||
fs.cpSync(path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "scripts", "mods.lua"), path.join(folder, "Mods", "Services", "Olympus", "scripts", "mods.lua"));
|
||||
}
|
||||
} catch (err) {
|
||||
logger.log(`Error installing mod in ${folder}: ${err}`)
|
||||
rej(err);
|
||||
}
|
||||
/* Check if backup user-editable files exist. If true copy them over */
|
||||
logger.log(path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "databases"));
|
||||
if (await exists(path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "databases"))) {
|
||||
logger.log("Backup databases found, copying over");
|
||||
await fsp.cp(path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "databases"), path.join(folder, "Mods", "Services", "Olympus", "databases"), { recursive: true });
|
||||
}
|
||||
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
})
|
||||
return promise;
|
||||
if (exists(path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "scripts", "mods.lua"))) {
|
||||
logger.log("Backup mods.lua found, copying over");
|
||||
fsp.cp(path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "scripts", "mods.lua"), path.join(folder, "Mods", "Services", "Olympus", "scripts", "mods.lua"));
|
||||
}
|
||||
}
|
||||
|
||||
/** Installs the olympus.json file
|
||||
/** Asynchronously installs the olympus.json file
|
||||
*
|
||||
* @param {String} folder The base Saved Games folder where the config json should be installed
|
||||
*/
|
||||
async function installJSON(folder) {
|
||||
logger.log(`Installing config in ${folder}`)
|
||||
var promise = new Promise((res, rej) => {
|
||||
fs.cp(path.join("..", "olympus.json"), path.join(folder, "Config", "olympus.json"), (err) => {
|
||||
if (err) {
|
||||
logger.log(`Error installing config in ${folder}: ${err}`)
|
||||
rej(err);
|
||||
}
|
||||
else {
|
||||
logger.log(`Config succesfully installed in ${folder}`)
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
})
|
||||
return promise;
|
||||
await fsp.cp(path.join("..", "olympus.json"), path.join(folder, "Config", "olympus.json"));
|
||||
logger.log(`Config succesfully installed in ${folder}`)
|
||||
}
|
||||
|
||||
/** Creates shortcuts both in the DCS Saved Games folder and on the desktop
|
||||
/** Asynchronously creates shortcuts both in the DCS Saved Games folder and on the desktop
|
||||
*
|
||||
* @param {String} folder The base Saved Games folder where the shortcuts should be installed
|
||||
* @param {String} name The name of the current DCS Instance, used to create the shortcut names
|
||||
*/
|
||||
async function installShortCuts(folder, name) {
|
||||
logger.log(`Installing shortcuts for Olympus in ${folder}`);
|
||||
var promise = new Promise((res, rej) => {
|
||||
var res1 = createShortcut({
|
||||
windows: {
|
||||
filePath: path.resolve(__dirname, '..', '..', 'client', 'client.vbs'),
|
||||
outputPath: folder,
|
||||
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 res2 = createShortcut({
|
||||
windows: {
|
||||
filePath: path.resolve(__dirname, '..', '..', 'client', 'server.vbs'),
|
||||
outputPath: folder,
|
||||
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')
|
||||
}
|
||||
});
|
||||
|
||||
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")
|
||||
var res1 = createShortcut({
|
||||
windows: {
|
||||
filePath: path.resolve(__dirname, '..', '..', 'client', 'client.vbs'),
|
||||
outputPath: folder,
|
||||
name: `DCS Olympus Client (${name})`,
|
||||
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
|
||||
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus.ico'),
|
||||
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
|
||||
var res2 = createShortcut({
|
||||
windows: {
|
||||
filePath: path.resolve(__dirname, '..', '..', 'client', 'server.vbs'),
|
||||
outputPath: folder,
|
||||
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')
|
||||
}
|
||||
});
|
||||
|
||||
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')
|
||||
}
|
||||
});
|
||||
|
||||
// TODO actually check if the shortcuts where created
|
||||
if (!res1 || !res2 || !res3 || !res4)
|
||||
throw "An error occurred while creating the shortcuts";
|
||||
}
|
||||
|
||||
/** Writes the configuration of an instance to the olympus.json file
|
||||
/** Asynchronously writes the configuration of an instance to the olympus.json file
|
||||
*
|
||||
* @param {String} folder The base Saved Games folder where Olympus should is installed
|
||||
* @param {DCSInstance} instance The DCSInstance of which we want to apply the configuration
|
||||
*/
|
||||
async function applyConfiguration(folder, instance) {
|
||||
logger.log(`Applying configuration to Olympus in ${folder}`);
|
||||
var promise = new Promise((res, rej) => {
|
||||
if (fs.existsSync(path.join(folder, "Config", "olympus.json"))) {
|
||||
var config = JSON.parse(fs.readFileSync(path.join(folder, "Config", "olympus.json")));
|
||||
|
||||
config["client"]["port"] = instance.clientPort;
|
||||
config["server"]["port"] = instance.backendPort;
|
||||
config["server"]["address"] = instance.backendAddress;
|
||||
config["authentication"]["gameMasterPassword"] = sha256(instance.gameMasterPassword);
|
||||
config["authentication"]["blueCommanderPassword"] = sha256(instance.blueCommanderPassword);
|
||||
config["authentication"]["redCommanderPassword"] = sha256(instance.redCommanderPassword);
|
||||
if (await exists(path.join(folder, "Config", "olympus.json"))) {
|
||||
var config = JSON.parse(await fsp.readFile(path.join(folder, "Config", "olympus.json")));
|
||||
|
||||
fs.writeFile(path.join(folder, "Config", "olympus.json"), JSON.stringify(config, null, 4), (err) => {
|
||||
if (err) {
|
||||
logger.log(`Error applying config in ${folder}: ${err}`)
|
||||
rej(err);
|
||||
}
|
||||
else {
|
||||
logger.log(`Config succesfully applied in ${folder}`)
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
rej("File does not exist")
|
||||
/* Automatically find free ports */
|
||||
if (instance.connectionsType === 'auto') {
|
||||
await instance.findFreePorts();
|
||||
}
|
||||
res(true);
|
||||
});
|
||||
return promise;
|
||||
|
||||
/* Apply the configuration */
|
||||
config["client"]["port"] = instance.clientPort;
|
||||
config["server"]["port"] = instance.backendPort;
|
||||
config["server"]["address"] = instance.backendAddress;
|
||||
config["authentication"]["gameMasterPassword"] = sha256(instance.gameMasterPassword);
|
||||
config["authentication"]["blueCommanderPassword"] = sha256(instance.blueCommanderPassword);
|
||||
config["authentication"]["redCommanderPassword"] = sha256(instance.redCommanderPassword);
|
||||
|
||||
await fsp.writeFile(path.join(folder, "Config", "olympus.json"), JSON.stringify(config, null, 4));
|
||||
logger.log(`Config succesfully applied in ${folder}`)
|
||||
} else {
|
||||
throw "File does not exist";
|
||||
}
|
||||
}
|
||||
|
||||
/** Deletes the Hooks script
|
||||
/** Asynchronously deletes the Hooks script
|
||||
*
|
||||
* @param {String} folder The base Saved Games folder where Olympus is installed
|
||||
*/
|
||||
async function deleteHooks(folder) {
|
||||
logger.log(`Deleting hooks from ${folder}`);
|
||||
return deleteFile(path.join(folder, "Scripts", "Hooks", "OlympusHook.lua"));
|
||||
await deleteFile(path.join(folder, "Scripts", "Hooks", "OlympusHook.lua"));
|
||||
}
|
||||
|
||||
/** Deletes the Mod folder
|
||||
/** Asynchronously deletes the Mod folder
|
||||
*
|
||||
* @param {String} folder The base Saved Games folder where Olympus is installed
|
||||
*/
|
||||
async function deleteMod(folder, name) {
|
||||
logger.log(`Deleting mod from ${folder}`);
|
||||
var promise = new Promise((res, rej) => {
|
||||
if (fs.existsSync(path.join(folder, "Mods", "Services", "Olympus"))) {
|
||||
/* Make a copy of the user-editable files */
|
||||
if (fs.existsSync(path.join(folder, "Mods", "Services", "Olympus", "databases")))
|
||||
fs.cpSync(path.join(folder, "Mods", "Services", "Olympus", "databases"), path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "databases"), {recursive: true});
|
||||
else
|
||||
logger.warn(`No database folder found in ${folder}, skipping backup...`)
|
||||
|
||||
if (fs.existsSync(path.join(folder, "Mods", "Services", "Olympus", "scripts", "mods.lua")))
|
||||
fs.cpSync(path.join(folder, "Mods", "Services", "Olympus", "scripts", "mods.lua"), path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "scripts", "mods.lua"));
|
||||
else
|
||||
logger.warn(`No mods.lua found in ${folder}, skipping backup...`)
|
||||
if (await exists(path.join(folder, "Mods", "Services", "Olympus"))) {
|
||||
/* Make a copy of the user-editable files */
|
||||
if (await exists(path.join(folder, "Mods", "Services", "Olympus", "databases")))
|
||||
await fsp.cp(path.join(folder, "Mods", "Services", "Olympus", "databases"), path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "databases"), { recursive: true });
|
||||
else
|
||||
logger.warn(`No database folder found in ${folder}, skipping backup...`)
|
||||
|
||||
/* Remove the mod folder */
|
||||
fs.rmdir(path.join(folder, "Mods", "Services", "Olympus"), { recursive: true, force: true }, (err) => {
|
||||
if (err) {
|
||||
logger.log(`Error removing mod from ${folder}: ${err}`)
|
||||
rej(err);
|
||||
}
|
||||
else {
|
||||
logger.log(`Mod succesfully removed from ${folder}`)
|
||||
res(true);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
res(true);
|
||||
};
|
||||
})
|
||||
return promise;
|
||||
if (await exists(path.join(folder, "Mods", "Services", "Olympus", "scripts", "mods.lua")))
|
||||
await fsp.cp(path.join(folder, "Mods", "Services", "Olympus", "scripts", "mods.lua"), path.join(__dirname, "..", "..", "..", "DCS Olympus backups", name, "scripts", "mods.lua"));
|
||||
else
|
||||
logger.warn(`No mods.lua found in ${folder}, skipping backup...`)
|
||||
|
||||
/* Remove the mod folder */
|
||||
await fsp.rmdir(path.join(folder, "Mods", "Services", "Olympus"), { recursive: true, force: true })
|
||||
logger.log(`Mod succesfully removed from ${folder}`)
|
||||
} else {
|
||||
logger.warn(`Mod does not exist in ${folder}, nothing to do`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Deletes the olympus.json configuration file
|
||||
/** Asynchronously deletes the olympus.json configuration file
|
||||
*
|
||||
* @param {String} folder The base Saved Games folder where Olympus is installed
|
||||
*/
|
||||
async function deleteJSON(folder) {
|
||||
logger.log(`Deleting JSON from ${folder}`);
|
||||
return deleteFile(path.join(folder, "Config", "olympus.json"));
|
||||
}
|
||||
|
||||
/** Deletes the shortcuts
|
||||
/** Asynchronously deletes the shortcuts
|
||||
*
|
||||
* @param {String} folder The base Saved Games folder where Olympus is installed
|
||||
* @param {String} name The name of the DCS Instance, used to find the correct shortcuts
|
||||
*/
|
||||
async function deleteShortCuts(folder, name) {
|
||||
logger.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;
|
||||
logger.log(`Deleting ShortCuts from ${folder} and desktop`);
|
||||
await deleteFile(path.join(folder, `DCS Olympus Server (${name}).lnk`))
|
||||
await deleteFile(path.join(folder, `DCS Olympus Client (${name}).lnk`))
|
||||
await deleteFile(path.join(homeDir, "Desktop", `DCS Olympus Server (${name}).lnk`))
|
||||
await deleteFile(path.join(homeDir, "Desktop", `DCS Olympus Client (${name}).lnk`))
|
||||
logger.log(`ShortCuts deleted from ${folder} and desktop`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@ -318,11 +237,9 @@ module.exports = {
|
||||
installHooks: installHooks,
|
||||
installMod: installMod,
|
||||
installShortCuts, installShortCuts,
|
||||
fixInstances: fixInstances,
|
||||
deleteHooks: deleteHooks,
|
||||
deleteJSON: deleteJSON,
|
||||
deleteMod: deleteMod,
|
||||
deleteShortCuts: deleteShortCuts,
|
||||
uninstallInstance: uninstallInstance,
|
||||
logger: logger
|
||||
}
|
||||
|
||||
@ -3,13 +3,13 @@ const fs = require("fs");
|
||||
|
||||
const DCSInstance = require('./dcsinstance');
|
||||
const { showErrorPopup, showWaitPopup, showConfirmPopup } = require('./popup');
|
||||
const { fixInstances } = require('./filesystem');
|
||||
const { logger } = require("./filesystem")
|
||||
|
||||
const ManagerPage = require("./managerpage");
|
||||
const WizardPage = require("./wizardpage");
|
||||
const { fetchWithTimeout } = require("./net");
|
||||
const { exec } = require("child_process");
|
||||
const { sleep } = require("./utils");
|
||||
|
||||
class Manager {
|
||||
options = {
|
||||
@ -29,6 +29,9 @@ class Manager {
|
||||
instancesPage = null;
|
||||
|
||||
constructor() {
|
||||
/* Simple framework to define callbacks to events directly in the .ejs files. When an event happens, e.g. a button is clicked, the signal function is called with the function
|
||||
to call and an optional object to pass. An event will then be created, defined in index.html, and will be listened here. Using an eval call, the appropriate member function
|
||||
will then be called */
|
||||
document.addEventListener("signal", (ev) => {
|
||||
const callback = ev.detail.callback;
|
||||
const params = JSON.stringify(ev.detail.params);
|
||||
@ -40,7 +43,11 @@ class Manager {
|
||||
});
|
||||
}
|
||||
|
||||
/** Asynchronously start the manager
|
||||
*
|
||||
*/
|
||||
async start() {
|
||||
|
||||
/* Check if the options file exists */
|
||||
if (fs.existsSync("options.json")) {
|
||||
/* Load the options from the json file */
|
||||
@ -49,6 +56,7 @@ class Manager {
|
||||
this.options.configLoaded = true;
|
||||
} catch (e) {
|
||||
logger.error(`An error occurred while reading the options.json file: ${e}`);
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,43 +83,36 @@ class Manager {
|
||||
|
||||
/* Get the list of DCS instances */
|
||||
this.setLoadingProgress("Retrieving DCS instances...", 0);
|
||||
DCSInstance.getInstances().then((instances) => {
|
||||
this.setLoadingProgress(`Analysis completed, starting manager!`, 100);
|
||||
DCSInstance.getInstances().then(async (instances) => {
|
||||
this.setLoadingProgress(`Analysis completed, starting manager...`, 100);
|
||||
await sleep(100);
|
||||
|
||||
this.options.instances = instances;
|
||||
|
||||
/* Get my public IP */
|
||||
this.getPublicIP().then(
|
||||
(ip) => {
|
||||
this.options.ip = ip;
|
||||
},
|
||||
() => {
|
||||
this.options.ip = undefined;
|
||||
}
|
||||
(ip) => { this.options.ip = ip; },
|
||||
() => { this.options.ip = undefined; }
|
||||
)
|
||||
|
||||
/* Check if there are corrupted or outdate instances */
|
||||
/* Check if there are corrupted or outdated instances */
|
||||
if (this.options.instances.some((instance) => {
|
||||
return instance.installed && instance.error;
|
||||
})) {
|
||||
/* Ask the user for confirmation */
|
||||
showConfirmPopup("<div style='font-size: 18px; max-width: 100%;'>One or more of your Olympus instances are not up to date! </div> <br> <br> If you have just updated Olympus this is normal. <br> <br> Press Accept and the Manager will fix your instances for you. <br> Press Close to update your instances manually using the Installation Wizard", async () => {
|
||||
showWaitPopup("Please wait while your instances are being fixed.")
|
||||
fixInstances(this.options.instances.filter((instance) => {
|
||||
return instance.installed && instance.error;
|
||||
})).then(
|
||||
() => { location.reload() },
|
||||
(err) => {
|
||||
logger.error(err);
|
||||
showErrorPopup(`An error occurred while trying to fix your installations. Please reinstall Olympus manually. <br><br> You can find more info in ${path.join(__dirname, "..", "manager.log")}`);
|
||||
}
|
||||
)
|
||||
showConfirmPopup("<div style='font-size: 18px; max-width: 100%;'>One or more of your Olympus instances are not up to date! </div> If you have just updated Olympus this is normal. Press Accept and the Manager will update your instances for you. <br> Press Close to update your instances manually using the Installation Wizard", async () => {
|
||||
try {
|
||||
await DCSInstance.fixInstances();
|
||||
location.reload();
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
showErrorPopup(`An error occurred while trying to fix your installations. Please reinstall Olympus manually. <br><br> You can find more info in ${this.options.logLocation}`);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.options.installEnabled = true;
|
||||
this.options.editEnabled = this.options.instances.find(instance => instance.installed);
|
||||
this.options.uninstallEnabled = this.options.instances.find(instance => instance.installed);
|
||||
|
||||
/* Hide the loading page */
|
||||
document.getElementById("loader").classList.add("hide");
|
||||
@ -152,10 +153,18 @@ class Manager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the currently active instance, i.e. the instance that is being edited/installed/removed
|
||||
*
|
||||
* @returns The active instance
|
||||
*/
|
||||
getActiveInstance() {
|
||||
return this.options.activeInstance;
|
||||
}
|
||||
|
||||
/** Creates the options file. This is done only the very first time you start Olympus.
|
||||
*
|
||||
* @param {String} mode The mode, either Basic or Expert
|
||||
*/
|
||||
createOptionsFile(mode) {
|
||||
try {
|
||||
fs.writeFileSync("options.json", JSON.stringify({ mode: mode }));
|
||||
@ -165,6 +174,10 @@ class Manager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Switch to a different mode of operation
|
||||
*
|
||||
* @param {String} newMode The mode to switch to
|
||||
*/
|
||||
switchMode(newMode) {
|
||||
/* Change the mode in the options.json and reload the page */
|
||||
var options = JSON.parse(fs.readFileSync("options.json"));
|
||||
@ -176,23 +189,32 @@ class Manager {
|
||||
/************************************************/
|
||||
/* CALLBACKS */
|
||||
/************************************************/
|
||||
/* Switch to basic mode */
|
||||
/** Switch to basic mode
|
||||
*
|
||||
*/
|
||||
onBasicClicked() {
|
||||
this.createOptionsFile("basic");
|
||||
}
|
||||
|
||||
/* Switch to expert mode */
|
||||
/** Switch to expert mode
|
||||
*
|
||||
*/
|
||||
onExpertClicked() {
|
||||
this.createOptionsFile("expert");
|
||||
}
|
||||
|
||||
/* When the install button is clicked go the installation page */
|
||||
/** When the install button is clicked go the installation page
|
||||
*
|
||||
*/
|
||||
onInstallMenuClicked() {
|
||||
this.options.install = true;
|
||||
|
||||
if (this.options.singleInstance) {
|
||||
this.options.activeInstance = this.options.instances[0];
|
||||
if (this.options.instances.length == 0) {
|
||||
// TODO: show error
|
||||
}
|
||||
|
||||
this.options.activeInstance = this.options.instances[0];
|
||||
if (this.options.singleInstance) {
|
||||
/* Show the type selection page */
|
||||
if (!this.options.activeInstance.installed) {
|
||||
this.activePage.hide()
|
||||
@ -212,28 +234,35 @@ class Manager {
|
||||
}
|
||||
}
|
||||
|
||||
/* When the edit button is clicked go to the instances page */
|
||||
/** When the edit button is clicked go to the settings page
|
||||
*
|
||||
*/
|
||||
onEditMenuClicked() {
|
||||
this.activePage.hide()
|
||||
this.options.install = false;
|
||||
|
||||
if (this.options.singleInstance) {
|
||||
this.options.activeInstance = this.options.instances[0];
|
||||
this.typePage.show();
|
||||
this.connectionsTypePage.show();
|
||||
} else {
|
||||
this.settingsPage.show();
|
||||
}
|
||||
}
|
||||
|
||||
onFolderClicked(name) {
|
||||
this.getClickedInstance(name).then((instance) => {
|
||||
var instanceDivs = this.folderPage.getElement().querySelectorAll(".button.radio");
|
||||
for (let i = 0; i < instanceDivs.length; i++) {
|
||||
instanceDivs[i].classList.toggle('selected', instanceDivs[i].dataset.folder === instance.folder);
|
||||
if (instanceDivs[i].dataset.folder === instance.folder)
|
||||
this.options.activeInstance = instance;
|
||||
}
|
||||
});
|
||||
/** When a folder is selected, find what instance was clicked to set as active
|
||||
*
|
||||
* @param {String} name The name of the instance
|
||||
*/
|
||||
|
||||
async onFolderClicked(name) {
|
||||
var instance = await this.getClickedInstance(name);
|
||||
|
||||
var instanceDivs = this.folderPage.getElement().querySelectorAll(".button.radio");
|
||||
for (let i = 0; i < instanceDivs.length; i++) {
|
||||
instanceDivs[i].classList.toggle('selected', instanceDivs[i].dataset.folder === instance.folder);
|
||||
if (instanceDivs[i].dataset.folder === instance.folder)
|
||||
this.options.activeInstance = instance;
|
||||
}
|
||||
}
|
||||
|
||||
/* When the installation type is selected */
|
||||
@ -243,7 +272,7 @@ class Manager {
|
||||
if (this.options.activeInstance)
|
||||
this.options.activeInstance.installationType = type;
|
||||
else
|
||||
showErrorPopup("A critical error has occurred. Please restart the Manager.")
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
|
||||
/* When the connections type is selected */
|
||||
@ -253,15 +282,24 @@ class Manager {
|
||||
if (this.options.activeInstance)
|
||||
this.options.activeInstance.connectionsType = type;
|
||||
else
|
||||
showErrorPopup("A critical error has occurred. Please restart the Manager.")
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
|
||||
/* When the next button of a wizard page is clicked */
|
||||
onNextClicked() {
|
||||
/* Choose which page to show depending on the active page */
|
||||
if (this.activePage == this.folderPage) {
|
||||
this.activePage.hide();
|
||||
this.typePage.show();
|
||||
if (this.options.activeInstance.installed) {
|
||||
showConfirmPopup("<div style='font-size: 18px; max-width: 100%; margin-bottom: 8px;'> Olympus is already installed in this instance! </div> If you click Accept, it will be installed again and all changes, e.g. custom databases or mods support, will be lost. Are you sure you want to continue?",
|
||||
() => {
|
||||
this.activePage.hide()
|
||||
this.typePage.show();
|
||||
}
|
||||
)
|
||||
} else {
|
||||
this.activePage.hide();
|
||||
this.typePage.show();
|
||||
}
|
||||
} else if (this.activePage == this.typePage) {
|
||||
this.activePage.hide();
|
||||
this.connectionsTypePage.show();
|
||||
@ -277,13 +315,13 @@ class Manager {
|
||||
this.connectionsPage.getElement().querySelector(".backend-address .checkbox").classList.toggle("checked", this.options.activeInstance.backendAddress === '*')
|
||||
}
|
||||
} else {
|
||||
showErrorPopup("A critical error has occurred. Please restart the Manager.")
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
} else if (this.activePage == this.connectionsPage) {
|
||||
this.options.activeInstance.checkClientPort(this.options.activeInstance.clientPort).then(
|
||||
this.options.activeInstance.checkClientPort().then(
|
||||
(portFree) => {
|
||||
if (portFree) {
|
||||
return this.options.activeInstance.checkBackendPort(this.options.activeInstance.backendPort);
|
||||
return this.options.activeInstance.checkBackendPort();
|
||||
} else {
|
||||
return Promise.reject('Port not free');
|
||||
}
|
||||
@ -297,13 +335,12 @@ class Manager {
|
||||
}).catch(() => {
|
||||
showErrorPopup('Please, make sure both the client and backend ports are free!');
|
||||
}
|
||||
);
|
||||
);
|
||||
} else if (this.activePage == this.passwordsPage) {
|
||||
if (this.options.activeInstance) {
|
||||
if (this.options.activeInstance.installed && !this.options.activeInstance.arePasswordsEdited()) {
|
||||
this.activePage.hide();
|
||||
showWaitPopup(`<span>Please wait while Olympus is being installed in <i>${this.options.activeInstance.name}</i></span><div class="loading-bar" style="width: 100%; height: 10px;"></div><div class="loading-message" style="font-weight: normal; text-align: center;"></div>`);
|
||||
this.options.activeInstance.install();
|
||||
this.options.install? this.options.activeInstance.install(): this.options.activeInstance.edit();
|
||||
}
|
||||
else {
|
||||
if (!this.options.activeInstance.arePasswordsSet()) {
|
||||
@ -312,12 +349,11 @@ class Manager {
|
||||
showErrorPopup('Please, set different passwords for the Game Master, Blue Commander, and Red Commander roles!');
|
||||
} else {
|
||||
this.activePage.hide();
|
||||
showWaitPopup(`<span>Please wait while Olympus is being installed in <i>${this.options.activeInstance.name}</i></span><div class="loading-bar" style="width: 100%; height: 10px;"></div><div class="loading-message" style="font-weight: normal; text-align: center;"></div>`);
|
||||
this.options.activeInstance.install();
|
||||
this.options.install? this.options.activeInstance.install(): this.options.activeInstance.edit();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showErrorPopup("A critical error has occurred. Please restart the Manager.")
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -342,7 +378,7 @@ class Manager {
|
||||
this.passwordsPage.getElement().querySelector(".red-commander input").value = "";
|
||||
}
|
||||
else
|
||||
showErrorPopup("A critical error has occurred. Please restart the Manager.")
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
|
||||
onBlueCommanderPasswordChanged(value) {
|
||||
@ -354,7 +390,7 @@ class Manager {
|
||||
this.passwordsPage.getElement().querySelector(".red-commander input").value = "";
|
||||
}
|
||||
else
|
||||
showErrorPopup("A critical error has occurred. Please restart the Manager.")
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
|
||||
onRedCommanderPasswordChanged(value) {
|
||||
@ -366,7 +402,7 @@ class Manager {
|
||||
this.passwordsPage.getElement().querySelector(".blue-commander input").value = "";
|
||||
}
|
||||
else
|
||||
showErrorPopup("A critical error has occurred. Please restart the Manager.")
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
|
||||
/* When the client port input value is changed */
|
||||
@ -390,7 +426,7 @@ class Manager {
|
||||
this.connectionsPage.getElement().querySelector(".note.warning").classList.toggle("hide", this.options.activeInstance.backendAddress !== '*')
|
||||
this.connectionsPage.getElement().querySelector(".backend-address .checkbox").classList.toggle("checked", this.options.activeInstance.backendAddress === '*')
|
||||
} else {
|
||||
showErrorPopup("A critical error has occurred. Please restart the Manager.")
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -405,57 +441,62 @@ class Manager {
|
||||
}
|
||||
|
||||
async onStartServerClicked(name) {
|
||||
this.getClickedInstanceDiv(name).then((div) => div.querySelector(".collapse").classList.add("loading"));
|
||||
this.getClickedInstance(name).then((instance) => instance.startServer());
|
||||
var div = await this.getClickedInstanceDiv(name);
|
||||
div.querySelector(".collapse").classList.add("loading")
|
||||
var instance = await this.getClickedInstance(name);
|
||||
instance.startServer();
|
||||
}
|
||||
|
||||
async onStartClientClicked(name) {
|
||||
this.getClickedInstanceDiv(name).then((div) => div.querySelector(".collapse").classList.add("loading"));
|
||||
this.getClickedInstance(name).then(instance => instance.startClient());
|
||||
var div = await this.getClickedInstanceDiv(name);
|
||||
div.querySelector(".collapse").classList.add("loading")
|
||||
var instance = await this.getClickedInstance(name);
|
||||
instance.startClient();
|
||||
}
|
||||
|
||||
async onOpenBrowserClicked(name) {
|
||||
this.getClickedInstance(name).then((instance) => exec(`start http://localhost:${instance.clientPort}`));
|
||||
var instance = await this.getClickedInstance(name);
|
||||
exec(`start http://localhost:${instance.clientPort}`)
|
||||
}
|
||||
|
||||
async onStopClicked(name) {
|
||||
this.getClickedInstance(name).then((instance) => instance.stop());
|
||||
var instance = await this.getClickedInstance(name);
|
||||
instance.stop();
|
||||
}
|
||||
|
||||
async onEditClicked(name) {
|
||||
this.getClickedInstance(name).then((instance) => {
|
||||
if (instance.webserverOnline || instance.backendOnline) {
|
||||
showErrorPopup("Error, the selected Olympus instance is currently active, please stop Olympus before editing it!")
|
||||
} else {
|
||||
this.options.activeInstance = instance;
|
||||
this.activePage.hide();
|
||||
this.typePage.show();
|
||||
}
|
||||
});
|
||||
var instance = await this.getClickedInstance(name);
|
||||
if (instance.webserverOnline || instance.backendOnline) {
|
||||
showErrorPopup("Error, the selected Olympus instance is currently active, please stop Olympus before editing it!")
|
||||
} else {
|
||||
this.options.activeInstance = instance;
|
||||
this.options.install = false;
|
||||
this.activePage.hide();
|
||||
this.connectionsTypePage.show();
|
||||
}
|
||||
}
|
||||
|
||||
async onInstallClicked(name) {
|
||||
this.getClickedInstance(name).then((instance) => {
|
||||
this.options.activeInstance = instance;
|
||||
this.options.install = true;
|
||||
this.options.singleInstance = false;
|
||||
this.activePage.hide();
|
||||
this.typePage.show();
|
||||
});
|
||||
var instance = await this.getClickedInstance(name);
|
||||
this.options.activeInstance = instance;
|
||||
this.options.install = true;
|
||||
this.options.singleInstance = false;
|
||||
this.activePage.hide();
|
||||
this.typePage.show();
|
||||
}
|
||||
|
||||
async onUninstallClicked(name) {
|
||||
this.getClickedInstance(name).then((instance) => {
|
||||
instance.webserverOnline || instance.backendOnline ? showErrorPopup("Error, the selected Olympus instance is currently active, please stop Olympus before uninstalling it!") : instance.uninstall();
|
||||
});
|
||||
var instance = await this.getClickedInstance(name);
|
||||
|
||||
if (instance.webserverOnline || instance.backendOnline)
|
||||
showErrorPopup("Error, the selected Olympus instance is currently active, please stop Olympus before uninstalling it!")
|
||||
else
|
||||
await instance.uninstall();
|
||||
}
|
||||
|
||||
async getClickedInstance(name) {
|
||||
return DCSInstance.getInstances().then((instances) => {
|
||||
return instances.find((instance) => {
|
||||
return instance.name === name
|
||||
})
|
||||
});
|
||||
var instances = await DCSInstance.getInstances()
|
||||
return instances.find((instance) => { return instance.name === name; });
|
||||
}
|
||||
|
||||
async getClickedInstanceDiv(name) {
|
||||
@ -472,7 +513,7 @@ class Manager {
|
||||
/* Set the selected port to the dcs instance */
|
||||
async setPort(port, value) {
|
||||
var success;
|
||||
if (port === 'client'){
|
||||
if (port === 'client') {
|
||||
success = await this.options.activeInstance.checkClientPort(value);
|
||||
this.options.activeInstance.setClientPort(value);
|
||||
}
|
||||
@ -545,14 +586,6 @@ class Manager {
|
||||
style.setProperty('--percent', `${percent}%`);
|
||||
}
|
||||
}
|
||||
|
||||
setPopupLoadingProgress(message, percent) {
|
||||
document.querySelector("#popup .loading-message").innerHTML = message;
|
||||
if (percent) {
|
||||
var style = document.querySelector('#popup .loading-bar').style;
|
||||
style.setProperty('--percent', `${percent}%`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Manager;
|
||||
@ -4,7 +4,7 @@ const { logger } = require("./filesystem")
|
||||
/** Checks if a port is already in use
|
||||
*
|
||||
*/
|
||||
function checkPort(port, callback) {
|
||||
function checkPortSync(port, callback) {
|
||||
portfinder.getPort({ port: port, stopPort: port }, (err, res) => {
|
||||
if (err !== null) {
|
||||
logger.error(`Port ${port} already in use`);
|
||||
@ -15,6 +15,14 @@ function checkPort(port, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function checkPort(port) {
|
||||
return portfinder.getPortPromise({ port: port, stopPort: port });
|
||||
}
|
||||
|
||||
function getFreePort(startPort) {
|
||||
return portfinder.getPortPromise({ port: startPort });
|
||||
}
|
||||
|
||||
/** Performs a fetch request, with a configurable timeout
|
||||
*
|
||||
*/
|
||||
@ -34,6 +42,8 @@ async function fetchWithTimeout(resource, options = {}) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getFreePort: getFreePort,
|
||||
checkPort: checkPort,
|
||||
checkPortSync: checkPortSync,
|
||||
fetchWithTimeout: fetchWithTimeout
|
||||
}
|
||||
|
||||
@ -48,6 +48,17 @@ function showWaitPopup(message) {
|
||||
document.getElementById("popup").querySelector(".content").innerHTML = message;
|
||||
}
|
||||
|
||||
function showWaitLoadingPopup(message) {
|
||||
document.getElementById("grayout").classList.remove("hide");
|
||||
document.getElementById("popup").classList.remove("hide");
|
||||
document.getElementById("popup").querySelector(".error").classList.add("hide");
|
||||
document.getElementById("popup").querySelector(".wait").classList.remove("hide");
|
||||
document.getElementById("popup").querySelector(".confirm").classList.add("hide");
|
||||
document.getElementById("popup").querySelector(".close-popup").classList.add("hide");
|
||||
document.getElementById("popup").querySelector(".accept-popup").classList.add("hide");
|
||||
document.getElementById("popup").querySelector(".content").innerHTML = `${message}<div class="loading-bar" style="width: 100%; height: 10px;"></div><div class="loading-message" style="font-weight: normal; text-align: center;"></div>` ;
|
||||
}
|
||||
|
||||
function showConfirmPopup(message, onAcceptCallback, onCloseCallback) {
|
||||
document.getElementById("grayout").classList.remove("hide");
|
||||
document.getElementById("popup").classList.remove("hide");
|
||||
@ -79,10 +90,20 @@ function hidePopup() {
|
||||
document.getElementById("popup").classList.add("hide");
|
||||
}
|
||||
|
||||
function setPopupLoadingProgress(message, percent) {
|
||||
document.querySelector("#popup .loading-message").innerHTML = message;
|
||||
if (percent) {
|
||||
var style = document.querySelector('#popup .loading-bar').style;
|
||||
style.setProperty('--percent', `${percent}%`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
showInfoPopup: showInfoPopup,
|
||||
showErrorPopup: showErrorPopup,
|
||||
showConfirmPopup: showConfirmPopup,
|
||||
showWaitPopup: showWaitPopup,
|
||||
hidePopup: hidePopup
|
||||
showWaitLoadingPopup: showWaitLoadingPopup,
|
||||
hidePopup: hidePopup,
|
||||
setPopupLoadingProgress: setPopupLoadingProgress
|
||||
}
|
||||
@ -44,12 +44,7 @@ function checkVersion() {
|
||||
/* If the current version is newer than the latest release, the user is probably a developer. Ask for a beta update */
|
||||
else if (reg2[0] > reg1[0] || (reg2[0] == reg1[0] && reg2[1] > reg1[1]) || (reg2[0] == reg1[0] && reg2[1] == reg1[1] && reg2[2] > reg1[2])) {
|
||||
logger.log(`Beta version detected: ${res["version"]} vs ${VERSION}`);
|
||||
showConfirmPopup(`You are currently running DCS Olympus ${VERSION}, which is newer than the latest release version. Do you want to download the latest beta version? <div style="max-width: 100%; color: orange">Note: DCS and Olympus MUST be stopped before proceeding.</div>`,
|
||||
() => {
|
||||
updateOlympusBeta();
|
||||
}, () => {
|
||||
logger.log("Update canceled");
|
||||
})
|
||||
updateOlympusBeta();
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -73,27 +68,34 @@ async function updateOlympusBeta() {
|
||||
/* Select the newest artifact */
|
||||
var artifact = artifacts.find((artifact) => { return artifact.name = "development_build_not_a_release" });
|
||||
|
||||
showConfirmPopup(`Latest beta artifact has a timestamp of ${artifact.updated_at}. Do you want to continue?`, () => {
|
||||
/* Run the browser and download the artifact */ //TODO: try and directly download the file from code rather than using the browser
|
||||
exec(`start https://github.com/Pax1601/DCSOlympus/actions/runs/${artifact.workflow_run.id}/artifacts/${artifact.id}`)
|
||||
showConfirmPopup('A browser window was opened to download the beta artifact. Please wait for the download to complete, then press "Accept" and select the downloaded beta artifact.',
|
||||
() => {
|
||||
/* Ask the user to select the downloaded file */
|
||||
var input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.click();
|
||||
input.onchange = e => {
|
||||
/* Run the update process */
|
||||
updateOlympus(e.target.files[0])
|
||||
}
|
||||
const date1 = new Date(artifact.updated_at);
|
||||
const date2 = fs.statSync(path.join("package.json")).birthtime;
|
||||
if (date1 > date2) {
|
||||
showConfirmPopup(`<span>Latest beta artifact has a timestamp of <i style="color: orange">${artifact.updated_at}</i>, while your installation was created on <i style="color: orange">${date2.toISOString()}</i>. Do you want to update to the newest beta version?</span>`, () => {
|
||||
/* Run the browser and download the artifact */ //TODO: try and directly download the file from code rather than using the browser
|
||||
exec(`start https://github.com/Pax1601/DCSOlympus/actions/runs/${artifact.workflow_run.id}/artifacts/${artifact.id}`)
|
||||
showConfirmPopup('A browser window was opened to download the beta artifact. Please wait for the download to complete, then press "Accept" and select the downloaded beta artifact.',
|
||||
() => {
|
||||
/* Ask the user to select the downloaded file */
|
||||
var input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.click();
|
||||
input.onchange = e => {
|
||||
/* Run the update process */
|
||||
updateOlympus(e.target.files[0])
|
||||
}
|
||||
},
|
||||
() => {
|
||||
logger.log("Update canceled");
|
||||
});
|
||||
},
|
||||
() => {
|
||||
logger.log("Update canceled");
|
||||
});
|
||||
},
|
||||
() => {
|
||||
logger.log("Update canceled");
|
||||
})
|
||||
}
|
||||
)
|
||||
} else {
|
||||
logger.log("Build is latest")
|
||||
}
|
||||
}
|
||||
|
||||
/** Update Olympus to the lastest release
|
||||
|
||||
10
manager/javascripts/utils.js
Normal file
10
manager/javascripts/utils.js
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
|
||||
async function sleep(ms) {
|
||||
await new Promise(r => setTimeout(r, ms));
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sleep: sleep
|
||||
}
|
||||
@ -1,97 +0,0 @@
|
||||
|
||||
/* Get the list of DCS instances */
|
||||
var instances = await DCSInstance.getInstances();
|
||||
|
||||
/* If there is only 1 DCS Instance and Olympus is not installed in it, go straight to the installation page (since there is nothing else to do) */
|
||||
this.basic = instances.length === 1 && !instances[0].installed;
|
||||
|
||||
document.getElementById("loader").classList.add("hide");
|
||||
|
||||
/* Check if there are corrupted or outdate instances */
|
||||
if (instances.some((instance) => {
|
||||
return instance.installed && instance.error;
|
||||
})) {
|
||||
/* Ask the user for confirmation */
|
||||
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(
|
||||
() => { location.reload() },
|
||||
(err) => {
|
||||
logger.error(err);
|
||||
showErrorPopup(`An error occurred while trying to fix your installations. Please reinstall Olympus manually. <br><br> You can find more info in ${path.join(__dirname, "..", "manager.log")}`);
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/* Check which buttons should be enabled */
|
||||
const installEnabled = true;
|
||||
const manageEnabled = instances.some((instance) => { return instance.installed; });
|
||||
|
||||
/* Menu */
|
||||
var menuPage = new MenuPage(this, {
|
||||
installEnabled: installEnabled,
|
||||
manageEnabled: manageEnabled
|
||||
});
|
||||
|
||||
/* Installations */
|
||||
this.installationPage = new installationPage(this, {
|
||||
instances: instances
|
||||
});
|
||||
|
||||
/* Instances */
|
||||
this.instancesPage = new InstancesPage(this, {
|
||||
instances: instances.filter((instance) => {
|
||||
return instance.installed;
|
||||
})
|
||||
});
|
||||
|
||||
/* Connections */
|
||||
this.connectionsPage = new ConnectionsPage(this);
|
||||
|
||||
/* Passwords */
|
||||
this.passwordsPage = new PasswordsPage(this);
|
||||
|
||||
/* Result */
|
||||
this.resultPage = new ResultPage(this, {
|
||||
logLocation: path.join(__dirname, "..", "manager.log")
|
||||
});
|
||||
|
||||
/* Create all the HTML pages */
|
||||
document.body.appendChild(this.menuPage.getElement());
|
||||
document.body.appendChild(this.installationPage.getElement());
|
||||
document.body.appendChild(this.instancesPage.getElement());
|
||||
document.body.appendChild(this.connectionsPage.getElement());
|
||||
document.body.appendChild(this.passwordsPage.getElement());
|
||||
document.body.appendChild(this.resultPage.getElement());
|
||||
|
||||
/* In basic mode we directly show the connections page */
|
||||
if (this.basic) {
|
||||
const options = {
|
||||
instance: instances[0],
|
||||
basic: this.basic,
|
||||
install: true
|
||||
}
|
||||
connectionsPage.options = {
|
||||
...connectionsPage.options,
|
||||
...options
|
||||
}
|
||||
passwordsPage.options = {
|
||||
...passwordsPage.options,
|
||||
...options
|
||||
}
|
||||
resultPage.options = {
|
||||
...resultPage.options,
|
||||
...options
|
||||
}
|
||||
|
||||
/* Show the connections page directly */
|
||||
instancesPage.hide();
|
||||
connectionsPage.show();
|
||||
} else {
|
||||
/* Show the main menu */
|
||||
menuPage.show();
|
||||
}
|
||||
}
|
||||
@ -1,102 +0,0 @@
|
||||
const ManagerPage = require("./managerpage");
|
||||
const ejs = require('ejs')
|
||||
const { logger } = require("./filesystem")
|
||||
|
||||
/** Connections page, allows the user to set the ports and address for each Olympus instance
|
||||
*
|
||||
*/
|
||||
class ConnectionsPage extends ManagerPage {
|
||||
constructor(manager, options) {
|
||||
super(manager, options);
|
||||
}
|
||||
|
||||
render(str) {
|
||||
const element = this.getElement();
|
||||
element.innerHTML = str;
|
||||
|
||||
if (this.element.querySelector(".button.auto"))
|
||||
this.element.querySelector(".button.auto").addEventListener("click", (e) => this.onOptionSelected(true));
|
||||
|
||||
if (this.element.querySelector(".button.manual"))
|
||||
this.element.querySelector(".button.manual").addEventListener("click", (e) => this.onOptionSelected(false));
|
||||
|
||||
if (this.element.querySelector(".client-port"))
|
||||
this.element.querySelector(".client-port").querySelector("input").addEventListener("change", async (e) => { this.setClientPort(Number(e.target.value)); })
|
||||
|
||||
if (this.element.querySelector(".backend-port"))
|
||||
this.element.querySelector(".backend-port").querySelector("input").addEventListener("change", async (e) => { this.setBackendPort(Number(e.target.value)); })
|
||||
|
||||
if (this.element.querySelector(".backend-address"))
|
||||
this.element.querySelector(".backend-address").querySelector("input").addEventListener("change", async (e) => { this.manager.getActiveInstance().setBackendAddress(e.target.value); })
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
show(previousPage) {
|
||||
ejs.renderFile("./ejs/connections.ejs", {...this.options, ...this.manager.options}, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
|
||||
/* Call the port setters to check if the ports are free */
|
||||
this.setClientPort(this.manager.getActiveInstance().clientPort);
|
||||
this.setBackendPort(this.manager.getActiveInstance().backendPort);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show(previousPage);
|
||||
}
|
||||
|
||||
onNextClicked() {
|
||||
this.hide();
|
||||
this.manager.passwordsPage.show(this);
|
||||
}
|
||||
|
||||
onCancelClicked() {
|
||||
this.hide();
|
||||
this.manager.menuPage.show()
|
||||
}
|
||||
|
||||
onOptionSelected(auto) {
|
||||
this.options.selectAutoOrManual = false;
|
||||
this.options.auto = auto;
|
||||
if (auto) {
|
||||
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
/** Asynchronously check if the client port is free and if it is, set the new value
|
||||
*
|
||||
*/
|
||||
async setClientPort(newPort) {
|
||||
const success = await this.manager.getActiveInstance().setClientPort(newPort);
|
||||
var successEls = this.element.querySelector(".client-port").querySelectorAll(".success");
|
||||
for (let i = 0; i < successEls.length; i++) {
|
||||
successEls[i].classList.toggle("hide", !success);
|
||||
}
|
||||
var errorEls = this.element.querySelector(".client-port").querySelectorAll(".error");
|
||||
for (let i = 0; i < errorEls.length; i++) {
|
||||
errorEls[i].classList.toggle("hide", success);
|
||||
}
|
||||
}
|
||||
|
||||
/** Asynchronously check if the backend port is free and if it is, set the new value
|
||||
*
|
||||
*/
|
||||
async setBackendPort(newPort) {
|
||||
const success = await this.manager.getActiveInstance().setBackendPort(newPort);
|
||||
var successEls = this.element.querySelector(".backend-port").querySelectorAll(".success");
|
||||
for (let i = 0; i < successEls.length; i++) {
|
||||
successEls[i].classList.toggle("hide", !success);
|
||||
}
|
||||
var errorEls = this.element.querySelector(".backend-port").querySelectorAll(".error");
|
||||
for (let i = 0; i < errorEls.length; i++) {
|
||||
errorEls[i].classList.toggle("hide", success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConnectionsPage;
|
||||
@ -1,67 +0,0 @@
|
||||
const DCSInstance = require("./dcsinstance");
|
||||
const ManagerPage = require("./managerpage");
|
||||
const ejs = require('ejs')
|
||||
const { logger } = require("./filesystem");
|
||||
const { showConfirmPopup } = require("./popup");
|
||||
|
||||
class installationPage extends ManagerPage {
|
||||
constructor(manager, options) {
|
||||
super(manager, options);
|
||||
}
|
||||
|
||||
render(str) {
|
||||
this.element.innerHTML = str;
|
||||
|
||||
var options = this.element.querySelectorAll(".option");
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
options[i].onclick = (e) => {this.onOptionClicked(e);}
|
||||
}
|
||||
|
||||
if (this.element.querySelector(".cancel"))
|
||||
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
async onOptionClicked(e) {
|
||||
this.onInstanceSelection((await DCSInstance.getInstances()).find((instance) => {return instance.folder === e.target.dataset.folder}));
|
||||
}
|
||||
|
||||
show(previousPage) {
|
||||
ejs.renderFile("./ejs/installations.ejs", {...this.options, ...this.manager.options}, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show(previousPage);
|
||||
}
|
||||
|
||||
onInstanceSelection(activeInstance) {
|
||||
this.manager.options.activeInstance = activeInstance;
|
||||
this.manager.options.install = !activeInstance.installed;
|
||||
|
||||
/* Show the connections page */
|
||||
if (!activeInstance.installed || !this.managers.options.install) {
|
||||
this.hide();
|
||||
this.manager.typePage.show(this);
|
||||
} else {
|
||||
showConfirmPopup("<div style='font-size: 18px; max-width: 100%; margin-bottom: 15px;'> Olympus is already installed in this instance! </div> If you click Accept, it will be installed again and all changes, e.g. custom databases or mods support, will be lost. Are you sure you want to continue?",
|
||||
() => {
|
||||
this.hide();
|
||||
this.manager.typePage.show(this);
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
onCancelClicked(e) {
|
||||
/* Go back to the main menu */
|
||||
this.hide();
|
||||
this.manager.menuPage.show();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = installationPage;
|
||||
@ -1,169 +0,0 @@
|
||||
const DCSInstance = require("./dcsinstance");
|
||||
const ManagerPage = require("./managerpage");
|
||||
const ejs = require('ejs');
|
||||
const { showErrorPopup } = require("./popup");
|
||||
const { exec } = require("child_process");
|
||||
const { logger } = require("./filesystem")
|
||||
|
||||
class InstancesPage extends ManagerPage {
|
||||
startInstance;
|
||||
|
||||
constructor(manager, options) {
|
||||
super(manager, options);
|
||||
}
|
||||
|
||||
render(str) {
|
||||
this.element.innerHTML = str;
|
||||
|
||||
var editButtons = this.element.querySelectorAll(".button.edit");
|
||||
for (let i = 0; i < editButtons.length; i++) {
|
||||
editButtons[i].onclick = (e) => {this.onEditClicked(e);}
|
||||
}
|
||||
|
||||
var installButtons = this.element.querySelectorAll(".button.install");
|
||||
for (let i = 0; i < installButtons.length; i++) {
|
||||
installButtons[i].onclick = (e) => {this.onInstallClicked(e);}
|
||||
}
|
||||
|
||||
var uninstallButtons = this.element.querySelectorAll(".button.uninstall");
|
||||
for (let i = 0; i < uninstallButtons.length; i++) {
|
||||
uninstallButtons[i].onclick = (e) => {this.onUninstallClicked(e);}
|
||||
}
|
||||
|
||||
var startServerButtons = this.element.querySelectorAll(".button.start-server");
|
||||
for (let i = 0; i < startServerButtons.length; i++) {
|
||||
startServerButtons[i].onclick = (e) => {this.onStartServerClicked(e);}
|
||||
}
|
||||
|
||||
var startClientButtons = this.element.querySelectorAll(".button.start-client");
|
||||
for (let i = 0; i < startClientButtons.length; i++) {
|
||||
startClientButtons[i].onclick = (e) => {this.onStartClientClicked(e);}
|
||||
}
|
||||
|
||||
var openBrowserButtons = this.element.querySelectorAll(".button.open-browser");
|
||||
for (let i = 0; i < openBrowserButtons.length; i++) {
|
||||
openBrowserButtons[i].onclick = (e) => {this.onOpenBrowserClicked(e);}
|
||||
}
|
||||
|
||||
var stopButtons = this.element.querySelectorAll(".button.stop");
|
||||
for (let i = 0; i < stopButtons.length; i++) {
|
||||
stopButtons[i].onclick = (e) => {this.onStopClicked(e);}
|
||||
}
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
async onStartServerClicked(e) {
|
||||
e.target.closest(".collapse").classList.add("loading");
|
||||
this.getClickedInstance(e).then((instance) => instance.startServer());
|
||||
}
|
||||
|
||||
async onStartClientClicked(e) {
|
||||
e.target.closest(".collapse").classList.add("loading");
|
||||
this.getClickedInstance(e).then(instance => instance.startClient());
|
||||
}
|
||||
|
||||
async onOpenBrowserClicked(e) {
|
||||
this.getClickedInstance(e).then((instance) => exec(`start http://localhost:${instance.clientPort}`));
|
||||
}
|
||||
|
||||
async onStopClicked(e) {
|
||||
this.getClickedInstance(e).then((instance) => instance.stop());
|
||||
}
|
||||
|
||||
async onEditClicked(e) {
|
||||
this.getClickedInstance(e).then((instance) => {
|
||||
if (instance.webserverOnline || instance.backendOnline) {
|
||||
showErrorPopup("Error, the selected Olympus instance is currently active, please stop Olympus before editing it!")
|
||||
} else {
|
||||
this.manager.options.activeInstance = instance;
|
||||
this.manager.options.install = false;
|
||||
this.manager.options.singleInstance = false;
|
||||
this.hide();
|
||||
this.manager.typePage.show(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async onInstallClicked(e) {
|
||||
this.getClickedInstance(e).then((instance) => {
|
||||
this.manager.options.activeInstance = instance;
|
||||
this.manager.options.install = true;
|
||||
this.manager.options.singleInstance = false;
|
||||
this.hide();
|
||||
this.manager.typePage.show(this);
|
||||
});
|
||||
}
|
||||
|
||||
async onUninstallClicked(e) {
|
||||
this.getClickedInstance(e).then((instance) => {
|
||||
instance.webserverOnline || instance.backendOnline? showErrorPopup("Error, the selected Olympus instance is currently active, please stop Olympus before uninstalling it!") : instance.uninstall();
|
||||
});
|
||||
}
|
||||
|
||||
async getClickedInstance(e) {
|
||||
return DCSInstance.getInstances().then((instances) => {
|
||||
return instances.find((instance) => {
|
||||
return instance.folder === e.target.closest('.option').dataset.folder
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
show(previousPage) {
|
||||
ejs.renderFile("./ejs/instances.ejs", {...this.options, ...this.manager.options}, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
var instanceDivs = this.element.querySelectorAll(`.option`);
|
||||
for (let i = 0; i < instanceDivs.length; i++) {
|
||||
var instanceDiv = instanceDivs[i];
|
||||
var instance = this.manager.options.instances.find((instance) => { return instance.folder === instanceDivs[i].dataset.folder;})
|
||||
if (instance) {
|
||||
instanceDiv.querySelector(".button.install").classList.toggle("hide", instance.installed);
|
||||
instanceDiv.querySelector(".button.start").classList.toggle("hide", !instance.installed)
|
||||
instanceDiv.querySelector(".button.uninstall").classList.toggle("hide", !instance.installed)
|
||||
instanceDiv.querySelector(".button.edit").classList.toggle("hide", !instance.installed)
|
||||
}
|
||||
}
|
||||
|
||||
super.show(previousPage);
|
||||
}
|
||||
|
||||
update() {
|
||||
|
||||
var instanceDivs = this.element.querySelectorAll(`.option`);
|
||||
for (let i = 0; i < instanceDivs.length; i++) {
|
||||
var instance = this.manager.options.instances.find((instance) => { return instance.folder === instanceDivs[i].dataset.folder;})
|
||||
if (instance && instance.installed) {
|
||||
var instanceDiv = instanceDivs[i];
|
||||
if (instanceDiv.querySelector(".webserver.online") !== null) {
|
||||
instanceDiv.querySelector(".webserver.online").classList.toggle("hide", !instance.webserverOnline)
|
||||
instanceDiv.querySelector(".webserver.offline").classList.toggle("hide", instance.webserverOnline)
|
||||
instanceDiv.querySelector(".backend.online").classList.toggle("hide", !instance.backendOnline)
|
||||
instanceDiv.querySelector(".backend.offline").classList.toggle("hide", instance.backendOnline)
|
||||
|
||||
if (this.backendOnline) {
|
||||
instanceDiv.querySelector(".fps .data").innerText = instance.fps;
|
||||
instanceDiv.querySelector(".load .data").innerText = instance.load;
|
||||
}
|
||||
|
||||
instanceDiv.querySelector(".button.start").classList.toggle("hide", instance.webserverOnline)
|
||||
instanceDiv.querySelector(".button.uninstall").classList.toggle("hide", instance.webserverOnline)
|
||||
instanceDiv.querySelector(".button.edit").classList.toggle("hide", instance.webserverOnline)
|
||||
instanceDiv.querySelector(".button.open-browser").classList.toggle("hide", !instance.webserverOnline)
|
||||
instanceDiv.querySelector(".button.stop").classList.toggle("hide", !instance.webserverOnline)
|
||||
|
||||
if (this.webserverOnline)
|
||||
instanceDiv.querySelector(".button.start").classList.remove("loading")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InstancesPage;
|
||||
@ -1,45 +0,0 @@
|
||||
const ManagerPage = require("./managerpage");
|
||||
const ejs = require('ejs')
|
||||
const { logger } = require("./filesystem")
|
||||
|
||||
class PasswordsPage extends ManagerPage {
|
||||
constructor(manager, options) {
|
||||
super(manager, options);
|
||||
}
|
||||
|
||||
render(str) {
|
||||
const element = this.getElement();
|
||||
element.innerHTML = str;
|
||||
|
||||
this.element.querySelector(".game-master").querySelector("input").addEventListener("change", async (e) => { this.manager.getActiveInstance().setGameMasterPassword(e.target.value); })
|
||||
this.element.querySelector(".blue-commander").querySelector("input").addEventListener("change", async (e) => { this.manager.getActiveInstance().setBlueCommanderPassword(e.target.value); })
|
||||
this.element.querySelector(".red-commander").querySelector("input").addEventListener("change", async (e) => { this.manager.getActiveInstance().setRedCommanderPassword(e.target.value); })
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
show(previousPage) {
|
||||
ejs.renderFile("./ejs/passwords.ejs", {...this.options, ...this.manager.options}, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show(previousPage);
|
||||
}
|
||||
|
||||
onNextClicked() {
|
||||
this.hide();
|
||||
this.manager.resultPage.show();
|
||||
this.manager.resultPage.startInstallation();
|
||||
}
|
||||
|
||||
onCancelClicked() {
|
||||
this.hide();
|
||||
this.manager.menuPage.show()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PasswordsPage;
|
||||
@ -1,113 +0,0 @@
|
||||
const { installMod, installHooks, installJSON, applyConfiguration, installShortCuts } = require("./filesystem");
|
||||
const ManagerPage = require("./managerpage");
|
||||
const ejs = require('ejs')
|
||||
const { logger } = require("./filesystem")
|
||||
|
||||
class ResultPage extends ManagerPage {
|
||||
constructor(manager, options) {
|
||||
super(manager, options);
|
||||
}
|
||||
|
||||
render(str) {
|
||||
const element = this.getElement();
|
||||
element.innerHTML = str;
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
show(previousPage) {
|
||||
ejs.renderFile("./ejs/result.ejs", {...this.options, ...this.manager.options}, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show(previousPage);
|
||||
}
|
||||
|
||||
onBackClicked() {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/** Installation is performed by using an then chain of async functions. Installation is aborted on any error along the chain
|
||||
*
|
||||
*/
|
||||
startInstallation() {
|
||||
installHooks(this.manager.getActiveInstance().folder).then(
|
||||
() => {
|
||||
this.applyStepSuccess(".hook");
|
||||
},
|
||||
(err) => {
|
||||
this.applyStepFailure(".hook");
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(() => installMod(this.manager.getActiveInstance().folder, this.manager.getActiveInstance().name)).then(
|
||||
() => {
|
||||
this.applyStepSuccess(".mod");
|
||||
},
|
||||
(err) => {
|
||||
this.applyStepFailure(".mod");
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(() => installJSON(this.manager.getActiveInstance().folder)).then(
|
||||
() => {
|
||||
this.applyStepSuccess(".json");
|
||||
},
|
||||
(err) => {
|
||||
this.applyStepFailure(".json");
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(() => applyConfiguration(this.manager.getActiveInstance().folder, this.manager.getActiveInstance())).then(
|
||||
() => {
|
||||
this.applyStepSuccess(".config");
|
||||
},
|
||||
(err) => {
|
||||
this.applyStepFailure(".config");
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(() => installShortCuts(this.manager.getActiveInstance().folder, this.manager.getActiveInstance().name)).then(
|
||||
() => {
|
||||
this.applyStepSuccess(".shortcuts");
|
||||
},
|
||||
(err) => {
|
||||
this.applyStepFailure(".shortcuts");
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(
|
||||
() => {
|
||||
this.element.querySelector(".summary.success").classList.remove("hide");
|
||||
this.element.querySelector(".summary.error").classList.add("hide");
|
||||
this.element.querySelector(".info.success").classList.remove("hide");
|
||||
this.element.querySelector(".info.error").classList.add("hide");
|
||||
this.element.querySelector(".result .success").classList.remove("hide");
|
||||
this.element.querySelector(".result .error").classList.add("hide");
|
||||
this.element.querySelector(".result .wait").classList.add("hide");
|
||||
},
|
||||
() => {
|
||||
this.element.querySelector(".summary.success").classList.add("hide");
|
||||
this.element.querySelector(".summary.error").classList.remove("hide");
|
||||
this.element.querySelector(".info.success").classList.add("hide");
|
||||
this.element.querySelector(".info.error").classList.remove("hide");
|
||||
this.element.querySelector(".result .success").classList.add("hide");
|
||||
this.element.querySelector(".result .error").classList.remove("hide");
|
||||
this.element.querySelector(".result .wait").classList.add("hide");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
applyStepSuccess(step) {
|
||||
this.element.querySelector(step).querySelector(".success").classList.remove("hide");
|
||||
this.element.querySelector(step).querySelector(".error").classList.add("hide");
|
||||
this.element.querySelector(step).querySelector(".wait").classList.add("hide");
|
||||
}
|
||||
|
||||
applyStepFailure(step) {
|
||||
this.element.querySelector(step).querySelector(".success").classList.add("hide");
|
||||
this.element.querySelector(step).querySelector(".error").classList.remove("hide");
|
||||
this.element.querySelector(step).querySelector(".wait").classList.add("hide");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ResultPage;
|
||||
@ -1,49 +0,0 @@
|
||||
const ManagerPage = require("./managerpage");
|
||||
const ejs = require('ejs')
|
||||
const { logger } = require("./filesystem")
|
||||
|
||||
/** Type of install page, allows the user to select single player or multi player installation
|
||||
*
|
||||
*/
|
||||
class TypePage extends ManagerPage {
|
||||
constructor(manager, options) {
|
||||
super(manager, options);
|
||||
}
|
||||
|
||||
render(str) {
|
||||
const element = this.getElement();
|
||||
element.innerHTML = str;
|
||||
|
||||
this.element.querySelector(".singleplayer").addEventListener("click", (e) => this.onOptionSelected(false));
|
||||
this.element.querySelector(".multiplayer").addEventListener("click", (e) => this.onOptionSelected(true));
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
show(previousPage) {
|
||||
ejs.renderFile("./ejs/type.ejs", {...this.options, ...this.manager.options}, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show(previousPage);
|
||||
}
|
||||
|
||||
onCancelClicked() {
|
||||
this.hide();
|
||||
this.manager.menuPage.show()
|
||||
}
|
||||
|
||||
onOptionSelected(multiplayer) {
|
||||
this.manager.options.multiplayer = multiplayer;
|
||||
this.hide();
|
||||
this.manager.connectionsPage.options.selectAutoOrManual = true;
|
||||
this.manager.connectionsPage.show(this);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TypePage;
|
||||
@ -168,6 +168,7 @@ body {
|
||||
width: var(--percent);
|
||||
background-color: var(--offwhite);
|
||||
height: 100%;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@ -385,6 +386,7 @@ input {
|
||||
}
|
||||
|
||||
.button.collapse {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@ -407,7 +409,7 @@ input {
|
||||
.button.collapse>div {
|
||||
display: none;
|
||||
position: absolute;
|
||||
transform: translate(-15px, calc(50% + 20px));
|
||||
transform: translate(-15px, calc(50% + 25px));
|
||||
}
|
||||
|
||||
.button.collapse.open>div {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user