Merge pull request #781 from Pax1601/manager-wizard

Manager wizard
This commit is contained in:
Pax1601 2023-12-22 19:23:35 +01:00 committed by GitHub
commit 17c74fbe91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1996 additions and 643 deletions

View File

@ -0,0 +1,110 @@
<style>
#manager-connections {
display: flex;
flex-direction: column;
row-gap: 15px;
}
#manager-connections .input-group {
color: var(--offwhite);
display: flex;
flex-direction: column;
row-gap: 5px;
}
#manager-connections .input-group>span:nth-child(1) {
font-size: 14px;
font-weight: 600;
}
#manager-connections .input-group>span:nth-child(2) {
font-size: 13px;
font-weight: normal;
}
#manager-connections .input-group div {
display: flex;
align-items: center;
column-gap: 5px;
flex-wrap: wrap;
}
#manager-connections>.instructions {
margin-bottom: 10px;
}
#manager-connections>.buttons-footer {
margin-top: 10px;
}
#manager-connections .client-port input {
width: 150px;
}
#manager-connections .backend-port input {
width: 150px;
}
#manager-connections .success {
content: url("./icons/check-solid-green.svg");
height: 20px;
width: 20px;
}
#manager-connections .error img {
content: url("./icons/triangle-exclamation-solid.svg");
height: 20px;
width: 20px;
}
#manager-connections .error span {
font-weight: 600;
font-size: 12px;
color: var(--red);
height: fit-content;
}
</style>
<div id="manager-connections">
<div class="page-header">
Step 2: Port and address settings
</div>
<div class="input-group client-port">
<span>Client port</span>
<span>This port is used to allow access to Olympus. Be sure to allow this port through your firewall if you want people to connect remotely.</span>
<div>
<input type="number" min="1024" max="65535" value="<%= instance["clientPort"] %>">
<img class="success hide">
<div class="error hide">
<img> <span>Port already in use</span>
</div>
</div>
</div>
<div class="input-group backend-port">
<span>Backend port</span>
<span>This port is used to communicate with DCS. It is not necessary to allow this port through your firewall.</span>
<div>
<input type="number" min="1024" max="65535" value="<%= instance["backendPort"] %>">
<img class="success hide">
<div class="error hide">
<img> <span>Port already in use</span>
</div>
</div>
</div>
<div class="input-group backend-address">
<span>Backend address</span>
<span>This is the backend address Olympus will listen on. Unless you know what you are doing, leave it as localhost, even for dedicated server installations.</span>
<input type="text" value="<%= instance["backendAddress"] %>">
</div>
<div class="buttons-footer">
<div class="button back">
Back
</div>
<div class="button next">
Next
</div>
<div class="button cancel">
Cancel
</div>
</div>
</div>

View File

@ -0,0 +1,134 @@
<style>
#manager-installations .scroll-container {
height: 440px;
overflow-y: auto;
}
#manager-installations .scrollable {
display: flex;
flex-direction: column;
row-gap: 8px;
height: fit-content;
}
#manager-installations .scrollable>.option {
cursor: pointer;
background-color: var(--darkgray);
width: 100%;
height: 100px;
color: white;
display: flex;
font-size: 13px;
font-weight: 600;
padding-left: 15px;
align-items: center;
border-radius: 5px;
border-left: 5px solid var(--blue);
}
#manager-installations .scrollable>.option * {
pointer-events: none;
}
#manager-installations .scrollable>.option.selected {
background-color: var(--blue);
}
#manager-installations .scrollable>.option .checkbox {
padding-right: 20px;
content: url("./icons/square-regular.svg");
}
#manager-installations .scrollable>.option.selected .checkbox {
content: url("./icons/check-solid.svg");
}
#manager-installations .scrollable>.option>div {
display: flex;
flex-direction: column;
row-gap: 5px;
}
#manager-installations .scrollable>.option>div>span:nth-child(1) {
font-size: 18px;
font-weight: 600;
}
#manager-installations .scrollable>.option>div>span:nth-child(2) {
font-size: 13px;
font-weight: 600;
color: var(--gray);
}
#manager-installations .scrollable>.option>div>span:nth-child(2).installed {
font-weight: 600;
color: var(--green);
}
#manager-installations .scrollable>.option>div>span:nth-child(2).error {
font-weight: 600;
color: orange;
}
#manager-installations .scrollable>.option>div>span:nth-child(3) {
display: flex;
column-gap: 10px;
justify-content: center;
font-size: 13px;
font-weight: normal;
}
#manager-installations>.instructions {
margin-bottom: 10px;
}
#manager-installations>.buttons-footer {
margin-top: 10px;
}
</style>
<div id="manager-installations">
<div class="page-header">
Step 1: Select DCS Installations
</div>
<div class="instructions">
<span>
Select the copy of DCS you want to install Olympus to.
</span>
<span>
For most people, this is your main DCS installation.
</span>
<span>
If you are running a dedicated server, you would also install Olympus to this DCS version.
</span>
</div>
<div class="scroll-container">
<div class="scrollable">
<% for (let i = 0; i < instances.length; i++) {%>
<div class="option" data-folder="<%= instances[i].folder %>">
<img class="checkbox">
<div>
<span><%= instances[i].name %></span>
<span class="<%= instances[i].installed? (instances[i].error? 'error': 'installed'): '' %>">
<%= instances[i].installed? (instances[i].error? 'Corrupted/outdated Olympus installation': 'Olympus already installed'): 'Olympus not installed yet' %>
</span>
<span><img src="./icons/folder-open-solid.svg"> <%= instances[i].folder %></span>
</div>
</div>
<% } %>
</div>
</div>
<div class="buttons-footer">
<div class="button back">
Back
</div>
<div class="button next">
Next
</div>
<div class="button cancel">
Cancel
</div>
</div>
</div>

View File

@ -0,0 +1,153 @@
<style>
#manager-instances .scroll-container {
height: 500px;
overflow-y: auto;
}
#manager-instances .scrollable {
display: flex;
flex-direction: column;
row-gap: 8px;
height: fit-content;
}
#manager-instances .scrollable>.option {
cursor: pointer;
background-color: var(--darkgray);
width: 100%;
height: 230px;
color: white;
display: flex;
font-size: 13px;
font-weight: 600;
padding-left: 15px;
align-items: center;
border-radius: 5px;
border-left: 5px solid var(--blue);
}
#manager-instances .scrollable>.option * {
pointer-events: none;
}
#manager-instances .scrollable>.option.selected {
background-color: var(--blue);
}
#manager-instances .scrollable>.option .checkbox {
padding-right: 20px;
content: url("./icons/square-regular.svg");
}
#manager-instances .scrollable>.option.selected .checkbox {
content: url("./icons/check-solid.svg");
}
#manager-instances .scrollable>.option>div {
display: flex;
flex-direction: column;
row-gap: 5px;
}
#manager-instances .scrollable>.option>div>span:nth-child(1) {
font-size: 18px;
font-weight: 600;
}
#manager-instances .scrollable>.option>div>span:nth-child(2) {
font-size: 13px;
font-weight: 600;
color: var(--gray);
}
#manager-instances .scrollable>.option>div>span:nth-child(2).installed {
font-weight: 600;
color: var(--green);
}
#manager-instances .scrollable>.option>div>span:nth-child(2).error {
font-weight: 600;
color: orange;
}
#manager-instances .scrollable>.option>div>span:nth-child(3) {
display: flex;
column-gap: 10px;
justify-content: center;
font-size: 13px;
font-weight: normal;
}
#manager-instances>.instructions {
margin-bottom: 10px;
}
#manager-instances>.buttons-footer {
margin-top: 10px;
}
#manager-instances .info {
display: flex;
flex-direction: column;
row-gap: 5px;
}
#manager-instances .info>div:nth-child(1) {
font-weight: 600;
font-size: 14px;
}
#manager-instances .info>div:nth-child(2) {
font-weight: normal;
font-size: 14px;
}
</style>
<div id="manager-instances">
<div class="page-header">
Step 1: Select Olympus Installations
</div>
<div class="instructions">
<span>
Select the copy of Olympus you want to manage.
</span>
</div>
<div class="scroll-container">
<div class="scrollable">
<% for (let i = 0; i < instances.length; i++) {%>
<div class="option" data-folder="<%= instances[i].folder %>">
<div>
<span><%= instances[i].name %></span>
<span class="<%= instances[i].installed? (instances[i].error? 'error': ''): '' %>">
<%= instances[i].installed? (instances[i].error? 'Corrupted/outdated Olympus installation': ''): '' %>
</span>
<span><img src="./icons/folder-open-solid.svg"> <%= instances[i].folder %></span>
<div class="info">
<div>Client port</div>
<div> <%= instances[i].clientPort %> </div>
</div>
<div class="info">
<div>Backend port</div>
<div> <%= instances[i].backendPort %> </div>
</div>
<div class="info">
<div>Backend address</div>
<div> <%= instances[i].backendAddress %> </div>
</div>
</div>
</div>
<% } %>
</div>
</div>
<div class="buttons-footer">
<div class="button back">
Back
</div>
<div class="button next">
Next
</div>
<div class="button cancel">
Cancel
</div>
</div>
</div>

