feat: update nps v0.29.36

This commit is contained in:
arch3rPro
2025-07-15 05:31:21 +08:00
parent 344cd68eab
commit 94ddb83d05
56 changed files with 1 additions and 1 deletions

View File

@@ -0,0 +1,140 @@
<div class="row tile">
<div class="col-md-12 col-md-auto">
<div class="ibox float-e-margins">
<h3 class="ibox-title" langtag="page-clientadd"></h3>
<div class="ibox-content">
<form class="form-horizontal">
<div class="form-group" id="remark">
<label class="control-label font-bold" langtag="word-remark"></label>
<div class="col-sm-12">
<input class="form-control" langtag="word-remark" name="remark" placeholder="" type="text">
</div>
</div>
{{if eq true .allow_flow_limit}}
<div class="form-group" id="flow_limit">
<label class="control-label font-bold" langtag="word-flowlimit"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="flow_limit" placeholder="" type="text">
<span class="help-block m-b-none" langtag="word-unit"></span>: M
</div>
</div>
{{end}}
{{if eq true .allow_time_limit}}
<div class="form-group" id="time_limit">
<label class="control-label font-bold" langtag="word-timelimit"></label>
<div class="col-sm-12">
<input class="form-control" id="time_limit_input" langtag="info-unrestricted" name="time_limit" placeholder="" type="datetime-local">
<span class="help-block m-b-none" langtag="info-timelimit"></span>
</div>
</div>
{{end}}
{{if eq true .allow_rate_limit}}
<div class="form-group" id="rate_limit">
<label class="control-label font-bold" langtag="word-ratelimit"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="rate_limit" placeholder="" type="text">
<span class="help-block m-b-none" langtag="word-unit"></span>: KB/S
</div>
</div>
{{end}}
{{if eq true .allow_connection_num_limit}}
<div class="form-group" id="max_conn">
<label class="control-label font-bold" langtag="word-maxconnections"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="max_conn" placeholder="" type="text">
</div>
</div>
{{end}}
{{if eq true .allow_tunnel_num_limit}}
<div class="form-group" id="max_tunnel">
<label class="control-label font-bold" langtag="word-maxtunnels"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="max_tunnel" placeholder="" type="text">
</div>
</div>
{{end}}
<div class="form-group" id="u">
<label class="control-label font-bold" langtag="word-basicusername"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="u" placeholder="" type="text">
<span class="help-block m-b-none" langtag="info-onlyproxy"></span>
</div>
</div>
<div class="form-group" id="p">
<label class="control-label font-bold" langtag="word-basicpassword"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="p" placeholder="" type="text">
<span class="help-block m-b-none" langtag="info-onlyproxy"></span>
</div>
</div>
<div class="form-group" id="vkey">
<label class="control-label font-bold" langtag="word-verifyKey"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="vkey" placeholder="" type="text">
<span class="help-block m-b-none" langtag="info-autogenerated"></span>
</div>
</div>
{{if eq true .allow_user_login}}
<div class="form-group" id="web_username">
<label class="control-label font-bold" langtag="word-webusername"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="web_username" placeholder="" type="text">
</div>
</div>
<div class="form-group" id="web_password">
<label class="control-label font-bold" langtag="word-webpassword"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="web_password" placeholder="" type="text">
</div>
</div>
{{end}}
<div class="form-group" id="config_conn_allow">
<label class="control-label font-bold" langtag="word-connectbyconfig"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="config_conn_allow">
<option langtag="word-yes" value="1"></option>
<option langtag="word-no" value="0"></option>
</select>
</div>
</div>
<div class="form-group" id="compress">
<label class="control-label font-bold" langtag="word-compress"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="compress">
<option langtag="word-no" value="0"></option>
<option langtag="word-yes" value="1"></option>
</select>
</div>
</div>
<div class="form-group" id="crypt">
<label class="control-label font-bold" langtag="word-crypt"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="crypt">
<option langtag="word-no" value="0"></option>
<option langtag="word-yes" value="1"></option>
</select>
</div>
</div>
<div class="form-group" id="black_ip_list">
<label class="control-label font-bold" langtag="word-blackiplist"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasblackiplist" name="blackiplist" placeholder="" rows="4" type="text">{{.BlackIpList}}</textarea>
<span class="help-block m-b-none" langtag="info-descblackiplist"></span>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-lg-12 col-sm-offset-2">
<button class="btn btn-secondary" onclick="goback()" type="button">
<i class="fa fa-fw fa-lg fa-window-close"></i> <span langtag="word-cancel"></span>
</button>
<button class="btn btn-success" onclick="submitform('add', '{{.web_base_url}}/client/add', $('form').serializeArray())" type="button">
<i class="fa fa-fw fa-lg fa-check-circle"></i> <span langtag="word-add"></span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,156 @@
<div class="row tile">
<div class="col-md-12 col-md-auto">
<div class="ibox float-e-margins">
<h3 class="ibox-title" langtag="page-clientedit"></h3>
<div class="ibox-content">
<form class="form-horizontal">
<input name="id" type="hidden" value="{{.c.Id}}">
<div class="form-group" id="remark">
<label class="control-label font-bold" langtag="word-remark"></label>
<div class="col-sm-12">
<input class="form-control" langtag="word-remark" name="remark" placeholder="" type="text" value="{{.c.Remark}}">
</div>
</div>
{{if eq true .isAdmin}}
<div class="form-group" id="flow_reset">
<label class="control-label font-bold" langtag="word-flowreset"></label>
<div class="col-sm-12">
<input name="flow_reset" type="checkbox" value="1">
<span class="help-block m-b-none" langtag="info-flowreset"></span>
</div>
</div>
{{if eq true .allow_flow_limit}}
<div class="form-group" id="flow_limit">
<label class="control-label font-bold" langtag="word-flowlimit"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="flow_limit" placeholder="" type="text" value="{{.c.Flow.FlowLimit}}">
<span class="help-block m-b-none" langtag="word-unit"></span>: M
</div>
</div>
{{end}}
{{if eq true .allow_time_limit}}
<div class="form-group" id="time_limit">
<label class="control-label font-bold" langtag="word-timelimit"></label>
<div class="col-sm-12">
<input class="form-control" id="time_limit_input" langtag="info-unrestricted" name="time_limit" placeholder="" type="text" value="{{.c.Flow.TimeLimit}}">
<span class="help-block m-b-none" langtag="info-timelimit"></span>
</div>
</div>
{{end}}
{{if eq true .allow_rate_limit}}
<div class="form-group" id="rate_limit">
<label class="control-label font-bold" langtag="word-ratelimit"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="rate_limit" placeholder="" type="text" value="{{.c.RateLimit}}">
<span class="help-block m-b-none" langtag="word-unit"></span>: KB/S
</div>
</div>
{{end}}
{{if eq true .allow_connection_num_limit}}
<div class="form-group" id="max_conn">
<label class="control-label font-bold" langtag="word-maxconnections"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="max_conn" placeholder="" type="text" value="{{.c.MaxConn}}">
</div>
</div>
{{end}}
{{if eq true .allow_tunnel_num_limit}}
<div class="form-group" id="max_tunnel">
<label class="control-label font-bold" langtag="word-maxtunnels"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="max_tunnel" placeholder="" type="text" value="{{.c.MaxTunnelNum}}">
</div>
</div>
{{end}}
{{end}}
<div class="form-group" id="u">
<label class="control-label font-bold" langtag="word-basicusername"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="u" placeholder="" type="text" value="{{.c.Cnf.U}}">
<span class="help-block m-b-none" langtag="info-onlyproxy"></span>
</div>
</div>
<div class="form-group" id="p">
<label class="control-label font-bold" langtag="word-basicpassword"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="p" placeholder="" type="text" value="{{.c.Cnf.P}}">
<span class="help-block m-b-none" langtag="info-onlyproxy"></span>
</div>
</div>
{{if eq true .isAdmin}}
<div class="form-group" id="vkey">
<label class="control-label font-bold" langtag="word-verifyKey"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="vkey" placeholder="" type="text" value="{{.c.VerifyKey}}">
<span class="help-block m-b-none" langtag="info-autogenerated"></span>
</div>
</div>
{{end}}
{{if eq true .allow_user_login}}
{{if or (eq true .allow_user_change_username) (eq true .isAdmin)}}
<div class="form-group" id="web_username">
<label class="control-label font-bold" langtag="word-webusername"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="web_username" placeholder="" type="text" value="{{.c.WebUserName}}">
</div>
</div>
{{end}}
<div class="form-group" id="web_password">
<label class="control-label font-bold" langtag="word-webpassword"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="web_password" placeholder="" type="text" value="{{.c.WebPassword}}">
</div>
</div>
{{end}}
<div class="form-group" id="config_conn_allow">
<label class="control-label font-bold" langtag="word-connectbyconfig"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="config_conn_allow">
<option {{if eq true .c.ConfigConnAllow}}selected{{end}} value="1" langtag="word-yes"></option>
<option {{if eq false .c.ConfigConnAllow}}selected{{end}} value="0" langtag="word-no"></option>
</select>
</div>
</div>
<div class="form-group" id="compress">
<label class="control-label font-bold" langtag="word-compress"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="compress">
<option {{if eq false .c.Cnf.Compress}}selected{{end}} value="0" langtag="word-no"></option>
<option {{if eq true .c.Cnf.Compress}}selected{{end}} value="1" langtag="word-yes"></option>
</select>
</div>
</div>
<div class="form-group" id="crypt">
<label class="control-label font-bold" langtag="word-crypt"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="crypt">
<option {{if eq false .c.Cnf.Crypt}}selected{{end}} value="0" langtag="word-no"></option>
<option {{if eq true .c.Cnf.Crypt}}selected{{end}} value="1" langtag="word-yes"></option>
</select>
</div>
</div>
<div class="form-group" id="black_ip_list">
<label class="control-label font-bold" langtag="word-blackiplist"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasblackiplist" name="blackiplist" placeholder="" rows="4" type="text">{{.BlackIpList}}</textarea>
<span class="help-block m-b-none" langtag="info-descblackiplist"></span>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-lg-12 col-sm-offset-2">
<button class="btn btn-secondary" onclick="goback()" type="button">
<i class="fa fa-fw fa-lg fa-window-close"></i> <span langtag="word-cancel"></span>
</button>
<button class="btn btn-success" onclick="submitform('add', '{{.web_base_url}}/client/edit', $('form').serializeArray())" type="button">
<i class="fa fa-fw fa-lg fa-save"></i><span langtag="word-save"></span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,345 @@
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="page-clientlist"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
{{if eq true .isAdmin}}
<div class="content">
<div class="table-responsive">
<div id="toolbar">
<div class="d-flex w-100">
<a class="btn btn-primary dim mr-2" href="{{.web_base_url}}/client/add">
<i class="fa fa-fw fa-lg fa-plus"></i>
<span langtag="word-add"></span></a>
<a class="btn btn-danger dim" onclick="submitform('clear', '{{.web_base_url}}/client/clear', {'id': 0, 'mode': 'flow'})">
<i class="fa fa-fw fa-lg fa-eraser"></i>
<span langtag="word-clearflow"></span></a>
</div>
</div>
<table class="table-striped table-hover" data-mobile-responsive="true" id="taskList_table"></table>
</div>
</div>
{{end}}
<div class="ibox-content">
<table id="table"></table>
</div>
</div>
</div>
</div>
</div>
<script>
/*bootstrap table*/
$('#table').bootstrapTable({
toolbar: "#toolbar",
method: 'post', // 服务器数据的请求方式 get or post
url: "{{.web_base_url}}/client/list", // 服务器数据的加载地址
contentType: "application/x-www-form-urlencoded",
striped: true, // 设置为true会有隔行变色效果
search: true,
showHeader: true,
showColumns: true,
showRefresh: true,
pagination: true,//分页
sidePagination: 'server',//服务器端分页
pageNumber: 1,
pageList: [5, 10, 20, 50, 100, 'all'],
cookie: true,
cookieIdTable: 'client',
cookieStorage: 'localStorage',
detailView: true,
smartDisplay: true, // 智能显示 pagination 和 cardview 等
onExpandRow: function () {$('body').setLang ('.detail-view');},
onPostBody: function (data) { if ($(this)[0].locale != undefined ) $('body').setLang ('#table'); },
detailFormatter: function (index, row, element) {
return '<b langtag="word-maxconnections"></b>: '
{{if eq true .isAdmin}}
+ '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/client/clear\', {\'id\':' + row.Id + ', \'mode\': \'conn_limit\'})">' + row.MaxConn + '</span>'
{{else}}
+ row.MaxConn
{{end}}
+ '&emsp;'
+ '<b langtag="word-curconnections"></b>: ' + row.NowConn + '&emsp;'
+ '<b langtag="word-flowlimit"></b>: '
{{if eq true .isAdmin}}
+ '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/client/clear\', {\'id\':' + row.Id + ', \'mode\': \'flow_limit\'})">' + row.Flow.FlowLimit + '</span>'
{{else}}
+ row.Flow.FlowLimit
{{end}}
+ 'MB&emsp;'
+ '<b langtag="word-timelimit"></b>: '
{{if eq true .isAdmin}}
+ '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/client/clear\', {\'id\':' + row.Id + ', \'mode\': \'time_limit\'})">' + row.Flow.TimeLimit + '</span>'
{{else}}
+ row.Flow.TimeLimit
{{end}}
+ '&emsp;'
+ '<b langtag="word-ratelimit"></b>: '
{{if eq true .isAdmin}}
+ '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/client/clear\', {\'id\':' + row.Id + ', \'mode\': \'rate_limit\'})">' + row.RateLimit + '</span>'
{{else}}
+ row.RateLimit
{{end}}
+ 'KB/s&emsp;'
+ '<b langtag="word-maxtunnels"></b>: '
{{if eq true .isAdmin}}
+ '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/client/clear\', {\'id\':' + row.Id + ', \'mode\': \'tunnel_limit\'})">' + row.MaxTunnelNum + '</span>'
{{else}}
+ row.MaxTunnelNum
{{end}}
+ '&emsp;<br/><br/>'
+ '<b langtag="word-createtime"></b>: ' + row.CreateTime + '&emsp;'
+ '<b langtag="word-lastonlinetime"></b>: ' + row.LastOnlineTime + '&emsp;'
+ '<b langtag="word-address"></b>: <span onclick="oCopy(this)">' + row.Addr + '</span>&emsp;'
+ '<b langtag="word-localaddress"></b>: <span onclick="oCopy(this)">' + row.LocalAddr + '</span>&emsp;<br/><br/>'
+ '<b langtag="word-webusername"></b>: ' + row.WebUserName + '&emsp;'
+ '<b langtag="word-webpassword"></b>: ' + row.WebPassword + '&emsp;'
+ '<b langtag="word-basicusername"></b>: ' + row.Cnf.U + '&emsp;'
+ '<b langtag="word-basicpassword"></b>: ' + row.Cnf.P + '&emsp;<br/><br/>'
+ '<b langtag="word-crypt"></b>: <span langtag="word-' + row.Cnf.Crypt + '"></span>&emsp;'
+ '<b langtag="word-compress"></b>: <span langtag="word-' + row.Cnf.Compress + '"></span>&emsp;'
+ '<b langtag="word-connectbyconfig"></b>: <span langtag="word-' + row.ConfigConnAllow + '"></span>&emsp;<br/><br/>'
+ '<b langtag="word-blackip"></b>: ' + row.BlackIpList + '&emsp;<br/><br/>'
{{if index . "tcp_p"}}
+ '<b langtag="word-commandclient-tcp"></b>: ' + '<code onclick="oCopy(this)">' + "./npc{{.win}} -server={{.ip}}:{{.tcp_p}} -vkey=" + row.VerifyKey + " -type=tcp</code>&emsp;<br/><br/>"
{{end}}
{{if index . "kcp_p"}}
+ '<b langtag="word-commandclient-kcp"></b>: ' + '<code onclick="oCopy(this)">' + "./npc{{.win}} -server={{.ip}}:{{.kcp_p}} -vkey=" + row.VerifyKey + " -type=kcp</code>&emsp;<br/><br/>"
{{end}}
{{if index . "tls_p"}}
+ '<b langtag="word-commandclient-tls"></b>: ' + '<code onclick="oCopy(this)">' + "./npc{{.win}} -server={{.ip}}:{{.tls_p}} -vkey=" + row.VerifyKey + " -type=tls</code>&emsp;<br/><br/>"
{{end}}
{{if index . "ws_p"}}
+ '<b langtag="word-commandclient-ws"></b>: ' + '<code onclick="oCopy(this)">' + "./npc{{.win}} -server={{.ip}}:{{.ws_p}}{{.ws_path}} -vkey=" + row.VerifyKey + " -type=ws</code>&emsp;<br/><br/>"
{{end}}
{{if index . "wss_p"}}
+ '<b langtag="word-commandclient-wss"></b>: ' + '<code onclick="oCopy(this)">' + "./npc{{.win}} -server={{.ip}}:{{.wss_p}}{{.ws_path}} -vkey=" + row.VerifyKey + " -type=wss</code>&emsp;<br/><br/>"
{{end}}
},
//表格的列
columns: [
{
field: 'Id',//域值
title: '<span langtag="word-id"></span>',//标题
halign: 'center',
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'Remark',//域值
title: '<span langtag="word-remark"></span>',//标题
halign: 'center',
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'Version',//域值
title: '<span langtag="word-version"></span>',//标题
halign: 'center',
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + escapeHtml(value) + '</span>'
}
},
{
field: 'Mode',//域值
title: '<span langtag="word-scheme"></span>',//标题
halign: 'center',
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + getBridgeMode(value) + '</span>'
}
},
{
field: 'VerifyKey',//域值
title: '<span langtag="word-verifykey"></span>',//标题
halign: 'center',
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
if (!row.NoStore) {
return '<span onclick="oCopy(this)">' + value + '</span>'
} else {
return '<span langtag="word-publicvkey"></span>'
}
}
},
{
field: 'Addr',//域值
title: '<span langtag="word-address"></span>',//标题
halign: 'center',
visible: false,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'LocalAddr',//域值
title: '<span langtag="word-localaddress"></span>',//标题
halign: 'center',
visible: false,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'InletFlow',//域值
title: '<span langtag="word-inletflow"></span>',//标题
halign: 'center',
visible: false,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
{{if eq true .isAdmin}}
return '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/client/clear\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.InletFlow) + '</span>'
{{else}}
return changeunit(row.Flow.InletFlow)
{{end}}
}
},
{
field: 'ExportFlow',//域值
title: '<span langtag="word-exportflow"></span>',//标题
halign: 'center',
visible: false,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
{{if eq true .isAdmin}}
return '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/client/clear\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.ExportFlow) + '</span>'
{{else}}
return changeunit(row.Flow.ExportFlow)
{{end}}
}
},
{
field: 'TotalFlow',//域值
title: '<span langtag="word-totalflow"></span>',//标题
halign: 'center',
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
{{if eq true .isAdmin}}
return '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/client/clear\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.InletFlow + row.Flow.ExportFlow) + '</span>'
{{else}}
return changeunit(row.Flow.InletFlow + row.Flow.ExportFlow)
{{end}}
}
},
{
field: 'NowConn',//域值
title: '<span langtag="word-nowconn"></span>',//内容
halign: 'center',
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'Rate.NowRate',//域值
title: '<span langtag="word-speed"></span>',//内容
halign: 'center',
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return changeunit(value) + "/s"
}
},
{
field: 'Status',//域值
title: '<span langtag="word-status"></span>',//内容
align: 'center',
halign: 'center',
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
if (value) {
return '<span class="badge badge-primary" langtag="word-open"></span>'
} else {
return '<span class="badge badge-badge" langtag="word-close"></span>'
}
}
},
{
field: 'IsConnect',//域值
title: '<span langtag="word-connect"></span>',//内容
align: 'center',
halign: 'center',
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
if (value) {
return '<span class="badge badge-primary" langtag="word-online"></span>'
} else {
return '<span class="badge badge-badge" langtag="word-offline"></span>'
}
}
},
{
field: 'option',//域值
title: '<span langtag="word-option"></span>',//内容
align: 'center',
halign: 'center',
visible: true,//false表示不显示
formatter: function (value, row, index) {
btn_group = '<div class="btn-group">'
{{if eq true .isAdmin}}
if (row.Status) {
btn_group += '<a onclick="submitform(\'stop\', \'{{.web_base_url}}/client/changestatus\', {\'id\':' + row.Id
btn_group += ', \'status\': 0})" class="btn btn-outline btn-warning"><i class="fa fa-pause"></i></a>'
} else {
btn_group += '<a onclick="submitform(\'start\', \'{{.web_base_url}}/client/changestatus\', {\'id\':' + row.Id
btn_group += ', \'status\': 1})" class="btn btn-outline btn-primary"><i class="fa fa-play"></i></a>'
}
{{if eq true .allow_local_proxy}}
if (row.Id > 0) {
btn_group += '<a onclick="submitform(\'delete\', \'{{.web_base_url}}/client/del\', {\'id\':' + row.Id
btn_group += '})" class="btn btn-outline btn-danger"><i class="fa fa-trash"></i></a>'
}
{{else}}
btn_group += '<a onclick="submitform(\'delete\', \'{{.web_base_url}}/client/del\', {\'id\':' + row.Id
btn_group += '})" class="btn btn-outline btn-danger"><i class="fa fa-trash"></i></a>'
{{end}}
{{end}}
btn_group += '<a href="{{.web_base_url}}/client/edit?id=' + row.Id
btn_group += '" class="btn btn-outline btn-success"><i class="fa fa-edit"></i></a></div>'
return btn_group
}
},
{
field: 'show',//域值
title: '<span langtag="word-show">',//内容
align: 'center',
halign: 'center',
visible: true,//false表示不显示
formatter: function (value, row, index) {
return '<div class="btn-group"><a href="{{.web_base_url}}/index/all?client_id=' + row.Id
+ '" class="btn btn-outline btn-primary" langtag="word-tunnel"></a>'
+ '<a href="{{.web_base_url}}/index/hostlist?client_id=' + row.Id
+ '" class="btn btn-outline btn-success" langtag="word-host"></a></div>'
}
}
]
});
</script>

