From e3fd5876d2efcbf527fcf8d978626dd2b24a7907 Mon Sep 17 00:00:00 2001 From: LingkongSky Date: Thu, 10 Apr 2025 13:30:04 +0800 Subject: [PATCH] add the Hostname Editbar into the Web UI --- server/proto/vm.go | 8 ++ server/router/vm.go | 3 + server/service/vm/hostname.go | 51 +++++++++++++ web/src/api/vm.ts | 10 +++ web/src/i18n/locales/en.ts | 7 ++ web/src/i18n/locales/zh.ts | 7 ++ .../desktop/menu/settings/device/hostname.tsx | 74 +++++++++++++++++++ .../desktop/menu/settings/device/index.tsx | 2 + 8 files changed, 162 insertions(+) create mode 100644 server/service/vm/hostname.go create mode 100644 web/src/pages/desktop/menu/settings/device/hostname.tsx diff --git a/server/proto/vm.go b/server/proto/vm.go index d8506b8..e0a47c6 100644 --- a/server/proto/vm.go +++ b/server/proto/vm.go @@ -102,3 +102,11 @@ type GetMouseJigglerStateRsp struct { type GetMdnsStateRsp struct { Enabled bool `json:"enabled"` } + +type SetHostnameReq struct { + Hostname string `json:"hostname"` +} + +type GetHostnameRsp struct { + Hostname string `json:"hostname"` +} diff --git a/server/router/vm.go b/server/router/vm.go index 46d1cc5..58d1e3b 100644 --- a/server/router/vm.go +++ b/server/router/vm.go @@ -48,6 +48,9 @@ func vmRouter(r *gin.Engine) { api.POST("/vm/mouseJiggler/enable", service.EnableMouseJiggler) // enable MouseJiggler api.POST("/vm/mouseJiggler/disable", service.DisableMouseJiggler) // disable MouseJiggler + api.GET("/vm/hostname", service.GetHostname) // Get Hostname + api.POST("/vm/hostname", service.SetHostname) // Set Hostname + api.GET("/vm/mdns", service.GetMdnsState) // get mDNS state api.POST("/vm/mdns/enable", service.EnableMdns) // enable mDNS api.POST("/vm/mdns/disable", service.DisableMdns) // disable mDNS diff --git a/server/service/vm/hostname.go b/server/service/vm/hostname.go new file mode 100644 index 0000000..37d7a5f --- /dev/null +++ b/server/service/vm/hostname.go @@ -0,0 +1,51 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "fmt" + "os" + "strings" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + HostnameFile = "/etc/hostname" +) + +func (s *Service) SetHostname(c *gin.Context) { + var req proto.SetHostnameReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + data := []byte(fmt.Sprintf("%s", req.Hostname)) + + err := os.WriteFile(HostnameFile, data, 0o644) + if err != nil { + rsp.ErrRsp(c, -2, "failed to write data") + return + } + + rsp.OkRsp(c) + log.Debugf("set Hostname: %s", req.Hostname) +} + +func (s *Service) GetHostname(c *gin.Context) { + var rsp proto.Response + + data, err := os.ReadFile(HostnameFile) + if err != nil { + rsp.ErrRsp(c, -1, "read Hostname failed") + return + } + + rsp.OkRspWithData(c, &proto.GetHostnameRsp{ + Hostname: strings.Replace(string(data), "\n", "", -1), + }) + log.Debugf("get Hostname successful") +} diff --git a/web/src/api/vm.ts b/web/src/api/vm.ts index 943a7b7..5d5493a 100644 --- a/web/src/api/vm.ts +++ b/web/src/api/vm.ts @@ -107,6 +107,16 @@ export function disableMouseJiggler() { return http.post('/api/vm/mouseJiggler/disable'); } +// get Hostname +export function getHostname() { + return http.get('/api/vm/hostname'); +} + +// set Hostname +export function setHostname(hostname: string) { + return http.post('/api/vm/hostname', { hostname }); +} + // get mDNS state export function getMdnsState() { return http.get('/api/vm/mdns'); diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index a6cea2f..2cf9091 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -225,6 +225,13 @@ const en = { description: 'Enable the swap partition', tip: 'Enable the default swap partition(128M)' }, + hostname: { + title: 'Hostname', + description: 'NanoKVM Hostname', + tip: 'Need reboot to apply the change', + success: 'Hostname changed successfully', + error: 'Hostname change failed' + }, mouseJiggler: { title: 'Mouse Jiggler', description: 'Enable the mouse jiggler', diff --git a/web/src/i18n/locales/zh.ts b/web/src/i18n/locales/zh.ts index 2abdcca..a2d7465 100644 --- a/web/src/i18n/locales/zh.ts +++ b/web/src/i18n/locales/zh.ts @@ -216,6 +216,13 @@ const zh = { description: '启用鼠标抖动', tip: '在鼠标离开60s未操作时自动进行抖动以防止系统睡眠' }, + hostname: { + title: 'Hostname', + description: 'NanoKVM 主机名', + tip: '修改后需要重启生效', + success: '主机名修改成功', + error: '主机名修改失败' + }, powerConfirm:{ title: '电源操作确认', description: '启用电源操作二次确认', diff --git a/web/src/pages/desktop/menu/settings/device/hostname.tsx b/web/src/pages/desktop/menu/settings/device/hostname.tsx new file mode 100644 index 0000000..51a9ad9 --- /dev/null +++ b/web/src/pages/desktop/menu/settings/device/hostname.tsx @@ -0,0 +1,74 @@ +import { useEffect, useState } from 'react'; +import { Input, Tooltip, message } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { CircleAlertIcon } from 'lucide-react'; +import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts'; +import { useSetAtom } from 'jotai'; + +import * as api from '@/api/vm.ts'; +import { LoadingOutlined } from '@ant-design/icons'; + +export const Hostname = () => { + + const { t } = useTranslation(); + const setIsKeyboardEnable = useSetAtom(isKeyboardEnableAtom); + const [isLoading, setIsLoading] = useState(false); + const [hostname, setHostname] = useState(""); + const [messageApi, contextHolder] = message.useMessage(); + useEffect(() => { + setIsLoading(true); + + api + .getHostname() + .then((rsp) => { + if (rsp.data?.hostname) { + setHostname(rsp.data?.hostname.toString()); + } + }) + .finally(() => { + setIsLoading(false); + }); + }, []); + + + async function update() { + if (isLoading) return; + setIsLoading(true); + + const rsp = await api.setHostname(hostname); + + if (rsp.code !== 0) { + console.log(rsp.msg); + messageApi.error(t('settings.device.hostname.error') + ":" + rsp.msg); + return; + }else{ + messageApi.success(t('settings.device.hostname.success')); + } + setIsLoading(false); + } + + return ( +
+
+
+ {t('settings.device.hostname.title')} + + + + +
+ {t('settings.device.hostname.description')} +
+ {!isLoading ? setIsKeyboardEnable(false)} onBlur={() => setIsKeyboardEnable(true)} style={{ width: 150 }} value={hostname} onChange={(e) => setHostname(e.target.value)} onPressEnter={update} /> : } + {contextHolder} +
+ + ); +}; + + diff --git a/web/src/pages/desktop/menu/settings/device/index.tsx b/web/src/pages/desktop/menu/settings/device/index.tsx index 7137edc..8e416c1 100644 --- a/web/src/pages/desktop/menu/settings/device/index.tsx +++ b/web/src/pages/desktop/menu/settings/device/index.tsx @@ -16,6 +16,7 @@ import { Wifi } from './wifi.tsx'; import { Swap } from './swap.tsx'; import { MouseJiggler } from './mouse-jiggler.tsx'; import { PowerConfirm } from './PowerConfirm.tsx'; +import { Hostname } from './hostname.tsx'; export const Device = () => { const { t } = useTranslation(); @@ -37,6 +38,7 @@ export const Device = () => {
+