first commit:add basic layout

This commit is contained in:
2023-09-18 18:22:59 +08:00
parent 4298c6a50d
commit ad425d9614
37 changed files with 6412 additions and 0 deletions

10
assets/lang/en.json Normal file
View File

@@ -0,0 +1,10 @@
{
"Frpc Panel": "Frpc Panel",
"Username": "Username",
"Password": "Password",
"Login": "Login",
"Please input username": "Please input username",
"Please input password": "Please input password",
"Login success": "Login success",
"Username or password incorrect": "Username or password incorrect"
}

10
assets/lang/zh.json Normal file
View File

@@ -0,0 +1,10 @@
{
"Frpc Panel": "Frpc 面板",
"Username": "用户名",
"Password": "密码",
"Login": "登录",
"Please input username": "请填写用户名",
"Please input password": "请填写密码",
"Login success": "登录成功",
"Username or password incorrect": "用户名或密码错误"
}

242
assets/static/css/color.css Normal file
View File

@@ -0,0 +1,242 @@
.layui-bg-blue {
background-color: #58b7ff !important;
}
.layui-btn {
background-color: #409eff;
}
.layui-layer-btn .layui-layer-btn0 {
background-color: #409eff;
}
.layui-btn-primary {
background-color: transparent;
}
.layui-btn-primary:hover {
border-color: #79bbff;
}
.layui-input:focus,
.layui-textarea:focus {
border-color: #79bbff !important;
box-shadow: none;
}
.layui-form-danger + .layui-form-select .layui-input,
.layui-form-danger:focus {
border-color: #ff5722 !important;
}
.layui-laypage .layui-laypage-curr .layui-laypage-em {
background-color: #409eff;
}
.layui-laypage input:focus, .layui-laypage select:focus {
border-color: #79bbff !important;
box-shadow: none;
}
.layui-table-view .layui-table td[data-edit]:hover:after {
border-color: #79bbff;
}
.layui-form-checkbox[lay-skin=primary]:hover > i {
border-color: #79bbff;
}
.layui-form-checkbox[lay-skin=primary] > .layui-icon-indeterminate:before {
background-color: #79bbff;
}
.layui-form-checkbox[lay-skin=primary] > .layui-icon-indeterminate {
border-color: #79bbff;
}
.layui-form-checked[lay-skin=primary] > i {
background-color: #409eff;
border-color: #409eff !important;
}
.layui-table-checked {
background-color: #ecf5ff;
}
.layui-table-checked:hover {
background-color: #d9ecff;
}
.layui-table-cell-c:hover {
border-color: #79bbff;
}
.layui-table-checked.layui-table-click,
.layui-table-checked.layui-table-hover {
background-color: #d9ecff;
}
.layui-nav-tree .layui-nav-child dd.layui-this,
.layui-nav-tree .layui-nav-child dd.layui-this a,
.layui-nav-tree .layui-this,
.layui-nav-tree .layui-this > a,
.layui-nav-tree .layui-this > a:hover {
background-color: #409eff;
}
.layui-nav-tree .layui-nav-bar {
background-color: #409eff;
}
.proxy-expand .layui-row .layui-row > div:first-child {
color: #99a9bf;
}
section.server-info .text-row .text-col:first-child {
color: #99a9bf;
}
section.proxy-list .proxy-info .layui-row .layui-row > div:first-child {
color: #99a9bf;
}
.online,
.offline {
border-radius: 4px;
padding: 2px 10px;
font-size: 12px;
border-width: 1px;
border-style: solid;
}
.online {
color: #67c23a;
background-color: #f0f9eb;
border-color: #e1f3d8;
}
.offline {
color: #f56c6c;
background-color: #fef0f0;
border-color: #fde2e2;
}
.version a,
.version span {
color: #99a9bf;
}
.login-title,
.login-title a {
color: #333 !important;
}
@media (prefers-color-scheme: dark) {
.login-title,
.login-title a {
color: #99a9bf !important;
}
.layui-bg-blue {
background-color: #395c74 !important;
}
.layui-btn {
background-color: #4f80a1;
}
.layui-layer-btn .layui-layer-btn0 {
background-color: #4f80a1;
}
.layui-btn-primary {
background-color: transparent;
border-color: #484849;
}
.layui-btn-primary:hover {
border-color: #5f5f60;
}
.layui-input:focus,
.layui-textarea:focus {
border-color: #5f5f60 !important;
box-shadow: none;
}
.layui-laypage .layui-laypage-curr .layui-laypage-em {
background-color: #4f80a1;
}
.layui-laypage input:focus, .layui-laypage select:focus {
border-color: #5f5f60 !important;
box-shadow: none;
}
.layui-table-view .layui-table td[data-edit]:hover:after {
border-color: #5f5f60;
}
.layui-form-danger + .layui-form-select .layui-input,
.layui-form-danger:focus {
border-color: #ff5722 !important;
}
.layui-form-checkbox[lay-skin=primary]:hover > i {
border-color: #5f5f60;
}
.layui-form-checkbox[lay-skin=primary] > .layui-icon-indeterminate:before {
background-color: #5f5f60;
}
.layui-form-checkbox[lay-skin=primary] > .layui-icon-indeterminate {
border-color: #5f5f60;
}
.layui-form-checked[lay-skin=primary] > i {
background-color: #484849;
border-color: #484849 !important;
}
.layui-table-checked {
background-color: rgba(255, 255, 255, .04);
}
.layui-table-checked:hover {
background-color: rgba(255, 255, 255, .08);
}
.layui-table-cell-c:hover {
border-color: #5f5f60;
}
.layui-table-checked.layui-table-click,
.layui-table-checked.layui-table-hover {
background-color: #5f5f60;
}
.layui-nav-tree .layui-nav-child dd.layui-this,
.layui-nav-tree .layui-nav-child dd.layui-this a,
.layui-nav-tree .layui-this,
.layui-nav-tree .layui-this > a,
.layui-nav-tree .layui-this > a:hover {
background-color: #4f80a1;
}
.layui-nav-tree .layui-nav-bar {
background-color: #4f80a1;
}
.online {
color: #67c23a;
background-color: #1c2518;
border-color: #25371c;
}
.offline {
color: #f56c6c;
background-color: #2b1d1d;
border-color: #412626;
}
}

