Browse Source

mergerfs_webui.cpp

webui
Antonio SJ Musumeci 1 week ago
parent
commit
c797146726
  1. 436
      index.html
  2. 2
      src/mergerfs_webui.cpp

436
index.html

@ -175,6 +175,17 @@
.submit-branches-btn:hover {
background-color: #1976d2;
}
.path-btn {
background-color: #1565c0;
color: white;
border: none;
padding: 5px 10px;
cursor: pointer;
margin-right: 5px;
}
.path-btn:hover {
background-color: #1976d2;
}
.mount-list-item {
padding: 10px;
cursor: pointer;
@ -251,137 +262,137 @@
function getMounts() {
return g_mounts;
}
function loadMounts() {
fetch('/mounts')
.then(r => {
if (!r.ok) throw new Error('Failed to fetch mounts');
return r.json();
})
.then(data => {
g_mounts = data;
populateMountSelect('mount-select-advanced', function() {
loadAllForMount(this.value);
});
populateMountSelect('mount-select-branches', function() {
loadAllForMount(this.value);
});
if (g_mounts.length > 0) {
loadAllForMount(g_mounts[0]);
}
})
.catch(err => console.error('Error loading mounts:', err));
}
function fetchBranches(mount) {
return fetch('/kvs/branches?mount=' + encodeURIComponent(mount))
.then(r => {
if (!r.ok) throw new Error('Failed to fetch branches');
return r.json();
});
}
function fetchKV(mount) {
return fetch('/kvs?mount=' + encodeURIComponent(mount))
.then(r => {
if (!r.ok) throw new Error('Failed to fetch kvs');
return r.json();
});
}
function loadAllForMount(mount) {
if (!mount) return;
Promise.all([fetchKV(mount), fetchBranches(mount)])
.then(([kvData, branchesStr]) => {
renderKV(kvData, mount);
renderBranches(branchesStr);
})
.catch(err => console.error('Error loading data for mount:', mount, err));
}
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'];
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);
}
function renderBranches(branchesStr) {
const div = document.getElementById('branches-list');
div.innerHTML = '';
if (!branchesStr) {
addBranchEntry();
return;
}
const branches = branchesStr.split(':').map(b => b.trim()).filter(b => b);
branches.forEach(branchStr => {
let path = branchStr;
let mode = 'RW';
let minfreespace = '';
const eqPos = branchStr.indexOf('=');
if (eqPos !== -1) {
path = branchStr.substring(0, eqPos).trim();
const options = branchStr.substring(eqPos + 1).trim();
if (options) {
const parts = options.split(',');
if (parts.length >= 1 && parts[0]) {
mode = parts[0].trim().toUpperCase();
}
if (parts.length >= 2 && parts[1]) {
minfreespace = parts[1].trim();
}
}
}
addBranchEntry(path, mode, minfreespace);
});
}
let branchEntryCounter = 0;
let pendingPathInput = null;
function addBranchEntry(path = '', mode = 'RW', minfreespace = '') {
const container = document.getElementById('branches-list');
function loadMounts() {
fetch('/mounts')
.then(r => {
if (!r.ok) throw new Error('Failed to fetch mounts');
return r.json();
})
.then(data => {
g_mounts = data;
populateMountSelect('mount-select-advanced', function() {
loadAllForMount(this.value);
});
populateMountSelect('mount-select-branches', function() {
loadAllForMount(this.value);
});
if (g_mounts.length > 0) {
loadAllForMount(g_mounts[0]);
}
})
.catch(err => console.error('Error loading mounts:', err));
}
function fetchBranches(mount) {
return fetch('/kvs/branches?mount=' + encodeURIComponent(mount))
.then(r => {
if (!r.ok) throw new Error('Failed to fetch branches');
return r.json();
});
}
function fetchKV(mount) {
return fetch('/kvs?mount=' + encodeURIComponent(mount))
.then(r => {
if (!r.ok) throw new Error('Failed to fetch kvs');
return r.json();
});
}
function loadAllForMount(mount) {
if (!mount) return;
Promise.all([fetchKV(mount), fetchBranches(mount)])
.then(([kvData, branchesStr]) => {
renderKV(kvData, mount);
renderBranches(branchesStr);
})
.catch(err => console.error('Error loading data for mount:', mount, err));
}
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'];
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);
}
function renderBranches(branchesStr) {
const div = document.getElementById('branches-list');
div.innerHTML = '';
if (!branchesStr) {
addBranchEntry();
return;
}
const branches = branchesStr.split(':').map(b => b.trim()).filter(b => b);
branches.forEach(branchStr => {
let path = branchStr;
let mode = 'RW';
let minfreespace = '';
const eqPos = branchStr.indexOf('=');
if (eqPos !== -1) {
path = branchStr.substring(0, eqPos).trim();
const options = branchStr.substring(eqPos + 1).trim();
if (options) {
const parts = options.split(',');
if (parts.length >= 1 && parts[0]) {
mode = parts[0].trim().toUpperCase();
}
if (parts.length >= 2 && parts[1]) {
minfreespace = parts[1].trim();
}
}
}
addBranchEntry(path, mode, minfreespace);
});
}
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++;
@ -391,6 +402,7 @@
pathInput.placeholder = 'Path';
pathInput.value = path;
const pathBtn = document.createElement('button');
pathBtn.className = 'path-btn';
pathBtn.textContent = 'Path';
pathBtn.onclick = () => openPathModal(pathInput);
const modeSelect = document.createElement('select');
@ -403,85 +415,85 @@
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 branches = [];
entries.forEach(entry => {
const pathInput = entry.querySelector('.branch-path');
const modeSelect = entry.querySelector('.branch-mode');
const minfreespaceInput = entry.querySelector('.branch-minfreespace');
if (pathInput && pathInput.value.trim()) {
let branchStr = pathInput.value.trim();
if (modeSelect && modeSelect.value && modeSelect.value !== 'RW') {
branchStr += '=' + modeSelect.value;
} else {
branchStr += '=RW';
}
if (minfreespaceInput && minfreespaceInput.value.trim()) {
branchStr += ',' + minfreespaceInput.value.trim();
}
branches.push(branchStr);
}
});
const branchesStr = branches.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(); };
</script>
</body>
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 branches = [];
entries.forEach(entry => {
const pathInput = entry.querySelector('.branch-path');
const modeSelect = entry.querySelector('.branch-mode');
const minfreespaceInput = entry.querySelector('.branch-minfreespace');
if (pathInput && pathInput.value.trim()) {
let branchStr = pathInput.value.trim();
if (modeSelect && modeSelect.value && modeSelect.value !== 'RW') {
branchStr += '=' + modeSelect.value;
} else {
branchStr += '=RW';
}
if (minfreespaceInput && minfreespaceInput.value.trim()) {
branchStr += ',' + minfreespaceInput.value.trim();
}
branches.push(branchStr);
}
});
const branchesStr = branches.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(); };
</script>
</body>
</html>

2
src/mergerfs_webui.cpp

@ -258,7 +258,7 @@ _post_kvs(const httplib::Request &req_,
<< key << ": " << (std::string)val << std::endl;
}
res_.set_content("{}", "application/json");
res_.set_content("{}","application/json");
}
catch (const std::exception& e)
{

Loading…
Cancel
Save