fix26: полный прод-фикс auth/jwt, import-export peer и frontend flow

This commit is contained in:
2026-05-09 01:18:13 +05:00
parent 7a64d76d08
commit c13841b61a
19 changed files with 390 additions and 90 deletions
@@ -0,0 +1,85 @@
<template>
<div class="app-container">
<el-card shadow="never" class="change-password-card">
<template #header>{{ $t("admin.changePasswordTitle") }}</template>
<el-alert
:title="$t('admin.forcePasswordChangeNotice')"
type="warning"
:closable="false"
class="mb-3"
/>
<el-form ref="formRef" :model="form" :rules="rules" label-width="180px">
<el-form-item :label="$t('admin.oldPassword')" prop="oldPassword">
<el-input v-model="form.oldPassword" show-password />
</el-form-item>
<el-form-item :label="$t('admin.newPassword')" prop="newPassword">
<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-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ElMessage, FormInstance, FormRules } from "element-plus";
import { useI18n } from "vue-i18n";
import { useRoute, useRouter } from "vue-router";
import { adminChangePasswordApi } from "@/api/admin";
import { useAdminStore } from "@/store/modules/admin";
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const adminStore = useAdminStore();
const formRef = ref<FormInstance>();
const saving = ref(false);
const form = reactive({
oldPassword: "",
newPassword: "",
});
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"] },
],
newPassword: [
{ required: true, message: t("common.required"), trigger: ["change", "blur"] },
{ pattern: passwordPattern, message: t("common.invalid"), trigger: ["change", "blur"] },
],
};
const submit = async () => {
if (!formRef.value) {
return;
}
const valid = await formRef.value.validate();
if (!valid) {
return;
}
saving.value = true;
try {
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 : "/";
router.push(redirect);
} finally {
saving.value = false;
}
};
</script>
<style scoped lang="scss">
.change-password-card {
max-width: 760px;
}
</style>
+8 -5
View File
@@ -1,7 +1,7 @@
<template>
<div class="dashboard-container">
<div class="dashboard-actions mb-2">
<el-button size="small" @click="loadDashboard">Refresh</el-button>
<el-button size="small" @click="loadDashboard">{{ $t("common.refresh") }}</el-button>
</div>
<el-alert
@@ -13,7 +13,7 @@
/>
<el-alert
v-else-if="isStale"
title="Dashboard data is stale. Retrying automatically..."
:title="$t('dashboard.stale')"
type="warning"
:closable="false"
class="mb-2"
@@ -22,7 +22,7 @@
<el-alert
v-for="risk in securityRisks"
:key="risk.key"
:title="risk.key"
:title="$t(risk.key)"
:type="risk.severity === 'critical' ? 'error' : risk.severity === 'warning' ? 'warning' : 'info'"
:closable="risk.dismissible"
class="mb-2"
@@ -40,7 +40,7 @@
</el-row>
<el-card shadow="never" class="mt-3">
<template #header>Top peers (24h)</template>
<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">
@@ -59,10 +59,13 @@
<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 { formatBytes } from "@/utils/byte";
const { t } = useI18n();
const summary = ref<DashboardSummaryVo>({
collectedAt: 0,
system: { cpuPercent: 0, memUsedBytes: 0, memTotalBytes: 0, memPercent: 0, diskUsedBytes: 0, diskTotalBytes: 0, diskPercent: 0 },
@@ -108,7 +111,7 @@ const loadDashboard = async () => {
lastSuccessAt.value = Date.now();
loadError.value = "";
} catch (error) {
loadError.value = "Failed to refresh dashboard data";
loadError.value = t("dashboard.refreshFailed");
} finally {
loading.value = false;
}
+4
View File
@@ -160,6 +160,10 @@ const handleLogin = () => {
adminStore
.login(params)
.then(() => {
if (adminStore.forcePasswordChange) {
router.push({ path: "/admin/change-password" });
return;
}
const query: LocationQuery = route.query;
const redirect = (query.redirect as LocationQueryValue) ?? "/";