mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-07-30 09:05:23 +02:00
Merge branch 'main' into dev-sam
This commit is contained in:
commit
93721b8dec
@ -1,2 +1,2 @@
|
|||||||
# cockpit-navigator
|
# Cockpit Navigator
|
||||||
File Browser for Cockpit
|
A File System Browser for Cockpit.
|
||||||
|
15
makefile
15
makefile
@ -1,3 +1,18 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
||||||
|
|
||||||
|
74
navigator/branding/logo-dark.svg
Normal file
74
navigator/branding/logo-dark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
73
navigator/branding/logo-light.svg
Normal file
73
navigator/branding/logo-light.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Cockpit Navigator - A File System Browser for Cockpit.
|
||||||
|
Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com>
|
||||||
|
Copyright (C) 2021 Sam Silver <ssilver@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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* white style */
|
/* white style */
|
||||||
--container: #fff;
|
--container: #fff;
|
||||||
@ -7,6 +25,12 @@
|
|||||||
--selected: #fff;
|
--selected: #fff;
|
||||||
--toggle-light: #151515;
|
--toggle-light: #151515;
|
||||||
--toggle-dark: #ccc;
|
--toggle-dark: #ccc;
|
||||||
|
--scrollbar-thumb: var(--border);
|
||||||
|
--scrollbar-bg: var(--navigation);
|
||||||
|
--scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-bg);
|
||||||
|
--loading-bg-color: rgba(255, 255, 255, 0.5);
|
||||||
|
--textarea-bg: var(--navigation);
|
||||||
|
--logo-45: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
@ -15,9 +39,81 @@
|
|||||||
--border: #3c3f42;
|
--border: #3c3f42;
|
||||||
--font: #fff;
|
--font: #fff;
|
||||||
--selected: #191a1b;
|
--selected: #191a1b;
|
||||||
--navigation: #121212;
|
--scrollbar-thumb: var(--container);
|
||||||
|
--scrollbar-bg: var(--navigation);
|
||||||
|
--scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-bg);
|
||||||
|
--loading-bg-color: rgba(33, 36, 39, 0.5);
|
||||||
|
--textarea-bg: var(--navigation);
|
||||||
|
--logo-45: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pf-c-button:disabled[data-theme="dark"] {
|
||||||
|
background-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-loader-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: var(--loading-bg-color);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-loader-centerer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 27%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-loader {
|
||||||
|
margin: auto;
|
||||||
|
border: 6px solid rgba(0,0,0,0);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top: 6px solid var(--border);
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
-webkit-animation: spin 2s linear infinite; /* Safari */
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safari */
|
||||||
|
@-webkit-keyframes spin {
|
||||||
|
0% { -webkit-transform: rotate(0deg); }
|
||||||
|
100% { -webkit-transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
body::-webkit-scrollbar {
|
||||||
|
width: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--scrollbar-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
body::-webkit-scrollbar-track {
|
||||||
|
background: var(--scrollbar-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
body::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--scrollbar-thumb) ;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 3px solid var(--scrollbar-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.flex-row {
|
.flex-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -36,10 +132,18 @@
|
|||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.horizontal-spacer-sm {
|
||||||
|
margin-right: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.vertical-spacer {
|
.vertical-spacer {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spacer-stretchy {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-hidden {
|
.nav-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -49,10 +153,12 @@
|
|||||||
background-color: var(--container);
|
background-color: var(--container);
|
||||||
color: var(--font);
|
color: var(--font);
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-bar {
|
.navigation-bar {
|
||||||
background-color: var(--container);
|
background-color: var(--container);
|
||||||
|
color: inherit;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 0.25em 1em 0.25em 1em;
|
padding: 0.25em 1em 0.25em 1em;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
@ -166,18 +272,63 @@
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor-header {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-file-contents {
|
||||||
|
height: 100%;
|
||||||
|
flex-basis: 0;
|
||||||
|
flex-grow: 8;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-file-contents > textarea {
|
||||||
|
flex-grow: 1;
|
||||||
|
white-space: pre;
|
||||||
|
overflow: auto;
|
||||||
|
resize: none;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: none;
|
||||||
|
padding: 5px;
|
||||||
|
color: var(--font);
|
||||||
|
background-color: var(--textarea-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-footer {
|
||||||
|
flex: 1;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-footer > a > img {
|
||||||
|
height: 1.25em;
|
||||||
|
width: auto;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-footer > a > .logo-45 {
|
||||||
|
font-weight: 900;
|
||||||
|
color: var(--logo-45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-footer > a > .logo-drives {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #981c20;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-toggle {
|
.nav-toggle {
|
||||||
position: absolute;
|
justify-self: flex-end;
|
||||||
right: 0;
|
|
||||||
bottom: 0.5em;
|
|
||||||
margin-right: 1.9em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch {
|
.switch {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 38px;
|
width: 30px;
|
||||||
height: 20px;
|
height: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch input {
|
.switch input {
|
||||||
@ -201,8 +352,8 @@
|
|||||||
.slider:before {
|
.slider:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: "";
|
||||||
height: 16px;
|
height: 13px;
|
||||||
width: 16px;
|
width: 13px;
|
||||||
left: 2px;
|
left: 2px;
|
||||||
bottom: 2px;
|
bottom: 2px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
@ -219,13 +370,13 @@ input:focus + .slider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input:checked + .slider:before {
|
input:checked + .slider:before {
|
||||||
-webkit-transform: translateX(18px);
|
-webkit-transform: translateX(13px);
|
||||||
-ms-transform: translateX(18px);
|
-ms-transform: translateX(13px);
|
||||||
transform: translateX(18px);
|
transform: translateX(13px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider.round {
|
.slider.round {
|
||||||
border-radius: 34px;
|
border-radius: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider.round:before {
|
.slider.round:before {
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
|
||||||
<!--
|
<!--
|
||||||
Cockpit Samba Manager - Cockpit plugin for managing Samba.
|
Cockpit Navigator - A File System Browser for Cockpit.
|
||||||
Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com>
|
Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com>
|
||||||
|
Copyright (C) 2021 Sam Silver <ssilver@45drives.com>
|
||||||
This file is part of Cockpit Samba Manager.
|
|
||||||
Cockpit Samba Manager is free software: you can redistribute it and/or modify
|
This file is part of Cockpit Navigator.
|
||||||
it under the terms of the GNU General Public License as published by
|
Cockpit Navigator is free software: you can redistribute it and/or modify
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
it under the terms of the GNU General Public License as published by
|
||||||
(at your option) any later version.
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
Cockpit Samba Manager is distributed in the hope that it will be useful,
|
(at your option) any later version.
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Cockpit Navigator is distributed in the hope that it will be useful,
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
GNU General Public License for more details.
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
You should have received a copy of the GNU General Public License
|
GNU General Public License for more details.
|
||||||
along with Cockpit Samba Manager. If not, see <https://www.gnu.org/licenses/>.
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -30,17 +30,28 @@
|
|||||||
<script defer src="navigator.js"></script>
|
<script defer src="navigator.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div class="nav-loader-container" id="nav-loader-container">
|
||||||
|
<div class="nav-loader-centerer">
|
||||||
|
<div class="nav-loader"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="flex-col outer-container">
|
<div class="flex-col outer-container">
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<div class="nav-btn-group">
|
<div class="nav-btn-group">
|
||||||
|
<button class="pf-c-button pf-m-secondary" id="nav-back-btn"><i class="fas fa-arrow-left"></i></button>
|
||||||
|
<div class="horizontal-spacer"></div>
|
||||||
|
<button class="pf-c-button pf-m-secondary" id="nav-forward-btn"><i class="fas fa-arrow-right"></i></button>
|
||||||
|
<div class="horizontal-spacer"></div>
|
||||||
<button class="pf-c-button pf-m-secondary" id="nav-up-dir-btn"><i class="fas fa-arrow-up"></i></button>
|
<button class="pf-c-button pf-m-secondary" id="nav-up-dir-btn"><i class="fas fa-arrow-up"></i></button>
|
||||||
<div class="horizontal-spacer"></div>
|
<div class="horizontal-spacer"></div>
|
||||||
<button class="pf-c-button pf-m-secondary" id="nav-refresh-btn"><i class="fas fa-sync"></i></button>
|
<button class="pf-c-button pf-m-secondary" id="nav-refresh-btn"><i class="fas fa-sync"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="horizontal-spacer"></div>
|
<div class="horizontal-spacer"></div>
|
||||||
<div class="navigation-bar" id="pwd">
|
<input type="text" list="possible-paths-list" autocomplete="off" class="navigation-bar" id="pwd"></input>
|
||||||
/current/dir
|
<datalist id="possible-paths-list">
|
||||||
</div>
|
<select id="possible-paths">
|
||||||
|
</select>
|
||||||
|
</datalist>
|
||||||
<div class="horizontal-spacer"></div>
|
<div class="horizontal-spacer"></div>
|
||||||
<button class="pf-c-button pf-m-primary" id="nav-mkdir-btn"><i class="fas fa-folder-plus"></i></button>
|
<button class="pf-c-button pf-m-primary" id="nav-mkdir-btn"><i class="fas fa-folder-plus"></i></button>
|
||||||
<div class="horizontal-spacer"></div>
|
<div class="horizontal-spacer"></div>
|
||||||
@ -49,6 +60,17 @@
|
|||||||
<div class="vertical-spacer"></div>
|
<div class="vertical-spacer"></div>
|
||||||
<div class="flex-row inner-container">
|
<div class="flex-row inner-container">
|
||||||
<div class="contents-view" id="nav-contents-view"></div>
|
<div class="contents-view" id="nav-contents-view"></div>
|
||||||
|
<div class="edit-file-contents nav-hidden" id="nav-edit-contents-view">
|
||||||
|
<div class="editor-header" id="nav-edit-contents-header"></div>
|
||||||
|
<div class="vertical-spacer"></div>
|
||||||
|
<textarea id="nav-edit-contents-textarea"></textarea>
|
||||||
|
<div class="vertical-spacer"></div>
|
||||||
|
<div class="nav-btn-group">
|
||||||
|
<button class="pf-c-button pf-m-danger editor-btn" id="nav-cancel-edit-contents-btn"><i class="fas fa-times"></i></button>
|
||||||
|
<div class="horizontal-spacer"></div>
|
||||||
|
<button class="pf-c-button pf-m-primary editor-btn" id="nav-continue-edit-contents-btn"><i class="fas fa-save"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="horizontal-spacer"></div>
|
<div class="horizontal-spacer"></div>
|
||||||
<div class="nav-info-column" id="nav-info-column">
|
<div class="nav-info-column" id="nav-info-column">
|
||||||
<div id="nav-show-properties">
|
<div id="nav-show-properties">
|
||||||
@ -57,17 +79,10 @@
|
|||||||
<div class="nav-btn-group">
|
<div class="nav-btn-group">
|
||||||
<button class="pf-c-button pf-m-danger" id="nav-delete-btn"><i class="fas fa-trash-alt"></i></button>
|
<button class="pf-c-button pf-m-danger" id="nav-delete-btn"><i class="fas fa-trash-alt"></i></button>
|
||||||
<div class="horizontal-spacer"></div>
|
<div class="horizontal-spacer"></div>
|
||||||
<button class="pf-c-button pf-m-primary" id="nav-edit-properties-btn"><i class="fas fa-edit"></i></button>
|
<button class="pf-c-button pf-m-primary" id="nav-edit-properties-btn"><i class="fas fa-sliders-h"></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>
|
||||||
<div class="nav-toggle">
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" id="toggle-theme">
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
<div class="vertical-spacer"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-hidden" id="nav-edit-properties">
|
<div class="nav-hidden" id="nav-edit-properties">
|
||||||
<div class="nav-info-column-filename"></div>
|
<div class="nav-info-column-filename"></div>
|
||||||
@ -107,13 +122,44 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="vertical-spacer"></div>
|
<div class="vertical-spacer"></div>
|
||||||
<div class="nav-btn-group">
|
<div class="nav-btn-group">
|
||||||
<button class="pf-c-button pf-m-danger" id="nav-cancel-edit-btn">Cancel</button>
|
<button class="pf-c-button pf-m-danger" id="nav-cancel-edit-btn"><i class="fas fa-times"></i></button>
|
||||||
<div class="horizontal-spacer"></div>
|
<div class="horizontal-spacer"></div>
|
||||||
<button class="pf-c-button pf-m-primary" id="nav-apply-edit-btn">Apply</button>
|
<button class="pf-c-button pf-m-primary" id="nav-apply-edit-btn"><i class="fas fa-save"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-row nav-footer">
|
||||||
|
<div>
|
||||||
|
<span id="nav-num-dirs">-</span> Directories, <span id="nav-num-files">-</span> Files
|
||||||
|
</div>
|
||||||
|
<div class="spacer-stretchy"></div>
|
||||||
|
<a href="https://45drives.com" target="_blank">
|
||||||
|
<img src="branding/logo-light.svg" id="logo-45d"><span class="logo-45">45</span><span class="logo-drives">Drives</span>
|
||||||
|
</a>
|
||||||
|
<div class="spacer-stretchy"></div>
|
||||||
|
<div class="nav-toggle">
|
||||||
|
<div class="nav-btn-group">
|
||||||
|
<i class="fas fa-low-vision" id="nav-show-hidden-icon"></i>
|
||||||
|
<div class="horizontal-spacer-sm"></div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="nav-show-hidden">
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="horizontal-spacer"></div>
|
||||||
|
<div class="nav-toggle">
|
||||||
|
<div class="nav-btn-group">
|
||||||
|
<i class="fas fa-sun" id="houston-theme-icon"></i>
|
||||||
|
<div class="horizontal-spacer-sm"></div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="toggle-theme">
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,13 +1,32 @@
|
|||||||
function property_entry_html(key, value) {
|
/*
|
||||||
|
Cockpit Navigator - A File System Browser for Cockpit.
|
||||||
|
Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com>
|
||||||
|
Copyright (C) 2021 Sam Silver <ssilver@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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function property_entry_html(/*string*/ key, /*string*/ value) {
|
||||||
var html = '<div class="nav-property-pair">';
|
var html = '<div class="nav-property-pair">';
|
||||||
html += '<span class="nav-property-pair-key">' + key + "</span>";
|
html += '<span class="nav-property-pair-key">' + key + '</span>';
|
||||||
html += '<span class="nav-property-pair-value">' + value + "</span>";
|
html += '<span class="nav-property-pair-value">' + value + '</span>';
|
||||||
html += "</div>";
|
html += '</div>';
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function format_bytes(bytes) {
|
function format_bytes(/*int*/ bytes) {
|
||||||
if (bytes === 0) return "0 B";
|
if (bytes === 0)
|
||||||
|
return "0 B";
|
||||||
var units = [" B", " KiB", " MiB", " GiB", " TiB", " PiB"];
|
var units = [" B", " KiB", " MiB", " GiB", " TiB", " PiB"];
|
||||||
var index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
var index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
||||||
var pow = Math.pow(1024, index);
|
var pow = Math.pow(1024, index);
|
||||||
@ -15,7 +34,7 @@ function format_bytes(bytes) {
|
|||||||
return formatted.toFixed(2).toString() + units[index];
|
return formatted.toFixed(2).toString() + units[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
function format_time(timestamp) {
|
function format_time(/*int*/ timestamp) {
|
||||||
var date = new Date(timestamp * 1000);
|
var date = new Date(timestamp * 1000);
|
||||||
return date.toLocaleString();
|
return date.toLocaleString();
|
||||||
}
|
}
|
||||||
@ -35,72 +54,71 @@ function format_permissions(/*int*/ mode) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
function set_last_theme_state() {
|
||||||
* Code to change theme
|
var toggle_switch = document.getElementById("toggle-theme");
|
||||||
*/
|
var state = localStorage.getItem("houston-theme-state");
|
||||||
|
var icon = document.getElementById("houston-theme-icon");
|
||||||
const toggleSwitch = document.getElementById("toggle-theme");
|
var logo = document.getElementById("logo-45d");
|
||||||
|
if (state === "light") {
|
||||||
function switchTheme(e) {
|
toggle_switch.checked = false;
|
||||||
if (e.target.checked) {
|
|
||||||
document.documentElement.setAttribute("data-theme", "dark");
|
|
||||||
} else {
|
|
||||||
document.documentElement.setAttribute("data-theme", "light");
|
document.documentElement.setAttribute("data-theme", "light");
|
||||||
}
|
icon.classList.remove("fa-moon");
|
||||||
}
|
icon.classList.add("fa-sun");
|
||||||
|
logo.src = "branding/logo-light.svg";
|
||||||
toggleSwitch.addEventListener("change", switchTheme, false);
|
} else if (state === "dark") {
|
||||||
|
toggle_switch.checked = true;
|
||||||
/* cephfs_dir_stats
|
document.documentElement.setAttribute("data-theme", "dark");
|
||||||
* Receives: path to folder
|
icon.classList.remove("fa-sun");
|
||||||
* Does: Tries command with --json flag at path to folder. If
|
icon.classList.add("fa-moon");
|
||||||
* command fails and gives an error then catch that error
|
logo.src = "branding/logo-dark.svg";
|
||||||
* Returns: JSON object or nothing
|
|
||||||
*/
|
|
||||||
async function cephfs_dir_stats(path) {
|
|
||||||
try {
|
|
||||||
var proc = await cockpit.spawn(["cephfs-dir-stats", "-j", path], {
|
|
||||||
err: "ignore",
|
|
||||||
});
|
|
||||||
return JSON.parse(proc.replace(/\[|\]/g, ""));
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* in_json
|
|
||||||
* Receives: A boolean to see if key is in JSON object
|
|
||||||
* and the JSON objects value if it exists
|
|
||||||
* Does: Checks if JSON key exists; if so, return the
|
|
||||||
* value of the key, if not, return the string "N/A"
|
|
||||||
* Returns: The value of key or "N/A"
|
|
||||||
*/
|
|
||||||
function in_json(is_key, value) {
|
|
||||||
if (is_key) {
|
|
||||||
return value;
|
|
||||||
} else {
|
} else {
|
||||||
return "N/A";
|
toggle_switch.checked = false;
|
||||||
|
state = "light";
|
||||||
|
localStorage.setItem("houston-theme-state", state);
|
||||||
|
logo.src = "branding/logo-light.svg";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function switch_theme(/*event*/ e) {
|
||||||
|
var icon = document.getElementById("houston-theme-icon");
|
||||||
|
var logo = document.getElementById("logo-45d");
|
||||||
|
var state = "";
|
||||||
|
if (e.target.checked) {
|
||||||
|
state = "dark";
|
||||||
|
icon.classList.remove("fa-sun");
|
||||||
|
icon.classList.add("fa-moon");
|
||||||
|
logo.src = "branding/logo-dark.svg";
|
||||||
|
} else {
|
||||||
|
state = "light";
|
||||||
|
icon.classList.remove("fa-moon");
|
||||||
|
icon.classList.add("fa-sun");
|
||||||
|
logo.src = "branding/logo-light.svg";
|
||||||
|
}
|
||||||
|
document.documentElement.setAttribute("data-theme", state);
|
||||||
|
localStorage.setItem("houston-theme-state", state);
|
||||||
|
}
|
||||||
|
|
||||||
class NavEntry {
|
class NavEntry {
|
||||||
constructor(/*string or array*/ path, /*dict*/ stat, /*NavWindow*/ nav_window_ref) {
|
constructor(/*string or array*/ path, /*dict*/ stat, /*NavWindow*/ nav_window_ref) {
|
||||||
this.nav_window_ref = nav_window_ref;
|
this.nav_window_ref = nav_window_ref;
|
||||||
if (typeof path == "string") this.path = path.split("/").splice(1);
|
if (typeof path == "string")
|
||||||
else this.path = path;
|
this.path = path.split("/").splice(1);
|
||||||
|
else
|
||||||
|
this.path = (path.length) ? path : [""];
|
||||||
this.dom_element = document.createElement("div");
|
this.dom_element = document.createElement("div");
|
||||||
this.dom_element.classList.add("nav-item");
|
this.dom_element.classList.add("nav-item");
|
||||||
let icon = (this.dom_element.nav_item_icon = document.createElement("i"));
|
let icon = this.dom_element.nav_item_icon = document.createElement("i");
|
||||||
icon.classList.add("nav-file-icon");
|
icon.classList.add("nav-file-icon");
|
||||||
let title = (this.dom_element.nav_item_title = document.createElement("div"));
|
let title = this.dom_element.nav_item_title = document.createElement("div");
|
||||||
title.classList.add("nav-item-title");
|
title.classList.add("nav-item-title");
|
||||||
title.innerText = this.filename();
|
title.innerText = this.filename();
|
||||||
this.dom_element.appendChild(icon);
|
this.dom_element.appendChild(icon);
|
||||||
this.dom_element.appendChild(title);
|
this.dom_element.appendChild(title);
|
||||||
this.stat = stat;
|
this.stat = stat;
|
||||||
this.dom_element.addEventListener("click", this);
|
this.dom_element.addEventListener("click", this);
|
||||||
|
this.is_hidden_file = this.filename().startsWith('.');
|
||||||
}
|
}
|
||||||
handleEvent(e) {
|
handleEvent(/*event*/ e) {
|
||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
case "click":
|
case "click":
|
||||||
this.show_properties();
|
this.show_properties();
|
||||||
@ -113,16 +131,21 @@ class NavEntry {
|
|||||||
while (this.dom_element.firstChild) {
|
while (this.dom_element.firstChild) {
|
||||||
this.dom_element.removeChild(this.dom_element.firstChild);
|
this.dom_element.removeChild(this.dom_element.firstChild);
|
||||||
}
|
}
|
||||||
if (this.dom_element.parentElement) this.dom_element.parentElement.removeChild(this.dom_element);
|
if (this.dom_element.parentElement)
|
||||||
|
this.dom_element.parentElement.removeChild(this.dom_element);
|
||||||
}
|
}
|
||||||
filename() {
|
filename() {
|
||||||
var name = this.path[this.path.length - 1];
|
var name = this.path[this.path.length - 1];
|
||||||
if (name === "") name = "/";
|
if (name === "")
|
||||||
|
name = "/";
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
path_str() {
|
path_str() {
|
||||||
return "/" + this.path.join("/");
|
return "/" + this.path.join("/");
|
||||||
}
|
}
|
||||||
|
parent_dir() {
|
||||||
|
return this.path.slice(0, this.path.length - 1);
|
||||||
|
}
|
||||||
show() {
|
show() {
|
||||||
document.getElementById("nav-contents-view").appendChild(this.dom_element);
|
document.getElementById("nav-contents-view").appendChild(this.dom_element);
|
||||||
}
|
}
|
||||||
@ -133,20 +156,20 @@ class NavEntry {
|
|||||||
return this.stat["mode"] & 0o777;
|
return this.stat["mode"] & 0o777;
|
||||||
}
|
}
|
||||||
async chmod(/*int*/ new_perms) {
|
async chmod(/*int*/ new_perms) {
|
||||||
var proc = cockpit.spawn(["chmod", (new_perms & 0o777).toString(8), this.path_str()], {
|
var proc = cockpit.spawn(
|
||||||
superuser: "try",
|
["chmod", (new_perms & 0o777).toString(8), this.path_str()],
|
||||||
err: "out",
|
{superuser: "try", err: "out"}
|
||||||
});
|
);
|
||||||
proc.fail((e, data) => {
|
proc.fail((e, data) => {
|
||||||
window.alert(data);
|
window.alert(data);
|
||||||
});
|
});
|
||||||
await proc;
|
await proc;
|
||||||
}
|
}
|
||||||
async chown(/*string*/ new_owner, /*string*/ new_group) {
|
async chown(/*string*/ new_owner, /*string*/ new_group) {
|
||||||
var proc = cockpit.spawn(["chown", [new_owner, new_group].join(":"), this.path_str()], {
|
var proc = cockpit.spawn(
|
||||||
superuser: "try",
|
["chown", [new_owner, new_group].join(":"), this.path_str()],
|
||||||
err: "out",
|
{superuser: "try", err: "out"}
|
||||||
});
|
);
|
||||||
proc.fail((e, data) => {
|
proc.fail((e, data) => {
|
||||||
window.alert(data);
|
window.alert(data);
|
||||||
});
|
});
|
||||||
@ -155,7 +178,7 @@ class NavEntry {
|
|||||||
async mv(/*string*/ new_path) {
|
async mv(/*string*/ new_path) {
|
||||||
var proc = cockpit.spawn(
|
var proc = cockpit.spawn(
|
||||||
["mv", "-n", this.path_str(), [this.nav_window_ref.pwd().path_str(), new_path].join("/")],
|
["mv", "-n", this.path_str(), [this.nav_window_ref.pwd().path_str(), new_path].join("/")],
|
||||||
{ superuser: "try", err: "out" }
|
{superuser: "try", err: "out"}
|
||||||
);
|
);
|
||||||
proc.fail((e, data) => {
|
proc.fail((e, data) => {
|
||||||
window.alert(data);
|
window.alert(data);
|
||||||
@ -181,20 +204,14 @@ class NavEntry {
|
|||||||
populate_edit_fields() {
|
populate_edit_fields() {
|
||||||
document.getElementById("nav-edit-filename").value = this.filename();
|
document.getElementById("nav-edit-filename").value = this.filename();
|
||||||
var mode_bits = [
|
var mode_bits = [
|
||||||
"other-exec",
|
"other-exec", "other-write", "other-read",
|
||||||
"other-write",
|
"group-exec", "group-write", "group-read",
|
||||||
"other-read",
|
"owner-exec", "owner-write", "owner-read"
|
||||||
"group-exec",
|
|
||||||
"group-write",
|
|
||||||
"group-read",
|
|
||||||
"owner-exec",
|
|
||||||
"owner-write",
|
|
||||||
"owner-read",
|
|
||||||
];
|
];
|
||||||
for (let i = 0; i < mode_bits.length; i++) {
|
for (let i = 0; i < mode_bits.length; i++) {
|
||||||
var bit_check = 1 << i;
|
var bit_check = 1 << i;
|
||||||
var result = this.stat["mode"] & bit_check;
|
var result = this.stat["mode"] & bit_check;
|
||||||
document.getElementById(mode_bits[i]).checked = result != 0;
|
document.getElementById(mode_bits[i]).checked = (result != 0);
|
||||||
}
|
}
|
||||||
document.getElementById("nav-edit-owner").value = this.stat["owner"];
|
document.getElementById("nav-edit-owner").value = this.stat["owner"];
|
||||||
document.getElementById("nav-edit-group").value = this.stat["group"];
|
document.getElementById("nav-edit-group").value = this.stat["group"];
|
||||||
@ -206,36 +223,17 @@ class NavFile extends NavEntry {
|
|||||||
super(path, stat, nav_window_ref);
|
super(path, stat, nav_window_ref);
|
||||||
this.nav_type = "file";
|
this.nav_type = "file";
|
||||||
this.dom_element.nav_item_icon.classList.add("fas", "fa-file");
|
this.dom_element.nav_item_icon.classList.add("fas", "fa-file");
|
||||||
}
|
|
||||||
handleEvent(e) {
|
|
||||||
super.handleEvent(e);
|
|
||||||
}
|
|
||||||
async rm() {
|
|
||||||
var proc = cockpit.spawn(["rm", "-f", this.path_str()], { superuser: "try", err: "out" });
|
|
||||||
proc.fail((e, data) => {
|
|
||||||
window.alert(data);
|
|
||||||
});
|
|
||||||
await proc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NavDir extends NavEntry {
|
|
||||||
constructor(/*string or array*/ path, /*dict*/ stat, nav_window_ref) {
|
|
||||||
super(path, stat, nav_window_ref); // super = call parent
|
|
||||||
this.nav_type = "dir";
|
|
||||||
this.dom_element.nav_item_icon.classList.add("fas", "fa-folder");
|
|
||||||
this.double_click = false;
|
this.double_click = false;
|
||||||
this.ceph_stats = [];
|
|
||||||
cephfs_dir_stats(this.path_str()).then((data) => (this.ceph_stats = data));
|
|
||||||
}
|
}
|
||||||
handleEvent(e) {
|
handleEvent(/*event*/ e) {
|
||||||
switch (e.type) {
|
switch(e.type){
|
||||||
case "click":
|
case "click":
|
||||||
if (this.double_click) this.nav_window_ref.cd(this);
|
if(this.double_click)
|
||||||
else {
|
this.show_edit_file_contents();
|
||||||
// single click
|
else{ // single click
|
||||||
this.double_click = true;
|
this.double_click = true;
|
||||||
if (this.timeout) clearTimeout(this.timeout);
|
if(this.timeout)
|
||||||
|
clearTimeout(this.timeout)
|
||||||
this.timeout = setTimeout(() => {
|
this.timeout = setTimeout(() => {
|
||||||
this.double_click = false;
|
this.double_click = false;
|
||||||
}, 500);
|
}, 500);
|
||||||
@ -244,75 +242,171 @@ class NavDir extends NavEntry {
|
|||||||
}
|
}
|
||||||
super.handleEvent(e);
|
super.handleEvent(e);
|
||||||
}
|
}
|
||||||
async get_children(nav_window_ref) {
|
|
||||||
var children = [];
|
|
||||||
var data = await cockpit.spawn(["/usr/share/cockpit/navigator/scripts/ls.py", this.path_str()], {
|
|
||||||
err: "ignore",
|
|
||||||
});
|
|
||||||
var response = JSON.parse(data);
|
|
||||||
this.stat = response["."]["stat"];
|
|
||||||
var entries = response["children"];
|
|
||||||
entries.forEach((entry) => {
|
|
||||||
var filename = entry["filename"];
|
|
||||||
var path = this.path.length >= 1 && this.path[0] ? [...this.path, filename] : [filename];
|
|
||||||
var stat = entry["stat"];
|
|
||||||
if (entry["isdir"]) children.push(new NavDir(path, stat, nav_window_ref));
|
|
||||||
else children.push(new NavFile(path, stat, nav_window_ref));
|
|
||||||
});
|
|
||||||
children.sort((first, second) => {
|
|
||||||
if (first.nav_type === second.nav_type) {
|
|
||||||
return first.filename().localeCompare(second.filename());
|
|
||||||
}
|
|
||||||
if (first.nav_type === "dir") return -1;
|
|
||||||
return 1;
|
|
||||||
});
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
async rm() {
|
async rm() {
|
||||||
var proc = cockpit.spawn(["rmdir", this.path_str()], { superuser: "try", err: "out" });
|
var proc = cockpit.spawn(
|
||||||
|
["rm", "-f", this.path_str()],
|
||||||
|
{superuser: "try", err: "out"}
|
||||||
|
);
|
||||||
proc.fail((e, data) => {
|
proc.fail((e, data) => {
|
||||||
window.alert(data);
|
window.alert(data);
|
||||||
});
|
});
|
||||||
await proc;
|
await proc;
|
||||||
}
|
}
|
||||||
show_properties() {
|
async show_edit_file_contents() {
|
||||||
var extra_properties = "";
|
for (let button of document.getElementsByTagName("button")) {
|
||||||
|
if (!button.classList.contains("editor-btn"))
|
||||||
|
button.disabled = true;
|
||||||
|
}
|
||||||
|
document.getElementById("pwd").disabled = true;
|
||||||
|
var proc_output = await cockpit.spawn(["file", "--mime-type", this.path_str()], {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", this.path_str()], {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 new_contents = document.getElementById("nav-edit-contents-textarea").value;
|
||||||
|
await cockpit.script("echo -n \"$1\" > $2", [new_contents, this.path_str()], {superuser: "try"});
|
||||||
|
this.nav_window_ref.refresh();
|
||||||
|
this.hide_edit_file_contents();
|
||||||
|
}
|
||||||
|
hide_edit_file_contents() {
|
||||||
|
document.getElementById("nav-edit-contents-view").style.display = "none";
|
||||||
|
document.getElementById("nav-contents-view").style.display = "flex";
|
||||||
|
for (let button of document.getElementsByTagName("button")) {
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
document.getElementById("pwd").disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NavDir extends NavEntry {
|
||||||
|
constructor(/*string or array*/ path, /*dict*/ stat, nav_window_ref) {
|
||||||
|
super(path, stat, nav_window_ref);
|
||||||
|
this.nav_type = "dir";
|
||||||
|
this.dom_element.nav_item_icon.classList.add("fas", "fa-folder");
|
||||||
|
this.double_click = false;
|
||||||
|
}
|
||||||
|
handleEvent(/*event*/ e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case "click":
|
||||||
|
if (this.double_click)
|
||||||
|
this.nav_window_ref.cd(this);
|
||||||
|
else {
|
||||||
|
// single click
|
||||||
|
this.double_click = true;
|
||||||
|
if (this.timeout)
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.double_click = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
super.handleEvent(e);
|
||||||
|
}
|
||||||
|
async get_children(/*NavWindow*/ nav_window_ref, /*boolean*/ no_alert = false) {
|
||||||
|
var children = [];
|
||||||
|
var proc = cockpit.spawn(
|
||||||
|
["/usr/share/cockpit/navigator/scripts/ls.py", this.path_str()],
|
||||||
|
{err:"out", superuser: "try"}
|
||||||
|
);
|
||||||
|
proc.fail((e, data) => {
|
||||||
|
if(!no_alert)
|
||||||
|
window.alert(data);
|
||||||
|
});
|
||||||
|
var data = await proc;
|
||||||
|
var response = JSON.parse(data);
|
||||||
|
this.stat = response["."]["stat"];
|
||||||
|
var entries = response["children"];
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
var filename = entry["filename"];
|
||||||
|
var path = (this.path.length >= 1 && this.path[0]) ? [...this.path, filename] : [filename];
|
||||||
|
var stat = entry["stat"];
|
||||||
|
if (entry["isdir"])
|
||||||
|
children.push(new NavDir(path, stat, nav_window_ref));
|
||||||
|
else
|
||||||
|
children.push(new NavFile(path, stat, nav_window_ref));
|
||||||
|
});
|
||||||
|
children.sort((first, second) => {
|
||||||
|
if (first.nav_type === second.nav_type) {
|
||||||
|
return first.filename().localeCompare(second.filename());
|
||||||
|
}
|
||||||
|
if (first.nav_type === "dir")
|
||||||
|
return -1;
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
async rm() {
|
||||||
|
var proc = cockpit.spawn(
|
||||||
|
["rmdir", this.path_str()],
|
||||||
|
{superuser: "try", err: "out"}
|
||||||
|
);
|
||||||
|
proc.fail((e, data) => {
|
||||||
|
window.alert(data);
|
||||||
|
});
|
||||||
|
await proc;
|
||||||
|
}
|
||||||
|
async cephfs_dir_stats() {
|
||||||
|
try {
|
||||||
|
var proc = await cockpit.spawn(
|
||||||
|
["cephfs-dir-stats", "-j", this.path_str()],
|
||||||
|
{err: "ignore"}
|
||||||
|
);
|
||||||
|
return JSON.parse(proc)[0];
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async show_properties() {
|
||||||
|
var extra_properties = "";
|
||||||
|
if(!this.hasOwnProperty("ceph_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
|
||||||
if (this.ceph_stats.length !== 0) {
|
if (this.ceph_stats !== null) {
|
||||||
extra_properties +=
|
extra_properties +=
|
||||||
'<div class="vertical-spacer"></div><h2 class="nav-info-column-filename">Ceph Status</h2>';
|
'<div class="vertical-spacer"></div><h2 class="nav-info-column-filename">Ceph Status</h2>';
|
||||||
extra_properties += property_entry_html(
|
extra_properties += property_entry_html(
|
||||||
"Files",
|
"Files",
|
||||||
in_json(this.ceph_stats.hasOwnProperty("files"), this.ceph_stats.files)
|
this.ceph_stats.hasOwnProperty("files") ? this.ceph_stats.files : "N/A"
|
||||||
);
|
);
|
||||||
extra_properties += property_entry_html(
|
extra_properties += property_entry_html(
|
||||||
"Directories",
|
"Directories",
|
||||||
in_json(this.ceph_stats.hasOwnProperty("subdirs"), this.ceph_stats.subdirs)
|
this.ceph_stats.hasOwnProperty("subdirs") ? this.ceph_stats.subdirs : "N/A"
|
||||||
);
|
);
|
||||||
extra_properties += property_entry_html(
|
extra_properties += property_entry_html(
|
||||||
"Recursive files",
|
"Recursive files",
|
||||||
in_json(this.ceph_stats.hasOwnProperty("rfiles"), this.ceph_stats.rfiles)
|
this.ceph_stats.hasOwnProperty("rfiles") ? this.ceph_stats.rfiles : "N/A"
|
||||||
);
|
);
|
||||||
extra_properties += property_entry_html(
|
extra_properties += property_entry_html(
|
||||||
"Recursive directories",
|
"Recursive directories",
|
||||||
in_json(this.ceph_stats.hasOwnProperty("rsubdirs"), this.ceph_stats.rsubdirs)
|
this.ceph_stats.hasOwnProperty("rsubdirs") ? this.ceph_stats.rsubdirs : "N/A"
|
||||||
);
|
);
|
||||||
extra_properties += property_entry_html(
|
extra_properties += property_entry_html(
|
||||||
"Total size",
|
"Total size",
|
||||||
in_json(this.ceph_stats.hasOwnProperty("rbytes"), this.ceph_stats.rbytes)
|
this.ceph_stats.hasOwnProperty("rbytes") ? this.ceph_stats.rbytes : "N/A"
|
||||||
);
|
);
|
||||||
extra_properties += property_entry_html(
|
extra_properties += property_entry_html(
|
||||||
"Layout pool",
|
"Layout pool",
|
||||||
in_json(this.ceph_stats.hasOwnProperty("layout.pool"), this.ceph_stats["layout.pool"])
|
this.ceph_stats.hasOwnProperty("layout.pool") ? this.ceph_stats["layout.pool"] : "N/A"
|
||||||
);
|
);
|
||||||
extra_properties += property_entry_html(
|
extra_properties += property_entry_html(
|
||||||
"Max files",
|
"Max files",
|
||||||
in_json(this.ceph_stats.hasOwnProperty("max_files"), this.ceph_stats.max_files)
|
this.ceph_stats.hasOwnProperty("max_files") ? this.ceph_stats.max_files : "N/A"
|
||||||
);
|
);
|
||||||
extra_properties += property_entry_html(
|
extra_properties += property_entry_html(
|
||||||
"Max bytes",
|
"Max bytes",
|
||||||
in_json(this.ceph_stats.hasOwnProperty("max_bytes"), this.ceph_stats.max_bytes)
|
this.ceph_stats.hasOwnProperty("max_bytes") ? this.ceph_stats.max_bytes : "N/A"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
super.show_properties(extra_properties);
|
super.show_properties(extra_properties);
|
||||||
@ -322,6 +416,7 @@ class NavDir extends NavEntry {
|
|||||||
class NavWindow {
|
class NavWindow {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.path_stack = [new NavDir("/", this)];
|
this.path_stack = [new NavDir("/", this)];
|
||||||
|
this.path_stack_index = this.path_stack.length - 1;
|
||||||
this.selected_entry = this.pwd();
|
this.selected_entry = this.pwd();
|
||||||
this.entries = [];
|
this.entries = [];
|
||||||
this.window = document.getElementById("nav-contents-view");
|
this.window = document.getElementById("nav-contents-view");
|
||||||
@ -336,34 +431,54 @@ class NavWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async refresh() {
|
async refresh() {
|
||||||
|
var num_dirs = 0;
|
||||||
|
var num_files = 0;
|
||||||
|
var show_hidden = document.getElementById("nav-show-hidden").checked;
|
||||||
|
this.start_load();
|
||||||
var files = await this.pwd().get_children(this);
|
var files = await this.pwd().get_children(this);
|
||||||
while (this.entries.length) {
|
while (this.entries.length) {
|
||||||
var entry = this.entries.pop();
|
var entry = this.entries.pop();
|
||||||
entry.destroy();
|
entry.destroy();
|
||||||
}
|
}
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
file.show();
|
if (file.nav_type === "dir")
|
||||||
|
num_dirs++;
|
||||||
|
else
|
||||||
|
num_files++;
|
||||||
|
if(!file.is_hidden_file || show_hidden)
|
||||||
|
file.show();
|
||||||
this.entries.push(file);
|
this.entries.push(file);
|
||||||
});
|
});
|
||||||
document.getElementById("pwd").innerText = this.pwd().path_str();
|
document.getElementById("pwd").value = this.pwd().path_str();
|
||||||
this.set_selected(this.pwd());
|
this.set_selected(this.pwd());
|
||||||
this.show_selected_properties();
|
this.show_selected_properties();
|
||||||
|
document.getElementById("nav-num-dirs").innerText = num_dirs.toString();
|
||||||
|
document.getElementById("nav-num-files").innerText = num_files.toString();
|
||||||
|
this.stop_load();
|
||||||
}
|
}
|
||||||
pwd() {
|
pwd() {
|
||||||
return this.path_stack[this.path_stack.length - 1];
|
return this.path_stack[this.path_stack_index];
|
||||||
}
|
}
|
||||||
cd(new_dir) {
|
cd(/*NavDir*/ new_dir) {
|
||||||
|
this.path_stack.length = this.path_stack_index + 1;
|
||||||
this.path_stack.push(new_dir);
|
this.path_stack.push(new_dir);
|
||||||
|
this.path_stack_index = this.path_stack.length - 1;
|
||||||
this.refresh().catch(() => {
|
this.refresh().catch(() => {
|
||||||
this.path_stack.pop();
|
this.back();
|
||||||
this.refresh();
|
|
||||||
window.alert(new_dir.path_str() + " is inaccessible.");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
up() {
|
back() {
|
||||||
if (this.path_stack.length > 1) this.path_stack.pop();
|
this.path_stack_index = Math.max(this.path_stack_index - 1, 0);
|
||||||
this.refresh();
|
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()));
|
||||||
|
}
|
||||||
show_selected_properties() {
|
show_selected_properties() {
|
||||||
this.selected_entry.show_properties();
|
this.selected_entry.show_properties();
|
||||||
}
|
}
|
||||||
@ -401,8 +516,8 @@ class NavWindow {
|
|||||||
if (
|
if (
|
||||||
!window.confirm(
|
!window.confirm(
|
||||||
"Warning: editing `" +
|
"Warning: editing `" +
|
||||||
this.selected_entry.path_str() +
|
this.selected_entry.path_str() +
|
||||||
"` can be dangerous. Are you sure?"
|
"` can be dangerous. Are you sure?"
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@ -424,7 +539,8 @@ class NavWindow {
|
|||||||
var bit = 0;
|
var bit = 0;
|
||||||
for (let category of category_list) {
|
for (let category of category_list) {
|
||||||
for (let action of action_list) {
|
for (let action of action_list) {
|
||||||
if (document.getElementById(category + "-" + action).checked) result |= 1 << bit;
|
if (document.getElementById(category + "-" + action).checked)
|
||||||
|
result |= 1 << bit;
|
||||||
bit++;
|
bit++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -470,15 +586,16 @@ class NavWindow {
|
|||||||
}
|
}
|
||||||
async mkdir() {
|
async mkdir() {
|
||||||
var new_dir_name = window.prompt("Directory Name: ");
|
var new_dir_name = window.prompt("Directory Name: ");
|
||||||
if (new_dir_name === null) return;
|
if (new_dir_name === null)
|
||||||
|
return;
|
||||||
if (new_dir_name.includes("/")) {
|
if (new_dir_name.includes("/")) {
|
||||||
window.alert("Directory name can't contain `/`.");
|
window.alert("Directory name can't contain `/`.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var proc = cockpit.spawn(["mkdir", this.pwd().path_str() + "/" + new_dir_name], {
|
var proc = cockpit.spawn(
|
||||||
superuser: "try",
|
["mkdir", this.pwd().path_str() + "/" + new_dir_name],
|
||||||
err: "out",
|
{superuser: "try", err: "out"}
|
||||||
});
|
);
|
||||||
proc.fail((e, data) => {
|
proc.fail((e, data) => {
|
||||||
window.alert(data);
|
window.alert(data);
|
||||||
});
|
});
|
||||||
@ -487,49 +604,113 @@ class NavWindow {
|
|||||||
}
|
}
|
||||||
async touch() {
|
async touch() {
|
||||||
var new_file_name = window.prompt("File Name: ");
|
var new_file_name = window.prompt("File Name: ");
|
||||||
if (new_file_name === null) return;
|
if (new_file_name === null)
|
||||||
|
return;
|
||||||
if (new_file_name.includes("/")) {
|
if (new_file_name.includes("/")) {
|
||||||
window.alert("File name can't contain `/`.");
|
window.alert("File name can't contain `/`.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var proc = cockpit.spawn(["touch", this.pwd().path_str() + "/" + new_file_name], {
|
var proc = cockpit.spawn(
|
||||||
superuser: "try",
|
["touch", this.pwd().path_str() + "/" + new_file_name],
|
||||||
err: "out",
|
{superuser: "try", err: "out"}
|
||||||
});
|
);
|
||||||
proc.fail((e, data) => {
|
proc.fail((e, data) => {
|
||||||
window.alert(data);
|
window.alert(data);
|
||||||
});
|
});
|
||||||
await proc;
|
await proc;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
nav_bar_event_handler(/*event*/ e) {
|
||||||
|
switch(e.key){
|
||||||
|
case 'Enter':
|
||||||
|
this.nav_bar_cd();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nav_bar_cd() {
|
||||||
|
var new_path = document.getElementById("pwd").value;
|
||||||
|
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);
|
||||||
|
console.log(parent_dir.path_str());
|
||||||
|
var error = false;
|
||||||
|
var objs = await parent_dir.get_children(this, true).catch(() => {error = true});
|
||||||
|
if(error)
|
||||||
|
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").hidden = false;
|
||||||
|
var buttons = document.getElementsByTagName("button");
|
||||||
|
for (let button of buttons) {
|
||||||
|
button.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stop_load() {
|
||||||
|
document.getElementById("nav-loader-container").hidden = true;
|
||||||
|
var buttons = document.getElementsByTagName("button");
|
||||||
|
for (let button of buttons) {
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toggle_show_hidden(e) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let nav_window = new NavWindow();
|
let nav_window = new NavWindow();
|
||||||
|
|
||||||
function set_up_buttons() {
|
function set_up_buttons() {
|
||||||
|
document.getElementById("nav-back-btn").addEventListener("click", nav_window.back.bind(nav_window));
|
||||||
|
document.getElementById("nav-forward-btn").addEventListener("click", nav_window.forward.bind(nav_window));
|
||||||
document.getElementById("nav-up-dir-btn").addEventListener("click", nav_window.up.bind(nav_window));
|
document.getElementById("nav-up-dir-btn").addEventListener("click", nav_window.up.bind(nav_window));
|
||||||
document.getElementById("nav-refresh-btn").addEventListener("click", nav_window.refresh.bind(nav_window));
|
document.getElementById("nav-refresh-btn").addEventListener("click", nav_window.refresh.bind(nav_window));
|
||||||
document.getElementById("nav-mkdir-btn").addEventListener("click", nav_window.mkdir.bind(nav_window));
|
document.getElementById("nav-mkdir-btn").addEventListener("click", nav_window.mkdir.bind(nav_window));
|
||||||
document.getElementById("nav-touch-btn").addEventListener("click", nav_window.touch.bind(nav_window));
|
document.getElementById("nav-touch-btn").addEventListener("click", nav_window.touch.bind(nav_window));
|
||||||
document
|
document.getElementById("nav-delete-btn").addEventListener("click", nav_window.delete_selected.bind(nav_window));
|
||||||
.getElementById("nav-delete-btn")
|
document.getElementById("nav-edit-properties-btn").addEventListener("click", nav_window.show_edit_selected.bind(nav_window));
|
||||||
.addEventListener("click", nav_window.delete_selected.bind(nav_window));
|
document.getElementById("nav-cancel-edit-btn").addEventListener("click", nav_window.hide_edit_selected.bind(nav_window));
|
||||||
document
|
document.getElementById("nav-apply-edit-btn").addEventListener("click", nav_window.apply_edit_selected.bind(nav_window));
|
||||||
.getElementById("nav-edit-properties-btn")
|
|
||||||
.addEventListener("click", nav_window.show_edit_selected.bind(nav_window));
|
|
||||||
document
|
|
||||||
.getElementById("nav-cancel-edit-btn")
|
|
||||||
.addEventListener("click", nav_window.hide_edit_selected.bind(nav_window));
|
|
||||||
document
|
|
||||||
.getElementById("nav-apply-edit-btn")
|
|
||||||
.addEventListener("click", nav_window.apply_edit_selected.bind(nav_window));
|
|
||||||
var mode_checkboxes = document.getElementsByClassName("mode-checkbox");
|
var mode_checkboxes = document.getElementsByClassName("mode-checkbox");
|
||||||
for (let checkbox of mode_checkboxes) {
|
for (let checkbox of mode_checkboxes) {
|
||||||
checkbox.addEventListener("change", nav_window.update_permissions_preview.bind(nav_window));
|
checkbox.addEventListener("change", nav_window.update_permissions_preview.bind(nav_window));
|
||||||
}
|
}
|
||||||
|
document.getElementById("pwd").addEventListener("input", nav_window.nav_bar_update_choices.bind(nav_window), false);
|
||||||
|
document.getElementById("pwd").addEventListener("focus", nav_window.nav_bar_update_choices.bind(nav_window), false);
|
||||||
|
document.getElementById("pwd").addEventListener("keydown", nav_window.nav_bar_event_handler.bind(nav_window));
|
||||||
|
document.getElementById("toggle-theme").addEventListener("change", switch_theme, false);
|
||||||
|
document.getElementById("nav-show-hidden").addEventListener("change", nav_window.toggle_show_hidden.bind(nav_window));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
set_last_theme_state();
|
||||||
await nav_window.refresh();
|
await nav_window.refresh();
|
||||||
set_up_buttons();
|
set_up_buttons();
|
||||||
}
|
}
|
||||||
|
139
navigator/scripts/cephfs-dir-stats
Executable file
139
navigator/scripts/cephfs-dir-stats
Executable file
@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import math
|
||||||
|
import json
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Name: dir_attributes
|
||||||
|
# Args: path to directory, command to run
|
||||||
|
# Desc: executes getfattr to retrive ceph dir attribute and returns as dictionary
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def dir_attributes(path, type, command):
|
||||||
|
attrs = {}
|
||||||
|
try:
|
||||||
|
child = subprocess.Popen(
|
||||||
|
["getfattr", "-n", "ceph." + type + "." + command, path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
|
||||||
|
output_string, err = child.communicate()
|
||||||
|
except OSError:
|
||||||
|
print("Error executing getfattr. Is xattr installed?")
|
||||||
|
sys.exit(1)
|
||||||
|
if child.wait() != 0:
|
||||||
|
if err.find("No such attribute") != -1:
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
print("Error executing getfattr. Is xattr installed?")
|
||||||
|
sys.exit(1)
|
||||||
|
# return {}
|
||||||
|
fields = re.findall(
|
||||||
|
r"^ceph\.[dir|quota]+\.([^=]+)=\"([^\"]+)\"$", output_string, re.MULTILINE)
|
||||||
|
if len(fields) == 0:
|
||||||
|
print(f'No ceph xattrs, is {path} in a ceph filesystem?')
|
||||||
|
sys.exit(1)
|
||||||
|
attrs[fields[0][0]] = fields[0][1]
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Name: run_dir_commands
|
||||||
|
# Args: path to directory
|
||||||
|
# Desc: calls dir_attributes and quota_attrivutes multiple times each with a different command
|
||||||
|
# Retu: Returns the outputs of all commands in a dictionary
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def run_dir_commands(path):
|
||||||
|
outputs = {'path':path}
|
||||||
|
dirList = ["entries", "files", "rbytes", "rentries",
|
||||||
|
"rfiles", "rsubdirs", "subdirs", "layout.pool"]
|
||||||
|
quotaList = ["max_files", "max_bytes"]
|
||||||
|
for items in dirList:
|
||||||
|
outputs.update(dir_attributes(path, "dir", items))
|
||||||
|
for items in quotaList:
|
||||||
|
outputs.update(dir_attributes(path, "quota", items))
|
||||||
|
|
||||||
|
if "rbytes" in outputs.keys():
|
||||||
|
outputs["rbytes"] = format_bytes(int(outputs["rbytes"]))
|
||||||
|
if "max_bytes" in outputs.keys():
|
||||||
|
outputs["max_bytes"] = format_bytes(int(outputs["max_bytes"]))
|
||||||
|
|
||||||
|
return outputs
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Name: display_attributes
|
||||||
|
# Args: path to directory
|
||||||
|
# Desc: calls run_dir_commands and prints output
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def display_attributes(path):
|
||||||
|
attrs = run_dir_commands(path)
|
||||||
|
|
||||||
|
print(path, ":")
|
||||||
|
max_width = len(
|
||||||
|
max(attrs.values(), key=lambda x: len(x.split(" ")[0])).split(" ")[0])
|
||||||
|
print("Files: ", "{0:>{1}}".format(
|
||||||
|
attrs["files"], max_width) if "files" in attrs.keys() else "N/A")
|
||||||
|
print("Directories: ", "{0:>{1}}".format(
|
||||||
|
attrs["subdirs"], max_width) if "subdirs" in attrs.keys() else "N/A")
|
||||||
|
print("Recursive Files: ", "{0:>{1}}".format(
|
||||||
|
attrs["rfiles"], max_width) if "rfiles" in attrs.keys() else "N/A")
|
||||||
|
print("Recursive Directories: ", "{0:>{1}}".format(
|
||||||
|
attrs["rsubdirs"], max_width) if "rsubdirs" in attrs.keys() else "N/A")
|
||||||
|
print("Total Size: ", "{0:>{1}}".format(attrs["rbytes"].split(" ")[
|
||||||
|
0], max_width) + " " + attrs["rbytes"].split(" ")[1] if "rbytes" in attrs.keys() else "N/A")
|
||||||
|
print("Layout Pool: ", "{0:>{1}}".format(
|
||||||
|
attrs["layout.pool"], max_width) if "layout.pool" in attrs.keys() else "N/A")
|
||||||
|
print("Max Files: ", "{0:>{1}}".format(
|
||||||
|
attrs["max_files"], max_width) if "max_files" in attrs.keys() else " N/A")
|
||||||
|
print("Max Bytes: ", "{0:>{1}}".format(
|
||||||
|
attrs["max_bytes"], max_width) if "max_bytes" in attrs.keys() else " N/A")
|
||||||
|
print()
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Name: format_bytes
|
||||||
|
# Args: integer value in bytes
|
||||||
|
# Desc: formats size_bytes in SI base units and returns as string
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def format_bytes(size_bytes):
|
||||||
|
if size_bytes == 0:
|
||||||
|
return "0 B"
|
||||||
|
size_name = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
|
||||||
|
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||||
|
p = math.pow(1024, i)
|
||||||
|
s = round(size_bytes / p, 2)
|
||||||
|
return "%s %s" % (s, size_name[i])
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Name: main (cephfs-dir-stats)
|
||||||
|
# Args: (see parser)
|
||||||
|
# Desc: lists recursive ceph stats of specified directory
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = OptionParser()
|
||||||
|
parser.add_option("-j", "--json", help="output stats in JSON format.", action="store_true", dest="json", default=False)
|
||||||
|
(options, args) = parser.parse_args()
|
||||||
|
if len(args) == 0:
|
||||||
|
args = ["."]
|
||||||
|
if(options.json):
|
||||||
|
obj = []
|
||||||
|
for arg in args:
|
||||||
|
obj.append(run_dir_commands(arg))
|
||||||
|
obj = json.dumps(obj)
|
||||||
|
print(obj)
|
||||||
|
else:
|
||||||
|
for arg in args:
|
||||||
|
display_attributes(arg)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -1,5 +1,22 @@
|
|||||||
#!/usr/bin/env python3
|
#!/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/>.
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from stat import S_ISDIR, filemode
|
from stat import S_ISDIR, filemode
|
||||||
import json
|
import json
|
||||||
@ -14,6 +31,16 @@ 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
|
||||||
|
owner = '?'
|
||||||
|
try:
|
||||||
|
owner = getpwuid(stats.st_uid).pw_name
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
group = '?'
|
||||||
|
try:
|
||||||
|
group = getgrgid(stats.st_gid).gr_name
|
||||||
|
except:
|
||||||
|
pass
|
||||||
response = {
|
response = {
|
||||||
"filename": filename,
|
"filename": filename,
|
||||||
"isdir": isdir,
|
"isdir": isdir,
|
||||||
@ -21,9 +48,9 @@ def get_stat(full_path, filename = '/'):
|
|||||||
"mode": stats.st_mode,
|
"mode": stats.st_mode,
|
||||||
"mode-str": filemode(stats.st_mode),
|
"mode-str": filemode(stats.st_mode),
|
||||||
"uid": stats.st_uid,
|
"uid": stats.st_uid,
|
||||||
"owner": getpwuid(stats.st_uid).pw_name,
|
"owner": owner,
|
||||||
"gid": stats.st_gid,
|
"gid": stats.st_gid,
|
||||||
"group": getgrgid(stats.st_gid).gr_name,
|
"group": group,
|
||||||
"size": stats.st_size,
|
"size": stats.st_size,
|
||||||
"atime": stats.st_atime,
|
"atime": stats.st_atime,
|
||||||
"mtime": stats.st_mtime,
|
"mtime": stats.st_mtime,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user