View File

@ -0,0 +1,45 @@
<style>
#manager-menu {
display: flex;
flex-direction: column;
row-gap: 8px;
}
#manager-menu>.option {
background-color: var(--blue);
width: 100%;
height: 50px;
color: white;
display: flex;
font-size: 13px;
font-weight: 600;
padding-left: 15px;
align-items: center;
border-radius: 5px;
cursor: pointer;
}
#manager-menu>.option * {
pointer-events: none;
}
.inverted {
background-color: var(--gray) !important;
color: var(--background) !important;
}
</style>
<div id="manager-menu">
<div class="page-header">
What do you want to do?
</div>
<div class="option install">
Install Olympus
</div>
<div class="option update">
Update/remove Olympus
</div>
<div class="option inverted manage">
View and manage instances (WIP)
</div>
</div>

View File

@ -0,0 +1,73 @@
<style>
#manager-passwords {
display: flex;
flex-direction: column;
row-gap: 15px;
}
#manager-passwords .input-group {
color: var(--offwhite);
display: flex;
flex-direction: column;
row-gap: 5px;
}
#manager-passwords .input-group>span:nth-child(1) {
font-size: 14px;
font-weight: 600;
}
#manager-passwords .input-group>span:nth-child(2) {
font-size: 13px;
font-weight: normal;
}
#manager-passwords>.instructions {
margin-bottom: 10px;
}
#manager-passwords>.buttons-footer {
margin-top: 10px;
}
#manager-passwords input {
width: 250px;
}
</style>
<div id="manager-passwords">
<div class="page-header">
Step 3: Passwords
</div>
<div class="input-group">
<span>Enter your passwords to access Olympus</span>
<span>By using the passwords below, you can access different roles in Olympus.</span>
</div>
<div class="input-group game-master">
<span>Game Master Password</span>
<span>This password is used to access Olympus as Game Master with full priviledges.</span>
<input type="password" minlength="8">
</div>
<div class="input-group blue-commander">
<span>Blue Commander Password</span>
<span>This password is used to access Olympus as blue coalition Commander.</span>
<input type="password" minlength="8">
</div>
<div class="input-group red-commander">
<span>Red Commander Password</span>
<span>This password is used to access Olympus as red coalition Commander.</span>
<input type="password" minlength="8">
</div>
<div class="buttons-footer">
<div class="button back">
Back
</div>
<div class="button next">
Next
</div>
<div class="button cancel">
Cancel
</div>
</div>
</div>

View File

@ -0,0 +1,160 @@
<style>
#manager-result {
display: flex;
flex-direction: column;
row-gap: 15px;
}
#manager-result img.success {
content: url("./icons/check-solid-green.svg");
height: 20px;
width: 20px;
}
#manager-result img.error {
content: url("./icons/triangle-exclamation-solid.svg");
height: 20px;
width: 20px;
}
#manager-result img.wait {
content: url("./icons/spinner-solid.svg");
height: 20px;
width: 20px;
animation: rotate 2s linear infinite;
}
@keyframes rotate {
0% {
transform: rotate(0deg)
}
100% {
transform: rotate(360deg)
}
}
#manager-result .summary {
font-weight: 600;
font-size: 14px;
}
#manager-result .summary.success {
color: var(--green);
}
#manager-result .summary.error {
color: var(--red);
}
#manager-result .info {
font-weight: 600;
font-size: 14px;
color: var(--offwhite);
}
.step {
display: flex;
align-items: center;
font-size: 13px;
font-weight: 600;
color: var(--offwhite);
column-gap: 10px;
}
#manager-result>.result {
cursor: pointer;
background-color: var(--blue);
width: 100%;
height: 80px;
color: white;
display: flex;
font-size: 13px;
font-weight: 600;
padding-left: 15px;
align-items: center;
border-radius: 5px;
}
#manager-result>.result>img {
margin-left: 5px;
margin-right: 20px;
}
#manager-result>.result>div {
display: flex;
flex-direction: column;
row-gap: 5px;
}
#manager-result>.result>div>span:nth-child(1) {
font-size: 15px;
font-weight: 600;
}
#manager-result>.result>div>span:nth-child(2) {
display: flex;
column-gap: 10px;
justify-content: center;
font-size: 13px;
font-weight: normal;
}
</style>
<div id="manager-result">
<div class="page-header">
Please wait while Olympus is being installed
</div>
<div class="step hook">
Installing hook scripts<img class="wait"><img class="success hide"><img class="error hide">
</div>
<div class="step mod">
Installing mod folder<img class="wait"><img class="success hide"><img class="error hide">
</div>
<div class="step json">
Installing configuration file<img class="wait"><img class="success hide"><img class="error hide">
</div>
<div class="step config">
Applying configuration<img class="wait"><img class="success hide"><img class="error hide">
</div>
<div class="step shortcuts">
Creating shortcuts<img class="wait"><img class="success hide"><img class="error hide">
</div>
<div class="summary success hide">
Olympus successfully installed in the following DCS instance
</div>
<div class="summary error hide">
An error has occurred while installing Olympus
</div>
<div class="result">
<img class="wait"><img class="success hide"><img class="error hide">
<div>
<span>
<%= instance.name %>
</span>
<span><img src="./icons/folder-open-solid.svg">
<%= instance.folder %>
</span>
</div>
</div>
<div class="info success">
You may now start DCS and use Olympus either with the shortcuts or the "View and manage Olympus" entry in the
main menu
</div>
<div class="info error">
Please make sure DCS is not currently being executed
</div>
<div class="buttons-footer">
<div class="button back">
Back to main menu
</div>
<!--<div class="button cancel">
Close
</div>-->
</div>
</div>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path fill="#8bff63" d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>

After

Width:  |  Height:  |  Size: 449 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#F2F2F2" d="M304 48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zm0 416a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM48 304a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm464-48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM142.9 437A48 48 0 1 0 75 369.1 48 48 0 1 0 142.9 437zm0-294.2A48 48 0 1 0 75 75a48 48 0 1 0 67.9 67.9zM369.1 437A48 48 0 1 0 437 369.1 48 48 0 1 0 369.1 437z"/></svg>

After

Width:  |  Height:  |  Size: 609 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#F2F2F2" d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM337 209L209 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L303 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/></svg>

After

Width:  |  Height:  |  Size: 529 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#F2F2F2" d="M384 80c8.8 0 16 7.2 16 16V416c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V96c0-8.8 7.2-16 16-16H384zM64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64z"/></svg>

After

Width:  |  Height:  |  Size: 484 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#FF5858" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>

After

Width:  |  Height:  |  Size: 584 B

View File

