Merge pull request #32 from 45Drives/dev-josh
Fix permissions & ownership while editing files, add edit and upload buttons
This commit is contained in:
commit
0f4b9d07de
|
@ -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.
|
12
README.md
12
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 <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
|
||||
```
|
||||
```
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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";
|
||||
|
||||
/**
|
||||
*
|
|
@ -4,7 +4,7 @@
|
|||
"menu": {
|
||||
"navigator": {
|
||||
"label": "Navigator",
|
||||
"path": "navigator.html",
|
||||
"path": "index.html",
|
||||
"order": 114
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue