Merge 3208c975bb9f7d55e96c326e3f910aafa26b61c2 into 01fed8d1a97dce7d19d520877949f6e19054ee54

This commit is contained in:
nkdns 2024-12-20 16:47:00 +08:00 committed by GitHub
commit 657a915b5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 668 additions and 360 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,8 @@
<head>
<meta charset="utf-8">
<title>frp client admin UI</title>
<script type="module" crossorigin src="./index-bLBhaJo8.js"></script>
<link rel="stylesheet" crossorigin href="./index-iuf46MlF.css">
<script type="module" crossorigin src="./index-Cgx39LXC.js"></script>
<link rel="stylesheet" crossorigin href="./index-N4E3zN8T.css">
</head>
<body>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,8 @@
<head>
<meta charset="utf-8">
<title>frps dashboard</title>
<script type="module" crossorigin src="./index-82-40HIG.js"></script>
<link rel="stylesheet" crossorigin href="./index-rzPDshRD.css">
<script type="module" crossorigin src="./index-yOnBcT7d.js"></script>
<link rel="stylesheet" crossorigin href="./index-5A9aPAsI.css">
</head>
<body>

View File

@ -10,6 +10,9 @@ declare module 'vue' {
ClientConfigure: typeof import('./src/components/ClientConfigure.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElInput: typeof import('element-plus/es')['ElInput']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']

View File

@ -13,6 +13,7 @@
"dependencies": {
"element-plus": "^2.5.3",
"vue": "^3.4.15",
"vue-i18n": "^10.0.5",
"vue-router": "^4.2.5"
},
"devDependencies": {

View File

@ -3,36 +3,39 @@
<header class="grid-content header-color">
<div class="header-content">
<div class="brand">
<a href="#">frp client</a>
<a href="#">{{ t("main.title") }}</a>
</div>
<div class="dark-switch">
<el-switch
v-model="darkmodeSwitch"
inline-prompt
active-text="Dark"
inactive-text="Light"
@change="toggleDark"
style="
<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 class="dark-switch">
<el-switch v-model="darkmodeSwitch" inline-prompt :active-text="t('main.dark.Dark')"
:inactive-text="t('main.dark.Light')" @change="toggleDark" style="
--el-switch-on-color: #444452;
--el-switch-off-color: #589ef8;
"
/>
" />
</div>
</div>
</div>
</header>
<section>
<el-row>
<el-col id="side-nav" :xs="24" :md="4">
<el-menu
default-active="1"
mode="vertical"
theme="light"
router="false"
@select="handleSelect"
>
<el-menu-item index="/">Overview</el-menu-item>
<el-menu-item index="/configure">Configure</el-menu-item>
<el-menu-item index="">Help</el-menu-item>
<el-menu default-active="1" mode="vertical" theme="light" router="false" @select="handleSelect">
<el-menu-item index="/">{{ t("main.Overview") }}</el-menu-item>
<el-menu-item index="/configure">{{ t("main.Configure") }}</el-menu-item>
<el-menu-item index="">{{ t("main.Help") }}</el-menu-item>
</el-menu>
</el-col>
@ -50,11 +53,16 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useDark, useToggle } from '@vueuse/core'
import { useI18n } from 'vue-i18n';
const { t, locale } = useI18n();
const isDark = useDark()
const darkmodeSwitch = ref(isDark)
const toggleDark = useToggle(isDark)
const switchLanguage = (lang: string) => {
locale.value = lang;
localStorage.setItem('i18n', lang);
}
const handleSelect = (key: string) => {
if (key == '') {
window.open('https://github.com/fatedier/frp')
@ -107,10 +115,20 @@ html.dark .header-color {
text-decoration: none;
}
.dark-switch {
.right-ability {
display: flex;
justify-content: flex-end;
flex-grow: 1;
padding-right: 40px;
}
.lang-switch {
width: 30px;
margin-right: 10px;
}
.lang-switch img {
width: 100%;
height: auto;
}
</style>

View 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

View File

@ -0,0 +1,25 @@
{
"main": {
"title": "frp client",
"Overview": "Overview",
"Configure": "Configure",
"Help": "Help",
"dark": {
"Dark": "Dark",
"Light": "Light"
}
},
"OverView": {
"name": "name",
"type": "type",
"local_addr": "local address",
"plugin": "plugin",
"remote_addr": "remote address",
"status": "status",
"err": "info"
},
"Configure": {
"Refresh": "Refresh",
"Upload": "Upload"
}
}

View File

@ -0,0 +1,25 @@
{
"main": {
"title": "frp 客户端",
"Overview": "总览",
"Configure": "配置",
"Help": "帮助",
"dark": {
"Dark": "暗",
"Light": "明"
}
},
"OverView": {
"name": "通道名称",
"type": "协议类型",
"local_addr": "本地地址",
"plugin": "插件",
"remote_addr": "远程地址",
"status": "状态",
"err": "信息"
},
"Configure": {
"Refresh": "读取配置",
"Upload": "保存配置"
}
}

View File

@ -1,21 +1,19 @@
<template>
<div>
<el-row id="head">
<el-button type="primary" @click="fetchData">Refresh</el-button>
<el-button type="primary" @click="uploadConfig">Upload</el-button>
<el-button type="primary" @click="fetchData">{{ t("Configure.Refresh") }}</el-button>
<el-button type="primary" @click="uploadConfig">{{ t("Configure.Upload") }}</el-button>
</el-row>
<el-input
type="textarea"
autosize
v-model="textarea"
placeholder="frpc configrue file, can not be empty..."
></el-input>
<el-input type="textarea" autosize v-model="textarea"
placeholder="frpc configrue file, can not be empty..."></el-input>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
let textarea = ref('')

View File

@ -3,47 +3,14 @@
<el-row>
<el-col :md="24">
<div>
<el-table
:data="status"
stripe
style="width: 100%"
:default-sort="{ prop: 'type', order: 'ascending' }"
>
<el-table-column
prop="name"
label="name"
sortable
></el-table-column>
<el-table-column
prop="type"
label="type"
width="150"
sortable
></el-table-column>
<el-table-column
prop="local_addr"
label="local address"
width="200"
sortable
></el-table-column>
<el-table-column
prop="plugin"
label="plugin"
width="200"
sortable
></el-table-column>
<el-table-column
prop="remote_addr"
label="remote address"
sortable
></el-table-column>
<el-table-column
prop="status"
label="status"
width="150"
sortable
></el-table-column>
<el-table-column prop="err" label="info"></el-table-column>
<el-table :data="status" stripe style="width: 100%" :default-sort="{ prop: 'type', order: 'ascending' }">
<el-table-column prop="name" :label="t('OverView.name')" sortable></el-table-column>
<el-table-column prop="type" :label="t('OverView.type')" width="150" sortable></el-table-column>
<el-table-column prop="local_addr" :label="t('OverView.local_addr')" width="200" sortable></el-table-column>
<el-table-column prop="plugin" :label="t('OverView.plugin')" width="200" sortable></el-table-column>
<el-table-column prop="remote_addr" :label="t('OverView.remote_addr')" sortable></el-table-column>
<el-table-column prop="status" :label="t('OverView.status')" width="150" sortable></el-table-column>
<el-table-column prop="err" :label="t('OverView.err')"></el-table-column>
</el-table>
</div>
</el-col>
@ -54,6 +21,8 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
let status = ref<any[]>([])

View File

@ -1,13 +1,24 @@
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n';
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import App from './App.vue'
import router from './router'
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)
app.use(router)
app.use(i18n);
app.mount('#app')

View File

@ -205,6 +205,27 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917"
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":
version "1.4.15"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
@ -2500,6 +2521,15 @@ vue-eslint-parser@^9.3.1, vue-eslint-parser@^9.4.2:
lodash "^4.17.21"
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:
version "4.2.5"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.5.tgz#b9e3e08f1bd9ea363fdd173032620bc50cf0e98a"

View File

@ -11,6 +11,9 @@ declare module 'vue' {
ElCol: typeof import('element-plus/es')['ElCol']
ElDialog: typeof import('element-plus/es')['ElDialog']
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']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElMenu: typeof import('element-plus/es')['ElMenu']

View File

@ -16,6 +16,7 @@
"element-plus": "^2.5.3",
"humanize-plus": "^1.8.2",
"vue": "^3.4.15",
"vue-i18n": "^10.0.5",
"vue-router": "^4.2.5"
},
"devDependencies": {

View File

@ -3,37 +3,40 @@
<header class="grid-content header-color">
<div class="header-content">
<div class="brand">
<a href="#">frp</a>
<a href="#">{{ t("main.title") }}</a>
</div>
<div class="dark-switch">
<el-switch
v-model="darkmodeSwitch"
inline-prompt
active-text="Dark"
inactive-text="Light"
@change="toggleDark"
style="
<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 class="dark-switch">
<el-switch v-model="darkmodeSwitch" inline-prompt :active-text="t('main.dark.Dark')"
:inactive-text="t('main.dark.Light')" @change="toggleDark" style="
--el-switch-on-color: #444452;
--el-switch-off-color: #589ef8;
"
/>
" />
</div>
</div>
</div>
</header>
<section>
<el-row>
<el-col id="side-nav" :xs="24" :md="4">
<el-menu
default-active="/"
mode="vertical"
theme="light"
router="false"
@select="handleSelect"
>
<el-menu-item index="/">Overview</el-menu-item>
<el-menu default-active="/" mode="vertical" theme="light" router="false" @select="handleSelect">
<el-menu-item index="/">{{ t("main.Overview") }}</el-menu-item>
<el-sub-menu index="/proxies">
<template #title>
<span>Proxies</span>
<span>{{ t("main.Proxies.title") }}</span>
</template>
<el-menu-item index="/proxies/tcp">TCP</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/sudp">SUDP</el-menu-item>
</el-sub-menu>
<el-menu-item index="">Help</el-menu-item>
<el-menu-item index="">{{ t("main.Help") }}</el-menu-item>
</el-menu>
</el-col>
@ -61,11 +64,16 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useDark, useToggle } from '@vueuse/core'
import { useI18n } from 'vue-i18n';
const { t, locale } = useI18n();
const isDark = useDark()
const darkmodeSwitch = ref(isDark)
const toggleDark = useToggle(isDark)
const switchLanguage = (lang: string) => {
locale.value = lang;
localStorage.setItem('i18n', lang);
}
const handleSelect = (key: string) => {
if (key == '') {
window.open('https://github.com/fatedier/frp')
@ -118,10 +126,20 @@ html.dark .header-color {
text-decoration: none;
}
.dark-switch {
.right-ability {
display: flex;
justify-content: flex-end;
flex-grow: 1;
padding-right: 40px;
}
.lang-switch {
width: 30px;
margin-right: 10px;
}
.lang-switch img {
width: 100%;
height: auto;
}
</style>

View 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

View 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"
}
}
}

View 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": "信息"
}
}

View File

@ -1,85 +1,57 @@
<template>
<div>
<el-page-header
:icon="null"
style="width: 100%; margin-left: 30px; margin-bottom: 20px"
>
<el-page-header :icon="null" style="width: 100%; margin-left: 30px; margin-bottom: 20px">
<template #title>
<span>{{ proxyType }}</span>
</template>
<template #content> </template>
<template #extra>
<div class="flex items-center" style="margin-right: 30px">
<el-popconfirm
title="Are you sure to clear all data of offline proxies?"
@confirm="clearOfflineProxies"
>
<el-popconfirm :title="t('ProxiesView.ClearOffLineInfo')" @confirm="clearOfflineProxies">
<template #reference>
<el-button>ClearOfflineProxies</el-button>
<el-button>{{ t("ProxiesView.ClearOffLine") }}</el-button>
</template>
</el-popconfirm>
<el-button @click="$emit('refresh')">Refresh</el-button>
<el-button @click="$emit('refresh')">{{ t("ProxiesView.Refresh") }}</el-button>
</div>
</template>
</el-page-header>
<el-table
:data="proxies"
:default-sort="{ prop: 'name', order: 'ascending' }"
style="width: 100%"
>
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
<el-table-column type="expand">
<template #default="props">
<ProxyViewExpand :row="props.row" :proxyType="proxyType" />
</template>
</el-table-column>
<el-table-column label="Name" prop="name" sortable> </el-table-column>
<el-table-column label="Port" prop="port" sortable> </el-table-column>
<el-table-column label="Connections" prop="conns" sortable>
<el-table-column :label="t('ProxiesView.name')" prop="name" sortable> </el-table-column>
<el-table-column :label="t('ProxiesView.Port')" prop="port" sortable> </el-table-column>
<el-table-column :label="t('ProxiesView.Connections')" prop="conns" sortable>
</el-table-column>
<el-table-column
label="Traffic In"
prop="trafficIn"
:formatter="formatTrafficIn"
sortable
>
<el-table-column :label="t('ProxiesView.Traffic_In')" prop="trafficIn" :formatter="formatTrafficIn" sortable>
</el-table-column>
<el-table-column
label="Traffic Out"
prop="trafficOut"
:formatter="formatTrafficOut"
sortable
>
<el-table-column :label="t('ProxiesView.Traffic_Out')" prop="trafficOut" :formatter="formatTrafficOut" sortable>
</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 label="Status" prop="status" sortable>
<el-table-column :label="t('ProxiesView.Status.title')" prop="status" sortable>
<template #default="scope">
<el-tag v-if="scope.row.status === 'online'" type="success">{{
scope.row.status
t("ProxiesView.Status.Successinfo")
}}</el-tag>
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="Operations">
<el-table-column :label="t('ProxiesView.Operations.title')">
<template #default="scope">
<el-button
type="primary"
:name="scope.row.name"
style="margin-bottom: 10px"
@click="dialogVisibleName = scope.row.name; dialogVisible = true"
>Traffic
<el-button type="primary" :name="scope.row.name" style="margin-bottom: 10px"
@click="dialogVisibleName = scope.row.name; dialogVisible = true">{{ t("ProxiesView.Operations.Traffic") }}
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog
v-model="dialogVisible"
destroy-on-close="true"
:title="dialogVisibleName"
width="700px">
<el-dialog v-model="dialogVisible" destroy-on-close="true" :title="dialogVisibleName" width="700px">
<Traffic :proxyName="dialogVisibleName" />
</el-dialog>
</template>
@ -91,6 +63,8 @@ import type { BaseProxy } from '../utils/proxy.js'
import { ElMessage } from 'element-plus'
import ProxyViewExpand from './ProxyViewExpand.vue'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
defineProps<{
proxies: BaseProxy[]

View File

@ -1,77 +1,74 @@
<template>
<el-form
label-position="left"
label-width="auto"
inline
class="proxy-table-expand"
>
<el-form-item label="Name">
<el-form label-position="left" label-width="auto" inline class="proxy-table-expand">
<el-form-item :label="t('OverView.Expand.Name')">
<span>{{ row.name }}</span>
</el-form-item>
<el-form-item label="Type">
<el-form-item :label="t('OverView.Expand.Type')">
<span>{{ row.type }}</span>
</el-form-item>
<el-form-item label="Encryption">
<el-form-item :label="t('OverView.Expand.Encryption')">
<span>{{ row.encryption }}</span>
</el-form-item>
<el-form-item label="Compression">
<el-form-item :label="t('OverView.Expand.Compression')">
<span>{{ row.compression }}</span>
</el-form-item>
<el-form-item label="Last Start">
<el-form-item :label="t('OverView.Expand.LastStart')">
<span>{{ row.lastStartTime }}</span>
</el-form-item>
<el-form-item label="Last Close">
<el-form-item :label="t('OverView.Expand.LastClose')">
<span>{{ row.lastCloseTime }}</span>
</el-form-item>
<div v-if="proxyType === 'http' || proxyType === 'https'">
<el-form-item label="Domains">
<el-form-item :label="t('OverView.Expand.Domains')">
<span>{{ row.customDomains }}</span>
</el-form-item>
<el-form-item label="SubDomain">
<el-form-item :label="t('OverView.Expand.SubDomain')">
<span>{{ row.subdomain }}</span>
</el-form-item>
<el-form-item label="locations">
<el-form-item :label="t('OverView.Expand.locations')">
<span>{{ row.locations }}</span>
</el-form-item>
<el-form-item label="HostRewrite">
<el-form-item :label="t('OverView.Expand.HostRewrite')">
<span>{{ row.hostHeaderRewrite }}</span>
</el-form-item>
</div>
<div v-else-if="proxyType === 'tcpmux'">
<el-form-item label="Multiplexer">
<el-form-item :label="t('OverView.Expand.Multiplexer')">
<span>{{ row.multiplexer }}</span>
</el-form-item>
<el-form-item label="RouteByHTTPUser">
<el-form-item :label="t('OverView.Expand.RouteByHTTPUser')">
<span>{{ row.routeByHTTPUser }}</span>
</el-form-item>
<el-form-item label="Domains">
<el-form-item :label="t('OverView.Expand.Domains')">
<span>{{ row.customDomains }}</span>
</el-form-item>
<el-form-item label="SubDomain">
<el-form-item :label="t('OverView.Expand.SubDomain')">
<span>{{ row.subdomain }}</span>
</el-form-item>
</div>
<div v-else>
<el-form-item label="Addr">
<el-form-item :label="t('OverView.Expand.Addr')">
<span>{{ row.addr }}</span>
</el-form-item>
</div>
</el-form>
<div v-if="row.annotations && row.annotations.size > 0">
<el-divider />
<el-text class="title-text" size="large">Annotations</el-text>
<ul>
<li v-for="item in annotationsArray()">
<span class="annotation-key">{{ item.key }}</span>
<span>{{ item.value }}</span>
</li>
</ul>
<el-divider />
<el-text class="title-text" size="large">{{ t("OverView.Expand.Annotations") }}</el-text>
<ul>
<li v-for="item in annotationsArray()">
<span class="annotation-key">{{ item.key }}</span>
<span>{{ item.value }}</span>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps<{
row: any

View File

@ -3,73 +3,60 @@
<el-row>
<el-col :md="12">
<div class="source">
<el-form
label-position="left"
label-width="220px"
class="server_info"
>
<el-form-item label="Version">
<el-form label-position="left" label-width="220px" class="server_info">
<el-form-item :label="t('OverView.Version')">
<span>{{ data.version }}</span>
</el-form-item>
<el-form-item label="BindPort">
<el-form-item :label="t('OverView.BindPort')">
<span>{{ data.bindPort }}</span>
</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>
</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>
</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>
</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>
</el-form-item>
<el-form-item
label="TCPMux HTTPConnect Port"
v-if="data.tcpmuxHTTPConnectPort != 0"
>
<el-form-item :label="t('OverView.TCPMux')" v-if="data.tcpmuxHTTPConnectPort != 0">
<span>{{ data.tcpmuxHTTPConnectPort }}</span>
</el-form-item>
<el-form-item
label="Subdomain Host"
v-if="data.subdomainHost != ''"
>
<el-form-item :label="t('OverView.Subdomain')" v-if="data.subdomainHost != ''">
<LongSpan :content="data.subdomainHost" :length="30"></LongSpan>
</el-form-item>
<el-form-item label="Max PoolCount">
<el-form-item :label="t('OverView.MaxPoolCount')">
<span>{{ data.maxPoolCount }}</span>
</el-form-item>
<el-form-item label="Max Ports Per Client">
<el-form-item :label="t('OverView.MaxPortsPerClient')">
<span>{{ data.maxPortsPerClient }}</span>
</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>
</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>
</el-form-item>
<el-form-item label="HeartBeat Timeout">
<el-form-item :label="t('OverView.HeartBeatTimeout')">
<span>{{ data.heartbeatTimeout }}</span>
</el-form-item>
<el-form-item label="Client Counts">
<el-form-item :label="t('OverView.ClientCounts')">
<span>{{ data.clientCounts }}</span>
</el-form-item>
<el-form-item label="Current Connections">
<el-form-item :label="t('OverView.curConns')">
<span>{{ data.curConns }}</span>
</el-form-item>
<el-form-item label="Proxy Counts">
<el-form-item :label="t('OverView.ProxyCounts')">
<span>{{ data.proxyCounts }}</span>
</el-form-item>
</el-form>
</div>
</el-col>
<el-col :md="12">
<div
id="traffic"
style="width: 400px; height: 250px; margin-bottom: 30px"
></div>
<div id="traffic" style="width: 400px; height: 250px; margin-bottom: 30px"></div>
<div id="proxies" style="width: 400px; height: 250px"></div>
</el-col>
</el-row>
@ -81,6 +68,8 @@ import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { DrawTrafficChart, DrawProxyChart } from '../utils/chart'
import LongSpan from './LongSpan.vue'
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
let data = ref({
version: '',

View File

@ -1,4 +1,5 @@
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n';
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import App from './App.vue'
@ -6,9 +7,19 @@ import router from './router'
import './assets/custom.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)
app.use(router)
app.use(i18n);
app.mount('#app')

View File

@ -205,6 +205,27 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917"
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":
version "1.4.15"
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"
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:
version "4.2.5"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.5.tgz#b9e3e08f1bd9ea363fdd173032620bc50cf0e98a"