fix26.2: полная UI-полировка peer/dashboard и завершение i18n

This commit is contained in:
2026-05-09 01:38:57 +05:00
parent c13841b61a
commit db3c88c58a
42 changed files with 421 additions and 198 deletions
+71 -15
View File
@@ -29,27 +29,48 @@
/>
<el-row :gutter="10" class="mt-2">
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">CPU: {{ summary.system.cpuPercent }}%</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">RAM: {{ summary.system.memPercent }}%</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">Disk: {{ summary.system.diskPercent }}%</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">Peers: {{ summary.peers.total }}</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">Online peers: {{ summary.peers.onlinePeers }}</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">Online devices: {{ summary.peers.onlineDevices }}</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">Today download: {{ formatBytes(summary.traffic.todayDownloadBytes || 0) }}</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">Today upload: {{ formatBytes(summary.traffic.todayUploadBytes || 0) }}</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.cpu") }}: {{ summary.system.cpuPercent }}%</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.ram") }}: {{ summary.system.memPercent }}%</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.disk") }}: {{ summary.system.diskPercent }}%</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.peers") }}: {{ summary.peers.total }}</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.onlinePeers") }}: {{ summary.peers.onlinePeers }}</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.onlineDevices") }}: {{ summary.peers.onlineDevices }}</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.todayDownload") }}: {{ formatBytes(summary.traffic.todayDownloadBytes || 0) }}</el-card></el-col>
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.todayUpload") }}: {{ formatBytes(summary.traffic.todayUploadBytes || 0) }}</el-card></el-col>
</el-row>
<el-card shadow="never" class="mt-3">
<template #header>
<div class="chart-header">
<span>{{ $t("dashboard.trafficChart") }}</span>
<el-radio-group v-model="range" size="small" @change="loadDashboard">
<el-radio-button label="1h">1h</el-radio-button>
<el-radio-button label="24h">24h</el-radio-button>
<el-radio-button label="7d">7d</el-radio-button>
<el-radio-button label="30d">30d</el-radio-button>
</el-radio-group>
</div>
</template>
<el-table :data="timeseriesRows" size="small" v-loading="timeseriesLoading">
<el-table-column prop="ts" :label="$t('common.createTime')" width="180" />
<el-table-column prop="download" :label="$t('dashboard.download')" />
<el-table-column prop="upload" :label="$t('dashboard.upload')" />
<el-table-column prop="cpu" :label="$t('dashboard.cpu')" width="120" />
<el-table-column prop="mem" :label="$t('dashboard.ram')" width="120" />
</el-table>
</el-card>
<el-card shadow="never" class="mt-3">
<template #header>{{ $t("dashboard.topPeers24h") }}</template>
<el-table :data="topPeers" size="small">
<el-table-column prop="name" label="Peer" />
<el-table-column prop="download" label="Download">
<el-table-column prop="name" :label="$t('peer.name')" />
<el-table-column prop="download" :label="$t('dashboard.download')">
<template #default="scope">{{ formatBytes(scope.row.download || 0) }}</template>
</el-table-column>
<el-table-column prop="upload" label="Upload">
<el-table-column prop="upload" :label="$t('dashboard.upload')">
<template #default="scope">{{ formatBytes(scope.row.upload || 0) }}</template>
</el-table-column>
<el-table-column prop="total" label="Total">
<el-table-column prop="total" :label="$t('dashboard.total')">
<template #default="scope">{{ formatBytes(scope.row.total || 0) }}</template>
</el-table-column>
</el-table>
@@ -60,9 +81,10 @@
<script setup lang="ts">
import { useIntervalFn } from "@vueuse/core";
import { useI18n } from "vue-i18n";
import { dashboardSecurityApi, dashboardSummaryApi, dashboardTopPeersApi } from "@/api/dashboard";
import { DashboardSummaryVo, DashboardTopPeerVo, SecurityRiskVo } from "@/api/dashboard/types";
import { dashboardSecurityApi, dashboardSummaryApi, dashboardTimeseriesApi, dashboardTopPeersApi } from "@/api/dashboard";
import { DashboardSummaryVo, DashboardTimeseriesVo, DashboardTopPeerVo, SecurityRiskVo } from "@/api/dashboard/types";
import { formatBytes } from "@/utils/byte";
import { timestampToDateTime } from "@/utils/time";
const { t } = useI18n();
@@ -80,6 +102,9 @@ const summary = ref<DashboardSummaryVo>({
});
const topPeers = ref<DashboardTopPeerVo[]>([]);
const securityRisks = ref<SecurityRiskVo[]>([]);
const timeseries = ref<DashboardTimeseriesVo>({ range: "24h", traffic: [], system: [], collectedAt: 0 });
const timeseriesLoading = ref(false);
const range = ref("24h");
const loadError = ref("");
const loading = ref(false);
const lastSuccessAt = ref(0);
@@ -102,7 +127,7 @@ const loadDashboard = async () => {
try {
const [summaryRes, topRes, secRes] = await Promise.all([
dashboardSummaryApi(),
dashboardTopPeersApi("24h", 10),
dashboardTopPeersApi(range.value, 10),
dashboardSecurityApi(),
]);
summary.value = summaryRes.data;
@@ -115,8 +140,33 @@ const loadDashboard = async () => {
} finally {
loading.value = false;
}
timeseriesLoading.value = true;
try {
const tsRes = await dashboardTimeseriesApi(range.value);
timeseries.value = tsRes.data;
} finally {
timeseriesLoading.value = false;
}
};
const timeseriesRows = computed(() => {
const trafficMap: Record<number, { download: number; upload: number }> = {};
for (const t of timeseries.value.traffic || []) {
trafficMap[t.ts] = { download: t.download || 0, upload: t.upload || 0 };
}
return (timeseries.value.system || []).map((s) => {
const tm = trafficMap[s.ts] || { download: 0, upload: 0 };
return {
ts: timestampToDateTime(s.ts),
download: formatBytes(tm.download),
upload: formatBytes(tm.upload),
cpu: `${Math.round((s.cpu || 0) * 10) / 10}%`,
mem: `${Math.round((s.mem || 0) * 10) / 10}%`,
};
});
});
const { pause: stopPolling, resume: startPolling } = useIntervalFn(
() => {
loadDashboard();
@@ -155,5 +205,11 @@ onUnmounted(() => {
display: flex;
justify-content: flex-end;
}
.chart-header {
display: flex;
align-items: center;
justify-content: space-between;
}
</style>