diff --git a/index.html b/index.html index 474fe162..dc0d2f75 100644 --- a/index.html +++ b/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(); }; - - + 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(); }; + + diff --git a/src/mergerfs_webui.cpp b/src/mergerfs_webui.cpp index 4feced27..85ff10a1 100644 --- a/src/mergerfs_webui.cpp +++ b/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) {