feat(opsserver): introduce OpsServer (TypedRequest API) and new lightweight web UI; replace legacy Angular UI and add typed interfaces

This commit is contained in:
2026-02-24 18:15:44 +00:00
parent 84c47cd7f5
commit ba05cc84fe
143 changed files with 46631 additions and 20632 deletions

View File

@@ -0,0 +1,175 @@
import * as plugins from '../../plugins.ts';
import { logger } from '../../logging.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
export interface IJwtData {
userId: string;
status: 'loggedIn' | 'loggedOut';
expiresAt: number;
}
export class AdminHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
public smartjwtInstance!: plugins.smartjwt.SmartJwt<IJwtData>;
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
}
public async initialize(): Promise<void> {
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
await this.smartjwtInstance.init();
await this.smartjwtInstance.createNewKeyPair();
this.registerHandlers();
}
private registerHandlers(): void {
// Login
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
'adminLoginWithUsernameAndPassword',
async (dataArg) => {
try {
const user = this.opsServerRef.oneboxRef.database.getUserByUsername(dataArg.username);
if (!user) {
throw new plugins.typedrequest.TypedResponseError('Invalid credentials');
}
// Verify password (base64 comparison to match existing DB scheme)
const passwordHash = btoa(dataArg.password);
if (passwordHash !== user.passwordHash) {
throw new plugins.typedrequest.TypedResponseError('Invalid credentials');
}
const expiresAt = Date.now() + 24 * 3600 * 1000;
const userId = String(user.id || user.username);
const jwt = await this.smartjwtInstance.createJWT({
userId,
status: 'loggedIn',
expiresAt,
});
logger.info(`User logged in: ${user.username}`);
return {
identity: {
jwt,
userId,
username: user.username,
expiresAt,
role: user.role,
},
};
} catch (error) {
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
throw new plugins.typedrequest.TypedResponseError('Login failed');
}
},
),
);
// Logout
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLogout>(
'adminLogout',
async (_dataArg) => {
return { ok: true };
},
),
);
// Verify Identity
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_VerifyIdentity>(
'verifyIdentity',
async (dataArg) => {
if (!dataArg.identity?.jwt) {
return { valid: false };
}
try {
const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt);
if (jwtData.expiresAt < Date.now()) return { valid: false };
if (jwtData.status !== 'loggedIn') return { valid: false };
return {
valid: true,
identity: {
jwt: dataArg.identity.jwt,
userId: jwtData.userId,
username: dataArg.identity.username,
expiresAt: jwtData.expiresAt,
role: dataArg.identity.role,
},
};
} catch {
return { valid: false };
}
},
),
);
// Change Password
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ChangePassword>(
'changePassword',
async (dataArg) => {
await this.requireValidIdentity(dataArg);
const user = this.opsServerRef.oneboxRef.database.getUserByUsername(dataArg.identity.username);
if (!user) {
throw new plugins.typedrequest.TypedResponseError('User not found');
}
const currentHash = btoa(dataArg.currentPassword);
if (currentHash !== user.passwordHash) {
throw new plugins.typedrequest.TypedResponseError('Current password is incorrect');
}
const newHash = btoa(dataArg.newPassword);
this.opsServerRef.oneboxRef.database.updateUserPassword(user.username, newHash);
logger.info(`Password changed for user: ${user.username}`);
return { ok: true };
},
),
);
}
private async requireValidIdentity(dataArg: { identity: interfaces.data.IIdentity }): Promise<void> {
const passed = await this.validIdentityGuard.exec({ identity: dataArg.identity });
if (!passed) {
throw new plugins.typedrequest.TypedResponseError('Valid identity required');
}
}
// Guard for valid identity
public validIdentityGuard = new plugins.smartguard.Guard<{
identity: interfaces.data.IIdentity;
}>(
async (dataArg) => {
if (!dataArg.identity?.jwt) return false;
try {
const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt);
if (jwtData.expiresAt < Date.now()) return false;
if (jwtData.status !== 'loggedIn') return false;
if (dataArg.identity.expiresAt !== jwtData.expiresAt) return false;
if (dataArg.identity.userId !== jwtData.userId) return false;
return true;
} catch {
return false;
}
},
{ failedHint: 'identity is not valid', name: 'validIdentityGuard' },
);
// Guard for admin identity
public adminIdentityGuard = new plugins.smartguard.Guard<{
identity: interfaces.data.IIdentity;
}>(
async (dataArg) => {
const isValid = await this.validIdentityGuard.exec(dataArg);
if (!isValid) return false;
return dataArg.identity.role === 'admin';
},
{ failedHint: 'user is not admin', name: 'adminIdentityGuard' },
);
}

