Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b6de75097 | |||
| 497f8f59a7 | |||
| 0c7d65e4ad | |||
| 3f2cd074ce | |||
| 59ed7233bd | |||
| 01e3ba16c4 |
22
changelog.md
22
changelog.md
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## 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)
|
||||
set HOME and DENO_DIR for the systemd service environment
|
||||
|
||||
|
||||
BIN
create-service-form.png
Normal file
BIN
create-service-form.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@serve.zone/onebox",
|
||||
"version": "1.15.2",
|
||||
"version": "1.17.0",
|
||||
"exports": "./mod.ts",
|
||||
"tasks": {
|
||||
"test": "deno test --allow-all test/",
|
||||
|
||||
@@ -250,8 +250,10 @@ echo ""
|
||||
mkdir -p /var/lib/onebox
|
||||
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
|
||||
echo "Refreshing systemd service..."
|
||||
onebox systemd enable
|
||||
echo "Restarting Onebox service..."
|
||||
systemctl restart "$SERVICE_NAME"
|
||||
echo "Service restarted successfully."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@serve.zone/onebox",
|
||||
"version": "1.15.2",
|
||||
"version": "1.17.0",
|
||||
"description": "Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers",
|
||||
"main": "mod.ts",
|
||||
"type": "module",
|
||||
@@ -57,7 +57,7 @@
|
||||
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||
"@design.estate/dees-catalog": "^3.43.3",
|
||||
"@design.estate/dees-element": "^2.1.6",
|
||||
"@serve.zone/catalog": "^2.5.0"
|
||||
"@serve.zone/catalog": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbundle": "^2.9.0",
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -18,8 +18,8 @@ importers:
|
||||
specifier: ^2.1.6
|
||||
version: 2.2.3
|
||||
'@serve.zone/catalog':
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0(@tiptap/pm@2.27.2)
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0(@tiptap/pm@2.27.2)
|
||||
devDependencies:
|
||||
'@git.zone/tsbundle':
|
||||
specifier: ^2.9.0
|
||||
@@ -836,8 +836,8 @@ packages:
|
||||
'@sec-ant/readable-stream@0.4.1':
|
||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||
|
||||
'@serve.zone/catalog@2.5.0':
|
||||
resolution: {integrity: sha512-bRwk7pbDxUB471wUAS7p22MTOOBCHlMWijsE43K9tDAPcxlRarhtf2Dgx0Y25s/dFXqj2JHwe6jjE84S80jFzg==}
|
||||
'@serve.zone/catalog@2.6.0':
|
||||
resolution: {integrity: sha512-Gp91Ed0MMLMPSAZrH2/UimGxU9AaTu5IPUobPD49PSvh3UnUl+HEFiy81kpKJhW16V37N3/vcNgZrn2VI7/LxQ==}
|
||||
|
||||
'@tempfix/idb@8.0.3':
|
||||
resolution: {integrity: sha512-hPJQKO7+oAIY+pDNImrZ9QAINbz9KmwT+yO4iRVwdPanok2YKpaUxdJzIvCUwY0YgAawlvYdffbLvRLV5hbs2g==}
|
||||
@@ -3474,7 +3474,7 @@ snapshots:
|
||||
|
||||
'@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:
|
||||
'@design.estate/dees-catalog': 3.48.5(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-domtools': 2.5.1
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/onebox',
|
||||
version: '1.15.2',
|
||||
version: '1.17.0',
|
||||
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 = {
|
||||
name: '@serve.zone/onebox',
|
||||
version: '1.15.2',
|
||||
version: '1.17.0',
|
||||
description: 'Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers'
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface IServicesState {
|
||||
currentServiceStats: interfaces.data.IContainerStats | null;
|
||||
platformServices: interfaces.data.IPlatformService[];
|
||||
currentPlatformService: interfaces.data.IPlatformService | null;
|
||||
currentPlatformServiceStats: interfaces.data.IContainerStats | null;
|
||||
}
|
||||
|
||||
export interface INetworkState {
|
||||
@@ -88,6 +89,7 @@ export const servicesStatePart = await appState.getStatePart<IServicesState>(
|
||||
currentServiceStats: null,
|
||||
platformServices: [],
|
||||
currentPlatformService: null,
|
||||
currentPlatformServiceStats: null,
|
||||
},
|
||||
'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
|
||||
// ============================================================================
|
||||
|
||||
@@ -24,6 +24,7 @@ export class ObViewDashboard extends DeesElement {
|
||||
currentServiceStats: null,
|
||||
platformServices: [],
|
||||
currentPlatformService: null,
|
||||
currentPlatformServiceStats: null,
|
||||
};
|
||||
|
||||
@state()
|
||||
@@ -149,6 +150,7 @@ export class ObViewDashboard extends DeesElement {
|
||||
],
|
||||
}}
|
||||
@action-click=${(e: CustomEvent) => this.handleQuickAction(e)}
|
||||
@service-click=${(e: CustomEvent) => this.handlePlatformServiceClick(e)}
|
||||
></sz-dashboard-view>
|
||||
`;
|
||||
}
|
||||
@@ -161,4 +163,21 @@ export class ObViewDashboard extends DeesElement {
|
||||
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' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ export class ObViewServices extends DeesElement {
|
||||
currentServiceStats: null,
|
||||
platformServices: [],
|
||||
currentPlatformService: null,
|
||||
currentPlatformServiceStats: null,
|
||||
};
|
||||
|
||||
@state()
|
||||
@@ -145,7 +146,37 @@ export class ObViewServices extends DeesElement {
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
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() {
|
||||
@@ -154,6 +185,18 @@ export class ObViewServices extends DeesElement {
|
||||
appstate.servicesStatePart.dispatchAction(appstate.fetchServicesAction, 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 {
|
||||
@@ -178,8 +221,23 @@ export class ObViewServices extends DeesElement {
|
||||
domain: s.domain || null,
|
||||
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`
|
||||
<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
|
||||
.services=${mappedServices}
|
||||
@service-click=${(e: CustomEvent) => {
|
||||
@@ -197,6 +255,20 @@ export class ObViewServices extends DeesElement {
|
||||
}}
|
||||
@service-action=${(e: CustomEvent) => this.handleServiceAction(e)}
|
||||
></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
|
||||
.registries=${[]}
|
||||
@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, {
|
||||
config: e.detail,
|
||||
config: serviceConfig,
|
||||
});
|
||||
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 {
|
||||
const platformService = this.servicesState.platformServices.find(
|
||||
(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`
|
||||
<ob-sectionheading>Platform Service</ob-sectionheading>
|
||||
<sz-platform-service-detail-view
|
||||
@@ -277,22 +386,45 @@ export class ObViewServices extends DeesElement {
|
||||
id: platformService.type,
|
||||
name: platformService.displayName,
|
||||
type: platformService.type,
|
||||
status: platformService.status,
|
||||
status: platformService.status === 'running'
|
||||
? 'running'
|
||||
: platformService.status === 'failed'
|
||||
? 'error'
|
||||
: 'stopped',
|
||||
version: '',
|
||||
host: 'localhost',
|
||||
port: 0,
|
||||
config: {},
|
||||
metrics,
|
||||
}
|
||||
: null}
|
||||
.logs=${[]}
|
||||
@start=${() => {
|
||||
appstate.servicesStatePart.dispatchAction(appstate.startPlatformServiceAction, {
|
||||
serviceType: this.selectedPlatformType as any,
|
||||
@back=${() => {
|
||||
this.currentView = 'list';
|
||||
}}
|
||||
@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=${() => {
|
||||
appstate.servicesStatePart.dispatchAction(appstate.stopPlatformServiceAction, {
|
||||
serviceType: this.selectedPlatformType as any,
|
||||
@stop=${async () => {
|
||||
await appstate.servicesStatePart.dispatchAction(appstate.stopPlatformServiceAction, {
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user