fix:恢复错误消失的 redfish 入口

This commit is contained in:
mofeng-git
2026-07-05 22:01:33 +08:00
parent 696c1b4bb2
commit 618599266e
3 changed files with 149 additions and 38 deletions

View File

@@ -521,7 +521,7 @@ export default {
environmentSubtitle: 'System runtime environment and USB device maintenance',
aboutSubtitle: 'Online upgrade, version info and hardware overview',
extTtydSubtitle: 'Open a host Shell terminal in the browser',
thirdPartyAccessSubtitle: 'Configure external RustDesk, VNC, and RTSP access',
thirdPartyAccessSubtitle: 'Configure external RustDesk, VNC, RTSP, and Redfish access',
extRemoteAccessSubtitle: 'Remote access through NAT-traversal services',
extFrpcSubtitle: 'NAT traversal through the FRP client',
aboutDesc: 'Open and Lightweight IP-KVM Solution',
@@ -573,10 +573,13 @@ export default {
httpsEnabledDesc: 'Serve over an encrypted connection. A self-signed certificate is generated automatically if none is provided.',
portConfig: 'Port & Protocol',
portConfigDesc: 'The service listens on a single port at a time, determined by the HTTPS toggle',
redfishTitle: 'Redfish API',
redfishTitle: 'Redfish',
redfishDesc: 'DMTF Redfish standard management interface',
redfishEnabled: 'Enable Redfish API',
redfishEnabledDesc: 'When enabled, the standard Redfish management interface is available at /redfish/v1/',
redfishEndpoint: 'Redfish Endpoint',
copyRedfishUrl: 'Copy Redfish URL',
openRedfishEndpoint: 'Open Redfish endpoint',
httpPortReserved: 'HTTP port (reserved)',
httpsPortReserved: 'HTTPS port (reserved)',
portActive: 'Active',
@@ -1075,6 +1078,7 @@ export default {
title: 'RTSP Streaming',
desc: 'Configure RTSP video output service (H.264/H.265)',
bind: 'Bind Address',
bindHint: 'Enter an IPv4 or IPv6 address; configure the port in the port field.',
port: 'Port',
path: 'Stream Path',
pathPlaceholder: 'live',
@@ -1092,6 +1096,7 @@ export default {
title: 'VNC Remote',
desc: 'Access via TigerVNC client',
bind: 'Bind Address',
bindHint: 'Enter an IPv4 or IPv6 address; configure the port in the port field.',
port: 'Port',
encoding: 'Video Encoding',
encodingTightJpeg: 'Tight JPEG',

View File

@@ -520,7 +520,7 @@ export default {
environmentSubtitle: '系统级运行环境与 USB 设备维护',
aboutSubtitle: '在线升级、版本信息与设备硬件概览',
extTtydSubtitle: '在浏览器中打开本机 Shell 终端',
thirdPartyAccessSubtitle: '集中配置 RustDesk、VNCRTSP 外部接入',
thirdPartyAccessSubtitle: '集中配置 RustDesk、VNCRTSP 与 Redfish 外部接入',
extRemoteAccessSubtitle: '通过内网穿透服务实现远程访问',
extFrpcSubtitle: '通过 FRP 客户端实现内网穿透',
aboutDesc: '开放轻量的 IP-KVM 解决方案',
@@ -572,10 +572,13 @@ export default {
httpsEnabledDesc: '使用加密连接对外提供服务,未配置证书时自动生成自签名证书',
portConfig: '端口与协议',
portConfigDesc: '服务一次仅监听一个端口,由 HTTPS 开关决定生效端口',
redfishTitle: 'Redfish API',
redfishTitle: 'Redfish',
redfishDesc: 'DMTF Redfish 标准管理接口',
redfishEnabled: '启用 Redfish API',
redfishEnabledDesc: '开启后可通过 /redfish/v1/ 访问标准 Redfish 管理接口',
redfishEndpoint: 'Redfish 入口',
copyRedfishUrl: '复制 Redfish 地址',
openRedfishEndpoint: '打开 Redfish 入口',
httpPortReserved: 'HTTP 端口(备用)',
httpsPortReserved: 'HTTPS 端口(备用)',
portActive: '当前生效',
@@ -1074,6 +1077,7 @@ export default {
title: 'RTSP 视频流',
desc: '配置 RTSP 推流服务H.264/H.265',
bind: '监听地址',
bindHint: '填写 IPv4 或 IPv6 地址;端口请在端口字段配置。',
port: '端口',
path: '流路径',
pathPlaceholder: 'live',
@@ -1091,6 +1095,7 @@ export default {
title: 'VNC 远程',
desc: '通过 TigerVNC 客户端访问',
bind: '监听地址',
bindHint: '填写 IPv4 或 IPv6 地址;端口请在端口字段配置。',
port: '端口',
encoding: '视频编码',
encodingTightJpeg: 'Tight JPEG',

View File

@@ -224,6 +224,7 @@ function normalizeSettingsSection(value: unknown): SettingsSectionId | null {
if (typeof value !== 'string') return null
if (value === 'access-control') return 'account'
if (value === 'ext-frpc') return 'ext-remote-access'
if (value === 'redfish') return 'third-party-access'
if (value === 'ext-rustdesk' || value === 'ext-vnc' || value === 'ext-rtsp') return 'third-party-access'
return isSettingsSectionId(value) ? value : null
}
@@ -242,10 +243,7 @@ async function loadSectionData(section: SettingsSectionId) {
await loadAuthConfig()
return
case 'network':
await Promise.all([
loadWebServerConfig(),
loadRedfishConfig(),
])
await loadWebServerConfig()
return
case 'video':
await Promise.all([
@@ -277,6 +275,8 @@ async function loadSectionData(section: SettingsSectionId) {
return
case 'third-party-access':
await Promise.all([
loadWebServerConfig(),
loadRedfishConfig(),
loadRustdeskConfig(),
loadRustdeskPassword(),
loadRtspConfig(),
@@ -520,9 +520,12 @@ const previewAccessUrl = computed(() => {
const host = firstAddr.includes(':') ? `[${firstAddr}]` : firstAddr
return `${scheme}://${host}:${port}`
})
const redfishAccessUrl = computed(() => `${previewAccessUrl.value}/redfish/v1/`)
const previewUrlCopied = ref(false)
const redfishUrlCopied = ref(false)
let previewUrlCopiedTimer: ReturnType<typeof setTimeout> | null = null
let redfishUrlCopiedTimer: ReturnType<typeof setTimeout> | null = null
async function copyPreviewUrl() {
const ok = await clipboardCopy(previewAccessUrl.value)
@@ -538,6 +541,20 @@ function openPreviewUrl() {
window.open(previewAccessUrl.value, '_blank', 'noopener,noreferrer')
}
async function copyRedfishUrl() {
const ok = await clipboardCopy(redfishAccessUrl.value)
if (!ok) return
redfishUrlCopied.value = true
if (redfishUrlCopiedTimer) clearTimeout(redfishUrlCopiedTimer)
redfishUrlCopiedTimer = setTimeout(() => {
redfishUrlCopied.value = false
}, 1500)
}
function openRedfishUrl() {
window.open(redfishAccessUrl.value, '_blank', 'noopener,noreferrer')
}
interface DeviceConfig {
video: Array<{
path: string
@@ -3890,34 +3907,6 @@ watch(isWindows, () => {
</Button>
</CardFooter>
</Card>
<!-- Redfish API Card -->
<Card>
<CardHeader>
<CardTitle>{{ t('settings.redfishTitle') }}</CardTitle>
<CardDescription>{{ t('settings.redfishDesc') }}</CardDescription>
</CardHeader>
<CardContent class="space-y-5">
<div class="flex items-start justify-between gap-4">
<div class="space-y-0.5">
<Label>{{ t('settings.redfishEnabled') }}</Label>
<p class="text-xs text-muted-foreground">{{ t('settings.redfishEnabledDesc') }}</p>
</div>
<Switch v-model="redfishEnabled" />
</div>
</CardContent>
<CardFooter class="flex items-center justify-between gap-3 border-t pt-4">
<p class="text-xs text-muted-foreground flex items-center gap-1.5">
<AlertTriangle class="h-3.5 w-3.5 text-amber-500" />
{{ t('settings.restartRequiredHint') }}
</p>
<Button @click="saveRedfishConfig" :disabled="redfishSaving || autoRestarting">
<RefreshCw v-if="autoRestarting" class="h-4 w-4 mr-2 animate-spin" />
<Save v-else class="h-4 w-4 mr-2" />
{{ autoRestarting ? t('settings.restarting') : t('common.save') }}
</Button>
</CardFooter>
</Card>
</div>
<!-- MSD Section -->
@@ -4726,7 +4715,10 @@ watch(isWindows, () => {
</div>
<div class="grid gap-2 sm:grid-cols-4 sm:items-center">
<Label class="sm:text-right">{{ t('extensions.rtsp.bind') }}</Label>
<Input v-model="rtspLocalConfig.bind" class="sm:col-span-3" placeholder="0.0.0.0" :disabled="rtspStatus?.service_status === 'running'" />
<div class="sm:col-span-3 space-y-1">
<Input v-model="rtspLocalConfig.bind" placeholder="0.0.0.0 / ::" :disabled="rtspStatus?.service_status === 'running'" />
<p class="text-xs text-muted-foreground">{{ t('extensions.rtsp.bindHint') }}</p>
</div>
</div>
<div class="grid gap-2 sm:grid-cols-4 sm:items-center">
<Label class="sm:text-right">{{ t('extensions.rtsp.port') }}</Label>
@@ -4856,7 +4848,10 @@ watch(isWindows, () => {
</div>
<div class="grid gap-2 sm:grid-cols-4 sm:items-center">
<Label class="sm:text-right">{{ t('extensions.vnc.bind') }}</Label>
<Input v-model="vncLocalConfig.bind" class="sm:col-span-3" placeholder="0.0.0.0" :disabled="vncStatus?.service_status === 'running'" />
<div class="sm:col-span-3 space-y-1">
<Input v-model="vncLocalConfig.bind" placeholder="0.0.0.0 / ::" :disabled="vncStatus?.service_status === 'running'" />
<p class="text-xs text-muted-foreground">{{ t('extensions.vnc.bindHint') }}</p>
</div>
</div>
<div class="grid gap-2 sm:grid-cols-4 sm:items-center">
<Label class="sm:text-right">{{ t('extensions.vnc.port') }}</Label>
@@ -5112,6 +5107,112 @@ watch(isWindows, () => {
</div>
</div>
<!-- Redfish Section -->
<div v-show="activeSection === 'third-party-access'" class="space-y-6">
<!-- Auto-restart: restarting progress -->
<div
v-if="autoRestarting"
class="flex items-center gap-3 rounded-lg border bg-card px-4 py-3 text-sm shadow-sm"
>
<RefreshCw class="h-4 w-4 animate-spin text-primary shrink-0" />
<div class="flex-1 min-w-0">
<p class="font-medium">{{ t('settings.autoRestarting') }}</p>
<p class="text-xs text-muted-foreground">
{{ webServerConfig.https_enabled
? t('settings.autoRestartingHttpsDesc', { sec: autoRestartCountdown })
: t('settings.autoRestartingDesc') }}
</p>
</div>
<span v-if="webServerConfig.https_enabled && autoRestartCountdown > 0"
class="tabular-nums text-lg font-bold text-primary shrink-0">
{{ autoRestartCountdown }}
</span>
</div>
<!-- Auto-restart: HTTPS manual redirect (cert must be accepted by user) -->
<div
v-if="autoRestartManualUrl"
class="rounded-lg border border-amber-300 bg-amber-50 dark:bg-amber-950/20 dark:border-amber-700 px-4 py-4 space-y-3"
>
<div class="flex items-start gap-2 text-sm text-amber-800 dark:text-amber-300">
<Lock class="h-4 w-4 shrink-0 mt-0.5" />
<div>
<p class="font-medium">{{ t('settings.httpsManualRedirectTitle') }}</p>
<p class="text-xs mt-0.5 opacity-80">{{ t('settings.httpsManualRedirectDesc') }}</p>
</div>
</div>
<a
:href="autoRestartManualUrl"
class="flex items-center justify-center gap-2 w-full rounded-md bg-amber-600 hover:bg-amber-700 text-white text-sm font-medium px-4 py-2 transition-colors"
>
<ExternalLink class="h-4 w-4" />
{{ autoRestartManualUrl }}
</a>
</div>
<!-- Auto-restart: failure / timeout -->
<div
v-if="autoRestartFailed"
class="flex items-center justify-between rounded-lg border border-destructive/40 bg-destructive/5 px-4 py-3 text-sm"
>
<p class="text-destructive">{{ t('settings.autoRestartFailed') }}</p>
<Button variant="outline" size="sm" @click="triggerAutoRestart">
<RefreshCw class="h-3 w-3 mr-1" />
{{ t('common.retry') }}
</Button>
</div>
<Card>
<CardHeader>
<CardTitle>{{ t('settings.redfishTitle') }}</CardTitle>
<CardDescription>{{ t('settings.redfishDesc') }}</CardDescription>
</CardHeader>
<CardContent class="space-y-5">
<div class="flex items-start justify-between gap-4">
<div class="space-y-0.5">
<Label>{{ t('settings.redfishEnabled') }}</Label>
<p class="text-xs text-muted-foreground">{{ t('settings.redfishEnabledDesc') }}</p>
</div>
<Switch v-model="redfishEnabled" />
</div>
<div class="rounded-md border bg-muted/40 p-3 space-y-1.5">
<p class="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">{{ t('settings.redfishEndpoint') }}</p>
<div class="flex items-center gap-2">
<code class="font-mono text-xs sm:text-sm break-all flex-1 min-w-0">{{ redfishAccessUrl }}</code>
<Button
variant="ghost" size="icon" class="h-7 w-7 shrink-0"
:title="t('settings.copyRedfishUrl')"
:aria-label="t('settings.copyRedfishUrl')"
@click="copyRedfishUrl"
>
<Check v-if="redfishUrlCopied" class="h-3.5 w-3.5 text-emerald-600" />
<Copy v-else class="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost" size="icon" class="h-7 w-7 shrink-0"
:title="t('settings.openRedfishEndpoint')"
:aria-label="t('settings.openRedfishEndpoint')"
@click="openRedfishUrl"
>
<ExternalLink class="h-3.5 w-3.5" />
</Button>
</div>
</div>
</CardContent>
<CardFooter class="flex items-center justify-between gap-3 border-t pt-4">
<p class="text-xs text-muted-foreground flex items-center gap-1.5">
<AlertTriangle class="h-3.5 w-3.5 text-amber-500" />
{{ t('settings.restartRequiredHint') }}
</p>
<Button @click="saveRedfishConfig" :disabled="redfishSaving || autoRestarting">
<RefreshCw v-if="autoRestarting || redfishSaving" class="h-4 w-4 mr-2 animate-spin" />
<Save v-else class="h-4 w-4 mr-2" />
{{ autoRestarting ? t('settings.restarting') : t('common.save') }}
</Button>
</CardFooter>
</Card>
</div>
<!-- About Section -->
<div v-show="activeSection === 'about'" class="space-y-6">
<Card v-if="!isAndroid">