diff --git a/navigator/navigator.html b/navigator/navigator.html index 89de2e8..deb9c87 100644 --- a/navigator/navigator.html +++ b/navigator/navigator.html @@ -43,7 +43,7 @@
-
+
diff --git a/navigator/navigator.js b/navigator/navigator.js index 48691b6..d16b37b 100644 --- a/navigator/navigator.js +++ b/navigator/navigator.js @@ -1,7 +1,7 @@ class NavEntry { - constructor(/*string or array*/ path) { + constructor(/*string or array*/ path, /*dict*/ stat) { if(typeof path == 'string') this.path = path.split('/').splice(1); else @@ -14,6 +14,8 @@ class NavEntry { title.innerText = this.filename(); this.dom_element.appendChild(icon); this.dom_element.appendChild(title); + this.stat = stat; + this.dom_element.nav_item_icon.addEventListener("click", this) } destroy() { while(this.dom_element.firstChild){ @@ -23,7 +25,10 @@ class NavEntry { this.dom_element.parentElement.removeChild(this.dom_element); } filename() { - return this.path[this.path.length -1]; + var name = this.path[this.path.length -1]; + if(name === "") + name = "/"; + return name; } path_str() { return "/" + this.path.join('/'); @@ -31,47 +36,81 @@ class NavEntry { show() { document.getElementById("nav-contents-view").appendChild(this.dom_element); } -} - -class NavFile extends NavEntry { - constructor(/*string or array*/ path) { - super(path); - this.nav_type = "file"; - this.dom_element.nav_item_icon.classList.add("nav-file-icon"); + get_properties() { + return this.stat; + } + show_properties(){ + var html = '\n'; + html += '' + document.getElementById("nav-info-column").innerHTML = html; } } -class NavDir extends NavEntry { - constructor(/*string or array*/ path, nav_window_ref) { - super(path); - this.nav_type = "dir"; - this.dom_element.nav_item_icon.classList.add("nav-dir-icon"); - this.nav_window_ref = nav_window_ref; - this.dom_element.nav_item_icon.addEventListener("click", this) +class NavFile extends NavEntry { + constructor(/*string or array*/ path, /*dict*/ stat) { + super(path, stat); + this.nav_type = "file"; + this.dom_element.nav_item_icon.classList.add("nav-file-icon"); } handleEvent(e) { switch(e.type){ case "click": - this.nav_window_ref.cd(this); + this.show_properties(); + e.stopPropagation(); + break; + } + } +} + +class NavDir extends NavEntry { + constructor(/*string or array*/ path, /*dict*/ stat, nav_window_ref) { + super(path, stat); + this.nav_type = "dir"; + this.dom_element.nav_item_icon.classList.add("nav-dir-icon"); + this.nav_window_ref = nav_window_ref; + this.double_click = false; + } + handleEvent(e) { + switch(e.type){ + case "click": + if(this.double_click) + this.nav_window_ref.cd(this); + else{ // single click + this.show_properties(); + this.double_click = true; + if(this.timeout) + clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + this.double_click = false; + }, 500); + } + e.stopPropagation(); break; } } 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 entries = JSON.parse(data); + var response = JSON.parse(data); + this.stat = response["."]["stat"]; + var entries = response["children"]; entries.forEach(entry => { var filename = entry["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, nav_window_ref)); + children.push(new NavDir(path, stat, nav_window_ref)); else - children.push(new NavFile(path)); + children.push(new NavFile(path, stat)); }); children.sort((first, second) => { - if(first.nav_type === second.nav_type) - return 0; - if(first.nav_type == "dir") + if(first.nav_type === second.nav_type){ + return first.filename().localeCompare(second.filename()); + } + if(first.nav_type === "dir") return -1; return 1; }) @@ -83,6 +122,15 @@ class NavWindow { constructor() { this.path_stack = [new NavDir("/", this)]; this.entries = []; + this.window = document.getElementById("nav-contents-view"); + this.window.addEventListener("click", this); + } + handleEvent(e) { + switch(e.type){ + case "click": + this.show_pwd_properties(); + break; + } } async refresh() { var files = await this.pwd().get_children(this); @@ -112,6 +160,9 @@ class NavWindow { this.path_stack.pop(); this.refresh(); } + show_pwd_properties() { + this.pwd().show_properties(); + } } let nav_window = new NavWindow(); diff --git a/navigator/scripts/ls.py b/navigator/scripts/ls.py index 2672a1c..5be3592 100755 --- a/navigator/scripts/ls.py +++ b/navigator/scripts/ls.py @@ -1,8 +1,36 @@ #!/usr/bin/env python3 import os +from stat import S_ISDIR, filemode import json import sys +from pwd import getpwuid +from grp import getgrgid + +def get_stat(full_path, filename = '/'): + stats = os.lstat(full_path) + isdir = False + try: + isdir = S_ISDIR(os.stat(full_path).st_mode) + except OSError: + pass + response = { + "filename": filename, + "isdir": isdir, + "stat": { + "mode": stats.st_mode, + "mode-str": filemode(stats.st_mode), + "uid": stats.st_uid, + "owner": getpwuid(stats.st_uid).pw_name, + "gid": stats.st_gid, + "group": getgrgid(stats.st_gid).gr_name, + "size": stats.st_size, + "atime": stats.st_atime, + "mtime": stats.st_mtime, + "ctime": stats.st_ctime + } + } + return response def main(): if(len(sys.argv) < 2): @@ -12,10 +40,14 @@ def main(): except: print("No such file or directory") sys.exit(1) - response = [] + response = { + ".": get_stat(sys.argv[1]), + "children": [] + } for node in nodes: - response.append({"filename": node, "isdir": os.path.isdir(sys.argv[1] + "/" + node)}) - print(json.dumps(response, indent=4)) + full_path = sys.argv[1] + "/" + node + response["children"].append(get_stat(full_path, node)) + print(json.dumps(response)) if __name__ == "__main__":