-
- Accept or modify port settings (optional)
-
-
- 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.
-
+
+
+
+ Step <%= instances.length === 1? "3": "4" %> of <%= instances.length === 1? "4": "5" %>
-
+
+
+ Manually set Olympus port and address settings
+
+
+ Please note: you may be required to allow these ports through your firewall and modem/router via port
+ forwarding.
+ Otherwise, others may not be able to connect to Olympus. +
+ + Otherwise, others may not be able to connect to Olympus. +
+
Client port
-
+
-
- ">
+ "
+ onchange="signal('onClientPortChanged', this.value)">
Port already in use
+
Backend port
-
+
\ No newline at end of file
diff --git a/manager/ejs/connectionsType.ejs b/manager/ejs/connectionsType.ejs
new file mode 100644
index 00000000..73724f1a
--- /dev/null
+++ b/manager/ejs/connectionsType.ejs
@@ -0,0 +1,25 @@
+
+
- ">
+ "
+ onchange="signal('onBackendPortChanged', this.value)">
Port already in use
@@ -69,25 +45,16 @@
- Backend address
-
+
+ Enable direct backend API connection
+
- ">
-
- <% if (!simplified) { %>
-
- Back
-
- <% } %>
-
- Next
-
+
+ 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.
See the Olympus + documentation for more details.
- <% if (!simplified) { %>
- See the Olympus + documentation for more details.
- <%= install? "Cancel installation": "Cancel editing" %>
-
- <% } %>
+
diff --git a/manager/ejs/expertsettings.ejs b/manager/ejs/expertsettings.ejs
new file mode 100644
index 00000000..ac6e3d0c
--- /dev/null
+++ b/manager/ejs/expertsettings.ejs
@@ -0,0 +1,78 @@
+
+
+
+
+ Step <%= instances.length === 1? "2": "3" %> of <%= instances.length === 1? "4": "5" %>
+
+
+ Do you want to set port and address settings?
+
+
+ We can automatically set port and address settings for you, or you can set them manually.
+ If you don't have a good understanding of how Olympus works, we recommend the auto apply settings option. +
+ + If you don't have a good understanding of how Olympus works, we recommend the auto apply settings option. +
+
+
+ Auto apply settings
+
+
+ Manually set
+
+
+
diff --git a/manager/ejs/folder.ejs b/manager/ejs/folder.ejs
new file mode 100644
index 00000000..51f24901
--- /dev/null
+++ b/manager/ejs/folder.ejs
@@ -0,0 +1,36 @@
+
+
+
+
+ Edit Olympus instance
+
+
+ Please note: you may be required to allow these ports through your firewall and modem/router via port forwarding.
+ Otherwise, others may not be able to connect to Olympus. +
+ + Otherwise, others may not be able to connect to Olympus. +
+
+
+
+
+ Game Master Password
+
+ ">
+
+
+ Blue Commander Password
+
+ ">
+
+
+ Red Commander Password
+
+ ">
+
+ " style="color: var(--offwhite); font-size: var(--normal); color: var(--lightgray);">
+ Note: to keep the old passwords, click Next without editing any value.
+
+
+
+
+ Client port
+
+
+
+
+ "
+ onchange="signal('onClientPortChanged', this.value)">
+
+
+
+
Port already in use
+
+
+ Backend port
+
+
+
+
+ "
+ onchange="signal('onBackendPortChanged', this.value)">
+
+
+
+
Port already in use
+
+
+
+ Enable direct backend API connection
+
+
+
+
+
diff --git a/manager/ejs/installations.ejs b/manager/ejs/installations.ejs
deleted file mode 100644
index 550dbc10..00000000
--- a/manager/ejs/installations.ejs
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
+ <% if (instances.length > 0) { %>
+
+ Olympus cannot be added unless there is a DCS Saved Games folder on your computer.
+ If you are still having issues, try re-installing DCS and Olympus
+ If DCS is installed but Olympus is failing to detect it, you can add it manually.
See the troubleshooting guide for more info. + + <% } %> +
+
+ Step 1 of <%= instances.length === 1? "4": "5" %>
+
+
+ Which DCS instance you want to add Olympus to?
+
+
+ Olympus is added to DCS instances individually, and will only work for that specific instance.
+ You can have Olympus installed across multiple DCS instances. Re-run in the install wizard to add Olympus to another DCS install. +
+ <% } else { %>
+
+ No DCS installs detected
+
+
+ Please ensure you have DCS installed correctly. + You can have Olympus installed across multiple DCS instances. Re-run in the install wizard to add Olympus to another DCS install. +
+ Olympus cannot be added unless there is a DCS Saved Games folder on your computer.
+ If you are still having issues, try re-installing DCS and Olympus
+ If DCS is installed but Olympus is failing to detect it, you can add it manually.
See the troubleshooting guide for more info. + + <% } %> +
+ <% for (var i = 0; i < instances.length; i++) { %>
+
+
+ <%= instances[i].name %>
+
+ <% } %>
+
-
\ No newline at end of file
diff --git a/manager/ejs/instances.ejs b/manager/ejs/instances.ejs
index e059c725..a02dae06 100644
--- a/manager/ejs/instances.ejs
+++ b/manager/ejs/instances.ejs
@@ -1,274 +1,121 @@
-
-
- User path
- Ports and address
- Passwords
- Install
-
-
-
-
- Select a DCS path to install Olympus to.
-
-
- We have automatically detected the following DCS installations under your Saved Games / DCS folder.
-
-
- Please select which DCS installations you want to add Olympus to.
-
-
-
-
-
-
- <% for (let i = 0; i < instances.length; i++) {%>
-
-
- <%= instances[i].name %>
-
<%= instances[i].folder %>
-
- <%= instances[i].installed? (instances[i].error? 'Corrupted/outdated Olympus installation': 'Olympus installed'): 'Olympus not installed' %>
-
-
- <% } %>
-
- Cancel installation
-
-
+
+ <% if (state === 'INSTALL') { %>
+
">
+
Olympus installed successfully in
+ <%= activeInstance !== undefined? activeInstance["name"]: "" %>!
+
+ ">
+
An error occurred while installing Olympus in
+ <%= activeInstance !== undefined? activeInstance["name"]: "" %>
+
+ <% } else if (state === 'EDIT') {%>
+ ">
+
Olympus settings updated for
+ <%= activeInstance !== undefined? activeInstance["name"]: "" %>!
+
+ ">
+
An error occurred while updating Olympus settings for
+ <%= activeInstance !== undefined? activeInstance["name"]: "" %>
+
+ <% } else {%>
+ ">
+
Olympus removed successfully from
+ <%= activeInstance !== undefined? activeInstance["name"]: "" %>!
+
+ ">
+
An error occurred while removing Olympus settings from
+ <%= activeInstance !== undefined? activeInstance["name"]: "" %>
+
+ <% } %>
+
-
-
Return to menu
-
-
- View and manage installs
-
-
- The following Oympus installs have been identified.
You can start an Olympus server, modify settings and uninstall below. - + <% if (instances.length > 0) { %> + + View and manage installs + + + The following DCS installations have been identified.
You can start an Olympus server, modify settings and uninstall below. + + <% } else { %> + + No DCS installs detected + + + Please ensure you have DCS installed correctly.
+ Olympus cannot be added unless there is a DCS Saved Games folder on your computer.
+ If you are still having issues, try re-installing DCS and Olympus
+ If DCS is installed but Olympus is failing to detect it, you can add it manually.
See the troubleshooting guide for more info. + + <% } %>
- You can start an Olympus server, modify settings and uninstall below. - + <% if (instances.length > 0) { %> + + View and manage installs + + + The following DCS installations have been identified.
You can start an Olympus server, modify settings and uninstall below. + + <% } else { %> + + No DCS installs detected + + + Please ensure you have DCS installed correctly.
+ Olympus cannot be added unless there is a DCS Saved Games folder on your computer.
+ If you are still having issues, try re-installing DCS and Olympus
+ If DCS is installed but Olympus is failing to detect it, you can add it manually.
See the troubleshooting guide for more info. + + <% } %>
-
- <% for (let i = 0; i < instances.length; i++) {%>
-
-
- <%= instances[i].name %>
-
- <%= instances[i].installed? (instances[i].error? 'Corrupted/outdated Olympus installation': ''): '' %>
-
-
<%= instances[i].folder %>
-
-
-
FPS: 0
-
Load: 0
-
-
-
- ACTIVE
- OFFLINE
- CONNECTED
- DISCONNECTED
-
-
- Client port
- <%= instances[i].clientPort %>
-
-
- Backend port
- <%= instances[i].backendPort %>
-
-
- Backend address
- <%= instances[i].backendAddress %>
-
-
diff --git a/manager/ejs/menu.ejs b/manager/ejs/menu.ejs
index 412c0b15..999fb28c 100644
--- a/manager/ejs/menu.ejs
+++ b/manager/ejs/menu.ejs
@@ -1,62 +1,55 @@
- Start Olympus
-
-
Start server
- Start client
+
+
+
+ <% for (let i = 0; i < instances.length; i++) {%>
+
- <% } %>
+ <% } %>
+
+
-
+ <%= instances[i].name %>
+
+
<%= instances[i].folder %>
+
+
+
+
FPS: 0
+
Load: 0
+
+
+
+
+
+
+ <%= instances[i].installed? (instances[i].error? 'Corrupted/outdated Olympus installation': 'Olympus installed'): 'Olympus not installed' %>
+
+
+ ACTIVE
+ OFFLINE
+ CONNECTED
+ DISCONNECTED
+
+
+
+ Client port
+ <%= instances[i].installed? instances[i].clientPort: "N/A" %>
+
+
+ Backend port
+ <%= instances[i].installed? instances[i].backendPort: "N/A" %>
+
+
Backend address
+ <%= instances[i].installed? instances[i].backendAddress: "N/A" %>
+
+
+
+ Start Olympus
+
-
+
+ Start server
+ Start client
+ Edit settings
- Uninstall Olympus
- Open in browser
- Stop Olympus
+ Edit settings
+ Install Olympus
+ Uninstall Olympus
+ Open in browser
+ Stop Olympus
+ Open logs
-
-
- DCS OLYMPUS 
- 
INSTALL WIZARD AND MANAGER
- Using this manager, you can install Olympus, update settings, and view and manage instances
-
-
- Install Olympus
+
+
\ No newline at end of file
diff --git a/manager/ejs/passwords.ejs b/manager/ejs/passwords.ejs
index 06a3ba97..68a52f0e 100644
--- a/manager/ejs/passwords.ejs
+++ b/manager/ejs/passwords.ejs
@@ -1,50 +1,40 @@
-
+ DCS OLYMPUS
-
- View / Manage installs
+
+
+ INSTALL WIZARD AND MANAGER
+
+
+ Using this manager, you can install Olympus, update settings, and view and manage instances
+
+
+
+ Add Olympus
+
+
+ Add or update Olympus to a new DCS instance
+
+
+ Change settings
+
+ Adjust port, address and password settings
+
-
-
- User path
- Ports and address
- Passwords
- <%= install? 'Install': 'Update' %>
-
-
-
- Enter your passwords to access Olympus
-
-
- When logging into Olympus, these passwords will let you access the different roles. Gamemaster is the default.
-
-
+
+
+
\ No newline at end of file
+
diff --git a/manager/ejs/result.ejs b/manager/ejs/result.ejs
index 0e8ca129..b7e60b6a 100644
--- a/manager/ejs/result.ejs
+++ b/manager/ejs/result.ejs
@@ -1,146 +1,124 @@
-
+
+
+ Step <%= instances.length === 1? "4": "5" %> of <%= instances.length === 1? "4": "5" %>
+
+
+ Enter your passwords for Olympus
+
+
+ When logging into Olympus, these passwords will let you access the different roles.
+ Game Master is the default and is used as a global commander. The other two are used as a part of the RTS mode. +
+ + Game Master is the default and is used as a global commander. The other two are used as a part of the RTS mode. +
- Game Master Password
+ Game Master Password
-
+ ">
- Blue Commander Password
+ Blue Commander Password
-
+ ">
- Red Commander Password
+ Red Commander Password
-
+ ">
-
-
-
- Back
-
-
- Next
-
+ " style="color: var(--offwhite); font-size: var(--normal); color: var(--lightgray);">
+ Note: to keep the old passwords, click Next without editing any value.
- <% if (!simplified) { %>
-
- <%= install? "Cancel installation": "Cancel editing" %>
-
- <% } %>
-
-
- Installing hook scripts![]()
![]()
+
+
+
Olympus successfully added to <%= activeInstance["name"] %>!
+
+ See the DCS Olympus Wiki for more information on how to use Olympus and for troubleshooting issues. You may now close the installer.
+
+
An error occurred while adding Olympus to <%= activeInstance["name"] %>
+
+ See the manager log located in <%= logLocation %> for more information.
+
+
+ How to launch Olympus
-
- Installing mod folder![]()
![]()
+
-
+ To launch Olympus, there are shortcuts available on the desktop and in the <%= activeInstance["name"] %> folder under Saved Games.
-
- Installing configuration file![]()
![]()
-
-
- Applying configuration![]()
![]()
-
-
- Creating shortcuts![]()
![]()
-
-
-
- Olympus successfully installed in the following DCS instance
-
-
-
- An error has occurred while installing Olympus
-
-
-
- You may now start DCS and use Olympus either with the shortcuts or the "View and manage instances" entry in the
- main menu.
-
-
-
- Please make sure DCS is not currently being executed. Check <%= logLocation %> for more info.
-
-
-
- ![]()
![]()
-
\ No newline at end of file
diff --git a/manager/ejs/type.ejs b/manager/ejs/type.ejs
new file mode 100644
index 00000000..b0781fb6
--- /dev/null
+++ b/manager/ejs/type.ejs
@@ -0,0 +1,25 @@
+
+
-
- <%= instance.name %>
-
-
- <%= instance.folder %>
-
+ <% if (activeInstance["installationType"] === "singleplayer") { %>
+
-
-
+
+
-
+
+
+
+ Launch the Olympus Client via the shortcut on your desktop or in <%= activeInstance["name"] %>.
+
+
+
+
+ Launch DCS, load a mission and unpause the game. Enjoy!
+
+
-
\ No newline at end of file
diff --git a/manager/ejs/settings.ejs b/manager/ejs/settings.ejs
new file mode 100644
index 00000000..ddfcdccc
--- /dev/null
+++ b/manager/ejs/settings.ejs
@@ -0,0 +1,89 @@
+
+
- Back to main menu
+
+
+
+ Alternatively, you can run the Olympus Server instead and visit
+ <% } else { %>
+ ')" >http://localhost:<%= activeInstance["clientPort"] %>
in a web browser (Google Chrome recommended) to replace the first step above.
+
+
+
+
+
+
+
+ Launch the Olympus Server via the shortcut on your desktop or in <%= activeInstance["name"] %>.
+
+
+
+
+
+ To access Olympus remotely visit
+ ')">http://<%= IP %>:<%= activeInstance["clientPort"] %>
in a web browser (Google Chrome recommended).
+
+
+
+
+ Launch DCS, load a mission and unpause the game. Enjoy!
+
+
+ To access Olympus from this PC, you need to visit
+ <% } %>
+ ')">http://localhost:<%= activeInstance["clientPort"] %>
in a web browser (Google Chrome recommended) instead.
+
+
+ Return to main menu
+
+
+ Close manager
+
+
+
Back to menu
+
+ <% if (state === 'EDIT') {%>
+ ">
+
Olympus settings updated for
+ <%= activeInstance !== undefined? activeInstance["name"]: "" %>!
+
+ ">
+
An error occurred while updating Olympus settings for
+ <%= activeInstance !== undefined? activeInstance["name"]: "" %>
+
+ <% } else {%>
+ ">
+
Olympus removed successfully from
+ <%= activeInstance !== undefined? activeInstance["name"]: "" %>!
+
+ ">
+
An error occurred while removing Olympus settings from
+ <%= activeInstance !== undefined? activeInstance["name"]: "" %>
+
+ <% } %>
+
+
+
+ <% if (instances.some(instance => instance.installed)) { %>
+
+ Change settings
+
+
+ Here you can see the DCS instances on your computer that have Olympus installed.
+ You can edit settings and uninstall Olympus from this screen. + + <% } else { %> + + No Olympus installs detected + + + Use the Add Olympus option in the main manu to install Olympus in your DCS instance.
+ If you have more than one DCS instance, you will need to add Olympus to each one of them. + + <% } %> +
+ + You can edit settings and uninstall Olympus from this screen. + + <% } else { %> + + No Olympus installs detected + + + Use the Add Olympus option in the main manu to install Olympus in your DCS instance.
+ If you have more than one DCS instance, you will need to add Olympus to each one of them. + + <% } %> +
+
+
+ <% for (let i = 0; i < instances.length; i++) {%>
+ <% if (instances[i].installed) { %>
+
+
+
+ <% } %>
+ <% } %>
+
+ <%= instances[i].name %>
+
<%= instances[i].folder %>
+
+
+ <%= instances[i].installed? (instances[i].error? 'Corrupted/outdated Olympus installation': 'Olympus installed'): 'Olympus not installed' %>
+
+
+
+
+
+
+ Client port
+ <%= instances[i].installed? instances[i].clientPort: "N/A" %>
+
+
+ Backend port
+ <%= instances[i].installed? instances[i].backendPort: "N/A" %>
+
+
+ Backend address
+ <%= instances[i].installed? instances[i].backendAddress: "N/A" %>
+ Open logs
+
+
+ Edit settings
+ Uninstall Olympus
+
+
diff --git a/manager/ejs/welcome.ejs b/manager/ejs/welcome.ejs
new file mode 100644
index 00000000..87384c41
--- /dev/null
+++ b/manager/ejs/welcome.ejs
@@ -0,0 +1,68 @@
+
+
+
+
+ Step <%= instances.length === 1? "1": "2" %> of <%= instances.length === 1? "4": "5" %>
+
+
+ Do you want to add Olympus for singleplayer or multiplayer?
+
+
+ Select singleplayer if you only want to play locally on your own computer.
+ Select multiplayer if you want Olympus to be useable over the internet from a different computer, or this instance is a dedicated server. +
+ + Select multiplayer if you want Olympus to be useable over the internet from a different computer, or this instance is a dedicated server. +
+
+
+ Singleplayer
+
+
+ Multiplayer
+
+
+
\ No newline at end of file
diff --git a/manager/ejs/wizard.ejs b/manager/ejs/wizard.ejs
new file mode 100644
index 00000000..5f069df7
--- /dev/null
+++ b/manager/ejs/wizard.ejs
@@ -0,0 +1,87 @@
+
+
+ Do you want to use the Olympus Manager in basic or Expert mode?
+
+
+ Basic mode is recommended for most users.
+ Expert mode is for those who know how Olympus works or for server owners. +
+ + Expert mode is for those who know how Olympus works or for server owners. +
+ You can change this setting at any time.
+
+
+ Basic mode
+
+
+ Expert mode
+
+
+
\ No newline at end of file
diff --git a/manager/icons/arrow-right-solid.svg b/manager/icons/arrow-right-solid.svg
new file mode 100644
index 00000000..418e8ad4
--- /dev/null
+++ b/manager/icons/arrow-right-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/manager/icons/check-solid-background.svg b/manager/icons/check-solid-background.svg
new file mode 100644
index 00000000..183217d9
--- /dev/null
+++ b/manager/icons/check-solid-background.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/manager/icons/chrome.svg b/manager/icons/chrome.svg
new file mode 100644
index 00000000..ee2a9807
--- /dev/null
+++ b/manager/icons/chrome.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/manager/icons/gamepad-solid.svg b/manager/icons/gamepad-solid.svg
new file mode 100644
index 00000000..79622b10
--- /dev/null
+++ b/manager/icons/gamepad-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/manager/icons/olympus_white.png b/manager/icons/olympus_white.png
new file mode 100644
index 00000000..7fd37a2b
Binary files /dev/null and b/manager/icons/olympus_white.png differ
diff --git a/manager/icons/server-solid.svg b/manager/icons/server-solid.svg
index 1a5af762..ac7da489 100644
--- a/manager/icons/server-solid.svg
+++ b/manager/icons/server-solid.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/manager/icons/triangle-exclamation-solid-background.svg b/manager/icons/triangle-exclamation-solid-background.svg
new file mode 100644
index 00000000..e3b5d51b
--- /dev/null
+++ b/manager/icons/triangle-exclamation-solid-background.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/manager/icons/triangle-exclamation-solid-orange.svg b/manager/icons/triangle-exclamation-solid-orange.svg
new file mode 100644
index 00000000..cf198f5c
--- /dev/null
+++ b/manager/icons/triangle-exclamation-solid-orange.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/manager/index.html b/manager/index.html
index f89cfbab..47a94e8a 100644
--- a/manager/index.html
+++ b/manager/index.html
@@ -20,7 +20,7 @@
+
<%= state === 'INSTALL'? "Cancel install": "Cancel editing" %>
+
+
+
+
+
+
+
+ Back
+
+
+ Next
+
+
+
-
-
-
+
+
+
-
DCS Olympus Manager
@@ -28,13 +28,16 @@
User Guide
Troubleshooting Guide
+
- Loading, please wait...
+
+
Loading, please wait...
+
+
@@ -42,7 +45,7 @@
You can find more info in ${path.join(__dirname, "..", "manager.log")}`, () => { - location.reload(); - }); - } - )); + /** Edit this instance + * + */ + async edit() { + showWaitLoadingPopup(`Please wait while Olympus is being edited in ${this.name}`); + try { + setPopupLoadingProgress("Applying configuration...", 0); + await sleep(500); + await applyConfiguration(getManager().getActiveInstance().folder, getManager().getActiveInstance()); + + setPopupLoadingProgress("Editing completed!", 100); + await sleep(1500); + logger.log(`Editing completed successfully`); + hidePopup(); + + getManager().getMode() === "basic"? getManager().settingsPage.show(): getManager().instancesPage.show(); + } catch (err) { + logger.log(`An error occurred during editing: ${err}`); + getManager().getActiveInstance().error = true; + + showErrorPopup(`
You can find more info in ${path.join(__dirname, "..", "manager.log")}`); + + /* Nested popup calls need to wait for animation to complete */ + await sleep(300); + showErrorPopup(`
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.
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.") + this.expertSettingsPage.options.onShow = () => { + if (this.getActiveInstance()) { + this.setPort('client', this.getActiveInstance().clientPort); + this.setPort('backend', this.getActiveInstance().backendPort); } - 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.
You can find more info in ${path.join(__dirname, "..", "manager.log")}`) + } + + /* Always force the IDLE state when reaching the menu page */ + this.menuPage.options.onShow = async () => { + await this.setState('IDLE'); } - } - passwordsPage.onCancelClicked = (e) => { - /* Go back to the main menu */ - passwordsPage.hide(); - menuPage.show(); - } + /* Update the instances when showing the dashboard */ + this.instancesPage.options.onShow = () => { + this.updateInstances(); + } - /* Result */ - var resultPage = new ResultPage({logLocation: path.join(__dirname, "..", "manager.log")}); - resultPage.onBackClicked = (e) => { - /* Reload the page to apply changes */ - resultPage.hide(); + /* Reset default radio buttons */ + this.typePage.options.onShow = () => { + if (this.getActiveInstance()) + this.getActiveInstance().installationType = 'singleplayer'; + else { + showErrorPopup(`
You can find more info in ${path.join(__dirname, "..", "manager.log")}`, () => { + showErrorPopup(`
-
+
Accept
@@ -77,6 +80,19 @@
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);
+ }
+
+ window.addEventListener("click", (ev) => {
+ var buttons = document.querySelectorAll(".button.collapse");
+ for (let button of buttons) {
+ if (button != ev.srcElement)
+ button.classList.remove("open");
+ }
+ })
\ No newline at end of file
diff --git a/manager/javascripts/connections.js b/manager/javascripts/connections.js
deleted file mode 100644
index 7d01f2e1..00000000
--- a/manager/javascripts/connections.js
+++ /dev/null
@@ -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;
\ No newline at end of file
diff --git a/manager/javascripts/dcsinstance.js b/manager/javascripts/dcsinstance.js
index 715be367..09dc577d 100644
--- a/manager/javascripts/dcsinstance.js
+++ b/manager/javascripts/dcsinstance.js
@@ -1,65 +1,114 @@
-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(force = false) {
+ if (this.instances === null || force)
+ DCSInstance.instances = this.findInstances();
+ return DCSInstance.instances;
+ }
+
+ /** Static asynchronous method to reload all DCS instances. It will not detect any new instance, but it will determine the
+ * installation status of the existing instances.
*
*/
- static async getInstances() {
- if (this.instances === null) {
- this.instances = await this.findInstances();
+ static async reloadInstances() {
+ var instances = await this.getInstances();
+ for (let instance of instances) {
+ await instance.checkInstallation();
}
- return this.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'];
+ var folders = fs.readdirSync(searchpath).map((folder) => {return path.join(searchpath, folder);});
+ var instances = [];
+ folders = folders.concat(getManager().getAdditionalDCSInstances());
+
+ /* 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(folder, "Config", "appsettings.lua")) || fs.existsSync(path.join(folder, "Config", "serversettings.lua")) || getManager().getAdditionalDCSInstances().includes(folder)) {
+ logger.log(`Found instance in ${folder}, checking for Olympus`)
+ var newInstance = new DCSInstance(path.join(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.")
+ showErrorPopup(`An error occured while trying to fetch the location of the DCS instances.
You can find more info in ${getManager().getLogLocation()}
`);
+ }
+ 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 +127,73 @@ 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() {
+ /* Reset values */
+ this.installed = false;
+ this.error = false;
+ this.installationType = 'singleplayer';
+ this.connectionsType = 'auto';
+
/* 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"];
+
+ this.gameMasterPasswordEdited = false;
+ this.blueCommanderPasswordEdited = false;
+ this.redCommanderPasswordEdited = false;
+
} 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 +202,39 @@ 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}`);
}
+ } else {
+ this.installed = false;
+ this.error = false;
}
-
- /* 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,91 +242,151 @@ 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.
*
*/
async getData() {
- if (this.installed && !this.error) {
+ if (this.installed) {
fetchWithTimeout(`http://localhost:${this.clientPort}`, { timeout: 250 })
.then(async (response) => {
this.webserverOnline = (await response.text()).includes("Olympus");
@@ -343,7 +455,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 +482,137 @@ class DCSInstance {
})
}
- /* Uninstall this instance */
- uninstall() {
- showConfirmPopup("Are you sure you want to completely remove this Olympus installation?", () =>
- uninstallInstance(this.folder, this.name).then(
- () => {
- location.reload();
- },
- (err) => {
- logger.error(err)
- showErrorPopup(`An error has occurred while uninstalling the Olympus instance. Make sure Olympus and DCS are not running. You can find more info in ${path.join(__dirname, "..", "manager.log")}`, () => { - location.reload(); - }); - } - )); + /** Edit this instance + * + */ + async edit() { + showWaitLoadingPopup(`Please wait while Olympus is being edited in ${this.name}`); + try { + setPopupLoadingProgress("Applying configuration...", 0); + await sleep(500); + await applyConfiguration(getManager().getActiveInstance().folder, getManager().getActiveInstance()); + + setPopupLoadingProgress("Editing completed!", 100); + await sleep(1500); + logger.log(`Editing completed successfully`); + hidePopup(); + + getManager().getMode() === "basic"? getManager().settingsPage.show(): getManager().instancesPage.show(); + } catch (err) { + logger.log(`An error occurred during editing: ${err}`); + getManager().getActiveInstance().error = true; + + showErrorPopup(`
A critical error occurred!
Check ${getManager().getLogLocation()} for more info.
`)
+ getManager().getMode() === "basic"? getManager().settingsPage.show(): getManager().instancesPage.show();
+ }
+ }
+
+ /** Install this instance
+ *
+ */
+ async install() {
+ showWaitLoadingPopup(`Please wait while Olympus is being installed in ${this.name}`);
+ try {
+ getManager().activePage.hide();
+ 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();
+ if (getManager().getMode() === 'basic') {
+ 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");
+ } else {
+ await getManager().reload();
+ getManager().instancesPage.show();
+ }
+ } catch (err) {
+ logger.log(`An error occurred during installation: ${err}`);
+ hidePopup();
+ if (getManager().getMode() === 'basic') {
+ getManager().resultPage.show();
+ getManager().resultPage.getElement().querySelector(".result-summary.success").classList.add("hide");
+ getManager().resultPage.getElement().querySelector(".result-summary.error").classList.remove("hide");
+ } else {
+ await getManager().reload();
+ getManager().instancesPage.show();
+ }
+ }
+ }
+
+ /** Uninstall this instance
+ *
+ */
+ async uninstall() {
+ showConfirmPopup(` Are you sure you want to remove Olympus from ${this.name}?
This will only remove Olympus for this particular DCS instance.
`, async () => {
+ try {
+ getManager().activePage.hide();
+ logger.log(`Uninstalling Olympus from ${this.folder}`)
+ await sleep(300);
+ showWaitLoadingPopup(`Please wait while Olympus is being removed from ${this.name}`);
+ 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}`)
+
+ hidePopup();
+ await getManager().reload();
+ if (getManager().getMode() === 'basic')
+ getManager().settingsPage.show();
+ else
+ getManager().instancesPage.show();
+ return true;
+ } catch (err) {
+ logger.error(err);
+
+ /* Nested popup calls need to wait for animation to complete */
+ await sleep(300);
+ showErrorPopup(`An error has occurred while uninstalling the Olympus instance.
Make sure Olympus and DCS are not running.
You can find more info in ${path.join(__dirname, "..", "manager.log")}
`, () => {
+ if (getManager().getMode() === 'basic')
+ getManager().settingsPage.show();
+ else
+ getManager().instancesPage.show();
+ });
+ }
+ }, () => {
+ getManager().setState('IDLE');
+ });
}
}
diff --git a/manager/javascripts/filesystem.js b/manager/javascripts/filesystem.js
index b876ddf3..f2365b60 100644
--- a/manager/javascripts/filesystem.js
+++ b/manager/javascripts/filesystem.js
@@ -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
}
diff --git a/manager/javascripts/installations.js b/manager/javascripts/installations.js
deleted file mode 100644
index c3014acb..00000000
--- a/manager/javascripts/installations.js
+++ /dev/null
@@ -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;
\ No newline at end of file
diff --git a/manager/javascripts/instances.js b/manager/javascripts/instances.js
deleted file mode 100644
index aa149fe8..00000000
--- a/manager/javascripts/instances.js
+++ /dev/null
@@ -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;
\ No newline at end of file
diff --git a/manager/javascripts/manager.js b/manager/javascripts/manager.js
index fcccc21b..3037a5ae 100644
--- a/manager/javascripts/manager.js
+++ b/manager/javascripts/manager.js
@@ -1,264 +1,767 @@
-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 = {
+ activeInstance: undefined,
+ additionalDCSInstances: [],
+ configLoaded: false,
+ instances: [],
+ IP: undefined,
+ logLocation: path.join(__dirname, "..", "manager.log"),
+ mode: 'basic',
+ state: 'IDLE'
+ };
+
+ /* Manager pages */
+ activePage = null;
+ welcomePage = null;
+ settingsPage = null;
+ folderPage = null;
+ typePage = null;
+ connectionsTypePage = null;
+ connectionsPage = null;
+ passwordsPage = null;
+ resultPage = null;
+ instancesPage = null;
+ expertSettingsPage = 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);
+ }
+ });
+ window.olympus = {
+ manager: this
+ };
}
+ /** 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.setConfigLoaded(true);
+ } catch (e) {
+ logger.error(`An error occurred while reading the options.json file: ${e}`);
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`)
+ }
+ }
- document.getElementById("loader").classList.add("hide");
+ if (!this.getConfigLoaded()) {
+ this.hideLoadingPage();
- /* Check if there are corrupted or outdate instances */
- if (instances.some((instance) => {
- return instance.installed && instance.error;
- })) {
- /* Ask the user for confirmation */
- showErrorPopup("One or more Olympus instances are corrupted or need updating. Press Close to fix this.", async () => {
- showWaitPopup("Please wait while your instances are being fixed.")
- fixInstances(instances.filter((instance) => {
- return instance.installed && instance.error;
- })).then(
- () => { location.reload() },
- (err) => {
+ /* 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.getMode() === "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);
+ var instances = await DCSInstance.getInstances();
+ this.setLoadingProgress(`Analysis completed, starting manager...`, 100);
+ await sleep(100);
+
+ this.setInstances(instances);
+
+ /* Get my public IP */
+ this.getPublicIP().then(
+ (IP) => { this.setIP(IP); },
+ (err) => {
+ logger.log(err)
+ this.setIP(undefined);
+ }
+ )
+
+ /* Check if there are corrupted or outdated instances */
+ if (this.getInstances().some((instance) => {
+ return instance.installed && instance.error;
+ })) {
+ /* Ask the user for confirmation */
+ showConfirmPopup(" One or more of your Olympus instances are not up to date!
If you have just updated Olympus this is normal.
Press Accept and the Manager will update your instances for you.
Press Close to update your instances manually using the Installation Wizard
", async () => {
+ try {
+ /* Nested popup calls need to wait for animation to complete */
+ await sleep(300);
+
+ await DCSInstance.fixInstances();
+ location.reload();
+ } catch (err) {
logger.error(err);
- showErrorPopup(`An error occurred while trying to fix your installations. Please reinstall Olympus manually. Press Accept and the Manager will update your instances for you.
Press Close to update your instances manually using the Installation Wizard
You can find more info in ${path.join(__dirname, "..", "manager.log")}`); + + /* Nested popup calls need to wait for animation to complete */ + await sleep(300); + showErrorPopup(`
An error occurred while trying to fix your installations. Please reinstall Olympus manually.
You can find more info in ${this.options.logLocation}
`);
}
- )
- })
- }
-
- /* 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();
+ /* Hide the loading page */
+ this.hideLoadingPage();
- connectionsPage.onBackClicked = (e) => {
- /* Show the installation page */
- connectionsPage.hide();
- installationsPage.show();
- }
- }
- installationsPage.onCancelClicked = (e) => {
- /* Go back to the main menu */
- installationsPage.hide();
- menuPage.show();
- }
+ /* 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");
+ this.expertSettingsPage = new WizardPage(this, "./ejs/expertsettings.ejs");
- /* 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.")
+ /* Force the setting of the ports whenever the page is shown */
+ this.connectionsPage.options.onShow = () => {
+ if (this.getActiveInstance()) {
+ this.setPort('client', this.getActiveInstance().clientPort);
+ this.setPort('backend', this.getActiveInstance().backendPort);
}
- } else {
- showErrorPopup(`An error has occurred, please restart the Olympus Manager. 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.
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.") + this.expertSettingsPage.options.onShow = () => { + if (this.getActiveInstance()) { + this.setPort('client', this.getActiveInstance().clientPort); + this.setPort('backend', this.getActiveInstance().backendPort); } - 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.
You can find more info in ${path.join(__dirname, "..", "manager.log")}`) + } + + /* Always force the IDLE state when reaching the menu page */ + this.menuPage.options.onShow = async () => { + await this.setState('IDLE'); } - } - passwordsPage.onCancelClicked = (e) => { - /* Go back to the main menu */ - passwordsPage.hide(); - menuPage.show(); - } + /* Update the instances when showing the dashboard */ + this.instancesPage.options.onShow = () => { + this.updateInstances(); + } - /* Result */ - var resultPage = new ResultPage({logLocation: path.join(__dirname, "..", "manager.log")}); - resultPage.onBackClicked = (e) => { - /* Reload the page to apply changes */ - resultPage.hide(); + /* Reset default radio buttons */ + this.typePage.options.onShow = () => { + if (this.getActiveInstance()) + this.getActiveInstance().installationType = 'singleplayer'; + else { + showErrorPopup(`
A critical error occurred!
Check ${this.getLogLocation()} for more info.
`);
+ }
+ }
+
+ this.connectionsTypePage.options.onShow = () => {
+ if (this.getActiveInstance())
+ this.getActiveInstance().connectionsType = 'auto';
+ else {
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`);
+ }
+ }
+
+ /* Reload the instances when we get to the folder page */
+ this.folderPage.options.onShow = async () => {
+ if (this.getInstances().length > 0)
+ this.setActiveInstance(this.getInstances()[0]);
+ await DCSInstance.reloadInstances();
+ }
+
+ if (this.getMode() === "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"));
+ }
+ }
+
+ /** Creates the options file. This is done only the very first time you start Olympus.
+ *
+ * @param {String} mode The mode, either Basic or Expert
+ */
+ async createOptionsFile(mode) {
+ try {
+ fs.writeFileSync("options.json", JSON.stringify({ mode: mode, additionalDCSInstances: [] }, null, 2));
location.reload();
+ } catch (err) {
+ logger.log(err);
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`)
}
- resultPage.onCancelClicked = (e) => {
- /* Reload the page to apply changes */
- resultPage.hide();
- location.reload();
+ }
+
+ /** Switch to a different mode of operation
+ *
+ * @param {String} newMode The mode to switch to
+ */
+ async 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, null, 2));
+ location.reload();
+ }
+
+ /************************************************/
+ /* CALLBACKS */
+ /************************************************/
+ /** Switch to basic mode
+ *
+ */
+ async onBasicClicked() {
+ this.createOptionsFile("basic");
+ }
+
+ /** Switch to expert mode
+ *
+ */
+ async onExpertClicked() {
+ this.createOptionsFile("expert");
+ }
+
+ /** When the install button is clicked go the installation page
+ *
+ */
+ async onInstallMenuClicked() {
+ await this.setState('INSTALL');
+
+ if (this.getInstances().length == 0) {
+ // TODO: show error
}
- /* 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());
+ if (this.getInstances().length === 1) {
+ this.setActiveInstance(this.getInstances()[0]);
- /* In simplified mode we directly show the connections page */
- if (this.simplified) {
- const options = {
- instance: instances[0],
- simplified: this.simplified,
- install: true
+ /* Show the type selection page */
+ if (!this.getActiveInstance().installed) {
+ this.activePage.hide()
+ this.typePage.show();
+ } else {
+ if (this.getActiveInstance().webserverOnline || this.getActiveInstance().backendOnline) {
+ showErrorPopup("The selected Olympus instance is currently active
Please stop DCS and Olympus Server/Client before editing it!
");
+ } else {
+ showConfirmPopup(" Olympus is already installed in this instance!
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();
+ },
+ async () => {
+ await this.setState('IDLE');
+ }
+ )
+ }
}
- 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
+ *
+ */
+ async onEditMenuClicked() {
+ this.activePage.hide();
+ await this.setState('IDLE');
+ 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.setActiveInstance(instance);
+ }
+ }
+
+ /* When the installation type is selected */
+ async onInstallTypeClicked(type) {
+ this.typePage.getElement().querySelector(`.singleplayer`).classList.toggle("selected", type === 'singleplayer');
+ this.typePage.getElement().querySelector(`.multiplayer`).classList.toggle("selected", type === 'multiplayer');
+ if (this.getActiveInstance())
+ this.getActiveInstance().installationType = type;
+ else {
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`);
+ }
+ }
+
+ /* When the connections type is selected */
+ async onConnectionsTypeClicked(type) {
+ this.connectionsTypePage.getElement().querySelector(`.auto`).classList.toggle("selected", type === 'auto');
+ this.connectionsTypePage.getElement().querySelector(`.manual`).classList.toggle("selected", type === 'manual');
+ if (this.getActiveInstance())
+ this.getActiveInstance().connectionsType = type;
+ else {
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`);
+ }
+ }
+
+ /* When the next button of a wizard page is clicked */
+ async onNextClicked() {
+ /* Choose which page to show depending on the active page */
+ /* Folder selection page */
+ if (this.activePage == this.folderPage) {
+ if (this.getActiveInstance().installed) {
+ if (this.getActiveInstance().webserverOnline || this.getActiveInstance().backendOnline) {
+ showErrorPopup("The selected Olympus instance is currently active
Please stop DCS and Olympus Server/Client before editing it!
");
+ } else {
+ showConfirmPopup(" Olympus is already installed in this instance!
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();
+ },
+ async () => {
+ await this.setState('IDLE');
+ }
+ )
+ }
+ } else {
+ this.activePage.hide();
+ this.typePage.show();
+ }
+ /* Installation type page */
+ } else if (this.activePage == this.typePage) {
+ this.activePage.hide();
+ this.connectionsTypePage.show();
+ /* Connection type page */
+ } else if (this.activePage == this.connectionsTypePage) {
+ if (this.getActiveInstance()) {
+ if (this.getActiveInstance().connectionsType === 'auto') {
+ this.activePage.hide();
+ this.passwordsPage.show();
+ }
+ else {
+ this.activePage.hide();
+ this.connectionsPage.show();
+ (this.getMode() === 'basic'? this.connectionsPage: this.expertSettingsPage).getElement().querySelector(".backend-address .checkbox").classList.toggle("checked", this.getActiveInstance().backendAddress === '*')
+ }
+ } else {
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`)
+ }
+ /* Connection page */
+ } else if (this.activePage == this.connectionsPage) {
+ if (await this.checkPorts()) {
+ this.activePage.hide();
+ this.passwordsPage.show();
+ }
+ /* Passwords page */
+ } else if (this.activePage == this.passwordsPage) {
+ if (await this.checkPasswords()) {
+ this.activePage.hide();
+ this.getState() === 'INSTALL' ? this.getActiveInstance().install() : this.getActiveInstance().edit();
+ }
+ /* Expert settings page */
+ } else if (this.activePage == this.expertSettingsPage) {
+ if (await this.checkPorts() && await this.checkPasswords()) {
+ this.activePage.hide();
+ this.getState() === 'INSTALL' ? this.getActiveInstance().install() : this.getActiveInstance().edit();
+ }
+ }
+ }
+
+ /* When the back button of a wizard page is clicked */
+ async onBackClicked() {
+ this.activePage.hide();
+
+ /* If we have backed to the menu, instances or settings page, reset the active instance */
+ if ([this.instancesPage, this.settingsPage].includes(this.activePage.previousPage)) {
+ await this.setState('IDLE');
+ }
+
+ this.activePage.previousPage.show(true); // Don't change the previous page (or we get stuck in a loop)
+ this.updateInstances();
+ }
+
+ async onCancelClicked() {
+ this.activePage.hide();
+ await this.setState('IDLE');
+ if (this.getMode() === "basic")
+ this.menuPage.show(true);
+ else
+ this.instancesPage.show(true);
+ this.updateInstances();
+ }
+
+ async onGameMasterPasswordChanged(value) {
+ for (let input of this.activePage.getElement().querySelectorAll("input[type='password']")) {
+ input.placeholder = "";
+ }
+
+ if (this.getActiveInstance())
+ this.getActiveInstance().setGameMasterPassword(value);
+ else
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`);
+ }
+
+ async onBlueCommanderPasswordChanged(value) {
+ for (let input of this.activePage.getElement().querySelectorAll("input[type='password']")) {
+ input.placeholder = "";
+ }
+
+ if (this.getActiveInstance())
+ this.getActiveInstance().setBlueCommanderPassword(value);
+ else
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`);
+ }
+
+ async onRedCommanderPasswordChanged(value) {
+ for (let input of this.activePage.getElement().querySelectorAll("input[type='password']")) {
+ input.placeholder = "";
+ }
+
+ if (this.getActiveInstance())
+ this.getActiveInstance().setRedCommanderPassword(value);
+ else
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`);
+ }
+
+ /* When the client port input value is changed */
+ async onClientPortChanged(value) {
+ this.setPort('client', Number(value));
+ }
+
+ /* When the backend port input value is changed */
+ async onBackendPortChanged(value) {
+ this.setPort('backend', Number(value));
+ }
+
+ /* When the "Enable API connection" checkbox is clicked */
+ async onEnableAPIClicked() {
+ if (this.getActiveInstance()) {
+ if (this.getActiveInstance().backendAddress === 'localhost') {
+ this.getActiveInstance().backendAddress = '*';
+ } else {
+ this.getActiveInstance().backendAddress = 'localhost';
+ }
+ if (this.getMode() === 'basic') {
+ this.connectionsPage.getElement().querySelector(".note.warning").classList.toggle("hide", this.getActiveInstance().backendAddress !== '*')
+ this.connectionsPage.getElement().querySelector(".backend-address .checkbox").classList.toggle("checked", this.getActiveInstance().backendAddress === '*')
+ } else {
+ this.expertSettingsPage.getElement().querySelector(".backend-address .checkbox").classList.toggle("checked", this.getActiveInstance().backendAddress === '*')
+ }
+ } else {
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`)
+ }
+ }
+
+ /* When the "Return to manager" button is pressed */
+ async onReturnClicked() {
+ await this.reload();
+ this.activePage.hide();
+ this.menuPage.show();
+ }
+
+ /* When the "Close manager" button is pressed */
+ async onCloseManagerClicked() {
+ document.querySelector('.close').click();
+ }
+
+ async checkPorts() {
+ var clientPortFree = await this.getActiveInstance().checkClientPort();
+ var backendPortFree = await this.getActiveInstance().checkBackendPort();
+ if (clientPortFree && backendPortFree) {
+ return true;
+ } else {
+ showErrorPopup(` Please, make sure both the client and backend ports are free!
If ports are already in use, Olympus will not be able to communicated correctly.
`);
+ return false;
+ }
+ }
+
+ async checkPasswords() {
+ if (this.getActiveInstance()) {
+ if (this.getActiveInstance().installed && !this.getActiveInstance().arePasswordsEdited()) {
+ return true;
+ }
+ else {
+ if (!this.getActiveInstance().arePasswordsSet()) {
+ showErrorPopup(`Please, make sure all passwords are set!
The role users will fulfill depends on the password they enter at login.
`);
+ return false;
+ } else if (!this.getActiveInstance().arePasswordsDifferent()) {
+ showErrorPopup(`Please, set different passwords!
The role users will fulfill depends on the password they enter at login.
`);
+ return false;
+ } else {
+ return true;
+ }
+ }
+ } else {
+ showErrorPopup(`A critical error occurred!
Check ${this.getLogLocation()} for more info.
`)
+ return false;
+ }
+ }
+
+ 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("The selected Olympus instance is currently active
Please stop DCS and Olympus Server/Client before editing it!
")
+ } else {
+ this.setActiveInstance(instance);
+ await this.setState('EDIT');
+ this.activePage.hide();
+ (this.getMode() === 'basic'? this.typePage: this.expertSettingsPage).show();
+ }
+ }
+
+ async onInstallClicked(name) {
+ var instance = await this.getClickedInstance(name);
+ this.setActiveInstance(instance);
+ await this.setState('INSTALL');
+ this.activePage.hide();
+ (this.getMode() === 'basic'? this.typePage: this.expertSettingsPage).show();
+ }
+
+ async onUninstallClicked(name) {
+ var instance = await this.getClickedInstance(name);
+ this.setActiveInstance(instance);
+ await this.setState('UNINSTALL');
+ if (instance.webserverOnline || instance.backendOnline)
+ showErrorPopup("The selected Olympus instance is currently active
Please stop DCS and Olympus Server/Client before removing it!
")
+ else
+ await instance.uninstall();
+ }
+
+ async onLinkClicked(url) {
+ exec(`start ${url}`);
+ }
+
+ async onTextFileClicked(path) {
+ exec(`notepad "${path}"`);
+ }
+
+ 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.getActiveInstance().checkClientPort(value);
+ this.getActiveInstance().setClientPort(value);
+ }
+ else {
+ success = await this.getActiveInstance().checkBackendPort(value);
+ this.getActiveInstance().setBackendPort(value);
+ }
+
+ var successEls = (this.getMode() === 'basic'? this.connectionsPage: this.expertSettingsPage).getElement().querySelector(`.${port}-port`).querySelectorAll(".success");
+ for (let i = 0; i < successEls.length; i++) {
+ successEls[i].classList.toggle("hide", !success);
+ }
+ var errorEls = (this.getMode() === 'basic'? this.connectionsPage: this.expertSettingsPage).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;
+ }
+
+ async updateInstances() {
+ var instanceDivs = this.instancesPage.getElement().querySelectorAll(`.option`);
+ for (let i = 0; i < instanceDivs.length; i++) {
+ var instanceDiv = instanceDivs[i];
+ var instance = this.getInstances().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 (instance.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 (instance.webserverOnline)
+ instanceDiv.querySelector(".button.start").classList.remove("loading");
+ }
+ }
+ }
+ }
+ }
+
+ async reload() {
+ await DCSInstance.reloadInstances();
+
+ this.options.installEnabled = true;
+ this.options.editEnabled = this.getInstances().find(instance => instance.installed);
+ }
+
+ async setLoadingProgress(message, percent) {
+ document.querySelector("#loader .loading-message").innerHTML = message;
+ if (percent) {
+ var style = document.querySelector('#loader .loading-bar').style;
+ style.setProperty('--percent', `${percent}%`);
+ }
+ }
+
+ async hideLoadingPage() {
+ /* Hide the loading page */
+ document.getElementById("loader").style.opacity = "0%";
+ window.setTimeout(() => {
+ document.getElementById("loader").classList.add("hide");
+ }, 250);
+ }
+
+ async setActiveInstance(newActiveInstance) {
+ this.options.activeInstance = newActiveInstance;
+ }
+
+ async setAdditionalDCSInstances(newAdditionalDCSInstances) {
+ this.options.additionalDCSInstances = newAdditionalDCSInstances;
+ }
+
+ async setConfigLoaded(newConfigLoaded) {
+ this.options.configLoaded = newConfigLoaded;
+ }
+
+ async setInstances(newInstances) {
+ this.options.instances = newInstances;
+ }
+
+ async setIP(newIP) {
+ this.options.IP = newIP;
+ }
+
+ async setLogLocation(newLogLocation) {
+ this.options.logLocation = newLogLocation;
+ }
+
+ async setState(newState) {
+ this.options.state = newState;
+ await DCSInstance.reloadInstances();
+ if (newState === 'IDLE')
+ this.setActiveInstance(undefined);
+ }
+
+ /** Get the currently active instance, i.e. the instance that is being edited/installed/removed
+ *
+ * @returns The active instance
+ */
+ getActiveInstance() {
+ return this.options.activeInstance;
+ }
+
+ getAdditionalDCSInstances() {
+ return this.options.additionalDCSInstances
+ }
+
+ getConfigLoaded() {
+ return this.options.configLoaded;
+ }
+
+ getInstances() {
+ return this.options.instances;
+ }
+
+ getIP() {
+ return this.options.IP;
+ }
+
+ getLogLocation() {
+ return this.options.logLocation;
+ }
+
+ getState() {
+ return this.options.state;
+ }
+
+ getMode() {
+ return this.options.mode;
+ }
}
module.exports = Manager;
\ No newline at end of file
diff --git a/manager/javascripts/managerfactory.js b/manager/javascripts/managerfactory.js
new file mode 100644
index 00000000..04411422
--- /dev/null
+++ b/manager/javascripts/managerfactory.js
@@ -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
+};
\ No newline at end of file
diff --git a/manager/javascripts/managerpage.js b/manager/javascripts/managerpage.js
index 4a02a731..1928377e 100644
--- a/manager/javascripts/managerpage.js
+++ b/manager/javascripts/managerpage.js
@@ -1,33 +1,64 @@
-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() {
- this.element.classList.remove("hide");
+ show(ignorePreviousPage) {
+ ejs.renderFile(this.ejsFile, {...this.options, ...this.manager.options}, {}, (err, str) => {
+ if (!err) {
+ this.render(str);
+ } else {
+ logger.error(err);
+ }
+ });
+
+ 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");
+ this.element.style.opacity = "0%";
+ window.setTimeout(() => {
+ this.element.classList.add("hide");
+ }, 250);
}
- render() {
+ render(str) {
+ this.element.innerHTML = str;
+ this.element.style.opacity = "0%";
+
+ this.element.classList.remove("hide");
+ window.setTimeout(() => {
+ this.element.style.opacity = "100%";
+ }, 0)
+
/* 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");
})
- }
+ }
}
}
diff --git a/manager/javascripts/menu.js b/manager/javascripts/menu.js
deleted file mode 100644
index 57657263..00000000
--- a/manager/javascripts/menu.js
+++ /dev/null
@@ -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;
\ No newline at end of file
diff --git a/manager/javascripts/net.js b/manager/javascripts/net.js
index bde54435..2c014b16 100644
--- a/manager/javascripts/net.js
+++ b/manager/javascripts/net.js
@@ -1,18 +1,27 @@
-const portfinder = require('portfinder')
-const { logger } = require("./filesystem")
+const portfinder = require('portfinder');
+const { logger } = require('./filesystem');
/** Checks if a port is already in use
*
*/
-function checkPort(port, callback) {
- portfinder.getPort({ port: port, stopPort: port }, (err, res) => {
- if (err !== null) {
- logger.error(`Port ${port} already in use`);
- callback(false);
- } else {
- callback(true);
- }
- });
+async function checkPort(port) {
+ try{
+ await portfinder.getPortPromise({ port: port, stopPort: port });
+ return true;
+ } catch (err) {
+ logger.log(err);
+ return false;
+ }
+}
+
+async function getFreePort(startPort) {
+ try{
+ var port = await portfinder.getPortPromise({ port: startPort });
+ return port;
+ } catch (err) {
+ logger.log(err);
+ return false;
+ }
}
/** Performs a fetch request, with a configurable timeout
@@ -34,6 +43,7 @@ async function fetchWithTimeout(resource, options = {}) {
}
module.exports = {
+ getFreePort: getFreePort,
checkPort: checkPort,
fetchWithTimeout: fetchWithTimeout
}
diff --git a/manager/javascripts/passwords.js b/manager/javascripts/passwords.js
deleted file mode 100644
index fe9ed99a..00000000
--- a/manager/javascripts/passwords.js
+++ /dev/null
@@ -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;
\ No newline at end of file
diff --git a/manager/javascripts/popup.js b/manager/javascripts/popup.js
index 88c6e2b0..12a615ea 100644
--- a/manager/javascripts/popup.js
+++ b/manager/javascripts/popup.js
@@ -1,8 +1,25 @@
// TODO: we can probably refactor this to be a bit cleaner
+function showInfoPopup(message, onCloseCallback) {
+ showPopup();
+ 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");
+ showPopup();
document.getElementById("popup").querySelector(".error").classList.remove("hide");
document.getElementById("popup").querySelector(".wait").classList.add("hide");
document.getElementById("popup").querySelector(".confirm").classList.add("hide");
@@ -19,8 +36,7 @@ function showErrorPopup(message, onCloseCallback) {
}
function showWaitPopup(message) {
- document.getElementById("grayout").classList.remove("hide");
- document.getElementById("popup").classList.remove("hide");
+ showPopup();
document.getElementById("popup").querySelector(".error").classList.add("hide");
document.getElementById("popup").querySelector(".wait").classList.remove("hide");
document.getElementById("popup").querySelector(".confirm").classList.add("hide");
@@ -29,9 +45,18 @@ function showWaitPopup(message) {
document.getElementById("popup").querySelector(".content").innerHTML = message;
}
+function showWaitLoadingPopup(message) {
+ showPopup();
+ 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}` ;
+}
+
function showConfirmPopup(message, onAcceptCallback, onCloseCallback) {
- document.getElementById("grayout").classList.remove("hide");
- document.getElementById("popup").classList.remove("hide");
+ showPopup();
document.getElementById("popup").querySelector(".error").classList.add("hide");
document.getElementById("popup").querySelector(".wait").classList.add("hide");
document.getElementById("popup").querySelector(".confirm").classList.remove("hide");
@@ -55,14 +80,40 @@ function showConfirmPopup(message, onAcceptCallback, onCloseCallback) {
document.getElementById("popup").querySelector(".content").innerHTML = message;
}
+function showPopup() {
+ document.getElementById("grayout").classList.remove("hide");
+ document.getElementById("popup").classList.remove("hide");
+
+ window.setTimeout(() => {
+ document.getElementById("grayout").style.opacity = "100%";
+ document.getElementById("popup").style.opacity = "100%";
+ }, 100);
+}
+
function hidePopup() {
- document.getElementById("grayout").classList.add("hide");
- document.getElementById("popup").classList.add("hide");
+ document.getElementById("grayout").style.opacity = "0%";
+ document.getElementById("popup").style.opacity = "0%";
+
+ window.setTimeout(() => {
+ document.getElementById("grayout").classList.add("hide");
+ document.getElementById("popup").classList.add("hide");
+ }, 250);
+}
+
+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
}
\ No newline at end of file
diff --git a/manager/javascripts/preload.js b/manager/javascripts/preload.js
index 48f3b53a..3dfedad7 100644
--- a/manager/javascripts/preload.js
+++ b/manager/javascripts/preload.js
@@ -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,9 @@ 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 { sleep } = require('./utils');
const VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
logger.log(`Running in ${__dirname}`);
@@ -35,7 +35,7 @@ function checkVersion() {
/* If a newer version is available update Olympus in Release mode */
if (reg1[0] > reg2[0] || (reg1[0] == reg2[0] && reg1[1] > reg2[1]) || (reg1[0] == reg2[0] && reg1[1] == reg2[1] && reg1[2] > reg2[2])) {
logger.log(`New version available: ${res["version"]}`);
- showConfirmPopup(`You are currently running DCS Olympus ${VERSION}, but ${res["version"]} is available. Do you want to update DCS Olympus automatically? Note: DCS and Olympus MUST be stopped before proceeding.
`,
+ showConfirmPopup(`You are currently running DCS Olympus ${VERSION}, but ${res["version"]} is available.
Do you want to update DCS Olympus automatically?
Note: DCS and Olympus MUST be stopped before proceeding.
`,
() => {
updateOlympusRelease();
}, () => {
@@ -45,12 +45,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? Note: DCS and Olympus MUST be stopped before proceeding.
`,
- () => {
- updateOlympusBeta();
- }, () => {
- logger.log("Update canceled");
- })
+ updateOlympusBeta();
}
}
})
@@ -74,27 +69,37 @@ 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(".").mtime;
+ if (date1 > date2) {
+ showConfirmPopup(` Looks like you are running a beta version of Olympus!
Latest beta artifact timestamp of: ${date1.toLocaleString()}
Your installation timestamp: ${date2.toLocaleString()}
Do you want to update to the newest beta version?
`, async () => {
+ /* Nested popup calls need to wait for animation to complete */
+ await sleep(300);
+
+ /* 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(`Your installation timestamp: ${date2.toLocaleString()}
Do you want to update to the newest beta version?
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
@@ -116,7 +121,7 @@ async function updateOlympusRelease() {
}
function updateOlympus(location) {
- showWaitPopup("Please wait while Olympus is being updated. The Manager will be closed and reopened automatically when updating is completed.")
+ showWaitPopup("Please wait while Olympus is being updated.
The Manager will be closed and reopened automatically when updating is completed.
")
/* If the location is a string, it is interpreted as a download url. Else, it is interpreted as a File (on disk)*/
if (typeof location === "string") {
@@ -201,7 +206,7 @@ function extractAndCopy(folder) {
*
*/
function failUpdate() {
- showErrorPopup(`An error has occurred while updating Olympus. Please delete Olympus and update it manually. A browser window will open automatically on the download page. You can find more info in ${path.join(__dirname, "..", "manager.log")}`, () => { + showErrorPopup(`
An error has occurred while updating Olympus.
Please delete Olympus and update it manually. A browser window will open automatically on the download page.
You can find more info in ${path.join(__dirname, "..", "manager.log")}
`, () => {
exec(`start https://github.com/Pax1601/DCSOlympus/releases`, () => {
ipcRenderer.send('window:close');
})
@@ -221,8 +226,7 @@ const ipc = {
/* From main to render. */
'receive': [
'event:maximized',
- 'event:unmaximized',
- 'check-version'
+ 'event:unmaximized'
],
/* From render to main and back again. */
'sendReceive': []
@@ -257,28 +261,25 @@ 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();
-
- /* 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);
- })
- }
+ await getManager().start();
+ await checkVersion();
})
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();
})
@@ -294,8 +295,3 @@ function computePagesHeight() {
pages[i].style.height = (window.innerHeight - (titleBar.clientHeight + header.clientHeight)) + "px";
}
}
-
-ipcRenderer.on("check-version", () => {
- /* Check if a new version is available */
- checkVersion();
-})
\ No newline at end of file
diff --git a/manager/javascripts/result.js b/manager/javascripts/result.js
deleted file mode 100644
index d55bb1b1..00000000
--- a/manager/javascripts/result.js
+++ /dev/null
@@ -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;
\ No newline at end of file
diff --git a/manager/javascripts/utils.js b/manager/javascripts/utils.js
new file mode 100644
index 00000000..713f13eb
--- /dev/null
+++ b/manager/javascripts/utils.js
@@ -0,0 +1,10 @@
+
+
+async function sleep(ms) {
+ await new Promise(r => setTimeout(r, ms));
+ return true;
+}
+
+module.exports = {
+ sleep: sleep
+}
\ No newline at end of file
diff --git a/manager/javascripts/wizardpage.js b/manager/javascripts/wizardpage.js
new file mode 100644
index 00000000..eeef41c0
--- /dev/null
+++ b/manager/javascripts/wizardpage.js
@@ -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;
\ No newline at end of file
diff --git a/manager/main.js b/manager/main.js
index 96f72972..4319a3ff 100644
--- a/manager/main.js
+++ b/manager/main.js
@@ -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,
@@ -38,7 +38,6 @@ function createWindow() {
electronApp.on('ready', () => {
window = createWindow();
- window.webContents.send('check-version')
});
electronApp.on('window-all-closed', () => {
diff --git a/manager/package.json b/manager/package.json
index ec4edcd1..45b8f75c 100644
--- a/manager/package.json
+++ b/manager/package.json
@@ -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"
}
-}
\ No newline at end of file
+}
diff --git a/manager/scripts/copy-package.bat b/manager/scripts/copy-package.bat
index 3da6aab3..a79e1627 100644
--- a/manager/scripts/copy-package.bat
+++ b/manager/scripts/copy-package.bat
@@ -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
\ No newline at end of file
diff --git a/manager/stylesheets/style.css b/manager/stylesheets/style.css
index b83e9deb..322baebc 100644
--- a/manager/stylesheets/style.css
+++ b/manager/stylesheets/style.css
@@ -3,17 +3,26 @@
--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;
+ --very-large: 18px;
+ --large: 16px;
+ --big: 15px;
+ --normal: 13px;
+ --small: 12px;
}
-* {
+* {
font-family: "Open Sans", sans-serif;
box-sizing: border-box;
}
@@ -31,12 +40,15 @@ body {
overflow-x: auto;
}
+/************************************************/
+/* Title bar */
+/************************************************/
#title-bar {
content: " ";
display: block;
-webkit-user-select: none;
-webkit-app-region: drag;
- height: 20px;
+ height: 30px;
width: 100%;
display: flex;
justify-content: end;
@@ -48,7 +60,7 @@ body {
#title-bar>*:first-child {
margin-right: auto;
color: #F2F2F2AA;
- font-size: 12px;
+ font-size: var(--small);
}
.title-bar-button {
@@ -85,13 +97,16 @@ body {
-webkit-app-region: no-drag;
}
+/************************************************/
+/* Header */
+/************************************************/
#header {
display: flex;
justify-content: start;
align-items: center;
color: #F2F2F2;
font-weight: bold;
- font-size: 16px;
+ font-size: var(--big);
padding: 20px 20px 20px 20px;
column-gap: 10px;
background-color: var(--background-dark);
@@ -100,7 +115,7 @@ body {
-webkit-app-region: drag;
}
-#header .link{
+#header .link {
-webkit-user-select: text;
-webkit-app-region: no-drag;
}
@@ -120,6 +135,7 @@ body {
font-weight: normal;
text-decoration: underline;
cursor: pointer;
+ font-size: var(--big);
}
.link.first {
@@ -139,17 +155,40 @@ body {
height: 60px;
}
+/************************************************/
+/* Loader */
+/************************************************/
#loader {
color: var(--offwhite);
- font-size: 20px;
+ font-size: var(--large);
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.25s linear;
+}
+
+/************************************************/
+/* Scrollbar */
+/************************************************/
::-webkit-scrollbar {
width: 10px;
height: 10px;
@@ -169,130 +208,52 @@ body {
opacity: 0.8;
}
-.accent-red {
- color: var(--red);
-}
-
-.accent-green {
- color: var(--green);
-}
-
-.page-header {
- font-size: 18px;
- font-weight: 600;
- color: var(--offwhite);
- border-bottom: 1px solid var(--offwhite);
- padding-bottom: 15px;
- 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;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- display: flex;
- align-items: center;
- column-gap: 10px;
-}
-
-.next {
- color: var(--background);
- background-color: var(--offwhite);
-}
-
-.back {
- color: var(--offwhite);
- background-color: var(--background);
- border: 1px solid var(--offwhite);
-}
-
-.cancel {
- padding: 10px 5px;
- color: var(--offwhite);
- text-decoration: underline;
-}
-
-.close-popup {
- color: var(--offwhite);
- background-color: var(--blue);
-}
-
-.accept-popup {
- color: var(--background);
- background-color: var(--offwhite);
-}
-
-input {
- outline: none;
- font-weight: 600;
- color: var(--background);
- font-size: 13px;
- padding: 3px 10px;
- border-radius: 5px;
- text-align: center;
- width: 300px;
-}
-
-.hide {
- display: none !important;
+/************************************************/
+/* Manager page */
+/************************************************/
+.manager-page {
+ position: absolute;
+ min-width: 1200px;
+ overflow-y: auto;
+ transition: opacity 0.25s linear;
+ opacity: 0%;
+ /* By default has 0% opacity to allow for fade transition */
}
+/************************************************/
+/* Popup */
+/************************************************/
#grayout {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
- background-color: black;
- opacity: 30%;
+ background-color: rgba(0, 0, 0, 0.30);
z-index: 999;
+ transition: opacity 0.25s linear;
+ opacity: 0%;
+ /* By default has 0% opacity to allow for fade transition */
}
#popup {
- width: 400px;
+ width: 600px;
height: fit-content;
min-height: 200px;
position: absolute;
background-color: var(--background);
border-radius: 5px;
- left: calc(50% - 200px);
+ left: calc(50% - 300px);
top: calc(50% - 100px);
display: flex;
flex-direction: column;
justify-content: space-between;
- padding: 20px;
+ padding: 20px 40px;
align-items: start;
z-index: 999;
+ transition: opacity 0.25s linear;
+ opacity: 0%;
+ /* By default has 0% opacity to allow for fade transition */
}
#popup img {
@@ -306,13 +267,16 @@ input {
#popup .content {
color: var(--offwhite);
- font-size: 13px;
+ font-size: var(--normal);
font-weight: 600;
width: 100%;
text-align: left;
padding: 15px 0px !important;
word-wrap: break-word;
overflow-wrap: anywhere;
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
}
#popup .footer {
@@ -323,109 +287,58 @@ input {
column-gap: 10px;
}
-.manager-page {
- min-width: 1200px;
- 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;
+.close-popup {
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;
+ background-color: transparent;
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 {
+.accept-popup {
+ color: var(--background);
background-color: var(--offwhite);
}
-.step-summary div.empty:before {
- background-color: var(--background);
+#popup .main-message {
+ font-size: var(--large);
+ max-width: 100%;
}
-.step-summary div.blue:before {
- border: 1px solid var(--blue);
- background-color: var(--blue);
+#popup .sub-message {
+ font-weight: normal;
}
-.content {
- height: 100%;
- width: 100%;
- display: flex;
- flex-direction: column;
- row-gap: 20px;
- align-items: center;
- justify-content: center;
- padding: 20px;
+/************************************************/
+/* Inputs */
+/************************************************/
+input {
+ outline: none;
+ font-weight: 600;
+ color: var(--background);
+ font-size: var(--normal);
+ padding: 3px 10px;
+ border-radius: 5px;
+ text-align: left;
+ width: 300px;
}
-.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;
}
.input-group>span:nth-child(1) {
- font-size: 14px;
+ font-size: var(--normal);
font-weight: 600;
}
.input-group>span:nth-child(2) {
- font-size: 13px;
+ font-size: var(--normal);
font-weight: normal;
}
@@ -443,33 +356,45 @@ input {
flex-wrap: wrap;
}
-.instructions {
- margin-bottom: 10px;
+.button {
+ padding: 10px 15px;
+ border-radius: 5px;
+ font-size: var(--normal);
+ font-weight: 600;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ column-gap: 10px;
}
-.buttons-footer {
- margin-top: 10px;
+.button.radio {
+ border: 1px solid var(--offwhite);
+ color: var(--offwhite);
}
-.divider {
- border-top: 0px solid transparent !important;
- border-bottom: 1px solid var(--offwhite) !important;
- opacity: 80%;
- height: 0px !important;
- cursor: default;
+.button.radio.selected {
+ background-color: var(--offwhite);
+ color: var(--background);
}
-@keyframes rotate {
- 0% {
- transform: rotate(0deg)
- }
+.button.radio::before {
+ content: "";
+ display: block;
+ width: 10px;
+ height: 10px;
+ border: 1px solid var(--offwhite);
+ border-radius: 999px;
+}
- 100% {
- transform: rotate(360deg)
- }
+.button.radio.selected::before {
+ background-color: var(--offwhite);
+ border: 4px solid var(--background);
+ width: 4px;
+ height: 4px;
}
.button.collapse {
+ position: relative;
display: flex;
justify-content: space-between;
}
@@ -492,7 +417,8 @@ input {
.button.collapse>div {
display: none;
position: absolute;
- transform: translate(-15px, calc(50% + 20px));
+ transform: translate(-15px, calc(50% + 25px));
+ z-index: 999;
}
.button.collapse.open>div {
@@ -522,3 +448,399 @@ input {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
+
+.buttons-footer {
+ display: flex;
+ column-gap: 10px;
+ justify-content: start;
+}
+
+.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);
+}
+
+/************************************************/
+/* Port checks */
+/************************************************/
+
+.port-input .success,
+.port-input .error {
+ position: absolute;
+ left: 320px;
+ display: flex;
+ width: 150px;
+ column-gap: 8px;
+}
+
+.port-input .success {
+ content: url("../icons/check-solid-green.svg");
+ height: 20px;
+ width: 20px;
+}
+
+.port-input .error img {
+ content: url("../icons/triangle-exclamation-solid.svg");
+ height: 20px;
+ width: 20px;
+}
+
+.port-input .error span {
+ font-weight: 600;
+ font-size: var(--small);
+ color: var(--red);
+ height: fit-content;
+}
+
+/************************************************/
+/* Dashboard */
+/************************************************/
+
+.dashboard {
+ display: flex;
+ flex-direction: column;
+ row-gap: 15px;
+ height: 100%;
+ padding: 40px 80px;
+}
+
+.dashboard .scroll-container {
+ overflow-y: auto;
+ max-width: 100% !important;
+ width: 100%;
+ height: 100%;
+}
+
+.dashboard .scrollable {
+ display: flex;
+ row-gap: 15px;
+ column-gap: 15px;
+ height: fit-content;
+ width: 100%;
+ flex-wrap: wrap;
+}
+
+.dashboard .instructions {
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+}
+
+.dashboard .instructions .title {
+ color: var(--offwhite);
+ font-size: var(--very-large);
+ font-weight: 600;
+}
+
+.dashboard .instructions .subtitle {
+ color: var(--lightgray);
+ font-size: var(--normal);
+}
+
+.dashboard .content {
+ height: 100%;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ display: flex;
+ flex-direction: column;
+ row-gap: 15px;
+}
+
+.dashboard .option {
+ background-color: var(--darkgray);
+ width: 48%;
+ color: white;
+ display: flex;
+ font-size: var(--normal);
+ font-weight: 600;
+ padding: 15px;
+ align-items: center;
+ border-radius: 5px;
+ border-left: 5px solid var(--blue);
+ flex-direction: column;
+ row-gap: 25px;
+ position: relative;
+}
+
+.dashboard .option:not(.installed) {
+ background-color: var(--background-disabled);
+}
+
+.dashboard .option:not(.installed) .info {
+ opacity: 50%;
+}
+
+.dashboard .option:not(.installed) .server-data {
+ opacity: 50%;
+}
+
+.dashboard .server-data {
+ display: flex;
+ column-gap: 15px;
+ row-gap: 5px;
+ flex-wrap: wrap;
+}
+
+.dashboard .server-status {
+ font-weight: 600;
+ font-size: var(--normal);
+ display: flex;
+ column-gap: 5px;
+ align-items: center;
+}
+
+.dashboard .server-status::before {
+ display: block;
+ content: "";
+ width: 15px;
+ height: 15px;
+ border-radius: 999px;
+ background-color: var(--gray);
+}
+
+.dashboard .server-status.offline {
+ color: var(--gray)
+}
+
+.dashboard .server-status.offline::before {
+ background-color: var(--gray);
+}
+
+.dashboard .server-status.online {
+ color: var(--green)
+}
+
+.dashboard .server-status.online::before {
+ background-color: var(--green);
+}
+
+.dashboard .server-status.backend {
+ margin-left: auto;
+}
+
+.dashboard .server-data-entry {
+ display: flex;
+ column-gap: 5px;
+ align-items: center;
+}
+
+.dashboard .server-data-entry span:nth-child(2) {
+ font-weight: 600;
+}
+
+.dashboard .server-data-entry span:nth-child(3) {
+ font-weight: normal;
+}
+
+.dashboard .instance-info {
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+ width: 100%;
+}
+
+.dashboard .instance-info>.name {
+ font-size: var(--large);
+ font-weight: 600;
+}
+
+.dashboard .instance-info>.folder {
+ font-size: var(--normal);
+ font-weight: normal;
+ color: var(--lightgray);
+}
+
+.dashboard .instance-info>.status {
+ font-size: var(--normal);
+ font-weight: 600;
+ color: var(--lightgray);
+ display: flex;
+ flex-direction: row;
+ column-gap: 8px;
+}
+
+.dashboard .instance-info>.status.installed {
+ font-weight: 600;
+ color: var(--green);
+}
+
+.dashboard .instance-info>.status.installed::before {
+ content: url("../icons/check-solid-green.svg");
+ height: 14px;
+ width: 14px;
+}
+
+.dashboard .instance-info>.status.error {
+ font-weight: 600;
+ color: orange;
+}
+
+.dashboard .instance-info>.status.error::before {
+ content: url("../icons/triangle-exclamation-solid-orange.svg");
+ height: 14px;
+ width: 14px;
+}
+
+.dashboard .instance-buttons {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ justify-content: space-between;
+ column-gap: 10px;
+}
+
+.dashboard .instance-info .info {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+.dashboard .instance-info .info>div:nth-child(1) {
+ font-weight: 600;
+ font-size: var(--normal);
+}
+
+.dashboard .instance-info .info>div:nth-child(2) {
+ font-weight: normal;
+ font-size: var(--normal);
+}
+
+.dashboard .instance-info .divider {
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+.dashboard .start,
+.dashboard .open-browser {
+ margin-right: auto;
+ color: var(--offwhite);
+ background-color: var(--blue);
+}
+
+.dashboard .start {
+ width: 160px;
+}
+
+.dashboard .start>div {
+ width: 160px;
+}
+
+.dashboard .edit,
+.dashboard .install,
+.dashboard .uninstall,
+.dashboard .stop {
+ color: var(--offwhite);
+ background-color: transparent;
+ border: 1px solid var(--offwhite);
+}
+
+.dashboard .edit:hover,
+.dashboard .install:hover,
+.dashboard .uninstall:hover,
+.dashboard .stop:hover {
+ color: var(--background);
+ background-color: var(--offwhite);
+}
+
+.dashboard .install {
+ margin-left: auto;
+}
+
+.dashboard .summary {
+ display: flex;
+ flex-direction: column;
+ row-gap: 5px;
+}
+
+.dashboard .logs-link {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.dashboard .divider {
+ border-top: 0px solid transparent !important;
+ border-bottom: 1px solid var(--offwhite) !important;
+ opacity: 80%;
+ height: 0px !important;
+ cursor: default;
+}
+
+/************************************************/
+/* Result summary */
+/************************************************/
+.result-summary {
+ padding: 25px 15px;
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+}
+
+.result-summary .title {
+ font-weight: bold;
+ font-size: var(--big);
+ display: flex;
+ align-items: center;
+}
+
+.result-summary .title img {
+ margin-right: 10px;
+}
+
+.result-summary .description {
+ font-size: var(--normal);
+}
+
+.result-summary.success{
+ color: var(--background-color);
+ background-color: var(--green);
+}
+
+.result-summary.error{
+ color: var(--background-color);
+ background-color: var(--red);
+}
+
+/************************************************/
+/* Misc */
+/************************************************/
+.accent-red {
+ color: var(--red);
+}
+
+.accent-green {
+ color: var(--green);
+}
+
+.hide {
+ display: none !important;
+}
+
+/************************************************/
+/* Animations */
+/************************************************/
+@keyframes rotate {
+ 0% {
+ transform: rotate(0deg)
+ }
+
+ 100% {
+ transform: rotate(360deg)
+ }
+}
\ No newline at end of file
You can find more info in ${path.join(__dirname, "..", "manager.log")}