@ -16,8 +16,8 @@
<div id="title-bar">
<div>DCS Olympus manager</div>
<button class="title-bar-button minimize"></button>
<button class="title-bar-button restore hide"></button>
<button class="title-bar-button maximize"></button>
<!--<button class="title-bar-button restore hide"></button>-->
<!--<button class="title-bar-button maximize"></button>-->
<button class="title-bar-button close"></button>
</div>
<div id="header">
@ -26,14 +26,19 @@
<div> DCS Olympus Manager</div>
<div class="accent-green">v1.0.4</div>
</div>
<div>
These are the DCS instances that the Olympus Manager has automatically detected in your system. <br>
You can install Olympus and manage your instances from here. <br>
Click on "Install Olympus to instance" to install Olympus.
</div>
</div>
<div id="main-div">
</div>
<div id="grayout" class="hide"></div>
<div id="popup" class="hide">
<img src="./icons/triangle-exclamation-solid.svg">
<div class="content">
This is an example content
</div>
<div class="footer">
<div class="button close-popup"> Close </div>
</div>
</div>
<div id="footer">
@ -45,13 +50,13 @@
window.ipcRender.send('window:minimize');
});
document.querySelector('.restore').addEventListener('click', () => {
/*document.querySelector('.restore').addEventListener('click', () => {
window.ipcRender.send('window:restore');
});
document.querySelector('.maximize').addEventListener('click', () => {
window.ipcRender.send('window:maximize');
});
});*/
document.querySelector('.close').addEventListener('click', () => {
window.ipcRender.send('window:close');
@ -66,7 +71,6 @@
document.querySelector('.restore').classList.add("hide");
document.querySelector('.maximize').classList.remove("hide");
})
</script>
</html>

View File

@ -0,0 +1,190 @@
var regedit = require('regedit')
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 vi = require('win-version-info');
const checkPort = require('./net')
const dircompare = require('dir-compare');
const { installJSON } = require('./filesystem')
class DCSInstance {
static instances = null;
static async getInstances() {
if (this.instances === null) {
this.instances = await this.findInstances();
}
return this.instances;
}
static async findInstances() {
let promise = new Promise((res, rej) => {
regedit.list(shellFoldersKey, function (err, result) {
if (err) {
rej(err);
}
else {
if (result[shellFoldersKey] !== undefined && result[shellFoldersKey]["exists"] && result[shellFoldersKey]['values'][saveGamesKey] !== undefined && result[shellFoldersKey]['values'][saveGamesKey]['value'] !== undefined) {
const searchpath = result[shellFoldersKey]['values'][saveGamesKey]['value'];
const folders = fs.readdirSync(searchpath);
var instances = [];
folders.forEach((folder) => {
if (fs.existsSync(path.join(searchpath, folder, "Config", "appsettings.lua")) ||
fs.existsSync(path.join(searchpath, folder, "Config", "serversettings.lua"))) {
instances.push(new DCSInstance(path.join(searchpath, folder)));
}
})
res(instances);
} else {
console.error("An error occured while trying to fetch the location of the DCS instances.")
rej("An error occured while trying to fetch the location of the DCS instances.");
}
}
})
});
return promise;
}
folder = "";
name = "";
clientPort = 3000;
backendPort = 3001;
backendAddress = "localhost";
gameMasterPassword = "";
blueCommanderPassword = "";
redCommanderPassword = "";
installed = false;
error = false;
constructor(folder) {
this.folder = folder;
this.name = path.basename(folder);
if (fs.existsSync(path.join(folder, "Config", "olympus.json"))){
try {
var config = JSON.parse(fs.readFileSync(path.join(folder, "Config", "olympus.json")));
this.clientPort = config["client"]["port"];
this.backendPort = config["server"]["port"];
this.backendAddress = config["server"]["address"];
} catch (err) {
console.error(err)
}
this.installed = true;
const options = { compareContent: true };
var err1 = true;
var err2 = true;
var res1;
var res2;
try {
res1 = dircompare.compareSync(path.join("..", "mod"), path.join(folder, "Mods", "Services", "Olympus"), options);
res2 = dircompare.compareSync(path.join("..", "scripts", "OlympusHook.lua"), path.join(folder, "Scripts", "Hooks", "OlympusHook.lua"), options);
err1 = res1.differences !== 0;
err2 = res2.differences !== 0;
} catch(e) {
console.log(e);
}
if (err1 || err2) {
console.log(res1)
console.log(res2)
this.error = true;
}
}
}
async setClientPort(newPort) {
if (await this.checkClientPort(newPort)) {
console.log(`Instance ${this.folder} client port set to ${newPort}`)
this.clientPort = newPort;
return true;
}
return false;
}
async setBackendPort(newPort) {
if (await this.checkBackendPort(newPort)) {
console.log(`Instance ${this.folder} client port set to ${newPort}`)
this.backendPort = newPort;
return true;
}
return false;
}
setBackendAddress(newAddress) {
this.backendAddress = newAddress;
}
setGameMasterPassword(newPassword) {
this.gameMasterPassword = newPassword;
}
setBlueCommanderPassword(newPassword) {
this.blueCommanderPassword = newPassword;
}
setRedCommanderPassword(newPassword) {
this.redCommanderPassword = newPassword;
}
async checkClientPort(port) {
var promise = new Promise((res, rej) => {
checkPort(port, async (portFree) => {
if (portFree) {
portFree = !(await DCSInstance.getInstances()).some((instance) => {
if (instance !== this) {
if (instance.clientPort === port || instance.backendPort === port) {
console.log(`Port ${port} already selected by other instance`);
return true;
}
} else {
if (instance.backendPort === port) {
console.log(`Port ${port} equal to backend port`);
return true;
}
}
return false;
})
}
else {
console.log(`Port ${port} currently in use`);
}
res(portFree);
})
})
return promise;
}
async checkBackendPort(port) {
var promise = new Promise((res, rej) => {
checkPort(port, async (portFree) => {
if (portFree) {
portFree = !(await DCSInstance.getInstances()).some((instance) => {
if (instance !== this) {
if (instance.clientPort === port || instance.backendPort === port) {
console.log(`Port ${port} already selected by other instance`);
return true;
}
} else {
if (instance.clientPort === port) {
console.log(`Port ${port} equal to client port`);
return true;
}
}
return false;
})
} else {
console.log(`Port ${port} currently in use`);
}
res(portFree);
})
})
return promise;
}
}
module.exports = DCSInstance;

View File

@ -0,0 +1,148 @@
const sha256 = require('sha256')
const createShortcut = require('create-desktop-shortcuts');
const fs = require('fs');
const path = require('path');
async function fixInstances(instances) {
var promise = new Promise((res, rej) => {
var instancePromises = instances.map((instance) => {
var instancePromise = new Promise((instanceRes, instanceErr) => {
installMod(instance.folder)
.then(() => installHooks(instance.folder))
.then(() => installShortCuts(instance.folder, instance.name))
.then(() => instanceRes(true), (err) => { instanceErr(err) })
})
return instancePromise;
});
console.log(instancePromises);
Promise.all(instancePromises).then(() => res(true), (err) => { rej(err) });
})
console.log(promise);
return promise;
}
async function installMod(folder) {
console.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) {
console.log(`Error installing mod in ${folder}: ${err}`)
rej(err);
}
else {
console.log(`Mod succesfully installed in ${folder}`)
res(true);
}
});
})
return promise;
}
async function installHooks(folder) {
console.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) {
console.log(`Error installing hooks in ${folder}: ${err}`)
rej(err);
}
else {
console.log(`Hooks succesfully installed in ${folder}`)
res(true);
}
});
})
return promise;
}
async function installJSON(folder) {
console.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) {
console.log(`Error installing config in ${folder}: ${err}`)
rej(err);
}
else {
console.log(`Config succesfully installed in ${folder}`)
res(true);
}
});
})
return promise;
}
async function applyConfiguration(folder, instance) {
console.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);
fs.writeFile(path.join(folder, "Config", "olympus.json"), JSON.stringify(config, null, 4), (err) => {
if (err) {
console.log(`Error applying config in ${folder}: ${err}`)
rej(err);
}
else {
console.log(`Config succesfully applied in ${folder}`)
res(true);
}
});
} else {
rej("File does not exist")
}
res(true);
});
return promise;
}
async function installShortCuts(folder, name) {
console.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')
}
});
if (res1 && res2) {
res(true);
} else {
rej("An error occurred while creating the shortcuts")
}
});
return promise;
}
module.exports = {
applyConfiguration: applyConfiguration,
installJSON: installJSON,
installHooks: installHooks,
installMod: installMod,
installShortCuts, installShortCuts,
fixInstances: fixInstances
}

View File

