the first commit, finish almost all function what i need

This commit is contained in:
杨黄林
2023-08-30 15:46:46 +08:00
commit 1e17465466
31 changed files with 5467 additions and 0 deletions

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

@@ -0,0 +1,48 @@
{
"User Manage": "User Manage",
"User": "User",
"Token": "Token",
"Notes": "Notes",
"Search": "Search",
"Reset": "Reset",
"New user": "New user",
"Remove user": "Remove user",
"Disable user": "Disable user",
"will take sometime to make effective": " will take sometime to make effective",
"Enable user": "Enable user",
"Remove": "Remove",
"Disable": "Disable",
"Enable": "Enable",
"Please input user account": "Please input user account",
"Please input user token": "Please input user token",
"Please input user notes": "Please input user notes",
"Status": "Status",
"Operation": "Operation",
"Confirm": "Confirm",
"Cancel": "Cancel",
"Confirm to remove user": "Confirm to remove user ?",
"Confirm to disable user": "Confirm to disable user ?",
"Confirm to enable user": "Confirm to enable user ?",
"Operate success": "Operate success",
"Operate failed": "Operate failed",
"Operate error": "Operate error",
"Other error": "Other error",
"Param error": "Param error",
"User exist": "User exist",
"Token cannot be empty": "Token cannot be empty",
"Please check at least one user": "Please Check at least one user",
"Operation confirm": "Operation confirm",
"Empty data": "Empty data",
"Allowed ports": "Allowed ports",
"Please input allowed ports": "Please input allowed ports, example: 8081, 9000-9010",
"Allowed domains": "Allowed domains",
"Please input allowed domains": "Please input allowed domains, example: web01.domain.com,web02.domain.com",
"Allowed subdomains": "Allowed subdomains",
"Please input allowed subdomains": "Please input allowed subdomains, example: web01,web02",
"Ports is invalid": "Ports is invalid",
"Domains is invalid": "Domains is invalid",
"Subdomains is invalid": "Subdomains is invalid",
"Comment is invalid": "Comment is invalid, it cannot include line breaks",
"Not limit": "Not limit",
"None": "None"
}

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

@@ -0,0 +1,48 @@
{
"User Manage": "用户管理",
"User": "用户名(user)",
"Token": "凭证(meta_token)",
"Notes": "备注",
"Search": "搜索",
"Reset": "重置",
"New user": "新增用户",
"Remove user": "删除用户",
"Disable user": "禁用用户",
"will take sometime to make effective": "需要一定时间才会生效",
"Enable user": "启用用户",
"Remove": "删除",
"Disable": "禁用",
"Enable": "启用",
"Please input user account": "请输入用户名(user)",
"Please input user token": "请输入Token(meta_token)",
"Please input user notes": "请输入备注",
"Status": "状态",
"Operation": "操作",
"Confirm": "确定",
"Cancel": "取消",
"Confirm to remove user": "确定删除用户 ?",
"Confirm to disable user": "确定禁用用户 ?",
"Confirm to enable user": "确定启用用户 ?",
"Operate success": "操作成功",
"Operate failed": "操作失败",
"Operate error": "操作异常",
"Other error": "其他异常",
"Param error": "参数异常",
"User exist": "用户已经存在",
"Token cannot be empty": "Token 不能为空",
"Please check at least one user": "请选中需要操作的用户",
"Operation confirm": "操作确认",
"Empty data": "无数据",
"Allowed ports": "允许端口",
"Please input allowed ports": "请输入允许使用的端口,如:8081, 9000-9010",
"Allowed domains": "允许域名",
"Please input allowed domains": "请输入允许使用的域名,如:web01.domain.com,web02.domain.com",
"Allowed subdomains": "允许子域名",
"Please input allowed subdomains": "请输入允许使用的端口,如:web01,web02",
"Ports is invalid": "端口不正确",
"Domains is invalid": "域名不正确",
"Subdomains is invalid": "子域名不正确",
"Comment is invalid": "备注不正确,不能包含换行",
"Not limit": "无限制",
"None": "无"
}

