Compare commits

...

10 Commits

Author SHA1 Message Date
c420a30341 v1.18.2
Some checks failed
Publish to npm / npm-publish (push) Failing after 9s
CI / Type Check & Lint (push) Failing after 28s
CI / Build Test (Current Platform) (push) Successful in 59s
CI / Build All Platforms (push) Successful in 2m0s
Release / build-and-release (push) Successful in 3m41s
2026-03-16 14:14:55 +00:00
fe109f0953 fix(repo): no changes to commit 2026-03-16 14:14:55 +00:00
012dce63b1 v1.18.1
Some checks failed
Publish to npm / npm-publish (push) Failing after 10s
Release / build-and-release (push) Successful in 4m0s
2026-03-16 14:14:34 +00:00
54780482c7 fix(repo): no changes to commit 2026-03-16 14:14:34 +00:00
7ab0fb3c1f v1.18.0
Some checks failed
Publish to npm / npm-publish (push) Failing after 9s
CI / Type Check & Lint (push) Failing after 27s
CI / Build Test (Current Platform) (push) Successful in 58s
CI / Build All Platforms (push) Successful in 1m52s
Release / build-and-release (push) Successful in 2m58s
2026-03-16 13:51:43 +00:00
713fda2a86 feat(platform-services): add platform service log retrieval and display in the services UI 2026-03-16 13:51:43 +00:00
ec32c19300 v1.17.4
Some checks failed
CI / Type Check & Lint (push) Failing after 30s
Publish to npm / npm-publish (push) Failing after 24s
CI / Build Test (Current Platform) (push) Successful in 1m1s
CI / Build All Platforms (push) Successful in 2m12s
Release / build-and-release (push) Successful in 4m0s
2026-03-16 13:26:56 +00:00
7d1d91157c fix(docs): add hello world running screenshot for documentation 2026-03-16 13:26:56 +00:00
b69c96c240 v1.17.3
Some checks failed
CI / Build Test (Current Platform) (push) Failing after 6s
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 32s
CI / Build All Platforms (push) Successful in 2m4s
Release / build-and-release (push) Successful in 3m13s
2026-03-16 13:05:47 +00:00
9ee8851d03 fix(mongodb): downgrade the MongoDB service image to 4.4 and use the legacy mongo shell for container operations 2026-03-16 13:05:47 +00:00
15 changed files with 135 additions and 26 deletions

View File

@@ -1,5 +1,32 @@
# Changelog # Changelog
## 2026-03-16 - 1.18.2 - fix(repo)
no changes to commit
## 2026-03-16 - 1.18.1 - fix(repo)
no changes to commit
## 2026-03-16 - 1.18.0 - feat(platform-services)
add platform service log retrieval and display in the services UI
- add typed request support in the ops server to fetch Docker logs for platform service containers
- store fetched platform service logs in web app state and load them when opening platform service details
- render platform service logs in the services detail view and add sidebar icons for main navigation tabs
## 2026-03-16 - 1.17.4 - fix(docs)
add hello world running screenshot for documentation
- Adds a new PNG asset showing the application in a running hello world state.
- Supports project documentation or README usage without changing runtime behavior.
## 2026-03-16 - 1.17.3 - fix(mongodb)
downgrade the MongoDB service image to 4.4 and use the legacy mongo shell for container operations
- changes the default MongoDB container image from mongo:7 to mongo:4.4
- replaces mongosh with mongo for health checks, provisioning, and deprovisioning inside the container
## 2026-03-16 - 1.17.2 - fix(platform-services) ## 2026-03-16 - 1.17.2 - fix(platform-services)
provision ClickHouse, MinIO, and MongoDB resources via docker exec instead of host port access provision ClickHouse, MinIO, and MongoDB resources via docker exec instead of host port access

View File

