Release v0.5.4

This commit is contained in:
joshuaboud 2021-07-20 16:08:17 -03:00
commit 79ba268f7b
No known key found for this signature in database
GPG Key ID: 17EFB59E2A8BF50E
22 changed files with 534 additions and 194 deletions

View File

@ -1,4 +1,6 @@
## Cockpit Navigator 0.5.3-1 ## Cockpit Navigator 0.5.4-1
* Implement inline filename editing. * Add fuzzy search.
* Add information popup button. * Optimize folder uploads.
* Fix bugs with selecting entries and renaming files.
* Stop user from deleting or renaming system-critical paths.

View File

@ -23,28 +23,28 @@ With no command line use needed, you can:
# Installation # Installation
## From Github Release ## From Github Release
### Ubuntu ### Ubuntu
1. `$ wget https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.3/cockpit-navigator_0.5.3-1focal_all.deb` 1. `$ wget https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.4/cockpit-navigator_0.5.4-1focal_all.deb`
1. `# apt install ./cockpit-navigator_0.5.3-1focal_all.deb` 1. `# apt install ./cockpit-navigator_0.5.4-1focal_all.deb`
### EL7 ### EL7
1. `# yum install https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.3/cockpit-navigator-0.5.3-1.el7.noarch.rpm` 1. `# yum install https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.4/cockpit-navigator-0.5.4-1.el7.noarch.rpm`
### EL8 ### EL8
1. `# dnf install https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.3/cockpit-navigator-0.5.3-1.el8.noarch.rpm` 1. `# dnf install https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.4/cockpit-navigator-0.5.4-1.el8.noarch.rpm`
## From Source ## From Source
1. Ensure dependencies are installed: `cockpit`, `python3`, `rsync`, `zip`. 1. Ensure dependencies are installed: `cockpit`, `python3`, `rsync`, `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.2 is latest) 1. `$ git checkout <version>` (v0.5.4 is latest)
1. `# make install` 1. `# make install`
## From 45Drives Repositories ## From 45Drives Repositories
### Ubuntu ### Ubuntu
1. Import GPG Key 1. Import GPG Key
```sh ```sh
wget -qO - http://repo.45drives.com/key.asc | sudo apt-key add - wget -qO - https://repo.45drives.com/key/gpg.asc | gpg --dearmor -o /usr/share/keyrings/45drives-archive-keyring.gpg
``` ```
2. Add 45drives.list 2. Add 45drives.sources
```sh ```sh
cd /etc/apt/sources.list.d cd /etc/apt/sources.list.d
sudo wget http://repo.45drives.com/debian/45drives.list curl -sSL https://repo.45drives.com/lists/45drives.sources -o /etc/apt/sources.list.d/45drives.sources
sudo apt update sudo apt update
``` ```
3. Install Package 3. Install Package
@ -52,10 +52,9 @@ sudo apt update
sudo apt install cockpit-navigator sudo apt install cockpit-navigator
``` ```
### EL7/EL8 ### EL7/EL8
1. Add Repository 1. Add 45drives.repo
```sh ```sh
cd /etc/yum.repos.d curl -sSL https://repo.45drives.com/lists/45drives.repo -o /etc/yum.repos.d/45drives.repo
sudo wget http://repo.45drives.com/rhel/45drives.repo
sudo yum clean all sudo yum clean all
``` ```
2. Install Package 2. Install Package

View File

