Implemented about page.
All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good

This commit is contained in:
Tom Butcher 2026-06-21 13:19:18 +01:00
parent a1e4bcaf18
commit 4bfc7fae2a
5 changed files with 93 additions and 0 deletions

3
.gitignore vendored
View File

@ -135,6 +135,9 @@ dist
test-results.xml
# Jenkins generated build metadata
src/buildInfo.json
DS_STORE
**/DS_Store

10
Jenkinsfile vendored
View File

@ -23,6 +23,16 @@ pipeline {
}
}
stage('Write Build Metadata') {
steps {
nodejs(nodeJSInstallationName: 'Node23') {
sh '''
node -e "const fs = require('fs'); fs.writeFileSync('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n');"
'''
}
}
}
stage('Install Dependencies') {
steps {
nodejs(nodeJSInstallationName: 'Node23') {

View File

@ -0,0 +1,42 @@
import { readFileSync } from 'node:fs';
import log4js from 'log4js';
import { loadConfig } from '../config.js';
const config = loadConfig();
// Setup logger
const logger = log4js.getLogger('Server Manager');
logger.level = config.server.logLevel;
const readJsonFile = fileUrl => {
try {
return JSON.parse(readFileSync(fileUrl, 'utf8'));
} catch (error) {
if (error?.code !== 'ENOENT') {
logger.debug('Failed to read server metadata:', error?.message);
}
return {};
}
};
const packageJsonUrl = new URL('../../package.json', import.meta.url);
const buildInfoUrl = new URL('../buildInfo.json', import.meta.url);
/**
* ServerManager exposes websocket server metadata to authenticated socket users.
*/
export class ServerManager {
constructor(socketClient) {
this.socketClient = socketClient;
}
getServerVersion() {
const packageJson = readJsonFile(packageJsonUrl);
const buildInfo = readJsonFile(buildInfoUrl);
return {
version: packageJson.version ?? 'dev',
buildNumber: buildInfo.buildNumber ?? 'dev'
};
}
}

View File

@ -67,6 +67,15 @@ jest.unstable_mockModule('../../notification/notificationmanager.js', () => ({
}))
}));
jest.unstable_mockModule('../../server/servermanager.js', () => ({
ServerManager: jest.fn().mockImplementation(() => ({
getServerVersion: jest.fn(() => ({
version: '1.0.0',
buildNumber: 'dev'
}))
}))
}));
jest.unstable_mockModule('log4js', () => ({
default: {
getLogger: () => ({
@ -116,6 +125,10 @@ describe('SocketUser', () => {
'disconnect',
expect.any(Function)
);
expect(mockSocket.on).toHaveBeenCalledWith(
'getServerVersion',
expect.any(Function)
);
});
describe('handleAuthenticateEvent', () => {
@ -224,6 +237,20 @@ describe('SocketUser', () => {
});
});
describe('handleGetServerVersionEvent', () => {
it('should return server version details', async () => {
const callback = jest.fn();
await socketUser.handleGetServerVersionEvent({}, callback);
expect(socketUser.serverManager.getServerVersion).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith({
version: '1.0.0',
buildNumber: 'dev'
});
});
});
describe('handleDisconnect', () => {
it('should remove all listeners', async () => {
await socketUser.handleDisconnect();

View File

@ -9,6 +9,7 @@ import { ActionManager } from '../actions/actionmanager.js';
import { EventManager } from '../events/eventmanager.js';
import { StatsManager } from '../stats/statsmanager.js';
import { NotificationManager } from '../notification/notificationmanager.js';
import { ServerManager } from '../server/servermanager.js';
const config = loadConfig();
@ -29,6 +30,7 @@ export class SocketUser {
this.eventManager = new EventManager(this);
this.statsManager = new StatsManager(this);
this.notificationManager = new NotificationManager(this);
this.serverManager = new ServerManager(this);
this.templateManager = socketManager.templateManager;
this.keycloakAuth = new KeycloakAuth();
this.setupSocketEventHandlers();
@ -84,6 +86,10 @@ export class SocketUser {
'generateHostOtp',
this.handleGenerateHostOtpEvent.bind(this)
);
this.socket.on(
'getServerVersion',
this.handleGetServerVersionEvent.bind(this)
);
this.socket.on('objectAction', this.handleObjectActionEvent.bind(this));
this.socket.on('disconnect', this.handleDisconnect.bind(this));
}
@ -245,6 +251,11 @@ export class SocketUser {
callback(result);
}
async handleGetServerVersionEvent(data, callback) {
const responseCallback = typeof callback === 'function' ? callback : data;
responseCallback(this.serverManager.getServerVersion());
}
async handleObjectActionEvent(data, callback) {
await this.actionManager.sendObjectAction(
data._id,