2026-02-24 18:15:44 +00:00
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 ,
2026-04-29 15:57:10 +00:00
css `
.gateway-card {
margin-bottom: 24px;
2026-05-08 19:32:40 +00:00
border: 1px solid ${ cssManager . bdTheme ( '#e4e4e7' , '#27272a' ) } ;
2026-04-29 15:57:10 +00:00
border-radius: 12px;
2026-05-08 19:32:40 +00:00
background: ${ cssManager . bdTheme ( '#ffffff' , '#09090b' ) } ;
2026-04-29 15:57:10 +00:00
overflow: hidden;
2026-05-08 19:32:40 +00:00
box-shadow: 0 1px 2px ${ cssManager . bdTheme ( 'rgba(0,0,0,0.04)' , 'rgba(0,0,0,0.2)' ) } ;
2026-04-29 15:57:10 +00:00
}
.gateway-header {
padding: 16px 20px;
2026-05-08 19:32:40 +00:00
border-bottom: 1px solid ${ cssManager . bdTheme ( '#f4f4f5' , '#27272a' ) } ;
background: ${ cssManager . bdTheme ( '#fafafa' , '#101013' ) } ;
2026-04-29 15:57:10 +00:00
}
.gateway-title {
font-size: 15px;
font-weight: 600;
2026-05-08 19:32:40 +00:00
color: ${ cssManager . bdTheme ( '#18181b' , '#fafafa' ) } ;
2026-04-29 15:57:10 +00:00
}
.gateway-subtitle {
margin-top: 4px;
font-size: 13px;
2026-05-08 19:32:40 +00:00
color: ${ cssManager . bdTheme ( '#71717a' , '#a1a1aa' ) } ;
2026-04-29 15:57:10 +00:00
}
.gateway-content {
padding: 20px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.gateway-field.full {
grid-column: 1 / -1;
}
2026-05-08 19:32:40 +00:00
dees-input-text {
2026-04-29 15:57:10 +00:00
width: 100%;
}
.gateway-footer {
display: flex;
justify-content: flex-end;
padding: 0 20px 20px;
}
@media (max-width: 700px) {
.gateway-content {
grid-template-columns: 1fr;
}
}
` ,
2026-02-24 18:15:44 +00:00
] ;
async connectedCallback() {
super . connectedCallback ( ) ;
await appstate . settingsStatePart . dispatchAction ( appstate . fetchSettingsAction , null ) ;
}
public render ( ) : TemplateResult {
return html `
<ob-sectionheading>Settings</ob-sectionheading>
2026-04-29 15:57:10 +00:00
${ this . renderExternalGatewaySettings ( ) }
2026-02-24 18:15:44 +00:00
<sz-settings-view
.settings= ${ this . settingsState . settings || {
darkMode : true ,
cloudflareToken : '' ,
cloudflareZoneId : '' ,
2026-05-09 11:58:51 +00:00
dcrouterGatewayClientId : '' ,
dcrouterWorkHosterId : '' ,
2026-02-24 18:15:44 +00:00
autoRenewCerts : false ,
renewalThreshold : 30 ,
acmeEmail : '' ,
httpPort : 80 ,
httpsPort : 443 ,
forceHttps : false ,
} }
.currentUser= ${ this . loginState . identity ? . username || 'admin' }
@setting-change= ${ ( e : CustomEvent ) = > {
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 ) ;
} }
></sz-settings-view>
` ;
}
2026-04-29 15:57:10 +00:00
private renderExternalGatewaySettings ( ) : TemplateResult {
const settings = this . settingsState . settings ;
return html `
<section class="gateway-card">
<div class="gateway-header">
2026-05-08 19:32:40 +00:00
<div class="gateway-title">Delegate Routing</div>
2026-05-09 11:58:51 +00:00
<div class="gateway-subtitle">Delegate public app routing, DNS, and certificates to a dcrouter edge authority.</div>
2026-04-29 15:57:10 +00:00
</div>
<div class="gateway-content">
2026-05-08 19:32:40 +00:00
${ this . renderGatewayInput ( 'dcrouterGatewayUrl' , 'Gateway URL' , settings ? . dcrouterGatewayUrl || '' , 'Base URL of the dcrouter OpsServer.' ) }
2026-05-09 11:58:51 +00:00
${ this . renderGatewayInput ( 'dcrouterGatewayApiToken' , 'API Token' , settings ? . dcrouterGatewayApiToken || '' , 'Requires gateway-client access in dcrouter.' , true ) }
${ this . renderGatewayInput ( 'dcrouterGatewayClientId' , 'Gateway Client ID' , settings ? . dcrouterGatewayClientId || settings ? . dcrouterWorkHosterId || '' , 'Leave empty to let Onebox create a stable ID.' ) }
2026-05-08 19:32:40 +00:00
${ this . renderGatewayInput ( 'dcrouterTargetHost' , 'Target Host' , settings ? . dcrouterTargetHost || '' , 'Defaults to the configured server IP when empty.' ) }
${ this . renderGatewayInput ( 'dcrouterTargetPort' , 'Target Port' , String ( settings ? . dcrouterTargetPort || 80 ) , 'Internal HTTP port dcrouter forwards to.' ) }
2026-04-29 15:57:10 +00:00
</div>
<div class="gateway-footer">
2026-05-08 19:32:40 +00:00
<dees-button
.text= ${ 'Save Gateway Settings' }
.type= ${ 'default' }
.icon= ${ 'lucide:Save' }
@click= ${ ( ) = > this . saveExternalGatewaySettings ( ) }
></dees-button>
2026-04-29 15:57:10 +00:00
</div>
</section>
` ;
}
private renderGatewayInput (
key : keyof NonNullable < appstate.ISettingsState [ 'settings' ] > ,
label : string ,
value : string ,
hint : string ,
2026-05-08 19:32:40 +00:00
isPassword = false ,
2026-04-29 15:57:10 +00:00
) : TemplateResult {
return html `
2026-05-08 19:32:40 +00:00
<div class="gateway-field ${ key === 'dcrouterGatewayUrl' ? 'full' : '' } ">
<dees-input-text
.key= ${ key }
.label= ${ label }
2026-04-29 15:57:10 +00:00
.value= ${ value }
2026-05-08 19:32:40 +00:00
.description= ${ hint }
.isPasswordBool= ${ isPassword }
2026-04-29 15:57:10 +00:00
@input= ${ ( event : Event ) = > this . updateGatewayDraft ( key , ( event . target as HTMLInputElement ) . value ) }
2026-05-08 19:32:40 +00:00
></dees-input-text>
</div>
2026-04-29 15:57:10 +00:00
` ;
}
private updateGatewayDraft (
key : keyof NonNullable < appstate.ISettingsState [ 'settings' ] > ,
value : string ,
) : void {
const currentSettings = this . settingsState . settings || { } as NonNullable < appstate.ISettingsState [ 'settings' ] > ;
const nextValue = key === 'dcrouterTargetPort' ? Number ( value ) || 0 : value ;
this . settingsState = {
. . . this . settingsState ,
settings : {
. . . currentSettings ,
[ key ] : nextValue ,
} ,
} ;
}
private async saveExternalGatewaySettings ( ) : Promise < void > {
const settings = this . settingsState . settings ;
if ( ! settings ) return ;
await appstate . settingsStatePart . dispatchAction ( appstate . updateSettingsAction , {
settings : {
dcrouterGatewayUrl : settings.dcrouterGatewayUrl || '' ,
dcrouterGatewayApiToken : settings.dcrouterGatewayApiToken || '' ,
2026-05-09 11:58:51 +00:00
dcrouterGatewayClientId : settings.dcrouterGatewayClientId || settings . dcrouterWorkHosterId || '' ,
2026-04-29 15:57:10 +00:00
dcrouterTargetHost : settings.dcrouterTargetHost || '' ,
dcrouterTargetPort : Number ( settings . dcrouterTargetPort ) || 80 ,
} ,
} ) ;
}
2026-02-24 18:15:44 +00:00
}