Compare commits

...

8 Commits

Author SHA1 Message Date
cd06c74cc3 v1.17.1
Some checks failed
Publish to npm / npm-publish (push) Failing after 10s
CI / Type Check & Lint (push) Failing after 41s
CI / Build Test (Current Platform) (push) Successful in 1m12s
Release / build-and-release (push) Failing after 1m54s
CI / Build All Platforms (push) Successful in 2m17s
2026-03-16 12:40:39 +00:00
d3acc720ca fix(repo): no changes to commit 2026-03-16 12:40:39 +00:00
1b6de75097 v1.17.0
Some checks failed
Publish to npm / npm-publish (push) Failing after 10s
CI / Type Check & Lint (push) Failing after 29s
CI / Build Test (Current Platform) (push) Successful in 56s
Release / build-and-release (push) Failing after 37s
CI / Build All Platforms (push) Successful in 2m7s
2026-03-16 12:36:02 +00:00
497f8f59a7 feat(web/services): add deploy service action to the services view 2026-03-16 12:36:02 +00:00
0c7d65e4ad v1.16.0
Some checks failed
Publish to npm / npm-publish (push) Failing after 10s
CI / Type Check & Lint (push) Failing after 28s
CI / Build Test (Current Platform) (push) Successful in 52s
CI / Build All Platforms (push) Successful in 1m50s
Release / build-and-release (push) Successful in 2m49s
2026-03-16 11:45:56 +00:00
3f2cd074ce feat(services): add platform service navigation and stats in the services UI 2026-03-16 11:45:56 +00:00
59ed7233bd v1.15.3
Some checks failed
CI / Build All Platforms (push) Failing after 3s
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 25s
CI / Build Test (Current Platform) (push) Successful in 54s
Release / build-and-release (push) Successful in 2m35s
2026-03-16 11:07:00 +00:00
01e3ba16c4 fix(install): refresh systemd service configuration before restarting previously running installations 2026-03-16 11:07:00 +00:00
12 changed files with 221 additions and 21 deletions

View File

@@ -1,5 +1,31 @@
# Changelog # Changelog
## 2026-03-16 - 1.17.1 - fix(repo)
no changes to commit
## 2026-03-16 - 1.17.0 - feat(web/services)
add deploy service action to the services view
- Adds a prominent "Deploy Service" button to the services page header.
- Routes users into the create service view directly from the services listing.
- Includes a new service creation form screenshot asset for the updated interface.
## 2026-03-16 - 1.16.0 - feat(services)
add platform service navigation and stats in the services UI
- add platform service stats state and fetch action
- show platform services in the services list and open a platform detail view
- enable dashboard clicks to jump directly to the selected platform service
- refresh platform service stats after start and restart actions
- bump @serve.zone/catalog to ^2.6.0 for the new platform service UI components
## 2026-03-16 - 1.15.3 - fix(install)
refresh systemd service configuration before restarting previously running installations
- Re-enable the systemd service during updates so unit file changes are applied before restart
- Add a log message indicating the service configuration is being refreshed
## 2026-03-16 - 1.15.2 - fix(systemd) ## 2026-03-16 - 1.15.2 - fix(systemd)
set HOME and DENO_DIR for the systemd service environment set HOME and DENO_DIR for the systemd service environment

BIN
create-service-form.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

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

View File

@@ -250,8 +250,10 @@ echo ""
mkdir -p /var/lib/onebox mkdir -p /var/lib/onebox
mkdir -p /var/www/certbot mkdir -p /var/www/certbot
# Restart service if it was running before update # Re-enable and restart service if it was previously running (refreshes unit file)
if [ $SERVICE_WAS_RUNNING -eq 1 ]; then if [ $SERVICE_WAS_RUNNING -eq 1 ]; then
echo "Refreshing systemd service..."
onebox systemd enable
echo "Restarting Onebox service..." echo "Restarting Onebox service..."
systemctl restart "$SERVICE_NAME" systemctl restart "$SERVICE_NAME"
echo "Service restarted successfully." echo "Service restarted successfully."

View File

