数据被迫合并
This commit is contained in:
@@ -238,6 +238,12 @@ func GetProxies() ([]Proxy, error) {
|
|||||||
}
|
}
|
||||||
proxies = append(proxies, p)
|
proxies = append(proxies, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🟢 关键:检查遍历过程中是否有错误
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return proxies, nil
|
return proxies, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -816,3 +816,9 @@
|
|||||||
2026-07-23 20:45:31.375 [W] [client/control.go:163] [926f05bb8cbbd6b8] [navidrome] start error: proxy [navidrome] already exists
|
2026-07-23 20:45:31.375 [W] [client/control.go:163] [926f05bb8cbbd6b8] [navidrome] start error: proxy [navidrome] already exists
|
||||||
2026-07-23 20:45:31.375 [W] [client/control.go:163] [926f05bb8cbbd6b8] [napcat-2] start error: proxy [napcat-2] already exists
|
2026-07-23 20:45:31.375 [W] [client/control.go:163] [926f05bb8cbbd6b8] [napcat-2] start error: proxy [napcat-2] already exists
|
||||||
2026-07-23 20:45:31.375 [W] [client/control.go:163] [926f05bb8cbbd6b8] [podux] start error: proxy [podux] already exists
|
2026-07-23 20:45:31.375 [W] [client/control.go:163] [926f05bb8cbbd6b8] [podux] start error: proxy [podux] already exists
|
||||||
|
2026-07-23 20:48:41.021 [I] [sub/root.go:201] start frpc service for config file [./frpc.toml] with aggregated configuration
|
||||||
|
2026-07-23 20:48:41.031 [I] [client/service.go:308] try to connect to server...
|
||||||
|
2026-07-23 20:48:41.209 [I] [client/service.go:328] [ddf463180e9c67e7] login to server success, get run id [ddf463180e9c67e7]
|
||||||
|
2026-07-23 20:56:09.600 [I] [sub/root.go:201] start frpc service for config file [./frpc.toml] with aggregated configuration
|
||||||
|
2026-07-23 20:56:09.609 [I] [client/service.go:308] try to connect to server...
|
||||||
|
2026-07-23 20:56:09.784 [I] [client/service.go:328] [41d8767c9cc81642] login to server success, get run id [41d8767c9cc81642]
|
||||||
|
|||||||
@@ -19,94 +19,3 @@ poolCount = 8
|
|||||||
[webServer]
|
[webServer]
|
||||||
addr = "127.0.0.1:7400"
|
addr = "127.0.0.1:7400"
|
||||||
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "sun-panel"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 3002
|
|
||||||
remotePort = 30005
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "1panel-1"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 10086
|
|
||||||
remotePort = 20003
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "navidrome"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 4533
|
|
||||||
remotePort = 20022
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "openlist"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 5244
|
|
||||||
remotePort = 20005
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "SSH"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 22
|
|
||||||
remotePort = 20001
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "SMB"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 445
|
|
||||||
remotePort = 20008
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "napcat"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 6099
|
|
||||||
remotePort = 20007
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "Gitea"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 3800
|
|
||||||
remotePort = 20017
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "immich-APP"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 2283
|
|
||||||
remotePort = 20020
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "Halo"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 65535
|
|
||||||
remotePort = 65535
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "napcat-2"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 6100
|
|
||||||
remotePort = 20012
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "podux"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 8090
|
|
||||||
remotePort = 20010
|
|
||||||
|
|
||||||
[[proxies]]
|
|
||||||
name = "Grafana"
|
|
||||||
type = "tcp"
|
|
||||||
localIP = "192.168.3.16"
|
|
||||||
localPort = 3030
|
|
||||||
remotePort = 20014
|
|
||||||
|
|||||||
+289
-15
@@ -1,19 +1,288 @@
|
|||||||
// app.js
|
// app.js - 普通脚本(非模块)
|
||||||
import { createApp, ref, reactive, computed, onMounted } from 'vue';
|
|
||||||
|
|
||||||
// 导入各模块
|
const { createApp, ref, reactive, computed, onMounted } = Vue;
|
||||||
import { apiFetch } from './modules/api.js';
|
|
||||||
import { login, loadAuthState, clearAuthState, saveAuthState } from './modules/auth.js';
|
// ============================================================
|
||||||
import { loadConfig, saveConfig as saveConfigApi } from './modules/config.js';
|
// 1. 把 modules/*.js 的内容直接合并进来(因为是普通脚本,不能 import)
|
||||||
import { loadProxies, createProxy, updateProxy, deleteProxy, filterProxies } from './modules/proxies.js';
|
// ============================================================
|
||||||
import { getFrpcStatus, reloadFrpc, startFrpc, stopFrpc } from './modules/frpc.js';
|
|
||||||
import {
|
// ---------- api ----------
|
||||||
activeTab, switchTab,
|
const API_BASE = '/api';
|
||||||
dialogVisible, dialogMode, dialogForm,
|
function getToken() {
|
||||||
openAddDialog, openEditDialog, closeDialog,
|
return localStorage.getItem('frpc_token') || '';
|
||||||
searchKeyword,
|
}
|
||||||
} from './modules/ui.js';
|
function getHeaders() {
|
||||||
import { importToml, exportToml } from './modules/import.js'; // 如果有的话
|
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 res = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers: { ...headers, ...(options.headers || {}) },
|
||||||
|
});
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- auth ----------
|
||||||
|
function parseJwtExpire(token) {
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||||
|
return payload.exp * 1000;
|
||||||
|
} catch {
|
||||||
|
return Date.now() + 7 * 24 * 60 * 60 * 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function saveAuthState(token, username) {
|
||||||
|
const expire = parseJwtExpire(token);
|
||||||
|
localStorage.setItem('frpc_token', token);
|
||||||
|
localStorage.setItem('frpc_token_expire', String(expire));
|
||||||
|
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);
|
||||||
|
const username = localStorage.getItem('frpc_username') || '';
|
||||||
|
if (token && expire > Date.now()) {
|
||||||
|
return { token, expire, username, valid: true };
|
||||||
|
}
|
||||||
|
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',
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
});
|
||||||
|
if (data.code === 0) {
|
||||||
|
const token = data.data.token;
|
||||||
|
const expire = saveAuthState(token, username);
|
||||||
|
return { success: true, token, expire };
|
||||||
|
}
|
||||||
|
return { success: false, error: data.msg || '登录失败' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- config ----------
|
||||||
|
const defaultConfig = {
|
||||||
|
serverAddr: 'frp.whitetop.xyz',
|
||||||
|
serverPort: 9358,
|
||||||
|
token: '',
|
||||||
|
logLevel: 'info',
|
||||||
|
logMaxDays: 3,
|
||||||
|
tcpMux: true,
|
||||||
|
tcpMuxKeepalive: 30,
|
||||||
|
heartbeatInterval: 15,
|
||||||
|
heartbeatTimeout: 70,
|
||||||
|
poolCount: 8,
|
||||||
|
};
|
||||||
|
async function loadConfig() {
|
||||||
|
try {
|
||||||
|
const data = await apiFetch('/config');
|
||||||
|
if (data.code === 0) {
|
||||||
|
return { ...defaultConfig, ...data.data };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('加载配置失败', e);
|
||||||
|
}
|
||||||
|
return { ...defaultConfig };
|
||||||
|
}
|
||||||
|
async function saveConfigApi(config) {
|
||||||
|
const data = await apiFetch('/config', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(config),
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- proxies ----------
|
||||||
|
async function loadProxies() {
|
||||||
|
try {
|
||||||
|
const data = await apiFetch('/proxies');
|
||||||
|
if (data.code === 0) {
|
||||||
|
return data.data || [];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('加载隧道失败', e);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
async function createProxy(proxy) {
|
||||||
|
const data = await apiFetch('/proxy', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(proxy),
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
async function updateProxy(proxy) {
|
||||||
|
const data = await apiFetch('/proxy/' + proxy.id, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- frpc ----------
|
||||||
|
async function getFrpcStatus() {
|
||||||
|
try {
|
||||||
|
const data = await apiFetch('/frpc/status');
|
||||||
|
if (data.code === 0) {
|
||||||
|
return data.data.running || false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('获取 frpc 状态失败', e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
async function reloadFrpc() {
|
||||||
|
const data = await apiFetch('/frpc/reload', { method: 'POST' });
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
async function startFrpc() {
|
||||||
|
const data = await apiFetch('/frpc/start', { method: 'POST' });
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
async function stopFrpc() {
|
||||||
|
const data = await apiFetch('/frpc/stop', { method: 'POST' });
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- ui ----------
|
||||||
|
// Tab 切换
|
||||||
|
const activeTab = ref('proxies');
|
||||||
|
function switchTab(tab) {
|
||||||
|
activeTab.value = tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗控制
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const dialogMode = ref('add');
|
||||||
|
const dialogForm = ref({
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
type: 'tcp',
|
||||||
|
localIP: '',
|
||||||
|
localPort: 0,
|
||||||
|
remotePort: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
function openAddDialog() {
|
||||||
|
dialogMode.value = 'add';
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchKeyword = ref('');
|
||||||
|
|
||||||
|
// ---------- 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 configData = await apiFetch('/config');
|
||||||
|
if (configData.code !== 0) {
|
||||||
|
alert('获取配置失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const proxyData = await apiFetch('/proxies');
|
||||||
|
if (proxyData.code !== 0) {
|
||||||
|
alert('获取隧道列表失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cfg = configData.data;
|
||||||
|
const proxyList = proxyData.data || [];
|
||||||
|
const tomlObj = {
|
||||||
|
serverAddr: cfg.serverAddr,
|
||||||
|
serverPort: cfg.serverPort,
|
||||||
|
auth: { token: cfg.token },
|
||||||
|
log: { to: './frpc.log', level: cfg.logLevel, maxDays: cfg.logMaxDays },
|
||||||
|
transport: {
|
||||||
|
tcpMux: cfg.tcpMux,
|
||||||
|
tcpMuxKeepaliveInterval: cfg.tcpMuxKeepalive,
|
||||||
|
heartbeatInterval: cfg.heartbeatInterval,
|
||||||
|
heartbeatTimeout: cfg.heartbeatTimeout,
|
||||||
|
poolCount: cfg.poolCount,
|
||||||
|
},
|
||||||
|
proxies: proxyList.map(p => ({
|
||||||
|
name: p.name,
|
||||||
|
type: p.type,
|
||||||
|
localIP: p.localIP,
|
||||||
|
localPort: p.localPort,
|
||||||
|
remotePort: p.remotePort,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
const tomlStr = TOML.stringify(tomlObj);
|
||||||
|
const blob = new Blob([tomlStr], { type: 'text/plain' });
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = 'frpc.toml';
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
} catch (e) {
|
||||||
|
alert('导出失败: ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 2. Vue 应用
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
setup() {
|
setup() {
|
||||||
@@ -32,6 +301,7 @@ const app = createApp({
|
|||||||
const globalConfig = reactive({});
|
const globalConfig = reactive({});
|
||||||
const proxies = ref([]);
|
const proxies = ref([]);
|
||||||
const frpcRunning = ref(false);
|
const frpcRunning = ref(false);
|
||||||
|
const showDetail = ref(false);
|
||||||
|
|
||||||
// ===== 初始化 =====
|
// ===== 初始化 =====
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -175,6 +445,7 @@ const app = createApp({
|
|||||||
activeTab,
|
activeTab,
|
||||||
switchTab,
|
switchTab,
|
||||||
globalConfig,
|
globalConfig,
|
||||||
|
showDetail,
|
||||||
saveConfig,
|
saveConfig,
|
||||||
proxies,
|
proxies,
|
||||||
filteredProxies,
|
filteredProxies,
|
||||||
@@ -191,6 +462,9 @@ const app = createApp({
|
|||||||
searchKeyword,
|
searchKeyword,
|
||||||
transitioning,
|
transitioning,
|
||||||
contentVisible,
|
contentVisible,
|
||||||
|
triggerImport,
|
||||||
|
handleImport,
|
||||||
|
exportToml,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -255,7 +255,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 应用逻辑 -->
|
<!-- 应用逻辑 -->
|
||||||
<script src="/static/app.js"></script>
|
<script src="/static/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// modules/api.js
|
|
||||||
const API_BASE = '/api';
|
|
||||||
|
|
||||||
export function getToken() {
|
|
||||||
return localStorage.getItem('frpc_token') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHeaders() {
|
|
||||||
return {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + getToken(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function apiFetch(endpoint, options = {}) {
|
|
||||||
const url = API_BASE + endpoint;
|
|
||||||
const headers = options.body instanceof FormData
|
|
||||||
? { 'Authorization': 'Bearer ' + getToken() }
|
|
||||||
: getHeaders();
|
|
||||||
|
|
||||||
const res = await fetch(url, {
|
|
||||||
...options,
|
|
||||||
headers: { ...headers, ...(options.headers || {}) },
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
// modules/auth.js
|
|
||||||
import { apiFetch } from './api.js';
|
|
||||||
|
|
||||||
export function parseJwtExpire(token) {
|
|
||||||
try {
|
|
||||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
|
||||||
return payload.exp * 1000;
|
|
||||||
} catch {
|
|
||||||
return Date.now() + 7 * 24 * 60 * 60 * 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveAuthState(token, username) {
|
|
||||||
const expire = parseJwtExpire(token);
|
|
||||||
localStorage.setItem('frpc_token', token);
|
|
||||||
localStorage.setItem('frpc_token_expire', String(expire));
|
|
||||||
localStorage.setItem('frpc_username', username);
|
|
||||||
return expire;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadAuthState() {
|
|
||||||
const token = localStorage.getItem('frpc_token');
|
|
||||||
const expire = parseInt(localStorage.getItem('frpc_token_expire') || '0', 10);
|
|
||||||
const username = localStorage.getItem('frpc_username') || '';
|
|
||||||
if (token && expire > Date.now()) {
|
|
||||||
return { token, expire, username, valid: true };
|
|
||||||
}
|
|
||||||
clearAuthState();
|
|
||||||
return { token: '', expire: 0, username: '', valid: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearAuthState() {
|
|
||||||
localStorage.removeItem('frpc_token');
|
|
||||||
localStorage.removeItem('frpc_token_expire');
|
|
||||||
localStorage.removeItem('frpc_username');
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function login(username, password) {
|
|
||||||
const data = await apiFetch('/login', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ username, password }),
|
|
||||||
});
|
|
||||||
if (data.code === 0) {
|
|
||||||
const token = data.data.token;
|
|
||||||
const expire = saveAuthState(token, username);
|
|
||||||
return { success: true, token, expire };
|
|
||||||
}
|
|
||||||
return { success: false, error: data.msg || '登录失败' };
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// modules/config.js
|
|
||||||
import { apiFetch } from './api.js';
|
|
||||||
|
|
||||||
const defaultConfig = {
|
|
||||||
serverAddr: 'frp.whitetop.xyz',
|
|
||||||
serverPort: 9358,
|
|
||||||
token: '',
|
|
||||||
logLevel: 'info',
|
|
||||||
logMaxDays: 3,
|
|
||||||
tcpMux: true,
|
|
||||||
tcpMuxKeepalive: 30,
|
|
||||||
heartbeatInterval: 15,
|
|
||||||
heartbeatTimeout: 70,
|
|
||||||
poolCount: 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function loadConfig() {
|
|
||||||
try {
|
|
||||||
const data = await apiFetch('/config');
|
|
||||||
if (data.code === 0) {
|
|
||||||
return { ...defaultConfig, ...data.data };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('加载配置失败', e);
|
|
||||||
}
|
|
||||||
return { ...defaultConfig };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveConfig(config) {
|
|
||||||
const data = await apiFetch('/config', {
|
|
||||||
method: 'PUT',
|
|
||||||
body: JSON.stringify(config),
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// modules/frpc.js
|
|
||||||
import { apiFetch } from './api.js';
|
|
||||||
|
|
||||||
export async function getFrpcStatus() {
|
|
||||||
try {
|
|
||||||
const data = await apiFetch('/frpc/status');
|
|
||||||
if (data.code === 0) {
|
|
||||||
return data.data.running || false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('获取 frpc 状态失败', e);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function reloadFrpc() {
|
|
||||||
const data = await apiFetch('/frpc/reload', { method: 'POST' });
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startFrpc() {
|
|
||||||
const data = await apiFetch('/frpc/start', { method: 'POST' });
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function stopFrpc() {
|
|
||||||
const data = await apiFetch('/frpc/stop', { method: 'POST' });
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// modules/proxies.js
|
|
||||||
import { apiFetch } from './api.js';
|
|
||||||
|
|
||||||
export async function loadProxies() {
|
|
||||||
try {
|
|
||||||
const data = await apiFetch('/proxies');
|
|
||||||
if (data.code === 0) {
|
|
||||||
return data.data || [];
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('加载隧道失败', e);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createProxy(proxy) {
|
|
||||||
const data = await apiFetch('/proxy', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(proxy),
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateProxy(proxy) {
|
|
||||||
const data = await apiFetch('/proxy/' + proxy.id, {
|
|
||||||
method: 'PUT',
|
|
||||||
body: JSON.stringify(proxy),
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteProxy(id) {
|
|
||||||
const data = await apiFetch('/proxy/' + id, {
|
|
||||||
method: 'DELETE',
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export 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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// modules/ui.js
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
// Tab 切换
|
|
||||||
export const activeTab = ref('proxies');
|
|
||||||
export function switchTab(tab) {
|
|
||||||
activeTab.value = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 弹窗控制
|
|
||||||
export const dialogVisible = ref(false);
|
|
||||||
export const dialogMode = ref('add');
|
|
||||||
export const dialogForm = ref({
|
|
||||||
id: null,
|
|
||||||
name: '',
|
|
||||||
type: 'tcp',
|
|
||||||
localIP: '',
|
|
||||||
localPort: 0,
|
|
||||||
remotePort: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function openAddDialog() {
|
|
||||||
dialogMode.value = 'add';
|
|
||||||
dialogForm.value = { id: null, name: '', type: 'tcp', localIP: '', localPort: 0, remotePort: 0 };
|
|
||||||
dialogVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function openEditDialog(proxy) {
|
|
||||||
dialogMode.value = 'edit';
|
|
||||||
dialogForm.value = { ...proxy };
|
|
||||||
dialogVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeDialog() {
|
|
||||||
dialogVisible.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索
|
|
||||||
export const searchKeyword = ref('');
|
|
||||||
Reference in New Issue
Block a user