revert(web,hub): drop the connection switch button, keep a read-only environment display

This commit is contained in:
2026-08-03 00:09:04 +08:00
parent 4711fcd69d
commit e969a0b1d0
8 changed files with 24 additions and 91 deletions
-13
View File
@@ -21,7 +21,6 @@ export interface ServerSettings {
listenPort: number
publicUrl: string
corsOrigins: string[]
lanUrl: string | null
}
export interface ServerSettingsResult {
@@ -34,7 +33,6 @@ export interface ServerSettingsResult {
listenHost: 'env' | 'file' | 'default'
listenPort: 'env' | 'file' | 'default'
publicUrl: 'env' | 'file' | 'default'
lanUrl: 'file' | 'default'
corsOrigins: 'env' | 'file' | 'default'
}
savedToFile: boolean
@@ -112,7 +110,6 @@ export async function loadServerSettings(dataDir: string): Promise<ServerSetting
listenHost: 'default',
listenPort: 'default',
publicUrl: 'default',
lanUrl: 'default',
corsOrigins: 'default',
}
// telegramBotToken: env > file > null
@@ -217,15 +214,6 @@ export async function loadServerSettings(dataDir: string): Promise<ServerSetting
sources.publicUrl = 'file'
}
// lanUrl: file only (optional alternative access URL, e.g. LAN direct)
let lanUrl: string | null = null
if (typeof settings.lanUrl === 'string' && settings.lanUrl.trim()) {
lanUrl = settings.lanUrl.trim()
sources.lanUrl = 'file'
} else {
sources.lanUrl = 'default'
}
// corsOrigins: env > file > derived from publicUrl
let corsOrigins: string[]
if (process.env.CORS_ORIGINS) {
@@ -256,7 +244,6 @@ export async function loadServerSettings(dataDir: string): Promise<ServerSetting
listenHost,
listenPort,
publicUrl,
lanUrl,
corsOrigins,
},
sources,
-5
View File
@@ -40,7 +40,6 @@ export interface ConfigSources {
listenHost: ConfigSource
listenPort: ConfigSource
publicUrl: ConfigSource
lanUrl: ConfigSource
corsOrigins: ConfigSource
cliApiToken: 'env' | 'file' | 'generated'
}
@@ -88,9 +87,6 @@ class Configuration {
/** Public URL for external access (e.g., Telegram Mini App) */
public readonly publicUrl: string
/** Optional alternative access URL (e.g. LAN direct) */
public readonly lanUrl: string | null
/** Allowed CORS origins for Mini App + Socket.IO (comma-separated env override) */
public readonly corsOrigins: string[]
@@ -117,7 +113,6 @@ class Configuration {
this.listenHost = serverSettings.listenHost
this.listenPort = serverSettings.listenPort
this.publicUrl = serverSettings.publicUrl
this.lanUrl = serverSettings.lanUrl
this.corsOrigins = serverSettings.corsOrigins
// CLI API token - will be set by _setCliApiToken() before create() returns
+1 -10
View File
@@ -232,16 +232,7 @@ function createWebApp(options: {
const configuration = getConfiguration()
const corsOrigins = options.corsOrigins ?? configuration.corsOrigins
const mergedCorsOrigins = configuration.lanUrl
? (() => {
try {
return [...new Set([...corsOrigins, new URL(configuration.lanUrl).origin])]
} catch {
return corsOrigins
}
})()
: corsOrigins
const corsOriginOption = mergedCorsOrigins.includes('*') ? '*' : mergedCorsOrigins
const corsOriginOption = corsOrigins.includes('*') ? '*' : corsOrigins
const corsMiddleware = cors({
origin: corsOriginOption,
allowMethods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],
+1 -12
View File
@@ -162,17 +162,6 @@ function AppInner() {
queryClient.clear()
}, [baseUrl, queryClient])
// Self-heal: when authentication fails against a stored hub URL that is
// not the page's own origin (e.g. a LAN address that is no longer
// reachable), drop it so the app falls back to the page origin instead of
// being stuck on a dead endpoint.
const pageOrigin = typeof window !== 'undefined' ? window.location.origin : null
useEffect(() => {
if (authError && serverUrl && pageOrigin && serverUrl !== pageOrigin) {
clearServerUrl()
}
}, [authError, serverUrl, pageOrigin, clearServerUrl])
// Clean up URL params after successful auth (for direct access links)
useEffect(() => {
if (!token || !api) return
@@ -460,7 +449,7 @@ function AppInner() {
}
return (
<AppContextProvider value={{ api, token, baseUrl, setServerUrl }}>
<AppContextProvider value={{ api, token, baseUrl }}>
<VoiceProvider>
<PwaUpdateBannerWithStatusOffset
isSyncing={isSyncing}
-3
View File
@@ -1,13 +1,10 @@
import { createContext, useContext, type ReactNode } from 'react'
import type { ApiClient } from '@/api/client'
import type { ServerUrlResult } from '@/hooks/useServerUrl'
type AppContextValue = {
api: ApiClient
token: string
baseUrl: string
setServerUrl: (input: string) => ServerUrlResult
}
const AppContext = createContext<AppContextValue | null>(null)
+1 -2
View File
@@ -78,10 +78,9 @@ export default {
'settings.connection.title': 'Connection',
'settings.connection.hint': 'When on the same network you can connect straight to the hub on your Mac for lower latency.',
'settings.connection.current': 'Current',
'settings.connection.mode': 'Environment',
'settings.connection.lan': 'LAN direct',
'settings.connection.public': 'Public domain',
'settings.connection.switchToLan': 'Switch to LAN (faster)',
'settings.connection.switchToPublic': 'Switch to public domain',
'sessions.group.new': 'New session in this directory',
'sessions.machineFilter.label': 'Filter sessions by machine',
'sessions.machineFilter.all': 'All',
+1 -2
View File
@@ -78,10 +78,9 @@ export default {
'settings.connection.title': '连接方式',
'settings.connection.hint': '同一局域网内可直连 Mac 上的 hub,速度更快。',
'settings.connection.current': '当前连接',
'settings.connection.mode': '当前环境',
'settings.connection.lan': '局域网直连',
'settings.connection.public': '公网域名',
'settings.connection.switchToLan': '切换到局域网直连(更快)',
'settings.connection.switchToPublic': '切换到公网域名',
'sessions.group.new': '在此目录新建会话',
'sessions.machineFilter.label': '按机器筛选会话',
'sessions.machineFilter.all': '全部',
+20 -44
View File
@@ -9,9 +9,9 @@ const locales: ReadonlyArray<{ value: Locale; label: string }> = [
{ value: 'zh-CN', label: '简体中文' },
]
function ConnectionSwitch() {
function ConnectionInfo() {
const { t } = useTranslation()
const { api, baseUrl, setServerUrl } = useAppContext()
const { api, baseUrl } = useAppContext()
const [info, setInfo] = useState<{ publicUrl: string | null; lanUrl: string | null } | null>(null)
useEffect(() => {
@@ -35,26 +35,19 @@ function ConnectionSwitch() {
}
}, [api])
if (!info?.lanUrl) {
return null
}
let publicOrigin: string | null = null
try {
publicOrigin = info.publicUrl ? new URL(info.publicUrl).origin : null
} catch {
publicOrigin = null
if (info?.publicUrl) {
try {
publicOrigin = new URL(info.publicUrl).origin
} catch {
publicOrigin = null
}
}
// Anything other than the public domain counts as the alternative
// (LAN) connection — this also covers IP-based access like
// http://192.168.x.x:3006 even when the configured lanUrl uses a
// hostname. baseUrl is the hub the app is actually talking to — it
// updates in place after setServerUrl() without a page reload.
// hostname. baseUrl is the hub the app is actually talking to.
const isOnLan = baseUrl !== '' && baseUrl !== publicOrigin
const target = isOnLan ? info.publicUrl : info.lanUrl
if (!target) {
return null
}
return (
<SettingsSection title={t('settings.connection.title')} description={t('settings.connection.hint')}>
@@ -62,33 +55,16 @@ function ConnectionSwitch() {
label={t('settings.connection.current')}
trailing={<span className="max-w-[60%] truncate text-xs text-[var(--app-hint)]">{baseUrl}</span>}
/>
<SettingsRow
label={isOnLan ? t('settings.connection.public') : t('settings.connection.lan')}
trailing={
<button
type="button"
onClick={() => {
// Browsers block in-place requests from an HTTPS
// page to an HTTP hub (mixed content), so an
// HTTPS→HTTP switch must navigate instead. The
// loaded page is then HTTP same-origin and works.
const pageIsHttps = typeof window !== 'undefined'
&& window.location.protocol === 'https:'
const targetIsHttps = target.startsWith('https://')
if (pageIsHttps && !targetIsHttps) {
window.location.href = target
return
}
// Otherwise switch the hub connection in place —
// the app reconnects without a full navigation.
setServerUrl(target)
}}
className="shrink-0 rounded-md border border-[var(--app-border)] bg-[var(--app-subtle-bg)] px-2 py-1.5 text-xs text-[var(--app-fg)] hover:bg-[var(--app-secondary-bg)]"
>
{isOnLan ? t('settings.connection.switchToPublic') : t('settings.connection.switchToLan')}
</button>
}
/>
{info ? (
<SettingsRow
label={t('settings.connection.mode')}
trailing={
<span className="text-xs text-[var(--app-hint)]">
{isOnLan ? t('settings.connection.lan') : t('settings.connection.public')}
</span>
}
/>
) : null}
</SettingsSection>
)
}
@@ -98,7 +74,7 @@ export default function SettingsGeneralPage() {
const { baseUrl } = useAppContext()
return (
<SettingsPageContent description={t('settings.general.description')}>
<ConnectionSwitch />
<ConnectionInfo />
<SettingsSection title={t('settings.language.label')}>
<SettingsChoiceGroup hideLabel label={t('settings.language.label')} value={locale} options={locales} onChange={setLocale} />
</SettingsSection>