Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eceb5d99c8 | |||
| 0631b7731f | |||
| 4c485cdc0a | |||
| 0f0da0f2ef | |||
| 88367f70eb | |||
| bfcfef79da | |||
| d95270613b | |||
| 14f6746833 |
31
changelog.md
31
changelog.md
@@ -1,5 +1,36 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-03-03 - 1.11.0 - feat(services)
|
||||||
|
map backend service data to UI components, add stats & logs parsing, fetch service stats, and fix logs request param
|
||||||
|
|
||||||
|
- Fix: rename service logs request property from 'lines' to 'tail' when calling typedRequest
|
||||||
|
- Add data transformation helpers: formatBytes, parseImageString, mapStatus, toServiceDetail, toServiceStats, parseLogs
|
||||||
|
- Transform service list and detail props to match @serve.zone/catalog component interfaces (map status, image, repo/tag, timestamps, registry)
|
||||||
|
- Dispatch fetchServiceStatsAction on service click and surface transformed stats with default values to avoid nulls
|
||||||
|
- Parse and normalize logs into timestamp/message pairs for the detail view
|
||||||
|
|
||||||
|
## 2026-03-02 - 1.10.3 - fix(bin)
|
||||||
|
make bin/onebox-wrapper.js executable
|
||||||
|
|
||||||
|
- Metadata-only change: file mode updated for bin/onebox-wrapper.js to include the executable bit
|
||||||
|
- No source or behavior changes to the code
|
||||||
|
|
||||||
|
## 2026-03-02 - 1.10.2 - fix(build)
|
||||||
|
update build/watch configuration, switch to esbuild bundler and tswatch, and bump catalog and tooling dependencies
|
||||||
|
|
||||||
|
- Switch watch script to 'tswatch' (replaced previous concurrently command invoking deno + tswatch).
|
||||||
|
- npmextra.json: set bundler to 'esbuild', enable production mode, include html/index.html in the bundle, and extend watchPatterns to include ./html/**/*.
|
||||||
|
- Backend watcher: expanded watch globs and changed command to include --unstable-ffi and runtime flags (--ephemeral --monitor); restart and debounce kept.
|
||||||
|
- Bump runtime deps: @design.estate/dees-catalog -> ^3.43.3, @serve.zone/catalog -> ^2.5.0.
|
||||||
|
- Bump devDependencies: @git.zone/tsbundle -> ^2.9.0, @git.zone/tswatch -> ^3.2.0.
|
||||||
|
|
||||||
|
## 2026-02-24 - 1.10.1 - fix(package.json)
|
||||||
|
update package metadata
|
||||||
|
|
||||||
|
- Single metadata-only file changed (+1 -1)
|
||||||
|
- No source code or runtime behavior modified; safe patch release
|
||||||
|
- Current package version is 1.10.0; recommend patch bump to 1.10.1
|
||||||
|
|
||||||
## 2026-02-24 - 1.10.0 - feat(opsserver)
|
## 2026-02-24 - 1.10.0 - feat(opsserver)
|
||||||
introduce OpsServer (TypedRequest API) and new lightweight web UI; replace legacy Angular UI and add typed interfaces
|
introduce OpsServer (TypedRequest API) and new lightweight web UI; replace legacy Angular UI and add typed interfaces
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/onebox",
|
"name": "@serve.zone/onebox",
|
||||||
"version": "1.10.0",
|
"version": "1.11.0",
|
||||||
"exports": "./mod.ts",
|
"exports": "./mod.ts",
|
||||||
"nodeModulesDir": "auto",
|
"nodeModulesDir": "auto",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
|
|||||||
@@ -17,15 +17,17 @@
|
|||||||
"from": "./ts_web/index.ts",
|
"from": "./ts_web/index.ts",
|
||||||
"to": "./ts_bundled/bundle.ts",
|
"to": "./ts_bundled/bundle.ts",
|
||||||
"outputMode": "base64ts",
|
"outputMode": "base64ts",
|
||||||
"watchPatterns": ["./ts_web/**/*"],
|
"bundler": "esbuild",
|
||||||
"triggerReload": true
|
"production": true,
|
||||||
|
"watchPatterns": ["./ts_web/**/*", "./html/**/*"],
|
||||||
|
"includeFiles": [{"from": "./html/index.html", "to": "index.html"}]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"watchers": [
|
"watchers": [
|
||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"watch": "./ts/**/*",
|
"watch": ["./ts/**/*", "./ts_interfaces/**/*", "./ts_bundled/**/*"],
|
||||||
"command": "deno run --allow-all mod.ts server",
|
"command": "deno run --allow-all --unstable-ffi mod.ts server --ephemeral --monitor",
|
||||||
"restart": true,
|
"restart": true,
|
||||||
"debounce": 500,
|
"debounce": 500,
|
||||||
"runOnStart": true
|
"runOnStart": true
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/onebox",
|
"name": "@serve.zone/onebox",
|
||||||
"version": "1.10.0",
|
"version": "1.11.0",
|
||||||
"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",
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "node scripts/install-binary.js",
|
"postinstall": "node scripts/install-binary.js",
|
||||||
"watch": "concurrently --kill-others --names \"BACKEND,UI\" --prefix-colors \"cyan,magenta\" \"deno run --allow-all --unstable-ffi --watch mod.ts server --ephemeral --monitor\" \"tswatch\"",
|
"watch": "tswatch",
|
||||||
"build": "tsbundle",
|
"build": "tsbundle",
|
||||||
"bundle": "tsbundle"
|
"bundle": "tsbundle"
|
||||||
},
|
},
|
||||||
@@ -55,13 +55,12 @@
|
|||||||
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
|
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedrequest-interfaces": "^3.0.19",
|
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||||
"@design.estate/dees-catalog": "^3.43.0",
|
"@design.estate/dees-catalog": "^3.43.3",
|
||||||
"@design.estate/dees-element": "^2.1.6",
|
"@design.estate/dees-element": "^2.1.6",
|
||||||
"@serve.zone/catalog": "^2.1.0"
|
"@serve.zone/catalog": "^2.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbundle": "^2.8.4",
|
"@git.zone/tsbundle": "^2.9.0",
|
||||||
"@git.zone/tswatch": "^2.3.13",
|
"@git.zone/tswatch": "^3.2.0"
|
||||||
"concurrently": "^9.1.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
561
pnpm-lock.yaml
generated
561
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/onebox',
|
name: '@serve.zone/onebox',
|
||||||
version: '1.10.0',
|
version: '1.11.0',
|
||||||
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'
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/onebox',
|
name: '@serve.zone/onebox',
|
||||||
version: '1.10.0',
|
version: '1.11.0',
|
||||||
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'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ export const fetchServiceLogsAction = servicesStatePart.createAction<{
|
|||||||
const response = await typedRequest.fire({
|
const response = await typedRequest.fire({
|
||||||
identity: context.identity!,
|
identity: context.identity!,
|
||||||
serviceName: dataArg.name,
|
serviceName: dataArg.name,
|
||||||
lines: dataArg.lines || 200,
|
tail: dataArg.lines || 200,
|
||||||
});
|
});
|
||||||
return { ...statePartArg.getState(), currentServiceLogs: response.logs };
|
return { ...statePartArg.getState(), currentServiceLogs: response.logs };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import * as shared from './shared/index.js';
|
import * as shared from './shared/index.js';
|
||||||
import * as appstate from '../appstate.js';
|
import * as appstate from '../appstate.js';
|
||||||
|
import * as interfaces from '../../ts_interfaces/index.js';
|
||||||
import {
|
import {
|
||||||
DeesElement,
|
DeesElement,
|
||||||
customElement,
|
customElement,
|
||||||
@@ -11,6 +12,91 @@ import {
|
|||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Data transformation helpers
|
||||||
|
// Maps backend data shapes to @serve.zone/catalog component interfaces
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
function formatBytes(bytes: number): string {
|
||||||
|
if (!bytes || bytes === 0) return '0 B';
|
||||||
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const k = 1024;
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
const value = bytes / Math.pow(k, i);
|
||||||
|
return `${value.toFixed(1)} ${units[i]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseImageString(image: string): { repository: string; tag: string } {
|
||||||
|
const lastColon = image.lastIndexOf(':');
|
||||||
|
const lastSlash = image.lastIndexOf('/');
|
||||||
|
if (lastColon > lastSlash && lastColon > 0) {
|
||||||
|
return {
|
||||||
|
repository: image.substring(0, lastColon),
|
||||||
|
tag: image.substring(lastColon + 1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { repository: image, tag: 'latest' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStatus(status: string): 'running' | 'stopped' | 'starting' | 'error' {
|
||||||
|
switch (status) {
|
||||||
|
case 'running': return 'running';
|
||||||
|
case 'starting': return 'starting';
|
||||||
|
case 'failed': return 'error';
|
||||||
|
case 'stopped':
|
||||||
|
case 'stopping':
|
||||||
|
default: return 'stopped';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toServiceDetail(service: interfaces.data.IService) {
|
||||||
|
const parsed = parseImageString(service.image);
|
||||||
|
return {
|
||||||
|
name: service.name,
|
||||||
|
status: mapStatus(service.status),
|
||||||
|
image: service.image,
|
||||||
|
port: service.port,
|
||||||
|
domain: service.domain || null,
|
||||||
|
containerId: service.containerID || '',
|
||||||
|
created: service.createdAt ? new Date(service.createdAt).toLocaleString() : '-',
|
||||||
|
updated: service.updatedAt ? new Date(service.updatedAt).toLocaleString() : '-',
|
||||||
|
registry: service.useOneboxRegistry ? 'Onebox Registry' : (service.registry || 'Docker Hub'),
|
||||||
|
repository: service.registryRepository || parsed.repository,
|
||||||
|
tag: service.registryImageTag || parsed.tag,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toServiceStats(stats: interfaces.data.IContainerStats) {
|
||||||
|
return {
|
||||||
|
cpu: stats.cpuPercent,
|
||||||
|
memory: formatBytes(stats.memoryUsed),
|
||||||
|
memoryLimit: formatBytes(stats.memoryLimit),
|
||||||
|
networkIn: formatBytes(stats.networkRx),
|
||||||
|
networkOut: formatBytes(stats.networkTx),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLogs(logs: any): Array<{ timestamp: string; message: string }> {
|
||||||
|
if (Array.isArray(logs)) {
|
||||||
|
return logs.map((entry: any) => ({
|
||||||
|
timestamp: entry.timestamp ? String(entry.timestamp) : '',
|
||||||
|
message: entry.message || String(entry),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultStats = { cpu: 0, memory: '0 B', memoryLimit: '0 B', networkIn: '0 B', networkOut: '0 B' };
|
||||||
|
|
||||||
@customElement('ob-view-services')
|
@customElement('ob-view-services')
|
||||||
export class ObViewServices extends DeesElement {
|
export class ObViewServices extends DeesElement {
|
||||||
@state()
|
@state()
|
||||||
@@ -86,10 +172,16 @@ export class ObViewServices extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderListView(): TemplateResult {
|
private renderListView(): TemplateResult {
|
||||||
|
const mappedServices = this.servicesState.services.map((s) => ({
|
||||||
|
name: s.name,
|
||||||
|
image: s.image,
|
||||||
|
domain: s.domain || null,
|
||||||
|
status: mapStatus(s.status),
|
||||||
|
}));
|
||||||
return html`
|
return html`
|
||||||
<ob-sectionheading>Services</ob-sectionheading>
|
<ob-sectionheading>Services</ob-sectionheading>
|
||||||
<sz-services-list-view
|
<sz-services-list-view
|
||||||
.services=${this.servicesState.services}
|
.services=${mappedServices}
|
||||||
@service-click=${(e: CustomEvent) => {
|
@service-click=${(e: CustomEvent) => {
|
||||||
this.selectedServiceName = e.detail.name || e.detail.service?.name;
|
this.selectedServiceName = e.detail.name || e.detail.service?.name;
|
||||||
appstate.servicesStatePart.dispatchAction(appstate.fetchServiceAction, {
|
appstate.servicesStatePart.dispatchAction(appstate.fetchServiceAction, {
|
||||||
@@ -98,6 +190,9 @@ export class ObViewServices extends DeesElement {
|
|||||||
appstate.servicesStatePart.dispatchAction(appstate.fetchServiceLogsAction, {
|
appstate.servicesStatePart.dispatchAction(appstate.fetchServiceLogsAction, {
|
||||||
name: this.selectedServiceName,
|
name: this.selectedServiceName,
|
||||||
});
|
});
|
||||||
|
appstate.servicesStatePart.dispatchAction(appstate.fetchServiceStatsAction, {
|
||||||
|
name: this.selectedServiceName,
|
||||||
|
});
|
||||||
this.currentView = 'detail';
|
this.currentView = 'detail';
|
||||||
}}
|
}}
|
||||||
@service-action=${(e: CustomEvent) => this.handleServiceAction(e)}
|
@service-action=${(e: CustomEvent) => this.handleServiceAction(e)}
|
||||||
@@ -124,12 +219,19 @@ export class ObViewServices extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderDetailView(): TemplateResult {
|
private renderDetailView(): TemplateResult {
|
||||||
|
const service = this.servicesState.currentService;
|
||||||
|
const transformedService = service ? toServiceDetail(service) : null;
|
||||||
|
const transformedStats = this.servicesState.currentServiceStats
|
||||||
|
? toServiceStats(this.servicesState.currentServiceStats)
|
||||||
|
: defaultStats;
|
||||||
|
const transformedLogs = parseLogs(this.servicesState.currentServiceLogs);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ob-sectionheading>Service Details</ob-sectionheading>
|
<ob-sectionheading>Service Details</ob-sectionheading>
|
||||||
<sz-service-detail-view
|
<sz-service-detail-view
|
||||||
.service=${this.servicesState.currentService}
|
.service=${transformedService}
|
||||||
.logs=${this.servicesState.currentServiceLogs}
|
.logs=${transformedLogs}
|
||||||
.stats=${this.servicesState.currentServiceStats}
|
.stats=${transformedStats}
|
||||||
@back=${() => {
|
@back=${() => {
|
||||||
this.currentView = 'list';
|
this.currentView = 'list';
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user