From 386d5298a23f89cdd52f372d540e69907a5f1682 Mon Sep 17 00:00:00 2001 From: Davide Passoni Date: Mon, 10 Mar 2025 17:16:02 +0100 Subject: [PATCH] feat: added admin password and admin modal --- frontend/react/src/constants/constants.ts | 4 +- frontend/react/src/events.ts | 17 + frontend/react/src/olympusapp.ts | 9 +- frontend/react/src/ui/modals/adminmodal.tsx | 337 ++++++++++++++++++ frontend/react/src/ui/modals/warningmodal.tsx | 9 + frontend/react/src/ui/panels/drawingmenu.tsx | 6 +- frontend/react/src/ui/panels/optionsmenu.tsx | 127 ++++--- frontend/react/src/ui/ui.tsx | 2 + frontend/server/package.json | 3 +- frontend/server/src/app.ts | 11 +- frontend/server/src/routes/admin.ts | 100 ++++++ manager/ejs/expertsettings.ejs | 12 +- manager/ejs/passwords.ejs | 32 +- manager/javascripts/dcsinstance.js | 21 +- manager/javascripts/filesystem.js | 3 + manager/javascripts/manager.js | 13 +- 16 files changed, 645 insertions(+), 61 deletions(-) create mode 100644 frontend/react/src/ui/modals/adminmodal.tsx create mode 100644 frontend/server/src/routes/admin.ts diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts index 36c2dd8b..e9b8cbde 100644 --- a/frontend/react/src/constants/constants.ts +++ b/frontend/react/src/constants/constants.ts @@ -328,7 +328,8 @@ export enum OlympusState { WARNING = "Warning modal", DATABASE_EDITOR = "Database editor", MEASURE = "Measure", - TRAINING = "Training" + TRAINING = "Training", + ADMIN = "Admin", } export const NO_SUBSTATE = "No substate"; @@ -384,6 +385,7 @@ export enum WarningSubstate { NO_SUBSTATE = "No substate", NOT_CHROME = "Not chrome", NOT_SECURE = "Not secure", + ERROR_UPLOADING_CONFIG = "Error uploading config" } export type OlympusSubState = DrawSubState | JTACSubState | SpawnSubState | OptionsSubstate | string; diff --git a/frontend/react/src/events.ts b/frontend/react/src/events.ts index cb989c05..aecae557 100644 --- a/frontend/react/src/events.ts +++ b/frontend/react/src/events.ts @@ -230,6 +230,23 @@ export class SessionDataChangedEvent { export class SessionDataSavedEvent extends SessionDataChangedEvent {} export class SessionDataLoadedEvent extends SessionDataChangedEvent {} +export class AdminPasswordChangedEvent { + static on(callback: (password: string) => void, singleShot = false) { + document.addEventListener( + this.name, + (ev: CustomEventInit) => { + callback(ev.detail.password); + }, + { once: singleShot } + ); + } + + static dispatch(password: string) { + document.dispatchEvent(new CustomEvent(this.name, { detail: { password } })); + console.log(`Event ${this.name} dispatched`); + } +} + /************** Map events ***************/ export class MouseMovedEvent { static on(callback: (latlng: LatLng, elevation: number) => void, singleShot = false) { diff --git a/frontend/react/src/olympusapp.ts b/frontend/react/src/olympusapp.ts index ed0ce16b..52e4140b 100644 --- a/frontend/react/src/olympusapp.ts +++ b/frontend/react/src/olympusapp.ts @@ -21,7 +21,7 @@ import { ServerManager } from "./server/servermanager"; import { AudioManager } from "./audio/audiomanager"; import { GAME_MASTER, LoginSubState, NO_SUBSTATE, OlympusState, OlympusSubState, WarningSubstate } from "./constants/constants"; -import { AppStateChangedEvent, ConfigLoadedEvent, InfoPopupEvent, MapOptionsChangedEvent, SelectedUnitsChangedEvent, ShortcutsChangedEvent } from "./events"; +import { AdminPasswordChangedEvent, AppStateChangedEvent, ConfigLoadedEvent, InfoPopupEvent, MapOptionsChangedEvent, SelectedUnitsChangedEvent, ShortcutsChangedEvent } from "./events"; import { OlympusConfig } from "./interfaces"; import { SessionDataManager } from "./sessiondata"; import { ControllerManager } from "./controllers/controllermanager"; @@ -57,6 +57,8 @@ export class OlympusApp { #drawingsManager: DrawingsManager; //#pluginsManager: // TODO + #adminPassword: string = ""; + constructor() { SelectedUnitsChangedEvent.on((selectedUnits) => { if (selectedUnits.length > 0) this.setState(OlympusState.UNIT_CONTROL); @@ -348,6 +350,11 @@ export class OlympusApp { }, 5000); } + setAdminPassword(newAdminPassword: string) { + this.#adminPassword = newAdminPassword; + AdminPasswordChangedEvent.dispatch(newAdminPassword); + } + startServerMode() { //ConfigLoadedEvent.on((config) => { // this.getAudioManager().start(); diff --git a/frontend/react/src/ui/modals/adminmodal.tsx b/frontend/react/src/ui/modals/adminmodal.tsx new file mode 100644 index 00000000..1f6bf47c --- /dev/null +++ b/frontend/react/src/ui/modals/adminmodal.tsx @@ -0,0 +1,337 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { Modal } from "./components/modal"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import { getApp } from "../../olympusapp"; +import { OlympusState, WarningSubstate } from "../../constants/constants"; +import { FaPlus, FaTrash } from "react-icons/fa"; +import { sha256 } from "js-sha256"; +import { AdminPasswordChangedEvent } from "../../events"; +import { OlDropdown } from "../components/oldropdown"; +import { OlCheckbox } from "../components/olcheckbox"; + +export function AdminModal(props: { open: boolean }) { + const [configs, setConfigs] = useState({} as { groups: { [key: string]: string[] }; users: { [key: string]: { password: string; roles: string[] } } }); + const [newUserName, setNewUserName] = useState(""); + const [newGroupName, setNewGroupName] = useState(""); + const [adminPassword, setAdminPassword] = useState(""); + + useEffect(() => { + AdminPasswordChangedEvent.on((password) => { + setAdminPassword(password); + + var hash = sha256.create(); + + const requestOptions: RequestInit = { + method: "GET", // Specify the request method + headers: { + Authorization: "Basic " + btoa(`Admin:${hash.update(password).hex()}`), + }, // Specify the content type + }; + + fetch(`./admin/config`, requestOptions) + .then((response) => { + if (response.status === 200) { + console.log(`Admin password correct`); + return response.json(); + } else { + throw new Error("Admin password incorrect"); + } + }) + .then((data) => { + setConfigs(data); + }) + .catch((error) => { + console.error(`Error reading configuration: ${error}`); + }); + }); + }, []); + + const uploadNewConfig = useCallback(() => { + var hash = sha256.create(); + + const requestOptions: RequestInit = { + method: "PUT", // Specify the request method + headers: { + Authorization: "Basic " + btoa(`Admin:${hash.update(adminPassword).hex()}`), + "Content-Type": "application/json", + }, // Specify the content type + body: JSON.stringify(configs), + }; + + fetch(`./admin/config`, requestOptions) + .then((response) => { + if (response.status === 200) { + console.log(`Configuration uploaded`); + } else { + throw new Error("Error uploading configuration"); + } + }) + .catch((error) => { + getApp().setState(OlympusState.WARNING, WarningSubstate.ERROR_UPLOADING_CONFIG); + console.error(`Error uploading configuration: ${error}`); + }); + }, [adminPassword, configs]); + + + return ( + +
+
+
Groups:
+
+ {configs.groups && + Object.keys(configs.groups).map((group: any) => { + return ( +
+
{group}
+ + {["Game master", "Blue commander", "Red commander"].map((role: any) => { + return ( +
+ { + if (ev.target.checked) { + configs["groups"][group].push(role); + } else { + configs["groups"][group] = configs["groups"][group].filter((r: any) => r !== role); + } + setConfigs({ ...configs }); + }} + > + {role} +
+ ); + })} +
+
{ + delete configs["users"][group]; + }} + > + +
+
+ ); + })} + {(configs.groups === undefined || Object.keys(configs.groups).length === 0) && ( +
+ No groups defined +
+ )} +
+
+ { + setNewUserName(ev.currentTarget.value); + }} + className={` + rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm + text-gray-900 + dark:border-gray-600 dark:bg-gray-700 dark:text-white + dark:placeholder-gray-400 dark:focus:border-blue-500 + dark:focus:ring-blue-500 + focus:border-blue-500 focus:ring-blue-500 + `} + placeholder="New group name" + value={newGroupName} + required + /> +
{ + if (newGroupName === "") return; + configs["groups"][newGroupName] = []; + setConfigs({ ...configs }); + setNewGroupName(""); + }} + > + +
+
+
+
+
Users:
+
+ {configs.users && + Object.keys(configs.users).map((user: any) => { + return ( +
+
{user}
+ + + {["Game master", "Blue commander", "Red commander"].map((role: any) => { + return ( +
+ { + if (ev.target.checked) { + configs["users"][user].roles.push(role); + } else { + configs["users"][user].roles = configs["users"][user].roles.filter((r: any) => r !== role); + } + setConfigs({ ...configs }); + }} + > + {role} +
+ ); + })} +
+ + { + var hash = sha256.create(); + configs["users"][user].password = hash.update(ev.currentTarget.value).hex(); + setConfigs({ ...configs }); + }} + className={` + max-w-44 rounded-lg border border-gray-300 bg-gray-50 + p-2.5 text-sm text-gray-900 + dark:border-gray-600 dark:bg-gray-700 dark:text-white + dark:placeholder-gray-400 dark:focus:border-blue-500 + dark:focus:ring-blue-500 + focus:border-blue-500 focus:ring-blue-500 + `} + placeholder="Change password" + required + /> +
{ + delete configs["users"][user]; + setConfigs({ ...configs }); + }} + > + +
+
+ ); + })} + {(configs.users === undefined || Object.keys(configs.users).length === 0) && ( +
+ No users defined +
+ )} +
+
+ { + setNewUserName(ev.currentTarget.value); + }} + className={` + rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm + text-gray-900 + dark:border-gray-600 dark:bg-gray-700 dark:text-white + dark:placeholder-gray-400 dark:focus:border-blue-500 + dark:focus:ring-blue-500 + focus:border-blue-500 focus:ring-blue-500 + `} + placeholder="New user name" + value={newUserName} + required + /> +
{ + if (newUserName === "") return; + configs["users"][newUserName] = { password: "", roles: [] }; + setConfigs({ ...configs }); + setNewUserName(""); + }} + > + +
+
+
+
+
+
+
Reset all user preferences, use with caution
+
+ +
+
+ + +
+
+ ); +} diff --git a/frontend/react/src/ui/modals/warningmodal.tsx b/frontend/react/src/ui/modals/warningmodal.tsx index 99fc3f58..2e3c349c 100644 --- a/frontend/react/src/ui/modals/warningmodal.tsx +++ b/frontend/react/src/ui/modals/warningmodal.tsx @@ -73,6 +73,15 @@ export function WarningModal(props: { open: boolean }) { ); break; + case WarningSubstate.ERROR_UPLOADING_CONFIG: + warningText = ( +
+ An error has occurred uploading the admin configuration. + + +
+ ); + break; default: break; } diff --git a/frontend/react/src/ui/panels/drawingmenu.tsx b/frontend/react/src/ui/panels/drawingmenu.tsx index f061e168..d3ea79ba 100644 --- a/frontend/react/src/ui/panels/drawingmenu.tsx +++ b/frontend/react/src/ui/panels/drawingmenu.tsx @@ -302,7 +302,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { > Automatic IADS generation - + {types.map((type, idx) => { if (!(type in typesSelection)) { typesSelection[type] = true; @@ -323,7 +323,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { ); })} - + {eras.map((era) => { if (!(era in erasSelection)) { erasSelection[era] = true; @@ -344,7 +344,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { ); })} - + {["Short range", "Medium range", "Long range"].map((range) => { if (!(range in rangesSelection)) { rangesSelection[range] = true; diff --git a/frontend/react/src/ui/panels/optionsmenu.tsx b/frontend/react/src/ui/panels/optionsmenu.tsx index e98df899..53946a1c 100644 --- a/frontend/react/src/ui/panels/optionsmenu.tsx +++ b/frontend/react/src/ui/panels/optionsmenu.tsx @@ -9,15 +9,18 @@ import { BindShortcutRequestEvent, MapOptionsChangedEvent, ShortcutsChangedEvent import { OlAccordion } from "../components/olaccordion"; import { Shortcut } from "../../shortcut/shortcut"; import { OlSearchBar } from "../components/olsearchbar"; -import { FaTrash, FaXmark } from "react-icons/fa6"; +import { FaTrash, FaUserGroup, FaXmark } from "react-icons/fa6"; import { OlCoalitionToggle } from "../components/olcoalitiontoggle"; -import { FaQuestionCircle } from "react-icons/fa"; +import { FaCog, FaKey, FaPlus, FaQuestionCircle } from "react-icons/fa"; +import { sha256 } from "js-sha256"; +import { OlDropdown, OlDropdownItem } from "../components/oldropdown"; const enum Accordion { NONE, BINDINGS, MAP_OPTIONS, CAMERA_PLUGIN, + ADMIN, } export function OptionsMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) { @@ -25,6 +28,31 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre const [shortcuts, setShortcuts] = useState({} as { [key: string]: Shortcut }); const [openAccordion, setOpenAccordion] = useState(Accordion.NONE); const [filterString, setFilterString] = useState(""); + const [admin, setAdmin] = useState(false); + const [password, setPassword] = useState(""); + + const checkPassword = (password: string) => { + var hash = sha256.create(); + + const requestOptions: RequestInit = { + method: "GET", // Specify the request method + headers: { + Authorization: "Basic " + btoa(`Admin:${hash.update(password).hex()}`), + }, // Specify the content type + }; + + fetch(`./admin/config`, requestOptions) + .then((response) => { + if (response.status === 200) { + console.log(`Admin password correct`); + getApp().setAdminPassword(password); + getApp().setState(OlympusState.ADMIN) + return response.json(); + } else { + throw new Error("Admin password incorrect"); + } + }) + }; useEffect(() => { MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions })); @@ -186,18 +214,14 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre }} >
-
- {}} coalition={mapOptions.AWACSCoalition} /> - Coalition of unit bullseye info -
-
- {" "} -
- Change the coalition of the bullseye to use to provide bullseye information in the unit tooltip. +
+ {}} coalition={mapOptions.AWACSCoalition} /> + Coalition of unit bullseye info +
+
+ {" "} +
Change the coalition of the bullseye to use to provide bullseye information in the unit tooltip.
-
@@ -207,12 +231,6 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre open={openAccordion === Accordion.CAMERA_PLUGIN} title="Camera plugin options" > -
void; childre
-
+ + +
+
+ + { + setPassword(ev.currentTarget.value); + }} + className={` + max-w-44 rounded-lg border border-gray-300 bg-gray-50 p-2.5 + text-sm text-gray-900 + dark:border-gray-600 dark:bg-gray-700 dark:text-white + dark:placeholder-gray-400 dark:focus:border-blue-500 + dark:focus:ring-blue-500 + focus:border-blue-500 focus:ring-blue-500 + `} + placeholder="Enter password" + required + /> +
-
diff --git a/frontend/react/src/ui/ui.tsx b/frontend/react/src/ui/ui.tsx index 21674db1..3f95e09d 100644 --- a/frontend/react/src/ui/ui.tsx +++ b/frontend/react/src/ui/ui.tsx @@ -32,6 +32,7 @@ import { ServerOverlay } from "./serveroverlay"; import { ImportExportModal } from "./modals/importexportmodal"; import { WarningModal } from "./modals/warningmodal"; import { TrainingModal } from "./modals/trainingmodal"; +import { AdminModal } from "./modals/adminmodal"; export function UI() { const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED); @@ -75,6 +76,7 @@ export function UI() { + )} diff --git a/frontend/server/package.json b/frontend/server/package.json index af228941..0c1daaf5 100644 --- a/frontend/server/package.json +++ b/frontend/server/package.json @@ -4,7 +4,8 @@ "version": "{{OLYMPUS_VERSION_NUMBER}}", "scripts": { "build-release": "call ./scripts/build-release.bat", - "server": "electron . --server", + "server-electron": "electron . --server", + "server": "node ./build/www.js", "client": "electron .", "tsc": "tsc" }, diff --git a/frontend/server/src/app.ts b/frontend/server/src/app.ts index eab4d934..b74b57e9 100644 --- a/frontend/server/src/app.ts +++ b/frontend/server/src/app.ts @@ -51,6 +51,7 @@ module.exports = function (configLocation, viteProxy) { /* Config specific routers */ const elevationRouter = require("./routes/api/elevation")(configLocation); const resourcesRouter = require("./routes/resources")(configLocation); + const adminRouter = require("./routes/admin")(configLocation); /* Default routers */ const airbasesRouter = require("./routes/api/airbases"); @@ -113,6 +114,9 @@ module.exports = function (configLocation, viteProxy) { "Blue commander": config["authentication"]["blueCommanderPassword"], "Red commander": config["authentication"]["redCommanderPassword"], }; + if (config["authentication"]["adminPassword"]) { + defaultUsers["Admin"] = config["authentication"]["adminPassword"]; + } let users = {}; Object.keys(usersConfig).forEach( (user) => (users[user] = usersConfig[user].password) @@ -122,7 +126,9 @@ module.exports = function (configLocation, viteProxy) { }); /* Define middleware */ - app.use(logger("dev")); + app.use(logger('dev', { + skip: function (req, res) { return res.statusCode < 400 } + })); /* Authorization middleware */ if ( @@ -238,6 +244,9 @@ module.exports = function (configLocation, viteProxy) { app.use("/api/speech", speechRouter); app.use("/resources", resourcesRouter); + app.use("/admin", auth); + app.use("/admin", adminRouter); + /* Set default index */ if (viteProxy) { app.use( diff --git a/frontend/server/src/routes/admin.ts b/frontend/server/src/routes/admin.ts new file mode 100644 index 00000000..d39a6a99 --- /dev/null +++ b/frontend/server/src/routes/admin.ts @@ -0,0 +1,100 @@ +import express = require("express"); +import fs = require("fs"); +import path = require("path"); + +const router = express.Router(); + +module.exports = function (configLocation) { + router.get("/config", function (req, res, next) { + if (req.auth?.user === "Admin") { + /* Read the users configuration file */ + let usersConfig = {}; + if ( + fs.existsSync( + path.join(path.dirname(configLocation), "olympusUsers.json") + ) + ) { + let rawdata = fs.readFileSync( + path.join(path.dirname(configLocation), "olympusUsers.json"), + { encoding: "utf-8" } + ); + usersConfig = JSON.parse(rawdata); + } + + /* Read the groups configuration file */ + let groupsConfig = {}; + if ( + fs.existsSync( + path.join(path.dirname(configLocation), "olympusGroups.json") + ) + ) { + let rawdata = fs.readFileSync( + path.join(path.dirname(configLocation), "olympusGroups.json"), + { encoding: "utf-8" } + ); + groupsConfig = JSON.parse(rawdata); + } + + res.send({ users: usersConfig, groups: groupsConfig }); + res.end(); + } else { + res.sendStatus(401); + } + }); + + router.put("/config", function (req, res, next) { + if (req.auth?.user === "Admin") { + /* Create a backup folder for the configuration files */ + let backupFolder = path.join(path.dirname(configLocation), "Olympus Configs Backup"); + if (!fs.existsSync(backupFolder)) { + fs.mkdirSync(backupFolder); + } + + /* Make a backup of the existing files */ + let timestamp = new Date().toISOString().replace(/:/g, "-"); + fs.copyFileSync( + path.join(path.dirname(configLocation), "olympusUsers.json"), + path.join( + path.dirname(configLocation), + "Olympus Configs Backup", + "olympusUsers.json." + timestamp + ) + ); + fs.copyFileSync( + path.join(path.dirname(configLocation), "olympusGroups.json"), + path.join( + path.dirname(configLocation), + "Olympus Configs Backup", + "olympusGroups.json." + timestamp + ) + ); + + /* Save the users configuration file */ + let usersConfig = req.body.users; + + if (usersConfig) { + fs.writeFileSync( + path.join(path.dirname(configLocation), "olympusUsers.json"), + JSON.stringify(usersConfig, null, 2) + ); + } + + /* Save the groups configuration file */ + let groupsConfig = req.body.groups; + + if (groupsConfig) { + fs.writeFileSync( + path.join(path.dirname(configLocation), "olympusGroups.json"), + JSON.stringify(groupsConfig, null, 2) + ); + } + + res.send({ users: usersConfig, groups: groupsConfig }); + res.end(); + } else { + res.sendStatus(401); + } + }); + + return router; +}; diff --git a/manager/ejs/expertsettings.ejs b/manager/ejs/expertsettings.ejs index f6a55080..48679ac2 100644 --- a/manager/ejs/expertsettings.ejs +++ b/manager/ejs/expertsettings.ejs @@ -19,19 +19,25 @@ Game Master Password - "> + ">
Blue Commander Password - "> + ">
Red Commander Password - "> + "> +
+
+ Admin Password + + ">
" style="color: var(--offwhite); font-size: var(--normal); color: var(--lightgray);"> Note: to keep the old passwords, click Next without editing any value. diff --git a/manager/ejs/passwords.ejs b/manager/ejs/passwords.ejs index d5dcc90b..15c8bd5b 100644 --- a/manager/ejs/passwords.ejs +++ b/manager/ejs/passwords.ejs @@ -1,5 +1,19 @@
@@ -15,33 +29,43 @@
+
Game Master Password - "> + ">
Blue Commander Password - "> + ">
Red Commander Password - "> + ">
Note: to keep the old passwords, click Next without editing any value.
-
Autoconnect when local +
Autoconnect when local
+
+
+
+ Admin Password + + "> +
+
diff --git a/manager/javascripts/dcsinstance.js b/manager/javascripts/dcsinstance.js index 3299d0f5..9aa93fd1 100644 --- a/manager/javascripts/dcsinstance.js +++ b/manager/javascripts/dcsinstance.js @@ -136,6 +136,7 @@ class DCSInstance { blueCommanderPassword = ""; redCommanderPassword = ""; gameMasterPasswordHash = ""; + adminPassword = ""; installed = false; error = false; webserverOnline = false; @@ -149,6 +150,7 @@ class DCSInstance { gameMasterPasswordEdited = false; blueCommanderPasswordEdited = false; redCommanderPasswordEdited = false; + adminPasswordEdited = false; autoconnectWhenLocal = false; SRSPort = 5002; @@ -196,6 +198,7 @@ class DCSInstance { this.gameMasterPasswordEdited = false; this.blueCommanderPasswordEdited = false; this.redCommanderPasswordEdited = false; + this.adminPasswordEdited = false; } catch (err) { showErrorPopup(`
A critical error has occurred while reading your Olympus configuration file.
Please manually reinstall Olympus in ${this.folder} using either the installation Wizard or the Expert view.
`) @@ -277,7 +280,7 @@ class DCSInstance { /** Set Blue Commander password * - * @param {String} newAddress The new Blue Commander password to set + * @param {String} newPassword The new Blue Commander password to set */ setBlueCommanderPassword(newPassword) { this.blueCommanderPassword = newPassword; @@ -286,13 +289,22 @@ class DCSInstance { /** Set Red Commander password * - * @param {String} newAddress The new Red Commander password to set + * @param {String} newPassword The new Red Commander password to set */ setRedCommanderPassword(newPassword) { this.redCommanderPassword = newPassword; this.redCommanderPasswordEdited = true; } + /** Set Admin password + * + * @param {String} newPassword The new Admin password to set + */ + setAdminPassword(newPassword) { + this.adminPassword = newPassword; + this.adminPasswordEdited = true; + } + /** Checks if any password has been edited by the user * * @returns true if any password was edited @@ -306,7 +318,10 @@ class DCSInstance { * @returns true if all the password have been set */ arePasswordsSet() { - return !(getManager().getActiveInstance().gameMasterPassword === '' || getManager().getActiveInstance().blueCommanderPassword === '' || getManager().getActiveInstance().redCommanderPassword === ''); + if (getManager().getActiveInstance().installationType === "singleplayer") + return !(getManager().getActiveInstance().gameMasterPassword === '' || getManager().getActiveInstance().blueCommanderPassword === '' || getManager().getActiveInstance().redCommanderPassword === ''); + else + return !(getManager().getActiveInstance().gameMasterPassword === '' || getManager().getActiveInstance().blueCommanderPassword === '' || getManager().getActiveInstance().redCommanderPassword === '' || getManager().getActiveInstance().adminPassword === ''); } /** Checks if all the passwords are different diff --git a/manager/javascripts/filesystem.js b/manager/javascripts/filesystem.js index 4e10048d..6bea517b 100644 --- a/manager/javascripts/filesystem.js +++ b/manager/javascripts/filesystem.js @@ -181,6 +181,9 @@ async function applyConfiguration(folder, instance) { if (instance.redCommanderPassword !== "") config["authentication"]["redCommanderPassword"] = sha256(instance.redCommanderPassword); + if (instance.adminPassword !== "") + config["authentication"]["adminPassword"] = sha256(instance.adminPassword); + await fsp.writeFile(path.join(folder, "Config", "olympus.json"), JSON.stringify(config, null, 4)); logger.log(`Config succesfully applied in ${folder}`) } else { diff --git a/manager/javascripts/manager.js b/manager/javascripts/manager.js index 451aa617..3bcbedb3 100644 --- a/manager/javascripts/manager.js +++ b/manager/javascripts/manager.js @@ -475,7 +475,7 @@ class Manager { } async onGameMasterPasswordChanged(value) { - for (let input of this.activePage.getElement().querySelectorAll("input[type='password']")) { + for (let input of this.activePage.getElement().querySelectorAll("input[type='password'].unique")) { input.placeholder = ""; } @@ -486,7 +486,7 @@ class Manager { } async onBlueCommanderPasswordChanged(value) { - for (let input of this.activePage.getElement().querySelectorAll("input[type='password']")) { + for (let input of this.activePage.getElement().querySelectorAll("input[type='password'].unique")) { input.placeholder = ""; } @@ -497,7 +497,7 @@ class Manager { } async onRedCommanderPasswordChanged(value) { - for (let input of this.activePage.getElement().querySelectorAll("input[type='password']")) { + for (let input of this.activePage.getElement().querySelectorAll("input[type='password'].unique")) { input.placeholder = ""; } @@ -507,6 +507,13 @@ class Manager { showErrorPopup(`
A critical error occurred!
Check ${this.getLogLocation()} for more info.
`); } + async onAdminPasswordChanged(value) { + if (this.getActiveInstance()) + this.getActiveInstance().setAdminPassword(value); + else + showErrorPopup(`
A critical error occurred!
Check ${this.getLogLocation()} for more info.
`); + } + /* When the frontend port input value is changed */ async onFrontendPortChanged(value) { this.setPort('frontend', Number(value));