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.
|
* 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.
|
|
12
README.md
12
README.md
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
|
@ -4,7 +4,7 @@
|
||||||
"menu": {
|
"menu": {
|
||||||
"navigator": {
|
"navigator": {
|
||||||
"label": "Navigator",
|
"label": "Navigator",
|
||||||
"path": "navigator.html",
|
"path": "index.html",
|
||||||
"order": 114
|
"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/*
|
/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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue