style: прогнать prettier по frontend
This commit is contained in:
@@ -9,5 +9,3 @@ const appStore = useAppStore();
|
|||||||
<router-view />
|
<router-view />
|
||||||
</el-config-provider>
|
</el-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ export function getAdminInfoApi(): AxiosPromise<AdminInfo> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function adminChangePasswordApi(data: AdminChangePasswordDto): AxiosPromise {
|
export function adminChangePasswordApi(
|
||||||
|
data: AdminChangePasswordDto
|
||||||
|
): AxiosPromise {
|
||||||
return request({
|
return request({
|
||||||
url: "/admin/change-password",
|
url: "/admin/change-password",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
|||||||
@@ -24,4 +24,3 @@ export interface AdminChangePasswordDto {
|
|||||||
export interface AdminSecurityVo {
|
export interface AdminSecurityVo {
|
||||||
forcePasswordChange: boolean;
|
forcePasswordChange: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,5 +110,3 @@ export function uploadCertFileApi(data: FormData): AxiosPromise<string> {
|
|||||||
headers: { "Content-Type": "multipart/form-data" },
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -300,5 +300,3 @@ export interface Hysteria2AcmePathVo {
|
|||||||
crtPath: string;
|
crtPath: string;
|
||||||
keyPath: string;
|
keyPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ export function dashboardSummaryApi(): AxiosPromise<DashboardSummaryVo> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dashboardTimeseriesApi(range = "24h"): AxiosPromise<DashboardTimeseriesVo> {
|
export function dashboardTimeseriesApi(
|
||||||
|
range = "24h"
|
||||||
|
): AxiosPromise<DashboardTimeseriesVo> {
|
||||||
return request({
|
return request({
|
||||||
url: "/dashboard/timeseries",
|
url: "/dashboard/timeseries",
|
||||||
method: "get",
|
method: "get",
|
||||||
@@ -22,7 +24,10 @@ export function dashboardTimeseriesApi(range = "24h"): AxiosPromise<DashboardTim
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dashboardTopPeersApi(range = "24h", limit = 10): AxiosPromise<DashboardTopPeerVo[]> {
|
export function dashboardTopPeersApi(
|
||||||
|
range = "24h",
|
||||||
|
limit = 10
|
||||||
|
): AxiosPromise<DashboardTopPeerVo[]> {
|
||||||
return request({
|
return request({
|
||||||
url: "/dashboard/top-peers",
|
url: "/dashboard/top-peers",
|
||||||
method: "get",
|
method: "get",
|
||||||
@@ -36,4 +41,3 @@ export function dashboardSecurityApi(): AxiosPromise<SecurityRiskVo[]> {
|
|||||||
method: "get",
|
method: "get",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,4 +69,3 @@ export interface DashboardTopPeerVo {
|
|||||||
upload: number;
|
upload: number;
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,5 +33,3 @@ export function exportLogApi(data: LogExportDto): AxiosPromise {
|
|||||||
responseType: "blob",
|
responseType: "blob",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,5 +22,3 @@ export interface LogHysteria2Vo {
|
|||||||
msg: string;
|
msg: string;
|
||||||
time: string;
|
time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -96,4 +96,3 @@ export function exportPeerApi(): AxiosPromise {
|
|||||||
responseType: "blob",
|
responseType: "blob",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,5 +58,3 @@ function toggleClick() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -75,5 +75,3 @@ const handleClose = (tag: string): void => {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,5 +34,3 @@ function handleLanguageChange(lang: string) {
|
|||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -180,5 +180,3 @@ const closeDialog = (): void => {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -86,5 +86,3 @@ function handleCurrentChange(val: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,5 +41,3 @@ const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
|
|||||||
fill: currentcolor; /* currentColor наследует текущий цвет элемента или родителя */
|
fill: currentcolor; /* currentColor наследует текущий цвет элемента или родителя */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,3 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,5 +7,3 @@ export function setupDirective(app: App<Element>) {
|
|||||||
// Делает v-hasRole доступным во всех компонентах
|
// Делает v-hasRole доступным во всех компонентах
|
||||||
app.directive("hasRole", hasRole);
|
app.directive("hasRole", hasRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,5 +23,3 @@ export const hasRole: Directive = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ function initialLocale(): string {
|
|||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return defaultSettings.language || "ru";
|
return defaultSettings.language || "ru";
|
||||||
}
|
}
|
||||||
return window.localStorage.getItem("language") || defaultSettings.language || "ru";
|
return (
|
||||||
|
window.localStorage.getItem("language") || defaultSettings.language || "ru"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
@@ -28,5 +30,3 @@ const i18n = createI18n({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,8 @@ export default {
|
|||||||
clientUri: "Client URI",
|
clientUri: "Client URI",
|
||||||
clientQr: "Client QR",
|
clientQr: "Client QR",
|
||||||
unlimited: "Unlimited",
|
unlimited: "Unlimited",
|
||||||
secretRotateConfirm: "Rotate peer secret? Existing client configurations will stop working until updated.",
|
secretRotateConfirm:
|
||||||
|
"Rotate peer secret? Existing client configurations will stop working until updated.",
|
||||||
quota: "Quota",
|
quota: "Quota",
|
||||||
download: "Download",
|
download: "Download",
|
||||||
upload: "Upload",
|
upload: "Upload",
|
||||||
@@ -406,5 +407,3 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ export default {
|
|||||||
passwordFormatIncorrect: "Неверный формат пароля",
|
passwordFormatIncorrect: "Неверный формат пароля",
|
||||||
},
|
},
|
||||||
dashboard: {
|
dashboard: {
|
||||||
stale: "Данные дашборда устарели. Выполняется автоматическая повторная попытка...",
|
stale:
|
||||||
|
"Данные дашборда устарели. Выполняется автоматическая повторная попытка...",
|
||||||
topPeers24h: "Топ пиров (24ч)",
|
topPeers24h: "Топ пиров (24ч)",
|
||||||
topPeersRange: "Топ пиров ({range})",
|
topPeersRange: "Топ пиров ({range})",
|
||||||
refreshFailed: "Не удалось обновить данные дашборда",
|
refreshFailed: "Не удалось обновить данные дашборда",
|
||||||
@@ -134,7 +135,8 @@ export default {
|
|||||||
clientUri: "URI клиента",
|
clientUri: "URI клиента",
|
||||||
clientQr: "QR клиента",
|
clientQr: "QR клиента",
|
||||||
unlimited: "Безлимит",
|
unlimited: "Безлимит",
|
||||||
secretRotateConfirm: "Сменить секрет пира? Текущие клиентские конфиги перестанут работать до обновления.",
|
secretRotateConfirm:
|
||||||
|
"Сменить секрет пира? Текущие клиентские конфиги перестанут работать до обновления.",
|
||||||
quota: "Квота",
|
quota: "Квота",
|
||||||
download: "Скачано",
|
download: "Скачано",
|
||||||
upload: "Отдано",
|
upload: "Отдано",
|
||||||
@@ -181,7 +183,8 @@ export default {
|
|||||||
mustBeInteger: "Поле должно быть целым числом",
|
mustBeInteger: "Поле должно быть целым числом",
|
||||||
invalidWebContext:
|
invalidWebContext:
|
||||||
"Поле должно начинаться с / и содержать только строчные буквы (a-z) и цифры (0-9)",
|
"Поле должно начинаться с / и содержать только строчные буквы (a-z) и цифры (0-9)",
|
||||||
invalidTrafficTime: "Поле должно быть числом максимум с одним знаком после запятой",
|
invalidTrafficTime:
|
||||||
|
"Поле должно быть числом максимум с одним знаком после запятой",
|
||||||
},
|
},
|
||||||
monitor: {
|
monitor: {
|
||||||
huiVersion: "Версия HY2XS admin",
|
huiVersion: "Версия HY2XS admin",
|
||||||
@@ -313,8 +316,15 @@ export default {
|
|||||||
outbounds: {
|
outbounds: {
|
||||||
name: "Имя outbound",
|
name: "Имя outbound",
|
||||||
type: "Тип",
|
type: "Тип",
|
||||||
socks5: { addr: "Адрес SOCKS5", username: "Логин SOCKS5", password: "Пароль SOCKS5" },
|
socks5: {
|
||||||
http: { url: "URL HTTP/HTTPS proxy", insecure: "Отключить TLS-проверку proxy" },
|
addr: "Адрес SOCKS5",
|
||||||
|
username: "Логин SOCKS5",
|
||||||
|
password: "Пароль SOCKS5",
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
url: "URL HTTP/HTTPS proxy",
|
||||||
|
insecure: "Отключить TLS-проверку proxy",
|
||||||
|
},
|
||||||
direct: {
|
direct: {
|
||||||
mode: "Тип",
|
mode: "Тип",
|
||||||
bindIPv4: "Локальный IPv4",
|
bindIPv4: "Локальный IPv4",
|
||||||
@@ -332,7 +342,11 @@ export default {
|
|||||||
rewriteHost: "Переписывать Host header",
|
rewriteHost: "Переписывать Host header",
|
||||||
insecure: "Отключить TLS-проверку",
|
insecure: "Отключить TLS-проверку",
|
||||||
},
|
},
|
||||||
string: { content: "Ответ строкой", headers: "HTTP headers", statusCode: "HTTP status code" },
|
string: {
|
||||||
|
content: "Ответ строкой",
|
||||||
|
headers: "HTTP headers",
|
||||||
|
statusCode: "HTTP status code",
|
||||||
|
},
|
||||||
listenHTTP: "HTTP listen address",
|
listenHTTP: "HTTP listen address",
|
||||||
listenHTTPS: "HTTPS listen address",
|
listenHTTPS: "HTTPS listen address",
|
||||||
forceHTTPS: "Принудительно использовать HTTPS",
|
forceHTTPS: "Принудительно использовать HTTPS",
|
||||||
@@ -340,5 +354,3 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,5 +17,3 @@
|
|||||||
background-color: var(--el-bg-color-page);
|
background-color: var(--el-bg-color-page);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ function logout() {
|
|||||||
cancelButtonText: t("common.cancel"),
|
cancelButtonText: t("common.cancel"),
|
||||||
type: "warning",
|
type: "warning",
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
adminStore
|
adminStore.logout().then(() => {
|
||||||
.logout()
|
|
||||||
.then(() => {
|
|
||||||
router.push(`/login?redirect=${route.fullPath}`);
|
router.push(`/login?redirect=${route.fullPath}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,5 +35,3 @@ function push() {
|
|||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
collapse: {
|
collapse: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -22,12 +21,7 @@ const logo = ref(new URL(`../../../assets/logo.png`, import.meta.url).href);
|
|||||||
<img :src="logo" class="sidebar-logo-image" />
|
<img :src="logo" class="sidebar-logo-image" />
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<router-link
|
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
||||||
v-else
|
|
||||||
key="expand"
|
|
||||||
class="sidebar-logo-link"
|
|
||||||
to="/"
|
|
||||||
>
|
|
||||||
<img :src="logo" class="sidebar-logo-image" />
|
<img :src="logo" class="sidebar-logo-image" />
|
||||||
<span class="sidebar-logo-title">HY2XS</span>
|
<span class="sidebar-logo-title">HY2XS</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -73,5 +67,3 @@ const logo = ref(new URL(`../../../assets/logo.png`, import.meta.url).href);
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,9 @@ function resolvePath(routePath: string) {
|
|||||||
:content="translateRouteTitleI18n(item.meta.title)"
|
:content="translateRouteTitleI18n(item.meta.title)"
|
||||||
placement="right"
|
placement="right"
|
||||||
>
|
>
|
||||||
<span class="menu-title">{{ translateRouteTitleI18n(item.meta.title) }}</span>
|
<span class="menu-title">{{
|
||||||
|
translateRouteTitleI18n(item.meta.title)
|
||||||
|
}}</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -139,4 +141,3 @@ function resolvePath(routePath: string) {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -38,4 +38,3 @@ const route = useRoute();
|
|||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
export { default as Navbar } from "./Navbar.vue";
|
export { default as Navbar } from "./Navbar.vue";
|
||||||
export { default as AppMain } from "./AppMain.vue";
|
export { default as AppMain } from "./AppMain.vue";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -97,4 +97,3 @@ function handleOutsideClick() {
|
|||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -23,5 +23,3 @@ setupDirective(app);
|
|||||||
setupStore(app);
|
setupStore(app);
|
||||||
|
|
||||||
app.use(router).use(i18n).mount("#app");
|
app.use(router).use(i18n).mount("#app");
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,14 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
const adminStore = useAdminStoreHook();
|
const adminStore = useAdminStoreHook();
|
||||||
const hasRoles = adminStore.roles && adminStore.roles.length > 0;
|
const hasRoles = adminStore.roles && adminStore.roles.length > 0;
|
||||||
if (hasRoles) {
|
if (hasRoles) {
|
||||||
if (adminStore.forcePasswordChange && to.path !== "/admin/change-password") {
|
if (
|
||||||
next({ path: "/admin/change-password", query: { redirect: to.fullPath } });
|
adminStore.forcePasswordChange &&
|
||||||
|
to.path !== "/admin/change-password"
|
||||||
|
) {
|
||||||
|
next({
|
||||||
|
path: "/admin/change-password",
|
||||||
|
query: { redirect: to.fullPath },
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Если маршрут не найден, перейти на 404
|
// Если маршрут не найден, перейти на 404
|
||||||
@@ -34,14 +40,19 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const { roles, forcePasswordChange } = await adminStore.getAdminInfo();
|
const { roles, forcePasswordChange } =
|
||||||
|
await adminStore.getAdminInfo();
|
||||||
const permissionStore = usePermissionStoreHook();
|
const permissionStore = usePermissionStoreHook();
|
||||||
const accessRoutes = permissionStore.generateRoutes(roles);
|
const accessRoutes = permissionStore.generateRoutes(roles);
|
||||||
accessRoutes.forEach((route) => {
|
accessRoutes.forEach((route) => {
|
||||||
router.addRoute(route);
|
router.addRoute(route);
|
||||||
});
|
});
|
||||||
if (forcePasswordChange && to.path !== "/admin/change-password") {
|
if (forcePasswordChange && to.path !== "/admin/change-password") {
|
||||||
next({ path: "/admin/change-password", query: { redirect: to.fullPath }, replace: true });
|
next({
|
||||||
|
path: "/admin/change-password",
|
||||||
|
query: { redirect: to.fullPath },
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
next({ ...to, replace: true });
|
next({ ...to, replace: true });
|
||||||
|
|||||||
@@ -7,4 +7,3 @@ const defaultSettings: DefaultSettings = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default defaultSettings;
|
export default defaultSettings;
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,3 @@ export function setupStore(app: App<Element>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export { store };
|
export { store };
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ export const useAdminStore = defineStore("admin", () => {
|
|||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
loginApi(adminLoginDto)
|
loginApi(adminLoginDto)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const { tokenType, accessToken, forcePasswordChange: mustChange } = response.data;
|
const {
|
||||||
|
tokenType,
|
||||||
|
accessToken,
|
||||||
|
forcePasswordChange: mustChange,
|
||||||
|
} = response.data;
|
||||||
token.value = tokenType + " " + accessToken;
|
token.value = tokenType + " " + accessToken;
|
||||||
forcePasswordChange.value = !!mustChange;
|
forcePasswordChange.value = !!mustChange;
|
||||||
resolve();
|
resolve();
|
||||||
@@ -84,4 +88,3 @@ export const useAdminStore = defineStore("admin", () => {
|
|||||||
export function useAdminStoreHook() {
|
export function useAdminStoreHook() {
|
||||||
return useAdminStore(store);
|
return useAdminStore(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,5 +72,3 @@ export const useAppStore = defineStore("app", () => {
|
|||||||
openSideBar,
|
openSideBar,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -95,5 +95,3 @@ export const usePermissionStore = defineStore("permission", () => {
|
|||||||
export function usePermissionStoreHook() {
|
export function usePermissionStoreHook() {
|
||||||
return usePermissionStore(store);
|
return usePermissionStore(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,5 +40,3 @@
|
|||||||
.el-table__header col[name="gutter"] {
|
.el-table__header col[name="gutter"] {
|
||||||
display: table-cell !important;
|
display: table-cell !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -73,5 +73,3 @@ a:active,
|
|||||||
div:focus {
|
div:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,3 @@
|
|||||||
menuText: $menuText;
|
menuText: $menuText;
|
||||||
menuActiveText: $menuActiveText;
|
menuActiveText: $menuActiveText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -32,4 +32,3 @@ $menuActiveBorder: var(--menuActiveBorder);
|
|||||||
|
|
||||||
$sideBarWidth: 210px;
|
$sideBarWidth: 210px;
|
||||||
$sideBarCollapsedWidth: 54px;
|
$sideBarCollapsedWidth: 54px;
|
||||||
|
|
||||||
|
|||||||
+867
-519
File diff suppressed because it is too large
Load Diff
+55
-55
@@ -1,65 +1,65 @@
|
|||||||
// generated by unplugin-vue-components
|
// generated by unplugin-vue-components
|
||||||
// We suggest you to commit this file into source control
|
// We suggest you to commit this file into source control
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
import '@vue/runtime-core'
|
import "@vue/runtime-core";
|
||||||
|
|
||||||
export {}
|
export {};
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module "@vue/runtime-core" {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
ElAlert: typeof import("element-plus/es")["ElAlert"];
|
||||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
ElAvatar: typeof import("element-plus/es")["ElAvatar"];
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import("element-plus/es")["ElButton"];
|
||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import("element-plus/es")["ElCard"];
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import("element-plus/es")["ElCol"];
|
||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
ElDatePicker: typeof import("element-plus/es")["ElDatePicker"];
|
||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
ElDialog: typeof import("element-plus/es")["ElDialog"];
|
||||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
ElDrawer: typeof import("element-plus/es")["ElDrawer"];
|
||||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
ElDropdown: typeof import("element-plus/es")["ElDropdown"];
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import("element-plus/es")["ElDropdownItem"];
|
||||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
ElDropdownMenu: typeof import("element-plus/es")["ElDropdownMenu"];
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
ElEmpty: typeof import("element-plus/es")["ElEmpty"];
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import("element-plus/es")["ElForm"];
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import("element-plus/es")["ElFormItem"];
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import("element-plus/es")["ElInput"];
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
ElInputNumber: typeof import("element-plus/es")["ElInputNumber"];
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import("element-plus/es")["ElMenu"];
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import("element-plus/es")["ElMenuItem"];
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import("element-plus/es")["ElOption"];
|
||||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
ElPagination: typeof import("element-plus/es")["ElPagination"];
|
||||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
ElProgress: typeof import("element-plus/es")["ElProgress"];
|
||||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
ElRadioButton: typeof import("element-plus/es")["ElRadioButton"];
|
||||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
ElRadioGroup: typeof import("element-plus/es")["ElRadioGroup"];
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import("element-plus/es")["ElRow"];
|
||||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
ElScrollbar: typeof import("element-plus/es")["ElScrollbar"];
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import("element-plus/es")["ElSelect"];
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import("element-plus/es")["ElSubMenu"];
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
ElSwitch: typeof import("element-plus/es")["ElSwitch"];
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import("element-plus/es")["ElTable"];
|
||||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
ElTableColumn: typeof import("element-plus/es")["ElTableColumn"];
|
||||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
ElTabPane: typeof import("element-plus/es")["ElTabPane"];
|
||||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
ElTabs: typeof import("element-plus/es")["ElTabs"];
|
||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import("element-plus/es")["ElTag"];
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
ElTooltip: typeof import("element-plus/es")["ElTooltip"];
|
||||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
ElUpload: typeof import("element-plus/es")["ElUpload"];
|
||||||
Hamburger: typeof import('./../components/Hamburger/index.vue')['default']
|
Hamburger: typeof import("./../components/Hamburger/index.vue")["default"];
|
||||||
IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default']
|
IEpCaretBottom: typeof import("~icons/ep/caret-bottom")["default"];
|
||||||
IEpDownload: typeof import('~icons/ep/download')['default']
|
IEpDownload: typeof import("~icons/ep/download")["default"];
|
||||||
IEpMoreFilled: typeof import('~icons/ep/more-filled')['default']
|
IEpMoreFilled: typeof import("~icons/ep/more-filled")["default"];
|
||||||
IEpRefresh: typeof import('~icons/ep/refresh')['default']
|
IEpRefresh: typeof import("~icons/ep/refresh")["default"];
|
||||||
IEpRefreshRight: typeof import('~icons/ep/refresh-right')['default']
|
IEpRefreshRight: typeof import("~icons/ep/refresh-right")["default"];
|
||||||
IEpUpload: typeof import('~icons/ep/upload')['default']
|
IEpUpload: typeof import("~icons/ep/upload")["default"];
|
||||||
IEpUserFilled: typeof import('~icons/ep/user-filled')['default']
|
IEpUserFilled: typeof import("~icons/ep/user-filled")["default"];
|
||||||
ImputMultiple: typeof import('./../components/ImputMultiple/index.vue')['default']
|
ImputMultiple: typeof import("./../components/ImputMultiple/index.vue")["default"];
|
||||||
LangSelect: typeof import('./../components/LangSelect/index.vue')['default']
|
LangSelect: typeof import("./../components/LangSelect/index.vue")["default"];
|
||||||
MapAdd: typeof import('./../components/MapAdd/index.vue')['default']
|
MapAdd: typeof import("./../components/MapAdd/index.vue")["default"];
|
||||||
Pagination: typeof import('./../components/Pagination/index.vue')['default']
|
Pagination: typeof import("./../components/Pagination/index.vue")["default"];
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import("vue-router")["RouterLink"];
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import("vue-router")["RouterView"];
|
||||||
SvgIcon: typeof import('./../components/SvgIcon/index.vue')['default']
|
SvgIcon: typeof import("./../components/SvgIcon/index.vue")["default"];
|
||||||
UnitSelect: typeof import('./../components/UnitSelect/index.vue')['default']
|
UnitSelect: typeof import("./../components/UnitSelect/index.vue")["default"];
|
||||||
}
|
}
|
||||||
export interface ComponentCustomProperties {
|
export interface ComponentCustomProperties {
|
||||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
vLoading: typeof import("element-plus/es")["ElLoadingDirective"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-2
@@ -13,5 +13,3 @@ type ImportMetaEnv = Record<string, string | boolean | undefined>;
|
|||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv;
|
readonly env: ImportMetaEnv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Vendored
-2
@@ -16,5 +16,3 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -93,5 +93,3 @@ export const formatStorageUnit = (bytes: number): string => {
|
|||||||
// Возврат отформатированной единицы хранения
|
// Возврат отформатированной единицы хранения
|
||||||
return sizes[i];
|
return sizes[i];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -49,5 +49,3 @@ export const deepCopy = <T>(source: Partial<T>): T => {
|
|||||||
|
|
||||||
return objCopy as T;
|
return objCopy as T;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,5 +10,3 @@ export function translateRouteTitleI18n(title: any) {
|
|||||||
}
|
}
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,5 +37,3 @@ export function isExternal(path: string) {
|
|||||||
const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path);
|
const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path);
|
||||||
return isExternal;
|
return isExternal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -67,5 +67,3 @@ export const scrollTo = (to: number, duration: number, callback?: any) => {
|
|||||||
};
|
};
|
||||||
animateScroll();
|
animateScroll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -99,5 +99,3 @@ export const getYearLater = (): number => {
|
|||||||
date.setFullYear(date.getFullYear() + 1);
|
date.setFullYear(date.getFullYear() + 1);
|
||||||
return date.getTime();
|
return date.getTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
<el-input v-model="form.newPassword" show-password />
|
<el-input v-model="form.newPassword" show-password />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<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-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -47,12 +49,28 @@ const form = reactive({
|
|||||||
const passwordPattern = /^[a-zA-Z0-9!@#$%^&*()_+-=]{6,64}$/;
|
const passwordPattern = /^[a-zA-Z0-9!@#$%^&*()_+-=]{6,64}$/;
|
||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
oldPassword: [
|
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: [
|
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;
|
saving.value = true;
|
||||||
try {
|
try {
|
||||||
await adminChangePasswordApi({ oldPassword: form.oldPassword, newPassword: form.newPassword });
|
await adminChangePasswordApi({
|
||||||
|
oldPassword: form.oldPassword,
|
||||||
|
newPassword: form.newPassword,
|
||||||
|
});
|
||||||
adminStore.forcePasswordChange = false;
|
adminStore.forcePasswordChange = false;
|
||||||
ElMessage.success(t("admin.changedSuccess"));
|
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);
|
router.push(redirect);
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false;
|
saving.value = false;
|
||||||
@@ -82,4 +104,3 @@ const submit = async () => {
|
|||||||
max-width: 760px;
|
max-width: 760px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,12 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('config.huiHttps')" prop="huiHttps">
|
<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
|
<el-option
|
||||||
v-for="item in huiHttpsList"
|
v-for="item in huiHttpsList"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
@@ -165,11 +170,7 @@ import {
|
|||||||
updateConfigsApi,
|
updateConfigsApi,
|
||||||
} from "@/api/config";
|
} from "@/api/config";
|
||||||
import { ConfigsUpdateDto } from "@/api/config/types";
|
import { ConfigsUpdateDto } from "@/api/config/types";
|
||||||
import {
|
import { UploadFile, UploadRawFile, UploadRequestOptions } from "element-plus";
|
||||||
UploadFile,
|
|
||||||
UploadRawFile,
|
|
||||||
UploadRequestOptions,
|
|
||||||
} from "element-plus";
|
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dashboard-container">
|
<div class="dashboard-container">
|
||||||
<div class="dashboard-actions mb-2">
|
<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>
|
</div>
|
||||||
|
|
||||||
<el-alert
|
<el-alert
|
||||||
@@ -23,20 +25,63 @@
|
|||||||
v-for="risk in securityRisks"
|
v-for="risk in securityRisks"
|
||||||
:key="risk.key"
|
:key="risk.key"
|
||||||
:title="$t(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"
|
:closable="risk.dismissible"
|
||||||
class="mb-2"
|
class="mb-2"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<el-row :gutter="10" class="mt-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-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.ram") }}: {{ summary.system.memPercent }}%</el-card></el-col>
|
><el-card shadow="never"
|
||||||
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.disk") }}: {{ summary.system.diskPercent }}%</el-card></el-col>
|
>{{ $t("dashboard.cpu") }}: {{ summary.system.cpuPercent }}%</el-card
|
||||||
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.peers") }}: {{ summary.peers.total }}</el-card></el-col>
|
></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-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.todayDownload") }}: {{ formatBytes(summary.traffic.todayDownloadBytes || 0) }}</el-card></el-col>
|
><el-card shadow="never"
|
||||||
<el-col :xs="24" :sm="12" :lg="6"><el-card shadow="never">{{ $t("dashboard.todayUpload") }}: {{ formatBytes(summary.traffic.todayUploadBytes || 0) }}</el-card></el-col>
|
>{{ $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-row>
|
||||||
|
|
||||||
<el-card shadow="never" class="mt-3">
|
<el-card shadow="never" class="mt-3">
|
||||||
@@ -62,17 +107,25 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card shadow="never" class="mt-3">
|
<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 :data="topPeers" size="small">
|
||||||
<el-table-column prop="name" :label="$t('peer.name')" />
|
<el-table-column prop="name" :label="$t('peer.name')" />
|
||||||
<el-table-column prop="download" :label="$t('dashboard.download')">
|
<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>
|
||||||
<el-table-column prop="upload" :label="$t('dashboard.upload')">
|
<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>
|
||||||
<el-table-column prop="total" :label="$t('dashboard.total')">
|
<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-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -93,20 +146,61 @@ import {
|
|||||||
TitleComponent,
|
TitleComponent,
|
||||||
} from "echarts/components";
|
} from "echarts/components";
|
||||||
import VChart from "vue-echarts";
|
import VChart from "vue-echarts";
|
||||||
import { dashboardSecurityApi, dashboardSummaryApi, dashboardTimeseriesApi, dashboardTopPeersApi } from "@/api/dashboard";
|
import {
|
||||||
import { DashboardSummaryVo, DashboardTimeseriesVo, DashboardTopPeerVo, SecurityRiskVo } from "@/api/dashboard/types";
|
dashboardSecurityApi,
|
||||||
|
dashboardSummaryApi,
|
||||||
|
dashboardTimeseriesApi,
|
||||||
|
dashboardTopPeersApi,
|
||||||
|
} from "@/api/dashboard";
|
||||||
|
import {
|
||||||
|
DashboardSummaryVo,
|
||||||
|
DashboardTimeseriesVo,
|
||||||
|
DashboardTopPeerVo,
|
||||||
|
SecurityRiskVo,
|
||||||
|
} from "@/api/dashboard/types";
|
||||||
import { formatBytes } from "@/utils/byte";
|
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 { t } = useI18n();
|
||||||
|
|
||||||
const summary = ref<DashboardSummaryVo>({
|
const summary = ref<DashboardSummaryVo>({
|
||||||
collectedAt: 0,
|
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 },
|
hysteria: { version: "-", running: false, apiReachable: false },
|
||||||
peers: { total: 0, enabled: 0, disabled: 0, expired: 0, onlinePeers: 0, onlineDevices: 0 },
|
peers: {
|
||||||
traffic: { downloadBytes: 0, uploadBytes: 0, totalBytes: 0, todayDownloadBytes: 0, todayUploadBytes: 0, sinceResetDownloadBytes: 0, sinceResetUploadBytes: 0 },
|
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: {
|
health: {
|
||||||
collector: { status: "stale" },
|
collector: { status: "stale" },
|
||||||
hysteria: { status: "ok" },
|
hysteria: { status: "ok" },
|
||||||
@@ -115,7 +209,12 @@ const summary = ref<DashboardSummaryVo>({
|
|||||||
});
|
});
|
||||||
const topPeers = ref<DashboardTopPeerVo[]>([]);
|
const topPeers = ref<DashboardTopPeerVo[]>([]);
|
||||||
const securityRisks = ref<SecurityRiskVo[]>([]);
|
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 timeseriesLoading = ref(false);
|
||||||
const range = ref("24h");
|
const range = ref("24h");
|
||||||
const loadError = ref("");
|
const loadError = ref("");
|
||||||
@@ -172,10 +271,14 @@ const trafficUploadData = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const hasTrafficData = 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(() =>
|
const systemCpuData = computed(() =>
|
||||||
(timeseries.value.system || []).map((item) => [item.ts, item.cpu || 0])
|
(timeseries.value.system || []).map((item) => [item.ts, item.cpu || 0])
|
||||||
@@ -186,7 +289,11 @@ const systemMemData = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const trafficChartOption = 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: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: "axis",
|
||||||
valueFormatter: (value: number) => formatBytes(value || 0),
|
valueFormatter: (value: number) => formatBytes(value || 0),
|
||||||
@@ -223,7 +330,11 @@ const trafficChartOption = computed(() => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const systemChartOption = 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: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: "axis",
|
||||||
valueFormatter: (value: number) => `${Math.round((value || 0) * 10) / 10}%`,
|
valueFormatter: (value: number) => `${Math.round((value || 0) * 10) / 10}%`,
|
||||||
@@ -319,4 +430,3 @@ onUnmounted(() => {
|
|||||||
height: 340px;
|
height: 340px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ function back() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="errPage-container">
|
<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-row>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<h1 class="text-jumbo text-ginormous">{{ $t("errorPage.oops") }}</h1>
|
<h1 class="text-jumbo text-ginormous">{{ $t("errorPage.oops") }}</h1>
|
||||||
@@ -98,5 +100,3 @@ function back() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ function message() {
|
|||||||
<div class="bullshit__info">
|
<div class="bullshit__info">
|
||||||
{{ $t("errorPage.notFoundInfo") }}
|
{{ $t("errorPage.notFoundInfo") }}
|
||||||
</div>
|
</div>
|
||||||
<a href="/#/" class="bullshit__return-home">{{ $t("errorPage.notFoundBackHome") }}</a>
|
<a href="/#/" class="bullshit__return-home">{{
|
||||||
|
$t("errorPage.notFoundBackHome")
|
||||||
|
}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -272,5 +274,3 @@ function message() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -380,5 +380,3 @@ const closeDialog = (): void => {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -50,10 +50,7 @@
|
|||||||
:model="dataForm"
|
:model="dataForm"
|
||||||
:disabled="true"
|
:disabled="true"
|
||||||
>
|
>
|
||||||
<el-tabs
|
<el-tabs v-model="activeName" class="tabs">
|
||||||
v-model="activeName"
|
|
||||||
class="tabs"
|
|
||||||
>
|
|
||||||
<el-tab-pane :label="$t('hysteria.extension')" name="extension">
|
<el-tab-pane :label="$t('hysteria.extension')" name="extension">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
:content="$t('hysteria.config.remark')"
|
:content="$t('hysteria.config.remark')"
|
||||||
|
|||||||
@@ -107,5 +107,3 @@ onMounted(() => {
|
|||||||
setRecords();
|
setRecords();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -107,4 +107,3 @@ onMounted(() => {
|
|||||||
setRecords();
|
setRecords();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,17 @@
|
|||||||
<el-input v-model="queryParams.remark" clearable style="width: 220px" />
|
<el-input v-model="queryParams.remark" clearable style="width: 220px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<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-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<div class="mb-3">
|
<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
|
<el-upload
|
||||||
v-model:file-list="importFileList"
|
v-model:file-list="importFileList"
|
||||||
:http-request="handleImport"
|
:http-request="handleImport"
|
||||||
@@ -26,7 +30,9 @@
|
|||||||
>
|
>
|
||||||
<el-button>{{ $t("common.import") }}</el-button>
|
<el-button>{{ $t("common.import") }}</el-button>
|
||||||
</el-upload>
|
</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>
|
</div>
|
||||||
<el-table v-loading="loading" :data="records">
|
<el-table v-loading="loading" :data="records">
|
||||||
<el-table-column :label="$t('peer.name')" min-width="220">
|
<el-table-column :label="$t('peer.name')" min-width="220">
|
||||||
@@ -37,42 +43,107 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('peer.status')" min-width="180">
|
<el-table-column :label="$t('peer.status')" min-width="180">
|
||||||
<template #default="scope">
|
<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
|
||||||
<el-tag size="small" class="ml-1" :type="scope.row.online ? 'success' : 'info'">{{ scope.row.online ? $t('peer.online') : $t('peer.offline') }}</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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('peer.traffic')" min-width="260">
|
<el-table-column :label="$t('peer.traffic')" min-width="260">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div>{{ formatBytes(scope.row.downloadBytes + scope.row.uploadBytes) }} / {{ quotaText(scope.row.quotaBytes) }}</div>
|
<div>
|
||||||
<el-progress :percentage="trafficPercent(scope.row)" :status="scope.row.quotaBytes < 0 ? undefined : (trafficPercent(scope.row) >= 90 ? 'exception' : undefined)" :show-text="false" />
|
{{
|
||||||
|
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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('peer.devices')" min-width="120">
|
<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>
|
||||||
<el-table-column :label="$t('peer.expireTime')" min-width="170">
|
<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>
|
||||||
<el-table-column :label="$t('peer.lastConnectionAt')" min-width="170">
|
<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>
|
||||||
<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">
|
<template #default="scope">
|
||||||
<div class="peer-actions">
|
<div class="peer-actions">
|
||||||
<el-button link type="primary" @click="openOverview(scope.row)">{{ $t("peer.overview") }}</el-button>
|
<el-button link type="primary" @click="openOverview(scope.row)">{{
|
||||||
<el-button link type="primary" @click="copyUri(scope.row)">{{ $t("peer.copyUri") }}</el-button>
|
$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-dropdown trigger="click" placement="bottom-end">
|
||||||
<el-button text circle class="peer-more-btn" @click.stop>
|
<el-button text circle class="peer-more-btn" @click.stop>
|
||||||
<i-ep-more-filled />
|
<i-ep-more-filled />
|
||||||
</el-button>
|
</el-button>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item @click="showQr(scope.row)">{{ $t("peer.clientQr") }}</el-dropdown-item>
|
<el-dropdown-item @click="showQr(scope.row)">{{
|
||||||
<el-dropdown-item @click="handleUpdate(scope.row)">{{ $t("common.edit") }}</el-dropdown-item>
|
$t("peer.clientQr")
|
||||||
<el-dropdown-item @click="handleResetTraffic(scope.row)">{{ $t("common.resetTraffic") }}</el-dropdown-item>
|
}}</el-dropdown-item>
|
||||||
<el-dropdown-item @click="handleKick(scope.row)">{{ $t("peer.kick") }}</el-dropdown-item>
|
<el-dropdown-item @click="handleUpdate(scope.row)">{{
|
||||||
<el-dropdown-item @click="handleReleaseKick(scope.row)">{{ $t("peer.releaseKick") }}</el-dropdown-item>
|
$t("common.edit")
|
||||||
<el-dropdown-item divided @click="handleDelete(scope.row)">{{ $t("common.delete") }}</el-dropdown-item>
|
}}</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>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
@@ -90,17 +161,41 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="620px">
|
<el-dialog v-model="dialog.visible" :title="dialog.title" width="620px">
|
||||||
<el-form ref="formRef" :model="dataForm" :rules="rules" label-width="140px">
|
<el-form
|
||||||
<el-form-item :label="$t('peer.name')" prop="name"><el-input v-model="dataForm.name" /></el-form-item>
|
ref="formRef"
|
||||||
<el-form-item :label="$t('peer.remark')"><el-input v-model="dataForm.remark" /></el-form-item>
|
:model="dataForm"
|
||||||
<el-form-item :label="$t('peer.secret')" prop="secret"><el-input v-model="dataForm.secret" show-password /></el-form-item>
|
:rules="rules"
|
||||||
<el-form-item :label="$t('peer.quota')"><el-input-number v-model="dataForm.quotaBytes" :min="-1" /></el-form-item>
|
label-width="140px"
|
||||||
<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.name')" prop="name"
|
||||||
<el-form-item :label="$t('peer.disabled')"><el-switch v-model="disabledBool" /></el-form-item>
|
><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>
|
</el-form>
|
||||||
<template #footer>
|
<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>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -110,32 +205,104 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</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">
|
<div v-if="overview.data">
|
||||||
<p><b>{{ $t('peer.authId') }}:</b> {{ overview.data.authId }}</p>
|
<p>
|
||||||
<p><b>{{ $t('peer.name') }}:</b> {{ overview.data.name }}</p>
|
<b>{{ $t("peer.authId") }}:</b> {{ overview.data.authId }}
|
||||||
<p><b>{{ $t('peer.remark') }}:</b> {{ overview.data.remark || '-' }}</p>
|
</p>
|
||||||
<p><b>{{ $t('peer.createdAt') }}:</b> {{ overview.data.createTime || '-' }}</p>
|
<p>
|
||||||
<p><b>{{ $t('peer.status') }}:</b> {{ overview.data.disabled === 1 ? $t('common.disable') : $t('common.enable') }}</p>
|
<b>{{ $t("peer.name") }}:</b> {{ overview.data.name }}
|
||||||
<p><b>{{ $t('peer.bannedUntil') }}:</b> {{ overview.data.bannedUntil > Date.now() ? timestampToDateTime(overview.data.bannedUntil) : '-' }}</p>
|
</p>
|
||||||
<p><b>{{ $t('peer.totalTraffic') }}:</b> {{ formatBytes((overview.data.downloadBytes || 0) + (overview.data.uploadBytes || 0)) }}</p>
|
<p>
|
||||||
<p><b>{{ $t('peer.quota') }}:</b> {{ quotaText(overview.data.quotaBytes) }}</p>
|
<b>{{ $t("peer.remark") }}:</b> {{ overview.data.remark || "-" }}
|
||||||
<p><b>{{ $t('peer.download') }}:</b> {{ formatBytes(overview.data.downloadBytes) }}</p>
|
</p>
|
||||||
<p><b>{{ $t('peer.upload') }}:</b> {{ formatBytes(overview.data.uploadBytes) }}</p>
|
<p>
|
||||||
<p><b>{{ $t('peer.devices') }}:</b> {{ overview.data.onlineDevices }} / {{ overview.data.maxDevices }}</p>
|
<b>{{ $t("peer.createdAt") }}:</b>
|
||||||
<p><b>{{ $t('peer.expireTime') }}:</b> {{ overview.data.expiresAt === 0 ? $t('peer.unlimited') : timestampToDateTime(overview.data.expiresAt) }}</p>
|
{{ overview.data.createTime || "-" }}
|
||||||
<p><b>{{ $t('peer.lastConnectionAt') }}:</b> {{ overview.data.lastConnectionAt ? timestampToDateTime(overview.data.lastConnectionAt) : '-' }}</p>
|
</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">
|
<div class="mt-2">
|
||||||
<p><b>{{ $t('peer.clientUri') }}:</b></p>
|
<p>
|
||||||
|
<b>{{ $t("peer.clientUri") }}:</b>
|
||||||
|
</p>
|
||||||
<el-input :model-value="overviewClientUrl" readonly />
|
<el-input :model-value="overviewClientUrl" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<el-button type="primary" @click="copyUri(overview.data)">{{ $t("peer.copyUri") }}</el-button>
|
<el-button type="primary" @click="copyUri(overview.data)">{{
|
||||||
<el-button @click="loadOverviewQr(overview.data)">{{ $t("peer.clientQr") }}</el-button>
|
$t("peer.copyUri")
|
||||||
|
}}</el-button>
|
||||||
|
<el-button @click="loadOverviewQr(overview.data)">{{
|
||||||
|
$t("peer.clientQr")
|
||||||
|
}}</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2" v-if="overviewQrUrl">
|
<div class="mt-2" v-if="overviewQrUrl">
|
||||||
<div class="qr-dialog-body">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -164,7 +331,12 @@ import {
|
|||||||
updatePeerApi,
|
updatePeerApi,
|
||||||
exportPeerApi,
|
exportPeerApi,
|
||||||
} from "@/api/peer";
|
} 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";
|
import { UploadFile, UploadRawFile, UploadRequestOptions } from "element-plus";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -177,9 +349,18 @@ const importFileList = ref<UploadFile[]>([]);
|
|||||||
const overviewQrUrl = ref("");
|
const overviewQrUrl = ref("");
|
||||||
const overviewClientUrl = ref("");
|
const overviewClientUrl = ref("");
|
||||||
const formRef = 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 dialog = reactive({ visible: false, title: "", editId: 0 });
|
||||||
const dataForm = reactive<PeerSaveDto & { id?: number }>({
|
const dataForm = reactive<PeerSaveDto & { id?: number }>({
|
||||||
name: "",
|
name: "",
|
||||||
@@ -197,7 +378,13 @@ const disabledBool = computed({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const rules = {
|
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) {
|
function quotaText(v: number) {
|
||||||
@@ -241,7 +428,16 @@ async function handleQuery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleAdd() {
|
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.title = t("common.add");
|
||||||
dialog.editId = 0;
|
dialog.editId = 0;
|
||||||
dialog.visible = true;
|
dialog.visible = true;
|
||||||
@@ -261,10 +457,23 @@ async function submitForm() {
|
|||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
}
|
}
|
||||||
if (dialog.editId > 0 && dataForm.secret) {
|
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) {
|
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);
|
await updatePeerApi(payload);
|
||||||
} else {
|
} else {
|
||||||
await savePeerApi(dataForm);
|
await savePeerApi(dataForm);
|
||||||
@@ -274,13 +483,21 @@ async function submitForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete(row: PeerVo) {
|
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 deletePeerApi({ id: row.id });
|
||||||
await handleQuery();
|
await handleQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleResetTraffic(row: PeerVo) {
|
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 resetPeerTrafficApi({ id: row.id });
|
||||||
await handleQuery();
|
await handleQuery();
|
||||||
}
|
}
|
||||||
@@ -332,7 +549,9 @@ function beforeImport(file: UploadRawFile) {
|
|||||||
async function handleExport() {
|
async function handleExport() {
|
||||||
try {
|
try {
|
||||||
const response = await exportPeerApi();
|
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 url = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
@@ -351,10 +570,26 @@ onMounted(handleQuery);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.peer-title { font-weight: 600; }
|
.peer-title {
|
||||||
.peer-sub { font-size: 12px; color: #909399; }
|
font-weight: 600;
|
||||||
.peer-actions { display: flex; gap: 8px; align-items: center; justify-content: flex-end; }
|
}
|
||||||
.peer-more-btn { font-size: 16px; }
|
.peer-sub {
|
||||||
.qr-dialog-body { display: flex; align-items: center; justify-content: center; padding: 12px 0 20px; }
|
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>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -13,5 +13,3 @@ const { path } = params;
|
|||||||
|
|
||||||
router.replace({ path: "/" + path, query });
|
router.replace({ path: "/" + path, query });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user