View File

@@ -0,0 +1,38 @@
<div class="wrapper wrapper-content">
<!--全局参数-->
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-globalparam"></h5>
</div>
<div class="ibox-content">
<form class="form-horizontal">
<div class="form-group" id="black_ip_list">
<label class="control-label font-bold" langtag="word-globalblackiplist"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasblackiplist" name="globalBlackIpList" placeholder="" rows="10" type="text">{{.globalBlackIpList}}</textarea>
<span class="help-block m-b-none" langtag="info-descblackiplist"></span>
</div>
</div>
<div class="form-group">
<div class="col-lg-12 col-sm-offset-2">
<button class="btn btn-success" onclick="submitform('global', '{{.web_base_url}}/global/save', $('form').serializeArray())" type="button">
<i class="fa fa-fw fa-lg fa-check-circle"></i> <span langtag="word-save"></span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script>
window.addEventListener('resize', () => {
for (var key in charts) {
charts[key].resize();
}
});
</script>

View File

@@ -0,0 +1,255 @@
<div class="row tile">
<div class="col-md-12 col-md-auto">
<div class="ibox float-e-margins">
<h3 class="ibox-title" langtag="page-add"></h3>
<div class="ibox-content">
<form class="form-horizontal">
<div class="form-group">
<label class="control-label font-bold" langtag="word-scheme"></label>
<div class="col-sm-12">
<span class="help-block m-b-none font-bold" langtag="word-usecase"></span>:
<span id="usecase">
<span id="casetcp" langtag="info-casetcp"></span>
<span id="caseudp" langtag="info-caseudp"></span>
<span id="casehttpProxy" langtag="info-casehttpproxy"></span>
<span id="casesocks5" langtag="info-casesocks5"></span>
<span id="casemixProxy" langtag="info-casemixproxy"></span>
<span id="casesecret" langtag="info-casesecret"></span>
<span id="casep2p" langtag="info-casep2p"></span>
<span id="casefile" langtag="info-casefile"></span>
</span>
<select class="form-control selectpicker" id="type" name="type">
<option value="tcp" langtag="scheme-tcp"></option>
<option value="udp" langtag="scheme-udp"></option>
<option value="mixProxy" langtag="scheme-mixProxy"></option>
{{/*<option value="httpProxy" langtag="scheme-httpProxy"></option>*/}}
{{/*<option value="socks5" langtag="scheme-socks5"></option>*/}}
<option value="secret" langtag="scheme-secret"></option>
<option value="p2p" langtag="scheme-p2p"></option>
{{/*<option value="file" langtag="scheme-file"></option>*/}}
</select>
</div>
</div>
<div class="form-group" id="client_id">
<label class="control-label font-bold" langtag="word-clientid"></label>
<div class="col-sm-12">
<select class="form-control" name="client_id" data-live-search="true"></select>
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-remark"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="remark" placeholder="" type="text">
</div>
</div>
{{if eq true .allow_multi_ip}}
<div class="form-group" id="server_ip">
<label class="control-label font-bold" langtag="word-serverip"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-suchasip" name="server_ip" placeholder="" type="text" value="0.0.0.0">
</div>
</div>
{{end}}
<div class="form-group" id="port">
<label class="control-label font-bold" langtag="word-serverport"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-suchasport" name="port" placeholder="" type="text">
</div>
</div>
<div class="form-group" id="mix_proxy">
<label class="control-label font-bold" langtag="scheme-mixproxy"></label>
<div class="col-sm-12">
<input name="enable_http" type="checkbox" value="1" checked>
<span class="help-block m-b-none" langtag="word-enablehttpproxy"></span>
</div>
<div class="col-sm-12">
<input name="enable_socks5" type="checkbox" value="1" checked>
<span class="help-block m-b-none" langtag="word-enablesocks5proxy"></span>
</div>
</div>
{{if eq true .allow_flow_limit}}
<div class="form-group" id="flow_limit">
<label class="control-label font-bold" langtag="word-flowlimit"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="flow_limit" placeholder="" type="text">
<span class="help-block m-b-none" langtag="word-unit"></span>: M
</div>
</div>
{{end}}
{{if eq true .allow_time_limit}}
<div class="form-group" id="time_limit">
<label class="control-label font-bold" langtag="word-timelimit"></label>
<div class="col-sm-12">
<input class="form-control" id="time_limit_input" langtag="info-unrestricted" name="time_limit" placeholder="" type="datetime-local">
<span class="help-block m-b-none" langtag="info-timelimit"></span>
</div>
</div>
{{end}}
<div class="form-group" id="proxy_protocol">
<label class="control-label font-bold" langtag="word-proxyprotocol"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="proxy_protocol">
<option langtag="word-disable" value="0"></option>
<option langtag="word-proxyprotocolv1" value="1"></option>
<option langtag="word-proxyprotocolv2" value="2"></option>
</select>
</div>
</div>
{{if eq true .allow_local_proxy}}
<div class="form-group" id="local_proxy">
<label class="control-label font-bold" langtag="word-proxytolocal"></label>
<div class="col-sm-12">
<select class="form-control" name="local_proxy">
<option langtag="word-no" value="0"></option>
<option langtag="word-yes" value="1"></option>
</select>
</div>
</div>
{{end}}
<div class="form-group" id="auth">
<label class="control-label font-bold" langtag="word-auth"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasauth" name="auth" placeholder="" rows="4"></textarea>
<span class="help-block m-b-none" langtag="info-targetauth"></span>
</div>
</div>
<div class="form-group" id="target">
<label class="control-label font-bold" langtag="word-target"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasiplist" name="target" placeholder="" rows="4"></textarea>
<span class="help-block m-b-none" langtag="info-targettunnel"></span>
</div>
</div>
<div class="form-group" id="local_path">
<label class="control-label font-bold" langtag="word-localpath"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-suchaslocalpath" name="local_path" placeholder="" type="text">
</div>
</div>
<div class="form-group" id="strip_pre">
<label class="control-label font-bold" langtag="word-stripprefix"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-suchasstripprefix" name="strip_pre" placeholder="" type="text">
</div>
</div>
<div class="form-group" id="password">
<label class="control-label font-bold" langtag="word-identificationkey"></label>
<div class="col-sm-12">
<input class="form-control" langtag="word-identificationkey" name="password" placeholder="" type="text">
<span class="help-block m-b-none" langtag="info-identificationkey"></span>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-lg-12 col-sm-offset-2">
<button class="btn btn-secondary" onclick="goback()" type="button">
<i class="fa fa-fw fa-lg fa-window-close"></i> <span langtag="word-cancel"></span>
</button>
<button class="btn btn-success" onclick="submitform('add', '{{.web_base_url}}/index/add', $('form').serializeArray())" type="button">
<i class="fa fa-fw fa-lg fa-check-circle"></i> <span langtag="word-add"></span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
var arr = []
arr["all"] = ["auth", "port", "target", "password", "flow_reset", "flow_limit", "time_limit", "mix_proxy", "local_path", "strip_pre", "proxy_protocol", "local_proxy", "client_id", "server_ip"]
arr["tcp"] = ["port", "target", "proxy_protocol", "local_proxy", "client_id", "server_ip", "flow_reset", "flow_limit", "time_limit"]
arr["udp"] = ["port", "target", "local_proxy", "client_id", "server_ip", "flow_reset", "flow_limit", "time_limit"]
arr["mixProxy"] = ["auth", "port", "client_id", "server_ip", "mix_proxy", "flow_reset", "flow_limit", "time_limit"]
//arr["socks5"] = ["auth", "port", "client_id", "server_ip", "flow_reset", "flow_limit", "time_limit"]
//arr["httpProxy"] = ["auth", "port", "client_id", "server_ip", "flow_reset", "flow_limit", "time_limit"]
arr["secret"] = ["target", "password", "client_id", "server_ip", "flow_reset", "flow_limit", "time_limit"]
arr["p2p"] = ["target", "password", "client_id", "server_ip", "flow_reset", "flow_limit", "time_limit"]
arr["file"] = ["port", "local_path", "strip_pre", "client_id", "server_ip", "flow_reset", "flow_limit", "time_limit"]
function resetForm() {
$(".form-group[id]").css("display", "none");
$("#usecase span").css("display", "none");
o = $("#type").val();
$('#case' + o).css("display", "inline")
for (var i = 0; i < arr[o].length; i++) {
$("#" + arr[o][i]).css("display", "block")
}
}
$(function () {
$("#type").val(('{{.type}}' == '') ? 'tcp' : '{{.type}}');
resetForm()
$("#type").on("change", function () {
resetForm()
})
$("#use_client").on("change", function () {
resetForm()
})
});
function getClientList() {
const clientId = "{{if .t.Client.Id}}{{.t.Client.Id}}{{else}}{{.client_id}}{{end}}"; // 根据优先级选择
$("select[name='client_id']").selectpicker({
liveSearch: true,
source: {
data: function (callback, page) {
$.ajax({
method: 'POST',
url: "{{.web_base_url}}/client/list",
dataType: 'json',
data: { order: "asc", offset: 0, limit: 0 },
success: function(response) {
// 转换数据格式(必须包含 `value` 和 `text`
const results = $.map(response.rows, function(item) {
let displayText = item.Remark ? item.Id + ' · ' + item.Remark : item.Id.toString();
return {
value: item.Id, // 选项的 value
text: displayText, // 选项的显示文本
selected: item.Id == clientId // 默认选中
};
});
callback(results); // 返回给 selectpicker
},
error: function() {
callback([]); // 出错时返回空数组
}
});
},
search: function (callback, page, searchTerm) {
let data = { search: searchTerm, order: "asc", offset: 0, limit: 0 };
$.ajax({
method: 'POST',
url: "{{.web_base_url}}/client/list",
data: data,
dataType: 'json',
success: function(response) {
// 转换数据格式(必须包含 `value` 和 `text`
const results = $.map(response.rows, function(item) {
let displayText = item.Remark ? item.Id + ' · ' + item.Remark : item.Id.toString();
return {
value: item.Id, // 选项的 value
text: displayText, // 选项的显示文本
selected: item.Id == clientId // 默认选中
};
});
callback(results); // 返回给 selectpicker
},
error: function() {
callback([]); // 出错时返回空数组
}
});
}
}
});
}
function internationalized(current) {
$.fn.selectpicker.defaults = {
noneSelectedText: languages['content']['bootstrap-select']['noneSelectedText'][current],
noneResultsText: languages['content']['bootstrap-select']['noneResultsText'][current]
};
getClientList();
}
</script>

View File

