Added web directory manager

+ Added web directory manager
+ Added dummy service expose proxy page
+ Moved ACME and renew to a new section in TLS management page
This commit is contained in:
Toby Chui
2023-09-24 23:44:48 +08:00
parent b63a0fc246
commit fd6ba56143
13 changed files with 782 additions and 188 deletions

View File

@@ -77,7 +77,6 @@
</tbody>
</table>
<button class="ui basic button" onclick="initManagedDomainCertificateList();"><i class="green refresh icon"></i> Refresh List</button>
<button class="ui basic button" onclick="openACMEManager();"><i class="yellow refresh icon"></i> Auto Renew (ACME) Settings</button>
</div>
<div class="ui message">
<h4><i class="info circle icon"></i> Sub-domain Certificates</h4>
@@ -85,11 +84,36 @@
depending on your certificates coverage, you might need to setup them one by one (i.e. having two seperate certificate for <code>a.example.com</code> and <code>b.example.com</code>).<br>
If you have a wildcard certificate that covers <code>*.example.com</code>, you can just enter <code>example.com</code> as server name in the form below to add a certificate.
</div>
<div class="ui divider"></div>
<h4>Certificate Authority (CA) and Auto Renew (ACME)</h4>
<p>Management features regarding CA and ACME</p>
<p>The default CA to use when create a new subdomain proxy endpoint with TLS certificate</p>
<div class="ui fluid form">
<div class="field">
<div class="ui selection dropdown" id="defaultCA">
<input type="hidden" name="defaultCA">
<i class="dropdown icon"></i>
<div class="default text">Let's Encrypt</div>
<div class="menu">
<div class="item" data-value="Let's Encrypt">Let's Encrypt</div>
<div class="item" data-value="Buypass">Buypass</div>
<div class="item" data-value="ZeroSSL">ZeroSSL</div>
</div>
</div>
</div>
<button class="ui basic icon button" onclick="saveDefaultCA();"><i class="ui blue save icon"></i> Save Settings</button>
</div><br>
<h5>Certificate Renew / Generation (ACME) Settings</h5>
<p>This tool provide you a graphical interface to setup auto certificate renew on your (sub)domains. You can also manually generate a certificate if one of your domain do not have certificate.</p>
<button class="ui basic button" onclick="openACMEManager();"><i class="yellow external icon"></i> Open ACME Tool</button>
</div>
<script>
var uploadPendingPublicKey = undefined;
var uploadPendingPrivateKey = undefined;
$("#defaultCA").dropdown();
//Delete the certificate by its domain
function deleteCertificate(domain){
if (confirm("Confirm delete certificate for " + domain + " ?")){
@@ -110,6 +134,11 @@
}
function saveDefaultCA(){
//TODO: Add an endpoint to handle default CA set and load
alert("WIP");
}
//List the stored certificates
function initManagedDomainCertificateList(){
$.get("/api/cert/list?date=true", function(data){

View File

@@ -14,6 +14,13 @@
<label>Root require TLS connection <br><small>Check this if your proxy root URL starts with https://</small></label>
</div>
</div>
<div class="ui horizontal divider">OR</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="useStaticWebServer" onchange="handleUseStaticWebServerAsRoot()">
<label>Use Static Web Server as Root <br><small>Check this if you prefer a more Apache Web Server like experience</small></label>
</div>
</div>
<br>
<button class="ui basic button" onclick="setProxyRoot()"><i class="teal home icon" ></i> Update Proxy Root</button>
<div class="ui divider"></div>
@@ -58,6 +65,30 @@
<script>
$("#advanceRootSettings").accordion();
function handleUseStaticWebServerAsRoot(){
let useStaticWebServer = $("#useStaticWebServer")[0].checked;
if (useStaticWebServer){
let staticWebServerURL = "127.0.0.1:" + $("#webserv_listenPort").val();
$("#proxyRoot").val(staticWebServerURL);
$("#proxyRoot").parent().addClass("disabled");
$("#rootReqTLS").parent().checkbox("set unchecked");
$("#rootReqTLS").parent().addClass("disabled");
//Check if web server is enabled. If not, ask if the user want to enable it
if (!$("#webserv_enable").parent().checkbox("is checked")){
confirmBox("Enable static web server now?", function(choice){
if (choice == true){
$("#webserv_enable").parent().checkbox("set checked");
}
});
}
}else{
$("#rootReqTLS").parent().removeClass("disabled");
$("#proxyRoot").parent().removeClass("disabled");
initRootInfo();
}
}
function initRootInfo(){
$.get("/api/proxy/list?type=root", function(data){
if (data == null){
@@ -67,7 +98,6 @@
checkRootRequireTLS(data.Domain);
}
});
}
initRootInfo();

View File

@@ -188,6 +188,8 @@
msgbox("Requesting certificate via Let's Encrypt...");
console.log("Trying to get a new certificate via ACME");
obtainCertificate(rootname);
}else{
msgbox("Proxy Endpoint Added");
}
});
}else{
@@ -467,7 +469,7 @@
});
// Obtain certificate from API, only support one domain
function obtainCertificate(domains) {
function obtainCertificate(domains, usingCa = "Let's Encrypt") {
let filename = "";
let email = acmeEmail;
if (acmeEmail == ""){
@@ -494,7 +496,7 @@
domains: domains,
filename: filename,
email: email,
ca: "Let's Encrypt",
ca: usingCa,
},
success: function(response) {
if (response.error) {

View File

@@ -78,6 +78,16 @@
<h2>Web Directory Manager</h2>
<p>Manage your files inside your web directory</p>
</div>
<div class="ui basic segment" style="display:none;" id="webdirManDisabledNotice">
<h4 class="ui header">
<i class="ui red times icon"></i>
<div class="content">
Web Directory Manager Disabled
<div class="sub header">Web Directory Manager has been disabled by the system administrator</div>
</div>
</h4>
</div>
<iframe id="webserv_dirManager" src="tools/fs.html" style="width: 100%; height: 800px; border: 0px; overflow-y: hidden;">
</iframe>
@@ -100,8 +110,14 @@
$("#webservRunningState").find(".webserv_status").text("Stopped");
}
}
function updateWebServState(){
$.get("/api/webserv/status", function(data){
//Clear all event listeners
$("#webserv_enableDirList").off("change");
$("#webserv_enable").off("change");
$("#webserv_listenPort").off("change");
setWebServerRunningState(data.Running);
if (data.EnableDirectoryListing){
@@ -113,6 +129,7 @@
$("#webserv_docRoot").val(data.WebRoot + "/html/");
if (!data.EnableWebDirManager){
$("#webdirManDisabledNotice").show();
$("#webserv_dirManager").remove();
}
@@ -141,8 +158,8 @@
}
});
}
});
$("#webserv_enableDirList").off("change").on("change", function(){
let enable = $(this)[0].checked;
$.ajax({
@@ -160,18 +177,46 @@
});
$("#webserv_listenPort").off("change").on("change", function(){
let newPort = $(this).val();
$.ajax({
url: "/api/webserv/setPort",
method: "POST",
data: {"port": newPort},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false);
//Check if the new value is same as listening port
let rpListeningPort = $("#incomingPort").val();
if (rpListeningPort == newPort){
confirmBox("This setting might cause port conflict. Continue Anyway?", function(choice){
if (choice == true){
//Continue anyway
$.ajax({
url: "/api/webserv/setPort",
method: "POST",
data: {"port": newPort},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false);
}else{
msgbox("Listening port updated");
}
updateWebServState();
}
});
}else{
msgbox("Listening port updated");
//Cancel. Restore to previous value
updateWebServState();
msgbox("Setting restored");
}
}
})
});
}else{
$.ajax({
url: "/api/webserv/setPort",
method: "POST",
data: {"port": newPort},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false);
}else{
msgbox("Listening port updated");
}
}
})
}
});
})
}

