From a3343bb7d4d2f3420d224a11941b95aa93692905 Mon Sep 17 00:00:00 2001 From: joshuaboud Date: Fri, 28 May 2021 14:50:06 -0300 Subject: [PATCH] implement symbolic link handling --- navigator/navigator.css | 21 ++++++++- navigator/navigator.js | 98 ++++++++++++++++++++++++++++++++++++++--- navigator/scripts/ls.py | 6 ++- 3 files changed, 117 insertions(+), 8 deletions(-) diff --git a/navigator/navigator.css b/navigator/navigator.css index 971ec70..16f093e 100644 --- a/navigator/navigator.css +++ b/navigator/navigator.css @@ -22,7 +22,7 @@ --border: #bebebe; --navigation: #fff; --font: #1c1c1c; - --selected: #fff; + --selected: #f8f8f8; --toggle-light: #151515; --toggle-dark: #ccc; --scrollbar-thumb: var(--border); @@ -33,6 +33,7 @@ --logo-45: #333; --nav-entry-color: #555F6E; --nav-border-radius: 4px; + --symlink-symbol-color: var(--navigation); } [data-theme="dark"] { @@ -49,6 +50,7 @@ --textarea-bg: var(--navigation); --logo-45: #fff; --nav-entry-color: #555F6E; + --symlink-symbol-color: var(--navigation); } .pf-c-button:disabled[data-theme="dark"] { @@ -215,12 +217,29 @@ input[type="text"] { } .nav-item .nav-item-icon { + position: relative; text-align: center; width: 100px; font-size: 80px; color: var(--nav-entry-color); } +.nav-item-symlink-symbol-dir { + position: absolute; + color: var(--symlink-symbol-color); + font-size: 12pt; + bottom: 18%; + right: 15%; +} + +.nav-item-symlink-symbol-file { + position: absolute; + color: var(--symlink-symbol-color); + font-size: 12pt; + bottom: 8%; + right: 25%; +} + .nav-info-column { background-color: var(--container); flex-basis: 0; diff --git a/navigator/navigator.js b/navigator/navigator.js index 0e478a1..5c8335e 100644 --- a/navigator/navigator.js +++ b/navigator/navigator.js @@ -300,6 +300,58 @@ class NavFile extends NavEntry { } } +class NavFileLink extends NavFile{ + constructor(/*string or array*/ path, /*dict*/ stat, nav_window_ref, link_target) { + super(path, stat, nav_window_ref); + var link_icon = this.dom_element.nav_item_icon.link_icon = document.createElement("i"); + link_icon.classList.add("fas", "fa-link", "nav-item-symlink-symbol-file"); + this.dom_element.nav_item_icon.appendChild(link_icon); + this.double_click = false; + this.link_target = link_target; + } + show_properties() { + var extra_properties = property_entry_html("Link Target", this.link_target); + super.show_properties(extra_properties); + } + get_link_target_path() { + var target = ""; + if(this.link_target.charAt(0) === '/') + target = this.link_target; + else + target = "/" + this.parent_dir().join("/") + "/" + this.link_target; + return target; + } + async show_edit_file_contents() { + for (let button of document.getElementsByTagName("button")) { + if (!button.classList.contains("editor-btn")) + button.disabled = true; + } + document.getElementById("pwd").disabled = true; + var target_path = this.get_link_target_path(); + var proc_output = await cockpit.spawn(["file", "--mime-type", target_path], {superuser: "try"}); + var fields = proc_output.split(':'); + var type = fields[1].trim(); + if(!(type.match(/^text/) || type.match(/^inode\/x-empty$/) || this.stat["size"] === 0)){ + if(!window.confirm("File is of type `" + type + "`. Are you sure you want to edit it?")) + return; + } + var contents = await cockpit.spawn(["cat", target_path], {superuser: "try"}); + document.getElementById("nav-edit-contents-textarea").value = contents; + document.getElementById("nav-cancel-edit-contents-btn").onclick = this.hide_edit_file_contents.bind(this); + document.getElementById("nav-continue-edit-contents-btn").onclick = this.write_to_file.bind(this); + document.getElementById("nav-edit-contents-header").innerText = "Editing " + this.path_str(); + document.getElementById("nav-contents-view").style.display = "none"; + document.getElementById("nav-edit-contents-view").style.display = "flex"; + } + async write_to_file() { + var target_path = this.get_link_target_path(); + var new_contents = document.getElementById("nav-edit-contents-textarea").value; + await cockpit.script("echo -n \"$1\" > $2", [new_contents, target_path], {superuser: "try"}); + this.nav_window_ref.refresh(); + this.hide_edit_file_contents(); + } +} + class NavDir extends NavEntry { constructor(/*string or array*/ path, /*dict*/ stat, nav_window_ref) { super(path, stat, nav_window_ref); @@ -343,10 +395,20 @@ class NavDir extends NavEntry { 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, stat, nav_window_ref)); - else - children.push(new NavFile(path, stat, nav_window_ref)); + switch(stat["mode-str"].charAt(0)) { + case 'd': + children.push(new NavDir(path, stat, nav_window_ref)); + break; + case 'l': + if(entry["isdir"]) + children.push(new NavDirLink(path, stat, nav_window_ref, entry["link-target"])); + else + children.push(new NavFileLink(path, stat, nav_window_ref, entry["link-target"])); + break; + default: + children.push(new NavFile(path, stat, nav_window_ref)); + break; + } }); children.sort((first, second) => { if (first.nav_type === second.nav_type) { @@ -379,8 +441,7 @@ class NavDir extends NavEntry { return null; } } - async show_properties() { - var extra_properties = ""; + async show_properties(/*string*/ extra_properties = "") { if(!this.hasOwnProperty("ceph_stats")) this.ceph_stats = await this.cephfs_dir_stats(); // See if a JSON object exists for folder we are currently looking at @@ -424,6 +485,31 @@ class NavDir extends NavEntry { } } +class NavDirLink extends NavDir{ + constructor(/*string or array*/ path, /*dict*/ stat, nav_window_ref, /*string*/ link_target) { + super(path, stat, nav_window_ref); + var link_icon = this.dom_element.nav_item_icon.link_icon = document.createElement("i"); + link_icon.classList.add("fas", "fa-link", "nav-item-symlink-symbol-dir"); + this.dom_element.nav_item_icon.appendChild(link_icon); + this.double_click = false; + this.link_target = link_target; + } + async rm() { + var proc = cockpit.spawn( + ["rm", "-f", this.path_str()], + {superuser: "try", err: "out"} + ); + proc.fail((e, data) => { + window.alert(data); + }); + await proc; + } + show_properties() { + var extra_properties = property_entry_html("Link Target", this.link_target); + super.show_properties(extra_properties); + } +} + class NavWindow { constructor() { this.path_stack = (localStorage.getItem('navigator-path') ?? '/').split('/'); diff --git a/navigator/scripts/ls.py b/navigator/scripts/ls.py index 4c49b56..0ea29bc 100755 --- a/navigator/scripts/ls.py +++ b/navigator/scripts/ls.py @@ -18,7 +18,7 @@ """ import os -from stat import S_ISDIR, filemode +from stat import S_ISDIR, S_ISLNK, filemode import json import sys from pwd import getpwuid @@ -31,6 +31,9 @@ def get_stat(full_path, filename = '/'): isdir = S_ISDIR(os.stat(full_path).st_mode) except OSError: pass + link_target = '?' + if S_ISLNK(stats.st_mode): + link_target = os.readlink(full_path) owner = '?' try: owner = getpwuid(stats.st_uid).pw_name @@ -44,6 +47,7 @@ def get_stat(full_path, filename = '/'): response = { "filename": filename, "isdir": isdir, + "link-target": link_target, "stat": { "mode": stats.st_mode, "mode-str": filemode(stats.st_mode),