mirror of
https://github.com/fatedier/frp.git
synced 2025-04-22 22:41:27 +00:00
frps多语言设计完成
This commit is contained in:
parent
ff0676a3b1
commit
aadb2a8953
3
web/frps/components.d.ts
vendored
3
web/frps/components.d.ts
vendored
@ -11,6 +11,9 @@ declare module 'vue' {
|
|||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||||
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||||
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']
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"element-plus": "^2.5.3",
|
"element-plus": "^2.5.3",
|
||||||
"humanize-plus": "^1.8.2",
|
"humanize-plus": "^1.8.2",
|
||||||
"vue": "^3.4.15",
|
"vue": "^3.4.15",
|
||||||
|
"vue-i18n": "^10.0.5",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -3,37 +3,40 @@
|
|||||||
<header class="grid-content header-color">
|
<header class="grid-content header-color">
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<a href="#">frp</a>
|
<a href="#">{{ t("main.title") }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="right-ability">
|
||||||
|
<div class="lang-switch">
|
||||||
|
<el-dropdown>
|
||||||
|
<img src="./assets/lang.svg" alt="lang">
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item :disabled="locale == 'en' ? true : false"
|
||||||
|
@click="switchLanguage('en')">English</el-dropdown-item>
|
||||||
|
<el-dropdown-item :disabled="locale == 'zh' ? true : false"
|
||||||
|
@click="switchLanguage('zh')">简体中文</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="dark-switch">
|
<div class="dark-switch">
|
||||||
<el-switch
|
<el-switch v-model="darkmodeSwitch" inline-prompt :active-text="t('main.dark.Dark')"
|
||||||
v-model="darkmodeSwitch"
|
:inactive-text="t('main.dark.Light')" @change="toggleDark" style="
|
||||||
inline-prompt
|
|
||||||
active-text="Dark"
|
|
||||||
inactive-text="Light"
|
|
||||||
@change="toggleDark"
|
|
||||||
style="
|
|
||||||
--el-switch-on-color: #444452;
|
--el-switch-on-color: #444452;
|
||||||
--el-switch-off-color: #589ef8;
|
--el-switch-off-color: #589ef8;
|
||||||
"
|
" />
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<section>
|
<section>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col id="side-nav" :xs="24" :md="4">
|
<el-col id="side-nav" :xs="24" :md="4">
|
||||||
<el-menu
|
<el-menu default-active="/" mode="vertical" theme="light" router="false" @select="handleSelect">
|
||||||
default-active="/"
|
<el-menu-item index="/">{{ t("main.Overview") }}</el-menu-item>
|
||||||
mode="vertical"
|
|
||||||
theme="light"
|
|
||||||
router="false"
|
|
||||||
@select="handleSelect"
|
|
||||||
>
|
|
||||||
<el-menu-item index="/">Overview</el-menu-item>
|
|
||||||
<el-sub-menu index="/proxies">
|
<el-sub-menu index="/proxies">
|
||||||
<template #title>
|
<template #title>
|
||||||
<span>Proxies</span>
|
<span>{{ t("main.Proxies.title") }}</span>
|
||||||
</template>
|
</template>
|
||||||
<el-menu-item index="/proxies/tcp">TCP</el-menu-item>
|
<el-menu-item index="/proxies/tcp">TCP</el-menu-item>
|
||||||
<el-menu-item index="/proxies/udp">UDP</el-menu-item>
|
<el-menu-item index="/proxies/udp">UDP</el-menu-item>
|
||||||
@ -43,7 +46,7 @@
|
|||||||
<el-menu-item index="/proxies/stcp">STCP</el-menu-item>
|
<el-menu-item index="/proxies/stcp">STCP</el-menu-item>
|
||||||
<el-menu-item index="/proxies/sudp">SUDP</el-menu-item>
|
<el-menu-item index="/proxies/sudp">SUDP</el-menu-item>
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
<el-menu-item index="">Help</el-menu-item>
|
<el-menu-item index="">{{ t("main.Help") }}</el-menu-item>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
@ -61,11 +64,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useDark, useToggle } from '@vueuse/core'
|
import { useDark, useToggle } from '@vueuse/core'
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
const isDark = useDark()
|
const isDark = useDark()
|
||||||
const darkmodeSwitch = ref(isDark)
|
const darkmodeSwitch = ref(isDark)
|
||||||
const toggleDark = useToggle(isDark)
|
const toggleDark = useToggle(isDark)
|
||||||
|
const switchLanguage = (lang: string) => {
|
||||||
|
locale.value = lang;
|
||||||
|
localStorage.setItem('i18n', lang);
|
||||||
|
}
|
||||||
const handleSelect = (key: string) => {
|
const handleSelect = (key: string) => {
|
||||||
if (key == '') {
|
if (key == '') {
|
||||||
window.open('https://github.com/fatedier/frp')
|
window.open('https://github.com/fatedier/frp')
|
||||||
@ -118,10 +126,20 @@ html.dark .header-color {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-switch {
|
.right-ability {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-right: 40px;
|
padding-right: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lang-switch {
|
||||||
|
width: 30px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-switch img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
1
web/frps/src/assets/lang.svg
Normal file
1
web/frps/src/assets/lang.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1732765556989" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1552" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M864 64a96 96 0 0 1 96 96v704a96 96 0 0 1-96 96H160a96 96 0 0 1-96-96V160a96 96 0 0 1 96-96h704z m0 64H160a32 32 0 0 0-32 32v704a32 32 0 0 0 32 32h704a32 32 0 0 0 32-32V160a32 32 0 0 0-32-32z m-322.4 256c0-31.456 40.64-44.032 58.4-18.08l133.6 195.168V384a32 32 0 0 1 64 0v280.48c0 31.456-40.64 44.032-58.4 18.08l-133.6-195.168v177.088a32 32 0 1 1-64 0z" fill="#ffffff" p-id="1553"></path><path d="M448 352a32 32 0 0 1 0 64H288v80h160a32 32 0 0 1 31.776 28.256L480 528a32 32 0 0 1-32 32H288v72.48h160a32 32 0 1 1 0 64H256a32 32 0 0 1-32-32V384a32 32 0 0 1 32-32z" fill="#ffffff" p-id="1554"></path></svg>
|
After Width: | Height: | Size: 936 B |
79
web/frps/src/assets/locales/en.json
Normal file
79
web/frps/src/assets/locales/en.json
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"main": {
|
||||||
|
"title": "frp",
|
||||||
|
"Overview": "Overview",
|
||||||
|
"Proxies": {
|
||||||
|
"title": "Proxies"
|
||||||
|
},
|
||||||
|
"Help": "Help",
|
||||||
|
"dark": {
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Light": "Light"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"OverView": {
|
||||||
|
"Version": "Version",
|
||||||
|
"BindPort": "BindPort",
|
||||||
|
"KPC_Port": "KCP Bind Port",
|
||||||
|
"QUIC_Port": "QUIC Bind Port",
|
||||||
|
"HTTP_Port": "HTTP Port",
|
||||||
|
"HTTPS_Port": "HTTPS Port",
|
||||||
|
"TCPMux": "TCPMux HTTPConnect Port",
|
||||||
|
"Subdomain": "Subdomain Host",
|
||||||
|
"MaxPoolCount": "Max PoolCount",
|
||||||
|
"MaxPortsPerClient": "Max Ports Per Client",
|
||||||
|
"AllowPorts": "Allow Ports",
|
||||||
|
"TLSForce": "TLS Force",
|
||||||
|
"HeartBeatTimeout": "HeartBeat Timeout",
|
||||||
|
"ClientCounts": "Client Counts",
|
||||||
|
"curConns": "Current Connections",
|
||||||
|
"ProxyCounts": "Proxy Counts",
|
||||||
|
"Chart": {
|
||||||
|
"Traffic": {
|
||||||
|
"title": "Network Traffic",
|
||||||
|
"subTitle": "today",
|
||||||
|
"TrafficIn": "Traffic In",
|
||||||
|
"TrafficOut": "Traffic Out"
|
||||||
|
},
|
||||||
|
"Proxies": {
|
||||||
|
"title": "Proxies",
|
||||||
|
"subTitle": "now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProxiesView": {
|
||||||
|
"Expand": {
|
||||||
|
"Name": "Name",
|
||||||
|
"Type": "Type",
|
||||||
|
"Encryption": "Encryption",
|
||||||
|
"Compression": "Compression",
|
||||||
|
"LastStart": "Last Start",
|
||||||
|
"LastClose": "Last Close",
|
||||||
|
"Domains": "Domains",
|
||||||
|
"SubDomain": "SubDomain",
|
||||||
|
"locations": "locations",
|
||||||
|
"HostRewrite": "HostRewrite",
|
||||||
|
"Multiplexer": "Multiplexer",
|
||||||
|
"RouteByHTTPUser": "RouteByHTTPUser",
|
||||||
|
"Addr": "Addr",
|
||||||
|
"Annotations": "Annotations"
|
||||||
|
},
|
||||||
|
"ClearOffLine": "ClearOfflineProxies",
|
||||||
|
"Refresh": "Refresh",
|
||||||
|
"ClearOffLineInfo": "Are you sure to clear all data of offline proxies?",
|
||||||
|
"name": "Name",
|
||||||
|
"Port": "Port",
|
||||||
|
"Connections": "Connections",
|
||||||
|
"Traffic_In": "Traffic In",
|
||||||
|
"Traffic_Out": "Traffic Out",
|
||||||
|
"ClientVersion": "ClientVersion",
|
||||||
|
"Status": {
|
||||||
|
"title": "Status",
|
||||||
|
"Successinfo": "online"
|
||||||
|
},
|
||||||
|
"Operations": {
|
||||||
|
"title": "Operations",
|
||||||
|
"Traffic": "Traffic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
88
web/frps/src/assets/locales/zh.json
Normal file
88
web/frps/src/assets/locales/zh.json
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"main": {
|
||||||
|
"title": "frp 服务端",
|
||||||
|
"Overview": "总览",
|
||||||
|
"Proxies": {
|
||||||
|
"title": "代理"
|
||||||
|
},
|
||||||
|
"Help": "帮助",
|
||||||
|
"dark": {
|
||||||
|
"Dark": "暗",
|
||||||
|
"Light": "明"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"OverView": {
|
||||||
|
"Expand": {
|
||||||
|
"Name": "通道名称",
|
||||||
|
"Type": "协议类型",
|
||||||
|
"Encryption": "加密",
|
||||||
|
"Compression": "压缩",
|
||||||
|
"LastStart": "最后一次启用时间",
|
||||||
|
"LastClose": "最后一次关闭时间",
|
||||||
|
"Domains": "域名",
|
||||||
|
"SubDomain": "二级域名后缀",
|
||||||
|
"locations": "URL 路由配置",
|
||||||
|
"HostRewrite": "替换 Host Header",
|
||||||
|
"Multiplexer": "复用器类型",
|
||||||
|
"RouteByHTTPUser": "根据 HTTP Basic Auth user 路由",
|
||||||
|
"Addr": "代理端口",
|
||||||
|
"Annotations": "注释"
|
||||||
|
},
|
||||||
|
"Version": "版本",
|
||||||
|
"BindPort": "frp 绑定端口",
|
||||||
|
"KPC_Port": "KCP 绑定端口",
|
||||||
|
"QUIC_Port": "QUIC 绑定端口",
|
||||||
|
"HTTP_Port": "HTTP 代理监听端口",
|
||||||
|
"HTTPS_Port": "HTTPS 代理监听端口",
|
||||||
|
"TCPMux": "TCPMux HTTPConnect 代理监听的端口",
|
||||||
|
"Subdomain": "二级域名后缀",
|
||||||
|
"MaxPoolCount": "最大连接池数量",
|
||||||
|
"MaxPortsPerClient": "单客户端最大代理数",
|
||||||
|
"AllowPorts": "允许代理的服务端端口",
|
||||||
|
"TLSForce": "TLS协议版本",
|
||||||
|
"HeartBeatTimeout": "心跳超时时间",
|
||||||
|
"ClientCounts": "客户端数量",
|
||||||
|
"curConns": "当前连接数",
|
||||||
|
"ProxyCounts": "代理数量",
|
||||||
|
"Chart": {
|
||||||
|
"Traffic": {
|
||||||
|
"title": "网络流量",
|
||||||
|
"subTitle": "今日",
|
||||||
|
"TrafficIn": "进站流量",
|
||||||
|
"TrafficOut": "出站流量"
|
||||||
|
},
|
||||||
|
"Proxies": {
|
||||||
|
"title": "代理占比",
|
||||||
|
"subTitle": "当前"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProxiesView": {
|
||||||
|
"ClearOffLine": "清理离线链接",
|
||||||
|
"Refresh": "刷新",
|
||||||
|
"ClearOffLineInfo": "确定清理所有离线数据?",
|
||||||
|
"name": "代理名称",
|
||||||
|
"Port": "代理端口",
|
||||||
|
"Connections": "连接数",
|
||||||
|
"Traffic_In": "入站流量",
|
||||||
|
"Traffic_Out": "出站流量",
|
||||||
|
"ClientVersion": "frp客户端版本",
|
||||||
|
"Status": {
|
||||||
|
"title": "状态",
|
||||||
|
"Successinfo": "在线"
|
||||||
|
},
|
||||||
|
"Operations": {
|
||||||
|
"title": "操作",
|
||||||
|
"Traffic": "流量"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProxiesViews": {
|
||||||
|
"name": "通道名称",
|
||||||
|
"type": "协议类型",
|
||||||
|
"local_addr": "本地地址",
|
||||||
|
"plugin": "插件",
|
||||||
|
"remote_addr": "远程地址",
|
||||||
|
"status": "状态",
|
||||||
|
"err": "信息"
|
||||||
|
}
|
||||||
|
}
|
@ -1,85 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-page-header
|
<el-page-header :icon="null" style="width: 100%; margin-left: 30px; margin-bottom: 20px">
|
||||||
:icon="null"
|
|
||||||
style="width: 100%; margin-left: 30px; margin-bottom: 20px"
|
|
||||||
>
|
|
||||||
<template #title>
|
<template #title>
|
||||||
<span>{{ proxyType }}</span>
|
<span>{{ proxyType }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #content> </template>
|
<template #content> </template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<div class="flex items-center" style="margin-right: 30px">
|
<div class="flex items-center" style="margin-right: 30px">
|
||||||
<el-popconfirm
|
<el-popconfirm :title="t('ProxiesView.ClearOffLineInfo')" @confirm="clearOfflineProxies">
|
||||||
title="Are you sure to clear all data of offline proxies?"
|
|
||||||
@confirm="clearOfflineProxies"
|
|
||||||
>
|
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button>ClearOfflineProxies</el-button>
|
<el-button>{{ t("ProxiesView.ClearOffLine") }}</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-popconfirm>
|
</el-popconfirm>
|
||||||
<el-button @click="$emit('refresh')">Refresh</el-button>
|
<el-button @click="$emit('refresh')">{{ t("ProxiesView.Refresh") }}</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-page-header>
|
</el-page-header>
|
||||||
|
|
||||||
<el-table
|
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||||
:data="proxies"
|
|
||||||
:default-sort="{ prop: 'name', order: 'ascending' }"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-table-column type="expand">
|
<el-table-column type="expand">
|
||||||
<template #default="props">
|
<template #default="props">
|
||||||
<ProxyViewExpand :row="props.row" :proxyType="proxyType" />
|
<ProxyViewExpand :row="props.row" :proxyType="proxyType" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="Name" prop="name" sortable> </el-table-column>
|
<el-table-column :label="t('ProxiesView.name')" prop="name" sortable> </el-table-column>
|
||||||
<el-table-column label="Port" prop="port" sortable> </el-table-column>
|
<el-table-column :label="t('ProxiesView.Port')" prop="port" sortable> </el-table-column>
|
||||||
<el-table-column label="Connections" prop="conns" sortable>
|
<el-table-column :label="t('ProxiesView.Connections')" prop="conns" sortable>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column :label="t('ProxiesView.Traffic_In')" prop="trafficIn" :formatter="formatTrafficIn" sortable>
|
||||||
label="Traffic In"
|
|
||||||
prop="trafficIn"
|
|
||||||
:formatter="formatTrafficIn"
|
|
||||||
sortable
|
|
||||||
>
|
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column :label="t('ProxiesView.Traffic_Out')" prop="trafficOut" :formatter="formatTrafficOut" sortable>
|
||||||
label="Traffic Out"
|
|
||||||
prop="trafficOut"
|
|
||||||
:formatter="formatTrafficOut"
|
|
||||||
sortable
|
|
||||||
>
|
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="ClientVersion" prop="clientVersion" sortable>
|
<el-table-column :label="t('ProxiesView.ClientVersion')" prop="clientVersion" sortable>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="Status" prop="status" sortable>
|
<el-table-column :label="t('ProxiesView.Status.title')" prop="status" sortable>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.status === 'online'" type="success">{{
|
<el-tag v-if="scope.row.status === 'online'" type="success">{{
|
||||||
scope.row.status
|
t("ProxiesView.Status.Successinfo")
|
||||||
}}</el-tag>
|
}}</el-tag>
|
||||||
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
|
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="Operations">
|
<el-table-column :label="t('ProxiesView.Operations.title')">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button type="primary" :name="scope.row.name" style="margin-bottom: 10px"
|
||||||
type="primary"
|
@click="dialogVisibleName = scope.row.name; dialogVisible = true">{{ t("ProxiesView.Operations.Traffic") }}
|
||||||
:name="scope.row.name"
|
|
||||||
style="margin-bottom: 10px"
|
|
||||||
@click="dialogVisibleName = scope.row.name; dialogVisible = true"
|
|
||||||
>Traffic
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog v-model="dialogVisible" destroy-on-close="true" :title="dialogVisibleName" width="700px">
|
||||||
v-model="dialogVisible"
|
|
||||||
destroy-on-close="true"
|
|
||||||
:title="dialogVisibleName"
|
|
||||||
width="700px">
|
|
||||||
<Traffic :proxyName="dialogVisibleName" />
|
<Traffic :proxyName="dialogVisibleName" />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
@ -91,6 +63,8 @@ import type { BaseProxy } from '../utils/proxy.js'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import ProxyViewExpand from './ProxyViewExpand.vue'
|
import ProxyViewExpand from './ProxyViewExpand.vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
proxies: BaseProxy[]
|
proxies: BaseProxy[]
|
||||||
|
@ -1,59 +1,54 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form
|
<el-form label-position="left" label-width="auto" inline class="proxy-table-expand">
|
||||||
label-position="left"
|
<el-form-item :label="t('OverView.Expand.Name')">
|
||||||
label-width="auto"
|
|
||||||
inline
|
|
||||||
class="proxy-table-expand"
|
|
||||||
>
|
|
||||||
<el-form-item label="Name">
|
|
||||||
<span>{{ row.name }}</span>
|
<span>{{ row.name }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Type">
|
<el-form-item :label="t('OverView.Expand.Type')">
|
||||||
<span>{{ row.type }}</span>
|
<span>{{ row.type }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Encryption">
|
<el-form-item :label="t('OverView.Expand.Encryption')">
|
||||||
<span>{{ row.encryption }}</span>
|
<span>{{ row.encryption }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Compression">
|
<el-form-item :label="t('OverView.Expand.Compression')">
|
||||||
<span>{{ row.compression }}</span>
|
<span>{{ row.compression }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Last Start">
|
<el-form-item :label="t('OverView.Expand.LastStart')">
|
||||||
<span>{{ row.lastStartTime }}</span>
|
<span>{{ row.lastStartTime }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Last Close">
|
<el-form-item :label="t('OverView.Expand.LastClose')">
|
||||||
<span>{{ row.lastCloseTime }}</span>
|
<span>{{ row.lastCloseTime }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<div v-if="proxyType === 'http' || proxyType === 'https'">
|
<div v-if="proxyType === 'http' || proxyType === 'https'">
|
||||||
<el-form-item label="Domains">
|
<el-form-item :label="t('OverView.Expand.Domains')">
|
||||||
<span>{{ row.customDomains }}</span>
|
<span>{{ row.customDomains }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="SubDomain">
|
<el-form-item :label="t('OverView.Expand.SubDomain')">
|
||||||
<span>{{ row.subdomain }}</span>
|
<span>{{ row.subdomain }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="locations">
|
<el-form-item :label="t('OverView.Expand.locations')">
|
||||||
<span>{{ row.locations }}</span>
|
<span>{{ row.locations }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="HostRewrite">
|
<el-form-item :label="t('OverView.Expand.HostRewrite')">
|
||||||
<span>{{ row.hostHeaderRewrite }}</span>
|
<span>{{ row.hostHeaderRewrite }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="proxyType === 'tcpmux'">
|
<div v-else-if="proxyType === 'tcpmux'">
|
||||||
<el-form-item label="Multiplexer">
|
<el-form-item :label="t('OverView.Expand.Multiplexer')">
|
||||||
<span>{{ row.multiplexer }}</span>
|
<span>{{ row.multiplexer }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="RouteByHTTPUser">
|
<el-form-item :label="t('OverView.Expand.RouteByHTTPUser')">
|
||||||
<span>{{ row.routeByHTTPUser }}</span>
|
<span>{{ row.routeByHTTPUser }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Domains">
|
<el-form-item :label="t('OverView.Expand.Domains')">
|
||||||
<span>{{ row.customDomains }}</span>
|
<span>{{ row.customDomains }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="SubDomain">
|
<el-form-item :label="t('OverView.Expand.SubDomain')">
|
||||||
<span>{{ row.subdomain }}</span>
|
<span>{{ row.subdomain }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<el-form-item label="Addr">
|
<el-form-item :label="t('OverView.Expand.Addr')">
|
||||||
<span>{{ row.addr }}</span>
|
<span>{{ row.addr }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
@ -61,7 +56,7 @@
|
|||||||
|
|
||||||
<div v-if="row.annotations && row.annotations.size > 0">
|
<div v-if="row.annotations && row.annotations.size > 0">
|
||||||
<el-divider />
|
<el-divider />
|
||||||
<el-text class="title-text" size="large">Annotations</el-text>
|
<el-text class="title-text" size="large">{{ t("OverView.Expand.Annotations") }}</el-text>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="item in annotationsArray()">
|
<li v-for="item in annotationsArray()">
|
||||||
<span class="annotation-key">{{ item.key }}</span>
|
<span class="annotation-key">{{ item.key }}</span>
|
||||||
@ -72,6 +67,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
row: any
|
row: any
|
||||||
|
@ -3,73 +3,60 @@
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col :md="12">
|
<el-col :md="12">
|
||||||
<div class="source">
|
<div class="source">
|
||||||
<el-form
|
<el-form label-position="left" label-width="220px" class="server_info">
|
||||||
label-position="left"
|
<el-form-item :label="t('OverView.Version')">
|
||||||
label-width="220px"
|
|
||||||
class="server_info"
|
|
||||||
>
|
|
||||||
<el-form-item label="Version">
|
|
||||||
<span>{{ data.version }}</span>
|
<span>{{ data.version }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="BindPort">
|
<el-form-item :label="t('OverView.BindPort')">
|
||||||
<span>{{ data.bindPort }}</span>
|
<span>{{ data.bindPort }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="KCP Bind Port" v-if="data.kcpBindPort != 0">
|
<el-form-item :label="t('OverView.KPC_Port')" v-if="data.kcpBindPort != 0">
|
||||||
<span>{{ data.kcpBindPort }}</span>
|
<span>{{ data.kcpBindPort }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="QUIC Bind Port" v-if="data.quicBindPort != 0">
|
<el-form-item :label="t('OverView.QUIC_Port')" v-if="data.quicBindPort != 0">
|
||||||
<span>{{ data.quicBindPort }}</span>
|
<span>{{ data.quicBindPort }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="HTTP Port" v-if="data.vhostHTTPPort != 0">
|
<el-form-item :label="t('OverView.HTTP_Port')" v-if="data.vhostHTTPPort != 0">
|
||||||
<span>{{ data.vhostHTTPPort }}</span>
|
<span>{{ data.vhostHTTPPort }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="HTTPS Port" v-if="data.vhostHTTPSPort != 0">
|
<el-form-item :label="t('OverView.HTTPS_Port')" v-if="data.vhostHTTPSPort != 0">
|
||||||
<span>{{ data.vhostHTTPSPort }}</span>
|
<span>{{ data.vhostHTTPSPort }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item :label="t('OverView.TCPMux')" v-if="data.tcpmuxHTTPConnectPort != 0">
|
||||||
label="TCPMux HTTPConnect Port"
|
|
||||||
v-if="data.tcpmuxHTTPConnectPort != 0"
|
|
||||||
>
|
|
||||||
<span>{{ data.tcpmuxHTTPConnectPort }}</span>
|
<span>{{ data.tcpmuxHTTPConnectPort }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item :label="t('OverView.Subdomain')" v-if="data.subdomainHost != ''">
|
||||||
label="Subdomain Host"
|
|
||||||
v-if="data.subdomainHost != ''"
|
|
||||||
>
|
|
||||||
<LongSpan :content="data.subdomainHost" :length="30"></LongSpan>
|
<LongSpan :content="data.subdomainHost" :length="30"></LongSpan>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Max PoolCount">
|
<el-form-item :label="t('OverView.MaxPoolCount')">
|
||||||
<span>{{ data.maxPoolCount }}</span>
|
<span>{{ data.maxPoolCount }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Max Ports Per Client">
|
<el-form-item :label="t('OverView.MaxPortsPerClient')">
|
||||||
<span>{{ data.maxPortsPerClient }}</span>
|
<span>{{ data.maxPortsPerClient }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Allow Ports" v-if="data.allowPortsStr != ''">
|
<el-form-item :label="t('OverView.AllowPorts')" v-if="data.allowPortsStr != ''">
|
||||||
<LongSpan :content="data.allowPortsStr" :length="30"></LongSpan>
|
<LongSpan :content="data.allowPortsStr" :length="30"></LongSpan>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="TLS Force" v-if="data.tlsForce === true">
|
<el-form-item :label="t('OverView.TLSForce')" v-if="data.tlsForce === true">
|
||||||
<span>{{ data.tlsForce }}</span>
|
<span>{{ data.tlsForce }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="HeartBeat Timeout">
|
<el-form-item :label="t('OverView.HeartBeatTimeout')">
|
||||||
<span>{{ data.heartbeatTimeout }}</span>
|
<span>{{ data.heartbeatTimeout }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Client Counts">
|
<el-form-item :label="t('OverView.ClientCounts')">
|
||||||
<span>{{ data.clientCounts }}</span>
|
<span>{{ data.clientCounts }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Current Connections">
|
<el-form-item :label="t('OverView.curConns')">
|
||||||
<span>{{ data.curConns }}</span>
|
<span>{{ data.curConns }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Proxy Counts">
|
<el-form-item :label="t('OverView.ProxyCounts')">
|
||||||
<span>{{ data.proxyCounts }}</span>
|
<span>{{ data.proxyCounts }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :md="12">
|
<el-col :md="12">
|
||||||
<div
|
<div id="traffic" style="width: 400px; height: 250px; margin-bottom: 30px"></div>
|
||||||
id="traffic"
|
|
||||||
style="width: 400px; height: 250px; margin-bottom: 30px"
|
|
||||||
></div>
|
|
||||||
<div id="proxies" style="width: 400px; height: 250px"></div>
|
<div id="proxies" style="width: 400px; height: 250px"></div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -81,6 +68,8 @@ import { ref } from 'vue'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { DrawTrafficChart, DrawProxyChart } from '../utils/chart'
|
import { DrawTrafficChart, DrawProxyChart } from '../utils/chart'
|
||||||
import LongSpan from './LongSpan.vue'
|
import LongSpan from './LongSpan.vue'
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
let data = ref({
|
let data = ref({
|
||||||
version: '',
|
version: '',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
@ -6,9 +7,19 @@ import router from './router'
|
|||||||
|
|
||||||
import './assets/custom.css'
|
import './assets/custom.css'
|
||||||
import './assets/dark.css'
|
import './assets/dark.css'
|
||||||
|
import en from './assets/locales/en.json';
|
||||||
|
import zh from './assets/locales/zh.json';
|
||||||
|
const storedLocale = localStorage.getItem('i18n') || 'en';
|
||||||
|
const i18n = createI18n({
|
||||||
|
locale: storedLocale, // 默认语言
|
||||||
|
messages: {
|
||||||
|
en,
|
||||||
|
zh,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
app.use(i18n);
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
@ -3,7 +3,7 @@ import * as echarts from 'echarts/core'
|
|||||||
import { PieChart, BarChart } from 'echarts/charts'
|
import { PieChart, BarChart } from 'echarts/charts'
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
import { LabelLayout } from 'echarts/features'
|
import { LabelLayout } from 'echarts/features'
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import {
|
import {
|
||||||
TitleComponent,
|
TitleComponent,
|
||||||
TooltipComponent,
|
TooltipComponent,
|
||||||
@ -27,6 +27,7 @@ function DrawTrafficChart(
|
|||||||
trafficIn: number,
|
trafficIn: number,
|
||||||
trafficOut: number
|
trafficOut: number
|
||||||
) {
|
) {
|
||||||
|
const { t } = useI18n();
|
||||||
const myChart = echarts.init(
|
const myChart = echarts.init(
|
||||||
document.getElementById(elementId) as HTMLElement,
|
document.getElementById(elementId) as HTMLElement,
|
||||||
'macarons'
|
'macarons'
|
||||||
@ -35,8 +36,8 @@ function DrawTrafficChart(
|
|||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
title: {
|
title: {
|
||||||
text: 'Network Traffic',
|
text: t("OverView.Chart.Traffic.title"),
|
||||||
subtext: 'today',
|
subtext: t("OverView.Chart.Traffic.subTitle"),
|
||||||
left: 'center',
|
left: 'center',
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@ -48,7 +49,7 @@ function DrawTrafficChart(
|
|||||||
legend: {
|
legend: {
|
||||||
orient: 'vertical',
|
orient: 'vertical',
|
||||||
left: 'left',
|
left: 'left',
|
||||||
data: ['Traffic In', 'Traffic Out'],
|
data: [t("OverView.Chart.Traffic.TrafficIn"), t("OverView.Chart.Traffic.TrafficOut")],
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
@ -58,11 +59,11 @@ function DrawTrafficChart(
|
|||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
value: trafficIn,
|
value: trafficIn,
|
||||||
name: 'Traffic In',
|
name: t("OverView.Chart.Traffic.TrafficIn"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: trafficOut,
|
value: trafficOut,
|
||||||
name: 'Traffic Out',
|
name: t("OverView.Chart.Traffic.TrafficOut"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
emphasis: {
|
emphasis: {
|
||||||
@ -80,6 +81,7 @@ function DrawTrafficChart(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DrawProxyChart(elementId: string, serverInfo: any) {
|
function DrawProxyChart(elementId: string, serverInfo: any) {
|
||||||
|
const { t } = useI18n();
|
||||||
const myChart = echarts.init(
|
const myChart = echarts.init(
|
||||||
document.getElementById(elementId) as HTMLElement,
|
document.getElementById(elementId) as HTMLElement,
|
||||||
'macarons'
|
'macarons'
|
||||||
@ -88,8 +90,8 @@ function DrawProxyChart(elementId: string, serverInfo: any) {
|
|||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
title: {
|
title: {
|
||||||
text: 'Proxies',
|
text: t("OverView.Chart.Proxies.title"),
|
||||||
subtext: 'now',
|
subtext: t("OverView.Chart.Proxies.subTitle"),
|
||||||
left: 'center',
|
left: 'center',
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
@ -205,6 +205,27 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917"
|
||||||
integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==
|
integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==
|
||||||
|
|
||||||
|
"@intlify/core-base@10.0.5":
|
||||||
|
version "10.0.5"
|
||||||
|
resolved "https://registry.npmmirror.com/@intlify/core-base/-/core-base-10.0.5.tgz#c4d992381f8c3a50c79faf67be3404b399c3be28"
|
||||||
|
integrity sha512-F3snDTQs0MdvnnyzTDTVkOYVAZOE/MHwRvF7mn7Jw1yuih4NrFYLNYIymGlLmq4HU2iIdzYsZ7f47bOcwY73XQ==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/message-compiler" "10.0.5"
|
||||||
|
"@intlify/shared" "10.0.5"
|
||||||
|
|
||||||
|
"@intlify/message-compiler@10.0.5":
|
||||||
|
version "10.0.5"
|
||||||
|
resolved "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-10.0.5.tgz#4eeace9f4560020d5e5d77f32bed7755e71d8efd"
|
||||||
|
integrity sha512-6GT1BJ852gZ0gItNZN2krX5QAmea+cmdjMvsWohArAZ3GmHdnNANEcF9JjPXAMRtQ6Ux5E269ymamg/+WU6tQA==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/shared" "10.0.5"
|
||||||
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
|
"@intlify/shared@10.0.5":
|
||||||
|
version "10.0.5"
|
||||||
|
resolved "https://registry.npmmirror.com/@intlify/shared/-/shared-10.0.5.tgz#1b46ca8b541f03508fe28da8f34e4bb85506d6bc"
|
||||||
|
integrity sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec@^1.4.15":
|
"@jridgewell/sourcemap-codec@^1.4.15":
|
||||||
version "1.4.15"
|
version "1.4.15"
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||||
@ -2523,6 +2544,15 @@ vue-eslint-parser@^9.3.1, vue-eslint-parser@^9.4.2:
|
|||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
semver "^7.3.6"
|
semver "^7.3.6"
|
||||||
|
|
||||||
|
vue-i18n@^10.0.5:
|
||||||
|
version "10.0.5"
|
||||||
|
resolved "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-10.0.5.tgz#fdf4e6c7b669e80cfa3a12ed9625e2b46671cdf0"
|
||||||
|
integrity sha512-9/gmDlCblz3i8ypu/afiIc/SUIfTTE1mr0mZhb9pk70xo2csHAM9mp2gdQ3KD2O0AM3Hz/5ypb+FycTj/lHlPQ==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/core-base" "10.0.5"
|
||||||
|
"@intlify/shared" "10.0.5"
|
||||||
|
"@vue/devtools-api" "^6.5.0"
|
||||||
|
|
||||||
vue-router@^4.2.5:
|
vue-router@^4.2.5:
|
||||||
version "4.2.5"
|
version "4.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.5.tgz#b9e3e08f1bd9ea363fdd173032620bc50cf0e98a"
|
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.5.tgz#b9e3e08f1bd9ea363fdd173032620bc50cf0e98a"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user