134
assets/static/css/index.css Normal file
View File

@@ -0,0 +1,134 @@
html, body {
padding: 0;
word-break: break-all;
}
section {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
padding: 15px;
overflow: hidden;
box-sizing: border-box;
}
.layui-layout-admin .layui-body {
padding: 0;
}
.layui-header {
line-height: 60px;
font-size: 16px;
}
.layui-layout-admin .layui-side {
width: 225px !important;
}
.layui-side-scroll {
width: 245px !important;
position: absolute !important;
top: 0 !important;
bottom: 24px !important;
height: auto !important;
}
.layui-logo {
width: 225px !important;
}
.layui-title {
position: absolute;
left: 225px !important;
right: 0;
top: 0;
height: 100%;
padding: 0 15px;
box-sizing: border-box;
display: flex;
}
.layui-title #title {
flex: 1;
display: inline-block;
}
.layui-title #logout{
display: inline-block;
font-size: 20px;
font-weight: bold;
cursor: pointer;
}
.layui-nav.layui-nav-tree {
width: 225px !important;
}
.layui-body {
left: 225px;
}
#searchForm input {
height: 30px;
line-height: 28px;
}
#searchForm .layui-input-suffix,
#searchForm .layui-input-prefix {
line-height: 30px;
padding: 0;
}
#addUserForm {
padding: 15px 15px 0 15px;
}
#addUserForm .layui-form-item:last-child {
margin-bottom: 0;
}
#addUserForm .layui-textarea {
min-height: 80px;
padding: 9px 10px;
resize: none;
}
.layui-form-label {
width: 140px;
}
.layui-input-block {
margin-left: 170px;
}
.layui-btn-sm {
line-height: 28px;
}
.layui-btn-xs {
line-height: 20px;
}
.layui-btn-container {
height: 30px;
}
.layui-layer-btn > a[class^=layui-layer-btn] {
line-height: 28px;
}
.layui-table-page {
margin-bottom: 0;
}
.version {
height: 24px;
line-height: 24px;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
font-size: 12px;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
html, body {
width: 100%;
height: 100%;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
}
.login-title {
text-align: center;
margin-bottom: 50px;
}
.login-title .title-text {
font-size: 24px;
text-align: center;
font-weight: bold;
}
.login-title .title-version {
font-size: 12px;
}
.login-container {
width: 320px;
margin: 0 auto;
}

View File

@@ -0,0 +1,260 @@
var loadProxyInfo = (function ($) {
var size = filesize.partial({base: 2, standard: "jedec"});
var i18n = {};
/**
* get proxy info
* @param lang {{}} language json
* @param title page title
* @param proxyType proxy type
*/
function loadProxyInfo(lang, title, proxyType) {
i18n = lang;
$("#title").text(title);
$('#content').empty();
var loading = layui.layer.load();
$.getJSON('/proxy/api/proxy/' + proxyType).done(function (result) {
if (result.success) {
$('#content').html($('#proxyListTableTemplate').html());
renderProxyListTable(JSON.parse(result.data)['proxies'], proxyType);
} else {
layui.layer.msg(result.message);
}
}).always(function () {
layui.layer.close(loading);
});
}
/**
* render proxy list table
* @param data proxy data
* @param proxyType proxy type
*/
function renderProxyListTable(data, proxyType) {
proxyType = proxyType.toLowerCase();
data.forEach(function (temp) {
temp.conf = temp.conf || {
remote_port: 0,
use_encryption: false,
use_compression: false,
custom_domains: null,
subdomain: null,
locations: null,
host_header_rewrite: null
};
temp.client_version = temp.client_version || '-';
temp.conf.custom_domains = temp.conf.custom_domains || '-';
temp.conf.subdomain = temp.conf.subdomain || '-';
temp.conf.locations = temp.conf.locations || '-';
temp.conf.host_header_rewrite = temp.conf.host_header_rewrite || '-';
if (temp.conf.custom_domains !== '-') {
temp.conf.custom_domains = JSON.stringify(temp.conf.custom_domains);
}
if (proxyType === 'http') {
temp.conf.remote_port = http_port;
} else if (proxyType === 'https') {
temp.conf.remote_port = https_port;
}
});
var $section = $('#content > section');
var cols = [
{field: 'id', type: 'space', width: 60, align: 'center', templet: '#toggleProxyInfoArrowTemplate'},
{field: 'name', title: i18n['Name'], sort: true},
{
field: 'port',
title: i18n['Port'],
width: '12%',
sort: true,
templet: '<span>{{= d.conf.remote_port }}</span>'
},
{field: 'cur_conns', title: i18n['Connections'], minWidth: 140, width: '12%', sort: true},
{
field: 'today_traffic_in',
title: i18n['TrafficIn'],
minWidth: 140,
width: '12%',
sort: true,
templet: function (d) {
return size(d.today_traffic_in);
}
},
{
field: 'today_traffic_out',
title: i18n['TrafficOut'],
minWidth: 140,
width: '12%',
sort: true,
templet: function (d) {
return size(d.today_traffic_out);
}
},
{field: 'client_version', title: i18n['ClientVersion'], minWidth: 140, width: '12%', sort: true},
{
field: 'status', title: i18n['Status'], width: '12%', sort: true, templet: function (d) {
return '<span class="' + d.status + '">' + i18n[d.status] + '</span>';
}
}
];
var proxyListTable = layui.table.render({
elem: '#proxyListTable',
height: $section.height(),
text: {none: i18n['EmptyData']},
cols: [cols],
page: navigator.language.indexOf("zh") !== -1,
data: data,
initSort: {
field: 'name',
type: 'asc'
},
done: function (res, curr, count, origin) {
//向每一行tr后面追加显示子table的tr
var $tr = $('.layui-table-view[lay-id=' + this.id + '] tbody tr');
var expandTrTemplateHtml = $('#expandTrTemplate').html();
for (var i = 0; i < $tr.length; i++) {
var datum = res.data[i];
var useEncryption = datum.conf.use_encryption;
var useCompression = datum.conf.use_compression;
datum.conf.use_encryption = i18n[useEncryption];
datum.conf.use_compression = i18n[useCompression];
var html = layui.laytpl(expandTrTemplateHtml).render({
index: i,
colspan: cols.length - 1,
proxyType: proxyType,
data: datum
});
$($tr[i]).after(html);
}
}
});
layui.table.on('tool(proxyListTable)', function (obj) {
var index = obj.index;
$(this).toggleClass('open');
var open = $(this).hasClass('open');
$('#childTr_' + index).toggleClass('layui-hide', !open);
proxyListTable.resize();
});
window.onresize = function () {
proxyListTable.resize();
}
}
/**
* load traffic statistics data
*/
function loadTrafficStatistics() {
var proxyName = $(this).closest('.layui-row').find('input').val();
var loading = layui.layer.load();
$.getJSON('/proxy/api/traffic/' + proxyName).done(function (result) {
if (result.success) {
renderTrafficChart(JSON.parse(result.data));
} else {
layui.layer.msg(result.message);
}
}).always(function () {
layui.layer.close(loading);
});
}
/**
* render traffic statistics chart
* @param data traffic data
*/
function renderTrafficChart(data) {
var html = layui.laytpl($('#trafficStaticTemplate').html()).render();
var dates = [];
var now = new Date();
for (var i = 0; i < data.traffic_in.length; i++) {
dates.push(now.getFullYear() + "/" + (now.getMonth() + 1) + "/" + now.getDate());
now.setDate(now.getDate() - 1);
}
layui.layer.open({
title: i18n['TrafficStatistics'],
type: 1,
content: html,
area: ['800px', '400px'],
success: function () {
var chartDom = document.getElementById('trafficBarChart');
var chart = echarts.init(chartDom);
var option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
formatter: function (data) {
var html = ''
if (data.length > 0) {
html += data[0].name + '<br/>'
}
for (var v of data) {
var colorEl = '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + v.color + '"></span>'
html += colorEl + v.seriesName + ': ' + size(v.value) + '<br/>'
}
return html
}
},
legend: {
data: [i18n['TrafficIn'], i18n['TrafficOut']],
textStyle: {
textBorderColor: '#fff',
textBorderWidth: 2
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: dates.reverse()
}
],
yAxis: [
{
type: 'value',
axisLabel: {
formatter: function (value) {
return size(value)
}
}
}
],
series: [
{
name: i18n['TrafficIn'],
type: 'bar',
data: data.traffic_in.reverse(),
},
{
name: i18n['TrafficOut'],
type: 'bar',
data: data.traffic_out.reverse(),
}
]
};
option && chart.setOption(option);
}
});
}
/**
* document event
*/
(function bindDocumentEvent() {
$(document).on('click.trafficStatistics', '.traffic-statistics', function () {
loadTrafficStatistics.call(this);
});
})();
return loadProxyInfo;
})(layui.$);

