Show file type icons with the option to disable

This commit is contained in:
joshuaboud 2021-12-16 14:47:07 -04:00
parent 344c2d1468
commit da920db018
No known key found for this signature in database
GPG Key ID: 17EFB59E2A8BF50E
8 changed files with 193 additions and 100 deletions

View File

@ -30,7 +30,7 @@ With no command line use needed, you can:
### EL8 ### EL8
1. `# dnf install https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.8/cockpit-navigator-0.5.8-1.el8.noarch.rpm` 1. `# dnf install https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.8/cockpit-navigator-0.5.8-1.el8.noarch.rpm`
## From Source ## From Source
1. Ensure dependencies are installed: `cockpit`, `python3`, `rsync`, `zip`. 1. Ensure dependencies are installed: `cockpit`, `python3`, `python3-magic`, `zip`.
1. `$ git clone https://github.com/45Drives/cockpit-navigator.git` 1. `$ git clone https://github.com/45Drives/cockpit-navigator.git`
1. `$ cd cockpit-navigator` 1. `$ cd cockpit-navigator`
1. `$ git checkout <version>` (v0.5.8 is latest) 1. `$ git checkout <version>` (v0.5.8 is latest)

View File

@ -25,13 +25,13 @@
"deb": [ "deb": [
"cockpit", "cockpit",
"python3", "python3",
"rsync", "python3-magic",
"zip" "zip"
], ],
"el": [ "el": [
"cockpit", "cockpit",
"python3", "python3",
"rsync", "python3-magic",
"zip" "zip"
] ]
}, },
@ -57,11 +57,12 @@
"version": "0.5.9", "version": "0.5.9",
"buildVersion": "1", "buildVersion": "1",
"ignore": [], "ignore": [],
"date": "2021-12-15T17:43:15.251513", "date": "2021-12-16T14:46:53.434779",
"packager": "Joshua Boudreau <jboudreau@45drives.com>", "packager": "Joshua Boudreau <jboudreau@45drives.com>",
"changes": [ "changes": [
"Disallow downloading files of size 0 to avoid Cockpit bug where fsread never returns.", "Disallow downloading files of size 0 to avoid Cockpit bug where fsread never returns.",
"Overhaul copying/moving to use built in python functions instead of rsync, making moving large files within the same fs instant." "Overhaul copying/moving to use built in python functions instead of rsync, making moving large files within the same fs instant.",
"Add descriptive icons using mimetype with the option to disable."
] ]
} }
} }

View File

@ -62,16 +62,21 @@ export class NavDir extends NavEntry {
/** /**
* *
* @param {NavWindow} nav_window_ref * @param {NavWindow} nav_window_ref
* @param {Boolean} show_mimetype_icons
* @returns {Promise<NavEntry[]>} * @returns {Promise<NavEntry[]>}
*/ */
get_children(nav_window_ref) { get_children(nav_window_ref, show_mimetype_icons = false) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
var children = []; var children = [];
var argv = ["/usr/share/cockpit/navigator/scripts/ls.py3", this.path_str()];
if (!show_mimetype_icons)
argv.push("--no-mimetype");
var proc = cockpit.spawn( var proc = cockpit.spawn(
["/usr/share/cockpit/navigator/scripts/ls.py3", this.path_str()], argv,
{err:"out", superuser: "try"} {err:"out", superuser: "try"}
); );
proc.fail((e, data) => { proc.fail((e, data) => {
console.log(argv);
reject(data); reject(data);
}); });
var data; var data;
@ -100,7 +105,7 @@ export class NavDir extends NavEntry {
children.push(new NavFileLink(path, stat, nav_window_ref, entry["link-target"])); children.push(new NavFileLink(path, stat, nav_window_ref, entry["link-target"]));
break; break;
default: default:
children.push(new NavFile(path, stat, nav_window_ref)); children.push(new NavFile(path, stat, nav_window_ref, entry["mimetype"]));
break; break;
} }
}); });

View File