View File

@@ -0,0 +1,10 @@
<div class="standardContainer">
<div class="ui basic segment">
<h2>Service Expose Proxy</h2>
<p>Expose your local test-site on the internet with single command</p>
</div>
<div class="ui message">
<h4>Work In Progress</h4>
We are looking for someone to help with implementing this feature in Zoraxy. <br>If you know how to write Golang and want to contribute, feel free to create a pull request to this feature!
</div>
</div>

View File

@@ -62,7 +62,7 @@
<a class="item" tag="gan">
<i class="simplistic globe icon"></i> Global Area Network
</a>
<a class="item" tag="">
<a class="item" tag="zgrok">
<i class="simplistic podcast icon"></i> Service Expose Proxy
</a>
<a class="item" tag="tcpprox">
@@ -117,6 +117,9 @@
<!-- Global Area Networking -->
<div id="gan" class="functiontab" target="gan.html"></div>
<!-- Service Expose Proxy -->
<div id="zgrok" class="functiontab" target="zgrok.html"></div>
<!-- TCP Proxy -->
<div id="tcpprox" class="functiontab" target="tcpprox.html"></div>

View File

@@ -78,10 +78,6 @@
<br>
</div>
<div id="propertiesView" class="small">
<div class="preview" style="margin-top: 0.4em;" align="center">
<img class="ui image" style="max-height: 300px;">
<audio src="" controls style="display:none; width: 100%;"></audio>
</div>
<h3 class="ui header" style="margin-top: 0.4em;">
<span class="filename" style="word-break: break-all;" locale="sidebar/default/nofileselected">No File Selected</span>
<div class="sub header vpath" style="word-break: break-all;" locale="sidebar/default/instruction">Select a file to view file properties</div>
@@ -91,7 +87,6 @@
</tbody>
</table>
<button id="loadPreviewButton" class="ui small fluid basic disabled button" onclick="loadPreview();">Load Preview</button>
</div>
<div id="uploadProgressBar">
<div class="ui small indicating progress" style="margin-bottom: 0px !important; border-radius: 0 !important;">
@@ -116,9 +111,7 @@
//File operations
let cutMode = false;
let cutPendingFilepath = [];
let copyBuffering = false;
let copySrcFilename = "";
let copyPendingFile = undefined;
let copyPendingFiles = [];
//History
let dirHistory = [];
@@ -479,9 +472,9 @@
msgbox(data.error, false);
}else{
data.forEach(function(filedata){
let isDir = filedata.IsDir;
let filename = filedata.Filename;
let filesize = filedata.Filesize;
let isDir = filedata.isDir;
let filename = filedata.filename;
let filesize = filedata.size;
if (isDir){
$("#folderList").append(`<div class="fileObject item" draggable="true" filename="${filename}" filepath="${path + filename}" ondblclick="openthis(this,event);" type="folder">
<span style="display:inline-block !important;word-break: break-all; width:100%;" class="normal object">
@@ -527,46 +520,7 @@
listDir(targetPath);
}else{
let ext = $(target).attr("filepath").split(".").pop();
if (ext == "txt" || ext == "md"){
//Open with markdown editor
let hash = encodeURIComponent(JSON.stringify({
"filename": $(target).attr("filename"),
"filepath": $(target).attr("filepath")
}))
window.open("mde/index.html#" + hash);
}else if (ext == "jpg" || ext == "jpeg" || ext == "png" || ext == "gif" || ext == "webp"){
//Open with photo viewer
let hash = encodeURIComponent(JSON.stringify({
"filename": $(target).attr("filename"),
"filepath": $(target).attr("filepath")
}))
window.open("photo.html#" + hash);
}else if (ext == "mp3" || ext == "aac" || ext == "ogg"){
//Open with music player
let hash = encodeURIComponent(JSON.stringify({
"filename": $(target).attr("filename"),
"filepath": $(target).attr("filepath")
}))
window.open("music.html#" + hash);
}else if (ext == "mp4" || ext == "webm"){
//Open with video player
let hash = encodeURIComponent(JSON.stringify({
"filename": $(target).attr("filename"),
"filepath": $(target).attr("filepath")
}))
window.open("video.html#" + hash);
}else if (isCodeFiles(ext)){
//Open with notepad
//**Notes the array wrapper in JSON object**
let hash = encodeURIComponent(JSON.stringify([{
"filename": $(target).attr("filename"),
"filepath": $(target).attr("filepath")
}]))
window.open("notepad/index.html#" + hash);
}else{
window.open("/api/fs/download?file=" + $(target).attr("filepath") + "&preview=true");
}
window.open("/api/fs/download?file=" + $(target).attr("filepath") + "&preview=true");
}
}
@@ -600,18 +554,6 @@
let ext = filenameOnly.pop();
filenameOnly = filenameOnly.join(".");
/*
//Skip checking as modern FAT32 file systems can store as LONG FILENAME
if (filenameOnly.length > 8){
msgbox("File name too long (8 char max)", false);
return;
}
if (ext.length > 3){
msgbox("File extension too long (3 char max)", false);
return
}
*/
//OK! Create the file
const blob = new Blob(["\n"], { type: 'text/plain' });
@@ -622,18 +564,13 @@
}
function newFolder(){
var folderName = window.prompt("Name for new folder (8 char max): ", "Folder");
var folderName = window.prompt("Name for new folder: ", "Folder");
if (folderName.indexOf("/") >= 0){
//Contains /. Reject
msgbox("Folder name cannot contain path seperator", false);
return;
}
if (folderName.length > 8){
msgbox("Folder name too long (8 char max)", false);
return;
}
$.post("/api/fs/newFolder?path=" + currentPath + folderName, function(data){
if (data.error != undefined){
msgbox(data.error, false);
@@ -656,20 +593,12 @@
msgbox("File name cannot contain path seperator", false);
return;
}
/*
//FAT32 allows filename longer than 8.3
if (!isFilenameValid(newName)){
msgbox("File name too long (8 char max)", false);
return;
}
*/
if (newName && newName != oldName) {
// User entered a new name, perform renaming logic here
console.log(oldPath, currentPath + newName);
$.ajax({
url: "/api/fs/move?src=" + oldPath + "&dest=" + currentPath + newName,
url: "/api/fs/move?srcpath=" + oldPath + "&destpath=" + currentPath + newName,
method: "POST",
success: function(data){
if (data.error != undefined){
@@ -729,7 +658,7 @@
File Size
</td>
<td>
${bytesToSize(data.filesize)}
${bytesToSize(data.size)}
</td>
</tr><tr>
<td style="${styleOverwrite}">
@@ -764,19 +693,6 @@
</td>
</tr>`);
//Folder is not previewable
$("#propertiesView").find(".preview").find("img").attr("xsrc", "");
$("#loadPreviewButton").addClass("disabled");
}else{
let ext = data.filepath.split(".").pop();
if (isPreviewable("." + ext)){
$("#propertiesView").find(".preview").find("img").attr("xsrc", "/api/fs/download?preview=true&file=" + data.filepath);
$("#loadPreviewButton").removeClass("disabled");
}else{
$("#propertiesView").find(".preview").find("img").attr("xsrc", "");
$("#loadPreviewButton").addClass("disabled");
}
}
})
}
@@ -825,7 +741,7 @@
}
}
function humanFileSize(bytes, si=false, dp=1) {
function humanFileSize(bytes, si=true, dp=1) {
const thresh = si ? 1000 : 1024;
if (Math.abs(bytes) < thresh) {
@@ -888,7 +804,7 @@
$("#pasteButton").addClass("disabled");
console.log('Selected file:', file);
var formdata = new FormData();
formdata.append("file1", file);
formdata.append("file", file);
var ajax = new XMLHttpRequest();
ajax.upload.addEventListener("progress", progressHandler, false);
ajax.addEventListener("load", function(event){
@@ -909,7 +825,7 @@
}, false); // doesnt appear to ever get called even upon success
ajax.addEventListener("error", errorHandler, false);
ajax.addEventListener("abort", abortHandler, false);
ajax.open("POST", "/upload?dir=" + dir);
ajax.open("POST", "/api/fs/upload?dir=" + dir);
ajax.send(formdata);
}
@@ -961,48 +877,23 @@
//Copy file
function copy(){
//TODO: Add copy API
if ($(".fileObject.selected").length == 0){
//No file selected
msgbox("No file selected", false);
return;
}
if ($(".fileObject.selected").length > 1){
msgbox("WebStick can only support 1 file copy at a time", false);
return;
}
if ($(".fileObject.selected").attr('type') == "folder"){
msgbox("Folder copy is not supported", false);
return;
}
let url = "/api/fs/download?file=" + $(".fileObject.selected").attr("filepath");
let filename = $(".fileObject.selected").attr("filename");
cutMode = false;
copyBuffering = true;
copySrcFilename = filename;
console.log("Buffering " + filename + " from " + url);
msgbox("Allocating memory for copy...")
$("#pasteButton").addClass("disabled");
$("#copyButton").addClass("disabled");
// Fetch the content from the URL
fetch(url).then(response => response.blob()).then(blob => {
// Create a new File object from the Blob
copyPendingFile = (blob);
msgbox("File Ready to Paste");
$("#pasteButton").removeClass("disabled");
$("#copyButton").removeClass("disabled");
copyBuffering = false;
}).catch(error => {
msgbox("Copy Allocation Failed", false);
$("#pasteButton").removeClass("disabled");
$("#copyButton").removeClass("disabled");
copyBuffering = false;
let selectedFiles = [];
$(".fileObject.selected").each(function(){
let filepath = $(this).attr("filepath");
selectedFiles.push(filepath);
console.log(filepath);
});
copyPendingFiles = selectedFiles;
cutMode = false;
msgbox(`${selectedFiles.length} files ready to paste`, true)
}
function fileExistsInThisFolder(filename){
@@ -1024,7 +915,7 @@
let filepath = fileToPaste.filepath;
$.ajax({
url: "/api/fs/move?src=" + filepath + "&dest=" + currentPath + filename,
url: "/api/fs/move?srcpath=" + filepath + "&destpath=" + currentPath + filename,
method: "POST",
success: function(data){
if (data.error != undefined){
@@ -1041,32 +932,42 @@
});
}else{
//Copy and Paste
if (copyBuffering){
msgbox("Copy buffer allocation in progress", false);
return;
}
let pasteFilename = copySrcFilename;
let counter = 1;
while(fileExistsInThisFolder(pasteFilename)){
let nameOnly = copySrcFilename.split(".");
let ext = nameOnly.pop();
nameOnly = nameOnly.join(".");
pasteFilename = nameOnly + "-" + counter + "." + ext;
counter++;
}
//Change the name if there is name clash
const file = new File([copyPendingFile], pasteFilename);
//Upload the file
msgbox("Writing file to SD card...");
handleFile(file, currentPath, function(){
msgbox("File Pasted");
});
copyFirstItemInQueueUntilAllCopied();
}
}
function copyFirstItemInQueueUntilAllCopied(){
let file = copyPendingFiles.shift();
let startingDir = currentPath;
$.ajax({
url: "/api/fs/copy",
method: "POST",
data: {
"srcpath": file,
"destpath": currentPath
},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false);
}else{
if (copyPendingFiles.length > 0){
//Contine to copy and paste the files
copyFirstItemInQueueUntilAllCopied();
if (startingDir == currentPath){
refresh();
}
}else{
//All copy operation done
msgbox("Files copied");
if (startingDir == currentPath){
refresh();
}
}
}
}
})
}
function sortFileList(){
sortFileObjects("folderList");
sortFileObjects("fileList");