mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-17 12:58:51 +08:00
feat: Add disk management (#10282)
Co-authored-by: wanghe-fit2cloud <wanghe@fit2cloud.com>
This commit is contained in:
parent
7448953b7d
commit
9c22005b79
34 changed files with 1426 additions and 8 deletions
99
agent/app/api/v2/disk.go
Normal file
99
agent/app/api/v2/disk.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Disk Management
|
||||
// @Summary Get complete disk information
|
||||
// @Description Get information about all disks including partitioned and unpartitioned disks
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.CompleteDiskInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /disks [get]
|
||||
func (b *BaseApi) GetCompleteDiskInfo(c *gin.Context) {
|
||||
diskInfo, err := diskService.GetCompleteDiskInfo()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, diskInfo)
|
||||
}
|
||||
|
||||
// @Tags Disk Management
|
||||
// @Summary Partition disk
|
||||
// @Description Create partition and format disk with specified filesystem
|
||||
// @Accept json
|
||||
// @Param request body request.DiskPartitionRequest true "partition request"
|
||||
// @Success 200 {string} string "Partition created successfully"
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /disks/partition [post]
|
||||
// @x-panel-log {"bodyKeys":["device", "filesystem", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"对磁盘 [device] 进行分区,文件系统 [filesystem],挂载点 [mountPoint]","formatEN":"Partition disk [device] with filesystem [filesystem], mount point [mountPoint]"}
|
||||
func (b *BaseApi) PartitionDisk(c *gin.Context) {
|
||||
var req request.DiskPartitionRequest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := diskService.PartitionDisk(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Disk Management
|
||||
// @Summary Mount disk
|
||||
// @Description Mount partition to specified mount point
|
||||
// @Accept json
|
||||
// @Param request body request.DiskMountRequest true "mount request"
|
||||
// @Success 200 {string} string "Disk mounted successfully"
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /disks/mount [post]
|
||||
// @x-panel-log {"bodyKeys":["device", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"挂载磁盘 [device] 到 [mountPoint]","formatEN":"Mount disk [device] to [mountPoint]"}
|
||||
func (b *BaseApi) MountDisk(c *gin.Context) {
|
||||
var req request.DiskMountRequest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := diskService.MountDisk(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Disk Management
|
||||
// @Summary Unmount disk
|
||||
// @Description Unmount partition from mount point
|
||||
// @Accept json
|
||||
// @Param request body request.DiskUnmountRequest true "unmount request"
|
||||
// @Success 200 {string} string "Disk unmounted successfully"
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /disks/unmount [post]
|
||||
// @x-panel-log {"bodyKeys":["device", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"卸载磁盘 [device] 从 [mountPoint]","formatEN":"Unmount disk [device] from [mountPoint]"}
|
||||
func (b *BaseApi) UnmountDisk(c *gin.Context) {
|
||||
var req request.DiskUnmountRequest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := diskService.UnmountDisk(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
|
@ -71,4 +71,6 @@ var (
|
|||
taskService = service.NewITaskService()
|
||||
groupService = service.NewIGroupService()
|
||||
alertService = service.NewIAlertService()
|
||||
|
||||
diskService = service.NewIDiskService()
|
||||
)
|
||||
|
|
|
|||
25
agent/app/dto/disk.go
Normal file
25
agent/app/dto/disk.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package dto
|
||||
|
||||
type LsblkDevice struct {
|
||||
Name string `json:"name"`
|
||||
Size string `json:"size"`
|
||||
Type string `json:"type"`
|
||||
MountPoint *string `json:"mountpoint"`
|
||||
FsType *string `json:"fstype"`
|
||||
Model *string `json:"model"`
|
||||
Serial string `json:"serial"`
|
||||
Tran string `json:"tran"`
|
||||
Rota bool `json:"rota"`
|
||||
Children []LsblkDevice `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type LsblkOutput struct {
|
||||
BlockDevices []LsblkDevice `json:"blockdevices"`
|
||||
}
|
||||
|
||||
type DiskFormatRequest struct {
|
||||
Device string `json:"device" `
|
||||
Filesystem string `json:"filesystem" `
|
||||
Label string `json:"label,omitempty" `
|
||||
QuickFormat bool `json:"quickFormat,omitempty"`
|
||||
}
|
||||
19
agent/app/dto/request/disk.go
Normal file
19
agent/app/dto/request/disk.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package request
|
||||
|
||||
type DiskPartitionRequest struct {
|
||||
Device string `json:"device" validate:"required"`
|
||||
Filesystem string `json:"filesystem" validate:"required,oneof=ext4 xfs"`
|
||||
Label string `json:"label"`
|
||||
AutoMount bool `json:"autoMount"`
|
||||
MountPoint string `json:"mountPoint"`
|
||||
}
|
||||
|
||||
type DiskMountRequest struct {
|
||||
Device string `json:"device" validate:"required"`
|
||||
MountPoint string `json:"mountPoint" validate:"required"`
|
||||
Filesystem string `json:"filesystem" validate:"required,oneof=ext4 xfs"`
|
||||
}
|
||||
|
||||
type DiskUnmountRequest struct {
|
||||
MountPoint string `json:"mountPoint" validate:"required"`
|
||||
}
|
||||
37
agent/app/dto/response/disk.go
Normal file
37
agent/app/dto/response/disk.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package response
|
||||
|
||||
type DiskInfo struct {
|
||||
DiskBasicInfo
|
||||
Partitions []DiskBasicInfo `json:"partitions"`
|
||||
}
|
||||
|
||||
type DiskBasicInfo struct {
|
||||
Device string `json:"device"`
|
||||
Size string `json:"size"`
|
||||
Model string `json:"model"`
|
||||
DiskType string `json:"diskType"`
|
||||
IsRemovable bool `json:"isRemovable"`
|
||||
IsSystem bool `json:"isSystem"`
|
||||
Filesystem string `json:"filesystem"`
|
||||
Used string `json:"used"`
|
||||
Avail string `json:"avail"`
|
||||
UsePercent int `json:"usePercent"`
|
||||
MountPoint string `json:"mountPoint"`
|
||||
IsMounted bool `json:"isMounted"`
|
||||
Serial string `json:"serial"`
|
||||
}
|
||||
|
||||
type CompleteDiskInfo struct {
|
||||
Disks []DiskInfo `json:"disks"`
|
||||
UnpartitionedDisks []DiskBasicInfo `json:"unpartitionedDisks"`
|
||||
SystemDisk *DiskInfo `json:"systemDisk"`
|
||||
TotalDisks int `json:"totalDisks"`
|
||||
TotalCapacity int64 `json:"totalCapacity"`
|
||||
}
|
||||
|
||||
type MountInfo struct {
|
||||
Device string `json:"device"`
|
||||
MountPoint string `json:"mountPoint"`
|
||||
Filesystem string `json:"filesystem"`
|
||||
Options string `json:"options"`
|
||||
}
|
||||
554
agent/app/service/disk.go
Normal file
554
agent/app/service/disk.go
Normal file
|
|
@ -0,0 +1,554 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
)
|
||||
|
||||
type DiskService struct{}
|
||||
|
||||
type IDiskService interface {
|
||||
GetCompleteDiskInfo() (*response.CompleteDiskInfo, error)
|
||||
PartitionDisk(req request.DiskPartitionRequest) (string, error)
|
||||
MountDisk(req request.DiskMountRequest) error
|
||||
UnmountDisk(req request.DiskUnmountRequest) error
|
||||
}
|
||||
|
||||
func NewIDiskService() IDiskService {
|
||||
return &DiskService{}
|
||||
}
|
||||
|
||||
func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error) {
|
||||
disksWithPartitions, unpartitionedDisks, err := getAllDisksInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var systemDisk *response.DiskInfo
|
||||
var totalCapacity int64
|
||||
var filteredDisksWithPartitions []response.DiskInfo
|
||||
|
||||
for i, disk := range disksWithPartitions {
|
||||
if disk.IsSystem {
|
||||
if systemDisk == nil {
|
||||
systemDisk = &disksWithPartitions[i]
|
||||
}
|
||||
} else {
|
||||
filteredDisksWithPartitions = append(filteredDisksWithPartitions, disk)
|
||||
}
|
||||
}
|
||||
|
||||
completeDiskInfo := &response.CompleteDiskInfo{
|
||||
Disks: filteredDisksWithPartitions,
|
||||
UnpartitionedDisks: unpartitionedDisks,
|
||||
SystemDisk: systemDisk,
|
||||
TotalDisks: len(filteredDisksWithPartitions) + len(unpartitionedDisks),
|
||||
TotalCapacity: totalCapacity,
|
||||
}
|
||||
return completeDiskInfo, nil
|
||||
}
|
||||
|
||||
func getDiskType(rota bool, tran string) string {
|
||||
if tran == "" {
|
||||
return ""
|
||||
}
|
||||
if !rota {
|
||||
return "SSD"
|
||||
}
|
||||
return "HDD"
|
||||
}
|
||||
|
||||
func getAllDisksInfo() ([]response.DiskInfo, []response.DiskBasicInfo, error) {
|
||||
var disksWithPartitions []response.DiskInfo
|
||||
var unpartitionedDisks []response.DiskBasicInfo
|
||||
|
||||
output, err := cmd.RunDefaultWithStdoutBashC("lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var lsblkOutput dto.LsblkOutput
|
||||
if err = json.Unmarshal([]byte(output), &lsblkOutput); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, device := range lsblkOutput.BlockDevices {
|
||||
if device.Type != "disk" {
|
||||
continue
|
||||
}
|
||||
|
||||
devicePath := "/dev/" + device.Name
|
||||
model := ""
|
||||
isPhysical := false
|
||||
|
||||
if device.Tran != "" {
|
||||
isPhysical = true
|
||||
if device.Model != nil {
|
||||
model = *device.Model
|
||||
}
|
||||
}
|
||||
|
||||
hasPartitions := len(device.Children) > 0
|
||||
|
||||
if hasPartitions {
|
||||
disk := response.DiskInfo{
|
||||
DiskBasicInfo: response.DiskBasicInfo{
|
||||
Device: devicePath,
|
||||
Size: device.Size,
|
||||
DiskType: getDiskType(device.Rota, device.Tran),
|
||||
IsRemovable: isRemovableDisk(devicePath),
|
||||
IsSystem: false,
|
||||
},
|
||||
Partitions: []response.DiskBasicInfo{},
|
||||
}
|
||||
|
||||
if isPhysical {
|
||||
disk.Serial = device.Serial
|
||||
disk.Model = model
|
||||
}
|
||||
|
||||
for _, partition := range device.Children {
|
||||
partitionInfo := processPartition(partition)
|
||||
if partitionInfo != nil {
|
||||
disk.Partitions = append(disk.Partitions, *partitionInfo)
|
||||
if partitionInfo.IsSystem {
|
||||
disk.IsSystem = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(disk.Partitions) > 0 {
|
||||
disksWithPartitions = append(disksWithPartitions, disk)
|
||||
}
|
||||
} else {
|
||||
if isSystemDiskByDevice(device) {
|
||||
continue
|
||||
}
|
||||
|
||||
unpartitionedDisk := response.DiskBasicInfo{
|
||||
Device: devicePath,
|
||||
Size: device.Size,
|
||||
Model: model,
|
||||
Serial: device.Serial,
|
||||
DiskType: getDiskType(device.Rota, device.Tran),
|
||||
IsRemovable: isRemovableDisk(devicePath),
|
||||
}
|
||||
unpartitionedDisks = append(unpartitionedDisks, unpartitionedDisk)
|
||||
}
|
||||
}
|
||||
|
||||
return disksWithPartitions, unpartitionedDisks, nil
|
||||
}
|
||||
|
||||
func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, error) {
|
||||
if !deviceExists(req.Device) {
|
||||
return "", buserr.WithName("DeviceNotFound", req.Device)
|
||||
}
|
||||
|
||||
if isDeviceMounted(req.Device) {
|
||||
return "", buserr.WithName("DeviceIsMounted", req.Device)
|
||||
}
|
||||
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second))
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mklabel gpt", req.Device)); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mkpart primary 1MiB 100%%", req.Device)); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
partition := req.Device + "1"
|
||||
|
||||
formatReq := dto.DiskFormatRequest{
|
||||
Device: partition,
|
||||
Filesystem: req.Filesystem,
|
||||
Label: req.Label,
|
||||
}
|
||||
if err := formatDisk(formatReq); err != nil {
|
||||
return "", buserr.WithErr("FormatDiskErr", err)
|
||||
}
|
||||
|
||||
if req.AutoMount && req.MountPoint != "" {
|
||||
mountReq := request.DiskMountRequest{
|
||||
Device: partition,
|
||||
MountPoint: req.MountPoint,
|
||||
Filesystem: req.Filesystem,
|
||||
}
|
||||
if err := s.MountDisk(mountReq); err != nil {
|
||||
return "", buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
}
|
||||
|
||||
return partition, nil
|
||||
}
|
||||
|
||||
func (s *DiskService) MountDisk(req request.DiskMountRequest) error {
|
||||
if !deviceExists(req.Device) {
|
||||
return buserr.WithName("DeviceNotFound", req.Device)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(req.MountPoint, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileSystem, err := getFilesystemType(req.Device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fileSystem == "" {
|
||||
formatReq := dto.DiskFormatRequest{
|
||||
Device: req.Device,
|
||||
Filesystem: req.Filesystem,
|
||||
}
|
||||
if err := formatDisk(formatReq); err != nil {
|
||||
return buserr.WithErr("FormatDiskErr", err)
|
||||
}
|
||||
}
|
||||
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Minute))
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("mount -t %s %s %s", req.Filesystem, req.Device, req.MountPoint)); err != nil {
|
||||
return buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
|
||||
if err := addToFstabWithOptions(req.Device, req.MountPoint, req.Filesystem, ""); err != nil {
|
||||
return buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DiskService) UnmountDisk(req request.DiskUnmountRequest) error {
|
||||
if !isPointMounted(req.MountPoint) {
|
||||
return buserr.New("MountDiskErr")
|
||||
}
|
||||
if err := cmd.RunDefaultBashC(fmt.Sprintf("umount -f %s", req.MountPoint)); err != nil {
|
||||
return buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
if err := removeFromFstab(req.MountPoint); err != nil {
|
||||
global.LOG.Errorf("remove %s mountPoint err: %v", req.MountPoint, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatDisk(req dto.DiskFormatRequest) error {
|
||||
var mkfsCmd *exec.Cmd
|
||||
|
||||
switch req.Filesystem {
|
||||
case "ext4":
|
||||
mkfsCmd = exec.Command("mkfs.ext4", "-F", req.Device)
|
||||
case "xfs":
|
||||
if !cmd.Which("mkfs.xfs") {
|
||||
return buserr.New("XfsNotFound")
|
||||
}
|
||||
mkfsCmd = exec.Command("mkfs.xfs", "-f", req.Device)
|
||||
default:
|
||||
return fmt.Errorf("unsupport type: %s", req.Filesystem)
|
||||
}
|
||||
if err := mkfsCmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDiskInfoLinux(fields []string) (response.DiskInfo, error) {
|
||||
device := fields[0]
|
||||
filesystem := fields[1]
|
||||
sizeStr := fields[2]
|
||||
usedStr := fields[3]
|
||||
availStr := fields[4]
|
||||
usePercentStr := fields[5]
|
||||
mountPoint := fields[6]
|
||||
|
||||
usePercent := 0
|
||||
if strings.HasSuffix(usePercentStr, "%") {
|
||||
if percent, err := strconv.Atoi(strings.TrimSuffix(usePercentStr, "%")); err == nil {
|
||||
usePercent = percent
|
||||
}
|
||||
}
|
||||
|
||||
return response.DiskInfo{
|
||||
DiskBasicInfo: response.DiskBasicInfo{
|
||||
Device: device,
|
||||
Filesystem: filesystem,
|
||||
Size: sizeStr,
|
||||
Used: usedStr,
|
||||
Avail: availStr,
|
||||
UsePercent: usePercent,
|
||||
MountPoint: mountPoint,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func deviceExists(device string) bool {
|
||||
_, err := os.Stat(device)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func isDeviceMounted(device string) bool {
|
||||
file, err := os.Open("/proc/mounts")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) >= 2 && fields[0] == device {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isPointMounted(mountPoint string) bool {
|
||||
file, err := os.Open("/proc/mounts")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) >= 2 && fields[1] == mountPoint {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func addToFstabWithOptions(device, mountPoint, filesystem, options string) error {
|
||||
if filesystem == "" {
|
||||
fsType, err := getFilesystemType(device)
|
||||
if err != nil {
|
||||
filesystem = "auto"
|
||||
} else {
|
||||
filesystem = fsType
|
||||
}
|
||||
}
|
||||
|
||||
if options == "" {
|
||||
options = "defaults"
|
||||
}
|
||||
|
||||
entry := fmt.Sprintf("%s %s %s %s 0 2\n", device, mountPoint, filesystem, options)
|
||||
|
||||
file, err := os.OpenFile("/etc/fstab", os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.WriteString(entry)
|
||||
return err
|
||||
}
|
||||
|
||||
func removeFromFstab(mountPoint string) error {
|
||||
file, err := os.Open("/etc/fstab")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var lines []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 && fields[1] == mountPoint {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
|
||||
return os.WriteFile("/etc/fstab", []byte(strings.Join(lines, "\n")+"\n"), 0644)
|
||||
}
|
||||
|
||||
func getFilesystemType(device string) (string, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("blkid -o value -s TYPE %s", device))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(output), nil
|
||||
}
|
||||
|
||||
func isSystemDisk(mountPoint string) bool {
|
||||
systemMountPoints := []string{
|
||||
"/",
|
||||
"/boot",
|
||||
"/boot/efi",
|
||||
"/usr",
|
||||
"/var",
|
||||
"/home",
|
||||
}
|
||||
|
||||
for _, sysMount := range systemMountPoints {
|
||||
if mountPoint == sysMount {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isSystemDiskByDevice(device dto.LsblkDevice) bool {
|
||||
for _, child := range device.Children {
|
||||
if child.Type == "part" {
|
||||
if child.MountPoint != nil && isSystemDisk(*child.MountPoint) {
|
||||
return true
|
||||
}
|
||||
for _, lvmChild := range child.Children {
|
||||
if lvmChild.Type == "lvm" && lvmChild.MountPoint != nil {
|
||||
if isSystemDisk(*lvmChild.MountPoint) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isRemovableDisk(device string) bool {
|
||||
deviceName := strings.TrimPrefix(device, "/dev/")
|
||||
for i := len(deviceName) - 1; i >= 0; i-- {
|
||||
if deviceName[i] < '0' || deviceName[i] > '9' {
|
||||
deviceName = deviceName[:i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
removablePath := filepath.Join("/sys/block", deviceName, "removable")
|
||||
if data, err := os.ReadFile(removablePath); err == nil {
|
||||
removable := strings.TrimSpace(string(data))
|
||||
return removable == "1"
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func processPartition(partition dto.LsblkDevice) *response.DiskBasicInfo {
|
||||
if partition.Type != "part" {
|
||||
return nil
|
||||
}
|
||||
|
||||
devicePath := "/dev/" + partition.Name
|
||||
|
||||
var mountPoint, filesystem string
|
||||
if partition.MountPoint != nil {
|
||||
mountPoint = *partition.MountPoint
|
||||
}
|
||||
if partition.FsType != nil {
|
||||
filesystem = *partition.FsType
|
||||
}
|
||||
|
||||
var (
|
||||
actualMountPoint, actualFilesystem string
|
||||
size, used, avail string
|
||||
usePercent int
|
||||
isMounted bool
|
||||
isSystem bool
|
||||
)
|
||||
|
||||
if len(partition.Children) > 0 {
|
||||
for _, child := range partition.Children {
|
||||
if child.Type == "lvm" {
|
||||
lvmDevicePath := "/dev/mapper/" + child.Name
|
||||
|
||||
if child.MountPoint != nil {
|
||||
actualMountPoint = *child.MountPoint
|
||||
isMounted = true
|
||||
}
|
||||
if child.FsType != nil {
|
||||
actualFilesystem = *child.FsType
|
||||
}
|
||||
|
||||
if actualMountPoint != "" {
|
||||
diskUsage, err := getDiskUsageInfo(lvmDevicePath)
|
||||
if err == nil {
|
||||
used = diskUsage.Used
|
||||
avail = diskUsage.Avail
|
||||
size = diskUsage.Size
|
||||
usePercent = diskUsage.UsePercent
|
||||
}
|
||||
}
|
||||
|
||||
isSystem = isSystemDisk(actualMountPoint)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
actualMountPoint = mountPoint
|
||||
actualFilesystem = filesystem
|
||||
isMounted = mountPoint != ""
|
||||
|
||||
if actualMountPoint != "" {
|
||||
diskUsage, err := getDiskUsageInfo(devicePath)
|
||||
if err == nil {
|
||||
used = diskUsage.Used
|
||||
avail = diskUsage.Avail
|
||||
size = diskUsage.Size
|
||||
usePercent = diskUsage.UsePercent
|
||||
}
|
||||
}
|
||||
|
||||
isSystem = isSystemDisk(actualMountPoint)
|
||||
}
|
||||
|
||||
if size == "" {
|
||||
size = partition.Size
|
||||
}
|
||||
|
||||
return &response.DiskBasicInfo{
|
||||
Device: devicePath,
|
||||
Size: size,
|
||||
Used: used,
|
||||
Avail: avail,
|
||||
UsePercent: usePercent,
|
||||
MountPoint: actualMountPoint,
|
||||
Filesystem: actualFilesystem,
|
||||
IsMounted: isMounted,
|
||||
IsSystem: isSystem,
|
||||
}
|
||||
}
|
||||
|
||||
func getDiskUsageInfo(device string) (response.DiskInfo, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("df -hT -P %s", device))
|
||||
if err != nil {
|
||||
return response.DiskInfo{}, err
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
if len(lines) < 2 {
|
||||
return response.DiskInfo{}, err
|
||||
}
|
||||
|
||||
fields := strings.Fields(lines[1])
|
||||
if len(fields) < 7 {
|
||||
return response.DiskInfo{}, err
|
||||
}
|
||||
|
||||
return parseDiskInfoLinux(fields)
|
||||
}
|
||||
|
|
@ -474,4 +474,13 @@ PanelPwdExpirationAlert: "Your 1Panel password will expire in {{ .day }} days. P
|
|||
CommonAlert: "Your 1Panel, {{ .msg }}, please log in to the panel for details."
|
||||
NodeExceptionAlert: "Your 1Panel, {{ .num }} nodes are abnormal, please log in to the panel for details."
|
||||
LicenseExceptionAlert: "Your 1Panel, {{ .num }} licenses are abnormal, please log in to the panel for details."
|
||||
SSHAndPanelLoginAlert: "Your 1Panel, abnormal panel {{ .name }} login from {{ .ip }}, please log in to the panel for details."
|
||||
SSHAndPanelLoginAlert: "Your 1Panel, abnormal panel {{ .name }} login from {{ .ip }}, please log in to the panel for details."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Device {{ .name }} not found"
|
||||
DeviceIsMounted: "Device {{ .name }} is mounted, please unmount first"
|
||||
PartitionDiskErr: "Failed to partition, {{ .err }}"
|
||||
FormatDiskErr: "Failed to format disk, {{ .err }}"
|
||||
MountDiskErr: "Failed to mount disk, {{ .err }}"
|
||||
UnMountDiskErr: "Failed to unmount disk, {{ .err }}"
|
||||
XfsNotFound: "xfs filesystem not detected, please install xfsprogs first"
|
||||
|
|
@ -474,4 +474,13 @@ PanelPwdExpirationAlert: "1Panel のパスワードは {{ .day }} 日後に期
|
|||
CommonAlert: "お使いの1Panel、{{ .msg }}、詳細はパネルにログインしてご確認ください。"
|
||||
NodeExceptionAlert: "お使いの1Panel、{{ .num }}個のノードに異常があります。詳細はパネルにログインしてご確認ください。"
|
||||
LicenseExceptionAlert: "お使いの1Panel、{{ .num }}個のライセンスに異常があります。詳細はパネルにログインしてご確認ください。"
|
||||
SSHAndPanelLoginAlert: "お使いの1Panel、パネル{{ .name }}が{{ .ip }}から異常ログインしました。詳細はパネルにログインしてご確認ください。"
|
||||
SSHAndPanelLoginAlert: "お使いの1Panel、パネル{{ .name }}が{{ .ip }}から異常ログインしました。詳細はパネルにログインしてご確認ください。"
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "デバイス {{ .name }} が見つかりません"
|
||||
DeviceIsMounted: "デバイス {{ .name }} はマウントされています、まずアンマウントしてください"
|
||||
PartitionDiskErr: "パーティションに失敗しました、{{ .err }}"
|
||||
FormatDiskErr: "ディスクのフォーマットに失敗しました、{{ .err }}"
|
||||
MountDiskErr: "ディスクのマウントに失敗しました、{{ .err }}"
|
||||
UnMountDiskErr: "ディスクのアンマウントに失敗しました、{{ .err }}"
|
||||
XfsNotFound: "xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください"
|
||||
|
|
@ -474,4 +474,13 @@ PanelPwdExpirationAlert: "1Panel 비밀번호가 {{ .day }}일 후에 만료됩
|
|||
CommonAlert: "귀하의 1Panel, {{ .msg }}. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
NodeExceptionAlert: "귀하의 1Panel, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
LicenseExceptionAlert: "귀하의 1Panel, {{ .num }}개의 라이선스에 이상이 있습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
SSHAndPanelLoginAlert: "귀하의 1Panel, 패널 {{ .name }}이(가) {{ .ip }}에서 비정상 로그인했습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
SSHAndPanelLoginAlert: "귀하의 1Panel, 패널 {{ .name }}이(가) {{ .ip }}에서 비정상 로그인했습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "장치 {{ .name }} 을(를) 찾을 수 없습니다"
|
||||
DeviceIsMounted: "장치 {{ .name }} 이(가) 마운트되었습니다, 먼저 마운트 해제하세요"
|
||||
PartitionDiskErr: "파티션 분할에 실패했습니다, {{ .err }}"
|
||||
FormatDiskErr: "디스크 포맷에 실패했습니다, {{ .err }}"
|
||||
MountDiskErr: "디스크 마운트에 실패했습니다, {{ .err }}"
|
||||
UnMountDiskErr: "디스크 마운트 해제에 실패했습니다, {{ .err }}"
|
||||
XfsNotFound: "xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요"
|
||||
|
|
@ -474,4 +474,13 @@ PanelPwdExpirationAlert: "Kata laluan 1Panel anda akan tamat dalam {{ .day }} ha
|
|||
CommonAlert: "1Panel anda, {{ .msg }}, sila log masuk ke panel untuk maklumat lanjut."
|
||||
NodeExceptionAlert: "1Panel anda, {{ .num }} nod bermasalah, sila log masuk ke panel untuk maklumat lanjut."
|
||||
LicenseExceptionAlert: "1Panel anda, {{ .num }} lesen bermasalah, sila log masuk ke panel untuk maklumat lanjut."
|
||||
SSHAndPanelLoginAlert: "1Panel anda, log masuk panel {{ .name }} yang tidak normal dari {{ .ip }}, sila log masuk ke panel untuk maklumat lanjut."
|
||||
SSHAndPanelLoginAlert: "1Panel anda, log masuk panel {{ .name }} yang tidak normal dari {{ .ip }}, sila log masuk ke panel untuk maklumat lanjut."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Peranti {{ .name }} tidak ditemui"
|
||||
DeviceIsMounted: "Peranti {{ .name }} telah dikaitkan, sila nyahkaitkan terlebih dahulu"
|
||||
PartitionDiskErr: "Gagal membahagikan, {{ .err }}"
|
||||
FormatDiskErr: "Gagal memformat cakera, {{ .err }}"
|
||||
MountDiskErr: "Gagal mengkaitkan cakera, {{ .err }}"
|
||||
UnMountDiskErr: "Gagal nyahkaitkan cakera, {{ .err }}"
|
||||
XfsNotFound: "Sistem fail xfs tidak dikesan, sila pasang xfsprogs terlebih dahulu"
|
||||
|
|
@ -474,4 +474,13 @@ PanelPwdExpirationAlert: "A senha do 1Panel expirará em {{ .day }} dias. Acesse
|
|||
CommonAlert: "Seu 1Panel, {{ .msg }}. Para mais detalhes, faça login no painel."
|
||||
NodeExceptionAlert: "Seu 1Panel, {{ .num }} nós estão com problemas. Para mais detalhes, faça login no painel."
|
||||
LicenseExceptionAlert: "Seu 1Panel, {{ .num }} licenças estão com problemas. Para mais detalhes, faça login no painel."
|
||||
SSHAndPanelLoginAlert: "Seu 1Panel, login anormal no painel {{ .name }} a partir de {{ .ip }}. Para mais detalhes, faça login no painel."
|
||||
SSHAndPanelLoginAlert: "Seu 1Panel, login anormal no painel {{ .name }} a partir de {{ .ip }}. Para mais detalhes, faça login no painel."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Dispositivo {{ .name }} não encontrado"
|
||||
DeviceIsMounted: "Dispositivo {{ .name }} está montado, por favor desmonte primeiro"
|
||||
PartitionDiskErr: "Falha ao particionar, {{ .err }}"
|
||||
FormatDiskErr: "Falha ao formatar disco, {{ .err }}"
|
||||
MountDiskErr: "Falha ao montar disco, {{ .err }}"
|
||||
UnMountDiskErr: "Falha ao desmontar disco, {{ .err }}"
|
||||
XfsNotFound: "Sistema de arquivos xfs não detectado, por favor instale xfsprogs primeiro"
|
||||
|
|
@ -474,4 +474,13 @@ PanelPwdExpirationAlert: "Пароль для 1Panel истекает через
|
|||
CommonAlert: "Ваш 1Panel, {{ .msg }}. Подробности смотрите, войдя в панель."
|
||||
NodeExceptionAlert: "Ваш 1Panel, {{ .num }} узлов работают неправильно. Подробности смотрите, войдя в панель."
|
||||
LicenseExceptionAlert: "Ваш 1Panel, {{ .num }} лицензий работают неправильно. Подробности смотрите, войдя в панель."
|
||||
SSHAndPanelLoginAlert: "Ваш 1Panel, обнаружен аномальный вход в панель {{ .name }} с {{ .ip }}. Подробности смотрите, войдя в панель."
|
||||
SSHAndPanelLoginAlert: "Ваш 1Panel, обнаружен аномальный вход в панель {{ .name }} с {{ .ip }}. Подробности смотрите, войдя в панель."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Устройство {{ .name }} не найдено"
|
||||
DeviceIsMounted: "Устройство {{ .name }} подключено, сначала отключите"
|
||||
PartitionDiskErr: "Не удалось разделить, {{ .err }}"
|
||||
FormatDiskErr: "Не удалось отформатировать диск, {{ .err }}"
|
||||
MountDiskErr: "Не удалось подключить диск, {{ .err }}"
|
||||
UnMountDiskErr: "Не удалось отключить диск, {{ .err }}"
|
||||
XfsNotFound: "Файловая система xfs не обнаружена, сначала установите xfsprogs"
|
||||
|
|
@ -476,3 +476,12 @@ CommonAlert: "1Panel'iniz, {{ .msg }}. Detaylar için panele giriş yapınız."
|
|||
NodeExceptionAlert: "1Panel'iniz, {{ .num }} düğümde sorun var. Detaylar için panele giriş yapınız."
|
||||
LicenseExceptionAlert: "1Panel'iniz, {{ .num }} lisansda sorun var. Detaylar için panele giriş yapınız."
|
||||
SSHAndPanelLoginAlert: "1Panel'iniz, {{ .ip }} adresinden {{ .name }} paneline anormal giriş tespit edildi. Detaylar için panele giriş yapınız."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Cihaz {{ .name }} bulunamadı"
|
||||
DeviceIsMounted: "Cihaz {{ .name }} bağlandı, önce çıkarın"
|
||||
PartitionDiskErr: "Bölümleme başarısız, {{ .err }}"
|
||||
FormatDiskErr: "Disk biçimlendirme başarısız, {{ .err }}"
|
||||
MountDiskErr: "Disk bağlama başarısız, {{ .err }}"
|
||||
UnMountDiskErr: "Disk bağının kaldırılması başarısız, {{ .err }}"
|
||||
XfsNotFound: "XFS dosya sistemi algılanmadı, lütfen önce xfsprogs'i yükleyin"
|
||||
|
|
|
|||
|
|
@ -474,3 +474,12 @@ CommonAlert: "您的 1Panel 面板,{{ .msg }},詳情請登入面板查看。
|
|||
NodeExceptionAlert: "您的 1Panel 面板,{{ .num }} 個節點存在異常,詳情請登入面板查看。"
|
||||
LicenseExceptionAlert: "您的 1Panel 面板,{{ .num }} 個許可證存在異常,詳情請登入面板查看。"
|
||||
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}從 {{ .ip }} 登錄異常,詳情請登入面板查看。"
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "設備 {{ .name }} 未找到"
|
||||
DeviceIsMounted: "設備 {{ .name }} 已掛載,請先卸載"
|
||||
PartitionDiskErr: "分區失敗,{{ .err }}"
|
||||
FormatDiskErr: "格式化磁盤失敗,{{ .err }}"
|
||||
MountDiskErr: "掛載磁盤失敗,{{ .err }}"
|
||||
UnMountDiskErr: "卸載磁盤失敗,{{ .err }}"
|
||||
XfsNotFound: "未檢測到 xfs 文件系統,請先安裝 xfsprogs"
|
||||
|
|
|
|||
|
|
@ -474,4 +474,13 @@ PanelPwdExpirationAlert: "您的 1Panel 面板,面板密码将在 {{ .day }}
|
|||
CommonAlert: "您的 1Panel 面板,{{ .msg }},详情请登录面板查看。"
|
||||
NodeExceptionAlert: "您的 1Panel 面板,{{ .num }} 个节点存在异常,详情请登录面板查看。"
|
||||
LicenseExceptionAlert: "您的 1Panel 面板,{{ .num }} 个许可证存在异常,详情请登录面板查看。"
|
||||
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}登录{{ .ip }}异常,详情请登录面板查看。"
|
||||
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}登录{{ .ip }}异常,详情请登录面板查看。"
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "设备 {{ .name }} 未找到"
|
||||
DeviceIsMounted: "设备 {{ .name }} 已挂载,请先卸载"
|
||||
PartitionDiskErr: "分区失败,{{ .err }}"
|
||||
FormatDiskErr: "格式化磁盘失败,{{ .err }}"
|
||||
MountDiskErr: "挂载磁盘失败,{{ .err }}"
|
||||
UnMountDiskErr: "卸载磁盘失败,{{ .err }}"
|
||||
XfsNotFound: "未检测到 xfs 文件系统,请先安装 xfsprogs"
|
||||
|
|
@ -52,5 +52,10 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
|||
hostRouter.POST("/tool/supervisor/process/file", baseApi.GetProcessFile)
|
||||
|
||||
hostRouter.GET("/terminal", baseApi.WsSSH)
|
||||
|
||||
hostRouter.GET("/disks", baseApi.GetCompleteDiskInfo)
|
||||
hostRouter.POST("/disks/partition", baseApi.PartitionDisk)
|
||||
hostRouter.POST("/disks/mount", baseApi.MountDisk)
|
||||
hostRouter.POST("/disks/unmount", baseApi.UnmountDisk)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ func Init() {
|
|||
migrations.AddClusterMenu,
|
||||
migrations.DeleteXpackHideMenu,
|
||||
migrations.AddCronjobGroup,
|
||||
migrations.AddDiskMenu,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
|
|
|||
|
|
@ -511,3 +511,17 @@ var AddCronjobGroup = &gormigrate.Migration{
|
|||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddDiskMenu = &gormigrate.Migration{
|
||||
ID: "20250811-add-disk-menu",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return helper.AddMenu(dto.ShowMenu{
|
||||
ID: "77",
|
||||
Disabled: false,
|
||||
Title: "menu.disk",
|
||||
IsShow: true,
|
||||
Label: "Disk",
|
||||
Path: "/hosts/disk",
|
||||
}, "7", tx)
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,4 +204,50 @@ export namespace Host {
|
|||
status: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface DiskBasicInfo {
|
||||
device: string;
|
||||
size: string;
|
||||
model: string;
|
||||
diskType: string;
|
||||
isRemovable: boolean;
|
||||
isSystem: boolean;
|
||||
filesystem: string;
|
||||
used: string;
|
||||
avail: string;
|
||||
usePercent: number;
|
||||
mountPoint: string;
|
||||
isMounted: boolean;
|
||||
serial: string;
|
||||
}
|
||||
|
||||
export interface DiskInfo extends DiskBasicInfo {
|
||||
partitions?: DiskBasicInfo[];
|
||||
}
|
||||
|
||||
export interface CompleteDiskInfo {
|
||||
disks: DiskInfo[];
|
||||
unpartitionedDisks: DiskBasicInfo[];
|
||||
systemDisk?: DiskInfo;
|
||||
totalDisks: number;
|
||||
totalCapacity: number;
|
||||
}
|
||||
|
||||
export interface DiskPartition {
|
||||
device: string;
|
||||
filesystem: string;
|
||||
label: string;
|
||||
autoMount: boolean;
|
||||
mountPoint: string;
|
||||
}
|
||||
|
||||
export interface DiskMount {
|
||||
device: string;
|
||||
mountPoint: string;
|
||||
filesystem?: string;
|
||||
}
|
||||
|
||||
export interface DiskUmount {
|
||||
mountPoint: string;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,5 +106,21 @@ export const loadSSHLogs = (params: Host.searchSSHLog) => {
|
|||
return http.post<ResPage<Host.sshHistory>>(`/hosts/ssh/log`, params);
|
||||
};
|
||||
export const exportSSHLogs = (params: Host.searchSSHLog) => {
|
||||
return http.post<string>('/hosts/ssh/log/export', params, TimeoutEnum.T_40S);
|
||||
return http.post<string>(`/hosts/ssh/log/export`, params, TimeoutEnum.T_40S);
|
||||
};
|
||||
|
||||
export const listDisks = () => {
|
||||
return http.get<Host.CompleteDiskInfo>(`/hosts/disks`);
|
||||
};
|
||||
|
||||
export const partitionDisk = (params: Host.DiskPartition) => {
|
||||
return http.post(`/hosts/disks/partition`, params, TimeoutEnum.T_60S);
|
||||
};
|
||||
|
||||
export const mountDisk = (params: Host.DiskMount) => {
|
||||
return http.post(`/hosts/disks/mount`, params, TimeoutEnum.T_60S);
|
||||
};
|
||||
|
||||
export const unmountDisk = (params: Host.DiskUmount) => {
|
||||
return http.post(`/hosts/disks/unmount`, params, TimeoutEnum.T_60S);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2927,6 +2927,27 @@ const message = {
|
|||
autoStartHelper: 'Whether to automatically start the service after Supervisor starts',
|
||||
},
|
||||
},
|
||||
disk: {
|
||||
management: 'Disk Management',
|
||||
partition: 'Partition',
|
||||
unmount: 'Unmount',
|
||||
unmountHelper: 'Do you want to unmount the partition {0}?',
|
||||
mount: 'Mount',
|
||||
partitionAlert:
|
||||
'Disk partitioning requires formatting the disk, and existing data will be deleted. Please save or take snapshots of your data in advance.',
|
||||
mountPoint: 'Mount Directory',
|
||||
systemDisk: 'System Disk',
|
||||
unpartitionedDisk: 'Unpartitioned Disk',
|
||||
handlePartition: 'Partition Now',
|
||||
filesystem: 'Filesystem',
|
||||
unmounted: 'Unmounted',
|
||||
cannotOperate: 'Cannot Operate',
|
||||
systemDiskHelper: 'Hint: The current disk is the system disk. It cannot be operated on.',
|
||||
autoMount: 'Auto Mount',
|
||||
model: 'Device Model',
|
||||
diskType: 'Disk Type',
|
||||
serial: 'Serial Number',
|
||||
},
|
||||
xpack: {
|
||||
expiresTrialAlert:
|
||||
'Friendly reminder: Your Pro trial will expire in {0} days, and all Pro features will no longer be accessible. Please renew or upgrade to the full version in a timely manner.',
|
||||
|
|
|
|||
|
|
@ -2822,6 +2822,27 @@ const message = {
|
|||
autoStartHelper: 'Supervisor 起動後にサービスを自動的に起動するかどうか',
|
||||
},
|
||||
},
|
||||
disk: {
|
||||
management: 'ディスク管理',
|
||||
partition: 'パーティション',
|
||||
unmount: 'アンマウント',
|
||||
unmountHelper: 'パーティション {0} をアンマウントしますか?',
|
||||
mount: 'マウント',
|
||||
partitionAlert:
|
||||
'ディスクのパーティション分割にはディスクのフォーマットが必要で、既存のデータは削除されます。事前にデータを保存またはスナップショットを取ってください。',
|
||||
mountPoint: 'マウントディレクトリ',
|
||||
systemDisk: 'システムディスク',
|
||||
unpartitionedDisk: '未パーティションディスク',
|
||||
handlePartition: '今すぐパーティション',
|
||||
filesystem: 'ファイルシステム',
|
||||
unmounted: 'アンマウント',
|
||||
cannotOperate: '操作不可',
|
||||
systemDiskHelper: 'ヒント: 現在のディスクはシステムディスクです。操作できません。',
|
||||
autoMount: '自動マウント',
|
||||
model: 'デバイスモデル',
|
||||
diskType: 'ディスクタイプ',
|
||||
serial: 'シリアルナンバー',
|
||||
},
|
||||
xpack: {
|
||||
expiresTrialAlert:
|
||||
'ご注意: あなたのProトライアルは{0}日後に終了し、すべてのPro機能が使用できなくなります。適時に更新またはフルバージョンにアップグレードしてください。',
|
||||
|
|
|
|||
|
|
@ -2773,6 +2773,27 @@ const message = {
|
|||
autoStartHelper: 'Supervisor 시작 후 서비스를 자동으로 시작할지 여부',
|
||||
},
|
||||
},
|
||||
disk: {
|
||||
management: '디스크 관리',
|
||||
partition: '파티션',
|
||||
unmount: '마운트 해제',
|
||||
unmountHelper: '파티션 {0} 을(를) 마운트 해제하시겠습니까?',
|
||||
mount: '마운트',
|
||||
partitionAlert:
|
||||
'디스크 파티션 작업은 디스크 포맷이 필요하며, 기존 데이터는 삭제됩니다. 데이터를 미리 저장하거나 스냅샷을 찍어주세요.',
|
||||
mountPoint: '마운트 디렉토리',
|
||||
systemDisk: '시스템 디스크',
|
||||
unpartitionedDisk: '미파티션 디스크',
|
||||
handlePartition: '지금 파티션',
|
||||
filesystem: '파일 시스템',
|
||||
unmounted: '마운트 해제됨',
|
||||
cannotOperate: '작업 불가',
|
||||
systemDiskHelper: '힌트: 현재 디스크는 시스템 디스크입니다. 작업할 수 없습니다.',
|
||||
autoMount: '자동 마운트',
|
||||
model: '장치 모델',
|
||||
diskType: '디스크 유형',
|
||||
serial: '시리얼 번호',
|
||||
},
|
||||
xpack: {
|
||||
expiresTrialAlert:
|
||||
'친절한 알림: 귀하의 Pro 체험판이 {0}일 후 만료되며, 모든 Pro 기능에 더 이상 접근할 수 없습니다. 제때 갱신하거나 전체 버전으로 업그레이드하시기 바랍니다.',
|
||||
|
|
|
|||
|
|
@ -2886,6 +2886,19 @@ const message = {
|
|||
autoStartHelper: 'Sama ada untuk memulakan perkhidmatan secara automatik selepas Supervisor mula',
|
||||
},
|
||||
},
|
||||
disk: {
|
||||
systemDisk: 'Cakera Sistem',
|
||||
unpartitionedDisk: 'Cakera Tidak Dibahagikan',
|
||||
handlePartition: 'Bahagikan Sekarang',
|
||||
filesystem: 'Sistem Fail',
|
||||
unmounted: 'Tidak Dikaitkan',
|
||||
cannotOperate: 'Tidak Boleh Beroperasi',
|
||||
systemDiskHelper: 'Petunjuk: Cakera semasa adalah cakera sistem, tidak boleh dioperasikan.',
|
||||
autoMount: 'Pemasangan Automatik',
|
||||
model: 'Model Peranti',
|
||||
diskType: 'Jenis Cakera',
|
||||
serial: 'Nombor Siri',
|
||||
},
|
||||
xpack: {
|
||||
expiresTrialAlert:
|
||||
'Peringatan mesra: Percubaan Pro anda akan tamat dalam {0} hari, dan semua ciri Pro tidak lagi dapat diakses. Sila perbaharui atau naik taraf ke versi penuh tepat pada masanya.',
|
||||
|
|
|
|||
|
|
@ -2889,6 +2889,27 @@ const message = {
|
|||
autoStartHelper: 'Se o serviço deve ser iniciado automaticamente após o Supervisor iniciar',
|
||||
},
|
||||
},
|
||||
disk: {
|
||||
management: 'Gerenciamento de Disco',
|
||||
partition: 'Partição',
|
||||
unmount: 'Desmontar',
|
||||
unmountHelper: 'Deseja desmontar a partição {0}?',
|
||||
mount: 'Montar',
|
||||
partitionAlert:
|
||||
'O particionamento de disco requer formatação do disco, e os dados existentes serão excluídos. Salve ou tire snapshots dos dados com antecedência.',
|
||||
mountPoint: 'Diretório de Montagem',
|
||||
systemDisk: 'Disco do Sistema',
|
||||
unpartitionedDisk: 'Disco Não Particionado',
|
||||
handlePartition: 'Particionar Agora',
|
||||
filesystem: 'Sistema de Arquivos',
|
||||
unmounted: 'Desmontado',
|
||||
cannotOperate: 'Não Pode Operar',
|
||||
systemDiskHelper: 'Dica: O disco atual é o disco do sistema, não pode ser operado.',
|
||||
autoMount: 'Montagem Automática',
|
||||
model: 'Modelo do Dispositivo',
|
||||
diskType: 'Tipo de Disco',
|
||||
serial: 'Número de Série',
|
||||
},
|
||||
xpack: {
|
||||
expiresTrialAlert:
|
||||
'Lembrete: Sua versão de avaliação profissional expirará em {0} dias. Após isso, todas as funcionalidades da versão profissional não estarão mais disponíveis. Por favor, renove ou faça upgrade para a versão oficial a tempo.',
|
||||
|
|
|
|||
|
|
@ -2881,6 +2881,27 @@ const message = {
|
|||
autoStartHelper: 'Автоматически запускать сервис после запуска Supervisor',
|
||||
},
|
||||
},
|
||||
disk: {
|
||||
management: 'Управление дисками',
|
||||
partition: 'Раздел',
|
||||
unmount: 'Отмонтировать',
|
||||
unmountHelper: 'Вы хотите отмонтировать раздел {0}?',
|
||||
mount: 'Подключить',
|
||||
partitionAlert:
|
||||
'Разделение диска требует форматирования диска, и существующие данные будут удалены. Пожалуйста, сохраните или сделайте снимки данных заранее.',
|
||||
mountPoint: 'Точка монтирования',
|
||||
systemDisk: 'Системный диск',
|
||||
unpartitionedDisk: 'Неразделенный диск',
|
||||
handlePartition: 'Разделить сейчас',
|
||||
filesystem: 'Файловая система',
|
||||
unmounted: 'Отмонтирован',
|
||||
cannotOperate: 'Невозможно выполнить операцию',
|
||||
systemDiskHelper: 'Подсказка: Текущий диск является системным диском, операции невозможны.',
|
||||
autoMount: 'Автоматическое монтирование',
|
||||
model: 'Модель устройства',
|
||||
diskType: 'Тип диска',
|
||||
serial: 'Серийный номер',
|
||||
},
|
||||
xpack: {
|
||||
expiresTrialAlert:
|
||||
'Дружеское напоминание: ваша пробная версия Pro истечет через {0} дней, и все функции Pro станут недоступны. Пожалуйста, своевременно продлите или обновите до полной версии.',
|
||||
|
|
|
|||
|
|
@ -2964,6 +2964,27 @@ const message = {
|
|||
autoStartHelper: 'Supervisor başlatıldıktan sonra servis otomatik olarak başlatılsın mı?',
|
||||
},
|
||||
},
|
||||
disk: {
|
||||
management: 'Disk Yönetimi',
|
||||
partition: 'Bölüm',
|
||||
unmount: 'Bağını Kaldır',
|
||||
unmountHelper: "Bölüm {0}'ın bağını kaldırmak istiyor musunuz?",
|
||||
mount: 'Bağla',
|
||||
partitionAlert:
|
||||
'Disk bölümleme, diske biçimlendirme gerektirir ve mevcut veriler silinir. Lütfen verilerinizi önceden kaydedin veya anlık görüntü alın.',
|
||||
mountPoint: 'Bağlama Noktası',
|
||||
systemDisk: 'Sistem Diski',
|
||||
unpartitionedDisk: 'Bölümlendirilmemiş Disk',
|
||||
handlePartition: 'Şimdi Bölümle',
|
||||
filesystem: 'Dosya Sistemi',
|
||||
unmounted: 'Bağlı Değil',
|
||||
cannotOperate: 'Operasyon Yapılamıyor',
|
||||
systemDiskHelper: 'İpucu: Mevcut disk sistem diskidir, işlem yapılamaz.',
|
||||
autoMount: 'Otomatik Bağlama',
|
||||
model: 'Cihaz Modeli',
|
||||
diskType: 'Disk Türü',
|
||||
serial: 'Seri Numarası',
|
||||
},
|
||||
xpack: {
|
||||
expiresTrialAlert:
|
||||
'Nazik hatırlatma: Pro deneme sürümünüz {0} gün içinde sona erecek ve tüm Pro özellikleri kullanılamaz hale gelecektir. Lütfen zamanında yenileyin veya tam sürüme yükseltin.',
|
||||
|
|
|
|||
|
|
@ -2727,6 +2727,26 @@ const message = {
|
|||
autoStartHelper: 'Supervisor 啟動後是否自動啟動服務',
|
||||
},
|
||||
},
|
||||
disk: {
|
||||
management: '磁盤管理',
|
||||
partition: '分區',
|
||||
unmount: '取消掛載',
|
||||
unmountHelper: '是否取消掛載分區 {0}?',
|
||||
mount: '掛載',
|
||||
partitionAlert: '進行磁盤分區時需要格式化磁盤,原有數據將被刪除,請提前保存或快照數據',
|
||||
mountPoint: '掛載目錄',
|
||||
systemDisk: '系統磁盤',
|
||||
unpartitionedDisk: '未分區磁盤',
|
||||
handlePartition: '立即分區',
|
||||
filesystem: '文件系統',
|
||||
unmounted: '未掛載',
|
||||
cannotOperate: '無法操作',
|
||||
systemDiskHelper: '提示:當前磁盤為系統盤,無法進行操作',
|
||||
autoMount: '自動掛載',
|
||||
model: '設備型號',
|
||||
diskType: '磁盤類型',
|
||||
serial: '序列號',
|
||||
},
|
||||
xpack: {
|
||||
expiresTrialAlert:
|
||||
'溫馨提醒:您的專業版試用將在 {0} 天後到期,屆時所有專業版功能將無法繼續使用,請及時續費或升級到正式版本。',
|
||||
|
|
|
|||
|
|
@ -370,6 +370,7 @@ const message = {
|
|||
tamper: '防篡改',
|
||||
app: '应用',
|
||||
msgCenter: '任务中心',
|
||||
disk: '磁盘管理',
|
||||
},
|
||||
home: {
|
||||
recommend: '推荐',
|
||||
|
|
@ -2718,6 +2719,26 @@ const message = {
|
|||
autoStartHelper: 'Supervisor 启动后是否自动启动服务',
|
||||
},
|
||||
},
|
||||
disk: {
|
||||
management: '磁盘管理',
|
||||
partition: '分区',
|
||||
unmount: '取消挂载',
|
||||
unmountHelper: '是否取消挂载分区 {0}?',
|
||||
mount: '挂载',
|
||||
partitionAlert: '进行磁盘分区时需要格式化磁盘,原有数据将被删除,请提前保存或快照数据',
|
||||
mountPoint: '挂载目录',
|
||||
systemDisk: '系统磁盘',
|
||||
unpartitionedDisk: '未分区磁盘',
|
||||
handlePartition: '立即分区',
|
||||
filesystem: '文件系统',
|
||||
unmounted: '未挂载',
|
||||
cannotOperate: '无法操作',
|
||||
systemDiskHelper: '提示:当前磁盘为系统盘,无法进行操作',
|
||||
autoMount: '自动挂载',
|
||||
model: '设备型号',
|
||||
diskType: '磁盘类型',
|
||||
serial: '序列号',
|
||||
},
|
||||
xpack: {
|
||||
expiresAlert: '温馨提醒:专业版试用将于 [{0}] 天后到期,届时将停止使用所有专业版功能。',
|
||||
name: '专业版',
|
||||
|
|
|
|||
|
|
@ -137,6 +137,16 @@ const hostRouter = {
|
|||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/hosts/disk',
|
||||
name: 'Disk',
|
||||
props: true,
|
||||
component: () => import('@/views/host/disk-management/disk/index.vue'),
|
||||
meta: {
|
||||
title: 'menu.disk',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
|||
133
frontend/src/views/host/disk-management/components/disk-card.vue
Normal file
133
frontend/src/views/host/disk-management/components/disk-card.vue
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<template>
|
||||
<el-card class="shadow-sm">
|
||||
<div class="border-b pb-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div>
|
||||
<h3 class="text-lg">
|
||||
{{ $t('home.disk') }}{{ $t('commons.table.name') }}: {{ diskInfo.device }}
|
||||
<el-tag size="small" type="warning" v-if="scope === 'system'">
|
||||
{{ $t('disk.systemDisk') }}
|
||||
</el-tag>
|
||||
<el-tag size="small" type="warning" v-if="scope === 'unpartitioned'">
|
||||
{{ $t('disk.unpartitionedDisk') }}
|
||||
</el-tag>
|
||||
</h3>
|
||||
<div class="flex items-center space-x-6 text-sm">
|
||||
<el-text type="info">{{ $t('container.size') }}: {{ diskInfo.size }}</el-text>
|
||||
<el-text type="info">
|
||||
{{ $t('disk.partition') }}:
|
||||
<span v-if="diskInfo.partitions">
|
||||
{{ diskInfo.partitions?.length }}
|
||||
</span>
|
||||
<span v-else>0</span>
|
||||
</el-text>
|
||||
<el-text type="info" v-if="diskInfo.diskType" class="flex items-center">
|
||||
{{ $t('disk.diskType') }}:
|
||||
<el-tag class="ml-2" size="small" type="info">{{ diskInfo.diskType }}</el-tag>
|
||||
</el-text>
|
||||
<el-text type="info" v-if="diskInfo.model" class="flex items-center">
|
||||
{{ $t('disk.model') }}:
|
||||
<span class="ml-2">{{ diskInfo.model }}</span>
|
||||
</el-text>
|
||||
<el-text type="info" v-if="diskInfo.serial" class="flex items-center">
|
||||
{{ $t('disk.model') }}:
|
||||
<span class="ml-2">{{ diskInfo.serial }}</span>
|
||||
</el-text>
|
||||
<div v-if="scope == 'unpartitioned'">
|
||||
<el-button type="primary" size="small" @click="handlePartition(diskInfo)">
|
||||
{{ $t('disk.handlePartition') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="diskInfo.partitions && diskInfo.partitions.length > 0">
|
||||
<el-table :data="diskInfo.partitions" class="w-full">
|
||||
<el-table-column prop="device" :label="$t('disk.partition') + $t('commons.table.name')" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<span class="font-medium">{{ row.device.split('/').pop() }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="size" :label="$t('container.size')" min-width="40" />
|
||||
<el-table-column prop="used" :label="$t('home.used')" min-width="40" />
|
||||
<el-table-column prop="avail" :label="$t('home.available')" min-width="40" />
|
||||
<el-table-column prop="usePercent" :label="$t('home.percent')" min-width="60">
|
||||
<template #default="{ row }">
|
||||
<el-progress
|
||||
:percentage="row.usePercent"
|
||||
:status="row.usePercent >= 90 ? 'exception' : 'success'"
|
||||
:text-inside="true"
|
||||
:stroke-width="14"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mountPoint" :label="$t('disk.mountPoint')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.mountPoint != ''">
|
||||
{{ row.mountPoint }}
|
||||
</span>
|
||||
<el-tag v-else size="small" type="warning">{{ $t('disk.unmounted') }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="filesystem" :label="$t('disk.filesystem')" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" type="info" v-if="row.filesystem != ''">{{ row.filesystem }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.operate')" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-text type="info" v-if="scope === 'system'">{{ $t('disk.cannotOperate') }}</el-text>
|
||||
<el-button type="primary" link v-else-if="row.mountPoint != ''" @click="unmount(row)">
|
||||
{{ $t('disk.unmount') }}
|
||||
</el-button>
|
||||
<el-button type="primary" link v-else @click="mount(row)">{{ $t('disk.mount') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-text v-if="scope === 'system'">{{ $t('disk.systemDiskHelper') }}</el-text>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Host } from '@/api/interface/host';
|
||||
import i18n from '@/lang';
|
||||
import { unmountDisk } from '@/api/modules/host';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const emit = defineEmits(['partition', 'search', 'mount']);
|
||||
|
||||
defineProps({
|
||||
diskInfo: {
|
||||
type: Object as () => Host.DiskInfo,
|
||||
required: true,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const handlePartition = (diskInfo: Host.DiskInfo) => {
|
||||
emit('partition', diskInfo);
|
||||
};
|
||||
|
||||
const mount = (diskInfo: Host.DiskInfo) => {
|
||||
emit('mount', diskInfo);
|
||||
};
|
||||
|
||||
const unmount = (diskInfo: Host.DiskInfo) => {
|
||||
ElMessageBox.confirm(i18n.global.t('disk.unmountHelper', [diskInfo.device]), i18n.global.t('disk.unmount'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
unmountDisk({
|
||||
mountPoint: diskInfo.mountPoint,
|
||||
}).then(() => {
|
||||
MsgSuccess(i18n.global.t('disk.unmount') + i18n.global.t('commons.status.success'));
|
||||
emit('search');
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
59
frontend/src/views/host/disk-management/disk/index.vue
Normal file
59
frontend/src/views/host/disk-management/disk/index.vue
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div>
|
||||
<DiskRouter />
|
||||
<MainDiv class="mt-2" :height-diff="140" v-loading="loading">
|
||||
<div v-if="diskInfo?.systemDisk">
|
||||
<DiskCard class="mt-2" :diskInfo="diskInfo.systemDisk" scope="system" />
|
||||
</div>
|
||||
<div v-if="diskInfo?.unpartitionedDisks">
|
||||
<DiskCard
|
||||
class="mt-2"
|
||||
v-for="(disk, index) in diskInfo.unpartitionedDisks"
|
||||
:key="index"
|
||||
:diskInfo="disk"
|
||||
scope="unpartitioned"
|
||||
@partition="(diskInfo) => partitionRef.acceptParams(diskInfo, 'partition')"
|
||||
@search="() => getDisk()"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="diskInfo?.disks">
|
||||
<DiskCard
|
||||
class="mt-2"
|
||||
v-for="(disk, index) in diskInfo.disks"
|
||||
:key="index"
|
||||
:diskInfo="disk"
|
||||
@mount="(diskInfo) => partitionRef.acceptParams(diskInfo, 'mount')"
|
||||
scope="normal"
|
||||
@search="() => getDisk()"
|
||||
/>
|
||||
</div>
|
||||
</MainDiv>
|
||||
<Partition ref="partitionRef" @search="getDisk" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { listDisks } from '@/api/modules/host';
|
||||
import DiskRouter from '@/views/host/disk-management/index.vue';
|
||||
import DiskCard from '@/views/host/disk-management/components/disk-card.vue';
|
||||
import Partition from '@/views/host/disk-management/partition/index.vue';
|
||||
|
||||
const loading = ref(false);
|
||||
const partitionRef = ref();
|
||||
const diskInfo = ref<Host.CompleteDiskInfo>();
|
||||
|
||||
const getDisk = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await listDisks();
|
||||
diskInfo.value = res.data;
|
||||
} catch (error) {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDisk();
|
||||
});
|
||||
</script>
|
||||
19
frontend/src/views/host/disk-management/index.vue
Normal file
19
frontend/src/views/host/disk-management/index.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<div>
|
||||
<RouterButton :buttons="buttons" />
|
||||
<LayoutContent>
|
||||
<router-view></router-view>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import i18n from '@/lang';
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('menu.disk'),
|
||||
path: '/hosts/disk',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
118
frontend/src/views/host/disk-management/partition/index.vue
Normal file
118
frontend/src/views/host/disk-management/partition/index.vue
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<template>
|
||||
<DrawerPro
|
||||
v-model="open"
|
||||
:header="$t('disk.' + operate)"
|
||||
:resource="form.device"
|
||||
@close="handleClose"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-alert
|
||||
v-if="operate == 'partition' || (operate == 'mount' && filesystem == '')"
|
||||
:title="$t('disk.partitionAlert')"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
/>
|
||||
<el-form
|
||||
@submit.prevent
|
||||
ref="partitionRef"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
:model="form"
|
||||
class="mt-2"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item :label="$t('disk.filesystem')" prop="filesystem">
|
||||
<el-radio-group v-model="form.filesystem" :disabled="operate == 'mount' && filesystem != ''">
|
||||
<el-radio-button label="ext4" value="ext4" />
|
||||
<el-radio-button label="xfs" value="xfs" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('disk.autoMount')" prop="autoMount">
|
||||
<el-switch v-model="form.autoMount" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('disk.mountPoint')" prop="mountPoint">
|
||||
<el-input v-model="form.mountPoint">
|
||||
<template #prepend>
|
||||
<el-button icon="Folder" @click="fileRef.acceptParams({ dir: true })" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<FileList ref="fileRef" @choose="loadBuildDir" />
|
||||
</DrawerPro>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { mountDisk, partitionDisk } from '@/api/modules/host';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const rules = ref<any>({
|
||||
filesystem: [Rules.requiredInput],
|
||||
autoMount: [Rules.requiredInput],
|
||||
mountPoint: [Rules.requiredInput],
|
||||
});
|
||||
const form = reactive({
|
||||
device: '',
|
||||
filesystem: 'ext4',
|
||||
autoMount: true,
|
||||
mountPoint: '',
|
||||
label: '',
|
||||
});
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const operate = ref('mount');
|
||||
const fileRef = ref();
|
||||
const emit = defineEmits(['search']);
|
||||
const filesystem = ref('');
|
||||
|
||||
const loadBuildDir = async (path: string) => {
|
||||
form.mountPoint = path;
|
||||
};
|
||||
|
||||
const acceptParams = (diskInfo: Host.DiskInfo, operateType: string) => {
|
||||
operate.value = operateType;
|
||||
form.device = diskInfo.device;
|
||||
form.mountPoint = '';
|
||||
if (operateType == 'mount' && diskInfo.filesystem) {
|
||||
filesystem.value = diskInfo.filesystem;
|
||||
form.filesystem = diskInfo.filesystem;
|
||||
}
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
if (operate.value == 'mount') {
|
||||
await mountDisk(form);
|
||||
MsgSuccess(i18n.global.t('disk.mount') + i18n.global.t('commons.status.success'));
|
||||
handleClose();
|
||||
} else {
|
||||
await partitionDisk(form);
|
||||
MsgSuccess(i18n.global.t('disk.partition') + i18n.global.t('commons.status.success'));
|
||||
handleClose();
|
||||
}
|
||||
} catch (error) {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
emit('search');
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
Loading…
Add table
Reference in a new issue