logo优化、逻辑调整、动作变动、边际问题发现并修复
This commit is contained in:
Binary file not shown.
@@ -1,9 +0,0 @@
|
|||||||
2026-07-23 23:08:37.413 [I] [sub/root.go:201] start frpc service for config file [./frpc.toml] with aggregated configuration
|
|
||||||
2026-07-23 23:08:37.422 [I] [client/service.go:308] try to connect to server...
|
|
||||||
2026-07-23 23:08:37.587 [I] [client/service.go:328] [e42214c3339795cc] login to server success, get run id [e42214c3339795cc]
|
|
||||||
2026-07-23 23:11:52.981 [I] [sub/root.go:201] start frpc service for config file [./frpc.toml] with aggregated configuration
|
|
||||||
2026-07-23 23:11:52.991 [I] [client/service.go:308] try to connect to server...
|
|
||||||
2026-07-23 23:11:53.214 [I] [client/service.go:328] [5fc7383f8cd6b111] login to server success, get run id [5fc7383f8cd6b111]
|
|
||||||
2026-07-23 23:13:55.183 [I] [sub/root.go:201] start frpc service for config file [./frpc.toml] with aggregated configuration
|
|
||||||
2026-07-23 23:13:55.201 [I] [client/service.go:308] try to connect to server...
|
|
||||||
2026-07-23 23:13:55.366 [I] [client/service.go:328] [39fcc2092c9b87ae] login to server success, get run id [39fcc2092c9b87ae]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
serverAddr = "frp.whitetop.xyz"
|
|
||||||
serverPort = 9358
|
|
||||||
|
|
||||||
[auth]
|
|
||||||
token = "Lxh10020328"
|
|
||||||
|
|
||||||
[log]
|
|
||||||
to = "./frpc.log"
|
|
||||||
level = "info"
|
|
||||||
maxDays = 3
|
|
||||||
|
|
||||||
[transport]
|
|
||||||
tcpMux = true
|
|
||||||
tcpMuxKeepaliveInterval = 30
|
|
||||||
heartbeatInterval = 15
|
|
||||||
heartbeatTimeout = 70
|
|
||||||
poolCount = 8
|
|
||||||
|
|
||||||
[webServer]
|
|
||||||
addr = "127.0.0.1:7400"
|
|
||||||
|
|
||||||
+111
-71
@@ -1,23 +1,29 @@
|
|||||||
|
// app.js - 普通脚本(非模块)
|
||||||
|
|
||||||
const { createApp, ref, reactive, computed, onMounted } = Vue;
|
const { createApp, ref, reactive, computed, onMounted } = Vue;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// API
|
// 1. 基础 API
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const API_BASE = "/api";
|
const API_BASE = "/api";
|
||||||
|
|
||||||
function getToken() {
|
function getToken() {
|
||||||
return localStorage.getItem("frpc_token") || "";
|
return localStorage.getItem("frpc_token") || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHeaders() {
|
function getHeaders() {
|
||||||
return {
|
return {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: "Bearer " + getToken(),
|
Authorization: "Bearer " + getToken(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(endpoint, options = {}) {
|
async function apiFetch(endpoint, options = {}) {
|
||||||
const url = API_BASE + endpoint;
|
const url = API_BASE + endpoint;
|
||||||
const headers = options.body instanceof FormData
|
const headers =
|
||||||
? { Authorization: "Bearer " + getToken() }
|
options.body instanceof FormData
|
||||||
: getHeaders();
|
? { Authorization: "Bearer " + getToken() }
|
||||||
|
: getHeaders();
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
...options,
|
...options,
|
||||||
headers: { ...headers, ...(options.headers || {}) },
|
headers: { ...headers, ...(options.headers || {}) },
|
||||||
@@ -26,7 +32,7 @@ async function apiFetch(endpoint, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Auth
|
// 2. Auth
|
||||||
// ============================================================
|
// ============================================================
|
||||||
function parseJwtExpire(token) {
|
function parseJwtExpire(token) {
|
||||||
try {
|
try {
|
||||||
@@ -36,6 +42,7 @@ function parseJwtExpire(token) {
|
|||||||
return Date.now() + 7 * 24 * 60 * 60 * 1000;
|
return Date.now() + 7 * 24 * 60 * 60 * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveAuthState(token, username) {
|
function saveAuthState(token, username) {
|
||||||
const expire = parseJwtExpire(token);
|
const expire = parseJwtExpire(token);
|
||||||
localStorage.setItem("frpc_token", token);
|
localStorage.setItem("frpc_token", token);
|
||||||
@@ -43,6 +50,7 @@ function saveAuthState(token, username) {
|
|||||||
localStorage.setItem("frpc_username", username);
|
localStorage.setItem("frpc_username", username);
|
||||||
return expire;
|
return expire;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadAuthState() {
|
function loadAuthState() {
|
||||||
const token = localStorage.getItem("frpc_token");
|
const token = localStorage.getItem("frpc_token");
|
||||||
const expire = parseInt(localStorage.getItem("frpc_token_expire") || "0", 10);
|
const expire = parseInt(localStorage.getItem("frpc_token_expire") || "0", 10);
|
||||||
@@ -53,11 +61,13 @@ function loadAuthState() {
|
|||||||
clearAuthState();
|
clearAuthState();
|
||||||
return { token: "", expire: 0, username: "", valid: false };
|
return { token: "", expire: 0, username: "", valid: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAuthState() {
|
function clearAuthState() {
|
||||||
localStorage.removeItem("frpc_token");
|
localStorage.removeItem("frpc_token");
|
||||||
localStorage.removeItem("frpc_token_expire");
|
localStorage.removeItem("frpc_token_expire");
|
||||||
localStorage.removeItem("frpc_username");
|
localStorage.removeItem("frpc_username");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(username, password) {
|
async function login(username, password) {
|
||||||
const data = await apiFetch("/login", {
|
const data = await apiFetch("/login", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -72,7 +82,7 @@ async function login(username, password) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Config
|
// 3. Config
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
serverAddr: "frp.whitetop.xyz",
|
serverAddr: "frp.whitetop.xyz",
|
||||||
@@ -86,6 +96,7 @@ const defaultConfig = {
|
|||||||
heartbeatTimeout: 70,
|
heartbeatTimeout: 70,
|
||||||
poolCount: 8,
|
poolCount: 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function loadConfig() {
|
async function loadConfig() {
|
||||||
try {
|
try {
|
||||||
const data = await apiFetch("/config");
|
const data = await apiFetch("/config");
|
||||||
@@ -97,6 +108,7 @@ async function loadConfig() {
|
|||||||
}
|
}
|
||||||
return { ...defaultConfig };
|
return { ...defaultConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveConfigApi(config) {
|
async function saveConfigApi(config) {
|
||||||
const data = await apiFetch("/config", {
|
const data = await apiFetch("/config", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -106,7 +118,7 @@ async function saveConfigApi(config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Proxies
|
// 4. Proxies
|
||||||
// ============================================================
|
// ============================================================
|
||||||
async function loadProxies() {
|
async function loadProxies() {
|
||||||
try {
|
try {
|
||||||
@@ -119,6 +131,7 @@ async function loadProxies() {
|
|||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createProxy(proxy) {
|
async function createProxy(proxy) {
|
||||||
const data = await apiFetch("/proxy", {
|
const data = await apiFetch("/proxy", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -126,6 +139,7 @@ async function createProxy(proxy) {
|
|||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateProxy(proxy) {
|
async function updateProxy(proxy) {
|
||||||
const data = await apiFetch("/proxy/" + proxy.id, {
|
const data = await apiFetch("/proxy/" + proxy.id, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -133,24 +147,27 @@ async function updateProxy(proxy) {
|
|||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteProxy(id) {
|
async function deleteProxy(id) {
|
||||||
const data = await apiFetch("/proxy/" + id, {
|
const data = await apiFetch("/proxy/" + id, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterProxies(list, keyword) {
|
function filterProxies(list, keyword) {
|
||||||
if (!keyword) return list;
|
if (!keyword) return list;
|
||||||
const kw = keyword.toLowerCase();
|
const kw = keyword.toLowerCase();
|
||||||
return list.filter(p =>
|
return list.filter(
|
||||||
p.name.toLowerCase().includes(kw) ||
|
(p) =>
|
||||||
p.localIP.includes(kw) ||
|
p.name.toLowerCase().includes(kw) ||
|
||||||
String(p.remotePort).includes(kw)
|
p.localIP.includes(kw) ||
|
||||||
|
String(p.remotePort).includes(kw)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Frpc
|
// 5. Frpc
|
||||||
// ============================================================
|
// ============================================================
|
||||||
async function getFrpcStatus() {
|
async function getFrpcStatus() {
|
||||||
try {
|
try {
|
||||||
@@ -165,7 +182,7 @@ async function getFrpcStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// UI State
|
// 6. UI State
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const activeTab = ref("proxies");
|
const activeTab = ref("proxies");
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
@@ -182,73 +199,29 @@ const searchKeyword = ref("");
|
|||||||
|
|
||||||
function openAddDialog() {
|
function openAddDialog() {
|
||||||
dialogMode.value = "add";
|
dialogMode.value = "add";
|
||||||
dialogForm.value = { id: null, name: "", type: "tcp", localIP: "", localPort: 0, remotePort: 0 };
|
dialogForm.value = {
|
||||||
|
id: null,
|
||||||
|
name: "",
|
||||||
|
type: "tcp",
|
||||||
|
localIP: "",
|
||||||
|
localPort: 0,
|
||||||
|
remotePort: 0,
|
||||||
|
};
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditDialog(proxy) {
|
function openEditDialog(proxy) {
|
||||||
dialogMode.value = "edit";
|
dialogMode.value = "edit";
|
||||||
dialogForm.value = { ...proxy };
|
dialogForm.value = { ...proxy };
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeDialog() {
|
function closeDialog() {
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Import / Export
|
// 7. Vue App
|
||||||
// ============================================================
|
|
||||||
async function triggerImport() {
|
|
||||||
document.getElementById("tomlFileInput").click();
|
|
||||||
}
|
|
||||||
async function handleImport(e) {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("file", file);
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/import/toml", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { Authorization: "Bearer " + getToken() },
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.code === 0) {
|
|
||||||
alert(data.msg);
|
|
||||||
await loadAllData();
|
|
||||||
} else {
|
|
||||||
alert("导入失败: " + data.msg);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
alert("网络错误: " + err.message);
|
|
||||||
}
|
|
||||||
e.target.value = "";
|
|
||||||
}
|
|
||||||
async function exportToml() {
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/export/toml", {
|
|
||||||
method: "GET",
|
|
||||||
headers: { Authorization: "Bearer " + getToken() },
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const data = await res.json();
|
|
||||||
alert("导出失败: " + (data.msg || "未知错误"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const blob = await res.blob();
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.href = URL.createObjectURL(blob);
|
|
||||||
a.download = "frpc.toml";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(a.href);
|
|
||||||
} catch (e) {
|
|
||||||
alert("导出失败: " + e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Vue App
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
setup() {
|
setup() {
|
||||||
@@ -394,6 +367,9 @@ const app = createApp({
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
|
// ✅ 注册成功,不再是首次启动
|
||||||
|
isFirstRun.value = false;
|
||||||
|
|
||||||
const token = data.data.token;
|
const token = data.data.token;
|
||||||
const expire = saveAuthState(token, registerForm.username);
|
const expire = saveAuthState(token, registerForm.username);
|
||||||
token.value = token;
|
token.value = token;
|
||||||
@@ -430,6 +406,14 @@ const app = createApp({
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
transitioning.value = false;
|
transitioning.value = false;
|
||||||
clearAuthState();
|
clearAuthState();
|
||||||
|
|
||||||
|
// ✅ 重置注册相关状态
|
||||||
|
isFirstRun.value = false;
|
||||||
|
registerForm.username = "";
|
||||||
|
registerForm.password = "";
|
||||||
|
registerForm.confirmPassword = "";
|
||||||
|
registerError.value = "";
|
||||||
|
registering.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- 修改密码 ----
|
// ---- 修改密码 ----
|
||||||
@@ -488,6 +472,7 @@ const app = createApp({
|
|||||||
frpcRunning.value = await getFrpcStatus();
|
frpcRunning.value = await getFrpcStatus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeProxy = async (id) => {
|
const removeProxy = async (id) => {
|
||||||
if (!confirm("确定要删除这条隧道吗?")) return;
|
if (!confirm("确定要删除这条隧道吗?")) return;
|
||||||
const result = await deleteProxy(id);
|
const result = await deleteProxy(id);
|
||||||
@@ -498,15 +483,17 @@ const app = createApp({
|
|||||||
alert("删除失败: " + result.msg);
|
alert("删除失败: " + result.msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmDialog = async () => {
|
const confirmDialog = async () => {
|
||||||
const form = dialogForm.value;
|
const form = dialogForm.value;
|
||||||
if (!form.name || !form.localIP || !form.localPort || !form.remotePort) {
|
if (!form.name || !form.localIP || !form.localPort || !form.remotePort) {
|
||||||
alert("请填写完整信息");
|
alert("请填写完整信息");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = dialogMode.value === "add"
|
const result =
|
||||||
? await createProxy(form)
|
dialogMode.value === "add"
|
||||||
: await updateProxy(form);
|
? await createProxy(form)
|
||||||
|
: await updateProxy(form);
|
||||||
if (result.code === 0) {
|
if (result.code === 0) {
|
||||||
closeDialog();
|
closeDialog();
|
||||||
proxies.value = await loadProxies();
|
proxies.value = await loadProxies();
|
||||||
@@ -516,6 +503,59 @@ const app = createApp({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- 导入导出(已移入 setup,可访问 loadAllData) ----
|
||||||
|
const triggerImport = () => {
|
||||||
|
document.getElementById("tomlFileInput").click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImport = async (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/import/toml", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { Authorization: "Bearer " + getToken() },
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.code === 0) {
|
||||||
|
alert(data.msg);
|
||||||
|
await loadAllData();
|
||||||
|
} else {
|
||||||
|
alert("导入失败: " + data.msg);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert("网络错误: " + err.message);
|
||||||
|
}
|
||||||
|
e.target.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportToml = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/export/toml", {
|
||||||
|
method: "GET",
|
||||||
|
headers: { Authorization: "Bearer " + getToken() },
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
alert("导出失败: " + (data.msg || "未知错误"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const blob = await res.blob();
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = "frpc.toml";
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
} catch (e) {
|
||||||
|
alert("导出失败: " + e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ---- 计算属性 ----
|
// ---- 计算属性 ----
|
||||||
const filteredProxies = computed(() =>
|
const filteredProxies = computed(() =>
|
||||||
filterProxies(proxies.value, searchKeyword.value)
|
filterProxies(proxies.value, searchKeyword.value)
|
||||||
|
|||||||
Reference in New Issue
Block a user