mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
feat: 首页全新设计
This commit is contained in:
@@ -50,7 +50,7 @@ func (r monitorRepo) UpdateSetting(setting *request.MonitorSetting) error {
|
||||
}
|
||||
|
||||
func (r monitorRepo) Clear() error {
|
||||
return app.Orm.Delete(&biz.Monitor{}).Error
|
||||
return app.Orm.Where("1 = 1").Delete(&biz.Monitor{}).Error
|
||||
}
|
||||
|
||||
func (r monitorRepo) List(start, end time.Time) ([]*biz.Monitor, error) {
|
||||
|
||||
@@ -2,6 +2,8 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -82,15 +84,36 @@ func (s *DashboardService) SystemInfo(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// 所有网卡名称
|
||||
var nets []types.LV
|
||||
netInterfaces, _ := net.Interfaces()
|
||||
for _, v := range netInterfaces {
|
||||
nets = append(nets, types.LV{
|
||||
Value: v.Name,
|
||||
Label: v.Name,
|
||||
})
|
||||
}
|
||||
// 所有硬盘名称
|
||||
var disks []types.LV
|
||||
partitions, _ := disk.Partitions(false)
|
||||
for _, v := range partitions {
|
||||
disks = append(disks, types.LV{
|
||||
Value: v.Device,
|
||||
Label: fmt.Sprintf("%s (%s)", v.Device, v.Mountpoint),
|
||||
})
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"procs": hostInfo.Procs,
|
||||
"hostname": hostInfo.Hostname,
|
||||
"panel_version": app.Version,
|
||||
"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,
|
||||
"uptime": hostInfo.Uptime,
|
||||
"nets": nets,
|
||||
"disks": disks,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -32,30 +32,34 @@ func CurrentInfo(nets, disks []string) types.CurrentInfo {
|
||||
res.Host, _ = host.Info()
|
||||
res.Mem, _ = mem.VirtualMemory()
|
||||
res.Swap, _ = mem.SwapMemory()
|
||||
res.Disk, _ = disk.Partitions(true)
|
||||
|
||||
// 硬盘IO
|
||||
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 {
|
||||
res.Disk, _ = disk.Partitions(false)
|
||||
res.Disk = slices.DeleteFunc(res.Disk, func(d disk.PartitionStat) bool {
|
||||
for _, exclude := range excludes {
|
||||
if strings.HasPrefix(partition.Mountpoint, exclude) {
|
||||
if strings.HasPrefix(d.Mountpoint, exclude) {
|
||||
return true
|
||||
}
|
||||
// 去除内存盘和overlay容器盘
|
||||
if slices.Contains([]string{"tmpfs", "overlay"}, d.Fstype) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// 去除内存盘和overlay容器盘
|
||||
if slices.Contains([]string{"tmpfs", "overlay"}, partition.Fstype) {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
})
|
||||
// 分区使用
|
||||
for _, partition := range res.Disk {
|
||||
usage, _ := disk.Usage(partition.Mountpoint)
|
||||
res.DiskUsage = append(res.DiskUsage, *usage)
|
||||
}
|
||||
|
||||
// 网络
|
||||
if len(nets) == 0 {
|
||||
netInfo, _ := net.IOCounters(false)
|
||||
res.Net = netInfo
|
||||
@@ -70,6 +74,7 @@ func CurrentInfo(nets, disks []string) types.CurrentInfo {
|
||||
res.Net = netStats
|
||||
}
|
||||
|
||||
res.Time = time.Now()
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/shirou/gopsutil/load"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"github.com/shirou/gopsutil/net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CurrentInfo 监控信息
|
||||
@@ -22,4 +23,5 @@ type CurrentInfo struct {
|
||||
DiskIO []disk.IOCountersStat `json:"disk_io"`
|
||||
Disk []disk.PartitionStat `json:"disk"`
|
||||
DiskUsage []disk.UsageStat `json:"disk_usage"`
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^20.16.11",
|
||||
"@unocss/eslint-config": "^0.63.4",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
|
||||
8
web/pnpm-lock.yaml
generated
8
web/pnpm-lock.yaml
generated
@@ -78,6 +78,9 @@ importers:
|
||||
'@types/lodash-es':
|
||||
specifier: ^4.17.12
|
||||
version: 4.17.12
|
||||
'@types/luxon':
|
||||
specifier: ^3.4.2
|
||||
version: 3.4.2
|
||||
'@types/node':
|
||||
specifier: ^20.16.11
|
||||
version: 20.16.11
|
||||
@@ -946,6 +949,9 @@ packages:
|
||||
'@types/lodash@4.17.10':
|
||||
resolution: {integrity: sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==}
|
||||
|
||||
'@types/luxon@3.4.2':
|
||||
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
|
||||
|
||||
'@types/markdown-it@14.1.2':
|
||||
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
|
||||
|
||||
@@ -3669,6 +3675,8 @@ snapshots:
|
||||
|
||||
'@types/lodash@4.17.10': {}
|
||||
|
||||
'@types/luxon@3.4.2': {}
|
||||
|
||||
'@types/markdown-it@14.1.2':
|
||||
dependencies:
|
||||
'@types/linkify-it': 5.0.0
|
||||
|
||||
29
web/src/api/panel/dashboard/index.ts
Normal file
29
web/src/api/panel/dashboard/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { AxiosResponse } from 'axios'
|
||||
|
||||
import { request } from '@/utils'
|
||||
|
||||
export default {
|
||||
// 面板信息
|
||||
panel: (): Promise<Response> => fetch('/api/dashboard/panel'),
|
||||
// 面板菜单
|
||||
menu: (): Promise<AxiosResponse<any>> => request.get('/dashboard/menu'),
|
||||
// 首页应用
|
||||
homeApps: (): Promise<AxiosResponse<any>> => request.get('/dashboard/homeApps'),
|
||||
// 实时信息
|
||||
current: (nets: string[], disks: string[]): Promise<AxiosResponse<any>> =>
|
||||
request.post('/dashboard/current', { nets, disks }),
|
||||
// 系统信息
|
||||
systemInfo: (): Promise<AxiosResponse<any>> => request.get('/dashboard/systemInfo'),
|
||||
// 统计信息
|
||||
countInfo: (): Promise<AxiosResponse<any>> => request.get('/dashboard/countInfo'),
|
||||
// 已安装的数据库和PHP
|
||||
installedDbAndPhp: (): Promise<AxiosResponse<any>> => request.get('/dashboard/installedDbAndPhp'),
|
||||
// 检查更新
|
||||
checkUpdate: (): Promise<AxiosResponse<any>> => request.get('/dashboard/checkUpdate'),
|
||||
// 更新日志
|
||||
updateInfo: (): Promise<AxiosResponse<any>> => request.get('/dashboard/updateInfo'),
|
||||
// 更新面板
|
||||
update: (): Promise<AxiosResponse<any>> => request.post('/dashboard/update', null),
|
||||
// 重启面板
|
||||
restart: (): Promise<AxiosResponse<any>> => request.post('/dashboard/restart')
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { AxiosResponse } from 'axios'
|
||||
|
||||
import { request } from '@/utils'
|
||||
|
||||
export default {
|
||||
// 面板信息
|
||||
panel: (): Promise<Response> => fetch('/api/info/panel'),
|
||||
// 面板菜单
|
||||
menu: (): Promise<AxiosResponse<any>> => request.get('/info/menu'),
|
||||
// 首页应用
|
||||
homeApps: (): Promise<AxiosResponse<any>> => request.get('/info/homeApps'),
|
||||
// 实时监控
|
||||
realtime: (): Promise<AxiosResponse<any>> => request.get('/info/realtime'),
|
||||
// 系统信息
|
||||
systemInfo: (): Promise<AxiosResponse<any>> => request.get('/info/systemInfo'),
|
||||
// 统计信息
|
||||
countInfo: (): Promise<AxiosResponse<any>> => request.get('/info/countInfo'),
|
||||
// 已安装的数据库和PHP
|
||||
installedDbAndPhp: (): Promise<AxiosResponse<any>> => request.get('/info/installedDbAndPhp'),
|
||||
// 检查更新
|
||||
checkUpdate: (): Promise<AxiosResponse<any>> => request.get('/info/checkUpdate'),
|
||||
// 更新日志
|
||||
updateInfo: (): Promise<AxiosResponse<any>> => request.get('/info/updateInfo'),
|
||||
// 更新面板
|
||||
update: (): Promise<AxiosResponse<any>> => request.post('/info/update', null),
|
||||
// 重启面板
|
||||
restart: (): Promise<AxiosResponse<any>> => request.post('/info/restart')
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import { setupNaiveDiscreteApi } from './utils'
|
||||
|
||||
import { install as VueMonacoEditorPlugin } from '@guolao/vue-monaco-editor'
|
||||
|
||||
import info from '@/api/panel/info'
|
||||
import dashboard from '@/api/panel/dashboard'
|
||||
|
||||
async function setupApp() {
|
||||
const app = createApp(App)
|
||||
@@ -37,7 +37,7 @@ const title = ref('')
|
||||
|
||||
const setupPanel = async () => {
|
||||
const themeStore = useThemeStore()
|
||||
await info
|
||||
await dashboard
|
||||
.panel()
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
|
||||
@@ -15,13 +15,13 @@ export function formatDate(date: Time = undefined, format = 'yyyy-MM-dd') {
|
||||
|
||||
/** 格式化持续时间,转为 x天x小时x分钟x秒 */
|
||||
export function formatDuration(seconds: number) {
|
||||
const duration = Duration.fromObject({ seconds })
|
||||
const days = Math.floor(duration.as('days'))
|
||||
const duration = Duration.fromObject({ seconds }).shiftTo('days', 'hours', 'minutes', 'seconds')
|
||||
const days = duration.days
|
||||
const hours = duration.hours
|
||||
const minutes = duration.minutes
|
||||
const secs = duration.seconds
|
||||
|
||||
return `${days}天${hours}时${minutes}分${secs}秒`
|
||||
return `${days}天${hours}小时${minutes}分钟${secs}秒`
|
||||
}
|
||||
|
||||
/** 生成随机字符串 */
|
||||
|
||||
@@ -3,6 +3,7 @@ import { reqReject, reqResolve, resReject, resResolve } from './interceptors'
|
||||
|
||||
export function createAxios(options = {}) {
|
||||
const defaultOptions = {
|
||||
adapter: 'fetch',
|
||||
timeout: 0
|
||||
}
|
||||
const service = axios.create({
|
||||
|
||||
@@ -3,8 +3,8 @@ import Editor from '@guolao/vue-monaco-editor'
|
||||
import { NButton, NDataTable, NInput, NPopconfirm, NSwitch } from 'naive-ui'
|
||||
|
||||
import cron from '@/api/panel/cron'
|
||||
import dashboard from '@/api/panel/dashboard'
|
||||
import file from '@/api/panel/file'
|
||||
import info from '@/api/panel/info'
|
||||
import website from '@/api/panel/website'
|
||||
import { formatDateTime, renderIcon } from '@/utils'
|
||||
import type { CronTask } from '@/views/cron/types'
|
||||
@@ -197,7 +197,7 @@ const getWebsiteList = async (page: number, limit: number) => {
|
||||
}
|
||||
|
||||
const getPhpAndDb = async () => {
|
||||
const { data } = await info.installedDbAndPhp()
|
||||
const { data } = await dashboard.installedDbAndPhp()
|
||||
installedDbAndPhp.value = data
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ import type { MessageReactive } from 'naive-ui'
|
||||
import { NButton } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import info from '@/api/panel/info'
|
||||
import dashboard from '@/api/panel/dashboard'
|
||||
import { router } from '@/router'
|
||||
import { formatDateTime } from '@/utils'
|
||||
import type { Version } from '@/views/home/types'
|
||||
@@ -15,7 +15,7 @@ const versions = ref<Version[] | null>(null)
|
||||
let messageReactive: MessageReactive | null = null
|
||||
|
||||
const getVersions = () => {
|
||||
info.updateInfo().then((res: any) => {
|
||||
dashboard.updateInfo().then((res: any) => {
|
||||
versions.value = res.data
|
||||
})
|
||||
}
|
||||
@@ -30,7 +30,7 @@ const handleUpdate = () => {
|
||||
messageReactive = window.$message.loading(t('homeUpdate.confirm.update.loading'), {
|
||||
duration: 0
|
||||
})
|
||||
info
|
||||
dashboard
|
||||
.update()
|
||||
.then(() => {
|
||||
messageReactive?.destroy()
|
||||
|
||||
@@ -140,7 +140,8 @@ interface DiskUsageStat {
|
||||
|
||||
export interface Realtime {
|
||||
cpus: CpuInfoStat[]
|
||||
percent: number[]
|
||||
percent: number
|
||||
percents: number[]
|
||||
load: LoadAvgStat
|
||||
host: HostInfoStat
|
||||
mem: VirtualMemoryStat
|
||||
@@ -152,9 +153,16 @@ export interface Realtime {
|
||||
}
|
||||
|
||||
export interface SystemInfo {
|
||||
os_name: string
|
||||
uptime: string
|
||||
procs: number
|
||||
hostname: string
|
||||
panel_version: string
|
||||
kernel_arch: string
|
||||
kernel_version: string
|
||||
os_name: string
|
||||
boot_time: number
|
||||
uptime: number
|
||||
nets: any[]
|
||||
disks: any[]
|
||||
}
|
||||
|
||||
export interface CountInfo {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import Editor from '@guolao/vue-monaco-editor'
|
||||
import { NButton } from 'naive-ui'
|
||||
|
||||
import info from '@/api/panel/info'
|
||||
import dashboard from '@/api/panel/dashboard'
|
||||
import website from '@/api/panel/website'
|
||||
import type { WebsiteListen, WebsiteSetting } from '@/views/website/types'
|
||||
|
||||
@@ -50,7 +50,7 @@ const installedDbAndPhp = ref({
|
||||
})
|
||||
|
||||
const getPhpAndDb = async () => {
|
||||
const { data } = await info.installedDbAndPhp()
|
||||
const { data } = await dashboard.installedDbAndPhp()
|
||||
installedDbAndPhp.value = data
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import info from '@/api/panel/info'
|
||||
import dashboard from '@/api/panel/dashboard'
|
||||
import website from '@/api/panel/website'
|
||||
import { generateRandomString, isNullOrUndef, renderIcon } from '@/utils'
|
||||
import type { Website } from './types'
|
||||
@@ -225,7 +225,7 @@ const installedDbAndPhp = ref({
|
||||
})
|
||||
|
||||
const getPhpAndDb = async () => {
|
||||
const { data } = await info.installedDbAndPhp()
|
||||
const { data } = await dashboard.installedDbAndPhp()
|
||||
installedDbAndPhp.value = data
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user