View File

@@ -0,0 +1,100 @@
import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class BackupsHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private registerHandlers(): void {
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackups>(
'getBackups',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const backups = this.opsServerRef.oneboxRef.backupManager.listBackups();
return { backups };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackup>(
'getBackup',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const backup = this.opsServerRef.oneboxRef.database.getBackupById(dataArg.backupId);
if (!backup) {
throw new plugins.typedrequest.TypedResponseError('Backup not found');
}
return { backup };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteBackup>(
'deleteBackup',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.backupManager.deleteBackup(dataArg.backupId);
return { ok: true };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RestoreBackup>(
'restoreBackup',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const backupPath = this.opsServerRef.oneboxRef.backupManager.getBackupFilePath(dataArg.backupId);
if (!backupPath) {
throw new plugins.typedrequest.TypedResponseError('Backup file not found');
}
const rawResult = await this.opsServerRef.oneboxRef.backupManager.restoreBackup(
backupPath,
dataArg.options,
);
return {
result: {
service: {
name: rawResult.service.name,
status: rawResult.service.status,
},
platformResourcesRestored: rawResult.platformResourcesRestored,
warnings: rawResult.warnings,
},
};
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DownloadBackup>(
'downloadBackup',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const backup = this.opsServerRef.oneboxRef.database.getBackupById(dataArg.backupId);
if (!backup) {
throw new plugins.typedrequest.TypedResponseError('Backup not found');
}
const filePath = this.opsServerRef.oneboxRef.backupManager.getBackupFilePath(dataArg.backupId);
if (!filePath) {
throw new plugins.typedrequest.TypedResponseError('Backup file not found');
}
// Return a download URL that the client can fetch directly
return {
downloadUrl: `/api/backups/${dataArg.backupId}/download`,
filename: backup.filename,
};
},
),
);
}
}

View File

@@ -0,0 +1,65 @@
import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class DnsHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private registerHandlers(): void {
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsRecords>(
'getDnsRecords',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const records = this.opsServerRef.oneboxRef.dns.listDNSRecords();
return { records };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateDnsRecord>(
'createDnsRecord',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.dns.addDNSRecord(dataArg.domain, dataArg.value);
const records = this.opsServerRef.oneboxRef.dns.listDNSRecords();
const record = records.find((r: any) => r.domain === dataArg.domain);
return { record: record! };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteDnsRecord>(
'deleteDnsRecord',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.dns.removeDNSRecord(dataArg.domain);
return { ok: true };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SyncDns>(
'syncDns',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
if (!this.opsServerRef.oneboxRef.dns.isConfigured()) {
throw new plugins.typedrequest.TypedResponseError('DNS manager not configured');
}
await this.opsServerRef.oneboxRef.dns.syncFromCloudflare();
const records = this.opsServerRef.oneboxRef.dns.listDNSRecords();
return { records };
},
),
);
}
}

View File

@@ -0,0 +1,101 @@
import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class DomainsHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private buildDomainViews(): interfaces.data.IDomainDetail[] {
const domains = this.opsServerRef.oneboxRef.database.getAllDomains();
const allServices = this.opsServerRef.oneboxRef.database.getAllServices();
return domains.map((domain: any) => {
const certificates = this.opsServerRef.oneboxRef.database.getCertificatesByDomain(domain.id!);
const requirements = this.opsServerRef.oneboxRef.database.getCertRequirementsByDomain(domain.id!);
const serviceCount = allServices.filter((service: any) => {
if (!service.domain) return false;
const baseDomain = service.domain.split('.').slice(-2).join('.');
return baseDomain === domain.domain;
}).length;
let certificateStatus: 'valid' | 'expiring-soon' | 'expired' | 'pending' | 'none' = 'none';
let daysRemaining: number | null = null;
const validCerts = certificates.filter((cert: any) => cert.isValid && cert.expiryDate > Date.now());
if (validCerts.length > 0) {
const latestCert = validCerts.reduce((latest: any, cert: any) =>
cert.expiryDate > latest.expiryDate ? cert : latest
);
daysRemaining = Math.floor((latestCert.expiryDate - Date.now()) / (24 * 60 * 60 * 1000));
certificateStatus = daysRemaining <= 30 ? 'expiring-soon' : 'valid';
} else if (certificates.some((cert: any) => !cert.isValid)) {
certificateStatus = 'expired';
} else if (requirements.some((req: any) => req.status === 'pending')) {
certificateStatus = 'pending';
}
return {
domain,
certificates,
requirements,
serviceCount,
certificateStatus,
daysRemaining,
};
});
}
private registerHandlers(): void {
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDomains>(
'getDomains',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const domains = this.buildDomainViews();
return { domains };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDomain>(
'getDomain',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const domain = this.opsServerRef.oneboxRef.database.getDomainByName(dataArg.domainName);
if (!domain) {
throw new plugins.typedrequest.TypedResponseError('Domain not found');
}
const views = this.buildDomainViews();
const domainView = views.find((v) => v.domain.domain === dataArg.domainName);
if (!domainView) {
throw new plugins.typedrequest.TypedResponseError('Domain not found');
}
return { domain: domainView };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SyncDomains>(
'syncDomains',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
if (!this.opsServerRef.oneboxRef.cloudflareDomainSync) {
throw new plugins.typedrequest.TypedResponseError('Cloudflare domain sync not configured');
}
await this.opsServerRef.oneboxRef.cloudflareDomainSync.syncZones();
const domains = this.buildDomainViews();
return { domains };
},
),
);
}
}

