Quota per user
This commit is contained in:
@@ -683,7 +683,12 @@ function clientTrafficHTML(c) {
|
||||
const up = Number(c.uplink_bytes || 0);
|
||||
const down = Number(c.downlink_bytes || 0);
|
||||
const total = Number(c.total_bytes || (up + down) || 0);
|
||||
return `${escapeHTML(formatBytes(total))}<div class="hint">↑ ${escapeHTML(formatBytes(up))} · ↓ ${escapeHTML(formatBytes(down))}</div>`;
|
||||
const quota = Number(c.data_quota_bytes || 0);
|
||||
const quotaLabel = quota > 0 ? formatBytes(quota) : "∞";
|
||||
const state = c.quota_exceeded
|
||||
? (c.quota_action === "throttle" ? ` · ${t("throttled")}` : ` · ${t("blocked")}`)
|
||||
: "";
|
||||
return `${escapeHTML(formatBytes(total))} / ${escapeHTML(quotaLabel)}${escapeHTML(state)}<div class="hint">↑ ${escapeHTML(formatBytes(up))} · ↓ ${escapeHTML(formatBytes(down))}</div>`;
|
||||
}
|
||||
|
||||
function updateCell(row, name, html) {
|
||||
|
||||
@@ -9,6 +9,10 @@ cancelUserBtn.addEventListener("click", () => {
|
||||
function prepareNewSSHUser() {
|
||||
userForm.reset();
|
||||
fTotpPeriod.value = 60; fTotpWindow.value = 1; fTotpDigits.value = 6;
|
||||
fQuotaAction.value = "block";
|
||||
fQuotaThrottle.value = 1;
|
||||
fUsageDisplay.value = "0 B";
|
||||
fResetUsage.checked = false;
|
||||
const heading = document.getElementById("userFormHeading");
|
||||
const title = document.getElementById("userFormTitle");
|
||||
if (heading) heading.textContent = t("Create user");
|
||||
@@ -68,11 +72,12 @@ const USER_SORT_EXTRACT = {
|
||||
max: u => u.max_connections || 0,
|
||||
up: u => u.limit_mbps_up || 0,
|
||||
down: u => u.limit_mbps_down || 0,
|
||||
usage: u => Number(u.total_bytes || ((u.total_uplink_bytes || 0) + (u.total_downlink_bytes || 0)) || 0),
|
||||
expires: u => u.expires_at ? new Date(u.expires_at).getTime() : Infinity,
|
||||
owner: u => String(u.owner_username || "").toLowerCase(),
|
||||
};
|
||||
// Columns that default to descending on first click (most/online first).
|
||||
const USER_SORT_DEFAULT_DESC = new Set(["status", "conn", "max", "up", "down"]);
|
||||
const USER_SORT_DEFAULT_DESC = new Set(["status", "conn", "max", "up", "down", "usage"]);
|
||||
|
||||
let userSort = { key: "username", dir: "asc" };
|
||||
let lastUsersData = [];
|
||||
@@ -139,6 +144,12 @@ function renderUsers(users) {
|
||||
const on = (u.active_conns || 0) > 0;
|
||||
if (on) online++;
|
||||
if (isExpiredDate(u.expires_at)) expiredCount++;
|
||||
const totalBytes = Number(u.total_bytes || ((u.total_uplink_bytes || 0) + (u.total_downlink_bytes || 0)) || 0);
|
||||
const quotaBytes = Number(u.data_quota_bytes || 0);
|
||||
const quotaLabel = quotaBytes > 0 ? formatBytes(quotaBytes) : "∞";
|
||||
const quotaState = u.quota_exceeded
|
||||
? (u.quota_action === "throttle" ? ` · ${t("throttled")}` : ` · ${t("blocked")}`)
|
||||
: "";
|
||||
const tr = document.createElement("tr");
|
||||
const cells = [
|
||||
u.username,
|
||||
@@ -148,6 +159,7 @@ function renderUsers(users) {
|
||||
u.max_connections || 0,
|
||||
u.limit_mbps_up || 0,
|
||||
u.limit_mbps_down || 0,
|
||||
`${formatBytes(totalBytes)} / ${quotaLabel}${quotaState}`,
|
||||
u.expires_at ? fmtDate(u.expires_at) : "—",
|
||||
];
|
||||
if (isSA) cells.push(u.owner_username || "—");
|
||||
@@ -194,6 +206,12 @@ function fillUserForm(u) {
|
||||
fMaxConn.value = u.max_connections || "";
|
||||
fUp.value = u.limit_mbps_up || "";
|
||||
fDown.value = u.limit_mbps_down || "";
|
||||
fQuotaGB.value = u.data_quota_bytes ? (Number(u.data_quota_bytes) / (1024 ** 3)).toFixed(2).replace(/\.00$/, "") : "0";
|
||||
fQuotaAction.value = u.quota_action === "throttle" ? "throttle" : "block";
|
||||
fQuotaThrottle.value = u.quota_throttle_mbps || 1;
|
||||
const totalBytes = Number(u.total_bytes || ((u.total_uplink_bytes || 0) + (u.total_downlink_bytes || 0)) || 0);
|
||||
fUsageDisplay.value = `${formatBytes(totalBytes)} (↑ ${formatBytes(u.total_uplink_bytes || 0)} · ↓ ${formatBytes(u.total_downlink_bytes || 0)})`;
|
||||
fResetUsage.checked = false;
|
||||
fExpires.value = u.expires_at ? localFromISO(u.expires_at) : "";
|
||||
const heading = document.getElementById("userFormHeading");
|
||||
const title = document.getElementById("userFormTitle");
|
||||
@@ -218,6 +236,10 @@ userForm.addEventListener("submit", async e => {
|
||||
expires_at: isoFromLocal(fExpires.value),
|
||||
limit_mbps_up: parseInt(fUp.value||"0",10),
|
||||
limit_mbps_down: parseInt(fDown.value||"0",10),
|
||||
data_quota_bytes: Math.round((parseFloat(fQuotaGB.value || "0") || 0) * (1024 ** 3)),
|
||||
quota_action: fQuotaAction.value === "throttle" ? "throttle" : "block",
|
||||
quota_throttle_mbps: parseInt(fQuotaThrottle.value || "1", 10) || 1,
|
||||
reset_usage: !!fResetUsage.checked,
|
||||
server_id: selectedSSHServer(),
|
||||
};
|
||||
try {
|
||||
@@ -225,6 +247,7 @@ userForm.addEventListener("submit", async e => {
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
userStatus.textContent = t("Saved.");
|
||||
fPassword.value = "";
|
||||
fResetUsage.checked = false;
|
||||
loadUsers();
|
||||
if (currentRole === "reseller") loadMe();
|
||||
showPanelToast(t("SSH user saved successfully."), "success", t("SSH / SlowDNS"));
|
||||
|
||||
@@ -325,6 +325,12 @@ function prepareXrayClientCreator(preferredTag = "") {
|
||||
if (uuid) uuid.value = genUUID();
|
||||
const maxConns = document.getElementById("xCreateMaxConns");
|
||||
if (maxConns) maxConns.value = "0";
|
||||
const quotaGB = document.getElementById("xCreateQuotaGB");
|
||||
if (quotaGB) quotaGB.value = "0";
|
||||
const quotaAction = document.getElementById("xCreateQuotaAction");
|
||||
if (quotaAction) quotaAction.value = "block";
|
||||
const quotaThrottle = document.getElementById("xCreateQuotaThrottle");
|
||||
if (quotaThrottle) quotaThrottle.value = "1";
|
||||
const status = document.getElementById("xCreateClientStatus");
|
||||
if (status) status.textContent = xrayCreatorInbounds.length ? t("Ready to create a new Xray client.") : t("Waiting for a compatible inbound.");
|
||||
updateXrayCreatorInboundLabel();
|
||||
@@ -351,6 +357,9 @@ async function submitXrayClientCreator(event) {
|
||||
name: (document.getElementById("xCreateName")?.value || "").trim(),
|
||||
expires_at: isoFromLocal(document.getElementById("xCreateExpiry")?.value || ""),
|
||||
max_connections: parseInt(document.getElementById("xCreateMaxConns")?.value || "0", 10) || 0,
|
||||
data_quota_bytes: Math.round((parseFloat(document.getElementById("xCreateQuotaGB")?.value || "0") || 0) * (1024 ** 3)),
|
||||
quota_action: document.getElementById("xCreateQuotaAction")?.value === "throttle" ? "throttle" : "block",
|
||||
quota_throttle_mbps: parseInt(document.getElementById("xCreateQuotaThrottle")?.value || "1", 10) || 1,
|
||||
server_id: selectedXrayServer(),
|
||||
};
|
||||
if (button) button.disabled = true;
|
||||
|
||||
@@ -6,6 +6,11 @@ function openEditXrayClient(tag, client) {
|
||||
document.getElementById("editXrayEmail").value = client.email || "";
|
||||
document.getElementById("editXrayExpiry").value = client.expires_at ? localFromISO(client.expires_at) : "";
|
||||
document.getElementById("editXrayMaxConns").value = client.max_conns || 0;
|
||||
document.getElementById("editXrayQuotaGB").value = client.data_quota_bytes ? (Number(client.data_quota_bytes) / (1024 ** 3)).toFixed(2).replace(/\.00$/, "") : "0";
|
||||
document.getElementById("editXrayQuotaAction").value = client.quota_action === "throttle" ? "throttle" : "block";
|
||||
document.getElementById("editXrayQuotaThrottle").value = client.quota_throttle_mbps || 1;
|
||||
document.getElementById("editXrayUsage").value = `${formatBytes(client.total_bytes || 0)} (↑ ${formatBytes(client.uplink_bytes || 0)} · ↓ ${formatBytes(client.downlink_bytes || 0)})`;
|
||||
document.getElementById("editXrayResetUsage").checked = false;
|
||||
document.getElementById("editXrayClientStatus").textContent = "";
|
||||
document.getElementById("editXrayClientPanel").classList.remove("hidden");
|
||||
document.getElementById("editXrayClientPanel").scrollIntoView({ behavior:"smooth", block:"nearest" });
|
||||
@@ -26,6 +31,10 @@ async function saveEditXrayClient() {
|
||||
email: document.getElementById("editXrayEmail").value.trim(),
|
||||
expires_at: isoFromLocal(document.getElementById("editXrayExpiry").value),
|
||||
max_connections: parseInt(document.getElementById("editXrayMaxConns").value || "0", 10),
|
||||
data_quota_bytes: Math.round((parseFloat(document.getElementById("editXrayQuotaGB").value || "0") || 0) * (1024 ** 3)),
|
||||
quota_action: document.getElementById("editXrayQuotaAction").value === "throttle" ? "throttle" : "block",
|
||||
quota_throttle_mbps: parseInt(document.getElementById("editXrayQuotaThrottle").value || "1", 10) || 1,
|
||||
reset_usage: !!document.getElementById("editXrayResetUsage").checked,
|
||||
server_id: selectedXrayServer(),
|
||||
};
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user