mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
refactor: 仪表盘实时数据接口
This commit is contained in:
@@ -25,10 +25,20 @@ var (
|
||||
Logger *zap.Logger
|
||||
)
|
||||
|
||||
// 定义面板状态常量
|
||||
const (
|
||||
StatusNormal = iota
|
||||
StatusMaintain
|
||||
StatusClosed
|
||||
StatusUpgrade
|
||||
StatusFailed
|
||||
)
|
||||
|
||||
// 面板全局变量
|
||||
var (
|
||||
Root string
|
||||
Version string
|
||||
Locale string
|
||||
IsCli bool
|
||||
Status = StatusNormal
|
||||
)
|
||||
|
||||
@@ -4,14 +4,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Info tools.MonitoringInfo `gorm:"not null;serializer:json" json:"info"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Info types.CurrentInfo `gorm:"not null;serializer:json" json:"info"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type MonitorRepo interface {
|
||||
|
||||
@@ -3,37 +3,36 @@ package middleware
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
// Status 检查程序状态
|
||||
func Status(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch types.Status {
|
||||
case types.StatusUpgrade:
|
||||
switch app.Status {
|
||||
case app.StatusUpgrade:
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusServiceUnavailable)
|
||||
render.JSON(chix.M{
|
||||
"message": "面板升级中,请稍后刷新",
|
||||
})
|
||||
return
|
||||
case types.StatusMaintain:
|
||||
case app.StatusMaintain:
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusServiceUnavailable)
|
||||
render.JSON(chix.M{
|
||||
"message": "面板正在运行维护任务,请稍后刷新",
|
||||
})
|
||||
return
|
||||
case types.StatusClosed:
|
||||
case app.StatusClosed:
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusForbidden)
|
||||
render.JSON(chix.M{
|
||||
"message": "面板已关闭",
|
||||
})
|
||||
return
|
||||
case types.StatusFailed:
|
||||
case app.StatusFailed:
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusInternalServerError)
|
||||
render.JSON(chix.M{
|
||||
|
||||
6
internal/http/request/dashboard.go
Normal file
6
internal/http/request/dashboard.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package request
|
||||
|
||||
type DashboardCurrent struct {
|
||||
Nets []string `json:"nets" form:"nets"`
|
||||
Disks []string `json:"disks" form:"disks"`
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
pkgcert "github.com/TheTNB/panel/pkg/cert"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
// CertRenew 证书续签
|
||||
@@ -24,7 +23,7 @@ func NewCertRenew() *CertRenew {
|
||||
}
|
||||
|
||||
func (r *CertRenew) Run() {
|
||||
if types.Status != types.StatusNormal {
|
||||
if app.Status != app.StatusNormal {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/pkg/tools"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
// Monitoring 系统监控
|
||||
@@ -25,7 +24,7 @@ func NewMonitoring() *Monitoring {
|
||||
}
|
||||
|
||||
func (r *Monitoring) Run() {
|
||||
if types.Status != types.StatusNormal {
|
||||
if app.Status != app.StatusNormal {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -38,13 +37,13 @@ func (r *Monitoring) Run() {
|
||||
return
|
||||
}
|
||||
|
||||
info := tools.GetMonitoringInfo()
|
||||
info := tools.CurrentInfo(nil, nil)
|
||||
|
||||
// 去除部分数据以减少数据库存储
|
||||
info.Disk = nil
|
||||
info.Cpus = nil
|
||||
|
||||
if types.Status != types.StatusNormal {
|
||||
if app.Status != app.StatusNormal {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -59,7 +58,7 @@ func (r *Monitoring) Run() {
|
||||
return
|
||||
}
|
||||
day := cast.ToInt(dayStr)
|
||||
if day <= 0 || types.Status != types.StatusNormal {
|
||||
if day <= 0 || app.Status != app.StatusNormal {
|
||||
return
|
||||
}
|
||||
if err = app.Orm.Where("created_at < ?", time.Now().AddDate(0, 0, -day).Format("2006-01-02 15:04:05")).Delete(&biz.Monitor{}).Error; err != nil {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
// PanelTask 面板每日任务
|
||||
@@ -28,15 +27,15 @@ func NewPanelTask() *PanelTask {
|
||||
}
|
||||
|
||||
func (r *PanelTask) Run() {
|
||||
types.Status = types.StatusMaintain
|
||||
app.Status = app.StatusMaintain
|
||||
|
||||
// 优化数据库
|
||||
if err := app.Orm.Exec("VACUUM").Error; err != nil {
|
||||
types.Status = types.StatusFailed
|
||||
app.Status = app.StatusFailed
|
||||
app.Logger.Error("优化面板数据库失败", zap.Error(err))
|
||||
}
|
||||
if err := app.Orm.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error; err != nil {
|
||||
types.Status = types.StatusFailed
|
||||
app.Status = app.StatusFailed
|
||||
app.Logger.Error("优化面板数据库失败", zap.Error(err))
|
||||
}
|
||||
|
||||
@@ -64,5 +63,5 @@ func (r *PanelTask) Run() {
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
types.Status = types.StatusNormal
|
||||
app.Status = app.StatusNormal
|
||||
}
|
||||
|
||||
@@ -23,18 +23,18 @@ func Http(r chi.Router) {
|
||||
r.With(middleware.MustLogin).Get("/info", user.Info)
|
||||
})
|
||||
|
||||
r.Route("/info", func(r chi.Router) {
|
||||
info := service.NewInfoService()
|
||||
r.Get("/panel", info.Panel)
|
||||
r.With(middleware.MustLogin).Get("/homeApps", info.HomeApps)
|
||||
r.With(middleware.MustLogin).Get("/realtime", info.Realtime)
|
||||
r.With(middleware.MustLogin).Get("/systemInfo", info.SystemInfo)
|
||||
r.With(middleware.MustLogin).Get("/countInfo", info.CountInfo)
|
||||
r.With(middleware.MustLogin).Get("/installedDbAndPhp", info.InstalledDbAndPhp)
|
||||
r.With(middleware.MustLogin).Get("/checkUpdate", info.CheckUpdate)
|
||||
r.With(middleware.MustLogin).Get("/updateInfo", info.UpdateInfo)
|
||||
r.With(middleware.MustLogin).Post("/update", info.Update)
|
||||
r.With(middleware.MustLogin).Post("/restart", info.Restart)
|
||||
r.Route("/dashboard", func(r chi.Router) {
|
||||
dashboard := service.NewDashboardService()
|
||||
r.Get("/panel", dashboard.Panel)
|
||||
r.With(middleware.MustLogin).Get("/homeApps", dashboard.HomeApps)
|
||||
r.With(middleware.MustLogin).Post("/current", dashboard.Current)
|
||||
r.With(middleware.MustLogin).Get("/systemInfo", dashboard.SystemInfo)
|
||||
r.With(middleware.MustLogin).Get("/countInfo", dashboard.CountInfo)
|
||||
r.With(middleware.MustLogin).Get("/installedDbAndPhp", dashboard.InstalledDbAndPhp)
|
||||
r.With(middleware.MustLogin).Get("/checkUpdate", dashboard.CheckUpdate)
|
||||
r.With(middleware.MustLogin).Get("/updateInfo", dashboard.UpdateInfo)
|
||||
r.With(middleware.MustLogin).Post("/update", dashboard.Update)
|
||||
r.With(middleware.MustLogin).Post("/restart", dashboard.Restart)
|
||||
})
|
||||
|
||||
r.Route("/task", func(r chi.Router) {
|
||||
|
||||
@@ -8,11 +8,13 @@ import (
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/api"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
@@ -21,7 +23,7 @@ import (
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type InfoService struct {
|
||||
type DashboardService struct {
|
||||
api *api.API
|
||||
taskRepo biz.TaskRepo
|
||||
websiteRepo biz.WebsiteRepo
|
||||
@@ -30,8 +32,8 @@ type InfoService struct {
|
||||
cronRepo biz.CronRepo
|
||||
}
|
||||
|
||||
func NewInfoService() *InfoService {
|
||||
return &InfoService{
|
||||
func NewDashboardService() *DashboardService {
|
||||
return &DashboardService{
|
||||
api: api.NewAPI(app.Version),
|
||||
taskRepo: data.NewTaskRepo(),
|
||||
websiteRepo: data.NewWebsiteRepo(),
|
||||
@@ -41,7 +43,7 @@ func NewInfoService() *InfoService {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *InfoService) Panel(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *DashboardService) Panel(w http.ResponseWriter, r *http.Request) {
|
||||
name, _ := s.settingRepo.Get(biz.SettingKeyName)
|
||||
if name == "" {
|
||||
name = "耗子面板"
|
||||
@@ -53,31 +55,46 @@ func (s *InfoService) Panel(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *InfoService) HomeApps(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *DashboardService) HomeApps(w http.ResponseWriter, r *http.Request) {
|
||||
apps, err := s.appRepo.GetHomeShow()
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "获取首页应用失败")
|
||||
Error(w, http.StatusInternalServerError, "获取首页应用失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, apps)
|
||||
}
|
||||
|
||||
func (s *InfoService) Realtime(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, tools.GetMonitoringInfo())
|
||||
func (s *DashboardService) Current(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.DashboardCurrent](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, tools.CurrentInfo(req.Nets, req.Disks))
|
||||
}
|
||||
|
||||
func (s *InfoService) SystemInfo(w http.ResponseWriter, r *http.Request) {
|
||||
monitorInfo := tools.GetMonitoringInfo()
|
||||
func (s *DashboardService) SystemInfo(w http.ResponseWriter, r *http.Request) {
|
||||
hostInfo, err := host.Info()
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "获取系统信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"os_name": monitorInfo.Host.Platform + " " + monitorInfo.Host.PlatformVersion,
|
||||
"uptime": fmt.Sprintf("%.2f", float64(monitorInfo.Host.Uptime)/86400),
|
||||
"panel_version": app.Version,
|
||||
"procs": hostInfo.Procs,
|
||||
"hostname": hostInfo.Hostname,
|
||||
"kernel_arch": hostInfo.KernelArch,
|
||||
"kernel_version": hostInfo.KernelVersion,
|
||||
"os_name": hostInfo.Platform + " " + hostInfo.PlatformVersion,
|
||||
"boot_time": hostInfo.BootTime,
|
||||
"uptime": fmt.Sprintf("%.2f", float64(hostInfo.Uptime)/86400),
|
||||
"panel_version": app.Version,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *InfoService) CountInfo(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *DashboardService) CountInfo(w http.ResponseWriter, r *http.Request) {
|
||||
websiteCount, err := s.websiteRepo.Count()
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "获取网站数量失败")
|
||||
@@ -173,7 +190,7 @@ func (s *InfoService) CountInfo(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *InfoService) InstalledDbAndPhp(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *DashboardService) InstalledDbAndPhp(w http.ResponseWriter, r *http.Request) {
|
||||
mysqlInstalled, _ := s.appRepo.IsInstalled("slug like ?", "mysql%")
|
||||
postgresqlInstalled, _ := s.appRepo.IsInstalled("slug like ?", "postgresql%")
|
||||
php, _ := s.appRepo.GetInstalledAll("slug like ?", "php%")
|
||||
@@ -206,7 +223,7 @@ func (s *InfoService) InstalledDbAndPhp(w http.ResponseWriter, r *http.Request)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *InfoService) CheckUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *DashboardService) CheckUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline {
|
||||
Error(w, http.StatusForbidden, "离线模式下无法检查更新")
|
||||
return
|
||||
@@ -241,7 +258,7 @@ func (s *InfoService) CheckUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *InfoService) UpdateInfo(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *DashboardService) UpdateInfo(w http.ResponseWriter, r *http.Request) {
|
||||
if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline {
|
||||
Error(w, http.StatusForbidden, "离线模式下无法检查更新")
|
||||
return
|
||||
@@ -278,7 +295,7 @@ func (s *InfoService) UpdateInfo(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, versions)
|
||||
}
|
||||
|
||||
func (s *InfoService) Update(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *DashboardService) Update(w http.ResponseWriter, r *http.Request) {
|
||||
if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline {
|
||||
Error(w, http.StatusForbidden, "离线模式下无法升级")
|
||||
return
|
||||
@@ -302,19 +319,19 @@ func (s *InfoService) Update(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
ver, url, checksum := panel.Version, download.URL, download.Checksum
|
||||
|
||||
types.Status = types.StatusUpgrade
|
||||
app.Status = app.StatusUpgrade
|
||||
if err = s.settingRepo.UpdatePanel(ver, url, checksum); err != nil {
|
||||
types.Status = types.StatusFailed
|
||||
app.Status = app.StatusFailed
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
types.Status = types.StatusNormal
|
||||
app.Status = app.StatusNormal
|
||||
Success(w, nil)
|
||||
tools.RestartPanel()
|
||||
}
|
||||
|
||||
func (s *InfoService) Restart(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *DashboardService) Restart(w http.ResponseWriter, r *http.Request) {
|
||||
if s.taskRepo.HasRunningTask() {
|
||||
Error(w, http.StatusInternalServerError, "后台任务正在运行,禁止重启,请稍后再试")
|
||||
return
|
||||
@@ -101,7 +101,7 @@ func (s *MonitorService) List(w http.ResponseWriter, r *http.Request) {
|
||||
list.Load.Load1 = append(list.Load.Load1, monitor.Info.Load.Load1)
|
||||
list.Load.Load5 = append(list.Load.Load5, monitor.Info.Load.Load5)
|
||||
list.Load.Load15 = append(list.Load.Load15, monitor.Info.Load.Load15)
|
||||
list.CPU.Percent = append(list.CPU.Percent, fmt.Sprintf("%.2f", monitor.Info.Percent[0]))
|
||||
list.CPU.Percent = append(list.CPU.Percent, fmt.Sprintf("%.2f", monitor.Info.Percent))
|
||||
list.Mem.Available = append(list.Mem.Available, fmt.Sprintf("%.2f", float64(monitor.Info.Mem.Available)/1024/1024))
|
||||
list.Mem.Used = append(list.Mem.Used, fmt.Sprintf("%.2f", float64(monitor.Info.Mem.Used)/1024/1024))
|
||||
list.SWAP.Used = append(list.SWAP.Used, fmt.Sprintf("%.2f", float64(monitor.Info.Swap.Used)/1024/1024))
|
||||
|
||||
@@ -3,6 +3,7 @@ package tools
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -15,50 +16,64 @@ import (
|
||||
"github.com/shirou/gopsutil/net"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
// MonitoringInfo 监控信息
|
||||
type MonitoringInfo struct {
|
||||
Cpus []cpu.InfoStat `json:"cpus"`
|
||||
Percent []float64 `json:"percent"`
|
||||
Load *load.AvgStat `json:"load"`
|
||||
Host *host.InfoStat `json:"host"`
|
||||
Mem *mem.VirtualMemoryStat `json:"mem"`
|
||||
Swap *mem.SwapMemoryStat `json:"swap"`
|
||||
Net []net.IOCountersStat `json:"net"`
|
||||
DiskIO []disk.IOCountersStat `json:"disk_io"`
|
||||
Disk []disk.PartitionStat `json:"disk"`
|
||||
DiskUsage []disk.UsageStat `json:"disk_usage"`
|
||||
}
|
||||
|
||||
// GetMonitoringInfo 获取监控数据
|
||||
func GetMonitoringInfo() MonitoringInfo {
|
||||
var res MonitoringInfo
|
||||
// CurrentInfo 获取监控数据
|
||||
func CurrentInfo(nets, disks []string) types.CurrentInfo {
|
||||
var res types.CurrentInfo
|
||||
res.Cpus, _ = cpu.Info()
|
||||
res.Percent, _ = cpu.Percent(time.Second, false)
|
||||
res.Percents, _ = cpu.Percent(100*time.Millisecond, true)
|
||||
percent, _ := cpu.Percent(100*time.Millisecond, false)
|
||||
if len(percent) > 0 {
|
||||
res.Percent = percent[0]
|
||||
}
|
||||
res.Load, _ = load.Avg()
|
||||
res.Host, _ = host.Info()
|
||||
res.Mem, _ = mem.VirtualMemory()
|
||||
res.Swap, _ = mem.SwapMemory()
|
||||
res.Net, _ = net.IOCounters(true)
|
||||
res.Disk, _ = disk.Partitions(true)
|
||||
|
||||
ioCounters, _ := disk.IOCounters()
|
||||
ioCounters, _ := disk.IOCounters(disks...)
|
||||
for _, info := range ioCounters {
|
||||
res.DiskIO = append(res.DiskIO, info)
|
||||
}
|
||||
|
||||
var excludes = []string{"/dev", "/boot", "/sys", "/dev", "/run", "/proc", "/usr", "/var", "/snap"}
|
||||
excludes = append(excludes, "/mnt/cdrom") // CDROM
|
||||
excludes = append(excludes, "/mnt/wsl") // Windows WSL
|
||||
for _, partition := range res.Disk {
|
||||
if strings.HasPrefix(partition.Mountpoint, "/dev") || strings.HasPrefix(partition.Mountpoint, "/sys") || strings.HasPrefix(partition.Mountpoint, "/proc") || strings.HasPrefix(partition.Mountpoint, "/run") || strings.HasPrefix(partition.Mountpoint, "/boot") || strings.HasPrefix(partition.Mountpoint, "/usr") || strings.HasPrefix(partition.Mountpoint, "/var") {
|
||||
for _, exclude := range excludes {
|
||||
if strings.HasPrefix(partition.Mountpoint, exclude) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// 去除内存盘和overlay容器盘
|
||||
if slices.Contains([]string{"tmpfs", "overlay"}, partition.Fstype) {
|
||||
continue
|
||||
}
|
||||
usage, _ := disk.Usage(partition.Mountpoint)
|
||||
res.DiskUsage = append(res.DiskUsage, *usage)
|
||||
}
|
||||
|
||||
if len(nets) == 0 {
|
||||
netInfo, _ := net.IOCounters(false)
|
||||
res.Net = netInfo
|
||||
} else {
|
||||
var netStats []net.IOCountersStat
|
||||
netInfo, _ := net.IOCounters(true)
|
||||
for _, state := range netInfo {
|
||||
if slices.Contains(nets, state.Name) {
|
||||
netStats = append(netStats, state)
|
||||
}
|
||||
}
|
||||
res.Net = netStats
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// RestartPanel 重启面板
|
||||
func RestartPanel() {
|
||||
_ = shell.ExecfAsync("sleep 1 && systemctl restart panel")
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestHelperTestSuite(t *testing.T) {
|
||||
}
|
||||
|
||||
func (s *HelperTestSuite) TestGetMonitoringInfo() {
|
||||
s.NotNil(GetMonitoringInfo())
|
||||
s.NotNil(CurrentInfo(nil, nil))
|
||||
}
|
||||
|
||||
func (s *HelperTestSuite) TestGetPublicIP() {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package types
|
||||
|
||||
// 定义面板状态常量
|
||||
const (
|
||||
StatusNormal = iota
|
||||
StatusMaintain
|
||||
StatusClosed
|
||||
StatusUpgrade
|
||||
StatusFailed
|
||||
)
|
||||
|
||||
var Status = StatusNormal
|
||||
25
pkg/types/system.go
Normal file
25
pkg/types/system.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
"github.com/shirou/gopsutil/load"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"github.com/shirou/gopsutil/net"
|
||||
)
|
||||
|
||||
// CurrentInfo 监控信息
|
||||
type CurrentInfo struct {
|
||||
Cpus []cpu.InfoStat `json:"cpus"`
|
||||
Percent float64 `json:"percent"` // 总使用率
|
||||
Percents []float64 `json:"percents"` // 每个核心使用率
|
||||
Load *load.AvgStat `json:"load"`
|
||||
Host *host.InfoStat `json:"host"`
|
||||
Mem *mem.VirtualMemoryStat `json:"mem"`
|
||||
Swap *mem.SwapMemoryStat `json:"swap"`
|
||||
Net []net.IOCountersStat `json:"net"`
|
||||
DiskIO []disk.IOCountersStat `json:"disk_io"`
|
||||
Disk []disk.PartitionStat `json:"disk"`
|
||||
DiskUsage []disk.UsageStat `json:"disk_usage"`
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { DateTime } from 'luxon'
|
||||
|
||||
/**
|
||||
* * 此处定义的是全局常量,启动或打包后将添加到window中
|
||||
* * 此处定义的是全局常量,启动或打包后将添加到 window 中
|
||||
* https://vitejs.cn/config/#define
|
||||
*/
|
||||
|
||||
// 项目构建时间
|
||||
const _BUILD_TIME_ = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss'))
|
||||
const _BUILD_TIME_ = JSON.stringify(DateTime.now().toFormat('yyyy-MM-dd HH:mm:ss'))
|
||||
|
||||
export const viteDefine = {
|
||||
_BUILD_TIME_
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"axios": "^1.7.7",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"install": "^0.13.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"luxon": "^3.5.0",
|
||||
"marked": "^14.1.2",
|
||||
"pinia": "^2.2.4",
|
||||
"remove": "^0.1.5",
|
||||
|
||||
31
web/pnpm-lock.yaml
generated
31
web/pnpm-lock.yaml
generated
@@ -26,9 +26,6 @@ importers:
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
echarts:
|
||||
specifier: ^5.5.1
|
||||
version: 5.5.1
|
||||
@@ -38,6 +35,9 @@ importers:
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
luxon:
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
marked:
|
||||
specifier: ^14.1.2
|
||||
version: 14.1.3
|
||||
@@ -761,30 +761,35 @@ packages:
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.4.1':
|
||||
resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.4.1':
|
||||
resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.4.1':
|
||||
resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.4.1':
|
||||
resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.4.1':
|
||||
resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==}
|
||||
@@ -852,46 +857,55 @@ packages:
|
||||
resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.24.0':
|
||||
resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.24.0':
|
||||
resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.24.0':
|
||||
resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
|
||||
resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.24.0':
|
||||
resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.24.0':
|
||||
resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.24.0':
|
||||
resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.24.0':
|
||||
resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.24.0':
|
||||
resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
|
||||
@@ -1464,9 +1478,6 @@ packages:
|
||||
date-fns@3.6.0:
|
||||
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
|
||||
|
||||
dayjs@1.11.13:
|
||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||
|
||||
de-indent@1.0.2:
|
||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||
|
||||
@@ -2058,6 +2069,10 @@ packages:
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
luxon@3.5.0:
|
||||
resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
magic-string@0.30.12:
|
||||
resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==}
|
||||
|
||||
@@ -4355,8 +4370,6 @@ snapshots:
|
||||
|
||||
date-fns@3.6.0: {}
|
||||
|
||||
dayjs@1.11.13: {}
|
||||
|
||||
de-indent@1.0.2: {}
|
||||
|
||||
debug@2.6.9:
|
||||
@@ -5060,6 +5073,8 @@ snapshots:
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
luxon@3.5.0: {}
|
||||
|
||||
magic-string@0.30.12:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { DateTime, Duration } from 'luxon'
|
||||
|
||||
type Time = undefined | string | Date
|
||||
|
||||
/** 格式化时间,默认格式:YYYY-MM-DD HH:mm:ss */
|
||||
export function formatDateTime(time: Time, format = 'YYYY-MM-DD HH:mm:ss'): string {
|
||||
return dayjs(time).format(format)
|
||||
/** 格式化时间,默认格式:yyyy-MM-dd HH:mm:ss */
|
||||
export function formatDateTime(time: Time, format = 'yyyy-MM-dd HH:mm:ss'): string {
|
||||
const dateTime = time ? DateTime.fromJSDate(new Date(time)) : DateTime.now()
|
||||
return dateTime.toFormat(format)
|
||||
}
|
||||
|
||||
/** 格式化日期,默认格式:YYYY-MM-DD */
|
||||
export function formatDate(date: Time = undefined, format = 'YYYY-MM-DD') {
|
||||
/** 格式化日期,默认格式:yyyy-MM-dd */
|
||||
export function formatDate(date: Time = undefined, format = 'yyyy-MM-dd') {
|
||||
return formatDateTime(date, format)
|
||||
}
|
||||
|
||||
/** 格式化持续时间,转为 x天x小时x分钟x秒 */
|
||||
export function formatDuration(seconds: number) {
|
||||
const duration = Duration.fromObject({ seconds })
|
||||
const days = Math.floor(duration.as('days'))
|
||||
const hours = duration.hours
|
||||
const minutes = duration.minutes
|
||||
const secs = duration.seconds
|
||||
|
||||
return `${days}天${hours}时${minutes}分${secs}秒`
|
||||
}
|
||||
|
||||
/** 生成随机字符串 */
|
||||
export function generateRandomString(length: number) {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
|
||||
Reference in New Issue
Block a user