View File

@@ -0,0 +1,13 @@
export * from './admin.handler.ts';
export * from './status.handler.ts';
export * from './services.handler.ts';
export * from './platform.handler.ts';
export * from './ssl.handler.ts';
export * from './domains.handler.ts';
export * from './dns.handler.ts';
export * from './registry.handler.ts';
export * from './network.handler.ts';
export * from './backups.handler.ts';
export * from './schedules.handler.ts';
export * from './settings.handler.ts';
export * from './logs.handler.ts';

View File

@@ -0,0 +1,219 @@
import * as plugins from '../../plugins.ts';
import { logger } from '../../logging.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class LogsHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private registerHandlers(): void {
// Service log stream
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceLogStream>(
'getServiceLogStream',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const service = this.opsServerRef.oneboxRef.database.getServiceByName(dataArg.serviceName);
if (!service) {
throw new plugins.typedrequest.TypedResponseError('Service not found');
}
const virtualStream = new plugins.typedrequest.VirtualStream<Uint8Array>();
const encoder = new TextEncoder();
// Get container and start streaming in background
(async () => {
try {
let container = await this.opsServerRef.oneboxRef.docker.getContainerById(service.containerID!);
if (!container) {
// Try finding by service label
const containers = await this.opsServerRef.oneboxRef.docker.listAllContainers();
const serviceContainer = containers.find((c: any) => {
const labels = c.Labels || {};
return labels['com.docker.swarm.service.id'] === service.containerID;
});
if (serviceContainer) {
container = await this.opsServerRef.oneboxRef.docker.getContainerById(serviceContainer.Id);
}
}
if (!container) {
virtualStream.sendData(encoder.encode(JSON.stringify({ error: 'Container not found' })));
return;
}
const logStream = await container.streamLogs({
stdout: true,
stderr: true,
timestamps: true,
tail: 100,
});
let buffer = new Uint8Array(0);
logStream.on('data', (chunk: Uint8Array) => {
// Append to buffer
const newBuffer = new Uint8Array(buffer.length + chunk.length);
newBuffer.set(buffer);
newBuffer.set(chunk, buffer.length);
buffer = newBuffer;
// Process Docker multiplexed frames
while (buffer.length >= 8) {
const frameSize = (buffer[4] << 24) | (buffer[5] << 16) | (buffer[6] << 8) | buffer[7];
if (buffer.length < 8 + frameSize) break;
const frameData = buffer.slice(8, 8 + frameSize);
try {
virtualStream.sendData(frameData);
} catch {
logStream.destroy();
return;
}
buffer = buffer.slice(8 + frameSize);
}
});
logStream.on('error', (error: Error) => {
logger.error(`Log stream error for ${dataArg.serviceName}: ${error.message}`);
});
} catch (error) {
logger.error(`Failed to start log stream: ${error}`);
}
})();
return { logStream: virtualStream as any };
},
),
);
// Platform service log stream
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceLogStream>(
'getPlatformServiceLogStream',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const platformService = this.opsServerRef.oneboxRef.database.getPlatformServiceByType(
dataArg.serviceType,
);
if (!platformService || !platformService.containerId) {
throw new plugins.typedrequest.TypedResponseError('Platform service has no container');
}
const virtualStream = new plugins.typedrequest.VirtualStream<Uint8Array>();
(async () => {
try {
const container = await this.opsServerRef.oneboxRef.docker.getContainerById(
platformService.containerId!,
);
if (!container) return;
const logStream = await container.streamLogs({
stdout: true,
stderr: true,
timestamps: true,
tail: 100,
});
let buffer = new Uint8Array(0);
logStream.on('data', (chunk: Uint8Array) => {
const newBuffer = new Uint8Array(buffer.length + chunk.length);
newBuffer.set(buffer);
newBuffer.set(chunk, buffer.length);
buffer = newBuffer;
while (buffer.length >= 8) {
const frameSize = (buffer[4] << 24) | (buffer[5] << 16) | (buffer[6] << 8) | buffer[7];
if (buffer.length < 8 + frameSize) break;
const frameData = buffer.slice(8, 8 + frameSize);
try {
virtualStream.sendData(frameData);
} catch {
logStream.destroy();
return;
}
buffer = buffer.slice(8 + frameSize);
}
});
} catch (error) {
logger.error(`Failed to start platform log stream: ${error}`);
}
})();
return { logStream: virtualStream as any };
},
),
);
// Network log stream
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkLogStream>(
'getNetworkLogStream',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const virtualStream = new plugins.typedrequest.VirtualStream<Uint8Array>();
const encoder = new TextEncoder();
const clientId = crypto.randomUUID();
// Create a mock WebSocket-like object for the CaddyLogReceiver
const mockSocket = {
readyState: 1, // WebSocket.OPEN
send: (data: string) => {
try {
virtualStream.sendData(encoder.encode(data));
} catch {
this.opsServerRef.oneboxRef.caddyLogReceiver.removeClient(clientId);
}
},
};
const filter = dataArg.filter || {};
this.opsServerRef.oneboxRef.caddyLogReceiver.addClient(
clientId,
mockSocket as any,
filter,
);
return { logStream: virtualStream as any };
},
),
);
// Event stream (general updates)
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEventStream>(
'getEventStream',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const virtualStream = new plugins.typedrequest.VirtualStream<Uint8Array>();
const encoder = new TextEncoder();
// Send initial connection message
virtualStream.sendData(
encoder.encode(
JSON.stringify({
type: 'connected',
message: 'Connected to Onebox event stream',
timestamp: Date.now(),
}),
),
);
return { eventStream: virtualStream as any };
},
),
);
}
}

