mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-07-30 09:05:23 +02:00
Release v0.5.4
This commit is contained in:
commit
79ba268f7b
@ -1,4 +1,6 @@
|
||||
## Cockpit Navigator 0.5.3-1
|
||||
## Cockpit Navigator 0.5.4-1
|
||||
|
||||
* Implement inline filename editing.
|
||||
* Add information popup button.
|
||||
* Add fuzzy search.
|
||||
* Optimize folder uploads.
|
||||
* Fix bugs with selecting entries and renaming files.
|
||||
* Stop user from deleting or renaming system-critical paths.
|
21
README.md
21
README.md
@ -23,28 +23,28 @@ With no command line use needed, you can:
|
||||
# Installation
|
||||
## From Github Release
|
||||
### Ubuntu
|
||||
1. `$ wget https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.3/cockpit-navigator_0.5.3-1focal_all.deb`
|
||||
1. `# apt install ./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.4-1focal_all.deb`
|
||||
### 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
|
||||
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
|
||||
1. Ensure dependencies are installed: `cockpit`, `python3`, `rsync`, `zip`.
|
||||
1. `$ git clone https://github.com/45Drives/cockpit-navigator.git`
|
||||
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`
|
||||
## From 45Drives Repositories
|
||||
### Ubuntu
|
||||
1. Import GPG Key
|
||||
```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
|
||||
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
|
||||
```
|
||||
3. Install Package
|
||||
@ -52,10 +52,9 @@ sudo apt update
|
||||
sudo apt install cockpit-navigator
|
||||
```
|
||||
### EL7/EL8
|
||||
1. Add Repository
|
||||
1. Add 45drives.repo
|
||||
```sh
|
||||
cd /etc/yum.repos.d
|
||||
sudo wget http://repo.45drives.com/rhel/45drives.repo
|
||||
curl -sSL https://repo.45drives.com/lists/45drives.repo -o /etc/yum.repos.d/45drives.repo
|
||||
sudo yum clean all
|
||||
```
|
||||
2. Install Package
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "cockpit-navigator",
|
||||
"title": "Cockpit Navigator",
|
||||
"prerelease": false,
|
||||
"version": "0.5.3",
|
||||
"version": "0.5.4",
|
||||
"buildVersion": "1",
|
||||
"author": "Josh Boudreau <jboudreau@45drives.com>",
|
||||
"url": "https://github.com/45Drives/cockpit-navigator",
|
||||
@ -54,7 +54,7 @@
|
||||
],
|
||||
"changelog": {
|
||||
"urgency": "medium",
|
||||
"version": "0.5.3",
|
||||
"version": "0.5.4",
|
||||
"buildVersion": "1",
|
||||
"ignore": [],
|
||||
"date": null,
|
||||
|
@ -41,34 +41,30 @@ export class FileUpload {
|
||||
this.reader = new FileReader();
|
||||
this.chunks = this.slice_file(file);
|
||||
this.chunk_index = 0;
|
||||
this.timestamp = Date.now();
|
||||
this.modal_prompt = new ModalPrompt();
|
||||
this.using_webkit = true;
|
||||
}
|
||||
|
||||
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)});
|
||||
});
|
||||
this.make_html_element();
|
||||
}
|
||||
|
||||
make_html_element() {
|
||||
var notification = document.createElement("div");
|
||||
var notification = this.dom_element = document.createElement("div");
|
||||
notification.classList.add("nav-notification");
|
||||
|
||||
var header = document.createElement("div");
|
||||
header.classList.add("nav-notification-header");
|
||||
notification.appendChild(header);
|
||||
header.innerText = "Uploading " + this.filename;
|
||||
header.style.position = "relative";
|
||||
header.style.paddingRight = "1em";
|
||||
header.style.display = "grid";
|
||||
header.style.gridTemplateColumns = "1fr 20px";
|
||||
header.style.gap = "5px";
|
||||
|
||||
var title = document.createElement("p");
|
||||
title.innerText = "Uploading " + this.filename;
|
||||
title.title = this.filename;
|
||||
|
||||
var cancel = document.createElement("i");
|
||||
cancel.classList.add("fa", "fa-times");
|
||||
cancel.style.position = "absolute"
|
||||
cancel.style.right = "0";
|
||||
cancel.style.justifySelf = "center";
|
||||
cancel.style.alignSelf = "center";
|
||||
cancel.style.cursor = "pointer";
|
||||
cancel.onclick = () => {
|
||||
if (this.proc) {
|
||||
@ -76,7 +72,8 @@ export class FileUpload {
|
||||
this.done();
|
||||
}
|
||||
}
|
||||
header.appendChild(cancel);
|
||||
|
||||
header.append(title, cancel);
|
||||
|
||||
var info = document.createElement("div");
|
||||
info.classList.add("flex-row", "space-between");
|
||||
@ -129,7 +126,8 @@ export class FileUpload {
|
||||
}
|
||||
|
||||
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.fail((e, data) => {
|
||||
this.reader.onload = () => {}
|
||||
@ -137,8 +135,10 @@ export class FileUpload {
|
||||
this.nav_window_ref.modal_prompt.alert(e, 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.modal_prompt.alert("Failed to read file: " + this.filename, "Upload of directories not supported.");
|
||||
this.done();
|
||||
@ -164,6 +164,7 @@ export class FileUpload {
|
||||
}
|
||||
this.done();
|
||||
}
|
||||
this.update_rates_interval = setInterval(this.display_xfr_rate.bind(this), 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,6 +200,7 @@ export class FileUpload {
|
||||
done() {
|
||||
this.proc.input(); // close stdin
|
||||
this.remove_html_element();
|
||||
clearInterval(this.update_rates_interval);
|
||||
}
|
||||
|
||||
update_xfr_rate() {
|
||||
@ -206,12 +208,19 @@ export class FileUpload {
|
||||
var elapsed = (now - this.timestamp) / 1000;
|
||||
this.timestamp = now;
|
||||
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
|
||||
this.chunk_time = (this.chunk_time)
|
||||
? (0.125 * elapsed + (0.875 * this.chunk_time))
|
||||
: elapsed;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
62
navigator/components/FileUploadManager.js
Normal file
62
navigator/components/FileUploadManager.js
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -72,7 +72,7 @@ export class NavContextMenu {
|
||||
new_link(e) {
|
||||
var default_target = "";
|
||||
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);
|
||||
}
|
||||
|
||||
@ -90,7 +90,14 @@ export class NavContextMenu {
|
||||
|
||||
async rename(e) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -213,6 +213,18 @@ export class NavDir extends NavEntry {
|
||||
}
|
||||
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{
|
||||
|
@ -26,7 +26,7 @@ export class NavDownloader {
|
||||
*/
|
||||
constructor(file) {
|
||||
this.path = file.path_str();
|
||||
this.filename = file.filename();
|
||||
this.filename = file.filename;
|
||||
this.read_size = file.stat["size"];
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,9 @@
|
||||
*/
|
||||
|
||||
import {FileUpload} from "./FileUpload.js";
|
||||
import { ModalPrompt } from "./ModalPrompt.js";
|
||||
import {ModalPrompt} from "./ModalPrompt.js";
|
||||
import {NavWindow} from "./NavWindow.js";
|
||||
import {FileUploadManager} from "./FileUploadManager.js";
|
||||
|
||||
export class NavDragDrop {
|
||||
/**
|
||||
@ -35,6 +36,7 @@ export class NavDragDrop {
|
||||
this.drop_area = drop_area;
|
||||
this.nav_window_ref = nav_window_ref;
|
||||
this.modal_prompt = new ModalPrompt();
|
||||
this.upload_manager = new FileUploadManager(this.nav_window_ref, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,7 +84,6 @@ export class NavDragDrop {
|
||||
let path = "";
|
||||
if (item) {
|
||||
let new_uploads = await this.scan_files(item, path);
|
||||
console.log(new_uploads);
|
||||
uploads.push(... new_uploads);
|
||||
} else {
|
||||
reject();
|
||||
@ -98,10 +99,29 @@ export class NavDragDrop {
|
||||
* @returns {FileUpload[]}
|
||||
*/
|
||||
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 requests = {};
|
||||
for (let upload of uploads) {
|
||||
if (!await upload.check_if_exists()) {
|
||||
if (!exist_result[upload.path]) {
|
||||
keepers.push(upload.filename);
|
||||
continue;
|
||||
}
|
||||
@ -147,6 +167,7 @@ export class NavDragDrop {
|
||||
this.drop_area.classList.remove("drag-enter");
|
||||
break;
|
||||
case "drop":
|
||||
this.nav_window_ref.start_load();
|
||||
let uploads;
|
||||
let items = e.dataTransfer.items;
|
||||
e.preventDefault();
|
||||
@ -162,10 +183,13 @@ export class NavDragDrop {
|
||||
}
|
||||
}
|
||||
this.drop_area.classList.remove("drag-enter");
|
||||
if (uploads.length === 0)
|
||||
if (uploads.length === 0) {
|
||||
this.nav_window_ref.stop_load();
|
||||
break;
|
||||
}
|
||||
uploads = await this.handle_conflicts(uploads);
|
||||
uploads.forEach((upload) => {upload.upload()});
|
||||
this.nav_window_ref.stop_load();
|
||||
this.upload_manager.add(... uploads);
|
||||
break;
|
||||
default:
|
||||
this.drop_area.classList.remove("drag-enter");
|
||||
|
@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
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 {
|
||||
/**
|
||||
@ -33,13 +33,14 @@ export class NavEntry {
|
||||
this.path = path.split("/").splice(1);
|
||||
else
|
||||
this.path = (path.length) ? path : [""];
|
||||
this.filename = this.get_filename();
|
||||
this.dom_element = document.createElement("div");
|
||||
this.dom_element.classList.add("nav-item");
|
||||
let icon = this.dom_element.nav_item_icon = document.createElement("i");
|
||||
icon.classList.add("nav-item-icon");
|
||||
let title = this.dom_element.nav_item_title = document.createElement("div");
|
||||
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(title);
|
||||
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("contextmenu", this);
|
||||
}
|
||||
this.is_hidden_file = this.filename().startsWith('.');
|
||||
this.is_hidden_file = this.filename.startsWith('.');
|
||||
if (this.is_hidden_file)
|
||||
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") {
|
||||
let mode = document.createElement("div");
|
||||
let owner = document.createElement("div");
|
||||
@ -85,6 +86,7 @@ export class NavEntry {
|
||||
this.dom_element.appendChild(group);
|
||||
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)) {
|
||||
switch (e.target) {
|
||||
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();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.nav_window_ref.set_selected(this, e.shiftKey, e.ctrlKey);
|
||||
this.context_menu_ref.hide();
|
||||
break;
|
||||
@ -127,7 +136,7 @@ export class NavEntry {
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
filename() {
|
||||
get_filename() {
|
||||
var name = this.path[this.path.length - 1];
|
||||
if (!name)
|
||||
name = "/";
|
||||
@ -151,10 +160,12 @@ export class NavEntry {
|
||||
}
|
||||
|
||||
show() {
|
||||
this.visible = true;
|
||||
this.dom_element.style.display = "flex";
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.visible = false;
|
||||
this.dom_element.style.display = "none";
|
||||
}
|
||||
|
||||
@ -255,10 +266,13 @@ export class NavEntry {
|
||||
* @param {string} new_path
|
||||
*/
|
||||
async rename(new_name) {
|
||||
if (new_name === this.filename())
|
||||
if (new_name === this.filename)
|
||||
return;
|
||||
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;
|
||||
} else if (new_name === "..") {
|
||||
this.nav_window_ref.modal_prompt.alert(
|
||||
@ -267,6 +281,14 @@ export class NavEntry {
|
||||
);
|
||||
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 {
|
||||
await this.mv(new_name);
|
||||
} catch(e) {
|
||||
@ -285,11 +307,22 @@ export class NavEntry {
|
||||
if (!element.editor)
|
||||
return;
|
||||
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);
|
||||
switch (element) {
|
||||
case this.dom_element.nav_item_title:
|
||||
element.editor.value = this.filename();
|
||||
element.editor.value = this.filename;
|
||||
break;
|
||||
default:
|
||||
element.editor.value = element.innerText;
|
||||
@ -301,7 +334,7 @@ export class NavEntry {
|
||||
element.editor.focus();
|
||||
}
|
||||
|
||||
hide_edit(element) {
|
||||
apply_edit(element) {
|
||||
if (!element.editor)
|
||||
return;
|
||||
switch (element) {
|
||||
@ -311,8 +344,14 @@ export class NavEntry {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hide_edit(element) {
|
||||
if (!element.editor)
|
||||
return;
|
||||
element.editor.style.display = "none";
|
||||
element.style.display = "inline-block";
|
||||
element.editor.removeEventListener("keydown", element.keydown_handler)
|
||||
window.removeEventListener("click", element.hide_func);
|
||||
}
|
||||
|
||||
@ -323,8 +362,8 @@ export class NavEntry {
|
||||
show_properties(extra_properties = "") {
|
||||
var selected_name_fields = document.getElementsByClassName("nav-info-column-filename");
|
||||
for (let elem of selected_name_fields) {
|
||||
elem.innerHTML = this.filename();
|
||||
elem.title = this.filename();
|
||||
elem.innerHTML = this.filename;
|
||||
elem.title = this.filename;
|
||||
}
|
||||
var html = "";
|
||||
html += property_entry_html("Mode", this.stat["mode-str"]);
|
||||
@ -339,7 +378,7 @@ export class NavEntry {
|
||||
}
|
||||
|
||||
populate_edit_fields() {
|
||||
document.getElementById("nav-edit-filename").innerText = this.filename();
|
||||
document.getElementById("nav-edit-filename").innerText = this.filename;
|
||||
var mode_bits = [
|
||||
"other-exec", "other-write", "other-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-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());
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,9 @@ export class NavFile extends NavEntry {
|
||||
switch(e.type){
|
||||
case "click":
|
||||
if (this.double_click) {
|
||||
if(this.timeout)
|
||||
clearTimeout(this.timeout);
|
||||
this.double_click = false;
|
||||
this.open();
|
||||
return;
|
||||
} else { // single click
|
||||
@ -94,7 +97,7 @@ export class NavFile extends NavEntry {
|
||||
this.show_edit_file_contents();
|
||||
} else {
|
||||
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);
|
||||
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-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-contents-view-holder").style.display = "none";
|
||||
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);
|
||||
document.getElementById("nav-edit-contents-textarea").removeEventListener("keydown", this);
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -195,7 +198,7 @@ export class NavFileLink extends NavFile{
|
||||
this.show_edit_file_contents();
|
||||
} else {
|
||||
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-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-contents-view").style.display = "none";
|
||||
document.getElementById("nav-contents-view-holder").style.display = "none";
|
||||
document.getElementById("nav-edit-contents-view").style.display = "flex";
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,6 @@ export class NavWindow {
|
||||
this.window.addEventListener("click", this);
|
||||
this.window.addEventListener("contextmenu", this);
|
||||
window.addEventListener("keydown", this);
|
||||
this.last_selected_index = -1;
|
||||
|
||||
this.context_menu = new NavContextMenu("nav-context-menu", this);
|
||||
|
||||
@ -49,6 +48,36 @@ export class NavWindow {
|
||||
this.sort_function = new SortFunctions();
|
||||
|
||||
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) {
|
||||
case "click":
|
||||
if (e.target === this.window) {
|
||||
this.clear_selected();
|
||||
this.reset_selection();
|
||||
this.show_selected_properties();
|
||||
}
|
||||
break;
|
||||
@ -73,6 +102,8 @@ export class NavWindow {
|
||||
} else if (e.keyCode === 65 && e.ctrlKey) {
|
||||
this.select_all();
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 27) {
|
||||
this.reset_selection();
|
||||
} else if (e.keyCode === 67 && e.ctrlKey) {
|
||||
this.copy();
|
||||
} else if (e.keyCode === 86 && e.ctrlKey) {
|
||||
@ -129,7 +160,7 @@ export class NavWindow {
|
||||
file.context_menu_ref = this.context_menu;
|
||||
});
|
||||
document.getElementById("pwd").value = this.pwd().path_str();
|
||||
this.set_selected(this.pwd(), false, false);
|
||||
this.reset_selection();
|
||||
this.show_selected_properties();
|
||||
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"}`;
|
||||
@ -178,65 +209,85 @@ export class NavWindow {
|
||||
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 {Boolean} select_range
|
||||
* @param {Boolean} append
|
||||
*/
|
||||
set_selected(entry, select_range, append) {
|
||||
this.hide_edit_selected();
|
||||
for (let i of this.selected_entries) {
|
||||
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);
|
||||
select_one(entry) {
|
||||
entry.style_selected();
|
||||
this.selected_entries.add(entry);
|
||||
}
|
||||
|
||||
clear_selected() {
|
||||
this.set_selected(this.pwd(), false, false);
|
||||
|
||||
deselect_one(entry) {
|
||||
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() {
|
||||
@ -248,29 +299,27 @@ export class NavWindow {
|
||||
}
|
||||
|
||||
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() {
|
||||
var dangerous_dirs = [
|
||||
"/",
|
||||
"/usr",
|
||||
"/bin",
|
||||
"/sbin",
|
||||
"/lib",
|
||||
"/lib32",
|
||||
"/lib64",
|
||||
"/usr/bin",
|
||||
"/usr/include",
|
||||
"/usr/lib",
|
||||
"/usr/lib32",
|
||||
"/usr/lib64",
|
||||
"/usr/sbin",
|
||||
];
|
||||
var dangerous_selected = [];
|
||||
for (let i of this.selected_entries) {
|
||||
var path = i.path_str();
|
||||
if (dangerous_dirs.includes(path)) {
|
||||
if (this.dangerous_dirs.includes(path)) {
|
||||
dangerous_selected.push(path);
|
||||
}
|
||||
}
|
||||
@ -321,7 +370,7 @@ export class NavWindow {
|
||||
}
|
||||
var targets = [];
|
||||
for (let target of this.selected_entries) {
|
||||
targets.push(target.filename());
|
||||
targets.push(target.filename);
|
||||
}
|
||||
var targets_str = targets.join(", ");
|
||||
document.getElementById("selected-files-list-header").innerText = "Applying edits to:";
|
||||
@ -395,6 +444,8 @@ export class NavWindow {
|
||||
}
|
||||
|
||||
async delete_selected() {
|
||||
if (await this.check_if_dangerous("delete"))
|
||||
return;
|
||||
var prompt = "";
|
||||
if (this.selected_entries.size > 1) {
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
this.start_load();
|
||||
for (let target of this.selected_entries) {
|
||||
try {
|
||||
await target.rm();
|
||||
@ -411,6 +463,7 @@ export class NavWindow {
|
||||
this.modal_prompt.alert(e);
|
||||
}
|
||||
}
|
||||
this.stop_load();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@ -545,7 +598,9 @@ export class NavWindow {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
cut() {
|
||||
async cut() {
|
||||
if (await this.check_if_dangerous("move"))
|
||||
return;
|
||||
this.clip_board = [...this.selected_entries];
|
||||
this.copy_or_move = "move";
|
||||
this.paste_cwd = this.pwd().path_str();
|
||||
@ -643,6 +698,7 @@ export class NavWindow {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
nav_bar_cd() {
|
||||
@ -681,7 +737,7 @@ export class NavWindow {
|
||||
|
||||
start_load() {
|
||||
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) {
|
||||
button.disabled = true;
|
||||
}
|
||||
@ -689,7 +745,7 @@ export class NavWindow {
|
||||
|
||||
stop_load() {
|
||||
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) {
|
||||
button.disabled = false;
|
||||
}
|
||||
@ -801,15 +857,6 @@ export class NavWindow {
|
||||
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() {
|
||||
var button = document.getElementById("nav-item-display-icon");
|
||||
if (this.item_display === "grid") {
|
||||
@ -833,11 +880,42 @@ export class NavWindow {
|
||||
|
||||
search_filter(event) {
|
||||
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) => {
|
||||
if (entry.filename().toLowerCase().startsWith(search_name.toLowerCase()))
|
||||
if (search_func(entry))
|
||||
entry.show();
|
||||
else
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -60,11 +60,11 @@ export class SortFunctions {
|
||||
}
|
||||
|
||||
name_asc(first, second) {
|
||||
return first.filename().localeCompare(second.filename());
|
||||
return first.filename.localeCompare(second.filename);
|
||||
}
|
||||
|
||||
name_desc(first, second) {
|
||||
return second.filename().localeCompare(first.filename());
|
||||
return second.filename.localeCompare(first.filename);
|
||||
}
|
||||
|
||||
owner_asc(first, second) {
|
||||
|
@ -70,9 +70,7 @@ export function format_time_remaining(seconds_) {
|
||||
if (hours) {
|
||||
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');
|
||||
return out;
|
||||
}
|
||||
@ -95,4 +93,17 @@ export function format_permissions(mode) {
|
||||
}
|
||||
}
|
||||
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)});
|
||||
});
|
||||
}
|
||||
|
@ -651,7 +651,7 @@ input:checked + .slider:before {
|
||||
.nav-notifications {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
right: 10px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-flow: column-reverse nowrap;
|
||||
@ -663,7 +663,7 @@ input:checked + .slider:before {
|
||||
.nav-notification {
|
||||
margin: 5px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
display: none;
|
||||
flex-flow: column nowrap;
|
||||
align-items: stretch;
|
||||
z-index: 10;
|
||||
@ -673,15 +673,23 @@ input:checked + .slider:before {
|
||||
border-radius: var(--nav-border-radius);
|
||||
color: var(--font);
|
||||
}
|
||||
|
||||
.nav-notification-header {
|
||||
/* .nav-notification-header {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
font-weight: bold;
|
||||
}
|
||||
} */
|
||||
|
||||
.nav-notification-header > progress {
|
||||
/* .nav-notification-header > progress {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
} */
|
||||
|
||||
.nav-notification-header > p {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.no-border {
|
||||
border: none;
|
||||
}
|
@ -40,13 +40,13 @@
|
||||
<div class="flex-col outer-container">
|
||||
<div class="flex-row nav-header">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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 class="horizontal-spacer"></div>
|
||||
<input type="text" list="possible-paths-list" autocomplete="off" class="navigation-bar" id="pwd" title="Navigation Bar"></input>
|
||||
@ -55,27 +55,29 @@
|
||||
</select>
|
||||
</datalist>
|
||||
<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>
|
||||
<div class="horizontal-spacer"></div>
|
||||
<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>
|
||||
<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>
|
||||
<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 class="vertical-spacer"></div>
|
||||
<div class="flex-row inner-container">
|
||||
<div class="contents-view contents-view-grid" id="nav-contents-view">
|
||||
<div class="contents-view-list-header nav-item">
|
||||
<i class="nav-item-icon"></i>
|
||||
<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-mode-btn">Mode</div>
|
||||
<div id="sort-owner-btn">Owner<i class="sort-arrow fas" id="sort-owner-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 class="contents-view" id="nav-contents-view-holder">
|
||||
<div class="contents-view contents-view-grid no-border" id="nav-contents-view">
|
||||
<div class="contents-view-list-header nav-item">
|
||||
<i class="nav-item-icon"></i>
|
||||
<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-mode-btn">Mode</div>
|
||||
<div id="sort-owner-btn">Owner<i class="sort-arrow fas" id="sort-owner-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 class="nav-notifications" id="nav-notifications">
|
||||
</div>
|
||||
@ -86,9 +88,9 @@
|
||||
<textarea id="nav-edit-contents-textarea" spellcheck="false"></textarea>
|
||||
<div class="vertical-spacer"></div>
|
||||
<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>
|
||||
<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 class="horizontal-spacer"></div>
|
||||
@ -97,9 +99,9 @@
|
||||
<div class="flex-row space-between">
|
||||
<div class="nav-info-column-filename"></div>
|
||||
<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>
|
||||
<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 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="vertical-spacer"></div>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -130,6 +130,7 @@ function set_up_buttons() {
|
||||
document.getElementById("search-bar").addEventListener("keydown", (e) => {
|
||||
if (e.keyCode === 13)
|
||||
nav_window.search_filter(e);
|
||||
e.stopPropagation();
|
||||
});
|
||||
// fix tab in editor input
|
||||
document.getElementById('nav-edit-contents-textarea').addEventListener('keydown', (e) => {
|
||||
|
44
navigator/scripts/return-exists.py3
Executable file
44
navigator/scripts/return-exists.py3
Executable 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()
|
@ -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:
|
||||
obj = {
|
||||
seek: <byte offset>
|
||||
@ -35,20 +35,24 @@ def write_chunk(chunk, file):
|
||||
if not file:
|
||||
path = sys.argv[1]
|
||||
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:
|
||||
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")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
seek = chunk["seek"]
|
||||
data = base64.b64decode(chunk["chunk"])
|
||||
file.seek(seek)
|
||||
file.write(data)
|
||||
try:
|
||||
file.seek(seek)
|
||||
file.write(data)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
file = None
|
||||
|
@ -32,6 +32,11 @@ rm -rf %{buildroot}
|
||||
/usr/share/cockpit/navigator/*
|
||||
|
||||
%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
|
||||
- Implement inline filename editing.
|
||||
- Add information popup button.
|
||||
|
@ -32,6 +32,11 @@ rm -rf %{buildroot}
|
||||
/usr/share/cockpit/navigator/*
|
||||
|
||||
%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
|
||||
- Implement inline filename editing.
|
||||
- Add information popup button.
|
||||
|
@ -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
|
||||
|
||||
* Implement inline filename editing.
|
||||
|
Loading…
x
Reference in New Issue
Block a user