mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-07-31 01:24:37 +02:00
commit
9b2886e81b
@ -22,7 +22,7 @@
|
|||||||
--border: #bebebe;
|
--border: #bebebe;
|
||||||
--navigation: #fff;
|
--navigation: #fff;
|
||||||
--font: #1c1c1c;
|
--font: #1c1c1c;
|
||||||
--selected: #fff;
|
--selected: #f8f8f8;
|
||||||
--toggle-light: #151515;
|
--toggle-light: #151515;
|
||||||
--toggle-dark: #ccc;
|
--toggle-dark: #ccc;
|
||||||
--scrollbar-thumb: var(--border);
|
--scrollbar-thumb: var(--border);
|
||||||
@ -31,8 +31,9 @@
|
|||||||
--loading-bg-color: rgba(255, 255, 255, 0.5);
|
--loading-bg-color: rgba(255, 255, 255, 0.5);
|
||||||
--textarea-bg: var(--navigation);
|
--textarea-bg: var(--navigation);
|
||||||
--logo-45: #333;
|
--logo-45: #333;
|
||||||
--nav-entry-color: #4f99e4;
|
--nav-entry-color: #555F6E;
|
||||||
--nav-border-radius: 4px;
|
--nav-border-radius: 4px;
|
||||||
|
--symlink-symbol-color: var(--navigation);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
@ -48,7 +49,8 @@
|
|||||||
--loading-bg-color: rgba(33, 36, 39, 0.5);
|
--loading-bg-color: rgba(33, 36, 39, 0.5);
|
||||||
--textarea-bg: var(--navigation);
|
--textarea-bg: var(--navigation);
|
||||||
--logo-45: #fff;
|
--logo-45: #fff;
|
||||||
--nav-entry-color: var(--border);
|
--nav-entry-color: #555F6E;
|
||||||
|
--symlink-symbol-color: var(--navigation);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-button:disabled[data-theme="dark"] {
|
.pf-c-button:disabled[data-theme="dark"] {
|
||||||
@ -215,12 +217,29 @@ input[type="text"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-item .nav-item-icon {
|
.nav-item .nav-item-icon {
|
||||||
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
font-size: 80px;
|
font-size: 80px;
|
||||||
color: var(--nav-entry-color);
|
color: var(--nav-entry-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-item-symlink-symbol-dir {
|
||||||
|
position: absolute;
|
||||||
|
color: var(--symlink-symbol-color);
|
||||||
|
font-size: 12pt;
|
||||||
|
bottom: 18%;
|
||||||
|
right: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-symlink-symbol-file {
|
||||||
|
position: absolute;
|
||||||
|
color: var(--symlink-symbol-color);
|
||||||
|
font-size: 12pt;
|
||||||
|
bottom: 8%;
|
||||||
|
right: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-info-column {
|
.nav-info-column {
|
||||||
background-color: var(--container);
|
background-color: var(--container);
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
|
@ -92,11 +92,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="nav-property-pair">
|
<div class="nav-property-pair">
|
||||||
<span class="nav-property-pair-key">Owner</span>
|
<span class="nav-property-pair-key">Owner</span>
|
||||||
<input type="text" class="nav-property-pair-value" id="nav-edit-owner"></input>
|
<input type="text" list="possible-owners-list" autocomplete="off" class="nav-property-pair-value" id="nav-edit-owner"></input>
|
||||||
|
<datalist id="possible-owners-list">
|
||||||
|
<select id="possible-owners">
|
||||||
|
</select>
|
||||||
|
</datalist>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-property-pair">
|
<div class="nav-property-pair">
|
||||||
<span class="nav-property-pair-key">Group</span>
|
<span class="nav-property-pair-key">Group</span>
|
||||||
<input type="text" class="nav-property-pair-value" id="nav-edit-group"></input>
|
<input type="text" list="possible-groups-list" autocomplete="off" class="nav-property-pair-value" id="nav-edit-group"></input>
|
||||||
|
<datalist id="possible-groups-list">
|
||||||
|
<select id="possible-groups">
|
||||||
|
</select>
|
||||||
|
</datalist>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-property-pair">
|
<div class="nav-property-pair">
|
||||||
<span class="nav-property-pair-key">Mode</span>
|
<span class="nav-property-pair-key">Mode</span>
|
||||||
|
@ -404,6 +404,73 @@ class NavFile extends NavEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NavFileLink extends NavFile{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} path
|
||||||
|
* @param {object} stat
|
||||||
|
* @param {NavWindow} nav_window_ref
|
||||||
|
* @param {string} link_target
|
||||||
|
*/
|
||||||
|
constructor(path, stat, nav_window_ref, link_target) {
|
||||||
|
super(path, stat, nav_window_ref);
|
||||||
|
var link_icon = this.dom_element.nav_item_icon.link_icon = document.createElement("i");
|
||||||
|
link_icon.classList.add("fas", "fa-link", "nav-item-symlink-symbol-file");
|
||||||
|
this.dom_element.nav_item_icon.appendChild(link_icon);
|
||||||
|
this.double_click = false;
|
||||||
|
this.link_target = link_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
show_properties() {
|
||||||
|
var extra_properties = property_entry_html("Link Target", this.link_target);
|
||||||
|
super.show_properties(extra_properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
get_link_target_path() {
|
||||||
|
var target = "";
|
||||||
|
if(this.link_target.charAt(0) === '/')
|
||||||
|
target = this.link_target;
|
||||||
|
else
|
||||||
|
target = "/" + this.parent_dir().join("/") + "/" + this.link_target;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
async show_edit_file_contents() {
|
||||||
|
for (let button of document.getElementsByTagName("button")) {
|
||||||
|
if (!button.classList.contains("editor-btn"))
|
||||||
|
button.disabled = true;
|
||||||
|
}
|
||||||
|
document.getElementById("pwd").disabled = true;
|
||||||
|
var target_path = this.get_link_target_path();
|
||||||
|
var proc_output = await cockpit.spawn(["file", "--mime-type", target_path], {superuser: "try"});
|
||||||
|
var fields = proc_output.split(':');
|
||||||
|
var type = fields[1].trim();
|
||||||
|
if(!(type.match(/^text/) || type.match(/^inode\/x-empty$/) || this.stat["size"] === 0)){
|
||||||
|
if(!window.confirm("File is of type `" + type + "`. Are you sure you want to edit it?"))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var contents = await cockpit.spawn(["cat", target_path], {superuser: "try"});
|
||||||
|
document.getElementById("nav-edit-contents-textarea").value = contents;
|
||||||
|
document.getElementById("nav-cancel-edit-contents-btn").onclick = this.hide_edit_file_contents.bind(this);
|
||||||
|
document.getElementById("nav-continue-edit-contents-btn").onclick = this.write_to_file.bind(this);
|
||||||
|
document.getElementById("nav-edit-contents-header").innerText = "Editing " + this.path_str();
|
||||||
|
document.getElementById("nav-contents-view").style.display = "none";
|
||||||
|
document.getElementById("nav-edit-contents-view").style.display = "flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
async write_to_file() {
|
||||||
|
var target_path = this.get_link_target_path();
|
||||||
|
var new_contents = document.getElementById("nav-edit-contents-textarea").value;
|
||||||
|
await cockpit.script("echo -n \"$1\" > $2", [new_contents, target_path], {superuser: "try"});
|
||||||
|
this.nav_window_ref.refresh();
|
||||||
|
this.hide_edit_file_contents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class NavDir extends NavEntry {
|
class NavDir extends NavEntry {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -465,10 +532,20 @@ class NavDir extends NavEntry {
|
|||||||
var filename = entry["filename"];
|
var filename = entry["filename"];
|
||||||
var path = (this.path.length >= 1 && this.path[0]) ? [...this.path, filename] : [filename];
|
var path = (this.path.length >= 1 && this.path[0]) ? [...this.path, filename] : [filename];
|
||||||
var stat = entry["stat"];
|
var stat = entry["stat"];
|
||||||
if (entry["isdir"])
|
switch(stat["mode-str"].charAt(0)) {
|
||||||
children.push(new NavDir(path, stat, nav_window_ref));
|
case 'd':
|
||||||
else
|
children.push(new NavDir(path, stat, nav_window_ref));
|
||||||
children.push(new NavFile(path, stat, nav_window_ref));
|
break;
|
||||||
|
case 'l':
|
||||||
|
if(entry["isdir"])
|
||||||
|
children.push(new NavDirLink(path, stat, nav_window_ref, entry["link-target"]));
|
||||||
|
else
|
||||||
|
children.push(new NavFileLink(path, stat, nav_window_ref, entry["link-target"]));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
children.push(new NavFile(path, stat, nav_window_ref));
|
||||||
|
break;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
children.sort((first, second) => {
|
children.sort((first, second) => {
|
||||||
if (first.nav_type === second.nav_type) {
|
if (first.nav_type === second.nav_type) {
|
||||||
@ -494,7 +571,7 @@ class NavDir extends NavEntry {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns {any}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
async cephfs_dir_stats() {
|
async cephfs_dir_stats() {
|
||||||
try {
|
try {
|
||||||
@ -508,8 +585,11 @@ class NavDir extends NavEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async show_properties() {
|
/**
|
||||||
var extra_properties = "";
|
*
|
||||||
|
* @param {string} extra_properties
|
||||||
|
*/
|
||||||
|
async show_properties(extra_properties = "") {
|
||||||
if(!this.hasOwnProperty("ceph_stats"))
|
if(!this.hasOwnProperty("ceph_stats"))
|
||||||
this.ceph_stats = await this.cephfs_dir_stats();
|
this.ceph_stats = await this.cephfs_dir_stats();
|
||||||
// See if a JSON object exists for folder we are currently looking at
|
// See if a JSON object exists for folder we are currently looking at
|
||||||
@ -553,6 +633,40 @@ class NavDir extends NavEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NavDirLink extends NavDir{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string|string[]} path
|
||||||
|
* @param {object} stat
|
||||||
|
* @param {NavWindow} nav_window_ref
|
||||||
|
* @param {string} link_target
|
||||||
|
*/
|
||||||
|
constructor(path, stat, nav_window_ref, link_target) {
|
||||||
|
super(path, stat, nav_window_ref);
|
||||||
|
var link_icon = this.dom_element.nav_item_icon.link_icon = document.createElement("i");
|
||||||
|
link_icon.classList.add("fas", "fa-link", "nav-item-symlink-symbol-dir");
|
||||||
|
this.dom_element.nav_item_icon.appendChild(link_icon);
|
||||||
|
this.double_click = false;
|
||||||
|
this.link_target = link_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
async rm() {
|
||||||
|
var proc = cockpit.spawn(
|
||||||
|
["rm", "-f", this.path_str()],
|
||||||
|
{superuser: "try", err: "out"}
|
||||||
|
);
|
||||||
|
proc.fail((e, data) => {
|
||||||
|
window.alert(data);
|
||||||
|
});
|
||||||
|
await proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
show_properties() {
|
||||||
|
var extra_properties = property_entry_html("Link Target", this.link_target);
|
||||||
|
super.show_properties(extra_properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class NavWindow {
|
class NavWindow {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.path_stack = (localStorage.getItem('navigator-path') ?? '/').split('/');
|
this.path_stack = (localStorage.getItem('navigator-path') ?? '/').split('/');
|
||||||
@ -645,7 +759,7 @@ class NavWindow {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {any} entry
|
* @param {NavEntry} entry
|
||||||
*/
|
*/
|
||||||
set_selected(entry) {
|
set_selected(entry) {
|
||||||
this.hide_edit_selected();
|
this.hide_edit_selected();
|
||||||
@ -828,7 +942,6 @@ class NavWindow {
|
|||||||
return;
|
return;
|
||||||
this.nav_bar_last_parent_path_str = parent_path_str;
|
this.nav_bar_last_parent_path_str = parent_path_str;
|
||||||
var parent_dir = new NavDir(parent_path_str);
|
var parent_dir = new NavDir(parent_path_str);
|
||||||
console.log(parent_dir.path_str());
|
|
||||||
var error = false;
|
var error = false;
|
||||||
var objs = await parent_dir.get_children(this, true).catch(() => {error = true});
|
var objs = await parent_dir.get_children(this, true).catch(() => {error = true});
|
||||||
if(error)
|
if(error)
|
||||||
@ -876,6 +989,38 @@ class NavWindow {
|
|||||||
}
|
}
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
async get_system_users() {
|
||||||
|
var proc = cockpit.spawn(["getent", "passwd"], {err: "ignore", superuser: "try"});
|
||||||
|
var list = document.getElementById("possible-owners");
|
||||||
|
while(list.firstChild) {
|
||||||
|
list.removeChild(list.firstChild);
|
||||||
|
}
|
||||||
|
var passwd = await proc;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async get_system_groups() {
|
||||||
|
var proc = cockpit.spawn(["getent", "group"], {err: "ignore", superuser: "try"});
|
||||||
|
var list = document.getElementById("possible-groups");
|
||||||
|
while(list.firstChild) {
|
||||||
|
list.removeChild(list.firstChild);
|
||||||
|
}
|
||||||
|
var group = await proc;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let nav_window = new NavWindow();
|
let nav_window = new NavWindow();
|
||||||
@ -905,7 +1050,12 @@ function set_up_buttons() {
|
|||||||
async function main() {
|
async function main() {
|
||||||
set_last_theme_state();
|
set_last_theme_state();
|
||||||
load_hidden_file_state(nav_window);
|
load_hidden_file_state(nav_window);
|
||||||
await nav_window.refresh();
|
var get_users = nav_window.get_system_users();
|
||||||
|
var get_groups = nav_window.get_system_groups();
|
||||||
|
var refresh = nav_window.refresh();
|
||||||
|
await get_users;
|
||||||
|
await get_groups;
|
||||||
|
await refresh;
|
||||||
set_up_buttons();
|
set_up_buttons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from stat import S_ISDIR, filemode
|
from stat import S_ISDIR, S_ISLNK, filemode
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from pwd import getpwuid
|
from pwd import getpwuid
|
||||||
@ -31,6 +31,9 @@ def get_stat(full_path, filename = '/'):
|
|||||||
isdir = S_ISDIR(os.stat(full_path).st_mode)
|
isdir = S_ISDIR(os.stat(full_path).st_mode)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
link_target = '?'
|
||||||
|
if S_ISLNK(stats.st_mode):
|
||||||
|
link_target = os.readlink(full_path)
|
||||||
owner = '?'
|
owner = '?'
|
||||||
try:
|
try:
|
||||||
owner = getpwuid(stats.st_uid).pw_name
|
owner = getpwuid(stats.st_uid).pw_name
|
||||||
@ -44,6 +47,7 @@ def get_stat(full_path, filename = '/'):
|
|||||||
response = {
|
response = {
|
||||||
"filename": filename,
|
"filename": filename,
|
||||||
"isdir": isdir,
|
"isdir": isdir,
|
||||||
|
"link-target": link_target,
|
||||||
"stat": {
|
"stat": {
|
||||||
"mode": stats.st_mode,
|
"mode": stats.st_mode,
|
||||||
"mode-str": filemode(stats.st_mode),
|
"mode-str": filemode(stats.st_mode),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user