View File

@@ -0,0 +1,123 @@
import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
import type { TPlatformServiceType } from '../../types.ts';
export class NetworkHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private getPlatformServicePort(type: TPlatformServiceType): number {
const ports: Record<TPlatformServiceType, number> = {
mongodb: 27017,
minio: 9000,
redis: 6379,
postgresql: 5432,
rabbitmq: 5672,
caddy: 80,
clickhouse: 8123,
};
return ports[type] || 0;
}
private registerHandlers(): void {
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkTargets>(
'getNetworkTargets',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const targets: interfaces.data.INetworkTarget[] = [];
// Services
const services = this.opsServerRef.oneboxRef.services.listServices();
for (const svc of services) {
targets.push({
type: 'service',
name: svc.name,
domain: svc.domain || null,
targetHost: (svc as any).containerIP || svc.containerID || 'unknown',
targetPort: svc.port || 80,
status: svc.status,
});
}
// Registry
const registryStatus = this.opsServerRef.oneboxRef.registry.getStatus();
if (registryStatus.running) {
targets.push({
type: 'registry',
name: 'onebox-registry',
domain: null,
targetHost: 'localhost',
targetPort: registryStatus.port,
status: 'running',
});
}
// Platform services
const platformServices = this.opsServerRef.oneboxRef.platformServices.getAllPlatformServices();
for (const ps of platformServices) {
const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(ps.type);
targets.push({
type: 'platform',
name: provider?.displayName || ps.type,
domain: null,
targetHost: 'localhost',
targetPort: this.getPlatformServicePort(ps.type),
status: ps.status,
});
}
return { targets };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkStats>(
'getNetworkStats',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any;
const logReceiverStats = this.opsServerRef.oneboxRef.caddyLogReceiver.getStats();
return {
stats: {
proxy: {
running: proxyStatus.running ?? proxyStatus.http?.running ?? false,
httpPort: proxyStatus.httpPort ?? proxyStatus.http?.port ?? 80,
httpsPort: proxyStatus.httpsPort ?? proxyStatus.https?.port ?? 443,
routes: proxyStatus.routes ?? 0,
certificates: proxyStatus.certificates ?? proxyStatus.https?.certificates ?? 0,
},
logReceiver: {
running: logReceiverStats.running,
port: logReceiverStats.port,
clients: logReceiverStats.clients,
connections: logReceiverStats.connections,
sampleRate: logReceiverStats.sampleRate,
recentLogsCount: logReceiverStats.recentLogsCount,
},
},
};
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetTrafficStats>(
'getTrafficStats',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const trafficStats = this.opsServerRef.oneboxRef.caddyLogReceiver.getTrafficStats(60);
return { stats: trafficStats };
},
),
);
}
}