@ -3,7 +3,7 @@
"name": "cockpit-navigator", "name": "cockpit-navigator",
"title": "Cockpit Navigator", "title": "Cockpit Navigator",
"prerelease": false, "prerelease": false,
"version": "0.5.3", "version": "0.5.4",
"buildVersion": "1", "buildVersion": "1",
"author": "Josh Boudreau <jboudreau@45drives.com>", "author": "Josh Boudreau <jboudreau@45drives.com>",
"url": "https://github.com/45Drives/cockpit-navigator", "url": "https://github.com/45Drives/cockpit-navigator",
@ -54,7 +54,7 @@
], ],
"changelog": { "changelog": {
"urgency": "medium", "urgency": "medium",
"version": "0.5.3", "version": "0.5.4",
"buildVersion": "1", "buildVersion": "1",
"ignore": [], "ignore": [],
"date": null, "date": null,

View File

@ -41,34 +41,30 @@ export class FileUpload {
this.reader = new FileReader(); this.reader = new FileReader();
this.chunks = this.slice_file(file); this.chunks = this.slice_file(file);
this.chunk_index = 0; this.chunk_index = 0;
this.timestamp = Date.now();
this.modal_prompt = new ModalPrompt(); this.modal_prompt = new ModalPrompt();
this.using_webkit = true; this.using_webkit = true;
} this.make_html_element();
check_if_exists() {
return new Promise((resolve, reject) => {
var proc = cockpit.spawn(["/usr/share/cockpit/navigator/scripts/fail-if-exists.py3", this.path], {superuser: "try"});
proc.done((data) => {resolve(false)});
proc.fail((e, data) => {resolve(true)});
});
} }
make_html_element() { make_html_element() {
var notification = document.createElement("div"); var notification = this.dom_element = document.createElement("div");
notification.classList.add("nav-notification"); notification.classList.add("nav-notification");
var header = document.createElement("div"); var header = document.createElement("div");
header.classList.add("nav-notification-header"); header.classList.add("nav-notification-header");
notification.appendChild(header); notification.appendChild(header);
header.innerText = "Uploading " + this.filename; header.style.display = "grid";
header.style.position = "relative"; header.style.gridTemplateColumns = "1fr 20px";
header.style.paddingRight = "1em"; header.style.gap = "5px";
var title = document.createElement("p");
title.innerText = "Uploading " + this.filename;
title.title = this.filename;
var cancel = document.createElement("i"); var cancel = document.createElement("i");
cancel.classList.add("fa", "fa-times"); cancel.classList.add("fa", "fa-times");
cancel.style.position = "absolute" cancel.style.justifySelf = "center";
cancel.style.right = "0"; cancel.style.alignSelf = "center";
cancel.style.cursor = "pointer"; cancel.style.cursor = "pointer";
cancel.onclick = () => { cancel.onclick = () => {
if (this.proc) { if (this.proc) {
@ -76,7 +72,8 @@ export class FileUpload {
this.done(); this.done();
} }
} }
header.appendChild(cancel);
header.append(title, cancel);
var info = document.createElement("div"); var info = document.createElement("div");
info.classList.add("flex-row", "space-between"); info.classList.add("flex-row", "space-between");
@ -129,7 +126,8 @@ export class FileUpload {
} }
async upload() { async upload() {
this.make_html_element(); this.timestamp = Date.now();
this.dom_element.style.display = "flex";
this.proc = cockpit.spawn(["/usr/share/cockpit/navigator/scripts/write-chunks.py3", this.path], {err: "out", superuser: "try"}); this.proc = cockpit.spawn(["/usr/share/cockpit/navigator/scripts/write-chunks.py3", this.path], {err: "out", superuser: "try"});
this.proc.fail((e, data) => { this.proc.fail((e, data) => {
this.reader.onload = () => {} this.reader.onload = () => {}
@ -137,8 +135,10 @@ export class FileUpload {
this.nav_window_ref.modal_prompt.alert(e, data); this.nav_window_ref.modal_prompt.alert(e, data);
}) })
this.proc.done((data) => { this.proc.done((data) => {
this.nav_window_ref.refresh(); if (!this.done_hook)
this.nav_window_ref.refresh();
}) })
this.proc.always(() => this?.done_hook?.());
this.reader.onerror = (evt) => { this.reader.onerror = (evt) => {
this.modal_prompt.alert("Failed to read file: " + this.filename, "Upload of directories not supported."); this.modal_prompt.alert("Failed to read file: " + this.filename, "Upload of directories not supported.");
this.done(); this.done();
@ -164,6 +164,7 @@ export class FileUpload {
} }
this.done(); this.done();
} }
this.update_rates_interval = setInterval(this.display_xfr_rate.bind(this), 1000);
} }
/** /**
@ -199,6 +200,7 @@ export class FileUpload {
done() { done() {
this.proc.input(); // close stdin this.proc.input(); // close stdin
this.remove_html_element(); this.remove_html_element();
clearInterval(this.update_rates_interval);
} }
update_xfr_rate() { update_xfr_rate() {
@ -206,12 +208,19 @@ export class FileUpload {
var elapsed = (now - this.timestamp) / 1000; var elapsed = (now - this.timestamp) / 1000;
this.timestamp = now; this.timestamp = now;
var rate = this.chunk_size / elapsed; var rate = this.chunk_size / elapsed;
this.rate.innerText = cockpit.format_bytes_per_sec(rate); this.rate_avg = (this.rate_avg)
? (0.125 * rate + (0.875 * this.rate_avg))
: rate;
// keep exponential moving average of chunk time for eta // keep exponential moving average of chunk time for eta
this.chunk_time = (this.chunk_time) this.chunk_time = (this.chunk_time)
? (0.125 * elapsed + (0.875 * this.chunk_time)) ? (0.125 * elapsed + (0.875 * this.chunk_time))
: elapsed; : elapsed;
var eta = (this.num_chunks - this.chunk_index) * this.chunk_time; var eta = (this.num_chunks - this.chunk_index) * this.chunk_time;
this.eta.innerText = format_time_remaining(eta); this.eta_avg = eta;
}
display_xfr_rate() {
this.rate.innerText = cockpit.format_bytes_per_sec(this.rate_avg);
this.eta.innerText = format_time_remaining(this.eta_avg);
} }
} }

View File

@ -0,0 +1,62 @@
/*
Cockpit Navigator - A File System Browser for Cockpit.
Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com>
Copyright (C) 2021 Sam Silver <ssilver@45drives.com>
Copyright (C) 2021 Dawson Della Valle <ddellavalle@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/>.
*/
import {FileUpload} from "./FileUpload.js";
import {NavWindow} from "./NavWindow.js";
export class FileUploadManager {
/**
*
* @param {NavWindow} nav_window_ref
* @param {number} max_concurrent
*/
constructor(nav_window_ref, max_concurrent = 10) {
this.nav_window_ref = nav_window_ref;
this.running = 0;
this.remaining_uploads = [];
this.max_concurrent = max_concurrent;
this.start_next = () => {
let next_upload = this.remaining_uploads.pop();
if (next_upload) {
next_upload?.upload?.();
this.running++;
}
}
}
/**
*
* @param {...FileUpload} uploads
*/
add(...uploads) {
let done_hook = () => {
this.running--;
this.start_next();
if (!this.running)
this.nav_window_ref.refresh();
}
for (let upload of uploads) {
upload.done_hook = done_hook;
this.remaining_uploads.unshift(upload);
}
while (this.remaining_uploads.length && this.running < this.max_concurrent) {
this.start_next();
}
}
}

View File

@ -72,7 +72,7 @@ export class NavContextMenu {
new_link(e) { new_link(e) {
var default_target = ""; var default_target = "";
if (this.nav_window_ref.selected_entries.size <= 1 && this.target !== this.nav_window_ref.pwd()) if (this.nav_window_ref.selected_entries.size <= 1 && this.target !== this.nav_window_ref.pwd())
default_target = this.target.filename(); default_target = this.target.filename;
this.nav_window_ref.ln(default_target); this.nav_window_ref.ln(default_target);
} }
@ -90,7 +90,14 @@ export class NavContextMenu {
async rename(e) { async rename(e) {
this.hide(); this.hide();
this.target.show_edit(this.target.dom_element.nav_item_title); if (this.target.is_dangerous_path()) {
await this.nav_window_ref.modal_prompt.alert(
"Cannot rename system-critical paths.",
"If you think you need to, use the terminal."
);
} else {
this.target.show_edit(this.target.dom_element.nav_item_title);
}
e.stopPropagation(); e.stopPropagation();
} }

View File

@ -213,6 +213,18 @@ export class NavDir extends NavEntry {
} }
super.show_properties(extra_properties); super.show_properties(extra_properties);
} }
style_selected() {
this.dom_element.nav_item_icon.classList.remove("fa-folder");
this.dom_element.nav_item_icon.classList.add("fa-folder-open");
super.style_selected();
}
unstyle_selected() {
this.dom_element.nav_item_icon.classList.add("fa-folder");
this.dom_element.nav_item_icon.classList.remove("fa-folder-open");
super.unstyle_selected();
}
} }
export class NavDirLink extends NavDir{ export class NavDirLink extends NavDir{

View File

@ -26,7 +26,7 @@ export class NavDownloader {
*/ */
constructor(file) { constructor(file) {
this.path = file.path_str(); this.path = file.path_str();
this.filename = file.filename(); this.filename = file.filename;
this.read_size = file.stat["size"]; this.read_size = file.stat["size"];
} }

View File

@ -18,8 +18,9 @@
*/ */
import {FileUpload} from "./FileUpload.js"; import {FileUpload} from "./FileUpload.js";
import { ModalPrompt } from "./ModalPrompt.js"; import {ModalPrompt} from "./ModalPrompt.js";
import {NavWindow} from "./NavWindow.js"; import {NavWindow} from "./NavWindow.js";
import {FileUploadManager} from "./FileUploadManager.js";
export class NavDragDrop { export class NavDragDrop {
/** /**
@ -35,6 +36,7 @@ export class NavDragDrop {
this.drop_area = drop_area; this.drop_area = drop_area;
this.nav_window_ref = nav_window_ref; this.nav_window_ref = nav_window_ref;
this.modal_prompt = new ModalPrompt(); this.modal_prompt = new ModalPrompt();
this.upload_manager = new FileUploadManager(this.nav_window_ref, 6);
} }
/** /**
@ -82,7 +84,6 @@ export class NavDragDrop {
let path = ""; let path = "";
if (item) { if (item) {
let new_uploads = await this.scan_files(item, path); let new_uploads = await this.scan_files(item, path);
console.log(new_uploads);
uploads.push(... new_uploads); uploads.push(... new_uploads);
} else { } else {
reject(); reject();
@ -98,10 +99,29 @@ export class NavDragDrop {
* @returns {FileUpload[]} * @returns {FileUpload[]}
*/ */
async handle_conflicts(uploads) { async handle_conflicts(uploads) {
let test_paths = [];
for (let upload of uploads)
test_paths.push(upload.path);
let proc = cockpit.spawn(
["/usr/share/cockpit/navigator/scripts/return-exists.py3", ... test_paths],
{error: "out", superuser: "try"}
);
let exist_result;
proc.done((data) => {
exist_result = JSON.parse(data);
});
proc.fail((e, data) => {
this.nav_window_ref.modal_prompt.alert(e, data);
});
try {
await proc;
} catch {
return;
}
let keepers = []; let keepers = [];
let requests = {}; let requests = {};
for (let upload of uploads) { for (let upload of uploads) {
if (!await upload.check_if_exists()) { if (!exist_result[upload.path]) {
keepers.push(upload.filename); keepers.push(upload.filename);
continue; continue;
} }
@ -147,6 +167,7 @@ export class NavDragDrop {
this.drop_area.classList.remove("drag-enter"); this.drop_area.classList.remove("drag-enter");
break; break;
case "drop": case "drop":
this.nav_window_ref.start_load();
let uploads; let uploads;
let items = e.dataTransfer.items; let items = e.dataTransfer.items;
e.preventDefault(); e.preventDefault();
@ -162,10 +183,13 @@ export class NavDragDrop {
} }
} }
this.drop_area.classList.remove("drag-enter"); this.drop_area.classList.remove("drag-enter");
if (uploads.length === 0) if (uploads.length === 0) {
this.nav_window_ref.stop_load();
break; break;
}
uploads = await this.handle_conflicts(uploads); uploads = await this.handle_conflicts(uploads);
uploads.forEach((upload) => {upload.upload()}); this.nav_window_ref.stop_load();
this.upload_manager.add(... uploads);
break; break;
default: default:
this.drop_area.classList.remove("drag-enter"); this.drop_area.classList.remove("drag-enter");

View File

@ -18,7 +18,7 @@
*/ */
import {NavWindow} from "./NavWindow.js"; import {NavWindow} from "./NavWindow.js";
import {format_bytes, property_entry_html, format_time} from "../functions.js"; import {format_bytes, property_entry_html, format_time, check_if_exists} from "../functions.js";
export class NavEntry { export class NavEntry {
/** /**
@ -33,13 +33,14 @@ export class NavEntry {
this.path = path.split("/").splice(1); this.path = path.split("/").splice(1);
else else
this.path = (path.length) ? path : [""]; this.path = (path.length) ? path : [""];
this.filename = this.get_filename();
this.dom_element = document.createElement("div"); this.dom_element = document.createElement("div");
this.dom_element.classList.add("nav-item"); 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-item-icon"); icon.classList.add("nav-item-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", "no-select"); title.classList.add("nav-item-title", "no-select");
title.innerText = this.filename(); title.innerText = this.filename;
this.dom_element.appendChild(icon); this.dom_element.appendChild(icon);
this.dom_element.appendChild(title); this.dom_element.appendChild(title);
let title_edit = this.dom_element.nav_item_title.editor = document.createElement("input"); let title_edit = this.dom_element.nav_item_title.editor = document.createElement("input");
@ -63,10 +64,10 @@ export class NavEntry {
this.dom_element.addEventListener("click", this); this.dom_element.addEventListener("click", this);
this.dom_element.addEventListener("contextmenu", this); this.dom_element.addEventListener("contextmenu", this);
} }
this.is_hidden_file = this.filename().startsWith('.'); this.is_hidden_file = this.filename.startsWith('.');
if (this.is_hidden_file) if (this.is_hidden_file)
icon.style.opacity = 0.5; icon.style.opacity = 0.5;
this.dom_element.title = this.filename(); this.dom_element.title = this.filename;
if (nav_window_ref && nav_window_ref.item_display === "list") { if (nav_window_ref && nav_window_ref.item_display === "list") {
let mode = document.createElement("div"); let mode = document.createElement("div");
let owner = document.createElement("div"); let owner = document.createElement("div");
@ -85,6 +86,7 @@ export class NavEntry {
this.dom_element.appendChild(group); this.dom_element.appendChild(group);
this.dom_element.appendChild(size); this.dom_element.appendChild(size);
} }
this.visible = true;
} }
/** /**
@ -97,13 +99,20 @@ export class NavEntry {
if (this.nav_window_ref.selected_entries.size === 1 && this.nav_window_ref.selected_entries.has(this)) { if (this.nav_window_ref.selected_entries.size === 1 && this.nav_window_ref.selected_entries.has(this)) {
switch (e.target) { switch (e.target) {
case this.dom_element.nav_item_title: case this.dom_element.nav_item_title:
this.show_edit(e.target); this.double_click = true;
if(this.timeout)
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.double_click = false;
if (!this.is_dangerous_path())
this.show_edit(e.target);
}, 500);
e.stopPropagation(); e.stopPropagation();
break; break;
default: default:
break; break;
} }
} }
this.nav_window_ref.set_selected(this, e.shiftKey, e.ctrlKey); this.nav_window_ref.set_selected(this, e.shiftKey, e.ctrlKey);
this.context_menu_ref.hide(); this.context_menu_ref.hide();
break; break;
@ -127,7 +136,7 @@ export class NavEntry {
* *
* @returns {string} * @returns {string}
*/ */
filename() { get_filename() {
var name = this.path[this.path.length - 1]; var name = this.path[this.path.length - 1];
if (!name) if (!name)
name = "/"; name = "/";
@ -151,10 +160,12 @@ export class NavEntry {
} }
show() { show() {
this.visible = true;
this.dom_element.style.display = "flex"; this.dom_element.style.display = "flex";
} }
hide() { hide() {
this.visible = false;
this.dom_element.style.display = "none"; this.dom_element.style.display = "none";
} }
@ -255,10 +266,13 @@ export class NavEntry {
* @param {string} new_path * @param {string} new_path
*/ */
async rename(new_name) { async rename(new_name) {
if (new_name === this.filename()) if (new_name === this.filename)
return; return;
if (new_name.includes("/")) { if (new_name.includes("/")) {
this.nav_window_ref.modal_prompt.alert("File name can't contain `/`."); this.nav_window_ref.modal_prompt.alert(
"File name can't contain `/`.",
"If you want to move the file, right click > cut then right click > paste."
);
return; return;
} else if (new_name === "..") { } else if (new_name === "..") {
this.nav_window_ref.modal_prompt.alert( this.nav_window_ref.modal_prompt.alert(
@ -267,6 +281,14 @@ export class NavEntry {
); );
return; return;
} }
let new_path = [this.nav_window_ref.pwd().path_str(), new_name].join("/");
if (await check_if_exists(new_path)) {
this.nav_window_ref.modal_prompt.alert(
"Failed to rename.",
"File exists: " + new_path
);
return;
}
try { try {
await this.mv(new_name); await this.mv(new_name);
} catch(e) { } catch(e) {
@ -285,11 +307,22 @@ export class NavEntry {
if (!element.editor) if (!element.editor)
return; return;
element.hide_func = () => {this.hide_edit(element)}; element.hide_func = () => {this.hide_edit(element)};
element.editor.onchange = element.hide_func; element.keydown_handler = (e) => {
switch (e.keyCode) {
case 13: // enter
this.apply_edit(element);
case 27: // esc
this.hide_edit(element);
break;
default:
break;
}
};
element.editor.addEventListener("keydown", element.keydown_handler);
window.addEventListener("click", element.hide_func); window.addEventListener("click", element.hide_func);
switch (element) { switch (element) {
case this.dom_element.nav_item_title: case this.dom_element.nav_item_title:
element.editor.value = this.filename(); element.editor.value = this.filename;
break; break;
default: default:
element.editor.value = element.innerText; element.editor.value = element.innerText;
@ -301,7 +334,7 @@ export class NavEntry {
element.editor.focus(); element.editor.focus();
} }
hide_edit(element) { apply_edit(element) {
if (!element.editor) if (!element.editor)
return; return;
switch (element) { switch (element) {
@ -311,8 +344,14 @@ export class NavEntry {
default: default:
break; break;
} }
}
hide_edit(element) {
if (!element.editor)
return;
element.editor.style.display = "none"; element.editor.style.display = "none";
element.style.display = "inline-block"; element.style.display = "inline-block";
element.editor.removeEventListener("keydown", element.keydown_handler)
window.removeEventListener("click", element.hide_func); window.removeEventListener("click", element.hide_func);
} }
@ -323,8 +362,8 @@ export class NavEntry {
show_properties(extra_properties = "") { show_properties(extra_properties = "") {
var selected_name_fields = document.getElementsByClassName("nav-info-column-filename"); 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.innerHTML = this.filename(); elem.innerHTML = this.filename;
elem.title = this.filename(); elem.title = this.filename;
} }
var html = ""; var html = "";
html += property_entry_html("Mode", this.stat["mode-str"]); html += property_entry_html("Mode", this.stat["mode-str"]);
@ -339,7 +378,7 @@ export class NavEntry {
} }
populate_edit_fields() { populate_edit_fields() {
document.getElementById("nav-edit-filename").innerText = this.filename(); document.getElementById("nav-edit-filename").innerText = this.filename;
var mode_bits = [ var mode_bits = [
"other-exec", "other-write", "other-read", "other-exec", "other-write", "other-read",
"group-exec", "group-write", "group-read", "group-exec", "group-write", "group-read",
@ -353,4 +392,20 @@ export class NavEntry {
document.getElementById("nav-edit-owner").value = this.stat["owner"]; document.getElementById("nav-edit-owner").value = this.stat["owner"];
document.getElementById("nav-edit-group").value = this.stat["group"]; document.getElementById("nav-edit-group").value = this.stat["group"];
} }
style_selected() {
this.dom_element.classList.add("nav-item-selected");
}
unstyle_selected() {
this.dom_element.classList.remove("nav-item-selected");
}
/**
*
* @returns {boolean}
*/
is_dangerous_path() {
return this.nav_window_ref.dangerous_dirs.includes(this.path_str());
}
} }

View File

@ -44,6 +44,9 @@ export class NavFile extends NavEntry {
switch(e.type){ switch(e.type){
case "click": case "click":
if (this.double_click) { if (this.double_click) {
if(this.timeout)
clearTimeout(this.timeout);
this.double_click = false;
this.open(); this.open();
return; return;
} else { // single click } else { // single click
@ -94,7 +97,7 @@ export class NavFile extends NavEntry {
this.show_edit_file_contents(); this.show_edit_file_contents();
} else { } else {
console.log("Unknown mimetype: " + type); console.log("Unknown mimetype: " + type);
if (await this.nav_window_ref.modal_prompt.confirm("Can't open " + this.filename() + " for editing.", "Download it instead?")) { if (await this.nav_window_ref.modal_prompt.confirm("Can't open " + this.filename + " for editing.", "Download it instead?")) {
var download = new NavDownloader(this); var download = new NavDownloader(this);
download.download(); download.download();
} }
@ -118,7 +121,7 @@ export class NavFile extends NavEntry {
document.getElementById("nav-cancel-edit-contents-btn").onclick = this.hide_edit_file_contents.bind(this); 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-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-edit-contents-header").innerText = "Editing " + this.path_str();
document.getElementById("nav-contents-view").style.display = "none"; document.getElementById("nav-contents-view-holder").style.display = "none";
document.getElementById("nav-edit-contents-view").style.display = "flex"; document.getElementById("nav-edit-contents-view").style.display = "flex";
} }
@ -140,7 +143,7 @@ export class NavFile extends NavEntry {
window.addEventListener("keydown", this.nav_window_ref); window.addEventListener("keydown", this.nav_window_ref);
document.getElementById("nav-edit-contents-textarea").removeEventListener("keydown", this); document.getElementById("nav-edit-contents-textarea").removeEventListener("keydown", this);
document.getElementById("nav-edit-contents-view").style.display = "none"; document.getElementById("nav-edit-contents-view").style.display = "none";
document.getElementById("nav-contents-view").style.display = "flex"; document.getElementById("nav-contents-view-holder").style.display = "flex";
this.nav_window_ref.enable_buttons(); this.nav_window_ref.enable_buttons();
} }
} }
@ -195,7 +198,7 @@ export class NavFileLink extends NavFile{
this.show_edit_file_contents(); this.show_edit_file_contents();
} else { } else {
console.log("Unknown mimetype: " + type); console.log("Unknown mimetype: " + type);
this.nav_window_ref.modal_prompt.alert("Can't open " + this.filename() + " for editing."); this.nav_window_ref.modal_prompt.alert("Can't open " + this.filename + " for editing.");
} }
} }
@ -218,7 +221,7 @@ export class NavFileLink extends NavFile{
document.getElementById("nav-cancel-edit-contents-btn").onclick = this.hide_edit_file_contents.bind(this); 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-continue-edit-contents-btn").onclick = this.write_to_file.bind(this);
document.getElementById("nav-edit-contents-header").innerHTML = "Editing " + this.path_str() + ' <i class="fas fa-long-arrow-alt-right"></i> ' + this.get_link_target_path(); document.getElementById("nav-edit-contents-header").innerHTML = "Editing " + this.path_str() + ' <i class="fas fa-long-arrow-alt-right"></i> ' + this.get_link_target_path();
document.getElementById("nav-contents-view").style.display = "none"; document.getElementById("nav-contents-view-holder").style.display = "none";
document.getElementById("nav-edit-contents-view").style.display = "flex"; document.getElementById("nav-edit-contents-view").style.display = "flex";
} }

View File

@ -38,7 +38,6 @@ export class NavWindow {
this.window.addEventListener("click", this); this.window.addEventListener("click", this);
this.window.addEventListener("contextmenu", this); this.window.addEventListener("contextmenu", this);
window.addEventListener("keydown", this); window.addEventListener("keydown", this);
this.last_selected_index = -1;
this.context_menu = new NavContextMenu("nav-context-menu", this); this.context_menu = new NavContextMenu("nav-context-menu", this);
@ -49,6 +48,36 @@ export class NavWindow {
this.sort_function = new SortFunctions(); this.sort_function = new SortFunctions();
this.modal_prompt = new ModalPrompt(); this.modal_prompt = new ModalPrompt();
this.dangerous_dirs = [
"/",
"/bin",
"/boot",
"/dev",
"/etc",
"/home",
"/lib",
"/lib32",
"/lib64",
"/mnt",
"/opt",
"/proc",
"/root",
"/run",
"/sbin",
"/srv",
"/sys",
"/tmp",
"/usr",
"/usr/bin",
"/usr/include",
"/usr/lib",
"/usr/lib32",
"/usr/lib64",
"/usr/sbin",
"/usr/share",
"/var"
];
} }
/** /**
@ -59,7 +88,7 @@ export class NavWindow {
switch (e.type) { switch (e.type) {
case "click": case "click":
if (e.target === this.window) { if (e.target === this.window) {
this.clear_selected(); this.reset_selection();
this.show_selected_properties(); this.show_selected_properties();
} }
break; break;
@ -73,6 +102,8 @@ export class NavWindow {
} else if (e.keyCode === 65 && e.ctrlKey) { } else if (e.keyCode === 65 && e.ctrlKey) {
this.select_all(); this.select_all();
e.preventDefault(); e.preventDefault();
} else if (e.keyCode === 27) {
this.reset_selection();
} else if (e.keyCode === 67 && e.ctrlKey) { } else if (e.keyCode === 67 && e.ctrlKey) {
this.copy(); this.copy();
} else if (e.keyCode === 86 && e.ctrlKey) { } else if (e.keyCode === 86 && e.ctrlKey) {
@ -129,7 +160,7 @@ export class NavWindow {
file.context_menu_ref = this.context_menu; file.context_menu_ref = this.context_menu;
}); });
document.getElementById("pwd").value = this.pwd().path_str(); document.getElementById("pwd").value = this.pwd().path_str();
this.set_selected(this.pwd(), false, false); this.reset_selection();
this.show_selected_properties(); this.show_selected_properties();
document.getElementById("nav-num-dirs").innerText = `${num_dirs} Director${(num_dirs === 1)? "y" : "ies"}`; document.getElementById("nav-num-dirs").innerText = `${num_dirs} Director${(num_dirs === 1)? "y" : "ies"}`;
document.getElementById("nav-num-files").innerText = `${num_files} File${(num_files === 1)? "" : "s"}`; document.getElementById("nav-num-files").innerText = `${num_files} File${(num_files === 1)? "" : "s"}`;
@ -178,65 +209,85 @@ export class NavWindow {
this.cd(new NavDir(this.pwd().parent_dir())); this.cd(new NavDir(this.pwd().parent_dir()));
} }
clear_selected() {
for (let entry of this.selected_entries) {
entry.unstyle_selected();
}
this.selected_entries.clear();
}
/** /**
* *
* @param {NavEntry} entry * @param {NavEntry} entry
* @param {Boolean} select_range
* @param {Boolean} append
*/ */
set_selected(entry, select_range, append) { select_one(entry) {
this.hide_edit_selected(); entry.style_selected();
for (let i of this.selected_entries) { this.selected_entries.add(entry);
i.dom_element.classList.remove("nav-item-selected");
if (i.nav_type === "dir") {
i.dom_element.nav_item_icon.classList.remove("fa-folder-open");
i.dom_element.nav_item_icon.classList.add("fa-folder");
}
}
var to_be_selected = [];
if (append && this.selected_entries.has(entry)) {
this.selected_entries.delete(entry);
if (this.selected_entries.size === 0) {
this.clear_selected();
}
} else if (select_range && this.last_selected_index !== -1) {
var start = this.last_selected_index;
var end = this.entries.indexOf(entry);
if (end < start)
[start, end] = [end, start];
if (end === -1)
return;
to_be_selected = this.entries.slice(start, end + 1).filter(entry => !entry.stat["inaccessible"]);
} else {
if (!append)
this.selected_entries.clear();
to_be_selected = [entry];
}
for (let i of to_be_selected) {
this.selected_entries.add(i);
}
for (let i of this.selected_entries) {
i.dom_element.classList.add("nav-item-selected");
if (i.nav_type === "dir") {
i.dom_element.nav_item_icon.classList.remove("fa-folder");
i.dom_element.nav_item_icon.classList.add("fa-folder-open");
}
}
if (this.selected_entries.size > 1){
var name_fields = document.getElementsByClassName("nav-info-column-filename");
for (let name_field of name_fields) {
name_field.innerText = this.selected_entries.size.toString() + " selected"
name_field.title = name_field.innerText;
}
document.getElementById("nav-info-column-properties").innerHTML = "";
} else {
this.show_selected_properties();
}
this.last_selected_index = this.entries.indexOf(entry);
} }
clear_selected() { deselect_one(entry) {
this.set_selected(this.pwd(), false, false); entry.unstyle_selected();
this.selected_entries.delete(entry);
}
/**
*
* @param {NavEntry} start
* @param {NavEntry} end
*/
select_range(start, end) {
let start_ind = this.entries.indexOf(start);
let end_ind = this.entries.indexOf(end);
if (start_ind === -1 || end_ind === -1)
return;
if (end_ind === start_ind)
this.select_one(start);
else if (end_ind < start_ind)
[start_ind, end_ind] = [end_ind, start_ind];
for (let i = start_ind; i <= end_ind; i++) {
let entry = this.entries[i];
if (entry.visible && (!entry.is_hidden_file || this.show_hidden))
this.select_one(entry);
}
}
reset_selection() {
this.clear_selected();
this.select_one(this.pwd());
this.last_selected_entry = null;
this.update_selection_info();
}
/**
*
* @param {NavEntry} target
* @param {Boolean} shift
* @param {Boolean} ctrl
*/
set_selected(target, shift, ctrl) {
if (!ctrl && !shift)
this.clear_selected();
if (!shift || !this.last_selected_entry)
this.last_selected_entry = target;
if (shift) {
if (!ctrl)
this.clear_selected();
this.select_range(
this.last_selected_entry ?? this.entries[0],
target
);
} else if (ctrl && this.selected_entries.has(target)) {
this.deselect_one(target)
} else {
this.select_one(target);
}
this.update_selection_info();
}
select_all() {
this.clear_selected();
this.select_range(this.entries[0], this.entries[this.entries.length - 1]);
this.update_selection_info();
} }
selected_entry() { selected_entry() {
@ -248,29 +299,27 @@ export class NavWindow {
} }
show_selected_properties() { show_selected_properties() {
this.selected_entry().show_properties(); this.selected_entry()?.show_properties?.();
}
update_selection_info() {
if (this.selected_entries.size > 1){
var name_fields = document.getElementsByClassName("nav-info-column-filename");
for (let name_field of name_fields) {
name_field.innerText = this.selected_entries.size.toString() + " selected"
name_field.title = name_field.innerText;
}
document.getElementById("nav-info-column-properties").innerHTML = "";
} else {
this.show_selected_properties();
}
} }
async show_edit_selected() { async show_edit_selected() {
var dangerous_dirs = [
"/",
"/usr",
"/bin",
"/sbin",
"/lib",
"/lib32",
"/lib64",
"/usr/bin",
"/usr/include",
"/usr/lib",
"/usr/lib32",
"/usr/lib64",
"/usr/sbin",
];
var dangerous_selected = []; var dangerous_selected = [];
for (let i of this.selected_entries) { for (let i of this.selected_entries) {
var path = i.path_str(); var path = i.path_str();
if (dangerous_dirs.includes(path)) { if (this.dangerous_dirs.includes(path)) {
dangerous_selected.push(path); dangerous_selected.push(path);
} }
} }
@ -321,7 +370,7 @@ export class NavWindow {
} }
var targets = []; var targets = [];
for (let target of this.selected_entries) { for (let target of this.selected_entries) {
targets.push(target.filename()); targets.push(target.filename);
} }
var targets_str = targets.join(", "); var targets_str = targets.join(", ");
document.getElementById("selected-files-list-header").innerText = "Applying edits to:"; document.getElementById("selected-files-list-header").innerText = "Applying edits to:";
@ -395,6 +444,8 @@ export class NavWindow {
} }
async delete_selected() { async delete_selected() {
if (await this.check_if_dangerous("delete"))
return;
var prompt = ""; var prompt = "";
if (this.selected_entries.size > 1) { if (this.selected_entries.size > 1) {
prompt = "Deleting " + this.selected_entries.size + " files."; prompt = "Deleting " + this.selected_entries.size + " files.";
@ -404,6 +455,7 @@ export class NavWindow {
if (!await this.modal_prompt.confirm(prompt, "This cannot be undone. Are you sure?", true)) { if (!await this.modal_prompt.confirm(prompt, "This cannot be undone. Are you sure?", true)) {
return; return;
} }
this.start_load();
for (let target of this.selected_entries) { for (let target of this.selected_entries) {
try { try {
await target.rm(); await target.rm();
@ -411,6 +463,7 @@ export class NavWindow {
this.modal_prompt.alert(e); this.modal_prompt.alert(e);
} }
} }
this.stop_load();
this.refresh(); this.refresh();
} }
@ -545,7 +598,9 @@ export class NavWindow {
this.refresh(); this.refresh();
} }
cut() { async cut() {
if (await this.check_if_dangerous("move"))
return;
this.clip_board = [...this.selected_entries]; this.clip_board = [...this.selected_entries];
this.copy_or_move = "move"; this.copy_or_move = "move";
this.paste_cwd = this.pwd().path_str(); this.paste_cwd = this.pwd().path_str();
@ -643,6 +698,7 @@ export class NavWindow {
default: default:
break; break;
} }
e.stopPropagation();
} }
nav_bar_cd() { nav_bar_cd() {
@ -681,7 +737,7 @@ export class NavWindow {
start_load() { start_load() {
document.getElementById("nav-loader-container").style.display = "block"; document.getElementById("nav-loader-container").style.display = "block";
var buttons = document.getElementsByTagName("button"); var buttons = document.getElementsByClassName("disable-while-loading");
for (let button of buttons) { for (let button of buttons) {
button.disabled = true; button.disabled = true;
} }
@ -689,7 +745,7 @@ export class NavWindow {
stop_load() { stop_load() {
document.getElementById("nav-loader-container").style.display = "none"; document.getElementById("nav-loader-container").style.display = "none";
var buttons = document.getElementsByTagName("button"); var buttons = document.getElementsByClassName("disable-while-loading");
for (let button of buttons) { for (let button of buttons) {
button.disabled = false; button.disabled = false;
} }
@ -801,15 +857,6 @@ export class NavWindow {
this.set_nav_button_state(); this.set_nav_button_state();
} }
select_all() {
this.selected_entries.clear();
for (let entry of this.entries) {
if (!entry.is_hidden_file || this.show_hidden) {
this.set_selected(entry, false, true);
}
}
}
async switch_item_display() { async switch_item_display() {
var button = document.getElementById("nav-item-display-icon"); var button = document.getElementById("nav-item-display-icon");
if (this.item_display === "grid") { if (this.item_display === "grid") {
@ -833,11 +880,42 @@ export class NavWindow {
search_filter(event) { search_filter(event) {
var search_name = event.target.value; var search_name = event.target.value;
let search_func;
if (search_name[0] === '*')
search_func = (entry) => entry.filename.toLowerCase().includes(search_name.slice(1).toLowerCase());
else
search_func = (entry) => entry.filename.toLowerCase().startsWith(search_name.toLowerCase());
this.entries.forEach((entry) => { this.entries.forEach((entry) => {
if (entry.filename().toLowerCase().startsWith(search_name.toLowerCase())) if (search_func(entry))
entry.show(); entry.show();
else else
entry.hide(); entry.hide();
}); });
} }
/**
*
* @param {string} verb
* @returns {Promise<boolean>}
*/
check_if_dangerous(verb) {
return new Promise(async (resolve, reject) => {
let dangerous_selected = [];
for (let entry of this.selected_entries) {
let path = entry.path_str();
if (this.dangerous_dirs.includes(path)) {
dangerous_selected.push(path);
}
}
if (dangerous_selected.length) {
await this.modal_prompt.alert(
`Cannot ${verb} system-critical paths.`,
`The following path(s) are very dangerous to ${verb}: ${dangerous_selected.join(", ")}. If you think you need to ${verb} them, use the terminal.`
);
resolve(true);
} else {
resolve(false);
}
});
}
} }

View File

@ -60,11 +60,11 @@ export class SortFunctions {
} }
name_asc(first, second) { name_asc(first, second) {
return first.filename().localeCompare(second.filename()); return first.filename.localeCompare(second.filename);
} }
name_desc(first, second) { name_desc(first, second) {
return second.filename().localeCompare(first.filename()); return second.filename.localeCompare(first.filename);
} }
owner_asc(first, second) { owner_asc(first, second) {

View File

@ -70,9 +70,7 @@ export function format_time_remaining(seconds_) {
if (hours) { if (hours) {
out = String(hours).padStart(2, '0') + ":"; out = String(hours).padStart(2, '0') + ":";
} }
if (minutes) { out += String(minutes).padStart(2, '0') + ":";
out += String(minutes).padStart(2, '0') + ":";
}
out += String(seconds).padStart(2, '0'); out += String(seconds).padStart(2, '0');
return out; return out;
} }
@ -95,4 +93,17 @@ export function format_permissions(mode) {
} }
} }
return result; return result;
} }
/**
*
* @param {string} path
* @returns {Promise<boolean>}
*/
export function check_if_exists(path) {
return new Promise((resolve, reject) => {
var proc = cockpit.spawn(["/usr/share/cockpit/navigator/scripts/fail-if-exists.py3", path], {superuser: "try"});
proc.done((data) => {resolve(false)});
proc.fail((e, data) => {resolve(true)});
});
}

View File

@ -651,7 +651,7 @@ input:checked + .slider:before {
.nav-notifications { .nav-notifications {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
right: 0; right: 10px;
padding: 5px; padding: 5px;
display: flex; display: flex;
flex-flow: column-reverse nowrap; flex-flow: column-reverse nowrap;
@ -663,7 +663,7 @@ input:checked + .slider:before {
.nav-notification { .nav-notification {
margin: 5px; margin: 5px;
position: relative; position: relative;
display: flex; display: none;
flex-flow: column nowrap; flex-flow: column nowrap;
align-items: stretch; align-items: stretch;
z-index: 10; z-index: 10;
@ -673,15 +673,23 @@ input:checked + .slider:before {
border-radius: var(--nav-border-radius); border-radius: var(--nav-border-radius);
color: var(--font); color: var(--font);
} }
/* .nav-notification-header {
.nav-notification-header {
position: relative; position: relative;
z-index: 10; z-index: 10;
font-weight: bold; font-weight: bold;
} } */
.nav-notification-header > progress { /* .nav-notification-header > progress {
position: relative; position: relative;
z-index: 10; z-index: 10;
} */
.nav-notification-header > p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.no-border {
border: none;
}

View File

@ -40,13 +40,13 @@
<div class="flex-col outer-container"> <div class="flex-col outer-container">
<div class="flex-row nav-header"> <div class="flex-row nav-header">
<div class="nav-btn-group"> <div class="nav-btn-group">
<button class="pf-c-button pf-m-secondary" id="nav-back-btn" title="Back"><i class="fas fa-arrow-left"></i></button> <button class="disable-while-loading pf-c-button pf-m-secondary" id="nav-back-btn" title="Back"><i class="fas fa-arrow-left"></i></button>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<button class="pf-c-button pf-m-secondary" id="nav-forward-btn" title="Forward"><i class="fas fa-arrow-right"></i></button> <button class="disable-while-loading pf-c-button pf-m-secondary" id="nav-forward-btn" title="Forward"><i class="fas fa-arrow-right"></i></button>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<button class="pf-c-button pf-m-secondary" id="nav-up-dir-btn" title="Up"><i class="fas fa-arrow-up"></i></button> <button class="disable-while-loading pf-c-button pf-m-secondary" id="nav-up-dir-btn" title="Up"><i class="fas fa-arrow-up"></i></button>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<button class="pf-c-button pf-m-secondary" id="nav-refresh-btn" title="Refresh"><i class="fas fa-sync"></i></button> <button class="disable-while-loading pf-c-button pf-m-secondary" id="nav-refresh-btn" title="Refresh"><i class="fas fa-sync"></i></button>
</div> </div>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<input type="text" list="possible-paths-list" autocomplete="off" class="navigation-bar" id="pwd" title="Navigation Bar"></input> <input type="text" list="possible-paths-list" autocomplete="off" class="navigation-bar" id="pwd" title="Navigation Bar"></input>
@ -55,27 +55,29 @@
</select> </select>
</datalist> </datalist>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<input type="text" autocomplete="off" class="search-bar" id="search-bar" title="Search in Directory" placeholder="Search in Directory"></input> <input type="text" autocomplete="off" class="search-bar" id="search-bar" title="Prepend * to fuzzy search" placeholder="Search in Directory"></input>
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<div class="nav-btn-group"> <div class="nav-btn-group">
<button class="pf-c-button pf-m-primary" id="nav-mkdir-btn" title="New Directory"><i class="fas fa-folder-plus"></i></button> <button class="disable-while-loading pf-c-button pf-m-primary" id="nav-mkdir-btn" title="New Directory"><i class="fas fa-folder-plus"></i></button>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<button class="pf-c-button pf-m-primary" id="nav-touch-btn" title="New File"><i class="fas fa-file-medical"></i></button> <button class="disable-while-loading pf-c-button pf-m-primary" id="nav-touch-btn" title="New File"><i class="fas fa-file-medical"></i></button>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<button class="pf-c-button pf-m-primary" id="nav-ln-btn" title="New Symbolic Link"><i class="fas fa-link nav-icon-decorated"><i class="fas fa-plus nav-icon-decoration"></i></i></button> <button class="disable-while-loading pf-c-button pf-m-primary" id="nav-ln-btn" title="New Symbolic Link"><i class="fas fa-link nav-icon-decorated"><i class="fas fa-plus nav-icon-decoration"></i></i></button>
</div> </div>
</div> </div>
<div class="vertical-spacer"></div> <div class="vertical-spacer"></div>
<div class="flex-row inner-container"> <div class="flex-row inner-container">
<div class="contents-view contents-view-grid" id="nav-contents-view"> <div class="contents-view" id="nav-contents-view-holder">
<div class="contents-view-list-header nav-item"> <div class="contents-view contents-view-grid no-border" id="nav-contents-view">
<i class="nav-item-icon"></i> <div class="contents-view-list-header nav-item">
<div class="nav-item-title" id="sort-name-btn">Name<i class="sort-arrow fas fa-chevron-up" id="sort-name-icon"></i></div> <i class="nav-item-icon"></i>
<div id="sort-mode-btn">Mode</div> <div class="nav-item-title" id="sort-name-btn">Name<i class="sort-arrow fas fa-chevron-up" id="sort-name-icon"></i></div>
<div id="sort-owner-btn">Owner<i class="sort-arrow fas" id="sort-owner-icon"></i></div> <div id="sort-mode-btn">Mode</div>
<div id="sort-group-btn">Group<i class="sort-arrow fas" id="sort-group-icon"></i></div> <div id="sort-owner-btn">Owner<i class="sort-arrow fas" id="sort-owner-icon"></i></div>
<div id="sort-size-btn">Size<i class="sort-arrow fas" id="sort-size-icon"></i></div> <div id="sort-group-btn">Group<i class="sort-arrow fas" id="sort-group-icon"></i></div>
<div id="sort-size-btn">Size<i class="sort-arrow fas" id="sort-size-icon"></i></div>
</div>
</div> </div>
<div class="nav-notifications" id="nav-notifications"> <div class="nav-notifications" id="nav-notifications">
</div> </div>
@ -86,9 +88,9 @@
<textarea id="nav-edit-contents-textarea" spellcheck="false"></textarea> <textarea id="nav-edit-contents-textarea" spellcheck="false"></textarea>
<div class="vertical-spacer"></div> <div class="vertical-spacer"></div>
<div class="nav-btn-group"> <div class="nav-btn-group">
<button class="pf-c-button pf-m-danger editor-btn" id="nav-cancel-edit-contents-btn" title="Cancel"><i class="fas fa-times"></i></button> <button class="disable-while-loading pf-c-button pf-m-danger editor-btn" id="nav-cancel-edit-contents-btn" title="Cancel"><i class="fas fa-times"></i></button>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<button class="pf-c-button pf-m-primary editor-btn" id="nav-continue-edit-contents-btn" title="Save"><i class="fas fa-save"></i></button> <button class="disable-while-loading pf-c-button pf-m-primary editor-btn" id="nav-continue-edit-contents-btn" title="Save"><i class="fas fa-save"></i></button>
</div> </div>
</div> </div>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
@ -97,9 +99,9 @@
<div class="flex-row space-between"> <div class="flex-row space-between">
<div class="nav-info-column-filename"></div> <div class="nav-info-column-filename"></div>
<div class="nav-btn-group"> <div class="nav-btn-group">
<button class="pf-c-button pf-m-danger" id="nav-delete-btn" title="Delete"><i class="fas fa-trash-alt"></i></button> <button class="disable-while-loading pf-c-button pf-m-danger" id="nav-delete-btn" title="Delete"><i class="fas fa-trash-alt"></i></button>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<button class="pf-c-button pf-m-primary" id="nav-edit-properties-btn" title="Edit Properties"><i class="fas fa-sliders-h"></i></button> <button class="disable-while-loading pf-c-button pf-m-primary" id="nav-edit-properties-btn" title="Edit Properties"><i class="fas fa-sliders-h"></i></button>
</div> </div>
</div> </div>
<div class="nav-info-column-properties" id="nav-info-column-properties"></div> <div class="nav-info-column-properties" id="nav-info-column-properties"></div>
@ -153,9 +155,9 @@
<div class="flex-grow monospace-sm" id="selected-files-list"></div> <div class="flex-grow monospace-sm" id="selected-files-list"></div>
<div class="vertical-spacer"></div> <div class="vertical-spacer"></div>
<div class="nav-btn-group"> <div class="nav-btn-group">
<button class="pf-c-button pf-m-danger" id="nav-cancel-edit-btn" title="Cancel"><i class="fas fa-times"></i></button> <button class="disable-while-loading pf-c-button pf-m-danger" id="nav-cancel-edit-btn" title="Cancel"><i class="fas fa-times"></i></button>
<div class="horizontal-spacer"></div> <div class="horizontal-spacer"></div>
<button class="pf-c-button pf-m-primary" id="nav-apply-edit-btn" title="Save Changes"><i class="fas fa-save"></i></button> <button class="disable-while-loading pf-c-button pf-m-primary" id="nav-apply-edit-btn" title="Save Changes"><i class="fas fa-save"></i></button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -130,6 +130,7 @@ function set_up_buttons() {
document.getElementById("search-bar").addEventListener("keydown", (e) => { document.getElementById("search-bar").addEventListener("keydown", (e) => {
if (e.keyCode === 13) if (e.keyCode === 13)
nav_window.search_filter(e); nav_window.search_filter(e);
e.stopPropagation();
}); });
// fix tab in editor input // fix tab in editor input
document.getElementById('nav-edit-contents-textarea').addEventListener('keydown', (e) => { document.getElementById('nav-edit-contents-textarea').addEventListener('keydown', (e) => {

View File

@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""
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/>.
"""
"""
Synopsys: return-exists.py3 /full/path1 [/full/path2 ...]
replys with JSON formatted dictionary of path : boolean where true means the file exists
"""
import os
import sys
import json
def main():
argv = sys.argv
argc = len(sys.argv)
if argc <= 1:
print("No arguments provided")
sys.exit(1)
response = {}
for i in range (1, argc):
path = argv[i]
response[path] = os.path.lexists(path)
print(json.dumps(response))
sys.exit(0)
if __name__ == "__main__":
main()

View File

@ -18,7 +18,7 @@
""" """
""" """
Synopsis: `write-chunks.py3 <newline delimited JSON objects>` Synopsis: `echo <newline delimited JSON objects> | write-chunks.py3`
JSON objects are of form: JSON objects are of form:
obj = { obj = {
seek: <byte offset> seek: <byte offset>
@ -35,20 +35,24 @@ def write_chunk(chunk, file):
if not file: if not file:
path = sys.argv[1] path = sys.argv[1]
parent_path = os.path.dirname(path) parent_path = os.path.dirname(path)
if not os.path.exists(parent_path):
os.makedirs(parent_path, exist_ok=True)
elif os.path.isfile(parent_path):
print(parent_path + ": exists and is not a directory.")
sys.exit(1)
try: try:
if not os.path.exists(parent_path):
os.makedirs(parent_path, exist_ok=True)
elif os.path.isfile(parent_path):
print(parent_path + ": exists and is not a directory.")
sys.exit(1)
file = open(path, "wb") file = open(path, "wb")
except Exception as e: except Exception as e:
print(e) print(e)
sys.exit(1) sys.exit(1)
seek = chunk["seek"] seek = chunk["seek"]
data = base64.b64decode(chunk["chunk"]) data = base64.b64decode(chunk["chunk"])
file.seek(seek) try:
file.write(data) file.seek(seek)
file.write(data)
except Exception as e:
print(e)
sys.exit(1)
def main(): def main():
file = None file = None

View File

@ -32,6 +32,11 @@ rm -rf %{buildroot}
/usr/share/cockpit/navigator/* /usr/share/cockpit/navigator/*
%changelog %changelog
* Tue Jul 20 2021 Josh Boudreau <jboudreau@45drives.com> 0.5.4-1
- Add fuzzy search.
- Optimize folder uploads.
- Fix bugs with selecting entries and renaming files.
- Stop user from deleting or renaming system-critical paths.
* Mon Jul 19 2021 Josh Boudreau <jboudreau@45drives.com> 0.5.3-1 * Mon Jul 19 2021 Josh Boudreau <jboudreau@45drives.com> 0.5.3-1
- Implement inline filename editing. - Implement inline filename editing.
- Add information popup button. - Add information popup button.

View File

@ -32,6 +32,11 @@ rm -rf %{buildroot}
/usr/share/cockpit/navigator/* /usr/share/cockpit/navigator/*
%changelog %changelog
* Tue Jul 20 2021 Josh Boudreau <jboudreau@45drives.com> 0.5.4-1
- Add fuzzy search.
- Optimize folder uploads.
- Fix bugs with selecting entries and renaming files.
- Stop user from deleting or renaming system-critical paths.
* Mon Jul 19 2021 Josh Boudreau <jboudreau@45drives.com> 0.5.3-1 * Mon Jul 19 2021 Josh Boudreau <jboudreau@45drives.com> 0.5.3-1
- Implement inline filename editing. - Implement inline filename editing.
- Add information popup button. - Add information popup button.

View File

@ -1,3 +1,12 @@
cockpit-navigator (0.5.4-1focal) focal; urgency=medium
* Add fuzzy search.
* Optimize folder uploads.
* Fix bugs with selecting entries and renaming files.
* Stop user from deleting or renaming system-critical paths.
-- Josh Boudreau <jboudreau@45drives.com> Tue, 20 Jul 2021 13:06:32 -0300
cockpit-navigator (0.5.3-1focal) focal; urgency=medium cockpit-navigator (0.5.3-1focal) focal; urgency=medium
* Implement inline filename editing. * Implement inline filename editing.