UNPKG

internet-information-services

Version:

internet-information-services

800 lines (522 loc) 22.8 kB
<!DOCTYPE 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> "use strict"; 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> "use strict"; 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 = "&nbsp;&nbsp;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 = "&nbsp;&nbsp;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>