diff --git a/backend/btrixcloud/colls.py b/backend/btrixcloud/colls.py index 94b6dc03a3..79dbe14e5a 100644 --- a/backend/btrixcloud/colls.py +++ b/backend/btrixcloud/colls.py @@ -537,6 +537,7 @@ async def list_collections( sort_direction: int = 1, name: Optional[str] = None, name_prefix: Optional[str] = None, + has_dedupe_index: Optional[bool] = None, access: Optional[str] = None, headers: Optional[dict] = None, ): @@ -554,6 +555,9 @@ async def list_collections( regex_pattern = f"^{name_prefix}" match_query["name"] = {"$regex": regex_pattern, "$options": "i"} + if has_dedupe_index is not None: + match_query["hasDedupeIndex"] = has_dedupe_index + if public_colls_out: match_query["access"] = CollAccessType.PUBLIC elif access: @@ -1121,6 +1125,7 @@ async def list_collection_all( sortDirection: int = 1, name: Optional[str] = None, namePrefix: Optional[str] = None, + hasDedupeIndex: Optional[bool] = None, access: Optional[str] = None, ): # pylint: disable=duplicate-code @@ -1132,6 +1137,7 @@ async def list_collection_all( sort_direction=sortDirection, name=name, name_prefix=namePrefix, + has_dedupe_index=hasDedupeIndex, access=access, headers=dict(request.headers), ) diff --git a/frontend/src/assets/icons/database-dedupe-fill.svg b/frontend/src/assets/icons/database-dedupe-fill.svg new file mode 100644 index 0000000000..9fb5f4ce9a --- /dev/null +++ b/frontend/src/assets/icons/database-dedupe-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/database-dedupe.svg b/frontend/src/assets/icons/database-dedupe.svg new file mode 100644 index 0000000000..929d56559c --- /dev/null +++ b/frontend/src/assets/icons/database-dedupe.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/icons/file-earmark-scan.svg b/frontend/src/assets/icons/file-earmark-scan.svg new file mode 100644 index 0000000000..408f744403 --- /dev/null +++ b/frontend/src/assets/icons/file-earmark-scan.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/src/assets/icons/file-earmark-scan2.svg b/frontend/src/assets/icons/file-earmark-scan2.svg new file mode 100644 index 0000000000..4fa3f2d470 --- /dev/null +++ b/frontend/src/assets/icons/file-earmark-scan2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/src/assets/icons/file-earmark-scan3.svg b/frontend/src/assets/icons/file-earmark-scan3.svg new file mode 100644 index 0000000000..a2988adca8 --- /dev/null +++ b/frontend/src/assets/icons/file-earmark-scan3.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/src/components/ui/data-grid/data-grid.ts b/frontend/src/components/ui/data-grid/data-grid.ts index 636232aa66..767a5f1107 100644 --- a/frontend/src/components/ui/data-grid/data-grid.ts +++ b/frontend/src/components/ui/data-grid/data-grid.ts @@ -149,9 +149,14 @@ export class DataGrid< return html` - + ${this.formControlLabel + ? html`` + : nothing}
= { + page: parsePage(new URLSearchParams(location.search).get("page")), + pageSize: INITIAL_PAGE_SIZE, + }; + + protected willUpdate(changedProperties: PropertyValues): void { + // Reset pagination when tab is hidden + if (changedProperties.has("visible") && !this.visible) { + this.pagination = { + ...this.pagination, + page: 1, + }; + } + } + + private readonly sources = new Task(this, { + task: async ([pagination], { signal }) => { + return this.getCollections({ ...pagination }, signal); + }, + args: () => [this.pagination] as const, + }); + + render() { + return html` ${this.sources.render({ + initial: loadingPanel, + pending: () => + this.sources.value + ? this.renderTable(this.sources.value) + : loadingPanel(), + complete: this.renderTable, + })}`; + } + + private readonly renderTable = ( + collections: APIPaginatedList, + ) => { + return html` + + + ${msg("Source Type")} + + ${msg("Name")} + + ${msg("Archived Items")} + + + ${msg("Index Entries")} + + + ${msg("Index Size")} + + + ${msg("Purgeable Entries")} + + + ${msg("Actions")} + + + + ${collections.items.map( + (item) => html` + + + ${msg("Collection")} + + ${item.name} + ${this.localize.number(item.crawlCount)} + ${noData} + ${noData} + ${noData} + + + + + + + + ${msg("Clear Index")} + ${msg("Delete Index")} + + + + + `, + )} + + + ${when( + collections.total > collections.pageSize, + () => html` +
+ { + this.pagination = { + ...this.pagination, + page: e.detail.page, + }; + }} + > +
+ `, + )} `; + }; + + private async getCollections( + params: APIPaginationQuery, + signal: AbortSignal, + ) { + const query = queryString.stringify({ + pageSize: 2, + ...params, + hasDedupeIndex: true, + }); + return this.api.fetch>( + `/orgs/${this.orgId}/collections?${query}`, + { + signal, + }, + ); + } +} diff --git a/frontend/src/pages/org/settings/settings.ts b/frontend/src/pages/org/settings/settings.ts index 896f06f331..3f353dc630 100644 --- a/frontend/src/pages/org/settings/settings.ts +++ b/frontend/src/pages/org/settings/settings.ts @@ -26,10 +26,16 @@ import { tw } from "@/utils/tailwind"; import "./components/general"; import "./components/billing"; import "./components/crawling-defaults"; +import "./components/deduplication"; const styles = unsafeCSS(stylesheet); -type Tab = "information" | "members" | "billing" | "crawling-defaults"; +type Tab = + | "information" + | "members" + | "billing" + | "crawling-defaults" + | "deduplication"; type User = { email: string; role: AccessCode; @@ -92,6 +98,7 @@ export class OrgSettings extends BtrixElement { members: msg("Members"), billing: msg("Billing"), "crawling-defaults": msg("Crawling Defaults"), + deduplication: msg("Deduplication"), }; } @@ -158,6 +165,7 @@ export class OrgSettings extends BtrixElement { this.renderTab("billing", "settings/billing"), )} ${this.renderTab("crawling-defaults", "settings/crawling-defaults")} + ${this.renderTab("deduplication", "settings/deduplication")} ${this.renderPanelHeader({ title: msg("General") })} @@ -194,7 +202,7 @@ export class OrgSettings extends BtrixElement { ${this.renderPanelHeader({ - title: msg("Crawling Defaults"), + title: this.tabLabels["crawling-defaults"], actions: html` + + ${this.renderPanelHeader({ title: msg("Deduplication Sources") })} + + `; } @@ -247,6 +259,11 @@ export class OrgSettings extends BtrixElement { "crawling-defaults", () => html``, ], + [ + "deduplication", + () => + html``, + ], ])} ${this.tabLabels[name]} diff --git a/frontend/src/pages/org/settings/templates/loading-panel.ts b/frontend/src/pages/org/settings/templates/loading-panel.ts new file mode 100644 index 0000000000..2ba16032ba --- /dev/null +++ b/frontend/src/pages/org/settings/templates/loading-panel.ts @@ -0,0 +1,7 @@ +import { html } from "lit"; + +export const loadingPanel = () => { + return html`
+ +
`; +}; diff --git a/frontend/src/theme.stylesheet.css b/frontend/src/theme.stylesheet.css index 2ad070739a..6efffed63b 100644 --- a/frontend/src/theme.stylesheet.css +++ b/frontend/src/theme.stylesheet.css @@ -241,6 +241,10 @@ @apply part-[base]:text-success part-[base]:hover:bg-success-50 part-[base]:hover:text-success-700 part-[base]:focus-visible:bg-success-50; } + .menu-item-warning { + @apply part-[base]:text-warning part-[base]:hover:bg-warning-50 part-[base]:hover:text-warning-700 part-[base]:focus-visible:bg-warning-50; + } + .menu-item-danger { @apply part-[base]:text-danger part-[base]:hover:bg-danger-50 part-[base]:hover:text-danger-700 part-[base]:focus-visible:bg-danger-50; }