internet-information-services
Version:
internet-information-services
800 lines (522 loc) • 22.8 kB
HTML
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>Admin</title>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
background-color: #eeeeee;
}
.display-none {
display: none;
}
.select-none {
user-select: none;
}
.cursor-pointer {
cursor: pointer;
}
.vertical-top {
vertical-align: top;
}
.size-0_8em {
font-size: 0.8em;
}
body > .title {
text-align: right;
padding: 0.6em 1em;
}
body > table {
/*width: 100%;*/
margin: 0 auto;
}
body > table tr > td:first-child {
width: 30%;
background-color: #666;
user-select: none;
}
body > table > tbody > tr > td:last-child {
background-color: #f6f6f6;
vertical-align: top;
padding: 2em 4em 2em 2em;
}
body > table .children > span {
display: block;
padding-left: 2em;
color: #eeeeee;
text-decoration: none;
margin-bottom: 0.4em;
}
body > table .site {
cursor: pointer;
color: white;
text-decoration: underline;
padding: 0.4em 1em;
font-size: 1.2em;
display: inline-block;
}
#sitesNode button.edit {
float: right;
margin-top: 0.6em;
margin-bottom: 0.6em;
margin-right: 1em;
}
#sitesNode > div.ca {
clear: both;
text-align: right;
padding: 0.6em 0.8em 1em;
border-top: 1px solid #aaa;
}
#sitesNode > div.ca button {
font-size: 1em;
}
body > table input:checked + span {
display: none;
}
#detailsNode .title input {
font-size: 1.2em;
}
#detailsNode tr > td:first-child {
background-color: initial;
white-space: nowrap;
text-align: right;
}
#detailsNode .domain button.add {
margin-top: 1em;
}
#detailsNode td.title > button.del {
font-size: 1em;
}
#detailsNode .action {
padding: 0.4em 0;
font-size: 1.2em;
color: #666;
}
#detailsNode .action .a, #detailsNode .action .b:active {
text-shadow: 0 0 2px #666;
color: black;
}
#detailsNode .action .b:hover {
color: black;
}
#detailsNode .domain > div {
margin: 0.4em 0;
padding-bottom: 0.4em;
}
#detailsNode .domain > div:last-child {
border: 0;
}
#detailsNode .domain .https {
margin-top: 0.4em;
}
#detailsNode {
white-space: nowrap;
}
#caNode {
white-space: nowrap;
}
#caNode .file-list > div {
padding: 0.4em;
}
</style>
<script>;
function post(data, fn) {
var xhr = typeof XMLHttpRequest === "undefined" ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
xhr.onload = function (ev) {
if (typeof fn === "function")
fn(JSON.parse(ev.target.response));
else
console.log(ev.target.response);
};
xhr.open("post", "/", true);
var fd = new FormData(document.createElement("form"));
for (var attr in data)
if (data.hasOwnProperty(attr))
fd.append(attr, data[attr]);
xhr.send(fd);
}
String.prototype.replaceAll = function (oldStr, newStr) {
var str = this;
try {
oldStr = String(oldStr);
newStr = String(newStr);
if (newStr.indexOf(oldStr) < 0)
while (str.indexOf(oldStr) >= 0)
str = str.replace(oldStr, newStr);
} catch (e) {
return this;
}
return str;
};
</script>
</head>
<body>
<div class="title">
<a href="/logout">Logout</a>
</div>
<table>
<tbody>
<tr>
<td class="vertical-top">
<div id="sitesNode">
<div>
</div>
</div>
</td>
<td>
<table id="detailsNode" data-name="admin">
<tr>
<td></td>
<td class="title"><input title="" onchange="rename(this)"/>
<button class="del" onclick="delSite(this)">删除</button>
</td>
</tr>
<tr>
<td>状态:</td>
<td class="select-none action">
<span class="cursor-pointer" onclick="stopSite()">运行</span>
<span class="cursor-pointer b" onclick="restart()">重启站点</span>
<span class="cursor-pointer" onclick="stopSite(true)">停止</span>
</td>
</tr>
<tr>
<td class="vertical-top">域名:</td>
<td class="domain">
<div>
<button class="add">添加</button>
<button class="save">保存</button>
</div>
</td>
</tr>
</table>
<div id="caNode" class="display-none">
<div>
<input title="" placeholder="文件名"/><input type="file"/>
<button onclick="uploadCa(this)">上传</button>
</div>
<div class="file-list"></div>
</div>
</td>
</tr>
</tbody>
</table>
<script>;
var sites;
function rename(input) {
var name = detailsNode.dataset.name, _name = input.value.trim();
if (name === "admin") {
alert("不能修改 admin 的站点名");
input.value = name;
return;
}
if (_name === "admin") {
alert("不能修改为 admin 站点名");
input.value = name;
return;
}
if (sites.hasOwnProperty(_name)) {
alert("站点名 " + _name + " 已存在");
input.value = name;
return;
}
if (_name.length === 0) {
input.value = name;
return;
}
var temp = sites[name];
delete sites[name];
sites[_name] = temp;
var div = document.querySelector("#sitesNode > div[data-name='" + name + "']");
div.dataset.name = _name;
temp = div.children[0].firstElementChild;
temp.setAttribute("for", "_" + _name);
temp.innerHTML = _name;
temp.nextElementSibling.setAttribute("onclick", "edit('" + _name + "')");
div.children[1].id = "_" + _name;
detailsNode.dataset.name = _name;
post({type: "rename", new: _name, old: name});
}
function stopSite(stop) {
var name = detailsNode.dataset.name;
if (sites.hasOwnProperty(name)) {
if (stop) {
if (name === "admin" && !confirm("确定关闭 admin 站点么?\n关闭后将不能访问站点管理服务,开启请在 sites.json 里面设置admin状态,然后浏览admin访问域名[domain]:[port]/checkAdmin生效。")) {
return;
}
sites[name].stop = true;
document.querySelector("#detailsNode .action > span:nth-child(1)").classList.remove("a");
document.querySelector("#detailsNode .action > span:nth-child(3)").classList.add("a");
} else {
delete sites[name].stop;
document.querySelector("#detailsNode .action > span:nth-child(1)").classList.add("a");
document.querySelector("#detailsNode .action > span:nth-child(3)").classList.remove("a");
}
post({type: "stop", name: name, stop: !!stop});
}
}
function restart() {
var name = detailsNode.dataset.name;
if (sites.hasOwnProperty(name)) {
stopSite();
delete sites[name].stop;
document.querySelector("#detailsNode .action > span:nth-child(1)").classList.add("a");
document.querySelector("#detailsNode .action > span:nth-child(3)").classList.remove("a");
post({type: "restart", name: name});
}
}
function addSite() {
var name = prompt("name");
if (name && name.length && !sites.hasOwnProperty(name)) {
var div = document.createElement("div");
div.dataset.name = name;
div.innerHTML = '<div><label for="_{name}" class="site">{name}</label><button class="edit" onclick="edit(\'{name}\')">编辑</button></div><input id="_{name}" class="display-none" type="checkbox"><span class="children"></span>'
.replaceAll("{name}", name);
sitesNode.insertBefore(div, sitesNode.lastElementChild.previousElementSibling);
sites[name] = {domains: [], stop: true};
edit(name);
var data = {};
data[name] = sites[name];
post({type: "add", name: name, site: JSON.stringify(data[name])});
}
}
function delSite() {
var name = detailsNode.dataset.name;
if (confirm("确认删除 " + name + "?" + (name === 'admin' ? "\n删除后将不能访问站点管理服务,开启请在 site.json 里面添加。" : "")) && name) {
document.querySelector("#sitesNode > div[data-name='" + name + "']").remove();
delete sites[name];
edit("admin");
post({type: "del", site: name});
}
}
function edit(name) {
if (sites.hasOwnProperty(name)) {
post({type: "caList"}, function (list) {
var options = "";
list.forEach(function (value) {
if (value.length - value.lastIndexOf(".pfx") === 4 || value.length - value.lastIndexOf(".pem") === 4) {
options += "<option>" + value + "</option>";
}
});
caNode.classList.add("display-none");
detailsNode.classList.remove("display-none");
detailsNode.dataset.name = name;
detailsNode.dataset.options = options;
detailsNode.deleteList = [];
detailsNode.querySelector("td.title > input").value = name;
if (sites[name].stop) {
document.querySelector("#detailsNode .action > span:nth-child(1)").classList.remove("a");
document.querySelector("#detailsNode .action > span:nth-child(3)").classList.add("a");
} else {
document.querySelector("#detailsNode .action > span:nth-child(1)").classList.add("a");
document.querySelector("#detailsNode .action > span:nth-child(3)").classList.remove("a");
}
var node = detailsNode.querySelector(".domain"), div;
node.innerHTML = '';
sites[name].domains.forEach(function (value, i) {
div = document.createElement("div");
div.innerHTML = '<select title="" onchange="setProtocol(this)">' +
(value.protocol === "https" ? '<option>http</option><option selected>https</option>' : '<option selected>http</option><option>https</option>') +
'</select> :// <input title="" value="{domain}">'.replaceAll("{domain}", value.domain) +
' : <input type="number" value="{port}" title="" min="80" max="8889">'.replaceAll("{port}", value.port) +
' <button class="del" onclick="delDomain(this, ' + i + ')">删除</button>';
var https = document.createElement("div");
https.className = "https";
if (value.options) {
https.innerHTML = " ca: <select onchange='setCa(this)'>" + options + "</select> key: ";
if (value.options.pfx) {
https.querySelector("select").value = value.options.pfx;
var input = document.createElement("input");
input.value = value.options.passphrase;
input.type = "password";
https.appendChild(input);
} else if (value.options.cert) {
https.querySelector("select").value = value.options.cert;
var select = document.createElement("select");
select.innerHTML = options;
for (i = 0; i < select.children.length; i++)
if (select.children[i].value.length - select.children[i].value.lastIndexOf(".pfx") === 4) {
select.children[i].remove();
i--;
}
select.value = value.options.key;
https.appendChild(select);
}
div.appendChild(https);
}
node.appendChild(div);
});
div = document.createElement("div");
div.innerHTML = '<button class="add" onclick="addDomain(this)">添加</button> <button class="save" onclick="save(this)">保存</button>';
node.appendChild(div);
});
} else {
detailsNode.dataset.name = "";
detailsNode.querySelector("td.title > input").value = "";
detailsNode.querySelector("td.title > button.del").dataset.name = "";
detailsNode.querySelector(".domain").innerHTML = '<div><button class="add">添加</button> <button class="save">保存</button></div>';
}
}
function save(node) {
var temp, protocol, domain, port, name = detailsNode.dataset.name;
detailsNode.deleteList.sort().reverse().forEach(function (i) {
sites[name].domains.splice(i, 1);
});
var list = node.parentElement.parentElement.children;
var domains = [];
for (var site in sites)
if (sites.hasOwnProperty(site))
sites[site].domains.forEach(function (value) {
domains.push(value.domain + ":" + value.port);
});
var msg = "";
for (var i = 0; i < list.length - 1; i++) {
temp = list[i].firstElementChild;
protocol = temp.value;
temp = temp.nextElementSibling;
domain = temp.value.trim();
port = temp.nextElementSibling.value;
sites[name].domains[i] = sites[name].domains[i] || {};
if (protocol === "https") {
temp = temp.parentElement.lastElementChild.firstElementChild;
if (temp.value.length - temp.value.lastIndexOf(".pfx") === 4)
sites[name].domains[i].options = {pfx: temp.value, passphrase: temp.nextElementSibling.value};
else if (temp.value.length - temp.value.lastIndexOf(".pem") === 4)
sites[name].domains[i].options = {cert: temp.value, key: temp.nextElementSibling.value};
}
if (domain.length && port) {
if (sites[name].domains[i]) {
sites[name].domains[i].protocol = protocol;
sites[name].domains[i].domain = domain;
sites[name].domains[i].port = port;
} else {
temp = domain + ":" + port;
if (domains.indexOf(temp) < 0) {
domains.push(temp);
sites[name].domains.push({
protocol: protocol,
domain: domain,
port: port
});
} else
msg += temp + ",";
}
}
}
if (msg.length)
alert("这些域名已存在 " + msg);
edit(name);
post({type: "save", name: name, site: JSON.stringify(sites[name])});
loadSites();
}
function addDomain(node) {
var div = document.createElement("div");
div.innerHTML = '<select title="" onchange="setProtocol(this)"><option selected>http</option><option>https</option>' +
'</select> :// <input title="">' +
' : <input type="number" value="80" title="" min="80" max="8889">' +
' <button class="del" onclick="delDomain(this)">删除</button>';
node.parentElement.parentElement.insertBefore(div, node.parentElement);
}
function delDomain(node, i) {
node.parentElement.remove();
if (i !== undefined)
detailsNode.deleteList.push(i);
}
function loadSites(admin) {
post({type: "getSites"}, function (obj) {
var html = "";
sites = obj;
for (var site in sites)
if (sites.hasOwnProperty(site)) {
html += '<div data-name="{site}"><div><label for="_{site}" class="site">{site}</label>'.replaceAll("{site}", site) +
'<button class="edit" onclick="edit(\'{site}\')">编辑</button></div><input id="_{site}" class="display-none" type="checkbox" ><span class="children">'.replaceAll("{site}", site);
sites[site].domains.forEach(function (value) {
html += '<span>' + (value.protocol || "http") + "://" + value.domain + ":" + value.port + '</span>'
});
html += '</span></div>';
}
sitesNode.innerHTML = html + '<button class="edit" onclick="addSite()">添加</button><div class="ca"><button onclick="caManager()">证书管理</button></div>';
if (admin)
edit("admin");
});
}
function caManager() {
detailsNode.classList.add("display-none");
caNode.classList.remove("display-none");
post({type: "caList"}, function (list) {
var fl = caNode.querySelector(".file-list");
var html = "";
list.forEach(function (fn) {
html += '<div><button onclick="delFile(\'' + fn + '\', this)">删除</button> <span>' + fn + '</span></div>';
});
fl.innerHTML = html;
});
}
function setCa(select) {
var temp;
if (select.value.length - select.value.lastIndexOf(".pfx") === 4) {
temp = document.createElement("input");
temp.type = "password";
} else if (select.value.length - select.value.lastIndexOf(".pem") === 4) {
temp = document.createElement("select");
temp.innerHTML = select.innerHTML;
for (var i = 0; i < temp.children.length; i++)
if (temp.children[i].value.length - temp.children[i].value.lastIndexOf(".pfx") === 4) {
temp.children[i].remove();
i--;
}
}
if (temp)
select.parentElement.replaceChild(temp, select.nextElementSibling);
}
function setProtocol(select) {
if (select.value === "https") {
var div = document.createElement("div");
div.className = "https";
div.innerHTML = " ca: <select onchange='setCa(this)'>" + detailsNode.dataset.options + "</select> key: ";
var value = div.querySelector("select").value, temp;
if (value.length - value.lastIndexOf(".pfx") === 4)
temp = document.createElement("input");
else if (value.length - value.lastIndexOf(".pem") === 4) {
temp = document.createElement("select");
temp.innerHTML = div.querySelector("select").innerHTML;
for (var i = 0; i < temp.children.length; i++)
if (temp.children[i].value.length - temp.children[i].value.lastIndexOf(".pfx") === 4) {
temp.children[i].remove();
i--;
}
}
div.appendChild(temp);
select.parentElement.appendChild(div);
} else
select.parentElement.lastElementChild.remove();
}
function delFile(fn, node) {
node = node.parentElement;
post({type: "delFile", fileName: fn}, function (obj) {
if (obj.rc === 0)
node.remove();
});
}
function uploadCa(node) {
node = node.previousElementSibling;
var files = node.files;
node = node.previousElementSibling;
var value = node.value.trim();
if (files.length && value.length) {
post({type: "uploadCa", file: files[0], name: value}, function (obj) {
caManager();
});
}
}
loadSites(true);
</script>
</body>
</html>