Merge pull request #32 from 45Drives/dev-josh

Fix permissions & ownership while editing files, add edit and upload buttons
This commit is contained in:
Josh Boudreau 2021-10-04 14:23:08 -03:00 committed by GitHub
commit 0f4b9d07de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 397 additions and 272 deletions

View File

@ -1,6 +1,4 @@
## Cockpit Navigator 0.5.4-1 ## Cockpit Navigator 0.5.5-1
* Add fuzzy search. * Fix maintaining file permissions and ownership after editing file.
* Optimize folder uploads. * Add file upload button to top bar.
* Fix bugs with selecting entries and renaming files.
* Stop user from deleting or renaming system-critical paths.

View File

@ -23,17 +23,17 @@ 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.4/cockpit-navigator_0.5.4-1focal_all.deb` 1. `$ wget https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.5/cockpit-navigator_0.5.5-1focal_all.deb`
1. `# apt install ./cockpit-navigator_0.5.4-1focal_all.deb` 1. `# apt install ./cockpit-navigator_0.5.5-1focal_all.deb`
### EL7 ### EL7
1. `# yum install https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.4/cockpit-navigator-0.5.4-1.el7.noarch.rpm` 1. `# yum install https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.5/cockpit-navigator-0.5.5-1.el7.noarch.rpm`
### EL8 ### EL8
1. `# dnf install https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.4/cockpit-navigator-0.5.4-1.el8.noarch.rpm` 1. `# dnf install https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.5/cockpit-navigator-0.5.5-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.4 is latest) 1. `$ git checkout <version>` (v0.5.5 is latest)
1. `# make install` 1. `# make install`
## From 45Drives Repositories ## From 45Drives Repositories
### Automatic Repo Setup with Script ### Automatic Repo Setup with Script
@ -77,4 +77,4 @@ sudo dnf clean all
2. Install Package 2. Install Package
```bash ```bash
sudo dnf install cockpit-navigator sudo dnf install cockpit-navigator
``` ```

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.4", "version": "0.5.5",
"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.4", "version": "0.5.5",
"buildVersion": "1", "buildVersion": "1",
"ignore": [], "ignore": [],
"date": null, "date": null,

View File

