add the Hostname Editbar into the Web UI

This commit is contained in:
LingkongSky
2025-04-10 13:30:04 +08:00
parent f71c2d3f8d
commit e3fd5876d2
8 changed files with 162 additions and 0 deletions

View File

@@ -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"`
}

View File

@@ -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

View File

@@ -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")
}

View File

@@ -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');

View File

@@ -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',

View File

@@ -216,6 +216,13 @@ const zh = {
description: '启用鼠标抖动',
tip: '在鼠标离开60s未操作时自动进行抖动以防止系统睡眠'
},
hostname: {
title: 'Hostname',
description: 'NanoKVM 主机名',
tip: '修改后需要重启生效',
success: '主机名修改成功',
error: '主机名修改失败'
},
powerConfirm:{
title: '电源操作确认',
description: '启用电源操作二次确认',

View File

@@ -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 (
<div className="flex items-center justify-between">
<div className="flex flex-col">
<div className="flex items-center space-x-2">
<span>{t('settings.device.hostname.title')}</span>
<Tooltip
title={t('settings.device.hostname.tip')}
className="cursor-pointer"
placement="bottom"
overlayStyle={{ maxWidth: '300px' }}
>
<CircleAlertIcon size={15} />
</Tooltip>
</div>
<span className="text-xs text-neutral-500">{t('settings.device.hostname.description')}</span>
</div>
{!isLoading ? <Input onFocus={() => setIsKeyboardEnable(false)} onBlur={() => setIsKeyboardEnable(true)} style={{ width: 150 }} value={hostname} onChange={(e) => setHostname(e.target.value)} onPressEnter={update} /> : <LoadingOutlined />}
{contextHolder}
</div>
);
};

View File

@@ -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 = () => {
<div className="flex flex-col space-y-6">
<Oled />
<Hostname />
<Wifi />
<Ssh />
<Mdns />