Browse Source

checkpoint

webui
Antonio SJ Musumeci 1 week ago
parent
commit
5d973fd3e4
  1. 593
      index.html

593
index.html

@ -21,171 +21,171 @@
cursor: pointer; cursor: pointer;
padding: 14px 16px; padding: 14px 16px;
transition: 0.3s; transition: 0.3s;
color: #e0e0e0;
color: #e0e0e0;
} }
.tab button:hover { .tab button:hover {
background-color: #444;
background-color: #444;
} }
.tab button.active { .tab button.active {
background-color: #3d3d3d;
background-color: #3d3d3d;
} }
.tabcontent { .tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #444;
border-top: none;
background-color: #1e1e1e;
display: none;
padding: 6px 12px;
border: 1px solid #444;
border-top: none;
background-color: #1e1e1e;
} }
table { table {
border-collapse: collapse;
width: 100%;
border-collapse: collapse;
width: 100%;
} }
th, td { th, td {
border: 1px solid #444;
padding: 8px;
text-align: left;
border: 1px solid #444;
padding: 8px;
text-align: left;
} }
th { th {
background-color: #2d2d2d;
color: #e0e0e0;
background-color: #2d2d2d;
color: #e0e0e0;
} }
tr:nth-child(even) { tr:nth-child(even) {
background-color: #252525;
background-color: #252525;
} }
tr:hover { tr:hover {
background-color: #333;
background-color: #333;
} }
input[type="text"] { input[type="text"] {
background-color: #333;
color: #e0e0e0;
border: 1px solid #555;
padding: 5px;
background-color: #333;
color: #e0e0e0;
border: 1px solid #555;
padding: 5px;
} }
input[type="text"]:focus { input[type="text"]:focus {
outline: none;
border-color: #666;
outline: none;
border-color: #666;
} }
select { select {
background-color: #333;
color: #e0e0e0;
border: 1px solid #555;
padding: 5px;
background-color: #333;
color: #e0e0e0;
border: 1px solid #555;
padding: 5px;
} }
select:focus { select:focus {
outline: none;
border-color: #666;
outline: none;
border-color: #666;
} }
label { label {
color: #e0e0e0;
color: #e0e0e0;
} }
.modal { .modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.7);
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.7);
} }
.modal-content { .modal-content {
background-color: #2d2d2d;
margin: 15% auto;
padding: 20px;
border: 1px solid #555;
width: 80%;
max-width: 500px;
color: #e0e0e0;
background-color: #2d2d2d;
margin: 15% auto;
padding: 20px;
border: 1px solid #555;
width: 80%;
max-width: 500px;
color: #e0e0e0;
} }
.close-modal { .close-modal {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
} }
.close-modal:hover, .close-modal:hover,
.close-modal:focus { .close-modal:focus {
color: #fff;
text-decoration: none;
cursor: pointer;
color: #fff;
text-decoration: none;
cursor: pointer;
} }
.branch-entry { .branch-entry {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #444;
background-color: #252525;
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #444;
background-color: #252525;
} }
.branch-entry input, .branch-entry input,
.branch-entry select { .branch-entry select {
padding: 5px;
background-color: #333;
color: #e0e0e0;
border: 1px solid #555;
padding: 5px;
background-color: #333;
color: #e0e0e0;
border: 1px solid #555;
} }
.branch-entry input:focus, .branch-entry input:focus,
.branch-entry select:focus { .branch-entry select:focus {
outline: none;
border-color: #666;
outline: none;
border-color: #666;
} }
.branch-path { .branch-path {
flex: 1;
min-width: 200px;
flex: 1;
min-width: 200px;
} }
.branch-mode { .branch-mode {
width: 80px;
width: 80px;
} }
.branch-minfreespace { .branch-minfreespace {
width: 100px;
width: 100px;
} }
.branch-remove { .branch-remove {
background-color: #8b0000;
color: white;
border: none;
padding: 5px 10px;
cursor: pointer;
background-color: #8b0000;
color: white;
border: none;
padding: 5px 10px;
cursor: pointer;
} }
.branch-remove:hover { .branch-remove:hover {
background-color: #a00000;
background-color: #a00000;
} }
.add-branch-btn { .add-branch-btn {
background-color: #2e7d32;
color: white;
border: none;
padding: 10px 20px;
cursor: pointer;
margin-bottom: 15px;
background-color: #2e7d32;
color: white;
border: none;
padding: 10px 20px;
cursor: pointer;
margin-bottom: 15px;
} }
.add-branch-btn:hover { .add-branch-btn:hover {
background-color: #388e3c;
background-color: #388e3c;
} }
.submit-branches-btn { .submit-branches-btn {
background-color: #1565c0;
color: white;
border: none;
padding: 10px 20px;
cursor: pointer;
margin-bottom: 15px;
margin-left: 10px;
background-color: #1565c0;
color: white;
border: none;
padding: 10px 20px;
cursor: pointer;
margin-bottom: 15px;
margin-left: 10px;
} }
.submit-branches-btn:hover { .submit-branches-btn:hover {
background-color: #1976d2;
background-color: #1976d2;
} }
.mount-list-item { .mount-list-item {
padding: 10px;
cursor: pointer;
border-bottom: 1px solid #444;
color: #e0e0e0;
padding: 10px;
cursor: pointer;
border-bottom: 1px solid #444;
color: #e0e0e0;
} }
.mount-list-item:hover { .mount-list-item:hover {
background-color: #444;
background-color: #444;
} }
h3 { h3 {
color: #e0e0e0;
color: #e0e0e0;
} }
</style> </style>
</head> </head>
@ -220,6 +220,7 @@
</div> </div>
</div> </div>
<script> <script>
let g_mounts = [];
function openTab(evt, tabName) { function openTab(evt, tabName) {
var i, tabcontent, tablinks; var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent"); tabcontent = document.getElementsByClassName("tabcontent");
@ -233,217 +234,209 @@
document.getElementById(tabName).style.display = "block"; document.getElementById(tabName).style.display = "block";
if (evt) evt.currentTarget.className += " active"; if (evt) evt.currentTarget.className += " active";
} }
function loadKV(mount) {
let url = '/kvs';
if (mount) {
url += '?mount=' + encodeURIComponent(mount);
function populateMountSelect(selectId, onchangeFunc) {
const select = document.getElementById(selectId);
select.innerHTML = '';
g_mounts.forEach(m => {
const opt = document.createElement('option');
opt.value = m;
opt.text = m;
select.appendChild(opt);
});
select.onchange = onchangeFunc;
if (g_mounts.length > 0) {
select.value = g_mounts[0];
} }
fetch(url)
}
function getMounts() {
return g_mounts;
}
function loadMounts() {
fetch('/mounts')
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
const div = document.getElementById('kv-list');
div.innerHTML = '';
const table = document.createElement('table');
const headerRow = document.createElement('tr');
const headerKey = document.createElement('th');
headerKey.textContent = 'Key';
const headerValue = document.createElement('th');
headerValue.textContent = 'Value';
headerRow.appendChild(headerKey);
headerRow.appendChild(headerValue);
table.appendChild(headerRow);
const priorityKeys = ['fsname','version', 'branches'];
const orderedEntries = [];
priorityKeys.forEach(key => {
if (data.hasOwnProperty(key)) {
orderedEntries.push([key, data[key]]);
delete data[key];
}
g_mounts = data;
populateMountSelect('mount-select-advanced', function() {
loadAllForMount(this.value);
}); });
for (const [k, v] of Object.entries(data)) {
orderedEntries.push([k, v]);
populateMountSelect('mount-select-branches', function() {
loadAllForMount(this.value);
});
if (g_mounts.length > 0) {
loadAllForMount(g_mounts[0]);
} }
for (const [k, v] of orderedEntries) {
const row = document.createElement('tr');
const keyCell = document.createElement('td');
keyCell.textContent = k;
const valueCell = document.createElement('td');
const input = document.createElement('input');
input.type = 'text';
input.value = v;
input.style.width = '100%';
input.onkeydown = function(e) {
if (e.key === 'Enter') {
url = '/kvs?mount=' + encodeURIComponent(mount)
fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({[k]: input.value})
}).then(response => {
if (!response.ok) {
response.text().then(body => {
alert('Status: ' + response.status + '\nBody: ' + body);
});
}
});
}
function loadAllForMount(mount) {
let url = '/kvs?mount=' + encodeURIComponent(mount);
fetch(url)
.then(r => r.json())
.then(data => {
renderKV(data, mount);
renderBranches(data);
});
}
function renderKV(data, mount) {
const div = document.getElementById('kv-list');
div.innerHTML = '';
const table = document.createElement('table');
const headerRow = document.createElement('tr');
const headerKey = document.createElement('th');
headerKey.textContent = 'Key';
const headerValue = document.createElement('th');
headerValue.textContent = 'Value';
headerRow.appendChild(headerKey);
headerRow.appendChild(headerValue);
table.appendChild(headerRow);
const priorityKeys = ['fsname','version', 'branches'];
const orderedEntries = [];
priorityKeys.forEach(key => {
if (data.hasOwnProperty(key)) {
orderedEntries.push([key, data[key]]);
delete data[key];
}
});
for (const [k, v] of Object.entries(data)) {
orderedEntries.push([k, v]);
}
for (const [k, v] of orderedEntries) {
const row = document.createElement('tr');
const keyCell = document.createElement('td');
keyCell.textContent = k;
const valueCell = document.createElement('td');
const input = document.createElement('input');
input.type = 'text';
input.value = v;
input.style.width = '100%';
input.onkeydown = function(e) {
if (e.key === 'Enter') {
const postUrl = '/kvs?mount=' + encodeURIComponent(mount);
fetch(postUrl, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({[k]: input.value})
}).then(response => {
if (!response.ok) {
response.text().then(body => {
alert('Status: ' + response.status + '\nBody: ' + body);
}); });
} }
};
valueCell.appendChild(input);
row.appendChild(keyCell);
row.appendChild(valueCell);
table.appendChild(row);
});
} }
div.appendChild(table);
});
};
valueCell.appendChild(input);
row.appendChild(keyCell);
row.appendChild(valueCell);
table.appendChild(row);
}
div.appendChild(table);
}
function renderBranches(data) {
const div = document.getElementById('branches-list');
div.innerHTML = '';
const branchesStr = data.branches;
if (!branchesStr) {
addBranchEntry();
return;
}
const branches = branchesStr.split(':').map(b => b.trim()).filter(b => b);
branches.forEach(branch => {
addBranchEntry(branch);
});
}
let branchEntryCounter = 0;
let pendingPathInput = null;
function addBranchEntry(path = '', mode = 'RW', minfreespace = '') {
const container = document.getElementById('branches-list');
const entry = document.createElement('div');
entry.className = 'branch-entry';
entry.id = 'branch-entry-' + branchEntryCounter++;
const pathInput = document.createElement('input');
pathInput.type = 'text';
pathInput.className = 'branch-path';
pathInput.placeholder = 'Path';
pathInput.value = path;
const pathBtn = document.createElement('button');
pathBtn.textContent = 'Path';
pathBtn.onclick = () => openPathModal(pathInput);
const modeSelect = document.createElement('select');
modeSelect.className = 'branch-mode';
['RW', 'NC', 'RO'].forEach(m => {
const opt = document.createElement('option');
opt.value = m;
opt.textContent = m;
if (m === mode) opt.selected = true;
modeSelect.appendChild(opt);
});
const minfreespaceInput = document.createElement('input');
minfreespaceInput.type = 'text';
minfreespaceInput.className = 'branch-minfreespace';
minfreespaceInput.placeholder = 'MinFreeSpace';
minfreespaceInput.value = minfreespace;
const removeBtn = document.createElement('button');
removeBtn.className = 'branch-remove';
removeBtn.textContent = 'Remove';
removeBtn.onclick = () => entry.remove();
entry.appendChild(pathInput);
entry.appendChild(pathBtn);
entry.appendChild(modeSelect);
entry.appendChild(minfreespaceInput);
entry.appendChild(removeBtn);
container.appendChild(entry);
}
function openPathModal(targetInput) {
pendingPathInput = targetInput;
const modal = document.getElementById('pathModal');
const mountList = document.getElementById('mount-list');
mountList.innerHTML = '';
g_mounts.forEach(m => {
const div = document.createElement('div');
div.className = 'mount-list-item';
div.textContent = m;
div.onclick = () => {
if (pendingPathInput) {
pendingPathInput.value = m;
}
closePathModal();
};
mountList.appendChild(div);
});
modal.style.display = 'block';
}
function closePathModal() {
document.getElementById('pathModal').style.display = 'none';
pendingPathInput = null;
}
function submitBranches() {
const mount = document.getElementById('mount-select-branches').value;
const entries = document.querySelectorAll('.branch-entry');
const paths = [];
entries.forEach(entry => {
const pathInput = entry.querySelector('.branch-path');
if (pathInput && pathInput.value.trim()) {
paths.push(pathInput.value.trim());
}
});
const branchesStr = paths.join(':');
fetch('/kvs?mount=' + encodeURIComponent(mount), {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({"branches": branchesStr})
}).then(response => {
if (!response.ok) {
response.text().then(body => {
alert('Status: ' + response.status + '\nBody: ' + body);
});
}
});
}
window.onclick = function(event) {
const modal = document.getElementById('pathModal');
if (event.target === modal) {
closePathModal();
}
} }
function loadMounts() {
fetch('/mounts')
.then(r => r.json())
.then(data => {
const selectAdvanced = document.getElementById('mount-select-advanced');
const selectBranches = document.getElementById('mount-select-branches');
[selectAdvanced, selectBranches].forEach(s => {
s.innerHTML = '';
data.forEach(m => {
const opt = document.createElement('option');
opt.value = m;
opt.text = m;
s.appendChild(opt);
});
});
const onchangeFunc = function() {
const mount = this.value;
loadKV(mount);
};
const onchangeBranchesFunc = function() {
const mount = this.value;
loadBranches(mount);
};
selectAdvanced.onchange = onchangeFunc;
selectBranches.onchange = onchangeBranchesFunc;
if (data.length > 0) {
selectAdvanced.value = data[0];
selectBranches.value = data[0];
loadKV(data[0]);
loadBranches(data[0]);
}
});
}
function loadBranches(mount) {
let url = '/kvs?mount=' + encodeURIComponent(mount);
fetch(url)
.then(r => r.json())
.then(data => {
const div = document.getElementById('branches-list');
div.innerHTML = '';
const branchesStr = data.branches;
if (!branchesStr) {
addBranchEntry();
return;
}
const branches = branchesStr.split(':').map(b => b.trim()).filter(b => b);
branches.forEach(branch => {
addBranchEntry(branch);
});
});
}
let branchEntryCounter = 0;
let pendingPathInput = null;
function addBranchEntry(path = '', mode = 'RW', minfreespace = '') {
const container = document.getElementById('branches-list');
const entry = document.createElement('div');
entry.className = 'branch-entry';
entry.id = 'branch-entry-' + branchEntryCounter++;
const pathInput = document.createElement('input');
pathInput.type = 'text';
pathInput.className = 'branch-path';
pathInput.placeholder = 'Path';
pathInput.value = path;
const pathBtn = document.createElement('button');
pathBtn.textContent = 'Path';
pathBtn.onclick = () => openPathModal(pathInput);
const modeSelect = document.createElement('select');
modeSelect.className = 'branch-mode';
['RW', 'NC', 'RO'].forEach(m => {
const opt = document.createElement('option');
opt.value = m;
opt.textContent = m;
if (m === mode) opt.selected = true;
modeSelect.appendChild(opt);
});
const minfreespaceInput = document.createElement('input');
minfreespaceInput.type = 'text';
minfreespaceInput.className = 'branch-minfreespace';
minfreespaceInput.placeholder = 'MinFreeSpace';
minfreespaceInput.value = minfreespace;
const removeBtn = document.createElement('button');
removeBtn.className = 'branch-remove';
removeBtn.textContent = 'Remove';
removeBtn.onclick = () => entry.remove();
entry.appendChild(pathInput);
entry.appendChild(pathBtn);
entry.appendChild(modeSelect);
entry.appendChild(minfreespaceInput);
entry.appendChild(removeBtn);
container.appendChild(entry);
}
function openPathModal(targetInput) {
pendingPathInput = targetInput;
const modal = document.getElementById('pathModal');
const mountList = document.getElementById('mount-list');
mountList.innerHTML = '';
fetch('/mounts')
.then(r => r.json())
.then(mounts => {
mounts.forEach(m => {
const div = document.createElement('div');
div.className = 'mount-list-item';
div.textContent = m;
div.onclick = () => {
if (pendingPathInput) {
pendingPathInput.value = m;
}
closePathModal();
};
mountList.appendChild(div);
});
});
modal.style.display = 'block';
}
function closePathModal() {
document.getElementById('pathModal').style.display = 'none';
pendingPathInput = null;
}
function submitBranches() {
const mount = document.getElementById('mount-select-branches').value;
const entries = document.querySelectorAll('.branch-entry');
const paths = [];
entries.forEach(entry => {
const pathInput = entry.querySelector('.branch-path');
if (pathInput && pathInput.value.trim()) {
paths.push(pathInput.value.trim());
}
});
const branchesStr = paths.join(':');
fetch('/kvs?mount=' + encodeURIComponent(mount), {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({"branches": branchesStr})
}).then(response => {
if (!response.ok) {
response.text().then(body => {
alert('Status: ' + response.status + '\nBody: ' + body);
});
}
});
}
window.onclick = function(event) {
const modal = document.getElementById('pathModal');
if (event.target === modal) {
closePathModal();
}
}
window.onload = () => { loadMounts(); };
window.onload = () => { loadMounts(); };
</script> </script>
</body> </body>
</html> </html>
Loading…
Cancel
Save