View File

@@ -0,0 +1,53 @@
body {
padding: 15px;
word-break: break-all;
}
#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;
}

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,563 @@
var $ = layui.$;
$(function () {
var apiType = {
Remove: 1,
Enable: 2,
Disable: 3
}
/**
* 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.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 (!/^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62}){1,3}$/.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, '')
};
}
/**
* load i18n language
* @param lang {{}}
*/
function langLoaded(lang) {
//set verify rules
var verifyRules = {
comment: function (value, item) {
var result = verifyComment(value);
if (!result.valid) {
return lang['CommentInvalid'];
}
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 lang['PortsInvalid'];
}
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 lang['DomainsInvalid'];
}
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 lang['SubdomainsInvalid'];
}
if (typeof item === "function") {
item && item(result.trim);
} else {
$(item).val(result.trim);
}
}
};
layui.form.verify(verifyRules);
layui.table.render({
elem: '#tokenTable',
url: '/tokens',
method: 'get',
where: {},
dataType: 'json',
editTrigger: 'dblclick',
page: navigator.language.indexOf("zh") === 0,
toolbar: '#toolbarTemplate',
defaultToolbar: false,
text: {none: lang['EmptyData']},
cols: [[
{type: 'checkbox'},
{field: 'user', title: lang['User'], width: 150, sort: true},
{field: 'token', title: lang['Token'], width: 200, sort: true, edit: true},
{field: 'comment', title: lang['Notes'], sort: true, edit: 'textarea'},
{field: 'ports', title: lang['AllowedPorts'], sort: true, edit: 'textarea'},
{field: 'domains', title: lang['AllowedDomains'], sort: true, edit: 'textarea'},
{field: 'subdomains', title: lang['AllowedSubdomains'], sort: true, edit: 'textarea'},
{
field: 'status',
title: lang['Status'],
width: 100,
templet: '<span>{{d.status? "' + lang['Enable'] + '":"' + lang['Disable'] + '"}}</span>',
sort: true
},
{title: lang['Operation'], width: 150, toolbar: '#operationTemplate'}
]]
});
/**
* 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);
}
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') {
if (value.trim() === '') {
layui.layer.msg(lang['TokenEmpty'])
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;
}
update(before, after);
});
layui.table.on('toolbar(tokenTable)', function (obj) {
var id = obj.config.id;
var checkStatus = layui.table.checkStatus(id);
switch (obj.event) {
case 'add':
addPopup();
break
case 'remove':
batchRemovePopup(checkStatus.data);
break
case 'disable':
batchDisablePopup(checkStatus.data);
break
case 'enable':
batchEnablePopup(checkStatus.data);
break
}
});
layui.table.on('tool(tokenTable)', function (obj) {
var data = obj.data;
switch (obj.event) {
case 'remove':
removePopup(data);
break;
case 'disable':
disablePopup(data);
break;
case 'enable':
enablePopup(data);
break
}
});
/**
* add user popup
*/
function addPopup() {
layui.layer.open({
type: 1,
title: lang['NewUser'],
area: ['500px'],
content: layui.laytpl(document.getElementById('addTemplate').innerHTML).render(),
btn: [lang['Confirm'], lang['Cancel']],
btn1: function (index) {
if (layui.form.validate('#addUserForm')) {
add(layui.form.val('addUserForm'), index)
}
},
btn2: function (index) {
layui.layer.close(index);
}
});
}
/**
* add user action
* @param data {{user:string, token:string, comment:string, status:boolean, ports:string, 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(lang['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, status:boolean, ports:string, domains:string, subdomains:string}} data before update
* @param after {{user:string, token:string, comment:string, status:boolean, ports:string, domains:string, subdomains:string}} data after update
*/
function update(before, after) {
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(lang['OperateSuccess']);
} else {
errorMsg(result);
}
},
complete: function () {
layui.layer.close(loading);
}
});
}
/**
* batch remove user popup
* @param data {[{user:string, token:string, comment:string, status:boolean, ports:string, domains:string, subdomains:string}]} user data list
*/
function batchRemovePopup(data) {
if (data.length === 0) {
layui.layer.msg(lang['ShouldCheckUser']);
return;
}
layui.layer.confirm(lang['ConfirmRemoveUser'], {
title: lang['OperationConfirm'],
btn: [lang['Confirm'], lang['Cancel']]
}, function (index) {
operate(apiType.Remove, data, index);
});
}
/**
* batch disable user popup
* @param data {[{user:string, token:string, comment:string, status:boolean, ports:string, domains:string, subdomains:string}]} user data list
*/
function batchDisablePopup(data) {
if (data.length === 0) {
layui.layer.msg(lang['ShouldCheckUser']);
return;
}
layui.layer.confirm(lang['ConfirmDisableUser'], {
title: lang['OperationConfirm'],
btn: [lang['Confirm'], lang['Cancel']]
}, function (index) {
operate(apiType.Disable, data, index);
});
}
/**
* batch enable user popup
* @param data {[{user:string, token:string, comment:string, status:boolean, ports:string, domains:string, subdomains:string}]} user data list
*/
function batchEnablePopup(data) {
if (data.length === 0) {
layui.layer.msg(lang['ShouldCheckUser']);
return;
}
layui.layer.confirm(lang['ConfirmEnableUser'], {
title: lang['OperationConfirm'],
btn: [lang['Confirm'], lang['Cancel']]
}, function (index) {
operate(apiType.Enable, data, index);
});
}
/**
* remove one user popup
* @param data {{user:string, token:string, comment:string, status:boolean, ports:string, domains:string, subdomains:string}} user data
*/
function removePopup(data) {
layui.layer.confirm(lang['ConfirmRemoveUser'], {
title: lang['OperationConfirm'],
btn: [lang['Confirm'], lang['Cancel']]
}, function (index) {
operate(apiType.Remove, [data], index);
});
}
/**
* disable one user popup
* @param data {{user:string, token:string, comment:string, status:boolean, ports:string, domains:string, subdomains:string}} user data
*/
function disablePopup(data) {
layui.layer.confirm(lang['ConfirmDisableUser'], {
title: lang['OperationConfirm'],
btn: [lang['Confirm'], lang['Cancel']]
}, function (index) {
operate(apiType.Disable, [data], index);
});
}
/**
* enable one user popup
* @param data {{user:string, token:string, comment:string, status:boolean, ports:string, domains:string, subdomains:string}} user data
*/
function enablePopup(data) {
layui.layer.confirm(lang['ConfirmEnableUser'], {
title: lang['OperationConfirm'],
btn: [lang['Confirm'], lang['Cancel']]
}, function (index) {
operate(apiType.Enable, [data], index);
});
}
/**
* operate actions
* @param type {apiType} action type
* @param data {[{user:string, token:string, comment:string, status:boolean, ports:string, 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 = ', ' + lang['RemoveUser'] + lang['TakeTimeMakeEffective'];
} else if (type === apiType.Disable) {
url = "/disable";
extendMessage = ', ' + lang['RemoveUser'] + lang['TakeTimeMakeEffective'];
} else if (type === apiType.Enable) {
url = "/enable";
} else {
layer.layer.msg(lang['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(lang['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 = lang['Other Error'];
if (result.code === 1)
reason = lang['ParamError'];
else if (result.code === 2)
reason = lang['UserExist'];
layui.layer.msg(lang['OperateFailed'] + ',' + reason)
}
/**
* click event
*/
$(document).on('click.search', '#searchBtn', function () {
reloadTable();
return false;
}).on('click.reset', '#resetBtn', function () {
$('#searchForm')[0].reset();
reloadTable();
return false;
});
}
var langLoading = layui.layer.load()
$.getJSON('/lang').done(langLoaded).always(function () {
layui.layer.close(langLoading);
});
});

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

