Merge pull request #89 from IceWhaleTech/dev
Compatible with more types of disks
This commit is contained in:
commit
493dc5c032
11 changed files with 200 additions and 69 deletions
2
UI
2
UI
|
@ -1 +1 @@
|
|||
Subproject commit a6074812f4dc40424cf6468e47d98eb8a26f9d58
|
||||
Subproject commit 25c7eec90d87dc3fdc40488ea3e31a9657e13f03
|
|
@ -20,6 +20,7 @@ type LSBLKModel struct {
|
|||
Format string `json:"format"`
|
||||
Health string `json:"health"`
|
||||
HotPlug bool `json:"hotplug"`
|
||||
UUID string `json:"uuid"`
|
||||
FSUsed string `json:"fsused"`
|
||||
Temperature int `json:"temperature"`
|
||||
Tran string `json:"tran"`
|
||||
|
@ -47,6 +48,15 @@ type Drive struct {
|
|||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type DriveUSB struct {
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
Used uint64 `json:"use"`
|
||||
Model string `json:"model"`
|
||||
Mount bool `json:"mount"` //是否完全挂载
|
||||
Avail uint64 `json:"avail"` //可用空间
|
||||
}
|
||||
|
||||
type Storage struct {
|
||||
Name string `json:"name"`
|
||||
MountPoint string `json:"mountpoint"`
|
||||
|
|
|
@ -101,7 +101,6 @@ func ExecSmartCTLByPath(path string) []byte {
|
|||
output, err := exec.CommandContext(ctx, "smartctl", "-a", path, "-j").Output()
|
||||
if err != nil {
|
||||
fmt.Println("smartctl", err)
|
||||
fmt.Println("output", string(output))
|
||||
return nil
|
||||
}
|
||||
return output
|
||||
|
|
|
@ -195,18 +195,18 @@ func CheckSerialDiskMount() {
|
|||
// check mount point
|
||||
dbList := service.MyService.Disk().GetSerialAll()
|
||||
|
||||
list := service.MyService.Disk().LSBLK()
|
||||
list := service.MyService.Disk().LSBLK(true)
|
||||
mountPoint := make(map[string]string, len(dbList))
|
||||
//remount
|
||||
for _, v := range dbList {
|
||||
mountPoint[v.Path] = v.MountPoint
|
||||
mountPoint[v.UUID] = v.MountPoint
|
||||
}
|
||||
for _, v := range list {
|
||||
command.ExecEnabledSMART(v.Path)
|
||||
if v.Children != nil {
|
||||
for _, h := range v.Children {
|
||||
if len(h.MountPoint) == 0 && len(v.Children) == 1 && h.FsType == "ext4" {
|
||||
if m, ok := mountPoint[h.Path]; ok {
|
||||
if m, ok := mountPoint[h.UUID]; ok {
|
||||
//mount point check
|
||||
volume := m
|
||||
if !file.CheckNotExist(m) {
|
||||
|
@ -217,7 +217,8 @@ func CheckSerialDiskMount() {
|
|||
service.MyService.Disk().MountDisk(h.Path, volume)
|
||||
if volume != m {
|
||||
ms := model2.SerialDisk{}
|
||||
ms.Serial = v.Serial
|
||||
ms.UUID = v.UUID
|
||||
ms.MountPoint = volume
|
||||
service.MyService.Disk().UpdateMountPoint(ms)
|
||||
}
|
||||
|
||||
|
|
|
@ -241,6 +241,7 @@ func InitRouter() *gin.Engine {
|
|||
|
||||
//删除分区
|
||||
v1DiskGroup.DELETE("/delpart", v1.RemovePartition)
|
||||
v1DiskGroup.GET("/usb", v1.GetUSBList)
|
||||
|
||||
}
|
||||
v1ShareGroup := v1Group.Group("/share")
|
||||
|
|
153
route/v1/disk.go
153
route/v1/disk.go
|
@ -27,7 +27,7 @@ var diskMap = make(map[string]string)
|
|||
// @Success 200 {string} string "ok"
|
||||
// @Router /disk/list [get]
|
||||
func GetDiskList(c *gin.Context) {
|
||||
list := service.MyService.Disk().LSBLK()
|
||||
list := service.MyService.Disk().LSBLK(false)
|
||||
dbList := service.MyService.Disk().GetSerialAll()
|
||||
part := make(map[string]int64, len(dbList))
|
||||
for _, v := range dbList {
|
||||
|
@ -53,28 +53,56 @@ func GetDiskList(c *gin.Context) {
|
|||
|
||||
if len(list[i].Children) > 0 && findSystem == 0 {
|
||||
for j := 0; j < len(list[i].Children); j++ {
|
||||
if list[i].Children[j].MountPoint == "/" {
|
||||
stor := model.Storage{}
|
||||
stor.Name = "System"
|
||||
stor.MountPoint = list[i].Children[j].MountPoint
|
||||
stor.Size = list[i].Children[j].FSSize
|
||||
stor.Avail = list[i].Children[j].FSAvail
|
||||
stor.Path = list[i].Children[j].Path
|
||||
stor.Type = list[i].Children[j].FsType
|
||||
stor.DriveName = "System"
|
||||
disk.Model = "System"
|
||||
if strings.Contains(list[i].Children[j].SubSystems, "mmc") {
|
||||
disk.DiskType = "MMC"
|
||||
} else if strings.Contains(list[i].Children[j].SubSystems, "usb") {
|
||||
disk.DiskType = "USB"
|
||||
}
|
||||
disk.Health = "true"
|
||||
if len(list[i].Children[j].Children) > 0 {
|
||||
for _, v := range list[i].Children[j].Children {
|
||||
if v.MountPoint == "/" {
|
||||
stor := model.Storage{}
|
||||
stor.Name = "System"
|
||||
stor.MountPoint = v.MountPoint
|
||||
stor.Size = v.FSSize
|
||||
stor.Avail = v.FSAvail
|
||||
stor.Path = v.Path
|
||||
stor.Type = v.FsType
|
||||
stor.DriveName = "System"
|
||||
disk.Model = "System"
|
||||
if strings.Contains(v.SubSystems, "mmc") {
|
||||
disk.DiskType = "MMC"
|
||||
} else if strings.Contains(v.SubSystems, "usb") {
|
||||
disk.DiskType = "USB"
|
||||
}
|
||||
disk.Health = "true"
|
||||
|
||||
disks = append(disks, disk)
|
||||
storage = append(storage, stor)
|
||||
findSystem = 1
|
||||
break
|
||||
disks = append(disks, disk)
|
||||
storage = append(storage, stor)
|
||||
findSystem = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if list[i].Children[j].MountPoint == "/" {
|
||||
stor := model.Storage{}
|
||||
stor.Name = "System"
|
||||
stor.MountPoint = list[i].Children[j].MountPoint
|
||||
stor.Size = list[i].Children[j].FSSize
|
||||
stor.Avail = list[i].Children[j].FSAvail
|
||||
stor.Path = list[i].Children[j].Path
|
||||
stor.Type = list[i].Children[j].FsType
|
||||
stor.DriveName = "System"
|
||||
disk.Model = "System"
|
||||
if strings.Contains(list[i].Children[j].SubSystems, "mmc") {
|
||||
disk.DiskType = "MMC"
|
||||
} else if strings.Contains(list[i].Children[j].SubSystems, "usb") {
|
||||
disk.DiskType = "USB"
|
||||
}
|
||||
disk.Health = "true"
|
||||
|
||||
disks = append(disks, disk)
|
||||
storage = append(storage, stor)
|
||||
findSystem = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if findSystem == 1 {
|
||||
|
@ -82,10 +110,10 @@ func GetDiskList(c *gin.Context) {
|
|||
continue
|
||||
}
|
||||
|
||||
if list[i].Tran == "sata" || list[i].Tran == "nvme" {
|
||||
if list[i].Tran == "sata" || list[i].Tran == "nvme" || list[i].Tran == "spi" || list[i].Tran == "sas" {
|
||||
temp := service.MyService.Disk().SmartCTL(list[i].Path)
|
||||
if reflect.DeepEqual(temp, model.SmartctlA{}) {
|
||||
continue
|
||||
temp.SmartStatus.Passed = true
|
||||
}
|
||||
if len(list[i].Children) == 1 && len(list[i].Children[0].MountPoint) > 0 {
|
||||
stor := model.Storage{}
|
||||
|
@ -137,7 +165,7 @@ func GetDiskList(c *gin.Context) {
|
|||
// @Router /disk/lists [get]
|
||||
func GetPlugInDisks(c *gin.Context) {
|
||||
|
||||
list := service.MyService.Disk().LSBLK()
|
||||
list := service.MyService.Disk().LSBLK(true)
|
||||
var result []*disk.UsageStat
|
||||
for _, item := range list {
|
||||
result = append(result, service.MyService.Disk().GetDiskInfoByPath(item.Path))
|
||||
|
@ -252,13 +280,12 @@ func RemovePartition(c *gin.Context) {
|
|||
func AddPartition(c *gin.Context) {
|
||||
name := c.PostForm("name")
|
||||
path := c.PostForm("path")
|
||||
serial := c.PostForm("serial")
|
||||
format, _ := strconv.ParseBool(c.PostForm("format"))
|
||||
if len(name) == 0 || len(path) == 0 || len(serial) == 0 {
|
||||
if len(name) == 0 || len(path) == 0 {
|
||||
c.JSON(http.StatusOK, model.Result{Success: oasis_err.INVALID_PARAMS, Message: oasis_err.GetMsg(oasis_err.INVALID_PARAMS)})
|
||||
return
|
||||
}
|
||||
if _, ok := diskMap[serial]; ok {
|
||||
if _, ok := diskMap[path]; ok {
|
||||
c.JSON(http.StatusOK, model.Result{Success: oasis_err.DISK_BUSYING, Message: oasis_err.GetMsg(oasis_err.DISK_BUSYING)})
|
||||
return
|
||||
}
|
||||
|
@ -267,36 +294,49 @@ func AddPartition(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, model.Result{Success: oasis_err.NAME_NOT_AVAILABLE, Message: oasis_err.GetMsg(oasis_err.NAME_NOT_AVAILABLE)})
|
||||
return
|
||||
}
|
||||
diskMap[serial] = "busying"
|
||||
diskMap[path] = "busying"
|
||||
currentDisk := service.MyService.Disk().GetDiskInfo(path)
|
||||
if !format {
|
||||
if len(currentDisk.Children) != 1 || !(len(currentDisk.Children) > 0 && currentDisk.Children[0].FsType == "ext4") {
|
||||
c.JSON(http.StatusOK, model.Result{Success: oasis_err.DISK_NEEDS_FORMAT, Message: oasis_err.GetMsg(oasis_err.DISK_NEEDS_FORMAT)})
|
||||
delete(diskMap, serial)
|
||||
delete(diskMap, path)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
service.MyService.Disk().AddPartition(path)
|
||||
}
|
||||
|
||||
formatBool := true
|
||||
for formatBool {
|
||||
currentDisk = service.MyService.Disk().GetDiskInfo(path)
|
||||
if len(currentDisk.Children) != 1 {
|
||||
formatBool = false
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
}
|
||||
currentDisk = service.MyService.Disk().GetDiskInfo(path)
|
||||
if len(currentDisk.Children) != 1 {
|
||||
c.JSON(http.StatusOK, model.Result{Success: oasis_err.DISK_NEEDS_FORMAT, Message: oasis_err.GetMsg(oasis_err.DISK_NEEDS_FORMAT)})
|
||||
return
|
||||
}
|
||||
|
||||
mountPath := "/DATA/" + name
|
||||
|
||||
service.MyService.Disk().MountDisk(path, mountPath)
|
||||
|
||||
m := model2.SerialDisk{}
|
||||
m.MountPoint = mountPath
|
||||
m.Path = path + "1"
|
||||
m.Serial = serial
|
||||
m.Path = currentDisk.Children[0].Path
|
||||
m.UUID = currentDisk.Children[0].UUID
|
||||
m.State = 0
|
||||
m.CreatedAt = time.Now().Unix()
|
||||
service.MyService.Disk().SaveMountPoint(m)
|
||||
|
||||
//mount dir
|
||||
service.MyService.Disk().MountDisk(path+"1", mountPath)
|
||||
service.MyService.Disk().MountDisk(currentDisk.Children[0].Path, mountPath)
|
||||
|
||||
service.MyService.Disk().RemoveLSBLKCache()
|
||||
|
||||
delete(diskMap, serial)
|
||||
delete(diskMap, path)
|
||||
c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)})
|
||||
}
|
||||
|
||||
|
@ -334,7 +374,7 @@ func PostMountDisk(c *gin.Context) {
|
|||
m := model2.SerialDisk{}
|
||||
m.MountPoint = mountPath
|
||||
m.Path = path
|
||||
m.Serial = serial
|
||||
m.UUID = serial
|
||||
m.State = 0
|
||||
//service.MyService.Disk().SaveMountPoint(m)
|
||||
c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)})
|
||||
|
@ -401,7 +441,7 @@ func DeleteDisk(c *gin.Context) {
|
|||
func GetDiskCheck(c *gin.Context) {
|
||||
|
||||
dbList := service.MyService.Disk().GetSerialAll()
|
||||
list := service.MyService.Disk().LSBLK()
|
||||
list := service.MyService.Disk().LSBLK(true)
|
||||
|
||||
mapList := make(map[string]string)
|
||||
|
||||
|
@ -410,7 +450,7 @@ func GetDiskCheck(c *gin.Context) {
|
|||
}
|
||||
|
||||
for _, v := range dbList {
|
||||
if _, ok := mapList[v.Serial]; !ok {
|
||||
if _, ok := mapList[v.UUID]; !ok {
|
||||
//disk undefind
|
||||
c.JSON(http.StatusOK, model.Result{Success: oasis_err.ERROR, Message: oasis_err.GetMsg(oasis_err.ERROR), Data: "disk undefind"})
|
||||
return
|
||||
|
@ -419,3 +459,38 @@ func GetDiskCheck(c *gin.Context) {
|
|||
|
||||
c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)})
|
||||
}
|
||||
|
||||
// @Summary check mount point
|
||||
// @Produce application/json
|
||||
// @Accept application/json
|
||||
// @Tags disk
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {string} string "ok"
|
||||
// @Router /disk/usb [get]
|
||||
func GetUSBList(c *gin.Context) {
|
||||
list := service.MyService.Disk().LSBLK(false)
|
||||
data := []model.DriveUSB{}
|
||||
for _, v := range list {
|
||||
if v.Tran == "usb" {
|
||||
temp := model.DriveUSB{}
|
||||
temp.Model = v.Model
|
||||
temp.Name = v.Name
|
||||
temp.Size = v.Size
|
||||
mountTemp := true
|
||||
if len(v.Children) == 0 {
|
||||
mountTemp = false
|
||||
}
|
||||
for _, child := range v.Children {
|
||||
if len(child.MountPoint) > 0 {
|
||||
avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
|
||||
temp.Avail += avail
|
||||
} else {
|
||||
mountTemp = false
|
||||
}
|
||||
}
|
||||
temp.Mount = mountTemp
|
||||
data = append(data, temp)
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: data})
|
||||
}
|
||||
|
|
|
@ -259,9 +259,9 @@ func PostKillCasaOS(c *gin.Context) {
|
|||
// @Success 200 {string} string "ok"
|
||||
// @Router /sys/info [get]
|
||||
func Info(c *gin.Context) {
|
||||
var data = make(map[string]interface{}, 5)
|
||||
var data = make(map[string]interface{}, 6)
|
||||
|
||||
list := service.MyService.Disk().LSBLK()
|
||||
list := service.MyService.Disk().LSBLK(true)
|
||||
|
||||
summary := model.Summary{}
|
||||
healthy := true
|
||||
|
@ -269,24 +269,42 @@ func Info(c *gin.Context) {
|
|||
|
||||
for i := 0; i < len(list); i++ {
|
||||
if len(list[i].Children) > 0 && findSystem == 0 {
|
||||
|
||||
for j := 0; j < len(list[i].Children); j++ {
|
||||
if list[i].Children[j].MountPoint == "/" {
|
||||
s, _ := strconv.ParseUint(list[i].Children[j].FSSize, 10, 64)
|
||||
a, _ := strconv.ParseUint(list[i].Children[j].FSAvail, 10, 64)
|
||||
u, _ := strconv.ParseUint(list[i].Children[j].FSUsed, 10, 64)
|
||||
summary.Size += s
|
||||
summary.Avail += a
|
||||
summary.Used += u
|
||||
findSystem = 1
|
||||
break
|
||||
|
||||
if len(list[i].Children[j].Children) > 0 {
|
||||
for _, v := range list[i].Children[j].Children {
|
||||
if v.MountPoint == "/" {
|
||||
s, _ := strconv.ParseUint(v.FSSize, 10, 64)
|
||||
a, _ := strconv.ParseUint(v.FSAvail, 10, 64)
|
||||
u, _ := strconv.ParseUint(v.FSUsed, 10, 64)
|
||||
summary.Size += s
|
||||
summary.Avail += a
|
||||
summary.Used += u
|
||||
findSystem = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if list[i].Children[j].MountPoint == "/" {
|
||||
s, _ := strconv.ParseUint(list[i].Children[j].FSSize, 10, 64)
|
||||
a, _ := strconv.ParseUint(list[i].Children[j].FSAvail, 10, 64)
|
||||
u, _ := strconv.ParseUint(list[i].Children[j].FSUsed, 10, 64)
|
||||
summary.Size += s
|
||||
summary.Avail += a
|
||||
summary.Used += u
|
||||
findSystem = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if findSystem == 1 {
|
||||
findSystem += 1
|
||||
continue
|
||||
}
|
||||
if list[i].Tran == "sata" || list[i].Tran == "nvme" {
|
||||
if list[i].Tran == "sata" || list[i].Tran == "nvme" || list[i].Tran == "spi" || list[i].Tran == "sas" {
|
||||
temp := service.MyService.Disk().SmartCTL(list[i].Path)
|
||||
if reflect.DeepEqual(temp, model.SmartctlA{}) {
|
||||
continue
|
||||
|
@ -312,6 +330,33 @@ func Info(c *gin.Context) {
|
|||
|
||||
summary.Health = healthy
|
||||
data["disk"] = summary
|
||||
usbList := service.MyService.Disk().LSBLK(false)
|
||||
usb := []model.DriveUSB{}
|
||||
for _, v := range usbList {
|
||||
if v.Tran == "usb" {
|
||||
temp := model.DriveUSB{}
|
||||
temp.Model = v.Model
|
||||
temp.Name = v.Name
|
||||
temp.Size = v.Size
|
||||
mountTemp := true
|
||||
if len(v.Children) == 0 {
|
||||
mountTemp = false
|
||||
}
|
||||
for _, child := range v.Children {
|
||||
if len(child.MountPoint) > 0 {
|
||||
avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
|
||||
temp.Avail += avail
|
||||
used, _ := strconv.ParseUint(child.FSUsed, 10, 64)
|
||||
temp.Used += used
|
||||
} else {
|
||||
mountTemp = false
|
||||
}
|
||||
}
|
||||
temp.Mount = mountTemp
|
||||
usb = append(usb, temp)
|
||||
}
|
||||
}
|
||||
data["usb"] = usb
|
||||
cpu := service.MyService.ZiMa().GetCpuPercent()
|
||||
num := service.MyService.ZiMa().GetCpuCoreNum()
|
||||
cpuData := make(map[string]interface{})
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
|
||||
type DiskService interface {
|
||||
GetPlugInDisk() []string
|
||||
LSBLK() []model.LSBLKModel
|
||||
LSBLK(isUseCache bool) []model.LSBLKModel
|
||||
SmartCTL(path string) model.SmartctlA
|
||||
FormatDisk(path, format string) []string
|
||||
UmountPointAndRemoveDir(path string) []string
|
||||
|
@ -119,11 +119,11 @@ func (d *diskService) GetDiskInfoByPath(path string) *disk.UsageStat {
|
|||
}
|
||||
|
||||
//get disk details
|
||||
func (d *diskService) LSBLK() []model.LSBLKModel {
|
||||
func (d *diskService) LSBLK(isUseCache bool) []model.LSBLKModel {
|
||||
key := "system_lsblk"
|
||||
var n []model.LSBLKModel
|
||||
|
||||
if result, ok := Cache.Get(key); ok {
|
||||
if result, ok := Cache.Get(key); ok && isUseCache {
|
||||
|
||||
res, ok := result.([]model.LSBLKModel)
|
||||
if ok {
|
||||
|
@ -247,12 +247,12 @@ func (d *diskService) MountDisk(path, volume string) {
|
|||
}
|
||||
|
||||
func (d *diskService) SaveMountPoint(m model2.SerialDisk) {
|
||||
d.db.Where("serial = ?", m.Serial).Delete(&model2.SerialDisk{})
|
||||
d.db.Where("uuid = ?", m.UUID).Delete(&model2.SerialDisk{})
|
||||
d.db.Create(&m)
|
||||
}
|
||||
|
||||
func (d *diskService) UpdateMountPoint(m model2.SerialDisk) {
|
||||
d.db.Model(&model2.SerialDisk{}).Where("serial = ?", m.Serial).Update("mount_point", m.MountPoint)
|
||||
d.db.Model(&model2.SerialDisk{}).Where("uui = ?", m.UUID).Update("mount_point", m.MountPoint)
|
||||
}
|
||||
|
||||
func (d *diskService) DeleteMount(id string) {
|
||||
|
|
|
@ -3,7 +3,7 @@ package model
|
|||
//SerialAdvanced Technology Attachment (STAT)
|
||||
type SerialDisk struct {
|
||||
Id uint `gorm:"column:id;primary_key" json:"id"`
|
||||
Serial string `json:"serial"`
|
||||
UUID string `json:"uuid"`
|
||||
Path string `json:"path"`
|
||||
State int `json:"state"`
|
||||
MountPoint string `json:"mount_point"`
|
||||
|
|
|
@ -115,8 +115,8 @@ AddPartition() {
|
|||
parted -s $1 mklabel gpt
|
||||
|
||||
parted -s $1 mkpart primary ext4 0 100%
|
||||
|
||||
mkfs.ext4 -m 1 $11
|
||||
PATH=`lsblk -r $1 | sort | grep part | head -n 1 | awk '{print $1}'`
|
||||
mkfs.ext4 -m 1 /dev/${PATH}
|
||||
|
||||
partprobe $1
|
||||
|
||||
|
@ -156,8 +156,8 @@ GetPartitionSectors() {
|
|||
|
||||
#检查没有使用的挂载点删除文件夹
|
||||
AutoRemoveUnuseDir() {
|
||||
DIRECTORY="/mnt/"
|
||||
dir=$(ls -l $DIRECTORY | awk '/^d/ {print $NF}')
|
||||
DIRECTORY="/DATA/"
|
||||
dir=$(ls -l $DIRECTORY | grep "Storage[0-9]" | awk '/^d/ {print $NF}')
|
||||
for i in $dir; do
|
||||
|
||||
path="$DIRECTORY$i"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
package types
|
||||
|
||||
const CURRENTVERSION = "0.2.7"
|
||||
const CURRENTVERSION = "0.2.8"
|
||||
|
||||
const BODY = "<li>Apply multilingual support</li><li>Fix a security vulnerability</li>"
|
||||
const BODY = "<li>Compatible with more types of disks</li><li>Add usb display</li><li>Change translation</li>"
|
||||
|
|
Loading…
Reference in a new issue