globular_admin
Version:
Administration panel for a Globular server.
821 lines (734 loc) • 24.5 kB
text/typescript
import { Panel } from "./panel";
import { GetAllFilesInfo, eventHub, getErrorMessage, renameFile, deleteDir, deleteFile, downloadDir, createDir, downloadFileHttp } from "./backend";
import { randomUUID } from "./utility";
import { PermissionExplorer } from "./permissionPanel";
/**
* The file panel give necessary functionalities to manages server files.
*/
export class FileManager extends Panel {
private pathNavigator: PathNavigator;
private fileNavigator: FileNavigator;
private permissionExplorer: PermissionExplorer; // delete file, rename fiel etc...
private webRoot: any; // contain all file information.
private directories: Map<string, any>;
constructor(id: string) {
super(id);
this.div.element.className = "row";
this.directories = new Map<string, any>();
// Create the path navigator.
this.pathNavigator = new PathNavigator(this.div);
// Create the file navigator.
this.fileNavigator = new FileNavigator(this.div);
// Create the permission explorer.
this.permissionExplorer = new PermissionExplorer("file_permission_explorer", this.div);
// first off all will get the file info that contain all directory and file information.
GetAllFilesInfo(
(filesInfo: any) => {
this.webRoot = filesInfo;
// Here I will keep all directory reference into the directories map.
let setDirectories = (dir: any) => {
for (var i = 0; i < dir.files.length; i++) {
let file = dir.files[i];
if (file.files != undefined) {
this.directories.set(file.path, file);
setDirectories(file);
}
}
};
this.setDirectory(this.webRoot);
// put all directories in the directories map.
this.directories.set(this.webRoot.path, this.webRoot);
setDirectories(this.webRoot);
// Emit when the user click on file icon.
eventHub.subscribe(
"set_dir_event",
(uuid: string) => { },
(evt: any) => {
// Set the dir to display.
this.setDirectory(evt.dir);
},
true
);
// Emit when user click on the path
eventHub.subscribe(
"set_file_path_event",
(uuid: string) => { },
(evt: any) => {
// Set the dir to display.
// Here I must retreive the directory from the given path.
this.setDirectory(this.directories.get(evt.path));
},
true
);
// When new file is created.
eventHub.subscribe(
"new_dir_event",
(uuid: string) => { },
(evt: any) => {
GetAllFilesInfo(
(filesInfo: any) => {
this.directories = new Map<string, any>();
this.webRoot = filesInfo;
// put all directories in the directories map.
this.directories.set(this.webRoot.path, this.webRoot);
setDirectories(this.webRoot);
this.setDirectory(this.directories.get(evt.path));
// Now I will select the new dir to set it name in edit mode.
},
(err: any) => {
M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
}
);
},
true
);
// emit delete dire event.
eventHub.subscribe(
"delete_file_event",
(uuid: string) => { },
(evt: any) => {
GetAllFilesInfo(
(filesInfo: any) => {
this.directories = new Map<string, any>();
this.webRoot = filesInfo;
// put all directories in the directories map.
this.directories.set(this.webRoot.path, this.webRoot);
setDirectories(this.webRoot);
this.setDirectory(this.directories.get(evt.path));
},
(err: any) => {
M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
}
);
},
true
);
// emit when a user change a file name.
eventHub.subscribe(
"rename_file_event",
(uuid: string) => { },
(evt: any) => {
GetAllFilesInfo(
(filesInfo: any) => {
this.directories = new Map<string, any>();
this.webRoot = filesInfo;
// put all directories in the directories map.
this.directories.set(this.webRoot.path, this.webRoot);
setDirectories(this.webRoot);
this.setDirectory(this.directories.get(evt.path));
},
(err: any) => {
M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
}
);
},
true
);
},
(err: any) => {
M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
}
);
}
setDirectory(dir: any) {
this.pathNavigator.setPath(dir.path);
this.fileNavigator.setDir(dir);
this.permissionExplorer.setRessource(dir)
}
// Here I will react to login information...
onlogin(data: any) {
// overide...
}
onlogout() {
// overide...
}
}
/**
* Navigator from the path.
*/
class PathNavigator extends Panel {
private editable: boolean;
private path: string;
constructor(parent: any) {
super(randomUUID())
this.div = parent
.appendElement({
tag: "nav",
class: "card col s12 /*m10 offset-m1*/ indigo darken-4"
})
.down()
.appendElement({ tag: "div", class: "nav-wrapper" })
.down()
.appendElement({ tag: "div" })
.down();
}
// Set the active path.
setPath(path: string) {
this.div.removeAllChilds();
this.path = path;
let values = path.split("/");
let div = this.div.appendElement({ tag: "div", class: "col s12" }).down()
for (var i = 0; i < values.length; i++) {
if (values[i].length > 0) {
let lnk = div
.appendElement({
tag: "a",
innerHtml: values[i],
class: "breadcrumb"
})
.down();
lnk.element.onmouseenter = function () {
this.style.cursor = "pointer";
};
lnk.element.onmouseleave = function () {
this.style.cursor = "default";
};
let index = i + 1;
lnk.element.onclick = () => {
let path_ = "/";
for (var j = 1; j < index; j++) {
path_ += values[j];
if (j < index - 1) {
path_ += "/";
}
}
eventHub.publish("set_file_path_event", { path: path_ }, true);
};
}
}
if (this.editable) {
// Set the col value.
div.element.className = "col s9"
let downloadDirBtn = this.div
.appendElement({
tag: "i",
class: "Small material-icons col s1",
innerHtml: "file_download",
title: "download " + path + " as .tgz archive file"
})
.down();
let uploadFileBtn = this.div
.appendElement({
tag: "input",
type: "file",
id: "file_input",
style: "display: none;",
multiple: true
})
.appendElement({
tag: "i",
class: "Small material-icons col s1",
innerHtml: "file_upload",
title: "upload a file in " + path
})
.down();
let createDirBtn = this.div
.appendElement({
tag: "i",
class: "Small material-icons col s1",
innerHtml: "create_new_folder",
title: "create a subdirectory in " + path
})
.down();
// Now The actions.
downloadDirBtn.element.onmouseenter = uploadFileBtn.element.onmouseenter = createDirBtn.element.onmouseenter = function () {
this.style.cursor = "pointer"
}
downloadDirBtn.element.onmouseleave = uploadFileBtn.element.onmouseleave = createDirBtn.element.onmouseleave = function () {
this.style.cursor = "default"
}
// The dowload file action.
downloadDirBtn.element.onclick = () => {
// Download the folder as a tar.gz file.
console.log("download dir " + this.path)
downloadDir(this.path,
() => {
console.log("file was download successfully!")
},
(err: any) => {
M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
})
}
// The create directory button.
createDirBtn.element.onclick = () => {
createDir(path, (dirName: string) => {
// publish new dir event.
eventHub.publish(
"new_dir_event",
{ path: path, name: dirName },
true
);
},
(err: any) => {
M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
})
}
// T
this.div.getChildById("file_input").element.onchange = (e: any) => {
let path = this.path.replace("/webroot", ""); // remove the /webroot part.
if (path.length == 0) {
path = "/";
}
const fd = new FormData();
// add all selected files
for (var i = 0; i < e.target.files.length; i++) {
let file = e.target.files[i];
fd.append("multiplefiles", file, file.name);
fd.append("path", path)
}
// create the request
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
// we done! I will use the rename file event to refresh the directory...
eventHub.publish("rename_file_event", { path: this.path }, true);
}
};
// path to server would be where you'd normally post the form to
xhr.open('POST', "/uploads", true);
xhr.setRequestHeader("token", localStorage.getItem("user_token"))
xhr.setRequestHeader("application", "admin")
xhr.setRequestHeader("domain", window.location.hostname)
xhr.onerror = (err: any) => {
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) { //if complete
if (xhr.status === 200) { //check if "OK" (200)
//success
} else {
M.toast({ html: "Permission denied to upload file " + path, displayLength: 2000 });
}
}
}
xhr.send(fd);
}
// The upload file btn.
uploadFileBtn.element.onclick = () => {
this.div.getChildById("file_input").element.click()
}
}
}
// Here I will react to login information...
onlogin(data: any) {
// overide...
this.editable = true;
this.setPath(this.path)
}
onlogout() {
// overide...
this.editable = false;
this.setPath(this.path) // reset to actual path.
}
}
class FilePanel {
private div: any;
constructor(parent: any) {
this.div = parent
.appendElement({
tag: "div",
class: "row",
style: "margin: 0px; padding: 5px;"
})
.down();
}
/**
* Set the diplayed file/folder in the panel.
* @param file
*/
setFile(file: any, editable: boolean) {
this.div.removeAllChilds();
// In that case I will set the
if (editable) {
let ico: any;
if (file.files != undefined) {
ico = this.div
.appendElement({
tag: "i",
class: "Small material-icons col s1",
innerHtml: "folder"
})
.down();
} else {
ico = this.div
.appendElement({
tag: "i",
class: "Small material-icons col s1",
innerHtml: "insert_drive_file"
})
.down();
}
let file_edit_input = randomUUID()
let file_edit_lnk = randomUUID()
let file_save_lnk = randomUUID()
let file_lnk = randomUUID()
// The file name link...
this.div
.appendElement({
tag: "div",
class: "col s5"
})
.down()
.appendElement({
tag: "a",
id: file_lnk,
heref: "javascript:void(0)",
innerHtml: file.name,
class: "col s10"
})
.appendElement({
tag: "i",
id: file_edit_lnk,
class: "Small material-icons col s2",
innerHtml: "edit"
})
.appendElement({
tag: "input",
id: file_edit_input,
class: "col s10",
style: "display: none",
value: file.name
})
.appendElement({
tag: "i",
id: file_save_lnk,
class: "Small material-icons col s2",
style: "display: none",
innerHtml: "save"
});
let lnk = this.div.getChildById(file_lnk);
let edit_lnk = this.div.getChildById(file_edit_lnk);
let edit_input = this.div.getChildById(file_edit_input);
let save_lnk = this.div.getChildById(file_save_lnk);
/**
* Display name input.
*/
edit_lnk.element.onclick = () => {
lnk.element.style.display = "none";
edit_lnk.element.style.display = "none";
edit_input.element.style.display = "";
save_lnk.element.style.display = "";
edit_input.element.focus();
edit_input.element.setSelectionRange(
0,
edit_input.element.value.length
);
};
/**
* Rename the file or directory.
*/
save_lnk.element.onclick = () => {
lnk.element.style.display = "";
edit_lnk.element.style.display = "";
edit_input.element.style.display = "none";
save_lnk.element.style.display = "none";
// Save the file...
let path = file.path.substring(0, file.path.lastIndexOf("/"));
renameFile(
path,
edit_input.element.value,
file.name,
() => {
M.toast({
html:
"file " +
lnk.element.innerHTML +
" was rename to " +
edit_input.element.value,
displayLength: 2000
});
lnk.element.innerHTML = edit_input.element.value;
// emit event.
eventHub.publish(
"rename_file_event",
{ file: file, path: path },
true
);
},
(err: any) => {
M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
}
);
};
let lastModified = new Date(file.last_modified * 1000);
this.div.appendElement({
tag: "div",
class: "col s6 m3",
innerHtml:
lastModified.toLocaleDateString() +
" " +
lastModified.toLocaleTimeString()
});
let fileSizeDiv = this.div
.appendElement({
tag: "div",
class: "col s6 m2"
})
.down();
if (file.files == undefined) {
if (file.size > 1024) {
if (file.size > 1024 * 1024) {
if (file.size > 1024 * 1024 * 1024) {
let fileSize = file.size / (1024 * 1024 * 1024);
fileSizeDiv.element.innerHTML = fileSize.toFixed(0) + " Gb";
} else {
let fileSize = file.size / (1024 * 1024);
fileSizeDiv.element.innerHTML = fileSize.toFixed(0) + " Mb";
}
} else {
let fileSize = file.size / 1024;
fileSizeDiv.element.innerHTML = fileSize.toFixed(0) + " Kb";
}
} else {
fileSizeDiv.element.innerHTML = file.size + " bytes";
}
ico.element.onclick = () => {
eventHub.publish("set_file_event", { id: "file_permission_explorer", file: file }, true);
}
} else {
// publish local event.
ico.element.onclick = () => {
eventHub.publish("set_dir_event", { dir: file }, true);
};
}
// I will append a delete button in that particular case.
let deleteFileBtn = this.div
.appendElement({
tag: "i",
class: "Small material-icons col s1",
innerHtml: "delete"
})
.down();
save_lnk.element.onmouseenter = deleteFileBtn.element.onmouseenter = edit_lnk.element.onmouseenter = ico.element.onmouseenter = lnk.element.onmouseenter = function () {
this.style.cursor = "pointer";
};
save_lnk.element.onmouseleave = deleteFileBtn.element.onmouseleave = edit_lnk.element.onmouseleave = ico.element.onmouseleave = lnk.element.onmouseleave = function () {
this.style.cursor = "default";
};
deleteFileBtn.element.onclick = () => {
let path = file.path.substring(0, file.path.lastIndexOf("/"));
if (file.files != undefined) {
// here I will delete a directory
deleteDir(
file.path,
() => {
M.toast({
html: "Dir " + file.name + " was deleted!",
displayLength: 2000
});
lnk.element.innerHTML = edit_input.element.value;
// emit event.
eventHub.publish(
"delete_file_event",
{ file: file, path: path },
true
);
},
(err: any) => {
M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
}
);
} else {
// here I will delete a file.
deleteFile(
file.path,
() => {
M.toast({
html: "file " + file.name + " was deleted!",
displayLength: 2000
});
lnk.element.innerHTML = edit_input.element.value;
// emit event.
eventHub.publish(
"delete_file_event",
{ file: file, path: path },
true
);
},
(err: any) => {
M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
}
);
}
};
} else {
// Not editable.
let ico: any;
if (file.files != undefined) {
ico = this.div
.appendElement({
tag: "i",
class: "Small material-icons col s1",
innerHtml: "folder"
})
.down();
} else {
ico = this.div
.appendElement({
tag: "i",
class: "Small material-icons col s1",
innerHtml: "insert_drive_file"
})
.down();
}
// The file name link...
let lnk = this.div
.appendElement({
tag: "a",
heref: "javascript:void(0)",
innerHtml: file.name,
class: "col s12 m6",
title: "Download file."
})
.down();
let lastModified = new Date(file.last_modified * 1000);
this.div.appendElement({
tag: "div",
class: "col s6 m3",
innerHtml:
lastModified.toLocaleDateString() +
" " +
lastModified.toLocaleTimeString()
});
let fileSizeDiv = this.div
.appendElement({
tag: "div",
class: "col s6 m2"
})
.down();
if (file.files == undefined) {
if (file.size > 1024) {
if (file.size > 1024 * 1024) {
if (file.size > 1024 * 1024 * 1024) {
let fileSize = file.size / (1024 * 1024 * 1024);
fileSizeDiv.element.innerHTML = fileSize.toFixed(0) + " Gb";
} else {
let fileSize = file.size / (1024 * 1024);
fileSizeDiv.element.innerHTML = fileSize.toFixed(0) + " Mb";
}
} else {
let fileSize = file.size / 1024;
fileSizeDiv.element.innerHTML = fileSize.toFixed(0) + " Kb";
}
} else {
fileSizeDiv.element.innerHTML = file.size + " bytes";
}
ico.element.onclick = () => {
eventHub.publish("set_file_event", { id: "file_permission_explorer", file: file }, true);
}
// On follow link
lnk.element.onclick = () => {
let path = file.path.replace("/webroot", ""); // remove the /webroot part.
if (path.length == 0) {
path = "/";
}
// Download the file in question.
downloadFileHttp(window.location.origin + path, file.name, () => {
})
}
lnk.element.onmouseenter = function () {
this.style.cursor = "pointer";
};
lnk.element.onmouseleave = function () {
this.style.cursor = "default";
};
} else {
// publish local event.
ico.element.onclick = () => {
eventHub.publish("set_dir_event", { dir: file }, true);
};
}
ico.element.onmouseenter = function () {
this.style.cursor = "pointer";
};
ico.element.onmouseleave = function () {
this.style.cursor = "default";
};
}
}
}
/**
* Navigate from the file.
*/
class FileNavigator extends Panel {
private editable: boolean;
private currentDirectory: any;
constructor(parent: any) {
super(randomUUID());
this.div.element.className = "card col s12 /*m10 offset-m1*/";
this.div.element.style = "padding: 10px;";
parent.appendElement(this.div);
}
setHeader() {
if (!this.editable) {
// Set the header...
this.div
.appendElement({ tag: "div", class: "row hide-on-small-only" })
.down()
.appendElement({
tag: "div",
class: "col s12 m7",
id: "file_name_div",
innerHtml: "Name",
style: "border-right: 1px solid lightgray;"
})
.appendElement({
tag: "div",
class: "col s6 m3",
innerHtml: "Date modified",
style: "border-right: 1px solid lightgray;"
})
.appendElement({
tag: "div",
class: "col s6 m2",
innerHtml: "Size"
});
} else {
this.div
.appendElement({ tag: "div", class: "row hide-on-small-only" })
.down()
.appendElement({
tag: "div",
class: "col s12 m6",
id: "file_name_div",
innerHtml: "Name",
style: "border-right: 1px solid lightgray;"
})
.appendElement({
tag: "div",
class: "col s6 m3",
innerHtml: "Date modifed",
style: "border-right: 1px solid lightgray;"
})
.appendElement({
tag: "div",
class: "col s6 m2",
innerHtml: "Size"
})
.appendElement({
tag: "div",
class: "col s1"
});
}
}
setDir(dir: any) {
this.currentDirectory = dir;
// Clear the div.
this.div.removeAllChilds();
this.setHeader();
for (var i = 0; i < dir.files.length; i++) {
let filePanel = new FilePanel(this.div);
filePanel.setFile(dir.files[i], this.editable);
}
}
// Here I will react to login information...
onlogin(data: any) {
// overide...
this.editable = true;
this.setDir(this.currentDirectory);
}
onlogout() {
// overide...
this.editable = false;
this.setDir(this.currentDirectory);
}
}