@@ -1,6 +1,6 @@
{ {
"name": "@serve.zone/onebox", "name": "@serve.zone/onebox",
"version": "1.15.2", "version": "1.17.1",
"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",
@@ -57,7 +57,7 @@
"@api.global/typedrequest-interfaces": "^3.0.19", "@api.global/typedrequest-interfaces": "^3.0.19",
"@design.estate/dees-catalog": "^3.43.3", "@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.5.0" "@serve.zone/catalog": "^2.6.0"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbundle": "^2.9.0", "@git.zone/tsbundle": "^2.9.0",

10
pnpm-lock.yaml generated
View File

@@ -18,8 +18,8 @@ importers:
specifier: ^2.1.6 specifier: ^2.1.6
version: 2.2.3 version: 2.2.3
'@serve.zone/catalog': '@serve.zone/catalog':
specifier: ^2.5.0 specifier: ^2.6.0
version: 2.5.0(@tiptap/pm@2.27.2) version: 2.6.0(@tiptap/pm@2.27.2)
devDependencies: devDependencies:
'@git.zone/tsbundle': '@git.zone/tsbundle':
specifier: ^2.9.0 specifier: ^2.9.0
@@ -836,8 +836,8 @@ packages:
'@sec-ant/readable-stream@0.4.1': '@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
'@serve.zone/catalog@2.5.0': '@serve.zone/catalog@2.6.0':
resolution: {integrity: sha512-bRwk7pbDxUB471wUAS7p22MTOOBCHlMWijsE43K9tDAPcxlRarhtf2Dgx0Y25s/dFXqj2JHwe6jjE84S80jFzg==} resolution: {integrity: sha512-Gp91Ed0MMLMPSAZrH2/UimGxU9AaTu5IPUobPD49PSvh3UnUl+HEFiy81kpKJhW16V37N3/vcNgZrn2VI7/LxQ==}
'@tempfix/idb@8.0.3': '@tempfix/idb@8.0.3':
resolution: {integrity: sha512-hPJQKO7+oAIY+pDNImrZ9QAINbz9KmwT+yO4iRVwdPanok2YKpaUxdJzIvCUwY0YgAawlvYdffbLvRLV5hbs2g==} resolution: {integrity: sha512-hPJQKO7+oAIY+pDNImrZ9QAINbz9KmwT+yO4iRVwdPanok2YKpaUxdJzIvCUwY0YgAawlvYdffbLvRLV5hbs2g==}
@@ -3474,7 +3474,7 @@ snapshots:
'@sec-ant/readable-stream@0.4.1': {} '@sec-ant/readable-stream@0.4.1': {}
'@serve.zone/catalog@2.5.0(@tiptap/pm@2.27.2)': '@serve.zone/catalog@2.6.0(@tiptap/pm@2.27.2)':
dependencies: dependencies:
'@design.estate/dees-catalog': 3.48.5(@tiptap/pm@2.27.2) '@design.estate/dees-catalog': 3.48.5(@tiptap/pm@2.27.2)
'@design.estate/dees-domtools': 2.5.1 '@design.estate/dees-domtools': 2.5.1

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/onebox', name: '@serve.zone/onebox',
version: '1.15.2', version: '1.17.1',
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

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/onebox', name: '@serve.zone/onebox',
version: '1.15.2', version: '1.17.1',
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

@@ -26,6 +26,7 @@ export interface IServicesState {
currentServiceStats: interfaces.data.IContainerStats | null; currentServiceStats: interfaces.data.IContainerStats | null;
platformServices: interfaces.data.IPlatformService[]; platformServices: interfaces.data.IPlatformService[];
currentPlatformService: interfaces.data.IPlatformService | null; currentPlatformService: interfaces.data.IPlatformService | null;
currentPlatformServiceStats: interfaces.data.IContainerStats | null;
} }
export interface INetworkState { export interface INetworkState {
@@ -88,6 +89,7 @@ export const servicesStatePart = await appState.getStatePart<IServicesState>(
currentServiceStats: null, currentServiceStats: null,
platformServices: [], platformServices: [],
currentPlatformService: null, currentPlatformService: null,
currentPlatformServiceStats: null,
}, },
'soft', 'soft',
); );
@@ -476,6 +478,25 @@ export const stopPlatformServiceAction = servicesStatePart.createAction<{
} }
}); });
export const fetchPlatformServiceStatsAction = servicesStatePart.createAction<{
serviceType: interfaces.data.TPlatformServiceType;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetPlatformServiceStats
>('/typedrequest', 'getPlatformServiceStats');
const response = await typedRequest.fire({
identity: context.identity!,
serviceType: dataArg.serviceType,
});
return { ...statePartArg.getState(), currentPlatformServiceStats: response.stats };
} catch (err) {
console.error('Failed to fetch platform service stats:', err);
return { ...statePartArg.getState(), currentPlatformServiceStats: null };
}
});
// ============================================================================ // ============================================================================
// Network Actions // Network Actions
// ============================================================================ // ============================================================================

View File

