diff --git a/web/src/i18n/en.json b/web/src/i18n/en.json index 567aacefff..694e2889fe 100644 --- a/web/src/i18n/en.json +++ b/web/src/i18n/en.json @@ -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", diff --git a/web/src/i18n/zh_CN.json b/web/src/i18n/zh_CN.json index fa958f79da..3b3b3a882a 100644 --- a/web/src/i18n/zh_CN.json +++ b/web/src/i18n/zh_CN.json @@ -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": "正在加载更新信息,稍等片刻", diff --git a/web/src/utils/common/common.ts b/web/src/utils/common/common.ts index 1a89f4ac4d..56b27a1885 100644 --- a/web/src/utils/common/common.ts +++ b/web/src/utils/common/common.ts @@ -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' diff --git a/web/src/views/home/IndexView.vue b/web/src/views/home/IndexView.vue index 51b8db1713..c03de4daf2 100644 --- a/web/src/views/home/IndexView.vue +++ b/web/src/views/home/IndexView.vue @@ -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(null) const systemInfo = ref(null) @@ -46,6 +46,13 @@ const countInfo = ref({ const nets = ref>([]) // 选择的网卡 const disks = ref>([]) // 选择的硬盘 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>([]) @@ -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 + '
' params.forEach(function (item: any) { - res += `${item.marker} ${item.seriesName}: ${item.value} MB
` + res += `${item.marker} ${item.seriesName}: ${item.value} ${unitType.value}
` }) 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) { - + - + - + - + - + - {{ $t('homeIndex.store.columns.path') }} + 挂载点 {{ item.path }} - {{ $t('homeIndex.store.columns.type') }} + 文件系统 {{ item.fstype }} @@ -561,61 +591,113 @@ if (import.meta.hot) { responsive="screen" > - - - - + + + - - - - - - - - - - - 您还没有设置任何应用在此显示! - - + + + + + + + + + + + + + + + 您还没有设置任何应用在此显示! + + + + + + + 主机名 + + {{ systemInfo?.hostname || '加载中...' }} + + + + 面板版本 + + {{ systemInfo?.panel_version || '加载中...' }} + + + + 系统版本 + + {{ `${systemInfo?.os_name} ${systemInfo?.kernel_arch}` || '加载中...' }} + + + + 内核版本 + + {{ systemInfo?.kernel_version || '加载中...' }} + + + + 运行时间 + + {{ formatDuration(Number(systemInfo?.uptime)) || '加载中...' }} + + + + 操作 + + + + + 确定要重启面板吗? + + + + 检查更新 + + + + + + + + - + - + + + + - + - + - - - - - {{ $t('homeIndex.system.columns.os') }} - - {{ systemInfo?.os_name || $t('homeIndex.system.columns.loading') }} - - - - {{ $t('homeIndex.system.columns.panel') }} - - {{ systemInfo?.panel_version || $t('homeIndex.system.columns.loading') }} - - - - {{ $t('homeIndex.system.columns.uptime') }} - - {{ - formatDuration(Number(systemInfo?.uptime)) || - $t('homeIndex.system.columns.loading') - }} - - - - {{ $t('homeIndex.system.columns.operate') }} - - - - - {{ $t('homeIndex.system.restart.confirm') }} - - - - - - {{ $t('homeIndex.system.update.label') }} - - - - - - diff --git a/web/src/views/home/route.ts b/web/src/views/home/route.ts index acdcadb77a..8de43db7ed 100644 --- a/web/src/views/home/route.ts +++ b/web/src/views/home/route.ts @@ -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