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
+
+ getApp().resetAllProfiles()}
+ className={`
+ flex content-center items-center gap-2 text-nowrap rounded-sm
+ border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
+ text-white
+ dark:border-red-600 dark:bg-red-800 dark:text-gray-400
+ dark:hover:bg-red-700 dark:focus:ring-blue-800
+ focus:outline-none focus:ring-4 focus:ring-blue-300
+ hover:bg-red-800
+ `}
+ >
+ Reset profiles
+
+
+
+
+
+
{
+ uploadNewConfig();
+ getApp().setState(OlympusState.IDLE)}
+ }
+ className={`
+ my-auto flex content-center items-center gap-2 rounded-sm
+ bg-blue-700 px-5 py-2.5 text-sm font-medium text-white
+ dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
+ focus:outline-none focus:ring-4 focus:ring-blue-300
+ hover:bg-blue-800
+ `}
+ >
+ Apply changes
+
+
+
+
+ );
+}
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
-
+
getApp().resetProfile()}
+ className={`
+ flex w-full content-center items-center justify-center gap-2
+ rounded-sm border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
+ text-white
+ dark:border-red-600 dark:bg-gray-800 dark:text-gray-400
+ dark:hover:bg-gray-700 dark:focus:ring-blue-800
+ focus:outline-none focus:ring-4 focus:ring-blue-300
+ hover:bg-blue-800
+ `}
+ >
+ Reset all settings
+
+
+
+
+
+
+ Admin password
+
+ {
+ 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
+ />
+
getApp().resetProfile()}
+ onClick={() => checkPassword(password)}
className={`
- mb-2 me-2 flex content-center items-center gap-2 rounded-sm
+ flex content-center items-center justify-center gap-2 rounded-sm
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
text-white
- dark:border-red-600 dark:bg-gray-800 dark:text-gray-400
+ dark:border-white dark:bg-gray-800 dark:text-gray-400
dark:hover:bg-gray-700 dark:focus:ring-blue-800
focus:outline-none focus:ring-4 focus:ring-blue-300
hover:bg-blue-800
`}
>
- Reset profile
-
-
-
getApp().resetAllProfiles()}
- className={`
- mb-2 me-2 flex content-center items-center gap-2 rounded-sm
- border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
- text-white
- dark:border-red-600 dark:bg-red-800 dark:text-gray-400
- dark:hover:bg-red-700 dark:focus:ring-blue-800
- focus:outline-none focus:ring-4 focus:ring-blue-300
- hover:bg-red-800
- `}
- >
- Reset all profiles
-
+ Open advanced settings menu
+
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
- ">
+ ">
+
" 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 @@
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));