@@ -24,6 +24,7 @@ export class ObViewDashboard extends DeesElement {
currentServiceStats: null, currentServiceStats: null,
platformServices: [], platformServices: [],
currentPlatformService: null, currentPlatformService: null,
currentPlatformServiceStats: null,
}; };
@state() @state()
@@ -149,6 +150,7 @@ export class ObViewDashboard extends DeesElement {
], ],
}} }}
@action-click=${(e: CustomEvent) => this.handleQuickAction(e)} @action-click=${(e: CustomEvent) => this.handleQuickAction(e)}
@service-click=${(e: CustomEvent) => this.handlePlatformServiceClick(e)}
></sz-dashboard-view> ></sz-dashboard-view>
`; `;
} }
@@ -161,4 +163,21 @@ export class ObViewDashboard extends DeesElement {
appstate.uiStatePart.dispatchAction(appstate.setActiveViewAction, { view: 'network' }); appstate.uiStatePart.dispatchAction(appstate.setActiveViewAction, { view: 'network' });
} }
} }
private handlePlatformServiceClick(e: CustomEvent) {
// Find the platform service type from the click event
const name = e.detail?.name;
const ps = this.servicesState.platformServices.find(
(p) => p.displayName === name,
);
if (ps) {
// Navigate to services tab — the ObViewServices component will pick up the type
// Store the selected platform type so the services view can open it
appstate.servicesStatePart.setState({
...appstate.servicesStatePart.getState(),
currentPlatformService: ps,
});
appstate.uiStatePart.dispatchAction(appstate.setActiveViewAction, { view: 'services' });
}
}
} }

View File

