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
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
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. `$ cd cockpit-navigator`
1. `$ git checkout <version>` (v0.5.8 is latest)

View File

@ -25,13 +25,13 @@
"deb": [
"cockpit",
"python3",
"rsync",
"python3-magic",
"zip"
],
"el": [
"cockpit",
"python3",
"rsync",
"python3-magic",
"zip"
]
},
@ -57,11 +57,12 @@
"version": "0.5.9",
"buildVersion": "1",
"ignore": [],
"date": "2021-12-15T17:43:15.251513",
"date": "2021-12-16T14:46:53.434779",
"packager": "Joshua Boudreau <jboudreau@45drives.com>",
"changes": [
"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 {Boolean} show_mimetype_icons
* @returns {Promise<NavEntry[]>}
*/
get_children(nav_window_ref) {
get_children(nav_window_ref, show_mimetype_icons = false) {
return new Promise(async (resolve, reject) => {
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(
["/usr/share/cockpit/navigator/scripts/ls.py3", this.path_str()],
argv,
{err:"out", superuser: "try"}
);
proc.fail((e, data) => {
console.log(argv);
reject(data);
});
var data;
@ -100,7 +105,7 @@ export class NavDir extends NavEntry {
children.push(new NavFileLink(path, stat, nav_window_ref, entry["link-target"]));
break;
default:
children.push(new NavFile(path, stat, nav_window_ref));
children.push(new NavFile(path, stat, nav_window_ref, entry["mimetype"]));
break;
}
});

View File

@ -29,10 +29,61 @@ export class NavFile extends NavEntry {
* @param {object} stat
* @param {NavWindow} nav_window_ref
*/
constructor(path, stat, nav_window_ref) {
constructor(path, stat, nav_window_ref, mimetype) {
super(path, stat, nav_window_ref);
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;
}

View File

@ -28,6 +28,9 @@ import { format_bytes, format_permissions } from "../functions.js";
export class NavWindow {
constructor() {
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 = 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.start_load();
try {
var files = await this.pwd().get_children(this);
var files = await this.pwd().get_children(this, this.show_mimetype_icons);
} catch(e) {
await this.modal_prompt.alert(e);
this.up();
@ -914,6 +917,13 @@ export class NavWindow {
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) {
var search_name = event.target.value;
let search_func;

View File

@ -181,6 +181,17 @@
<i class="fas fa-list" id="nav-item-display-icon"></i>
</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-btn-group">
<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("keydown", nav_window.nav_bar_event_handler.bind(nav_window));
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-item-display-btn").addEventListener("click", nav_window.switch_item_display.bind(nav_window));
for (let option of ["name", "owner", "group", "size", "modified", "created"]) {

View File

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