mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-13 17:24:44 +08:00
feat: 工具箱增加 Swap 管理 (#3047)
This commit is contained in:
parent
038879819d
commit
b5592327dd
@ -73,7 +73,7 @@ func (b *BaseApi) LoadDeviceConf(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/device/update/byconf [post]
|
||||
func (b *BaseApi) UpdateDevicByFile(c *gin.Context) {
|
||||
func (b *BaseApi) UpdateDeviceByFile(c *gin.Context) {
|
||||
var req dto.UpdateByNameAndFile
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
@ -138,7 +138,7 @@ func (b *BaseApi) UpdateDeviceHost(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/device/update/passwd [post]
|
||||
func (b *BaseApi) UpdateDevicPasswd(c *gin.Context) {
|
||||
func (b *BaseApi) UpdateDevicePasswd(c *gin.Context) {
|
||||
var req dto.ChangePasswd
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
@ -159,6 +159,28 @@ func (b *BaseApi) UpdateDevicPasswd(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Device
|
||||
// @Summary Update device swap
|
||||
// @Description 修改系统 Swap
|
||||
// @Accept json
|
||||
// @Param request body dto.SwapHelper true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/device/update/swap [post]
|
||||
// @x-panel-log {"bodyKeys":["operate","path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 主机 swap [path]","formatEN":"[operate] device swap [path]"}
|
||||
func (b *BaseApi) UpdateDeviceSwap(c *gin.Context) {
|
||||
var req dto.SwapHelper
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := deviceService.UpdateSwap(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Device
|
||||
// @Summary Check device DNS conf
|
||||
// @Description 检查系统 DNS 配置可用性
|
||||
|
@ -8,6 +8,12 @@ type DeviceBaseInfo struct {
|
||||
LocalTime string `json:"localTime"`
|
||||
Ntp string `json:"ntp"`
|
||||
User string `json:"user"`
|
||||
|
||||
SwapMemoryTotal uint64 `json:"swapMemoryTotal"`
|
||||
SwapMemoryAvailable uint64 `json:"swapMemoryAvailable"`
|
||||
SwapMemoryUsed uint64 `json:"swapMemoryUsed"`
|
||||
|
||||
SwapDetails []SwapHelper `json:"swapDetails"`
|
||||
}
|
||||
|
||||
type HostHelper struct {
|
||||
@ -15,6 +21,14 @@ type HostHelper struct {
|
||||
Host string `json:"host"`
|
||||
}
|
||||
|
||||
type SwapHelper struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Size uint64 `json:"size"`
|
||||
Used string `json:"used"`
|
||||
|
||||
IsNew bool `json:"isNew"`
|
||||
}
|
||||
|
||||
type TimeZoneOptions struct {
|
||||
From string `json:"from"`
|
||||
Zones []string `json:"zones"`
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -14,10 +16,12 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
const defaultDNSPath = "/etc/resolv.conf"
|
||||
const defaultHostPath = "/etc/hosts"
|
||||
const defaultFstab = "/etc/fstab"
|
||||
|
||||
type DeviceService struct{}
|
||||
|
||||
@ -26,6 +30,7 @@ type IDeviceService interface {
|
||||
Update(key, value string) error
|
||||
UpdateHosts(req []dto.HostHelper) error
|
||||
UpdatePasswd(req dto.ChangePasswd) error
|
||||
UpdateSwap(req dto.SwapHelper) error
|
||||
UpdateByConf(req dto.UpdateByNameAndFile) error
|
||||
LoadTimeZone() ([]string, error)
|
||||
CheckDNS(key, value string) (bool, error)
|
||||
@ -47,6 +52,14 @@ func (u *DeviceService) LoadBaseInfo() (dto.DeviceBaseInfo, error) {
|
||||
ntp, _ := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
|
||||
baseInfo.Ntp = ntp.Value
|
||||
|
||||
swapInfo, _ := mem.SwapMemory()
|
||||
baseInfo.SwapMemoryTotal = swapInfo.Total
|
||||
baseInfo.SwapMemoryAvailable = swapInfo.Free
|
||||
baseInfo.SwapMemoryUsed = swapInfo.Used
|
||||
if baseInfo.SwapMemoryTotal != 0 {
|
||||
baseInfo.SwapDetails = loadSwap()
|
||||
}
|
||||
|
||||
return baseInfo, nil
|
||||
}
|
||||
|
||||
@ -104,11 +117,20 @@ func (u *DeviceService) Update(key, value string) error {
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
case "LocalTime":
|
||||
if err := settingRepo.Update("NtpSite", value); err != nil {
|
||||
return err
|
||||
case "Ntp", "LocalTime":
|
||||
ntpValue := value
|
||||
if key == "LocalTime" {
|
||||
ntpItem, err := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ntpValue = ntpItem.Value
|
||||
} else {
|
||||
if err := settingRepo.Update("NtpSite", ntpValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ntime, err := ntp.GetRemoteTime(value)
|
||||
ntime, err := ntp.GetRemoteTime(ntpValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -168,6 +190,35 @@ func (u *DeviceService) UpdatePasswd(req dto.ChangePasswd) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *DeviceService) UpdateSwap(req dto.SwapHelper) error {
|
||||
if !req.IsNew {
|
||||
std, err := cmd.Execf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handle swapoff %s failed, err: %s", req.Path, std)
|
||||
}
|
||||
}
|
||||
if req.Size == 0 {
|
||||
if req.Path == path.Join(global.CONF.System.BaseDir, ".1panel_swap") {
|
||||
_ = os.Remove(path.Join(global.CONF.System.BaseDir, ".1panel_swap"))
|
||||
}
|
||||
return operateSwapWithFile(true, req)
|
||||
}
|
||||
std1, err := cmd.Execf("%s dd if=/dev/zero of=%s bs=1024 count=%d", cmd.SudoHandleCmd(), req.Path, req.Size)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handle dd path %s failed, err: %s", req.Path, std1)
|
||||
}
|
||||
std2, err := cmd.Execf("%s mkswap -f %s", cmd.SudoHandleCmd(), req.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handle dd path %s failed, err: %s", req.Path, std2)
|
||||
}
|
||||
std3, err := cmd.Execf("%s swapon %s", cmd.SudoHandleCmd(), req.Path)
|
||||
if err != nil {
|
||||
_, _ = cmd.Execf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path)
|
||||
return fmt.Errorf("handle dd path %s failed, err: %s", req.Path, std3)
|
||||
}
|
||||
return operateSwapWithFile(false, req)
|
||||
}
|
||||
|
||||
func (u *DeviceService) LoadConf(name string) (string, error) {
|
||||
pathItem := ""
|
||||
switch name {
|
||||
@ -291,3 +342,55 @@ func loadUser() string {
|
||||
}
|
||||
return strings.ReplaceAll(std, "\n", "")
|
||||
}
|
||||
|
||||
func loadSwap() []dto.SwapHelper {
|
||||
var data []dto.SwapHelper
|
||||
std, err := cmd.Execf("%s swapon --show --summary", cmd.SudoHandleCmd())
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
lines := strings.Split(std, "\n")
|
||||
for index, line := range lines {
|
||||
if index == 0 {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 5 {
|
||||
continue
|
||||
}
|
||||
sizeItem, _ := strconv.Atoi(parts[2])
|
||||
data = append(data, dto.SwapHelper{Path: parts[0], Size: uint64(sizeItem), Used: parts[3]})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func operateSwapWithFile(delete bool, req dto.SwapHelper) error {
|
||||
conf, err := os.ReadFile(defaultFstab)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read file %s failed, err: %v", defaultFstab, err)
|
||||
}
|
||||
lines := strings.Split(string(conf), "\n")
|
||||
newFile := ""
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) == 6 && parts[0] == req.Path {
|
||||
continue
|
||||
}
|
||||
newFile += line + "\n"
|
||||
}
|
||||
if !delete {
|
||||
newFile += fmt.Sprintf("%s swap swap defaults 0 0\n", req.Path)
|
||||
}
|
||||
file, err := os.OpenFile(defaultFstab, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(newFile)
|
||||
write.Flush()
|
||||
return nil
|
||||
}
|
||||
|
@ -20,8 +20,9 @@ func (s *ToolboxRouter) InitToolboxRouter(Router *gin.RouterGroup) {
|
||||
toolboxRouter.GET("/device/zone/options", baseApi.LoadTimeOption)
|
||||
toolboxRouter.POST("/device/update/conf", baseApi.UpdateDeviceConf)
|
||||
toolboxRouter.POST("/device/update/host", baseApi.UpdateDeviceHost)
|
||||
toolboxRouter.POST("/device/update/passwd", baseApi.UpdateDevicPasswd)
|
||||
toolboxRouter.POST("/device/update/byconf", baseApi.UpdateDevicByFile)
|
||||
toolboxRouter.POST("/device/update/passwd", baseApi.UpdateDevicePasswd)
|
||||
toolboxRouter.POST("/device/update/swap", baseApi.UpdateDeviceSwap)
|
||||
toolboxRouter.POST("/device/update/byconf", baseApi.UpdateDeviceByFile)
|
||||
toolboxRouter.POST("/device/check/dns", baseApi.CheckDNS)
|
||||
toolboxRouter.POST("/device/conf", baseApi.LoadDeviceConf)
|
||||
|
||||
|
@ -10331,6 +10331,49 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/device/update/swap": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "修改系统 Swap",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Device"
|
||||
],
|
||||
"summary": "Update device swap",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SwapHelper"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"operate",
|
||||
"path"
|
||||
],
|
||||
"formatEN": "[operate] device swap [path]",
|
||||
"formatZH": "[operate] 主机 swap [path]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/device/zone/options": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -13748,6 +13791,9 @@ const docTemplate = `{
|
||||
"openStdin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"privileged": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"publishAllPorts": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -14519,6 +14565,21 @@ const docTemplate = `{
|
||||
"ntp": {
|
||||
"type": "string"
|
||||
},
|
||||
"swapDetails": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.SwapHelper"
|
||||
}
|
||||
},
|
||||
"swapMemoryAvailable": {
|
||||
"type": "integer"
|
||||
},
|
||||
"swapMemoryTotal": {
|
||||
"type": "integer"
|
||||
},
|
||||
"swapMemoryUsed": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -16653,6 +16714,32 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SwapHelper": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"operate",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"operate": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"create",
|
||||
"delete",
|
||||
"update"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.TreeChild": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -10324,6 +10324,49 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/device/update/swap": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "修改系统 Swap",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Device"
|
||||
],
|
||||
"summary": "Update device swap",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.SwapHelper"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"operate",
|
||||
"path"
|
||||
],
|
||||
"formatEN": "[operate] device swap [path]",
|
||||
"formatZH": "[operate] 主机 swap [path]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/toolbox/device/zone/options": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -13741,6 +13784,9 @@
|
||||
"openStdin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"privileged": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"publishAllPorts": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -14512,6 +14558,21 @@
|
||||
"ntp": {
|
||||
"type": "string"
|
||||
},
|
||||
"swapDetails": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.SwapHelper"
|
||||
}
|
||||
},
|
||||
"swapMemoryAvailable": {
|
||||
"type": "integer"
|
||||
},
|
||||
"swapMemoryTotal": {
|
||||
"type": "integer"
|
||||
},
|
||||
"swapMemoryUsed": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -16646,6 +16707,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.SwapHelper": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"operate",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"operate": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"create",
|
||||
"delete",
|
||||
"update"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.TreeChild": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -433,6 +433,8 @@ definitions:
|
||||
type: string
|
||||
openStdin:
|
||||
type: boolean
|
||||
privileged:
|
||||
type: boolean
|
||||
publishAllPorts:
|
||||
type: boolean
|
||||
restartPolicy:
|
||||
@ -954,6 +956,16 @@ definitions:
|
||||
type: string
|
||||
ntp:
|
||||
type: string
|
||||
swapDetails:
|
||||
items:
|
||||
$ref: '#/definitions/dto.SwapHelper'
|
||||
type: array
|
||||
swapMemoryAvailable:
|
||||
type: integer
|
||||
swapMemoryTotal:
|
||||
type: integer
|
||||
swapMemoryUsed:
|
||||
type: integer
|
||||
timeZone:
|
||||
type: string
|
||||
user:
|
||||
@ -2395,6 +2407,24 @@ definitions:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
dto.SwapHelper:
|
||||
properties:
|
||||
operate:
|
||||
enum:
|
||||
- create
|
||||
- delete
|
||||
- update
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
size:
|
||||
type: integer
|
||||
used:
|
||||
type: string
|
||||
required:
|
||||
- operate
|
||||
- path
|
||||
type: object
|
||||
dto.TreeChild:
|
||||
properties:
|
||||
id:
|
||||
@ -11107,6 +11137,34 @@ paths:
|
||||
summary: Update device passwd
|
||||
tags:
|
||||
- Device
|
||||
/toolbox/device/update/swap:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 修改系统 Swap
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SwapHelper'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Update device swap
|
||||
tags:
|
||||
- Device
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
- path
|
||||
formatEN: '[operate] device swap [path]'
|
||||
formatZH: '[operate] 主机 swap [path]'
|
||||
paramKeys: []
|
||||
/toolbox/device/zone/options:
|
||||
get:
|
||||
consumes:
|
||||
|
@ -7,6 +7,19 @@ export namespace Toolbox {
|
||||
user: string;
|
||||
timeZone: string;
|
||||
localTime: string;
|
||||
|
||||
swapMemoryTotal: number;
|
||||
swapMemoryAvailable: number;
|
||||
swapMemoryUsed: number;
|
||||
|
||||
swapDetails: Array<SwapHelper>;
|
||||
}
|
||||
export interface SwapHelper {
|
||||
path: string;
|
||||
size: number;
|
||||
used: string;
|
||||
|
||||
isNew: boolean;
|
||||
}
|
||||
export interface HostHelper {
|
||||
ip: string;
|
||||
|
@ -2,6 +2,7 @@ import http from '@/api';
|
||||
import { UpdateByFile } from '../interface';
|
||||
import { Toolbox } from '../interface/toolbox';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
|
||||
// device
|
||||
export const getDeviceBase = () => {
|
||||
@ -19,6 +20,9 @@ export const updateDeviceHost = (param: Array<Toolbox.TimeZoneOptions>) => {
|
||||
export const updateDevicePasswd = (user: string, passwd: string) => {
|
||||
return http.post(`/toolbox/device/update/passwd`, { user: user, passwd: Base64.encode(passwd) });
|
||||
};
|
||||
export const updateDeviceSwap = (params: Toolbox.SwapHelper) => {
|
||||
return http.post(`/toolbox/device/update/swap`, params, TimeoutEnum.T_5M);
|
||||
};
|
||||
export const updateDeviceByConf = (name: string, file: string) => {
|
||||
return http.post(`/toolbox/device/update/byconf`, { name: name, file: file });
|
||||
};
|
||||
|
@ -884,13 +884,30 @@ const message = {
|
||||
emptyTerminal: 'No terminal is currently connected',
|
||||
},
|
||||
toolbox: {
|
||||
swap: {
|
||||
swap: 'Swap Partition',
|
||||
swapHelper1:
|
||||
'The size of the swap should be 1 to 2 times the physical memory, adjustable based on specific requirements;',
|
||||
swapHelper2:
|
||||
'Before creating a swap file, ensure that the system disk has sufficient available space, as the swap file size will occupy the corresponding disk space;',
|
||||
swapHelper3:
|
||||
'Swap can help alleviate memory pressure, but it is only an alternative. Excessive reliance on swap may lead to a decrease in system performance. It is recommended to prioritize increasing memory or optimizing application memory usage;',
|
||||
swapHelper4: 'It is advisable to regularly monitor the usage of swap to ensure normal system operation.',
|
||||
swapDeleteHelper:
|
||||
'This operation will remove the Swap partition {0}. For system security reasons, the corresponding file will not be automatically deleted. If deletion is required, please proceed manually!',
|
||||
saveHelper: 'Please save the current settings first!',
|
||||
saveSwap:
|
||||
'Saving the current configuration will adjust the Swap partition {0} size to {1}. Do you want to continue?',
|
||||
saveSwapHelper: 'The minimum partition size is 40 KB. Please modify and try again!',
|
||||
swapOff: 'The minimum partition size is 40 KB. Setting it to 0 will disable the Swap partition.',
|
||||
},
|
||||
device: {
|
||||
dnsHelper: 'Server Address Domain Resolution',
|
||||
hostsHelper: 'Hostname Resolution',
|
||||
hosts: 'Domain',
|
||||
toolbox: 'Toolbox',
|
||||
hostname: 'Hostname',
|
||||
passwd: 'Host Password',
|
||||
passwd: 'System Password',
|
||||
passwdHelper: 'Input characters cannot include $ and &',
|
||||
timeZone: 'System Time Zone',
|
||||
localTime: 'Server Time',
|
||||
|
@ -845,13 +845,26 @@ const message = {
|
||||
emptyTerminal: '暫無終端連接',
|
||||
},
|
||||
toolbox: {
|
||||
swap: {
|
||||
swap: 'Swap',
|
||||
swapHelper1: 'Swap 的大小應該是物理內存的 1 到 2 倍,可根據具體情況進行調整;',
|
||||
swapHelper2: '在創建 Swap 文件之前,請確保系統硬盤有足夠的可用空間,Swap 文件的大小將佔用相應的磁盤空間;',
|
||||
swapHelper3:
|
||||
'Swap 可以幫助緩解內存壓力,但僅是一個備選項,過多依賴可能導致系統性能下降,建議優先考慮增加內存或者優化應用程序內存使用;',
|
||||
swapHelper4: '建議定期監控 Swap 的使用情況,以確保系統正常運行。',
|
||||
swapDeleteHelper: '此操作將移除 Swap 分區 {0},出於系統安全考慮,不會自動刪除該文件,如需刪除請手動操作!',
|
||||
saveHelper: '請先保存當前設置!',
|
||||
saveSwap: '儲存當前配置將調整 Swap 分區 {0} 大小到 {1},是否繼續?',
|
||||
saveSwapHelper: '分區大小最小值為 40 KB,請修改後重試!',
|
||||
swapOff: '分區大小最小值為 40 KB,設置為 0 則關閉 Swap 分區。',
|
||||
},
|
||||
device: {
|
||||
dnsHelper: '伺服器地址域名解析',
|
||||
hostsHelper: '主機名解析',
|
||||
hosts: '域名',
|
||||
toolbox: '工具箱',
|
||||
hostname: '主機名',
|
||||
passwd: '主機密碼',
|
||||
passwd: '系統密碼',
|
||||
passwdHelper: '輸入的字符不能包含 $ 和 &',
|
||||
timeZone: '系統時區',
|
||||
localTime: '伺服器時間',
|
||||
@ -863,6 +876,7 @@ const message = {
|
||||
ntpALi: '阿里',
|
||||
ntpGoogle: '谷歌',
|
||||
syncSite: 'NTP 伺服器',
|
||||
syncSiteHelper: '該操作將使用 {0} 作為源進行系統時間同步,是否繼續?',
|
||||
hostnameHelper: '主機名修改依賴於 hostnamectl 命令,如未安裝可能導致修改失敗',
|
||||
userHelper: '用戶名依賴於 whoami 命令獲取,如未安裝可能導致獲取失敗。',
|
||||
passwordHelper: '密碼修改依賴於 chpasswd 命令,如未安裝可能導致修改失敗',
|
||||
@ -1090,7 +1104,6 @@ const message = {
|
||||
systemIP: '服務器地址',
|
||||
systemIPWarning: '當前未設置服務器地址,請先在面板設置中設置!',
|
||||
defaultNetwork: '默認網卡',
|
||||
syncSiteHelper: '該操作將使用 {0} 作為源進行系統時間同步,是否繼續?',
|
||||
changePassword: '密碼修改',
|
||||
oldPassword: '原密碼',
|
||||
newPassword: '新密碼',
|
||||
|
@ -846,13 +846,26 @@ const message = {
|
||||
emptyTerminal: '暂无终端连接',
|
||||
},
|
||||
toolbox: {
|
||||
swap: {
|
||||
swap: 'Swap 分区',
|
||||
swapHelper1: 'Swap 的大小应该是物理内存的 1 到 2 倍,可根据具体情况进行调整;',
|
||||
swapHelper2: '在创建 Swap 文件之前,请确保系统硬盘有足够的可用空间,Swap 文件的大小将占用相应的磁盘空间;',
|
||||
swapHelper3:
|
||||
'Swap 可以帮助缓解内存压力,但仅是一个备选项,过多依赖可能导致系统性能下降,建议优先考虑增加内存或者优化应用程序内存使用;',
|
||||
swapHelper4: '建议定期监控 Swap 的使用情况,以确保系统正常运行。',
|
||||
swapDeleteHelper: '此操作将移除 Swap 分区 {0},出于系统安全考虑,不会自动删除该文件,如需删除请手动操作!',
|
||||
saveHelper: '请先保存当前设置!',
|
||||
saveSwap: '保存当前配置将调整 Swap 分区 {0} 大小到 {1},是否继续?',
|
||||
saveSwapHelper: '分区大小最小值为 40 KB,请修改后重试!',
|
||||
swapOff: '分区大小最小值为 40 KB,设置成 0 则关闭 Swap 分区。',
|
||||
},
|
||||
device: {
|
||||
dnsHelper: '服务器地址域名解析',
|
||||
hostsHelper: '主机名解析',
|
||||
hosts: '域名',
|
||||
toolbox: '工具箱',
|
||||
hostname: '主机名',
|
||||
passwd: '主机密码',
|
||||
passwd: '系统密码',
|
||||
passwdHelper: '输入字符不能包含 $ 和 &',
|
||||
timeZone: '系统时区',
|
||||
localTime: '服务器时间',
|
||||
@ -864,6 +877,7 @@ const message = {
|
||||
ntpALi: '阿里',
|
||||
ntpGoogle: '谷歌',
|
||||
syncSite: 'NTP 服务器',
|
||||
syncSiteHelper: '该操作将使用 {0} 作为源进行系统时间同步,是否继续?',
|
||||
hostnameHelper: '主机名修改依赖于 hostnamectl 命令,如未安装可能导致修改失败',
|
||||
userHelper: '用户名依赖于 whoami 命令获取,如未安装可能导致获取失败。',
|
||||
passwordHelper: '密码修改依赖于 chpasswd 命令,如未安装可能导致修改失败',
|
||||
@ -1091,7 +1105,6 @@ const message = {
|
||||
systemIP: '服务器地址',
|
||||
systemIPWarning: '当前未设置服务器地址,请先在面板设置中设置!',
|
||||
defaultNetwork: '默认网卡',
|
||||
syncSiteHelper: '该操作将使用 {0} 作为源进行系统时间同步,是否继续?',
|
||||
changePassword: '密码修改',
|
||||
oldPassword: '原密码',
|
||||
newPassword: '新密码',
|
||||
|
@ -134,10 +134,25 @@ export function loadZero(i: number) {
|
||||
export function computeSize(size: number): string {
|
||||
const num = 1024.0;
|
||||
if (size < num) return size + ' B';
|
||||
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' KB';
|
||||
if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' MB';
|
||||
if (size < Math.pow(num, 4)) return (size / Math.pow(num, 3)).toFixed(2) + ' GB';
|
||||
return (size / Math.pow(num, 4)).toFixed(2) + ' TB';
|
||||
if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' KB';
|
||||
if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' MB';
|
||||
if (size < Math.pow(num, 4)) return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' GB';
|
||||
return formattedNumber((size / Math.pow(num, 4)).toFixed(2)) + ' TB';
|
||||
}
|
||||
|
||||
export function splitSize(size: number): any {
|
||||
const num = 1024.0;
|
||||
if (size < num) return { size: Number(size), unit: 'B' };
|
||||
if (size < Math.pow(num, 2)) return { size: formattedNumber((size / num).toFixed(2)), unit: 'KB' };
|
||||
if (size < Math.pow(num, 3))
|
||||
return { size: formattedNumber((size / Number(Math.pow(num, 2).toFixed(2))).toFixed(2)), unit: 'MB' };
|
||||
if (size < Math.pow(num, 4))
|
||||
return { size: formattedNumber((size / Number(Math.pow(num, 3))).toFixed(2)), unit: 'GB' };
|
||||
return { size: formattedNumber((size / Number(Math.pow(num, 4))).toFixed(2)), unit: 'TB' };
|
||||
}
|
||||
|
||||
export function formattedNumber(num: string) {
|
||||
return num.endsWith('.00') ? Number(num.slice(0, -3)) : Number(num);
|
||||
}
|
||||
|
||||
export function computeSizeFromMB(size: number): string {
|
||||
|
@ -203,13 +203,51 @@ const rules = reactive({
|
||||
name: [Rules.requiredInput],
|
||||
driver: [Rules.requiredSelect],
|
||||
subnet: [{ validator: checkCidr, trigger: 'blur' }],
|
||||
gateway: [Rules.ip],
|
||||
gateway: [{ validator: checkGateway, trigger: 'blur' }],
|
||||
scope: [{ validator: checkCidr, trigger: 'blur' }],
|
||||
subnetV6: [{ validator: checkFixedCidrV6, trigger: 'blur' }],
|
||||
gatewayV6: [Rules.ipV6],
|
||||
gatewayV6: [{ validator: checkGatewayV6, trigger: 'blur' }],
|
||||
scopeV6: [{ validator: checkFixedCidrV6, trigger: 'blur' }],
|
||||
});
|
||||
|
||||
function checkGateway(rule: any, value: any, callback: any) {
|
||||
if (value === '') {
|
||||
callback();
|
||||
}
|
||||
const reg =
|
||||
/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
|
||||
if (!reg.test(value) && value !== '') {
|
||||
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function checkGatewayV6(rule: any, value: any, callback: any) {
|
||||
if (value === '') {
|
||||
callback();
|
||||
} else {
|
||||
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
|
||||
const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
|
||||
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
|
||||
const IPv6AddressRegExp = new RegExp(
|
||||
'^(' +
|
||||
`(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
|
||||
`(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
|
||||
')(%[0-9a-zA-Z-.:]{1,})?$',
|
||||
);
|
||||
if (!IPv6AddressRegExp.test(value) && value !== '') {
|
||||
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
function checkCidr(rule: any, value: any, callback: any) {
|
||||
if (value === '') {
|
||||
callback();
|
||||
|
@ -71,7 +71,7 @@
|
||||
{{ $t('home.free') }}: {{ formatNumber(currentInfo.swapMemoryAvailable / 1024 / 1024) }} MB
|
||||
</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('home.percent') }}: {{ formatNumber(100 - currentInfo.swapMemoryUsedPercent * 100) }}%
|
||||
{{ $t('home.percent') }}: {{ formatNumber(currentInfo.swapMemoryUsedPercent * 100) }}%
|
||||
</el-tag>
|
||||
</div>
|
||||
<template #reference>
|
||||
|
@ -24,6 +24,15 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Swap" prop="swap">
|
||||
<el-input disabled v-model="form.swapItem">
|
||||
<template #append>
|
||||
<el-button @click="onChangeSwap" icon="Setting">
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('toolbox.device.hostname')" prop="hostname">
|
||||
<el-input disabled v-model="form.hostname">
|
||||
<template #append>
|
||||
@ -34,7 +43,7 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('toolbox.device.passwd')" prop="passwd">
|
||||
<el-input disabled v-model="form.passwd">
|
||||
<el-input disabled v-model="form.passwd" type="password">
|
||||
<template #append>
|
||||
<el-button @click="onChangePasswd" icon="Setting">
|
||||
{{ $t('commons.button.set') }}
|
||||
@ -42,6 +51,15 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('toolbox.device.syncSite')" prop="ntp">
|
||||
<el-input disabled v-model="form.ntp">
|
||||
<template #append>
|
||||
<el-button @click="onChangeNtp" icon="Setting">
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('toolbox.device.timeZone')" prop="timeZone">
|
||||
<el-input disabled v-model="form.timeZone">
|
||||
<template #append>
|
||||
@ -54,8 +72,8 @@
|
||||
<el-form-item :label="$t('toolbox.device.localTime')" prop="localTime">
|
||||
<el-input disabled v-model="form.localTime">
|
||||
<template #append>
|
||||
<el-button @click="onChangeLocalTime" icon="Setting">
|
||||
{{ $t('commons.button.set') }}
|
||||
<el-button @click="onChangeLocalTime" icon="Refresh">
|
||||
{{ $t('commons.button.sync') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
@ -66,9 +84,10 @@
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<Swap ref="swapRef" @search="search" />
|
||||
<Passwd ref="passwdRef" @search="search" />
|
||||
<TimeZone ref="timeZoneRef" @search="search" />
|
||||
<LocalTime ref="localTimeRef" @search="search" />
|
||||
<Ntp ref="ntpRef" @search="search" />
|
||||
<DNS ref="dnsRef" @search="search" />
|
||||
<Hostname ref="hostnameRef" @search="search" />
|
||||
<Hosts ref="hostsRef" @search="search" />
|
||||
@ -77,19 +96,23 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import Swap from '@/views/toolbox/device/swap/index.vue';
|
||||
import Passwd from '@/views/toolbox/device/passwd/index.vue';
|
||||
import TimeZone from '@/views/toolbox/device/time-zone/index.vue';
|
||||
import LocalTime from '@/views/toolbox/device/local-time/index.vue';
|
||||
import Ntp from '@/views/toolbox/device/ntp/index.vue';
|
||||
import DNS from '@/views/toolbox/device/dns/index.vue';
|
||||
import Hostname from '@/views/toolbox/device/hostname/index.vue';
|
||||
import Hosts from '@/views/toolbox/device/hosts/index.vue';
|
||||
import { getDeviceBase } from '@/api/modules/toolbox';
|
||||
import { getDeviceBase, updateDevice } from '@/api/modules/toolbox';
|
||||
import i18n from '@/lang';
|
||||
import { computeSize } from '@/utils/util';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const swapRef = ref();
|
||||
const timeZoneRef = ref();
|
||||
const localTimeRef = ref();
|
||||
const ntpRef = ref();
|
||||
const passwdRef = ref();
|
||||
const dnsRef = ref();
|
||||
const hostnameRef = ref();
|
||||
@ -106,13 +129,27 @@ const form = reactive({
|
||||
timeZone: '',
|
||||
localTime: '',
|
||||
ntp: '',
|
||||
|
||||
swapItem: '',
|
||||
});
|
||||
|
||||
const onChangeTimeZone = () => {
|
||||
timeZoneRef.value.acceptParams({ timeZone: form.timeZone });
|
||||
};
|
||||
const onChangeLocalTime = () => {
|
||||
localTimeRef.value.acceptParams({ localTime: form.localTime, ntpSite: form.ntp });
|
||||
const onChangeNtp = () => {
|
||||
ntpRef.value.acceptParams({ ntpSite: form.ntp });
|
||||
};
|
||||
const onChangeLocalTime = async () => {
|
||||
loading.value = true;
|
||||
await updateDevice('LocalTime', '')
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
search();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
const onChangePasswd = () => {
|
||||
passwdRef.value.acceptParams({ user: form.user });
|
||||
@ -126,6 +163,9 @@ const onChangeHostname = () => {
|
||||
const onChangeHost = () => {
|
||||
hostsRef.value.acceptParams({ hosts: form.hosts });
|
||||
};
|
||||
const onChangeSwap = () => {
|
||||
swapRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
const res = await getDeviceBase();
|
||||
@ -138,6 +178,10 @@ const search = async () => {
|
||||
form.dnsItem = form.dns ? i18n.global.t('toolbox.device.dnsHelper') : i18n.global.t('setting.unSetting');
|
||||
form.hosts = res.data.hosts || [];
|
||||
form.hostItem = form.hosts ? i18n.global.t('toolbox.device.hostsHelper') : i18n.global.t('setting.unSetting');
|
||||
|
||||
form.swapItem = res.data.swapMemoryTotal
|
||||
? computeSize(res.data.swapMemoryTotal)
|
||||
: i18n.global.t('setting.unSetting');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('toolbox.device.localTime')" :back="handleClose" />
|
||||
<DrawerHeader :header="$t('toolbox.device.syncSite')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||
<el-row type="flex" justify="center">
|
||||
@ -19,10 +19,6 @@
|
||||
{{ $t('toolbox.device.ntpGoogle') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('toolbox.device.localTime')" prop="localTime">
|
||||
<el-input v-model="form.localTime" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
@ -30,7 +26,7 @@
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="onSyncTime(formRef)">
|
||||
{{ $t('commons.button.sync') }}
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
@ -49,21 +45,18 @@ import { updateDevice } from '@/api/modules/toolbox';
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
interface DialogProps {
|
||||
localTime: string;
|
||||
ntpSite: string;
|
||||
}
|
||||
const drawerVisible = ref();
|
||||
const loading = ref();
|
||||
|
||||
const form = reactive({
|
||||
localTime: '',
|
||||
ntpSite: '',
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
form.localTime = params.localTime;
|
||||
form.ntpSite = params.ntpSite;
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
@ -82,7 +75,7 @@ const onSyncTime = async (formEl: FormInstance | undefined) => {
|
||||
},
|
||||
).then(async () => {
|
||||
loading.value = true;
|
||||
await updateDevice('LocalTime', form.ntpSite)
|
||||
await updateDevice('Ntp', form.ntpSite)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
201
frontend/src/views/toolbox/device/swap/index.vue
Normal file
201
frontend/src/views/toolbox/device/swap/index.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader header="Swap" :back="handleClose" />
|
||||
</template>
|
||||
|
||||
<el-row type="flex" justify="center" v-loading="loading">
|
||||
<el-col :span="22">
|
||||
<el-alert class="common-prompt" :closable="false" type="warning">
|
||||
<template #default>
|
||||
<ul style="margin-left: -20px">
|
||||
<li>{{ $t('toolbox.swap.swapHelper1') }}</li>
|
||||
<li>{{ $t('toolbox.swap.swapHelper2') }}</li>
|
||||
<li>{{ $t('toolbox.swap.swapHelper3') }}</li>
|
||||
<li>{{ $t('toolbox.swap.swapHelper4') }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-card>
|
||||
<el-form label-position="top" class="ml-3">
|
||||
<el-row type="flex" justify="center" :gutter="20">
|
||||
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span class="status-label">Swap {{ $t('home.total') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ form.swapMemoryTotal }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span class="status-label">Swap {{ $t('home.used') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ form.swapMemoryUsed }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span class="status-label">Swap {{ $t('home.free') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ form.swapMemoryAvailable }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-table :data="form.swapDetails" class="mt-5">
|
||||
<el-table-column :label="$t('file.path')" min-width="120" prop="path">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.path }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('file.size')" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input placeholder="1024" v-model.number="row.size">
|
||||
<template #append>
|
||||
<el-select v-model="row.sizeUnit" style="width: 85px">
|
||||
<el-option label="KB" value="KB" />
|
||||
<el-option label="MB" value="MB" />
|
||||
<el-option label="GB" value="G" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('home.used')" min-width="70" prop="used">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.used !== '-'">{{ computeSize(row.used * 1024) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="70">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="onSave(scope.row)">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<span class="input-help">{{ $t('toolbox.swap.swapOff') }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { updateDeviceSwap, getDeviceBase } from '@/api/modules/toolbox';
|
||||
import { computeSize, splitSize } from '@/utils/util';
|
||||
import { loadBaseDir } from '@/api/modules/setting';
|
||||
|
||||
const form = reactive({
|
||||
swapItem: '',
|
||||
swapMemoryTotal: '',
|
||||
swapMemoryAvailable: '',
|
||||
swapMemoryUsed: '',
|
||||
|
||||
swapDetails: [],
|
||||
});
|
||||
|
||||
const drawerVisible = ref();
|
||||
const loading = ref();
|
||||
|
||||
const acceptParams = (): void => {
|
||||
search();
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
loading.value = true;
|
||||
const res = await getDeviceBase();
|
||||
form.swapMemoryTotal = computeSize(res.data.swapMemoryTotal);
|
||||
form.swapMemoryUsed = computeSize(res.data.swapMemoryUsed);
|
||||
form.swapMemoryAvailable = computeSize(res.data.swapMemoryAvailable);
|
||||
form.swapDetails = res.data.swapDetails || [];
|
||||
|
||||
await loadBaseDir()
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
loadData(res.data.substring(0, res.data.lastIndexOf('/1panel')));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
loadData('');
|
||||
});
|
||||
};
|
||||
|
||||
const loadData = (path: string) => {
|
||||
let isExist = false;
|
||||
for (const item of form.swapDetails) {
|
||||
if (item.path === path + '/.1panel_swap') {
|
||||
isExist = true;
|
||||
}
|
||||
let itemSize = splitSize(item.size * 1024);
|
||||
item.size = itemSize.size;
|
||||
item.sizeUnit = itemSize.unit;
|
||||
}
|
||||
if (!isExist && path !== '') {
|
||||
form.swapDetails.push({
|
||||
path: path + '/.1panel_swap',
|
||||
size: 0,
|
||||
used: 0,
|
||||
isNew: true,
|
||||
sizeUnit: 'MB',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSave = async (row) => {
|
||||
if (row.sizeUnit === 'KB' && row.size < 40 && row.size !== 0) {
|
||||
MsgError(i18n.global.t('toolbox.swap.saveSwapHelper'));
|
||||
return;
|
||||
}
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('toolbox.swap.saveSwap', [row.path, row.size + ' ' + row.sizeUnit]),
|
||||
i18n.global.t('commons.button.save'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
},
|
||||
).then(async () => {
|
||||
let params = {
|
||||
path: row.path,
|
||||
size: row.size * 1024,
|
||||
used: '0',
|
||||
|
||||
isNew: row.isNew,
|
||||
};
|
||||
loading.value = true;
|
||||
await updateDeviceSwap(params)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -84,7 +84,6 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
outDir: '../cmd/server/web',
|
||||
minify: 'esbuild',
|
||||
rollupOptions: {
|
||||
external: ['codemirror'],
|
||||
output: {
|
||||
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||
entryFileNames: 'assets/js/[name]-[hash].js',
|
||||
|
Loading…
x
Reference in New Issue
Block a user