View File

@@ -0,0 +1,169 @@
import * as plugins from '../../plugins.ts';
import { logger } from '../../logging.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class PlatformHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private registerHandlers(): void {
// Get all platform services
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServices>(
'getPlatformServices',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const platformServices = this.opsServerRef.oneboxRef.platformServices.getAllPlatformServices();
const providers = this.opsServerRef.oneboxRef.platformServices.getAllProviders();
const result = providers.map((provider: any) => {
const service = platformServices.find((s: any) => s.type === provider.type);
const isCore = 'isCore' in provider && (provider as any).isCore === true;
let status: string = service?.status || 'not-deployed';
if (provider.type === 'caddy') {
const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any;
status = (proxyStatus.running ?? proxyStatus.http?.running) ? 'running' : 'stopped';
}
return {
type: provider.type,
displayName: provider.displayName,
resourceTypes: provider.resourceTypes,
status: status as interfaces.data.TPlatformServiceStatus,
containerId: service?.containerId,
isCore,
createdAt: service?.createdAt,
updatedAt: service?.updatedAt,
};
});
return { platformServices: result as interfaces.data.IPlatformService[] };
},
),
);
// Get specific platform service
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformService>(
'getPlatformService',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
if (!provider) {
throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
}
const service = this.opsServerRef.oneboxRef.database.getPlatformServiceByType(dataArg.serviceType);
const isCore = 'isCore' in provider && (provider as any).isCore === true;
let rawStatus: string = service?.status || 'not-deployed';
if (dataArg.serviceType === 'caddy') {
const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any;
rawStatus = (proxyStatus.running ?? proxyStatus.http?.running) ? 'running' : 'stopped';
}
return {
platformService: {
type: provider.type,
displayName: provider.displayName,
resourceTypes: provider.resourceTypes,
status: rawStatus as interfaces.data.TPlatformServiceStatus,
containerId: service?.containerId,
isCore,
createdAt: service?.createdAt,
updatedAt: service?.updatedAt,
} as interfaces.data.IPlatformService,
};
},
),
);
// Start platform service
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StartPlatformService>(
'startPlatformService',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
if (!provider) {
throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
}
logger.info(`Starting platform service: ${dataArg.serviceType}`);
const service = await this.opsServerRef.oneboxRef.platformServices.ensureRunning(dataArg.serviceType);
return {
platformService: {
type: service.type,
displayName: provider.displayName,
resourceTypes: provider.resourceTypes,
status: service.status,
containerId: service.containerId,
},
};
},
),
);
// Stop platform service
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StopPlatformService>(
'stopPlatformService',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
if (!provider) {
throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
}
const isCore = 'isCore' in provider && (provider as any).isCore === true;
if (isCore) {
throw new plugins.typedrequest.TypedResponseError(
`${provider.displayName} is a core service and cannot be stopped`,
);
}
logger.info(`Stopping platform service: ${dataArg.serviceType}`);
await this.opsServerRef.oneboxRef.platformServices.stopPlatformService(dataArg.serviceType);
return {
platformService: {
type: dataArg.serviceType,
displayName: provider.displayName,
resourceTypes: provider.resourceTypes,
status: 'stopped' as const,
},
};
},
),
);
// Get platform service stats
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceStats>(
'getPlatformServiceStats',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const service = this.opsServerRef.oneboxRef.database.getPlatformServiceByType(dataArg.serviceType);
if (!service || !service.containerId) {
throw new plugins.typedrequest.TypedResponseError('Platform service has no container');
}
const stats = await this.opsServerRef.oneboxRef.docker.getContainerStats(service.containerId);
if (!stats) {
throw new plugins.typedrequest.TypedResponseError('Could not retrieve container stats');
}
return { stats };
},
),
);
}
}

