style: прогнать prettier по frontend
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user