changes
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import { stat } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { config } from '../config.js';
|
||||
import { run } from '../utils/shell.js';
|
||||
import { domainFromEmail, localPartFromEmail, normalizeEmail } from '../utils/email.js';
|
||||
@@ -11,6 +9,14 @@ export interface DmsAccount {
|
||||
domain: string;
|
||||
}
|
||||
|
||||
export interface DmsQuota {
|
||||
usedBytes: number;
|
||||
quotaBytes: number | null;
|
||||
quotaPercent: number | null;
|
||||
messageCount: number | null;
|
||||
messageLimit: number | null;
|
||||
}
|
||||
|
||||
function parseAccounts(output: string): DmsAccount[] {
|
||||
const accounts: DmsAccount[] = [];
|
||||
for (const line of output.split('\n')) {
|
||||
@@ -22,6 +28,60 @@ function parseAccounts(output: string): DmsAccount[] {
|
||||
return [...new Map(accounts.map((a) => [a.email, a])).values()].sort((a, b) => a.email.localeCompare(b.email));
|
||||
}
|
||||
|
||||
function parseQuotaNumber(value: string | undefined): number | null {
|
||||
if (!value || value === '-' || value.trim() === '') return null;
|
||||
const parsed = Number.parseInt(value.trim(), 10);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function parseDoveadmQuota(output: string): DmsQuota {
|
||||
let usedStorageKb: number | null = null;
|
||||
let storageLimitKb: number | null = null;
|
||||
let storagePercent: number | null = null;
|
||||
let messageCount: number | null = null;
|
||||
let messageLimit: number | null = null;
|
||||
|
||||
for (const line of output.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.toLowerCase().startsWith('quota name')) continue;
|
||||
|
||||
const parts = trimmed.split(/\s+/);
|
||||
const typeIndex = parts.findIndex((p) => p === 'STORAGE' || p === 'MESSAGE');
|
||||
if (typeIndex < 0) continue;
|
||||
|
||||
const type = parts[typeIndex];
|
||||
const value = parseQuotaNumber(parts[typeIndex + 1]);
|
||||
const limit = parseQuotaNumber(parts[typeIndex + 2]);
|
||||
const percent = parseQuotaNumber(parts[typeIndex + 3]);
|
||||
|
||||
if (type === 'STORAGE') {
|
||||
usedStorageKb = value;
|
||||
storageLimitKb = limit;
|
||||
storagePercent = percent;
|
||||
}
|
||||
|
||||
if (type === 'MESSAGE') {
|
||||
messageCount = value;
|
||||
messageLimit = limit;
|
||||
}
|
||||
}
|
||||
|
||||
const usedBytes = Math.max(0, usedStorageKb ?? 0) * 1024;
|
||||
const quotaBytes = storageLimitKb && storageLimitKb > 0 ? storageLimitKb * 1024 : null;
|
||||
|
||||
// Dovecot's % column is integer-rounded. For the UI we calculate a more useful
|
||||
// value ourselves, so small accounts do not always show exactly 0%.
|
||||
const calculatedPercent = quotaBytes && quotaBytes > 0 ? (usedBytes / quotaBytes) * 100 : null;
|
||||
|
||||
return {
|
||||
usedBytes,
|
||||
quotaBytes,
|
||||
quotaPercent: calculatedPercent ?? storagePercent,
|
||||
messageCount,
|
||||
messageLimit,
|
||||
};
|
||||
}
|
||||
|
||||
export class DmsService {
|
||||
async listAccounts(): Promise<DmsAccount[]> {
|
||||
const { stdout } = await run('docker', ['exec', config.dmsContainer, 'setup', 'email', 'list']);
|
||||
@@ -58,26 +118,13 @@ export class DmsService {
|
||||
}
|
||||
}
|
||||
|
||||
async getMailboxUsageBytes(email: string): Promise<number> {
|
||||
const domain = domainFromEmail(email);
|
||||
const local = localPartFromEmail(email);
|
||||
const candidates = [
|
||||
join(config.mailDataPath, domain, local),
|
||||
join(config.mailDataPath, domain, `${local}/`),
|
||||
join(config.mailDataPath, domain, `${local}/Maildir`),
|
||||
join(config.mailDataPath, email),
|
||||
];
|
||||
|
||||
for (const p of candidates) {
|
||||
try {
|
||||
await stat(p);
|
||||
const { stdout } = await run('du', ['-sb', p], 60000);
|
||||
const value = parseInt(stdout.trim().split(/\s+/)[0] ?? '0', 10);
|
||||
return Number.isFinite(value) ? value : 0;
|
||||
} catch {
|
||||
// try next path
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
async getMailboxQuota(email: string): Promise<DmsQuota> {
|
||||
const normalized = normalizeEmail(email);
|
||||
const { stdout } = await run(
|
||||
'docker',
|
||||
['exec', config.dmsContainer, 'doveadm', 'quota', 'get', '-u', normalized],
|
||||
60000,
|
||||
);
|
||||
return parseDoveadmQuota(stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user