View File

@@ -0,0 +1,147 @@
import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class RegistryHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private registerHandlers(): void {
// Get registry tags
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRegistryTags>(
'getRegistryTags',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const tags = await this.opsServerRef.oneboxRef.registry.getImageTags(dataArg.serviceName);
return { tags };
},
),
);
// Get registry tokens
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRegistryTokens>(
'getRegistryTokens',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const rawTokens = this.opsServerRef.oneboxRef.database.getAllRegistryTokens();
const now = Date.now();
const tokens = rawTokens.map((token: any) => {
const isExpired = token.expiresAt !== null && token.expiresAt < now;
let scopeDisplay: string;
if (token.scope === 'all') {
scopeDisplay = 'All services';
} else if (Array.isArray(token.scope)) {
scopeDisplay = token.scope.length === 1 ? token.scope[0] : `${token.scope.length} services`;
} else {
scopeDisplay = 'Unknown';
}
return {
id: token.id!,
name: token.name,
type: token.type,
scope: token.scope,
scopeDisplay,
expiresAt: token.expiresAt,
createdAt: token.createdAt,
lastUsedAt: token.lastUsedAt,
createdBy: token.createdBy,
isExpired,
};
});
return { tokens };
},
),
);
// Create registry token
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRegistryToken>(
'createRegistryToken',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const config = dataArg.tokenConfig;
// Calculate expiration
const now = Date.now();
let expiresAt: number | null = null;
if (config.expiresIn !== 'never') {
const daysMap: Record<string, number> = { '30d': 30, '90d': 90, '365d': 365 };
const days = daysMap[config.expiresIn];
if (days) expiresAt = now + days * 24 * 60 * 60 * 1000;
}
// Generate token
const plainToken = crypto.randomUUID() + crypto.randomUUID();
const encoder = new TextEncoder();
const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(plainToken));
const hashArray = Array.from(new Uint8Array(hashBuffer));
const tokenHash = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
const token = this.opsServerRef.oneboxRef.database.createRegistryToken({
name: config.name,
tokenHash,
type: config.type,
scope: config.scope,
expiresAt,
createdAt: now,
lastUsedAt: null,
createdBy: dataArg.identity.username,
});
let scopeDisplay: string;
if (token.scope === 'all') {
scopeDisplay = 'All services';
} else if (Array.isArray(token.scope)) {
scopeDisplay = token.scope.length === 1 ? token.scope[0] : `${token.scope.length} services`;
} else {
scopeDisplay = 'Unknown';
}
return {
result: {
token: {
id: token.id!,
name: token.name,
type: token.type,
scope: token.scope,
scopeDisplay,
expiresAt: token.expiresAt,
createdAt: token.createdAt,
lastUsedAt: token.lastUsedAt,
createdBy: token.createdBy,
isExpired: false,
},
plainToken,
},
};
},
),
);
// Delete registry token
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRegistryToken>(
'deleteRegistryToken',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const token = this.opsServerRef.oneboxRef.database.getRegistryTokenById(dataArg.tokenId);
if (!token) {
throw new plugins.typedrequest.TypedResponseError('Token not found');
}
this.opsServerRef.oneboxRef.database.deleteRegistryToken(dataArg.tokenId);
return { ok: true };
},
),
);
}
}

View File

