feat: Update storage stats with seed file and collection thumbnails (#2767)

Resolves https://github.com/webrecorder/browsertrix/issues/2733

## Changes

- Always displays storage size breakdown in dashboard panel
- Includes "Miscellaneous" storage size
- Fixes storage meter bar background color (tested with
https://www.color-blindness.com/coblis-color-blindness-simulator/)
This commit is contained in:
sua yoo 2025-07-28 23:01:17 -07:00 committed by GitHub
parent 0c8c397fca
commit ed580c41e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 101 additions and 33 deletions

View File

@ -4,11 +4,12 @@ Your **Dashboard** delivers key statistics about the org's resource usage. You c
## Storage
The storage panel displays the total size and count of archived items and browser profiles.
For organizations with a set storage quota, the storage panel displays a visual breakdown of how much space the organization has left and how much has been taken up by all types of archived items and browser profiles. To view additional information about each item, hover over its section in the graph.
For organizations with no storage limits the storage panel displays the total size and count of all types of archived items and browser profiles.
For all organizations the storage panel displays the total number of archived items.
??? Info "Miscellaneous storage"
You may see an additional _Miscellaneous_ size depending on your crawl workflow and collection configuration. _Miscellaneous_ is the total size of all supplementary files in use by your organization, such as [workflow URL list files](./workflow-setup.md#page-urls) and [custom collection thumbnails](./presentation-sharing.md#thumbnail).
## Crawling

View File

@ -35,6 +35,8 @@ type Metrics = {
storageUsedCrawls: number;
storageUsedUploads: number;
storageUsedProfiles: number;
storageUsedSeedFiles: number;
storageUsedThumbnails: number;
storageQuotaBytes: number;
archivedItemCount: number;
crawlCount: number;
@ -80,10 +82,12 @@ export class Dashboard extends BtrixElement {
private readonly colors = {
default: tw`text-neutral-600`,
crawls: tw`text-green-600`,
uploads: tw`text-sky-600`,
browserProfiles: tw`text-indigo-600`,
crawls: tw`text-lime-500`,
uploads: tw`text-sky-500`,
archivedItems: tw`text-primary-500`,
browserProfiles: tw`text-orange-500`,
runningTime: tw`text-blue-600`,
misc: tw`text-gray-400`,
};
private readonly collections = new Task(this, {
@ -126,7 +130,6 @@ export class Dashboard extends BtrixElement {
}
render() {
const hasQuota = Boolean(this.metrics?.storageQuotaBytes);
const quotaReached =
this.metrics &&
this.metrics.storageQuotaBytes > 0 &&
@ -251,11 +254,12 @@ export class Dashboard extends BtrixElement {
<dl>
${this.renderStat({
value: metrics.crawlCount,
secondaryValue: hasQuota
? ""
: this.localize.bytes(metrics.storageUsedCrawls),
secondaryValue: this.localize.bytes(
metrics.storageUsedCrawls,
),
singleLabel: msg("Crawl"),
pluralLabel: msg("Crawls"),
iconProps: {
name: "gear-wide-connected",
class: this.colors.crawls,
@ -266,11 +270,12 @@ export class Dashboard extends BtrixElement {
})}
${this.renderStat({
value: metrics.uploadCount,
secondaryValue: hasQuota
? ""
: this.localize.bytes(metrics.storageUsedUploads),
secondaryValue: this.localize.bytes(
metrics.storageUsedUploads,
),
singleLabel: msg("Upload"),
pluralLabel: msg("Uploads"),
iconProps: { name: "upload", class: this.colors.uploads },
button: {
url: "/items/upload",
@ -278,9 +283,9 @@ export class Dashboard extends BtrixElement {
})}
${this.renderStat({
value: metrics.profileCount,
secondaryValue: hasQuota
? ""
: this.localize.bytes(metrics.storageUsedProfiles),
secondaryValue: this.localize.bytes(
metrics.storageUsedProfiles,
),
singleLabel: msg("Browser Profile"),
pluralLabel: msg("Browser Profiles"),
iconProps: {
@ -291,21 +296,39 @@ export class Dashboard extends BtrixElement {
url: "/browser-profiles",
},
})}
${metrics.storageUsedSeedFiles || metrics.storageUsedThumbnails
? this.renderMiscStorage(metrics)
: nothing}
<sl-divider
style="--spacing:var(--sl-spacing-small)"
></sl-divider>
${this.renderStat({
value: metrics.archivedItemCount,
secondaryValue: hasQuota
? ""
: this.localize.bytes(metrics.storageUsedBytes),
singleLabel: msg("Archived Item"),
pluralLabel: msg("Archived Items"),
iconProps: { name: "file-zip-fill" },
iconProps: {
name: "file-zip-fill",
class: this.colors.archivedItems,
},
button: {
url: "/items",
},
})}
${when(
metrics.storageUsedBytes && !metrics.storageQuotaBytes,
() => html`
${this.renderStat({
value: this.localize.bytes(metrics.storageUsedBytes, {
compactDisplay: "short",
}),
singleLabel: msg("Total"),
iconProps: {
name: "database-fill",
},
})}
`,
)}
</dl>
`,
)}
@ -536,6 +559,35 @@ export class Dashboard extends BtrixElement {
`;
}
private renderMiscStorage(metrics: Metrics) {
return html`
<div class="mb-2 flex items-center gap-2 last:mb-0">
<dt class="mr-auto flex items-center tabular-nums">
<sl-icon
class=${clsx(tw`mr-2 text-base`, this.colors.misc)}
name="box2"
></sl-icon>
${msg("Miscellaneous")}
<btrix-popover
content=${msg(
"Total size of all supplementary files in use by your organization, such as workflow URL list files and custom collection thumbnails.",
)}
>
<sl-icon
name="info-circle"
class="ml-1.5 text-neutral-500"
></sl-icon>
</btrix-popover>
</dt>
<dd class="font-monostyle text-xs text-neutral-500">
${this.localize.bytes(
metrics.storageUsedSeedFiles + metrics.storageUsedThumbnails,
)}
</dd>
</div>
`;
}
private renderNoPublicCollections() {
if (!this.org || !this.metrics) return;
@ -576,10 +628,19 @@ export class Dashboard extends BtrixElement {
const hasQuota = Boolean(metrics.storageQuotaBytes);
const isStorageFull =
hasQuota && metrics.storageUsedBytes >= metrics.storageQuotaBytes;
const renderBar = (value: number, label: string, color: string) => html`
const misc = metrics.storageUsedSeedFiles + metrics.storageUsedThumbnails;
const renderBar = (
value: number,
label: string,
colorClassname: string,
) => html`
<btrix-meter-bar
value=${(value / metrics.storageUsedBytes) * 100}
style="--background-color:var(--sl-color-${color}-400)"
style="--background-color:var(--sl-color-${colorClassname.replace(
"text-",
"",
)})"
>
<div class="text-center">
<div>${label}</div>
@ -608,7 +669,7 @@ export class Dashboard extends BtrixElement {
${this.localize.bytes(
metrics.storageQuotaBytes - metrics.storageUsedBytes,
)}
${msg("Available")}
${msg("available")}
`
: "",
)}
@ -643,6 +704,9 @@ export class Dashboard extends BtrixElement {
this.colors.browserProfiles,
),
)}
${when(misc, () =>
renderBar(misc, msg("Miscellaneous"), this.colors.misc),
)}
<div slot="available" class="flex-1">
<sl-tooltip class="text-center">
<div slot="content">
@ -935,11 +999,11 @@ export class Dashboard extends BtrixElement {
}
private renderStat(stat: {
value: number | string | TemplateResult;
value: number | string | TemplateResult | null;
secondaryValue?: number | string | TemplateResult;
button?: { label?: string | TemplateResult; url: string };
singleLabel: string;
pluralLabel: string;
pluralLabel?: string;
iconProps: { name: string; library?: string; class?: string };
}) {
const { value, iconProps } = stat;
@ -955,7 +1019,9 @@ export class Dashboard extends BtrixElement {
library=${ifDefined(iconProps.library)}
></sl-icon>
<dt class="order-last">
${value === 1 ? stat.singleLabel : stat.pluralLabel}
${value === 1
? stat.singleLabel
: stat.pluralLabel ?? stat.singleLabel}
</dt>
<dd class="mr-1">
${typeof value === "number" ? this.localize.number(value) : value}
@ -972,13 +1038,14 @@ export class Dashboard extends BtrixElement {
${when(
stat.button,
(button) =>
html`<btrix-button size="x-small" href=${`${this.navigate.orgBasePath}${button.url}`} @click=${this.navigate.link}
>${
button.label ??
html`<sl-tooltip content=${msg("View All")} placement="right"
><sl-icon name="arrow-right-circle"></sl-icon
></sl-tooltip>`
}</sl-button
html`<btrix-button
size="x-small"
href=${`${this.navigate.orgBasePath}${button.url}`}
@click=${this.navigate.link}
>${button.label ??
html`<sl-tooltip content=${msg("View All")} placement="right"
><sl-icon name="arrow-right-circle"></sl-icon
></sl-tooltip>`}</btrix-button
>`,
)}
</div>