@@ -107,6 +107,7 @@ export class ObViewServices extends DeesElement {
currentServiceStats: null, currentServiceStats: null,
platformServices: [], platformServices: [],
currentPlatformService: null, currentPlatformService: null,
currentPlatformServiceStats: null,
}; };
@state() @state()
@@ -145,7 +146,37 @@ export class ObViewServices extends DeesElement {
public static styles = [ public static styles = [
cssManager.defaultStyles, cssManager.defaultStyles,
shared.viewHostCss, shared.viewHostCss,
css``, css`
.page-actions {
display: flex;
justify-content: flex-end;
margin-bottom: 16px;
}
.deploy-button {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: ${cssManager.bdTheme('#18181b', '#fafafa')};
color: ${cssManager.bdTheme('#fafafa', '#18181b')};
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: opacity 200ms ease;
}
.deploy-button:hover {
opacity: 0.9;
}
.deploy-button svg {
width: 16px;
height: 16px;
}
`,
]; ];
async connectedCallback() { async connectedCallback() {
@@ -154,6 +185,18 @@ export class ObViewServices extends DeesElement {
appstate.servicesStatePart.dispatchAction(appstate.fetchServicesAction, null), appstate.servicesStatePart.dispatchAction(appstate.fetchServicesAction, null),
appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServicesAction, null), appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServicesAction, null),
]); ]);
// If a platform service was selected from the dashboard, navigate to its detail
const state = appstate.servicesStatePart.getState();
if (state.currentPlatformService) {
const type = state.currentPlatformService.type;
// Clear the selection so it doesn't persist on next visit
appstate.servicesStatePart.setState({
...appstate.servicesStatePart.getState(),
currentPlatformService: null,
});
this.navigateToPlatformDetail(type);
}
} }
public render(): TemplateResult { public render(): TemplateResult {
@@ -178,8 +221,23 @@ export class ObViewServices extends DeesElement {
domain: s.domain || null, domain: s.domain || null,
status: mapStatus(s.status), status: mapStatus(s.status),
})); }));
const mappedPlatformServices = this.servicesState.platformServices.map((ps) => ({
name: ps.displayName,
status: ps.status === 'running' ? `Running` : ps.status,
running: ps.status === 'running',
type: ps.type,
}));
return html` return html`
<ob-sectionheading>Services</ob-sectionheading> <ob-sectionheading>Services</ob-sectionheading>
<div class="page-actions">
<button class="deploy-button" @click=${() => { this.currentView = 'create'; }}>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Deploy Service
</button>
</div>
<sz-services-list-view <sz-services-list-view
.services=${mappedServices} .services=${mappedServices}
@service-click=${(e: CustomEvent) => { @service-click=${(e: CustomEvent) => {
@@ -197,6 +255,20 @@ export class ObViewServices extends DeesElement {
}} }}
@service-action=${(e: CustomEvent) => this.handleServiceAction(e)} @service-action=${(e: CustomEvent) => this.handleServiceAction(e)}
></sz-services-list-view> ></sz-services-list-view>
<ob-sectionheading style="margin-top: 32px;">Platform Services</ob-sectionheading>
<div style="max-width: 500px;">
<sz-platform-services-card
.services=${mappedPlatformServices}
@service-click=${(e: CustomEvent) => {
const type = e.detail.type || this.servicesState.platformServices.find(
(ps) => ps.displayName === e.detail.name,
)?.type;
if (type) {
this.navigateToPlatformDetail(type);
}
}}
></sz-platform-services-card>
</div>
`; `;
} }
@@ -206,8 +278,26 @@ export class ObViewServices extends DeesElement {
<sz-service-create-view <sz-service-create-view
.registries=${[]} .registries=${[]}
@create-service=${async (e: CustomEvent) => { @create-service=${async (e: CustomEvent) => {
const formConfig = e.detail;
const serviceConfig: interfaces.data.IServiceCreate = {
name: formConfig.name,
image: formConfig.image,
port: formConfig.ports?.[0]?.containerPort
? parseInt(formConfig.ports[0].containerPort, 10)
: 80,
envVars: formConfig.envVars?.reduce(
(acc: Record<string, string>, ev: { key: string; value: string }) => {
if (ev.key) acc[ev.key] = ev.value;
return acc;
},
{} as Record<string, string>,
),
enableMongoDB: formConfig.enableMongoDB || false,
enableS3: formConfig.enableS3 || false,
enableClickHouse: formConfig.enableClickHouse || false,
};
await appstate.servicesStatePart.dispatchAction(appstate.createServiceAction, { await appstate.servicesStatePart.dispatchAction(appstate.createServiceAction, {
config: e.detail, config: serviceConfig,
}); });
this.currentView = 'list'; this.currentView = 'list';
}} }}
@@ -265,10 +355,29 @@ export class ObViewServices extends DeesElement {
`; `;
} }
private navigateToPlatformDetail(type: string): void {
this.selectedPlatformType = type;
// Fetch stats for this platform service
appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServiceStatsAction, {
serviceType: type as interfaces.data.TPlatformServiceType,
});
this.currentView = 'platform-detail';
}
private renderPlatformDetailView(): TemplateResult { private renderPlatformDetailView(): TemplateResult {
const platformService = this.servicesState.platformServices.find( const platformService = this.servicesState.platformServices.find(
(ps) => ps.type === this.selectedPlatformType, (ps) => ps.type === this.selectedPlatformType,
); );
const stats = this.servicesState.currentPlatformServiceStats;
const metrics = stats
? {
cpu: Math.round(stats.cpuPercent),
memory: Math.round(stats.memoryPercent),
storage: 0,
connections: 0,
}
: undefined;
return html` return html`
<ob-sectionheading>Platform Service</ob-sectionheading> <ob-sectionheading>Platform Service</ob-sectionheading>
<sz-platform-service-detail-view <sz-platform-service-detail-view
@@ -277,22 +386,45 @@ export class ObViewServices extends DeesElement {
id: platformService.type, id: platformService.type,
name: platformService.displayName, name: platformService.displayName,
type: platformService.type, type: platformService.type,
status: platformService.status, status: platformService.status === 'running'
? 'running'
: platformService.status === 'failed'
? 'error'
: 'stopped',
version: '', version: '',
host: 'localhost', host: 'localhost',
port: 0, port: 0,
config: {}, config: {},
metrics,
} }
: null} : null}
.logs=${[]} .logs=${[]}
@start=${() => { @back=${() => {
appstate.servicesStatePart.dispatchAction(appstate.startPlatformServiceAction, { this.currentView = 'list';
serviceType: this.selectedPlatformType as any, }}
@start=${async () => {
await appstate.servicesStatePart.dispatchAction(appstate.startPlatformServiceAction, {
serviceType: this.selectedPlatformType as interfaces.data.TPlatformServiceType,
});
// Refresh stats after starting
appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServiceStatsAction, {
serviceType: this.selectedPlatformType as interfaces.data.TPlatformServiceType,
}); });
}} }}
@stop=${() => { @stop=${async () => {
appstate.servicesStatePart.dispatchAction(appstate.stopPlatformServiceAction, { await appstate.servicesStatePart.dispatchAction(appstate.stopPlatformServiceAction, {
serviceType: this.selectedPlatformType as any, serviceType: this.selectedPlatformType as interfaces.data.TPlatformServiceType,
});
}}
@restart=${async () => {
await appstate.servicesStatePart.dispatchAction(appstate.stopPlatformServiceAction, {
serviceType: this.selectedPlatformType as interfaces.data.TPlatformServiceType,
});
await appstate.servicesStatePart.dispatchAction(appstate.startPlatformServiceAction, {
serviceType: this.selectedPlatformType as interfaces.data.TPlatformServiceType,
});
appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServiceStatsAction, {
serviceType: this.selectedPlatformType as interfaces.data.TPlatformServiceType,
}); });
}} }}
></sz-platform-service-detail-view> ></sz-platform-service-detail-view>