@@ -0,0 +1,93 @@
import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class SchedulesHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private registerHandlers(): void {
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackupSchedules>(
'getBackupSchedules',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const schedules = this.opsServerRef.oneboxRef.backupScheduler.getAllSchedules();
return { schedules };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateBackupSchedule>(
'createBackupSchedule',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const schedule = await this.opsServerRef.oneboxRef.backupScheduler.createSchedule(
dataArg.scheduleConfig,
);
return { schedule };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackupSchedule>(
'getBackupSchedule',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const schedule = this.opsServerRef.oneboxRef.backupScheduler.getScheduleById(dataArg.scheduleId);
if (!schedule) {
throw new plugins.typedrequest.TypedResponseError('Schedule not found');
}
return { schedule };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateBackupSchedule>(
'updateBackupSchedule',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const schedule = await this.opsServerRef.oneboxRef.backupScheduler.updateSchedule(
dataArg.scheduleId,
dataArg.updates,
);
return { schedule };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteBackupSchedule>(
'deleteBackupSchedule',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.backupScheduler.deleteSchedule(dataArg.scheduleId);
return { ok: true };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_TriggerBackupSchedule>(
'triggerBackupSchedule',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.backupScheduler.triggerBackup(dataArg.scheduleId);
// triggerBackup is void; the backup is created async by the scheduler
// Return the most recent backup for the schedule
const allBackups = this.opsServerRef.oneboxRef.backupManager.listBackups();
const latestBackup = allBackups.find((b: any) => b.scheduleId === dataArg.scheduleId);
return { backup: latestBackup! };
},
),
);
}
}

View File

@@ -0,0 +1,244 @@
import * as plugins from '../../plugins.ts';
import { logger } from '../../logging.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class ServicesHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private registerHandlers(): void {
// Get all services
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServices>(
'getServices',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const services = this.opsServerRef.oneboxRef.services.listServices();
return { services };
},
),
);
// Get single service
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetService>(
'getService',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
if (!service) {
throw new plugins.typedrequest.TypedResponseError('Service not found');
}
return { service };
},
),
);
// Create service
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateService>(
'createService',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const service = await this.opsServerRef.oneboxRef.services.deployService(dataArg.serviceConfig);
return { service };
},
),
);
// Update service
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateService>(
'updateService',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const service = await this.opsServerRef.oneboxRef.services.updateService(
dataArg.serviceName,
dataArg.updates,
);
return { service };
},
),
);
// Delete service
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteService>(
'deleteService',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.services.removeService(dataArg.serviceName);
return { ok: true };
},
),
);
// Start service
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StartService>(
'startService',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.services.startService(dataArg.serviceName);
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
return { service: service! };
},
),
);
// Stop service
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StopService>(
'stopService',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.services.stopService(dataArg.serviceName);
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
return { service: service! };
},
),
);
// Restart service
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RestartService>(
'restartService',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.services.restartService(dataArg.serviceName);
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
return { service: service! };
},
),
);
// Get service logs
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceLogs>(
'getServiceLogs',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const logs = await this.opsServerRef.oneboxRef.services.getServiceLogs(dataArg.serviceName);
return { logs: String(logs) };
},
),
);
// Get service stats
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceStats>(
'getServiceStats',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
if (!service || !service.containerID) {
throw new plugins.typedrequest.TypedResponseError('Service has no container');
}
const stats = await this.opsServerRef.oneboxRef.docker.getContainerStats(service.containerID);
if (!stats) {
throw new plugins.typedrequest.TypedResponseError('Could not retrieve container stats');
}
return { stats };
},
),
);
// Get service metrics
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceMetrics>(
'getServiceMetrics',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
if (!service || !service.id) {
throw new plugins.typedrequest.TypedResponseError('Service not found');
}
const metrics = this.opsServerRef.oneboxRef.database.getMetrics(service.id, dataArg.limit || 60);
return { metrics };
},
),
);
// Get service platform resources
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServicePlatformResources>(
'getServicePlatformResources',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const rawResources = await this.opsServerRef.oneboxRef.services.getServicePlatformResources(
dataArg.serviceName,
);
const resources = rawResources.map((r: any) => ({
id: r.resource.id,
resourceType: r.resource.resourceType,
resourceName: r.resource.resourceName,
platformService: {
type: r.platformService.type,
name: r.platformService.name,
status: r.platformService.status,
},
envVars: Object.keys(r.credentials).reduce((acc: Record<string, string>, key: string) => {
const value = r.credentials[key];
if (key.toLowerCase().includes('password') || key.toLowerCase().includes('secret')) {
acc[key] = '********';
} else {
acc[key] = value;
}
return acc;
}, {}),
createdAt: r.resource.createdAt,
}));
return { resources };
},
),
);
// Get service backups
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceBackups>(
'getServiceBackups',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const backups = this.opsServerRef.oneboxRef.backupManager.listBackups(dataArg.serviceName);
return { backups };
},
),
);
// Create service backup
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateServiceBackup>(
'createServiceBackup',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const result = await this.opsServerRef.oneboxRef.backupManager.createBackup(dataArg.serviceName);
return { backup: result.backup };
},
),
);
// Get service backup schedules
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceBackupSchedules>(
'getServiceBackupSchedules',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const service = this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
if (!service) {
throw new plugins.typedrequest.TypedResponseError('Service not found');
}
const schedules = this.opsServerRef.oneboxRef.backupScheduler.getSchedulesForService(
dataArg.serviceName,
);
return { schedules };
},
),
);
}
}

