Compare commits

...

7 Commits

Author SHA1 Message Date
jkunz ba370cbce8 v1.26.3
Release / build-and-release (push) Successful in 2m29s
2026-05-21 17:06:38 +00:00
jkunz 43c8f261cc fix(web): use dees-table for gateway domains and DNS records views 2026-05-21 17:06:15 +00:00
jkunz 2984c41081 v1.26.2
Release / build-and-release (push) Successful in 2m30s
2026-05-20 14:32:04 +00:00
jkunz d143d73ea9 chore(release): remove duplicate pending heading 2026-05-20 14:31:56 +00:00
jkunz 9f8a6eaa76 chore(release): format pending changelog entry 2026-05-20 14:31:18 +00:00
jkunz 0af8da2c9d chore(release): document proxy reload fix 2026-05-20 14:30:31 +00:00
jkunz fa96d371d6 fix(proxy): reload routes after SmartProxy startup 2026-05-20 14:27:17 +00:00
9 changed files with 139 additions and 77 deletions
+20
View File
@@ -1,5 +1,25 @@
# Changelog # Changelog
## Pending
## 2026-05-21 - 1.26.3
### Fixes
- use `dees-table` for gateway domains and DNS records views (web)
- replace custom row grids with catalog tables, filtering, refresh, and row actions
- use dees-table for gateway domains and DNS records views (web)
- replace custom row layouts with dees-table in gateway domains and DNS records views
- add table filtering, refresh actions, and row/context actions for dcrouter management
## 2026-05-20 - 1.26.2
### Fixes
- reload SmartProxy routes after managed startup (proxy)
- reloads SmartProxy routes immediately after the admin API is ready during startup, avoiding an empty route table when Docker task state lags behind service readiness
## 2026-05-09 - 1.26.1 - fix(external-gateway) ## 2026-05-09 - 1.26.1 - fix(external-gateway)
derive gateway client identity from the dcrouter token and make the settings UI read-only derive gateway client identity from the dcrouter token and make the settings UI read-only
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@serve.zone/onebox", "name": "@serve.zone/onebox",
"version": "1.26.1", "version": "1.26.3",
"exports": "./mod.ts", "exports": "./mod.ts",
"tasks": { "tasks": {
"test": "deno test --allow-all test/", "test": "deno test --allow-all test/",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@serve.zone/onebox", "name": "@serve.zone/onebox",
"version": "1.26.1", "version": "1.26.3",
"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",
+1 -1
View File
@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/onebox', name: '@serve.zone/onebox',
version: '1.26.1', version: '1.26.3',
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'
} }
+8 -6
View File
@@ -179,7 +179,7 @@ export class SmartProxyManager {
await this.waitForReady(); await this.waitForReady();
this.serviceRunning = true; this.serviceRunning = true;
await this.reloadConfig(); await this.reloadConfig({ skipRunningCheck: true });
logger.success(`SmartProxy started (HTTP: ${this.httpPort}, HTTPS: ${this.httpsPort}, Admin: ${this.adminUrl})`); logger.success(`SmartProxy started (HTTP: ${this.httpPort}, HTTPS: ${this.httpsPort}, Admin: ${this.adminUrl})`);
} catch (error) { } catch (error) {
@@ -360,11 +360,13 @@ export class SmartProxyManager {
return routeConfigs; return routeConfigs;
} }
async reloadConfig(): Promise<void> { async reloadConfig(options: { skipRunningCheck?: boolean } = {}): Promise<void> {
const isRunning = await this.isRunning(); if (!options.skipRunningCheck) {
if (!isRunning) { const isRunning = await this.isRunning();
logger.warn('SmartProxy not running, cannot reload config'); if (!isRunning) {
return; logger.warn('SmartProxy not running, cannot reload config');
return;
}
} }
const routes = this.buildRoutes(); const routes = this.buildRoutes();
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/onebox', name: '@serve.zone/onebox',
version: '1.26.1', version: '1.26.3',
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'
} }
+58 -29
View File
@@ -1,4 +1,5 @@
import * as shared from './shared/index.js'; import * as shared from './shared/index.js';
import * as plugins from '../plugins.js';
import * as appstate from '../appstate.js'; import * as appstate from '../appstate.js';
import { appRouter } from '../router.js'; import { appRouter } from '../router.js';
import { import {
@@ -11,6 +12,8 @@ import {
type TemplateResult, type TemplateResult,
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
type TGatewayDnsRecord = appstate.INetworkState['gatewayDnsRecords'][number];
@customElement('ob-view-dns-records') @customElement('ob-view-dns-records')
export class ObViewDnsRecords extends DeesElement { export class ObViewDnsRecords extends DeesElement {
@state() @state()
@@ -37,16 +40,11 @@ export class ObViewDnsRecords extends DeesElement {
cssManager.defaultStyles, cssManager.defaultStyles,
shared.viewHostCss, shared.viewHostCss,
css` css`
.table { border: 1px solid var(--ci-shade-2, #e4e4e7); border-radius: 10px; overflow: hidden; }
.row { display: grid; grid-template-columns: 2fr 90px 2fr 90px 140px 220px; gap: 16px; align-items: center; padding: 14px 16px; border-bottom: 1px solid var(--ci-shade-2, #e4e4e7); }
.row:last-child { border-bottom: none; }
.header { font-size: 12px; font-weight: 700; text-transform: uppercase; color: var(--ci-shade-5, #71717a); background: var(--ci-shade-1, #f4f4f5); }
.name { font-weight: 600; } .name { font-weight: 600; }
.value { font-family: monospace; color: var(--ci-shade-5, #71717a); overflow-wrap: anywhere; } .value { font-family: monospace; color: var(--ci-shade-5, #71717a); overflow-wrap: anywhere; }
.muted { color: var(--ci-shade-5, #71717a); font-size: 13px; }
.badge { border-radius: 999px; padding: 3px 8px; background: var(--ci-shade-1, #f4f4f5); font-size: 12px; } .badge { border-radius: 999px; padding: 3px 8px; background: var(--ci-shade-1, #f4f4f5); font-size: 12px; }
.missing { color: #dc2626; } .missing { color: #dc2626; }
a, button.link { color: var(--ci-primary, #2563eb); background: none; border: none; padding: 0; cursor: pointer; font: inherit; text-decoration: none; }
.actions { display: flex; gap: 12px; }
.empty { padding: 32px; text-align: center; color: var(--ci-shade-5, #71717a); } .empty { padding: 32px; text-align: center; color: var(--ci-shade-5, #71717a); }
`, `,
]; ];
@@ -60,29 +58,60 @@ export class ObViewDnsRecords extends DeesElement {
const records = this.networkState.gatewayDnsRecords; const records = this.networkState.gatewayDnsRecords;
return html` return html`
<ob-sectionheading>DNS Records</ob-sectionheading> <ob-sectionheading>DNS Records</ob-sectionheading>
<div class="table"> ${records.length
<div class="row header"> ? html`
<span>Name</span> <dees-table
<span>Type</span> .heading1=${'Gateway DNS Records'}
<span>Value</span> .heading2=${'DNS records published through dcrouter for Onebox services'}
<span>Status</span> .data=${records}
<span>Service</span> .showColumnFilters=${true}
<span>Actions</span> .displayFunction=${(record: TGatewayDnsRecord) => ({
</div> Name: html`
${records.length ? records.map((record) => html` <div>
<div class="row ${record.status === 'missing' ? 'missing' : ''}"> <div class="name">${record.name}</div>
<span class="name">${record.name}</span> ${record.domainName ? html`<div class="muted">${record.domainName}</div>` : ''}
<span><span class="badge">${record.type}</span></span> </div>
<span class="value">${record.value || '-'}</span> `,
<span>${record.status}</span> Type: html`<span class="badge">${record.type}</span>`,
<span>${record.serviceName || record.appId}</span> Value: html`<span class="value">${record.value || '-'}</span>`,
<span class="actions"> Status: html`<span class=${record.status === 'missing' ? 'missing' : ''}>${record.status}</span>`,
<button class="link" @click=${() => appRouter.navigateToView('services')}>View service</button> Service: record.serviceName || record.appId || '-',
${record.manageUrl ? html`<a href=${record.manageUrl} target="_blank" rel="noopener">Manage in dcrouter</a>` : ''} })}
</span> .dataActions=${[
</div> {
`) : html`<div class="empty">No gateway DNS records found. Configure a dcrouter gateway in Settings.</div>`} name: 'Refresh',
</div> iconName: 'lucide:rotateCw',
type: ['header'],
actionFunc: async () => {
await appstate.networkStatePart.dispatchAction(
appstate.fetchGatewayDnsRecordsAction,
null,
);
},
},
{
name: 'View service',
iconName: 'lucide:boxes',
type: ['inRow', 'contextmenu'],
actionFunc: async () => {
appRouter.navigateToView('services');
},
},
{
name: 'Manage in dcrouter',
iconName: 'lucide:externalLink',
type: ['inRow', 'contextmenu'],
actionRelevancyCheckFunc: (record: TGatewayDnsRecord) => !!record.manageUrl,
actionFunc: async (actionData: plugins.deesCatalog.ITableActionDataArg<TGatewayDnsRecord>) => {
if (actionData.item.manageUrl) {
globalThis.open(actionData.item.manageUrl, '_blank', 'noopener');
}
},
},
] as plugins.deesCatalog.ITableAction<TGatewayDnsRecord>[]}
></dees-table>
`
: html`<div class="empty">No gateway DNS records found. Configure a dcrouter gateway in Settings.</div>`}
`; `;
} }
} }
+48 -37
View File
@@ -1,4 +1,5 @@
import * as shared from './shared/index.js'; import * as shared from './shared/index.js';
import * as plugins from '../plugins.js';
import * as appstate from '../appstate.js'; import * as appstate from '../appstate.js';
import { import {
DeesElement, DeesElement,
@@ -10,6 +11,8 @@ import {
type TemplateResult, type TemplateResult,
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
type TGatewayDomain = appstate.INetworkState['gatewayDomains'][number];
@customElement('ob-view-domains') @customElement('ob-view-domains')
export class ObViewDomains extends DeesElement { export class ObViewDomains extends DeesElement {
@state() @state()
@@ -36,25 +39,9 @@ export class ObViewDomains extends DeesElement {
cssManager.defaultStyles, cssManager.defaultStyles,
shared.viewHostCss, shared.viewHostCss,
css` css`
.table {
border: 1px solid var(--ci-shade-2, #e4e4e7);
border-radius: 10px;
overflow: hidden;
}
.row {
display: grid;
grid-template-columns: 2fr 1fr 120px 120px 140px;
gap: 16px;
align-items: center;
padding: 14px 16px;
border-bottom: 1px solid var(--ci-shade-2, #e4e4e7);
}
.row:last-child { border-bottom: none; }
.header { font-size: 12px; font-weight: 700; text-transform: uppercase; color: var(--ci-shade-5, #71717a); background: var(--ci-shade-1, #f4f4f5); }
.domain { font-weight: 600; } .domain { font-weight: 600; }
.muted { color: var(--ci-shade-5, #71717a); font-size: 13px; } .muted { color: var(--ci-shade-5, #71717a); font-size: 13px; }
.badge { border-radius: 999px; padding: 3px 8px; background: var(--ci-shade-1, #f4f4f5); font-size: 12px; } .badge { border-radius: 999px; padding: 3px 8px; background: var(--ci-shade-1, #f4f4f5); font-size: 12px; }
a { color: var(--ci-primary, #2563eb); text-decoration: none; }
.empty { padding: 32px; text-align: center; color: var(--ci-shade-5, #71717a); } .empty { padding: 32px; text-align: center; color: var(--ci-shade-5, #71717a); }
`, `,
]; ];
@@ -71,27 +58,51 @@ export class ObViewDomains extends DeesElement {
<div class="muted" style="margin-bottom: 16px;"> <div class="muted" style="margin-bottom: 16px;">
Domains are managed in dcrouter. Onebox shows gateway visibility for deployed services. Domains are managed in dcrouter. Onebox shows gateway visibility for deployed services.
</div> </div>
<div class="table"> ${domains.length
<div class="row header"> ? html`
<span>Domain</span> <dees-table
<span>Source</span> .heading1=${'Gateway Domains'}
<span>Authoritative</span> .heading2=${'Domains imported from dcrouter gateway visibility'}
<span>Services</span> .data=${domains}
<span>Actions</span> .showColumnFilters=${true}
</div> .displayFunction=${(domain: TGatewayDomain) => ({
${domains.length ? domains.map((domain) => html` Domain: html`
<div class="row"> <div>
<span> <div class="domain">${domain.name}</div>
<span class="domain">${domain.name}</span> ${domain.providerId ? html`<div class="muted">Provider: ${domain.providerId}</div>` : ''}
${domain.providerId ? html`<div class="muted">Provider: ${domain.providerId}</div>` : ''} </div>
</span> `,
<span><span class="badge">${domain.source || 'dcrouter'}</span></span> Source: html`<span class="badge">${domain.source || 'dcrouter'}</span>`,
<span>${domain.authoritative ? 'Yes' : 'No'}</span> Authoritative: domain.authoritative ? 'Yes' : 'No',
<span>${domain.serviceCount || 0}</span> Services: domain.serviceCount || 0,
<span>${domain.manageUrl ? html`<a href=${domain.manageUrl} target="_blank" rel="noopener">Manage in dcrouter</a>` : '-'}</span> })}
</div> .dataActions=${[
`) : html`<div class="empty">No gateway domains found. Configure a dcrouter gateway in Settings.</div>`} {
</div> name: 'Refresh',
iconName: 'lucide:rotateCw',
type: ['header'],
actionFunc: async () => {
await appstate.networkStatePart.dispatchAction(
appstate.fetchGatewayDomainsAction,
null,
);
},
},
{
name: 'Manage in dcrouter',
iconName: 'lucide:externalLink',
type: ['inRow', 'contextmenu'],
actionRelevancyCheckFunc: (domain: TGatewayDomain) => !!domain.manageUrl,
actionFunc: async (actionData: plugins.deesCatalog.ITableActionDataArg<TGatewayDomain>) => {
if (actionData.item.manageUrl) {
globalThis.open(actionData.item.manageUrl, '_blank', 'noopener');
}
},
},
] as plugins.deesCatalog.ITableAction<TGatewayDomain>[]}
></dees-table>
`
: html`<div class="empty">No gateway domains found. Configure a dcrouter gateway in Settings.</div>`}
`; `;
} }
} }