diff --git a/frpc-console.db b/frpc-console.db deleted file mode 100644 index 5bb67bd..0000000 Binary files a/frpc-console.db and /dev/null differ diff --git a/frpc.log b/frpc.log deleted file mode 100644 index 5c10e9d..0000000 --- a/frpc.log +++ /dev/null @@ -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] diff --git a/frpc.toml b/frpc.toml deleted file mode 100644 index e2dfb58..0000000 --- a/frpc.toml +++ /dev/null @@ -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" - diff --git a/static/app.js b/static/app.js index 080f81c..29b147c 100644 --- a/static/app.js +++ b/static/app.js @@ -1,23 +1,29 @@ +// app.js - 普通脚本(非模块) + const { createApp, ref, reactive, computed, onMounted } = Vue; // ============================================================ -// API +// 1. 基础 API // ============================================================ const API_BASE = "/api"; + function getToken() { return localStorage.getItem("frpc_token") || ""; } + function getHeaders() { return { "Content-Type": "application/json", Authorization: "Bearer " + getToken(), }; } + async function apiFetch(endpoint, options = {}) { const url = API_BASE + endpoint; - const headers = options.body instanceof FormData - ? { Authorization: "Bearer " + getToken() } - : getHeaders(); + const headers = + options.body instanceof FormData + ? { Authorization: "Bearer " + getToken() } + : getHeaders(); const res = await fetch(url, { ...options, headers: { ...headers, ...(options.headers || {}) }, @@ -26,7 +32,7 @@ async function apiFetch(endpoint, options = {}) { } // ============================================================ -// Auth +// 2. Auth // ============================================================ function parseJwtExpire(token) { try { @@ -36,6 +42,7 @@ function parseJwtExpire(token) { return Date.now() + 7 * 24 * 60 * 60 * 1000; } } + function saveAuthState(token, username) { const expire = parseJwtExpire(token); localStorage.setItem("frpc_token", token); @@ -43,6 +50,7 @@ function saveAuthState(token, username) { localStorage.setItem("frpc_username", username); return expire; } + function loadAuthState() { const token = localStorage.getItem("frpc_token"); const expire = parseInt(localStorage.getItem("frpc_token_expire") || "0", 10); @@ -53,11 +61,13 @@ function loadAuthState() { clearAuthState(); return { token: "", expire: 0, username: "", valid: false }; } + function clearAuthState() { localStorage.removeItem("frpc_token"); localStorage.removeItem("frpc_token_expire"); localStorage.removeItem("frpc_username"); } + async function login(username, password) { const data = await apiFetch("/login", { method: "POST", @@ -72,7 +82,7 @@ async function login(username, password) { } // ============================================================ -// Config +// 3. Config // ============================================================ const defaultConfig = { serverAddr: "frp.whitetop.xyz", @@ -86,6 +96,7 @@ const defaultConfig = { heartbeatTimeout: 70, poolCount: 8, }; + async function loadConfig() { try { const data = await apiFetch("/config"); @@ -97,6 +108,7 @@ async function loadConfig() { } return { ...defaultConfig }; } + async function saveConfigApi(config) { const data = await apiFetch("/config", { method: "PUT", @@ -106,7 +118,7 @@ async function saveConfigApi(config) { } // ============================================================ -// Proxies +// 4. Proxies // ============================================================ async function loadProxies() { try { @@ -119,6 +131,7 @@ async function loadProxies() { } return []; } + async function createProxy(proxy) { const data = await apiFetch("/proxy", { method: "POST", @@ -126,6 +139,7 @@ async function createProxy(proxy) { }); return data; } + async function updateProxy(proxy) { const data = await apiFetch("/proxy/" + proxy.id, { method: "PUT", @@ -133,24 +147,27 @@ async function updateProxy(proxy) { }); return data; } + async function deleteProxy(id) { const data = await apiFetch("/proxy/" + id, { method: "DELETE", }); return data; } + function filterProxies(list, keyword) { if (!keyword) return list; const kw = keyword.toLowerCase(); - return list.filter(p => - p.name.toLowerCase().includes(kw) || - p.localIP.includes(kw) || - String(p.remotePort).includes(kw) + return list.filter( + (p) => + p.name.toLowerCase().includes(kw) || + p.localIP.includes(kw) || + String(p.remotePort).includes(kw) ); } // ============================================================ -// Frpc +// 5. Frpc // ============================================================ async function getFrpcStatus() { try { @@ -165,7 +182,7 @@ async function getFrpcStatus() { } // ============================================================ -// UI State +// 6. UI State // ============================================================ const activeTab = ref("proxies"); const dialogVisible = ref(false); @@ -182,73 +199,29 @@ const searchKeyword = ref(""); function openAddDialog() { 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; } + function openEditDialog(proxy) { dialogMode.value = "edit"; dialogForm.value = { ...proxy }; dialogVisible.value = true; } + function closeDialog() { dialogVisible.value = false; } // ============================================================ -// Import / Export -// ============================================================ -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 +// 7. Vue App // ============================================================ const app = createApp({ setup() { @@ -394,6 +367,9 @@ const app = createApp({ }); const data = await res.json(); if (data.code === 0) { + // ✅ 注册成功,不再是首次启动 + isFirstRun.value = false; + const token = data.data.token; const expire = saveAuthState(token, registerForm.username); token.value = token; @@ -430,6 +406,14 @@ const app = createApp({ loading.value = false; transitioning.value = false; 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(); } }; + const removeProxy = async (id) => { if (!confirm("确定要删除这条隧道吗?")) return; const result = await deleteProxy(id); @@ -498,15 +483,17 @@ const app = createApp({ alert("删除失败: " + result.msg); } }; + const confirmDialog = async () => { const form = dialogForm.value; if (!form.name || !form.localIP || !form.localPort || !form.remotePort) { alert("请填写完整信息"); return; } - const result = dialogMode.value === "add" - ? await createProxy(form) - : await updateProxy(form); + const result = + dialogMode.value === "add" + ? await createProxy(form) + : await updateProxy(form); if (result.code === 0) { closeDialog(); 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(() => filterProxies(proxies.value, searchKeyword.value)