@ -0,0 +1,69 @@
const ManagerPage = require("./managerpage");
const ejs = require('ejs')
class ManagerConnections extends ManagerPage {
onBackClicked;
onNextClicked;
onCancelClicked;
instance;
constructor(options) {
super(options);
}
render(str) {
const element = this.getElement();
element.innerHTML = str;
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
this.element.querySelector(".next").addEventListener("click", (e) => this.onNextClicked(e));
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
this.element.querySelector(".client-port").querySelector("input").addEventListener("change", async (e) => { this.setClientPort(Number(e.target.value)); })
this.element.querySelector(".backend-port").querySelector("input").addEventListener("change", async (e) => { this.setBackendPort(Number(e.target.value)); })
this.element.querySelector(".backend-address").querySelector("input").addEventListener("change", async (e) => { this.instance.setBackendAddress(e.target.value); })
}
show(instance) {
this.instance = instance;
this.options["instance"] = instance;
ejs.renderFile("./ejs/managerconnections.ejs", this.options, {}, (err, str) => {
if (!err) {
this.render(str);
this.setClientPort(this.instance.clientPort);
this.setBackendPort(this.instance.backendPort);
} else {
console.error(err);
}
});
super.show();
}
async setClientPort(newPort) {
const success = await this.instance.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);
}
}
async setBackendPort(newPort) {
const success = await this.instance.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 = ManagerConnections;

View File

@ -0,0 +1,55 @@
const ManagerPage = require("./managerpage");
const ejs = require('ejs')
class ManagerInstallations extends ManagerPage {
onBackClicked;
onNextClicked;
onCancelClicked;
constructor(options) {
super(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);}
}
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
this.element.querySelector(".next").addEventListener("click", (e) => this.onNextClicked(e));
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
}
onOptionClicked(e) {
var options = this.element.querySelectorAll(".option");
for (let i = 0; i < options.length; i++) {
options[i].classList.remove("selected");
}
e.target.classList.add("selected");
}
getSelectedInstance() {
return this.options.instances.find((instance) => {
const selected = this.element.querySelector(".selected");
return selected? selected.dataset.folder === instance.folder: false;
});
}
show() {
ejs.renderFile("./ejs/managerinstallations.ejs", this.options, {}, (err, str) => {
if (!err) {
this.render(str);
} else {
console.error(err);
}
});
super.show();
}
}
module.exports = ManagerInstallations;

View File

@ -0,0 +1,55 @@
const ManagerPage = require("./managerpage");
const ejs = require('ejs')
class ManagerInstances extends ManagerPage {
onBackClicked;
onNextClicked;
onCancelClicked;
constructor(options) {
super(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);}
}
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
this.element.querySelector(".next").addEventListener("click", (e) => this.onNextClicked(e));
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
}
onOptionClicked(e) {
var options = this.element.querySelectorAll(".option");
for (let i = 0; i < options.length; i++) {
options[i].classList.remove("selected");
}
e.target.classList.add("selected");
}
getSelectedInstance() {
return this.options.instances.find((instance) => {
const selected = this.element.querySelector(".selected");
return selected? selected.dataset.folder === instance.folder: false;
});
}
show() {
ejs.renderFile("./ejs/managerinstances.ejs", this.options, {}, (err, str) => {
if (!err) {
this.render(str);
} else {
console.error(err);
}
});
super.show();
}
}
module.exports = ManagerInstances;

View File

@ -0,0 +1,31 @@
const ManagerPage = require("./managerpage");
const ejs = require('ejs')
class ManagerMenu extends ManagerPage {
onInstallClicked;
onUpdateClicked;
onManageClicked;
constructor(options) {
super(options);
ejs.renderFile("./ejs/managermenu.ejs", options, {}, (err, str) => {
if (!err) {
this.render(str);
} else {
console.error(err);
}
});
}
render(str) {
const element = this.getElement();
element.innerHTML = str;
element.querySelector(".install").addEventListener("click", (e) => this.onInstallClicked(e));
element.querySelector(".update").addEventListener("click", (e) => this.onUpdateClicked(e))
element.querySelector(".manage").addEventListener("click", (e) => this.onManageClicked(e))
}
}
module.exports = ManagerMenu;

View File

@ -0,0 +1,24 @@
class ManagerPage {
element;
options;
constructor(options) {
this.options = options ?? {};
this.element = document.createElement('div');
this.element.classList.add("manager-page", "hide");
}
getElement() {
return this.element;
}
show() {
this.element.classList.remove("hide");
}
hide() {
this.element.classList.add("hide");
}
}
module.exports = ManagerPage;

View File

@ -0,0 +1,42 @@
const ManagerPage = require("./managerpage");
const ejs = require('ejs')
class ManagerPasswords extends ManagerPage {
onBackClicked;
onNextClicked;
onCancelClicked;
constructor(options) {
super(options);
}
render(str) {
const element = this.getElement();
element.innerHTML = str;
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
this.element.querySelector(".next").addEventListener("click", (e) => this.onNextClicked(e));
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
this.element.querySelector(".game-master").querySelector("input").addEventListener("change", async (e) => { this.instance.setGameMasterPassword(e.target.value); })
this.element.querySelector(".blue-commander").querySelector("input").addEventListener("change", async (e) => { this.instance.setBlueCommanderPassword(e.target.value); })
this.element.querySelector(".red-commander").querySelector("input").addEventListener("change", async (e) => { this.instance.setRedCommanderPassword(e.target.value); })
}
show(instance) {
this.instance = instance;
this.options["instance"] = instance;
ejs.renderFile("./ejs/managerpasswords.ejs", this.options, {}, (err, str) => {
if (!err) {
this.render(str);
} else {
console.error(err);
}
});
super.show();
}
}
module.exports = ManagerPasswords;

View File

