25
client/@types/olympus/index.d.ts
vendored
@ -105,6 +105,10 @@ declare module "constants/constants" {
|
||||
export const ROEs: string[];
|
||||
export const reactionsToThreat: string[];
|
||||
export const emissionsCountermeasures: string[];
|
||||
export const ERAS: {
|
||||
name: string;
|
||||
chronologicalOrder: number;
|
||||
}[];
|
||||
export const ROEDescriptions: string[];
|
||||
export const reactionsToThreatDescriptions: string[];
|
||||
export const emissionsCountermeasuresDescriptions: string[];
|
||||
@ -836,6 +840,7 @@ declare module "other/utils" {
|
||||
}): UnitBlueprint | null;
|
||||
export function getMarkerCategoryByName(name: string): "aircraft" | "helicopter" | "groundunit-sam" | "navyunit" | "groundunit-other";
|
||||
export function getUnitDatabaseByCategory(category: string): import("unit/databases/aircraftdatabase").AircraftDatabase | import("unit/databases/helicopterdatabase").HelicopterDatabase | import("unit/databases/groundunitdatabase").GroundUnitDatabase | import("unit/databases/navyunitdatabase").NavyUnitDatabase | null;
|
||||
export function getCategoryBlueprintIconSVG(category: string, unitName: string): string | false;
|
||||
export function base64ToBytes(base64: string): ArrayBufferLike;
|
||||
export function enumToState(state: number): string;
|
||||
export function enumToROE(ROE: number): string;
|
||||
@ -1600,6 +1605,7 @@ declare module "map/map" {
|
||||
import { CoalitionAreaContextMenu } from "contextmenus/coalitionareacontextmenu";
|
||||
import { AirbaseSpawnContextMenu } from "contextmenus/airbasespawnmenu";
|
||||
export type MapMarkerVisibilityControl = {
|
||||
"category"?: string;
|
||||
"image": string;
|
||||
"isProtected"?: boolean;
|
||||
"name": string;
|
||||
@ -1997,6 +2003,25 @@ declare module "unit/importexport/unitdatafileexport" {
|
||||
showForm(units: Unit[]): void;
|
||||
}
|
||||
}
|
||||
declare module "schemas/schema" {
|
||||
import Ajv from "ajv";
|
||||
import { AnySchemaObject } from "ajv/dist/core";
|
||||
abstract class JSONSchemaValidator {
|
||||
#private;
|
||||
constructor(schema: AnySchemaObject);
|
||||
getAjv(): Ajv;
|
||||
getCompiledValidator(): any;
|
||||
getErrors(): any;
|
||||
getSchema(): AnySchemaObject;
|
||||
validate(data: any): any;
|
||||
}
|
||||
export class AirbasesJSONSchemaValidator extends JSONSchemaValidator {
|
||||
constructor();
|
||||
}
|
||||
export class ImportFileJSONSchemaValidator extends JSONSchemaValidator {
|
||||
constructor();
|
||||
}
|
||||
}
|
||||
declare module "unit/importexport/unitdatafileimport" {
|
||||
import { Dialog } from "dialog/dialog";
|
||||
import { UnitDataFile } from "unit/importexport/unitdatafile";
|
||||
|
||||
@ -60,6 +60,11 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.leaflet-container img.leaflet-tile {
|
||||
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
|
||||
.leaflet-container.leaflet-touch-zoom {
|
||||
-ms-touch-action: pan-x pan-y;
|
||||
touch-action: pan-x pan-y;
|
||||
@ -646,7 +651,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||
}
|
||||
|
||||
/* Printing */
|
||||
|
||||
|
||||
@media print {
|
||||
/* Prevent printers from removing background-images of controls. */
|
||||
.leaflet-control {
|
||||
|
||||
@ -1,55 +1,55 @@
|
||||
<style>
|
||||
#manager-connections .success,
|
||||
#manager-connections .error {
|
||||
#connections-page .success,
|
||||
#connections-page .error {
|
||||
position: absolute;
|
||||
left: 420px;
|
||||
left: 320px;
|
||||
display: flex;
|
||||
width: 150px;
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
#manager-connections .success {
|
||||
#connections-page .success {
|
||||
content: url("./icons/check-solid-green.svg");
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
#manager-connections .error img {
|
||||
#connections-page .error img {
|
||||
content: url("./icons/triangle-exclamation-solid.svg");
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
#manager-connections .error span {
|
||||
#connections-page .error span {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
color: var(--red);
|
||||
height: fit-content;
|
||||
}
|
||||
</style>
|
||||
<div id="manager-connections">
|
||||
<div class="step-summary">
|
||||
<div class="blue <%= !install || simplified? 'hide': '' %>">User path</div>
|
||||
<div class="white">Ports and address</div>
|
||||
<div class="empty">Passwords</div>
|
||||
<div class="empty"> <%= install? 'Install': 'Update' %></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="instructions">
|
||||
<span>
|
||||
Accept or modify port settings (optional)
|
||||
</span>
|
||||
<span>
|
||||
If you are installing Olympus locally for Single player use, it's recommended you leave these as default and continue.
|
||||
If you are installing a dedicated server, then follow the instructions available on the DCS Olympus Wiki.
|
||||
</span>
|
||||
<div id="connections-page">
|
||||
<div class="instructions">
|
||||
<div class="step">
|
||||
Step <%= singleInstance? "3": "4" %> of <%= singleInstance? "4": "5" %>
|
||||
</div>
|
||||
<div class="title">
|
||||
Manually set Olympus port and address settings
|
||||
</div>
|
||||
<div class="note">
|
||||
Please note: you may be required to allow these ports through your firewall and modem/router via port
|
||||
forwarding. <br>
|
||||
Otherwise, others may not be able to connect to Olympus.
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-inputs">
|
||||
<div class="input-group client-port">
|
||||
<span>Client port
|
||||
<img src="./icons/circle-info-solid.svg" title="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">
|
||||
<img src="./icons/circle-info-solid.svg"
|
||||
title="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"] %>">
|
||||
<input type="number" min="1024" max="65535" value="<%= activeInstance["clientPort"] %>"
|
||||
onchange="signal('onClientPortChanged', this.value)">
|
||||
<img class="success hide">
|
||||
<div class="error hide">
|
||||
<img> <span>Port already in use</span>
|
||||
@ -58,10 +58,12 @@
|
||||
</div>
|
||||
<div class="input-group backend-port">
|
||||
<span>Backend port
|
||||
<img src="./icons/circle-info-solid.svg" title="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.">
|
||||
<img src="./icons/circle-info-solid.svg"
|
||||
title="This port is used by Olympus to communicate with DCS. You only need to allow it through your firewall if you enable direct API connection">
|
||||
</span>
|
||||
<div>
|
||||
<input type="number" min="1024" max="65535" value="<%= instance["backendPort"] %>">
|
||||
<input type="number" min="1024" max="65535" value="<%= activeInstance["backendPort"] %>"
|
||||
onchange="signal('onBackendPortChanged', this.value)">
|
||||
<img class="success hide">
|
||||
<div class="error hide">
|
||||
<img> <span>Port already in use</span>
|
||||
@ -69,25 +71,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group backend-address">
|
||||
<span>Backend address
|
||||
<img src="./icons/circle-info-solid.svg" title="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 onclick="signal('onEnableAPIClicked')">
|
||||
<div class="checkbox"></div> Enable direct backend API connection
|
||||
<img src="./icons/circle-info-solid.svg"
|
||||
title="Allows services to connect to Olympus directly. This is NOT NEEDED for normal Olympus operation, even for dedicated servers. Leave it unchecked if in doubt.">
|
||||
</span>
|
||||
<input type="text" value="<%= instance["backendAddress"] %>">
|
||||
</div>
|
||||
<div class="buttons-footer">
|
||||
<% if (!simplified) { %>
|
||||
<div class="button back">
|
||||
Back
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="button next">
|
||||
Next
|
||||
</div>
|
||||
<div class="note warning hide">
|
||||
Note: if you enable direct backend API connection, you will be required to run DCS as admin or run the netsh
|
||||
command for others to connect. Leave unchecked if you don't know what this is. <br>See the Olympus
|
||||
documentation for more details.
|
||||
</div>
|
||||
<% if (!simplified) { %>
|
||||
<div class="button cancel">
|
||||
<%= install? "Cancel installation": "Cancel editing" %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
25
manager/ejs/connectionsType.ejs
Normal file
@ -0,0 +1,25 @@
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<div>
|
||||
<div class="instructions">
|
||||
<div class="step">
|
||||
Step <%= singleInstance? "2": "3" %> of <%= singleInstance? "4": "5" %>
|
||||
</div>
|
||||
<div class="title">
|
||||
Do you want to set port and address settings?
|
||||
</div>
|
||||
<div class="description">
|
||||
We can automatically set port and address settings for you, or you can set them manually. <br>
|
||||
If you don't have a good understanding of how Olympus works, we recommend the <i>auto apply settings</i> option.
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-inputs">
|
||||
<div class="button radio auto <%= activeInstance.connectionsType === 'auto'? 'selected': '' %>" onclick="signal('onConnectionsTypeClicked', 'auto')">
|
||||
Auto apply settings
|
||||
</div>
|
||||
<div class="button radio manual <%= activeInstance.connectionsType !== 'auto'? 'selected': '' %>" onclick="signal('onConnectionsTypeClicked', 'manual')">
|
||||
Manually set
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
24
manager/ejs/folder.ejs
Normal file
@ -0,0 +1,24 @@
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<div>
|
||||
<div class="instructions">
|
||||
<div class="step">
|
||||
Step 1 of <%= singleInstance? "4": "5" %>
|
||||
</div>
|
||||
<div class="title">
|
||||
Which DCS instance you want to add Olympus to?
|
||||
</div>
|
||||
<div class="description">
|
||||
Olympus is added to DCS instances individually, and will only work for that specific instance. <br>
|
||||
You can have Olympus installed across multiple DCS instances. Re-run in the install wizard to add Olympus to another DCS install.
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-inputs">
|
||||
<% for (var i = 0; i < instances.length; i++) { %>
|
||||
<div class="button radio <%= (i === 0)? 'selected': '' %>" onclick="signal('onFolderClicked', '<%= instances[i].name %>')" data-folder="<%= instances[i].folder %>">
|
||||
<%= instances[i].name %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,126 +0,0 @@
|
||||
<style>
|
||||
#manager-installations .scroll-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#manager-installations .scrollable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 8px;
|
||||
height: fit-content;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#manager-installations .option {
|
||||
cursor: pointer;
|
||||
background-color: var(--darkgray);
|
||||
width: 600px;
|
||||
height: 100px;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
padding-left: 15px;
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#manager-installations .option * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#manager-installations .option {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 5px;
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#manager-installations .option>span:nth-child(1) {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#manager-installations .option>span:nth-child(2) {
|
||||
display: flex;
|
||||
column-gap: 10px;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#manager-installations .option>span:nth-child(3) {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--lightgray);
|
||||
}
|
||||
|
||||
#manager-installations .option>span:nth-child(3).installed {
|
||||
font-weight: 600;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
#manager-installations .option>span:nth-child(3).error {
|
||||
font-weight: 600;
|
||||
color: orange;
|
||||
}
|
||||
|
||||
#manager-installations .option.installed {
|
||||
pointer-events: none;
|
||||
background-color: var(--background-disabled);
|
||||
}
|
||||
|
||||
#manager-installations .option:not(.installed)::after {
|
||||
display: block;
|
||||
content: " ";
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-image: url("./icons/chevron-right-solid.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
}
|
||||
</style>
|
||||
<div id="manager-installations">
|
||||
<div class="step-summary">
|
||||
<div class="white">User path</div>
|
||||
<div class="empty">Ports and address</div>
|
||||
<div class="empty">Passwords</div>
|
||||
<div class="empty">Install</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="instructions">
|
||||
<span>
|
||||
Select a DCS path to install Olympus to.
|
||||
</span>
|
||||
<span>
|
||||
We have automatically detected the following DCS installations under your Saved Games / DCS folder.
|
||||
</span>
|
||||
<span>
|
||||
Please select which DCS installations you want to add Olympus to.
|
||||
</span>
|
||||
</div>
|
||||
<div class="scroll-container">
|
||||
<div class="scrollable">
|
||||
<% for (let i = 0; i < instances.length; i++) {%>
|
||||
<div class="option <%= instances[i].installed? 'installed': '' %>" data-folder="<%= instances[i].folder %>">
|
||||
<span><%= instances[i].name %></span>
|
||||
<span><img src="./icons/folder-open-solid.svg"> <%= instances[i].folder %></span>
|
||||
<span class="<%= instances[i].installed? (instances[i].error? 'error': 'installed'): '' %>">
|
||||
<%= instances[i].installed? (instances[i].error? 'Corrupted/outdated Olympus installation': 'Olympus installed'): 'Olympus not installed' %>
|
||||
</span>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button cancel">
|
||||
Cancel installation
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,9 +1,4 @@
|
||||
<style>
|
||||
#manager-instances {
|
||||
padding-left: 80px;
|
||||
padding-right: 80px;
|
||||
}
|
||||
|
||||
#manager-instances .scroll-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
@ -18,7 +13,7 @@
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
padding: 15px;
|
||||
padding: 15px 40px;
|
||||
}
|
||||
|
||||
#manager-instances .option {
|
||||
@ -36,8 +31,16 @@
|
||||
row-gap: 25px;
|
||||
}
|
||||
|
||||
#manager-instances>.instructions {
|
||||
margin-bottom: 10px;
|
||||
#manager-instances .option:not(.installed) {
|
||||
background-color: var(--background-disabled);
|
||||
}
|
||||
|
||||
#manager-instances .option:not(.installed) .info {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
#manager-instances .option:not(.installed) .server-data {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
#manager-instances .button.cancel {
|
||||
@ -107,7 +110,7 @@
|
||||
#manager-instances .instance-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 5px;
|
||||
row-gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -192,6 +195,7 @@
|
||||
}
|
||||
|
||||
#manager-instances .edit,
|
||||
#manager-instances .install,
|
||||
#manager-instances .uninstall,
|
||||
#manager-instances .stop {
|
||||
color: var(--offwhite);
|
||||
@ -200,36 +204,34 @@
|
||||
}
|
||||
|
||||
#manager-instances .edit:hover,
|
||||
#manager-instances .install:hover,
|
||||
#manager-instances .uninstall:hover,
|
||||
#manager-instances .stop:hover {
|
||||
color: var(--background);
|
||||
background-color: var(--offwhite);
|
||||
}
|
||||
|
||||
#manager-instances .install {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div id="manager-instances">
|
||||
<div id="manager-instances" style="margin-bottom: 10px;">
|
||||
<div class="content">
|
||||
<div class="button cancel">
|
||||
<img src="./icons/chevron-left-solid.svg"/> Return to menu
|
||||
</div>
|
||||
<div class="instructions">
|
||||
<span>
|
||||
<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 and manage installs
|
||||
</span>
|
||||
<span>
|
||||
The following Oympus installs have been identified. <br>You can start an Olympus server, modify settings and uninstall below.
|
||||
<span style="color: var(--offwhite); font-size: 14px; text-align: center;">
|
||||
The following DCS installations have been identified. <br>You can start an Olympus server, modify settings and uninstall below.
|
||||
</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 class="option <%= instances[i].installed? 'installed': '' %>" data-folder="<%= instances[i].folder %>">
|
||||
<div class="instance-info">
|
||||
<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="server-data">
|
||||
<div class="server-status webserver online hide">ACTIVE</div>
|
||||
<div class="server-status webserver offline">OFFLINE</div>
|
||||
@ -239,33 +241,42 @@
|
||||
<div class="server-data-entry load"><img src="./icons/server-solid.svg"><span>Load: </span><span class="data">0</span></div>
|
||||
<div class="server-data-entry uptime"></div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
<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>
|
||||
|
||||
<span class="<%= instances[i].installed? (instances[i].error? 'error': 'installed'): '' %>">
|
||||
<%= instances[i].installed? (instances[i].error? 'Corrupted/outdated Olympus installation': 'Olympus installed'): 'Olympus not installed' %>
|
||||
</span>
|
||||
<span><img src="./icons/folder-open-solid.svg"> <%= instances[i].folder %></span>
|
||||
<div style="display: flex; flex-direction: column; row-gap: 5px;">
|
||||
<div class="info">
|
||||
<div>Client port</div>
|
||||
<div> <%= instances[i].installed? instances[i].clientPort: "N/A" %> </div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div>Backend port</div>
|
||||
<div> <%= instances[i].installed? instances[i].backendPort: "N/A" %> </div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div>Backend address</div>
|
||||
<div> <%= instances[i].installed? instances[i].backendAddress: "N/A" %> </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="instance-buttons">
|
||||
<div class="button start collapse">
|
||||
Start Olympus
|
||||
<div>
|
||||
<div class="button start-server">Start server</div>
|
||||
<div class="button start-client">Start client</div>
|
||||
<div class="button start-server" onclick="signal('onStartServerClicked', '<%= instances[i].name %>')">Start server</div>
|
||||
<div class="button start-client" onclick="signal('onStartClientClicked', '<%= instances[i].name %>')">Start client</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button edit">Edit settings</div>
|
||||
<div class="button uninstall">Uninstall Olympus</div>
|
||||
<div class="button open-browser hide">Open in browser</div>
|
||||
<div class="button stop hide">Stop Olympus</div>
|
||||
<div class="button edit" onclick="signal('onEditClicked', '<%= instances[i].name %>')">Edit settings</div>
|
||||
<div class="button install" onclick="signal('onInstallClicked', '<%= instances[i].name %>')">Install Olympus</div>
|
||||
<div class="button uninstall" onclick="signal('onUninstallClicked', '<%= instances[i].name %>')">Uninstall Olympus</div>
|
||||
<div class="button open-browser hide" onclick="signal('onOpenBrowserClicked', '<%= instances[i].name %>')">Open in browser</div>
|
||||
<div class="button stop hide" onclick="signal('onStopClicked', '<%= instances[i].name %>')">Stop Olympus</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
@ -1,62 +1,55 @@
|
||||
<style>
|
||||
#summary {
|
||||
width: 70%;
|
||||
#manager-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
color: var(--offwhite);
|
||||
padding: 80px;
|
||||
min-height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#summary .icon {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
margin-bottom: -30px;
|
||||
}
|
||||
|
||||
#summary div:nth-child(1) {
|
||||
font-size: 50px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#summary div:nth-child(2) {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#summary div:nth-child(3) {
|
||||
color: var(--lightgray);
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
margin-top: 20px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
#manager-menu #menu {
|
||||
#manager-menu>div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 20px;
|
||||
width: 60%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#manager-menu .option {
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--offwhite);
|
||||
width: 460px;
|
||||
height: 70px;
|
||||
height: 110px;
|
||||
color: var(--offwhite);
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
padding-left: 15px;
|
||||
align-items: center;
|
||||
align-items: start;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
color: var(--offwhite);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
row-gap: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#manager-menu .option>div {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#manager-menu .option::after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: " ";
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-image: url("./icons/chevron-right-solid.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
#manager-menu .option:hover {
|
||||
@ -64,6 +57,10 @@
|
||||
background-color: var(--offwhite);
|
||||
}
|
||||
|
||||
#manager-menu .option:hover::after {
|
||||
filter: invert();
|
||||
}
|
||||
|
||||
#manager-menu .option.disabled {
|
||||
pointer-events: none;
|
||||
color: var(--darkgray);
|
||||
@ -73,20 +70,31 @@
|
||||
#manager-menu .option * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div id="manager-menu">
|
||||
<div id="summary">
|
||||
<div>DCS OLYMPUS <img class="icon" src="../img/OlympusLogoFinal_4k.png" \></div>
|
||||
<div>INSTALL WIZARD AND MANAGER</div>
|
||||
<div>Using this manager, you can install Olympus, update settings, and view and manage instances</div>
|
||||
</div>
|
||||
<div id="menu">
|
||||
<div class="option install <%= installEnabled? '': 'disabled' %>">
|
||||
Install Olympus
|
||||
<div id="summary" style="width: 70%; height: 100%; color: var(--offwhite); padding: 80px;">
|
||||
<div style="font-size: 50px; font-weight: bold;">
|
||||
DCS OLYMPUS <img src="../img/OlympusLogoFinal_4k.png" style="height: 100px; width: 100px; margin-bottom: -30px;"\>
|
||||
</div>
|
||||
<div class="option manage <%= manageEnabled? '': 'disabled' %>">
|
||||
View / Manage installs
|
||||
<div style="font-size: 20px; font-weight: bold;">
|
||||
INSTALL WIZARD AND MANAGER
|
||||
</div>
|
||||
<div style="color: var(--lightgray); font-size: 13px; font-weight: normal; margin-top: 20px; width: 300px;">
|
||||
Using this manager, you can install Olympus, update settings, and view and manage instances
|
||||
</div>
|
||||
</div>
|
||||
<div id="menu" style="row-gap: 20px; width: 60%;">
|
||||
<div class="option <%= installEnabled? '': 'disabled' %>" onclick="signal('onInstallMenuClicked')">
|
||||
Add Olympus
|
||||
<div>
|
||||
Add or update Olympus to a new DCS instance
|
||||
</div>
|
||||
</div>
|
||||
<div class="option <%= editEnabled? '': 'disabled' %>" onclick="signal('onEditMenuClicked')">
|
||||
Change settings
|
||||
<div>
|
||||
Adjust port, address and password settings
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,50 +1,40 @@
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<div id="manager-passwords">
|
||||
<div class="step-summary">
|
||||
<div class="blue <%= !install || simplified? 'hide': '' %>">User path</div>
|
||||
<div class="blue">Ports and address</div>
|
||||
<div class="white">Passwords</div>
|
||||
<div class="empty"> <%= install? 'Install': 'Update' %></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="instructions">
|
||||
<span>
|
||||
Enter your passwords to access Olympus
|
||||
</span>
|
||||
<span>
|
||||
When logging into Olympus, these passwords will let you access the different roles. Gamemaster is the default.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</style>
|
||||
<div id="passwords-page">
|
||||
<div class="instructions">
|
||||
<div class="step">
|
||||
Step <%= singleInstance? "4": "5" %> of <%= singleInstance? "4": "5" %>
|
||||
</div>
|
||||
<div class="title">
|
||||
Enter your passwords for Olympus
|
||||
</div>
|
||||
<div class="description">
|
||||
When logging into Olympus, these passwords will let you access the different roles. <br>
|
||||
Game Master is the default and is used as a global commander. The other two are used as a part of the RTS mode.
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-inputs">
|
||||
<div class="input-group game-master">
|
||||
<span>Game Master Password<img src="./icons/circle-info-solid.svg" title="This password is used to access Olympus as Game Master with full privileges.">
|
||||
<span>Game Master Password<img src="./icons/circle-info-solid.svg"
|
||||
title="This password is used to access Olympus as Game Master with full privileges.">
|
||||
</span>
|
||||
<input type="password" minlength="8">
|
||||
<input type="password" minlength="8" onchange="signal('onGameMasterPasswordChanged', this.value)" value="<%= activeInstance["installed"]? 'This is a long string so that users know this is not actually their password. Hi Tony!': '' %>">
|
||||
</div>
|
||||
<div class="input-group blue-commander">
|
||||
<span>Blue Commander Password<img src="./icons/circle-info-solid.svg" title="This password is used to access Olympus as blue coalition Commander.">
|
||||
<span>Blue Commander Password<img src="./icons/circle-info-solid.svg"
|
||||
title="This password is used to access Olympus as blue coalition Commander.">
|
||||
</span>
|
||||
<input type="password" minlength="8">
|
||||
<input type="password" minlength="8" onchange="signal('onBlueCommanderPasswordChanged', this.value)" value="<%= activeInstance["installed"]? 'This is a long string so that users know this is not actually their password. Hi Tony!': '' %>">
|
||||
</div>
|
||||
<div class="input-group red-commander">
|
||||
<span>Red Commander Password<img src="./icons/circle-info-solid.svg" title="This password is used to access Olympus as red coalition Commander.">
|
||||
<span>Red Commander Password<img src="./icons/circle-info-solid.svg"
|
||||
title="This password is used to access Olympus as red coalition Commander.">
|
||||
</span>
|
||||
<input type="password" minlength="8">
|
||||
<input type="password" minlength="8" onchange="signal('onRedCommanderPasswordChanged', this.value)" value="<%= activeInstance["installed"]? 'This is a long string so that users know this is not actually their password. Hi Tony!': '' %>">
|
||||
</div>
|
||||
<div class="buttons-footer">
|
||||
<div class="button back">
|
||||
Back
|
||||
</div>
|
||||
<div class="button next">
|
||||
Next
|
||||
</div>
|
||||
<div class="<%= activeInstance["installed"]? '': 'hide' %>" style="color: var(--offwhite); font-size: 14px; color: var(--lightgray);">
|
||||
Note: to keep the old passwords, click <b>Next</b> without editing any value.
|
||||
</div>
|
||||
<% if (!simplified) { %>
|
||||
<div class="button cancel">
|
||||
<%= install? "Cancel installation": "Cancel editing" %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,146 +1,156 @@
|
||||
<style>
|
||||
#manager-result .content {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#manager-result img.success {
|
||||
content: url("./icons/check-solid-green.svg");
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
#result-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 30px;
|
||||
padding: 60px 120px;
|
||||
}
|
||||
|
||||
#manager-result img.error {
|
||||
content: url("./icons/triangle-exclamation-solid.svg");
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
#result-page .result-summary {
|
||||
padding: 25px 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
#manager-result img.wait {
|
||||
content: url("./icons/spinner-solid.svg");
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
animation: rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
#manager-result .summary {
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
#manager-result .step {
|
||||
#result-page .result-summary .title {
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
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(--darkgray);
|
||||
border-left: 5px solid var(--blue);
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
color: white;
|
||||
#result-page .result-summary .title img {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#result-page .result-summary .description {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#result-page .result-summary.success{
|
||||
color: var(--background-color);
|
||||
background-color: var(--green);
|
||||
}
|
||||
|
||||
#result-page .result-summary.error{
|
||||
color: var(--background-color);
|
||||
background-color: var(--red);
|
||||
}
|
||||
|
||||
#result-page .instructions-group {
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
padding-left: 15px;
|
||||
color: var(--offwhite);
|
||||
flex-direction: column;
|
||||
row-gap: 15px;
|
||||
}
|
||||
|
||||
#result-page .usage-instructions {
|
||||
background-color: var(--background-usage);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
column-gap: 25px;
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
width: 500px;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
#manager-result .result>img {
|
||||
margin-left: 5px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#manager-result .result>div {
|
||||
#result-page .usage-instructions>div {
|
||||
color: var(--offwhite);
|
||||
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;
|
||||
justify-items: center;
|
||||
align-items: start;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#result-page .usage-instructions>div>img {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
#result-page .usage-instructions>img {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
#result-page .link {
|
||||
display: inline;
|
||||
color: #5CA7FF;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
<div id="manager-result">
|
||||
<div class="content">
|
||||
<div class="step hook <%= !install? 'hide': '' %>">
|
||||
Installing hook scripts<img class="wait"><img class="success hide"><img class="error hide">
|
||||
<div id="result-page">
|
||||
<div class="result-summary success hide">
|
||||
<div class="title"><img src="./icons/check-solid-background.svg">Olympus successfully added to <i style="margin-left: 3px"><%= activeInstance["name"] %></i>!</div>
|
||||
<div class="description">See the <b>DCS Olympus Wiki</b> for more information on how to use Olympus and for troubleshooting issues. You may now close the installer.</div>
|
||||
</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 <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 class="step mod <%= !install? 'hide': '' %>">
|
||||
Installing mod folder<img class="wait"><img class="success hide"><img class="error hide">
|
||||
<div style="font-size: 13px; color: var(--offwhite);">
|
||||
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>
|
||||
<div class="step json <%= !install? 'hide': '' %>">
|
||||
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 <%= !install? 'hide': '' %>">
|
||||
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="info success hide">
|
||||
You may now start DCS and use Olympus either with the shortcuts or the "View and manage instances" entry in the
|
||||
main menu.
|
||||
</div>
|
||||
|
||||
<div class="info error hide">
|
||||
Please make sure DCS is not currently being executed. Check <%= logLocation %> for more info.
|
||||
</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>
|
||||
<% if (activeInstance["installationType"] === "singleplayer") { %>
|
||||
<div class="usage-instructions" style="width: 600px;">
|
||||
<div>
|
||||
<img src="./icons/olympus_white.png">
|
||||
<div>
|
||||
Launch the <b>Olympus Client</b> via the shortcut on your desktop or in <i><b><%= activeInstance["name"] %></b></i>.
|
||||
</div>
|
||||
</div>
|
||||
<img src="./icons/arrow-right-solid.svg">
|
||||
<div>
|
||||
<img src="./icons/gamepad-solid.svg">
|
||||
<div>
|
||||
<b>Launch DCS</b>, load a mission and unpause the game. Enjoy!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons-footer">
|
||||
<div class="button back">
|
||||
Back to main menu
|
||||
<div style="font-size: 13px;">
|
||||
Alternatively, you can run the <b>Olympus Server</b> instead and visit <div class="link">http://localhost:<%= activeInstance["clientPort"] %></div> in a web browser (Google Chrome recommended) to replace the first step above.
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="usage-instructions">
|
||||
<div>
|
||||
<img src="./icons/server-solid.svg">
|
||||
<div>
|
||||
Launch the <b>Olympus Server</b> via the shortcut on your desktop or in <b><%= activeInstance["name"] %></b>.
|
||||
</div>
|
||||
</div>
|
||||
<img src="./icons/arrow-right-solid.svg">
|
||||
<div>
|
||||
<img src="./icons/chrome.svg">
|
||||
<div>
|
||||
To access Olympus remotely visit <div class="link">http://<%= ip %>:<%= activeInstance["clientPort"] %></div> <b>in a web browser</b> (Google Chrome recommended).
|
||||
</div>
|
||||
</div>
|
||||
<img src="./icons/arrow-right-solid.svg">
|
||||
<div>
|
||||
<img src="./icons/gamepad-solid.svg">
|
||||
<div>
|
||||
<b>Launch DCS</b>, load a mission and unpause the game. Enjoy!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size: 13px;">
|
||||
<b>To access Olympus from this PC</b>, you need to visit <div class="link">http://localhost:<%= activeInstance["clientPort"] %></div> in a web browser (Google Chrome recommended) instead.
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<div class="buttons-footer">
|
||||
<div class="button back" style="color: var(--offwhite); background-color: var(--background); border: 1px solid var(--offwhite);" onclick="signal('onReturnClicked')">
|
||||
Return to main menu
|
||||
</div>
|
||||
<div class="button next" style="color: var(--background); background-color: var(--offwhite);" onclick="signal('onCloseManagerClicked')">
|
||||
Close manager
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
266
manager/ejs/settings.ejs
Normal file
@ -0,0 +1,266 @@
|
||||
<style>
|
||||
#manager-settings .scroll-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
max-width: 100% !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#manager-settings .scrollable {
|
||||
display: flex;
|
||||
row-gap: 15px;
|
||||
column-gap: 15px;
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
padding: 15px 40px;
|
||||
}
|
||||
|
||||
#manager-settings .option {
|
||||
background-color: var(--darkgray);
|
||||
width: 48%;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
padding: 15px;
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
border-left: 5px solid var(--blue);
|
||||
flex-direction: column;
|
||||
row-gap: 25px;
|
||||
}
|
||||
|
||||
#manager-settings .option:not(.installed) {
|
||||
background-color: var(--background-disabled);
|
||||
}
|
||||
|
||||
#manager-settings .option:not(.installed) .info {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
#manager-settings .option:not(.installed) .server-data {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
#manager-settings .button.cancel {
|
||||
position: absolute;
|
||||
left: 110px;
|
||||
top: 130px;
|
||||
}
|
||||
|
||||
#manager-settings .server-data {
|
||||
display: flex;
|
||||
column-gap: 15px;
|
||||
row-gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#manager-settings .server-status {
|
||||
font-weight: 600;
|
||||
font-size: 15;
|
||||
display: flex;
|
||||
column-gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#manager-settings .server-status::before {
|
||||
display: block;
|
||||
content: "";
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 999px;
|
||||
background-color: var(--gray);
|
||||
}
|
||||
|
||||
#manager-settings .server-status.offline {
|
||||
color: var(--gray)
|
||||
}
|
||||
|
||||
#manager-settings .server-status.offline::before {
|
||||
background-color: var(--gray);
|
||||
}
|
||||
|
||||
#manager-settings .server-status.online {
|
||||
color: var(--green)
|
||||
}
|
||||
|
||||
#manager-settings .server-status.online::before {
|
||||
background-color: var(--green);
|
||||
}
|
||||
|
||||
#manager-settings .server-status.backend {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#manager-settings .server-data-entry {
|
||||
display: flex;
|
||||
column-gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#manager-settings .server-data-entry span:nth-child(2) {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#manager-settings .server-data-entry span:nth-child(3) {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#manager-settings .instance-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#manager-settings .instance-info>span:nth-child(1) {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#manager-settings .instance-info>span:nth-child(2) {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--lightgray);
|
||||
}
|
||||
|
||||
#manager-settings .instance-info>span:nth-child(2).installed {
|
||||
font-weight: 600;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
#manager-settings .instance-info>span:nth-child(2).error {
|
||||
font-weight: 600;
|
||||
color: orange;
|
||||
}
|
||||
|
||||
#manager-settings .instance-info>span:nth-child(3) {
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
color: var(--lightgray);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
#manager-settings .instance-info>span:nth-child(4) {
|
||||
display: flex;
|
||||
column-gap: 10px;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#manager-settings .instance-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
#manager-settings .instance-info .info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#manager-settings .instance-info .info>div:nth-child(1) {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#manager-settings .instance-info .info>div:nth-child(2) {
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#manager-settings .instance-info .divider {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#manager-settings .start, #manager-settings .open-browser {
|
||||
margin-right: auto;
|
||||
color: var(--offwhite);
|
||||
background-color: var(--blue);
|
||||
}
|
||||
|
||||
#manager-settings .start {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
#manager-settings .start>div {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
#manager-settings .edit,
|
||||
#manager-settings .install,
|
||||
#manager-settings .uninstall,
|
||||
#manager-settings .stop {
|
||||
color: var(--offwhite);
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--offwhite);
|
||||
}
|
||||
|
||||
#manager-settings .edit:hover,
|
||||
#manager-settings .install:hover,
|
||||
#manager-settings .uninstall:hover,
|
||||
#manager-settings .stop:hover {
|
||||
color: var(--background);
|
||||
background-color: var(--offwhite);
|
||||
}
|
||||
|
||||
#manager-settings .install {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
<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 and manage installs
|
||||
</span>
|
||||
<span style="color: var(--offwhite); font-size: 14px; text-align: center;">
|
||||
The following DCS installations have been identified. <br>You can modify settings and uninstall below.
|
||||
</span>
|
||||
</div>
|
||||
<div class="scroll-container">
|
||||
<div class="scrollable">
|
||||
<% for (let i = 0; i < instances.length; i++) {%>
|
||||
<% if (instances[i].installed) { %>
|
||||
<div class="option <%= instances[i].installed? 'installed': '' %>" data-folder="<%= instances[i].folder %>">
|
||||
<div class="instance-info">
|
||||
<span><%= instances[i].name %></span>
|
||||
<span><img src="./icons/folder-open-solid.svg"> <%= instances[i].folder %></span>
|
||||
<div class="divider"></div>
|
||||
<div style="display: flex; flex-direction: column; row-gap: 5px;">
|
||||
<div class="info">
|
||||
<div>Client port</div>
|
||||
<div> <%= instances[i].installed? instances[i].clientPort: "N/A" %> </div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div>Backend port</div>
|
||||
<div> <%= instances[i].installed? instances[i].backendPort: "N/A" %> </div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div>Backend address</div>
|
||||
<div> <%= instances[i].installed? instances[i].backendAddress: "N/A" %> </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="instance-buttons">
|
||||
<div class="button edit" onclick="signal('onEditClicked', '<%= instances[i].name %>')">Edit settings</div>
|
||||
<div class="button uninstall" onclick="signal('onUninstallClicked', '<%= instances[i].name %>')">Uninstall Olympus</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
25
manager/ejs/type.ejs
Normal file
@ -0,0 +1,25 @@
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<div>
|
||||
<div class="instructions">
|
||||
<div class="step">
|
||||
Step <%= singleInstance? "1": "2" %> of <%= singleInstance? "4": "5" %>
|
||||
</div>
|
||||
<div class="title">
|
||||
Do you want to add Olympus for singleplayer or multiplayer?
|
||||
</div>
|
||||
<div class="description">
|
||||
Select singleplayer if you only want to play locally on your own computer. <br>
|
||||
Select multiplayer if you want Olympus to be useable over the internet from a different computer, or this instance is a dedicated server.
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-inputs">
|
||||
<div class="button radio singleplayer <%= activeInstance.installationType === 'singleplayer'? 'selected': '' %>" onclick="signal('onInstallTypeClicked', 'singleplayer')">
|
||||
Singleplayer
|
||||
</div>
|
||||
<div class="button radio multiplayer <%= activeInstance.installationType !== 'singleplayer'? 'selected': '' %>" onclick="signal('onInstallTypeClicked', 'multiplayer')">
|
||||
Multiplayer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
68
manager/ejs/welcome.ejs
Normal file
@ -0,0 +1,68 @@
|
||||
<style>
|
||||
#manager-welcome {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 20px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#manager-welcome .instructions {
|
||||
width: 40%;
|
||||
text-align: center;
|
||||
color: var(--offwhite);
|
||||
}
|
||||
|
||||
#manager-welcome .option {
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--offwhite);
|
||||
width: 460px;
|
||||
height: 70px;
|
||||
color: var(--offwhite);
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
padding-left: 15px;
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
color: var(--offwhite);
|
||||
}
|
||||
|
||||
#manager-welcome .option:hover {
|
||||
color: var(--background);
|
||||
background-color: var(--offwhite);
|
||||
}
|
||||
|
||||
#manager-welcome .option.disabled {
|
||||
pointer-events: none;
|
||||
color: var(--darkgray);
|
||||
border-color: var(--darkgray);
|
||||
}
|
||||
|
||||
#manager-welcome .option * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div id="manager-welcome">
|
||||
<div class="instructions" style="font-size: 28px; font-weight: bold;">
|
||||
Do you want to use the Olympus Manager in basic or Expert mode?
|
||||
</div>
|
||||
<div class="instructions" style="color: var(--gray);">
|
||||
Basic mode is recommended for most users. <br>
|
||||
Expert mode is for those who know how Olympus works or for server owners.
|
||||
</div>
|
||||
<div class="instructions" style="color: var(--gray); font-weight: bold;">
|
||||
You can change this setting at any time.
|
||||
</div>
|
||||
<div class="option basic" onclick="signal('onBasicClicked')">
|
||||
Basic mode
|
||||
</div>
|
||||
<div class="option expert" onclick="signal('onExpertClicked')">
|
||||
Expert mode
|
||||
</div>
|
||||
</div>
|
||||
93
manager/ejs/wizard.ejs
Normal file
@ -0,0 +1,93 @@
|
||||
<style>
|
||||
.wizard-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 30px;
|
||||
padding: 60px 120px;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.buttons-footer {
|
||||
display: flex;
|
||||
column-gap: 10px;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 15px;
|
||||
color: var(--offwhite);
|
||||
}
|
||||
|
||||
.instructions .step {
|
||||
font-size: 14px;
|
||||
color: var(--lightgray);
|
||||
}
|
||||
|
||||
.instructions .description {
|
||||
font-size: 14px;
|
||||
color: var(--lightgray);
|
||||
}
|
||||
|
||||
.instructions .title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.content > div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 20px;
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wizard-inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.wizard-page .button.radio {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.note {
|
||||
width: 100%;
|
||||
background-color: var(--background-note);
|
||||
color: var(--offwhite);
|
||||
border-left: 5px solid var(--offwhite);
|
||||
font-size: 14px;
|
||||
padding: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: var(--background-warning);
|
||||
border-left: 5px solid var(--orange);
|
||||
}
|
||||
</style>
|
||||
<div class="wizard-page">
|
||||
<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('onCancelClicked')">
|
||||
<img src="./icons/chevron-left-solid.svg" style=" height: 14px;">Cancel install
|
||||
</div>
|
||||
<div class="content">
|
||||
|
||||
</div>
|
||||
<div class="buttons-footer">
|
||||
<div class="button back" style="color: var(--offwhite); background-color: var(--background); border: 1px solid var(--offwhite);" onclick="signal('onBackClicked')">
|
||||
Back
|
||||
</div>
|
||||
<div class="button next" style="color: var(--background); background-color: var(--offwhite);" onclick="signal('onNextClicked')">
|
||||
Next
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1
manager/icons/arrow-right-solid.svg
Normal 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 2024 Fonticons, Inc.--><path fill="#F2F2F2" d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>
|
||||
|
After Width: | Height: | Size: 490 B |
1
manager/icons/check-solid-background.svg
Normal 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="#181e25" 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 |
1
manager/icons/chrome.svg
Normal 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 2024 Fonticons, Inc.--><path fill="#F2F2F2" d="M0 256C0 209.4 12.5 165.6 34.3 127.1L144.1 318.3C166 357.5 207.9 384 256 384C270.3 384 283.1 381.7 296.8 377.4L220.5 509.6C95.9 492.3 0 385.3 0 256zM365.1 321.6C377.4 302.4 384 279.1 384 256C384 217.8 367.2 183.5 340.7 160H493.4C505.4 189.6 512 222.1 512 256C512 397.4 397.4 511.1 256 512L365.1 321.6zM477.8 128H256C193.1 128 142.3 172.1 130.5 230.7L54.2 98.5C101 38.5 174 0 256 0C350.8 0 433.5 51.5 477.8 128V128zM168 256C168 207.4 207.4 168 256 168C304.6 168 344 207.4 344 256C344 304.6 304.6 344 256 344C207.4 344 168 304.6 168 256z"/></svg>
|
||||
|
After Width: | Height: | Size: 804 B |
1
manager/icons/gamepad-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="20" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#F2F2F2" d="M192 64C86 64 0 150 0 256S86 448 192 448H448c106 0 192-86 192-192s-86-192-192-192H192zM496 168a40 40 0 1 1 0 80 40 40 0 1 1 0-80zM392 304a40 40 0 1 1 80 0 40 40 0 1 1 -80 0zM168 200c0-13.3 10.7-24 24-24s24 10.7 24 24v32h32c13.3 0 24 10.7 24 24s-10.7 24-24 24H216v32c0 13.3-10.7 24-24 24s-24-10.7-24-24V280H136c-13.3 0-24-10.7-24-24s10.7-24 24-24h32V200z"/></svg>
|
||||
|
After Width: | Height: | Size: 622 B |
BIN
manager/icons/olympus_white.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
@ -1 +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="M64 32C28.7 32 0 60.7 0 96v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zm280 72a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm48 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0zM64 288c-35.3 0-64 28.7-64 64v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V352c0-35.3-28.7-64-64-64H64zm280 72a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm56 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0z"/></svg>
|
||||
<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 2024 Fonticons, Inc.--><path fill="#F2F2F2" d="M64 32C28.7 32 0 60.7 0 96v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zm280 72a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm48 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0zM64 288c-35.3 0-64 28.7-64 64v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V352c0-35.3-28.7-64-64-64H64zm280 72a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm56 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0z"/></svg>
|
||||
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 647 B |
1
manager/icons/triangle-exclamation-solid-background.svg
Normal 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="#181e25" 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 |
@ -20,7 +20,7 @@
|
||||
<button class="title-bar-button maximize"></button>
|
||||
<button class="title-bar-button close"></button>
|
||||
</div>
|
||||
<div id="header">
|
||||
<div id="header" class="hide">
|
||||
<img class="main-icon" src="../img/OlympusLogoFinal_4k.png" \>
|
||||
<div class="version">
|
||||
<div> DCS Olympus Manager</div>
|
||||
@ -28,13 +28,16 @@
|
||||
</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 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"/>
|
||||
<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>
|
||||
<div id="loader" class="manager-page hide">
|
||||
Loading, please wait...
|
||||
<div style="font-weight: bold;">Loading, please wait...</div>
|
||||
<div class="loading-bar" style="width: 400px; height: 15px;"></div>
|
||||
<div class="loading-message" style="font-size: 14px; color: var(--lightgray)"></div>
|
||||
</div>
|
||||
<div id="grayout" class="hide"></div>
|
||||
<div id="popup" class="hide">
|
||||
@ -42,7 +45,7 @@
|
||||
<img src="./icons/circle-question-regular.svg" class="confirm">
|
||||
<img src="./icons/spinner-solid.svg" class="wait">
|
||||
<div class="content">
|
||||
|
||||
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="button accept-popup"> Accept </div>
|
||||
@ -77,6 +80,11 @@
|
||||
document.querySelector('.restore').classList.add("hide");
|
||||
document.querySelector('.maximize').classList.remove("hide");
|
||||
})
|
||||
|
||||
function signal(callback, params) {
|
||||
const event = new CustomEvent("signal", { detail: { callback: callback, params: params } });
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@ -1,87 +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 {
|
||||
onBackClicked;
|
||||
onNextClicked;
|
||||
onCancelClicked;
|
||||
instance;
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
render(str) {
|
||||
const element = this.getElement();
|
||||
element.innerHTML = str;
|
||||
|
||||
if (this.element.querySelector(".back"))
|
||||
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
|
||||
|
||||
if (this.element.querySelector(".next"))
|
||||
this.element.querySelector(".next").addEventListener("click", (e) => this.onNextClicked(e));
|
||||
|
||||
if (this.element.querySelector(".cancel"))
|
||||
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
|
||||
|
||||
this.element.querySelector(".client-port").querySelector("input").addEventListener("change", async (e) => { this.setClientPort(Number(e.target.value)); })
|
||||
this.element.querySelector(".backend-port").querySelector("input").addEventListener("change", async (e) => { this.setBackendPort(Number(e.target.value)); })
|
||||
this.element.querySelector(".backend-address").querySelector("input").addEventListener("change", async (e) => { this.instance.setBackendAddress(e.target.value); })
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.instance = this.options.instance;
|
||||
|
||||
ejs.renderFile("./ejs/connections.ejs", this.options, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
|
||||
/* Call the port setters to check if the ports are free */
|
||||
this.setClientPort(this.instance.clientPort);
|
||||
this.setBackendPort(this.instance.backendPort);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show();
|
||||
}
|
||||
|
||||
/** Asynchronously check if the client port is free and if it is, set the new value
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/** Asynchronously check if the backend port is free and if it is, set the new value
|
||||
*
|
||||
*/
|
||||
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 = ConnectionsPage;
|
||||
@ -1,65 +1,102 @@
|
||||
var regedit = require('regedit')
|
||||
const shellFoldersKey = 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders'
|
||||
const saveGamesKey = '{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}'
|
||||
const { getManager } = require('./managerfactory')
|
||||
var regedit = require('regedit').promisified;
|
||||
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 } = 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 { 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) {
|
||||
this.instances = await this.findInstances();
|
||||
}
|
||||
return this.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() {
|
||||
let promise = new Promise((res, rej) => {
|
||||
/* Get the Saved Games folder from the registry */
|
||||
regedit.list(shellFoldersKey, function (err, result) {
|
||||
if (err) {
|
||||
rej(err);
|
||||
/* Get the Saved Games folder from the registry */
|
||||
getManager().setLoadingProgress("Finding DCS instances...");
|
||||
|
||||
var result = await regedit.list(shellFoldersKey);
|
||||
/* Check that the registry read was successfull */
|
||||
if (result[shellFoldersKey] !== undefined && result[shellFoldersKey]["exists"] && result[shellFoldersKey]['values'][saveGamesKey] !== undefined && result[shellFoldersKey]['values'][saveGamesKey]['value'] !== undefined) {
|
||||
/* Read all the folders in Saved Games */
|
||||
const searchpath = result[shellFoldersKey]['values'][saveGamesKey]['value'];
|
||||
const folders = fs.readdirSync(searchpath);
|
||||
var instances = [];
|
||||
|
||||
/* 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"))) {
|
||||
logger.log(`Found instance in ${folder}, checking for Olympus`)
|
||||
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);
|
||||
}
|
||||
else {
|
||||
/* Check that the registry read was successfull */
|
||||
if (result[shellFoldersKey] !== undefined && result[shellFoldersKey]["exists"] && result[shellFoldersKey]['values'][saveGamesKey] !== undefined && result[shellFoldersKey]['values'][saveGamesKey]['value'] !== undefined) {
|
||||
/* Read all the folders in Saved Games */
|
||||
const searchpath = result[shellFoldersKey]['values'][saveGamesKey]['value'];
|
||||
const folders = fs.readdirSync(searchpath);
|
||||
var instances = [];
|
||||
}
|
||||
} else {
|
||||
logger.error("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);
|
||||
|
||||
/* A DCS Instance is created if either the appsettings.lua or serversettings.lua file is detected */
|
||||
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)));
|
||||
}
|
||||
})
|
||||
return instances;
|
||||
}
|
||||
|
||||
res(instances);
|
||||
} else {
|
||||
logger.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.");
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
/** 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);
|
||||
|
||||
return promise;
|
||||
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 = "";
|
||||
@ -78,38 +115,61 @@ class DCSInstance {
|
||||
missionTime = "";
|
||||
load = 0;
|
||||
fps = 0;
|
||||
installationType = 'singleplayer';
|
||||
connectionsType = 'auto';
|
||||
gameMasterPasswordEdited = false;
|
||||
blueCommanderPasswordEdited = false;
|
||||
redCommanderPasswordEdited = false;
|
||||
|
||||
constructor(folder) {
|
||||
this.folder = folder;
|
||||
this.name = path.basename(folder);
|
||||
|
||||
/* Periodically "ping" Olympus to check if either the client or the backend are active */
|
||||
window.setInterval(async () => {
|
||||
await this.getData();
|
||||
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(folder, "Config", "olympus.json"))) {
|
||||
if (fs.existsSync(path.join(this.folder, "Config", "olympus.json"))) {
|
||||
getManager().setLoadingProgress(`Olympus installed in ${this.folder}`);
|
||||
try {
|
||||
/* Read the olympus.json */
|
||||
var config = JSON.parse(fs.readFileSync(path.join(folder, "Config", "olympus.json")));
|
||||
var config = JSON.parse(fs.readFileSync(path.join(this.folder, "Config", "olympus.json")));
|
||||
this.clientPort = config["client"]["port"];
|
||||
this.backendPort = config["server"]["port"];
|
||||
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;
|
||||
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);
|
||||
logger.log(`Comparing Mods content in ${this.folder}`)
|
||||
getManager().setLoadingProgress(`Comparing Mods content in ${this.folder}`);
|
||||
res1 = await dircompare.compare(path.join("..", "mod"), path.join(this.folder, "Mods", "Services", "Olympus"), options);
|
||||
logger.log(`Comparing Scripts content in ${this.folder}`)
|
||||
getManager().setLoadingProgress(`Comparing Scripts content in ${this.folder}`);
|
||||
res2 = await dircompare.compareSync(path.join("..", "scripts", "OlympusHook.lua"), path.join(this.folder, "Scripts", "Hooks", "OlympusHook.lua"), options);
|
||||
err1 = res1.differences !== 0;
|
||||
err2 = res2.differences !== 0;
|
||||
} catch (e) {
|
||||
@ -118,71 +178,37 @@ class DCSInstance {
|
||||
|
||||
if (err1 || err2) {
|
||||
this.error = true;
|
||||
getManager().setLoadingProgress(`Differences found in ${this.folder}`);
|
||||
logger.log("Differences found!")
|
||||
} else {
|
||||
getManager().setLoadingProgress(`No differences found in ${this.folder}`);
|
||||
}
|
||||
}
|
||||
|
||||
/* Periodically "ping" Olympus to check if either the client or the backend are active */
|
||||
window.setInterval(async () => {
|
||||
await this.getData();
|
||||
|
||||
var page = document.getElementById("manager-instances");
|
||||
if (page) {
|
||||
var instanceDivs = page.querySelectorAll(`.option`);
|
||||
for (let i = 0; i < instanceDivs.length; i++) {
|
||||
if (instanceDivs[i].dataset.folder == this.folder) {
|
||||
var instanceDiv = instanceDivs[i];
|
||||
if (instanceDiv.querySelector(".webserver.online") !== null) {
|
||||
instanceDiv.querySelector(".webserver.online").classList.toggle("hide", !this.webserverOnline)
|
||||
instanceDiv.querySelector(".webserver.offline").classList.toggle("hide", this.webserverOnline)
|
||||
instanceDiv.querySelector(".backend.online").classList.toggle("hide", !this.backendOnline)
|
||||
instanceDiv.querySelector(".backend.offline").classList.toggle("hide", this.backendOnline)
|
||||
|
||||
if (this.backendOnline) {
|
||||
instanceDiv.querySelector(".fps .data").innerText = this.fps;
|
||||
instanceDiv.querySelector(".load .data").innerText = this.load;
|
||||
}
|
||||
|
||||
instanceDiv.querySelector(".button.start").classList.toggle("hide", this.webserverOnline)
|
||||
instanceDiv.querySelector(".button.uninstall").classList.toggle("hide", this.webserverOnline)
|
||||
instanceDiv.querySelector(".button.edit").classList.toggle("hide", this.webserverOnline)
|
||||
instanceDiv.querySelector(".button.open-browser").classList.toggle("hide", !this.webserverOnline)
|
||||
instanceDiv.querySelector(".button.stop").classList.toggle("hide", !this.webserverOnline)
|
||||
|
||||
if (this.webserverOnline)
|
||||
instanceDiv.querySelector(".button.start").classList.remove("loading")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
return this.error;
|
||||
}
|
||||
|
||||
/** Asynchronously check if the client port is free and if it is, set the new value
|
||||
/** Set the client port
|
||||
*
|
||||
* @param {Number} newPort The new client port to set
|
||||
*/
|
||||
async setClientPort(newPort) {
|
||||
if (await this.checkClientPort(newPort)) {
|
||||
logger.log(`Instance ${this.folder} client port set to ${newPort}`)
|
||||
this.clientPort = newPort;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
setClientPort(newPort) {
|
||||
logger.log(`Instance ${this.folder} client port set to ${newPort}`)
|
||||
this.clientPort = newPort;
|
||||
}
|
||||
|
||||
/** Asynchronously check if the client port is free and if it is, set the new value
|
||||
/** Set the backend port
|
||||
*
|
||||
* @param {Number} newPort The new backend port to set
|
||||
*/
|
||||
async setBackendPort(newPort) {
|
||||
if (await this.checkBackendPort(newPort)) {
|
||||
logger.log(`Instance ${this.folder} client port set to ${newPort}`)
|
||||
this.backendPort = newPort;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
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;
|
||||
@ -190,84 +216,144 @@ class DCSInstance {
|
||||
|
||||
/** Set Game Master password
|
||||
*
|
||||
* @param {String} newPassword The new Game Master password to set
|
||||
*/
|
||||
setGameMasterPassword(newPassword) {
|
||||
this.gameMasterPassword = newPassword;
|
||||
this.gameMasterPasswordEdited = true;
|
||||
}
|
||||
|
||||
/** Set Blue Commander password
|
||||
*
|
||||
* @param {String} newAddress The new Blue Commander password to set
|
||||
*/
|
||||
setBlueCommanderPassword(newPassword) {
|
||||
this.blueCommanderPassword = newPassword;
|
||||
this.blueCommanderPasswordEdited = true;
|
||||
}
|
||||
|
||||
/** Set Red Commander password
|
||||
*
|
||||
* @param {String} newAddress The new Red Commander password to set
|
||||
*/
|
||||
setRedCommanderPassword(newPassword) {
|
||||
this.redCommanderPassword = newPassword;
|
||||
this.redCommanderPasswordEdited = true;
|
||||
}
|
||||
|
||||
/** Check if the client port is free
|
||||
/** 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);
|
||||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
|
||||
/** 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
|
||||
*/
|
||||
async 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;
|
||||
})
|
||||
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`);
|
||||
}
|
||||
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) {
|
||||
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;
|
||||
})
|
||||
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 {
|
||||
logger.log(`Port ${port} currently in use`);
|
||||
if (instance.clientPort === port) {
|
||||
logger.log(`Backend port ${port} equal to client port`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
res(portFree);
|
||||
return false;
|
||||
})
|
||||
})
|
||||
return promise;
|
||||
} 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.
|
||||
*
|
||||
*/
|
||||
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.
|
||||
@ -343,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) => {
|
||||
@ -368,20 +456,109 @@ class DCSInstance {
|
||||
})
|
||||
}
|
||||
|
||||
/* Uninstall this instance */
|
||||
uninstall() {
|
||||
showConfirmPopup("Are you sure you want to completely remove this Olympus installation?", () =>
|
||||
uninstallInstance(this.folder, this.name).then(
|
||||
() => {
|
||||
/** 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.`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Install this instance
|
||||
*
|
||||
*/
|
||||
async install() {
|
||||
showWaitLoadingPopup(`<span>Please wait while Olympus is being installed in <i>${this.name}</i></span>`);
|
||||
try {
|
||||
setPopupLoadingProgress("Installing hook scripts...", 0);
|
||||
await sleep(100);
|
||||
await installHooks(getManager().getActiveInstance().folder);
|
||||
|
||||
setPopupLoadingProgress("Installing mod folder...", 20);
|
||||
await sleep(100);
|
||||
await installMod(getManager().getActiveInstance().folder, getManager().getActiveInstance().name);
|
||||
|
||||
setPopupLoadingProgress("Installing JSON file...", 40);
|
||||
await sleep(100);
|
||||
await installJSON(getManager().getActiveInstance().folder);
|
||||
|
||||
setPopupLoadingProgress("Applying configuration...", 60);
|
||||
await sleep(100);
|
||||
await applyConfiguration(getManager().getActiveInstance().folder, getManager().getActiveInstance());
|
||||
|
||||
setPopupLoadingProgress("Creating shortcuts...", 80);
|
||||
await sleep(100);
|
||||
await installShortCuts(getManager().getActiveInstance().folder, getManager().getActiveInstance().name);
|
||||
|
||||
setPopupLoadingProgress("Installation completed!", 100);
|
||||
await sleep(500);
|
||||
logger.log(`Installation completed successfully`);
|
||||
hidePopup();
|
||||
getManager().resultPage.show();
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.success").classList.remove("hide");
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.error").classList.add("hide");
|
||||
getManager().resultPage.getElement().querySelector(".instructions-group").classList.remove("hide");
|
||||
} catch (err) {
|
||||
logger.log(`An error occurred during installation: ${err}`);
|
||||
hidePopup();
|
||||
getManager().resultPage.show();
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.success").classList.add("hide");
|
||||
getManager().resultPage.getElement().querySelector(".result-summary.error").classList.remove("hide");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Uninstall this instance
|
||||
*
|
||||
*/
|
||||
async uninstall() {
|
||||
showConfirmPopup("<div style='font-size: 18px; max-width: 100%; margin-bottom: 15px;'> Are you sure you want to remove Olympus? </div> If you click Accept, the Olympus mod will be removed from your DCS installation.", async () => {
|
||||
try {
|
||||
logger.log(`Uninstalling Olympus from ${this.folder}`)
|
||||
showWaitLoadingPopup(`<span>Please wait while Olympus is being removed from <i>${this.name}</i></span>`);
|
||||
setPopupLoadingProgress("Deleting mod folder...", 0);
|
||||
await sleep(100);
|
||||
await deleteMod(this.folder, this.name);
|
||||
|
||||
setPopupLoadingProgress("Deleting hook scripts...", 25);
|
||||
await sleep(100);
|
||||
await deleteHooks(this.folder);
|
||||
|
||||
setPopupLoadingProgress("Deleting JSON...", 50);
|
||||
await sleep(100);
|
||||
await deleteJSON(this.folder);
|
||||
|
||||
setPopupLoadingProgress("Deleting shortcuts...", 75);
|
||||
await sleep(100);
|
||||
await deleteShortCuts(this.folder, this.name);
|
||||
|
||||
await sleep(500);
|
||||
setPopupLoadingProgress("Instance removed!", 100);
|
||||
logger.log(`Olympus removed from ${this.folder}`)
|
||||
location.reload();
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
showErrorPopup(`An error has occurred while uninstalling the Olympus instance. Make sure Olympus and DCS are not running. <br><br> You can find more info in ${path.join(__dirname, "..", "manager.log")}`, () => {
|
||||
location.reload();
|
||||
},
|
||||
(err) => {
|
||||
logger.error(err)
|
||||
showErrorPopup(`An error has occurred while uninstalling the Olympus instance. Make sure Olympus and DCS are not running. <br><br> You can find more info in ${path.join(__dirname, "..", "manager.log")}`, () => {
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,315 +1,234 @@
|
||||
const sha256 = require('sha256')
|
||||
const createShortcut = require('create-desktop-shortcuts');
|
||||
const fs = require('fs');
|
||||
const fsp = require('fs').promises;
|
||||
const path = require('path');
|
||||
const { showWaitPopup } = require('./popup');
|
||||
const { Console } = require('console');
|
||||
const homeDir = require('os').homedir();
|
||||
|
||||
var output = fs.createWriteStream('./manager.log', {flags: 'a'});
|
||||
var output = fs.createWriteStream('./manager.log', { flags: 'a' });
|
||||
var logger = new Console(output, output);
|
||||
const date = new Date();
|
||||
output.write(` ======================= New log starting at ${date.toString()} =======================\n`);
|
||||
|
||||
/** Conveniency function to asynchronously delete a single file, with error catching
|
||||
*
|
||||
* @param {String} filePath The path to the file to delete
|
||||
*/
|
||||
async function deleteFile(filePath) {
|
||||
logger.log(`Deleting ${filePath}`);
|
||||
var promise = new Promise((res, rej) => {
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.rm(filePath, (err) => {
|
||||
if (err) {
|
||||
logger.error(`Error removing ${filePath}: ${err}`)
|
||||
rej(err);
|
||||
}
|
||||
else {
|
||||
logger.log(`Removed ${filePath}`)
|
||||
res(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
res(true);
|
||||
}
|
||||
})
|
||||
return promise;
|
||||
if (await exists(filePath) && await fsp.rm(filePath))
|
||||
logger.log(`Removed ${filePath}`);
|
||||
else
|
||||
logger.log(`${filePath} does not exist, nothing to do`);
|
||||
}
|
||||
|
||||
/** Given a list of Olympus instances, it fixes/updates them by deleting the existing installation and the copying over the clean files
|
||||
/** Conveniency function to asynchronously check if a file or folder exists
|
||||
*
|
||||
* @param {*} location Path to the folder or file to check for existance
|
||||
* @returns true if file exists, false if it doesn't
|
||||
*/
|
||||
async function fixInstances(instances) {
|
||||
var promise = new Promise((res, rej) => {
|
||||
var instancePromises = instances.map((instance) => {
|
||||
var instancePromise = new Promise((instanceRes, instanceErr) => {
|
||||
logger.log(`Fixing Olympus in ${instance.folder}`)
|
||||
deleteMod(instance.folder, instance.name)
|
||||
.then(() => deleteHooks(instance.folder), (err) => { return Promise.reject(err); })
|
||||
.then(() => installMod(instance.folder, instance.name), (err) => { return Promise.reject(err); })
|
||||
.then(() => installHooks(instance.folder), (err) => { return Promise.reject(err); })
|
||||
.then(() => installShortCuts(instance.folder, instance.name), (err) => { return Promise.reject(err); })
|
||||
.then(() => instanceRes(true), (err) => { instanceErr(err) })
|
||||
})
|
||||
return instancePromise;
|
||||
});
|
||||
Promise.all(instancePromises).then(() => res(true), (err) => { rej(err) });
|
||||
})
|
||||
return promise;
|
||||
async function exists(location) {
|
||||
try {
|
||||
await fsp.stat(location);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return false
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Uninstalls a specific instance given its folder
|
||||
*
|
||||
*/
|
||||
async function uninstallInstance(folder, name) {
|
||||
logger.log(`Uninstalling Olympus from ${folder}`)
|
||||
showWaitPopup("Please wait while the Olympus installation is being uninstalled.")
|
||||
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
|
||||
}
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
const DCSInstance = require("./dcsinstance");
|
||||
const ManagerPage = require("./managerpage");
|
||||
const ejs = require('ejs')
|
||||
const { logger } = require("./filesystem")
|
||||
|
||||
class InstallationsPage extends ManagerPage {
|
||||
onCancelClicked;
|
||||
setSelectedInstance;
|
||||
|
||||
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(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
async onOptionClicked(e) {
|
||||
this.setSelectedInstance((await DCSInstance.getInstances()).find((instance) => {return instance.folder === e.target.dataset.folder}));
|
||||
}
|
||||
|
||||
show() {
|
||||
ejs.renderFile("./ejs/installations.ejs", this.options, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InstallationsPage;
|
||||
@ -1,108 +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 {
|
||||
onCancelClicked;
|
||||
setSelectedInstance;
|
||||
startInstance;
|
||||
|
||||
constructor(options) {
|
||||
super(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 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);}
|
||||
}
|
||||
|
||||
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
async onEditClicked(e) {
|
||||
this.getClickedInstance(e).then((instance) => {
|
||||
instance.webserverOnline || instance.backendOnline? showErrorPopup("Error, the selected Olympus instance is currently active, please stop Olympus before editing it!") :
|
||||
this.setSelectedInstance(instance);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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 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() {
|
||||
ejs.renderFile("./ejs/instances.ejs", this.options, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InstancesPage;
|
||||
@ -1,262 +1,589 @@
|
||||
const MenuPage = require("./menu");
|
||||
const InstallationsPage = require('./installations');
|
||||
const ConnectionsPage = require('./connections');
|
||||
const PasswordsPage = require('./passwords');
|
||||
const ResultPage = require('./result');
|
||||
const InstancesPage = require('./instances');
|
||||
const path = require("path")
|
||||
const fs = require("fs");
|
||||
|
||||
const DCSInstance = require('./dcsinstance');
|
||||
const { showErrorPopup, showWaitPopup } = require('./popup');
|
||||
const { fixInstances } = require('./filesystem');
|
||||
const { showErrorPopup, showWaitPopup, showConfirmPopup } = require('./popup');
|
||||
const { logger } = require("./filesystem")
|
||||
const path = require("path")
|
||||
|
||||
const ManagerPage = require("./managerpage");
|
||||
const WizardPage = require("./wizardpage");
|
||||
const { fetchWithTimeout } = require("./net");
|
||||
const { exec } = require("child_process");
|
||||
const { sleep } = require("./utils");
|
||||
|
||||
class Manager {
|
||||
simplified = true;
|
||||
options = {
|
||||
logLocation: path.join(__dirname, "..", "manager.log"),
|
||||
configLoaded: false
|
||||
};
|
||||
|
||||
activePage = null;
|
||||
welcomePage = null;
|
||||
settingsPage = null;
|
||||
folderPage = null;
|
||||
typePage = null;
|
||||
connectionsTypePage = null;
|
||||
connectionsPage = null;
|
||||
passwordsPage = null;
|
||||
resultPage = null;
|
||||
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);
|
||||
try {
|
||||
eval(`this.${callback}(${params})`)
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Asynchronously start the manager
|
||||
*
|
||||
*/
|
||||
async start() {
|
||||
/* 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.simplified = instances.length === 1 && !instances[0].installed;
|
||||
/* Check if the options file exists */
|
||||
if (fs.existsSync("options.json")) {
|
||||
/* Load the options from the json file */
|
||||
try {
|
||||
this.options = { ...this.options, ...JSON.parse(fs.readFileSync("options.json")) };
|
||||
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.`)
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("loader").classList.add("hide");
|
||||
if (!this.options.configLoaded) {
|
||||
/* Hide the loading page */
|
||||
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) => {
|
||||
/* Show page to select basic vs expert mode */
|
||||
this.welcomePage = new ManagerPage(this, "./ejs/welcome.ejs");
|
||||
this.welcomePage.show();
|
||||
}
|
||||
else {
|
||||
document.getElementById("header").classList.remove("hide");
|
||||
|
||||
/* Initialize mode switching */
|
||||
if (this.options.mode === "basic") {
|
||||
document.getElementById("switch-mode").innerText = "Expert mode";
|
||||
document.getElementById("switch-mode").onclick = () => { this.switchMode("expert"); }
|
||||
}
|
||||
else {
|
||||
document.getElementById("switch-mode").innerText = "Basic mode";
|
||||
document.getElementById("switch-mode").onclick = () => { this.switchMode("basic"); }
|
||||
}
|
||||
|
||||
/* Get the list of DCS instances */
|
||||
this.setLoadingProgress("Retrieving DCS instances...", 0);
|
||||
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; }
|
||||
)
|
||||
|
||||
/* Check if there are corrupted or outdated instances */
|
||||
if (this.options.instances.some((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")}`);
|
||||
})) {
|
||||
/* 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> 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);
|
||||
|
||||
/* Hide the loading page */
|
||||
document.getElementById("loader").classList.add("hide");
|
||||
|
||||
this.options.singleInstance = this.options.instances.length === 1;
|
||||
|
||||
/* Create all the HTML pages */
|
||||
this.menuPage = new ManagerPage(this, "./ejs/menu.ejs");
|
||||
this.folderPage = new WizardPage(this, "./ejs/folder.ejs");
|
||||
this.settingsPage = new ManagerPage(this, "./ejs/settings.ejs");
|
||||
this.typePage = new WizardPage(this, "./ejs/type.ejs");
|
||||
this.connectionsTypePage = new WizardPage(this, "./ejs/connectionsType.ejs");
|
||||
this.connectionsPage = new WizardPage(this, "./ejs/connections.ejs");
|
||||
this.passwordsPage = new WizardPage(this, "./ejs/passwords.ejs");
|
||||
this.resultPage = new ManagerPage(this, "./ejs/result.ejs");
|
||||
this.instancesPage = new ManagerPage(this, "./ejs/instances.ejs");
|
||||
|
||||
/* Force the setting of the ports whenever the page is shown */
|
||||
this.connectionsPage.options.onShow = () => {
|
||||
if (this.options.activeInstance) {
|
||||
this.setPort('client', this.options.activeInstance.clientPort);
|
||||
this.setPort('backend', this.options.activeInstance.backendPort);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.mode === "basic") {
|
||||
/* In basic mode no dashboard is shown */
|
||||
this.menuPage.show();
|
||||
} else {
|
||||
/* In Expert mode we go directly to the dashboard */
|
||||
this.instancesPage.show();
|
||||
this.updateInstances();
|
||||
}
|
||||
|
||||
/* Send an event on manager started */
|
||||
document.dispatchEvent(new CustomEvent("managerStarted"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 }));
|
||||
location.reload();
|
||||
} catch (e) {
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
}
|
||||
|
||||
/** 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"));
|
||||
options.mode = newMode;
|
||||
fs.writeFileSync("options.json", JSON.stringify(options));
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/************************************************/
|
||||
/* CALLBACKS */
|
||||
/************************************************/
|
||||
/** Switch to basic mode
|
||||
*
|
||||
*/
|
||||
onBasicClicked() {
|
||||
this.createOptionsFile("basic");
|
||||
}
|
||||
|
||||
/** Switch to expert mode
|
||||
*
|
||||
*/
|
||||
onExpertClicked() {
|
||||
this.createOptionsFile("expert");
|
||||
}
|
||||
|
||||
/** When the install button is clicked go the installation page
|
||||
*
|
||||
*/
|
||||
onInstallMenuClicked() {
|
||||
this.options.install = true;
|
||||
|
||||
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()
|
||||
this.typePage.show();
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/* Check which buttons should be enabled */
|
||||
const installEnabled = true;
|
||||
const manageEnabled = instances.some((instance) => { return instance.installed; });
|
||||
|
||||
/* Menu */
|
||||
var menuPage = new MenuPage();
|
||||
menuPage.options = {
|
||||
...menuPage.options,
|
||||
installEnabled: installEnabled,
|
||||
manageEnabled: manageEnabled
|
||||
}
|
||||
/* When the install button is clicked go the installation page */
|
||||
menuPage.onInstallClicked = (e) => {
|
||||
menuPage.hide();
|
||||
installationsPage.show();
|
||||
}
|
||||
/* When the manage button is clicked go to the instances page in "manage mode" (i.e. manage = true) */
|
||||
menuPage.onManageClicked = (e) => {
|
||||
menuPage.hide();
|
||||
instancesPage.show();
|
||||
}
|
||||
|
||||
/* Installations */
|
||||
var installationsPage = new InstallationsPage();
|
||||
installationsPage.options = {
|
||||
...installationsPage.options,
|
||||
instances: instances
|
||||
}
|
||||
installationsPage.setSelectedInstance = (activeInstance) => {
|
||||
/* Set the active options for the pages */
|
||||
const options = {
|
||||
instance: activeInstance,
|
||||
simplified: this.simplified,
|
||||
install: true
|
||||
}
|
||||
connectionsPage.options = {
|
||||
...connectionsPage.options,
|
||||
...options
|
||||
}
|
||||
passwordsPage.options = {
|
||||
...passwordsPage.options,
|
||||
...options
|
||||
}
|
||||
resultPage.options = {
|
||||
...resultPage.options,
|
||||
...options
|
||||
}
|
||||
|
||||
/* Show the connections page */
|
||||
installationsPage.hide();
|
||||
connectionsPage.show();
|
||||
|
||||
connectionsPage.onBackClicked = (e) => {
|
||||
/* Show the installation page */
|
||||
connectionsPage.hide();
|
||||
installationsPage.show();
|
||||
}
|
||||
}
|
||||
installationsPage.onCancelClicked = (e) => {
|
||||
/* Go back to the main menu */
|
||||
installationsPage.hide();
|
||||
menuPage.show();
|
||||
}
|
||||
|
||||
/* Instances */
|
||||
var instancesPage = new InstancesPage();
|
||||
instancesPage.options = {
|
||||
...instancesPage.options,
|
||||
instances: instances.filter((instance) => { return instance.installed; })
|
||||
}
|
||||
instancesPage.setSelectedInstance = (activeInstance) => {
|
||||
/* Set the active options for the pages */
|
||||
const options = {
|
||||
instance: activeInstance,
|
||||
simplified: this.simplified,
|
||||
install: false
|
||||
}
|
||||
connectionsPage.options = {
|
||||
...connectionsPage.options,
|
||||
...options
|
||||
}
|
||||
passwordsPage.options = {
|
||||
...passwordsPage.options,
|
||||
...options
|
||||
}
|
||||
resultPage.options = {
|
||||
...resultPage.options,
|
||||
...options
|
||||
}
|
||||
|
||||
/* Show the connections page */
|
||||
instancesPage.hide();
|
||||
connectionsPage.show();
|
||||
|
||||
connectionsPage.onBackClicked = (e) => {
|
||||
/* Show the instances page */
|
||||
connectionsPage.hide();
|
||||
instancesPage.show();
|
||||
}
|
||||
}
|
||||
instancesPage.onCancelClicked = (e) => {
|
||||
/* Go back to the main menu */
|
||||
instancesPage.hide();
|
||||
menuPage.show();
|
||||
}
|
||||
|
||||
/* Connections */
|
||||
var connectionsPage = new ConnectionsPage();
|
||||
connectionsPage.onNextClicked = async (e) => {
|
||||
let activeInstance = connectionsPage.options.instance;
|
||||
if (activeInstance) {
|
||||
/* Check that the selected ports are free before proceeding */
|
||||
if (await activeInstance.checkClientPort(activeInstance.clientPort) && await activeInstance.checkBackendPort(activeInstance.backendPort)) {
|
||||
connectionsPage.hide();
|
||||
passwordsPage.show();
|
||||
} else {
|
||||
showErrorPopup("Please make sure the selected ports are not already in use.")
|
||||
}
|
||||
} else {
|
||||
showErrorPopup(`An error has occurred, please restart the Olympus Manager. <br><br> You can find more info in ${path.join(__dirname, "..", "manager.log")}`)
|
||||
}
|
||||
}
|
||||
connectionsPage.onCancelClicked = (e) => {
|
||||
/* Go back to the main menu */
|
||||
connectionsPage.hide();
|
||||
menuPage.show();
|
||||
}
|
||||
|
||||
/* Passwords */
|
||||
var passwordsPage = new PasswordsPage();
|
||||
passwordsPage.onBackClicked = (e) => {
|
||||
/* Go back to the connections page */
|
||||
let activeInstance = connectionsPage.options.instance;
|
||||
if (activeInstance) {
|
||||
passwordsPage.hide();
|
||||
connectionsPage.show();
|
||||
} else {
|
||||
showErrorPopup(`An error has occurred, please restart the Olympus Manager. <br><br> You can find more info in ${path.join(__dirname, "..", "manager.log")}`)
|
||||
}
|
||||
}
|
||||
passwordsPage.onNextClicked = (e) => {
|
||||
let activeInstance = connectionsPage.options.instance;
|
||||
if (activeInstance) {
|
||||
/* Check that all the passwords have been set */
|
||||
if (activeInstance.gameMasterPassword === "" || activeInstance.blueCommanderPassword === "" || activeInstance.redCommanderPassword === "") {
|
||||
showErrorPopup("Please fill all the password inputs.")
|
||||
}
|
||||
else if (activeInstance.gameMasterPassword === activeInstance.blueCommanderPassword || activeInstance.blueCommanderPassword === activeInstance.redCommanderPassword || activeInstance.gameMasterPassword === activeInstance.redCommanderPassword) {
|
||||
showErrorPopup("All the passwords must be different from each other.")
|
||||
} else {
|
||||
passwordsPage.hide();
|
||||
resultPage.show();
|
||||
resultPage.startInstallation();
|
||||
}
|
||||
} else {
|
||||
showErrorPopup(`An error has occurred, please restart the Olympus Manager. <br><br> You can find more info in ${path.join(__dirname, "..", "manager.log")}`)
|
||||
}
|
||||
|
||||
}
|
||||
passwordsPage.onCancelClicked = (e) => {
|
||||
/* Go back to the main menu */
|
||||
passwordsPage.hide();
|
||||
menuPage.show();
|
||||
}
|
||||
|
||||
/* Result */
|
||||
var resultPage = new ResultPage({logLocation: path.join(__dirname, "..", "manager.log")});
|
||||
resultPage.onBackClicked = (e) => {
|
||||
/* Reload the page to apply changes */
|
||||
resultPage.hide();
|
||||
location.reload();
|
||||
}
|
||||
resultPage.onCancelClicked = (e) => {
|
||||
/* Reload the page to apply changes */
|
||||
resultPage.hide();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/* Create all the HTML pages */
|
||||
document.body.appendChild(menuPage.getElement());
|
||||
document.body.appendChild(installationsPage.getElement());
|
||||
document.body.appendChild(instancesPage.getElement());
|
||||
document.body.appendChild(connectionsPage.getElement());
|
||||
document.body.appendChild(passwordsPage.getElement());
|
||||
document.body.appendChild(resultPage.getElement());
|
||||
|
||||
/* In simplified mode we directly show the connections page */
|
||||
if (this.simplified) {
|
||||
const options = {
|
||||
instance: instances[0],
|
||||
simplified: this.simplified,
|
||||
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();
|
||||
/* Show the folder selection page */
|
||||
this.activePage.hide()
|
||||
this.folderPage.show();
|
||||
}
|
||||
}
|
||||
|
||||
/** 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.connectionsTypePage.show();
|
||||
} else {
|
||||
this.settingsPage.show();
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 */
|
||||
onInstallTypeClicked(type) {
|
||||
this.typePage.getElement().querySelector(`.singleplayer`).classList.toggle("selected", type === 'singleplayer');
|
||||
this.typePage.getElement().querySelector(`.multiplayer`).classList.toggle("selected", type === 'multiplayer');
|
||||
if (this.options.activeInstance)
|
||||
this.options.activeInstance.installationType = type;
|
||||
else
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
|
||||
/* When the connections type is selected */
|
||||
onConnectionsTypeClicked(type) {
|
||||
this.connectionsTypePage.getElement().querySelector(`.auto`).classList.toggle("selected", type === 'auto');
|
||||
this.connectionsTypePage.getElement().querySelector(`.manual`).classList.toggle("selected", type === 'manual');
|
||||
if (this.options.activeInstance)
|
||||
this.options.activeInstance.connectionsType = type;
|
||||
else
|
||||
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) {
|
||||
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();
|
||||
} else if (this.activePage == this.connectionsTypePage) {
|
||||
if (this.options.activeInstance) {
|
||||
if (this.options.activeInstance.connectionsType === 'auto') {
|
||||
this.activePage.hide();
|
||||
this.passwordsPage.show();
|
||||
}
|
||||
else {
|
||||
this.activePage.hide();
|
||||
this.connectionsPage.show();
|
||||
this.connectionsPage.getElement().querySelector(".backend-address .checkbox").classList.toggle("checked", this.options.activeInstance.backendAddress === '*')
|
||||
}
|
||||
} else {
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
} else if (this.activePage == this.connectionsPage) {
|
||||
this.options.activeInstance.checkClientPort().then(
|
||||
(portFree) => {
|
||||
if (portFree) {
|
||||
return this.options.activeInstance.checkBackendPort();
|
||||
} else {
|
||||
return Promise.reject('Port not free');
|
||||
}
|
||||
}).then((portFree) => {
|
||||
if (portFree) {
|
||||
this.activePage.hide();
|
||||
this.passwordsPage.show();
|
||||
} else {
|
||||
return Promise.reject('Port not free');
|
||||
}
|
||||
}).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();
|
||||
this.options.install? this.options.activeInstance.install(): this.options.activeInstance.edit();
|
||||
}
|
||||
else {
|
||||
if (!this.options.activeInstance.arePasswordsSet()) {
|
||||
showErrorPopup('Please, make sure all passwords are set!');
|
||||
} else if (!this.options.activeInstance.arePasswordsDifferent()) {
|
||||
showErrorPopup('Please, set different passwords for the Game Master, Blue Commander, and Red Commander roles!');
|
||||
} else {
|
||||
this.activePage.hide();
|
||||
this.options.install? this.options.activeInstance.install(): this.options.activeInstance.edit();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* When the back button of a wizard page is clicked */
|
||||
onBackClicked() {
|
||||
this.activePage.hide();
|
||||
this.activePage.previousPage.show(true); // Don't change the previous page
|
||||
this.updateInstances();
|
||||
}
|
||||
|
||||
onCancelClicked() {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
onGameMasterPasswordChanged(value) {
|
||||
if (this.options.activeInstance) {
|
||||
this.options.activeInstance.setGameMasterPassword(value);
|
||||
if (!this.options.activeInstance.blueCommanderPasswordEdited)
|
||||
this.passwordsPage.getElement().querySelector(".blue-commander input").value = "";
|
||||
if (!this.options.activeInstance.redCommanderPasswordEdited)
|
||||
this.passwordsPage.getElement().querySelector(".red-commander input").value = "";
|
||||
}
|
||||
else
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
|
||||
onBlueCommanderPasswordChanged(value) {
|
||||
if (this.options.activeInstance) {
|
||||
this.options.activeInstance.setBlueCommanderPassword(value);
|
||||
if (!this.options.activeInstance.gameMasterPasswordEdited)
|
||||
this.passwordsPage.getElement().querySelector(".game-master input").value = "";
|
||||
if (!this.options.activeInstance.redCommanderPasswordEdited)
|
||||
this.passwordsPage.getElement().querySelector(".red-commander input").value = "";
|
||||
}
|
||||
else
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
|
||||
onRedCommanderPasswordChanged(value) {
|
||||
if (this.options.activeInstance) {
|
||||
this.options.activeInstance.setRedCommanderPassword(value);
|
||||
if (!this.options.activeInstance.gameMasterPasswordEdited)
|
||||
this.passwordsPage.getElement().querySelector(".game-master input").value = "";
|
||||
if (!this.options.activeInstance.blueCommanderPasswordEdited)
|
||||
this.passwordsPage.getElement().querySelector(".blue-commander input").value = "";
|
||||
}
|
||||
else
|
||||
showErrorPopup(`A critical error occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
|
||||
/* When the client port input value is changed */
|
||||
onClientPortChanged(value) {
|
||||
this.setPort('client', Number(value));
|
||||
}
|
||||
|
||||
/* When the backend port input value is changed */
|
||||
onBackendPortChanged(value) {
|
||||
this.setPort('backend', Number(value));
|
||||
}
|
||||
|
||||
/* When the "Enable API connection" checkbox is clicked */
|
||||
onEnableAPIClicked() {
|
||||
if (this.options.activeInstance) {
|
||||
if (this.options.activeInstance.backendAddress === 'localhost') {
|
||||
this.options.activeInstance.backendAddress = '*';
|
||||
} else {
|
||||
this.options.activeInstance.backendAddress = 'localhost';
|
||||
}
|
||||
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 occurred, check ${this.options.logLocation} for more info.`)
|
||||
}
|
||||
}
|
||||
|
||||
/* When the "Return to manager" button is pressed */
|
||||
onReturnClicked() {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/* When the "Close manager" button is pressed */
|
||||
onCloseManagerClicked() {
|
||||
document.querySelector('.close').click();
|
||||
}
|
||||
|
||||
async onStartServerClicked(name) {
|
||||
var div = await this.getClickedInstanceDiv(name);
|
||||
div.querySelector(".collapse").classList.add("loading")
|
||||
var instance = await this.getClickedInstance(name);
|
||||
instance.startServer();
|
||||
}
|
||||
|
||||
async onStartClientClicked(name) {
|
||||
var div = await this.getClickedInstanceDiv(name);
|
||||
div.querySelector(".collapse").classList.add("loading")
|
||||
var instance = await this.getClickedInstance(name);
|
||||
instance.startClient();
|
||||
}
|
||||
|
||||
async onOpenBrowserClicked(name) {
|
||||
var instance = await this.getClickedInstance(name);
|
||||
exec(`start http://localhost:${instance.clientPort}`)
|
||||
}
|
||||
|
||||
async onStopClicked(name) {
|
||||
var instance = await this.getClickedInstance(name);
|
||||
instance.stop();
|
||||
}
|
||||
|
||||
async onEditClicked(name) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
var instances = await DCSInstance.getInstances()
|
||||
return instances.find((instance) => { return instance.name === name; });
|
||||
}
|
||||
|
||||
async getClickedInstanceDiv(name) {
|
||||
var instance = await this.getClickedInstance(name);
|
||||
var instanceDivs = this.instancesPage.getElement().querySelectorAll(`.option`);
|
||||
for (let i = 0; i < instanceDivs.length; i++) {
|
||||
var instanceDiv = instanceDivs[i];
|
||||
if (instanceDiv.dataset.folder === instance.folder) {
|
||||
return instanceDiv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the selected port to the dcs instance */
|
||||
async setPort(port, value) {
|
||||
var success;
|
||||
if (port === 'client') {
|
||||
success = await this.options.activeInstance.checkClientPort(value);
|
||||
this.options.activeInstance.setClientPort(value);
|
||||
}
|
||||
else {
|
||||
success = await this.options.activeInstance.checkBackendPort(value);
|
||||
this.options.activeInstance.setBackendPort(value);
|
||||
}
|
||||
|
||||
var successEls = this.connectionsPage.getElement().querySelector(`.${port}-port`).querySelectorAll(".success");
|
||||
for (let i = 0; i < successEls.length; i++) {
|
||||
successEls[i].classList.toggle("hide", !success);
|
||||
}
|
||||
var errorEls = this.connectionsPage.getElement().querySelector(`.${port}-port`).querySelectorAll(".error");
|
||||
for (let i = 0; i < errorEls.length; i++) {
|
||||
errorEls[i].classList.toggle("hide", success);
|
||||
}
|
||||
}
|
||||
|
||||
async getPublicIP() {
|
||||
const res = await fetchWithTimeout("https://ipecho.io/json", { timeout: 2500 });
|
||||
const data = await res.json();
|
||||
return data.ip;
|
||||
}
|
||||
|
||||
updateInstances() {
|
||||
var instanceDivs = this.instancesPage.getElement().querySelectorAll(`.option`);
|
||||
for (let i = 0; i < instanceDivs.length; i++) {
|
||||
var instanceDiv = instanceDivs[i];
|
||||
var instance = this.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);
|
||||
|
||||
if (instance.installed) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.activePage.show();
|
||||
}
|
||||
|
||||
setLoadingProgress(message, percent) {
|
||||
document.querySelector("#loader .loading-message").innerHTML = message;
|
||||
if (percent) {
|
||||
var style = document.querySelector('#loader .loading-bar').style;
|
||||
style.setProperty('--percent', `${percent}%`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
manager/javascripts/managerfactory.js
Normal file
@ -0,0 +1,15 @@
|
||||
/* TODO: find a better solution without using the window object to persist the manager singleton */
|
||||
|
||||
function getManager() {
|
||||
if (window.manager) {
|
||||
return window.manager;
|
||||
} else {
|
||||
const Manager = require("./manager");
|
||||
window.manager = new Manager();
|
||||
return window.manager;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getManager: getManager
|
||||
};
|
||||
@ -1,33 +1,57 @@
|
||||
class ManagerPage {
|
||||
element;
|
||||
options;
|
||||
const { logger } = require("./filesystem");
|
||||
const ejs = require('ejs')
|
||||
|
||||
constructor(options) {
|
||||
this.options = options ?? {};
|
||||
class ManagerPage {
|
||||
manager;
|
||||
ejsFile;
|
||||
element;
|
||||
options = {};
|
||||
previousPage;
|
||||
|
||||
constructor(manager, ejsFile) {
|
||||
this.manager = manager;
|
||||
this.element = document.createElement('div');
|
||||
this.element.classList.add("manager-page", "hide");
|
||||
this.ejsFile = ejsFile;
|
||||
document.body.appendChild(this.element);
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
show() {
|
||||
show(ignorePreviousPage) {
|
||||
ejs.renderFile(this.ejsFile, {...this.options, ...this.manager.options}, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
this.element.classList.remove("hide");
|
||||
|
||||
this.previousPage = ignorePreviousPage ? this.previousPage : this.manager.activePage;
|
||||
this.manager.activePage = this;
|
||||
|
||||
if (this.options.onShow)
|
||||
this.options.onShow();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.element.classList.add("hide");
|
||||
}
|
||||
|
||||
render() {
|
||||
render(str) {
|
||||
this.element.innerHTML = str;
|
||||
|
||||
/* Connect all the collapsable buttons */
|
||||
let buttons = document.querySelectorAll(".button.collapse");
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
buttons[i].addEventListener("click", () => {
|
||||
buttons[i].classList.toggle("open");
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
const ManagerPage = require("./managerpage");
|
||||
const ejs = require('ejs')
|
||||
const { logger } = require("./filesystem")
|
||||
|
||||
class MenuPage extends ManagerPage {
|
||||
onInstallClicked;
|
||||
onUpdateClicked;
|
||||
onManageClicked;
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
render(str) {
|
||||
const element = this.getElement();
|
||||
element.innerHTML = str;
|
||||
|
||||
element.querySelector(".install").addEventListener("click", (e) => this.onInstallClicked(e));
|
||||
element.querySelector(".manage").addEventListener("click", (e) => this.onManageClicked(e));
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.instance = this.options.instance;
|
||||
|
||||
ejs.renderFile("./ejs/menu.ejs", this.options, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MenuPage;
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
const ManagerPage = require("./managerpage");
|
||||
const ejs = require('ejs')
|
||||
const { logger } = require("./filesystem")
|
||||
|
||||
class PasswordsPage extends ManagerPage {
|
||||
onBackClicked;
|
||||
onNextClicked;
|
||||
onCancelClicked;
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
render(str) {
|
||||
const element = this.getElement();
|
||||
element.innerHTML = str;
|
||||
|
||||
if (this.element.querySelector(".back"))
|
||||
this.element.querySelector(".back").addEventListener("click", (e) => this.onBackClicked(e));
|
||||
|
||||
if (this.element.querySelector(".next"))
|
||||
this.element.querySelector(".next").addEventListener("click", (e) => this.onNextClicked(e));
|
||||
|
||||
if (this.element.querySelector(".cancel"))
|
||||
this.element.querySelector(".cancel").addEventListener("click", (e) => this.onCancelClicked(e));
|
||||
|
||||
this.element.querySelector(".game-master").querySelector("input").addEventListener("change", async (e) => { this.instance.setGameMasterPassword(e.target.value); })
|
||||
this.element.querySelector(".blue-commander").querySelector("input").addEventListener("change", async (e) => { this.instance.setBlueCommanderPassword(e.target.value); })
|
||||
this.element.querySelector(".red-commander").querySelector("input").addEventListener("change", async (e) => { this.instance.setRedCommanderPassword(e.target.value); })
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.instance = this.options.instance;
|
||||
|
||||
ejs.renderFile("./ejs/passwords.ejs", this.options, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PasswordsPage;
|
||||
@ -1,5 +1,24 @@
|
||||
// TODO: we can probably refactor this to be a bit cleaner
|
||||
|
||||
function showInfoPopup(message, onCloseCallback) {
|
||||
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.add("hide");
|
||||
document.getElementById("popup").querySelector(".confirm").classList.remove("hide");
|
||||
document.getElementById("popup").querySelector(".close-popup").classList.remove("hide");
|
||||
document.getElementById("popup").querySelector(".accept-popup").classList.add("hide");
|
||||
|
||||
/* Not using event listeners to make sure we only have one callback */
|
||||
document.getElementById("popup").querySelector(".close-popup").onclick = (e) => {
|
||||
hidePopup();
|
||||
if (onCloseCallback)
|
||||
onCloseCallback();
|
||||
}
|
||||
document.getElementById("popup").querySelector(".content").innerHTML = message;
|
||||
}
|
||||
|
||||
|
||||
function showErrorPopup(message, onCloseCallback) {
|
||||
document.getElementById("grayout").classList.remove("hide");
|
||||
document.getElementById("popup").classList.remove("hide");
|
||||
@ -29,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");
|
||||
@ -60,9 +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
|
||||
}
|
||||
@ -1,5 +1,3 @@
|
||||
const Manager = require('./manager');
|
||||
|
||||
const contextBridge = require('electron').contextBridge;
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
const { exec, spawn } = require("child_process");
|
||||
@ -10,7 +8,8 @@ const https = require('follow-redirects').https;
|
||||
const fs = require('fs');
|
||||
const AdmZip = require("adm-zip");
|
||||
const { Octokit } = require('octokit');
|
||||
const { logger } = require("./filesystem")
|
||||
const { logger } = require("./filesystem");
|
||||
const { getManager } = require('./managerfactory');
|
||||
|
||||
const VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
|
||||
logger.log(`Running in ${__dirname}`);
|
||||
@ -45,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();
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -74,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
|
||||
@ -257,28 +258,35 @@ contextBridge.exposeInMainWorld(
|
||||
}
|
||||
});
|
||||
|
||||
/* New instance of the manager app */
|
||||
const manager = new Manager();
|
||||
|
||||
/* On content loaded */
|
||||
window.addEventListener('DOMContentLoaded', async () => {
|
||||
/* Compute the height of the content page */
|
||||
computePagesHeight();
|
||||
document.getElementById("loader").classList.remove("hide");
|
||||
await manager.start();
|
||||
/* Compute the height of the content page to account for the pages created by the manager*/
|
||||
computePagesHeight();
|
||||
await getManager().start();
|
||||
|
||||
/* Create event listeners for the hyperlinks */
|
||||
var links = document.querySelectorAll(".link");
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
links[i].addEventListener("click", (e) => {
|
||||
exec("start " + e.target.dataset.link);
|
||||
})
|
||||
if (links[i].dataset.link) {
|
||||
links[i].addEventListener("click", (e) => {
|
||||
if (e.target.dataset.link)
|
||||
exec("start " + e.target.dataset.link);
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
/* Compute the height of the content page */
|
||||
computePagesHeight();
|
||||
})
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
/* Compute the height of the content page */
|
||||
computePagesHeight();
|
||||
})
|
||||
|
||||
document.addEventListener('managerStarted', () => {
|
||||
/* Compute the height of the content page */
|
||||
computePagesHeight();
|
||||
})
|
||||
|
||||
@ -298,4 +306,4 @@ function computePagesHeight() {
|
||||
ipcRenderer.on("check-version", () => {
|
||||
/* Check if a new version is available */
|
||||
checkVersion();
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,117 +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 {
|
||||
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));
|
||||
|
||||
super.render();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.instance = this.options.instance;
|
||||
|
||||
ejs.renderFile("./ejs/result.ejs", this.options, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.render(str);
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
super.show();
|
||||
}
|
||||
|
||||
/** Installation is performed by using an then chain of async functions. Installation is aborted on any error along the chain
|
||||
*
|
||||
*/
|
||||
startInstallation() {
|
||||
installHooks(this.instance.folder).then(
|
||||
() => {
|
||||
this.applyStepSuccess(".hook");
|
||||
},
|
||||
(err) => {
|
||||
this.applyStepFailure(".hook");
|
||||
return Promise.reject(err);
|
||||
}
|
||||
).then(() => installMod(this.instance.folder, this.instance.name)).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(".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;
|
||||
10
manager/javascripts/utils.js
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
|
||||
async function sleep(ms) {
|
||||
await new Promise(r => setTimeout(r, ms));
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sleep: sleep
|
||||
}
|
||||
26
manager/javascripts/wizardpage.js
Normal file
@ -0,0 +1,26 @@
|
||||
const ManagerPage = require("./managerpage");
|
||||
const ejs = require('ejs')
|
||||
|
||||
class WizardPage extends ManagerPage {
|
||||
contentEjsFile;
|
||||
|
||||
constructor(manager, contentEjsFile) {
|
||||
super(manager, './ejs/wizard.ejs');
|
||||
this.contentEjsFile = contentEjsFile;
|
||||
}
|
||||
|
||||
render(str) {
|
||||
super.render(str);
|
||||
|
||||
ejs.renderFile(this.contentEjsFile, {...this.options, ...this.manager.options}, {}, (err, str) => {
|
||||
if (!err) {
|
||||
this.element.querySelector(".content").innerHTML = str;
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WizardPage;
|
||||
@ -10,8 +10,8 @@ process.env['PATH'] = process.env['PATH'] + "%WINDIR%\\System32;"
|
||||
|
||||
function createWindow() {
|
||||
const window = new electronBrowserWindow({
|
||||
width: 1500,
|
||||
height: 850,
|
||||
width: 1200,
|
||||
height: 750,
|
||||
frame: false,
|
||||
resizable: true,
|
||||
maximizable: true,
|
||||
|
||||
317
manager/manager.log
Normal file
@ -0,0 +1,317 @@
|
||||
======================= New log starting at Thu Jan 11 2024 16:07:31 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
Development build detected, skipping version checks...
|
||||
======================= New log starting at Thu Jan 11 2024 16:11:52 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:11:54 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:11:56 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:13:03 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:13:31 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:13:41 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:13:48 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:23:11 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
ReferenceError: ./ejs/connections.ejs:32
|
||||
30| <div id="manager-connections">
|
||||
31| <div class="step-summary">
|
||||
>> 32| <div class="blue <%= !install || basic? 'hide': '' %>">User path</div>
|
||||
33| <div class="white">Ports and address</div>
|
||||
34| <div class="empty">Passwords</div>
|
||||
35| <div class="empty"> <%= install? 'Install': 'Update' %></div>
|
||||
|
||||
basic is not defined
|
||||
at eval ("./ejs/connections.ejs":12:38)
|
||||
at connections (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at ConnectionsPage.show (D:\Documents\DCSOlympus\manager\javascripts\connections.js:41:13)
|
||||
at Manager.start (D:\Documents\DCSOlympus\manager\javascripts\manager.js:70:42)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
at async D:\Documents\DCSOlympus\manager\javascripts\preload.js:268:5 {
|
||||
path: './ejs/connections.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:24:03 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
ReferenceError: ./ejs/connections.ejs:32
|
||||
30| <div id="manager-connections">
|
||||
31| <div class="step-summary">
|
||||
>> 32| <div class="blue <%= !install || basic? 'hide': '' %>">User path</div>
|
||||
33| <div class="white">Ports and address</div>
|
||||
34| <div class="empty">Passwords</div>
|
||||
35| <div class="empty"> <%= install? 'Install': 'Update' %></div>
|
||||
|
||||
basic is not defined
|
||||
at eval ("./ejs/connections.ejs":12:38)
|
||||
at connections (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at ConnectionsPage.show (D:\Documents\DCSOlympus\manager\javascripts\connections.js:41:13)
|
||||
at Manager.start (D:\Documents\DCSOlympus\manager\javascripts\manager.js:70:42)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
at async D:\Documents\DCSOlympus\manager\javascripts\preload.js:268:5 {
|
||||
path: './ejs/connections.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:24:25 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
ReferenceError: ./ejs/connections.ejs:32
|
||||
30| <div id="manager-connections">
|
||||
31| <div class="step-summary">
|
||||
>> 32| <div class="blue <%= !install || basic? 'hide': '' %>">User path</div>
|
||||
33| <div class="white">Ports and address</div>
|
||||
34| <div class="empty">Passwords</div>
|
||||
35| <div class="empty"> <%= install? 'Install': 'Update' %></div>
|
||||
|
||||
basic is not defined
|
||||
at eval ("./ejs/connections.ejs":12:38)
|
||||
at connections (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at ConnectionsPage.show (D:\Documents\DCSOlympus\manager\javascripts\connections.js:41:13)
|
||||
at Manager.start (D:\Documents\DCSOlympus\manager\javascripts\manager.js:71:42)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
at async D:\Documents\DCSOlympus\manager\javascripts\preload.js:268:5 {
|
||||
path: './ejs/connections.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:24:45 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
ReferenceError: ./ejs/connections.ejs:32
|
||||
30| <div id="manager-connections">
|
||||
31| <div class="step-summary">
|
||||
>> 32| <div class="blue <%= !install || basic? 'hide': '' %>">User path</div>
|
||||
33| <div class="white">Ports and address</div>
|
||||
34| <div class="empty">Passwords</div>
|
||||
35| <div class="empty"> <%= install? 'Install': 'Update' %></div>
|
||||
|
||||
basic is not defined
|
||||
at eval ("./ejs/connections.ejs":12:38)
|
||||
at connections (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at ConnectionsPage.show (D:\Documents\DCSOlympus\manager\javascripts\connections.js:41:13)
|
||||
at Manager.start (D:\Documents\DCSOlympus\manager\javascripts\manager.js:71:42)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
at async D:\Documents\DCSOlympus\manager\javascripts\preload.js:268:5 {
|
||||
path: './ejs/connections.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:25:20 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
ReferenceError: ./ejs/connections.ejs:32
|
||||
30| <div id="manager-connections">
|
||||
31| <div class="step-summary">
|
||||
>> 32| <div class="blue <%= !install || basic? 'hide': '' %>">User path</div>
|
||||
33| <div class="white">Ports and address</div>
|
||||
34| <div class="empty">Passwords</div>
|
||||
35| <div class="empty"> <%= install? 'Install': 'Update' %></div>
|
||||
|
||||
basic is not defined
|
||||
at eval ("./ejs/connections.ejs":12:38)
|
||||
at connections (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at ConnectionsPage.show (D:\Documents\DCSOlympus\manager\javascripts\connections.js:41:13)
|
||||
at Manager.start (D:\Documents\DCSOlympus\manager\javascripts\manager.js:78:42)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
at async D:\Documents\DCSOlympus\manager\javascripts\preload.js:268:5 {
|
||||
path: './ejs/connections.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:28:55 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
ReferenceError: ./ejs/connections.ejs:52
|
||||
50| </span>
|
||||
51| <div>
|
||||
>> 52| <input type="number" min="1024" max="65535" value="<%= instance["clientPort"] %>">
|
||||
53| <img class="success hide">
|
||||
54| <div class="error hide">
|
||||
55| <img> <span>Port already in use</span>
|
||||
|
||||
instance is not defined
|
||||
at eval ("./ejs/connections.ejs":18:26)
|
||||
at connections (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at ConnectionsPage.show (D:\Documents\DCSOlympus\manager\javascripts\connections.js:41:13)
|
||||
at Manager.start (D:\Documents\DCSOlympus\manager\javascripts\manager.js:78:42)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
at async D:\Documents\DCSOlympus\manager\javascripts\preload.js:268:5 {
|
||||
path: './ejs/connections.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:29:02 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
ReferenceError: ./ejs/connections.ejs:52
|
||||
50| </span>
|
||||
51| <div>
|
||||
>> 52| <input type="number" min="1024" max="65535" value="<%= instance["clientPort"] %>">
|
||||
53| <img class="success hide">
|
||||
54| <div class="error hide">
|
||||
55| <img> <span>Port already in use</span>
|
||||
|
||||
instance is not defined
|
||||
at eval ("./ejs/connections.ejs":18:26)
|
||||
at connections (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at ConnectionsPage.show (D:\Documents\DCSOlympus\manager\javascripts\connections.js:41:13)
|
||||
at Manager.start (D:\Documents\DCSOlympus\manager\javascripts\manager.js:78:42)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
at async D:\Documents\DCSOlympus\manager\javascripts\preload.js:268:5 {
|
||||
path: './ejs/connections.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:31:06 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
ReferenceError: ./ejs/connections.ejs:52
|
||||
50| </span>
|
||||
51| <div>
|
||||
>> 52| <input type="number" min="1024" max="65535" value="<%= instance["clientPort"] %>">
|
||||
53| <img class="success hide">
|
||||
54| <div class="error hide">
|
||||
55| <img> <span>Port already in use</span>
|
||||
|
||||
instance is not defined
|
||||
at eval ("./ejs/connections.ejs":18:26)
|
||||
at connections (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at ConnectionsPage.show (D:\Documents\DCSOlympus\manager\javascripts\connections.js:41:13)
|
||||
at Manager.start (D:\Documents\DCSOlympus\manager\javascripts\manager.js:77:42)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
at async D:\Documents\DCSOlympus\manager\javascripts\preload.js:268:5 {
|
||||
path: './ejs/connections.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:37:31 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
ReferenceError: ./ejs/connections.ejs:52
|
||||
50| </span>
|
||||
51| <div>
|
||||
>> 52| <input type="number" min="1024" max="65535" value="<%= instance["clientPort"] %>">
|
||||
53| <img class="success hide">
|
||||
54| <div class="error hide">
|
||||
55| <img> <span>Port already in use</span>
|
||||
|
||||
instance is not defined
|
||||
at eval ("./ejs/connections.ejs":18:26)
|
||||
at connections (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at ConnectionsPage.show (D:\Documents\DCSOlympus\manager\javascripts\connections.js:38:13)
|
||||
at Manager.start (D:\Documents\DCSOlympus\manager\javascripts\manager.js:77:42)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
at async D:\Documents\DCSOlympus\manager\javascripts\preload.js:268:5 {
|
||||
path: './ejs/connections.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:38:57 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:39:52 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:41:14 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:41:25 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:41:48 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:42:27 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:42:52 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:43:06 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:43:24 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3000
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3001
|
||||
======================= New log starting at Thu Jan 11 2024 16:54:16 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
======================= New log starting at Thu Jan 11 2024 16:54:21 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3000
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3001
|
||||
ReferenceError: ./ejs/passwords.ejs:6
|
||||
4| <div id="manager-passwords">
|
||||
5| <div class="step-summary">
|
||||
>> 6| <div class="blue <%= !install || singleInstance? 'hide': '' %>">User path</div>
|
||||
7| <div class="blue">Ports and address</div>
|
||||
8| <div class="white">Passwords</div>
|
||||
9| <div class="empty"> <%= install? 'Install': 'Update' %></div>
|
||||
|
||||
install is not defined
|
||||
at eval ("./ejs/passwords.ejs":12:27)
|
||||
at passwords (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at PasswordsPage.show (D:\Documents\DCSOlympus\manager\javascripts\passwords.js:35:13)
|
||||
at ConnectionsPage.onNextClicked (D:\Documents\DCSOlympus\manager\javascripts\connections.js:58:36)
|
||||
at HTMLDivElement.<anonymous> (D:\Documents\DCSOlympus\manager\javascripts\connections.js:21:87) {
|
||||
path: './ejs/passwords.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:54:49 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3000
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3001
|
||||
ReferenceError: ./ejs/passwords.ejs:44
|
||||
42| </div>
|
||||
43| </div>
|
||||
>> 44| <% if (!basic) { %>
|
||||
45| <div class="button cancel">
|
||||
46| <%= install? "Cancel installation": "Cancel editing" %>
|
||||
47| </div>
|
||||
|
||||
basic is not defined
|
||||
at eval ("./ejs/passwords.ejs":18:8)
|
||||
at passwords (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at PasswordsPage.show (D:\Documents\DCSOlympus\manager\javascripts\passwords.js:35:13)
|
||||
at ConnectionsPage.onNextClicked (D:\Documents\DCSOlympus\manager\javascripts\connections.js:58:36)
|
||||
at HTMLDivElement.<anonymous> (D:\Documents\DCSOlympus\manager\javascripts\connections.js:21:87) {
|
||||
path: './ejs/passwords.ejs'
|
||||
}
|
||||
======================= New log starting at Thu Jan 11 2024 16:55:57 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3000
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3001
|
||||
======================= New log starting at Fri Jan 12 2024 08:51:22 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
Development build detected, skipping version checks...
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3000
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3001
|
||||
======================= New log starting at Fri Jan 12 2024 08:53:17 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3000
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3001
|
||||
======================= New log starting at Fri Jan 12 2024 08:53:42 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3000
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3001
|
||||
ReferenceError: ./ejs/result.ejs:132
|
||||
130| <div>
|
||||
131| <span>
|
||||
>> 132| <%= instance.name %>
|
||||
133| </span>
|
||||
134| <span><img src="./icons/folder-open-solid.svg">
|
||||
135| <%= instance.folder %>
|
||||
|
||||
instance is not defined
|
||||
at eval ("./ejs/result.ejs":27:26)
|
||||
at result (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:703:17)
|
||||
at tryHandleCache (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:274:36)
|
||||
at exports.renderFile (D:\Documents\DCSOlympus\manager\node_modules\ejs\lib\ejs.js:491:10)
|
||||
at ResultPage.show (D:\Documents\DCSOlympus\manager\javascripts\result.js:25:13)
|
||||
at PasswordsPage.onNextClicked (D:\Documents\DCSOlympus\manager\javascripts\passwords.js:40:33)
|
||||
at HTMLDivElement.<anonymous> (D:\Documents\DCSOlympus\manager\javascripts\managerpage.js:39:87) {
|
||||
path: './ejs/result.ejs'
|
||||
}
|
||||
======================= New log starting at Fri Jan 12 2024 08:54:41 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3000
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3001
|
||||
======================= New log starting at Fri Jan 12 2024 08:56:11 GMT+0100 (Central European Standard Time) =======================
|
||||
Running in D:\Documents\DCSOlympus\manager\javascripts
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3000
|
||||
Instance C:\Users\dpassoni\Saved Games\dcs.openbeta client port set to 3001
|
||||
Installing hooks in C:\Users\dpassoni\Saved Games\dcs.openbeta
|
||||
Error installing hooks in C:\Users\dpassoni\Saved Games\dcs.openbeta: Error: ENOENT: no such file or directory, lstat 'D:\Documents\DCSOlympus\scripts\OlympusHook.lua'
|
||||
@ -5,7 +5,7 @@
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"build-release": "call ./scripts/build-release.bat"
|
||||
"build-release": "call ./scripts/build-release.bat"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
@ -26,4 +26,4 @@
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
echo D|xcopy /Y /S /E .\icons ..\package\manager\icons
|
||||
echo D|xcopy /Y /S /E .\ejs ..\package\manager\ejs
|
||||
echo D|xcopy /Y /S /E .\javascripts ..\package\manager\javascripts
|
||||
echo D|xcopy /Y /S /E .\stylesheets ..\package\manager\stylesheets
|
||||
|
||||
echo F|xcopy /Y /I .\*.* ..\package\manager
|
||||
xcopy /Y /S /E .\icons ..\package\manager\icons
|
||||
xcopy /Y /S /E .\ejs ..\package\manager\ejs
|
||||
xcopy /Y /S /E .\javascripts ..\package\manager\javascripts
|
||||
xcopy /Y /S /E .\stylesheets ..\package\manager\stylesheets
|
||||
xcopy /Y /I .\*.* ..\package\manager
|
||||
|
||||
cd ..
|
||||
call node .\scripts\node\set_version_text.js
|
||||
@ -3,14 +3,18 @@
|
||||
--background-dark: #13181f;
|
||||
--background-light: #202831;
|
||||
--background-disabled: #212A34;
|
||||
--background-note: #2C3540;
|
||||
--background-warning: #3D3322;
|
||||
--background-usage: #28313A;
|
||||
--offwhite: #F2F2F2;
|
||||
--offwhite-transparent: #F2F2F255;
|
||||
--blue: #247be2;
|
||||
--red: #FF5858;
|
||||
--green: #8bff63;
|
||||
--green: #8BFF63;
|
||||
--lightgray: #cfd9e8;
|
||||
--gray: #989898;
|
||||
--darkgray: #3d4651;
|
||||
--orange: #FF7B42;
|
||||
}
|
||||
|
||||
* {
|
||||
@ -36,7 +40,7 @@ body {
|
||||
display: block;
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
height: 20px;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
@ -141,13 +145,30 @@ body {
|
||||
|
||||
#loader {
|
||||
color: var(--offwhite);
|
||||
font-size: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
border: 1px solid var(--offwhite);
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.loading-bar::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: var(--percent);
|
||||
background-color: var(--offwhite);
|
||||
height: 100%;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@ -186,34 +207,6 @@ body {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
color: var(--offwhite);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.instructions>span {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.instructions>span:first-child {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.instructions>span:not(:first-child) {
|
||||
font-size: 15px;
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.buttons-footer {
|
||||
display: flex;
|
||||
column-gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
@ -225,21 +218,30 @@ body {
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.next {
|
||||
color: var(--background);
|
||||
background-color: var(--offwhite);
|
||||
}
|
||||
|
||||
.back {
|
||||
color: var(--offwhite);
|
||||
background-color: var(--background);
|
||||
.button.radio {
|
||||
border: 1px solid var(--offwhite);
|
||||
color: var(--offwhite);
|
||||
}
|
||||
|
||||
.cancel {
|
||||
padding: 10px 5px;
|
||||
color: var(--offwhite);
|
||||
text-decoration: underline;
|
||||
.button.radio.selected {
|
||||
background-color: var(--offwhite);
|
||||
color: var(--background);
|
||||
}
|
||||
|
||||
.button.radio::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 1px solid var(--offwhite);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.button.radio.selected::before {
|
||||
background-color: var(--offwhite);
|
||||
border: 4px solid var(--background);
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.close-popup {
|
||||
@ -259,7 +261,7 @@ input {
|
||||
font-size: 13px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
@ -273,13 +275,13 @@ input {
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
opacity: 30%;
|
||||
background-color: white;
|
||||
opacity: 15%;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
#popup {
|
||||
width: 400px;
|
||||
width: 450px;
|
||||
height: fit-content;
|
||||
min-height: 200px;
|
||||
position: absolute;
|
||||
@ -290,7 +292,7 @@ input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
padding: 20px 40px;
|
||||
align-items: start;
|
||||
z-index: 999;
|
||||
}
|
||||
@ -313,6 +315,9 @@ input {
|
||||
padding: 15px 0px !important;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
#popup .footer {
|
||||
@ -328,93 +333,12 @@ input {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.manager-page>div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.step-summary {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 30%;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--offwhite);
|
||||
border-left: 1px dashed var(--offwhite);
|
||||
height: 200px;
|
||||
row-gap: 100px;
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.step-summary div {
|
||||
display: flex;
|
||||
width: 280px;
|
||||
height: 80px;
|
||||
align-items: center;
|
||||
column-gap: 15px;
|
||||
margin-left: -15px;
|
||||
margin-top: -40px;
|
||||
margin-bottom: -40px;
|
||||
font-size: 14px;
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.step-summary div:before {
|
||||
display: inline-block;
|
||||
content: "";
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid var(--offwhite);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.step-summary div.white {
|
||||
color: var(--offwhite);
|
||||
}
|
||||
|
||||
.step-summary div.blue {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.step-summary div.white:before {
|
||||
background-color: var(--offwhite);
|
||||
}
|
||||
|
||||
.step-summary div.empty:before {
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
.step-summary div.blue:before {
|
||||
border: 1px solid var(--blue);
|
||||
background-color: var(--blue);
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content>div {
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
color: var(--offwhite);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 5px;
|
||||
align-items: center;
|
||||
align-items: start;
|
||||
position: relative;
|
||||
width: 500px;
|
||||
}
|
||||
@ -443,14 +367,6 @@ input {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.buttons-footer {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-top: 0px solid transparent !important;
|
||||
border-bottom: 1px solid var(--offwhite) !important;
|
||||
@ -470,6 +386,7 @@ input {
|
||||
}
|
||||
|
||||
.button.collapse {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@ -492,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 {
|
||||
@ -522,3 +439,22 @@ input {
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
position: relative;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
border: 1px solid var(--offwhite);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.checkbox.checked::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 3px;
|
||||
width: 8px;
|
||||
transform: translate(1px, -1px) rotate(-45deg);
|
||||
border-left: 2px solid var(--offwhite);
|
||||
border-bottom: 2px solid var(--offwhite);
|
||||
}
|
||||
|
||||