View File

@@ -0,0 +1,190 @@
var loadServerInfo = (function ($) {
var size = filesize.partial({base: 2, standard: "jedec"});
var i18n = {};
/**
* get server info
* @param lang {{}} language json
* @param title page title
*/
function loadServerInfo(lang, title) {
i18n = lang;
$("#title").text(title);
$('#content').empty();
var loading = layui.layer.load();
$.getJSON('/proxy/api/serverinfo').done(function (result) {
if (result.success) {
var data = JSON.parse(result.data);
data.proxy_counts = 0;
http_port = data.vhost_http_port;
https_port = data.vhost_https_port;
for (var proxy in data.proxy_type_count) {
data.proxy_counts = data.proxy_counts + data.proxy_type_count[proxy];
}
data.bind_port = data.bind_port || i18n['Disable'];
data.kcp_bind_port = data.kcp_bind_port || i18n['Disable'];
data.quic_bind_port = data.quic_bind_port || i18n['Disable'];
data.vhost_http_port = data.vhost_http_port || i18n['Disable'];
data.vhost_https_port = data.vhost_https_port || i18n['Disable'];
data.tcpmux_httpconnect_port = data.tcpmux_httpconnect_port || i18n['Disable'];
data.subdomain_host = data.subdomain_host || i18n['NotSet'];
data.max_pool_count = data.max_pool_count || i18n['NotSet'];
data.max_ports_per_client = data.max_ports_per_client || i18n['NotLimit'];
data.heart_beat_timeout = data.heart_beat_timeout || i18n['NotSet'];
data.allow_ports_str = data.allow_ports_str || i18n['NotLimit'];
data.tls_only = i18n[data.tls_only || false];
renderServerInfo(data);
} else {
layui.layer.msg(result.message);
}
}).always(function () {
layui.layer.close(loading);
});
}
/**
* render server info page
* @param data server info data
*/
function renderServerInfo(data) {
var html = layui.laytpl($('#serverInfoTemplate').html()).render(data);
$('#content').html(html);
$('#frpVersion').text(data.version);
renderTrafficChart(data);
renderCountChart(data);
}
/**
* render traffic chart with echarts
* @param data traffic data
*/
function renderTrafficChart(data) {
var chartLegend = [i18n['TrafficIn'], i18n['TrafficOut']];
var chartData = [
{value: data.total_traffic_in, name: i18n['TrafficIn']},
{value: data.total_traffic_out, name: i18n['TrafficOut']}
];
var chartDom = document.getElementById('trafficPieChart');
var chart = echarts.init(chartDom);
var option = {
title: {
text: i18n['NetworkTraffic'],
subtext: i18n['today'],
left: 'center',
textStyle: {
textBorderColor: '#fff',
textBorderWidth: 2
},
subtextStyle: {
textBorderColor: '#fff',
textBorderWidth: 2
}
},
tooltip: {
trigger: 'item',
formatter: function (v) {
return size(v.value) + ' (' + v.percent + '%)';
},
},
legend: {
orient: 'vertical',
left: 'left',
data: chartLegend,
textStyle: {
textBorderColor: '#fff',
textBorderWidth: 2
}
},
series: [
{
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: chartData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
option && chart.setOption(option);
}
/**
* render proxy count chat with echarts
* @param data proxy count data
*/
function renderCountChart(data) {
var proxies = data.proxy_type_count;
var chartLegend = [];
var chartData = [];
for (var type in proxies) {
var temp = {
name: type.toUpperCase(),
value: proxies[type]
};
chartLegend.push(type.toUpperCase());
chartData.push(temp);
}
var chartDom = document.getElementById('countPieChart');
var chart = echarts.init(chartDom);
var option = {
title: {
text: i18n['Proxy'],
subtext: i18n['now'],
left: 'center',
textStyle: {
textBorderColor: '#fff',
textBorderWidth: 2
},
subtextStyle: {
textBorderColor: '#fff',
textBorderWidth: 2
}
},
tooltip: {
trigger: 'item',
formatter: function (v) {
return v.value + ' (' + v.percent + '%)';
}
},
legend: {
orient: 'vertical',
left: 'left',
data: chartLegend,
textStyle: {
textBorderColor: '#fff',
textBorderWidth: 2
}
},
series: [
{
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: chartData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
option && chart.setOption(option);
}
return loadServerInfo;
})(layui.$);

View File

@@ -0,0 +1,730 @@
var loadUserList = (function ($) {
var i18n = {};
var apiType = {
Remove: 1,
Enable: 2,
Disable: 3
};
var verifyRules = {
user: function (value, item) {
var result = verifyUser(value);
if (!result.valid) {
return i18n['UserFormatError'];
}
if (item != null) {
if (typeof item === "function") {
item && item(result.trim);
} else {
$(item).val(result.trim);
}
}
},
token: function (value, item) {
var result = verifyToken(value);
if (!result.valid) {
return i18n['TokenFormatError'];
}
if (item != null) {
if (typeof item === "function") {
item && item(result.trim);
} else {
$(item).val(result.trim);
}
}
},
comment: function (value, item) {
var result = verifyComment(value);
if (!result.valid) {
return i18n['CommentInvalid'];
}
if (item != null) {
if (typeof item === "function") {
item && item(result.trim);
} else {
$(item).val(result.trim);
}
}
},
ports: function (value, item) {
var result = verifyPorts(value);
if (!result.valid) {
return i18n['PortsInvalid'];
}
if (item != null) {
if (typeof item === "function") {
item && item(result.trim);
} else {
$(item).val(result.trim);
}
}
},
domains: function (value, item) {
var result = verifyDomains(value);
if (!result.valid) {
return i18n['DomainsInvalid'];
}
if (item != null) {
if (typeof item === "function") {
item && item(result.trim);
} else {
$(item).val(result.trim);
}
}
},
subdomains: function (value, item) {
var result = verifySubdomains(value);
if (!result.valid) {
return i18n['SubdomainsInvalid'];
}
if (item != null) {
if (typeof item === "function") {
item && item(result.trim);
} else {
$(item).val(result.trim);
}
}
}
};
/**
* verify user value
* @param username
*/
function verifyUser(username) {
var valid = true;
if (username.trim() === '' || !/^\w+$/.test(username)) {
valid = false;
}
return {
valid: valid,
trim: username.trim()
};
}
/**
* verify token value
* @param token
*/
function verifyToken(token) {
var valid = true;
if (token.trim() === '' || !/^[\w!@#$%^&*()]+$/.test(token)) {
valid = false;
}
return {
valid: valid,
trim: token.trim()
};
}
/**
* verify comment is valid
* @param comment
*
* @return {{valid:boolean, trim:string}}
*/
function verifyComment(comment) {
var valid = true;
if (comment.trim() !== '' && /[\n\t\r]/.test(comment)) {
valid = false;
}
return {
valid: valid,
trim: comment.trim().replace(/[\n\t\r]/g, '')
};
}
/**
* verify ports is valid
* @param ports
*
* @return {{valid:boolean, trim:string}}
*/
function verifyPorts(ports) {
var valid = true;
if (ports.trim() !== '') {
try {
ports.split(",").forEach(function (port) {
if (/^\s*\d{1,5}\s*$/.test(port)) {
if (parseInt(port) < 1 || parseInt(port) > 65535) {
valid = false;
}
} else if (/^\s*\d{1,5}\s*-\s*\d{1,5}\s*$/.test(port)) {
var portRange = port.split('-');
if (parseInt(portRange[0]) < 1 || parseInt(portRange[0]) > 65535) {
valid = false;
} else if (parseInt(portRange[1]) < 1 || parseInt(portRange[1]) > 65535) {
valid = false;
} else if (parseInt(portRange[0]) > parseInt(portRange[1])) {
valid = false;
}
} else {
valid = false;
}
if (valid === false) {
throw 'break';
}
});
} catch (e) {
}
}
return {
valid: valid,
trim: ports.replace(/\s/g, '')
};
}
/**
* verify domains is valid
* @param domains
*
* @return {{valid:boolean, trim:string}}
*/
function verifyDomains(domains) {
var valid = true;
if (domains.trim() !== '') {
try {
domains.split(',').forEach(function (domain) {
if (!/^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$/.test(domain.trim())) {
valid = false;
throw 'break';
}
});
} catch (e) {
}
}
return {
valid: valid,
trim: domains.replace(/\s/g, '')
};
}
/**
* verify subdomains is valid
* @param subdomains
*
* @return {{valid:boolean, trim:string}}
*/
function verifySubdomains(subdomains) {
var valid = true;
if (subdomains.trim() !== '') {
try {
subdomains.split(',').forEach(function (subdomain) {
if (!/^[a-zA-z0-9][a-zA-z0-9-]{0,19}$/.test(subdomain.trim())) {
valid = false;
throw 'break';
}
});
} catch (e) {
}
}
return {
valid: valid,
trim: subdomains.replace(/\s/g, '')
};
}
/**
* set verify rule of layui.form
*/
(function setFormVerifyRule() {
layui.form.verify(verifyRules);
})();
/**
* load i18n language
* @param lang {{}} language json
* @param title page title
*/
function loadUserList(lang, title) {
i18n = lang;
$("#title").text(title);
var html = layui.laytpl($('#userListTemplate').html()).render();
$('#content').html(html);
var $section = $('#content > section');
layui.table.render({
elem: '#tokenTable',
height: $section.height() - $('#searchForm').height() + 8,
text: {none: i18n['EmptyData']},
url: '/tokens',
method: 'get',
where: {},
dataType: 'json',
editTrigger: 'dblclick',
page: {
layout: navigator.language.indexOf("zh") === -1 ? ['first', 'prev', 'next', 'last'] : ['prev', 'page', 'next', 'skip', 'count', 'limit']
},
toolbar: '#userListToolbarTemplate',
defaultToolbar: false,
cols: [[
{type: 'checkbox'},
{field: 'user', title: i18n['User'], width: 150, sort: true},
{field: 'token', title: i18n['Token'], width: 200, sort: true, edit: true},
{field: 'comment', title: i18n['Notes'], sort: true, edit: 'textarea'},
{field: 'ports', title: i18n['AllowedPorts'], sort: true, edit: 'textarea'},
{field: 'domains', title: i18n['AllowedDomains'], sort: true, edit: 'textarea'},
{field: 'subdomains', title: i18n['AllowedSubdomains'], sort: true, edit: 'textarea'},
{
field: 'enable',
title: i18n['Status'],
width: 100,
templet: '<span>{{d.enable? "' + i18n['Enable'] + '":"' + i18n['Disable'] + '"}}</span>',
sort: true
},
{title: i18n['Operation'], width: 150, toolbar: '#userListOperationTemplate'}
]],
parseData: function (res) {
res.data.forEach(function (data) {
data.ports = data.ports.join(',');
data.domains = data.domains.join(',');
data.subdomains = data.subdomains.join(',');
});
}
});
bindFormEvent();
}
/**
* bind event of {{@link layui.form}}
*/
function bindFormEvent() {
layui.table.on('edit(tokenTable)', function (obj) {
var field = obj.field;
var value = obj.value;
var oldValue = obj.oldValue;
var before = $.extend(true, {}, obj.data);
var after = $.extend(true, {}, obj.data);
var verifyMsg = false;
if (field === 'token') {
verifyMsg = verifyRules.token(value, function (trim) {
updateTableField(obj, field, trim)
});
if (verifyMsg) {
layui.layer.msg(verifyMsg);
return obj.reedit();
}
before.token = oldValue;
after.token = value;
} else if (field === 'comment') {
verifyMsg = verifyRules.comment(value, function (trim) {
updateTableField(obj, field, trim)
});
if (verifyMsg) {
layui.layer.msg(verifyMsg);
return obj.reedit();
}
before.comment = oldValue;
after.comment = value;
} else if (field === 'ports') {
verifyMsg = verifyRules.ports(value, function (trim) {
updateTableField(obj, field, trim)
});
if (verifyMsg) {
layui.layer.msg(verifyMsg);
return obj.reedit();
}
before.ports = oldValue;
after.ports = value;
} else if (field === 'domains') {
verifyMsg = verifyRules.domains(value, function (trim) {
updateTableField(obj, field, trim)
});
if (verifyMsg) {
layui.layer.msg(verifyMsg);
return obj.reedit();
}
before.domains = oldValue;
after.domains = value;
} else if (field === 'subdomains') {
verifyMsg = verifyRules.subdomains(value, function (trim) {
updateTableField(obj, field, trim)
});
if (verifyMsg) {
layui.layer.msg(verifyMsg);
return obj.reedit();
}
before.subdomains = oldValue;
after.subdomains = value;
}
before.ports = before.ports.split(',')
before.domains = before.domains.split(',')
before.subdomains = before.subdomains.split(',')
after.ports = after.ports.split(',')
after.domains = after.domains.split(',')
after.subdomains = after.subdomains.split(',')
update(before, after);
});
layui.table.on('toolbar(tokenTable)', function (obj) {
var id = obj.config.id;
var checkStatus = layui.table.checkStatus(id);
var data = checkStatus.data;
data.forEach(function (temp) {
temp.ports = temp.ports.split(',')
temp.ports.forEach(function (port, index) {
if (/^\d+$/.test(String(port))) {
temp.ports[index] = parseInt(String(port));
}
});
temp.domains = temp.domains.split(',')
temp.subdomains = temp.subdomains.split(',')
});
switch (obj.event) {
case 'add':
addPopup();
break
case 'remove':
batchRemovePopup(data);
break
case 'disable':
batchDisablePopup(data);
break
case 'enable':
batchEnablePopup(data);
break
}
});
layui.table.on('tool(tokenTable)', function (obj) {
var data = obj.data;
data.ports = data.ports.split(',')
data.ports.forEach(function (port, index) {
if (/^\d+$/.test(String(port))) {
data.ports[index] = parseInt(String(port));
}
});
data.domains = data.domains.split(',')
data.subdomains = data.subdomains.split(',')
switch (obj.event) {
case 'remove':
removePopup(data);
break;
case 'disable':
disablePopup(data);
break;
case 'enable':
enablePopup(data);
break
}
});
}
/**
* update layui table data
* @param obj table update obj
* @param field update field
* @param trim new value
*/
function updateTableField(obj, field, trim) {
var newData = {};
newData[field] = trim;
obj.update(newData);
}
/**
* add user popup
*/
function addPopup() {
layui.layer.open({
type: 1,
title: i18n['NewUser'],
area: ['500px'],
content: layui.laytpl(document.getElementById('addUserTemplate').innerHTML).render(),
btn: [i18n['Confirm'], i18n['Cancel']],
btn1: function (index) {
if (layui.form.validate('#addUserForm')) {
var formData = layui.form.val('addUserForm');
if (formData.ports != null) {
formData.ports = formData.ports.split(',')
formData.ports.forEach(function (port, index) {
if (/^\d+$/.test(String(port))) {
formData.ports[index] = parseInt(String(port));
}
})
}
if (formData.domains != null) {
formData.domains = formData.domains.split(',')
}
if (formData.subdomains != null) {
formData.subdomains = formData.subdomains.split(',')
}
add(formData, index);
}
},
btn2: function (index) {
layui.layer.close(index);
}
});
}
/**
* add user action
* @param data {{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}} user data
* @param index popup index
*/
function add(data, index) {
var loading = layui.layer.load();
$.ajax({
url: '/add',
type: 'post',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (result) {
if (result.success) {
reloadTable();
layui.layer.close(index);
layui.layer.msg(i18n['OperateSuccess'], function (index) {
layui.layer.close(index);
});
} else {
errorMsg(result);
}
},
complete: function () {
layui.layer.close(loading);
}
});
}
/**
* update user action
* @param before {{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}} data before update
* @param after {{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}} data after update
*/
function update(before, after) {
before.ports.forEach(function (port, index) {
if (/^\d+$/.test(String(port))) {
before.ports[index] = parseInt(String(port));
}
});
after.ports.forEach(function (port, index) {
if (/^\d+$/.test(String(port)) && typeof port === "string") {
after.ports[index] = parseInt(String(port));
}
});
var loading = layui.layer.load();
$.ajax({
url: '/update',
type: 'post',
contentType: 'application/json',
data: JSON.stringify({
before: before,
after: after,
}),
success: function (result) {
if (result.success) {
layui.layer.msg(i18n['OperateSuccess']);
} else {
errorMsg(result);
}
},
complete: function () {
layui.layer.close(loading);
}
});
}
/**
* batch remove user popup
* @param data {[{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}]} user data list
*/
function batchRemovePopup(data) {
if (data.length === 0) {
layui.layer.msg(i18n['ShouldCheckUser']);
return;
}
layui.layer.confirm(i18n['ConfirmRemoveUser'], {
title: i18n['OperationConfirm'],
btn: [i18n['Confirm'], i18n['Cancel']]
}, function (index) {
operate(apiType.Remove, data, index);
});
}
/**
* batch disable user popup
* @param data {[{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}]} user data list
*/
function batchDisablePopup(data) {
if (data.length === 0) {
layui.layer.msg(i18n['ShouldCheckUser']);
return;
}
layui.layer.confirm(i18n['ConfirmDisableUser'], {
title: i18n['OperationConfirm'],
btn: [i18n['Confirm'], i18n['Cancel']]
}, function (index) {
operate(apiType.Disable, data, index);
});
}
/**
* batch enable user popup
* @param data {[{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}]} user data list
*/
function batchEnablePopup(data) {
if (data.length === 0) {
layui.layer.msg(i18n['ShouldCheckUser']);
return;
}
layui.layer.confirm(i18n['ConfirmEnableUser'], {
title: i18n['OperationConfirm'],
btn: [i18n['Confirm'], i18n['Cancel']]
}, function (index) {
operate(apiType.Enable, data, index);
});
}
/**
* remove one user popup
* @param data {{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}} user data
*/
function removePopup(data) {
layui.layer.confirm(i18n['ConfirmRemoveUser'], {
title: i18n['OperationConfirm'],
btn: [i18n['Confirm'], i18n['Cancel']]
}, function (index) {
operate(apiType.Remove, [data], index);
});
}
/**
* disable one user popup
* @param data {{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}} user data
*/
function disablePopup(data) {
layui.layer.confirm(i18n['ConfirmDisableUser'], {
title: i18n['OperationConfirm'],
btn: [i18n['Confirm'], i18n['Cancel']]
}, function (index) {
operate(apiType.Disable, [data], index);
});
}
/**
* enable one user popup
* @param data {{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}} user data
*/
function enablePopup(data) {
layui.layer.confirm(i18n['ConfirmEnableUser'], {
title: i18n['OperationConfirm'],
btn: [i18n['Confirm'], i18n['Cancel']]
}, function (index) {
operate(apiType.Enable, [data], index);
});
}
/**
* operate actions
* @param type {apiType} action type
* @param data {[{user:string, token:string, comment:string, enable:boolean, ports:[string|number], domains:[string], subdomains:[string]}]} user data list
* @param index popup index
*/
function operate(type, data, index) {
var url;
var extendMessage = '';
if (type === apiType.Remove) {
url = "/remove";
extendMessage = ', ' + i18n['RemoveUser'] + i18n['TakeTimeMakeEffective'];
} else if (type === apiType.Disable) {
url = "/disable";
extendMessage = ', ' + i18n['RemoveUser'] + i18n['TakeTimeMakeEffective'];
} else if (type === apiType.Enable) {
url = "/enable";
} else {
layer.layer.msg(i18n['OperateError']);
return;
}
var loading = layui.layer.load();
$.post({
url: url,
type: 'post',
contentType: 'application/json',
data: JSON.stringify({
users: data
}),
success: function (result) {
if (result.success) {
reloadTable();
layui.layer.close(index);
layui.layer.msg(i18n['OperateSuccess'] + extendMessage, function (index) {
layui.layer.close(index);
});
} else {
errorMsg(result);
}
},
complete: function () {
layui.layer.close(loading);
}
});
}
/**
* reload user table
*/
function reloadTable() {
var searchData = layui.form.val('searchForm');
layui.table.reloadData('tokenTable', {
where: searchData
}, true)
}
/**
* show error message popup
* @param result
*/
function errorMsg(result) {
var reason = i18n['OtherError'];
if (result.code === 1)
reason = i18n['ParamError'];
else if (result.code === 2)
reason = i18n['UserExist'];
else if (result.code === 3)
reason = i18n['UserNotExist'];
else if (result.code === 4)
reason = i18n['ParamError'];
else if (result.code === 5)
reason = i18n['UserFormatError'];
else if (result.code === 6)
reason = i18n['TokenFormatError'];
else if (result.code === 7)
reason = i18n['CommentInvalid'];
else if (result.code === 8)
reason = i18n['PortsInvalid'];
else if (result.code === 9)
reason = i18n['DomainsInvalid'];
else if (result.code === 10)
reason = i18n['SubdomainsInvalid'];
layui.layer.msg(i18n['OperateFailed'] + ',' + reason)
}
/**
* document event
*/
(function bindDocumentEvent() {
$(document).on('click.search', '#searchBtn', function () {
reloadTable();
return false;
}).on('click.reset', '#resetBtn', function () {
$('#searchForm')[0].reset();
reloadTable();
return false;
});
})();
return loadUserList;
})(layui.$)

50
assets/static/js/index.js Normal file
View File

@@ -0,0 +1,50 @@
var http_port, https_port;
(function ($) {
$(function () {
function init() {
var langLoading = layui.layer.load()
$.getJSON('/lang.json').done(function (lang) {
$.ajaxSetup({
error: function (xhr,) {
if (xhr.status === 401) {
layui.layer.msg(lang['TokenInvalid'], function () {
window.location.reload();
});
}
},
})
layui.element.on('nav(leftNav)', function (elem) {
var id = elem.attr('id');
var title = elem.text();
if (id === 'serverInfo') {
loadServerInfo(lang, title.trim());
} else if (id === 'userList') {
loadUserList(lang, title.trim());
} else if (elem.closest('.layui-nav-item').attr('id') === 'proxyList') {
if (id != null && id.trim() !== '') {
var suffix = elem.closest('.layui-nav-item').children('a').text().trim();
loadProxyInfo(lang, title + " " + suffix, id);
}
}
});
$('#leftNav .layui-this > a').click();
}).always(function () {
layui.layer.close(langLoading);
});
}
function logout() {
$.get("/logout", function (result) {
window.location.reload();
});
}
$(document).on('click.logout', '#logout', function () {
logout();
});
init();
});
})(layui.$);

33
assets/static/js/login.js Normal file
View File

@@ -0,0 +1,33 @@
(function ($) {
$(function () {
function login() {
if (!layui.form.validate('#loginForm')) {
return;
}
$.ajax({
url: "/login",
type: 'post',
data: {
username: $('#username').val(),
password: $('#password').val()
},
success: function (result) {
if (result.success) {
window.location.reload();
} else {
layui.layer.msg(result.message);
}
}
});
}
$(document).on('click.login', '#login', function () {
login();
}).on('keydown', function (e) {
if (e.keyCode === 13) {
login();
}
});
})
})(layui.$)

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>${ .FrpcPanel }</title>
<link rel="stylesheet" href="./static/lib/layui/css/layui.css?v=${ .version }">
<link rel="stylesheet" href="./static/css/layui-theme-dark.css?v=${ .version }">
<link rel="stylesheet" href="./static/css/index.css?v=${ .version }">
<link rel="stylesheet" href="./static/css/color.css?v=${ .version }">
<script src="./static/lib/layui/layui.js?v=${ .version }"></script>
<script src="./static/js/index-server-info.js?v=${ .version }"></script>
<script src="./static/js/index-user-list.js?v=${ .version }"></script>
<script src="./static/js/index-proxy-list.js?v=${ .version }"></script>
<script src="./static/js/index.js?v=${ .version }"></script>
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header layui-bg-blue">
<div class="layui-logo layui-bg-black">${ .FrpcPanel }</div>
<div class="layui-title">
<span id="title"></span>
${ if .showExit }
<span class="layui-icon layui-icon-logout" id="logout"></span>
${ end }
</div>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<ul class="layui-nav layui-nav-tree" lay-filter="leftNav" id="leftNav">
<li class="layui-nav-item layui-this">
<a href="javascript:void(0)" id="overview">Overview</a>
</li>
<li class="layui-nav-item">
<a href="javascript:void(0)" id="configure">Configure</a>
</li>
<li class="layui-nav-item layui-nav-itemed" id="proxyList">
<a class="" href="javascript:void(0)">Proxies</a>
<dl class="layui-nav-child">
<dd>
<a href="javascript:void(0)" id="tcp">TCP</a>
</dd>
<dd>
<a href="javascript:void(0)" id="udp">UDP</a>
</dd>
<dd>
<a href="javascript:void(0)" id="http">HTTP</a>
</dd>
<dd>
<a href="javascript:void(0)" id="https">HTTPS</a>
</dd>
<dd>
<a href="javascript:void(0)" id="stcp">STCP</a>
</dd>
<dd>
<a href="javascript:void(0)" id="sudp">SUDP</a>
</dd>
</dl>
</li>
</ul>
</div>
<div class="version">
<a href="https://github.com/yhl452493373/frpc-panel" target="_blank">${ .FrpcPanel } ${ .version }</a>
</div>
</div>
<div class="layui-body" id="content"></div>
</div>
<!--概览信息模板-->
<script type="text/html" id="overviewTemplate">
<section class="overview">
</section>
</script>
<!--代理列表-代理表格模板-->
<script type="text/html" id="proxyListTableTemplate">
<section class="proxy-list">
<table id="proxyListTable" lay-filter="proxyListTable"></table>
</section>
</script>
</body>
</html>

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="">
<head>
<title>Login</title>
<link rel="stylesheet" href="./static/lib/layui/css/layui.css?v=${ .version }">
<link rel="stylesheet" href="./static/css/layui-theme-dark.css?v=${ .version }">
<link rel="stylesheet" href="./static/css/color.css?v=${ .version }">
<link rel="stylesheet" href="./static/css/login.css?v=${ .version }">
<script src="./static/lib/layui/layui.js?v=${ .version }"></script>
<script src="./static/js/login.js?v=${ .version }"></script>
</head>
<body>
<div class="login-title">
<a href="https://github.com/yhl452493373/frpc-panel" target="_blank">
<span class="title-text">${ .FrpcPanel }</span>
<span class="title-version">${ .version }</span>
</a>
</div>
<div class="layui-form login-container" id="loginForm">
<div class="layui-form-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-username"></i>
</div>
<input type="text" id="username" value="" lay-verify="required" placeholder="${ .Username }"
lay-reqtext="${ .PleaseInputUsername }" autocomplete="off" class="layui-input" lay-affix="clear">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-password"></i>
</div>
<input type="password" id="password" value="" lay-verify="required" placeholder="${ .Password }"
lay-reqtext="${ .PleaseInputPassword }" autocomplete="off" class="layui-input" lay-affix="eye">
</div>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" id="login">${ .Login }</button>
</div>
</div>
</body>
</html>