@@ -1,6 +1,6 @@
{ {
"name": "@serve.zone/onebox", "name": "@serve.zone/onebox",
"version": "1.17.2", "version": "1.18.2",
"exports": "./mod.ts", "exports": "./mod.ts",
"tasks": { "tasks": {
"test": "deno test --allow-all test/", "test": "deno test --allow-all test/",

BIN
hello-world-running.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "@serve.zone/onebox", "name": "@serve.zone/onebox",
"version": "1.17.2", "version": "1.18.2",
"description": "Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers", "description": "Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers",
"main": "mod.ts", "main": "mod.ts",
"type": "module", "type": "module",

BIN
sidebar-icons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/onebox', name: '@serve.zone/onebox',
version: '1.17.2', version: '1.18.2',
description: 'Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers' description: 'Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers'
} }

View File

@@ -28,7 +28,7 @@ export class MongoDBProvider extends BasePlatformServiceProvider {
getDefaultConfig(): IPlatformServiceConfig { getDefaultConfig(): IPlatformServiceConfig {
return { return {
image: 'mongo:7', image: 'mongo:4.4',
port: 27017, port: 27017,
volumes: ['/var/lib/onebox/mongodb:/data/db'], volumes: ['/var/lib/onebox/mongodb:/data/db'],
environment: { environment: {
@@ -165,7 +165,7 @@ export class MongoDBProvider extends BasePlatformServiceProvider {
// This avoids network issues with overlay networks // This avoids network issues with overlay networks
const result = await this.oneboxRef.docker.execInContainer( const result = await this.oneboxRef.docker.execInContainer(
platformService.containerId, platformService.containerId,
['mongosh', '--eval', 'db.adminCommand("ping")', '--username', adminCreds.username, '--password', adminCreds.password, '--authenticationDatabase', 'admin', '--quiet'] ['mongo', '--eval', 'db.adminCommand("ping")', '--username', adminCreds.username, '--password', adminCreds.password, '--authenticationDatabase', 'admin', '--quiet']
); );
if (result.exitCode === 0) { if (result.exitCode === 0) {
@@ -201,8 +201,8 @@ export class MongoDBProvider extends BasePlatformServiceProvider {
const escapedPassword = password.replace(/'/g, "'\\''"); const escapedPassword = password.replace(/'/g, "'\\''");
const escapedAdminPassword = adminCreds.password.replace(/'/g, "'\\''"); const escapedAdminPassword = adminCreds.password.replace(/'/g, "'\\''");
// Create database and user via mongosh inside the container // Create database and user via mongo inside the container
const mongoshScript = ` const mongoScript = `
db = db.getSiblingDB('${dbName}'); db = db.getSiblingDB('${dbName}');
db.createCollection('_onebox_init'); db.createCollection('_onebox_init');
db.createUser({ db.createUser({
@@ -216,12 +216,12 @@ export class MongoDBProvider extends BasePlatformServiceProvider {
const result = await this.oneboxRef.docker.execInContainer( const result = await this.oneboxRef.docker.execInContainer(
platformService.containerId, platformService.containerId,
[ [
'mongosh', 'mongo',
'--username', adminCreds.username, '--username', adminCreds.username,
'--password', escapedAdminPassword, '--password', escapedAdminPassword,
'--authenticationDatabase', 'admin', '--authenticationDatabase', 'admin',
'--quiet', '--quiet',
'--eval', mongoshScript, '--eval', mongoScript,
] ]
); );
@@ -268,7 +268,7 @@ export class MongoDBProvider extends BasePlatformServiceProvider {
logger.info(`Deprovisioning MongoDB database '${resource.resourceName}'...`); logger.info(`Deprovisioning MongoDB database '${resource.resourceName}'...`);
const mongoshScript = ` const mongoScript = `
db = db.getSiblingDB('${resource.resourceName}'); db = db.getSiblingDB('${resource.resourceName}');
try { db.dropUser('${credentials.username}'); } catch(e) { print('User drop failed: ' + e); } try { db.dropUser('${credentials.username}'); } catch(e) { print('User drop failed: ' + e); }
db.dropDatabase(); db.dropDatabase();
@@ -278,12 +278,12 @@ export class MongoDBProvider extends BasePlatformServiceProvider {
const result = await this.oneboxRef.docker.execInContainer( const result = await this.oneboxRef.docker.execInContainer(
platformService.containerId, platformService.containerId,
[ [
'mongosh', 'mongo',
'--username', adminCreds.username, '--username', adminCreds.username,
'--password', escapedAdminPassword, '--password', escapedAdminPassword,
'--authenticationDatabase', 'admin', '--authenticationDatabase', 'admin',
'--quiet', '--quiet',
'--eval', mongoshScript, '--eval', mongoScript,
] ]
); );

View File

@@ -165,5 +165,42 @@ export class PlatformHandler {
}, },
), ),
); );
// Get platform service logs
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceLogs>(
'getPlatformServiceLogs',
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 tail = dataArg.tail || 100;
const rawLogs = await this.opsServerRef.oneboxRef.docker.getContainerLogs(service.containerId, tail);
// Parse raw log output into structured entries
const logLines = (rawLogs.stdout + rawLogs.stderr)
.split('\n')
.filter((line: string) => line.trim());
const logs = logLines.map((line: string, index: number) => {
const isError = line.toLowerCase().includes('error') || line.toLowerCase().includes('fatal');
const isWarn = line.toLowerCase().includes('warn');
return {
id: index,
serviceId: 0,
timestamp: Date.now(),
message: line,
level: (isError ? 'error' : isWarn ? 'warn' : 'info') as 'info' | 'warn' | 'error' | 'debug',
source: 'stdout' as const,
};
});
return { logs };
},
),
);
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -69,3 +69,18 @@ export interface IReq_GetPlatformServiceStats extends plugins.typedrequestInterf
stats: data.IContainerStats; stats: data.IContainerStats;
}; };
} }
export interface IReq_GetPlatformServiceLogs extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetPlatformServiceLogs
> {
method: 'getPlatformServiceLogs';
request: {
identity: data.IIdentity;
serviceType: data.TPlatformServiceType;
tail?: number;
};
response: {
logs: data.ILogEntry[];
};
}

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/onebox', name: '@serve.zone/onebox',
version: '1.17.2', version: '1.18.2',
description: 'Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers' description: 'Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers'
} }

View File

@@ -27,6 +27,7 @@ export interface IServicesState {
platformServices: interfaces.data.IPlatformService[]; platformServices: interfaces.data.IPlatformService[];
currentPlatformService: interfaces.data.IPlatformService | null; currentPlatformService: interfaces.data.IPlatformService | null;
currentPlatformServiceStats: interfaces.data.IContainerStats | null; currentPlatformServiceStats: interfaces.data.IContainerStats | null;
currentPlatformServiceLogs: interfaces.data.ILogEntry[];
} }
export interface INetworkState { export interface INetworkState {
@@ -90,6 +91,7 @@ export const servicesStatePart = await appState.getStatePart<IServicesState>(
platformServices: [], platformServices: [],
currentPlatformService: null, currentPlatformService: null,
currentPlatformServiceStats: null, currentPlatformServiceStats: null,
currentPlatformServiceLogs: [],
}, },
'soft', 'soft',
); );
@@ -497,6 +499,27 @@ export const fetchPlatformServiceStatsAction = servicesStatePart.createAction<{
} }
}); });
export const fetchPlatformServiceLogsAction = servicesStatePart.createAction<{
serviceType: interfaces.data.TPlatformServiceType;
tail?: number;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetPlatformServiceLogs
>('/typedrequest', 'getPlatformServiceLogs');
const response = await typedRequest.fire({
identity: context.identity!,
serviceType: dataArg.serviceType,
tail: dataArg.tail || 100,
});
return { ...statePartArg.getState(), currentPlatformServiceLogs: response.logs };
} catch (err) {
console.error('Failed to fetch platform service logs:', err);
return { ...statePartArg.getState(), currentPlatformServiceLogs: [] };
}
});
// ============================================================================ // ============================================================================
// Network Actions // Network Actions
// ============================================================================ // ============================================================================

