diff --git a/assets/lang/en.json b/assets/lang/en.json index 600f55e..e42eb1b 100644 --- a/assets/lang/en.json +++ b/assets/lang/en.json @@ -7,5 +7,53 @@ "Please input password": "Please input password", "Login success": "Login success", "Username or password incorrect": "Username or password incorrect", - "Empty data": "Empty data" + "Empty data": "Empty data", + "Client Info": "Client Info", + "Proxies Status": "Proxies Status", + "Proxies": "Proxies", + "Server Address": "Server Address", + "Server Port": "Server Port", + "Protocol": "Protocol", + "TCP Mux": "TCP Mux", + "User": "User", + "User Token": "User Token", + "Admin Address": "Admin Address", + "Admin Port": "Admin Port", + "Admin User": "Admin User", + "Admin Pwd": "Admin Pwd", + "Heartbeat Interval": "Heartbeat Interval", + "Heartbeat Timeout": "Heartbeat Timeout", + "TLS Enable": "TLS Enable", + "TLS Key File": "TLS Key File", + "TLS Cert File": "TLS Cert File", + "TLS Trusted CA File": "TLS Trusted CA File", + "false": "No", + "true": "Yes", + "New Proxy": "New Proxy", + "Remove Proxy": "Remove Proxy", + "Update": "Update", + "Remove": "Remove", + "Basic": "Basic Params", + "Extra": "Extra Params", + "Proxy Name": "Proxy Name", + "Customize Domains": "Customize Domains", + "Subdomain": "Subdomain", + "Remote Port": "Remote Port", + "Use Encryption": "Use Encryption", + "Use Compression": "Use Compression", + "Param Name": "Param Name", + "Param Value": "Param Value", + "Name": "Name", + "Type": "Type", + "Local Address": "Local Address", + "Plugin": "Plugin", + "Remote Address": "Remote Address", + "Status": "Status", + "Info": "Info", + "running": "running", + "Local Ip": "Local Ip", + "Local Port": "Local Port", + "Operation": "Operation", + "Confirm": "Confirm", + "Cancel": "Cancel" } \ No newline at end of file diff --git a/assets/lang/zh.json b/assets/lang/zh.json index 1b28c6a..3fa43f7 100644 --- a/assets/lang/zh.json +++ b/assets/lang/zh.json @@ -7,5 +7,53 @@ "Please input password": "请填写密码", "Login success": "登录成功", "Username or password incorrect": "用户名或密码错误", - "Empty data": "无数据" + "Empty data": "无数据", + "Client Info": "客户端信息", + "Proxies Status": "代理状态", + "Proxies": "代理", + "Server Address": "服务器地址", + "Server Port": "服务器端口", + "Protocol": "连接协议", + "TCP Mux": "TCP端口复用", + "User": "用户", + "User Token": "鉴权凭证", + "Admin Address": "客户端管理监听地址", + "Admin Port": "客户端管理监听端口", + "Admin User": "客户端管理账号", + "Admin Pwd": "客户端管理密码", + "Heartbeat Interval": "心跳检测间隔", + "Heartbeat Timeout": "心跳检测超时时间", + "TLS Enable": "启用TLS加密", + "TLS Key File": "TLS密钥文件路径", + "TLS Cert File": "TLS证书文件路径", + "TLS Trusted CA File": "TLS CA证书文件路径", + "false": "否", + "true": "是", + "New Proxy": "新增代理", + "Remove Proxy": "删除代理", + "Update": "更新", + "Remove": "删除", + "Basic": "基础参数", + "Extra": "附加参数", + "Proxy Name": "代理名称", + "Customize Domains": "自定义域名", + "Subdomain": "子域名", + "Remote Port": "服务端端口", + "Use Encryption": "加密传输", + "Use Compression": "压缩传输", + "Param Name": "参数名称", + "Param Value": "参数值", + "Name": "代理名称", + "Type": "代理类型", + "Local Address": "本地地址", + "Plugin": "插件", + "Remote Address": "服务端地址", + "Status": "状态", + "Info": "消息", + "running": "运行中", + "Local Ip": "本地IP", + "Local Port": "本地端口", + "Operation": "操作", + "Confirm": "确定", + "Cancel": "取消" } \ No newline at end of file diff --git a/assets/static/css/index.css b/assets/static/css/index.css index b3bbad0..35c09c2 100644 --- a/assets/static/css/index.css +++ b/assets/static/css/index.css @@ -118,25 +118,25 @@ section { font-size: 12px; } -section.common-info { +section.client-info { display: flex; } -section.common-info .text-info { +section.client-info .text-info { flex: 1; } -section.common-info .text-info { +section.client-info .text-info { padding: 0 20px; } -section.common-info .text-row { +section.client-info .text-row { display: flex; font-size: 14px; line-height: 40px; } -section.common-info .text-row .text-col { +section.client-info .text-row .text-col { flex: 1; } diff --git a/assets/static/js/index-configure.js b/assets/static/js/index-client-info.js similarity index 72% rename from assets/static/js/index-configure.js rename to assets/static/js/index-client-info.js index e18a9a5..3be655f 100644 --- a/assets/static/js/index-configure.js +++ b/assets/static/js/index-client-info.js @@ -1,4 +1,4 @@ -var loadCommon = (function ($) { +var loadClientInfo = (function ($) { var i18n = {}; /** @@ -6,7 +6,7 @@ var loadCommon = (function ($) { * @param lang {Map} language json * @param title {string} page title */ - function loadCommon(lang, title) { + function loadClientInfo(lang, title) { i18n = lang; $("#title").text(title); $('#content').empty(); @@ -26,9 +26,11 @@ var loadCommon = (function ($) { } function renderCommonInfo(data) { - var html = layui.laytpl($('#commonTemplate').html()).render(data); + data.tcp_mux = i18n[data.tcp_mux]; + data.tls_enable = i18n[data.tls_enable]; + var html = layui.laytpl($('#clientInfoTemplate').html()).render(data); $('#content').html(html); } - return loadCommon; + return loadClientInfo; })(layui.$); \ No newline at end of file diff --git a/assets/static/js/index-overview.js b/assets/static/js/index-overview.js deleted file mode 100644 index c64023e..0000000 --- a/assets/static/js/index-overview.js +++ /dev/null @@ -1,95 +0,0 @@ -var loadOverview = (function ($) { - var i18n = {}; - - /** - * get proxy info - * @param lang {{}} language json - * @param title page title - */ - function loadOverview(lang, title) { - i18n = lang; - $("#title").text(title); - $('#content').empty(); - var loading = layui.layer.load(); - - $.getJSON('/proxy/api/status').done(function (result) { - if (result.success) { - $('#content').html($('#overviewTableTemplate').html()); - renderOverviewTable(JSON.parse(result.data)); - } else { - layui.layer.msg(result.message); - } - }).always(function () { - layui.layer.close(loading); - }); - } - - /** - * render proxy list table - * @param data {Map>} proxy data - * @param proxyType proxy type - */ - function renderOverviewTable(data, proxyType) { - var dataList = []; - for (var type in data) { - var temp = data[type]; - dataList = dataList.concat(temp); - } - - var $section = $('#content > section'); - var cols = [ - {field: 'name', title: 'Name', sort: true}, - {field: 'type', title: 'Type', width: 100, sort: true}, - { - field: 'local_addr', - title: 'Local Address', - templet: '{{= d.local_addr || "-" }}', - width: 220, - sort: true - }, - {field: 'plugin', title: 'plugin', templet: '{{= d.plugin || "-" }}', sort: true}, - {field: 'remote_addr', title: 'Remote Address', sort: true}, - {field: 'status', title: 'Status', width: 100, sort: true}, - {field: 'err', title: 'Info',templet: '{{= d.err || "-" }}', width: 200} - ]; - - var overviewTable = layui.table.render({ - elem: '#overviewTable', - height: $section.height(), - text: {none: i18n['EmptyData']}, - cols: [cols], - page: navigator.language.indexOf("zh") !== -1, - data: dataList, - initSort: { - field: 'name', - type: 'asc' - } - }); - - window.onresize = function () { - overviewTable.resize(); - } - - bindFormEvent(); - } - - /** - * bind event of {{@link layui.form}} - */ - function bindFormEvent() { - layui.table.on('tool(proxyListTable)', function (obj) { - var data = obj.data; - - switch (obj.event) { - case 'update': - break; - // updatePopup(data); - case 'remove': - // removePopup(data); - break; - } - }); - } - - return loadOverview; -})(layui.$); \ No newline at end of file diff --git a/assets/static/js/index-proxy-list.js b/assets/static/js/index-proxy-list.js index fc574d4..96a4fb1 100644 --- a/assets/static/js/index-proxy-list.js +++ b/assets/static/js/index-proxy-list.js @@ -1,16 +1,7 @@ var loadProxyInfo = (function ($) { var i18n = {}, currentProxyType, currentTitle; //param names in Basic tab - var basicParamNames = ['name', 'type', 'local_ip', 'local_port', 'custom_domains', 'subdomain', 'remote_port', 'use_encryption', 'use_compression']; - //param need to convert type - var intParamNames = ['local_port', 'health_check_timeout_s', 'health_check_max_failed', 'health_check_interval_s']; - var booleanParamNames = ['use_encryption', 'use_compression']; - var stringArrayParamNames = ['custom_domains', 'locations'] - var mapParamPrefixes = [ - {name: 'metas', prefix: 'meta_'}, - {name: 'plugin_params', prefix: 'plugin_'}, - {name: 'headers', prefix: 'header_'} - ]; + var basicParamNames = ['name', 'type', 'local_ip', 'local_port', 'custom_domains', 'sub_domain', 'remote_port', 'use_encryption', 'use_compression']; /** * get proxy info @@ -60,11 +51,11 @@ var loadProxyInfo = (function ($) { var $section = $('#content > section'); var cols = [ {type: 'checkbox'}, - {field: 'name', title: 'Name', sort: true}, - {field: 'type', title: 'Type', sort: true}, - {field: 'local_ip', title: 'Local Ip', sort: true}, - {field: 'local_port', title: 'Local Port', sort: true}, - {title: 'Operation', width: 150, toolbar: '#proxyListOperationTemplate'} + {field: 'name', title: i18n['Name'], sort: true}, + {field: 'type', title: i18n['Type'], sort: true}, + {field: 'local_ip', title: i18n['LocalIp'], sort: true}, + {field: 'local_port', title: i18n['LocalPort'], sort: true}, + {title: i18n['Operation'], width: 150, toolbar: '#proxyListOperationTemplate'} ]; var proxyListTable = layui.table.render({ @@ -72,7 +63,9 @@ var loadProxyInfo = (function ($) { height: $section.height(), text: {none: i18n['EmptyData']}, cols: [cols], - page: navigator.language.indexOf("zh") !== -1, + page: { + layout: navigator.language.indexOf("zh") === -1 ? ['first', 'prev', 'next', 'last'] : ['prev', 'page', 'next', 'skip', 'count', 'limit'] + }, toolbar: '#proxyListToolbarTemplate', defaultToolbar: false, data: dataList, @@ -109,7 +102,7 @@ var loadProxyInfo = (function ($) { }, false); break case 'remove': - // batchRemovePopup(data); + batchRemovePopup(data); break } }); @@ -123,11 +116,10 @@ var loadProxyInfo = (function ($) { switch (obj.event) { case 'update': - data.oldName = data.name; proxyPopup(data, true); break; case 'remove': - // removePopup(data); + batchRemovePopup([data]); break; } }); @@ -167,7 +159,7 @@ var loadProxyInfo = (function ($) { skin: 'proxy-popup', area: ['500px', '400px'], content: content, - btn: ['Confirm', 'Cancel'], + btn: [i18n['Confirm'], i18n['Cancel']], btn1: function (index) { if (layui.form.validate('#addProxyTemplate')) { var formData = layui.form.val('addProxyForm'); @@ -185,50 +177,16 @@ var loadProxyInfo = (function ($) { layui.layer.close(index); }, success: function (layero, index, that) { + //get and set old name for update form + var oldNameKey = layero.find('#oldName').attr('name'); + basicData[oldNameKey] = basicData.name; layui.form.val('addProxyForm', basicData); - proxyPopupSuccess(layero, index, that, basicData); + proxyPopupSuccess(); } }); } - - /** - * repack form data - * @param formData - * @returns {*} - */ - function repackData(formData) { - mapParamPrefixes.forEach(function (temp) { - var name = temp.name; - var prefix = temp.prefix; - for (var key in formData) { - if (key !== name && key.startsWith(prefix)) { - formData[name] = formData[name] || {}; - var newKey = key.replace(prefix, ''); - formData[name][newKey] = formData[key]; - delete formData[key]; - } - } - }); - intParamNames.forEach(function (paramName) { - if (formData.hasOwnProperty(paramName) && formData[paramName] !== '') { - formData[paramName] = parseInt(formData[paramName]); - } - }); - booleanParamNames.forEach(function (paramName) { - if (formData.hasOwnProperty(paramName) && formData[paramName] !== '') { - formData[paramName] = formData[paramName] === 'true'; - } - }); - stringArrayParamNames.forEach(function (paramName) { - if (formData.hasOwnProperty(paramName) && formData[paramName] !== '') { - formData[paramName] = formData[paramName].split(','); - } - }); - return formData; - } - - function proxyPopupSuccess(layero, index, that) { + function proxyPopupSuccess() { layui.form.render(null, 'addProxyForm'); layui.form.on('input-affix(addition)', function (obj) { var $paramValue = $(obj.elem); @@ -245,10 +203,6 @@ var loadProxyInfo = (function ($) { $paramValue.val(''); layui.form.render(); - - // var tabContent = $paramValue.closest('.layui-tab-content'); - // var scrollHeight = tabContent.prop("scrollHeight"); - // tabContent.scrollTop(scrollHeight) }); layui.form.on('input-affix(subtraction)', function (obj) { var $elem = $(obj.elem); @@ -294,23 +248,43 @@ var loadProxyInfo = (function ($) { /** * batch remove proxy popup - * @param data {[{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}]} user data list + * @param data {[Map]} proxy data list */ function batchRemovePopup(data) { if (data.length === 0) { - layui.layer.msg(i18n['ShouldCheckUser']); + layui.layer.msg('ShouldCheckProxy'); return; } - layui.layer.confirm(i18n['ConfirmRemoveUser'], { - title: i18n['OperationConfirm'], + layui.layer.confirm('ConfirmRemoveProxy', { + title: 'OperationConfirm', btn: [i18n['Confirm'], i18n['Cancel']] }, function (index) { - operate(apiType.Remove, data, index); + var loading = layui.layer.load(); + $.post({ + url: '/remove', + type: 'post', + contentType: 'application/json', + data: JSON.stringify(data), + success: function (result) { + if (result.success) { + reloadTable(); + layui.layer.close(index); + layui.layer.msg('OperateSuccess', function (index) { + layui.layer.close(index); + }); + } else { + errorMsg(result); + } + }, + complete: function () { + layui.layer.close(loading); + } + }); }); } /** - * reload user table + * reload proxy table */ function reloadTable() { loadProxyInfo(null, null, null); diff --git a/assets/static/js/index-proxy-status.js b/assets/static/js/index-proxy-status.js new file mode 100644 index 0000000..59a092f --- /dev/null +++ b/assets/static/js/index-proxy-status.js @@ -0,0 +1,81 @@ +var loadProxiesStatus = (function ($) { + var i18n = {}; + + /** + * get proxy info + * @param lang {{}} language json + * @param title page title + */ + function loadProxiesStatus(lang, title) { + i18n = lang; + $("#title").text(title); + $('#content').empty(); + var loading = layui.layer.load(); + + $.getJSON('/proxy/api/status').done(function (result) { + if (result.success) { + $('#content').html($('#proxiesTableTemplate').html()); + renderProxiesTable(JSON.parse(result.data)); + } else { + layui.layer.msg(result.message); + } + }).always(function () { + layui.layer.close(loading); + }); + } + + /** + * render proxy list table + * @param data {Map>} proxy data + * @param proxyType proxy type + */ + function renderProxiesTable(data, proxyType) { + var dataList = []; + for (var type in data) { + var temp = data[type]; + dataList = dataList.concat(temp); + } + + var $section = $('#content > section'); + var cols = [ + {field: 'name', title: i18n['Name'], sort: true}, + {field: 'type', title: i18n['Type'], width: 100, sort: true}, + { + field: 'local_addr', + title: i18n['LocalAddress'], + templet: '{{= d.local_addr || "-" }}', + width: 220, + sort: true + }, + {field: 'plugin', title: i18n['Plugin'], templet: '{{= d.plugin || "-" }}', sort: true}, + {field: 'remote_addr', title: i18n['RemoteAddress'], sort: true}, + { + field: 'status', title: i18n['Status'], templet: function (d) { + return i18n[d.status]; + }, width: 100, sort: true + }, + {field: 'err', title: i18n['Info'], templet: '{{= d.err || "-" }}', width: 200} + ]; + + var proxiesTable = layui.table.render({ + elem: '#proxiesTable', + height: $section.height(), + text: {none: i18n['EmptyData']}, + cols: [cols], + page: { + layout: navigator.language.indexOf("zh") === -1 ? ['first', 'prev', 'next', 'last'] : ['prev', 'page', 'next', 'skip', 'count', 'limit'] + }, + data: dataList, + initSort: { + field: 'name', + type: 'asc' + } + }); + + window.onresize = function () { + proxiesTable.resize(); + } + } + + return loadProxiesStatus; +})(layui.$); \ No newline at end of file diff --git a/assets/static/js/index.js b/assets/static/js/index.js index 759dab7..0d1d831 100644 --- a/assets/static/js/index.js +++ b/assets/static/js/index.js @@ -17,16 +17,14 @@ var http_port, https_port; layui.element.on('nav(leftNav)', function (elem) { var id = elem.attr('id'); var title = elem.text(); - if (id === 'overview') { - loadOverview(lang, title.trim()); - } else if (elem.closest('.layui-nav-item').attr('id') === 'configure') { + if (id === 'clientInfo') { + loadClientInfo(lang, title.trim()); + } else if (id === 'proxiesStatus') { + loadProxiesStatus(lang, title.trim()); + } else if (elem.closest('.layui-nav-item').attr('id') === 'proxies') { if (id != null && id.trim() !== '') { var suffix = elem.closest('.layui-nav-item').children('a').text().trim(); - if (id === 'common') { - loadCommon(lang, title + " " + suffix); - } else { - loadProxyInfo(lang, title + " " + suffix, id); - } + loadProxyInfo(lang, title + " " + suffix, id); } } }); diff --git a/assets/templates/index.html b/assets/templates/index.html index 218249d..3fd4d52 100644 --- a/assets/templates/index.html +++ b/assets/templates/index.html @@ -7,8 +7,8 @@ - - + + @@ -27,14 +27,14 @@
- - @@ -277,16 +273,16 @@ diff --git a/config/frpc-panel.toml b/config/frpc-panel.toml index d8b1efd..0fd31cf 100644 --- a/config/frpc-panel.toml +++ b/config/frpc-panel.toml @@ -14,8 +14,8 @@ tls_mode = false #tls_key_file = "cert.key" # frpc dashboard info -dashboard_addr = "127.0.0.1" -dashboard_port = 7400 +dashboard_addr = "home.frp.yanghuanglin.com" +dashboard_port = 80 dashboard_user = "admin" -dashboard_pwd = "admin" +dashboard_pwd = "19910621" diff --git a/pkg/server/controller/controller.go b/pkg/server/controller/controller.go index df45e56..bea5be2 100644 --- a/pkg/server/controller/controller.go +++ b/pkg/server/controller/controller.go @@ -2,16 +2,12 @@ package controller import ( "bytes" - "crypto/tls" "fmt" ginI18n "github.com/gin-contrib/i18n" "github.com/gin-gonic/gin" "github.com/vaughan0/go-ini" - "io" "log" "net/http" - "strconv" - "strings" ) func (c *HandleController) MakeLoginFunc() func(context *gin.Context) { @@ -60,9 +56,46 @@ func (c *HandleController) MakeLogoutFunc() func(context *gin.Context) { func (c *HandleController) MakeIndexFunc() func(context *gin.Context) { return func(context *gin.Context) { context.HTML(http.StatusOK, "index.html", gin.H{ - "version": c.Version, - "FrpcPanel": ginI18n.MustGetMessage(context, "Frpc Panel"), - "showExit": trimString(c.CommonInfo.AdminUser) != "" && trimString(c.CommonInfo.AdminPwd) != "", + "version": c.Version, + "oldNameKey": oldNameKey, + "showExit": trimString(c.CommonInfo.AdminUser) != "" && trimString(c.CommonInfo.AdminPwd) != "", + "FrpcPanel": ginI18n.MustGetMessage(context, "Frpc Panel"), + "ClientInfo": ginI18n.MustGetMessage(context, "Client Info"), + "ProxiesStatus": ginI18n.MustGetMessage(context, "Proxies Status"), + "Proxies": ginI18n.MustGetMessage(context, "Proxies"), + "ServerAddress": ginI18n.MustGetMessage(context, "Server Address"), + "ServerPort": ginI18n.MustGetMessage(context, "Server Port"), + "Protocol": ginI18n.MustGetMessage(context, "Protocol"), + "TCPMux": ginI18n.MustGetMessage(context, "TCP Mux"), + "User": ginI18n.MustGetMessage(context, "User"), + "UserToken": ginI18n.MustGetMessage(context, "User Token"), + "AdminAddress": ginI18n.MustGetMessage(context, "Admin Address"), + "AdminPort": ginI18n.MustGetMessage(context, "Admin Port"), + "AdminUser": ginI18n.MustGetMessage(context, "Admin User"), + "AdminPwd": ginI18n.MustGetMessage(context, "Admin Pwd"), + "HeartbeatInterval": ginI18n.MustGetMessage(context, "Heartbeat Interval"), + "HeartbeatTimeout": ginI18n.MustGetMessage(context, "Heartbeat Timeout"), + "TLSEnable": ginI18n.MustGetMessage(context, "TLS Enable"), + "TLSKeyFile": ginI18n.MustGetMessage(context, "TLS Key File"), + "TLSCertFile": ginI18n.MustGetMessage(context, "TLS Cert File"), + "TLSTrustedCAFile": ginI18n.MustGetMessage(context, "TLS Trusted CA File"), + "NewProxy": ginI18n.MustGetMessage(context, "New Proxy"), + "RemoveProxy": ginI18n.MustGetMessage(context, "Remove Proxy"), + "Update": ginI18n.MustGetMessage(context, "Update"), + "Remove": ginI18n.MustGetMessage(context, "Remove"), + "Basic": ginI18n.MustGetMessage(context, "Basic"), + "Extra": ginI18n.MustGetMessage(context, "Extra"), + "ProxyName": ginI18n.MustGetMessage(context, "Proxy Name"), + "LocalIp": ginI18n.MustGetMessage(context, "Local Ip"), + "LocalPort": ginI18n.MustGetMessage(context, "Local Port"), + "RemotePort": ginI18n.MustGetMessage(context, "Remote Port"), + "CustomizeDomains": ginI18n.MustGetMessage(context, "Customize Domains"), + "Subdomain": ginI18n.MustGetMessage(context, "Subdomain"), + "UseEncryption": ginI18n.MustGetMessage(context, "Use Encryption"), + "true": ginI18n.MustGetMessage(context, "true"), + "UseCompression": ginI18n.MustGetMessage(context, "Use Compression"), + "ParamName": ginI18n.MustGetMessage(context, "Param Name"), + "ParamValue": ginI18n.MustGetMessage(context, "Param Value"), }) } } @@ -70,7 +103,22 @@ func (c *HandleController) MakeIndexFunc() func(context *gin.Context) { func (c *HandleController) MakeLangFunc() func(context *gin.Context) { return func(context *gin.Context) { context.JSON(http.StatusOK, gin.H{ - "EmptyData": ginI18n.MustGetMessage(context, "Empty data"), + "EmptyData": ginI18n.MustGetMessage(context, "Empty data"), + "true": ginI18n.MustGetMessage(context, "true"), + "false": ginI18n.MustGetMessage(context, "false"), + "Name": ginI18n.MustGetMessage(context, "Name"), + "Type": ginI18n.MustGetMessage(context, "Type"), + "LocalAddress": ginI18n.MustGetMessage(context, "Local Address"), + "Plugin": ginI18n.MustGetMessage(context, "Plugin"), + "RemoteAddress": ginI18n.MustGetMessage(context, "Remote Address"), + "Status": ginI18n.MustGetMessage(context, "Status"), + "Info": ginI18n.MustGetMessage(context, "Info"), + "running": ginI18n.MustGetMessage(context, "running"), + "LocalIp": ginI18n.MustGetMessage(context, "Local Ip"), + "LocalPort": ginI18n.MustGetMessage(context, "Local Port"), + "Operation": ginI18n.MustGetMessage(context, "Operation"), + "Confirm": ginI18n.MustGetMessage(context, "Confirm"), + "Cancel": ginI18n.MustGetMessage(context, "Cancel"), }) } } @@ -95,7 +143,7 @@ func (c *HandleController) MakeAddProxyFunc() func(context *gin.Context) { return } - name := proxy["name"] + name := proxy[nameKey] if trimString(name) == "" { response.Success = false @@ -115,10 +163,10 @@ func (c *HandleController) MakeAddProxyFunc() func(context *gin.Context) { return } - delete(proxy, "name") + delete(proxy, nameKey) clientProxies[name] = proxy - res := c.ReloadFrpc() + res := c.UpdateFrpcConfig() if !res.Success { response.Success = false response.Code = SaveError @@ -152,8 +200,8 @@ func (c *HandleController) MakeUpdateProxyFunc() func(context *gin.Context) { return } - oldName := proxy["oldName"] - name := proxy["name"] + oldName := proxy[oldNameKey] + name := proxy[nameKey] if trimString(oldName) == "" || trimString(name) == "" { response.Success = false @@ -175,12 +223,12 @@ func (c *HandleController) MakeUpdateProxyFunc() func(context *gin.Context) { } } - delete(proxy, "name") - delete(proxy, "oldName") + delete(proxy, nameKey) + delete(proxy, oldNameKey) delete(clientProxies, oldName) clientProxies[name] = proxy - res := c.ReloadFrpc() + res := c.UpdateFrpcConfig() if !res.Success { response.Success = false response.Code = SaveError @@ -196,7 +244,7 @@ func (c *HandleController) MakeUpdateProxyFunc() func(context *gin.Context) { func (c *HandleController) MakeRemoveProxyFunc() func(context *gin.Context) { return func(context *gin.Context) { - proxy := make(map[string]interface{}) + var proxies []ini.Section response := OperationResponse{ Success: true, @@ -204,21 +252,50 @@ func (c *HandleController) MakeRemoveProxyFunc() func(context *gin.Context) { Message: "proxy remove success", } - err := context.BindJSON(&proxy) + err := context.BindJSON(&proxies) if err != nil { response.Success = false response.Code = ParamError - response.Message = fmt.Sprintf("user remove failed, param error : %v", err) + response.Message = fmt.Sprintf("proxy remove failed, param error : %v", err) log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } - res := c.ReloadFrpc() + tempProxyNames := make([]string, len(proxies)) + for index, proxy := range proxies { + name := proxy[nameKey] + + if trimString(name) == "" { + response.Success = false + response.Code = ParamError + response.Message = fmt.Sprintf("proxy remove failed, proxy %v name invalid", name) + log.Printf(response.Message) + context.JSON(http.StatusOK, &response) + return + } + + if _, exist := clientProxies[name]; !exist { + response.Success = false + response.Code = ProxyExist + response.Message = fmt.Sprintf("proxy remove failed, proxy %v not exist", name) + log.Printf(response.Message) + context.JSON(http.StatusOK, &response) + return + } + + tempProxyNames[index] = name + } + + for _, name := range tempProxyNames { + delete(clientProxies, name) + } + + res := c.UpdateFrpcConfig() if !res.Success { response.Success = false response.Code = SaveError - response.Message = fmt.Sprintf("user update failed, error : %v", res.Message) + response.Message = fmt.Sprintf("proxy remvoe failed, error : %v", res.Message) log.Printf(response.Message) context.JSON(http.StatusOK, &response) return @@ -230,37 +307,11 @@ func (c *HandleController) MakeRemoveProxyFunc() func(context *gin.Context) { func (c *HandleController) MakeProxyFunc() func(context *gin.Context) { return func(context *gin.Context) { - var client *http.Client - var protocol string - - if c.CommonInfo.DashboardTls { - client = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - } - protocol = "https://" - } else { - client = http.DefaultClient - protocol = "http://" - } - res := ProxyResponse{} - host := c.CommonInfo.DashboardAddr - port := c.CommonInfo.DashboardPort serverApi := context.Param("serverApi") - requestUrl := protocol + host + ":" + strconv.Itoa(port) + serverApi + requestUrl := c.buildRequestUrl(serverApi) request, _ := http.NewRequest("GET", requestUrl, nil) - username := c.CommonInfo.DashboardUser - password := c.CommonInfo.DashboardPwd - if trimString(username) != "" && trimString(password) != "" { - request.SetBasicAuth(username, password) - log.Printf("Proxy to %s", requestUrl) - } - - response, err := client.Do(request) + response, err := c.getClientResponse(request, c.buildClient()) if err != nil { res.Code = FrpServerError @@ -271,27 +322,7 @@ func (c *HandleController) MakeProxyFunc() func(context *gin.Context) { return } - res.Code = response.StatusCode - body, err := io.ReadAll(response.Body) - - if err != nil { - res.Success = false - res.Message = err.Error() - } else { - if res.Code == http.StatusOK { - res.Success = true - res.Data = string(body) - res.Message = fmt.Sprintf("Proxy to %s success", requestUrl) - } else { - res.Success = false - if res.Code == http.StatusNotFound { - res.Message = fmt.Sprintf("Proxy to %s error: url not found", requestUrl) - } else { - res.Message = fmt.Sprintf("Proxy to %s error: %s", requestUrl, string(body)) - } - } - } - log.Printf(res.Message) + c.parseResponse(&res, response) if serverApi == "/api/config" { proxyType, _ := context.GetQuery("type") @@ -310,37 +341,11 @@ func (c *HandleController) MakeProxyFunc() func(context *gin.Context) { } } -func (c *HandleController) ReloadFrpc() ProxyResponse { - var client *http.Client - var protocol string - - if c.CommonInfo.DashboardTls { - client = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - } - protocol = "https://" - } else { - client = http.DefaultClient - protocol = "http://" - } - +func (c *HandleController) UpdateFrpcConfig() ProxyResponse { res := ProxyResponse{} - host := c.CommonInfo.DashboardAddr - port := c.CommonInfo.DashboardPort - serverApi := "/api/config" - requestUrl := protocol + host + ":" + strconv.Itoa(port) + serverApi + requestUrl := c.buildRequestUrl("/api/config") request, _ := http.NewRequest("PUT", requestUrl, bytes.NewReader(serializeSectionsToString())) - username := c.CommonInfo.DashboardUser - password := c.CommonInfo.DashboardPwd - if trimString(username) != "" && trimString(password) != "" { - request.SetBasicAuth(username, password) - } - - response, err := client.Do(request) + response, err := c.getClientResponse(request, c.buildClient()) if err != nil { res.Code = FrpServerError @@ -350,45 +355,25 @@ func (c *HandleController) ReloadFrpc() ProxyResponse { return res } - res.Code = response.StatusCode - body, err := io.ReadAll(response.Body) - - if err != nil { - res.Success = false - res.Message = err.Error() - } else { - if res.Code == http.StatusOK { - res.Success = true - res.Data = string(body) - res.Message = fmt.Sprintf("Proxy to %s success", requestUrl) - } else { - res.Success = false - if res.Code == http.StatusNotFound { - res.Message = fmt.Sprintf("Proxy to %s error: url not found", requestUrl) - } else { - res.Message = fmt.Sprintf("Proxy to %s error: %s", requestUrl, string(body)) - } - } + c.parseResponse(&res, response) + if res.Success { + c.ReloadFrpcConfig(&res) } - log.Printf(res.Message) return res } -func serializeSectionsToString() []byte { - var build strings.Builder - build.WriteString("[common]\n") - for key, value := range clientCommon { - build.WriteString(fmt.Sprintf("%s = %s\n", key, value)) - } - build.WriteString("\n") +func (c *HandleController) ReloadFrpcConfig(res *ProxyResponse) { + requestUrl := c.buildRequestUrl("/api/reload") + request, _ := http.NewRequest("GET", requestUrl, nil) + response, err := c.getClientResponse(request, c.buildClient()) - for name, section := range clientProxies { - build.WriteString(fmt.Sprintf("[%s]\n", name)) - for key, value := range section { - build.WriteString(fmt.Sprintf("%s = %s\n", key, value)) - } - build.WriteString("\n") + if err != nil { + res.Code = FrpServerError + res.Success = false + res.Message = err.Error() + log.Print(err) + return } - return []byte(build.String()) + c.parseResponse(res, response) } diff --git a/pkg/server/controller/utils.go b/pkg/server/controller/utils.go index da18373..d98df48 100644 --- a/pkg/server/controller/utils.go +++ b/pkg/server/controller/utils.go @@ -1,8 +1,14 @@ package controller import ( + "crypto/tls" + "fmt" "github.com/fatedier/frp/pkg/config" "github.com/vaughan0/go-ini" + "io" + "log" + "net/http" + "strconv" "strings" ) @@ -10,17 +16,95 @@ func trimString(str string) string { return strings.TrimSpace(str) } -func cleanString(originalString string) string { - return trimString(originalString) +func serializeSectionsToString() []byte { + var build strings.Builder + build.WriteString("[common]\n") + for key, value := range clientCommon { + build.WriteString(fmt.Sprintf("%s = %s\n", key, value)) + } + build.WriteString("\n") + + for name, section := range clientProxies { + build.WriteString(fmt.Sprintf("[%s]\n", name)) + for key, value := range section { + build.WriteString(fmt.Sprintf("%s = %s\n", key, value)) + } + build.WriteString("\n") + } + + return []byte(build.String()) } -func stringContains(element string, data []string) bool { - for _, v := range data { - if element == v { - return true +func (c *HandleController) buildRequestUrl(serverApi string) string { + var protocol string + + if c.CommonInfo.DashboardTls { + protocol = "https://" + } else { + protocol = "http://" + } + + host := c.CommonInfo.DashboardAddr + port := c.CommonInfo.DashboardPort + requestUrl := protocol + host + ":" + strconv.Itoa(port) + serverApi + + return requestUrl +} + +func (c *HandleController) buildClient() *http.Client { + var client *http.Client + + if c.CommonInfo.DashboardTls { + client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + } else { + client = http.DefaultClient + } + + return client +} + +func (c *HandleController) getClientResponse(request *http.Request, client *http.Client) (*http.Response, error) { + username := c.CommonInfo.DashboardUser + password := c.CommonInfo.DashboardPwd + if trimString(username) != "" && trimString(password) != "" { + request.SetBasicAuth(username, password) + } + + response, err := client.Do(request) + return response, err +} + +func (c *HandleController) parseResponse(res *ProxyResponse, response *http.Response) string { + res.Code = response.StatusCode + body, err := io.ReadAll(response.Body) + if err != nil { + res.Success = false + res.Message = err.Error() + } else { + url := response.Request.URL + if res.Code == http.StatusOK { + res.Success = true + res.Data = string(body) + res.Message = fmt.Sprintf("Proxy to %s success", url) + } else { + res.Success = false + if res.Code == http.StatusNotFound { + res.Message = fmt.Sprintf("Proxy to %s error: url not found", url) + } else { + res.Message = fmt.Sprintf("Proxy to %s error: %s", url, string(body)) + } } } - return false + + log.Printf(res.Message) + + return string(body) } func (c *HandleController) parseConfigure(content, proxyType string) (interface{}, error) { @@ -44,7 +128,7 @@ func (c *HandleController) parseConfigure(content, proxyType string) (interface{ currentProxies[name] = section } clientProxies[name] = section - delete(clientProxies[name], "name") + delete(clientProxies[name], nameKey) } if proxyType == "none" { diff --git a/pkg/server/controller/variables.go b/pkg/server/controller/variables.go index 51eb381..17412bd 100644 --- a/pkg/server/controller/variables.go +++ b/pkg/server/controller/variables.go @@ -19,6 +19,11 @@ const ( ProxyRemove ) +const ( + nameKey = "name" + oldNameKey = "_old_name" +) + const ( SessionName = "GOSESSION" AuthName = "_PANEL_AUTH"