@@ -0,0 +1,266 @@
<div class="row tile">
<div class="col-md-12 col-md-auto">
<div class="ibox float-e-margins">
<h3 class="ibox-title" langtag="page-edit"></h3>
<div class="ibox-content">
<form class="form-horizontal">
<input name="id" type="hidden" value="{{.t.Id}}">
<div class="form-group">
<label class="control-label font-bold" langtag="word-scheme"></label>
<div class="col-sm-12">
<span class="help-block m-b-none font-bold" langtag="word-usecase"></span>:
<span id="usecase">
<span id="casetcp" langtag="info-casetcp"></span>
<span id="caseudp" langtag="info-caseudp"></span>
<span id="casehttpProxy" langtag="info-casehttpproxy"></span>
<span id="casesocks5" langtag="info-casesocks5"></span>
<span id="casemixProxy" langtag="info-casemixproxy"></span>
<span id="casesecret" langtag="info-casesecret"></span>
<span id="casep2p" langtag="info-casep2p"></span>
<span id="casefile" langtag="info-casefile"></span>
</span>
<select class="form-control selectpicker" id="type" name="type">
<option value="tcp" langtag="scheme-tcp"></option>
<option value="udp" langtag="scheme-udp"></option>
<option value="mixProxy" langtag="scheme-mixProxy"></option>
{{/*<option value="httpProxy" langtag="scheme-httpProxy"></option>*/}}
{{/*<option value="socks5" langtag="scheme-socks5"></option>*/}}
<option value="secret" langtag="scheme-secret"></option>
<option value="p2p" langtag="scheme-p2p"></option>
{{/*<option value="file" langtag="scheme-file"></option>*/}}
</select>
</div>
</div>
<div class="form-group" id="client_id">
<label class="control-label font-bold" langtag="word-clientid"></label>
<div class="col-sm-12">
<select class="form-control" name="client_id" data-live-search="true"></select>
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-remark"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="remark" placeholder="" type="text" value="{{.t.Remark}}">
</div>
</div>
{{if eq true .allow_multi_ip}}
<div class="form-group" id="server_ip">
<label class="control-label font-bold" langtag="word-serverip"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-suchasip" name="server_ip" placeholder="" type="text" value="{{.t.ServerIp}}">
</div>
</div>
{{end}}
<div class="form-group" id="port">
<label class="control-label font-bold" langtag="word-serverport"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-suchasport" name="port" placeholder="" type="text" value="{{.t.Port}}">
</div>
</div>
<div class="form-group" id="mix_proxy">
<label class="control-label font-bold" langtag="scheme-mixproxy"></label>
<div class="col-sm-12">
<input name="enable_http" type="checkbox" value="1" {{if .t.HttpProxy}}checked{{end}}>
<span class="help-block m-b-none" langtag="word-enablehttpproxy"></span>
</div>
<div class="col-sm-12">
<input name="enable_socks5" type="checkbox" value="1" {{if .t.Socks5Proxy}}checked{{end}}>
<span class="help-block m-b-none" langtag="word-enablesocks5proxy"></span>
</div>
</div>
<div class="form-group" id="flow_reset">
<label class="control-label font-bold" langtag="word-flowreset"></label>
<div class="col-sm-12">
<input name="flow_reset" type="checkbox" value="1">
<span class="help-block m-b-none" langtag="info-flowreset"></span>
</div>
</div>
{{if eq true .allow_flow_limit}}
<div class="form-group" id="flow_limit">
<label class="control-label font-bold" langtag="word-flowlimit"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="flow_limit" placeholder="" type="text" value="{{.t.Flow.FlowLimit}}">
<span class="help-block m-b-none" langtag="word-unit"></span>: M
</div>
</div>
{{end}}
{{if eq true .allow_time_limit}}
<div class="form-group" id="time_limit">
<label class="control-label font-bold" langtag="word-timelimit"></label>
<div class="col-sm-12">
<input class="form-control" id="time_limit_input" langtag="info-unrestricted" name="time_limit" placeholder="" type="text" value="{{.t.Flow.TimeLimit}}">
<span class="help-block m-b-none" langtag="info-timelimit"></span>
</div>
</div>
{{end}}
<div class="form-group" id="proxy_protocol">
<label class="control-label font-bold" langtag="word-proxyprotocol"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="proxy_protocol">
<option value="0" {{if eq .t.Target.ProxyProtocol 0}}selected{{end}} langtag="word-disable"></option>
<option value="1" {{if eq .t.Target.ProxyProtocol 1}}selected{{end}} langtag="word-proxyprotocolv1"></option>
<option value="2" {{if eq .t.Target.ProxyProtocol 2}}selected{{end}} langtag="word-proxyprotocolv2"></option>
</select>
</div>
</div>
{{if eq true .allow_local_proxy}}
<div class="form-group" id="local_proxy">
<label class="control-label font-bold" langtag="word-proxytolocal"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="local_proxy">
<option {{if eq false .t.Target.LocalProxy}}selected{{end}} value="0" langtag="word-no"></option>
<option {{if eq true .t.Target.LocalProxy}}selected{{end}} value="1" langtag="word-yes"></option>
</select>
</div>
</div>
{{end}}
<div class="form-group" id="auth">
<label class="control-label font-bold" langtag="word-auth"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasauth" name="auth" placeholder="" rows="4">{{.auth}}</textarea>
<span class="help-block m-b-none" langtag="info-targetauth"></span>
</div>
</div>
<div class="form-group" id="target">
<label class="control-label font-bold" langtag="word-target"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasiplist" name="target" placeholder="" rows="4">{{.t.Target.TargetStr}}</textarea>
<span class="help-block m-b-none" langtag="info-targettunnel"></span>
</div>
</div>
<div class="form-group" id="local_path">
<label class="control-label font-bold" langtag="word-localpath"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-suchaslocalpath" name="local_path" placeholder="" type="text" value="{{.t.LocalPath}}">
</div>
</div>
<div class="form-group" id="strip_pre">
<label class="control-label font-bold" langtag="word-stripprefix"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-suchasstripprefix" name="strip_pre" placeholder="" type="text" value="{{.t.StripPre}}">
</div>
</div>
<div class="form-group" id="password">
<label class="control-label font-bold" langtag="word-identificationkey"></label>
<div class="col-sm-12">
<input class="form-control" langtag="word-identificationkey" name="password" placeholder="" type="text" value="{{.t.Password}}">
<span class="help-block m-b-none" langtag="info-identificationkey"></span>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-lg-12 col-sm-offset-2">
<button class="btn btn-secondary" onclick="goback()" type="button">
<i class="fa fa-fw fa-lg fa-window-close"></i> <span langtag="word-cancel"></span>
</button>
<button class="btn btn-success" onclick="submitform('edit', '{{.web_base_url}}/index/edit', $('form').serializeArray())" type="button">
<i class="fa fa-fw fa-lg fa-check-circle"></i> <span langtag="word-save"></span>
</button>
<button class="btn btn-warning" onclick="if ($('input[name=\'port\']').val() == '{{.t.Port}}') $('input[name=\'port\']').val(0); submitform('add', '{{.web_base_url}}/index/add', $('form').serializeArray())" type="button">
<i class="fa fa-fw fa-lg fa-check-circle"></i> <span langtag="word-add"></span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
var arr = []
arr["all"] = ["auth", "server_ip", "port", "target", "password", "flow_reset", "flow_limit", "time_limit", "mix_proxy", "local_path", "strip_pre", "proxy_protocol", "local_proxy"]
arr["tcp"] = ["client_id", "port", "target", "proxy_protocol", "local_proxy", "server_ip", "flow_reset", "flow_limit", "time_limit"]
arr["udp"] = ["client_id", "port", "target", "local_proxy", "server_ip", "flow_reset", "flow_limit", "time_limit"]
arr["mixProxy"] = ["auth", "client_id", "port", "server_ip", "mix_proxy", "flow_reset", "flow_limit", "time_limit"]
//arr["socks5"] = ["auth", "client_id", "port", "server_ip", "flow_reset", "flow_limit", "time_limit"]
//arr["httpProxy"] = ["auth", "client_id", "port", "server_ip", "flow_reset", "flow_limit", "time_limit"]
arr["secret"] = ["client_id", "target", "password", "flow_reset", "flow_limit", "time_limit"]
arr["p2p"] = ["client_id", "target", "password", "flow_reset", "flow_limit", "time_limit"]
arr["file"] = ["client_id", "port", "local_path", "strip_pre", "server_ip", "flow_reset", "flow_limit", "time_limit"]
function resetForm() {
$(".form-group[id]").css("display", "none");
$("#usecase span").css("display", "none");
o = $("#type").val();
$('#case' + o).css("display", "inline")
for (var i = 0; i < arr[o].length; i++) {
$("#" + arr[o][i]).css("display", "block")
}
}
$(function () {
$("#type").val('{{.t.Mode}}');
resetForm()
$("#type").on("change", function () {
resetForm()
})
$("#use_client").on("change", function () {
resetForm()
})
});
function getClientList() {
const clientId = "{{if .t.Client.Id}}{{.t.Client.Id}}{{else}}{{.client_id}}{{end}}"; // 根据优先级选择
$("select[name='client_id']").selectpicker({
liveSearch: true,
source: {
data: function (callback, page) {
$.ajax({
method: 'POST',
url: "{{.web_base_url}}/client/list",
dataType: 'json',
data: { order: "asc", offset: 0, limit: 0 },
success: function(response) {
// 转换数据格式(必须包含 `value` 和 `text`
const results = $.map(response.rows, function(item) {
let displayText = item.Remark ? item.Id + ' · ' + item.Remark : item.Id.toString();
return {
value: item.Id, // 选项的 value
text: displayText, // 选项的显示文本
selected: item.Id == clientId // 默认选中
};
});
callback(results); // 返回给 selectpicker
},
error: function() {
callback([]); // 出错时返回空数组
}
});
},
search: function (callback, page, searchTerm) {
let data = { search: searchTerm, order: "asc", offset: 0, limit: 0 };
$.ajax({
method: 'POST',
url: "{{.web_base_url}}/client/list",
data: data,
dataType: 'json',
success: function(response) {
// 转换数据格式(必须包含 `value` 和 `text`
const results = $.map(response.rows, function(item) {
let displayText = item.Remark ? item.Id + ' · ' + item.Remark : item.Id.toString();
return {
value: item.Id, // 选项的 value
text: displayText, // 选项的显示文本
selected: item.Id == clientId // 默认选中
};
});
callback(results); // 返回给 selectpicker
},
error: function() {
callback([]); // 出错时返回空数组
}
});
}
}
});
}
function internationalized(current) {
$.fn.selectpicker.defaults = {
noneSelectedText: languages['content']['bootstrap-select']['noneSelectedText'][current],
noneResultsText: languages['content']['bootstrap-select']['noneResultsText'][current]
};
getClientList();
}
</script>

View File

@@ -0,0 +1,329 @@
<div class="row tile">
<div class="col-md-12 col-md-auto">
<div class="ibox float-e-margins">
<h3 class="ibox-title" langtag="page-hostadd"></h3>
<div class="ibox-content">
<form class="form-horizontal">
<div class="form-group">
<label class="control-label font-bold" langtag="word-clientid"></label>
<div class="col-sm-12">
<select class="form-control" name="client_id" data-live-search="true"></select>
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-remark"></label>
<div class="col-sm-12">
<input class="form-control" type="text" name="remark" placeholder="" langtag="word-remark">
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-host"></label>
<div class="col-sm-12">
<input class="form-control" type="text" name="host" placeholder="" langtag="info-suchashost">
</div>
</div>
<div class="form-group" id="scheme">
<label class="control-label font-bold" langtag="word-scheme"></label>
<div class="col-sm-12">
<select id="scheme_select" class="form-control selectpicker" name="scheme">
<option value="all" langtag="word-all"></option>
<option value="http" langtag="word-http"></option>
<option value="https" langtag="word-https"></option>
</select>
</div>
</div>
{{if eq true .allow_flow_limit}}
<div class="form-group" id="flow_limit">
<label class="control-label font-bold" langtag="word-flowlimit"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="flow_limit" placeholder="" type="text">
<span class="help-block m-b-none" langtag="word-unit"></span>: M
</div>
</div>
{{end}}
{{if eq true .allow_time_limit}}
<div class="form-group" id="time_limit">
<label class="control-label font-bold" langtag="word-timelimit"></label>
<div class="col-sm-12">
<input class="form-control" id="time_limit_input" langtag="info-unrestricted" name="time_limit" placeholder="" type="datetime-local">
<span class="help-block m-b-none" langtag="info-timelimit"></span>
</div>
</div>
{{end}}
<div class="form-group" id="auto_https">
<label class="control-label font-bold" langtag="word-autohttps"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="auto_https">
<option langtag="word-no" value="0"></option>
<option langtag="word-yes" value="1"></option>
</select>
</div>
</div>
<div class="form-group" id="auto_cors">
<label class="control-label font-bold" langtag="word-autocors"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="auto_cors">
<option langtag="word-no" value="0"></option>
<option langtag="word-yes" value="1"></option>
</select>
</div>
</div>
<div class="form-group" id="compat_mode">
<label class="control-label font-bold" langtag="word-compatmode"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="compat_mode">
<option langtag="word-no" value="0"></option>
<option langtag="word-yes" value="1"></option>
</select>
</div>
</div>
<div class="form-group" id="https_just_proxy">
<label class="control-label font-bold" langtag="word-httpsjustproxy"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" id="https_just_proxy_select" name="https_just_proxy">
<option langtag="word-no" value="0"></option>
<option langtag="word-yes" value="1"></option>
</select>
</div>
</div>
<div class="form-group" id="proxy_protocol">
<label class="control-label font-bold" langtag="word-proxyprotocol"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="proxy_protocol">
<option langtag="word-disable" value="0"></option>
<option langtag="word-proxyprotocolv1" value="1"></option>
<option langtag="word-proxyprotocolv2" value="2"></option>
</select>
</div>
</div>
<div class="form-group" id="auto_ssl">
<label class="control-label font-bold" langtag="word-autossl"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="auto_ssl">
<option langtag="word-no" value="0"></option>
<option langtag="word-yes" value="1"></option>
</select>
</div>
</div>
<div class="form-group" id="cert_file">
<label class="control-label font-bold" langtag="word-httpscert"></label>
<div class="col-sm-12">
<textarea class="form-control" id="pemText" langtag="info-pemtext" name="cert_file" placeholder="" rows="6" type="text"></textarea>
</div>
</div>
<div class="form-group" id="key_file">
<label class="control-label font-bold" langtag="word-httpskey"></label>
<div class="col-sm-12">
<textarea class="form-control" id="pemKey" langtag="info-pemkey" name="key_file" placeholder="" rows="6" type="text"></textarea>
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-urlroute"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="location" placeholder="" type="text">
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-urlrewrite"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-urlrewrite" name="path_rewrite" placeholder="" type="text">
</div>
</div>
{{if eq true .allow_local_proxy}}
<div class="form-group" id="local_proxy">
<label class="control-label font-bold" langtag="word-proxytolocal"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="local_proxy">
<option langtag="word-no" value="0"></option>
<option langtag="word-yes" value="1"></option>
</select>
</div>
</div>
{{end}}
<div class="form-group" id="auth">
<label class="control-label font-bold" langtag="word-auth"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasauth" name="auth" placeholder="" rows="4"></textarea>
<span class="help-block m-b-none" langtag="info-targetauth"></span>
</div>
</div>
<div class="form-group" id="target_is_https">
<label class="control-label font-bold" langtag="word-targetishttps"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="target_is_https">
<option value="0">HTTP</option>
<option value="1">HTTPS</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-target"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasiplist" name="target" placeholder="" rows="4" type="text"></textarea>
<span class="help-block m-b-none" langtag="info-targethost"></span>
</div>
</div>
<div class="form-group" id="header">
<label class="control-label font-bold" langtag="word-requestheader"></label>
<div class="col-sm-12">
<textarea class="form-control" name="header" placeholder="Cache-Control: no-cache" rows="4" type="text"></textarea>
<span class="help-block m-b-none" langtag="info-header"></span>
</div>
</div>
<div class="form-group" id="hostchange">
<label class="control-label font-bold" langtag="word-requesthost"></label>
<div class="col-sm-12">
<input class="form-control" langtag="word-requesthost" name="hostchange" placeholder="" type="text" value="">
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-lg-12 col-sm-offset-2">
<button class="btn btn-secondary" onclick="goback()" type="button">
<i class="fa fa-fw fa-lg fa-window-close"></i> <span langtag="word-cancel"></span>
</button>
<button class="btn btn-success" onclick="submitform('add', '{{.web_base_url}}/index/addhost', $('form').serializeArray())" type="button">
<i class="fa fa-fw fa-lg fa-check-circle"></i> <span langtag="word-add"></span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
$(function () {
const pemText = document.getElementById("pemText");
const pemKey = document.getElementById("pemKey");
// 阻止浏览器默认打开文件的行为
document.addEventListener("dragover", function (event) {
event.preventDefault();
});
pemText.addEventListener("drop", function (event) {
event.preventDefault();
const file = event.dataTransfer.files[0];
const reader = new FileReader();
reader.onload = function (e) {
pemText.value = e.target.result;
console.log("Upload pem")
$('#edit_form').data('bootstrapValidator').updateStatus('pemText', 'NOT_VALIDATED');
$('#edit_form').data('bootstrapValidator').validateField('pemText');
}
reader.readAsText(file);
});
pemKey.addEventListener("drop", function (event) {
event.preventDefault();
const file = event.dataTransfer.files[0];
const reader = new FileReader();
reader.onload = function (e) {
pemKey.value = e.target.result;
$('#edit_form').data('bootstrapValidator').updateStatus('pemKey', 'NOT_VALIDATED');
$('#edit_form').data('bootstrapValidator').validateField('pemKey');
}
reader.readAsText(file);
});
$("#scheme_select").on("change", function () {
if ($("#scheme_select").val() == "all" || $("#scheme_select").val() == "https") {
$("#auto_https").css("display", "block")
$("#https_just_proxy").css("display", "block")
if ($("#https_just_proxy_select").val() == "1") {
$("#auto_ssl").css("display", "none")
$("#cert_file").css("display", "none")
$("#key_file").css("display", "none")
} else {
$("#auto_ssl").css("display", "block")
$("#cert_file").css("display", "block")
$("#key_file").css("display", "block")
}
} else {
$("#auto_ssl").css("display", "none")
$("#cert_file").css("display", "none")
$("#key_file").css("display", "none")
$("#https_just_proxy").css("display", "none")
$("#auto_https").css("display", "none")
}
})
$("#https_just_proxy_select").on("change", function () {
if ($("#https_just_proxy_select").val() == "1") {
$("#auto_ssl").css("display", "none")
$("#cert_file").css("display", "none")
$("#key_file").css("display", "none")
} else {
$("#auto_ssl").css("display", "block")
$("#cert_file").css("display", "block")
$("#key_file").css("display", "block")
}
})
});
function getClientList() {
const clientId = "{{.client_id}}"; // 获取 client_id
$("select[name='client_id']").selectpicker({
liveSearch: true,
source: {
data: function (callback, page) {
$.ajax({
method: 'POST',
url: "{{.web_base_url}}/client/list",
dataType: 'json',
data: { order: "asc", offset: 0, limit: 0 },
success: function(response) {
// 转换数据格式(必须包含 `value` 和 `text`
const results = $.map(response.rows, function(item) {
let displayText = item.Remark ? item.Id + ' · ' + item.Remark : item.Id.toString();
return {
value: item.Id, // 选项的 value
text: displayText, // 选项的显示文本
selected: item.Id == clientId // 默认选中
};
});
callback(results); // 返回给 selectpicker
},
error: function() {
callback([]); // 出错时返回空数组
}
});
},
search: function (callback, page, searchTerm) {
let data = { search: searchTerm, order: "asc", offset: 0, limit: 0 };
$.ajax({
method: 'POST',
url: "{{.web_base_url}}/client/list",
data: data,
dataType: 'json',
success: function(response) {
// 转换数据格式(必须包含 `value` 和 `text`
const results = $.map(response.rows, function(item) {
let displayText = item.Remark ? item.Id + ' · ' + item.Remark : item.Id.toString();
return {
value: item.Id, // 选项的 value
text: displayText, // 选项的显示文本
selected: item.Id == clientId // 默认选中
};
});
callback(results); // 返回给 selectpicker
},
error: function() {
callback([]); // 出错时返回空数组
}
});
}
}
});
}
function internationalized(current) {
$.fn.selectpicker.defaults = {
noneSelectedText: languages['content']['bootstrap-select']['noneSelectedText'][current],
noneResultsText: languages['content']['bootstrap-select']['noneResultsText'][current]
};
getClientList();
}
</script>

