mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 14:39:06 +00:00
committed by
Carlos Valente
parent
eace97ec7a
commit
f219ebb8f8
@@ -1,7 +1,6 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { unobfuscate } from 'ontime-utils';
|
import { unobfuscate } from 'ontime-utils';
|
||||||
|
|
||||||
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
|
||||||
import { APP_SETTINGS } from '../api/constants';
|
import { APP_SETTINGS } from '../api/constants';
|
||||||
import { getSettings } from '../api/settings';
|
import { getSettings } from '../api/settings';
|
||||||
import { ontimePlaceholderSettings } from '../models/OntimeSettings';
|
import { ontimePlaceholderSettings } from '../models/OntimeSettings';
|
||||||
@@ -11,10 +10,6 @@ export default function useSettings() {
|
|||||||
queryKey: APP_SETTINGS,
|
queryKey: APP_SETTINGS,
|
||||||
queryFn: getSettings,
|
queryFn: getSettings,
|
||||||
placeholderData: (previousData, _previousQuery) => previousData,
|
placeholderData: (previousData, _previousQuery) => previousData,
|
||||||
retry: 5,
|
|
||||||
retryDelay: (attempt) => attempt * 2500,
|
|
||||||
refetchInterval: queryRefetchIntervalSlow,
|
|
||||||
networkMode: 'always',
|
|
||||||
select: (data) => {
|
select: (data) => {
|
||||||
const unobfuscated = { ...data };
|
const unobfuscated = { ...data };
|
||||||
if (data.editorKey) {
|
if (data.editorKey) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
|
|
||||||
import { isProduction, websocketUrl } from '../../externals';
|
import { isProduction, websocketUrl } from '../../externals';
|
||||||
import {
|
import {
|
||||||
|
APP_SETTINGS,
|
||||||
CLIENT_LIST,
|
CLIENT_LIST,
|
||||||
CUSTOM_FIELDS,
|
CUSTOM_FIELDS,
|
||||||
PROJECT_DATA,
|
PROJECT_DATA,
|
||||||
@@ -177,6 +178,9 @@ export const connectSocket = () => {
|
|||||||
case RefetchKey.Translation:
|
case RefetchKey.Translation:
|
||||||
ontimeQueryClient.invalidateQueries({ queryKey: TRANSLATION });
|
ontimeQueryClient.invalidateQueries({ queryKey: TRANSLATION });
|
||||||
break;
|
break;
|
||||||
|
case RefetchKey.Settings:
|
||||||
|
ontimeQueryClient.invalidateQueries({ queryKey: APP_SETTINGS });
|
||||||
|
break;
|
||||||
default: {
|
default: {
|
||||||
target satisfies never;
|
target satisfies never;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
import { ErrorResponse, Settings } from 'ontime-types';
|
|
||||||
import { getErrorMessage, obfuscate } from 'ontime-utils';
|
|
||||||
|
|
||||||
import type { Request, Response } from 'express';
|
|
||||||
|
|
||||||
import { isDocker } from '../../setup/environment.js';
|
|
||||||
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
|
||||||
import * as appState from '../../services/app-state-service/AppStateService.js';
|
|
||||||
|
|
||||||
import { extractPin } from './settings.utils.js';
|
|
||||||
|
|
||||||
export async function getSettings(_req: Request, res: Response<Settings>) {
|
|
||||||
const settings = getDataProvider().getSettings();
|
|
||||||
const obfuscatedSettings = { ...settings };
|
|
||||||
if (settings.editorKey) {
|
|
||||||
obfuscatedSettings.editorKey = obfuscate(settings.editorKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.operatorKey) {
|
|
||||||
obfuscatedSettings.operatorKey = obfuscate(settings.operatorKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).send(obfuscatedSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function postSettings(req: Request, res: Response<Settings | ErrorResponse>) {
|
|
||||||
try {
|
|
||||||
const settings = getDataProvider().getSettings();
|
|
||||||
const editorKey = extractPin(req.body?.editorKey, settings.editorKey);
|
|
||||||
const operatorKey = extractPin(req.body?.operatorKey, settings.operatorKey);
|
|
||||||
const serverPort = Number(req.body?.serverPort);
|
|
||||||
//TODO: should this not be part of the validator?
|
|
||||||
if (isNaN(serverPort)) {
|
|
||||||
res.status(400).send({ message: `Invalid value found for server port: ${req.body?.serverPort}` });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasChangedPort = settings.serverPort !== serverPort;
|
|
||||||
|
|
||||||
if (isDocker && hasChangedPort) {
|
|
||||||
res.status(403).json({ message: 'Can`t change port when running inside docker' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeFormat = settings.timeFormat;
|
|
||||||
if (req.body?.timeFormat === '12' || req.body?.timeFormat === '24') {
|
|
||||||
timeFormat = req.body.timeFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
const language = req.body?.language || 'en';
|
|
||||||
|
|
||||||
const newData = {
|
|
||||||
...settings,
|
|
||||||
editorKey,
|
|
||||||
operatorKey,
|
|
||||||
timeFormat,
|
|
||||||
language,
|
|
||||||
serverPort,
|
|
||||||
};
|
|
||||||
await getDataProvider().setSettings(newData);
|
|
||||||
res.status(200).send(newData);
|
|
||||||
} catch (error) {
|
|
||||||
const message = getErrorMessage(error);
|
|
||||||
res.status(400).send({ message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function postWelcomeDialog(req: Request, res: Response) {
|
|
||||||
const show = await appState.setShowWelcomeDialog(req.body.show);
|
|
||||||
res.status(200).send({ show });
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,58 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { getSettings, postSettings, postWelcomeDialog } from './settings.controller.js';
|
import { matchedData } from 'express-validator';
|
||||||
|
import type { Request, Response } from 'express';
|
||||||
|
import { deepEqual } from 'fast-equals';
|
||||||
|
|
||||||
|
import { ErrorResponse, RefetchKey, Settings } from 'ontime-types';
|
||||||
|
import { getErrorMessage, obfuscate } from 'ontime-utils';
|
||||||
|
|
||||||
import { validateSettings, validateWelcomeDialog } from './settings.validation.js';
|
import { validateSettings, validateWelcomeDialog } from './settings.validation.js';
|
||||||
|
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
||||||
|
import * as appState from '../../services/app-state-service/AppStateService.js';
|
||||||
|
import { isDocker } from '../../setup/environment.js';
|
||||||
|
import { sendRefetch } from '../../adapters/WebsocketAdapter.js';
|
||||||
|
|
||||||
export const router = express.Router();
|
export const router = express.Router();
|
||||||
|
|
||||||
router.post('/welcomedialog', validateWelcomeDialog, postWelcomeDialog);
|
router.post('/welcomedialog', validateWelcomeDialog, async (req: Request, res: Response) => {
|
||||||
|
const show = await appState.setShowWelcomeDialog(req.body.show);
|
||||||
|
res.status(200).json({ show });
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/', getSettings);
|
router.get('/', (_req: Request, res: Response<Settings>) => {
|
||||||
router.post('/', validateSettings, postSettings);
|
const settings = getDataProvider().getSettings();
|
||||||
|
const obfuscatedSettings = { ...settings };
|
||||||
|
if (settings.editorKey) {
|
||||||
|
obfuscatedSettings.editorKey = obfuscate(settings.editorKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.operatorKey) {
|
||||||
|
obfuscatedSettings.operatorKey = obfuscate(settings.operatorKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json(obfuscatedSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/', validateSettings, async (req: Request, res: Response<Settings | ErrorResponse>) => {
|
||||||
|
try {
|
||||||
|
const data = matchedData<Settings>(req);
|
||||||
|
const settings = getDataProvider().getSettings();
|
||||||
|
|
||||||
|
if (isDocker && settings.serverPort !== data.serverPort) {
|
||||||
|
res.status(403).json({ message: 'Can`t change port when running inside docker' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.version = settings.version;
|
||||||
|
|
||||||
|
if (!deepEqual(data, settings)) {
|
||||||
|
await getDataProvider().setSettings(data);
|
||||||
|
sendRefetch(RefetchKey.Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json(data);
|
||||||
|
} catch (error) {
|
||||||
|
const message = getErrorMessage(error);
|
||||||
|
res.status(400).json({ message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* Business logic for resolving a string
|
|
||||||
*/
|
|
||||||
export function extractPin(value: string | undefined | null, fallback: string | null): string | null {
|
|
||||||
if (value === null) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
if (typeof value === 'undefined') {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
if (value.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { body } from 'express-validator';
|
import { body, checkExact } from 'express-validator';
|
||||||
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
|
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -6,16 +6,30 @@ import { requestValidationFunction } from '../validation-utils/validationFunctio
|
|||||||
*/
|
*/
|
||||||
export const validateWelcomeDialog = [body('show').isBoolean(), requestValidationFunction];
|
export const validateWelcomeDialog = [body('show').isBoolean(), requestValidationFunction];
|
||||||
|
|
||||||
|
const pinValidator = (key: string) => {
|
||||||
|
return body(key)
|
||||||
|
.isString()
|
||||||
|
.trim()
|
||||||
|
.isLength({ min: 0, max: 4 })
|
||||||
|
.customSanitizer((input) => {
|
||||||
|
if (input.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Validates object for POST /ontime/settings
|
* @description Validates object for POST /ontime/settings
|
||||||
*/
|
*/
|
||||||
export const validateSettings = [
|
export const validateSettings = [
|
||||||
body().notEmpty().withMessage('No object found in request'),
|
pinValidator('editorKey'),
|
||||||
body('editorKey').isString().isLength({ min: 0, max: 4 }).optional({ nullable: true }),
|
pinValidator('operatorKey'),
|
||||||
body('operatorKey').isString().isLength({ min: 0, max: 4 }).optional({ nullable: true }),
|
body('timeFormat').isString().isIn(['12', '24']).withMessage('Time format can only be "12" or "24"'),
|
||||||
body('timeFormat').isString().isIn(['12', '24']),
|
body('language').isString().trim().notEmpty(),
|
||||||
body('language').isString(),
|
body('serverPort').isPort().withMessage('Invalid value found for server port').toInt(),
|
||||||
body('serverPort').isPort().optional(),
|
checkExact(),
|
||||||
|
|
||||||
requestValidationFunction,
|
requestValidationFunction,
|
||||||
];
|
];
|
||||||
|
// TODO: dont allow other keys
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ export enum RefetchKey {
|
|||||||
UrlPresets = 'url-presets',
|
UrlPresets = 'url-presets',
|
||||||
ViewSettings = 'view-settings',
|
ViewSettings = 'view-settings',
|
||||||
Translation = 'translation',
|
Translation = 'translation',
|
||||||
|
Settings = 'settings',
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user