View File

@@ -0,0 +1,86 @@
import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class SettingsHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private getSettingsObject(): interfaces.data.ISettings {
const db = this.opsServerRef.oneboxRef.database;
const settingsMap = db.getAllSettings(); // Returns Record<string, string>
return {
cloudflareToken: settingsMap['cloudflareToken'] || '',
cloudflareZoneId: settingsMap['cloudflareZoneId'] || '',
autoRenewCerts: settingsMap['autoRenewCerts'] === 'true',
renewalThreshold: parseInt(settingsMap['renewalThreshold'] || '30', 10),
acmeEmail: settingsMap['acmeEmail'] || '',
httpPort: parseInt(settingsMap['httpPort'] || '80', 10),
httpsPort: parseInt(settingsMap['httpsPort'] || '443', 10),
forceHttps: settingsMap['forceHttps'] === 'true',
};
}
private registerHandlers(): void {
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSettings>(
'getSettings',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const settings = this.getSettingsObject();
return { settings };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateSettings>(
'updateSettings',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const db = this.opsServerRef.oneboxRef.database;
const updates = dataArg.settings;
// Store each setting as key-value pair
for (const [key, value] of Object.entries(updates)) {
if (value !== undefined) {
db.setSetting(key, String(value));
}
}
const settings = this.getSettingsObject();
return { settings };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetBackupPassword>(
'setBackupPassword',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
this.opsServerRef.oneboxRef.database.setSetting('backupPassword', dataArg.password);
return { ok: true };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBackupPasswordStatus>(
'getBackupPasswordStatus',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const backupPassword = this.opsServerRef.oneboxRef.database.getSetting('backupPassword');
const isConfigured = !!backupPassword;
return { status: { isConfigured } };
},
),
);
}
}

View File

@@ -0,0 +1,64 @@
import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class SslHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private registerHandlers(): void {
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ObtainCertificate>(
'obtainCertificate',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.ssl.obtainCertificate(dataArg.domain, false);
const certificate = this.opsServerRef.oneboxRef.ssl.getCertificate(dataArg.domain);
return { certificate: certificate as unknown as interfaces.data.ICertificate };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListCertificates>(
'listCertificates',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const certificates = this.opsServerRef.oneboxRef.ssl.listCertificates();
return { certificates: certificates as unknown as interfaces.data.ICertificate[] };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCertificate>(
'getCertificate',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const certificate = this.opsServerRef.oneboxRef.ssl.getCertificate(dataArg.domain);
if (!certificate) {
throw new plugins.typedrequest.TypedResponseError('Certificate not found');
}
return { certificate: certificate as unknown as interfaces.data.ICertificate };
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RenewCertificate>(
'renewCertificate',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
await this.opsServerRef.oneboxRef.ssl.renewCertificate(dataArg.domain);
const certificate = this.opsServerRef.oneboxRef.ssl.getCertificate(dataArg.domain);
return { certificate: certificate as unknown as interfaces.data.ICertificate };
},
),
);
}
}

View File

@@ -0,0 +1,26 @@
import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
export class StatusHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
private registerHandlers(): void {
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSystemStatus>(
'getSystemStatus',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const status = await this.opsServerRef.oneboxRef.getSystemStatus();
return { status: status as unknown as interfaces.data.ISystemStatus };
},
),
);
}
}