Remove quota feature (incompatible with ZFS-backed LXC)

Linux setquota/repquota require quota support on the underlying
filesystem. The target deployment is a Proxmox LXC on ZFS storage where
this is not available inside the container.

Removed: /api/quota endpoint, _quota_supported, _quota_usage,
_find_quota_fs, quotas.js, quota modal, dashboard quota table,
Quota button from share rows.

PLANNING.md updated: notes quota as out of scope with reason.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Sonnet 4.6 committed 2026-06-19 23:07:28 +02:00
1 parent af315b37b2
commit bf7daf1c2c
6 files changed
+4 -239

No files matched your search

-27
View File
@@ -36,32 +36,5 @@ async function loadDashboard() {
</tr>`).join('');
}
// Persist quota support flag for shares.js to read
window._quotaSupported = d.quota_supported !== false;
window._quotaReason = d.quota_reason || '';
const qb = document.getElementById('dashQuotasBody');
if (!d.quota_supported) {
qb.innerHTML = `<tr><td colspan="4" style="color:var(--muted); font-size:.87rem">${esc(d.quota_reason)}</td></tr>`;
} else if (!d.quotas?.length) {
qb.innerHTML = `<tr><td colspan="4" style="color:var(--muted)">No quotas set.</td></tr>`;
} else {
qb.innerHTML = d.quotas.map(q => {
const p = Math.min(q.percent, 100);
const col = p > 90 ? 'var(--red)' : p > 75 ? 'var(--yellow)' : 'var(--green)';
return `<tr>
<td>${esc(q.user)}</td>
<td>${q.used_gb} GB</td>
<td>${q.soft_gb} GB</td>
<td>
<div class="progress" style="height:8px">
<div class="progress-bar" style="width:${p}%; background:${col}; transition:width .6s ease"></div>
</div>
<div class="mt-1" style="font-size:.75rem; color:var(--muted)">${p.toFixed(1)}%</div>
</td>
</tr>`;
}).join('');
}
_startRefreshCountdown();
}
-76
View File
@@ -1,76 +0,0 @@
let _qCtx = null;
function openQuotaModal(type, name, path, validUsers) {
_qCtx = { type, name, path, users: validUsers ? validUsers.split(/[\s,]+/).filter(Boolean) : [] };
document.getElementById('quotaModalTitle').textContent =
'Quota — ' + (type === 'smb' ? esc(name) : esc(path));
let body = '';
if (type === 'smb' && _qCtx.users.length > 0) {
body = _qCtx.users.map(u => `
<div class="mb-3">
<label class="form-label" style="color:var(--muted); font-size:.85rem">${esc(u)}</label>
<div class="input-group">
<input type="number" class="form-control quota-inp" data-user="${esc(u)}" placeholder="GB" min="1">
<span class="input-group-text">GB</span>
</div>
</div>`).join('');
} else {
const note = type === 'nfs'
? `<p class="mb-3" style="color:var(--muted); font-size:.87rem">Path: <code>${esc(path)}</code></p>`
: `<p class="mb-3" style="color:var(--muted); font-size:.87rem">No valid users on this share — enter a username manually.</p>`;
body = note + `
<div class="mb-3">
<label class="form-label" style="color:var(--muted); font-size:.85rem">Username</label>
<input type="text" id="qManualUser" class="form-control" placeholder="username">
</div>
<div class="mb-3">
<label class="form-label" style="color:var(--muted); font-size:.85rem">Quota</label>
<div class="input-group">
<input type="number" id="qManualGB" class="form-control" placeholder="GB" min="1">
<span class="input-group-text">GB</span>
</div>
</div>`;
}
document.getElementById('quotaModalBody').innerHTML = body;
new bootstrap.Modal(document.getElementById('quotaModal')).show();
}
async function submitQuota() {
if (!_qCtx) return;
const calls = [];
if (_qCtx.type === 'smb' && _qCtx.users.length > 0) {
for (const inp of document.querySelectorAll('.quota-inp')) {
const gb = parseInt(inp.value);
if (!gb || gb <= 0) continue;
calls.push({ user: inp.dataset.user, quota: gb, path: _qCtx.path });
}
} else {
const user = document.getElementById('qManualUser')?.value.trim();
const gb = parseInt(document.getElementById('qManualGB')?.value);
if (!user) return showToast('Username is required.', 'danger');
if (!gb || gb <= 0) return showToast('Quota must be a positive number.', 'danger');
calls.push({ user, path: _qCtx.path || '/srv', quota: gb });
}
if (!calls.length) return showToast('No quota values entered.', 'warning');
bootstrap.Modal.getInstance(document.getElementById('quotaModal')).hide();
const responses = await Promise.all(calls.map(body =>
apiFetch('/api/quota', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(body) })
));
const errors = [];
for (const r of responses) {
if (r && !r.ok) {
const d = await r.json().catch(() => ({}));
errors.push(d.error || `HTTP ${r.status}`);
}
}
if (errors.length) showToast('Quota error: ' + errors.join('; '), 'danger');
else showToast(`Quota${calls.length > 1 ? 's' : ''} applied.`);
loadDashboard();
}
-5
View File
@@ -14,7 +14,6 @@ async function loadShares() {
const shareJson = esc(JSON.stringify(s));
const nameJson = esc(JSON.stringify(s.name));
const pathJson = esc(JSON.stringify(s.path));
const usersJson = esc(JSON.stringify(s.valid_users));
return `<tr style="${enabled ? '' : 'opacity:.55'}">
<td>${esc(s.name)}</td>
<td><code>${esc(s.path)}</code></td>
@@ -23,8 +22,6 @@ async function loadShares() {
<td>
<button class="btn btn-sm me-1" style="border:1px solid var(--border); color:var(--muted)"
onclick="openEditModal('smb',${shareJson})">Edit</button>
${window._quotaSupported !== false ? `<button class="btn btn-sm me-1" style="border:1px solid var(--border); color:var(--muted)"
onclick="openQuotaModal('smb',${nameJson},${pathJson},${usersJson})">Quota</button>` : ''}
<button class="btn btn-sm me-1" style="border:1px solid var(--border); color:var(--muted)"
onclick="toggleShare('smb',${nameJson},${!enabled})">${enabled ? 'Disable' : 'Enable'}</button>
<button class="btn btn-sm btn-danger"
@@ -50,8 +47,6 @@ async function loadShares() {
<td>
<button class="btn btn-sm me-1" style="border:1px solid var(--border); color:var(--muted)"
onclick="openEditModal('nfs',${shareJson})">Edit</button>
${window._quotaSupported !== false ? `<button class="btn btn-sm me-1" style="border:1px solid var(--border); color:var(--muted)"
onclick="openQuotaModal('nfs',null,${pathJson},null)">Quota</button>` : ''}
<button class="btn btn-sm me-1" style="border:1px solid var(--border); color:var(--muted)"
onclick="toggleShare('nfs',${pathJson},${!enabled})">${enabled ? 'Disable' : 'Enable'}</button>
<button class="btn btn-sm btn-danger"