Completed basic functionality development

This commit is contained in:
Pax1601 2024-01-26 17:31:36 +01:00
parent 613aed2d2b
commit f2161da162
21 changed files with 626 additions and 1139 deletions

View File

@ -6,7 +6,7 @@
{
"label": "mirror-package",
"type": "shell",
"command": "./scripts/mirror-package.bat",
"command": "call ./scripts/mirror-package.bat",
"isBackground": true
}
]

View File

@ -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>
<% } %>

View File

@ -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);

View File

@ -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;">

View File

@ -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;">

View File

@ -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>

View File

@ -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();
});
}
)
);
});
}
});
}
}

View File

@ -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
}

View File

@ -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;

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -0,0 +1,10 @@
async function sleep(ms) {
await new Promise(r => setTimeout(r, ms));
return true;
}
module.exports = {
sleep: sleep
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 {