124
assets/templates/index.html Normal file
View File

@@ -0,0 +1,124 @@
<!--suppress HtmlFormInputWithoutLabel -->
<!DOCTYPE html>
<head>
<title>${ .UserManage }</title>
<link rel="stylesheet" href="./static/layui/css/layui.css">
<link rel="stylesheet" href="./static/css/layui-theme-dark.css">
<link rel="stylesheet" href="./static/css/index.css">
<script src="./static/layui/layui.js"></script>
<script src="./static/js/index.js"></script>
<style>
.layui-table-cell:empty::after {
content: '${ .NotLimit }';
}
td[data-field=comment] .layui-table-cell:empty::after {
content: '${ .None }';
}
</style>
</head>
<body>
<form class="layui-form layui-row layui-col-space16" id="searchForm" lay-filter="searchForm">
<div class="layui-col-md3">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-username"></i>
</div>
<input type="text" name="user" value="" placeholder="${ .User }" class="layui-input" autocomplete="off"
lay-affix="clear">
</div>
</div>
<div class="layui-col-md3">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-vercode"></i>
</div>
<input type="text" name="token" placeholder="${ .Token }" class="layui-input" autocomplete="off"
lay-affix="clear">
</div>
</div>
<div class="layui-col-md3">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-note"></i>
</div>
<input type="text" name="comment" placeholder="${ .Notes }" class="layui-input" autocomplete="off"
lay-affix="clear">
</div>
</div>
<div class="layui-col-md3">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" id="searchBtn">${ .Search }</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" type="reset" id="resetBtn">${ .Reset }</button>
</div>
</div>
</form>
<table id="tokenTable" lay-filter="tokenTable"></table>
<script type="text/html" id="toolbarTemplate">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add">${ .NewUser }</button>
<button class="layui-btn layui-btn-sm" lay-event="remove">${ .RemoveUser }</button>
<button class="layui-btn layui-btn-sm" lay-event="disable">${ .DisableUser }</button>
<button class="layui-btn layui-btn-sm" lay-event="enable">${ .EnableUser }</button>
</div>
</script>
<script type="text/html" id="operationTemplate">
<div class="layui-clear-space">
<a class="layui-btn layui-btn-xs" lay-event="remove">${ .Remove }</a>
{{# if (d.status) { }}
<a class="layui-btn layui-btn-xs" lay-event="disable">${ .Disable }</a>
{{# } else { }}
<a class="layui-btn layui-btn-xs" lay-event="enable">${ .Enable }</a>
{{# } }}
</div>
</script>
<script type="text/html" id="addTemplate">
<div class="layui-form" id="addUserForm" lay-filter="addUserForm">
<div class="layui-form-item">
<label class="layui-form-label">${ .User }</label>
<div class="layui-input-block">
<input type="text" name="user" placeholder="${ .PleaseInputUserAccount }" autocomplete="off"
class="layui-input"
lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">${ .Token }</label>
<div class="layui-input-block">
<input type="text" name="token" lay-verify="required" placeholder="${ .PleaseInputUserToken }"
class="layui-input"
autocomplete="off">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">${ .Notes }</label>
<div class="layui-input-block">
<textarea name="comment" lay-verify="comment" placeholder="${ .PleaseInputUserNotes }"
autocomplete="off" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">${ .AllowedPorts }</label>
<div class="layui-input-block">
<textarea name="ports" lay-verify="ports" placeholder="${ .PleaseInputAllowedPorts }"
autocomplete="off" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">${ .AllowedDomains }</label>
<div class="layui-input-block">
<textarea name="domains" lay-verify="domains" placeholder="${ .PleaseInputAllowedDomains }"
autocomplete="off" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">${ .AllowedSubdomains }</label>
<div class="layui-input-block">
<textarea name="subdomains" lay-verify="subdomains" placeholder="${ .PleaseInputAllowedSubdomains }"
autocomplete="off" class="layui-textarea"></textarea>
</div>
</div>
</div>
</script>
</body>
</html>