import * as plugins from '../plugins.js'; import * as shared from './shared/index.js'; import * as appstate from '../appstate.js'; import { DeesElement, customElement, html, state, css, cssManager, type TemplateResult, } from '@design.estate/dees-element'; @customElement('ob-view-settings') export class ObViewSettings extends DeesElement { @state() accessor settingsState: appstate.ISettingsState = { settings: null, backupPasswordConfigured: false, }; @state() accessor loginState: appstate.ILoginState = { identity: null, isLoggedIn: false, }; constructor() { super(); const settingsSub = appstate.settingsStatePart .select((s) => s) .subscribe((newState) => { this.settingsState = newState; }); this.rxSubscriptions.push(settingsSub); const loginSub = appstate.loginStatePart .select((s) => s) .subscribe((newState) => { this.loginState = newState; }); this.rxSubscriptions.push(loginSub); } public static styles = [ cssManager.defaultStyles, shared.viewHostCss, css` .gateway-card { margin-bottom: 24px; border: 1px solid var(--dees-color-border-subtle); border-radius: 12px; background: var(--dees-color-background, #ffffff); overflow: hidden; } .gateway-header { padding: 16px 20px; border-bottom: 1px solid var(--dees-color-border-subtle); } .gateway-title { font-size: 15px; font-weight: 600; color: var(--dees-color-text-primary); } .gateway-subtitle { margin-top: 4px; font-size: 13px; color: var(--dees-color-text-muted); } .gateway-content { padding: 20px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; } .gateway-field.full { grid-column: 1 / -1; } .field-label { display: block; margin-bottom: 6px; font-size: 13px; font-weight: 500; color: var(--dees-color-text-secondary); } input { width: 100%; box-sizing: border-box; padding: 10px 12px; border: 1px solid var(--dees-color-border-subtle); border-radius: 8px; background: transparent; color: var(--dees-color-text-primary); font-size: 14px; } input:focus { outline: none; border-color: #3b82f6; } .field-hint { margin-top: 5px; font-size: 12px; color: var(--dees-color-text-muted); } .gateway-footer { display: flex; justify-content: flex-end; padding: 0 20px 20px; } .save-button { border: none; border-radius: 8px; background: #2563eb; color: white; cursor: pointer; font-size: 13px; font-weight: 600; padding: 9px 14px; } .save-button:hover { background: #1d4ed8; } @media (max-width: 700px) { .gateway-content { grid-template-columns: 1fr; } } `, ]; async connectedCallback() { super.connectedCallback(); await appstate.settingsStatePart.dispatchAction(appstate.fetchSettingsAction, null); } public render(): TemplateResult { return html` Settings ${this.renderExternalGatewaySettings()} { const { key, value } = e.detail; appstate.settingsStatePart.dispatchAction(appstate.updateSettingsAction, { settings: { [key]: value }, }); }} @save=${(e: CustomEvent) => { appstate.settingsStatePart.dispatchAction(appstate.updateSettingsAction, { settings: e.detail, }); }} @change-password=${(e: CustomEvent) => { console.log('Change password requested:', e.detail); }} @reset=${() => { appstate.settingsStatePart.dispatchAction(appstate.fetchSettingsAction, null); }} > `; } private renderExternalGatewaySettings(): TemplateResult { const settings = this.settingsState.settings; return html`
External dcrouter Gateway
Delegate public WorkApp routing, DNS, and certificates to a dcrouter edge authority.
${this.renderGatewayInput('dcrouterGatewayUrl', 'Gateway URL', settings?.dcrouterGatewayUrl || '', 'https://edge.example.com', 'Base URL of the dcrouter OpsServer.')} ${this.renderGatewayInput('dcrouterGatewayApiToken', 'API Token', settings?.dcrouterGatewayApiToken || '', 'dcrouter API token', 'Requires workhosters and certificates scopes.', 'password')} ${this.renderGatewayInput('dcrouterWorkHosterId', 'WorkHoster ID', settings?.dcrouterWorkHosterId || '', 'optional stable owner ID', 'Leave empty to let Onebox create a stable ID.')} ${this.renderGatewayInput('dcrouterTargetHost', 'Target Host', settings?.dcrouterTargetHost || '', 'public or private host/IP', 'Defaults to the configured server IP when empty.')} ${this.renderGatewayInput('dcrouterTargetPort', 'Target Port', String(settings?.dcrouterTargetPort || 80), '80', 'Internal HTTP port dcrouter forwards to.', 'number')}
`; } private renderGatewayInput( key: keyof NonNullable, label: string, value: string, placeholder: string, hint: string, type: 'text' | 'password' | 'number' = 'text', ): TemplateResult { return html` `; } private updateGatewayDraft( key: keyof NonNullable, value: string, ): void { const currentSettings = this.settingsState.settings || {} as NonNullable; const nextValue = key === 'dcrouterTargetPort' ? Number(value) || 0 : value; this.settingsState = { ...this.settingsState, settings: { ...currentSettings, [key]: nextValue, }, }; } private async saveExternalGatewaySettings(): Promise { const settings = this.settingsState.settings; if (!settings) return; await appstate.settingsStatePart.dispatchAction(appstate.updateSettingsAction, { settings: { dcrouterGatewayUrl: settings.dcrouterGatewayUrl || '', dcrouterGatewayApiToken: settings.dcrouterGatewayApiToken || '', dcrouterWorkHosterId: settings.dcrouterWorkHosterId || '', dcrouterTargetHost: settings.dcrouterTargetHost || '', dcrouterTargetPort: Number(settings.dcrouterTargetPort) || 80, }, }); } }