feat(ops-dashboard): stream user service logs to the ops dashboard and resolve service containers for Docker log streaming
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/onebox',
|
||||
version: '1.19.12',
|
||||
version: '1.20.0',
|
||||
description: 'Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers'
|
||||
}
|
||||
|
||||
@@ -997,6 +997,37 @@ socketRouter.addTypedHandler(
|
||||
),
|
||||
);
|
||||
|
||||
// Handle server-pushed user service log entries
|
||||
socketRouter.addTypedHandler(
|
||||
new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushServiceLog>(
|
||||
'pushServiceLog',
|
||||
async (dataArg) => {
|
||||
const state = servicesStatePart.getState();
|
||||
// Only append if we're currently viewing this service
|
||||
if (!state.currentService || state.currentService.name !== dataArg.serviceName) {
|
||||
return {};
|
||||
}
|
||||
const entry: interfaces.data.ILogEntry = {
|
||||
id: state.currentServiceLogs.length,
|
||||
serviceId: 0,
|
||||
timestamp: new Date(dataArg.entry.timestamp).getTime(),
|
||||
message: dataArg.entry.message,
|
||||
level: dataArg.entry.level as 'info' | 'warn' | 'error' | 'debug',
|
||||
source: 'stdout',
|
||||
};
|
||||
const updated = [...state.currentServiceLogs, entry];
|
||||
if (updated.length > 2000) {
|
||||
updated.splice(0, updated.length - 2000);
|
||||
}
|
||||
servicesStatePart.setState({
|
||||
...state,
|
||||
currentServiceLogs: updated,
|
||||
});
|
||||
return {};
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
async function connectSocket() {
|
||||
if (socketClient) return;
|
||||
try {
|
||||
|
||||
@@ -76,20 +76,29 @@ function toServiceStats(stats: interfaces.data.IContainerStats) {
|
||||
};
|
||||
}
|
||||
|
||||
function parseLogs(logs: any): Array<{ timestamp: string; message: string }> {
|
||||
function parseLogs(logs: any): Array<{ timestamp: string; message: string; level?: string }> {
|
||||
if (Array.isArray(logs)) {
|
||||
return logs.map((entry: any) => ({
|
||||
timestamp: entry.timestamp ? String(entry.timestamp) : '',
|
||||
message: entry.message || String(entry),
|
||||
}));
|
||||
return logs.map((entry: any) => {
|
||||
const ts = entry.timestamp
|
||||
? (typeof entry.timestamp === 'number' ? new Date(entry.timestamp).toISOString() : String(entry.timestamp))
|
||||
: new Date().toISOString();
|
||||
const message = entry.message || String(entry);
|
||||
const level = entry.level || 'info';
|
||||
return { timestamp: ts, message, level };
|
||||
});
|
||||
}
|
||||
if (typeof logs === 'string' && logs.trim()) {
|
||||
return logs.split('\n').filter((line: string) => line.trim()).map((line: string) => {
|
||||
const match = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s+(.*)/);
|
||||
if (match) {
|
||||
return { timestamp: match[1], message: match[2] };
|
||||
}
|
||||
return { timestamp: '', message: line };
|
||||
const timestamp = match ? match[1] : new Date().toISOString();
|
||||
const message = match ? match[2] : line;
|
||||
const msgLower = message.toLowerCase();
|
||||
const level = msgLower.includes('error') || msgLower.includes('fatal')
|
||||
? 'error'
|
||||
: msgLower.includes('warn')
|
||||
? 'warn'
|
||||
: 'info';
|
||||
return { timestamp, message, level };
|
||||
});
|
||||
}
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user