View File

@@ -0,0 +1,360 @@
<div class="row tile">
<div class="col-md-12 col-md-auto">
<div class="ibox float-e-margins">
<h3 class="ibox-title" langtag="page-hostedit"></h3>
<div class="ibox-content">
<form class="form-horizontal">
<input name="id" type="hidden" value="{{.h.Id}}">
<div class="form-group">
<label class="control-label font-bold" langtag="word-clientid"></label>
<div class="col-sm-12">
<select class="form-control" name="client_id" data-live-search="true"></select>
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-remark"></label>
<div class="col-sm-12">
<input class="form-control" name="remark" placeholder="remark" type="text" value="{{.h.Remark}}">
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-host"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-suchashost" name="host" placeholder="" type="text" value="{{.h.Host}}">
</div>
</div>
<div class="form-group" id="scheme">
<label class="control-label font-bold" langtag="word-scheme"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" id="scheme_select" name="scheme">
<option {{if eq "all" .h.Scheme}}selected{{end}} value="all" langtag="word-all"></option>
<option {{if eq "http" .h.Scheme}}selected{{end}} value="http" langtag="word-http"></option>
<option {{if eq "https" .h.Scheme}}selected{{end}} value="https" langtag="word-https"></option>
</select>
</div>
</div>
<div class="form-group" id="flow_reset">
<label class="control-label font-bold" langtag="word-flowreset"></label>
<div class="col-sm-12">
<input name="flow_reset" type="checkbox" value="1">
<span class="help-block m-b-none" langtag="info-flowreset"></span>
</div>
</div>
{{if eq true .allow_flow_limit}}
<div class="form-group" id="flow_limit">
<label class="control-label font-bold" langtag="word-flowlimit"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="flow_limit" placeholder="" type="text" value="{{.h.Flow.FlowLimit}}">
<span class="help-block m-b-none" langtag="word-unit"></span>: M
</div>
</div>
{{end}}
{{if eq true .allow_time_limit}}
<div class="form-group" id="time_limit">
<label class="control-label font-bold" langtag="word-timelimit"></label>
<div class="col-sm-12">
<input class="form-control" id="time_limit_input" langtag="info-unrestricted" name="time_limit" placeholder="" type="text" value="{{.h.Flow.TimeLimit}}">
<span class="help-block m-b-none" langtag="info-timelimit"></span>
</div>
</div>
{{end}}
<div class="form-group" id="auto_https">
<label class="control-label font-bold" langtag="word-autohttps"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="auto_https">
<option {{if eq false .h.AutoHttps}}selected{{end}} value="0" langtag="word-no"></option>
<option {{if eq true .h.AutoHttps}}selected{{end}} value="1" langtag="word-yes"></option>
</select>
</div>
</div>
<div class="form-group" id="auto_cors">
<label class="control-label font-bold" langtag="word-autocors"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="auto_cors">
<option {{if eq false .h.AutoCORS}}selected{{end}} value="0" langtag="word-no"></option>
<option {{if eq true .h.AutoCORS}}selected{{end}} value="1" langtag="word-yes"></option>
</select>
</div>
</div>
<div class="form-group" id="compat_mode">
<label class="control-label font-bold" langtag="word-compatmode"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="compat_mode">
<option {{if eq false .h.CompatMode}}selected{{end}} value="0" langtag="word-no"></option>
<option {{if eq true .h.CompatMode}}selected{{end}} value="1" langtag="word-yes"></option>
</select>
</div>
</div>
<div class="form-group" id="https_just_proxy">
<label class="control-label font-bold" langtag="word-httpsjustproxy"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" id="https_just_proxy_select" name="https_just_proxy">
<option {{if eq false .h.HttpsJustProxy}}selected{{end}} value="0" langtag="word-no"></option>
<option {{if eq true .h.HttpsJustProxy}}selected{{end}} value="1" langtag="word-yes"></option>
</select>
</div>
</div>
<div class="form-group" id="proxy_protocol">
<label class="control-label font-bold" langtag="word-proxyprotocol"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="proxy_protocol">
<option value="0" {{if eq .h.Target.ProxyProtocol 0}}selected{{end}} langtag="word-disable"></option>
<option value="1" {{if eq .h.Target.ProxyProtocol 1}}selected{{end}} langtag="word-proxyprotocolv1"></option>
<option value="2" {{if eq .h.Target.ProxyProtocol 2}}selected{{end}} langtag="word-proxyprotocolv2"></option>
</select>
</div>
</div>
<div class="form-group" id="auto_ssl">
<label class="control-label font-bold" langtag="word-autossl"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="auto_ssl">
<option {{if eq false .h.AutoSSL}}selected{{end}} value="0" langtag="word-no"></option>
<option {{if eq true .h.AutoSSL}}selected{{end}} value="1" langtag="word-yes"></option>
</select>
</div>
</div>
<div class="form-group" id="cert_file">
<label class="control-label font-bold" langtag="word-httpscert"></label>
<div class="col-sm-12">
<textarea class="form-control" id="pemText" langtag="info-pemtext" name="cert_file" placeholder="" rows="6" type="text">{{.h.CertFile}}</textarea>
</div>
</div>
<div class="form-group" id="key_file">
<label class="control-label font-bold" langtag="word-httpskey"></label>
<div class="col-sm-12">
<textarea class="form-control" id="pemKey" langtag="info-pemkey" name="key_file" placeholder="" rows="6" type="text">{{.h.KeyFile}}</textarea>
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-urlroute"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-unrestricted" name="location" placeholder="" type="text" value="{{.h.Location}}">
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-urlrewrite"></label>
<div class="col-sm-12">
<input class="form-control" langtag="info-urlrewrite" name="path_rewrite" placeholder="" type="text" value="{{.h.PathRewrite}}">
</div>
</div>
{{if eq true .allow_local_proxy}}
<div class="form-group" id="local_proxy">
<label class="control-label font-bold" langtag="word-proxytolocal"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="local_proxy">
<option {{if eq false .h.Target.LocalProxy}}selected{{end}} value="0" langtag="word-no"></option>
<option {{if eq true .h.Target.LocalProxy}}selected{{end}} value="1" langtag="word-yes"></option>
</select>
</div>
</div>
{{end}}
<div class="form-group" id="auth">
<label class="control-label font-bold" langtag="word-auth"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasauth" name="auth" placeholder="" rows="4">{{.auth}}</textarea>
<span class="help-block m-b-none" langtag="info-targetauth"></span>
</div>
</div>
<div class="form-group" id="target_is_https">
<label class="control-label font-bold" langtag="word-targetishttps"></label>
<div class="col-sm-12">
<select class="form-control selectpicker" name="target_is_https">
<option {{if eq false .h.TargetIsHttps}}selected{{end}} value="0">HTTP</option>
<option {{if eq true .h.TargetIsHttps}}selected{{end}} value="1">HTTPS</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label font-bold" langtag="word-target"></label>
<div class="col-sm-12">
<textarea class="form-control" langtag="info-suchasiplist" name="target" placeholder="" rows="4" type="text">{{.h.Target.TargetStr}}</textarea>
<span class="help-block m-b-none" langtag="info-targethost"></span>
</div>
</div>
<div class="form-group" id="header">
<label class="control-label font-bold" langtag="word-requestheader"></label>
<div class="col-sm-12">
<textarea class="form-control" name="header" placeholder="Cache-Control: no-cache" rows="4" type="text">{{.h.HeaderChange}}</textarea>
<span class="help-block m-b-none" langtag="info-header"></span>
</div>
</div>
<div class="form-group" id="hostchange">
<label class="control-label font-bold" langtag="word-requesthost"></label>
<div class="col-sm-12">
<input class="form-control" langtag="word-requesthost" name="hostchange" placeholder="" type="text" value="{{.h.HostChange}}" value="">
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-lg-12 col-sm-offset-2">
<button class="btn btn-secondary" onclick="goback()" type="button">
<i class="fa fa-fw fa-lg fa-window-close"></i> <span langtag="word-cancel"></span>
</button>
<button class="btn btn-success" onclick="submitform('edit', '{{.web_base_url}}/index/edithost', $('form').serializeArray())" type="button">
<i class="fa fa-fw fa-lg fa-save"></i> <span langtag="word-save"></span>
</button>
<button class="btn btn-warning" onclick="submitform('add', '{{.web_base_url}}/index/addhost', $('form').serializeArray())" type="button">
<i class="fa fa-fw fa-lg fa-check-circle"></i> <span langtag="word-add"></span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
$(function () {
if ($("#scheme_select").val() == "all" || $("#scheme_select").val() == "https") {
$("#auto_https").css("display", "block")
$("#https_just_proxy").css("display", "block")
if ($("#https_just_proxy_select").val() == "1") {
$("#auto_ssl").css("display", "none")
$("#cert_file").css("display", "none")
$("#key_file").css("display", "none")
} else {
$("#auto_ssl").css("display", "block")
$("#cert_file").css("display", "block")
$("#key_file").css("display", "block")
}
} else {
$("#auto_ssl").css("display", "none")
$("#cert_file").css("display", "none")
$("#key_file").css("display", "none")
$("#https_just_proxy").css("display", "none")
$("#auto_https").css("display", "none")
}
const pemText = document.getElementById("pemText");
const pemKey = document.getElementById("pemKey");
// 阻止浏览器默认打开文件的行为
document.addEventListener("dragover", function (event) {
event.preventDefault();
});
pemText.addEventListener("drop", function (event) {
event.preventDefault();
const file = event.dataTransfer.files[0];
const reader = new FileReader();
reader.onload = function (e) {
pemText.value = e.target.result;
console.log("Upload pem")
$('#edit_form').data('bootstrapValidator').updateStatus('pemText', 'NOT_VALIDATED');
$('#edit_form').data('bootstrapValidator').validateField('pemText');
}
reader.readAsText(file);
});
pemKey.addEventListener("drop", function (event) {
event.preventDefault();
const file = event.dataTransfer.files[0];
const reader = new FileReader();
reader.onload = function (e) {
pemKey.value = e.target.result;
$('#edit_form').data('bootstrapValidator').updateStatus('pemKey', 'NOT_VALIDATED');
$('#edit_form').data('bootstrapValidator').validateField('pemKey');
}
reader.readAsText(file);
});
$("#scheme_select").on("change", function () {
if ($("#scheme_select").val() == "all" || $("#scheme_select").val() == "https") {
$("#auto_https").css("display", "block")
$("#https_just_proxy").css("display", "block")
if ($("#https_just_proxy_select").val() == "1") {
$("#auto_ssl").css("display", "none")
$("#cert_file").css("display", "none")
$("#key_file").css("display", "none")
} else {
$("#auto_ssl").css("display", "block")
$("#cert_file").css("display", "block")
$("#key_file").css("display", "block")
}
} else {
$("#auto_ssl").css("display", "none")
$("#cert_file").css("display", "none")
$("#key_file").css("display", "none")
$("#https_just_proxy").css("display", "none")
$("#auto_https").css("display", "none")
}
})
$("#https_just_proxy_select").on("change", function () {
if ($("#https_just_proxy_select").val() == "1") {
$("#auto_ssl").css("display", "none")
$("#cert_file").css("display", "none")
$("#key_file").css("display", "none")
} else {
$("#auto_ssl").css("display", "block")
$("#cert_file").css("display", "block")
$("#key_file").css("display", "block")
}
})
});
function getClientList() {
const clientId = "{{if .h.Client.Id}}{{.h.Client.Id}}{{else}}{{.client_id}}{{end}}"; // 根据优先级选择
$("select[name='client_id']").selectpicker({
liveSearch: true,
source: {
data: function (callback, page) {
$.ajax({
method: 'POST',
url: "{{.web_base_url}}/client/list",
dataType: 'json',
data: { order: "asc", offset: 0, limit: 0 },
success: function(response) {
// 转换数据格式(必须包含 `value` 和 `text`
const results = $.map(response.rows, function(item) {
let displayText = item.Remark ? item.Id + ' · ' + item.Remark : item.Id.toString();
return {
value: item.Id, // 选项的 value
text: displayText, // 选项的显示文本
selected: item.Id == clientId // 默认选中
};
});
callback(results); // 返回给 selectpicker
},
error: function() {
callback([]); // 出错时返回空数组
}
});
},
search: function (callback, page, searchTerm) {
let data = { search: searchTerm, order: "asc", offset: 0, limit: 0 };
$.ajax({
method: 'POST',
url: "{{.web_base_url}}/client/list",
data: data,
dataType: 'json',
success: function(response) {
// 转换数据格式(必须包含 `value` 和 `text`
const results = $.map(response.rows, function(item) {
let displayText = item.Remark ? item.Id + ' · ' + item.Remark : item.Id.toString();
return {
value: item.Id, // 选项的 value
text: displayText, // 选项的显示文本
selected: item.Id == clientId // 默认选中
};
});
callback(results); // 返回给 selectpicker
},
error: function() {
callback([]); // 出错时返回空数组
}
});
}
}
});
}
function internationalized(current) {
$.fn.selectpicker.defaults = {
noneSelectedText: languages['content']['bootstrap-select']['noneSelectedText'][current],
noneResultsText: languages['content']['bootstrap-select']['noneResultsText'][current]
};
getClientList();
}
</script>