View File

@@ -37,15 +37,15 @@ export class ObAppShell extends DeesElement {
accessor loginError: string = ''; accessor loginError: string = '';
private viewTabs = [ private viewTabs = [
{ name: 'Dashboard', element: (async () => (await import('./ob-view-dashboard.js')).ObViewDashboard)() }, { name: 'Dashboard', iconName: 'lucide:layoutDashboard', element: (async () => (await import('./ob-view-dashboard.js')).ObViewDashboard)() },
{ name: 'Services', element: (async () => (await import('./ob-view-services.js')).ObViewServices)() }, { name: 'Services', iconName: 'lucide:boxes', element: (async () => (await import('./ob-view-services.js')).ObViewServices)() },
{ name: 'Network', element: (async () => (await import('./ob-view-network.js')).ObViewNetwork)() }, { name: 'Network', iconName: 'lucide:network', element: (async () => (await import('./ob-view-network.js')).ObViewNetwork)() },
{ name: 'Registries', element: (async () => (await import('./ob-view-registries.js')).ObViewRegistries)() }, { name: 'Registries', iconName: 'lucide:package', element: (async () => (await import('./ob-view-registries.js')).ObViewRegistries)() },
{ name: 'Tokens', element: (async () => (await import('./ob-view-tokens.js')).ObViewTokens)() }, { name: 'Tokens', iconName: 'lucide:key', element: (async () => (await import('./ob-view-tokens.js')).ObViewTokens)() },
{ name: 'Settings', element: (async () => (await import('./ob-view-settings.js')).ObViewSettings)() }, { name: 'Settings', iconName: 'lucide:settings', element: (async () => (await import('./ob-view-settings.js')).ObViewSettings)() },
]; ];
private resolvedViewTabs: Array<{ name: string; element: any }> = []; private resolvedViewTabs: Array<{ name: string; iconName?: string; element: any }> = [];
constructor() { constructor() {
super(); super();
@@ -104,6 +104,7 @@ export class ObAppShell extends DeesElement {
this.resolvedViewTabs = await Promise.all( this.resolvedViewTabs = await Promise.all(
this.viewTabs.map(async (tab) => ({ this.viewTabs.map(async (tab) => ({
name: tab.name, name: tab.name,
iconName: tab.iconName,
element: await tab.element, element: await tab.element,
})), })),
); );

View File

@@ -25,6 +25,7 @@ export class ObViewDashboard extends DeesElement {
platformServices: [], platformServices: [],
currentPlatformService: null, currentPlatformService: null,
currentPlatformServiceStats: null, currentPlatformServiceStats: null,
currentPlatformServiceLogs: [],
}; };
@state() @state()

View File

@@ -108,6 +108,7 @@ export class ObViewServices extends DeesElement {
platformServices: [], platformServices: [],
currentPlatformService: null, currentPlatformService: null,
currentPlatformServiceStats: null, currentPlatformServiceStats: null,
currentPlatformServiceLogs: [],
}; };
@state() @state()
@@ -357,10 +358,10 @@ export class ObViewServices extends DeesElement {
private navigateToPlatformDetail(type: string): void { private navigateToPlatformDetail(type: string): void {
this.selectedPlatformType = type; this.selectedPlatformType = type;
// Fetch stats for this platform service // Fetch stats and logs for this platform service
appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServiceStatsAction, { const serviceType = type as interfaces.data.TPlatformServiceType;
serviceType: type as interfaces.data.TPlatformServiceType, appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServiceStatsAction, { serviceType });
}); appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServiceLogsAction, { serviceType });
this.currentView = 'platform-detail'; this.currentView = 'platform-detail';
} }
@@ -398,7 +399,11 @@ export class ObViewServices extends DeesElement {
metrics, metrics,
} }
: null} : null}
.logs=${[]} .logs=${this.servicesState.currentPlatformServiceLogs.map((log) => ({
timestamp: new Date(log.timestamp).toLocaleString(),
level: log.level,
message: log.message,
}))}
@back=${() => { @back=${() => {
this.currentView = 'list'; this.currentView = 'list';
}} }}