mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 12:40:25 +08:00
feat: 首页全新设计
This commit is contained in:
@@ -12,81 +12,6 @@
|
||||
"fileIndex": {
|
||||
"title": "Files"
|
||||
},
|
||||
"homeIndex": {
|
||||
"title": "Dashboard",
|
||||
"website": "Website",
|
||||
"database": "Database",
|
||||
"cron": "Scheduled Tasks",
|
||||
"sponsor": "Sponsor",
|
||||
"git": "Open Source",
|
||||
"resources": {
|
||||
"title": "Resource Usage",
|
||||
"cpu": {
|
||||
"used": "{used} CPU / {total} threads"
|
||||
},
|
||||
"memory": {
|
||||
"title": "Memory",
|
||||
"physical": {
|
||||
"used": "Physical used {used} / {total} total"
|
||||
},
|
||||
"swap": {
|
||||
"used": "Swap used {used} / {total} total"
|
||||
}
|
||||
}
|
||||
},
|
||||
"loads": {
|
||||
"title": "System Load",
|
||||
"load": "{load} minute load",
|
||||
"time": "nearly {time} minutes"
|
||||
},
|
||||
"traffic": {
|
||||
"title": "Real-Time Traffic",
|
||||
"network": {
|
||||
"title": "Network",
|
||||
"current": "Current uplink { sent } / current downlink { received }",
|
||||
"total": "Total uplink { sent } / total downlink { received }"
|
||||
},
|
||||
"disk": {
|
||||
"title": "Disk",
|
||||
"current": "Current read { read } / current write { write }",
|
||||
"total": "Total read { read } / total write { write }"
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"title": "Storage",
|
||||
"columns": {
|
||||
"path": "Path",
|
||||
"type": "Type",
|
||||
"usageRate": "Usage Rate",
|
||||
"total": "Total",
|
||||
"used": "Used",
|
||||
"free": "Free"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"title": "System Information",
|
||||
"columns": {
|
||||
"os": "OS",
|
||||
"panel": "Panel",
|
||||
"uptime": "Uptime",
|
||||
"operate": "Operate",
|
||||
"loading": "loading..."
|
||||
},
|
||||
"restart": {
|
||||
"label": "Restart",
|
||||
"confirm": "Are you sure you want to restart the panel?",
|
||||
"success": "Restart successful",
|
||||
"loading": "Restarting..."
|
||||
},
|
||||
"update": {
|
||||
"label": "Update",
|
||||
"success": "Update successful"
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"title": "Quick App"
|
||||
}
|
||||
},
|
||||
"homeUpdate": {
|
||||
"title": "Update panel",
|
||||
"loading": "Loading update information, please wait a moment",
|
||||
|
||||
@@ -12,81 +12,6 @@
|
||||
"fileIndex": {
|
||||
"title": "文件管理"
|
||||
},
|
||||
"homeIndex": {
|
||||
"title": "仪表盘",
|
||||
"website": "网站",
|
||||
"database": "数据库",
|
||||
"cron": "计划任务",
|
||||
"sponsor": "赞助支持",
|
||||
"git": "开源地址",
|
||||
"resources": {
|
||||
"title": "资源使用",
|
||||
"cpu": {
|
||||
"used": "{used} CPU / 共 {total} 线程"
|
||||
},
|
||||
"memory": {
|
||||
"title": "内存",
|
||||
"physical": {
|
||||
"used": "物理内存 使用 {used} / 总共 {total}"
|
||||
},
|
||||
"swap": {
|
||||
"used": "交换分区 使用 {used} / 总共 {total}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"loads": {
|
||||
"title": "系统负载",
|
||||
"load": "{load} 分钟负载",
|
||||
"time": "近 {time} 分钟"
|
||||
},
|
||||
"traffic": {
|
||||
"title": "实时流量",
|
||||
"network": {
|
||||
"title": "网络",
|
||||
"current": "实时上行 { sent }/s / 实时下行 { received }/s",
|
||||
"total": "累计上行 { sent } / 累计下行 { received }"
|
||||
},
|
||||
"disk": {
|
||||
"title": "硬盘",
|
||||
"current": "实时读取 { read }/s / 实时写入 { write }/s",
|
||||
"total": "累计读取 { read } / 累计写入 { write }"
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"title": "存储信息",
|
||||
"columns": {
|
||||
"path": "挂载点",
|
||||
"type": "文件系统",
|
||||
"usageRate": "使用率",
|
||||
"total": "总共",
|
||||
"used": "已用",
|
||||
"free": "可用"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"title": "系统信息",
|
||||
"columns": {
|
||||
"os": "操作系统",
|
||||
"panel": "面板版本",
|
||||
"uptime": "运行时间",
|
||||
"operate": "操作",
|
||||
"loading": "加载中..."
|
||||
},
|
||||
"restart": {
|
||||
"label": "重启面板",
|
||||
"confirm": "确定重启面板吗?",
|
||||
"success": "面板重启成功",
|
||||
"loading": "面板重启中..."
|
||||
},
|
||||
"update": {
|
||||
"label": "检查更新",
|
||||
"success": "当前已是最新版本"
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"title": "快捷应用"
|
||||
}
|
||||
},
|
||||
"homeUpdate": {
|
||||
"title": "升级面板",
|
||||
"loading": "正在加载更新信息,稍等片刻",
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
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 {
|
||||
export function formatDateTime(time: any, 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') {
|
||||
export function formatDate(date: any, format = 'yyyy-MM-dd') {
|
||||
return formatDateTime(date, format)
|
||||
}
|
||||
|
||||
@@ -24,6 +22,11 @@ export function formatDuration(seconds: number) {
|
||||
return `${days}天${hours}小时${minutes}分钟${secs}秒`
|
||||
}
|
||||
|
||||
/** 转时间戳 */
|
||||
export function toTimestamp(time: any) {
|
||||
return DateTime.fromJSDate(new Date(time)).toSeconds()
|
||||
}
|
||||
|
||||
/** 生成随机字符串 */
|
||||
export function generateRandomString(length: number) {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import dashboard from '@/api/panel/dashboard'
|
||||
import { router } from '@/router'
|
||||
import { useAppStore } from '@/store'
|
||||
import { formatDateTime, formatDuration } from '@/utils/common'
|
||||
import { formatDateTime, formatDuration, toTimestamp } from '@/utils/common'
|
||||
import { formatBytes, formatPercent } from '@/utils/file'
|
||||
import VChart from 'vue-echarts'
|
||||
import type { CountInfo, HomeApp, Realtime, SystemInfo } from './types'
|
||||
@@ -30,7 +30,7 @@ use([
|
||||
DataZoomComponent
|
||||
])
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const { locale } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
const realtime = ref<Realtime | null>(null)
|
||||
const systemInfo = ref<SystemInfo | null>(null)
|
||||
@@ -46,6 +46,13 @@ const countInfo = ref<CountInfo>({
|
||||
const nets = ref<Array<string>>([]) // 选择的网卡
|
||||
const disks = ref<Array<string>>([]) // 选择的硬盘
|
||||
const chartType = ref('net')
|
||||
const unitType = ref('KB')
|
||||
const units = [
|
||||
{ label: 'B', value: 'B' },
|
||||
{ label: 'KB', value: 'KB' },
|
||||
{ label: 'MB', value: 'MB' },
|
||||
{ label: 'GB', value: 'GB' }
|
||||
]
|
||||
|
||||
const cores = ref(0)
|
||||
const diskReadBytes = ref<Array<number>>([])
|
||||
@@ -69,7 +76,8 @@ const current = reactive({
|
||||
diskRWBytes: 0,
|
||||
diskRWTime: 0,
|
||||
netBytesSent: 0,
|
||||
netBytesRecv: 0
|
||||
netBytesRecv: 0,
|
||||
time: 0
|
||||
})
|
||||
|
||||
const chartDisk = computed(() => {
|
||||
@@ -89,7 +97,7 @@ const chartDisk = computed(() => {
|
||||
formatter: function (params: any) {
|
||||
let res = params[0].name + '<br/>'
|
||||
params.forEach(function (item: any) {
|
||||
res += `${item.marker} ${item.seriesName}: ${item.value} MB<br/>`
|
||||
res += `${item.marker} ${item.seriesName}: ${item.value} ${unitType.value}<br/>`
|
||||
})
|
||||
return res
|
||||
}
|
||||
@@ -104,10 +112,10 @@ const chartDisk = computed(() => {
|
||||
data: timeDiskData.value
|
||||
},
|
||||
yAxis: {
|
||||
name: '单位 MB',
|
||||
name: `单位 ${unitType.value}`,
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value} MB'
|
||||
formatter: `{value} ${unitType.value}`
|
||||
}
|
||||
},
|
||||
series: [
|
||||
@@ -145,94 +153,110 @@ const chartDisk = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const getCurrent = async () => {
|
||||
const { data } = await dashboard.current(nets.value, disks.value)
|
||||
data.percent = formatPercent(data.percent)
|
||||
data.mem.usedPercent = formatPercent(data.mem.usedPercent)
|
||||
// 计算 CPU 核心数
|
||||
if (cores.value == 0) {
|
||||
for (let i = 0; i < data.cpus.length; i++) {
|
||||
cores.value += data.cpus[i].cores
|
||||
}
|
||||
}
|
||||
// 计算实时数据
|
||||
let netTotalSentTemp = 0
|
||||
let netTotalRecvTemp = 0
|
||||
for (let i = 0; i < data.net.length; i++) {
|
||||
if (data.net[i].name === 'lo') {
|
||||
continue
|
||||
}
|
||||
netTotalSentTemp += data.net[i].bytesSent
|
||||
netTotalRecvTemp += data.net[i].bytesRecv
|
||||
}
|
||||
current.netBytesSent = total.netBytesSent != 0 ? (netTotalSentTemp - total.netBytesSent) / 3 : 0
|
||||
current.netBytesRecv = total.netBytesRecv != 0 ? (netTotalRecvTemp - total.netBytesRecv) / 3 : 0
|
||||
total.netBytesSent = netTotalSentTemp
|
||||
total.netBytesRecv = netTotalRecvTemp
|
||||
// 计算硬盘读写
|
||||
let diskTotalReadTemp = 0
|
||||
let diskTotalWriteTemp = 0
|
||||
let diskRWTimeTemp = 0
|
||||
for (let i = 0; i < data.disk_io.length; i++) {
|
||||
diskTotalReadTemp += data.disk_io[i].readBytes
|
||||
diskTotalWriteTemp += data.disk_io[i].writeBytes
|
||||
diskRWTimeTemp += data.disk_io[i].readTime + data.disk_io[i].writeTime
|
||||
}
|
||||
current.diskReadBytes =
|
||||
total.diskReadBytes != 0 ? (diskTotalReadTemp - total.diskReadBytes) / 3 : 0
|
||||
current.diskWriteBytes =
|
||||
total.diskWriteBytes != 0 ? (diskTotalWriteTemp - total.diskWriteBytes) / 3 : 0
|
||||
current.diskRWBytes =
|
||||
total.diskRWBytes != 0 ? (diskTotalReadTemp + diskTotalWriteTemp - total.diskRWBytes) / 3 : 0
|
||||
current.diskRWTime =
|
||||
total.diskRWTime != 0 ? Number(((diskRWTimeTemp - total.diskRWTime) / 3).toFixed(2)) : 0
|
||||
total.diskReadBytes = diskTotalReadTemp
|
||||
total.diskWriteBytes = diskTotalWriteTemp
|
||||
total.diskRWBytes = diskTotalReadTemp + diskTotalWriteTemp
|
||||
total.diskRWTime = diskRWTimeTemp
|
||||
let isFetching = false
|
||||
|
||||
// 图表数据填充
|
||||
netBytesSent.value.push(Number((current.netBytesSent / 1024 / 1024).toFixed(2)))
|
||||
if (netBytesSent.value.length > 20) {
|
||||
netBytesSent.value.splice(0, 1)
|
||||
}
|
||||
netBytesRecv.value.push(Number((current.netBytesRecv / 1024 / 1024).toFixed(2)))
|
||||
if (netBytesRecv.value.length > 20) {
|
||||
netBytesRecv.value.splice(0, 1)
|
||||
}
|
||||
diskReadBytes.value.push(Number((current.diskReadBytes / 1024 / 1024).toFixed(2)))
|
||||
if (diskReadBytes.value.length > 20) {
|
||||
diskReadBytes.value.splice(0, 1)
|
||||
}
|
||||
diskWriteBytes.value.push(Number((current.diskWriteBytes / 1024 / 1024).toFixed(2)))
|
||||
if (diskWriteBytes.value.length > 20) {
|
||||
diskWriteBytes.value.splice(0, 1)
|
||||
}
|
||||
timeDiskData.value.push(formatDateTime(data.time))
|
||||
if (timeDiskData.value.length > 20) {
|
||||
timeDiskData.value.splice(0, 1)
|
||||
}
|
||||
timeNetData.value.push(formatDateTime(data.time))
|
||||
if (timeNetData.value.length > 20) {
|
||||
timeNetData.value.splice(0, 1)
|
||||
}
|
||||
const fetchCurrent = async () => {
|
||||
if (isFetching) return
|
||||
isFetching = true
|
||||
dashboard
|
||||
.current(nets.value, disks.value)
|
||||
.then(({ data }) => {
|
||||
data.percent = formatPercent(data.percent)
|
||||
data.mem.usedPercent = formatPercent(data.mem.usedPercent)
|
||||
// 计算 CPU 核心数
|
||||
if (cores.value == 0) {
|
||||
for (let i = 0; i < data.cpus.length; i++) {
|
||||
cores.value += data.cpus[i].cores
|
||||
}
|
||||
}
|
||||
// 计算实时数据
|
||||
let time = current.time == 0 ? 3 : toTimestamp(data.time) - current.time
|
||||
let netTotalSentTemp = 0
|
||||
let netTotalRecvTemp = 0
|
||||
for (let i = 0; i < data.net.length; i++) {
|
||||
if (data.net[i].name === 'lo') {
|
||||
continue
|
||||
}
|
||||
netTotalSentTemp += data.net[i].bytesSent
|
||||
netTotalRecvTemp += data.net[i].bytesRecv
|
||||
}
|
||||
current.netBytesSent =
|
||||
total.netBytesSent != 0 ? (netTotalSentTemp - total.netBytesSent) / time : 0
|
||||
current.netBytesRecv =
|
||||
total.netBytesRecv != 0 ? (netTotalRecvTemp - total.netBytesRecv) / time : 0
|
||||
total.netBytesSent = netTotalSentTemp
|
||||
total.netBytesRecv = netTotalRecvTemp
|
||||
// 计算硬盘读写
|
||||
let diskTotalReadTemp = 0
|
||||
let diskTotalWriteTemp = 0
|
||||
let diskRWTimeTemp = 0
|
||||
for (let i = 0; i < data.disk_io.length; i++) {
|
||||
diskTotalReadTemp += data.disk_io[i].readBytes
|
||||
diskTotalWriteTemp += data.disk_io[i].writeBytes
|
||||
diskRWTimeTemp += data.disk_io[i].readTime + data.disk_io[i].writeTime
|
||||
}
|
||||
current.diskReadBytes =
|
||||
total.diskReadBytes != 0 ? (diskTotalReadTemp - total.diskReadBytes) / time : 0
|
||||
current.diskWriteBytes =
|
||||
total.diskWriteBytes != 0 ? (diskTotalWriteTemp - total.diskWriteBytes) / time : 0
|
||||
current.diskRWBytes =
|
||||
total.diskRWBytes != 0
|
||||
? (diskTotalReadTemp + diskTotalWriteTemp - total.diskRWBytes) / time
|
||||
: 0
|
||||
current.diskRWTime =
|
||||
total.diskRWTime != 0 ? Number(((diskRWTimeTemp - total.diskRWTime) / time).toFixed(2)) : 0
|
||||
current.time = toTimestamp(data.time)
|
||||
total.diskReadBytes = diskTotalReadTemp
|
||||
total.diskWriteBytes = diskTotalWriteTemp
|
||||
total.diskRWBytes = diskTotalReadTemp + diskTotalWriteTemp
|
||||
total.diskRWTime = diskRWTimeTemp
|
||||
|
||||
realtime.value = data
|
||||
// 图表数据填充
|
||||
netBytesSent.value.push(calculateSize(current.netBytesSent))
|
||||
if (netBytesSent.value.length > 20) {
|
||||
netBytesSent.value.splice(0, 1)
|
||||
}
|
||||
netBytesRecv.value.push(calculateSize(current.netBytesRecv))
|
||||
if (netBytesRecv.value.length > 20) {
|
||||
netBytesRecv.value.splice(0, 1)
|
||||
}
|
||||
diskReadBytes.value.push(calculateSize(current.diskReadBytes))
|
||||
if (diskReadBytes.value.length > 20) {
|
||||
diskReadBytes.value.splice(0, 1)
|
||||
}
|
||||
diskWriteBytes.value.push(calculateSize(current.diskWriteBytes))
|
||||
if (diskWriteBytes.value.length > 20) {
|
||||
diskWriteBytes.value.splice(0, 1)
|
||||
}
|
||||
timeDiskData.value.push(formatDateTime(data.time))
|
||||
if (timeDiskData.value.length > 20) {
|
||||
timeDiskData.value.splice(0, 1)
|
||||
}
|
||||
timeNetData.value.push(formatDateTime(data.time))
|
||||
if (timeNetData.value.length > 20) {
|
||||
timeNetData.value.splice(0, 1)
|
||||
}
|
||||
|
||||
realtime.value = data
|
||||
})
|
||||
.finally(() => {
|
||||
isFetching = false
|
||||
})
|
||||
}
|
||||
|
||||
const getSystemInfo = async () => {
|
||||
const fetchSystemInfo = async () => {
|
||||
dashboard.systemInfo().then((res) => {
|
||||
systemInfo.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const getCountInfo = async () => {
|
||||
const fetchCountInfo = async () => {
|
||||
dashboard.countInfo().then((res) => {
|
||||
countInfo.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const getHomeApps = async () => {
|
||||
const fetchHomeApps = async () => {
|
||||
homeAppsLoading.value = true
|
||||
dashboard.homeApps().then((res) => {
|
||||
homeApps.value = res.data
|
||||
@@ -242,9 +266,9 @@ const getHomeApps = async () => {
|
||||
|
||||
const handleRestartPanel = () => {
|
||||
clearInterval(homeInterval)
|
||||
window.$message.loading(t('homeIndex.system.restart.loading'))
|
||||
window.$message.loading('面板重启中...')
|
||||
dashboard.restart().then(() => {
|
||||
window.$message.success(t('homeIndex.system.restart.success'))
|
||||
window.$message.success('面板重启成功')
|
||||
setTimeout(() => {
|
||||
appStore.reloadPage()
|
||||
}, 3000)
|
||||
@@ -256,7 +280,7 @@ const handleUpdate = () => {
|
||||
if (res.data.update) {
|
||||
router.push({ name: 'home-update' })
|
||||
} else {
|
||||
window.$message.success(t('homeIndex.system.update.success'))
|
||||
window.$message.success('当前已是最新版本')
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -277,6 +301,21 @@ const handleManageApp = (slug: string) => {
|
||||
router.push({ name: 'apps-' + slug + '-index' })
|
||||
}
|
||||
|
||||
const calculateSize = (bytes: any) => {
|
||||
switch (unitType.value) {
|
||||
case 'B':
|
||||
return Number(bytes.toFixed(2))
|
||||
case 'KB':
|
||||
return Number((bytes / 1024).toFixed(2))
|
||||
case 'MB':
|
||||
return Number((bytes / 1024 / 1024).toFixed(2))
|
||||
case 'GB':
|
||||
return Number((bytes / 1024 / 1024 / 1024).toFixed(2))
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const clearCurrent = () => {
|
||||
total.netBytesSent = 0
|
||||
total.netBytesRecv = 0
|
||||
@@ -299,13 +338,12 @@ const quantifier = computed(() => {
|
||||
let homeInterval: any = null
|
||||
|
||||
onMounted(() => {
|
||||
getCurrent()
|
||||
getSystemInfo()
|
||||
getCountInfo()
|
||||
getHomeApps()
|
||||
fetchCurrent()
|
||||
fetchSystemInfo()
|
||||
fetchCountInfo()
|
||||
fetchHomeApps()
|
||||
homeInterval = setInterval(() => {
|
||||
getCurrent()
|
||||
getSystemInfo()
|
||||
fetchCurrent()
|
||||
}, 3000)
|
||||
})
|
||||
|
||||
@@ -329,37 +367,29 @@ if (import.meta.hot) {
|
||||
<n-page-header :subtitle="systemInfo?.panel_version">
|
||||
<n-grid :cols="4" pb-10>
|
||||
<n-gi>
|
||||
<n-statistic
|
||||
:label="$t('homeIndex.website')"
|
||||
:value="countInfo.website + quantifier"
|
||||
/>
|
||||
<n-statistic label="网站" :value="countInfo.website + quantifier" />
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-statistic
|
||||
:label="$t('homeIndex.database')"
|
||||
:value="countInfo.database + quantifier"
|
||||
/>
|
||||
<n-statistic label="数据库" :value="countInfo.database + quantifier" />
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-statistic label="FTP" :value="countInfo.ftp + quantifier" />
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-statistic :label="$t('homeIndex.cron')" :value="countInfo.cron + quantifier" />
|
||||
<n-statistic label="计划任务" :value="countInfo.cron + quantifier" />
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<template #title>{{ $t('name') }}</template>
|
||||
<template #title>耗子面板</template>
|
||||
<template #extra>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="toSponsor">
|
||||
{{ $t('homeIndex.sponsor') }}
|
||||
</n-button>
|
||||
<n-button @click="toGit">{{ $t('homeIndex.git') }}</n-button>
|
||||
<n-button type="primary" @click="toSponsor"> 赞助支持 </n-button>
|
||||
<n-button @click="toGit">开源地址</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-page-header>
|
||||
</n-card>
|
||||
|
||||
<n-card :segmented="true" rounded-10 size="small" :title="$t('homeIndex.resources.title')">
|
||||
<n-card :segmented="true" rounded-10 size="small" title="资源总览">
|
||||
<n-flex v-if="realtime" size="large">
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
@@ -525,11 +555,11 @@ if (import.meta.hot) {
|
||||
</template>
|
||||
<n-table :single-line="false">
|
||||
<tr>
|
||||
<th>{{ $t('homeIndex.store.columns.path') }}</th>
|
||||
<th>挂载点</th>
|
||||
<td>{{ item.path }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ $t('homeIndex.store.columns.type') }}</th>
|
||||
<th>文件系统</th>
|
||||
<td>{{ item.fstype }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -561,61 +591,113 @@ if (import.meta.hot) {
|
||||
responsive="screen"
|
||||
>
|
||||
<n-gi>
|
||||
<n-card
|
||||
:segmented="true"
|
||||
rounded-10
|
||||
size="small"
|
||||
:title="$t('homeIndex.apps.title')"
|
||||
min-h-375
|
||||
>
|
||||
<n-grid
|
||||
v-if="!homeAppsLoading"
|
||||
x-gap="12"
|
||||
y-gap="12"
|
||||
cols="3 s:1 m:2 l:3"
|
||||
item-responsive
|
||||
responsive="screen"
|
||||
>
|
||||
<n-gi v-for="item in homeApps" :key="item.name">
|
||||
<n-card
|
||||
:segmented="true"
|
||||
size="small"
|
||||
cursor-pointer
|
||||
rounded-10
|
||||
hover:card-shadow
|
||||
@click="handleManageApp(item.slug)"
|
||||
<n-flex vertical>
|
||||
<n-card :segmented="true" size="small" title="快捷应用" min-h-280 rounded-10>
|
||||
<n-scrollbar max-h-210>
|
||||
<n-grid
|
||||
v-if="!homeAppsLoading"
|
||||
x-gap="12"
|
||||
y-gap="12"
|
||||
cols="3 s:1 m:2 l:3"
|
||||
item-responsive
|
||||
responsive="screen"
|
||||
>
|
||||
<n-space>
|
||||
<n-thing>
|
||||
<template #avatar>
|
||||
<n-avatar class="mt-4">
|
||||
<n-icon>
|
||||
<icon-mdi:package-variant-closed />
|
||||
</n-icon>
|
||||
</n-avatar>
|
||||
</template>
|
||||
<template #header>
|
||||
{{ item.name }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{ item.version }}
|
||||
</template>
|
||||
</n-thing>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-text v-if="!homeAppsLoading && !homeApps"> 您还没有设置任何应用在此显示! </n-text>
|
||||
<n-skeleton v-if="homeAppsLoading" text :repeat="9" />
|
||||
</n-card>
|
||||
<n-gi v-for="item in homeApps" :key="item.name">
|
||||
<n-card
|
||||
:segmented="true"
|
||||
size="small"
|
||||
cursor-pointer
|
||||
rounded-10
|
||||
hover:card-shadow
|
||||
@click="handleManageApp(item.slug)"
|
||||
>
|
||||
<n-space>
|
||||
<n-thing>
|
||||
<template #avatar>
|
||||
<n-avatar class="mt-4">
|
||||
<TheIcon :size="24" icon="mdi:package-variant-closed" />
|
||||
</n-avatar>
|
||||
</template>
|
||||
<template #header>
|
||||
{{ item.name }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{ item.version }}
|
||||
</template>
|
||||
</n-thing>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-scrollbar>
|
||||
<n-text v-if="!homeAppsLoading && !homeApps">
|
||||
您还没有设置任何应用在此显示!
|
||||
</n-text>
|
||||
<n-skeleton v-if="homeAppsLoading" text :repeat="10" />
|
||||
</n-card>
|
||||
<n-card :segmented="true" rounded-10 size="small" title="系统信息">
|
||||
<n-table v-if="systemInfo" :single-line="false">
|
||||
<tr>
|
||||
<th>主机名</th>
|
||||
<td>
|
||||
{{ systemInfo?.hostname || '加载中...' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>面板版本</th>
|
||||
<td>
|
||||
{{ systemInfo?.panel_version || '加载中...' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>系统版本</th>
|
||||
<td>
|
||||
{{ `${systemInfo?.os_name} ${systemInfo?.kernel_arch}` || '加载中...' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>内核版本</th>
|
||||
<td>
|
||||
{{ systemInfo?.kernel_version || '加载中...' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>运行时间</th>
|
||||
<td>
|
||||
{{ formatDuration(Number(systemInfo?.uptime)) || '加载中...' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>操作</th>
|
||||
<td>
|
||||
<n-space>
|
||||
<n-popconfirm @positive-click="handleRestartPanel">
|
||||
<template #trigger>
|
||||
<n-button type="warning" size="small">
|
||||
<TheIcon :size="20" class="mr-5" icon="mdi:restart" />
|
||||
重启面板
|
||||
</n-button>
|
||||
</template>
|
||||
确定要重启面板吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="success" @click="handleUpdate" size="small">
|
||||
<TheIcon
|
||||
:size="20"
|
||||
class="mr-5"
|
||||
icon="mdi:arrow-up-bold-circle-outline"
|
||||
/>
|
||||
检查更新
|
||||
</n-button>
|
||||
</n-space>
|
||||
</td>
|
||||
</tr>
|
||||
</n-table>
|
||||
<n-skeleton v-else text :repeat="10" />
|
||||
</n-card>
|
||||
</n-flex>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-card
|
||||
:segmented="true"
|
||||
rounded-10
|
||||
size="small"
|
||||
:title="$t('homeIndex.traffic.title')"
|
||||
>
|
||||
<n-card :segmented="true" rounded-10 size="small" title="实时监控">
|
||||
<n-flex vertical v-if="systemInfo">
|
||||
<n-form
|
||||
inline
|
||||
@@ -629,7 +711,15 @@ if (import.meta.hot) {
|
||||
<n-radio-button value="disk" label="硬盘" />
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="chartType == 'net'" label="网卡" ml-auto>
|
||||
<n-form-item label="单位" ml-auto>
|
||||
<n-select
|
||||
v-model:value="unitType"
|
||||
:options="units"
|
||||
@update-value="clearCurrent"
|
||||
w-80
|
||||
></n-select>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="chartType == 'net'" label="网卡">
|
||||
<n-select
|
||||
multiple
|
||||
v-model:value="nets"
|
||||
@@ -638,7 +728,7 @@ if (import.meta.hot) {
|
||||
w-200
|
||||
></n-select>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="chartType == 'disk'" label="硬盘" ml-auto>
|
||||
<n-form-item v-if="chartType == 'disk'" label="硬盘">
|
||||
<n-select
|
||||
multiple
|
||||
v-model:value="disks"
|
||||
@@ -664,60 +754,10 @@ if (import.meta.hot) {
|
||||
<v-chart class="chart" :option="chartDisk" autoresize />
|
||||
</n-card>
|
||||
</n-flex>
|
||||
<n-skeleton v-else text :repeat="10" />
|
||||
<n-skeleton v-else text :repeat="24" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-card :segmented="true" rounded-10 size="small" :title="$t('homeIndex.system.title')">
|
||||
<n-table :single-line="false">
|
||||
<tr>
|
||||
<th>{{ $t('homeIndex.system.columns.os') }}</th>
|
||||
<td>
|
||||
{{ systemInfo?.os_name || $t('homeIndex.system.columns.loading') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ $t('homeIndex.system.columns.panel') }}</th>
|
||||
<td>
|
||||
{{ systemInfo?.panel_version || $t('homeIndex.system.columns.loading') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ $t('homeIndex.system.columns.uptime') }}</th>
|
||||
<td>
|
||||
{{
|
||||
formatDuration(Number(systemInfo?.uptime)) ||
|
||||
$t('homeIndex.system.columns.loading')
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ $t('homeIndex.system.columns.operate') }}</th>
|
||||
<td>
|
||||
<n-space>
|
||||
<n-popconfirm @positive-click="handleRestartPanel">
|
||||
<template #trigger>
|
||||
<n-button type="warning">
|
||||
<n-icon size="20">
|
||||
<icon-mdi:restart />
|
||||
</n-icon>
|
||||
{{ $t('homeIndex.system.restart.label') }}
|
||||
</n-button>
|
||||
</template>
|
||||
{{ $t('homeIndex.system.restart.confirm') }}
|
||||
</n-popconfirm>
|
||||
<n-button type="success" @click="handleUpdate">
|
||||
<n-icon size="20">
|
||||
<icon-mdi:arrow-up-bold-circle-outline />
|
||||
</n-icon>
|
||||
{{ $t('homeIndex.system.update.label') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</td>
|
||||
</tr>
|
||||
</n-table>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</div>
|
||||
</AppPage>
|
||||
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
path: 'home',
|
||||
component: () => import('./IndexView.vue'),
|
||||
meta: {
|
||||
title: 'homeIndex.title',
|
||||
title: '仪表盘',
|
||||
icon: 'mdi:monitor-dashboard',
|
||||
role: ['admin'],
|
||||
requireAuth: true
|
||||
|
||||
Reference in New Issue
Block a user