mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-07-29 16:21:45 +08:00
feat: 支持 WatchDog
This commit is contained in:
@@ -34,6 +34,8 @@ import type {
|
||||
FrpcConfigUpdate,
|
||||
WebConfigResponse,
|
||||
WebConfigUpdate,
|
||||
WatchdogConfigResponse,
|
||||
WatchdogConfigUpdate,
|
||||
} from '@/types/generated'
|
||||
|
||||
import { request } from './request'
|
||||
@@ -42,6 +44,16 @@ export const configApi = {
|
||||
getAll: () => request<AppConfig>('/config'),
|
||||
}
|
||||
|
||||
export const watchdogConfigApi = {
|
||||
get: () => request<WatchdogConfigResponse>('/config/watchdog', {}, { toastOnError: false }),
|
||||
|
||||
update: (config: WatchdogConfigUpdate) =>
|
||||
request<WatchdogConfigResponse>('/config/watchdog', {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(config),
|
||||
}, { toastOnError: false }),
|
||||
}
|
||||
|
||||
export const authConfigApi = {
|
||||
get: () => request<AuthConfig>('/config/auth'),
|
||||
|
||||
|
||||
@@ -813,6 +813,7 @@ export {
|
||||
rtspConfigApi,
|
||||
vncConfigApi,
|
||||
webConfigApi,
|
||||
watchdogConfigApi,
|
||||
type RustDeskConfigResponse,
|
||||
type RustDeskStatusResponse,
|
||||
type RustDeskConfigUpdate,
|
||||
|
||||
@@ -513,11 +513,32 @@ export default {
|
||||
msdSubtitle: 'Manage Mass Storage Device image directory',
|
||||
atxSubtitle: 'Configure remote power control hardware and Wake-on-LAN',
|
||||
environmentSubtitle: 'System runtime environment and USB device maintenance',
|
||||
otherSubtitle: 'Additional system hardware controls',
|
||||
aboutSubtitle: 'Online upgrade, version info and hardware overview',
|
||||
extTtydSubtitle: 'Open a host Shell terminal in the browser',
|
||||
thirdPartyAccessSubtitle: 'Configure external RustDesk, VNC, RTSP, and Redfish access',
|
||||
extRemoteAccessSubtitle: 'Remote access through NAT-traversal services',
|
||||
extFrpcSubtitle: 'NAT traversal through the FRP client',
|
||||
watchdog: {
|
||||
title: 'Hardware Watchdog',
|
||||
description: 'Automatically use an available hardware watchdog while One-KVM is running.',
|
||||
status: {
|
||||
running: 'Running',
|
||||
closed: 'Off',
|
||||
unsupported: 'Unsupported',
|
||||
error: 'Error',
|
||||
},
|
||||
safety: 'If One-KVM stops feeding the watchdog, the hardware may restart the system according to its watchdog policy.',
|
||||
loadFailed: 'Failed to load hardware watchdog status',
|
||||
toggleFailed: 'Failed to update hardware watchdog',
|
||||
enableFailed: 'Failed to enable the hardware watchdog.',
|
||||
unsupportedReason: 'No supported hardware watchdog was found.',
|
||||
discoveryFailed: 'Failed to detect the hardware watchdog.',
|
||||
openFailed: 'The hardware watchdog could not be opened.',
|
||||
feedFailed: 'The hardware watchdog is not being fed successfully.',
|
||||
disableFailed: 'The hardware watchdog cannot be safely disabled and keepalive will continue.',
|
||||
abnormalReason: 'The hardware watchdog is in an abnormal state.',
|
||||
},
|
||||
aboutDesc: 'Open and Lightweight IP-KVM Solution',
|
||||
deviceInfo: 'Device Info',
|
||||
deviceInfoDesc: 'Host system information',
|
||||
|
||||
@@ -512,11 +512,32 @@ export default {
|
||||
msdSubtitle: '管理虚拟存储设备 (MSD) 镜像目录',
|
||||
atxSubtitle: '配置远程电源控制硬件与网络唤醒',
|
||||
environmentSubtitle: '系统级运行环境与 USB 设备维护',
|
||||
otherSubtitle: '其他系统硬件控制',
|
||||
aboutSubtitle: '在线升级、版本信息与设备硬件概览',
|
||||
extTtydSubtitle: '在浏览器中打开本机 Shell 终端',
|
||||
thirdPartyAccessSubtitle: '集中配置 RustDesk、VNC、RTSP 与 Redfish 外部接入',
|
||||
extRemoteAccessSubtitle: '通过内网穿透服务实现远程访问',
|
||||
extFrpcSubtitle: '通过 FRP 客户端实现内网穿透',
|
||||
watchdog: {
|
||||
title: '硬件 Watchdog',
|
||||
description: 'One-KVM 运行期间自动使用可用的硬件 Watchdog。',
|
||||
status: {
|
||||
running: '运行中',
|
||||
closed: '已关闭',
|
||||
unsupported: '不支持',
|
||||
error: '异常',
|
||||
},
|
||||
safety: '如果 One-KVM 停止喂狗,硬件可能会按照 Watchdog 策略重启系统。',
|
||||
loadFailed: '无法加载硬件 Watchdog 状态',
|
||||
toggleFailed: '无法更新硬件 Watchdog',
|
||||
enableFailed: '无法开启硬件 Watchdog。',
|
||||
unsupportedReason: '未发现支持的硬件 Watchdog。',
|
||||
discoveryFailed: '无法发现硬件 Watchdog。',
|
||||
openFailed: '无法打开硬件 Watchdog。',
|
||||
feedFailed: '硬件 Watchdog 当前未能正常喂狗。',
|
||||
disableFailed: '硬件 Watchdog 无法安全停用,将继续喂狗。',
|
||||
abnormalReason: '硬件 Watchdog 状态异常。',
|
||||
},
|
||||
aboutDesc: '开放轻量的 IP-KVM 解决方案',
|
||||
deviceInfo: '设备信息',
|
||||
deviceInfoDesc: '主机系统信息',
|
||||
|
||||
@@ -289,6 +289,10 @@ export interface RedfishConfig {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface WatchdogConfig {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
initialized: boolean;
|
||||
auth: AuthConfig;
|
||||
@@ -306,6 +310,7 @@ export interface AppConfig {
|
||||
vnc: VncConfig;
|
||||
rtsp: RtspConfig;
|
||||
redfish: RedfishConfig;
|
||||
watchdog: WatchdogConfig;
|
||||
}
|
||||
|
||||
/** Update for a single ATX output binding */
|
||||
@@ -708,6 +713,17 @@ export interface VncStatusResponse {
|
||||
connection_count: number;
|
||||
}
|
||||
|
||||
export interface WatchdogConfigResponse {
|
||||
enabled: boolean;
|
||||
supported: boolean;
|
||||
running: boolean;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface WatchdogConfigUpdate {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Web server settings returned by `GET` / `PATCH /api/config/web`.
|
||||
*
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
updateApi,
|
||||
usbApi,
|
||||
vncConfigApi,
|
||||
watchdogConfigApi,
|
||||
type EncoderBackendInfo,
|
||||
type AuthConfig,
|
||||
type RustDeskConfigResponse,
|
||||
@@ -49,6 +50,7 @@ import type {
|
||||
Ch9329DescriptorState,
|
||||
NetworkInterfaceInfo,
|
||||
OtgNetworkStatus,
|
||||
WatchdogConfigResponse,
|
||||
} from '@/types/generated'
|
||||
import { FrpProxyType, FrpcConfigMode } from '@/types/generated'
|
||||
import { formatFpsLabel, toConfigFps } from '@/lib/fps'
|
||||
@@ -115,6 +117,7 @@ import {
|
||||
Loader2,
|
||||
AlertTriangle,
|
||||
Bot,
|
||||
TimerReset,
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
const { t, te } = useI18n()
|
||||
@@ -140,6 +143,7 @@ const SETTINGS_SECTION_IDS = [
|
||||
'hid',
|
||||
'atx',
|
||||
'environment',
|
||||
'other',
|
||||
'ext-ttyd',
|
||||
'third-party-access',
|
||||
'ext-remote-access',
|
||||
@@ -165,6 +169,7 @@ const navGroups = computed(() => [
|
||||
{ id: 'hid', label: t('settings.hid'), icon: Keyboard },
|
||||
{ id: 'atx', label: t('settings.atx'), icon: Power },
|
||||
{ id: 'environment', label: t('settings.environment'), icon: Server },
|
||||
{ id: 'other', label: t('settings.other'), icon: TimerReset },
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -263,6 +268,9 @@ async function loadSectionData(section: SettingsSectionId) {
|
||||
case 'environment':
|
||||
await fetchUsbDevices()
|
||||
return
|
||||
case 'other':
|
||||
await loadWatchdogConfig()
|
||||
return
|
||||
case 'ext-ttyd':
|
||||
case 'ext-remote-access':
|
||||
await loadExtensions()
|
||||
@@ -308,6 +316,77 @@ const authConfig = ref<AuthConfig>({
|
||||
})
|
||||
const authConfigLoading = ref(false)
|
||||
|
||||
const watchdogStatus = ref<WatchdogConfigResponse | null>(null)
|
||||
const watchdogLoading = ref(false)
|
||||
const watchdogError = ref('')
|
||||
|
||||
const watchdogStatusKey = computed(() => {
|
||||
const status = watchdogStatus.value
|
||||
if (!status) return 'closed'
|
||||
if (!status.supported) return status.enabled ? 'error' : 'unsupported'
|
||||
if (status.running) return 'running'
|
||||
if (status.enabled) return 'error'
|
||||
return 'closed'
|
||||
})
|
||||
|
||||
const watchdogDisplayReason = computed(() => {
|
||||
const reason = watchdogStatus.value?.reason
|
||||
if (!reason) return ''
|
||||
if (reason.includes('No hardware watchdog device found')) return t('settings.watchdog.unsupportedReason')
|
||||
if (reason.includes('discover')) return t('settings.watchdog.discoveryFailed')
|
||||
if (reason.includes('open a hardware watchdog')) return t('settings.watchdog.openFailed')
|
||||
if (reason.includes('keepalive')) return t('settings.watchdog.feedFailed')
|
||||
if (reason.includes('safely disabled') || reason.includes('nowayout')) return t('settings.watchdog.disableFailed')
|
||||
return t('settings.watchdog.abnormalReason')
|
||||
})
|
||||
|
||||
function watchdogRequestError(error: unknown, action: 'enable' | 'disable'): string {
|
||||
const message = error instanceof Error ? error.message : ''
|
||||
if (message.includes('No hardware watchdog device found')) return t('settings.watchdog.unsupportedReason')
|
||||
if (message.includes('cannot be safely disabled') || message.includes('nowayout')) {
|
||||
return t('settings.watchdog.disableFailed')
|
||||
}
|
||||
return t(action === 'enable' ? 'settings.watchdog.enableFailed' : 'settings.watchdog.toggleFailed')
|
||||
}
|
||||
|
||||
const watchdogStatusClass = computed(() => {
|
||||
switch (watchdogStatusKey.value) {
|
||||
case 'running': return 'bg-green-500'
|
||||
case 'error': return 'bg-red-500'
|
||||
default: return 'bg-gray-400'
|
||||
}
|
||||
})
|
||||
|
||||
async function loadWatchdogConfig() {
|
||||
watchdogLoading.value = true
|
||||
watchdogError.value = ''
|
||||
try {
|
||||
watchdogStatus.value = await watchdogConfigApi.get()
|
||||
} catch (error) {
|
||||
watchdogError.value = error instanceof Error ? error.message : t('settings.watchdog.loadFailed')
|
||||
} finally {
|
||||
watchdogLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateWatchdog(enabled: boolean) {
|
||||
if (!watchdogStatus.value || watchdogLoading.value) return
|
||||
watchdogLoading.value = true
|
||||
watchdogError.value = ''
|
||||
try {
|
||||
watchdogStatus.value = await watchdogConfigApi.update({ enabled })
|
||||
} catch (error) {
|
||||
watchdogError.value = watchdogRequestError(error, enabled ? 'enable' : 'disable')
|
||||
try {
|
||||
watchdogStatus.value = await watchdogConfigApi.get()
|
||||
} catch {
|
||||
// Preserve the last confirmed state when status refresh also fails.
|
||||
}
|
||||
} finally {
|
||||
watchdogLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const extensions = ref<ExtensionsStatus | null>(null)
|
||||
const extensionsLoading = ref(false)
|
||||
const extensionLogs = ref<Record<string, string[]>>({
|
||||
@@ -3603,6 +3682,46 @@ watch(isWindows, () => {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
<div v-show="activeSection === 'other'" class="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0 space-y-1">
|
||||
<CardTitle>{{ t('settings.watchdog.title') }}</CardTitle>
|
||||
<CardDescription>{{ t('settings.watchdog.description') }}</CardDescription>
|
||||
</div>
|
||||
<Switch
|
||||
:aria-label="t('settings.watchdog.title')"
|
||||
:model-value="watchdogStatus?.enabled ?? false"
|
||||
:disabled="watchdogLoading || !watchdogStatus || (!watchdogStatus.supported && !watchdogStatus.enabled)"
|
||||
@update:model-value="updateWatchdog"
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<Loader2 v-if="watchdogLoading" class="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
<span v-else class="h-2.5 w-2.5 rounded-full" :class="watchdogStatusClass" />
|
||||
<span class="font-medium">
|
||||
{{ watchdogLoading
|
||||
? t('common.loading')
|
||||
: t(`settings.watchdog.status.${watchdogStatusKey}`) }}
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
v-if="watchdogError || watchdogDisplayReason"
|
||||
class="text-sm"
|
||||
:class="watchdogStatusKey === 'error' || watchdogError ? 'text-destructive' : 'text-muted-foreground'"
|
||||
>
|
||||
{{ watchdogError || watchdogDisplayReason }}
|
||||
</p>
|
||||
<div class="flex items-start gap-2 rounded-md border border-amber-300/70 bg-amber-50 px-3 py-2.5 text-xs text-amber-900 dark:border-amber-700 dark:bg-amber-950/20 dark:text-amber-200">
|
||||
<AlertTriangle class="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
||||
<p>{{ t('settings.watchdog.safety') }}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div v-show="activeSection === 'network'" class="space-y-6">
|
||||
|
||||
<!-- Auto-restart: restarting progress -->
|
||||
|
||||
Reference in New Issue
Block a user