fix: storage quota not shown with Stalwart #577
This commit is contained in:
@@ -1679,7 +1679,8 @@ function handleEmailSubmissionGet(args: MethodArgs, callId: string): MethodResul
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleQuotaGet(_args: MethodArgs, callId: string): MethodResult {
|
function handleQuotaGet(_args: MethodArgs, callId: string): MethodResult {
|
||||||
return ['Quota/get', { accountId: ACCOUNT_ID, state: nextState(), list: [{ resourceType: 'mail', scope: 'mail', used: 52428800, hardLimit: 1073741824 }], notFound: [] }, callId];
|
// mirroring Stalwart: resourceType "octets", scope "account"
|
||||||
|
return ['Quota/get', { accountId: ACCOUNT_ID, state: nextState(), list: [{ id: 'quota-1', resourceType: 'octets', scope: 'account', types: ['Email', 'SieveScript'], used: 52428800, hardLimit: 1073741824 }], notFound: [] }, callId];
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleVacationResponseGet(_args: MethodArgs, callId: string): MethodResult {
|
function handleVacationResponseGet(_args: MethodArgs, callId: string): MethodResult {
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ function StorageQuotaCircle({ quota, usagePercent }: { quota: { used: number; to
|
|||||||
return () => document.removeEventListener("mousedown", handleClick);
|
return () => document.removeEventListener("mousedown", handleClick);
|
||||||
}, [open, updatePosition]);
|
}, [open, updatePosition]);
|
||||||
|
|
||||||
const free = quota.total - quota.used;
|
// Usage can legitimately exceed the quota (e.g. limit lowered after the fact)
|
||||||
|
const free = Math.max(0, quota.total - quota.used);
|
||||||
const strokeColor = usagePercent > 90
|
const strokeColor = usagePercent > 90
|
||||||
? "stroke-destructive"
|
? "stroke-destructive"
|
||||||
: usagePercent > 70
|
: usagePercent > 70
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function AccountSettings() {
|
|||||||
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
||||||
const draggedIndexRef = useRef<number | null>(null);
|
const draggedIndexRef = useRef<number | null>(null);
|
||||||
|
|
||||||
const quotaPercentage = quota ? Math.round((quota.used / quota.total) * 100) : 0;
|
const quotaPercentage = quota && quota.total > 0 ? Math.min(Math.round((quota.used / quota.total) * 100), 100) : 0;
|
||||||
const displayName = primaryIdentity?.name || account?.displayName || (isDemoMode ? 'Demo User' : undefined);
|
const displayName = primaryIdentity?.name || account?.displayName || (isDemoMode ? 'Demo User' : undefined);
|
||||||
const email = primaryIdentity?.email || account?.email || username;
|
const email = primaryIdentity?.email || account?.email || username;
|
||||||
const max = getMaxAccounts();
|
const max = getMaxAccounts();
|
||||||
|
|||||||
+11
-2
@@ -45,6 +45,7 @@ interface JMAPAccount {
|
|||||||
interface JMAPQuota {
|
interface JMAPQuota {
|
||||||
resourceType?: string;
|
resourceType?: string;
|
||||||
scope?: string;
|
scope?: string;
|
||||||
|
types?: string[];
|
||||||
used?: number;
|
used?: number;
|
||||||
hardLimit?: number;
|
hardLimit?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
@@ -878,16 +879,24 @@ export class JMAPClient implements IJMAPClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getQuota(): Promise<{ used: number; total: number } | null> {
|
async getQuota(): Promise<{ used: number; total: number } | null> {
|
||||||
|
if (!this.supportsQuota()) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.request([
|
const response = await this.request([
|
||||||
["Quota/get", {
|
["Quota/get", {
|
||||||
accountId: this.accountId,
|
accountId: this.accountId,
|
||||||
}, "0"]
|
}, "0"]
|
||||||
]);
|
], ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:quota"]);
|
||||||
|
|
||||||
if (response.methodResponses?.[0]?.[0] === "Quota/get") {
|
if (response.methodResponses?.[0]?.[0] === "Quota/get") {
|
||||||
const quotas = (response.methodResponses[0][1].list || []) as JMAPQuota[];
|
const quotas = (response.methodResponses[0][1].list || []) as JMAPQuota[];
|
||||||
const mailQuota = quotas.find((q) => q.resourceType === "mail" || q.scope === "mail");
|
const coversMail = (q: JMAPQuota) =>
|
||||||
|
!q.types?.length || q.types.some((t) => t === "Email" || t === "Mail");
|
||||||
|
// storage quotas use resourceType "octets" (e.g. Stalwart, with
|
||||||
|
// scope "account"); fall back to the pre-RFC "mail" shape for older servers.
|
||||||
|
const mailQuota =
|
||||||
|
quotas.find((q) => q.resourceType === "octets" && coversMail(q)) ||
|
||||||
|
quotas.find((q) => q.resourceType === "mail" || q.scope === "mail");
|
||||||
|
|
||||||
if (mailQuota) {
|
if (mailQuota) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
+1
-1
@@ -223,7 +223,7 @@ export function truncateText(text: string, maxLength: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatFileSize(bytes: number): string {
|
export function formatFileSize(bytes: number): string {
|
||||||
if (bytes === 0) return '0 Bytes';
|
if (!Number.isFinite(bytes) || bytes <= 0) return '0 Bytes';
|
||||||
|
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
|||||||
Reference in New Issue
Block a user