@ -29,10 +29,61 @@ export class NavFile extends NavEntry {
* @param {object} stat * @param {object} stat
* @param {NavWindow} nav_window_ref * @param {NavWindow} nav_window_ref
*/ */
constructor(path, stat, nav_window_ref) { constructor(path, stat, nav_window_ref, mimetype) {
super(path, stat, nav_window_ref); super(path, stat, nav_window_ref);
this.nav_type = "file"; this.nav_type = "file";
this.dom_element.nav_item_icon.classList.add("fas", "fa-file"); if (mimetype) {
if (/^image\//.test(mimetype)) {
this.dom_element.nav_item_icon.classList.add("fas", "fa-file-image");
} else if (/^video\//.test(mimetype)) {
this.dom_element.nav_item_icon.classList.add("fas", "fa-file-video");
} else if (/^audio\//.test(mimetype)) {
this.dom_element.nav_item_icon.classList.add("fas", "fa-file-audio");
} else {
switch (mimetype) {
case "text/plain":
case "application/rtf":
this.dom_element.nav_item_icon.classList.add("fas", "fa-file-alt");
break;
case "text/csv":
this.dom_element.nav_item_icon.classList.add("fas", "fa-file-csv");
break;
case "application/msword":
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
case "application/vnd.oasis.opendocument.text":
this.dom_element.nav_item_icon.classList.add("fas", "fa-file-word");
break;
case "application/vnd.ms-powerpoint":
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
case "application/vnd.oasis.opendocument.presentation":
this.dom_element.nav_item_icon.classList.add("fas", "fa-file-powerpoint");
break;
case "application/vnd.ms-excel":
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
case "application/vnd.oasis.opendocument.spreadsheet":
this.dom_element.nav_item_icon.classList.add("fas", "fa-file-excel");
break;
case "application/pdf":
this.dom_element.nav_item_icon.classList.add("fas", "fa-file-pdf");
break;
case "application/x-freearc":
case "application/x-bzip":
case "application/x-bzip2":
case "application/gzip":
case "application/vnd.rar":
case "application/x-tar":
case "application/zip":
case "application/x-7z-compressed":
this.dom_element.nav_item_icon.classList.add("fas", "fa-file-archive");
break;
default:
this.dom_element.nav_item_icon.classList.add("fas", "fa-file");
break;
}
}
} else {
this.dom_element.nav_item_icon.classList.add("fas", "fa-file");
}
this.double_click = false; this.double_click = false;
} }

View File

@ -28,6 +28,9 @@ import { format_bytes, format_permissions } from "../functions.js";
export class NavWindow { export class NavWindow {
constructor() { constructor() {
this.item_display = "grid"; this.item_display = "grid";
var show_mimetype_icons_str = localStorage.getItem('show-mimetype-icons') ?? 'true';
this.show_mimetype_icons = show_mimetype_icons_str === 'true';
document.getElementById("nav-show-mimetype-icons").checked = this.show_mimetype_icons;
this.path_stack = (localStorage.getItem('navigator-path') ?? '/').split('/'); this.path_stack = (localStorage.getItem('navigator-path') ?? '/').split('/');
this.path_stack = this.path_stack.map((_, index) => new NavDir([...this.path_stack.slice(0, index + 1)].filter(part => part != ''), this)); this.path_stack = this.path_stack.map((_, index) => new NavDir([...this.path_stack.slice(0, index + 1)].filter(part => part != ''), this));
@ -126,7 +129,7 @@ export class NavWindow {
this.show_hidden = document.getElementById("nav-show-hidden").checked; this.show_hidden = document.getElementById("nav-show-hidden").checked;
this.start_load(); this.start_load();
try { try {
var files = await this.pwd().get_children(this); var files = await this.pwd().get_children(this, this.show_mimetype_icons);
} catch(e) { } catch(e) {
await this.modal_prompt.alert(e); await this.modal_prompt.alert(e);
this.up(); this.up();
@ -914,6 +917,13 @@ export class NavWindow {
localStorage.setItem("item-display", this.item_display); localStorage.setItem("item-display", this.item_display);
} }
async set_show_mimetype_icons(e) {
console.log(e);
this.show_mimetype_icons = e.target.checked;
localStorage.setItem("show-mimetype-icons", e.target.checked.toString());
await this.refresh();
}
search_filter(event) { search_filter(event) {
var search_name = event.target.value; var search_name = event.target.value;
let search_func; let search_func;

View File

@ -181,6 +181,17 @@
<i class="fas fa-list" id="nav-item-display-icon"></i> <i class="fas fa-list" id="nav-item-display-icon"></i>
</div> </div>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<div class="nav-toggle">
<div class="nav-btn-group">
<i class="fas fa-icons" id="nav-show-mimetype-icons-icon"></i>
<div class="horizontal-spacer-sm"></div>
<label class="switch" title="Show File Type Icons (Disable for faster load)">
<input type="checkbox" id="nav-show-mimetype-icons">
<span class="slider round"></span>
</label>
</div>
</div>
<div class="horizontal-spacer"></div>
<div class="nav-toggle"> <div class="nav-toggle">
<div class="nav-btn-group"> <div class="nav-btn-group">
<i class="fas fa-low-vision" id="nav-show-hidden-icon"></i> <i class="fas fa-low-vision" id="nav-show-hidden-icon"></i>

View File

@ -117,6 +117,7 @@ function set_up_buttons() {
document.getElementById("pwd").addEventListener("focus", nav_window.nav_bar_update_choices.bind(nav_window), false); document.getElementById("pwd").addEventListener("focus", nav_window.nav_bar_update_choices.bind(nav_window), false);
document.getElementById("pwd").addEventListener("keydown", nav_window.nav_bar_event_handler.bind(nav_window)); document.getElementById("pwd").addEventListener("keydown", nav_window.nav_bar_event_handler.bind(nav_window));
document.getElementById("toggle-theme").addEventListener("change", switch_theme, false); document.getElementById("toggle-theme").addEventListener("change", switch_theme, false);
document.getElementById("nav-show-mimetype-icons").addEventListener("change", nav_window.set_show_mimetype_icons.bind(nav_window));
document.getElementById("nav-show-hidden").addEventListener("change", nav_window.toggle_show_hidden.bind(nav_window)); document.getElementById("nav-show-hidden").addEventListener("change", nav_window.toggle_show_hidden.bind(nav_window));
document.getElementById("nav-item-display-btn").addEventListener("click", nav_window.switch_item_display.bind(nav_window)); document.getElementById("nav-item-display-btn").addEventListener("click", nav_window.switch_item_display.bind(nav_window));
for (let option of ["name", "owner", "group", "size", "modified", "created"]) { for (let option of ["name", "owner", "group", "size", "modified", "created"]) {

View File

@ -1,106 +1,120 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Cockpit Navigator - A File System Browser for Cockpit. Cockpit Navigator - A File System Browser for Cockpit.
Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com> Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator. This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify Cockpit Navigator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, Cockpit Navigator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
""" """
import os import os
from stat import S_ISDIR, S_ISLNK, filemode from stat import S_ISDIR, S_ISLNK, filemode
import json import json
import sys import sys
import magic
from optparse import OptionParser
from pwd import getpwuid from pwd import getpwuid
from grp import getgrgid from grp import getgrgid
def get_stat(full_path, filename = '/'): def get_stat(full_path, filename = '/', no_mimetype=False):
try: try:
stats = os.lstat(full_path) stats = os.lstat(full_path)
except OSError: except OSError:
return { return {
"filename": filename, "filename": filename,
"isdir": False, "isdir": False,
"link-target": "", "link-target": "",
"stat": { "stat": {
"inaccessible": True, "inaccessible": True,
"mode": 0, "mode": 0,
"mode-str": "?", "mode-str": "?",
"uid": 0, "uid": 0,
"owner": "?", "owner": "?",
"gid": 0, "gid": 0,
"group": "?", "group": "?",
"size": 0, "size": 0,
"atime": 0, "atime": 0,
"mtime": 0, "mtime": 0,
"ctime": 0 "ctime": 0
} }
} }
isdir = False isdir = False
try: try:
isdir = S_ISDIR(os.stat(full_path).st_mode) isdir = S_ISDIR(os.stat(full_path).st_mode)
except OSError: except OSError:
pass pass
link_target = '?' link_target = '?'
if S_ISLNK(stats.st_mode): if S_ISLNK(stats.st_mode):
link_target = os.readlink(full_path) link_target = os.readlink(full_path)
owner = '?' owner = '?'
try: try:
owner = getpwuid(stats.st_uid).pw_name owner = getpwuid(stats.st_uid).pw_name
except: except:
pass pass
group = '?' group = '?'
try: try:
group = getgrgid(stats.st_gid).gr_name group = getgrgid(stats.st_gid).gr_name
except: except:
pass pass
response = { mimetype = None
"inaccessible": False, if not no_mimetype:
"filename": filename, try:
"isdir": isdir, if not isdir:
"link-target": link_target, mimetype = magic.detect_from_filename(full_path)[0]
"stat": { except:
"mode": stats.st_mode, pass
"mode-str": filemode(stats.st_mode), response = {
"uid": stats.st_uid, "inaccessible": False,
"owner": owner, "filename": filename,
"gid": stats.st_gid, "isdir": isdir,
"group": group, "link-target": link_target,
"size": stats.st_size, "stat": {
"atime": stats.st_atime, "mode": stats.st_mode,
"mtime": stats.st_mtime, "mode-str": filemode(stats.st_mode),
"ctime": stats.st_ctime "uid": stats.st_uid,
} "owner": owner,
} "gid": stats.st_gid,
return response "group": group,
"size": stats.st_size,
"atime": stats.st_atime,
"mtime": stats.st_mtime,
"ctime": stats.st_ctime
},
"mimetype": mimetype
}
return response
def main(): def main():
if(len(sys.argv) < 2): parser = OptionParser()
sys.exit(1) parser.add_option("-m", "--no-mimetype", dest="no_mimetype", default=False, action="store_true")
try: (options, args) = parser.parse_args()
nodes = os.listdir(sys.argv[1]) if(len(args) != 1):
except Exception as e: print("Not enough args: ", args)
print(e) sys.exit(1)
sys.exit(1) try:
response = { nodes = os.listdir(args[0])
".": get_stat(sys.argv[1]), except Exception as e:
"children": [] print(e)
} sys.exit(1)
for node in nodes: response = {
full_path = sys.argv[1] + "/" + node ".": get_stat(args[0], no_mimetype=options.no_mimetype),
response["children"].append(get_stat(full_path, node)) "children": []
print(json.dumps(response)) }
for node in nodes:
full_path = args[0] + "/" + node
response["children"].append(get_stat(full_path, node, no_mimetype=options.no_mimetype))
print(json.dumps(response))
if __name__ == "__main__": if __name__ == "__main__":
main() main()