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.
* Optimize folder uploads.
* Fix bugs with selecting entries and renaming files.
* Stop user from deleting or renaming system-critical paths.
* Fix maintaining file permissions and ownership after editing file.
* Add file upload button to top bar.

View File

@ -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 <version>` (v0.5.4 is latest)
1. `$ git checkout <version>` (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
```
```

View File

@ -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 <jboudreau@45drives.com>",
"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,

View File

@ -17,9 +17,9 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/
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 {
/**

View File

@ -17,8 +17,8 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/
import {FileUpload} from "./FileUpload.js";
import {NavWindow} from "./NavWindow.js";
import { FileUpload } from "./FileUpload.js";
import { NavWindow } from "./NavWindow.js";
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 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
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 <https://www.gnu.org/licenses/>.
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 <https://www.gnu.org/licenses/>.
*/
/**
@ -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();
}
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);
}
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";
}
show() {
this.modal.style.display = "block";
}
hide() {
this.modal.style.display = "none";
}
hide() {
this.modal.style.display = "none";
}
/**
*
* @param {string} header
*/
set_header(header) {
this.header.innerText = header;
}
/**
*
* @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} 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
* @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<boolean>}
*/
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 {string} message
* @param {boolean} danger
* @returns {Promise<boolean>}
*/
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.<string, Request>} requests
* @returns {Promise<Object|string>}
*/
prompt(header, requests) {
this.set_header(header);
this.body.innerHTML = "";
this.footer.innerHTML = "";
this.footer.append(this.cancel, this.ok);
let inputs = [];
/**
*
* @param {string} header
* @param {Object.<string, Request>} requests
* @returns {Promise<Object|string>}
*/
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 === "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();
}
});
}
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();
}
});
}
}

View File

@ -17,10 +17,10 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/
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 {
/**

View File

@ -17,10 +17,10 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/
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 {
/**

View File

@ -17,7 +17,7 @@
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 {
/**

View File

@ -17,10 +17,10 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@ -17,8 +17,8 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/
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 {
/**

View File

@ -17,10 +17,10 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/
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();

View File

@ -17,13 +17,13 @@
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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<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=".">
<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="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">
<script src="fontawesome/attribution.js"></script>
<script src="../base1/cockpit.js"></script>
<script src="../manifests.js"></script>
<script src="../*/po.js"></script>
<script src="navigator.js" type="module"></script>
<script src="main.js" type="module"></script>
</head>
<body>
<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>
<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>
<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 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>
<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>
<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 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/>.
*/
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";
/**
*

View File

@ -4,7 +4,7 @@
"menu": {
"navigator": {
"label": "Navigator",
"path": "navigator.html",
"path": "index.html",
"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/*
%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
- Add fuzzy search.
- Optimize folder uploads.

View File

@ -32,6 +32,9 @@ rm -rf %{buildroot}
/usr/share/cockpit/navigator/*
%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
- Add fuzzy search.
- 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
* Add fuzzy search.