style: прогнать prettier по frontend

This commit is contained in:
2026-05-21 15:52:43 +05:00
parent b0cdf6207c
commit 95b8d97ccf
63 changed files with 1517 additions and 863 deletions
@@ -18,7 +18,9 @@
<el-input v-model="form.newPassword" show-password />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="submit">{{ $t("common.confirm") }}</el-button>
<el-button type="primary" :loading="saving" @click="submit">{{
$t("common.confirm")
}}</el-button>
</el-form-item>
</el-form>
</el-card>
@@ -47,12 +49,28 @@ const form = reactive({
const passwordPattern = /^[a-zA-Z0-9!@#$%^&*()_+-=]{6,64}$/;
const rules: FormRules = {
oldPassword: [
{ required: true, message: t("common.required"), trigger: ["change", "blur"] },
{ pattern: passwordPattern, message: t("common.invalid"), trigger: ["change", "blur"] },
{
required: true,
message: t("common.required"),
trigger: ["change", "blur"],
},
{
pattern: passwordPattern,
message: t("common.invalid"),
trigger: ["change", "blur"],
},
],
newPassword: [
{ required: true, message: t("common.required"), trigger: ["change", "blur"] },
{ pattern: passwordPattern, message: t("common.invalid"), trigger: ["change", "blur"] },
{
required: true,
message: t("common.required"),
trigger: ["change", "blur"],
},
{
pattern: passwordPattern,
message: t("common.invalid"),
trigger: ["change", "blur"],
},
],
};
@@ -66,10 +84,14 @@ const submit = async () => {
}
saving.value = true;
try {
await adminChangePasswordApi({ oldPassword: form.oldPassword, newPassword: form.newPassword });
await adminChangePasswordApi({
oldPassword: form.oldPassword,
newPassword: form.newPassword,
});
adminStore.forcePasswordChange = false;
ElMessage.success(t("admin.changedSuccess"));
const redirect = typeof route.query.redirect === "string" ? route.query.redirect : "/";
const redirect =
typeof route.query.redirect === "string" ? route.query.redirect : "/";
router.push(redirect);
} finally {
saving.value = false;
@@ -82,4 +104,3 @@ const submit = async () => {
max-width: 760px;
}
</style>
@@ -84,7 +84,12 @@
/>
</el-form-item>
<el-form-item :label="$t('config.huiHttps')" prop="huiHttps">
<el-select v-model="huiHttps" style="width: 50%" ref="huiHttpsRef" disabled>
<el-select
v-model="huiHttps"
style="width: 50%"
ref="huiHttpsRef"
disabled
>
<el-option
v-for="item in huiHttpsList"
:key="item.key"
@@ -165,11 +170,7 @@ import {
updateConfigsApi,
} from "@/api/config";
import { ConfigsUpdateDto } from "@/api/config/types";
import {
UploadFile,
UploadRawFile,
UploadRequestOptions,
} from "element-plus";
import { UploadFile, UploadRawFile, UploadRequestOptions } from "element-plus";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
+136 -26
View File
@@ -1,7 +1,9 @@
<template>
<div class="dashboard-container">
<div class="dashboard-actions mb-2">
<el-button size="small" @click="loadDashboard">{{ $t("common.refresh") }}</el-button>
<el-button size="small" @click="loadDashboard">{{
$t("common.refresh")
}}</el-button>
</div>
<el-alert
@@ -23,20 +25,63 @@
v-for="risk in securityRisks"
:key="risk.key"
:title="$t(risk.key)"
:type="risk.severity === 'critical' ? 'error' : risk.severity === 'warning' ? 'warning' : 'info'"
:type="
risk.severity === 'critical'
? 'error'
: risk.severity === 'warning'
? 'warning'
: 'info'
"
:closable="risk.dismissible"
class="mb-2"
/>
<el-row :gutter="10" class="mt-2">
<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-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">
@@ -62,17 +107,25 @@
</el-card>
<el-card shadow="never" class="mt-3">
<template #header>{{ $t("dashboard.topPeersRange", { range }) }}</template>
<template #header>{{
$t("dashboard.topPeersRange", { range })
}}</template>
<el-table :data="topPeers" size="small">
<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>
<template #default="scope">{{
formatBytes(scope.row.download || 0)
}}</template>
</el-table-column>
<el-table-column prop="upload" :label="$t('dashboard.upload')">
<template #default="scope">{{ formatBytes(scope.row.upload || 0) }}</template>
<template #default="scope">{{
formatBytes(scope.row.upload || 0)
}}</template>
</el-table-column>
<el-table-column prop="total" :label="$t('dashboard.total')">
<template #default="scope">{{ formatBytes(scope.row.total || 0) }}</template>
<template #default="scope">{{
formatBytes(scope.row.total || 0)
}}</template>
</el-table-column>
</el-table>
</el-card>
@@ -93,20 +146,61 @@ import {
TitleComponent,
} from "echarts/components";
import VChart from "vue-echarts";
import { dashboardSecurityApi, dashboardSummaryApi, dashboardTimeseriesApi, dashboardTopPeersApi } from "@/api/dashboard";
import { DashboardSummaryVo, DashboardTimeseriesVo, 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";
use([CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent, DataZoomComponent, TitleComponent]);
use([
CanvasRenderer,
LineChart,
GridComponent,
TooltipComponent,
LegendComponent,
DataZoomComponent,
TitleComponent,
]);
const { t } = useI18n();
const summary = ref<DashboardSummaryVo>({
collectedAt: 0,
system: { cpuPercent: 0, memUsedBytes: 0, memTotalBytes: 0, memPercent: 0, diskUsedBytes: 0, diskTotalBytes: 0, diskPercent: 0 },
system: {
cpuPercent: 0,
memUsedBytes: 0,
memTotalBytes: 0,
memPercent: 0,
diskUsedBytes: 0,
diskTotalBytes: 0,
diskPercent: 0,
},
hysteria: { version: "-", running: false, apiReachable: false },
peers: { total: 0, enabled: 0, disabled: 0, expired: 0, onlinePeers: 0, onlineDevices: 0 },
traffic: { downloadBytes: 0, uploadBytes: 0, totalBytes: 0, todayDownloadBytes: 0, todayUploadBytes: 0, sinceResetDownloadBytes: 0, sinceResetUploadBytes: 0 },
peers: {
total: 0,
enabled: 0,
disabled: 0,
expired: 0,
onlinePeers: 0,
onlineDevices: 0,
},
traffic: {
downloadBytes: 0,
uploadBytes: 0,
totalBytes: 0,
todayDownloadBytes: 0,
todayUploadBytes: 0,
sinceResetDownloadBytes: 0,
sinceResetUploadBytes: 0,
},
health: {
collector: { status: "stale" },
hysteria: { status: "ok" },
@@ -115,7 +209,12 @@ const summary = ref<DashboardSummaryVo>({
});
const topPeers = ref<DashboardTopPeerVo[]>([]);
const securityRisks = ref<SecurityRiskVo[]>([]);
const timeseries = ref<DashboardTimeseriesVo>({ range: "24h", traffic: [], system: [], collectedAt: 0 });
const timeseries = ref<DashboardTimeseriesVo>({
range: "24h",
traffic: [],
system: [],
collectedAt: 0,
});
const timeseriesLoading = ref(false);
const range = ref("24h");
const loadError = ref("");
@@ -172,10 +271,14 @@ const trafficUploadData = computed(() =>
);
const hasTrafficData = computed(() =>
(timeseries.value.traffic || []).some((item) => (item.download || 0) > 0 || (item.upload || 0) > 0)
(timeseries.value.traffic || []).some(
(item) => (item.download || 0) > 0 || (item.upload || 0) > 0
)
);
const hasSingleTrafficPoint = computed(() => (timeseries.value.traffic || []).length === 1);
const hasSingleTrafficPoint = computed(
() => (timeseries.value.traffic || []).length === 1
);
const systemCpuData = computed(() =>
(timeseries.value.system || []).map((item) => [item.ts, item.cpu || 0])
@@ -186,7 +289,11 @@ const systemMemData = computed(() =>
);
const trafficChartOption = computed(() => ({
title: { text: t("dashboard.trafficSeriesChart"), left: "left", textStyle: { fontSize: 14, fontWeight: 600 } },
title: {
text: t("dashboard.trafficSeriesChart"),
left: "left",
textStyle: { fontSize: 14, fontWeight: 600 },
},
tooltip: {
trigger: "axis",
valueFormatter: (value: number) => formatBytes(value || 0),
@@ -223,7 +330,11 @@ const trafficChartOption = computed(() => ({
}));
const systemChartOption = computed(() => ({
title: { text: t("dashboard.systemSeriesChart"), left: "left", textStyle: { fontSize: 14, fontWeight: 600 } },
title: {
text: t("dashboard.systemSeriesChart"),
left: "left",
textStyle: { fontSize: 14, fontWeight: 600 },
},
tooltip: {
trigger: "axis",
valueFormatter: (value: number) => `${Math.round((value || 0) * 10) / 10}%`,
@@ -319,4 +430,3 @@ onUnmounted(() => {
height: 340px;
}
</style>
+3 -3
View File
@@ -27,7 +27,9 @@ function back() {
<template>
<div class="errPage-container">
<el-button class="pan-back-btn" @click="back">{{ $t("errorPage.back") }}</el-button>
<el-button class="pan-back-btn" @click="back">{{
$t("errorPage.back")
}}</el-button>
<el-row>
<el-col :span="12">
<h1 class="text-jumbo text-ginormous">{{ $t("errorPage.oops") }}</h1>
@@ -98,5 +100,3 @@ function back() {
}
}
</style>
+3 -3
View File
@@ -46,7 +46,9 @@ function message() {
<div class="bullshit__info">
{{ $t("errorPage.notFoundInfo") }}
</div>
<a href="/#/" class="bullshit__return-home">{{ $t("errorPage.notFoundBackHome") }}</a>
<a href="/#/" class="bullshit__return-home">{{
$t("errorPage.notFoundBackHome")
}}</a>
</div>
</div>
</div>
@@ -272,5 +274,3 @@ function message() {
}
}
</style>
@@ -380,5 +380,3 @@ const closeDialog = (): void => {
flex-wrap: wrap;
}
</style>
@@ -43,17 +43,14 @@
</el-form-item>
</el-form>
<el-form
<el-form
ref="dataFormRef"
:rules="dataFormRules"
label-position="top"
:model="dataForm"
:disabled="true"
>
<el-tabs
v-model="activeName"
class="tabs"
>
<el-tabs v-model="activeName" class="tabs">
<el-tab-pane :label="$t('hysteria.extension')" name="extension">
<el-tooltip
:content="$t('hysteria.config.remark')"
@@ -980,8 +977,8 @@ export default {
<script setup lang="ts">
import {
defaultHysteria2ServerConfig,
Hysteria2ServerConfig,
defaultHysteria2ServerConfig,
Hysteria2ServerConfig,
} from "@/api/config/types";
import Outbounds from "./components/Outbounds/index.vue";
import {
@@ -107,5 +107,3 @@ onMounted(() => {
setRecords();
});
</script>
@@ -107,4 +107,3 @@ onMounted(() => {
setRecords();
});
</script>
+297 -62
View File
@@ -8,13 +8,17 @@
<el-input v-model="queryParams.remark" clearable style="width: 220px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">{{ $t("common.search") }}</el-button>
<el-button type="primary" @click="handleQuery">{{
$t("common.search")
}}</el-button>
</el-form-item>
</el-form>
<el-card shadow="never">
<div class="mb-3">
<el-button type="primary" @click="handleAdd">{{ $t("common.add") }}</el-button>
<el-button type="primary" @click="handleAdd">{{
$t("common.add")
}}</el-button>
<el-upload
v-model:file-list="importFileList"
:http-request="handleImport"
@@ -26,7 +30,9 @@
>
<el-button>{{ $t("common.import") }}</el-button>
</el-upload>
<el-button class="ml-2" @click="handleExport">{{ $t("common.export") }}</el-button>
<el-button class="ml-2" @click="handleExport">{{
$t("common.export")
}}</el-button>
</div>
<el-table v-loading="loading" :data="records">
<el-table-column :label="$t('peer.name')" min-width="220">
@@ -37,42 +43,107 @@
</el-table-column>
<el-table-column :label="$t('peer.status')" min-width="180">
<template #default="scope">
<el-tag size="small" :type="scope.row.disabled === 1 ? 'danger' : 'success'">{{ scope.row.disabled === 1 ? $t('common.disable') : $t('common.enable') }}</el-tag>
<el-tag size="small" class="ml-1" :type="scope.row.online ? 'success' : 'info'">{{ scope.row.online ? $t('peer.online') : $t('peer.offline') }}</el-tag>
<el-tag
size="small"
:type="scope.row.disabled === 1 ? 'danger' : 'success'"
>{{
scope.row.disabled === 1
? $t("common.disable")
: $t("common.enable")
}}</el-tag
>
<el-tag
size="small"
class="ml-1"
:type="scope.row.online ? 'success' : 'info'"
>{{
scope.row.online ? $t("peer.online") : $t("peer.offline")
}}</el-tag
>
</template>
</el-table-column>
<el-table-column :label="$t('peer.traffic')" min-width="260">
<template #default="scope">
<div>{{ formatBytes(scope.row.downloadBytes + scope.row.uploadBytes) }} / {{ quotaText(scope.row.quotaBytes) }}</div>
<el-progress :percentage="trafficPercent(scope.row)" :status="scope.row.quotaBytes < 0 ? undefined : (trafficPercent(scope.row) >= 90 ? 'exception' : undefined)" :show-text="false" />
<div>
{{
formatBytes(scope.row.downloadBytes + scope.row.uploadBytes)
}}
/ {{ quotaText(scope.row.quotaBytes) }}
</div>
<el-progress
:percentage="trafficPercent(scope.row)"
:status="
scope.row.quotaBytes < 0
? undefined
: trafficPercent(scope.row) >= 90
? 'exception'
: undefined
"
:show-text="false"
/>
</template>
</el-table-column>
<el-table-column :label="$t('peer.devices')" min-width="120">
<template #default="scope">{{ scope.row.onlineDevices }} / {{ scope.row.maxDevices }}</template>
<template #default="scope"
>{{ scope.row.onlineDevices }} /
{{ scope.row.maxDevices }}</template
>
</el-table-column>
<el-table-column :label="$t('peer.expireTime')" min-width="170">
<template #default="scope">{{ scope.row.expiresAt === 0 ? $t('peer.unlimited') : timestampToDateTime(scope.row.expiresAt) }}</template>
<template #default="scope">{{
scope.row.expiresAt === 0
? $t("peer.unlimited")
: timestampToDateTime(scope.row.expiresAt)
}}</template>
</el-table-column>
<el-table-column :label="$t('peer.lastConnectionAt')" min-width="170">
<template #default="scope">{{ scope.row.lastConnectionAt ? timestampToDateTime(scope.row.lastConnectionAt) : '-' }}</template>
<template #default="scope">{{
scope.row.lastConnectionAt
? timestampToDateTime(scope.row.lastConnectionAt)
: "-"
}}</template>
</el-table-column>
<el-table-column :label="$t('common.operate')" width="230" fixed="right" align="right" header-align="right">
<el-table-column
:label="$t('common.operate')"
width="230"
fixed="right"
align="right"
header-align="right"
>
<template #default="scope">
<div class="peer-actions">
<el-button link type="primary" @click="openOverview(scope.row)">{{ $t("peer.overview") }}</el-button>
<el-button link type="primary" @click="copyUri(scope.row)">{{ $t("peer.copyUri") }}</el-button>
<el-button link type="primary" @click="openOverview(scope.row)">{{
$t("peer.overview")
}}</el-button>
<el-button link type="primary" @click="copyUri(scope.row)">{{
$t("peer.copyUri")
}}</el-button>
<el-dropdown trigger="click" placement="bottom-end">
<el-button text circle class="peer-more-btn" @click.stop>
<i-ep-more-filled />
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="showQr(scope.row)">{{ $t("peer.clientQr") }}</el-dropdown-item>
<el-dropdown-item @click="handleUpdate(scope.row)">{{ $t("common.edit") }}</el-dropdown-item>
<el-dropdown-item @click="handleResetTraffic(scope.row)">{{ $t("common.resetTraffic") }}</el-dropdown-item>
<el-dropdown-item @click="handleKick(scope.row)">{{ $t("peer.kick") }}</el-dropdown-item>
<el-dropdown-item @click="handleReleaseKick(scope.row)">{{ $t("peer.releaseKick") }}</el-dropdown-item>
<el-dropdown-item divided @click="handleDelete(scope.row)">{{ $t("common.delete") }}</el-dropdown-item>
<el-dropdown-item @click="showQr(scope.row)">{{
$t("peer.clientQr")
}}</el-dropdown-item>
<el-dropdown-item @click="handleUpdate(scope.row)">{{
$t("common.edit")
}}</el-dropdown-item>
<el-dropdown-item @click="handleResetTraffic(scope.row)">{{
$t("common.resetTraffic")
}}</el-dropdown-item>
<el-dropdown-item @click="handleKick(scope.row)">{{
$t("peer.kick")
}}</el-dropdown-item>
<el-dropdown-item @click="handleReleaseKick(scope.row)">{{
$t("peer.releaseKick")
}}</el-dropdown-item>
<el-dropdown-item
divided
@click="handleDelete(scope.row)"
>{{ $t("common.delete") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -90,17 +161,41 @@
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" width="620px">
<el-form ref="formRef" :model="dataForm" :rules="rules" label-width="140px">
<el-form-item :label="$t('peer.name')" prop="name"><el-input v-model="dataForm.name" /></el-form-item>
<el-form-item :label="$t('peer.remark')"><el-input v-model="dataForm.remark" /></el-form-item>
<el-form-item :label="$t('peer.secret')" prop="secret"><el-input v-model="dataForm.secret" show-password /></el-form-item>
<el-form-item :label="$t('peer.quota')"><el-input-number v-model="dataForm.quotaBytes" :min="-1" /></el-form-item>
<el-form-item :label="$t('peer.expireTime')"><el-date-picker v-model="dataForm.expiresAt" type="datetime" value-format="x" /></el-form-item>
<el-form-item :label="$t('peer.maxDevices')"><el-input-number v-model="dataForm.maxDevices" :min="1" /></el-form-item>
<el-form-item :label="$t('peer.disabled')"><el-switch v-model="disabledBool" /></el-form-item>
<el-form
ref="formRef"
:model="dataForm"
:rules="rules"
label-width="140px"
>
<el-form-item :label="$t('peer.name')" prop="name"
><el-input v-model="dataForm.name"
/></el-form-item>
<el-form-item :label="$t('peer.remark')"
><el-input v-model="dataForm.remark"
/></el-form-item>
<el-form-item :label="$t('peer.secret')" prop="secret"
><el-input v-model="dataForm.secret" show-password
/></el-form-item>
<el-form-item :label="$t('peer.quota')"
><el-input-number v-model="dataForm.quotaBytes" :min="-1"
/></el-form-item>
<el-form-item :label="$t('peer.expireTime')"
><el-date-picker
v-model="dataForm.expiresAt"
type="datetime"
value-format="x"
/></el-form-item>
<el-form-item :label="$t('peer.maxDevices')"
><el-input-number v-model="dataForm.maxDevices" :min="1"
/></el-form-item>
<el-form-item :label="$t('peer.disabled')"
><el-switch v-model="disabledBool"
/></el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm">{{ $t("common.confirm") }}</el-button>
<el-button type="primary" @click="submitForm">{{
$t("common.confirm")
}}</el-button>
</template>
</el-dialog>
@@ -110,32 +205,104 @@
</div>
</el-dialog>
<el-drawer v-model="overview.visible" :title="$t('peer.overview')" size="45%">
<el-drawer
v-model="overview.visible"
:title="$t('peer.overview')"
size="45%"
>
<div v-if="overview.data">
<p><b>{{ $t('peer.authId') }}:</b> {{ overview.data.authId }}</p>
<p><b>{{ $t('peer.name') }}:</b> {{ overview.data.name }}</p>
<p><b>{{ $t('peer.remark') }}:</b> {{ overview.data.remark || '-' }}</p>
<p><b>{{ $t('peer.createdAt') }}:</b> {{ overview.data.createTime || '-' }}</p>
<p><b>{{ $t('peer.status') }}:</b> {{ overview.data.disabled === 1 ? $t('common.disable') : $t('common.enable') }}</p>
<p><b>{{ $t('peer.bannedUntil') }}:</b> {{ overview.data.bannedUntil > Date.now() ? timestampToDateTime(overview.data.bannedUntil) : '-' }}</p>
<p><b>{{ $t('peer.totalTraffic') }}:</b> {{ formatBytes((overview.data.downloadBytes || 0) + (overview.data.uploadBytes || 0)) }}</p>
<p><b>{{ $t('peer.quota') }}:</b> {{ quotaText(overview.data.quotaBytes) }}</p>
<p><b>{{ $t('peer.download') }}:</b> {{ formatBytes(overview.data.downloadBytes) }}</p>
<p><b>{{ $t('peer.upload') }}:</b> {{ formatBytes(overview.data.uploadBytes) }}</p>
<p><b>{{ $t('peer.devices') }}:</b> {{ overview.data.onlineDevices }} / {{ overview.data.maxDevices }}</p>
<p><b>{{ $t('peer.expireTime') }}:</b> {{ overview.data.expiresAt === 0 ? $t('peer.unlimited') : timestampToDateTime(overview.data.expiresAt) }}</p>
<p><b>{{ $t('peer.lastConnectionAt') }}:</b> {{ overview.data.lastConnectionAt ? timestampToDateTime(overview.data.lastConnectionAt) : '-' }}</p>
<p>
<b>{{ $t("peer.authId") }}:</b> {{ overview.data.authId }}
</p>
<p>
<b>{{ $t("peer.name") }}:</b> {{ overview.data.name }}
</p>
<p>
<b>{{ $t("peer.remark") }}:</b> {{ overview.data.remark || "-" }}
</p>
<p>
<b>{{ $t("peer.createdAt") }}:</b>
{{ overview.data.createTime || "-" }}
</p>
<p>
<b>{{ $t("peer.status") }}:</b>
{{
overview.data.disabled === 1
? $t("common.disable")
: $t("common.enable")
}}
</p>
<p>
<b>{{ $t("peer.bannedUntil") }}:</b>
{{
overview.data.bannedUntil > Date.now()
? timestampToDateTime(overview.data.bannedUntil)
: "-"
}}
</p>
<p>
<b>{{ $t("peer.totalTraffic") }}:</b>
{{
formatBytes(
(overview.data.downloadBytes || 0) +
(overview.data.uploadBytes || 0)
)
}}
</p>
<p>
<b>{{ $t("peer.quota") }}:</b>
{{ quotaText(overview.data.quotaBytes) }}
</p>
<p>
<b>{{ $t("peer.download") }}:</b>
{{ formatBytes(overview.data.downloadBytes) }}
</p>
<p>
<b>{{ $t("peer.upload") }}:</b>
{{ formatBytes(overview.data.uploadBytes) }}
</p>
<p>
<b>{{ $t("peer.devices") }}:</b> {{ overview.data.onlineDevices }} /
{{ overview.data.maxDevices }}
</p>
<p>
<b>{{ $t("peer.expireTime") }}:</b>
{{
overview.data.expiresAt === 0
? $t("peer.unlimited")
: timestampToDateTime(overview.data.expiresAt)
}}
</p>
<p>
<b>{{ $t("peer.lastConnectionAt") }}:</b>
{{
overview.data.lastConnectionAt
? timestampToDateTime(overview.data.lastConnectionAt)
: "-"
}}
</p>
<div class="mt-2">
<p><b>{{ $t('peer.clientUri') }}:</b></p>
<p>
<b>{{ $t("peer.clientUri") }}:</b>
</p>
<el-input :model-value="overviewClientUrl" readonly />
</div>
<div class="mt-2">
<el-button type="primary" @click="copyUri(overview.data)">{{ $t("peer.copyUri") }}</el-button>
<el-button @click="loadOverviewQr(overview.data)">{{ $t("peer.clientQr") }}</el-button>
<el-button type="primary" @click="copyUri(overview.data)">{{
$t("peer.copyUri")
}}</el-button>
<el-button @click="loadOverviewQr(overview.data)">{{
$t("peer.clientQr")
}}</el-button>
</div>
<div class="mt-2" v-if="overviewQrUrl">
<div class="qr-dialog-body">
<qrcode-vue :value="overviewQrUrl" :size="220" level="M" render-as="svg" />
<qrcode-vue
:value="overviewQrUrl"
:size="220"
level="M"
render-as="svg"
/>
</div>
</div>
</div>
@@ -164,7 +331,12 @@ import {
updatePeerApi,
exportPeerApi,
} from "@/api/peer";
import { PeerPageDto, PeerSaveDto, PeerUpdateDto, PeerVo } from "@/api/peer/types";
import {
PeerPageDto,
PeerSaveDto,
PeerUpdateDto,
PeerVo,
} from "@/api/peer/types";
import { UploadFile, UploadRawFile, UploadRequestOptions } from "element-plus";
const { t } = useI18n();
@@ -177,9 +349,18 @@ const importFileList = ref<UploadFile[]>([]);
const overviewQrUrl = ref("");
const overviewClientUrl = ref("");
const formRef = ref();
const overview = reactive<{ visible: boolean; data: PeerVo | null }>({ visible: false, data: null });
const overview = reactive<{ visible: boolean; data: PeerVo | null }>({
visible: false,
data: null,
});
const queryParams = reactive<PeerPageDto>({ pageNum: 1, pageSize: 10, name: undefined, remark: undefined, disabled: undefined });
const queryParams = reactive<PeerPageDto>({
pageNum: 1,
pageSize: 10,
name: undefined,
remark: undefined,
disabled: undefined,
});
const dialog = reactive({ visible: false, title: "", editId: 0 });
const dataForm = reactive<PeerSaveDto & { id?: number }>({
name: "",
@@ -197,7 +378,13 @@ const disabledBool = computed({
});
const rules = {
name: [{ required: true, message: t("common.required"), trigger: ["change", "blur"] }],
name: [
{
required: true,
message: t("common.required"),
trigger: ["change", "blur"],
},
],
};
function quotaText(v: number) {
@@ -241,7 +428,16 @@ async function handleQuery() {
}
function handleAdd() {
Object.assign(dataForm, { id: undefined, name: "", secret: "", quotaBytes: -1, expiresAt: getMonthLater(), maxDevices: 3, disabled: 0, remark: "" });
Object.assign(dataForm, {
id: undefined,
name: "",
secret: "",
quotaBytes: -1,
expiresAt: getMonthLater(),
maxDevices: 3,
disabled: 0,
remark: "",
});
dialog.title = t("common.add");
dialog.editId = 0;
dialog.visible = true;
@@ -261,10 +457,23 @@ async function submitForm() {
if (!ok) return;
}
if (dialog.editId > 0 && dataForm.secret) {
await ElMessageBox.confirm(t("peer.secretRotateConfirm"), t("common.warning"), { type: "warning" });
await ElMessageBox.confirm(
t("peer.secretRotateConfirm"),
t("common.warning"),
{ type: "warning" }
);
}
if (dialog.editId > 0) {
const payload: PeerUpdateDto = { id: dialog.editId, name: dataForm.name, secret: dataForm.secret || undefined, quotaBytes: dataForm.quotaBytes, expiresAt: dataForm.expiresAt, maxDevices: dataForm.maxDevices, disabled: dataForm.disabled, remark: dataForm.remark };
const payload: PeerUpdateDto = {
id: dialog.editId,
name: dataForm.name,
secret: dataForm.secret || undefined,
quotaBytes: dataForm.quotaBytes,
expiresAt: dataForm.expiresAt,
maxDevices: dataForm.maxDevices,
disabled: dataForm.disabled,
remark: dataForm.remark,
};
await updatePeerApi(payload);
} else {
await savePeerApi(dataForm);
@@ -274,13 +483,21 @@ async function submitForm() {
}
async function handleDelete(row: PeerVo) {
await ElMessageBox.confirm(t("common.deleteConfirm", { username: row.name }), t("common.warning"), { type: "warning" });
await ElMessageBox.confirm(
t("common.deleteConfirm", { username: row.name }),
t("common.warning"),
{ type: "warning" }
);
await deletePeerApi({ id: row.id });
await handleQuery();
}
async function handleResetTraffic(row: PeerVo) {
await ElMessageBox.confirm(t("common.resetTrafficConfirm"), t("common.warning"), { type: "warning" });
await ElMessageBox.confirm(
t("common.resetTrafficConfirm"),
t("common.warning"),
{ type: "warning" }
);
await resetPeerTrafficApi({ id: row.id });
await handleQuery();
}
@@ -332,7 +549,9 @@ function beforeImport(file: UploadRawFile) {
async function handleExport() {
try {
const response = await exportPeerApi();
const blob = new Blob([response.data], { type: "application/octet-stream" });
const blob = new Blob([response.data], {
type: "application/octet-stream",
});
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
document.body.appendChild(a);
@@ -351,10 +570,26 @@ onMounted(handleQuery);
</script>
<style scoped>
.peer-title { font-weight: 600; }
.peer-sub { font-size: 12px; color: #909399; }
.peer-actions { display: flex; gap: 8px; align-items: center; justify-content: flex-end; }
.peer-more-btn { font-size: 16px; }
.qr-dialog-body { display: flex; align-items: center; justify-content: center; padding: 12px 0 20px; }
.peer-title {
font-weight: 600;
}
.peer-sub {
font-size: 12px;
color: #909399;
}
.peer-actions {
display: flex;
gap: 8px;
align-items: center;
justify-content: flex-end;
}
.peer-more-btn {
font-size: 16px;
}
.qr-dialog-body {
display: flex;
align-items: center;
justify-content: center;
padding: 12px 0 20px;
}
</style>
@@ -13,5 +13,3 @@ const { path } = params;
router.replace({ path: "/" + path, query });
</script>