Kaynağa Gözat

Storage Manager

Fix the app store classification problem
Fix the application market classification problem
link 3 yıl önce
ebeveyn
işleme
fcb906aa85

+ 3 - 0
model/disk.go

@@ -21,6 +21,7 @@ type LSBLKModel struct {
 	Health      string       `json:"health"`
 	HotPlug     bool         `json:"hotplug"`
 	FSUsed      string       `json:"fsused"`
+	Temperature int          `json:"temperature"`
 	Tran        string       `json:"tran"`
 	MinIO       uint64       `json:"min-io"`
 	UsedPercent float64      `json:"used_percent"`
@@ -28,5 +29,7 @@ type LSBLKModel struct {
 	Children    []LSBLKModel `json:"children"`
 	//详情特有
 	StartSector uint64 `json:"start_sector,omitempty"`
+	Rota        bool   `json:"rota"` //true(hhd) false(ssd)
+	DiskType    string `json:"disk_type"`
 	EndSector   uint64 `json:"end_sector,omitempty"`
 }

+ 69 - 0
model/smartctl_model.go

@@ -0,0 +1,69 @@
+package model
+
+//
+type SmartctlA struct {
+	Smartctl struct {
+		Version      []int    `json:"version"`
+		SvnRevision  string   `json:"svn_revision"`
+		PlatformInfo string   `json:"platform_info"`
+		BuildInfo    string   `json:"build_info"`
+		Argv         []string `json:"argv"`
+		ExitStatus   int      `json:"exit_status"`
+	} `json:"smartctl"`
+	Device struct {
+		Name     string `json:"name"`
+		InfoName string `json:"info_name"`
+		Type     string `json:"type"`
+		Protocol string `json:"protocol"`
+	} `json:"device"`
+	ModelName       string `json:"model_name"`
+	SerialNumber    string `json:"serial_number"`
+	FirmwareVersion string `json:"firmware_version"`
+	UserCapacity    struct {
+		Blocks int   `json:"blocks"`
+		Bytes  int64 `json:"bytes"`
+	} `json:"user_capacity"`
+	SmartStatus struct {
+		Passed bool `json:"passed"`
+	} `json:"smart_status"`
+	AtaSmartData struct {
+		OfflineDataCollection struct {
+			Status struct {
+				Value  int    `json:"value"`
+				String string `json:"string"`
+			} `json:"status"`
+			CompletionSeconds int `json:"completion_seconds"`
+		} `json:"offline_data_collection"`
+		SelfTest struct {
+			Status struct {
+				Value  int    `json:"value"`
+				String string `json:"string"`
+				Passed bool   `json:"passed"`
+			} `json:"status"`
+			PollingMinutes struct {
+				Short      int `json:"short"`
+				Extended   int `json:"extended"`
+				Conveyance int `json:"conveyance"`
+			} `json:"polling_minutes"`
+		} `json:"self_test"`
+		Capabilities struct {
+			Values                        []int `json:"values"`
+			ExecOfflineImmediateSupported bool  `json:"exec_offline_immediate_supported"`
+			OfflineIsAbortedUponNewCmd    bool  `json:"offline_is_aborted_upon_new_cmd"`
+			OfflineSurfaceScanSupported   bool  `json:"offline_surface_scan_supported"`
+			SelfTestsSupported            bool  `json:"self_tests_supported"`
+			ConveyanceSelfTestSupported   bool  `json:"conveyance_self_test_supported"`
+			SelectiveSelfTestSupported    bool  `json:"selective_self_test_supported"`
+			AttributeAutosaveEnabled      bool  `json:"attribute_autosave_enabled"`
+			ErrorLoggingSupported         bool  `json:"error_logging_supported"`
+			GpLoggingSupported            bool  `json:"gp_logging_supported"`
+		} `json:"capabilities"`
+	} `json:"ata_smart_data"`
+	PowerOnTime struct {
+		Hours int `json:"hours"`
+	} `json:"power_on_time"`
+	PowerCycleCount int `json:"power_cycle_count"`
+	Temperature     struct {
+		Current int `json:"current"`
+	} `json:"temperature"`
+}

+ 21 - 0
pkg/utils/command/command_helper.go

@@ -2,9 +2,11 @@ package command
 
 import (
 	"bufio"
+	"context"
 	"fmt"
 	"io/ioutil"
 	"os/exec"
+	"time"
 )
 
 func OnlyExec(cmdStr string) {
@@ -85,7 +87,26 @@ func ExecLSBLK() []byte {
 func ExecLSBLKByPath(path string) []byte {
 	output, err := exec.Command("lsblk", path, "-O", "-J", "-b").Output()
 	if err != nil {
+		fmt.Println("lsblk", err)
+		return nil
+	}
+	return output
+}
+
+//exec smart
+func ExecSmartCTLByPath(path string) []byte {
+	timeout := 3
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
+	defer cancel()
+	output, err := exec.CommandContext(ctx, "smartctl", "-a", path, "-j").Output()
+	if err != nil {
+		fmt.Println("smartctl", err)
 		return nil
 	}
 	return output
 }
+
+func ExecEnabledSMART(path string) {
+
+	exec.Command("smartctl", "-s on", path).Output()
+}

+ 27 - 13
pkg/utils/oasis_err/e.go

@@ -21,6 +21,13 @@ const (
 	//zerotier
 	GET_TOKEN_ERROR = 30001
 
+	//disk
+	NAME_NOT_AVAILABLE       = 40001
+	DISK_NEEDS_FORMAT        = 40002
+	DISK_BUSYING             = 40003
+	REMOVE_MOUNT_POINT_ERROR = 40004
+	FORMAT_ERROR             = 40005
+
 	//app
 	UNINSTALL_APP_ERROR = 50001
 	PULL_IMAGE_ERROR    = 50002
@@ -37,34 +44,41 @@ const (
 var MsgFlags = map[int]string{
 	SUCCESS:          "ok",
 	ERROR:            "fail",
-	INVALID_PARAMS:   "Invalid params",
-	ERROR_AUTH_TOKEN: "error auth token",
+	INVALID_PARAMS:   "Parameters Error",
+	ERROR_AUTH_TOKEN: "Error auth token",
 
 	//user
-	PWD_INVALID:     "Password invalid",
+	PWD_INVALID:     "Invalid password",
 	PWD_IS_EMPTY:    "Password is empty",
-	PWD_INVALID_OLD: "Old Password invalid",
-	ACCOUNT_LOCK:    "Account Lock",
+	PWD_INVALID_OLD: "Invalid old password",
+	ACCOUNT_LOCK:    "Account is locked",
 
 	//system
-	DIR_ALREADY_EXISTS:  "Directory already exists",
+	DIR_ALREADY_EXISTS:  "Folder already exists",
 	FILE_ALREADY_EXISTS: "File already exists",
-	FILE_OR_DIR_EXISTS:  "File or directory already exists",
+	FILE_OR_DIR_EXISTS:  "File or folder already exists",
 	PORT_IS_OCCUPIED:    "Port is occupied",
 
 	//zerotier
 	GET_TOKEN_ERROR: "Get token error,Please log in to zerotier's official website to confirm whether the account is available",
 
 	//app
-	UNINSTALL_APP_ERROR: "uninstall app error",
-	PULL_IMAGE_ERROR:    "pull image error",
-	DEVICE_NOT_EXIST:    "device not exist",
+	UNINSTALL_APP_ERROR: "Error uninstalling app",
+	PULL_IMAGE_ERROR:    "Error pulling image",
+	DEVICE_NOT_EXIST:    "Device does not exist",
+
+	//disk
+	NAME_NOT_AVAILABLE:       "Name not available",
+	DISK_NEEDS_FORMAT:        "Drive needs to be formatted",
+	REMOVE_MOUNT_POINT_ERROR: "Failed to remove mount point",
+	DISK_BUSYING:             "Drive is busy",
+	FORMAT_ERROR:             "Formatting failed, please check if the directory is occupied",
 
 	//
-	FILE_DOES_NOT_EXIST: "file does not exist",
+	FILE_DOES_NOT_EXIST: "File does not exist",
 
-	FILE_READ_ERROR:     "file read error",
-	SHORTCUTS_URL_ERROR: "url error",
+	FILE_READ_ERROR:     "File read error",
+	SHORTCUTS_URL_ERROR: "URL error",
 }
 
 //获取错误信息

+ 31 - 15
route/init.go

@@ -3,6 +3,7 @@ package route
 import (
 	"encoding/json"
 	"encoding/xml"
+	"fmt"
 	"strconv"
 	"time"
 
@@ -34,10 +35,9 @@ func installSyncthing(appId string) {
 	m := model.CustomizationPostData{}
 	var dockerImage string
 	var dockerImageVersion string
-
-	appInfo = service.MyService.OAPI().GetServerAppInfo(appId)
-
+	appInfo = service.MyService.OAPI().GetServerAppInfo(appId, "system")
 	dockerImage = appInfo.Image
+	dockerImageVersion = appInfo.ImageVersion
 
 	if len(appInfo.ImageVersion) == 0 {
 		dockerImageVersion = "latest"
@@ -84,9 +84,9 @@ func installSyncthing(appId string) {
 	err := service.MyService.Docker().DockerPullImage(dockerImage+":"+dockerImageVersion, installLog)
 	if err != nil {
 		//pull image error
+		fmt.Println("pull image error", err, dockerImage, dockerImageVersion)
 		return
 	}
-
 	for !service.MyService.Docker().IsExistImage(dockerImage + ":" + dockerImageVersion) {
 		time.Sleep(time.Second)
 	}
@@ -101,8 +101,8 @@ func installSyncthing(appId string) {
 	m.Volumes = appInfo.Volumes
 
 	containerId, err := service.MyService.Docker().DockerContainerCreate(dockerImage+":"+dockerImageVersion, id, m, appInfo.NetworkModel)
-
 	if err != nil {
+		fmt.Println("container create error", err)
 		// create container error
 		return
 	}
@@ -170,7 +170,7 @@ func checkSystemApp() {
 			path := ""
 			for _, i := range paths {
 				if i.ContainerPath == "/config" {
-					path = docker.GetDir(v.CustomId, i.Path) + "config.xml"
+					path = docker.GetDir(v.CustomId, i.Path) + "/config.xml"
 					for i := 0; i < 10; i++ {
 						if file.CheckNotExist(path) {
 							time.Sleep(1 * time.Second)
@@ -197,21 +197,37 @@ func CheckSerialDiskMount() {
 
 	list := service.MyService.Disk().LSBLK()
 	mountPoint := make(map[string]string, len(dbList))
-
+	//remount
+	for _, v := range dbList {
+		mountPoint[v.Path] = v.MountPoint
+	}
 	for _, v := range list {
+		command.ExecEnabledSMART(v.Path)
 		if v.Children != nil {
 			for _, h := range v.Children {
-				mountPoint[h.MountPoint] = "1"
-			}
-		}
-	}
+				if len(h.MountPoint) == 0 && len(v.Children) == 1 && h.FsType == "ext4" {
+					if m, ok := mountPoint[h.Path]; ok {
+						//mount point check
+						volume := m
+						if !file.CheckNotExist(m) {
+							for i := 0; file.CheckNotExist(volume); i++ {
+								volume = m + strconv.Itoa(i+1)
+							}
+						}
+						service.MyService.Disk().MountDisk(h.Path, volume)
+						if volume != m {
+							ms := model2.SerialDisk{}
+							ms.Serial = v.Serial
+							service.MyService.Disk().UpdateMountPoint(ms)
+						}
 
-	//remount
-	for _, item := range dbList {
-		if _, ok := mountPoint[item.MountPoint]; !ok {
-			service.MyService.Disk().MountDisk(item.Path, item.MountPoint)
+					}
+				}
+			}
 		}
 	}
+	service.MyService.Disk().RemoveLSBLKCache()
+	command.OnlyExec("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;AutoRemoveUnuseDir")
 
 }
 func Update2_3() {

+ 11 - 11
route/route.go

@@ -218,17 +218,23 @@ func InitRouter() *gin.Engine {
 		v1DiskGroup.Use()
 		{
 			v1DiskGroup.GET("/check", v1.GetDiskCheck)
-			//获取磁盘列表
-			v1DiskGroup.GET("/list", v1.GetPlugInDisk)
+
+			v1DiskGroup.GET("/list", v1.GetDiskList)
 
 			//获取磁盘详情
 			v1DiskGroup.GET("/info", v1.GetDiskInfo)
 
-			//格式化磁盘
+			//format storage
 			v1DiskGroup.POST("/format", v1.FormatDisk)
 
-			//添加分区
-			v1DiskGroup.POST("/part", v1.AddPartition)
+			// add storage
+			v1DiskGroup.POST("/storage", v1.AddPartition)
+
+			//mount SATA disk
+			v1DiskGroup.POST("/mount", v1.PostMountDisk)
+
+			//umount sata disk
+			v1DiskGroup.POST("/umount", v1.PostDiskUmount)
 
 			//获取可以格式化的内容
 			v1DiskGroup.GET("/type", v1.FormatDiskType)
@@ -236,12 +242,6 @@ func InitRouter() *gin.Engine {
 			//删除分区
 			v1DiskGroup.DELETE("/delpart", v1.RemovePartition)
 
-			//mount SATA disk
-			v1DiskGroup.POST("/mount", v1.PostMountDisk)
-
-			//umount SATA disk
-			v1DiskGroup.POST("/umount", v1.PostDiskUmount)
-			v1DiskGroup.DELETE("/remove/:id", v1.DeleteDisk)
 		}
 		v1ShareGroup := v1Group.Group("/share")
 		v1ShareGroup.Use()

+ 19 - 19
route/v1/app.go

@@ -36,24 +36,24 @@ func AppList(c *gin.Context) {
 	categoryId := c.DefaultQuery("category_id", "0")
 	key := c.DefaultQuery("key", "")
 	recommend, list, community := service.MyService.OAPI().GetServerList(index, size, t, categoryId, key)
-	for i := 0; i < len(recommend); i++ {
-		ct, _ := service.MyService.Docker().DockerListByImage(recommend[i].Image, recommend[i].ImageVersion)
-		if ct != nil {
-			list[i].State = ct.State
-		}
-	}
-	for i := 0; i < len(list); i++ {
-		ct, _ := service.MyService.Docker().DockerListByImage(list[i].Image, list[i].ImageVersion)
-		if ct != nil {
-			list[i].State = ct.State
-		}
-	}
-	for i := 0; i < len(community); i++ {
-		ct, _ := service.MyService.Docker().DockerListByImage(community[i].Image, community[i].ImageVersion)
-		if ct != nil {
-			list[i].State = ct.State
-		}
-	}
+	// for i := 0; i < len(recommend); i++ {
+	// 	ct, _ := service.MyService.Docker().DockerListByImage(recommend[i].Image, recommend[i].ImageVersion)
+	// 	if ct != nil {
+	// 		recommend[i].State = ct.State
+	// 	}
+	// }
+	// for i := 0; i < len(list); i++ {
+	// 	ct, _ := service.MyService.Docker().DockerListByImage(list[i].Image, list[i].ImageVersion)
+	// 	if ct != nil {
+	// 		list[i].State = ct.State
+	// 	}
+	// }
+	// for i := 0; i < len(community); i++ {
+	// 	ct, _ := service.MyService.Docker().DockerListByImage(community[i].Image, community[i].ImageVersion)
+	// 	if ct != nil {
+	// 		community[i].State = ct.State
+	// 	}
+	// }
 	data := make(map[string]interface{}, 3)
 	data["recommend"] = recommend
 	data["list"] = list
@@ -137,7 +137,7 @@ func AppUsageList(c *gin.Context) {
 func AppInfo(c *gin.Context) {
 
 	id := c.Param("id")
-	info := service.MyService.OAPI().GetServerAppInfo(id)
+	info := service.MyService.OAPI().GetServerAppInfo(id, "")
 	if info.NetworkModel != "host" {
 		for i := 0; i < len(info.Ports); i++ {
 			if p, _ := strconv.Atoi(info.Ports[i].ContainerPort); port2.IsPortAvailable(p, info.Ports[i].Protocol) {

+ 138 - 21
route/v1/disk.go

@@ -2,9 +2,13 @@ package v1
 
 import (
 	"net/http"
+	"reflect"
 	"strconv"
+	"strings"
 
 	"github.com/IceWhaleTech/CasaOS/model"
+	"github.com/IceWhaleTech/CasaOS/pkg/config"
+	"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
 	"github.com/IceWhaleTech/CasaOS/pkg/utils/oasis_err"
 	"github.com/IceWhaleTech/CasaOS/service"
 	model2 "github.com/IceWhaleTech/CasaOS/service/model"
@@ -12,18 +16,53 @@ import (
 	"github.com/shirou/gopsutil/v3/disk"
 )
 
-// @Summary 获取磁盘列表
+var diskMap = make(map[string]string)
+
+// @Summary disk list
 // @Produce  application/json
 // @Accept application/json
 // @Tags disk
 // @Security ApiKeyAuth
 // @Success 200 {string} string "ok"
 // @Router /disk/list [get]
-func GetPlugInDisk(c *gin.Context) {
-
+func GetDiskList(c *gin.Context) {
 	list := service.MyService.Disk().LSBLK()
+	newList := []model.LSBLKModel{}
+	for i := len(list) - 1; i >= 0; i-- {
+		if list[i].Rota {
+			list[i].DiskType = "HDD"
+		} else {
+			list[i].DiskType = "SSD"
+		}
+		if list[i].Tran == "sata" {
+			temp := service.MyService.Disk().SmartCTL(list[i].Path)
+
+			if reflect.DeepEqual(temp, model.SmartctlA{}) {
+				continue
+			}
+			if len(list[i].Children) == 1 && len(list[i].Children[0].MountPoint) > 0 {
+				pathArr := strings.Split(list[i].Children[0].MountPoint, "/")
+				if len(pathArr) == 3 {
+					list[i].Children[0].Name = pathArr[2]
+				}
+			}
 
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: list})
+			list[i].Temperature = temp.Temperature.Current
+			list[i].Health = strconv.FormatBool(temp.SmartStatus.Passed)
+
+			newList = append(newList, list[i])
+		} else if len(list[i].Children) > 0 && list[i].Children[0].MountPoint == "/" {
+			//system
+			list[i].Children[0].Name = "System"
+			list[i].Model = "System"
+			list[i].DiskType = "EMMC"
+			list[i].Health = "true"
+			newList = append(newList, list[i])
+
+		}
+	}
+
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: newList})
 }
 
 // @Summary get disk list
@@ -60,25 +99,46 @@ func GetDiskInfo(c *gin.Context) {
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: m})
 }
 
-// @Summary format disk
+// @Summary format storage
 // @Produce  application/json
 // @Accept multipart/form-data
 // @Tags disk
 // @Security ApiKeyAuth
-// @Param  path formData string true "for example  /dev/sda1"
+// @Param  path formData string true "e.g. /dev/sda1"
+// @Param  pwd formData string true "user password"
+// @Param  volume formData string true "mount point"
 // @Success 200 {string} string "ok"
 // @Router /disk/format [post]
 func FormatDisk(c *gin.Context) {
 	path := c.PostForm("path")
+	t := "ext4"
+	pwd := c.PostForm("pwd")
+	volume := c.PostForm("volume")
 
-	t := c.PostForm("type")
+	if pwd != config.UserInfo.PWD {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err.PWD_INVALID, Message: oasis_err.GetMsg(oasis_err.PWD_INVALID)})
+		return
+	}
 
 	if len(path) == 0 || len(t) == 0 {
 		c.JSON(http.StatusOK, model.Result{Success: oasis_err.INVALID_PARAMS, Message: oasis_err.GetMsg(oasis_err.INVALID_PARAMS)})
 		return
 	}
-	service.MyService.Disk().FormatDisk(path, t)
-
+	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
+	}
+	diskMap[path] = "busying"
+	service.MyService.Disk().UmountPointAndRemoveDir(path)
+	format := service.MyService.Disk().FormatDisk(path, t)
+	if len(format) == 0 {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err.FORMAT_ERROR, Message: oasis_err.GetMsg(oasis_err.FORMAT_ERROR)})
+		delete(diskMap, path)
+		return
+	}
+	service.MyService.Disk().MountDisk(path, volume)
+	service.MyService.Disk().RemoveLSBLKCache()
+	delete(diskMap, path)
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)})
 }
 
@@ -115,23 +175,64 @@ func RemovePartition(c *gin.Context) {
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)})
 }
 
-// @Summary serial number
+// @Summary  add storage
 // @Produce  application/json
 // @Accept multipart/form-data
 // @Tags disk
 // @Security ApiKeyAuth
-// @Param  path formData string true "磁盘路径 例如/dev/sda"
+// @Param  path formData string true "disk path  e.g. /dev/sda"
 // @Param  serial formData string true "serial"
+// @Param  name formData string true "name"
+// @Param  format formData bool true "need format(true)"
 // @Success 200 {string} string "ok"
-// @Router /disk/addpart [post]
+// @Router /disk/storage [post]
 func AddPartition(c *gin.Context) {
+	name := c.PostForm("name")
 	path := c.PostForm("path")
 	serial := c.PostForm("serial")
-	if len(path) == 0 || len(serial) == 0 {
+	format, _ := strconv.ParseBool(c.PostForm("format"))
+	if len(name) == 0 || len(path) == 0 || len(serial) == 0 {
 		c.JSON(http.StatusOK, model.Result{Success: oasis_err.INVALID_PARAMS, Message: oasis_err.GetMsg(oasis_err.INVALID_PARAMS)})
 		return
 	}
-	service.MyService.Disk().AddPartition(path)
+	if _, ok := diskMap[serial]; ok {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err.DISK_BUSYING, Message: oasis_err.GetMsg(oasis_err.DISK_BUSYING)})
+		return
+	}
+	if !file.CheckNotExist("/mnt/" + name) {
+		// /mnt/name exist
+		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"
+	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)
+			return
+		}
+	} else {
+		service.MyService.Disk().AddPartition(path)
+	}
+
+	mountPath := "/mnt/" + name
+
+	service.MyService.Disk().MountDisk(path, mountPath)
+
+	m := model2.SerialDisk{}
+	m.MountPoint = mountPath
+	m.Path = path + "1"
+	m.Serial = serial
+	m.State = 0
+	service.MyService.Disk().SaveMountPoint(m)
+
+	//mount dir
+	service.MyService.Disk().MountDisk(path+"1", mountPath)
+
+	service.MyService.Disk().RemoveLSBLKCache()
+
+	delete(diskMap, serial)
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)})
 }
 
@@ -165,13 +266,13 @@ func PostMountDisk(c *gin.Context) {
 
 	//mount dir
 	service.MyService.Disk().MountDisk(path, mountPath)
-	//save to data
+
 	m := model2.SerialDisk{}
 	m.MountPoint = mountPath
 	m.Path = path
 	m.Serial = serial
 	m.State = 0
-	service.MyService.Disk().SaveMountPoint(m)
+	//service.MyService.Disk().SaveMountPoint(m)
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)})
 }
 
