diff --git a/CHANGELOG.md b/CHANGELOG.md index 847f661..eea0a32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,4 @@ -## Cockpit Navigator 0.5.4-1 +## Cockpit Navigator 0.5.5-1 -* Add fuzzy search. -* Optimize folder uploads. -* Fix bugs with selecting entries and renaming files. -* Stop user from deleting or renaming system-critical paths. \ No newline at end of file +* Fix maintaining file permissions and ownership after editing file. +* Add file upload button to top bar. \ No newline at end of file diff --git a/README.md b/README.md index db5617a..064a967 100644 --- a/README.md +++ b/README.md @@ -23,17 +23,17 @@ 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.4/cockpit-navigator_0.5.4-1focal_all.deb` -1. `# apt install ./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.5-1focal_all.deb` ### 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 -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 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 ` (v0.5.4 is latest) +1. `$ git checkout ` (v0.5.5 is latest) 1. `# make install` ## From 45Drives Repositories ### Automatic Repo Setup with Script @@ -77,4 +77,4 @@ sudo dnf clean all 2. Install Package ```bash sudo dnf install cockpit-navigator -``` \ No newline at end of file +``` diff --git a/manifest.json b/manifest.json index da34e60..76fc0de 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "name": "cockpit-navigator", "title": "Cockpit Navigator", "prerelease": false, - "version": "0.5.4", + "version": "0.5.5", "buildVersion": "1", "author": "Josh Boudreau ", "url": "https://github.com/45Drives/cockpit-navigator", @@ -54,7 +54,7 @@ ], "changelog": { "urgency": "medium", - "version": "0.5.4", + "version": "0.5.5", "buildVersion": "1", "ignore": [], "date": null, diff --git a/navigator/components/FileUpload.js b/navigator/components/FileUpload.js index e5877f3..1d64349 100644 --- a/navigator/components/FileUpload.js +++ b/navigator/components/FileUpload.js @@ -17,9 +17,9 @@ along with Cockpit Navigator. If not, see . */ -import {NavWindow} from "./NavWindow.js"; -import {format_time_remaining} from "../functions.js"; -import {ModalPrompt} from "./ModalPrompt.js"; +import { NavWindow } from "./NavWindow.js"; +import { format_time_remaining } from "../functions.js"; +import { ModalPrompt } from "./ModalPrompt.js"; export class FileUpload { /** diff --git a/navigator/components/FileUploadManager.js b/navigator/components/FileUploadManager.js index 22e6f74..0ab3263 100644 --- a/navigator/components/FileUploadManager.js +++ b/navigator/components/FileUploadManager.js @@ -17,8 +17,8 @@ along with Cockpit Navigator. If not, see . */ -import {FileUpload} from "./FileUpload.js"; -import {NavWindow} from "./NavWindow.js"; +import { FileUpload } from "./FileUpload.js"; +import { NavWindow } from "./NavWindow.js"; export class FileUploadManager { /** diff --git a/navigator/components/ModalPrompt.js b/navigator/components/ModalPrompt.js index 9c48988..bf1bba4 100644 --- a/navigator/components/ModalPrompt.js +++ b/navigator/components/ModalPrompt.js @@ -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 - Copyright (C) 2021 Sam Silver - Copyright (C) 2021 Dawson Della Valle - 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 + This program is free software: you can redistribute it and/or modify + it under the terms of the Lesser 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, + + This program 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 . + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see . */ /** @@ -30,218 +29,234 @@ let danger_btn = "pf-m-danger"; let all_btn = [primary_btn, secondary_btn, danger_btn]; export class ModalPrompt { - constructor() { - this.ok = document.createElement("button"); - this.ok.innerText = "OK"; - this.ok.classList.add("pf-c-button", "pf-m-primary"); - this.cancel = document.createElement("button"); - this.cancel.innerText = "Cancel"; - this.cancel.classList.add("pf-c-button", "pf-m-secondary"); - this.yes = document.createElement("button"); - this.yes.innerText = "Yes"; - this.yes.classList.add("pf-c-button", "pf-m-primary"); - this.no = document.createElement("button"); - this.no.innerText = "No"; - this.no.classList.add("pf-c-button", "pf-m-secondary"); - this.construct_element(); - } - - construct_element() { - let bg = this.modal = document.createElement("div"); - bg.classList.add("modal"); - bg.style.overflowY = "auto"; - let fg = document.createElement("div"); - fg.classList.add("modal-dialog"); - bg.appendChild(fg); - let popup = document.createElement("div"); - popup.classList.add("modal-content"); - fg.appendChild(popup); - let header = document.createElement("div"); - header.classList.add("modal-header"); - popup.appendChild(header); - let header_text = this.header = document.createElement("h4"); - header_text.classList.add("modal-title"); - header.appendChild(header_text); - let body = this.body = document.createElement("div"); - body.classList.add("modal-body"); - popup.appendChild(body); - let footer = this.footer = document.createElement("div"); - footer.classList.add("modal-footer"); - footer.style.display = "flex"; - footer.style.flexFlow = "row no-wrap"; - footer.style.justifyContent = "flex-end"; - popup.appendChild(footer); - document.body.appendChild(this.modal); - } - - show() { - this.modal.style.display = "block"; - } - - hide() { - this.modal.style.display = "none"; - } - - /** - * - * @param {string} header - */ - set_header(header) { - this.header.innerText = header; - } - - /** - * - * @param {string} message - */ - set_body(message) { - this.body.innerHTML = ""; - this.body.innerHTML = message; - } - - /** - * - * @param {string} header - * @param {string} message - * @returns {Promise} - */ - alert(header, message = "") { - this.set_header(header); - this.set_body(message); - this.footer.innerHTML = ""; - this.footer.appendChild(this.ok); - this.show(); - this.ok.focus(); - return new Promise((resolve, reject) => { - this.ok.onclick = () => { - resolve(); - this.hide(); - } - }); - } - - /** - * - * @param {string} header - * @param {string} message - * @param {boolean} danger - * @returns {Promise} - */ - confirm(header, message = "", danger = false) { - this.set_header(header); - this.set_body(message); - this.footer.innerHTML = ""; - this.footer.append(this.no, this.yes); - this.yes.classList.remove(... all_btn); - if (danger) - this.yes.classList.add(danger_btn); - else - this.yes.classList.add(primary_btn); - this.show(); - if (danger) - this.no.focus(); - else - this.yes.focus(); - return new Promise((resolve, reject) => { - let resolve_true = () => { - resolve(true); - this.hide(); - } - let resolve_false = () => { - resolve(false); - this.hide(); - } - this.yes.onclick = resolve_true; - this.no.onclick = resolve_false; - }); - } - - /** - * - * @param {string} header - * @param {Object.} requests - * @returns {Promise} - */ - prompt(header, requests) { - this.set_header(header); - this.body.innerHTML = ""; - this.footer.innerHTML = ""; - this.footer.append(this.cancel, this.ok); - let inputs = []; - - 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(); - 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 = {}; - 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(); - } - }); - } + constructor() { + this.ok = document.createElement("button"); + this.ok.innerText = "OK"; + this.ok.classList.add("pf-c-button", "pf-m-primary"); + this.cancel = document.createElement("button"); + this.cancel.innerText = "Cancel"; + this.cancel.classList.add("pf-c-button", "pf-m-secondary"); + this.yes = document.createElement("button"); + this.yes.innerText = "Yes"; + this.yes.classList.add("pf-c-button", "pf-m-primary"); + this.no = document.createElement("button"); + this.no.innerText = "No"; + this.no.classList.add("pf-c-button", "pf-m-secondary"); + this.construct_element(); + } + + construct_element() { + let bg = this.modal = document.createElement("div"); + bg.classList.add("modal"); + bg.style.overflowY = "auto"; + let fg = document.createElement("div"); + fg.classList.add("modal-dialog"); + bg.appendChild(fg); + let popup = document.createElement("div"); + popup.classList.add("modal-content"); + fg.appendChild(popup); + let header = document.createElement("div"); + header.classList.add("modal-header"); + popup.appendChild(header); + let header_text = this.header = document.createElement("h4"); + header_text.classList.add("modal-title"); + header.appendChild(header_text); + let body = this.body = document.createElement("div"); + body.classList.add("modal-body"); + popup.appendChild(body); + let footer = this.footer = document.createElement("div"); + footer.classList.add("modal-footer"); + footer.style.display = "flex"; + footer.style.flexFlow = "row no-wrap"; + footer.style.justifyContent = "flex-end"; + popup.appendChild(footer); + document.body.appendChild(this.modal); + } + + show() { + this.modal.style.display = "block"; + } + + hide() { + this.modal.style.display = "none"; + } + + /** + * + * @param {string} header + */ + set_header(header) { + this.header.innerText = header; + } + + /** + * + * @param {string} message + */ + set_body(message) { + this.body.innerHTML = ""; + this.body.innerHTML = message; + } + + /** + * + * @param {string} header + * @param {string} message + * @returns {Promise} + */ + alert(header, message = "") { + this.set_header(header); + this.set_body(message); + this.footer.innerHTML = ""; + this.footer.appendChild(this.ok); + this.show(); + this.ok.focus(); + return new Promise((resolve, reject) => { + this.ok.onclick = () => { + resolve(); + this.hide(); + } + }); + } + + /** + * + * @param {string} header + * @param {string} message + * @param {boolean} danger + * @returns {Promise} + */ + confirm(header, message = "", danger = false) { + this.set_header(header); + this.set_body(message); + this.footer.innerHTML = ""; + this.footer.append(this.no, this.yes); + this.yes.classList.remove(... all_btn); + if (danger) + this.yes.classList.add(danger_btn); + else + this.yes.classList.add(primary_btn); + this.show(); + if (danger) + this.no.focus(); + else + this.yes.focus(); + return new Promise((resolve, reject) => { + let resolve_true = () => { + resolve(true); + this.hide(); + } + let resolve_false = () => { + resolve(false); + this.hide(); + } + this.yes.onclick = resolve_true; + this.no.onclick = resolve_false; + }); + } + + /** + * + * @param {string} header + * @param {Object.} requests + * @returns {Promise} + */ + prompt(header, requests) { + this.set_header(header); + this.body.innerHTML = ""; + this.footer.innerHTML = ""; + this.footer.append(this.cancel, this.ok); + let inputs = []; + let simple_prompt = false; + + + if (typeof requests === "string") { + let label = requests; + simple_prompt = true; + requests = { + "key": { + "label": label, + "type": "text" + } + } + } + + 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(); + 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(); + } + }); + } } diff --git a/navigator/components/NavContextMenu.js b/navigator/components/NavContextMenu.js index 9e83295..bd23fe2 100644 --- a/navigator/components/NavContextMenu.js +++ b/navigator/components/NavContextMenu.js @@ -17,10 +17,10 @@ along with Cockpit Navigator. If not, see . */ -import {NavEntry} from "./NavEntry.js"; -import {NavFile, NavFileLink} from "./NavFile.js"; -import {NavDir, NavDirLink} from "./NavDir.js"; -import {NavDownloader} from "./NavDownloader.js"; +import { NavEntry } from "./NavEntry.js"; +import { NavFile, NavFileLink } from "./NavFile.js"; +import { NavDir, NavDirLink } from "./NavDir.js"; +import { NavDownloader } from "./NavDownloader.js"; export class NavContextMenu { /** diff --git a/navigator/components/NavDir.js b/navigator/components/NavDir.js index d77afd0..039baad 100644 --- a/navigator/components/NavDir.js +++ b/navigator/components/NavDir.js @@ -17,10 +17,10 @@ along with Cockpit Navigator. If not, see . */ -import {NavEntry} from "./NavEntry.js"; -import {NavFile, NavFileLink} from "./NavFile.js"; -import {NavWindow} from "./NavWindow.js"; -import {property_entry_html} from "../functions.js"; +import { NavEntry } from "./NavEntry.js"; +import { NavFile, NavFileLink } from "./NavFile.js"; +import { NavWindow } from "./NavWindow.js"; +import { property_entry_html } from "../functions.js"; export class NavDir extends NavEntry { /** diff --git a/navigator/components/NavDownloader.js b/navigator/components/NavDownloader.js index 12b234b..b0e4731 100644 --- a/navigator/components/NavDownloader.js +++ b/navigator/components/NavDownloader.js @@ -17,7 +17,7 @@ along with Cockpit Navigator. If not, see . */ -import {NavFile} from "./NavFile.js"; +import { NavFile } from "./NavFile.js"; export class NavDownloader { /** diff --git a/navigator/components/NavDragDrop.js b/navigator/components/NavDragDrop.js index 2d3f751..31fea20 100644 --- a/navigator/components/NavDragDrop.js +++ b/navigator/components/NavDragDrop.js @@ -17,10 +17,10 @@ along with Cockpit Navigator. If not, see . */ -import {FileUpload} from "./FileUpload.js"; -import {ModalPrompt} from "./ModalPrompt.js"; -import {NavWindow} from "./NavWindow.js"; -import {FileUploadManager} from "./FileUploadManager.js"; +import { FileUpload } from "./FileUpload.js"; +import { ModalPrompt } from "./ModalPrompt.js"; +import { NavWindow } from "./NavWindow.js"; +import { FileUploadManager } from "./FileUploadManager.js"; export class NavDragDrop { /** @@ -37,6 +37,21 @@ export class NavDragDrop { this.nav_window_ref = nav_window_ref; this.modal_prompt = new ModalPrompt(); 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; } } + + upload_dialog() { + this.nav_window_ref.start_load(); + this.upload_element.click(); + } } diff --git a/navigator/components/NavEntry.js b/navigator/components/NavEntry.js index 2d97663..2ea2ee3 100644 --- a/navigator/components/NavEntry.js +++ b/navigator/components/NavEntry.js @@ -17,8 +17,8 @@ along with Cockpit Navigator. If not, see . */ -import {NavWindow} from "./NavWindow.js"; -import {format_bytes, property_entry_html, format_time, check_if_exists} from "../functions.js"; +import { NavWindow } from "./NavWindow.js"; +import { format_bytes, property_entry_html, format_time, check_if_exists } from "../functions.js"; export class NavEntry { /** diff --git a/navigator/components/NavFile.js b/navigator/components/NavFile.js index 6fd717f..170138b 100644 --- a/navigator/components/NavFile.js +++ b/navigator/components/NavFile.js @@ -17,10 +17,10 @@ along with Cockpit Navigator. If not, see . */ -import {NavEntry} from "./NavEntry.js"; -import {NavDownloader} from "./NavDownloader.js"; -import {NavWindow} from "./NavWindow.js"; -import {property_entry_html} from "../functions.js"; +import { NavEntry } from "./NavEntry.js"; +import { NavDownloader } from "./NavDownloader.js"; +import { NavWindow } from "./NavWindow.js"; +import { property_entry_html, simple_spawn } from "../functions.js"; 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 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(); } else { console.log("Unknown mimetype: " + type); @@ -128,12 +128,9 @@ export class NavFile extends NavEntry { async write_to_file() { var new_contents = document.getElementById("nav-edit-contents-textarea").value; try { - if (new_contents.length) - await cockpit.file(this.path_str(), {superuser: "try"}).replace(new_contents); - else - await cockpit.script("echo -n > $1", [this.path_str()], {superuser: "try"}); + await simple_spawn(["/usr/share/cockpit/navigator/scripts/write-to-file.py3", this.path_str()], new_contents); } 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.hide_edit_file_contents(); @@ -229,9 +226,9 @@ export class NavFileLink extends NavFile{ var target_path = this.get_link_target_path(); var new_contents = document.getElementById("nav-edit-contents-textarea").value; 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) { - this.nav_window_ref.modal_prompt.alert(e.message); + this.nav_window_ref.modal_prompt.alert(e); } this.nav_window_ref.refresh(); this.hide_edit_file_contents(); diff --git a/navigator/components/NavWindow.js b/navigator/components/NavWindow.js index 063682e..29d56ef 100644 --- a/navigator/components/NavWindow.js +++ b/navigator/components/NavWindow.js @@ -17,13 +17,13 @@ along with Cockpit Navigator. If not, see . */ -import {NavEntry} from "./NavEntry.js"; -import {NavDir} from "./NavDir.js"; -import {NavContextMenu} from "./NavContextMenu.js"; -import {NavDragDrop} from "./NavDragDrop.js"; -import {SortFunctions} from "./SortFunctions.js"; -import {ModalPrompt} from "./ModalPrompt.js"; -import {format_bytes, format_permissions} from "../functions.js"; +import { NavEntry } from "./NavEntry.js"; +import { NavDir } from "./NavDir.js"; +import { NavContextMenu } from "./NavContextMenu.js"; +import { NavDragDrop } from "./NavDragDrop.js"; +import { SortFunctions } from "./SortFunctions.js"; +import { ModalPrompt } from "./ModalPrompt.js"; +import { format_bytes, format_permissions } from "../functions.js"; export class NavWindow { constructor() { @@ -256,6 +256,9 @@ export class NavWindow { this.select_one(this.pwd()); this.last_selected_entry = null; 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.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() { @@ -747,7 +761,8 @@ export class NavWindow { document.getElementById("nav-loader-container").style.display = "none"; var buttons = document.getElementsByClassName("disable-while-loading"); for (let button of buttons) { - button.disabled = false; + if (!button.keep_disabled) + button.disabled = false; } } diff --git a/navigator/functions.js b/navigator/functions.js index 0dc0853..8211b5e 100644 --- a/navigator/functions.js +++ b/navigator/functions.js @@ -107,3 +107,25 @@ export function check_if_exists(path) { 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} + */ +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); + }); +} diff --git a/navigator/navigator.html b/navigator/index.html similarity index 95% rename from navigator/navigator.html rename to navigator/index.html index 9ad6bc2..71a5eb9 100644 --- a/navigator/navigator.html +++ b/navigator/index.html @@ -23,13 +23,13 @@ - + - +
@@ -102,6 +104,8 @@
+
+ diff --git a/navigator/navigator.js b/navigator/main.js similarity index 97% rename from navigator/navigator.js rename to navigator/main.js index 20dfdb4..ad401bb 100644 --- a/navigator/navigator.js +++ b/navigator/main.js @@ -17,9 +17,9 @@ along with Cockpit Navigator. If not, see . */ -import {ModalPrompt} from "./components/ModalPrompt.js"; -import {NavWindow} from "./components/NavWindow.js"; -import {NAVIGATOR_VERSION} from "./version.js"; +import { ModalPrompt } from "./components/ModalPrompt.js"; +import { NavWindow } from "./components/NavWindow.js"; +import { NAVIGATOR_VERSION } from "./version.js"; /** * diff --git a/navigator/manifest.json b/navigator/manifest.json index 8b68597..7d73529 100644 --- a/navigator/manifest.json +++ b/navigator/manifest.json @@ -4,7 +4,7 @@ "menu": { "navigator": { "label": "Navigator", - "path": "navigator.html", + "path": "index.html", "order": 114 } } diff --git a/navigator/scripts/write-to-file.py3 b/navigator/scripts/write-to-file.py3 new file mode 100755 index 0000000..19c70ea --- /dev/null +++ b/navigator/scripts/write-to-file.py3 @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +""" + Cockpit Navigator - A File System Browser for Cockpit. + Copyright (C) 2021 Josh Boudreau + + 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 . +""" + +""" +Synopsis: echo | write-to-file.py3 +""" + +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() diff --git a/navigator/navigator.css b/navigator/style.css similarity index 100% rename from navigator/navigator.css rename to navigator/style.css diff --git a/packaging/el7/main.spec b/packaging/el7/main.spec index b6d2012..c5634a6 100644 --- a/packaging/el7/main.spec +++ b/packaging/el7/main.spec @@ -32,6 +32,9 @@ rm -rf %{buildroot} /usr/share/cockpit/navigator/* %changelog +* Mon Oct 04 2021 Joshua Boudreau 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 0.5.4-1 - Add fuzzy search. - Optimize folder uploads. diff --git a/packaging/el8/main.spec b/packaging/el8/main.spec index b6d2012..c5634a6 100644 --- a/packaging/el8/main.spec +++ b/packaging/el8/main.spec @@ -32,6 +32,9 @@ rm -rf %{buildroot} /usr/share/cockpit/navigator/* %changelog +* Mon Oct 04 2021 Joshua Boudreau 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 0.5.4-1 - Add fuzzy search. - Optimize folder uploads. diff --git a/packaging/focal/changelog b/packaging/focal/changelog index 96fd20f..08863af 100644 --- a/packaging/focal/changelog +++ b/packaging/focal/changelog @@ -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 Mon, 04 Oct 2021 11:14:20 -0300 + cockpit-navigator (0.5.4-1focal) focal; urgency=medium * Add fuzzy search.