From b5e48a07e82df3aedd60442342ae50b95684a697 Mon Sep 17 00:00:00 2001 From: wj-xiao Date: Fri, 21 Feb 2025 14:52:48 +0800 Subject: [PATCH 1/3] fix: mounted image were not being detected correctly perf: add support for CD-ROM mode when mounting image perf: add a loading state during login perf: add changelog link in settings --- server/proto/storage.go | 7 +- server/router/storage.go | 1 + server/service/storage/image.go | 106 +++++++++--------- server/service/vm/info.go | 1 + web/src/api/storage.ts | 10 +- web/src/pages/auth/login/index.tsx | 10 +- web/src/pages/desktop/menu/image/images.tsx | 5 +- web/src/pages/desktop/menu/image/index.tsx | 26 ++++- web/src/pages/desktop/menu/image/tips.tsx | 8 +- .../desktop/menu/settings/update/index.tsx | 13 ++- 10 files changed, 116 insertions(+), 71 deletions(-) diff --git a/server/proto/storage.go b/server/proto/storage.go index 75a6124..33c25cf 100644 --- a/server/proto/storage.go +++ b/server/proto/storage.go @@ -5,9 +5,14 @@ type GetImagesRsp struct { } type MountImageReq struct { - File string `json:"file" validate:"omitempty"` + File string `json:"file" validate:"omitempty"` + Cdrom bool `json:"cdrom" validate:"omitempty"` } type GetMountedImageRsp struct { File string `json:"file"` } + +type GetCdRomRsp struct { + Cdrom int64 `json:"cdrom"` +} diff --git a/server/router/storage.go b/server/router/storage.go index f7fdf89..d29a327 100644 --- a/server/router/storage.go +++ b/server/router/storage.go @@ -14,4 +14,5 @@ func storageRouter(r *gin.Engine) { api.GET("/storage/image", service.GetImages) // get image list api.GET("/storage/image/mounted", service.GetMountedImage) // get mounted image api.POST("/storage/image/mount", service.MountImage) // mount image + api.GET("/storage/cdrom", service.GetCdRom) // get CD-ROM flag } diff --git a/server/service/storage/image.go b/server/service/storage/image.go index cd0884e..63360ed 100644 --- a/server/service/storage/image.go +++ b/server/service/storage/image.go @@ -1,11 +1,12 @@ package storage import ( - "errors" "os" "os/exec" "path/filepath" + "strconv" "strings" + "time" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" @@ -18,9 +19,7 @@ const ( imageNone = "/dev/mmcblk0p3" cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom" mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file" - removableFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/removable" roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro" - roDisk = "/boot/usb.disk0.ro" ) func (s *Service) GetImages(c *gin.Context) { @@ -61,55 +60,43 @@ func (s *Service) MountImage(c *gin.Context) { return } + // cdrom and ro flag + // set to 0 when unmount image + // set to 1 when mount image and the CD-ROM is enabled + if req.File == "" || req.Cdrom { + flag := "0" + if req.File != "" && req.Cdrom { + flag = "1" + } + + // unmount + if err := os.WriteFile(mountDevice, []byte("\n"), 0o666); err != nil { + log.Errorf("unmount file failed: %s", err) + rsp.ErrRsp(c, -2, "unmount image failed") + return + } + + // ro flag + if err := os.WriteFile(roFlag, []byte(flag), 0o666); err != nil { + log.Errorf("set ro flag failed: %s", err) + rsp.ErrRsp(c, -2, "set ro flag failed") + return + } + + // cdrom flag + if err := os.WriteFile(cdromFlag, []byte(flag), 0o666); err != nil { + log.Errorf("set cdrom flag failed: %s", err) + rsp.ErrRsp(c, -2, "set cdrom flag failed") + return + } + } + + // mount image := req.File if image == "" { image = imageNone } - imageRemovable := "1" - - imageRo := "0" - isImageRo, _ := isFlagExist(roDisk) - if isImageRo { - imageRo = "1" - } - - imageLow := strings.ToLower(image) - imageCdrom := "0" - if strings.HasSuffix(imageLow, ".iso") { - imageRo = "1" - imageCdrom = "1" - } - - // unmount - if err := os.WriteFile(mountDevice, []byte("\n"), 0o666); err != nil { - log.Errorf("unmount file failed: %s", err) - rsp.ErrRsp(c, -2, "unmount image failed") - return - } - - // removable flag - if err := os.WriteFile(removableFlag, []byte(imageRemovable), 0o666); err != nil { - log.Errorf("set removable flag failed: %s", err) - rsp.ErrRsp(c, -2, "set removable flag failed") - return - } - - // ro flag - if err := os.WriteFile(roFlag, []byte(imageRo), 0o666); err != nil { - log.Errorf("set ro flag failed: %s", err) - rsp.ErrRsp(c, -2, "set ro flag failed") - return - } - - // cdrom flag - if err := os.WriteFile(cdromFlag, []byte(imageCdrom), 0o666); err != nil { - log.Errorf("set cdrom flag failed: %s", err) - rsp.ErrRsp(c, -2, "set cdrom flag failed") - return - } - - // mount if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil { log.Errorf("mount file %s failed: %s", image, err) rsp.ErrRsp(c, -2, "mount image failed") @@ -128,6 +115,7 @@ func (s *Service) MountImage(c *gin.Context) { rsp.ErrRsp(c, -2, "execute command failed") return } + time.Sleep(100 * time.Millisecond) } rsp.OkRsp(c) @@ -155,17 +143,25 @@ func (s *Service) GetMountedImage(c *gin.Context) { rsp.OkRspWithData(c, data) } -func isFlagExist(flag string) (bool, error) { - _, err := os.Stat(flag) +func (s *Service) GetCdRom(c *gin.Context) { + var rsp proto.Response - if err == nil { - return true, nil + content, err := os.ReadFile(cdromFlag) + if err != nil { + rsp.ErrRsp(c, -1, "read failed") + return } - if errors.Is(err, os.ErrNotExist) { - return false, nil + flag := strings.ReplaceAll(string(content), "\n", "") + flatInt, err := strconv.ParseInt(flag, 10, 64) + if err != nil { + rsp.ErrRsp(c, -2, "parse failed") + return } - log.Errorf("check file %s err: %s", flag, err) - return false, err + data := &proto.GetCdRomRsp{ + Cdrom: flatInt, + } + + rsp.OkRspWithData(c, data) } diff --git a/server/service/vm/info.go b/server/service/vm/info.go index fb9b32b..ebc92c7 100644 --- a/server/service/vm/info.go +++ b/server/service/vm/info.go @@ -18,6 +18,7 @@ var imageVersionMap = map[string]string{ "2024-07-23-20-18-587710.img": "v1.1.0", "2024-08-08-19-44-bef2ca.img": "v1.2.0", "2024-11-13-09-59-9c961a.img": "v1.3.0", + "2025-02-17-16-59-3649fe.img": "v1.4.0", } func (s *Service) GetInfo(c *gin.Context) { diff --git a/web/src/api/storage.ts b/web/src/api/storage.ts index b9dcdc8..9f6c1b8 100644 --- a/web/src/api/storage.ts +++ b/web/src/api/storage.ts @@ -11,9 +11,15 @@ export function getMountedImage() { } // mount/unmount image -export function mountImage(file?: string) { +export function mountImage(file?: string, cdrom?: boolean) { const data = { - file: file ? file : '' + file: file ? file : '', + cdrom: cdrom }; return http.post('/api/storage/image/mount', data); } + +// get CD-ROM flag +export function getCdRom() { + return http.get('/api/storage/cdrom'); +} diff --git a/web/src/pages/auth/login/index.tsx b/web/src/pages/auth/login/index.tsx index 91741f0..e3afdd8 100644 --- a/web/src/pages/auth/login/index.tsx +++ b/web/src/pages/auth/login/index.tsx @@ -14,6 +14,8 @@ import { Tips } from './tips.tsx'; export const Login = () => { const navigate = useNavigate(); const { t } = useTranslation(); + + const [isLoading, setIsloading] = useState(false); const [msg, setMsg] = useState(''); useEffect(() => { @@ -29,6 +31,9 @@ export const Login = () => { }, [msg]); function login(values: any) { + if (isLoading) return; + setIsloading(true); + const username = values.username; const password = encrypt(values.password); @@ -48,6 +53,9 @@ export const Login = () => { }) .catch(() => { setMsg(t('auth.error')); + }) + .finally(() => { + setIsloading(false); }); } @@ -84,7 +92,7 @@ export const Login = () => {
{msg}
- diff --git a/web/src/pages/desktop/menu/image/images.tsx b/web/src/pages/desktop/menu/image/images.tsx index c21a7a4..c68ff45 100644 --- a/web/src/pages/desktop/menu/image/images.tsx +++ b/web/src/pages/desktop/menu/image/images.tsx @@ -14,10 +14,11 @@ import * as api from '@/api/storage.ts'; import { client } from '@/lib/websocket.ts'; type ImagesProps = { + cdrom: boolean; setIsMounted: (isMounted: boolean) => void; }; -export const Images = ({ setIsMounted }: ImagesProps) => { +export const Images = ({ cdrom, setIsMounted }: ImagesProps) => { const { t } = useTranslation(); const [notify, contextHolder] = notification.useNotification(); @@ -77,7 +78,7 @@ export const Images = ({ setIsMounted }: ImagesProps) => { const filename = mountedImage === image ? '' : image; api - .mountImage(filename) + .mountImage(filename, cdrom) .then((rsp) => { if (rsp.code !== 0) { openNotification(); diff --git a/web/src/pages/desktop/menu/image/index.tsx b/web/src/pages/desktop/menu/image/index.tsx index b0e5d29..bb07a5e 100644 --- a/web/src/pages/desktop/menu/image/index.tsx +++ b/web/src/pages/desktop/menu/image/index.tsx @@ -1,11 +1,11 @@ import { useEffect, useState } from 'react'; -import { Divider, Popover } from 'antd'; +import { Divider, Popover, Switch, Tooltip } from 'antd'; import clsx from 'clsx'; import { DiscIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useMediaQuery } from 'react-responsive'; -import { getMountedImage } from '@/api/storage.ts'; +import { getCdRom, getMountedImage } from '@/api/storage.ts'; import { Images } from './images.tsx'; import { Tips } from './tips.tsx'; @@ -16,24 +16,40 @@ export const Image = () => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isMounted, setIsMounted] = useState(false); + const [cdrom, setCdrom] = useState(false); useEffect(() => { getMountedImage().then((rsp) => { if (rsp.code !== 0) return; setIsMounted(!!rsp.data?.file); }); + + getCdRom().then((rsp) => { + if (rsp.code !== 0) return; + setCdrom(rsp.data?.cdrom === 1); + }); }, []); const content = (
- {t('image.title')} - +
+ {t('image.title')} + +
+ + +
+ CD-ROM + + setCdrom(checked)}> +
+
- {isPopoverOpen && } + {isPopoverOpen && }
); diff --git a/web/src/pages/desktop/menu/image/tips.tsx b/web/src/pages/desktop/menu/image/tips.tsx index 01af38f..516fe0e 100644 --- a/web/src/pages/desktop/menu/image/tips.tsx +++ b/web/src/pages/desktop/menu/image/tips.tsx @@ -51,13 +51,13 @@ export const Tips = () => { return ( <>
setIsModalOpen(true)} > - - {t('image.tips.title')} - + {/**/} + {/* {t('image.tips.title')}*/} + {/**/}
{ setErrMsg(''); window.location.reload(); - }, 10000); + }, 12000); }); } @@ -114,6 +114,17 @@ export const Update = ({ setIsLocked }: UpdateProps) => { )} {status === 'failed' && } + +
+ +
); }; From f973316f8f26e60d16b6129d805c8db456b59e50 Mon Sep 17 00:00:00 2001 From: wj-xiao Date: Fri, 21 Feb 2025 15:04:39 +0800 Subject: [PATCH 2/3] Fix cdrom translation --- web/src/i18n/locales/en.ts | 5 +++-- web/src/i18n/locales/zh.ts | 1 + web/src/pages/desktop/menu/image/index.tsx | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index 5450b77..d776aa6 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -89,6 +89,7 @@ const en = { title: 'Image', loading: 'Loading...', empty: 'Nothing Found', + cdrom: 'Mount the image in CD-ROM mode', mountFailed: 'Mount Failed', mountDesc: "In some systems, it's necessary to eject the virtual disk on the remote host before mounting the image.", @@ -256,8 +257,8 @@ const en = { } }, error: { - title: 'We\'ve ran into an issue', - refresh: 'Refresh', + title: "We've ran into an issue", + refresh: 'Refresh' } } }; diff --git a/web/src/i18n/locales/zh.ts b/web/src/i18n/locales/zh.ts index 53377b9..57efb88 100644 --- a/web/src/i18n/locales/zh.ts +++ b/web/src/i18n/locales/zh.ts @@ -87,6 +87,7 @@ const zh = { title: '镜像', loading: '加载中', empty: '无镜像文件', + cdrom: '以 CD-ROM 模式挂载镜像', mountFailed: '挂载失败', mountDesc: '在某些系统中,需要在远程主机中弹出虚拟硬盘后再挂载镜像。', tips: { diff --git a/web/src/pages/desktop/menu/image/index.tsx b/web/src/pages/desktop/menu/image/index.tsx index bb07a5e..d93da94 100644 --- a/web/src/pages/desktop/menu/image/index.tsx +++ b/web/src/pages/desktop/menu/image/index.tsx @@ -38,7 +38,7 @@ export const Image = () => { - +
CD-ROM From e4367f0b69dbd06ee220d3a8ecd490fb92d85554 Mon Sep 17 00:00:00 2001 From: wj-xiao Date: Fri, 21 Feb 2025 19:20:19 +0800 Subject: [PATCH 3/3] update CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43afbb9..69d4730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.2.1 [b5e48a0](https://github.com/sipeed/NanoKVM/commit/b5e48a07e82df3aedd60442342ae50b95684a697) (2025-02-21) + +* fix: mounted image were not being detected correctly +* perf: add support for CD-ROM mode when mounting image (thanks to [@scpcom](https://github.com/scpcom)) +* perf: add a loading state during login +* perf: add changelog link in settings +* perf: update translation and cleanup the code (thanks to [@ChokunPlayZ](https://github.com/ChokunPlayZ) [@Stoufiler](https://github.com/Stoufiler) [@polyzium](https://github.com/polyzium) [@Jonher937](https://github.com/Jonher937) [@S33G](https://github.com/S33G)) + ## 2.2.0 [0dbf8c0](https://github.com/sipeed/NanoKVM/commit/0dbf8c007f2d0183d0f0601c3da6d3c3fccd8b31) (2025-02-17) NanoKVM [Image v1.4.0](https://github.com/sipeed/NanoKVM/releases/tag/v1.4.0) has been released!