diff --git a/frontend/components.d.ts b/frontend/components.d.ts
index 37699f1..2e1dbfa 100644
--- a/frontend/components.d.ts
+++ b/frontend/components.d.ts
@@ -49,6 +49,7 @@ declare module 'vue' {
Computer: typeof import('./src/components/computer/Computer.vue')['default']
ComputerNavBar: typeof import('./src/components/computer/ComputerNavBar.vue')['default']
ContextMenu: typeof import('./src/components/builtin/ContextMenu.vue')['default']
+ copy: typeof import('./src/components/setting/NetProxy copy.vue')['default']
CreateUrl: typeof import('./src/components/builtin/CreateUrl.vue')['default']
CustomIcon: typeof import('./src/components/taskbar/CustomIcon.vue')['default']
DateNote: typeof import('./src/components/builtin/DateNote.vue')['default']
@@ -72,28 +73,41 @@ declare module 'vue' {
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCol: typeof import('element-plus/es')['ElCol']
+ ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
+ ElFor: typeof import('element-plus/es')['ElFor']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
+ ElIn: typeof import('element-plus/es')['ElIn']
ElInput: typeof import('element-plus/es')['ElInput']
+ ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
+ ElItem: typeof import('element-plus/es')['ElItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress']
+ ElRadio: typeof import('element-plus/es')['ElRadio']
+ ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
+ ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
+ ElTable: typeof import('element-plus/es')['ElTable']
+ ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+ ElTabPane: typeof import('element-plus/es')['ElTabPane']
+ ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElText: typeof import('element-plus/es')['ElText']
+ ElTo: typeof import('element-plus/es')['ElTo']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
Error: typeof import('./src/components/taskbar/Error.vue')['default']
FileIcon: typeof import('./src/components/builtin/FileIcon.vue')['default']
@@ -127,6 +141,7 @@ declare module 'vue' {
MusicViewer: typeof import('./src/components/builtin/MusicViewer.vue')['default']
NasClient: typeof import('./src/components/setting/NasClient.vue')['default']
NetProxy: typeof import('./src/components/setting/NetProxy.vue')['default']
+ 'NetProxy copy': typeof import('./src/components/setting/NetProxy copy.vue')['default']
NetWork: typeof import('./src/components/taskbar/NetWork.vue')['default']
NetworkPop: typeof import('./src/components/taskbar/NetworkPop.vue')['default']
NotFound: typeof import('./src/components/window/NotFound.vue')['default']
diff --git a/frontend/src/components/builtin/FileList.vue b/frontend/src/components/builtin/FileList.vue
index b5083c2..7e33a01 100644
--- a/frontend/src/components/builtin/FileList.vue
+++ b/frontend/src/components/builtin/FileList.vue
@@ -52,6 +52,7 @@
}
"
>
+
diff --git a/frontend/src/components/setting/NetProxy copy.vue b/frontend/src/components/setting/NetProxy copy.vue
new file mode 100644
index 0000000..95cf01f
--- /dev/null
+++ b/frontend/src/components/setting/NetProxy copy.vue
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 确认
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/setting/NetProxy.vue b/frontend/src/components/setting/NetProxy.vue
index 95cf01f..2e125d7 100644
--- a/frontend/src/components/setting/NetProxy.vue
+++ b/frontend/src/components/setting/NetProxy.vue
@@ -1,204 +1,546 @@
+
-
-
-
-
-
-
-
-
-
-
-
- 编辑
- 删除
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 确认
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+ {{ type }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 添加
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ model.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 保存
+ 取消
+
+
+
+
+
+
+
diff --git a/frontend/src/stores/proxy.ts b/frontend/src/stores/proxy.ts
new file mode 100644
index 0000000..69dbe18
--- /dev/null
+++ b/frontend/src/stores/proxy.ts
@@ -0,0 +1,182 @@
+import { defineStore } from 'pinia';
+import { ref } from 'vue';
+
+export interface ProxyItem {
+ port: string;
+ domain: string;
+ type: string;
+ name: string;
+ serverAddr: string;
+ serverPort: string;
+ httpAuth: boolean;
+ authUsername: string;
+ authPassword: string;
+ https2http: boolean;
+ https2httpCaFile: string;
+ https2httpKeyFile: string;
+ remotePort?: string;
+ stcpModel?: string;
+ secretKey?: string;
+ visitedName?: string;
+ bindAddr?: string;
+ bindPort?: string;
+ fallbackTo?: string;
+ fallbackTimeoutMs?: number;
+ keepAlive?: boolean;
+}
+
+export const useProxyStore = defineStore('proxyStore', () => {
+ const localKey = "godoos_net_proxy";
+
+ const proxies = ref(getProxies());
+ const customDomains = ref([""]);
+ const proxyData = ref(createNewProxyData());
+
+ function createNewProxyData(): ProxyItem {
+ return {
+ type: "http",
+ name: "",
+ port: "",
+ domain: "",
+ serverAddr: "",
+ serverPort: "",
+ httpAuth: false,
+ authUsername: "",
+ authPassword: "",
+ https2http: false,
+ https2httpCaFile: "",
+ https2httpKeyFile: "",
+ remotePort: "",
+ stcpModel: "",
+ secretKey: "",
+ visitedName: "",
+ bindAddr: "",
+ bindPort: "",
+ fallbackTo: "",
+ fallbackTimeoutMs: 0,
+ keepAlive: false,
+ };
+ }
+
+ function getProxies(): ProxyItem[] {
+ const proxies = localStorage.getItem(localKey);
+ return proxies ? JSON.parse(proxies) : [];
+ }
+
+ function saveProxies(proxies: ProxyItem[]) {
+ localStorage.setItem(localKey, JSON.stringify(proxies));
+ }
+
+ const addProxy = (proxy: ProxyItem) => {
+ proxies.value.push(proxy);
+ saveProxies(proxies.value);
+ };
+
+ const updateProxy = (updatedProxy: ProxyItem) => {
+ const index = proxies.value.findIndex(p => p.id === updatedProxy.id);
+ if (index !== -1) {
+ proxies.value[index] = updatedProxy;
+ saveProxies(proxies.value);
+ }
+ };
+
+ const deleteProxy = (id: number) => {
+ proxies.value = proxies.value.filter(p => p.id !== id);
+ saveProxies(proxies.value);
+ };
+
+ const addCustomDomain = () => {
+ customDomains.value.push("");
+ };
+
+ const removeCustomDomain = (index: number) => {
+ if (customDomains.value.length > 1) {
+ customDomains.value.splice(index, 1);
+ }
+ };
+
+ const resetProxyData = () => {
+ proxyData.value = createNewProxyData();
+ };
+
+ const createFrpcConfig = async () => {
+ const url = "http://localhost:56780/frpc/create";
+ const requestData = {
+ config: {
+ ServerAddr: proxyData.value.serverAddr,
+ ServerPort: Number(proxyData.value.serverPort),
+ AuthMethod: "token",
+ AuthToken: "your-auth-token",
+ User: "your-username",
+ MetaToken: "your-meta-token",
+ TransportHeartbeatInterval: 30,
+ TransportHeartbeatTimeout: 90,
+ LogLevel: "info",
+ LogMaxDays: 3,
+ WebPort: 7500,
+ TlsConfigEnable: true,
+ TlsConfigCertFile: "/path/to/cert/file",
+ TlsConfigKeyFile: "/path/to/key/file",
+ TlsConfigTrustedCaFile: "/path/to/ca/file",
+ TlsConfigServerName: "server-name",
+ ProxyConfigEnable: true,
+ ProxyConfigProxyUrl: "http://proxy.example.com"
+ },
+ proxies: [
+ {
+ Name: proxyData.value.name,
+ Type: proxyData.value.type,
+ LocalIp: proxyData.value.serverAddr,
+ LocalPort: Number(proxyData.value.port),
+ RemotePort: Number(proxyData.value.remotePort),
+ CustomDomains: customDomains.value,
+ Subdomain: proxyData.value.domain,
+ BasicAuth: proxyData.value.httpAuth,
+ HttpUser: proxyData.value.authUsername,
+ HttpPassword: proxyData.value.authPassword,
+ StcpModel: proxyData.value.stcpModel,
+ ServerName: "server1",
+ BindAddr: proxyData.value.bindAddr,
+ BindPort: Number(proxyData.value.bindPort),
+ FallbackTo: proxyData.value.fallbackTo,
+ FallbackTimeoutMs: Number(proxyData.value.fallbackTimeoutMs),
+ SecretKey: proxyData.value.secretKey
+ }
+ ]
+ };
+
+ try {
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify(requestData)
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ console.log("Response data:", data);
+ // 可以在这里添加成功通知
+ } catch (error) {
+ console.error("Error:", error);
+ // 可以在这里添加错误通知
+ }
+ };
+
+ return {
+ proxies,
+ customDomains,
+ proxyData,
+ addProxy,
+ updateProxy,
+ deleteProxy,
+ addCustomDomain,
+ removeCustomDomain,
+ resetProxyData,
+ createFrpcConfig,
+ };
+});
\ No newline at end of file
diff --git a/godo/cmd/main.go b/godo/cmd/main.go
index 92e2758..0610eae 100644
--- a/godo/cmd/main.go
+++ b/godo/cmd/main.go
@@ -168,6 +168,13 @@ func OsStart() {
proxyRouter.HandleFunc("/local/get", proxy.GetLocalProxyHandler).Methods(http.MethodGet)
proxyRouter.HandleFunc("/local/list", proxy.GetLocalProxiesHandler).Methods(http.MethodGet)
proxyRouter.HandleFunc("/local/update", proxy.UpdateLocalProxyHandler).Methods(http.MethodPost)
+ // 注册frpc路由
+ frpcRouter := router.PathPrefix("/frpc").Subrouter()
+ frpcRouter.HandleFunc("/create", proxy.CreateFrpcHandler).Methods(http.MethodPost)
+ frpcRouter.HandleFunc("/delete", proxy.DeleteFrpcConfigHandler).Methods(http.MethodGet) // 删除frpc配置
+ frpcRouter.HandleFunc("/get", proxy.GetFrpcConfigHandler).Methods(http.MethodGet) // 获取frpc配置
+ frpcRouter.HandleFunc("/list", proxy.GetFrpcProxiesHandler).Methods(http.MethodGet) // 获取frpc代理列表
+ frpcRouter.HandleFunc("/update", proxy.UpdateFrpcConfigHandler).Methods(http.MethodPost) // 更新frpc配置
// 注册根路径的处理函数
distFS, _ := fs.Sub(deps.Frontendassets, "dist")
diff --git a/godo/go.mod b/godo/go.mod
index 68081cc..52abe2d 100644
--- a/godo/go.mod
+++ b/godo/go.mod
@@ -46,4 +46,5 @@ require (
github.com/tklauser/numcpus v0.9.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.31.0 // indirect
+ gopkg.in/ini.v1 v1.67.0
)
diff --git a/godo/go.sum b/godo/go.sum
index 69dd19f..a8361de 100644
--- a/godo/go.sum
+++ b/godo/go.sum
@@ -150,6 +150,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
diff --git a/godo/proxy/frpc.go b/godo/proxy/frpc.go
index 2c5083f..bc119d4 100644
--- a/godo/proxy/frpc.go
+++ b/godo/proxy/frpc.go
@@ -1,51 +1,74 @@
package proxy
import (
+ "encoding/json"
"fmt"
+ "godo/libs"
+ "net/http"
+ "os"
+ "path/filepath"
"strings"
+
+ "gopkg.in/ini.v1"
)
// FrpConfig 结构体用于存储 FRP 配置
type FrpConfig struct {
- ServerAddr string
- ServerPort int
- AuthMethod string
- AuthToken string
- User string
- MetaToken string
- TransportHeartbeatInterval int
- TransportHeartbeatTimeout int
- LogLevel string
- LogMaxDays int
- WebPort int
- TlsConfigEnable bool
- TlsConfigCertFile string
- TlsConfigKeyFile string
- TlsConfigTrustedCaFile string
- TlsConfigServerName string
- ProxyConfigEnable bool
- ProxyConfigProxyUrl string
+ ServerAddr string // 服务器地址
+ ServerPort int // 服务器端口
+ AuthMethod string // 认证方法,例如 "token" 或 "multiuser"
+ AuthToken string // 认证令牌,当 AuthMethod 为 "token" 时使用
+ User string // 用户名,当 AuthMethod 为 "multiuser" 时使用
+ MetaToken string // 元数据令牌,当 AuthMethod 为 "multiuser" 时使用
+ TransportHeartbeatInterval int // 传输心跳间隔(秒)
+ TransportHeartbeatTimeout int // 传输心跳超时(秒)
+ LogLevel string // 日志级别,例如 "info"、"warn"、"error"
+ LogMaxDays int // 日志保留天数
+ WebPort int // Web 管理界面的端口
+ TlsConfigEnable bool // 是否启用 TLS 配置
+ TlsConfigCertFile string // TLS 证书文件路径
+ TlsConfigKeyFile string // TLS 密钥文件路径
+ TlsConfigTrustedCaFile string // TLS 信任的 CA 文件路径
+ TlsConfigServerName string // TLS 服务器名称
+ ProxyConfigEnable bool // 是否启用代理配置
+ ProxyConfigProxyUrl string // 代理 URL
}
// Proxy 结构体用于存储代理配置
type Proxy struct {
- Name string
- Type string
- LocalIp string
- LocalPort int
- RemotePort int
- CustomDomains []string
- Subdomain string
- BasicAuth bool
- HttpUser string
- HttpPassword string
- StcpModel string
- ServerName string
- BindAddr string
- BindPort int
- FallbackTo string
- FallbackTimeoutMs int
- SecretKey string
+ Name string `json:"name"` // 代理名称
+ Type string `json:"type"` // 代理类型,例如 "tcp"、"http"、"stcp"
+ LocalIp string `json:"localIp"` // 本地 IP 地址
+ LocalPort int `json:"localPort"` // 本地端口
+ RemotePort int `json:"remotePort"` // 远程端口
+ CustomDomains []string `json:"customDomains"` // 自定义域名列表
+ Subdomain string `json:"subdomain"` // 子域名
+ BasicAuth bool `json:"basicAuth"` // 是否启用基本认证
+ HttpUser string `json:"httpUser"` // HTTP 基本认证用户名
+ HttpPassword string `json:"httpPassword"` // HTTP 基本认证密码
+ StcpModel string `json:"stcpModel"` // STCP 模式,例如 "visitors" 或 "visited"
+ ServerName string `json:"serverName"` // 服务器名称
+ BindAddr string `json:"bindAddr"` // 绑定地址
+ BindPort int `json:"bindPort"` // 绑定端口
+ FallbackTo string `json:"fallbackTo"` // 回退到的目标
+ FallbackTimeoutMs int `json:"fallbackTimeoutMs"` // 回退超时时间(毫秒)
+ SecretKey string `json:"secretKey"` // 密钥,用于加密通信
+}
+
+type Response struct {
+ Code string `json:"code"`
+ Message string `json:"message"`
+ Data interface{} `json:"data"`
+ Success bool `json:"success"`
+}
+
+func NewResponse(code string, message string, data interface{}) Response {
+ return Response{
+ Code: code,
+ Message: message,
+ Data: data,
+ Success: true,
+ }
}
// isRangePort 检查端口是否为范围端口
@@ -283,3 +306,345 @@ httpProxy = "%s"
return ini
}
+
+// CreateFrpcHandler 创建frpc配置
+func CreateFrpcHandler(w http.ResponseWriter, r *http.Request) {
+ var requestData struct {
+ Config FrpConfig `json:"config"`
+ Proxies []Proxy `json:"proxies"`
+ }
+
+ err := json.NewDecoder(r.Body).Decode(&requestData)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ // 读取现有配置
+ path := libs.GetAppExecDir()
+ filePath := filepath.Join(path, "frpc", "frpc.ini")
+
+ var existingProxies []Proxy
+ if iniContent, err := os.ReadFile(filePath); err == nil {
+ cfg, err := ini.Load(iniContent)
+ if err == nil {
+ for _, section := range cfg.Sections() {
+ if section.Name() == "common" {
+ continue
+ }
+ proxy := Proxy{
+ Name: section.Name(),
+ Type: section.Key("type").String(),
+ LocalIp: section.Key("localIP").String(),
+ LocalPort: section.Key("localPort").MustInt(),
+ RemotePort: section.Key("remotePort").MustInt(),
+ CustomDomains: strings.Split(section.Key("custom_domains").String(), ","),
+ Subdomain: section.Key("subdomain").String(),
+ BasicAuth: section.Key("httpUser").String() != "",
+ HttpUser: section.Key("httpUser").String(),
+ HttpPassword: section.Key("httpPassword").String(),
+ StcpModel: section.Key("role").String(),
+ ServerName: section.Key("serverName").String(),
+ BindAddr: section.Key("bindAddr").String(),
+ BindPort: section.Key("bindPort").MustInt(),
+ FallbackTo: section.Key("fallbackTo").String(),
+ FallbackTimeoutMs: section.Key("fallbackTimeoutMs").MustInt(),
+ SecretKey: section.Key("sk").String(),
+ }
+ existingProxies = append(existingProxies, proxy)
+ }
+ }
+ }
+
+ // 合并现有代理和新代理
+ proxyMap := make(map[string]Proxy)
+ for _, proxy := range existingProxies {
+ proxyMap[proxy.Name] = proxy
+ }
+
+ for _, proxy := range requestData.Proxies {
+ proxyMap[proxy.Name] = proxy // 新代理会覆盖同 Name 的旧代理
+ }
+
+ var allProxies []Proxy
+ for _, proxy := range proxyMap {
+ allProxies = append(allProxies, proxy)
+ }
+
+ // 生成新的配置内容
+ iniContent := GenFrpcIniConfig(requestData.Config, allProxies)
+
+ if _, err := os.Stat(filepath.Join(path, "frpc")); os.IsNotExist(err) {
+ os.MkdirAll(filepath.Join(path, "frpc"), 0755)
+ }
+
+ err = os.WriteFile(filePath, []byte(iniContent), 0644)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(NewResponse("0", "Configuration created successfully", nil))
+}
+
+// GetFrpcConfigHandler 处理获取单个 FRPC 配置的 HTTP 请求
+func GetFrpcConfigHandler(w http.ResponseWriter, r *http.Request) {
+ // 获取请求参数中的 name
+ name := r.URL.Query().Get("name")
+ if name == "" {
+ http.Error(w, "Name parameter is required", http.StatusBadRequest)
+ return
+ }
+
+ // 使用 os.ReadFile 读取配置文件内容
+ path := libs.GetAppExecDir()
+ filePath := filepath.Join(path, "frpc", "frpc.ini")
+
+ iniContent, err := os.ReadFile(filePath)
+ if err != nil {
+ http.Error(w, "Failed to read configuration file", http.StatusInternalServerError)
+ return
+ }
+
+ // 解析 ini 文件
+ cfg, err := ini.Load(iniContent)
+ if err != nil {
+ http.Error(w, "Failed to parse configuration file", http.StatusInternalServerError)
+ return
+ }
+
+ // 提取 [common] 部分
+ commonSection := cfg.Section("common")
+ frpConfig := FrpConfig{
+ ServerAddr: commonSection.Key("serverAddr").String(),
+ ServerPort: commonSection.Key("serverPort").MustInt(),
+ AuthMethod: commonSection.Key("authenticationMethod").String(),
+ AuthToken: commonSection.Key("token").String(),
+ User: commonSection.Key("user").String(),
+ MetaToken: commonSection.Key("metaToken").String(),
+ TransportHeartbeatInterval: commonSection.Key("heartbeatInterval").MustInt(),
+ TransportHeartbeatTimeout: commonSection.Key("heartbeatTimeout").MustInt(),
+ LogLevel: commonSection.Key("logLevel").String(),
+ LogMaxDays: commonSection.Key("logMaxDays").MustInt(),
+ WebPort: commonSection.Key("adminPort").MustInt(),
+ TlsConfigEnable: commonSection.Key("tlsEnable").MustBool(),
+ TlsConfigCertFile: commonSection.Key("tlsCertFile").String(),
+ TlsConfigKeyFile: commonSection.Key("tlsKeyFile").String(),
+ TlsConfigTrustedCaFile: commonSection.Key("tlsTrustedCaFile").String(),
+ TlsConfigServerName: commonSection.Key("tlsServerName").String(),
+ ProxyConfigEnable: commonSection.Key("httpProxy").String() != "",
+ ProxyConfigProxyUrl: commonSection.Key("httpProxy").String(),
+ }
+
+ // 查找指定 name 的代理
+ var proxy *Proxy
+ for _, section := range cfg.Sections() {
+ if section.Name() == "common" {
+ continue
+ }
+ if section.Name() == name {
+ proxy = &Proxy{
+ Name: section.Name(),
+ Type: section.Key("type").String(),
+ LocalIp: section.Key("localIP").String(),
+ LocalPort: section.Key("localPort").MustInt(),
+ RemotePort: section.Key("remotePort").MustInt(),
+ // 继续提取其他字段...
+ }
+ break
+ }
+ }
+
+ if proxy == nil {
+ http.Error(w, "Proxy not found", http.StatusNotFound)
+ return
+ }
+
+ // 创建响应
+ responseData := struct {
+ Config FrpConfig `json:"config"`
+ Proxy Proxy `json:"proxy"`
+ }{
+ Config: frpConfig,
+ Proxy: *proxy,
+ }
+
+ response := NewResponse("0", "Configuration retrieved successfully", responseData)
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(response)
+}
+
+// GetFrpcProxiesHandler 获取 FRPC 代理列表
+func GetFrpcProxiesHandler(w http.ResponseWriter, r *http.Request) {
+ // 使用 os.ReadFile 读取配置文件内容
+ path := libs.GetAppExecDir()
+ filePath := filepath.Join(path, "frpc", "frpc.ini")
+
+ iniContent, err := os.ReadFile(filePath)
+ if err != nil {
+ http.Error(w, "Failed to read configuration file", http.StatusInternalServerError)
+ return
+ }
+
+ // 解析 ini 文件
+ cfg, err := ini.Load(iniContent)
+ if err != nil {
+ http.Error(w, "Failed to parse configuration file", http.StatusInternalServerError)
+ return
+ }
+
+ // 提取代理部分
+ var proxies []Proxy
+ for _, section := range cfg.Sections() {
+ if section.Name() == "common" {
+ continue
+ }
+ proxy := Proxy{
+ Name: section.Name(),
+ Type: section.Key("type").String(),
+ LocalIp: section.Key("localIP").String(),
+ LocalPort: section.Key("localPort").MustInt(),
+ RemotePort: section.Key("remotePort").MustInt(),
+ CustomDomains: strings.Split(section.Key("custom_domains").String(), ","),
+ Subdomain: section.Key("subdomain").String(),
+ BasicAuth: section.Key("httpUser").String() != "",
+ HttpUser: section.Key("httpUser").String(),
+ HttpPassword: section.Key("httpPassword").String(),
+ StcpModel: section.Key("role").String(),
+ ServerName: section.Key("serverName").String(),
+ BindAddr: section.Key("bindAddr").String(),
+ BindPort: section.Key("bindPort").MustInt(),
+ FallbackTo: section.Key("fallbackTo").String(),
+ FallbackTimeoutMs: section.Key("fallbackTimeoutMs").MustInt(),
+ SecretKey: section.Key("sk").String(),
+ }
+ proxies = append(proxies, proxy)
+ }
+
+ // 创建响应
+ response := NewResponse("0", "Proxies retrieved successfully", proxies)
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(response)
+}
+
+func DeleteFrpcConfigHandler(w http.ResponseWriter, r *http.Request) {
+ // 获取请求参数中的 name
+ name := r.URL.Query().Get("name")
+ if name == "" {
+ http.Error(w, "Name parameter is required", http.StatusBadRequest)
+ return
+ }
+
+ // 使用 os.ReadFile 读取配置文件内容
+ path := libs.GetAppExecDir()
+ filePath := filepath.Join(path, "frpc", "frpc.ini")
+
+ iniContent, err := os.ReadFile(filePath)
+ if err != nil {
+ http.Error(w, "Failed to read configuration file", http.StatusInternalServerError)
+ return
+ }
+
+ // 解析 ini 文件
+ cfg, err := ini.Load(iniContent)
+ if err != nil {
+ http.Error(w, "Failed to parse configuration file", http.StatusInternalServerError)
+ return
+ }
+
+ // 检查是否存在指定 name 的代理
+ if cfg.Section(name) == nil {
+ http.Error(w, "Proxy not found", http.StatusNotFound)
+ return
+ }
+
+ // 删除指定 name 的代理
+ cfg.DeleteSection(name)
+
+ // 将更新后的配置写回文件
+ err = cfg.SaveTo(filePath)
+ if err != nil {
+ http.Error(w, "Failed to save configuration file", http.StatusInternalServerError)
+ return
+ }
+
+ // 创建响应
+ response := NewResponse("0", "Configuration deleted successfully", nil)
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(response)
+}
+
+func UpdateFrpcConfigHandler(w http.ResponseWriter, r *http.Request) {
+ // 获取请求参数中的 name
+ name := r.URL.Query().Get("name")
+ if name == "" {
+ http.Error(w, "Name parameter is required", http.StatusBadRequest)
+ return
+ }
+
+ // 解析请求体中的更新数据
+ var updateData Proxy
+ err := json.NewDecoder(r.Body).Decode(&updateData)
+ if err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ // 使用 os.ReadFile 读取配置文件内容
+ path := libs.GetAppExecDir()
+ filePath := filepath.Join(path, "frpc", "frpc.ini")
+
+ iniContent, err := os.ReadFile(filePath)
+ if err != nil {
+ http.Error(w, "Failed to read configuration file", http.StatusInternalServerError)
+ return
+ }
+
+ // 解析 ini 文件
+ cfg, err := ini.Load(iniContent)
+ if err != nil {
+ http.Error(w, "Failed to parse configuration file", http.StatusInternalServerError)
+ return
+ }
+
+ // 查找指定 name 的代理
+ section := cfg.Section(name)
+ if section == nil {
+ http.Error(w, "Proxy not found", http.StatusNotFound)
+ return
+ }
+
+ // 更新代理配置
+ section.Key("type").SetValue(updateData.Type)
+ section.Key("localIP").SetValue(updateData.LocalIp)
+ section.Key("localPort").SetValue(fmt.Sprintf("%d", updateData.LocalPort))
+ section.Key("remotePort").SetValue(fmt.Sprintf("%d", updateData.RemotePort))
+ section.Key("custom_domains").SetValue(strings.Join(updateData.CustomDomains, ","))
+ section.Key("subdomain").SetValue(updateData.Subdomain)
+ section.Key("httpUser").SetValue(updateData.HttpUser)
+ section.Key("httpPassword").SetValue(updateData.HttpPassword)
+ section.Key("role").SetValue(updateData.StcpModel)
+ section.Key("serverName").SetValue(updateData.ServerName)
+ section.Key("bindAddr").SetValue(updateData.BindAddr)
+ section.Key("bindPort").SetValue(fmt.Sprintf("%d", updateData.BindPort))
+ section.Key("fallbackTo").SetValue(updateData.FallbackTo)
+ section.Key("fallbackTimeoutMs").SetValue(fmt.Sprintf("%d", updateData.FallbackTimeoutMs))
+ section.Key("sk").SetValue(updateData.SecretKey)
+
+ // 将更新后的配置写回文件
+ err = cfg.SaveTo(filePath)
+ if err != nil {
+ http.Error(w, "Failed to save configuration file", http.StatusInternalServerError)
+ return
+ }
+
+ // 创建响应
+ response := NewResponse("0", "Configuration updated successfully", nil)
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(response)
+}