@@ -180,19 +281,35 @@ func PostMountDisk(c *gin.Context) {
 // @Accept multipart/form-data
 // @Tags disk
 // @Security ApiKeyAuth
-// @Param  path formData string true "for example: /dev/sda1"
-// @Param  mount_point formData string true "for example: /mnt/volume1"
+// @Param  path formData string true "e.g. /dev/sda1"
+// @Param  mount_point formData string true "e.g. /mnt/volume1"
+// @Param  pwd formData string true "user password"
 // @Success 200 {string} string "ok"
 // @Router /disk/umount [post]
 func PostDiskUmount(c *gin.Context) {
 
-	//
 	path := c.PostForm("path")
-	mountPoint := c.PostForm("mount_point")
-	service.MyService.Disk().UmountPointAndRemoveDir(path)
+	mountPoint := c.PostForm("volume")
+	pwd := c.PostForm("pwd")
+
+	if len(path) == 0 || len(mountPoint) == 0 {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err.INVALID_PARAMS, Message: oasis_err.GetMsg(oasis_err.INVALID_PARAMS)})
+		return
+	}
+	if pwd != config.UserInfo.PWD {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err.PWD_INVALID, Message: oasis_err.GetMsg(oasis_err.PWD_INVALID)})
+		return
+	}
+
+	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
+	}
 