View File

@@ -0,0 +1,153 @@
<div class="row">
<div class="col-md-12">
<div class="tile">
<iframe frameborder="0" height="30px" scrolling="0"
src="https://ghbtns.com/github-btn.html?user=djylb&repo=nps&type=star&count=true&size=large"
width="160px"></iframe>
<iframe frameborder="0" height="30px" scrolling="0"
src="https://ghbtns.com/github-btn.html?user=djylb&repo=nps&type=watch&count=true&size=large&v=2"
width="160px"></iframe>
<iframe frameborder="0" height="30px" scrolling="0"
src="https://ghbtns.com/github-btn.html?user=djylb&repo=nps&type=fork&count=true&size=large"
width="158px"></iframe>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="tile">
<h3 class="tile-title">域名代理模式</h3>
<p>
<b>适用范围:</b> 小程序开发、微信公众号开发、产品演示
</p>
<p>
<b>假设场景:</b>
<li>有一个域名proxy.com有一台公网机器ip为{{.ip}}</li>
<li>两个内网开发站点127.0.0.1:81127.0.0.1:82</li>
<li>想通过a.proxy.com访问127.0.0.1:81通过b.proxy.com访问127.0.0.1:82</li>
</p>
<p><b>使用步骤:</b></p>
<ul>
<li>将*.proxy.com解析到公网服务器{{.ip}}</li>
<li>在客户端管理中创建一个客户端,记录下验证密钥</li>
<li>
点击该客户端的域名管理添加两条规则规则1、域名a.proxy.com内网目标127.0.0.1:812、域名b.proxy.com内网目标127.0.0.1:82
</li>
<li>内网客户端运行<code>
<pre>./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥</pre>
</code></pre></li>
<li>现在访问a.proxy.comb.proxy.com即可成功</li>
</ul>
<p>上文中提到公网ip{{.ip}})为系统自动识别,如果是在测试环境中请自行对应,<b>如需使用https请在配置文件中将https端口设置为443和将对应的证书文件路径添加到配置文件中
</b></p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">tcp隧道模式</h3>
<p>
<b>适用范围:</b> ssh、远程桌面等tcp连接场景
</p>
<p>
<b>假设场景:</b> 想通过访问公网服务器{{.ip}}的8001端口连接内网机器10.1.50.101的22端口实现ssh连接
</p>
<p><b>使用步骤:</b></p>
<ul>
<li>在客户端管理中创建一个客户端,记录下验证密钥</li>
<li>内网客户端运行<code>
<pre>./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥</pre>
</code></pre>
</li>
<li>
在该客户端隧道管理中添加一条tcp隧道填写监听的端口8001、内网目标ip和目标端口10.1.50.101:22选择压缩方式保存。
</li>
<li>
访问公网服务器ip{{.ip}},填写的监听端口(8001)相当于访问内网ip(10.1.50.101):目标端口(22)例如ssh -p 8001 root@{{.ip}}
</li>
</ul>
<p>上文中提到公网ip{{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动</p>
</div>
</div>
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">udp隧道模式</h3>
<p>
<b>适用范围:</b> 内网dns解析等udp连接场景
</p>
<p>
<b>假设场景:</b> 内网有一台dns10.1.50.102:53在非内网环境下想使用该dns公网服务器为{{.ip}}
</p>
<p><b>使用步骤:</b></p>
<ul>
<li>在客户端管理中创建一个客户端,记录下验证密钥</li>
<li>内网客户端运行<code>
<pre>./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥</pre>
</code></pre>
</li>
<li>
在该客户端的隧道管理中添加一条udp隧道填写监听的端口53、内网目标ip和目标端口10.1.50.102:53选择压缩方式保存。
</li>
<li>修改本机dns为{{.ip}}则相当于使用10.1.50.202作为dns服务器</li>
</ul>
<p>上文中提到公网ip{{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动</p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">socks5代理模式</h3>
<p>
<b>适用范围:</b> 在外网环境下如同使用vpn一样访问内网设备或者资源
</p>
<p>
<b>假设场景:</b> 想将公网服务器{{.ip}}的8003端口作为socks5代理达到访问内网任意设备或者资源的效果
</p>
<p><b>使用步骤:</b></p>
<ul>
<li>在客户端管理中创建一个客户端,记录下验证密钥</li>
<li>内网客户端运行<code>
<pre>./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥</pre>
</code></pre>
</li>
<li>
在该客户端隧道管理中添加一条socks5代理填写监听的端口8003验证用户名和密码自行选择建议先不填部分客户端不支持proxifer支持选择压缩方式保存。
</li>
<li>
在外网环境的本机配置socks5代理ip为公网服务器ip{{.ip}}),端口为填写的监听端口(8003),即可畅享内网了
</li>
</ul>
<p>上文中提到公网ip{{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动</p>
</div>
</div>
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">http代理模式</h3>
<p>
<b>适用范围:</b> 在外网环境下访问内网站点
</p>
<p>
<b>假设场景:</b> 想将公网服务器{{.ip}}的8004端口作为http代理访问内网网站
</p>
<p><b>使用步骤:</b></p>
<ul>
<li>在客户端管理中创建一个客户端,记录下验证密钥</li>
<li>内网客户端运行<code>
<pre>./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥</pre>
</code></pre>
</li>
<li>在该客户端隧道管理中添加一条http代理填写监听的端口8004选择压缩方式保存。</li>
<li>在外网环境的本机配置http代理ip为公网服务器ip{{.ip}}),端口为填写的监听端口(8004),即可访问了</li>
</ul>
<p>上文中提到公网ip{{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动</p>
</div>
</div>
<div class="col-md-12">
<div class="tile">
<p><b>单个客户端可以添加多条隧道或者域名解析</b></p>
</div>
</div>
</div>

View File

@@ -0,0 +1,374 @@
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="page-hostlist"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="content">
<div class="table-responsive">
<div id="toolbar">
<a class="btn btn-primary dim" href="{{.web_base_url}}/index/addhost?vkey={{.task_id}}&client_id={{.client_id}}">
<i class="fa fa-fw fa-lg fa-plus"></i>
<span langtag="word-add"></span>
</a>
</div>
<table class="table-striped table-hover" data-mobile-responsive="true" id="taskList_table"></table>
</div>
</div>
<div class="ibox-content">
<table id="table"></table>
</div>
</div>
</div>
</div>
</div>
<script>
/*bootstrap table*/
$('#table').bootstrapTable({
toolbar: "#toolbar",
method: 'post', // 服务器数据的请求方式 get or post
url: window.location, // 服务器数据的加载地址
queryParams: function (params) {
return {
"offset": params.offset,
"limit": params.limit,
"client_id": {{.client_id}},
"search": params.search,
"sort": params.sort,
"order": params.order
}
},
search: true,
contentType: "application/x-www-form-urlencoded",
striped: true, // 设置为true会有隔行变色效果
showHeader: true,
showColumns: true,
showRefresh: true,
pagination: true,//分页
sidePagination: 'server',//服务器端分页
pageNumber: 1,
pageList: [5, 10, 20, 50, 100, 'all'],
cookie: true,
cookieIdTable: 'host',
cookieStorage: 'localStorage',
detailView: true,
smartDisplay: true, // 智能显示 pagination 和 cardview 等
onExpandRow: function () {$('body').setLang ('.detail-view');},
onPostBody: function (data) { if ($(this)[0].locale != undefined ) $('body').setLang ('#table'); },
detailFormatter: function (index, row, element) {
tmp = '<b langtag="word-exportflow"></b>: <span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.ExportFlow) + '</span>&emsp;'
+ '<b langtag="word-inletflow"></b>: <span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.InletFlow) + '</span>&emsp;'
+ '<b langtag="word-crypt"></b>: <span langtag="word-' + row.Client.Cnf.Crypt + '"></span>&emsp;'
+ '<b langtag="word-compress"></b>: <span langtag="word-' + row.Client.Cnf.Compress + '"></span>&emsp;'
+ '<b langtag="word-flowlimit"></b>: <span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'flow_limit\'})">' + row.Flow.FlowLimit + 'MB</span>&emsp;'
+ '<b langtag="word-timelimit"></b>: <span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'time_limit\'})">' + row.Flow.TimeLimit + '</span>&emsp;<br/><br>'
+ '<b langtag="word-autohttps"></b>: <span onclick="submitform(\'turn\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'auto_https\'})" langtag="word-' + row.AutoHttps + '"></span>&emsp;'
+ '<b langtag="word-autocors"></b>: <span onclick="submitform(\'turn\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'auto_cors\'})" langtag="word-' + row.AutoCORS + '"></span>&emsp;'
+ '<b langtag="word-compatmode"></b>: <span onclick="submitform(\'turn\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'compat_mode\'})" langtag="word-' + row.CompatMode + '"></span>&emsp;'
+ '<b langtag="word-httpsjustproxy"></b>: <span onclick="submitform(\'turn\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'https_just_proxy\'})" langtag="word-' + row.HttpsJustProxy + '"></span>&emsp;'
+ '<b langtag="word-targetishttps"></b>: <span onclick="submitform(\'turn\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'target_is_https\'})" langtag="word-ishttps' + row.TargetIsHttps + '"></span>&emsp;<br/><br>'
+ '<b langtag="word-basicusername"></b>: ' + row.Client.Cnf.U + '&emsp;'
+ '<b langtag="word-basicpassword"></b>: ' + row.Client.Cnf.P + '&emsp;<br/><br>'
+ '<b langtag="word-auth"></b>: ' + (row.UserAuth && row.UserAuth.Content ? row.UserAuth.Content : '') + '&emsp;<br/><br>'
+ '<b langtag="word-requesthost"></b>: ' + row.HostChange + '&emsp;<br/><br>'
+ '<b langtag="word-requestheader"></b>: ' + row.HeaderChange + '&emsp;<br/><br>'
+ '<b langtag="word-autossl"></b>: <span onclick="submitform(\'turn\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'auto_ssl\'})" langtag="word-' + row.AutoSSL + '"></span>&emsp;'
if (row.Scheme != "http" && row.HttpsJustProxy == false && row.AutoSSL == false) {
return tmp + "<br/><br>"
+ '<b langtag="word-httpscert"></b>: <div onclick="oCopy(this)" style="height:60px; max-width:75vw; overflow:auto; white-space:nowrap; border:1px solid #ccc; padding:5px; box-sizing:border-box;">' + row.CertFile + '</div>&emsp;<br/>'
+ '<b langtag="word-httpskey"></b>: <div onclick="oCopy(this)" style="height:60px; max-width:75vw; overflow:auto; white-space:nowrap; border:1px solid #ccc; padding:5px; box-sizing:border-box;">' + row.KeyFile + '</div>&emsp;<br/><br>'
}
return tmp
},
// 表格的列
columns: [
{
field: 'Id', //域值
title: '<span langtag="word-id"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'Client.Id', //域值
title: '<span langtag="word-clientid"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
var text = row.Client.Remark ? (row.Client.Id + ' · ' + row.Client.Remark) : row.Client.Id
return '<span onclick="copyText(\'' + row.Client.VerifyKey + '\')">' + text + '</span>'
}
},
{
field: 'Remark', //域值
title: '<span langtag="word-remark"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'Client.VerifyKey', //域值
title: '<span langtag="word-verifykey"></span>', //标题
halign: 'center',
visible: false, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'Host', //域值
title: '<span langtag="word-host"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
hosturl = ((row.Scheme == 'http') ? 'http://' : 'https://') + row.Host + ((row.Scheme == 'http') ? ":{{.httpProxyPort}}" : ":{{.httpsProxyPort}}") + row.Location
return '<a href="' + hosturl + '" target="_blank">' + row.Host + '</a>'
}
},
{
field: 'Scheme', //域值
title: '<span langtag="word-scheme"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span langtag="word-' + value + '"></span>'
}
},
{
field: 'TargetIsHttps', //域值
title: '<span langtag="word-targettype"></span>', //标题
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
if (value) {
return '<span langtag="word-ishttps' + value + '" onclick="submitform(\'stop\', \'{{.web_base_url}}/index/stophost\', {\'id\':' + row.Id + ', \'mode\': \'target_is_https\'})"></span>'
} else {
return '<span langtag="word-ishttps' + value + '" onclick="submitform(\'start\', \'{{.web_base_url}}/index/starthost\', {\'id\':' + row.Id + ', \'mode\': \'target_is_https\'})"></span>'
}
}
},
{
field: 'Target.TargetStr', //域值
title: '<span langtag="word-target"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'Location', //域值
title: '<span langtag="word-path"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'PathRewrite', //域值
title: '<span langtag="word-pathrewrite"></span>', //标题
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'HttpsJustProxy', //域值
title: '<span langtag="word-httpsjustproxytitle"></span>', //标题
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
if (value) {
return '<span langtag="word-' + value + '" onclick="submitform(\'stop\', \'{{.web_base_url}}/index/stophost\', {\'id\':' + row.Id + ', \'mode\': \'https_just_proxy\'})"></span>'
} else {
return '<span langtag="word-' + value + '" onclick="submitform(\'start\', \'{{.web_base_url}}/index/starthost\', {\'id\':' + row.Id + ', \'mode\': \'https_just_proxy\'})"></span>'
}
}
},
{
field: 'AutoSSL', //域值
title: '<span langtag="word-autossl"></span>', //标题
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
if (value) {
return '<span langtag="word-' + value + '" onclick="submitform(\'stop\', \'{{.web_base_url}}/index/stophost\', {\'id\':' + row.Id + ', \'mode\': \'auto_ssl\'})"></span>'
} else {
return '<span langtag="word-' + value + '" onclick="submitform(\'start\', \'{{.web_base_url}}/index/starthost\', {\'id\':' + row.Id + ', \'mode\': \'auto_ssl\'})"></span>'
}
}
},
{
field: 'AutoHttps', //域值
title: '<span langtag="word-autohttpstitle"></span>', //标题
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
if (value) {
return '<span langtag="word-' + value + '" onclick="submitform(\'stop\', \'{{.web_base_url}}/index/stophost\', {\'id\':' + row.Id + ', \'mode\': \'auto_https\'})"></span>'
} else {
return '<span langtag="word-' + value + '" onclick="submitform(\'start\', \'{{.web_base_url}}/index/starthost\', {\'id\':' + row.Id + ', \'mode\': \'auto_https\'})"></span>'
}
}
},
{
field: 'AutoCORS', //域值
title: '<span langtag="word-autocorstitle"></span>', //标题
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
if (value) {
return '<span langtag="word-' + value + '" onclick="submitform(\'stop\', \'{{.web_base_url}}/index/stophost\', {\'id\':' + row.Id + ', \'mode\': \'auto_cors\'})"></span>'
} else {
return '<span langtag="word-' + value + '" onclick="submitform(\'start\', \'{{.web_base_url}}/index/starthost\', {\'id\':' + row.Id + ', \'mode\': \'auto_cors\'})"></span>'
}
}
},
{
field: 'CompatMode', //域值
title: '<span langtag="word-compatmode"></span>', //标题
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
if (value) {
return '<span langtag="word-' + value + '" onclick="submitform(\'stop\', \'{{.web_base_url}}/index/stophost\', {\'id\':' + row.Id + ', \'mode\': \'compat_mode\'})"></span>'
} else {
return '<span langtag="word-' + value + '" onclick="submitform(\'start\', \'{{.web_base_url}}/index/starthost\', {\'id\':' + row.Id + ', \'mode\': \'compat_mode\'})"></span>'
}
}
},
{
field: 'InletFlow', //域值
title: '<span langtag="word-inletflow"></span>', //内容
align: 'center',
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.InletFlow) + '</span>'
}
},
{
field: 'ExportFlow', //域值
title: '<span langtag="word-exportflow"></span>', //内容
align: 'center',
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.ExportFlow) + '</span>'
}
},
{
field: 'TotalFlow', //域值
title: '<span langtag="word-totalflow"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clearhost\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.InletFlow + row.Flow.ExportFlow) + '</span>'
}
},
{
field: 'NowConn', //域值
title: '<span langtag="word-nowconn"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'IsClose', //域值
title: '<span langtag="word-runstatus"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
if (value) {
return '<span class="badge badge-badge" langtag="word-close"></span>'
} else {
return '<span class="badge badge-primary" langtag="word-open"></span>'
}
}
},
{
field: 'Client.IsConnect', //域值
title: '<span langtag="word-clientstatus"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
hosturl = ((row.Scheme == 'http') ? 'http://' : 'https://') + row.Host + row.Location
if (value) {
return '<a href="' + hosturl + '" target="_blank"><span class="badge badge-primary" langtag="word-online"></span></a>'
} else {
return '<span class="badge badge-badge" langtag="word-offline"></span>'
}
}
},
{
field: 'option', //域值
title: '<span langtag="word-option"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
formatter: function (value, row, index) {
btn_group = '<div class="btn-group">'
if (row.IsClose) {
btn_group += "<a onclick=\"submitform('start', '{{.web_base_url}}/index/starthost', {\'id\':" + row.Id
btn_group += '})" class="btn btn-outline btn-primary"><i class="fa fa-play"></i></a>'
} else {
btn_group += "<a onclick=\"submitform('stop', '{{.web_base_url}}/index/stophost', {'id':" + row.Id
btn_group += '})" class="btn btn-outline btn-warning"><i class="fa fa-pause"></i></a>'
}
btn_group += "<a onclick=\"submitform('delete', '{{.web_base_url}}/index/delhost', {'id':" + row.Id
btn_group += '})" class="btn btn-outline btn-danger"><i class="fa fa-trash"></i></a>'
btn_group += '<a href="{{.web_base_url}}/index/edithost?id=' + row.Id
btn_group += '" class="btn btn-outline btn-success"><i class="fa fa-edit"></i></a></div>'
return btn_group
}
}
]
});
</script>

