1- import { Component , Show , createMemo , createResource , onMount , type JSX } from "solid-js"
1+ import { Component , Show , createEffect , createMemo , createResource , createSignal , onMount , type JSX } from "solid-js"
22import { createStore } from "solid-js/store"
33import { Button } from "@opencode-ai/ui/button"
44import { Icon } from "@opencode-ai/ui/icon"
@@ -91,6 +91,11 @@ export const SettingsGeneral: Component = () => {
9191
9292 const [ store , setStore ] = createStore ( {
9393 checking : false ,
94+ inboundEnabled : false ,
95+ inboundUsername : "" ,
96+ inboundPassword : "" ,
97+ inboundPort : "" ,
98+ inboundSaving : false ,
9499 } )
95100
96101 const linux = createMemo ( ( ) => platform . platform === "desktop" && platform . os === "linux" )
@@ -120,6 +125,100 @@ export const SettingsGeneral: Component = () => {
120125 permission . disableAutoAccept ( params . id , value )
121126 }
122127 const desktop = createMemo ( ( ) => platform . platform === "desktop" )
128+ const [ inboundConfig ] = createResource ( ( ) => platform . getInboundServerConfig ?.( ) )
129+ const [ inboundRuntimeConfig ] = createResource ( ( ) => platform . getInboundRuntimeServerConfig ?.( ) )
130+ const [ inboundHydrated , setInboundHydrated ] = createSignal ( false )
131+ const [ savedInboundConfig , setSavedInboundConfig ] = createSignal < {
132+ enabled : boolean
133+ username : string
134+ password : string
135+ port : number | null
136+ } > ( )
137+
138+ createEffect ( ( ) => {
139+ if ( inboundHydrated ( ) ) return
140+
141+ const config = inboundConfig ( )
142+ if ( ! config ) return
143+ setStore ( "inboundEnabled" , config . enabled )
144+ setStore ( "inboundUsername" , config . username )
145+ setStore ( "inboundPassword" , config . password )
146+ setStore ( "inboundPort" , config . port === null ? "" : String ( config . port ) )
147+ setSavedInboundConfig ( config )
148+ setInboundHydrated ( true )
149+ } )
150+ const inboundRuntime = createMemo ( ( ) => platform . inboundRuntimeServerConfig ?.( ) ?? inboundRuntimeConfig ( ) )
151+ const inboundUsernamePlaceholder = createMemo ( ( ) => inboundRuntime ( ) ?. username ?? "opencode" )
152+ const inboundPasswordPlaceholder = createMemo (
153+ ( ) => inboundRuntime ( ) ?. password ?? language . t ( "settings.general.row.inboundPassword.placeholder" ) ,
154+ )
155+ const inboundPortPlaceholder = createMemo ( ( ) => {
156+ const port = inboundRuntime ( ) ?. port
157+ return typeof port === "number" ? String ( port ) : ""
158+ } )
159+
160+ const parseInboundPort = ( value : string ) => {
161+ const trimmed = value . trim ( )
162+ if ( ! trimmed ) return null
163+
164+ const parsed = Number . parseInt ( trimmed , 10 )
165+ if ( ! Number . isInteger ( parsed ) || parsed < 1 || parsed > 65535 ) return undefined
166+
167+ return parsed
168+ }
169+
170+ const saveInboundConfig = ( config : { enabled : boolean ; username : string ; password : string ; port : string } ) => {
171+ if ( ! platform . setInboundServerConfig ) return
172+
173+ const current = savedInboundConfig ( ) ?? inboundConfig . latest
174+ const port = parseInboundPort ( config . port )
175+ if ( config . enabled && port === undefined ) {
176+ showToast ( {
177+ title : language . t ( "common.requestFailed" ) ,
178+ description : language . t ( "settings.general.row.inboundPort.invalid" ) ,
179+ } )
180+ return
181+ }
182+
183+ const next = {
184+ enabled : config . enabled ,
185+ username : config . username . trim ( ) ,
186+ password : config . password ,
187+ port : port ?? null ,
188+ }
189+
190+ const changed =
191+ ! current ||
192+ current . enabled !== next . enabled ||
193+ current . username !== next . username ||
194+ current . password !== next . password ||
195+ current . port !== next . port
196+
197+ if ( ! changed ) return
198+
199+ setStore ( "inboundSaving" , true )
200+ void platform
201+ . setInboundServerConfig ( next )
202+ . then ( ( ) => {
203+ setSavedInboundConfig ( next )
204+ } )
205+ . finally ( ( ) => setStore ( "inboundSaving" , false ) )
206+ }
207+
208+ const saveInboundOnBlur = ( ) => {
209+ saveInboundConfig ( {
210+ enabled : store . inboundEnabled ,
211+ username : store . inboundUsername ,
212+ password : store . inboundPassword ,
213+ port : store . inboundPort ,
214+ } )
215+ }
216+
217+ const saveInboundOnEnter = ( event : KeyboardEvent ) => {
218+ if ( event . key !== "Enter" ) return
219+ event . preventDefault ( )
220+ saveInboundOnBlur ( )
221+ }
123222
124223 const check = ( ) => {
125224 if ( ! platform . checkUpdate ) return
@@ -728,7 +827,106 @@ export const SettingsGeneral: Component = () => {
728827 </ div >
729828 )
730829
731- console . log ( import . meta. env )
830+ const DesktopNetworkSection = ( ) => (
831+ < Show when = { desktop ( ) && platform . getInboundServerConfig && platform . setInboundServerConfig } >
832+ < div class = "flex flex-col gap-1" >
833+ < h3 class = "text-14-medium text-text-strong pb-2" > { language . t ( "settings.general.section.network" ) } </ h3 >
834+ < SettingsList >
835+ < SettingsRow
836+ title = { language . t ( "settings.general.row.inboundAccess.title" ) }
837+ description = { language . t ( "settings.general.row.inboundAccess.description" ) }
838+ >
839+ < div data-action = "settings-inbound-access" >
840+ < Switch
841+ checked = { store . inboundEnabled }
842+ disabled = { inboundConfig . state === "pending" || store . inboundSaving }
843+ onChange = { ( checked ) => {
844+ setStore ( "inboundEnabled" , checked )
845+ saveInboundConfig ( {
846+ enabled : checked ,
847+ username : store . inboundUsername ,
848+ password : store . inboundPassword ,
849+ port : store . inboundPort ,
850+ } )
851+ } }
852+ />
853+ </ div >
854+ </ SettingsRow >
855+ < Show when = { store . inboundEnabled } >
856+ < SettingsRow
857+ title = { language . t ( "settings.general.row.inboundUsername.title" ) }
858+ description = { language . t ( "settings.general.row.inboundUsername.description" ) }
859+ >
860+ < div class = "w-full sm:w-[220px]" >
861+ < TextField
862+ data-action = "settings-inbound-username"
863+ label = { language . t ( "settings.general.row.inboundUsername.title" ) }
864+ hideLabel
865+ type = "text"
866+ value = { store . inboundUsername || "" }
867+ onChange = { ( value ) => setStore ( "inboundUsername" , value ) }
868+ onBlur = { saveInboundOnBlur }
869+ onKeyDown = { saveInboundOnEnter }
870+ placeholder = { inboundUsernamePlaceholder ( ) }
871+ spellcheck = { false }
872+ autocorrect = "off"
873+ autocomplete = "off"
874+ autocapitalize = "off"
875+ class = "text-12-regular"
876+ />
877+ </ div >
878+ </ SettingsRow >
879+ < SettingsRow
880+ title = { language . t ( "settings.general.row.inboundPassword.title" ) }
881+ description = { language . t ( "settings.general.row.inboundPassword.description" ) }
882+ >
883+ < div class = "w-full sm:w-[220px]" >
884+ < TextField
885+ data-action = "settings-inbound-password"
886+ label = { language . t ( "settings.general.row.inboundPassword.title" ) }
887+ hideLabel
888+ type = "text"
889+ value = { store . inboundPassword || "" }
890+ onChange = { ( value ) => setStore ( "inboundPassword" , value ) }
891+ onBlur = { saveInboundOnBlur }
892+ onKeyDown = { saveInboundOnEnter }
893+ placeholder = { inboundPasswordPlaceholder ( ) }
894+ class = "text-12-regular"
895+ />
896+ </ div >
897+ </ SettingsRow >
898+ < SettingsRow
899+ title = { language . t ( "settings.general.row.inboundPort.title" ) }
900+ description = { language . t ( "settings.general.row.inboundPort.description" ) }
901+ >
902+ < div class = "flex gap-2 items-center" >
903+ < div class = "w-full sm:w-[220px]" >
904+ < TextField
905+ data-action = "settings-inbound-port"
906+ label = { language . t ( "settings.general.row.inboundPort.title" ) }
907+ hideLabel
908+ type = "text"
909+ inputMode = "numeric"
910+ value = { store . inboundPort || "" }
911+ onChange = { ( value ) => setStore ( "inboundPort" , value ) }
912+ onBlur = { saveInboundOnBlur }
913+ onKeyDown = { saveInboundOnEnter }
914+ placeholder = { inboundPortPlaceholder ( ) }
915+ spellcheck = { false }
916+ autocorrect = "off"
917+ autocomplete = "off"
918+ autocapitalize = "off"
919+ class = "text-12-regular"
920+ />
921+ </ div >
922+ </ div >
923+ </ SettingsRow >
924+ </ Show >
925+ </ SettingsList >
926+ </ div >
927+ </ Show >
928+ )
929+
732930 return (
733931 < div class = "flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10" >
734932 < div class = "sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]" >
@@ -745,6 +943,7 @@ export const SettingsGeneral: Component = () => {
745943 < NotificationsSection />
746944
747945 < SoundsSection />
946+ < DesktopNetworkSection />
748947
749948 < UpdatesSection />
750949
0 commit comments