cockpit-navigator/navigator/components/NavWindow.js

743 lines
19 KiB
JavaScript

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 {format_bytes} from "../functions.js";
export class NavWindow {
constructor() {
this.item_display = "grid";
this.path_stack = (localStorage.getItem('navigator-path') ?? '/').split('/');
this.path_stack = this.path_stack.map((_, index) => new NavDir([...this.path_stack.slice(0, index + 1)].filter(part => part != ''), this));
this.path_stack_index = this.path_stack.length - 1;
this.selected_entries = new Set([this.pwd()]);
this.entries = [];
this.window = document.getElementById("nav-contents-view");
this.window.addEventListener("click", this);
this.window.addEventListener("contextmenu", this);
window.addEventListener("keydown", this);
this.last_selected_index = -1;
this.context_menu = new NavContextMenu("nav-context-menu", this);
this.clip_board = [];
this.uploader = new NavDragDrop(this.window, this);
this.sort_function = new SortFunctions();
}
/**
*
* @param {Event} e
*/
handleEvent(e) {
switch (e.type) {
case "click":
this.clear_selected();
this.show_selected_properties();
break;
case "contextmenu":
this.context_menu.show(e, this.pwd());
e.preventDefault();
break;
case "keydown":
if (e.keyCode === 46) {
this.delete_selected();
} else if (e.keyCode === 65 && e.ctrlKey) {
this.select_all();
e.preventDefault();
} else if (e.keyCode === 67 && e.ctrlKey) {
this.copy();
} else if (e.keyCode === 86 && e.ctrlKey) {
this.paste();
} else if (e.keyCode === 88 && e.ctrlKey) {
this.cut();
}
break;
default:
break;
}
}
async refresh() {
localStorage.setItem('navigator-path', `/${this.path_stack[this.path_stack_index].path.join('/')}`);
var num_dirs = 0;
var num_files = 0;
var bytes_sum = 0;
this.show_hidden = document.getElementById("nav-show-hidden").checked;
this.start_load();
try {
var files = await this.pwd().get_children(this);
} catch(e) {
this.up();
window.alert(e);
return;
}
while (this.entries.length) {
var entry = this.entries.pop();
entry.destroy();
}
files.sort((first, second) => {
if (first.nav_type === second.nav_type) {
return this.item_display === "list"
? this.sort_function.get_func()(first, second)
: this.sort_function.name_asc(first, second); // default to sort by name in grid view
}
if (first.nav_type === "dir")
return -1;
return 1;
});
files.forEach((file) => {
if (file.nav_type === "dir")
num_dirs++;
else {
num_files++;
bytes_sum += file.stat["size"];
}
if(!file.is_hidden_file || this.show_hidden) {
this.window.appendChild(file.dom_element);
this.entries.push(file);
}
file.context_menu_ref = this.context_menu;
});
document.getElementById("pwd").value = this.pwd().path_str();
this.set_selected(this.pwd(), false, false);
this.show_selected_properties();
document.getElementById("nav-num-dirs").innerText = num_dirs.toString();
document.getElementById("nav-num-files").innerText = num_files.toString();
document.getElementById("nav-num-bytes").innerText = format_bytes(bytes_sum);
this.stop_load();
this.set_nav_button_state();
}
set_nav_button_state() {
document.getElementById("nav-back-btn").disabled = (this.path_stack_index === 1);
document.getElementById("nav-forward-btn").disabled = (this.path_stack_index === this.path_stack.length - 1);
document.getElementById("nav-up-dir-btn").disabled = (this.pwd().path_str() === "/");
}
/**
*
* @returns {NavDir}
*/
pwd() {
return this.path_stack[this.path_stack_index];
}
/**
*
* @param {NavDir} new_dir
*/
cd(new_dir) {
this.path_stack.length = this.path_stack_index + 1;
this.path_stack.push(new_dir);
this.path_stack_index = this.path_stack.length - 1;
this.refresh();
}
back() {
this.path_stack_index = Math.max(this.path_stack_index - 1, 0);
this.refresh();
}
forward() {
this.path_stack_index = Math.min(this.path_stack_index + 1, this.path_stack.length - 1);
this.refresh();
}
up() {
if(this.pwd().path_str() !== '/')
this.cd(new NavDir(this.pwd().parent_dir()));
}
/**
*
* @param {NavEntry} entry
* @param {Boolean} select_range
* @param {Boolean} append
*/
set_selected(entry, select_range, append) {
this.hide_edit_selected();
for (let i of this.selected_entries) {
i.dom_element.classList.remove("nav-item-selected");
if (i.nav_type === "dir") {
i.dom_element.nav_item_icon.classList.remove("fa-folder-open");
i.dom_element.nav_item_icon.classList.add("fa-folder");
}
}
var to_be_selected = [];
if (append && this.selected_entries.has(entry)) {
this.selected_entries.delete(entry);
if (this.selected_entries.size === 0) {
this.clear_selected();
}
} else if (select_range && this.last_selected_index !== -1) {
var start = this.last_selected_index;
var end = this.entries.indexOf(entry);
if (end < start)
[start, end] = [end, start];
if (end === -1)
return;
to_be_selected = this.entries.slice(start, end + 1).filter(entry => !entry.stat["inaccessible"]);
} else {
if (!append)
this.selected_entries.clear();
to_be_selected = [entry];
}
for (let i of to_be_selected) {
this.selected_entries.add(i);
}
for (let i of this.selected_entries) {
i.dom_element.classList.add("nav-item-selected");
if (i.nav_type === "dir") {
i.dom_element.nav_item_icon.classList.remove("fa-folder");
i.dom_element.nav_item_icon.classList.add("fa-folder-open");
}
}
if (this.selected_entries.size > 1){
var name_fields = document.getElementsByClassName("nav-info-column-filename");
for (let name_field of name_fields) {
name_field.innerText = this.selected_entries.size.toString() + " selected"
name_field.title = name_field.innerText;
}
document.getElementById("nav-info-column-properties").innerHTML = "";
} else {
this.show_selected_properties();
}
this.last_selected_index = this.entries.indexOf(entry);
}
clear_selected() {
this.set_selected(this.pwd(), false, false);
}
selected_entry() {
return [...this.selected_entries][this.selected_entries.size - 1];
}
none_selected() {
return this.selected_entries.size === 1 && this.selected_entry() === this.pwd();
}
show_selected_properties() {
this.selected_entry().show_properties();
}
show_edit_selected() {
var dangerous_dirs = [
"/",
"/usr",
"/bin",
"/sbin",
"/lib",
"/lib32",
"/lib64",
"/usr/bin",
"/usr/include",
"/usr/lib",
"/usr/lib32",
"/usr/lib64",
"/usr/sbin",
];
var dangerous_selected = [];
for (let i of this.selected_entries) {
var path = i.path_str();
if (dangerous_dirs.includes(path)) {
dangerous_selected.push(path);
}
}
if (dangerous_selected.length > 0) {
var dangerous_selected_str = "";
if (dangerous_selected.length > 2) {
var last = dangerous_selected.pop();
dangerous_selected_str = dangerous_selected.join(", ");
dangerous_selected_str += ", and " + last;
} else if (dangerous_selected.length === 2) {
dangerous_selected_str = dangerous_selected.join(" and ");
} else {
dangerous_selected_str = dangerous_selected[0];
}
if (!window.confirm(
"Warning: editing " +
dangerous_selected_str +
" can be dangerous. Are you sure?"
)) {
return;
}
} else if (this.selected_entries.size > 1) {
if (!window.confirm(
"Warning: are you sure you want to edit permissions for " +
this.selected_entries.size +
" files?"
)) {
return;
}
}
if (this.selected_entries.size === 1) {
this.selected_entry().populate_edit_fields();
document.getElementById("selected-files-list-header").innerText = "";
document.getElementById("selected-files-list").innerText = "";
} else {
for (let field of ["owner", "group"]) {
document.getElementById("nav-edit-" + field).value = "";
}
var filename = document.getElementById("nav-edit-filename");
filename.value = "N/A";
filename.disabled = true;
for (let checkbox of document.getElementsByClassName("mode-checkbox")) {
checkbox.checked = false;
}
var targets = [];
for (let target of this.selected_entries) {
targets.push(target.filename());
}
var targets_str = targets.join(", ");
document.getElementById("selected-files-list-header").innerText = "Applying edits to:";
document.getElementById("selected-files-list").innerText = targets_str;
}
this.update_permissions_preview();
this.changed_mode = false;
document.getElementById("nav-mode-preview").innerText = "unchanged";
document.getElementById("nav-edit-properties").style.display = "flex";
document.getElementById("nav-show-properties").style.display = "none";
}
hide_edit_selected() {
document.getElementById("nav-show-properties").style.display = "flex";
document.getElementById("nav-edit-properties").style.display = "none";
}
/**
*
* @returns {number}
*/
get_new_permissions() {
var category_list = ["other", "group", "owner"];
var action_list = ["exec", "write", "read"];
var result = 0;
var bit = 0;
for (let category of category_list) {
for (let action of action_list) {
if (document.getElementById(category + "-" + action).checked)
result |= 1 << bit;
bit++;
}
}
return result;
}
update_permissions_preview() {
var new_perms = this.get_new_permissions();
var text = format_permissions(new_perms);
text += " (" + (new_perms & 0o777).toString(8) + ")";
document.getElementById("nav-mode-preview").innerText = text;
this.changed_mode = true;
}
async apply_edit_selected() {
var new_owner = document.getElementById("nav-edit-owner").value;
var new_group = document.getElementById("nav-edit-group").value;
var new_perms = this.get_new_permissions();
for (let entry of this.selected_entries) {
if (
new_owner !== entry.stat["owner"] ||
new_group !== entry.stat["group"]
) {
try {
await entry.chown(new_owner, new_group);
} catch(e) {
window.alert(e);
}
}
if (this.changed_mode && (new_perms & 0o777) !== (entry.stat["mode"] & 0o777)) {
try {
await entry.chmod(new_perms);
} catch(e) {
window.alert(e);
}
}
}
this.refresh();
this.hide_edit_selected();
}
async delete_selected() {
var prompt = "";
if (this.selected_entries.size > 1) {
prompt = "Deleting " + this.selected_entries.size + " files. This cannot be undone. Are you sure?";
} else {
prompt = "Deleting `" + this.selected_entry().path_str() + "` cannot be undone. Are you sure?";
}
if (!window.confirm(prompt)) {
return;
}
for (let target of this.selected_entries) {
try {
await target.rm();
} catch(e) {
window.alert(e);
}
}
this.refresh();
}
async mkdir() {
var new_dir_name = window.prompt("Directory Name: ");
if (new_dir_name === null)
return;
if (new_dir_name.includes("/")) {
window.alert("Directory name can't contain `/`.");
return;
}
var promise = new Promise((resolve, reject) => {
var proc = cockpit.spawn(
["mkdir", this.pwd().path_str() + "/" + new_dir_name],
{superuser: "try", err: "out"}
);
proc.done((data) => {
resolve();
});
proc.fail((e, data) => {
reject(data);
});
});
try {
await promise;
} catch(e) {
window.alert(e);
}
this.refresh();
}
async touch() {
var new_file_name = window.prompt("File Name: ");
if (new_file_name === null)
return;
if (new_file_name.includes("/")) {
window.alert("File name can't contain `/`.");
return;
}
var promise = new Promise((resolve, reject) => {
var proc = cockpit.spawn(
["/usr/share/cockpit/navigator/scripts/touch.py", this.pwd().path_str() + "/" + new_file_name],
{superuser: "try", err: "out"}
);
proc.done((data) => {
resolve();
});
proc.fail((e, data) => {
reject(data);
});
});
try {
await promise;
} catch(e) {
window.alert(e);
}
this.refresh();
}
async ln(default_target = "") {
var link_target = window.prompt("Link Target: ", default_target);
if (link_target === null)
return;
var link_name = window.prompt("Link Name: ");
if (link_name === null)
return;
if (link_name.includes("/")) {
window.alert("Link name can't contain `/`.");
return;
}
var link_path = this.pwd().path_str() + "/" + link_name;
var promise = new Promise((resolve, reject) => {
var proc = cockpit.spawn(
["ln", "-sn", link_target, link_path],
{superuser: "try", err: "out"}
);
proc.done((data) => {
resolve();
});
proc.fail((e, data) => {
reject(data);
});
});
try {
await promise;
} catch(e) {
window.alert(e);
}
this.refresh();
}
cut() {
this.clip_board = [...this.selected_entries];
this.copy_or_move = "move";
this.paste_cwd = this.pwd().path_str();
this.context_menu.menu_options["paste"].style.display = "flex";
}
copy() {
this.clip_board = [...this.selected_entries];
this.copy_or_move = "copy";
this.paste_cwd = this.pwd().path_str();
this.context_menu.menu_options["paste"].style.display = "flex";
}
paste() {
this.paste_clipboard();
this.context_menu.hide_paste();
}
async paste_clipboard() {
this.start_load();
var cmd = ["/usr/share/cockpit/navigator/scripts/paste.py"];
var dest = this.pwd().path_str();
if (this.copy_or_move === "move") {
cmd.push("-m");
}
cmd.push(this.paste_cwd);
for (let item of this.clip_board) {
cmd.push(item.path_str());
}
cmd.push(dest);
this.clip_board.length = 0; // clear clipboard
var promise = new Promise((resolve, reject) => {
var proc = cockpit.spawn(
cmd,
{superuser: "try", err: "ignore"}
);
proc.stream((data) => {
var payload = JSON.parse(data);
if (payload["wants-response"]) {
var user_response = window.confirm(payload["message"]);
proc.input(JSON.stringify(user_response) + "\n", true);
} else {
window.alert(payload["message"]);
}
});
proc.done((data) => {
resolve();
});
proc.fail((e, data) => {
reject("Paste failed.");
});
});
try {
await promise;
} catch(e) {
window.alert(e);
}
this.stop_load();
this.refresh();
}
/**
*
* @param {Event} e
*/
nav_bar_event_handler(e) {
switch(e.key){
case 'Enter':
this.nav_bar_cd();
break;
default:
break;
}
}
nav_bar_cd() {
var new_path = document.getElementById("pwd").value;
while (new_path.charAt(new_path.length - 1) === '/' && new_path.length > 1)
new_path = new_path.substr(0, new_path.length - 1);
this.cd(new NavDir(new_path));
}
async nav_bar_update_choices() {
var list = document.getElementById("possible-paths");
var partial_path_str = document.getElementById("pwd").value;
var last_delim = partial_path_str.lastIndexOf('/');
if(last_delim === -1)
return;
var parent_path_str = partial_path_str.slice(0, last_delim);
if(this.nav_bar_last_parent_path_str === parent_path_str)
return;
this.nav_bar_last_parent_path_str = parent_path_str;
var parent_dir = new NavDir(parent_path_str);
var objs;
try {
objs = await parent_dir.get_children(this);
} catch(e) {
return;
}
objs = objs.filter((child) => {return child.nav_type === "dir"});
while(list.firstChild)
list.removeChild(list.firstChild);
objs.forEach((obj) => {
var option = document.createElement("option");
option.value = obj.path_str();
list.appendChild(option);
});
}
start_load() {
document.getElementById("nav-loader-container").style.display = "block";
var buttons = document.getElementsByTagName("button");
for (let button of buttons) {
button.disabled = true;
}
}
stop_load() {
document.getElementById("nav-loader-container").style.display = "none";
var buttons = document.getElementsByTagName("button");
for (let button of buttons) {
button.disabled = false;
}
}
/**
*
* @param {Event} e
*/
toggle_show_hidden(e) {
localStorage.setItem('show-hidden-files', e.target.checked);
var icon = document.getElementById("nav-show-hidden-icon");
if (e.target.checked) {
icon.classList.remove("fa-low-vision");
icon.classList.add("fa-eye");
} else {
icon.classList.remove("fa-eye");
icon.classList.add("fa-low-vision");
}
this.refresh();
}
/**
*
* @returns {Promise<void>}
*/
get_system_users() {
return new Promise(async (resolve, reject) => {
var proc = cockpit.spawn(["getent", "passwd"], {err: "ignore", superuser: "try"});
proc.fail((e, data) => {
reject(data);
});
var list = document.getElementById("possible-owners");
while(list.firstChild) {
list.removeChild(list.firstChild);
}
var passwd;
try {
passwd = await proc;
} catch(e) {
reject(e);
return;
}
var passwd_entries = passwd.split("\n");
for (let entry of passwd_entries) {
var cols = entry.split(":");
var username = cols[0];
var option = document.createElement("option");
option.value = username;
list.appendChild(option);
}
resolve();
});
}
/**
*
* @returns {Promise<void>}
*/
get_system_groups() {
return new Promise(async (resolve, reject) => {
var proc = cockpit.spawn(["getent", "group"], {err: "ignore", superuser: "try"});
proc.fail((e, data) => {
reject(data);
});
var list = document.getElementById("possible-groups");
while(list.firstChild) {
list.removeChild(list.firstChild);
}
var group
try {
group = await proc;
} catch(e) {
reject(e);
return;
}
var group_entries = group.split("\n");
for (let entry of group_entries) {
var cols = entry.split(":");
var groupname = cols[0];
var option = document.createElement("option");
option.value = groupname;
list.appendChild(option);
}
resolve();
});
}
disable_buttons_for_editing() {
for (let button of document.getElementsByTagName("button")) {
if (!button.classList.contains("editor-btn"))
button.disabled = true;
}
document.getElementById("pwd").disabled = true;
}
enable_buttons() {
for (let button of document.getElementsByTagName("button")) {
button.disabled = false;
}
document.getElementById("pwd").disabled = false;
this.set_nav_button_state();
}
select_all() {
this.selected_entries.clear();
for (let entry of this.entries) {
if (!entry.is_hidden_file || this.show_hidden) {
this.set_selected(entry, false, true);
}
}
}
async switch_item_display() {
var button = document.getElementById("nav-item-display-icon");
if (this.item_display === "grid") {
this.item_display = "list";
await this.refresh();
this.window.classList.remove("contents-view-grid");
this.window.classList.add("contents-view-list");
button.classList.remove("fa-list");
button.classList.add("fa-th");
} else {
this.item_display = "grid";
await this.refresh();
this.window.classList.remove("contents-view-list");
this.window.classList.add("contents-view-grid");
button.classList.remove("fa-th");
button.classList.add("fa-list");
}
localStorage.setItem("item-display", this.item_display);
}
search_filter(event) {
var search_name = event.target.value;
this.entries.forEach((entry) => {
if (entry.filename().toLowerCase().startsWith(search_name.toLowerCase()))
entry.show();
else
entry.hide();
});
}
}