View File

@@ -0,0 +1,936 @@
<div class="wrapper wrapper-content">
{{if eq false .isAdmin}}
<div class="row">
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-tcpports"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{or .tcp_p "0"}} / {{or .tls_p "0"}}</h1>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-kcpport"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{or .kcp_p "0"}}</h1>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-wsports"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{or .ws_p "0"}} / {{or .wss_p "0"}}</h1>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-wspath"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{or .ws_path "/ws"}}</h1>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-serverip"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{.ip}}</h1>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-serverversion"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{.data.version}}</h1>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-minsupportversion"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{.data.minVersion}}</h1>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-iprestriction"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins" langtag="word-{{or .data.ipLimit "false"}}"></h1>
</div>
</div>
</div>
</div>
{{end}}
{{if eq true .isAdmin}}
<div class="row">
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-totalclients"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins" id="stat-clientCount">{{.data.clientCount}}</h1>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
{{/*<span class="label label-primary pull-right">今日</span>*/}}
<h5 langtag="word-onlineclients"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins" id="stat-clientOnlineCount">{{.data.clientOnlineCount}}</h1>
{{/*<div class="stat-percent font-bold text-navy">44% <i class="fa fa-level-up"></i></div>*/}}
{{/*<small>新访客</small>*/}}
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-tcpconnections"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins" id="stat-tcpCount">{{.data.tcpCount}}</h1>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-uptime"></h5>
</div>
<div class="ibox-content">
<h1 class="no-margins" id="stat-upTime">{{or .data.upTime ""}}</h1>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-configurationinformation"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content no-padding">
<ul class="list-group">
<li class="list-group-item">
<div class="row">
<div class="col-sm-4">
<strong langtag="word-bridgingmode"></strong>
</div>
<div class="col-sm-8 text-right">
<strong>{{if index . "tcp_p"}}TCP:{{.tcp_p}} {{end}}{{if index . "kcp_p"}}KCP:{{.kcp_p}} {{end}}{{if index . "tls_p"}}TLS:{{.tls_p}} {{end}}{{if index . "ws_p"}}WS:{{.ws_p}} {{end}}{{if index . "wss_p"}}WSS:{{.wss_p}} {{end}}{{if index . "ws_path"}}Path:{{.ws_path}}{{end}}</strong>
</div>
</div>
</li>
<li class="list-group-item">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-httpports"></strong>
</div>
<div class="col-sm-6 text-right">
<strong>{{or .data.httpProxyPort "0"}} / {{or .data.httpsProxyPort "0"}}</strong>
</div>
</div>
</li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-iprestriction"></strong>
</div>
<div class="col-sm-6 text-right">
<strong langtag="word-{{or .data.ipLimit "false"}}"></strong>
</div>
</div>
</li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-trafficdatapersistence"></strong>
</div>
<div class="col-sm-6 text-right">
<strong>{{.data.flowStoreInterval}}</strong>
</div>
</div>
</li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-loglevel"></strong>
</div>
<div class="col-sm-6 text-right">
<strong>{{.data.logLevel}}</strong>
</div>
</div>
</li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-4">
<strong langtag="word-p2paddr"></strong>
</div>
<div class="col-sm-8 text-right">
<strong>{{.data.p2pAddr}}</strong>
</div>
</div>
</li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-2">
<strong langtag="word-serverip"></strong>
</div>
<div class="col-sm-10 text-right">
<strong>{{.data.p2pIp}} | {{.data.serverIpv4}} | {{.data.serverIpv6}}</strong>
</div>
</div>
</li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-serverversion"></strong>
</div>
<div class="col-sm-6 text-right">
<strong>{{.data.version}} ({{.data.minVersion}})</strong>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-systeminformation"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content no-padding">
<ul class="list-group">
<li class="list-group-item">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-cpu"></strong>
</div>
<div class="col-sm-6 text-right">
<strong id="overview_cpu"></strong>
</div>
</div>
<div class="progress progress-small">
<div class="progress-bar" id="overview_cpu_bar"></div>
</div>
</li>
<li class="list-group-item">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-memory"></strong>
</div>
<div class="col-sm-6 text-right">
<strong id="overview_memory"></strong>
</div>
</div>
<div class="progress progress-small">
<div class="progress-bar" id="overview_memory_bar"></div>
</div>
</li>
<li class="list-group-item">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-load"></strong>
</div>
<div class="col-sm-6 text-right">
<strong id="overview_load"></strong>
</div>
</div>
</li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-tcpconnections_established"></strong>
</div>
<div class="col-sm-6 text-right">
<strong id="overview_tcp"></strong>
</div>
</div>
</li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-udpconnections_established"></strong>
</div>
<div class="col-sm-6 text-right">
<strong id="overview_udp"></strong>
</div>
</div>
</li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-outbandwidth"></strong>
</div>
<div class="col-sm-6 text-right">
<strong id="overview_send"></strong>
</div>
</div>
</li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-6">
<strong langtag="word-inbandwidth"></strong>
</div>
<div class="col-sm-6 text-right">
<strong id="overview_recv"></strong>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
{{end}}
</div>
{{if eq true .isAdmin}}
{{if eq true .system_info_display}}
<div class="row">
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-load"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div id="load" style="height: 300px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-cpu"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div id="cpu" style="height: 300px"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-memory"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div id="memory" style="height: 300px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-connections_established"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div id="connections" style="height: 300px"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-bandwidth"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div id="bandwidth" style="height: 300px"></div>
</div>
</div>
</div>
</div>
{{end}}
<div class="row">
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-trafficstatistics"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div id="flow" style="height: 400px;"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5 langtag="word-type"></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div id="counts" style="height:400px;"></div>
</div>
</div>
</div>
</div>
{{end}}
</div>
{{if eq true .isAdmin}}
<script>
$("#overview_cpu").text("{{.data.cpu}}%")
$("#overview_cpu_bar").width("{{.data.cpu}}%")
$("#overview_memory").text("{{.data.virtual_mem}}%")
$("#overview_memory_bar").width("{{.data.virtual_mem}}%")
$.each( JSON.parse({{.data.load}}), function(i, value) { $("#overview_load").append('&emsp;' + value) });
$("#overview_tcp").text("{{.data.tcp}}")
$("#overview_udp").text("{{.data.udp}}")
$("#overview_send").text(changeunit({{.data.io_send}}) + "/s")
$("#overview_recv").text(changeunit({{.data.io_recv}}) + "/s")
const chartTextColor = getComputedStyle(document.documentElement)
.getPropertyValue('--text').trim();
const chartBgColor = getComputedStyle(document.documentElement)
.getPropertyValue('--bg').trim();
echarts.registerTheme('nps-theme', {
textStyle: {
color: chartTextColor,
textBorderColor: chartBgColor,
textBorderWidth: 0
},
axisPointer: { label: { color: chartTextColor, textBorderColor: chartBgColor } },
tooltip: { textStyle: { color: chartTextColor, textBorderColor: chartBgColor } },
legend: { textStyle: { color: chartTextColor, textBorderColor: chartBgColor } },
xAxis: { axisLabel: { color: chartTextColor, textBorderColor: chartBgColor } },
yAxis: { axisLabel: { color: chartTextColor, textBorderColor: chartBgColor } },
series: [{
label: {
color: chartTextColor,
textBorderColor: chartBgColor,
textBorderWidth: 0
},
emphasis: {
label: {
color: chartTextColor,
textBorderColor: chartBgColor,
textBorderWidth: 0
}
}
}],
pie: {
label: {
color: chartTextColor,
textBorderColor: chartBgColor,
textBorderWidth: 0
},
labelLine: {
lineStyle: {
color: chartTextColor
}
}
}
});
chartdatas['load'] = {
tooltip: {
trigger: 'axis',
formatter: function (params) {
var str = params[0].axisValue + '<br/>';
for (i in params){
str += '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' + params[i].color + ';"></span>' + params[i].seriesName +': '+ params[i].data +'</br>';
}
return str;
}
},
grid: {
left: '3%',
right: '3%',
top: '5%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['{{.data.sys1.time}}', '{{.data.sys2.time}}', '{{.data.sys3.time}}', '{{.data.sys4.time}}', '{{.data.sys5.time}}', '{{.data.sys6.time}}', '{{.data.sys7.time}}', '{{.data.sys8.time}}', '{{.data.sys9.time}}', '{{.data.sys10.time}}']
},
yAxis: {
type: 'value'
},
series: [
{
name: '',
type: 'line',
stack: 'load1',
smooth: true,
data: [{{.data.sys1.load1}}, {{.data.sys2.load1}}, {{.data.sys3.load1}}, {{.data.sys4.load1}}, {{.data.sys5.load1}}, {{.data.sys6.load1}}, {{.data.sys7.load1}}, {{.data.sys8.load1}}, {{.data.sys9.load1}}, {{.data.sys10.load1}}]
},
{
name: '',
type: 'line',
stack: 'load5',
smooth: true,
data: [{{.data.sys1.load5}}, {{.data.sys2.load5}}, {{.data.sys3.load5}}, {{.data.sys4.load5}}, {{.data.sys5.load5}}, {{.data.sys6.load5}}, {{.data.sys7.load5}}, {{.data.sys8.load5}}, {{.data.sys9.load5}}, {{.data.sys10.load5}}]
},
{
name: '',
type: 'line',
stack: 'load15',
smooth: true,
data: [{{.data.sys1.load15}}, {{.data.sys2.load15}}, {{.data.sys3.load15}}, {{.data.sys4.load15}}, {{.data.sys5.load15}}, {{.data.sys6.load15}}, {{.data.sys7.load15}}, {{.data.sys8.load15}}, {{.data.sys9.load15}}, {{.data.sys10.load15}}]
}
]
};
chartdatas['cpu'] = {
tooltip: {
trigger: 'axis',
formatter: function (params) {
var str = params[0].axisValue + '<br/>';
for (i in params){
str += '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' + params[i].color + ';"></span>' + params[i].seriesName +': '+ params[i].data +'%</br>';
}
return str;
}
},
grid: {
left: '3%',
right: '3%',
top: '5%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['{{.data.sys1.time}}', '{{.data.sys2.time}}', '{{.data.sys3.time}}', '{{.data.sys4.time}}', '{{.data.sys5.time}}', '{{.data.sys6.time}}', '{{.data.sys7.time}}', '{{.data.sys8.time}}', '{{.data.sys9.time}}', '{{.data.sys10.time}}']
},
yAxis: {
type: 'value',
axisLabel: {
show: true,
interval: 'auto',
formatter: '{value} %'
}
},
series: [
{
name: '',
type: 'line',
stack: 'cpu',
smooth: true,
data: [{{.data.sys1.cpu}}, {{.data.sys2.cpu}}, {{.data.sys3.cpu}}, {{.data.sys4.cpu}}, {{.data.sys5.cpu}}, {{.data.sys6.cpu}}, {{.data.sys7.cpu}}, {{.data.sys8.cpu}}, {{.data.sys9.cpu}}, {{.data.sys10.cpu}}]
}
]
};
chartdatas['memory'] = {
tooltip: {
trigger: 'axis',
formatter: function (params) {
var str = params[0].axisValue + '<br/>';
for (i in params){
str += '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' + params[i].color + ';"></span>' + params[i].seriesName +': '+ params[i].data +'MB</br>';
}
return str;
}
},
grid: {
left: '3%',
right: '3%',
top: '5%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['{{.data.sys1.time}}', '{{.data.sys2.time}}', '{{.data.sys3.time}}', '{{.data.sys4.time}}', '{{.data.sys5.time}}', '{{.data.sys6.time}}', '{{.data.sys7.time}}', '{{.data.sys8.time}}', '{{.data.sys9.time}}', '{{.data.sys10.time}}']
},
yAxis: {
type: 'value',
axisLabel: {
show: true,
interval: 'auto',
formatter: '{value} MB'
}
},
series: [
{
name: '',
type: 'line',
stack: 'virtual_mem',
smooth: true,
data: [{{.data.sys1.virtual_mem}}, {{.data.sys2.virtual_mem}}, {{.data.sys3.virtual_mem}}, {{.data.sys4.virtual_mem}}, {{.data.sys5.virtual_mem}}, {{.data.sys6.virtual_mem}}, {{.data.sys7.virtual_mem}}, {{.data.sys8.virtual_mem}}, {{.data.sys9.virtual_mem}}, {{.data.sys10.virtual_mem}}]
},
{
name: '',
type: 'line',
stack: 'swap_mem',
smooth: true,
data: [{{.data.sys1.swap_mem}}, {{.data.sys2.swap_mem}}, {{.data.sys3.swap_mem}}, {{.data.sys4.swap_mem}}, {{.data.sys5.swap_mem}}, {{.data.sys6.swap_mem}}, {{.data.sys7.swap_mem}}, {{.data.sys8.swap_mem}}, {{.data.sys9.swap_mem}}, {{.data.sys10.swap_mem}}]
}
]
};
chartdatas['connections'] = {
tooltip: {
trigger: 'axis'
},
grid: {
left: '3%',
right: '3%',
top: '5%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['{{.data.sys1.time}}', '{{.data.sys2.time}}', '{{.data.sys3.time}}', '{{.data.sys4.time}}', '{{.data.sys5.time}}', '{{.data.sys6.time}}', '{{.data.sys7.time}}', '{{.data.sys8.time}}', '{{.data.sys9.time}}', '{{.data.sys10.time}}']
},
yAxis: {
type: 'value'
},
series: [
{
name: '',
type: 'line',
stack: 'tcp',
smooth: true,
data: [{{.data.sys1.tcp}}, {{.data.sys2.tcp}}, {{.data.sys3.tcp}}, {{.data.sys4.tcp}}, {{.data.sys5.tcp}}, {{.data.sys6.tcp}}, {{.data.sys7.tcp}}, {{.data.sys8.tcp}}, {{.data.sys9.tcp}}, {{.data.sys10.tcp}}]
},
{
name: '',
type: 'line',
stack: 'udp',
smooth: true,
data: [{{.data.sys1.udp}}, {{.data.sys2.udp}}, {{.data.sys3.udp}}, {{.data.sys4.udp}}, {{.data.sys5.udp}}, {{.data.sys6.udp}}, {{.data.sys7.udp}}, {{.data.sys8.udp}}, {{.data.sys9.udp}}, {{.data.sys10.udp}}]
}
]
};
chartdatas['bandwidth'] = {
tooltip: {
trigger: 'axis',
formatter: function (params) {
var str = params[0].axisValue + '<br/>';
for (i in params){
str += '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' + params[i].color + ';"></span>' + params[i].seriesName +': '+ changeunit(params[i].data) +'/s</br>';
}
return str;
}
},
grid: {
left: '3%',
right: '3%',
top: '5%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['{{.data.sys1.time}}', '{{.data.sys2.time}}', '{{.data.sys3.time}}', '{{.data.sys4.time}}', '{{.data.sys5.time}}', '{{.data.sys6.time}}', '{{.data.sys7.time}}', '{{.data.sys8.time}}', '{{.data.sys9.time}}', '{{.data.sys10.time}}']
},
yAxis: {
type: 'value',
axisLabel: {
show: true,
interval: 'auto',
formatter: function (params){
return changeunit (params);
}
}
},
series: [
{
name: '',
type: 'line',
stack: 'in',
smooth: true,
data: [{{.data.sys1.io_recv}}, {{.data.sys2.io_recv}}, {{.data.sys3.io_recv}}, {{.data.sys4.io_recv}}, {{.data.sys5.io_recv}}, {{.data.sys6.io_recv}}, {{.data.sys7.io_recv}}, {{.data.sys8.io_recv}}, {{.data.sys9.io_recv}}, {{.data.sys10.io_recv}}]
},
{
name: '',
type: 'line',
stack: 'out',
smooth: true,
data: [{{.data.sys1.io_send}}, {{.data.sys2.io_send}}, {{.data.sys3.io_send}}, {{.data.sys4.io_send}}, {{.data.sys5.io_send}}, {{.data.sys6.io_send}}, {{.data.sys7.io_send}}, {{.data.sys8.io_send}}, {{.data.sys9.io_send}}, {{.data.sys10.io_send}}]
}
]
};
chartdatas['flow'] = {
tooltip: {
trigger: 'item',
formatter: function (p) {
return p.seriesName + '<br>' + p.name + ':' + changeunit(p.data.value);
}
},
legend: {
orient: 'vertical',
left: 'left',
data: ['', '']
},
series: [
{
name: '',
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [
{
name: '',
value:{{.data.inletFlowCount}}
},
{
name: '',
value:{{.data.exportFlowCount}}
}
],
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
chartdatas['counts'] = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
data: ['', '', '', '', '', '', '']
},
series: [
{
name: '',
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [
{
name: '',
value:{{.data.hostCount}}
},
{
name: '',
value:{{.data.tcpC}}
},
{
name: '',
value:{{.data.udpCount}}
},
{
name: '',
value:{{.data.httpProxyCount}}
},
{
name: '',
value:{{.data.socks5Count}}
},
{
name: '',
value:{{.data.secretCount}}
},
{
name: '',
value:{{.data.p2pCount}}
}
],
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
window.charts = window.charts || {};
Object.keys(chartdatas).forEach(key => {
const dom = document.getElementById(key);
if (dom) {
window.charts[key] = echarts.init(dom, 'nps-theme');
window.charts[key].setOption(chartdatas[key]);
}
});
window.addEventListener('resize', () => {
for (var key in charts) {
charts[key].resize();
}
});
let upTimeSeconds = 0;
function formatUpTime(secs) {
const d = Math.floor(secs / 86400);
secs %= 86400;
const h = Math.floor(secs / 3600);
secs %= 3600;
const m = Math.floor(secs / 60);
const s = secs % 60;
const parts = [];
if (d) parts.push(d + 'd');
if (h) parts.push(h + 'h');
if (m) parts.push(m + 'm');
parts.push(s + 's');
return parts.join(' ');
}
function ensureChart(key) {
if (!charts[key]) {
const dom = document.getElementById(key);
if (dom) {
charts[key] = echarts.init(dom, 'nps-theme');
}
}
}
function refreshStats() {
$.getJSON('{{.web_base_url}}/index/stats', function(res) {
if (res.code !== 1) return;
const d = res.data;
$('#stat-clientCount').text(d.clientCount);
$('#stat-clientOnlineCount').text(d.clientOnlineCount);
$('#stat-tcpCount').text(d.tcpCount);
const nowSecs = Math.floor(Date.now() / 1000);
upTimeSeconds = nowSecs - d.startTime;
$('#overview_cpu').text(d.cpu + '%');
$('#overview_cpu_bar').width(d.cpu + '%');
$('#overview_memory').text(d.virtual_mem + '%');
$('#overview_memory_bar').width(d.virtual_mem + '%');
const loadObj = JSON.parse(d.load);
$('#overview_load')
.empty()
.append([loadObj.load1, loadObj.load5, loadObj.load15]
.map(v => '&emsp;'+v).join(''));
$('#overview_tcp').text(d.tcp);
$('#overview_udp').text(d.udp);
$('#overview_send').text(changeunit(d.io_send) + '/s');
$('#overview_recv').text(changeunit(d.io_recv) + '/s');
});
}
$(function() {
refreshStats();
setInterval(refreshStats, 3000);
setInterval(function() {
upTimeSeconds++;
$('#stat-upTime').text(formatUpTime(upTimeSeconds));
}, 1000);
});
</script>
{{end}}

View File

@@ -0,0 +1,374 @@
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5><span id="langtag"></span><span></span></h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="content">
<div class="table-responsive">
<div id="toolbar">
{{ if or (not .type) (ne .type "file") }}
<a class="btn btn-primary dim" href="{{.web_base_url}}/index/add?type={{.type}}&client_id={{.client_id}}">
<i class="fa fa-fw fa-lg fa-plus"></i>
<span langtag="word-add"></span>
</a>
{{ end }}
</div>
<table class="table-striped table-hover" data-mobile-responsive="true" id="taskList_table"></table>
</div>
</div>
<div class="ibox-content">
<table id="table"></table>
</div>
</div>
</div>
</div>
</div>
<script>
name = '{{.name}}:'.replace(/\s*/g, "")
$('#langtag').attr('langtag', 'page-list' + name.replace(/:.*/, "")).next().text(name.split(":")[1])
/*bootstrap table*/
$('#table').bootstrapTable({
toolbar: "#toolbar",
method: 'post', // 服务器数据的请求方式 get or post
url: "{{.web_base_url}}/index/gettunnel", // 服务器数据的加载地址
queryParams: function (params) {
return {
"offset": params.offset,
"limit": params.limit,
"type": {{.type}},
"client_id": {{.client_id}},
"search": params.search,
"sort": params.sort,
"order": params.order
}
},
search: true,
contentType: "application/x-www-form-urlencoded",
striped: true, // 设置为true会有隔行变色效果
showHeader: true,
showColumns: true,
showRefresh: true,
pagination: true, //分页
sidePagination: 'server', //服务器端分页
pageNumber: 1,
pageList: [5, 10, 20, 50, 100, 'all'],
cookie: true,
cookieIdTable: '{{.type}}',
cookieStorage: 'localStorage',
detailView: true,
smartDisplay: true, // 智能显示 pagination 和 cardview 等
onExpandRow: function () {$('body').setLang ('.detail-view');},
onLoadSuccess:function (data) {$('body').setLang ('.detail-view');},
onPostBody: function (data) { if ($(this)[0].locale != undefined ) $('body').setLang ('#table'); },
detailFormatter: function (index, row, element) {
tmp = '<b langtag="word-exportflow"></b>: <span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clear\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.ExportFlow) + '</span>&emsp;'
+ '<b langtag="word-inletflow"></b>: <span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clear\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.InletFlow) + '</span>&emsp;'
+ '<b langtag="word-crypt"></b>: <span langtag="word-' + row.Client.Cnf.Crypt + '"></span>&emsp;'
+ '<b langtag="word-compress"></b>: <span langtag="word-' + row.Client.Cnf.Compress + '"></span>&emsp;'
+ '<b langtag="word-ishttp"></b>: <span langtag="word-' + row.IsHttp + '"></span>&emsp;<br/><br>'
+ '<b langtag="word-flowlimit"></b>: <span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clear\', {\'id\':' + row.Id + ', \'mode\': \'flow_limit\'})">' + row.Flow.FlowLimit + 'MB</span>&emsp;'
+ '<b langtag="word-timelimit"></b>: <span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clear\', {\'id\':' + row.Id + ', \'mode\': \'time_limit\'})">' + row.Flow.TimeLimit + '</span>&emsp;'
if (row.Mode == "mixProxy") {
return tmp + "<br/><br>"
+ '<b langtag="word-basicusername"></b>: ' + row.Client.Cnf.U + '&emsp;'
+ '<b langtag="word-basicpassword"></b>: ' + row.Client.Cnf.P + '&emsp;<br/><br>'
+ '<b langtag="word-auth"></b>: ' + (row.UserAuth && row.UserAuth.Content ? row.UserAuth.Content : '') + '&emsp;<br/><br>'
}
if (row.Mode == "file") {
return tmp + "<br/><br>"
+ '<b langtag="word-localpath"></b>: ' + row.LocalPath + '&emsp;'
+ '<b langtag="word-stripprefix"></b>: ' + row.StripPre + '&emsp;'
}
if (row.Mode == "p2p") {
return tmp + "<br/><br>"
+ '<b langtag="word-commandaccessp2p"></b>: ' + '<code onclick="oCopy(this)">' + "./npc{{.win}} -server={{.addr}} -vkey=" + row.Client.VerifyKey
+ " -type=" + {{.bridgeType}} + " -password=" + row.Password + " -target=" + row.Target.TargetStr + "</code>" + "<br/><br>"
+ '<b langtag="word-commandaccessp2ps"></b>: ' + '<code onclick="oCopy(this)">' + "./npc{{.win}} -server={{.addr}} -vkey=" + row.Client.VerifyKey
+ " -type=" + {{.bridgeType}} + " -password=" + row.Password + " -local_type=p2ps" + "</code>" + "<br/><br>"
+ '<b langtag="word-commandaccessp2pt"></b>: ' + '<code onclick="oCopy(this)">' + "./npc{{.win}} -server={{.addr}} -vkey=" + row.Client.VerifyKey
+ " -type=" + {{.bridgeType}} + " -password=" + row.Password + " -local_type=p2pt" + "</code>"
}
if (row.Mode == "secret") {
return tmp + "<br/><br>" + '<b langtag="word-commandaccess"></b>: ' + '<code onclick="oCopy(this)">' + "./npc{{.win}} -server={{.addr}} -vkey=" + row.Client.VerifyKey
+ " -type=" + {{.bridgeType}} + " -password=" + row.Password + " -local_type=secret" + "</code>"
}
return tmp
},
// 表格的列
columns: [
{
field: 'Id', //域值
title: '<span langtag="word-id"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'Client.Id', //域值
title: '<span langtag="word-clientid"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
var text = row.Client.Remark ? (row.Client.Id + ' · ' + row.Client.Remark) : row.Client.Id
return '<span onclick="copyText(\'' + row.Client.VerifyKey + '\')">' + text + '</span>'
}
},
{
field: 'Remark', //域值
title: '<span langtag="word-remark"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'Client.VerifyKey', //域值
title: '<span langtag="word-verifykey"></span>', //标题
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{{ if not .type }}
{
field: 'Mode', //域值
title: '<span langtag="word-scheme"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span langtag="scheme-' + value + '"></span>'
}
},
{{ end }}
{
field: 'Port', //域值
title: '<span langtag="word-port"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{{ if or (not .type) (eq .type "tcp") (eq .type "udp") (eq .type "secret") (eq .type "p2p") }}
{
field: 'Target.TargetStr', //域值
title: '<span langtag="word-target"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{{ end }}
{{ if or (not .type) (eq .type "secret") (eq .type "p2p") }}
{
field: 'Password', //域值
title: '<span langtag="word-identificationkey"></span>', //标题
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{{ end }}
{{ if and .type (eq .type "file") }}
{
field: 'LocalPath', //域值
title: '<span langtag="word-localpath"></span>', //标题
halign: 'center',
visible: true //false表示不显示
},
{
field: 'StripPre', //域值
title: '<span langtag="word-stripprefix"></span>', //标题
halign: 'center',
visible: true //false表示不显示
},
{{ end }}
{{ if and .type (eq .type "mixProxy") }}
{
field: 'HttpProxy', //域值
title: '<span langtag="word-httpproxy"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
btn_group = '<div class="btn-group">'
if (value) {
btn_group += '<a onclick="submitform(\'stop\', \'{{.web_base_url}}/index/stop\', {\'id\':' + row.Id + ', \'mode\': \'http\'})" class="btn btn-outline btn-warning"><i class="fa fa-pause"></i></a>'
} else {
btn_group += '<a onclick="submitform(\'start\', \'{{.web_base_url}}/index/start\', {\'id\':' + row.Id + ', \'mode\': \'http\'})" class="btn btn-outline btn-primary"><i class="fa fa-play"></i></a>'
}
btn_group += '</div>'
return btn_group
}
},
{
field: 'Socks5Proxy', //域值
title: '<span langtag="word-socks5proxy"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
btn_group = '<div class="btn-group">'
if (value) {
btn_group += '<a onclick="submitform(\'stop\', \'{{.web_base_url}}/index/stop\', {\'id\':' + row.Id + ', \'mode\': \'socks5\'})" class="btn btn-outline btn-warning"><i class="fa fa-pause"></i></a>'
} else {
btn_group += '<a onclick="submitform(\'start\', \'{{.web_base_url}}/index/start\', {\'id\':' + row.Id + ', \'mode\': \'socks5\'})" class="btn btn-outline btn-primary"><i class="fa fa-play"></i></a>'
}
btn_group += '</div>'
return btn_group
}
},
{{ end }}
{
field: 'InletFlow', //域值
title: '<span langtag="word-inletflow"></span>', //内容
align: 'center',
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clear\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.InletFlow) + '</span>'
}
},
{
field: 'ExportFlow', //域值
title: '<span langtag="word-exportflow"></span>', //内容
align: 'center',
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clear\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.ExportFlow) + '</span>'
}
},
{
field: 'TotalFlow', //域值
title: '<span langtag="word-totalflow"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="submitform(\'clear\', \'{{.web_base_url}}/index/clear\', {\'id\':' + row.Id + ', \'mode\': \'flow\'})">' + changeunit(row.Flow.InletFlow + row.Flow.ExportFlow) + '</span>'
}
},
{
field: 'NowConn', //域值
title: '<span langtag="word-nowconn"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
return '<span onclick="oCopy(this)">' + value + '</span>'
}
},
{
field: 'Status', //域值
title: '<span langtag="word-status"></span>', //内容
align: 'center',
halign: 'center',
visible: false, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
if (value) {
return '<span class="badge badge-primary" langtag="word-open"></span>'
} else {
return '<span class="badge badge-badge" langtag="word-close"></span>'
}
}
},
{
field: 'RunStatus', //域值
title: '<span langtag="word-runstatus"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
if (value) {
if (row.Mode && row.Mode == "tcp") {
return '<a href="' + 'http://{{.ip}}:' + row.Port + '" target="_blank"><span class="badge badge-primary" langtag="word-open"></span></a>'
} else {
return '<span class="badge badge-primary" langtag="word-open"></span>'
}
} else {
return '<span class="badge badge-badge" langtag="word-close"></span>'
}
}
},
{
field: 'Client.IsConnect', //域值
title: '<span langtag="word-clientstatus"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
sortable: true, //启用排序
formatter: function (value, row, index) {
if (value) {
if (row.Mode && row.Mode == "tcp") {
return '<a href="' + (row.IsHttp ? 'http://' : 'https://') + '{{.ip}}:' + row.Port + '" target="_blank"><span class="badge badge-primary" langtag="word-online"></span></a>'
} else {
return '<span class="badge badge-primary" langtag="word-online"></span>'
}
} else {
return '<span class="badge badge-badge" langtag="word-offline"></span>'
}
}
},
{
field: 'option', //域值
title: '<span langtag="word-option"></span>', //内容
align: 'center',
halign: 'center',
visible: true, //false表示不显示
formatter: function (value, row, index) {
btn_group = '<div class="btn-group">'
if (row.Status) {
btn_group += "<a onclick=\"submitform('stop', '{{.web_base_url}}/index/stop', {'id':" + row.Id
btn_group += '})" class="btn btn-outline btn-warning"><i class="fa fa-pause"></i></a>'
} else {
btn_group += "<a onclick=\"submitform('start', '{{.web_base_url}}/index/start', {\'id\':" + row.Id
btn_group += '})" class="btn btn-outline btn-primary"><i class="fa fa-play"></i></a>'
}
btn_group += "<a onclick=\"submitform('delete', '{{.web_base_url}}/index/del', {'id':" + row.Id
btn_group += '})" class="btn btn-outline btn-danger"><i class="fa fa-trash"></i></a>'
if (row.Mode && row.Mode !== "file") {
btn_group += '<a href="{{.web_base_url}}/index/edit?id=' + row.Id + '" class="btn btn-outline btn-success"><i class="fa fa-edit"></i></a>'
}
btn_group += '</div>'
return btn_group
}
}
]
});
</script>

