fix(admin): dominio trocado ao criar, apelidos escondidos e quota a zero
Criar "lucas@cienciaembarcada.com.br" criava "lucas@lucasrguerra.dev.br". O formulario partia o endereco e deitava fora a metade do dominio, e nao enviava domain_id nenhum -- o servidor caia entao no dominio do admin com sessao iniciada. Passa a ser parte local mais um dominio escolhido de uma lista: a metade que decide o endereco deixa de poder ser descartada em silencio. Apelidos de outros dominios estavam escondidos, e a culpa era minha: procurava so no dominio onde a morada do proprio utilizador vive. Uma caixa recebe postmaster, abuse, dmarc e tlsrpt de varios dominios ao mesmo tempo, por isso o painel mostrava quatro onde entregavam oito. Ha um teste que falha com a consulta antiga. Os apelidos passam a poder ser criados e removidos dentro do modal. O dominio sai do endereco escrito, para nao repetir a armadilha do formulario de cima. USER, DOMAIN_ADMIN e SUPER_ADMIN eram constantes impressas no ecra, logo ficavam em ingles em qualquer idioma. Um papel desconhecido continua a ser mostrado tal como esta guardado, porque quem o ve precisa de saber o que a base de dados tem. A quota estava certa e mal mostrada. used_bytes vale 140771 -- os 137,5 KB reais -- mas a pagina arredondava para megabytes inteiros, portanto qualquer coisa abaixo de 512 KB lia-se "0 / 2 MB" e a barra ficava vazia, calculada sobre os megabytes ja arredondados. Passa a formatar a partir dos bytes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f801894f53
commit
bb12e603a3
9 files changed
+308
-35
No files matched your search
@@ -0,0 +1,43 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { formatBytes, usageRatio } from "./formatBytes";
|
||||
|
||||
describe("formatBytes", () => {
|
||||
// The case from the console: 140771 bytes rendered as "0 / 2 MB".
|
||||
it("shows a mailbox smaller than a megabyte as itself", () => {
|
||||
expect(formatBytes(140771)).toBe("137.5 KB");
|
||||
});
|
||||
|
||||
it("climbs through the units", () => {
|
||||
expect(formatBytes(0)).toBe("0 B");
|
||||
expect(formatBytes(512)).toBe("512 B");
|
||||
expect(formatBytes(1024)).toBe("1.0 KB");
|
||||
expect(formatBytes(2097152)).toBe("2.0 MB");
|
||||
expect(formatBytes(5_368_709_120)).toBe("5.0 GB");
|
||||
});
|
||||
|
||||
it("keeps one decimal at every size, so the reading stays comparable", () => {
|
||||
expect(formatBytes(157_286_400)).toBe("150.0 MB");
|
||||
});
|
||||
|
||||
it.each([-1, Number.NaN, Number.POSITIVE_INFINITY])("survives %s", (value) => {
|
||||
expect(typeof formatBytes(value)).toBe("string");
|
||||
});
|
||||
});
|
||||
|
||||
describe("usageRatio", () => {
|
||||
it("measures from the raw bytes, not from rounded megabytes", () => {
|
||||
// Rounding first gave 0/2 = 0%, and the bar stayed empty on a mailbox
|
||||
// that was really 6.7% full.
|
||||
expect(usageRatio(140771, 2097152)).toBeCloseTo(0.0671, 3);
|
||||
});
|
||||
|
||||
// Zero means unlimited; dividing by it reported every unlimited mailbox as
|
||||
// over its limit.
|
||||
it("never calls an unlimited mailbox full", () => {
|
||||
expect(usageRatio(5_000_000, 0)).toBe(0);
|
||||
});
|
||||
|
||||
it("stops at full rather than overflowing the bar", () => {
|
||||
expect(usageRatio(3_000_000, 2_000_000)).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* A byte count as a person reads it.
|
||||
*
|
||||
* The admin list rounded bytes to whole megabytes, so a mailbox holding
|
||||
* 137.5 KB displayed as "0 / 2 MB" and its usage bar sat empty - the figure
|
||||
* was right in the database and lost on the way to the screen.
|
||||
*/
|
||||
export function formatBytes(bytes: number): string {
|
||||
const value = Number(bytes);
|
||||
if (!Number.isFinite(value) || value <= 0) return "0 B";
|
||||
|
||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||
let n = value;
|
||||
let unit = 0;
|
||||
while (n >= 1024 && unit < units.length - 1) {
|
||||
n /= 1024;
|
||||
unit++;
|
||||
}
|
||||
|
||||
// Bytes are whole things; above that always one decimal, which is what
|
||||
// tells 137.5 KB from 137 KB. Dropping it on larger numbers would have hid
|
||||
// exactly the digit that made the reading recognisable.
|
||||
const rendered = unit === 0 ? String(Math.round(n)) : n.toFixed(1);
|
||||
return `${rendered} ${units[unit]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* How full a mailbox is, as a percentage, computed from the raw byte counts.
|
||||
*
|
||||
* A quota of zero means unlimited, so it is never full - dividing by it would
|
||||
* report every unlimited mailbox as over its limit.
|
||||
*/
|
||||
export function usageRatio(usedBytes: number, quotaBytes: number): number {
|
||||
const used = Number(usedBytes);
|
||||
const quota = Number(quotaBytes);
|
||||
if (!Number.isFinite(used) || !Number.isFinite(quota) || quota <= 0 || used <= 0) return 0;
|
||||
return Math.min(used / quota, 1);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* A role as a person reads it.
|
||||
*
|
||||
* The console printed the stored constants - USER, DOMAIN_ADMIN, SUPER_ADMIN -
|
||||
* straight onto the screen, so those three words stayed in English whatever
|
||||
* the interface was set to.
|
||||
*/
|
||||
export function roleLabel(t: (key: string) => string, role: string): string {
|
||||
const known: Record<string, string> = {
|
||||
USER: "roles.user",
|
||||
DOMAIN_ADMIN: "roles.domainAdmin",
|
||||
SUPER_ADMIN: "roles.superAdmin",
|
||||
};
|
||||
const key = known[String(role).toUpperCase()];
|
||||
// An unknown role is shown as stored rather than as a missing-translation
|
||||
// marker: whoever sees it needs to know what the database actually holds.
|
||||
return key ? t(key) : String(role);
|
||||
}
|
||||
Reference in new issue
Block a user