diff --git a/apps/server/src/adapters/WebsocketAdapter.ts b/apps/server/src/adapters/WebsocketAdapter.ts index 155811898..2dc93a272 100644 --- a/apps/server/src/adapters/WebsocketAdapter.ts +++ b/apps/server/src/adapters/WebsocketAdapter.ts @@ -33,6 +33,7 @@ export class SocketServer implements IAdapter { private wss: WebSocketServer | null; private readonly clients: Map; + private lastConnection: Date | null = null; constructor() { if (instance) { @@ -58,6 +59,7 @@ export class SocketServer implements IAdapter { path: '', }); + this.lastConnection = new Date(); logger.info(LogOrigin.Client, `${this.clients.size} Connections with new: ${clientId}`); ws.send( @@ -171,6 +173,13 @@ export class SocketServer implements IAdapter { }); } + getStats() { + return { + connectedClients: this.clients.size, + lastConnection: this.lastConnection, + }; + } + private sendClientList(): void { const payload = Object.fromEntries(this.clients.entries()); this.sendAsJson({ type: 'client-list', payload }); diff --git a/apps/server/src/api-data/session/session.controller.ts b/apps/server/src/api-data/session/session.controller.ts index 851c4d83a..ffbb16821 100644 --- a/apps/server/src/api-data/session/session.controller.ts +++ b/apps/server/src/api-data/session/session.controller.ts @@ -1,10 +1,20 @@ import { getErrorMessage } from 'ontime-utils'; -import { ErrorResponse, GetInfo } from 'ontime-types'; +import { ErrorResponse, GetInfo, SessionStats } from 'ontime-types'; import type { Request, Response } from 'express'; import * as sessionService from './session.service.js'; +export async function getSessionStats(_req: Request, res: Response) { + try { + const stats = await sessionService.getSessionStats(); + res.status(200).send(stats); + } catch (error) { + const message = getErrorMessage(error); + res.status(500).send({ message }); + } +} + export async function getInfo(_req: Request, res: Response) { try { const info = await sessionService.getInfo(); diff --git a/apps/server/src/api-data/session/session.router.ts b/apps/server/src/api-data/session/session.router.ts index e2e00dfb0..f6dbff659 100644 --- a/apps/server/src/api-data/session/session.router.ts +++ b/apps/server/src/api-data/session/session.router.ts @@ -1,7 +1,8 @@ import express from 'express'; -import { getInfo } from './session.controller.js'; +import { getInfo, getSessionStats } from './session.controller.js'; export const router = express.Router(); +router.get('/', getSessionStats); router.get('/info', getInfo); diff --git a/apps/server/src/api-data/session/session.service.ts b/apps/server/src/api-data/session/session.service.ts index ae264f6fb..6165ab1d1 100644 --- a/apps/server/src/api-data/session/session.service.ts +++ b/apps/server/src/api-data/session/session.service.ts @@ -1,8 +1,32 @@ -import { GetInfo } from 'ontime-types'; +import { GetInfo, SessionStats } from 'ontime-types'; import { getDataProvider } from '../../classes/data-provider/DataProvider.js'; import { publicFiles } from '../../setup/index.js'; import { getNetworkInterfaces } from '../../utils/networkInterfaces.js'; +import { socket } from '../../adapters/WebsocketAdapter.js'; +import { getLastRequest } from '../../api-integration/integration.controller.js'; +import { getLastLoadedProject } from '../../services/app-state-service/AppStateService.js'; +import { runtimeService } from '../../services/runtime-service/RuntimeService.js'; + +const startedAt = new Date(); + +/** Gathers information related to runtime */ +export async function getSessionStats(): Promise { + const { connectedClients, lastConnection } = socket.getStats(); + const lastRequest = getLastRequest(); + const projectName = await getLastLoadedProject(); + const { playback } = runtimeService.getRuntimeState(); + + return { + startedAt: startedAt.toISOString(), + connectedClients, + lastConnection: lastConnection !== null ? lastConnection.toISOString() : null, + lastRequest: lastRequest !== null ? lastRequest.toISOString() : null, + projectName, + playback, + timezone: startedAt.getTimezoneOffset(), + }; +} /** * Adds business logic to gathering data for the info endpoint diff --git a/apps/server/src/api-integration/integration.controller.ts b/apps/server/src/api-integration/integration.controller.ts index 09a33d56b..a9e83e035 100644 --- a/apps/server/src/api-integration/integration.controller.ts +++ b/apps/server/src/api-integration/integration.controller.ts @@ -19,10 +19,13 @@ import { willCauseRegeneration } from '../services/rundown-service/rundownCacheU import { handleLegacyMessageConversion } from './integration.legacy.js'; const throttledUpdateEvent = throttle(updateEvent, 20); +let lastRequest: Date | null = null; export function dispatchFromAdapter(type: string, payload: unknown, _source?: 'osc' | 'ws' | 'http') { const action = type.toLowerCase(); const handler = actionHandlers[action]; + lastRequest = new Date(); + if (handler) { return handler(payload); } else { @@ -30,6 +33,10 @@ export function dispatchFromAdapter(type: string, payload: unknown, _source?: 'o } } +export function getLastRequest() { + return lastRequest; +} + type ActionHandler = (payload: unknown) => { payload: unknown }; const actionHandlers: Record = { diff --git a/apps/server/src/services/runtime-service/RuntimeService.ts b/apps/server/src/services/runtime-service/RuntimeService.ts index e708b1cff..43c50e888 100644 --- a/apps/server/src/services/runtime-service/RuntimeService.ts +++ b/apps/server/src/services/runtime-service/RuntimeService.ts @@ -293,6 +293,10 @@ class RuntimeService { return success; } + public getRuntimeState() { + return { playback: runtimeState.getState().timer.playback }; + } + /** * starts event matching given ID * @param {string} eventId diff --git a/packages/types/src/api/ontime-controller/BackendResponse.type.ts b/packages/types/src/api/ontime-controller/BackendResponse.type.ts index 0a4db7480..ed4dd3b28 100644 --- a/packages/types/src/api/ontime-controller/BackendResponse.type.ts +++ b/packages/types/src/api/ontime-controller/BackendResponse.type.ts @@ -1,11 +1,23 @@ import type { OSCSettings } from '../../definitions/core/OscSettings.type.js'; import type { OntimeRundown } from '../../definitions/core/Rundown.type.js'; +import type { Playback } from '../../definitions/runtime/Playback.type.js'; +import type { MaybeString } from '../../utils/utils.type.js'; export type NetworkInterface = { name: string; address: string; }; +export interface SessionStats { + startedAt: string; + connectedClients: number; + lastConnection: MaybeString; + lastRequest: MaybeString; + projectName: string; + playback: Playback; + timezone: number; +} + export interface GetInfo { networkInterfaces: NetworkInterface[]; version: string; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index da93286bf..bd0b6620d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -51,6 +51,7 @@ export type { ProjectFileListResponse, MessageResponse, RundownPaginated, + SessionStats, } from './api/ontime-controller/BackendResponse.type.js'; export type { RundownCached, NormalisedRundown } from './api/rundown-controller/BackendResponse.type.js';