View File

@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title langtag="title-login"></title>
<!-- Mainly scripts -->
<!-- Latest compiled and minified CSS -->
<link href="{{.web_base_url}}/static/css/fontawesome.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/solid.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/bootstrap.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/style.css?v={{.version}}" rel="stylesheet">
<!-- Latest compiled and minified JavaScript -->
<script src="{{.web_base_url}}/static/js/jquery-3.7.1.min.js"></script>
<script src="{{.web_base_url}}/static/js/bootstrap.min.js"></script>
<script src="{{.web_base_url}}/static/js/jsencrypt.min.js"></script>
<!-- Latest compiled and minified Locales -->
<script src="{{.web_base_url}}/static/js/language.js?v={{.version}}" type="text/javascript"></script>
</head>
<body class="login-page">
<nav class="navbar navbar-static-top navbar-right navbar-login">
<div class="ml-auto d-flex align-items-center" style="gap: 0.5rem;">
<button id="theme-toggle" class="btn btn-outline-secondary" onclick="toggleTheme()">
<i class="fa fa-moon"></i>
</button>
<span class="btn-group dropdown">
<button id="languagemenu" class="btn btn-outline-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-globe-asia fa-lg"></i>
<span></span>
</button>
<ul class="dropdown-menu"></ul>
</span>
</div>
</nav>
<div class="login-card">
<div class="login-header">
<img src="{{.web_base_url}}/static/img/nps.svg?v={{.version}}" alt="Logo" style="width: 32px; height: 32px; margin-right: 20px !important;">
<div class="navbar-brand" style="font-weight: lighter; font-size: 2.0rem !important"><h1 langtag="title-login"></h1></div>
</div>
<div class="login-body">
<form class="m-t" onsubmit="return false;">
<div class="form-group position-relative">
<i class="fas fa-user-circle input-icon"></i>
<input class="form-control" langtag="word-username" name="username" placeholder="username" required="" type="text">
</div>
<div class="form-group position-relative">
<i class="fas fa-key input-icon"></i>
<input class="form-control" langtag="word-password" name="password" placeholder="password" required="" type="password">
</div>
{{if eq true .captcha_open}}
<div class="form-group captcha-group position-relative">
<div class="flex-fill">
<i class="fas fa-shield-alt input-icon"></i>
<input class="form-control" langtag="word-captcha" name="captcha" placeholder="captcha" required="">
</div>
<div class="captcha-container">
{{create_captcha}}
</div>
</div>
{{end}}
<button class="btn btn-primary block full-width m-b" langtag="word-login" onclick="if (this.form.reportValidity()) login()" type="submit"></button>
{{if eq true .register_allow}}
<p class="text-muted text-center">
<small langtag="info-noaccount"></small>
<small><a href="{{.web_base_url}}/login/register" langtag="word-register"></a></small>
</p>
{{end}}
</form>
</div>
<div class="footer">
<div class="float-right">
<span langtag="word-readmore"></span>
<strong><a href="https://github.com/djylb/nps" langtag="word-go"></a></strong>
</div>
<div><strong langtag="word-copyright"></strong> <span langtag="application"></span> &copy; 2018-{{.year}}</div>
</div>
<script type="text/javascript">
window.nps = {
web_base_url: "{{.web_base_url}}",
version: "{{.version}}",
publicKey: `{{.public_key}}`,
loginNonce: "{{.login_nonce}}"
};
function encryptWithRSA(plain) {
const crypt = new JSEncrypt();
crypt.setPublicKey(window.nps.publicKey);
return crypt.encrypt(plain);
}
function login() {
const data = {};
$("form").serializeArray().forEach(item => {
data[item.name] = item.value;
});
const ts = Date.now();
const payload = JSON.stringify({
n: window.nps.loginNonce,
t: ts,
p: data.password
});
data.password = encryptWithRSA(payload);
$.ajax({
type: "POST",
url: window.nps.web_base_url + "/login/verify",
data: data,
success: function (res) {
if (res.status) {
showMsg(langreply(res.msg), 'success', 1000, function() {
window.location.href = window.nps.web_base_url + "/index/index";
});
} else {
showMsg(langreply(res.msg), 'error', 3000, function() {
if (res.nonce) {
window.nps.loginNonce = res.nonce;
}
if (res.cert) {
window.nps.publicKey = res.cert;
}
{{if eq true .captcha_open}}
var $img = $('.captcha-img');
if ($img.length) {
$img.trigger('click');
}
$('input[name="captcha"]').val('');
{{end}}
});
}
}
});
return false;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title langtag="title-register"></title>
<!-- Mainly scripts -->
<!-- Latest compiled and minified CSS -->
<link href="{{.web_base_url}}/static/css/fontawesome.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/solid.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/bootstrap.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/style.css?v={{.version}}" rel="stylesheet">
<!-- Latest compiled and minified JavaScript -->
<script src="{{.web_base_url}}/static/js/jquery-3.7.1.min.js"></script>
<script src="{{.web_base_url}}/static/js/bootstrap.min.js"></script>
<script src="{{.web_base_url}}/static/js/jsencrypt.min.js"></script>
<!-- Latest compiled and minified Locales -->
<script src="{{.web_base_url}}/static/js/language.js?v={{.version}}" type="text/javascript"></script>
</head>
<body class="login-page">
<nav class="navbar navbar-static-top navbar-right navbar-login">
<div class="ml-auto d-flex align-items-center" style="gap: 0.5rem;">
<button id="theme-toggle" class="btn btn-outline-secondary" onclick="toggleTheme()">
<i class="fa fa-moon"></i>
</button>
<span class="btn-group dropdown">
<button id="languagemenu" class="btn btn-outline-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-globe-asia fa-lg"></i>
<span></span>
</button>
<ul class="dropdown-menu"></ul>
</span>
</div>
</nav>
<div class="login-card">
<div class="login-header register">
<img src="{{.web_base_url}}/static/img/nps.svg?v={{.version}}" alt="Logo" style="width: 32px; height: 32px; margin-right: 20px !important;">
<div class="navbar-brand" style="font-weight: lighter; font-size: 2.0rem !important"><h1 langtag="title-register"></h1></div>
</div>
<div class="login-body">
<form class="m-t" onsubmit="return false;">
<div class="form-group position-relative">
<i class="fas fa-user-circle input-icon"></i>
<input class="form-control" langtag="word-username" name="username" placeholder="username" required="" type="text">
</div>
<div class="form-group position-relative">
<i class="fas fa-key input-icon"></i>
<input class="form-control" langtag="word-password" name="password" placeholder="password" required="" type="password">
</div>
{{if eq true .captcha_open}}
<div class="form-group captcha-group position-relative">
<div class="flex-fill">
<i class="fas fa-shield-alt input-icon"></i>
<input class="form-control" langtag="word-captcha" name="captcha" placeholder="captcha" required="">
</div>
<div class="captcha-container">
{{create_captcha}}
</div>
</div>
{{end}}
<button class="btn btn-primary block full-width m-b" langtag="word-register" onclick="if (this.form.reportValidity()) register()" type="submit"></button>
<p class="text-muted text-center">
<small langtag="info-haveaccount"></small>
<small><a href="{{.web_base_url}}/login/index" langtag="word-login"></a></small>
</p>
</form>
</div>
<div class="footer">
<div class="float-right">
<span langtag="word-readmore"></span>
<strong><a href="https://github.com/djylb/nps" langtag="word-go"></a></strong>
</div>
<div><strong langtag="word-copyright"></strong> <span langtag="application"></span> &copy; 2018-{{.year}}</div>
</div>
<script type="text/javascript">
window.nps = {
web_base_url: "{{.web_base_url}}",
version: "{{.version}}",
publicKey: `{{.public_key}}`,
loginNonce: "{{.login_nonce}}"
};
function encryptWithRSA(plain) {
const crypt = new JSEncrypt();
crypt.setPublicKey(window.nps.publicKey);
return crypt.encrypt(plain);
}
function register() {
const data = {};
$("form").serializeArray().forEach(item => {
data[item.name] = item.value;
});
const ts = Date.now();
const payload = JSON.stringify({
n: window.nps.loginNonce,
t: ts,
p: data.password
});
data.password = encryptWithRSA(payload);
$.ajax({
type: "POST",
url: window.nps.web_base_url + "/login/register",
data: data,
success: function (res) {
if (res.status) {
showMsg(langreply(res.msg), 'success', 1000, function() {
window.location.href = window.nps.web_base_url + "/login/index";
});
} else {
showMsg(langreply(res.msg), 'error', 5000, function() {
if (res.nonce) {
window.nps.loginNonce = res.nonce;
}
if (res.cert) {
window.nps.publicKey = res.cert;
}
{{if eq true .captcha_open}}
var $img = $('.captcha-img');
if ($img.length) {
$img.trigger('click');
}
$('input[name="captcha"]').val('');
{{end}}
});
}
}
});
return false;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,5 @@
<div class="page-error tile">
<h1><i class="fa fa-exclamation-circle"></i> Error 404: Page not found</h1>
<p>The page you have requested is not found.</p>
<p><a class="btn btn-primary" href="javascript:window.history.back();">Go Back</a></p>
</div>

View File

@@ -0,0 +1,175 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<title langtag="title-admin"></title>
<!-- Mainly scripts -->
<!-- Latest compiled and minified CSS -->
<link href="{{.web_base_url}}/static/css/fontawesome.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/solid.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/bootstrap.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/bootstrap-select.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/bootstrap-table.min.css" rel="stylesheet">
<link href="{{.web_base_url}}/static/css/style.css?v={{.version}}" rel="stylesheet">
<!-- Latest compiled and minified JavaScript -->
<script src="{{.web_base_url}}/static/js/jquery-3.7.1.min.js"></script>
<script src="{{.web_base_url}}/static/js/popper.min.js"></script>
<script src="{{.web_base_url}}/static/js/bootstrap.min.js"></script>
<script src="{{.web_base_url}}/static/js/bootstrap-select.min.js"></script>
<script src="{{.web_base_url}}/static/js/bootstrap-table.min.js"></script>
<script src="{{.web_base_url}}/static/js/bootstrap-table-cookie.min.js"></script>
<script src="{{.web_base_url}}/static/js/bootstrap-table-locale-all.min.js"></script>
<script src="{{.web_base_url}}/static/js/echarts.min.js"></script>
<script src="{{.web_base_url}}/static/js/inspinia.js"></script>
<!-- Latest compiled and minified Locales -->
<script src="{{.web_base_url}}/static/js/language.js?v={{.version}}" type="text/javascript"></script>
</head>
<body class="pace-done fixed-nav fixed-nav-basic">
<div id="wrapper">
<nav class="navbar-default navbar-static-side" role="navigation">
<div class="sidebar-collapse">
<ul class="nav metismenu" id="side-menu">
<li class="nav-header">
<div class="dropdown profile-element">
{{if eq true .isAdmin}}
<span><i class="fa fa-user-cog fa-2x"></i></span>
<span class="clear"> <span class="block m-t-xs"><strong class="font-bold" langtag="word-admin"></strong></span>
<span class="text-muted text-xs block" langtag="word-system">
{{else}}
<span><i class="fa fa-user fa-3x"></i></span>
<span class="clear"> <span class="block m-t-xs"><strong class="font-bold">{{.username}}</strong></span>
<span class="text-muted text-xs block" langtag="word-user">
{{end}}
</span>
</div>
<div class="logo-element" langtag="application"></div>
</li>
<li class="{{if eq "index" .menu}}active{{end}}">
<a href="{{.web_base_url}}/" class="d-flex align-items-center">
<i class="fa fa-tachometer-alt fa-lg fa-fw"></i>
<span class="nav-label" langtag="word-dashboard"></span></a>
</li>
<li class="{{if eq "client" .menu}}active{{end}}">
<a href="{{.web_base_url}}/client/list" class="d-flex align-items-center">
<i class="fa fa-desktop fa-lg fa-fw"></i>
<span class="nav-label" langtag="word-client"></span></a>
</li>
<li class="{{if eq "host" .menu}}active{{end}}">
<a href="{{.web_base_url}}/index/hostlist" class="d-flex align-items-center">
<i class="fa fa-globe fa-lg fa-fw"></i>
<span class="nav-label" langtag="scheme-host"></span></a>
</li>
<li class="{{if eq "tcp" .menu}}active{{end}}">
<a href="{{.web_base_url}}/index/tcp" class="d-flex align-items-center">
<i class="fa fa-retweet fa-lg fa-fw"></i>
<span class="nav-label" langtag="scheme-tcp"></span></a>
</li>
<li class="{{if eq "udp" .menu}}active{{end}}">
<a href="{{.web_base_url}}/index/udp" class="d-flex align-items-center">
<i class="fa fa-random fa-lg fa-fw"></i>
<span class="nav-label" langtag="scheme-udp"></span></a>
</li>
<li class="{{if eq "mix" .menu}}active{{end}}">
<a href="{{.web_base_url}}/index/mix" class="d-flex align-items-center">
<i class="fa fa-layer-group fa-lg fa-fw"></i>
<span class="nav-label" langtag="scheme-mixproxy"></span></a>
</li>
<li class="{{if eq "secret" .menu}}active{{end}}">
<a href="{{.web_base_url}}/index/secret" class="d-flex align-items-center">
<i class="fa fa-low-vision fa-lg fa-fw"></i>
<span class="nav-label" langtag="scheme-secret"></span></a>
</li>
<li class="{{if eq "p2p" .menu}}active{{end}}">
<a href="{{.web_base_url}}/index/p2p" class="d-flex align-items-center">
<i class="fa fa-exchange-alt fa-lg fa-fw"></i>
<span class="nav-label" langtag="scheme-p2p"></span></a>
</li>
<li class="{{if eq "file" .menu}}active{{end}}">
<a href="{{.web_base_url}}/index/file" class="d-flex align-items-center">
<i class="fa fa-briefcase fa-lg fa-fw"></i>
<span class="nav-label" langtag="scheme-file"></span></a>
</li>
{{if eq true .isAdmin}}
<li class="{{if eq "global" .menu}}active{{end}}">
<a href="{{.web_base_url}}/global/index" class="d-flex align-items-center">
<i class="fa fa-cog fa-lg fa-fw"></i>
<span class="nav-label" langtag="word-globalparam"></span></a>
</li>
{{end}}
<li class="{{if eq "help" .menu}}active{{end}}">
<a href="https://d-jy.net/docs/nps/" target="_blank" class="d-flex align-items-center">
<i class="fa fa-lightbulb fa-lg fa-fw"></i>
<span class="nav-label" langtag="word-help"></span></a>
</li>
</ul>
</div>
</nav>
<div id="page-wrapper" class="gray-bg">
<div class="row border-bottom">
<nav class="navbar navbar-fixed-top" role="navigation" style="margin-bottom: 0">
<div class="navbar-header">
<a class="navbar-minimalize minimalize-styl-2 btn btn-primary " href="#"><i class="fa fa-bars"></i></a>
</div>
<ul class="nav navbar-top-links navbar-right">
<li>
<span class="m-r-sm text-muted welcome-message"><span langtag="word-welcome"></span>
<a href="https://github.com/djylb/nps" langtag="application"></a></span>
</li>
<li>
<div class="ml-auto d-flex align-items-center" style="gap: 0.5rem;">
<button id="theme-toggle" class="btn btn-outline-secondary" onclick="toggleTheme()">
<i class="fa fa-moon"></i>
</button>
<span class="btn-group dropdown">
<button id="languagemenu" class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-globe-asia fa-lg"></i>
<span></span>
</button>
<ul class="dropdown-menu"></ul>
</span>
</div>
</li>
<li>
<a href="{{.web_base_url}}/login/out" onclick="localStorage.clear(); return true;">
<i class="fa fa-sign-in-alt"></i><span langtag="word-logout"></span>
</a>
</li>
</ul>
</nav>
</div>
<div class="page-content">
{{.LayoutContent}}
</div>
<div class="footer fixed">
<div class="float-right">
<span langtag="word-readmore"></span> <strong><a href="https://github.com/djylb/nps" langtag="word-go"></a></strong>
</div>
<div><strong langtag="word-copyright"></strong> <span langtag="application"></span> &copy; 2018-{{.year}}</div>
</div>
</div>
</div>
</body>
</html>
<script>
window.nps = {
"web_base_url": "{{.web_base_url}}",
"version": "{{.version}}"
}
/*
googleTranslateElementInit()
function googleTranslateElementInit() {
new google.translate.TranslateElement({
layout: google.translate.TranslateElement.InlineLayout.HORIZONTAL
}, 'wrapper');
}
*/
</script>
{{/*<script src="http://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>*/}}