@ -17,9 +17,9 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {NavWindow} from "./NavWindow.js"; import { NavWindow } from "./NavWindow.js";
import {format_time_remaining} from "../functions.js"; import { format_time_remaining } from "../functions.js";
import {ModalPrompt} from "./ModalPrompt.js"; import { ModalPrompt } from "./ModalPrompt.js";
export class FileUpload { export class FileUpload {
/** /**

View File

@ -17,8 +17,8 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {FileUpload} from "./FileUpload.js"; import { FileUpload } from "./FileUpload.js";
import {NavWindow} from "./NavWindow.js"; import { NavWindow } from "./NavWindow.js";
export class FileUploadManager { export class FileUploadManager {
/** /**

View File

@ -1,20 +1,19 @@
/* /*
Cockpit Navigator - A File System Browser for Cockpit. ModalPrompt - A Custom Prompt Module for Cockpit Plugins.
Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com> 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. This program is free software: you can redistribute it and/or modify
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms of the Lesser GNU General Public License as published by
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. Lesser 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/>. You should have received a copy of the Lesser GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/** /**
@ -30,218 +29,234 @@ let danger_btn = "pf-m-danger";
let all_btn = [primary_btn, secondary_btn, danger_btn]; let all_btn = [primary_btn, secondary_btn, danger_btn];
export class ModalPrompt { export class ModalPrompt {
constructor() { constructor() {
this.ok = document.createElement("button"); this.ok = document.createElement("button");
this.ok.innerText = "OK"; this.ok.innerText = "OK";
this.ok.classList.add("pf-c-button", "pf-m-primary"); this.ok.classList.add("pf-c-button", "pf-m-primary");
this.cancel = document.createElement("button"); this.cancel = document.createElement("button");
this.cancel.innerText = "Cancel"; this.cancel.innerText = "Cancel";
this.cancel.classList.add("pf-c-button", "pf-m-secondary"); this.cancel.classList.add("pf-c-button", "pf-m-secondary");
this.yes = document.createElement("button"); this.yes = document.createElement("button");
this.yes.innerText = "Yes"; this.yes.innerText = "Yes";
this.yes.classList.add("pf-c-button", "pf-m-primary"); this.yes.classList.add("pf-c-button", "pf-m-primary");
this.no = document.createElement("button"); this.no = document.createElement("button");
this.no.innerText = "No"; this.no.innerText = "No";
this.no.classList.add("pf-c-button", "pf-m-secondary"); this.no.classList.add("pf-c-button", "pf-m-secondary");
this.construct_element(); this.construct_element();
} }
construct_element() { construct_element() {
let bg = this.modal = document.createElement("div"); let bg = this.modal = document.createElement("div");
bg.classList.add("modal"); bg.classList.add("modal");
bg.style.overflowY = "auto"; bg.style.overflowY = "auto";
let fg = document.createElement("div"); let fg = document.createElement("div");
fg.classList.add("modal-dialog"); fg.classList.add("modal-dialog");
bg.appendChild(fg); bg.appendChild(fg);
let popup = document.createElement("div"); let popup = document.createElement("div");
popup.classList.add("modal-content"); popup.classList.add("modal-content");
fg.appendChild(popup); fg.appendChild(popup);
let header = document.createElement("div"); let header = document.createElement("div");
header.classList.add("modal-header"); header.classList.add("modal-header");
popup.appendChild(header); popup.appendChild(header);
let header_text = this.header = document.createElement("h4"); let header_text = this.header = document.createElement("h4");
header_text.classList.add("modal-title"); header_text.classList.add("modal-title");
header.appendChild(header_text); header.appendChild(header_text);
let body = this.body = document.createElement("div"); let body = this.body = document.createElement("div");
body.classList.add("modal-body"); body.classList.add("modal-body");
popup.appendChild(body); popup.appendChild(body);
let footer = this.footer = document.createElement("div"); let footer = this.footer = document.createElement("div");
footer.classList.add("modal-footer"); footer.classList.add("modal-footer");
footer.style.display = "flex"; footer.style.display = "flex";
footer.style.flexFlow = "row no-wrap"; footer.style.flexFlow = "row no-wrap";
footer.style.justifyContent = "flex-end"; footer.style.justifyContent = "flex-end";
popup.appendChild(footer); popup.appendChild(footer);
document.body.appendChild(this.modal); document.body.appendChild(this.modal);
} }
show() { show() {
this.modal.style.display = "block"; this.modal.style.display = "block";
} }
hide() { hide() {
this.modal.style.display = "none"; this.modal.style.display = "none";
} }
/** /**
* *
* @param {string} header * @param {string} header
*/ */
set_header(header) { set_header(header) {
this.header.innerText = header; this.header.innerText = header;
} }
/** /**
* *
* @param {string} message * @param {string} message
*/ */
set_body(message) { set_body(message) {
this.body.innerHTML = ""; this.body.innerHTML = "";
this.body.innerHTML = message; this.body.innerHTML = message;
} }
/** /**
* *
* @param {string} header * @param {string} header
* @param {string} message * @param {string} message
* @returns {Promise} * @returns {Promise}
*/ */
alert(header, message = "") { alert(header, message = "") {
this.set_header(header); this.set_header(header);
this.set_body(message); this.set_body(message);
this.footer.innerHTML = ""; this.footer.innerHTML = "";
this.footer.appendChild(this.ok); this.footer.appendChild(this.ok);
this.show(); this.show();
this.ok.focus(); this.ok.focus();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.ok.onclick = () => { this.ok.onclick = () => {
resolve(); resolve();
this.hide(); this.hide();
} }
}); });
} }
/** /**
* *
* @param {string} header * @param {string} header
* @param {string} message * @param {string} message
* @param {boolean} danger * @param {boolean} danger
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
confirm(header, message = "", danger = false) { confirm(header, message = "", danger = false) {
this.set_header(header); this.set_header(header);
this.set_body(message); this.set_body(message);
this.footer.innerHTML = ""; this.footer.innerHTML = "";
this.footer.append(this.no, this.yes); this.footer.append(this.no, this.yes);
this.yes.classList.remove(... all_btn); this.yes.classList.remove(... all_btn);
if (danger) if (danger)
this.yes.classList.add(danger_btn); this.yes.classList.add(danger_btn);
else else
this.yes.classList.add(primary_btn); this.yes.classList.add(primary_btn);
this.show(); this.show();
if (danger) if (danger)
this.no.focus(); this.no.focus();
else else
this.yes.focus(); this.yes.focus();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let resolve_true = () => { let resolve_true = () => {
resolve(true); resolve(true);
this.hide(); this.hide();
} }
let resolve_false = () => { let resolve_false = () => {
resolve(false); resolve(false);
this.hide(); this.hide();
} }
this.yes.onclick = resolve_true; this.yes.onclick = resolve_true;
this.no.onclick = resolve_false; this.no.onclick = resolve_false;
}); });
} }
/** /**
* *
* @param {string} header * @param {string} header
* @param {Object.<string, Request>} requests * @param {Object.<string, Request>} requests
* @returns {Promise<Object|string>} * @returns {Promise<Object|string>}
*/ */
prompt(header, requests) { prompt(header, requests) {
this.set_header(header); this.set_header(header);
this.body.innerHTML = ""; this.body.innerHTML = "";
this.footer.innerHTML = ""; this.footer.innerHTML = "";
this.footer.append(this.cancel, this.ok); this.footer.append(this.cancel, this.ok);
let inputs = []; let inputs = [];
let simple_prompt = false;
if (typeof requests === "object") {
let req_holder = document.createElement("div");
req_holder.style.display = "flex";
req_holder.style.flexFlow = "column nowrap";
req_holder.style.alignItems = "stretch";
this.body.appendChild(req_holder);
for(let key of Object.keys(requests)) {
let row = document.createElement("div");
row.style.display = "flex";
row.style.alignItems = "baseline";
row.style.padding = "2px";
let request = requests[key];
let label = document.createElement("label");
label.innerText = request.label;
label.htmlFor = key;
label.style.paddingRight = "1em";
label.style.flexBasis = "0";
label.style.flexGrow = "1";
let req = document.createElement("input");
req.id = key;
req.type = request.type;
req.style.flexBasis = "0";
if (request.hasOwnProperty("default")) {
req.value = request.default;
}
row.append(label, req);
req_holder.appendChild(row);
inputs.push(req);
switch (request.type) {
case "text":
req.style.flexGrow = "3";
break;
case "checkbox":
label.style.cursor = req.style.cursor = "pointer";
break;
default:
break;
}
}
}
this.show(); if (typeof requests === "string") {
inputs[0].focus(); let label = requests;
for (let i = 0; i < inputs.length - 1; i++) { simple_prompt = true;
inputs[i].onchange = () => { requests = {
inputs[i+1].focus(); "key": {
} "label": label,
} "type": "text"
inputs[inputs.length - 1].onchange = () => { }
this.ok.focus(); }
} }
return new Promise((resolve, reject) => {
this.ok.onclick = () => { let req_holder = document.createElement("div");
let response = {}; req_holder.style.display = "flex";
for (let input of inputs) { req_holder.style.flexFlow = "column nowrap";
switch (input.type) { req_holder.style.alignItems = "stretch";
case "checkbox": this.body.appendChild(req_holder);
response[input.id] = input.checked; for(let key of Object.keys(requests)) {
break; let row = document.createElement("div");
case "text": row.style.display = "flex";
default: row.style.alignItems = "baseline";
response[input.id] = input.value; row.style.padding = "2px";
break; let request = requests[key];
} let label = document.createElement("label");
} label.innerText = request.label;
resolve(response); label.htmlFor = key;
this.hide(); label.style.paddingRight = "1em";
} label.style.flexBasis = "0";
this.cancel.onclick = () => { label.style.flexGrow = "1";
resolve(null); let req = document.createElement("input");
this.hide(); req.id = key;
} req.type = request.type;
}); req.style.flexBasis = "0";
} if (request.hasOwnProperty("default")) {
req.value = request.default;
}
row.append(label, req);
req_holder.appendChild(row);
inputs.push(req);
switch (request.type) {
case "text":
req.style.flexGrow = "3";
break;
case "checkbox":
label.style.cursor = req.style.cursor = "pointer";
break;
default:
break;
}
}
this.show();
inputs[0].focus();
for (let i = 0; i < inputs.length - 1; i++) {
inputs[i].onchange = () => {
inputs[i+1].focus();
}
}
inputs[inputs.length - 1].onchange = () => {
this.ok.focus();
}
return new Promise((resolve, reject) => {
this.ok.onclick = () => {
let response
if (simple_prompt) {
response = inputs[0].value;
} else {
response = {};
for (let input of inputs) {
switch (input.type) {
case "checkbox":
response[input.id] = input.checked;
break;
case "text":
default:
response[input.id] = input.value;
break;
}
}
}
resolve(response);
this.hide();
}
this.cancel.onclick = () => {
resolve(null);
this.hide();
}
});
}
} }

