mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 08:00:53 +00:00
feat: Disk Management Compatible with Lower Version Operating Systems (#10298)
This commit is contained in:
+9
-419
@@ -1,17 +1,12 @@
|
||||
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"
|
||||
|
||||
@@ -33,127 +28,24 @@ func NewIDiskService() IDiskService {
|
||||
}
|
||||
|
||||
func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error) {
|
||||
disksWithPartitions, unpartitionedDisks, err := getAllDisksInfo()
|
||||
output, err := cmd.RunDefaultWithStdoutBashC("lsblk -P -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
|
||||
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)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to execute lsblk command: %v", err)
|
||||
}
|
||||
|
||||
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")
|
||||
diskInfos, err := parseLsblkOutput(output)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, fmt.Errorf("failed to parse lsblk output: %v", 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
|
||||
result := organizeDiskInfo(diskInfos)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, error) {
|
||||
if !strings.HasPrefix("/dev", req.Device) {
|
||||
req.Device = "/dev/" + req.Device
|
||||
}
|
||||
if !deviceExists(req.Device) {
|
||||
return "", buserr.WithName("DeviceNotFound", req.Device)
|
||||
}
|
||||
@@ -250,305 +142,3 @@ func (s *DiskService) UnmountDisk(req request.DiskUnmountRequest) error {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func organizeDiskInfo(diskInfos []response.DiskBasicInfo) response.CompleteDiskInfo {
|
||||
var result response.CompleteDiskInfo
|
||||
var systemDisk *response.DiskInfo
|
||||
diskMap := make(map[string]*response.DiskInfo)
|
||||
partitions := make(map[string][]response.DiskBasicInfo)
|
||||
|
||||
for _, info := range diskInfos {
|
||||
isPartition := isPartitionDevice(info.Device)
|
||||
|
||||
if isPartition {
|
||||
parentDevice := getParentDevice(info.Device)
|
||||
partitions[parentDevice] = append(partitions[parentDevice], info)
|
||||
} else {
|
||||
disk := &response.DiskInfo{
|
||||
DiskBasicInfo: info,
|
||||
Partitions: []response.DiskBasicInfo{},
|
||||
}
|
||||
diskMap[info.Device] = disk
|
||||
}
|
||||
}
|
||||
|
||||
for parentDevice, partList := range partitions {
|
||||
if disk, exists := diskMap[parentDevice]; exists {
|
||||
for index, part := range partList {
|
||||
part.Device = fmt.Sprintf("/dev/%s", part.Device)
|
||||
if part.IsSystem {
|
||||
disk.IsSystem = true
|
||||
}
|
||||
partList[index] = part
|
||||
}
|
||||
disk.Partitions = partList
|
||||
}
|
||||
}
|
||||
|
||||
var totalCapacity int64
|
||||
for _, disk := range diskMap {
|
||||
capacity := parseSizeToBytes(disk.Size)
|
||||
totalCapacity += capacity
|
||||
|
||||
if disk.IsSystem {
|
||||
systemDisk = disk
|
||||
} else if len(disk.Partitions) == 0 {
|
||||
result.UnpartitionedDisks = append(result.UnpartitionedDisks, disk.DiskBasicInfo)
|
||||
} else {
|
||||
result.Disks = append(result.Disks, *disk)
|
||||
}
|
||||
}
|
||||
|
||||
result.SystemDisk = systemDisk
|
||||
result.TotalDisks = len(diskMap)
|
||||
result.TotalCapacity = totalCapacity
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func parseLsblkOutput(output string) ([]response.DiskBasicInfo, error) {
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
if len(lines) == 0 {
|
||||
return nil, fmt.Errorf("invalid lsblk output")
|
||||
}
|
||||
|
||||
var diskInfos []response.DiskBasicInfo
|
||||
lvmMap := make(map[string]response.DiskBasicInfo)
|
||||
var pendingDevices []map[string]string
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := parseKeyValuePairs(line)
|
||||
|
||||
name, ok := fields["NAME"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "loop") ||
|
||||
strings.HasPrefix(name, "dm-") {
|
||||
continue
|
||||
}
|
||||
|
||||
diskType := fields["TYPE"]
|
||||
mountPoint := fields["MOUNTPOINT"]
|
||||
fsType := fields["FSTYPE"]
|
||||
size := fields["SIZE"]
|
||||
|
||||
if diskType == "lvm" {
|
||||
total, used, avail, usePercent, _ := getDiskUsageInfo("/dev/mapper/" + name)
|
||||
if total != "" {
|
||||
size = total
|
||||
}
|
||||
|
||||
lvmInfo := response.DiskBasicInfo{
|
||||
Device: name,
|
||||
Size: size,
|
||||
Model: fields["MODEL"],
|
||||
DiskType: "LVM",
|
||||
IsRemovable: false,
|
||||
IsSystem: isSystemDisk(mountPoint),
|
||||
Filesystem: fsType,
|
||||
Used: used,
|
||||
Avail: avail,
|
||||
UsePercent: usePercent,
|
||||
MountPoint: mountPoint,
|
||||
IsMounted: mountPoint != "" && mountPoint != "-",
|
||||
Serial: fields["SERIAL"],
|
||||
}
|
||||
lvmMap[name] = lvmInfo
|
||||
} else if diskType == "disk" || diskType == "part" {
|
||||
pendingDevices = append(pendingDevices, fields)
|
||||
}
|
||||
}
|
||||
|
||||
for _, fields := range pendingDevices {
|
||||
name := fields["NAME"]
|
||||
size := fields["SIZE"]
|
||||
mountPoint := fields["MOUNTPOINT"]
|
||||
fsType := fields["FSTYPE"]
|
||||
model := fields["MODEL"]
|
||||
serial := fields["SERIAL"]
|
||||
tran := fields["TRAN"]
|
||||
rota := fields["ROTA"]
|
||||
|
||||
totalSize, used, avail, usePercent, _ := getDiskUsageInfo("/dev/" + name)
|
||||
if totalSize != "" {
|
||||
size = totalSize
|
||||
}
|
||||
actualMountPoint := mountPoint
|
||||
actualFsType := fsType
|
||||
actualUsed := used
|
||||
actualAvail := avail
|
||||
actualUsePercent := usePercent
|
||||
isMounted := mountPoint != "" && mountPoint != "-"
|
||||
isSystemPartition := isSystemDisk(mountPoint)
|
||||
|
||||
if fsType == "LVM2_member" {
|
||||
for _, lvmInfo := range lvmMap {
|
||||
if lvmInfo.IsMounted {
|
||||
actualMountPoint = lvmInfo.MountPoint
|
||||
actualFsType = lvmInfo.Filesystem
|
||||
actualUsed = lvmInfo.Used
|
||||
size = lvmInfo.Size
|
||||
actualAvail = lvmInfo.Avail
|
||||
actualUsePercent = lvmInfo.UsePercent
|
||||
isMounted = true
|
||||
isSystemPartition = lvmInfo.IsSystem
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diskInfo := response.DiskBasicInfo{
|
||||
Device: name,
|
||||
Size: size,
|
||||
Model: model,
|
||||
DiskType: getDiskType(rota),
|
||||
IsRemovable: tran == "usb",
|
||||
IsSystem: isSystemPartition,
|
||||
Filesystem: actualFsType,
|
||||
Used: actualUsed,
|
||||
Avail: actualAvail,
|
||||
UsePercent: actualUsePercent,
|
||||
MountPoint: actualMountPoint,
|
||||
IsMounted: isMounted,
|
||||
Serial: serial,
|
||||
}
|
||||
|
||||
diskInfos = append(diskInfos, diskInfo)
|
||||
}
|
||||
|
||||
return diskInfos, nil
|
||||
}
|
||||
|
||||
func getDiskType(rota string) string {
|
||||
if rota == "0" {
|
||||
return "SSD"
|
||||
} else if rota == "1" {
|
||||
return "HDD"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
var kvRe = regexp.MustCompile(`([A-Za-z0-9_]+)=("([^"\\]|\\.)*"|[^ \t]+)`)
|
||||
|
||||
func parseKeyValuePairs(line string) map[string]string {
|
||||
fields := make(map[string]string)
|
||||
|
||||
matches := kvRe.FindAllStringSubmatch(line, -1)
|
||||
for _, m := range matches {
|
||||
key := m[1]
|
||||
raw := m[2]
|
||||
|
||||
val := raw
|
||||
if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' {
|
||||
if unq, err := strconv.Unquote(val); err == nil {
|
||||
val = unq
|
||||
} else {
|
||||
val = val[1 : len(val)-1]
|
||||
}
|
||||
}
|
||||
fields[key] = val
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func isPartitionDevice(device string) bool {
|
||||
if strings.Contains(device, "nvme") {
|
||||
return strings.Contains(device, "p") &&
|
||||
strings.ContainsAny(device[strings.LastIndex(device, "p")+1:], "0123456789")
|
||||
} else if strings.HasPrefix(device, "sd") || strings.HasPrefix(device, "hd") {
|
||||
return len(device) > 3 &&
|
||||
strings.ContainsAny(device[len(device)-1:], "0123456789")
|
||||
} else if strings.HasPrefix(device, "vd") {
|
||||
return len(device) > 3 &&
|
||||
strings.ContainsAny(device[len(device)-1:], "0123456789")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getParentDevice(device string) string {
|
||||
if strings.Contains(device, "nvme") {
|
||||
if idx := strings.LastIndex(device, "p"); idx != -1 {
|
||||
return device[:idx]
|
||||
}
|
||||
} else {
|
||||
return strings.TrimRight(device, "0123456789")
|
||||
}
|
||||
|
||||
return device
|
||||
}
|
||||
|
||||
func getDiskUsageInfo(device string) (size, used, avail string, usePercent int, err error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("df -h %s | tail -1", device))
|
||||
if err != nil {
|
||||
return "", "", "", 0, nil
|
||||
}
|
||||
|
||||
fields := strings.Fields(output)
|
||||
if len(fields) >= 5 {
|
||||
size = fields[1]
|
||||
used = fields[2]
|
||||
avail = fields[3]
|
||||
usePercentStr := strings.TrimSuffix(fields[4], "%")
|
||||
usePercent, _ = strconv.Atoi(usePercentStr)
|
||||
}
|
||||
|
||||
return size, used, avail, usePercent, 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 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 parseSizeToBytes(sizeStr string) int64 {
|
||||
if sizeStr == "" || sizeStr == "-" {
|
||||
return 0
|
||||
}
|
||||
|
||||
sizeStr = strings.TrimSpace(sizeStr)
|
||||
if len(sizeStr) < 2 {
|
||||
return 0
|
||||
}
|
||||
|
||||
unit := strings.ToUpper(sizeStr[len(sizeStr)-1:])
|
||||
valueStr := sizeStr[:len(sizeStr)-1]
|
||||
|
||||
value, err := strconv.ParseFloat(valueStr, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
switch unit {
|
||||
case "K":
|
||||
return int64(value * 1024)
|
||||
case "M":
|
||||
return int64(value * 1024 * 1024)
|
||||
case "G":
|
||||
return int64(value * 1024 * 1024 * 1024)
|
||||
case "T":
|
||||
return int64(value * 1024 * 1024 * 1024 * 1024)
|
||||
default:
|
||||
val, _ := strconv.ParseInt(sizeStr, 10, 64)
|
||||
return val
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,7 @@ var WebUrlMap = map[string]struct{}{
|
||||
"/hosts/ssh/ssh": {},
|
||||
"/hosts/ssh/log": {},
|
||||
"/hosts/ssh/session": {},
|
||||
"/hosts/disk": {},
|
||||
|
||||
"/terminal": {},
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<span class="ml-2">{{ diskInfo.model }}</span>
|
||||
</el-text>
|
||||
<el-text type="info" v-if="diskInfo.serial" class="flex items-center">
|
||||
{{ $t('disk.model') }}:
|
||||
{{ $t('disk.serial') }}:
|
||||
<span class="ml-2">{{ diskInfo.serial }}</span>
|
||||
</el-text>
|
||||
<div v-if="scope == 'unpartitioned'">
|
||||
@@ -118,10 +118,14 @@ const mount = (diskInfo: Host.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 () => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('disk.unmountHelper', [diskInfo.device.split('/').pop()]),
|
||||
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(() => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<DrawerPro
|
||||
v-model="open"
|
||||
:header="$t('disk.' + operate)"
|
||||
:resource="form.device"
|
||||
:resource="form.device.split('/').pop()"
|
||||
@close="handleClose"
|
||||
v-loading="loading"
|
||||
>
|
||||
@@ -109,6 +109,7 @@ const submit = async () => {
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user