@ -0,0 +1,113 @@
const { installMod, installHooks, installJSON, applyConfiguration, installShortCuts } = require("./filesystem");
const ManagerPage = require("./managerpage");
const ejs = require('ejs')
class ManagerResult extends ManagerPage {
onBackClicked;
onNextClicked;
onCancelClicked;
constructor(options) {
super(options);
}
render(str) {
const element = this.getElement();
element.innerHTML = str;
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
//this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
}
show(instance) {
this.instance = instance;
this.options["instance"] = instance;
ejs.renderFile("./ejs/managerresult.ejs", this.options, {}, (err, str) => {
if (!err) {
this.render(str);
} else {
console.error(err);
}
});
super.show();
}
startInstallation() {
installHooks(this.instance.folder).then(
() => {
this.applyStepSuccess(".hook");
},
(err) => {
this.applyStepFailure(".hook");
return Promise.reject(err);
}
).then(() => installMod(this.instance.folder)).then(
() => {
this.applyStepSuccess(".mod");
},
(err) => {
this.applyStepFailure(".mod");
return Promise.reject(err);
}
).then(() => installJSON(this.instance.folder)).then(
() => {
this.applyStepSuccess(".json");
},
(err) => {
this.applyStepFailure(".json");
return Promise.reject(err);
}
).then(() => applyConfiguration(this.instance.folder, this.instance)).then(
() => {
this.applyStepSuccess(".config");
},
(err) => {
this.applyStepFailure(".config");
return Promise.reject(err);
}
).then(() => installShortCuts(this.instance.folder, this.instance.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(".page-header").innerText = "Install successfull!";
},
() => {
this.element.querySelector(".summary.success").classList.add("hide");
this.element.querySelector(".summary.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");
this.element.querySelector(".page-header").innerText = "Install error!";
}
);
}
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 = ManagerResult;

View File

@ -0,0 +1,14 @@
const portfinder = require('portfinder')
function checkPort(port, callback) {
portfinder.getPort({ port: port, stopPort: port }, (err, res) => {
if (err !== null) {
console.error(`Port ${port} already in use`);
callback(false);
} else {
callback(true);
}
});
}
module.exports = checkPort;

273
manager/javascripts/old.js Normal file
View File

@ -0,0 +1,273 @@
function showPopup(message, otherButton, otherButtonCallback) {
var data = {
message: message,
otherButton: otherButton
};
var popups = document.querySelectorAll(".popup");
for (let i = 0; i < popups.length; i++) {
document.body.removeChild(popups[i])
}
ejs.renderFile("./ejs/popup.ejs", data, {}, (err, str) => {
var div = document.createElement("div");
div.classList.add("popup");
div.innerHTML = str;
document.body.appendChild(div);
div.querySelector(".apply").addEventListener("click", () => {
document.body.removeChild(div);
})
div.querySelector(".other").addEventListener("click", () => {
otherButtonCallback();
})
});
}
class InstanceDiv {
element = null;
parent = null;
folder = "";
constructor(parent, folder) {
this.element = parent;
this.folder = folder;
this.render();
}
render() {
this.element = document.createElement("div");
var data = {
folder: this.folder,
installed: false,
index: instanceDivs.length * 10
};
var newVersionInfo = vi(path.join("..", "mod", "bin", "olympus.dll"));
data["newVersion"] = newVersionInfo.ProductVersion;
data["version"] = "n/a";
if (fs.existsSync(path.join(this.folder, "Config", "olympus.json"))) {
var config = JSON.parse(fs.readFileSync(path.join(this.folder, "Config", "olympus.json")));
data = {
...data,
...config
}
data["installed"] = true;
try {
data["version"] = vi(path.join(this.folder, "Mods", "Services", "Olympus", "bin", "olympus.dll")).ProductVersion;
} catch (e) {
data["version"] = "n/a";
}
}
ejs.renderFile("./ejs/instanceDiv.ejs", data, {}, (err, str) => {
this.element.innerHTML = str;
this.element.querySelector(".add").addEventListener("click", (e) => {
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while Olympus is being installed");
window.setTimeout(() => {
if (installOlympus(this.folder)) {
showPopup("Olympus installed successfully. Use the provided form to set Olympus properties. All fields are mandatory. Click on \"Create desktop shortcuts\" to generate Olympus shortcuts on your desktop.", "Create desktop shortcuts", () => {
createDesktopShortcuts(this.folder);
});
} else {
showPopup("An error has occurred during installation");
}
}, 100);
}
});
this.element.querySelector(".remove").addEventListener("click", (e) => {
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while Olympus is being uninstalled from DCS instance");
window.setTimeout(() => {
if (uninstallOlympus(this.folder)) {
showPopup("Olympus uninstalled successfully from DCS instance!");
} else {
showPopup("An error has occurred during uninstallation");
}
}, 100);
}
});
this.element.querySelector(".apply").addEventListener("click", (e) => {
e.srcElement.classList.remove("blink");
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while the configuration is being applied");
window.setTimeout(() => {
if (applyConfiguration(this.folder, this.getFields())) {
showPopup("Olympus configuration applied successfully!");
} else {
showPopup("An error has occurred while applying the configuration");
}
}, 100)
}
});
this.element.querySelector(".update").addEventListener("click", (e) => {
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while Olympus is being updated in the DCS instance");
window.setTimeout(() => {
if (updateOlympus(this.folder)) {
showPopup("Olympus updated successfully from DCS instance!");
} else {
showPopup("An error has occurred during the update");
}
}, 100);
}
});
var inputs = this.element.querySelectorAll("input");
for (let i = 0; i < inputs.length; i++) {
inputs[i].addEventListener("change", () => {
inputs[i].classList.remove("error");
instanceDivs.forEach((instanceDiv) => instanceDiv.checkFields())
})
}
});
}
getDiv() {
return this.element;
}
getFields() {
return {
clientPort: Number(this.element.querySelector("#client-port").value),
backendPort: Number(this.element.querySelector("#backend-port").value),
backendAddress: this.element.querySelector("#backend-address").value,
gameMasterPassword: this.element.querySelector("#game-master-password").value,
blueCommanderPassword: this.element.querySelector("#blue-commander-password").value,
redCommanderPassword: this.element.querySelector("#red-commander-password").value,
}
}
checkFields() {
var data = this.getFields();
/* Clear existing errors */
var inputs = this.element.querySelectorAll("input");
for (let i = 0; i < inputs.length; i++) {
inputs[i].classList.remove("error");
}
var messages = this.element.querySelectorAll(".error");
for (let i = 0; i < messages.length; i++) {
messages[i].innerText = "";
}
/* Enable the button */
this.element.querySelector(".apply").classList.remove("disabled");
if (data["clientPort"] !== 0 && data["backendPort"] !== 0) {
if (data["clientPort"] === data["backendPort"]) {
this.element.querySelector("#client-port").classList.add("error");
this.element.querySelector("#client-port-error").innerText = "Ports must be different";
this.element.querySelector("#backend-port").classList.add("error");
this.element.querySelector("#backend-port-error").innerText = "Ports must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
else {
checkPort(data["clientPort"], (res) => {
var otherInstanceUsesPort = instanceDivs.find((instanceDiv) => {
if (instanceDiv != this) {
var fields = instanceDiv.getFields();
if (fields["clientPort"] === data["clientPort"] || fields["backendPort"] === data["clientPort"]) {
return true;
}
}
})
if (!res || otherInstanceUsesPort) {
this.element.querySelector("#client-port").classList.add("error");
this.element.querySelector("#client-port-error").innerText = "Port already in use";
this.element.querySelector(".apply").classList.add("disabled");
}
});
checkPort(data["backendPort"], (res) => {
var otherInstanceUsesPort = instanceDivs.find((instanceDiv) => {
if (instanceDiv != this) {
var fields = instanceDiv.getFields();
if (fields["clientPort"] === data["backendPort"] || fields["backendPort"] === data["backendPort"]) {
return true;
}
}
})
if (!res || otherInstanceUsesPort) {
this.element.querySelector("#backend-port").classList.add("error");
this.element.querySelector("#backend-port-error").innerText = "Port already in use";
this.element.querySelector(".apply").classList.add("disabled");
}
});
}
}
if (data["gameMasterPassword"] !== "" && data["blueCommanderPassword"] !== "" && data["gameMasterPassword"] === data["blueCommanderPassword"]) {
this.element.querySelector("#game-master-password").classList.add("error");
this.element.querySelector("#game-master-password-error").innerText = "Passwords must be different";
this.element.querySelector("#blue-commander-password").classList.add("error");
this.element.querySelector("#blue-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
if (data["gameMasterPassword"] !== "" && data["redCommanderPassword"] !== "" && data["gameMasterPassword"] === data["redCommanderPassword"]) {
this.element.querySelector("#game-master-password").classList.add("error");
this.element.querySelector("#game-master-password-error").innerText = "Passwords must be different";
this.element.querySelector("#red-commander-password").classList.add("error");
this.element.querySelector("#red-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
if (data["blueCommanderPassword"] !== "" && data["redCommanderPassword"] !== "" && data["blueCommanderPassword"] === data["redCommanderPassword"]) {
this.element.querySelector("#blue-commander-password").classList.add("error");
this.element.querySelector("#blue-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector("#red-commander-password").classList.add("error");
this.element.querySelector("#red-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
if (data["gameMasterPassword"] === "" || data["blueCommanderPassword"] === "" || data["redCommanderPassword"] === "") {
this.element.querySelector(".apply").classList.add("disabled");
}
}
}
function loadDivs() {
regedit.list(shellFoldersKey, function (err, result) {
if (err) {
console.log(err);
}
else {
if (result[shellFoldersKey] !== undefined && result[shellFoldersKey]["exists"] && result[shellFoldersKey]['values'][saveGamesKey] !== undefined && result[shellFoldersKey]['values'][saveGamesKey]['value'] !== undefined) {
const searchpath = result[shellFoldersKey]['values'][saveGamesKey]['value'];
const folders = fs.readdirSync(searchpath);
instanceDivs = [];
const mainDiv = document.getElementById("main-div");
folders.forEach((folder) => {
if (fs.existsSync(path.join(searchpath, folder, "Config", "appsettings.lua")) ||
fs.existsSync(path.join(searchpath, folder, "Config", "serversettings.lua"))) {
instanceDivs.push(new InstanceDiv(mainDiv, path.join(searchpath, folder)));
}
});
mainDiv.replaceChildren(...instanceDivs.map((instanceDiv) => {
return instanceDiv.getDiv();
}));
instanceDivs.forEach((instanceDiv) => instanceDiv.checkFields())
} else {
console.error("An error occured while trying to fetch the location of the DCS folders.")
}
}
})
}

View File

@ -0,0 +1,19 @@
function showPopup(message, onCloseCallback) {
document.getElementById("grayout").classList.remove("hide");
document.getElementById("popup").classList.remove("hide");
document.getElementById("popup").querySelector(".close-popup").addEventListener("click", (e) => {
hidePopup();
onCloseCallback();
})
document.getElementById("popup").querySelector(".content").innerText = message;
}
function hidePopup() {
document.getElementById("grayout").classList.add("hide");
document.getElementById("popup").classList.add("hide");
}
module.exports = {
showPopup: showPopup,
hidePopup: hidePopup
}

View File

@ -1,59 +1,56 @@
var regedit = require('regedit')
var fs = require('fs')
var path = require('path')
const ejs = require('ejs')
const portfinder = require('portfinder')
const sha256 = require('sha256')
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
const createShortcut = require('create-desktop-shortcuts');
const vi = require('win-version-info');
const shellFoldersKey = 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders'
const saveGamesKey = '{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}'
const ManagerMenu = require("./managermenu");
const ManagerInstallations = require('./managerinstallations');
const DCSInstance = require('./dcsinstance');
const ManagerConnections = require('./managerconnections');
const ManagerPasswords = require('./managerpasswords');
const { showPopup } = require('./popup');
const ManagerResult = require('./managerresult');
const { fixInstances } = require('./filesystem');
const ManagerInstances = require('./managerinstances');
var instanceDivs = [];
// White-listed channels.
/* White-listed channels. */
const ipc = {
'render': {
// From render to main.
/* From render to main. */
'send': [
'window:minimize', // Channel names
'window:minimize',
'window:maximize',
'window:restore',
'window:close'
],
// From main to render.
/* From main to render. */
'receive': [
'event:maximized',
'event:unmaximized'
],
// From render to main and back again.
/* From render to main and back again. */
'sendReceive': []
}
};
// Exposed protected methods in the render process.
/* Exposed protected methods in the render process. */
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
/* Allowed 'ipcRenderer' methods. */
'ipcRender', {
// From render to main.
/* From render to main. */
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
/* From main to render. */
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
/* Deliberately strip event as it includes `sender`. */
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
/* From render to main and back again. */
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
@ -63,402 +60,153 @@ contextBridge.exposeInMainWorld(
}
);
function showPopup(message, otherButton, otherButtonCallback) {
var data = {
message: message,
otherButton: otherButton
};
var activeInstance;
var popups = document.querySelectorAll(".popup");
async function setup() {
var instances = await DCSInstance.getInstances();
for (let i = 0; i < popups.length; i++) {
document.body.removeChild(popups[i])
}
ejs.renderFile("./ejs/popup.ejs", data, {}, (err, str) => {
var div = document.createElement("div");
div.classList.add("popup");
div.innerHTML = str;
document.body.appendChild(div);
div.querySelector(".apply").addEventListener("click", () => {
document.body.removeChild(div);
})
div.querySelector(".other").addEventListener("click", () => {
otherButtonCallback();
})
});
}
function checkPort(port, callback) {
portfinder.getPort({ port: port, stopPort: port }, (err, res) => {
if (err !== null) {
console.error(`Port ${port} already in use`);
callback(false);
} else {
callback(true);
}
});
}
function installOlympus(folder) {
console.log(`Installing Olympus in ${folder}`);
try {
fs.cpSync(path.join("..", "mod"), path.join(folder, "Mods", "Services", "Olympus"), { recursive: true });
fs.cpSync(path.join("..", "scripts", "OlympusHook.lua"), path.join(folder, "Scripts", "Hooks", "OlympusHook.lua"));
fs.cpSync(path.join("..", "olympus.json"), path.join(folder, "Config", "olympus.json"));
if (createShortcut({
windows: {
filePath: path.resolve(__dirname, '..', '..', 'client', 'client.vbs'),
outputPath: folder,
name: "DCS Olympus Client",
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus.ico'),
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
}
}) &&
createShortcut({
windows: {
filePath: path.resolve(__dirname, '..', '..', 'client', 'server.vbs'),
outputPath: folder,
name: "DCS Olympus Server",
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus_server.ico'),
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
}
})) {
console.log("Shorcuts created succesfully")
} else {
return false;
}
} catch (e) {
console.error(e);
return false;
}
loadDivs();
return true;
}
function uninstallOlympus(folder) {
console.log(`Uninstalling Olympus from ${folder}`);
try {
fs.rmSync(path.join(folder, "Mods", "Services", "Olympus"), { recursive: true, force: true });
fs.rmSync(path.join(folder, "Config", "olympus.json"), {force: true});
loadDivs();
} catch (e) {
console.error(e);
return false;
}
return true;
}
function applyConfiguration(folder, data) {
console.log(`Applying configuration to Olympus from ${folder}`);
if (fs.existsSync(path.join(folder, "Config", "olympus.json"))) {
var config = JSON.parse(fs.readFileSync(path.join(folder, "Config", "olympus.json")));
config["client"]["port"] = data["clientPort"];
config["server"]["port"] = data["backendPort"];
config["server"]["address"] = data["backendAddress"];
config["authentication"]["gameMasterPassword"] = sha256(data["gameMasterPassword"]);
config["authentication"]["blueCommanderPassword"] = sha256(data["blueCommanderPassword"]);
config["authentication"]["redCommanderPassword"] = sha256(data["redCommanderPassword"]);
try {
fs.writeFileSync(path.join(folder, "Config", "olympus.json"), JSON.stringify(config, null, 4));
} catch (e) {
console.error(e);
return false;
}
} else {
return false;
}
return true;
}
function updateOlympus(folder) {
console.log(`Updating Olympus in ${folder}`);
try {
fs.cpSync(path.join("..", "mod"), path.join(folder, "Mods", "Services", "Olympus"), { recursive: true });
fs.cpSync(path.join("..", "scripts", "OlympusHook.lua"), path.join(folder, "Scripts", "Hook", "OlympusHook.lua"));
loadDivs();
} catch (e) {
console.error(e);
return false;
}
return true;
}
function createDesktopShortcuts(folder) {
if (createShortcut({
windows: {
filePath: path.resolve(__dirname, '..', '..', 'client', 'client.vbs'),
name: "DCS Olympus Client",
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus.ico'),
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
}
}) && createShortcut({
windows: {
filePath: path.resolve(__dirname, '..', '..', 'client', 'server.vbs'),
name: "DCS Olympus Server",
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus_server.ico'),
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
}
if (instances.some((instance) => {
return instance.installed && instance.error;
})) {
showPopup("Shortcuts created successfully!")
} else {
showPopup("And error occurred while creating the shortcuts.")
}
}
class InstanceDiv {
element = null;
parent = null;
folder = "";
constructor(parent, folder) {
this.element = parent;
this.folder = folder;
this.render();
showPopup("One or more Olympus instances are corrupted or need updating. Press Close to fix this.", async () => {
fixInstances(instances.filter((instance) => {
return instance.installed && instance.error;
})).then(
() => { location.reload() },
() => { showPopup("An error occurred while trying to fix you installations. Please reinstall Olympus manually") }
)
})
}
render() {
this.element = document.createElement("div");
var data = {
folder: this.folder,
installed: false,
index: instanceDivs.length * 10
};
var newVersionInfo = vi(path.join("..", "mod", "bin", "olympus.dll"));
data["newVersion"] = newVersionInfo.ProductVersion;
data["version"] = "n/a";
if (fs.existsSync(path.join(this.folder, "Config", "olympus.json"))) {
var config = JSON.parse(fs.readFileSync(path.join(this.folder, "Config", "olympus.json")));
data = {
...data,
...config
}
data["installed"] = true;
try {
data["version"] = vi(path.join(this.folder, "Mods", "Services", "Olympus", "bin", "olympus.dll")).ProductVersion;
} catch (e) {
data["version"] = "n/a";
}
}
ejs.renderFile("./ejs/instanceDiv.ejs", data, {}, (err, str) => {
this.element.innerHTML = str;
this.element.querySelector(".add").addEventListener("click", (e) => {
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while Olympus is being installed");
window.setTimeout(() => {
if (installOlympus(this.folder)) {
showPopup("Olympus installed successfully. Use the provided form to set Olympus properties. All fields are mandatory. Click on \"Create desktop shortcuts\" to generate Olympus shortcuts on your desktop.", "Create desktop shortcuts", () => {
createDesktopShortcuts(this.folder);
});
} else {
showPopup("An error has occurred during installation");
}
}, 100);
}
});
this.element.querySelector(".remove").addEventListener("click", (e) => {
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while Olympus is being uninstalled from DCS instance");
window.setTimeout(() => {
if (uninstallOlympus(this.folder)) {
showPopup("Olympus uninstalled successfully from DCS instance!");
} else {
showPopup("An error has occurred during uninstallation");
}
}, 100);
}
});
this.element.querySelector(".apply").addEventListener("click", (e) => {
e.srcElement.classList.remove("blink");
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while the configuration is being applied");
window.setTimeout(() => {
if (applyConfiguration(this.folder, this.getFields())) {
showPopup("Olympus configuration applied successfully!");
} else {
showPopup("An error has occurred while applying the configuration");
}
}, 100)
}
});
this.element.querySelector(".update").addEventListener("click", (e) => {
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while Olympus is being updated in the DCS instance");
window.setTimeout(() => {
if (updateOlympus(this.folder)) {
showPopup("Olympus updated successfully from DCS instance!");
} else {
showPopup("An error has occurred during the update");
}
}, 100);
}
});
var inputs = this.element.querySelectorAll("input");
for (let i = 0; i < inputs.length; i++) {
inputs[i].addEventListener("change", () => {
inputs[i].classList.remove("error");
instanceDivs.forEach((instanceDiv) => instanceDiv.checkFields())
})
}
});
/* Menu */
var managerMenu = new ManagerMenu();
managerMenu.onInstallClicked = (e) => {
managerMenu.hide();
managerInstallations.show();
}
managerMenu.onUpdateClicked = (e) => {
managerMenu.hide();
managerInstances.show();
}
getDiv() {
return this.element;
/* Installations */
var managerInstallations = new ManagerInstallations({ instances: instances });
managerInstallations.onBackClicked = (e) => {
managerInstallations.hide();
managerMenu.show();
}
getFields() {
return {
clientPort: Number(this.element.querySelector("#client-port").value),
backendPort: Number(this.element.querySelector("#backend-port").value),
backendAddress: this.element.querySelector("#backend-address").value,
gameMasterPassword: this.element.querySelector("#game-master-password").value,
blueCommanderPassword: this.element.querySelector("#blue-commander-password").value,
redCommanderPassword: this.element.querySelector("#red-commander-password").value,
managerInstallations.onNextClicked = (e) => {
activeInstance = managerInstallations.getSelectedInstance();
if (activeInstance) {
managerInstallations.hide();
managerConnections.show(activeInstance);
} else {
showPopup("Please select the instance you want to install Olympus into.")
}
}
managerInstallations.onCancelClicked = (e) => {
managerInstallations.hide();
managerMenu.show();
}
checkFields() {
var data = this.getFields();
/* Clear existing errors */
var inputs = this.element.querySelectorAll("input");
for (let i = 0; i < inputs.length; i++) {
inputs[i].classList.remove("error");
}
var messages = this.element.querySelectorAll(".error");
for (let i = 0; i < messages.length; i++) {
messages[i].innerText = "";
}
/* Enable the button */
this.element.querySelector(".apply").classList.remove("disabled");
if (data["clientPort"] !== 0 && data["backendPort"] !== 0) {
if (data["clientPort"] === data["backendPort"]) {
this.element.querySelector("#client-port").classList.add("error");
this.element.querySelector("#client-port-error").innerText = "Ports must be different";
this.element.querySelector("#backend-port").classList.add("error");
this.element.querySelector("#backend-port-error").innerText = "Ports must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
else {
checkPort(data["clientPort"], (res) => {
var otherInstanceUsesPort = instanceDivs.find((instanceDiv) => {
if (instanceDiv != this) {
var fields = instanceDiv.getFields();
if (fields["clientPort"] === data["clientPort"] || fields["backendPort"] === data["clientPort"]) {
return true;
}
}
})
if (!res || otherInstanceUsesPort) {
this.element.querySelector("#client-port").classList.add("error");
this.element.querySelector("#client-port-error").innerText = "Port already in use";
this.element.querySelector(".apply").classList.add("disabled");
}
});
checkPort(data["backendPort"], (res) => {
var otherInstanceUsesPort = instanceDivs.find((instanceDiv) => {
if (instanceDiv != this) {
var fields = instanceDiv.getFields();
if (fields["clientPort"] === data["backendPort"] || fields["backendPort"] === data["backendPort"]) {
return true;
}
}
})
if (!res || otherInstanceUsesPort) {
this.element.querySelector("#backend-port").classList.add("error");
this.element.querySelector("#backend-port-error").innerText = "Port already in use";
this.element.querySelector(".apply").classList.add("disabled");
}
});
}
}
if (data["gameMasterPassword"] !== "" && data["blueCommanderPassword"] !== "" && data["gameMasterPassword"] === data["blueCommanderPassword"]) {
this.element.querySelector("#game-master-password").classList.add("error");
this.element.querySelector("#game-master-password-error").innerText = "Passwords must be different";
this.element.querySelector("#blue-commander-password").classList.add("error");
this.element.querySelector("#blue-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
if (data["gameMasterPassword"] !== "" && data["redCommanderPassword"] !== "" && data["gameMasterPassword"] === data["redCommanderPassword"]) {
this.element.querySelector("#game-master-password").classList.add("error");
this.element.querySelector("#game-master-password-error").innerText = "Passwords must be different";
this.element.querySelector("#red-commander-password").classList.add("error");
this.element.querySelector("#red-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
if (data["blueCommanderPassword"] !== "" && data["redCommanderPassword"] !== "" && data["blueCommanderPassword"] === data["redCommanderPassword"]) {
this.element.querySelector("#blue-commander-password").classList.add("error");
this.element.querySelector("#blue-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector("#red-commander-password").classList.add("error");
this.element.querySelector("#red-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
if (data["gameMasterPassword"] === "" || data["blueCommanderPassword"] === "" || data["redCommanderPassword"] === "") {
this.element.querySelector(".apply").classList.add("disabled");
/* Instances */
var managerInstances = new ManagerInstances({ instances: instances.filter((instance) => {return instance.installed; }) });
managerInstances.onBackClicked = (e) => {
managerInstances.hide();
managerMenu.show();
}
managerInstances.onNextClicked = (e) => {
activeInstance = managerInstances.getSelectedInstance();
if (activeInstance) {
managerInstances.hide();
managerConnections.show(activeInstance);
} else {
showPopup("Please select the instance you want to manage.")
}
}
}
function loadDivs() {
regedit.list(shellFoldersKey, function (err, result) {
if (err) {
console.log(err);
}
else {
if (result[shellFoldersKey] !== undefined && result[shellFoldersKey]["exists"] && result[shellFoldersKey]['values'][saveGamesKey] !== undefined && result[shellFoldersKey]['values'][saveGamesKey]['value'] !== undefined) {
const searchpath = result[shellFoldersKey]['values'][saveGamesKey]['value'];
const folders = fs.readdirSync(searchpath);
instanceDivs = [];
const mainDiv = document.getElementById("main-div");
folders.forEach((folder) => {
if (fs.existsSync(path.join(searchpath, folder, "Config", "appsettings.lua")) ||
fs.existsSync(path.join(searchpath, folder, "Config", "serversettings.lua"))) {
instanceDivs.push(new InstanceDiv(mainDiv, path.join(searchpath, folder)));
}
});
mainDiv.replaceChildren(...instanceDivs.map((instanceDiv) => {
return instanceDiv.getDiv();
}));
instanceDivs.forEach((instanceDiv) => instanceDiv.checkFields())
managerInstances.onCancelClicked = (e) => {
managerInstances.hide();
managerMenu.show();
}
/* Connections */
var managerConnections = new ManagerConnections();
managerConnections.onBackClicked = (e) => {
managerConnections.hide();
managerInstallations.show();
}
managerConnections.onNextClicked = async (e) => {
if (activeInstance) {
if (await activeInstance.checkClientPort(activeInstance.clientPort) && await activeInstance.checkBackendPort(activeInstance.backendPort)) {
managerConnections.hide();
managerPasswords.show(activeInstance);
} else {
console.error("An error occured while trying to fetch the location of the DCS folders.")
showPopup("Please make sure the selected ports are not already in use.")
}
} else {
showPopup("An error has occurred, please restart the Olympus Manager.")
}
})
}
managerConnections.onCancelClicked = (e) => {
managerConnections.hide();
managerMenu.show();
}
/* Passwords */
var managerPasswords = new ManagerPasswords();
managerPasswords.onBackClicked = (e) => {
if (activeInstance) {
managerPasswords.hide();
managerConnections.show(activeInstance);
} else {
showPopup("An error has occurred, please restart the Olympus Manager.")
}
}
managerPasswords.onNextClicked = (e) => {
if (activeInstance) {
if (activeInstance.gameMasterPassword === "" || activeInstance.blueCommanderPassword === "" || activeInstance.redCommanderPassword === "") {
showPopup("Please fill all the password inputs.")
}
else if (activeInstance.gameMasterPassword === activeInstance.blueCommanderPassword || activeInstance.blueCommanderPassword === activeInstance.redCommanderPassword || activeInstance.gameMasterPassword === activeInstance.redCommanderPassword) {
showPopup("All the passwords must be different from each other.")
} else {
managerPasswords.hide();
managerResult.show(activeInstance);
managerResult.startInstallation();
}
} else {
showPopup("An error has occurred, please restart the Olympus Manager.")
}
}
managerPasswords.onCancelClicked = (e) => {
managerPasswords.hide();
managerMenu.show();
}
/* Result */
var managerResult = new ManagerResult();
managerResult.onBackClicked = (e) => {
managerResult.hide();
location.reload();
}
managerResult.onCancelClicked = (e) => {
managerResult.hide();
location.reload();
}
document.body.appendChild(managerMenu.getElement());
document.body.appendChild(managerInstallations.getElement());
document.body.appendChild(managerInstances.getElement());
document.body.appendChild(managerConnections.getElement());
document.body.appendChild(managerPasswords.getElement());
document.body.appendChild(managerResult.getElement());
managerMenu.show();
}
/* On content loaded */
window.addEventListener('DOMContentLoaded', () => {
loadDivs();
setup();
})

View File

@ -7,9 +7,10 @@ let window;
function createWindow() {
const window = new electronBrowserWindow({
width: 1310,
width: 500,
height: 800,
frame: false,
resizable: false,
webPreferences: {
contextIsolation: true,
preload: path.join(__dirname, "javascripts", 'preload.js'),

View File

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

View File

@ -1,10 +1,20 @@
:root {
--background: #181e25;
--offwhite: #F2F2F2;
--blue: #247be2;
--red: #FF5858;
--green: #8bff63;
--gray: #cfd9e8;
--darkgray: #3d4651;
}
* {
font-family: "Open Sans", sans-serif;
box-sizing: border-box;
}
body {
background-color: #181e25;
background-color: var(--background);
padding: 0px;
margin: 0px;
}
@ -65,37 +75,15 @@ body {
color: #F2F2F2;
font-weight: bold;
font-size: 16px;
padding: 20px;
padding: 20px 20px 0px 20px;
column-gap: 10px;
}
#header>div:first-of-type{
width: 300px;
}
#header>div:last-child {
font-size: 13px;
margin-left: 30px;
font-weight: 400;
text-align: right;
width: 100%;
}
.main-icon {
width: 60px;
height: 60px;
}
#main-div {
display: flex;
flex-direction: row;
height: 100%;
row-gap: 30px;
column-gap: 30px;
flex-wrap: wrap;
padding: 25px;
}
body {
overflow: auto;
scrollbar-color: white transparent;
@ -121,233 +109,131 @@ body {
opacity: 0.8;
}
.instance-div {
display: flex;
flex-direction: column;
row-gap: 10px;
background-color: #3d4651;
height: fit-content;
padding: 20px 40px;
border-radius: 5px;
border-left: 5px solid #017DC1;
width: 600px;
box-shadow: 0px 0px 5px #000A;
}
.instance-content {
display: flex;
flex-direction: column;
row-gap: 15px;
}
.folder-name {
color: #F2F2F2;
font-weight: bold;
display: flex;
border-bottom: 1px solid #F2F2F2;
padding-bottom: 10px;
width: 100%;
}
.folder-name span {
width: 500px;
overflow: hidden;
text-wrap: nowrap;
text-overflow: ellipsis;
direction: rtl;
text-align: left;
}
.folder {
width: 20px;
height: 20px;
background-image: url("../icons/folder-open-solid.svg");
margin-right: 15px;
}
.version {
font-size: 13px;
color: #F2F2F2;
}
.input-table {
padding: 0px;
margin: 0px;
border-width: 0px;
border-collapse: collapse;
}
.input-table td {
color: #F2F2F2;
padding: 5px 0px;
font-size: 13px;
vertical-align: top;
}
.input-table td>div {
display: flex;
flex-direction: column;
width: fit-content;
}
.action-buttons {
display: flex;
flex-direction: row;
column-gap: 12px;
justify-content: start;
}
.label {
display: flex;
align-items: center;
column-gap: 5px;
}
.icon {
background-size: 100% 100%;
background-repeat: no-repeat;
}
.button {
width: fit-content;
height: 40px;
color: #F2F2F2;
border: 1px solid #F2F2F2;
background-size: 40px 60%;
background-position: 0px 50%;
background-repeat: no-repeat;
border-radius: 5px;
padding: 5px 15px 5px 45px;
font-weight: 600;
display: flex;
align-items: center;
font-size: 14px;
background-color: transparent;
}
.button:not(.disabled) {
cursor: pointer;
}
.apply {
background-image: url("../icons/check-solid.svg");
background-color: #017DC1;
border: 1px solid transparent;
}
.add {
padding: 5px 15px 5px 15px;
}
.update {
background-image: url("../icons/rotate-right-solid.svg");
}
.other {
padding: 5px 15px 5px 15px;
}
.remove {
background-image: url("../icons/trash-can-regular.svg");
}
.disabled {
background-color: #797E83;
}
.hide {
display: none;
}
.message {
font-weight: 600;
height: 20px;
font-size: 13px;
color: #F2F2F2;
}
input {
font-weight: 600;
font-size: 13px;
width: 240px;
border-radius: 4px;
}
input:focus{
outline: none;
}
input.error {
border-color: #FF5858;
}
.error {
font-weight: bold;
color: #FF5858;
}
.accent-red {
color: #FF5858;
color: var(--red);
}
.accent-green {
color: #8bff63;
color: var(--green);
}
.info {
width: 12px;
height: 12px;
background-image: url("../icons/circle-info-solid.svg");
background-position: 50% 50%;
.manager-page {
height: 100%;
padding: 35px;
}
.blink {
animation: blinker 1s linear infinite;
.page-header {
font-size: 18px;
font-weight: 600;
color: var(--offwhite);
border-bottom: 1px solid var(--offwhite);
padding-bottom: 15px;
margin-bottom: 10px;
}
@keyframes blinker {
50% {
color: transparent;
}
}
.popup {
.instructions {
color: var(--offwhite);
display: flex;
flex-direction: column;
row-gap: 15px;
height: 300px;
width: 500px;
position: absolute;
top: 50%;
left: 50%;
background-color: #181e25;
border-radius: 5px;
transform: translate(-250px, -150px);
padding: 30px;
box-shadow: 0px 0px 5px #000A;
row-gap: 4px;
}
.popup-header {
height: 20px;
color: #F2F2F2;
.instructions>span:first-child {
font-size: 14px;
font-weight: 600;
}
.popup-content {
height: calc(100% - 50px);
color: #F2F2F2;
.instructions>span:not(:first-child) {
font-size: 13px;
}
.popup-footer {
height: 30px;
.buttons-footer {
display: flex;
justify-content: end;
column-gap: 15px;
column-gap: 10px;
}
.popup-footer .apply {
background-image: none;
padding: 5px 15px 5px 15px;
.button {
padding: 10px 15px;
border-radius: 5px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.back {
color: var(--background);
background-color: var(--offwhite);
}
.next {
color: var(--offwhite);
background-color: var(--blue);
}
.cancel {
padding: 10px 5px;
color: var(--offwhite);
}
.close-popup {
color: var(--offwhite);
background-color: var(--blue);
}
input {
outline: none;
font-weight: 600;
color: var(--background);
font-size: 13px;
padding: 3px 10px;
border-radius: 5px;
}
.hide {
display: none !important;
}
#grayout {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: black;
opacity: 30%;
}
#popup {
width: 300px;
height: 200px;
position: absolute;
background-color: var(--background);
border-radius: 5px;
left: calc(50% - 150px);
top: calc(50% - 100px);
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 20px;
align-items: center;
}
#popup img {
width: 50px;
height: 50px;
}
#popup .content {
color: var(--offwhite);
font-size: 13px;
font-weight: 600;
width: 100%;
text-align: left;
}
#popup .footer {
height: fit-content;
display: flex;
justify-content: end;
width: 100%;
}

View File

@ -9,7 +9,7 @@ echo "* |_____/ \_____|_____/ \____/|_|\__, |_| |_| |_| .__/ \__,_|___/ *
echo "* __/ | | | *"
echo "* |___/ |_| *"
echo "*********************************************************************"
echo Welcome to the DCS Olympus v{{OLYMPUS_VERSION_NUMBER}} installation script. Please wait while the necessary dependencies are installed!
echo Welcome to the DCS Olympus {{OLYMPUS_VERSION_NUMBER}} installation script. Please wait while the necessary dependencies are installed!
echo:
@ -17,7 +17,7 @@ WHERE /q powershell
if %ERRORLEVEL% NEQ 0 (
echo Powershell not installed in the system, no output log available.
) else (
echo The output of this script is also available in the file %CD%\output.log. If you encounter any error, make sure to attach that file to your help request!
echo The output of this script is also available in the file "%CD%\output.log". If you encounter any error, make sure to attach that file to your help request!
)
timeout /t 5