+	service.MyService.Disk().UmountPointAndRemoveDir(path)
 	//delete data
 	service.MyService.Disk().DeleteMountPoint(path, mountPoint)
+	service.MyService.Disk().RemoveLSBLKCache()
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)})
 }
 

+ 1 - 1
route/v1/docker.go

@@ -174,7 +174,7 @@ func InstallApp(c *gin.Context) {
 		dockerImageVersion = "latest"
 	}
 	if m.Origin != "custom" {
-		appInfo = service.MyService.OAPI().GetServerAppInfo(appId)
+		appInfo = service.MyService.OAPI().GetServerAppInfo(appId, "")
 
 	} else {
 

+ 63 - 3
route/v1/system.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"net/http"
 	"os"
+	"reflect"
 	"strconv"
 	"strings"
 	"time"
@@ -107,9 +108,33 @@ func GetSystemConfigDebug(c *gin.Context) {
 
 	array := service.MyService.System().GetSystemConfigDebug()
 	disk := service.MyService.ZiMa().GetDiskInfo()
-	array = append(array, fmt.Sprintf("disk,total:%v,used:%v,UsedPercent:%v", disk.Total>>20, disk.Used>>20, disk.UsedPercent))
+	sys := service.MyService.ZiMa().GetSysInfo()
+	//todo 准备sync需要显示的数据(镜像,容器)
+	var systemAppStatus string
+	images := service.MyService.Docker().IsExistImage("linuxserver/syncthing")
+	systemAppStatus += "Sync img: " + strconv.FormatBool(images) + "\n\t"
 
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: array})
+	list := service.MyService.App().GetSystemAppList()
+	for _, v := range *list {
+		systemAppStatus += v.Image + ",\n\t"
+	}
+	systemAppStatus += "Sync Key: " + config.SystemConfigInfo.SyncKey + "\n\t"
+
+	var bugContent string = fmt.Sprintf(`
+	**Desktop (please complete the following information):**
+	 - OS: %s
+	 - CasaOS Version: %s
+	 - Disk Total: %v 
+	 - Disk Used: %v 
+	 - Sync State: %s
+	 - System Info: %s
+	 - Browser: $Browser$ 
+	 - Version: $Version$
+`, sys.OS, types.CURRENTVERSION, disk.Total>>20, disk.Used>>20, systemAppStatus, array)
+
+	//	array = append(array, fmt.Sprintf("disk,total:%v,used:%v,UsedPercent:%v", disk.Total>>20, disk.Used>>20, disk.UsedPercent))
+
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: bugContent})
 }
 func Sys(c *gin.Context) {
 	service.DockerPull()
@@ -237,7 +262,42 @@ func Info(c *gin.Context) {
 	var data = make(map[string]interface{}, 5)
 
 	list := service.MyService.Disk().LSBLK()
-	data["disk"] = list
+
+	newList := []model.LSBLKModel{}
+	for i := len(list) - 1; i >= 0; i-- {
+		if list[i].Rota {
+			list[i].DiskType = "HDD"
+		} else {
+			list[i].DiskType = "SSD"
+		}
+		if list[i].Tran == "sata" {
+
+			temp := service.MyService.Disk().SmartCTL(list[i].Path)
+			if reflect.DeepEqual(temp, model.SmartctlA{}) {
+				continue
+			}
+			if len(list[i].Children) == 1 && len(list[i].Children[0].MountPoint) > 0 {
+				pathArr := strings.Split(list[i].Children[0].MountPoint, "/")
+				if len(pathArr) == 3 {
+					list[i].Children[0].Name = pathArr[2]
+				}
+			}
+
+			list[i].Temperature = temp.Temperature.Current
+			list[i].Health = strconv.FormatBool(temp.SmartStatus.Passed)
+			newList = append(newList, list[i])
+		} else if len(list[i].Children) > 0 && list[i].Children[0].MountPoint == "/" {
+			//system
+			list[i].Children[0].Name = "System"
+			list[i].Model = "System"
+			list[i].DiskType = "EMMC"
+			list[i].Health = "true"
+			newList = append(newList, list[i])
+
+		}
+	}
+
+	data["disk"] = newList
 	cpu := service.MyService.ZiMa().GetCpuPercent()
 	num := service.MyService.ZiMa().GetCpuCoreNum()
 	cpuData := make(map[string]interface{})

+ 3 - 4
service/casa.go

@@ -16,7 +16,7 @@ type CasaService interface {
 	GetServerList(index, size, tp, categoryId, key string) (recommend, list, community []model.ServerAppList)
 	GetServerCategoryList() []model.ServerCategoryList
 	GetTaskList(size int) []model2.TaskDBModel
-	GetServerAppInfo(id string) model.ServerAppList
+	GetServerAppInfo(id, t string) model.ServerAppList
 	ShareAppFile(body []byte) string
 }
 
@@ -88,13 +88,12 @@ func (o *casaService) GetServerCategoryList() []model.ServerCategoryList {
 
 	return list
 }
-func (o *casaService) GetServerAppInfo(id string) model.ServerAppList {
+func (o *casaService) GetServerAppInfo(id, t string) model.ServerAppList {
 
 	head := make(map[string]string)
 
 	head["Authorization"] = GetToken()
-
-	infoS := httper2.Get(config.ServerInfo.ServerApi+"/v2/app/info/"+id, head)
+	infoS := httper2.Get(config.ServerInfo.ServerApi+"/v2/app/info/"+id+"?t="+t, head)
 
 	info := model.ServerAppList{}
 	json2.Unmarshal([]byte(gjson.Get(infoS, "data").String()), &info)

+ 59 - 21
service/disk.go

@@ -3,6 +3,7 @@ package service
 import (
 	json2 "encoding/json"
 	"fmt"
+	"reflect"
 	"strconv"
 	"strings"
 	"time"
@@ -20,8 +21,9 @@ import (
 type DiskService interface {
 	GetPlugInDisk() []string
 	LSBLK() []model.LSBLKModel
-	FormatDisk(path, format string) string
-	UmountPointAndRemoveDir(path string) string
+	SmartCTL(path string) model.SmartctlA
+	FormatDisk(path, format string) []string
+	UmountPointAndRemoveDir(path string) []string
 	GetDiskInfo(path string) model.LSBLKModel
 	DelPartition(path, num string) string
 	AddPartition(path string) string
@@ -31,30 +33,60 @@ type DiskService interface {
 	SaveMountPoint(m model2.SerialDisk)
 	DeleteMountPoint(path, mountPoint string)
 	DeleteMount(id string)
+	UpdateMountPoint(m model2.SerialDisk)
+	RemoveLSBLKCache()
 }
 type diskService struct {
 	log loger2.OLog
 	db  *gorm.DB
 }
 
+func (d *diskService) RemoveLSBLKCache() {
+	key := "system_lsblk"
+	Cache.Delete(key)
+}
+func (d *diskService) SmartCTL(path string) model.SmartctlA {
+
+	key := "system_smart_" + path
+	if result, ok := Cache.Get(key); ok {
+
+		res, ok := result.(model.SmartctlA)
+		if ok {
+			return res
+		}
+	}
+	var m model.SmartctlA
+	str := command2.ExecSmartCTLByPath(path)
+	if str == nil {
+		d.log.Error("smartctl exec error,smartctl")
+		return m
+	}
+
+	err := json2.Unmarshal([]byte(str), &m)
+	if err != nil {
+		d.log.Error("json ummarshal error", err)
+	}
+	if !reflect.DeepEqual(m, model.SmartctlA{}) {
+		Cache.Add(key, m, time.Second*10)
+	}
+	return m
+}
+
 //通过脚本获取外挂磁盘
 func (d *diskService) GetPlugInDisk() []string {
 	return command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetPlugInDisk")
 }
 
 //格式化硬盘
-func (d *diskService) FormatDisk(path, format string) string {
-
+func (d *diskService) FormatDisk(path, format string) []string {
 	r := command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;FormatDisk " + path + " " + format)
-	fmt.Println(r)
-	return ""
+	return r
 }
 
 //移除挂载点,删除目录
-func (d *diskService) UmountPointAndRemoveDir(path string) string {
+func (d *diskService) UmountPointAndRemoveDir(path string) []string {
 	r := command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;UMountPorintAndRemoveDir " + path)
-	fmt.Println(r)
-	return ""
+	return r
 }
 
 //删除分区
@@ -66,8 +98,7 @@ func (d *diskService) DelPartition(path, num string) string {
 
 //part
 func (d *diskService) AddPartition(path string) string {
-	r := command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;AddPartition " + path)
-	fmt.Println(r)
+	command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;AddPartition " + path)
 	return ""
 }
 
@@ -78,11 +109,10 @@ func (d *diskService) AddAllPartition(path string) {
 //获取硬盘详情
 func (d *diskService) GetDiskInfoByPath(path string) *disk.UsageStat {
 	diskInfo, err := disk.Usage(path + "1")
+
 	if err != nil {
 		fmt.Println(err)
 	}
-	fmt.Println(path)
-	fmt.Println(diskInfo)
 	diskInfo.UsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.1f", diskInfo.UsedPercent), 64)
 	diskInfo.InodesUsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.1f", diskInfo.InodesUsedPercent), 64)
 	return diskInfo
@@ -91,7 +121,6 @@ func (d *diskService) GetDiskInfoByPath(path string) *disk.UsageStat {
 //get disk details
 func (d *diskService) LSBLK() []model.LSBLKModel {
 	key := "system_lsblk"
-
 	var n []model.LSBLKModel
 
 	if result, ok := Cache.Get(key); ok {
@@ -151,7 +180,7 @@ func (d *diskService) LSBLK() []model.LSBLKModel {
 		}
 	}
 	if len(n) > 0 {
-		Cache.Add(key, n, time.Second*10)
+		Cache.Add(key, n, time.Second*100)
 	}
 	return n
 }
@@ -162,6 +191,7 @@ func (d *diskService) GetDiskInfo(path string) model.LSBLKModel {
 		d.log.Error("lsblk exec error,str")
 		return model.LSBLKModel{}
 	}
+
 	var ml []model.LSBLKModel
 	err := json2.Unmarshal([]byte(gjson.Get(string(str), "blockdevices").String()), &ml)
 	if err != nil {
@@ -169,9 +199,13 @@ func (d *diskService) GetDiskInfo(path string) model.LSBLKModel {
 		d.log.Error("json ummarshal error", err)
 		return model.LSBLKModel{}
 	}
-	//todo 需要判断长度
-	m := ml[0]
-	//声明数组
+
+	m := model.LSBLKModel{}
+	if len(ml) > 0 {
+		m = ml[0]
+	}
+	return m
+	// 下面为计算是否可以继续分区的部分,暂时不需要
 	chiArr := make(map[string]string)
 	chiList := command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetPartitionSectors " + m.Path)
 	if len(chiList) == 0 {
@@ -182,7 +216,6 @@ func (d *diskService) GetDiskInfo(path string) model.LSBLKModel {
 		tempArr := strings.Split(chiList[i], ",")
 		chiArr[tempArr[0]] = chiList[i]
 	}
-
 	var maxSector uint64 = 0
 	for i := 0; i < len(m.Children); i++ {
 		tempArr := strings.Split(chiArr[m.Children[i].Path], ",")
@@ -191,13 +224,13 @@ func (d *diskService) GetDiskInfo(path string) model.LSBLKModel {
 		if m.Children[i].EndSector > maxSector {
 			maxSector = m.Children[i].EndSector
 		}
+
 	}
 	diskEndSector := command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetDiskSizeAndSectors " + m.Path)
 
 	if len(diskEndSector) < 2 {
 		d.log.Error("diskEndSector length error")
 	}
-
 	diskEndSectorInt, _ := strconv.ParseUint(diskEndSector[len(diskEndSector)-1], 10, 64)
 	if (diskEndSectorInt-maxSector)*m.MinIO/1024/1024 > 100 {
 		//添加可以分区情况
@@ -214,9 +247,14 @@ 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.Create(&m)
 }
 
+func (d *diskService) UpdateMountPoint(m model2.SerialDisk) {
+	d.db.Model(&model2.SerialDisk{}).Where("serial = ?", m.Serial).Update("mount_point", m.MountPoint)
+}
+
 func (d *diskService) DeleteMount(id string) {
 
 	d.db.Delete(&model2.SerialDisk{}).Where("id = ?", id)
@@ -224,7 +262,7 @@ func (d *diskService) DeleteMount(id string) {
 
 func (d *diskService) DeleteMountPoint(path, mountPoint string) {
 
-	d.db.Delete(&model2.SerialDisk{}).Where("path= ?  && mount_point = ?", path, mountPoint)
+	d.db.Where("path = ? AND mount_point = ?", path, mountPoint).Delete(&model2.SerialDisk{})
 
 	command2.OnlyExec("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;do_umount " + path)
 }

+ 29 - 0
shell/assist.sh

@@ -9,4 +9,33 @@ version_0_2_3() {
 
 }
 
+# add in v0.2.5
+readonly CASA_DEPANDS="curl smartmontools"
+version_0_2_5{
+  install_depends "$CASA_DEPANDS"
+}
+
+
+#Install Depends
+install_depends() {
+    ((EUID)) && sudo_cmd="sudo"
+    if [[ ! -x "$(command -v '$1')" ]]; then
+        show 2 "Install the necessary dependencies: $1"
+        packagesNeeded=$1
+        if [ -x "$(command -v apk)" ]; then
+            $sudo_cmd apk add --no-cache $packagesNeeded
+        elif [ -x "$(command -v apt-get)" ]; then
+            $sudo_cmd apt-get -y -q install $packagesNeeded
+        elif [ -x "$(command -v dnf)" ]; then
+            $sudo_cmd dnf install $packagesNeeded
+        elif [ -x "$(command -v zypper)" ]; then
+            $sudo_cmd zypper install $packagesNeeded
+        else
+            show 1 "Package manager not found. You must manually install: $packagesNeeded"
+        fi
+    fi
+}
+
 version_0_2_3
+
+version_0_2_5

+ 6 - 10
shell/helper.sh

@@ -73,10 +73,8 @@ UMountPorintAndRemoveDir() {
   if [[ -z ${MOUNT_POINT} ]]; then
     ${log} "Warning: ${DEVICE} is not mounted"
   else
-    umount -l ${DEVICE}
-    ${log} "Unmounted ${DEVICE} from ${MOUNT_POINT}"
+    umount -lf ${DEVICE}
     /bin/rmdir "${MOUNT_POINT}"
-    sed -i.bak "\@${MOUNT_POINT}@d" /var/log/usb-mount.track
   fi
 }
 
@@ -89,11 +87,11 @@ FormatDisk() {
   elif [ "$2" == "ntfs" ]; then
     mkfs.ntfs $1
   elif [ "$2" == "ext4" ]; then
-    mkfs.ext4 -F $1
+    mkfs.ext4 -m 1 -F $1
   elif [ "$2" == "exfat" ]; then
     mkfs.exfat $1
   else
-    mkfs.ext4 -F $1
+    mkfs.ext4 -m 1 -F $1
   fi
 }
 
@@ -118,12 +116,10 @@ AddPartition() {
 
   parted -s $1 mkpart primary ext4 0 100%
 
-  mkfs.ext4 $11
+  mkfs.ext4 -m 1 $11
 
   partprobe $1
 
-  #  mount $11 $2
-
 }
 
 #磁盘类型
@@ -148,14 +144,14 @@ GetDiskHealthState() {
 #result bytes
 #result sectors
 GetDiskSizeAndSectors() {
-  fdisk $1 -l | grep "/dev/sda:" | awk -F, 'BEGIN {OFS="\n"}{print $2,$3}' | awk '{print $1}'
+  fdisk $1 -l | grep "$1:" | awk -F, 'BEGIN {OFS="\n"}{print $2,$3}' | awk '{print $1}'
 }
 
 #获取磁盘分区数据扇区
 #param 磁盘路径  /dev/sda
 #result start,end,sectors
 GetPartitionSectors() {
-  fdisk $1 -l | grep "/dev/sda[1-9]" | awk 'BEGIN{OFS=","}{print $1,$2,$3,$4}'
+  fdisk $1 -l | grep "$1[1-9]" | awk 'BEGIN{OFS=","}{print $1,$2,$3,$4}'
 }
 
 #检查没有使用的挂载点删除文件夹

+ 2 - 2
types/system.go

@@ -1,4 +1,4 @@
 package types
 
-const CURRENTVERSION = "0.2.4"
-const BODY = "<li>New App Store</li><li>Fix minor bugs</li>"
+const CURRENTVERSION = "0.2.5"
+const BODY = "<li>Storage Manager</li><li>File synchronization issues</li><li>Fix the app store classification problem</li>"