diff --git a/navigator/navigator.js b/navigator/navigator.js index 578175f..c3178a8 100644 --- a/navigator/navigator.js +++ b/navigator/navigator.js @@ -1,18 +1,15 @@ - - function property_entry_html(key, value) { var html = '' + html += '' + key + ""; + html += '' + value + ""; + html += ""; return html; } function format_bytes(bytes) { - if(bytes === 0) - return "0 B"; + if (bytes === 0) return "0 B"; var units = [" B", " KiB", " MiB", " GiB", " TiB", " PiB"]; - var index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1) + var index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1); var pow = Math.pow(1024, index); var formatted = bytes / pow; return formatted.toFixed(2).toString() + units[index]; @@ -26,39 +23,69 @@ function format_time(timestamp) { function format_permissions(/*int*/ mode) { var bit_list = ["x", "w", "r"]; var result = ""; - for(let bit = 8; bit >= 0; bit--){ + for (let bit = 8; bit >= 0; bit--) { var test_bit = 1 << bit; var test_result = mode & test_bit; - if(test_result != 0){ + if (test_result != 0) { result += bit_list[bit % bit_list.length]; - }else{ + } else { result += "-"; } } return result; } +/* cephfs_dir_stats + * Receives: path to folder + * Does: Tries command with --json flag at path to folder. If + * command fails and gives an error then catch that error + * Returns: JSON object or nothing + */ +async function cephfs_dir_stats(path) { + try { + var proc = await cockpit.spawn(["cephfs-dir-stats", "-j", path], { + err: "ignore", + }); + return JSON.parse(proc.replace(/\[|\]/g, "")); + } catch { + return []; + } +} + +/* in_json + * Receives: A boolean to see if key is in JSON object + * and the JSON objects value if it exists + * Does: Checks if JSON key exists; if so, return the + * value of the key, if not, return the string "N/A" + * Returns: The value of key or "N/A" + */ +function in_json(is_key, value) { + if (is_key) { + return value; + } else { + return "N/A"; + } +} + class NavEntry { constructor(/*string or array*/ path, /*dict*/ stat, /*NavWindow*/ nav_window_ref) { this.nav_window_ref = nav_window_ref; - if(typeof path == 'string') - this.path = path.split('/').splice(1); - else - this.path = path; + if (typeof path == "string") this.path = path.split("/").splice(1); + else this.path = path; this.dom_element = document.createElement("div"); this.dom_element.classList.add("nav-item"); - let icon = this.dom_element.nav_item_icon = document.createElement("i"); + let icon = (this.dom_element.nav_item_icon = document.createElement("i")); icon.classList.add("nav-file-icon"); - let title = this.dom_element.nav_item_title = document.createElement("div"); + let title = (this.dom_element.nav_item_title = document.createElement("div")); title.classList.add("nav-item-title"); title.innerText = this.filename(); this.dom_element.appendChild(icon); this.dom_element.appendChild(title); this.stat = stat; - this.dom_element.addEventListener("click", this) + this.dom_element.addEventListener("click", this); } handleEvent(e) { - switch(e.type){ + switch (e.type) { case "click": this.show_properties(); this.nav_window_ref.set_selected(this); @@ -67,20 +94,18 @@ class NavEntry { } } destroy() { - while(this.dom_element.firstChild){ + while (this.dom_element.firstChild) { this.dom_element.removeChild(this.dom_element.firstChild); } - if(this.dom_element.parentElement) - this.dom_element.parentElement.removeChild(this.dom_element); + if (this.dom_element.parentElement) this.dom_element.parentElement.removeChild(this.dom_element); } filename() { - var name = this.path[this.path.length -1]; - if(name === "") - name = "/"; + var name = this.path[this.path.length - 1]; + if (name === "") name = "/"; return name; } path_str() { - return "/" + this.path.join('/'); + return "/" + this.path.join("/"); } show() { document.getElementById("nav-contents-view").appendChild(this.dom_element); @@ -92,20 +117,20 @@ class NavEntry { return this.stat["mode"] & 0o777; } async chmod(/*int*/ new_perms) { - var proc = cockpit.spawn( - ["chmod", (new_perms & 0o777).toString(8), this.path_str()], - {superuser: "try", err:"out"} - ); + var proc = cockpit.spawn(["chmod", (new_perms & 0o777).toString(8), this.path_str()], { + superuser: "try", + err: "out", + }); proc.fail((e, data) => { window.alert(data); }); await proc; } async chown(/*string*/ new_owner, /*string*/ new_group) { - var proc = cockpit.spawn( - ["chown", [new_owner, new_group].join(":"), this.path_str()], - {superuser: "try", err:"out"} - ); + var proc = cockpit.spawn(["chown", [new_owner, new_group].join(":"), this.path_str()], { + superuser: "try", + err: "out", + }); proc.fail((e, data) => { window.alert(data); }); @@ -113,8 +138,8 @@ class NavEntry { } async mv(/*string*/ new_path) { var proc = cockpit.spawn( - ["mv", "-n", this.path_str(), [this.nav_window_ref.pwd().path_str(), new_path].join('/')], - {superuser: "try", err:"out"} + ["mv", "-n", this.path_str(), [this.nav_window_ref.pwd().path_str(), new_path].join("/")], + { superuser: "try", err: "out" } ); proc.fail((e, data) => { window.alert(data); @@ -123,7 +148,7 @@ class NavEntry { } show_properties(/*string*/ extra_properties = "") { var selected_name_fields = document.getElementsByClassName("nav-info-column-filename"); - for(let elem of selected_name_fields){ + for (let elem of selected_name_fields) { elem.innerText = this.filename(); } var html = ""; @@ -139,13 +164,21 @@ class NavEntry { } populate_edit_fields() { document.getElementById("nav-edit-filename").value = this.filename(); - var mode_bits = ["other-exec", "other-write", "other-read", - "group-exec", "group-write", "group-read", - "owner-exec", "owner-write", "owner-read"]; - for(let i = 0; i < mode_bits.length; i++){ - var bit_check = (1 << i); + var mode_bits = [ + "other-exec", + "other-write", + "other-read", + "group-exec", + "group-write", + "group-read", + "owner-exec", + "owner-write", + "owner-read", + ]; + for (let i = 0; i < mode_bits.length; i++) { + var bit_check = 1 << i; var result = this.stat["mode"] & bit_check; - document.getElementById(mode_bits[i]).checked = (result != 0); + document.getElementById(mode_bits[i]).checked = result != 0; } document.getElementById("nav-edit-owner").value = this.stat["owner"]; document.getElementById("nav-edit-group").value = this.stat["group"]; @@ -162,10 +195,7 @@ class NavFile extends NavEntry { super.handleEvent(e); } async rm() { - var proc = cockpit.spawn( - ["rm", "-f", this.path_str()], - {superuser: "try", err:"out"} - ); + var proc = cockpit.spawn(["rm", "-f", this.path_str()], { superuser: "try", err: "out" }); proc.fail((e, data) => { window.alert(data); }); @@ -175,26 +205,21 @@ class NavFile extends NavEntry { class NavDir extends NavEntry { constructor(/*string or array*/ path, /*dict*/ stat, nav_window_ref) { - super(path, stat, nav_window_ref); + super(path, stat, nav_window_ref); // super = call parent this.nav_type = "dir"; this.dom_element.nav_item_icon.classList.add("fas", "fa-folder"); this.double_click = false; - /* - Sam: - add a method to NavDir that calls cephfs-dir-stats and call it here. - The function should save the results as a dictionary like the following: - this.cephfs_dir_stats = {key : "value", etc}; - */ + this.ceph_stats = []; + cephfs_dir_stats(this.path_str()).then((data) => (this.ceph_stats = data)); } handleEvent(e) { - switch(e.type){ + switch (e.type) { case "click": - if(this.double_click) - this.nav_window_ref.cd(this); - else{ // single click + if (this.double_click) this.nav_window_ref.cd(this); + else { + // single click this.double_click = true; - if(this.timeout) - clearTimeout(this.timeout) + if (this.timeout) clearTimeout(this.timeout); this.timeout = setTimeout(() => { this.double_click = false; }, 500); @@ -205,34 +230,30 @@ class NavDir extends NavEntry { } async get_children(nav_window_ref) { var children = []; - var data = await cockpit.spawn(["/usr/share/cockpit/navigator/scripts/ls.py", this.path_str()], {err:"ignore"}); + var data = await cockpit.spawn(["/usr/share/cockpit/navigator/scripts/ls.py", this.path_str()], { + err: "ignore", + }); var response = JSON.parse(data); this.stat = response["."]["stat"]; var entries = response["children"]; - entries.forEach(entry => { + entries.forEach((entry) => { var filename = entry["filename"]; - var path = (this.path.length >= 1 && this.path[0]) ? [... this.path, filename] : [filename]; + var path = this.path.length >= 1 && this.path[0] ? [...this.path, filename] : [filename]; var stat = entry["stat"]; - if(entry["isdir"]) - children.push(new NavDir(path, stat, nav_window_ref)); - else - children.push(new NavFile(path, stat, nav_window_ref)); + if (entry["isdir"]) children.push(new NavDir(path, stat, nav_window_ref)); + else children.push(new NavFile(path, stat, nav_window_ref)); }); children.sort((first, second) => { - if(first.nav_type === second.nav_type){ + if (first.nav_type === second.nav_type) { return first.filename().localeCompare(second.filename()); } - if(first.nav_type === "dir") - return -1; + if (first.nav_type === "dir") return -1; return 1; - }) + }); return children; } async rm() { - var proc = cockpit.spawn( - ["rmdir", this.path_str()], - {superuser: "try", err:"out"} - ); + var proc = cockpit.spawn(["rmdir", this.path_str()], { superuser: "try", err: "out" }); proc.fail((e, data) => { window.alert(data); }); @@ -240,12 +261,44 @@ class NavDir extends NavEntry { } show_properties() { var extra_properties = ""; - /* - Sam: - Follow NavEntry.show_properties() as an example to put the cephfs-dir-stats results - into extra_properties as html elements. If cephfs-dir-stats failed, i.e. it's not in a - ceph filesystem, make sure extra_properties is an empty string. - */ + + // See if a JSON object exists for folder we are currently looking at + if (this.ceph_stats.length !== 0) { + extra_properties += + '
'; + extra_properties += property_entry_html( + "Files", + in_json(this.ceph_stats.hasOwnProperty("files"), this.ceph_stats.files) + ); + extra_properties += property_entry_html( + "Directories", + in_json(this.ceph_stats.hasOwnProperty("subdirs"), this.ceph_stats.subdirs) + ); + extra_properties += property_entry_html( + "Recursive files", + in_json(this.ceph_stats.hasOwnProperty("rfiles"), this.ceph_stats.rfiles) + ); + extra_properties += property_entry_html( + "Recursive directories", + in_json(this.ceph_stats.hasOwnProperty("rsubdirs"), this.ceph_stats.rsubdirs) + ); + extra_properties += property_entry_html( + "Total size", + in_json(this.ceph_stats.hasOwnProperty("rbytes"), this.ceph_stats.rbytes) + ); + extra_properties += property_entry_html( + "Layout pool", + in_json(this.ceph_stats.hasOwnProperty("layout.pool"), this.ceph_stats["layout.pool"]) + ); + extra_properties += property_entry_html( + "Max files", + in_json(this.ceph_stats.hasOwnProperty("max_files"), this.ceph_stats.max_files) + ); + extra_properties += property_entry_html( + "Max bytes", + in_json(this.ceph_stats.hasOwnProperty("max_bytes"), this.ceph_stats.max_bytes) + ); + } super.show_properties(extra_properties); } } @@ -259,7 +312,7 @@ class NavWindow { this.window.addEventListener("click", this); } handleEvent(e) { - switch(e.type){ + switch (e.type) { case "click": this.set_selected(this.pwd()); this.show_selected_properties(); @@ -268,11 +321,11 @@ class NavWindow { } async refresh() { var files = await this.pwd().get_children(this); - while(this.entries.length){ + while (this.entries.length) { var entry = this.entries.pop(); entry.destroy(); } - files.forEach(file => { + files.forEach((file) => { file.show(); this.entries.push(file); }); @@ -292,8 +345,7 @@ class NavWindow { }); } up() { - if(this.path_stack.length > 1) - this.path_stack.pop(); + if (this.path_stack.length > 1) this.path_stack.pop(); this.refresh(); } show_selected_properties() { @@ -302,24 +354,41 @@ class NavWindow { set_selected(/*NavEntry*/ entry) { this.hide_edit_selected(); this.selected_entry.dom_element.classList.remove("nav-item-selected"); - if(this.selected_entry.nav_type === "dir"){ + if (this.selected_entry.nav_type === "dir") { this.selected_entry.dom_element.nav_item_icon.classList.remove("fa-folder-open"); this.selected_entry.dom_element.nav_item_icon.classList.add("fa-folder"); } this.selected_entry = entry; this.selected_entry.dom_element.classList.add("nav-item-selected"); - if(this.selected_entry.nav_type === "dir"){ + if (this.selected_entry.nav_type === "dir") { this.selected_entry.dom_element.nav_item_icon.classList.remove("fa-folder"); this.selected_entry.dom_element.nav_item_icon.classList.add("fa-folder-open"); } } show_edit_selected() { var dangerous_dirs = [ - "/", "/usr", "/bin", "/sbin", "/lib", "/lib32", "/lib64", "/usr/bin", - "/usr/include", "/usr/lib", "/usr/lib32", "/usr/lib64", "/usr/sbin" + "/", + "/usr", + "/bin", + "/sbin", + "/lib", + "/lib32", + "/lib64", + "/usr/bin", + "/usr/include", + "/usr/lib", + "/usr/lib32", + "/usr/lib64", + "/usr/sbin", ]; - if(dangerous_dirs.includes(this.selected_entry.path_str())){ - if(!window.confirm("Warning: editing `" + this.selected_entry.path_str() + "` can be dangerous. Are you sure?")){ + if (dangerous_dirs.includes(this.selected_entry.path_str())) { + if ( + !window.confirm( + "Warning: editing `" + + this.selected_entry.path_str() + + "` can be dangerous. Are you sure?" + ) + ) { return; } } @@ -337,10 +406,9 @@ class NavWindow { var action_list = ["exec", "write", "read"]; var result = 0; var bit = 0; - for(let category of category_list){ - for(let action of action_list){ - if(document.getElementById(category + "-" + action).checked) - result |= 1 << bit; + for (let category of category_list) { + for (let action of action_list) { + if (document.getElementById(category + "-" + action).checked) result |= 1 << bit; bit++; } } @@ -356,22 +424,29 @@ class NavWindow { // do mv last so the rest don't fail from not finding path var new_owner = document.getElementById("nav-edit-owner").value; var new_group = document.getElementById("nav-edit-group").value; - if(new_owner !== this.selected_entry.stat["owner"] || new_group !== this.selected_entry.stat["group"]){ + if ( + new_owner !== this.selected_entry.stat["owner"] || + new_group !== this.selected_entry.stat["group"] + ) { await this.selected_entry.chown(new_owner, new_group).catch(/*ignore, caught in chown*/); } var new_perms = this.get_new_permissions(); - if((new_perms & 0o777) !== (this.selected_entry.stat["mode"] & 0o777)){ + if ((new_perms & 0o777) !== (this.selected_entry.stat["mode"] & 0o777)) { await this.selected_entry.chmod(new_perms).catch(/*ignore, caught in chmod*/); } var new_name = document.getElementById("nav-edit-filename").value; - if(new_name !== this.selected_entry.filename()){ + if (new_name !== this.selected_entry.filename()) { await this.selected_entry.mv(new_name).catch(/*ignore, caught in mv*/); } this.refresh(); this.hide_edit_selected(); } async delete_selected() { - if(!window.confirm("Deleting `" + this.selected_entry.path_str() + "` cannot be undone. Are you sure?")){ + if ( + !window.confirm( + "Deleting `" + this.selected_entry.path_str() + "` cannot be undone. Are you sure?" + ) + ) { return; } await this.selected_entry.rm().catch(/*ignore, caught in rm*/); @@ -379,16 +454,15 @@ class NavWindow { } async mkdir() { var new_dir_name = window.prompt("Directory Name: "); - if(new_dir_name === null) - return; - if(new_dir_name.includes("/")){ + if (new_dir_name === null) return; + if (new_dir_name.includes("/")) { window.alert("Directory name can't contain `/`."); return; } - var proc = cockpit.spawn( - ["mkdir", this.pwd().path_str() + "/" + new_dir_name], - {superuser: "try", err:"out"} - ); + var proc = cockpit.spawn(["mkdir", this.pwd().path_str() + "/" + new_dir_name], { + superuser: "try", + err: "out", + }); proc.fail((e, data) => { window.alert(data); }); @@ -397,16 +471,15 @@ class NavWindow { } async touch() { var new_file_name = window.prompt("File Name: "); - if(new_file_name === null) - return; - if(new_file_name.includes("/")){ + if (new_file_name === null) return; + if (new_file_name.includes("/")) { window.alert("File name can't contain `/`."); return; } - var proc = cockpit.spawn( - ["touch", this.pwd().path_str() + "/" + new_file_name], - {superuser: "try", err:"out"} - ); + var proc = cockpit.spawn(["touch", this.pwd().path_str() + "/" + new_file_name], { + superuser: "try", + err: "out", + }); proc.fail((e, data) => { window.alert(data); }); @@ -422,12 +495,20 @@ function set_up_buttons() { document.getElementById("nav-refresh-btn").addEventListener("click", nav_window.refresh.bind(nav_window)); document.getElementById("nav-mkdir-btn").addEventListener("click", nav_window.mkdir.bind(nav_window)); document.getElementById("nav-touch-btn").addEventListener("click", nav_window.touch.bind(nav_window)); - document.getElementById("nav-delete-btn").addEventListener("click", nav_window.delete_selected.bind(nav_window)); - document.getElementById("nav-edit-properties-btn").addEventListener("click", nav_window.show_edit_selected.bind(nav_window)); - document.getElementById("nav-cancel-edit-btn").addEventListener("click", nav_window.hide_edit_selected.bind(nav_window)); - document.getElementById("nav-apply-edit-btn").addEventListener("click", nav_window.apply_edit_selected.bind(nav_window)); + document + .getElementById("nav-delete-btn") + .addEventListener("click", nav_window.delete_selected.bind(nav_window)); + document + .getElementById("nav-edit-properties-btn") + .addEventListener("click", nav_window.show_edit_selected.bind(nav_window)); + document + .getElementById("nav-cancel-edit-btn") + .addEventListener("click", nav_window.hide_edit_selected.bind(nav_window)); + document + .getElementById("nav-apply-edit-btn") + .addEventListener("click", nav_window.apply_edit_selected.bind(nav_window)); var mode_checkboxes = document.getElementsByClassName("mode-checkbox"); - for(let checkbox of mode_checkboxes){ + for (let checkbox of mode_checkboxes) { checkbox.addEventListener("change", nav_window.update_permissions_preview.bind(nav_window)); } }