quota reset button

This commit is contained in:
2026-07-14 22:42:49 -03:00
parent ed0e240241
commit ff175174e4
7 changed files with 264 additions and 9 deletions
+18
View File
@@ -172,6 +172,24 @@ Object.assign(I18N_TEXT["pt-BR"], {
"Reseller areas":"Áreas de revendedores","Reseller area":"Área de revendedores","Create reseller":"Criar revendedor","Edit reseller":"Editar revendedor","Registered resellers":"Revendedores cadastrados","Reseller list section copy":"Consulte cotas, consumo compartilhado, validade e situação de cada parceiro.","Reseller create section copy":"Defina login, limite compartilhado, validade e acesso em uma tela dedicada.","Reseller saved successfully.":"Revendedor salvo com sucesso.",
"Configuration areas":"Áreas de configuração","Configuration area":"Área de configuração","Network and SSH":"Rede e SSH","SlowDNS / DNSTT":"SlowDNS / DNSTT","TLS forwarders":"Encaminhadores TLS","01 · Base":"01 · Base","02 · DNS tunnel":"02 · Túnel DNS","03 · UDP":"03 · UDP","04 · Security":"04 · Segurança","05 · Core":"05 · Core","Network and SSH section copy":"Configure listeners, limites padrão, tempo ocioso e o banner de conexão.","SlowDNS section copy":"Gerencie domínios, DNS local, capacidade, filas e reinício controlado.","UDP section copy":"Defina listener, capacidade, expiração de mapa e reinício do serviço.","TLS section copy":"Crie listeners TLS com certificado automático, colado ou armazenado em arquivo.","Xray core section copy":"Ative o core, escolha o runtime e aplique os ajustes nativos seguros."
});
Object.assign(I18N_TEXT["en-US"], {
"Reset":"Reset","Reset traffic":"Reset traffic","Reset SSH traffic":"Reset SSH traffic","Reset Xray traffic":"Reset Xray traffic",
"Reset traffic for user \"{name}\"?":"Reset traffic for user \"{name}\"?","Reset traffic for client {id}…?":"Reset traffic for client {id}…?",
"Current uploaded and downloaded usage will return to zero. The account, password, expiry and quota remain unchanged.":"Current uploaded and downloaded usage will return to zero. The account, password, expiry and quota remain unchanged.",
"Current uploaded and downloaded usage will return to zero. The account, expiry and quota remain unchanged.":"Current uploaded and downloaded usage will return to zero. The account, expiry and quota remain unchanged.",
"Resetting traffic for {name}…":"Resetting traffic for {name}…","Resetting traffic for client {id}…":"Resetting traffic for client {id}…",
"Traffic reset successfully.":"Traffic reset successfully.","Xray traffic reset successfully.":"Xray traffic reset successfully.",
"Could not reset traffic: {error}":"Could not reset traffic: {error}","Reset traffic counter":"Reset traffic counter"
});
Object.assign(I18N_TEXT["pt-BR"], {
"Reset":"Zerar","Reset traffic":"Zerar tráfego","Reset SSH traffic":"Zerar tráfego SSH","Reset Xray traffic":"Zerar tráfego Xray",
"Reset traffic for user \"{name}\"?":"Zerar o tráfego do usuário \"{name}\"?","Reset traffic for client {id}…?":"Zerar o tráfego do cliente {id}…?",
"Current uploaded and downloaded usage will return to zero. The account, password, expiry and quota remain unchanged.":"O consumo de upload e download voltará para zero. A conta, senha, validade e cota não serão alteradas.",
"Current uploaded and downloaded usage will return to zero. The account, expiry and quota remain unchanged.":"O consumo de upload e download voltará para zero. A conta, validade e cota não serão alteradas.",
"Resetting traffic for {name}…":"Zerando o tráfego de {name}…","Resetting traffic for client {id}…":"Zerando o tráfego do cliente {id}…",
"Traffic reset successfully.":"Tráfego zerado com sucesso.","Xray traffic reset successfully.":"Tráfego Xray zerado com sucesso.",
"Could not reset traffic: {error}":"Não foi possível zerar o tráfego: {error}","Reset traffic counter":"Zerar contador de tráfego"
});
const I18N_REVERSE = Object.fromEntries(SUPPORTED_LANGS.map(lang => [lang, Object.fromEntries(Object.entries(I18N_TEXT[lang] || {}).map(([k, v]) => [v, k]))]));
let currentLang = detectInitialLanguage();
let i18nTranslating = false;
+38 -1
View File
@@ -173,12 +173,18 @@ function renderUsers(users) {
className:"btn btn-ghost btn-sm", textContent:t("Edit"),
onclick: () => fillUserForm(u),
});
const resetBtn = Object.assign(document.createElement("button"), {
className:"btn btn-warn btn-sm", textContent:t("Reset"),
style: "margin-left:4px;",
title: t("Reset traffic"),
onclick: () => resetUserTraffic(u.username, resetBtn),
});
const delBtn = Object.assign(document.createElement("button"), {
className:"btn btn-danger btn-sm", textContent:t("Del"),
style: "margin-left:4px;",
onclick: () => deleteUser(u.username),
});
tdA.append(editBtn, delBtn);
tdA.append(editBtn, resetBtn, delBtn);
tr.appendChild(tdA);
usersBody.appendChild(tr);
});
@@ -260,6 +266,37 @@ userForm.addEventListener("submit", async e => {
}
});
async function resetUserTraffic(username, button) {
const accepted = await panelConfirm({
tone:"warning", icon:"↺", title:t("Reset SSH traffic"),
message:t("Reset traffic for user \"{name}\"?", {name: username}),
detail:t("Current uploaded and downloaded usage will return to zero. The account, password, expiry and quota remain unchanged."),
confirmLabel:t("Reset traffic"),
});
if (!accepted) return;
const previousDisabled = !!button?.disabled;
if (button) button.disabled = true;
userStatus.textContent = t("Resetting traffic for {name}…", {name: username});
try {
const res = await api("/api/users/reset-traffic", {
method:"POST",
body: JSON.stringify({ username, server_id:selectedSSHServer() }),
});
if (!res.ok) throw new Error((await res.text()) || "reset failed");
userStatus.textContent = t("Traffic reset successfully.");
showPanelToast(t("Traffic reset successfully."), "success", t("SSH / SlowDNS"));
await loadUsers();
} catch (e) {
if (e.message === "auth") doAuthError();
else {
userStatus.textContent = t("Could not reset traffic: {error}", {error:e.message});
showPanelToast(t("Could not reset traffic: {error}", {error:e.message}), "error", t("SSH / SlowDNS"));
}
} finally {
if (button) button.disabled = previousDisabled;
}
}
async function deleteUser(username) {
const accepted = await panelConfirm({
tone:"danger", icon:"×", title:t("Delete SSH account"),
+45 -1
View File
@@ -244,12 +244,18 @@ function renderInbounds(inbounds, options = {}) {
editBtn.style.marginLeft = "4px";
editBtn.textContent = t("Edit");
editBtn.onclick = () => openEditXrayClient(ib.tag, c);
const resetBtn = document.createElement("button");
resetBtn.className = "btn btn-warn btn-sm";
resetBtn.style.marginLeft = "4px";
resetBtn.textContent = t("Reset");
resetBtn.title = t("Reset traffic");
resetBtn.onclick = () => resetXrayClientTraffic(c.id, resetBtn);
const delBtn = document.createElement("button");
delBtn.className = "btn btn-danger btn-sm";
delBtn.style.marginLeft = "4px";
delBtn.textContent = t("Del");
delBtn.onclick = () => removeClient(ib.tag, c.id);
actTd.append(copyBtn, editBtn, delBtn);
actTd.append(copyBtn, editBtn, resetBtn, delBtn);
tr.appendChild(actTd);
tbody.appendChild(tr);
});
@@ -394,6 +400,44 @@ async function submitXrayClientCreator(event) {
}
}
async function resetXrayClientTraffic(uuid, button) {
const shortID = String(uuid || "").slice(0, 8);
const accepted = await panelConfirm({
tone:"warning", icon:"↺", title:t("Reset Xray traffic"),
message:t("Reset traffic for client {id}…?", {id:shortID}),
detail:t("Current uploaded and downloaded usage will return to zero. The account, expiry and quota remain unchanged."),
confirmLabel:t("Reset traffic"),
});
if (!accepted) return;
const previousDisabled = !!button?.disabled;
if (button) button.disabled = true;
xStatus.textContent = t("Resetting traffic for client {id}…", {id:shortID});
try {
const res = await api("/api/xray/clients/reset-traffic", {
method:"POST",
body:JSON.stringify({ uuid, server_id:selectedXrayServer() }),
});
if (!res.ok) throw new Error((await res.text()) || "reset failed");
xStatus.textContent = t("Xray traffic reset successfully.");
showPanelToast(t("Xray traffic reset successfully."), "success", t("Xray user"));
if (editingXrayClientId === uuid) {
const usage = document.getElementById("editXrayUsage");
if (usage) usage.value = "0 B (↑ 0 B · ↓ 0 B)";
const resetUsage = document.getElementById("editXrayResetUsage");
if (resetUsage) resetUsage.checked = false;
}
await loadInbounds({ force:true });
} catch (e) {
if (e.message === "auth") doAuthError();
else {
xStatus.textContent = t("Could not reset traffic: {error}", {error:e.message});
showPanelToast(t("Could not reset traffic: {error}", {error:e.message}), "error", t("Xray user"));
}
} finally {
if (button) button.disabled = previousDisabled;
}
}
async function removeClient(tag, uuid) {
const accepted = await panelConfirm({
tone:"danger", icon:"×", title:t("Remove Xray client"),
+3 -3
View File
@@ -1555,10 +1555,10 @@
<!-- app.js was split into ordered modules for maintainability. They are plain
classic scripts sharing one global scope; `defer` preserves execution order,
so behavior is identical to the old single file. Keep this load order. -->
<script defer src="assets/js/01-core.js?v=20260714quota1"></script>
<script defer src="assets/js/01-core.js?v=20260715quotareset1"></script>
<script defer src="assets/js/02-shell.js?v=20260714pamfix1"></script>
<script defer src="assets/js/03-ssh-users.js?v=20260714quota1"></script>
<script defer src="assets/js/04-xray.js?v=20260714quota1"></script>
<script defer src="assets/js/03-ssh-users.js?v=20260715quotareset1"></script>
<script defer src="assets/js/04-xray.js?v=20260715quotareset1"></script>
<script defer src="assets/js/05-resellers.js?v=20260714pamfix1"></script>
<script defer src="assets/js/06-servers.js?v=20260714pamfix1"></script>
<script defer src="assets/js/07-stats-logs.js?v=20260714pamfix1"></script>