View File

@ -17,10 +17,10 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {NavEntry} from "./NavEntry.js"; import { NavEntry } from "./NavEntry.js";
import {NavFile, NavFileLink} from "./NavFile.js"; import { NavFile, NavFileLink } from "./NavFile.js";
import {NavDir, NavDirLink} from "./NavDir.js"; import { NavDir, NavDirLink } from "./NavDir.js";
import {NavDownloader} from "./NavDownloader.js"; import { NavDownloader } from "./NavDownloader.js";
export class NavContextMenu { export class NavContextMenu {
/** /**

View File

@ -17,10 +17,10 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {NavEntry} from "./NavEntry.js"; import { NavEntry } from "./NavEntry.js";
import {NavFile, NavFileLink} from "./NavFile.js"; import { NavFile, NavFileLink } from "./NavFile.js";
import {NavWindow} from "./NavWindow.js"; import { NavWindow } from "./NavWindow.js";
import {property_entry_html} from "../functions.js"; import { property_entry_html } from "../functions.js";
export class NavDir extends NavEntry { export class NavDir extends NavEntry {
/** /**

View File

@ -17,7 +17,7 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {NavFile} from "./NavFile.js"; import { NavFile } from "./NavFile.js";
export class NavDownloader { export class NavDownloader {
/** /**

View File

@ -17,10 +17,10 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {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"; import { FileUploadManager } from "./FileUploadManager.js";
export class NavDragDrop { export class NavDragDrop {
/** /**
@ -37,6 +37,21 @@ export class NavDragDrop {
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); this.upload_manager = new FileUploadManager(this.nav_window_ref, 6);
this.upload_element = document.createElement('input');
this.upload_element.type = 'file';
this.upload_element.multiple = true;
this.upload_element.onchange = async e => {
var uploads = []
for (const file of e.target.files) {
let uploader = new FileUpload(file, this.nav_window_ref);
uploader.using_webkit = false;
uploads.push(uploader);
}
uploads = await this.handle_conflicts(uploads);
this.upload_manager.add(... uploads);
this.nav_window_ref.stop_load();
}
document.getElementById("nav-upload-btn").addEventListener("click", this.upload_dialog.bind(this));
} }
/** /**
@ -196,4 +211,9 @@ export class NavDragDrop {
break; break;
} }
} }
upload_dialog() {
this.nav_window_ref.start_load();
this.upload_element.click();
}
} }

View File

@ -17,8 +17,8 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {NavWindow} from "./NavWindow.js"; import { NavWindow } from "./NavWindow.js";
import {format_bytes, property_entry_html, format_time, check_if_exists} from "../functions.js"; import { format_bytes, property_entry_html, format_time, check_if_exists } from "../functions.js";
export class NavEntry { export class NavEntry {
/** /**

View File

@ -17,10 +17,10 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {NavEntry} from "./NavEntry.js"; import { NavEntry } from "./NavEntry.js";
import {NavDownloader} from "./NavDownloader.js"; import { NavDownloader } from "./NavDownloader.js";
import {NavWindow} from "./NavWindow.js"; import { NavWindow } from "./NavWindow.js";
import {property_entry_html} from "../functions.js"; import { property_entry_html, simple_spawn } from "../functions.js";
export class NavFile extends NavEntry { export class NavFile extends NavEntry {
/** /**
@ -93,7 +93,7 @@ export class NavFile extends NavEntry {
var fields = proc_output.split(/:(?=[^:]+$)/); // ensure it's the last : with lookahead var fields = proc_output.split(/:(?=[^:]+$)/); // ensure it's the last : with lookahead
var type = fields[1].trim(); var type = fields[1].trim();
if ((/^text/.test(type) || /^inode\/x-empty$/.test(type) || this.stat["size"] === 0)) { if (/^text/.test(type) || /^inode\/x-empty$/.test(type) || this.stat["size"] === 0 || (/^application\/octet-stream/.test(type) && this.stat["size"] === 1)) {
this.show_edit_file_contents(); this.show_edit_file_contents();
} else { } else {
console.log("Unknown mimetype: " + type); console.log("Unknown mimetype: " + type);
@ -128,12 +128,9 @@ export class NavFile extends NavEntry {
async write_to_file() { async write_to_file() {
var new_contents = document.getElementById("nav-edit-contents-textarea").value; var new_contents = document.getElementById("nav-edit-contents-textarea").value;
try { try {
if (new_contents.length) await simple_spawn(["/usr/share/cockpit/navigator/scripts/write-to-file.py3", this.path_str()], new_contents);
await cockpit.file(this.path_str(), {superuser: "try"}).replace(new_contents);
else
await cockpit.script("echo -n > $1", [this.path_str()], {superuser: "try"});
} catch (e) { } catch (e) {
this.nav_window_ref.modal_prompt.alert(e.message); this.nav_window_ref.modal_prompt.alert(e);
} }
this.nav_window_ref.refresh(); this.nav_window_ref.refresh();
this.hide_edit_file_contents(); this.hide_edit_file_contents();
@ -229,9 +226,9 @@ export class NavFileLink extends NavFile{
var target_path = this.get_link_target_path(); var target_path = this.get_link_target_path();
var new_contents = document.getElementById("nav-edit-contents-textarea").value; var new_contents = document.getElementById("nav-edit-contents-textarea").value;
try { try {
await cockpit.file(target_path, {superuser: "try"}).replace(new_contents); await simple_spawn(["/usr/share/cockpit/navigator/scripts/write-to-file.py3", target_path], new_contents);
} catch (e) { } catch (e) {
this.nav_window_ref.modal_prompt.alert(e.message); this.nav_window_ref.modal_prompt.alert(e);
} }
this.nav_window_ref.refresh(); this.nav_window_ref.refresh();
this.hide_edit_file_contents(); this.hide_edit_file_contents();

View File

@ -17,13 +17,13 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {NavEntry} from "./NavEntry.js"; import { NavEntry } from "./NavEntry.js";
import {NavDir} from "./NavDir.js"; import { NavDir } from "./NavDir.js";
import {NavContextMenu} from "./NavContextMenu.js"; import { NavContextMenu } from "./NavContextMenu.js";
import {NavDragDrop} from "./NavDragDrop.js"; import { NavDragDrop } from "./NavDragDrop.js";
import {SortFunctions} from "./SortFunctions.js"; import { SortFunctions } from "./SortFunctions.js";
import {ModalPrompt} from "./ModalPrompt.js"; import { ModalPrompt } from "./ModalPrompt.js";
import {format_bytes, format_permissions} from "../functions.js"; import { format_bytes, format_permissions } from "../functions.js";
export class NavWindow { export class NavWindow {
constructor() { constructor() {
@ -256,6 +256,9 @@ export class NavWindow {
this.select_one(this.pwd()); this.select_one(this.pwd());
this.last_selected_entry = null; this.last_selected_entry = null;
this.update_selection_info(); this.update_selection_info();
var edit_btn = document.getElementById("nav-edit-contents-btn");
edit_btn.keep_disabled = edit_btn.disabled = true;
edit_btn.onclick = () => {};
} }
/** /**
@ -282,6 +285,17 @@ export class NavWindow {
this.select_one(target); this.select_one(target);
} }
this.update_selection_info(); this.update_selection_info();
var edit_btn = document.getElementById("nav-edit-contents-btn")
if (target.nav_type === "file") {
edit_btn.keep_disabled = edit_btn.disabled = false;
edit_btn.onclick = () => {
target.open();
};
} else {
edit_btn.onclick = () => {};
edit_btn.keep_disabled = edit_btn.disabled = true;
}
} }
select_all() { select_all() {
@ -747,7 +761,8 @@ export class NavWindow {
document.getElementById("nav-loader-container").style.display = "none"; document.getElementById("nav-loader-container").style.display = "none";
var buttons = document.getElementsByClassName("disable-while-loading"); var buttons = document.getElementsByClassName("disable-while-loading");
for (let button of buttons) { for (let button of buttons) {
button.disabled = false; if (!button.keep_disabled)
button.disabled = false;
} }
} }

View File

@ -107,3 +107,25 @@ export function check_if_exists(path) {
proc.fail((e, data) => {resolve(true)}); proc.fail((e, data) => {resolve(true)});
}); });
} }
/**
* Spawn process and resolve/reject with output
*
* @param {string[]} argv argv of proc
* @param {string | null} input optional input to stdin of proc
* @returns {Promise<string>}
*/
export function simple_spawn(argv, input = null) {
return new Promise((resolve, reject) => {
var proc = cockpit.spawn(argv, {superuser: "try"});
proc.done(data => resolve(data));
proc.fail((e, data) => {
if (data)
reject(data);
else
reject("Execution error: " + e);
});
if (input != null)
proc.input(input);
});
}

View File

@ -23,13 +23,13 @@
<base href="."> <base href=".">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link href="../base1/cockpit.css" type="text/css" rel="stylesheet"> <link href="../base1/cockpit.css" type="text/css" rel="stylesheet">
<link href="navigator.css" type="text/css" rel="stylesheet"> <link href="style.css" type="text/css" rel="stylesheet">
<link href="fontawesome/css/all.min.css" rel="stylesheet"> <link href="fontawesome/css/all.min.css" rel="stylesheet">
<script src="fontawesome/attribution.js"></script> <script src="fontawesome/attribution.js"></script>
<script src="../base1/cockpit.js"></script> <script src="../base1/cockpit.js"></script>
<script src="../manifests.js"></script> <script src="../manifests.js"></script>
<script src="../*/po.js"></script> <script src="../*/po.js"></script>
<script src="navigator.js" type="module"></script> <script src="main.js" type="module"></script>
</head> </head>
<body> <body>
<div class="nav-loader-container" id="nav-loader-container"> <div class="nav-loader-container" id="nav-loader-container">
@ -64,6 +64,8 @@
<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> <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="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> <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 class="horizontal-spacer"></div>
<button class="disable-while-loading pf-c-button pf-m-primary" id="nav-upload-btn" title="Upload File(s)"><i class="fas fa-upload"></i></button>
</div> </div>
</div> </div>
<div class="vertical-spacer"></div> <div class="vertical-spacer"></div>
@ -102,6 +104,8 @@
<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> <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="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> <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 class="horizontal-spacer"></div>
<button class="disable-while-loading pf-c-button pf-m-primary" id="nav-edit-contents-btn" title="Edit Contents"><i class="fas fa-edit"></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>

View File

@ -17,9 +17,9 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {ModalPrompt} from "./components/ModalPrompt.js"; import { ModalPrompt } from "./components/ModalPrompt.js";
import {NavWindow} from "./components/NavWindow.js"; import { NavWindow } from "./components/NavWindow.js";
import {NAVIGATOR_VERSION} from "./version.js"; import { NAVIGATOR_VERSION } from "./version.js";
/** /**
* *

View File

@ -4,7 +4,7 @@
"menu": { "menu": {
"navigator": { "navigator": {
"label": "Navigator", "label": "Navigator",
"path": "navigator.html", "path": "index.html",
"order": 114 "order": 114
} }
} }

View File

@ -0,0 +1,41 @@
#!/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/>.
"""
"""
Synopsis: echo <contents of file> | write-to-file.py3 <path/to/file>
"""
import sys
import os
def main():
if len(sys.argv) != 2:
print("Invalid number of arguments.")
sys.exit(1)
file_path = sys.argv[1]
try:
with open(file_path, "w") as f:
for line in sys.stdin:
f.write(line)
except Exception as e:
print(e)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -32,6 +32,9 @@ rm -rf %{buildroot}
/usr/share/cockpit/navigator/* /usr/share/cockpit/navigator/*
%changelog %changelog
* Mon Oct 04 2021 Joshua Boudreau <jboudreau@45drives.com> 0.5.5-1
- Fix maintaining file permissions and ownership after editing file.
- Add file upload button to top bar.
* Tue Jul 20 2021 Josh Boudreau <jboudreau@45drives.com> 0.5.4-1 * Tue Jul 20 2021 Josh Boudreau <jboudreau@45drives.com> 0.5.4-1
- Add fuzzy search. - Add fuzzy search.
- Optimize folder uploads. - Optimize folder uploads.

View File

@ -32,6 +32,9 @@ rm -rf %{buildroot}
/usr/share/cockpit/navigator/* /usr/share/cockpit/navigator/*
%changelog %changelog
* Mon Oct 04 2021 Joshua Boudreau <jboudreau@45drives.com> 0.5.5-1
- Fix maintaining file permissions and ownership after editing file.
- Add file upload button to top bar.
* Tue Jul 20 2021 Josh Boudreau <jboudreau@45drives.com> 0.5.4-1 * Tue Jul 20 2021 Josh Boudreau <jboudreau@45drives.com> 0.5.4-1
- Add fuzzy search. - Add fuzzy search.
- Optimize folder uploads. - Optimize folder uploads.

View File

@ -1,3 +1,10 @@
cockpit-navigator (0.5.5-1focal) focal; urgency=medium
* Fix maintaining file permissions and ownership after editing file.
* Add file upload button to top bar.
-- Joshua Boudreau <jboudreau@45drives.com> Mon, 04 Oct 2021 11:14:20 -0300
cockpit-navigator (0.5.4-1focal) focal; urgency=medium cockpit-navigator (0.5.4-1focal) focal; urgency=medium
* Add fuzzy search. * Add fuzzy search.