From 42ebfd303dd41e25908495d23ce039a21a505cb0 Mon Sep 17 00:00:00 2001 From: Tessa Walsh Date: Fri, 22 Nov 2024 11:47:44 -0500 Subject: [PATCH 01/12] Make changes to collections to support publicly listed collections (#2164) Fixes #2158 - Adds `Organization.listPublicCollections` field and API endpoint to update it - Replaces `Collection.isPublic` boolean with `Collection.access` (values: `private`, `unlisted`, `public`) and add database migration - Update frontend to use `Collection.access` instead of `isPublic`, otherwise not changing current behavior --------- Co-authored-by: sua yoo --- backend/btrixcloud/colls.py | 10 ++- backend/btrixcloud/db.py | 2 +- .../migration_0036_coll_visibility.py | 49 +++++++++++++ backend/btrixcloud/models.py | 26 ++++++- backend/btrixcloud/orgs.py | 26 ++++++- backend/test/test_collections.py | 69 +++++++++++++++++-- backend/test/test_org.py | 18 +++++ .../collections/collection-metadata-dialog.ts | 6 +- frontend/src/pages/org/collection-detail.ts | 23 ++++--- frontend/src/pages/org/collections-list.ts | 15 ++-- frontend/src/types/collection.ts | 8 ++- 11 files changed, 224 insertions(+), 28 deletions(-) create mode 100644 backend/btrixcloud/migrations/migration_0036_coll_visibility.py diff --git a/backend/btrixcloud/colls.py b/backend/btrixcloud/colls.py index d0fdd43a7c..23640404bd 100644 --- a/backend/btrixcloud/colls.py +++ b/backend/btrixcloud/colls.py @@ -89,7 +89,7 @@ async def add_collection(self, oid: UUID, coll_in: CollIn): name=coll_in.name, description=coll_in.description, modified=modified, - isPublic=coll_in.isPublic, + access=coll_in.access, ) try: await self.collections.insert_one(coll.to_dict()) @@ -189,7 +189,7 @@ async def get_collection( """Get collection by id""" query: dict[str, object] = {"_id": coll_id} if public_only: - query["isPublic"] = True + query["access"] = {"$in": ["public", "unlisted"]} result = await self.collections.find_one(query) if not result: @@ -210,6 +210,7 @@ async def list_collections( sort_direction: int = 1, name: Optional[str] = None, name_prefix: Optional[str] = None, + access: Optional[str] = None, ): """List all collections for org""" # pylint: disable=too-many-locals, duplicate-code @@ -226,6 +227,9 @@ async def list_collections( regex_pattern = f"^{name_prefix}" match_query["name"] = {"$regex": regex_pattern, "$options": "i"} + if access: + match_query["access"] = access + aggregate = [{"$match": match_query}] if sort_by: @@ -427,6 +431,7 @@ async def list_collection_all( sortDirection: int = 1, name: Optional[str] = None, namePrefix: Optional[str] = None, + access: Optional[str] = None, ): collections, total = await colls.list_collections( org.id, @@ -436,6 +441,7 @@ async def list_collection_all( sort_direction=sortDirection, name=name, name_prefix=namePrefix, + access=access, ) return paginated_format(collections, total, page, pageSize) diff --git a/backend/btrixcloud/db.py b/backend/btrixcloud/db.py index 91b2bc7057..f453442191 100644 --- a/backend/btrixcloud/db.py +++ b/backend/btrixcloud/db.py @@ -17,7 +17,7 @@ from .migrations import BaseMigration -CURR_DB_VERSION = "0035" +CURR_DB_VERSION = "0036" # ============================================================================ diff --git a/backend/btrixcloud/migrations/migration_0036_coll_visibility.py b/backend/btrixcloud/migrations/migration_0036_coll_visibility.py new file mode 100644 index 0000000000..48b97ce314 --- /dev/null +++ b/backend/btrixcloud/migrations/migration_0036_coll_visibility.py @@ -0,0 +1,49 @@ +""" +Migration 0036 -- collection access +""" + +from btrixcloud.migrations import BaseMigration + + +MIGRATION_VERSION = "0036" + + +class Migration(BaseMigration): + """Migration class.""" + + # pylint: disable=unused-argument + def __init__(self, mdb, **kwargs): + super().__init__(mdb, migration_version=MIGRATION_VERSION) + + async def migrate_up(self): + """Perform migration up. + + Move from Collection.isPublic cool to Collection.access enum + """ + colls_mdb = self.mdb["collections"] + + # Set non-public collections to private + try: + await colls_mdb.update_many( + {"isPublic": False}, + {"$set": {"access": "private"}, "$unset": {"isPublic": 1}}, + ) + # pylint: disable=broad-exception-caught + except Exception as err: + print( + f"Error migrating private collections: {err}", + flush=True, + ) + + # Set public collections to unlisted + try: + await colls_mdb.update_many( + {"isPublic": True}, + {"$set": {"access": "unlisted"}, "$unset": {"isPublic": 1}}, + ) + # pylint: disable=broad-exception-caught + except Exception as err: + print( + f"Error migrating public unlisted collections: {err}", + flush=True, + ) diff --git a/backend/btrixcloud/models.py b/backend/btrixcloud/models.py index 2209c5f79e..f3642b12dc 100644 --- a/backend/btrixcloud/models.py +++ b/backend/btrixcloud/models.py @@ -1055,6 +1055,15 @@ class UpdateUpload(UpdateCrawl): ### COLLECTIONS ### +# ============================================================================ +class CollAccessType(str, Enum): + """Collection access types""" + + PRIVATE = "private" + UNLISTED = "unlisted" + PUBLIC = "public" + + # ============================================================================ class Collection(BaseMongoModel): """Org collection structure""" @@ -1071,7 +1080,7 @@ class Collection(BaseMongoModel): # Sorted by count, descending tags: Optional[List[str]] = [] - isPublic: Optional[bool] = False + access: CollAccessType = CollAccessType.PRIVATE # ============================================================================ @@ -1082,7 +1091,7 @@ class CollIn(BaseModel): description: Optional[str] = None crawlIds: Optional[List[str]] = [] - isPublic: bool = False + access: CollAccessType = CollAccessType.PRIVATE # ============================================================================ @@ -1098,7 +1107,7 @@ class UpdateColl(BaseModel): name: Optional[str] = None description: Optional[str] = None - isPublic: Optional[bool] = None + access: Optional[CollAccessType] = None # ============================================================================ @@ -1371,6 +1380,13 @@ class OrgReadOnlyUpdate(BaseModel): readOnlyReason: Optional[str] = None +# ============================================================================ +class OrgListPublicCollectionsUpdate(BaseModel): + """Organization listPublicCollections update""" + + listPublicCollections: bool + + # ============================================================================ class OrgWebhookUrls(BaseModel): """Organization webhook URLs""" @@ -1439,6 +1455,8 @@ class OrgOut(BaseMongoModel): allowedProxies: list[str] = [] crawlingDefaults: Optional[CrawlConfigDefaults] = None + listPublicCollections: bool = False + # ============================================================================ class Organization(BaseMongoModel): @@ -1494,6 +1512,8 @@ class Organization(BaseMongoModel): allowedProxies: list[str] = [] crawlingDefaults: Optional[CrawlConfigDefaults] = None + listPublicCollections: bool = False + def is_owner(self, user): """Check if user is owner""" return self._is_auth(user, UserRole.OWNER) diff --git a/backend/btrixcloud/orgs.py b/backend/btrixcloud/orgs.py index e5b1ae4751..f2f8897265 100644 --- a/backend/btrixcloud/orgs.py +++ b/backend/btrixcloud/orgs.py @@ -78,6 +78,7 @@ RemovedResponse, OrgSlugsResponse, OrgImportResponse, + OrgListPublicCollectionsUpdate, ) from .pagination import DEFAULT_PAGE_SIZE, paginated_format from .utils import ( @@ -940,7 +941,7 @@ async def get_org_metrics(self, org: Organization) -> dict[str, int]: ) collections_count = await self.colls_db.count_documents({"oid": org.id}) public_collections_count = await self.colls_db.count_documents( - {"oid": org.id, "isPublic": True} + {"oid": org.id, "access": {"$in": ["public", "unlisted"]}} ) return { @@ -997,6 +998,16 @@ async def update_read_only_on_cancel( ) return res is not None + async def update_list_public_collections( + self, org: Organization, list_public_collections: bool + ): + """Update listPublicCollections field on organization""" + res = await self.orgs.find_one_and_update( + {"_id": org.id}, + {"$set": {"listPublicCollections": list_public_collections}}, + ) + return res is not None + async def export_org( self, org: Organization, user_manager: UserManager ) -> StreamingResponse: @@ -1553,6 +1564,19 @@ async def update_read_only_on_cancel( return {"updated": True} + @router.post( + "/list-public-collections", + tags=["organizations", "collections"], + response_model=UpdatedResponse, + ) + async def update_list_public_collections( + update: OrgListPublicCollectionsUpdate, + org: Organization = Depends(org_owner_dep), + ): + await ops.update_list_public_collections(org, update.listPublicCollections) + + return {"updated": True} + @router.post( "/event-webhook-urls", tags=["organizations"], response_model=UpdatedResponse ) diff --git a/backend/test/test_collections.py b/backend/test/test_collections.py index d0210e24da..9be94f7249 100644 --- a/backend/test/test_collections.py +++ b/backend/test/test_collections.py @@ -58,7 +58,7 @@ def test_create_public_collection( json={ "crawlIds": [crawler_crawl_id], "name": PUBLIC_COLLECTION_NAME, - "isPublic": True, + "access": "public", }, ) assert r.status_code == 200 @@ -73,7 +73,7 @@ def test_create_public_collection( f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}", headers=crawler_auth_headers, ) - assert r.json()["isPublic"] + assert r.json()["access"] == "public" def test_create_collection_taken_name( @@ -311,12 +311,31 @@ def test_collection_public(crawler_auth_headers, default_org_id): ) assert r.status_code == 404 - # make public + # make public and test replay headers r = requests.patch( f"{API_PREFIX}/orgs/{default_org_id}/collections/{_coll_id}", headers=crawler_auth_headers, json={ - "isPublic": True, + "access": "public", + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_coll_id}/public/replay.json", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + assert r.headers["Access-Control-Allow-Origin"] == "*" + assert r.headers["Access-Control-Allow-Headers"] == "*" + + # make unlisted and test replay headers + r = requests.patch( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_coll_id}", + headers=crawler_auth_headers, + json={ + "access": "unlisted", }, ) assert r.status_code == 200 @@ -335,7 +354,7 @@ def test_collection_public(crawler_auth_headers, default_org_id): f"{API_PREFIX}/orgs/{default_org_id}/collections/{_coll_id}", headers=crawler_auth_headers, json={ - "isPublic": False, + "access": "private", }, ) @@ -346,6 +365,24 @@ def test_collection_public(crawler_auth_headers, default_org_id): assert r.status_code == 404 +def test_collection_access_invalid_value(crawler_auth_headers, default_org_id): + r = requests.patch( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_coll_id}", + headers=crawler_auth_headers, + json={ + "access": "invalid", + }, + ) + assert r.status_code == 422 + + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_coll_id}", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + assert r.json()["access"] == "private" + + def test_add_upload_to_collection(crawler_auth_headers, default_org_id): with open(os.path.join(curr_dir, "data", "example.wacz"), "rb") as fh: r = requests.put( @@ -429,6 +466,7 @@ def test_list_collections( assert first_coll["totalSize"] > 0 assert first_coll["modified"] assert first_coll["tags"] == ["wr-test-2", "wr-test-1"] + assert first_coll["access"] == "private" second_coll = [coll for coll in items if coll["name"] == SECOND_COLLECTION_NAME][0] assert second_coll["id"] @@ -440,6 +478,7 @@ def test_list_collections( assert second_coll["totalSize"] > 0 assert second_coll["modified"] assert second_coll["tags"] == ["wr-test-2"] + assert second_coll["access"] == "private" def test_remove_upload_from_collection(crawler_auth_headers, default_org_id): @@ -525,6 +564,26 @@ def test_filter_sort_collections( assert coll["oid"] == default_org_id assert coll.get("description") is None + # Test filtering by access + name_prefix = name_prefix.upper() + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/collections?access=public", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + assert data["total"] == 1 + + items = data["items"] + assert len(items) == 1 + + coll = items[0] + assert coll["id"] + assert coll["name"] == PUBLIC_COLLECTION_NAME + assert coll["oid"] == default_org_id + assert coll.get("description") is None + assert coll["access"] == "public" + # Test sorting by name, ascending (default) r = requests.get( f"{API_PREFIX}/orgs/{default_org_id}/collections?sortBy=name", diff --git a/backend/test/test_org.py b/backend/test/test_org.py index ab652aa266..7ef3d0ae9b 100644 --- a/backend/test/test_org.py +++ b/backend/test/test_org.py @@ -697,6 +697,24 @@ def test_update_read_only(admin_auth_headers, default_org_id): assert data["readOnlyReason"] == "" +def test_update_list_public_collections(admin_auth_headers, default_org_id): + # Test that default is false + r = requests.get(f"{API_PREFIX}/orgs/{default_org_id}", headers=admin_auth_headers) + assert r.json()["listPublicCollections"] is False + + # Update + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/list-public-collections", + headers=admin_auth_headers, + json={"listPublicCollections": True}, + ) + assert r.json()["updated"] + + # Test update is reflected in GET response + r = requests.get(f"{API_PREFIX}/orgs/{default_org_id}", headers=admin_auth_headers) + assert r.json()["listPublicCollections"] + + def test_sort_orgs(admin_auth_headers): # Create a few new orgs for testing r = requests.post( diff --git a/frontend/src/features/collections/collection-metadata-dialog.ts b/frontend/src/features/collections/collection-metadata-dialog.ts index aa2c307a84..ba70149f5d 100644 --- a/frontend/src/features/collections/collection-metadata-dialog.ts +++ b/frontend/src/features/collections/collection-metadata-dialog.ts @@ -14,7 +14,7 @@ import { when } from "lit/directives/when.js"; import { BtrixElement } from "@/classes/BtrixElement"; import type { Dialog } from "@/components/ui/dialog"; import type { MarkdownEditor } from "@/components/ui/markdown-editor"; -import type { Collection } from "@/types/collection"; +import { CollectionAccess, type Collection } from "@/types/collection"; import { isApiError } from "@/utils/api"; import { maxLengthValidator } from "@/utils/form"; @@ -180,7 +180,9 @@ export class CollectionMetadataDialog extends BtrixElement { const body = JSON.stringify({ name, description, - isPublic: Boolean(isPublic), + access: !isPublic + ? CollectionAccess.Private + : CollectionAccess.Unlisted, }); let path = `/orgs/${this.orgId}/collections`; let method = "POST"; diff --git a/frontend/src/pages/org/collection-detail.ts b/frontend/src/pages/org/collection-detail.ts index 62d5364c05..edb689e3c5 100644 --- a/frontend/src/pages/org/collection-detail.ts +++ b/frontend/src/pages/org/collection-detail.ts @@ -17,7 +17,7 @@ import type { APIPaginationQuery, APISortQuery, } from "@/types/api"; -import type { Collection } from "@/types/collection"; +import { CollectionAccess, type Collection } from "@/types/collection"; import type { ArchivedItem, Crawl, Upload } from "@/types/crawler"; import type { CrawlState } from "@/types/crawlState"; import { pluralOf } from "@/utils/pluralize"; @@ -103,7 +103,7 @@ export class CollectionDetail extends BtrixElement {
- ${this.collection?.isPublic + ${this.collection?.access === CollectionAccess.Unlisted ? html`
${when( - this.isCrawler || this.collection?.isPublic, + this.isCrawler || + this.collection?.access !== CollectionAccess.Private, () => html` ${ - this.collection?.isPublic + this.collection?.access === CollectionAccess.Unlisted ? "" : html`

${msg( @@ -270,7 +271,8 @@ export class CollectionDetail extends BtrixElement { () => html`

void this.onTogglePublic((e.target as SlCheckbox).checked)} >${msg("Collection is Shareable")} - ${when(this.collection?.isPublic, this.renderShareInfo)} + ${when(this.collection?.access === CollectionAccess.Unlisted, this.renderShareInfo)}
(this.showShareInfo = false)} >${msg("Done")} - ${!this.collection?.isPublic + ${this.collection?.access === CollectionAccess.Private ? html` ( `/orgs/${this.orgId}/collections/${this.collectionId}`, { method: "PATCH", - body: JSON.stringify({ isPublic }), + body: JSON.stringify({ access }), }, ); if (res.updated && this.collection) { - this.collection = { ...this.collection, isPublic }; + this.collection = { ...this.collection, access }; } } diff --git a/frontend/src/pages/org/collections-list.ts b/frontend/src/pages/org/collections-list.ts index 0c59635093..d541fe243e 100644 --- a/frontend/src/pages/org/collections-list.ts +++ b/frontend/src/pages/org/collections-list.ts @@ -15,7 +15,11 @@ import type { PageChangeEvent } from "@/components/ui/pagination"; import type { CollectionSavedEvent } from "@/features/collections/collection-metadata-dialog"; import { pageHeader } from "@/layouts/pageHeader"; import type { APIPaginatedList, APIPaginationQuery } from "@/types/api"; -import type { Collection, CollectionSearchValues } from "@/types/collection"; +import { + CollectionAccess, + type Collection, + type CollectionSearchValues, +} from "@/types/collection"; import type { UnderlyingFunction } from "@/types/utils"; import { isApiError } from "@/utils/api"; import { pluralOf } from "@/utils/pluralize"; @@ -498,7 +502,7 @@ export class CollectionsList extends BtrixElement { class="cursor-pointer select-none rounded border shadow transition-all focus-within:bg-neutral-50 hover:bg-neutral-50 hover:shadow-none" > - ${col.isPublic + ${col.access === CollectionAccess.Unlisted ? html` - ${!col.isPublic + ${col.access === CollectionAccess.Private ? html` Date: Wed, 27 Nov 2024 10:34:25 -0500 Subject: [PATCH 02/12] Add public API endpoint for public collections (#2174) Fixes #1051 If org with provided slug doesn't exist or no public collections exist for that org, return same 404 response with a detail of "public_profile_not_found" to prevent people from using public endpoint to determine whether an org exists. Endpoint is `GET /api/public-collections/` (no auth needed) to avoid collisions with existing org and collection endpoints. --- backend/btrixcloud/colls.py | 35 +++++++++ backend/btrixcloud/models.py | 34 +++++++-- backend/btrixcloud/orgs.py | 31 +++++--- backend/test/test_collections.py | 121 ++++++++++++++++++++++++++++++- backend/test/test_org.py | 20 +---- 5 files changed, 207 insertions(+), 34 deletions(-) diff --git a/backend/btrixcloud/colls.py b/backend/btrixcloud/colls.py index 23640404bd..411e659ac9 100644 --- a/backend/btrixcloud/colls.py +++ b/backend/btrixcloud/colls.py @@ -30,6 +30,9 @@ UpdatedResponse, SuccessResponse, CollectionSearchValuesResponse, + OrgPublicCollections, + PublicOrgDetails, + CollAccessType, ) from .utils import dt_now @@ -395,6 +398,30 @@ async def add_successful_crawl_to_collections(self, crawl_id: str, cid: UUID): ) await self.update_crawl_collections(crawl_id) + async def get_org_public_collections(self, org_slug: str): + """List public collections for org""" + try: + org = await self.orgs.get_org_by_slug(org_slug) + # pylint: disable=broad-exception-caught + except Exception: + # pylint: disable=raise-missing-from + raise HTTPException(status_code=404, detail="public_profile_not_found") + + if not org.enablePublicProfile: + raise HTTPException(status_code=404, detail="public_profile_not_found") + + collections, _ = await self.list_collections( + org.id, access=CollAccessType.PUBLIC + ) + + public_org_details = PublicOrgDetails( + name=org.name, + description=org.publicDescription or "", + url=org.publicUrl or "", + ) + + return OrgPublicCollections(org=public_org_details, collections=collections) + # ============================================================================ # pylint: disable=too-many-locals @@ -582,4 +609,12 @@ async def download_collection( ): return await colls.download_collection(coll_id, org) + @app.get( + "/public-collections/{org_slug}", + tags=["collections"], + response_model=OrgPublicCollections, + ) + async def get_org_public_collections(org_slug: str): + return await colls.get_org_public_collections(org_slug) + return colls diff --git a/backend/btrixcloud/models.py b/backend/btrixcloud/models.py index f3642b12dc..f18412c2df 100644 --- a/backend/btrixcloud/models.py +++ b/backend/btrixcloud/models.py @@ -1152,6 +1152,24 @@ class RenameOrg(BaseModel): slug: Optional[str] = None +# ============================================================================ +class PublicOrgDetails(BaseModel): + """Model for org details that are available in public profile""" + + name: str + description: str = "" + url: str = "" + + +# ============================================================================ +class OrgPublicCollections(BaseModel): + """Model for listing public collections in org""" + + org: PublicOrgDetails + + collections: List[CollOut] = [] + + # ============================================================================ class OrgStorageRefs(BaseModel): """Input model for setting primary storage + optional replicas""" @@ -1381,10 +1399,12 @@ class OrgReadOnlyUpdate(BaseModel): # ============================================================================ -class OrgListPublicCollectionsUpdate(BaseModel): - """Organization listPublicCollections update""" +class OrgPublicProfileUpdate(BaseModel): + """Organization enablePublicProfile update""" - listPublicCollections: bool + enablePublicProfile: Optional[bool] = None + publicDescription: Optional[str] = None + publicUrl: Optional[str] = None # ============================================================================ @@ -1455,7 +1475,9 @@ class OrgOut(BaseMongoModel): allowedProxies: list[str] = [] crawlingDefaults: Optional[CrawlConfigDefaults] = None - listPublicCollections: bool = False + enablePublicProfile: bool = False + publicDescription: str = "" + publicUrl: str = "" # ============================================================================ @@ -1512,7 +1534,9 @@ class Organization(BaseMongoModel): allowedProxies: list[str] = [] crawlingDefaults: Optional[CrawlConfigDefaults] = None - listPublicCollections: bool = False + enablePublicProfile: bool = False + publicDescription: Optional[str] = None + publicUrl: Optional[str] = None def is_owner(self, user): """Check if user is owner""" diff --git a/backend/btrixcloud/orgs.py b/backend/btrixcloud/orgs.py index f2f8897265..8ba4d3508b 100644 --- a/backend/btrixcloud/orgs.py +++ b/backend/btrixcloud/orgs.py @@ -78,7 +78,7 @@ RemovedResponse, OrgSlugsResponse, OrgImportResponse, - OrgListPublicCollectionsUpdate, + OrgPublicProfileUpdate, ) from .pagination import DEFAULT_PAGE_SIZE, paginated_format from .utils import ( @@ -295,6 +295,14 @@ async def get_org_by_id(self, oid: UUID) -> Organization: return Organization.from_dict(res) + async def get_org_by_slug(self, slug: str) -> Organization: + """Get an org by id""" + res = await self.orgs.find_one({"slug": slug}) + if not res: + raise HTTPException(status_code=400, detail="invalid_org_slug") + + return Organization.from_dict(res) + async def get_default_org(self) -> Organization: """Get default organization""" res = await self.orgs.find_one({"default": True}) @@ -998,13 +1006,18 @@ async def update_read_only_on_cancel( ) return res is not None - async def update_list_public_collections( - self, org: Organization, list_public_collections: bool + async def update_public_profile( + self, org: Organization, update: OrgPublicProfileUpdate ): - """Update listPublicCollections field on organization""" + """Update or enable/disable organization's public profile""" + query = update.dict(exclude_unset=True) + + if len(query) == 0: + raise HTTPException(status_code=400, detail="no_update_data") + res = await self.orgs.find_one_and_update( {"_id": org.id}, - {"$set": {"listPublicCollections": list_public_collections}}, + {"$set": query}, ) return res is not None @@ -1565,15 +1578,15 @@ async def update_read_only_on_cancel( return {"updated": True} @router.post( - "/list-public-collections", + "/public-profile", tags=["organizations", "collections"], response_model=UpdatedResponse, ) - async def update_list_public_collections( - update: OrgListPublicCollectionsUpdate, + async def update_public_profile( + update: OrgPublicProfileUpdate, org: Organization = Depends(org_owner_dep), ): - await ops.update_list_public_collections(org, update.listPublicCollections) + await ops.update_public_profile(org, update) return {"updated": True} diff --git a/backend/test/test_collections.py b/backend/test/test_collections.py index 9be94f7249..4faf42540d 100644 --- a/backend/test/test_collections.py +++ b/backend/test/test_collections.py @@ -4,7 +4,7 @@ from zipfile import ZipFile, ZIP_STORED from tempfile import TemporaryFile -from .conftest import API_PREFIX +from .conftest import API_PREFIX, NON_DEFAULT_ORG_NAME, NON_DEFAULT_ORG_SLUG from .utils import read_in_chunks COLLECTION_NAME = "Test collection" @@ -15,6 +15,7 @@ _coll_id = None _second_coll_id = None +_public_coll_id = None upload_id = None modified = None @@ -66,6 +67,7 @@ def test_create_public_collection( assert data["added"] assert data["name"] == PUBLIC_COLLECTION_NAME + global _public_coll_id _public_coll_id = data["id"] # Verify that it is public @@ -725,6 +727,123 @@ def test_filter_sort_collections( assert r.json()["detail"] == "invalid_sort_direction" +def test_list_public_collections( + crawler_auth_headers, + admin_auth_headers, + default_org_id, + non_default_org_id, + crawler_crawl_id, + admin_crawl_id, +): + # Create new public collection + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/collections", + headers=crawler_auth_headers, + json={ + "crawlIds": [crawler_crawl_id], + "name": "Second public collection", + "access": "public", + }, + ) + assert r.status_code == 200 + second_public_coll_id = r.json()["id"] + + # Get default org slug + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + org_slug = data["slug"] + org_name = data["name"] + + # Verify that public profile isn't enabled + assert data["enablePublicProfile"] is False + assert data["publicDescription"] == "" + assert data["publicUrl"] == "" + + # Try listing public collections without org public profile enabled + r = requests.get(f"{API_PREFIX}/public-collections/{org_slug}") + assert r.status_code == 404 + assert r.json()["detail"] == "public_profile_not_found" + + # Enable public profile on org + public_description = "This is a test public org!" + public_url = "https://example.com" + + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/public-profile", + headers=admin_auth_headers, + json={ + "enablePublicProfile": True, + "publicDescription": public_description, + "publicUrl": public_url, + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}", + headers=admin_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + assert data["enablePublicProfile"] + assert data["publicDescription"] == public_description + assert data["publicUrl"] == public_url + + # List public collections with no auth (no public profile) + r = requests.get(f"{API_PREFIX}/public-collections/{org_slug}") + assert r.status_code == 200 + data = r.json() + + org_data = data["org"] + assert org_data["name"] == org_name + assert org_data["description"] == public_description + assert org_data["url"] == public_url + + collections = data["collections"] + assert len(collections) == 2 + for collection in collections: + assert collection["id"] in (_public_coll_id, second_public_coll_id) + assert collection["access"] == "public" + + # Test non-existing slug - it should return a 404 but not reveal + # whether or not an org exists with that slug + r = requests.get(f"{API_PREFIX}/public-collections/nonexistentslug") + assert r.status_code == 404 + assert r.json()["detail"] == "public_profile_not_found" + + +def test_list_public_collections_no_colls(non_default_org_id, admin_auth_headers): + # Test existing org that's not public - should return same 404 as + # if org doesn't exist + r = requests.get(f"{API_PREFIX}/public-collections/{NON_DEFAULT_ORG_SLUG}") + assert r.status_code == 404 + assert r.json()["detail"] == "public_profile_not_found" + + # Enable public profile on org with zero public collections + r = requests.post( + f"{API_PREFIX}/orgs/{non_default_org_id}/public-profile", + headers=admin_auth_headers, + json={ + "enablePublicProfile": True, + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + # List public collections with no auth - should still get profile even + # with no public collections + r = requests.get(f"{API_PREFIX}/public-collections/{NON_DEFAULT_ORG_SLUG}") + assert r.status_code == 200 + data = r.json() + assert data["org"]["name"] == NON_DEFAULT_ORG_NAME + assert data["collections"] == [] + + def test_delete_collection(crawler_auth_headers, default_org_id, crawler_crawl_id): # Delete second collection r = requests.delete( diff --git a/backend/test/test_org.py b/backend/test/test_org.py index 7ef3d0ae9b..a3de79c5cd 100644 --- a/backend/test/test_org.py +++ b/backend/test/test_org.py @@ -17,7 +17,7 @@ def test_ensure_only_one_default_org(admin_auth_headers): r = requests.get(f"{API_PREFIX}/orgs", headers=admin_auth_headers) data = r.json() - assert data["total"] == 1 + assert data["total"] == 2 orgs = data["items"] default_orgs = [org for org in orgs if org["default"]] @@ -697,24 +697,6 @@ def test_update_read_only(admin_auth_headers, default_org_id): assert data["readOnlyReason"] == "" -def test_update_list_public_collections(admin_auth_headers, default_org_id): - # Test that default is false - r = requests.get(f"{API_PREFIX}/orgs/{default_org_id}", headers=admin_auth_headers) - assert r.json()["listPublicCollections"] is False - - # Update - r = requests.post( - f"{API_PREFIX}/orgs/{default_org_id}/list-public-collections", - headers=admin_auth_headers, - json={"listPublicCollections": True}, - ) - assert r.json()["updated"] - - # Test update is reflected in GET response - r = requests.get(f"{API_PREFIX}/orgs/{default_org_id}", headers=admin_auth_headers) - assert r.json()["listPublicCollections"] - - def test_sort_orgs(admin_auth_headers): # Create a few new orgs for testing r = requests.post( From 6e48f957f961ccd1070b47200f53660743a0d479 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 3 Dec 2024 19:04:29 -0800 Subject: [PATCH 03/12] feat: Public org profile page (#2172) - Enables creating a public org profile page with description and website at `/profile/` - Updates current "Overview" page to be "Dashboard", found under `/dashboard` - Organizes org "General" settings tab by "General", "Profile", and "Developer Tools" - Adds sign up banner to log in page for consistent CTA banners - Updates copy and docs to support changes - Allows user to set collection to private, public, or unlisted - Adds route for public collection page with basic page layout - Refactors copy button to abstract clipboard functionality --------- Co-authored-by: Henry Wilkinson Co-authored-by: emma --- .../docs/docs/user-guide/crawl-workflows.md | 4 +- .../docs/docs/user-guide/getting-started.md | 2 +- frontend/docs/docs/user-guide/org-settings.md | 12 +- frontend/docs/docs/user-guide/overview.md | 2 +- frontend/src/components/index.ts | 1 + frontend/src/components/not-found.ts | 35 +- frontend/src/components/ui/index.ts | 2 + frontend/src/components/ui/link.ts | 50 +++ frontend/src/components/ui/url-input.ts | 75 ++++ frontend/src/components/verified-badge.ts | 26 ++ frontend/src/controllers/navigate.ts | 3 +- .../crawl-workflows/workflow-editor.ts | 9 +- frontend/src/index.test.ts | 9 +- frontend/src/index.ts | 121 +++++-- frontend/src/layouts/columns.ts | 2 +- frontend/src/pages/account-settings.ts | 7 +- frontend/src/pages/admin.ts | 5 +- frontend/src/pages/invite/accept.test.ts | 8 +- frontend/src/pages/invite/accept.ts | 9 +- frontend/src/pages/invite/join.test.ts | 6 +- frontend/src/pages/invite/join.ts | 7 +- frontend/src/pages/log-in.ts | 55 ++- frontend/src/pages/org/index.ts | 79 ++--- frontend/src/pages/org/profile.ts | 328 ++++++++++++++++++ .../pages/org/settings/components/profile.ts | 158 +++++++++ frontend/src/pages/org/settings/settings.ts | 121 ++++--- frontend/src/routes.ts | 33 +- frontend/src/theme.stylesheet.css | 8 +- frontend/src/types/org.ts | 3 + frontend/src/utils/APIRouter.ts | 29 +- frontend/src/utils/form.ts | 20 ++ frontend/src/utils/state.ts | 7 +- 32 files changed, 1038 insertions(+), 198 deletions(-) create mode 100644 frontend/src/components/ui/link.ts create mode 100644 frontend/src/components/ui/url-input.ts create mode 100644 frontend/src/components/verified-badge.ts create mode 100644 frontend/src/pages/org/profile.ts create mode 100644 frontend/src/pages/org/settings/components/profile.ts diff --git a/frontend/docs/docs/user-guide/crawl-workflows.md b/frontend/docs/docs/user-guide/crawl-workflows.md index f5396e020b..ecf039c0dc 100644 --- a/frontend/docs/docs/user-guide/crawl-workflows.md +++ b/frontend/docs/docs/user-guide/crawl-workflows.md @@ -8,7 +8,7 @@ You can create, view, search for, and run crawl workflows from the **Crawling** ## Create a Crawl Workflow -Create new crawl workflows from the **Crawling** page, or the _Create New ..._ shortcut from **Overview**. +Create new crawl workflows from the **Crawling** page, or the _Create New ..._ shortcut from **Dashboard**. ### Choose what to crawl @@ -38,4 +38,4 @@ Re-running a crawl workflow can be useful to capture a website as it changes ove ## Status -Finished crawl workflows inherit the [status of the last archived item they created](archived-items.md#status). Crawl workflows that are in progress maintain their [own statuses](./running-crawl.md#crawl-workflow-status). \ No newline at end of file +Finished crawl workflows inherit the [status of the last archived item they created](archived-items.md#status). Crawl workflows that are in progress maintain their [own statuses](./running-crawl.md#crawl-workflow-status). diff --git a/frontend/docs/docs/user-guide/getting-started.md b/frontend/docs/docs/user-guide/getting-started.md index c08d6bed55..d0b83a9ccb 100644 --- a/frontend/docs/docs/user-guide/getting-started.md +++ b/frontend/docs/docs/user-guide/getting-started.md @@ -12,7 +12,7 @@ To start crawling with hosted Browsertrix, you'll need a Browsertrix account. [S ## Starting the crawl -Once you've logged in you should see your org [overview](overview.md). If you land somewhere else, navigate to **Overview**. +Once you've logged in you should see your org [overview](overview.md). If you land somewhere else, navigate to **Dashboard**. 1. Tap the _Create New..._ shortcut and select **Crawl Workflow**. 2. Choose **Page List**. We'll get into the details of the options [later](./crawl-workflows.md), but this is a good starting point for a simple crawl. diff --git a/frontend/docs/docs/user-guide/org-settings.md b/frontend/docs/docs/user-guide/org-settings.md index caf842b5d6..5c5f50d37a 100644 --- a/frontend/docs/docs/user-guide/org-settings.md +++ b/frontend/docs/docs/user-guide/org-settings.md @@ -4,7 +4,17 @@ Settings that apply to the entire organization are found in the **Settings** pag ## General -Change your organization's name and URL identifier in this tab. Choose an org name that's unique and memorable, like the name of your company or organization. Org name and URLs are unique to each Browsertrix instance (for example, on browsertrix.com) and you may be asked to change the org name or URL identifier if either are already in use by another org. +### Name and URL + +Choose a display name for your org that's unique and memorable, like the name of your company, organization, or personal project. This name will be visible in the org's [public profile](#profile), if that page is enabled. + +The org URL is where you and other org members will go to view the dashboard, configure org settings, and manage all other org-related activities. Changing this URL will also update the URL of your org's public profile, if enabled. + +Org name and URLs are unique to each Browsertrix instance (for example, on `app.browsertrix.com`) and you may be prompted to change the org name or URL identifier if either are already in use by another org. + +### Profile + +Enable and configure a public profile page for your org. Once enabled, anyone on the internet with a link to your org's profile page will be able to view public information like the org name, description, and public collections. ## Billing diff --git a/frontend/docs/docs/user-guide/overview.md b/frontend/docs/docs/user-guide/overview.md index b67b5d6a08..63b55e7309 100644 --- a/frontend/docs/docs/user-guide/overview.md +++ b/frontend/docs/docs/user-guide/overview.md @@ -1,6 +1,6 @@ # View Usage Stats and Quotas -The **Overview** dashboard delivers key statistics about the org's resource usage. You can also create crawl workflows, upload archived items, create collections, and create browser profiles through the _Create New ..._ shortcut. +Your **Dashboard** delivers key statistics about the org's resource usage. You can also create crawl workflows, upload archived items, create collections, and create browser profiles through the _Create New ..._ shortcut. ## Storage diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index a3e01bfe52..40a362b8bc 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -7,3 +7,4 @@ import("./not-found"); import("./screencast"); import("./beta-badges"); import("./detail-page-title"); +import("./verified-badge"); diff --git a/frontend/src/components/not-found.ts b/frontend/src/components/not-found.ts index 6bb2fe74e2..7f64e8904e 100644 --- a/frontend/src/components/not-found.ts +++ b/frontend/src/components/not-found.ts @@ -1,17 +1,36 @@ import { localized, msg } from "@lit/localize"; -import { html, LitElement } from "lit"; +import { html } from "lit"; import { customElement } from "lit/decorators.js"; -@customElement("btrix-not-found") +import { BtrixElement } from "@/classes/BtrixElement"; + @localized() -export class NotFound extends LitElement { - createRenderRoot() { - return this; - } +@customElement("btrix-not-found") +export class NotFound extends BtrixElement { render() { return html` -
- ${msg("Page not found")} +
+

+ ${msg("Page not found")} +

+

+ ${msg("Did you click a link to get here?")} + +
+ ${msg("Or")} + + ${msg("Report a Broken Link")} + +

`; } diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts index c762e6c998..a2e674a1ff 100644 --- a/frontend/src/components/ui/index.ts +++ b/frontend/src/components/ui/index.ts @@ -5,9 +5,11 @@ import "./card"; import "./data-table"; import "./desc-list"; import "./dialog"; +import "./link"; import "./navigation"; import "./tab-group"; import "./tab-list"; +import "./url-input"; import("./code"); import("./combobox"); diff --git a/frontend/src/components/ui/link.ts b/frontend/src/components/ui/link.ts new file mode 100644 index 0000000000..c833207030 --- /dev/null +++ b/frontend/src/components/ui/link.ts @@ -0,0 +1,50 @@ +import clsx from "clsx"; +import { html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { BtrixElement } from "@/classes/BtrixElement"; + +@customElement("btrix-link") +export class Link extends BtrixElement { + @property({ type: String }) + href?: HTMLAnchorElement["href"]; + + @property({ type: String }) + target?: HTMLAnchorElement["target"]; + + @property({ type: String }) + rel?: HTMLAnchorElement["rel"]; + + @property({ type: String }) + variant: "primary" | "neutral" = "neutral"; + + render() { + if (!this.href) return; + + return html` + {} + : this.navigate.link} + > + + + `; + } +} diff --git a/frontend/src/components/ui/url-input.ts b/frontend/src/components/ui/url-input.ts new file mode 100644 index 0000000000..90cfe18830 --- /dev/null +++ b/frontend/src/components/ui/url-input.ts @@ -0,0 +1,75 @@ +// import type { SlInputEvent } from "@shoelace-style/shoelace"; +import { msg } from "@lit/localize"; +import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js"; +import { customElement, property } from "lit/decorators.js"; + +export function validURL(url: string) { + // adapted from: https://gist.github.com/dperini/729294 + return /^(?:https?:\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( + url, + ); +} + +/** + * URL input field with validation. + * + * @TODO Use types from SlInput + * + * @attr {String} name + * @attr {String} size + * @attr {String} name + * @attr {String} label + * @attr {String} value + */ +@customElement("btrix-url-input") +export class Component extends SlInput { + @property({ type: Number, reflect: true }) + minlength = 4; + + @property({ type: String, reflect: true }) + placeholder = "https://example.com"; + + connectedCallback(): void { + this.inputmode = "url"; + + super.connectedCallback(); + + this.addEventListener("sl-input", this.onInput); + this.addEventListener("sl-blur", this.onBlur); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + + this.removeEventListener("sl-input", this.onInput); + this.removeEventListener("sl-blur", this.onBlur); + } + + private readonly onInput = async () => { + console.log("input 1"); + await this.updateComplete; + + if (!this.checkValidity() && validURL(this.value)) { + this.setCustomValidity(""); + this.helpText = ""; + } + }; + + private readonly onBlur = async () => { + await this.updateComplete; + + const value = this.value; + + if (value && !validURL(value)) { + const text = msg("Please enter a valid URL."); + this.helpText = text; + this.setCustomValidity(text); + } else if ( + value && + !value.startsWith("https://") && + !value.startsWith("http://") + ) { + this.value = `https://${value}`; + } + }; +} diff --git a/frontend/src/components/verified-badge.ts b/frontend/src/components/verified-badge.ts new file mode 100644 index 0000000000..99dc1cb9b7 --- /dev/null +++ b/frontend/src/components/verified-badge.ts @@ -0,0 +1,26 @@ +import { localized, msg } from "@lit/localize"; +import { html } from "lit"; +import { customElement } from "lit/decorators.js"; + +import { BtrixElement } from "@/classes/BtrixElement"; + +@localized() +@customElement("btrix-verified-badge") +export class Component extends BtrixElement { + render() { + return html` + + ${msg( + "Verified", + )} + + `; + } +} diff --git a/frontend/src/controllers/navigate.ts b/frontend/src/controllers/navigate.ts index 87bd0a1c35..6e94f56b45 100644 --- a/frontend/src/controllers/navigate.ts +++ b/frontend/src/controllers/navigate.ts @@ -1,5 +1,6 @@ import type { ReactiveController, ReactiveControllerHost } from "lit"; +import { RouteNamespace } from "@/routes"; import appState from "@/utils/state"; export type NavigateEventDetail = { @@ -31,7 +32,7 @@ export class NavigateController implements ReactiveController { get orgBasePath() { const slug = appState.orgSlug; if (slug) { - return `/orgs/${slug}`; + return `/${RouteNamespace.PrivateOrgs}/${slug}`; } return "/"; } diff --git a/frontend/src/features/crawl-workflows/workflow-editor.ts b/frontend/src/features/crawl-workflows/workflow-editor.ts index d21da340c8..9c5ae5a926 100644 --- a/frontend/src/features/crawl-workflows/workflow-editor.ts +++ b/frontend/src/features/crawl-workflows/workflow-editor.ts @@ -44,6 +44,7 @@ import type { SelectCrawlerProxyChangeEvent } from "@/components/ui/select-crawl import type { Tab } from "@/components/ui/tab-list"; import type { TagInputEvent, TagsChangeEvent } from "@/components/ui/tag-input"; import type { TimeInputChangeEvent } from "@/components/ui/time-input"; +import { validURL } from "@/components/ui/url-input"; import { proxiesContext, type ProxiesContext } from "@/context/org"; import { type SelectBrowserProfileChangeEvent } from "@/features/browser-profiles/select-browser-profile"; import type { CollectionsChangeEvent } from "@/features/collections/collections-add"; @@ -163,13 +164,6 @@ function getLocalizedWeekDays() { ); } -function validURL(url: string) { - // adapted from: https://gist.github.com/dperini/729294 - return /^(?:https?:\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( - url, - ); -} - const trimArray = flow(uniq, compact); const urlListToArray = flow( (str?: string) => (str?.length ? str.trim().split(/\s+/g) : []), @@ -791,6 +785,7 @@ export class WorkflowEditor extends BtrixElement { ${this.formState.scopeType === ScopeType.Page ? html` ${inputCol(html` + { AppStateService.resetAll(); AuthService.broadcastChannel = new BroadcastChannel(AuthService.storageKey); window.sessionStorage.clear(); + window.localStorage.clear(); stub(window.history, "pushState"); stub(NotifyController.prototype, "toast"); }); @@ -173,7 +174,7 @@ describe("browsertrix-app", () => { expect(el.appState.orgSlug).to.equal("test-org"); }); - it("sets org slug from path", async () => { + it("sets org slug from path if user is in org", async () => { const id = self.crypto.randomUUID(); const mockOrg = { id: id, @@ -181,13 +182,13 @@ describe("browsertrix-app", () => { slug: id, role: 10, }; - stub(App.prototype, "getLocationPathname").callsFake(() => `/orgs/${id}`); - stub(App.prototype, "getUserInfo").callsFake(async () => - Promise.resolve({ + AppStateService.updateUser( + formatAPIUser({ ...mockAPIUser, orgs: [...mockAPIUser.orgs, mockOrg], }), ); + stub(App.prototype, "getLocationPathname").callsFake(() => `/orgs/${id}`); stub(AuthService.prototype, "startFreshnessCheck").callsFake(() => {}); stub(AuthService, "initSessionStorage").callsFake(async () => Promise.resolve({ diff --git a/frontend/src/index.ts b/frontend/src/index.ts index b7bae09ed5..fabe7e5014 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -21,8 +21,7 @@ import "./assets/fonts/Inter/inter.css"; import "./assets/fonts/Recursive/recursive.css"; import "./styles.css"; -import type { OrgTab } from "./pages/org"; -import { ROUTES } from "./routes"; +import { OrgTab, RouteNamespace, ROUTES } from "./routes"; import type { UserInfo, UserOrg } from "./types/user"; import APIRouter, { type ViewState } from "./utils/APIRouter"; import AuthService, { @@ -98,7 +97,7 @@ export class App extends BtrixElement { private translationReady = false; @state() - private viewState!: ViewState; + private viewState!: ViewState; @state() private fullDocsUrl = "/docs/"; @@ -112,6 +111,12 @@ export class App extends BtrixElement { @query("#userGuideDrawer") private readonly userGuideDrawer!: SlDrawer; + get isUserInCurrentOrg(): boolean { + const { slug } = this.viewState.params; + if (!this.userInfo || !slug) return false; + return Boolean(this.userInfo.orgs.some((org) => org.slug === slug)); + } + async connectedCallback() { let authState: AuthService["authState"] = null; try { @@ -123,7 +128,6 @@ export class App extends BtrixElement { if (authState) { this.authService.saveLogin(authState); } - this.syncViewState(); if (authState && !this.userInfo) { void this.fetchAndUpdateUserInfo(); } @@ -162,14 +166,30 @@ export class App extends BtrixElement { } } if (changedProperties.has("viewState")) { - if (this.viewState.route === "orgs") { + this.handleViewStateChange( + changedProperties.get("viewState") as undefined | ViewState, + ); + } + } + + private handleViewStateChange(prevValue?: ViewState) { + switch (this.viewState.route) { + case "orgs": + // Orgs index page don't exist right now this.routeTo(this.navigate.orgBasePath); - } else if ( - changedProperties.get("viewState") && - this.viewState.route === "org" - ) { - this.updateOrgSlugIfNeeded(); + break; + case "publicOrgs": + // Public index page don't exist right now + this.routeTo("/"); + break; + case "org": { + if (prevValue) { + this.updateOrgSlugIfNeeded(); + } + break; } + default: + break; } } @@ -211,7 +231,11 @@ export class App extends BtrixElement { private updateOrgSlugIfNeeded() { const slug = this.viewState.params.slug || null; - if (this.viewState.route === "org" && slug !== this.appState.orgSlug) { + if ( + this.isUserInCurrentOrg && + this.viewState.route === "org" && + slug !== this.appState.orgSlug + ) { AppStateService.updateOrgSlug(slug); } } @@ -279,7 +303,9 @@ export class App extends BtrixElement { return html`
${this.renderNavBar()} ${this.renderAlertBanner()} -
${this.renderPage()}
+
+ ${this.renderPage()} +
${this.renderFooter()}
@@ -352,8 +378,8 @@ export class App extends BtrixElement { private renderNavBar() { const isSuperAdmin = this.userInfo?.isSuperAdmin; let homeHref = "/"; - if (!isSuperAdmin && this.appState.orgSlug) { - homeHref = this.navigate.orgBasePath; + if (!isSuperAdmin && this.appState.orgSlug && this.authState) { + homeHref = `${this.navigate.orgBasePath}/${OrgTab.Dashboard}`; } const showFullLogo = @@ -415,7 +441,11 @@ export class App extends BtrixElement { `, )}
-
+
${this.authState ? html`${this.userInfo && !isSuperAdmin ? html` @@ -470,7 +500,18 @@ export class App extends BtrixElement { ` : html` - ${this.renderSignUpLink()} + ${this.viewState.route === "publicOrgProfile" + ? html` + + ${msg("Sign In")} + + ` + : nothing} ${(translatedLocales as unknown as string[]).length > 2 ? html` ${selectedOption.name.slice(0, orgNameLength)} @@ -578,7 +619,9 @@ export class App extends BtrixElement { @sl-select=${(e: CustomEvent<{ item: { value: string } }>) => { const { value } = e.detail.item; if (value) { - this.routeTo(`/orgs/${value}`); + this.routeTo( + `/${RouteNamespace.PrivateOrgs}/${value}/${OrgTab.Dashboard}`, + ); } else { if (this.userInfo) { this.clearSelectedOrg(); @@ -652,9 +695,9 @@ export class App extends BtrixElement { private renderFooter() { return html`
-
+ -
+
${this.version ? html` -
+
this.version} @@ -735,7 +782,7 @@ export class App extends BtrixElement { case "loginWithRedirect": case "forgotPassword": return html``; } + case "publicOrgProfile": + return html``; + case "accountSettings": return html``; - default: return this.renderNotFoundPage(); } @@ -939,7 +989,10 @@ export class App extends BtrixElement { }); if (!detail.api) { - this.routeTo(detail.redirectUrl || this.navigate.orgBasePath); + this.routeTo( + detail.redirectUrl || + `${this.navigate.orgBasePath}/${OrgTab.Dashboard}`, + ); } if (detail.firstLogin) { diff --git a/frontend/src/layouts/columns.ts b/frontend/src/layouts/columns.ts index d65b4401dd..99ae4ead6b 100644 --- a/frontend/src/layouts/columns.ts +++ b/frontend/src/layouts/columns.ts @@ -29,7 +29,7 @@ export function infoCol(content: TemplateResult | string, topPadding: string) { class="block h-4 w-4 flex-shrink-0 text-base" name="info-circle" > -
${content}
+
${content}
`; } diff --git a/frontend/src/pages/account-settings.ts b/frontend/src/pages/account-settings.ts index f18adfa6ca..ff045a5b98 100644 --- a/frontend/src/pages/account-settings.ts +++ b/frontend/src/pages/account-settings.ts @@ -386,14 +386,13 @@ export class AccountSettings extends BtrixElement {
diff --git a/frontend/src/pages/admin.ts b/frontend/src/pages/admin.ts index a3e8b6a174..9d289e0ee0 100644 --- a/frontend/src/pages/admin.ts +++ b/frontend/src/pages/admin.ts @@ -7,6 +7,7 @@ import { customElement, state } from "lit/decorators.js"; import { BtrixElement } from "@/classes/BtrixElement"; import type { InviteSuccessDetail } from "@/features/accounts/invite-form"; import type { APIUser } from "@/index"; +import { OrgTab, RouteNamespace } from "@/routes"; import type { APIPaginatedList } from "@/types/api"; import { isApiError } from "@/utils/api"; import { maxLengthValidator } from "@/utils/form"; @@ -68,7 +69,9 @@ export class Admin extends BtrixElement { if (this.userInfo.isSuperAdmin) { this.initSuperAdmin(); } else if (this.userInfo.orgs.length) { - this.navigate.to(`/orgs/${this.userInfo.orgs[0].slug}`); + this.navigate.to( + `/${RouteNamespace.PrivateOrgs}/${this.userInfo.orgs[0].slug}/${OrgTab.Dashboard}`, + ); } else { this.navigate.to(`/account/settings`); } diff --git a/frontend/src/pages/invite/accept.test.ts b/frontend/src/pages/invite/accept.test.ts index 5d954d285a..fb1f655aaf 100644 --- a/frontend/src/pages/invite/accept.test.ts +++ b/frontend/src/pages/invite/accept.test.ts @@ -196,7 +196,9 @@ describe("btrix-accept-invite", () => { await oneEvent(orgFormEl, "btrix-org-updated"); - expect(el.navigate.to).to.have.been.calledWith("/orgs/fake-org-slug-2"); + expect(el.navigate.to).to.have.been.calledWith( + "/orgs/fake-org-slug-2/dashboard", + ); }); }); @@ -294,7 +296,9 @@ describe("btrix-accept-invite", () => { await el._onAccept(); - expect(el.navigate.to).to.have.calledWith("/orgs/fake-org-name"); + expect(el.navigate.to).to.have.calledWith( + "/orgs/fake-org-name/dashboard", + ); }); it("redirects to home on decline", async () => { diff --git a/frontend/src/pages/invite/accept.ts b/frontend/src/pages/invite/accept.ts index f27bd55a0a..fb2f8528f2 100644 --- a/frontend/src/pages/invite/accept.ts +++ b/frontend/src/pages/invite/accept.ts @@ -8,6 +8,7 @@ import { renderInviteMessage } from "./ui/inviteMessage"; import { BtrixElement } from "@/classes/BtrixElement"; import type { APIUser } from "@/index"; import type { OrgUpdatedDetail } from "@/pages/invite/ui/org-form"; +import { OrgTab, RouteNamespace } from "@/routes"; import type { UserOrg, UserOrgInviteInfo } from "@/types/user"; import { isApiError } from "@/utils/api"; import { AppStateService } from "@/utils/state"; @@ -126,7 +127,9 @@ export class AcceptInvite extends BtrixElement { e: CustomEvent, ) => { e.stopPropagation(); - this.navigate.to(`/orgs/${e.detail.data.slug}`); + this.navigate.to( + `/${RouteNamespace.PrivateOrgs}/${e.detail.data.slug}/${OrgTab.Dashboard}`, + ); }} > ` @@ -244,7 +247,9 @@ export class AcceptInvite extends BtrixElement { icon: "check2-circle", }); - this.navigate.to(`/orgs/${org.slug}`); + this.navigate.to( + `/${RouteNamespace.PrivateOrgs}/${org.slug}/${OrgTab.Dashboard}`, + ); } } catch (err) { if (isApiError(err) && err.message === "Invalid Invite Code") { diff --git a/frontend/src/pages/invite/join.test.ts b/frontend/src/pages/invite/join.test.ts index eb7ac5995f..711fa77b62 100644 --- a/frontend/src/pages/invite/join.test.ts +++ b/frontend/src/pages/invite/join.test.ts @@ -145,7 +145,9 @@ describe("btrix-join", () => { await oneEvent(orgFormEl, "btrix-org-updated"); - expect(el.navTo).to.have.been.calledWith("/orgs/fake-org-slug-2"); + expect(el.navTo).to.have.been.calledWith( + "/orgs/fake-org-slug-2/dashboard", + ); }); }); @@ -190,7 +192,7 @@ describe("btrix-join", () => { await el.updateComplete; - expect(el.navTo).to.have.been.calledWith("/orgs/fake-org-name"); + expect(el.navTo).to.have.been.calledWith("/orgs/fake-org-name/dashboard"); }); }); }); diff --git a/frontend/src/pages/invite/join.ts b/frontend/src/pages/invite/join.ts index 3998e935bf..ccb99ae048 100644 --- a/frontend/src/pages/invite/join.ts +++ b/frontend/src/pages/invite/join.ts @@ -6,6 +6,7 @@ import { renderInviteMessage } from "./ui/inviteMessage"; import type { SignUpSuccessDetail } from "@/features/accounts/sign-up-form"; import type { OrgUpdatedDetail } from "@/pages/invite/ui/org-form"; +import { OrgTab, RouteNamespace } from "@/routes"; import type { UserOrg, UserOrgInviteInfo } from "@/types/user"; import AuthService, { type LoggedInEventDetail } from "@/utils/AuthService"; import LiteElement, { html } from "@/utils/LiteElement"; @@ -76,7 +77,9 @@ export class Join extends LiteElement { e: CustomEvent, ) => { e.stopPropagation(); - this.navTo(`/orgs/${e.detail.data.slug}`); + this.navTo( + `/${RouteNamespace.PrivateOrgs}/${e.detail.data.slug}/${OrgTab.Dashboard}`, + ); }} > ` @@ -171,7 +174,7 @@ export class Join extends LiteElement { if (!inviteInfo?.firstOrgAdmin) { if (inviteInfo?.orgSlug) { - this.navTo(`/orgs/${inviteInfo.orgSlug}`); + this.navTo(`/orgs/${inviteInfo.orgSlug}/dashboard`); } else { this.navTo(this.orgBasePath); } diff --git a/frontend/src/pages/log-in.ts b/frontend/src/pages/log-in.ts index 4d7f2c1b9d..849a92eaeb 100644 --- a/frontend/src/pages/log-in.ts +++ b/frontend/src/pages/log-in.ts @@ -1,11 +1,10 @@ // cSpell:words xstate import { localized, msg } from "@lit/localize"; import { assign, createMachine, interpret } from "@xstate/fsm"; -import { html, type PropertyValues } from "lit"; +import { html, nothing, type PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { BtrixElement } from "@/classes/BtrixElement"; -import { type Routes } from "@/routes"; import { isApiError } from "@/utils/api"; import type { ViewState } from "@/utils/APIRouter"; import AuthService from "@/utils/AuthService"; @@ -144,7 +143,7 @@ const machine = createMachine( @localized() export class LogInPage extends BtrixElement { @property({ type: Object }) - viewState!: ViewState; + viewState!: ViewState; @property({ type: String }) redirectUrl?: string; @@ -159,8 +158,8 @@ export class LogInPage extends BtrixElement { this.formStateService.subscribe((state) => { this.formState = state; }); - this.formStateService.start(); + this.syncFormStateView(); void this.checkBackendInitialized(); } @@ -188,17 +187,17 @@ export class LogInPage extends BtrixElement { form = this.renderForgotPasswordForm(); link = html` ${msg("Sign in with password")}${msg("Return to Sign In")} `; } else { form = this.renderLoginForm(); link = html` ${msg("Forgot your password?")} - ${successMessage} - -
-
${form}
-
-
${link}
- +
+
+ ${successMessage} + +
+
${form}
+
+
${link}
+
+
+ ${registrationEnabled || signUpUrl + ? html` +
+ ${msg("Need an account?")} + + ${msg("Sign Up")} + +
+ ` + : nothing} `; } @@ -337,7 +352,7 @@ export class LogInPage extends BtrixElement { variant="primary" ?loading=${this.formState.value === "submittingForgotPassword"} type="submit" - >${msg("Request password reset")}${msg("Request Password Reset")} `; @@ -372,7 +387,13 @@ export class LogInPage extends BtrixElement { try { const data = await AuthService.login({ email: username, password }); - AppStateService.updateUser(formatAPIUser(data.user)); + // Check if org slug in app state matches newly logged in user + const slug = + this.orgSlug && data.user.orgs.some((org) => org.slug === this.orgSlug) + ? this.orgSlug + : data.user.orgs[0].slug; + + AppStateService.updateUser(formatAPIUser(data.user), slug); await this.updateComplete; diff --git a/frontend/src/pages/org/index.ts b/frontend/src/pages/org/index.ts index 38db12698d..e3fa1b5699 100644 --- a/frontend/src/pages/org/index.ts +++ b/frontend/src/pages/org/index.ts @@ -21,6 +21,7 @@ import type { QuotaUpdateDetail } from "@/controllers/api"; import needLogin from "@/decorators/needLogin"; import type { CollectionSavedEvent } from "@/features/collections/collection-metadata-dialog"; import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow-dialog"; +import { OrgTab, RouteNamespace } from "@/routes"; import type { ProxiesAPIResponse } from "@/types/crawler"; import type { UserOrg } from "@/types/user"; import { isApiError } from "@/utils/api"; @@ -40,6 +41,7 @@ import "./browser-profiles-detail"; import "./browser-profiles-list"; import "./settings/settings"; import "./dashboard"; +import "./profile"; import(/* webpackChunkName: "org" */ "./archived-item-qa/archived-item-qa"); import(/* webpackChunkName: "org" */ "./workflows-new"); @@ -54,19 +56,19 @@ type ArchivedItemPageParams = { collectionId?: string; }; export type OrgParams = { - home: Record; - workflows: ArchivedItemPageParams & { + [OrgTab.Dashboard]: Record; + [OrgTab.Workflows]: ArchivedItemPageParams & { scopeType?: WorkflowFormState["scopeType"]; new?: ResourceName; itemPageId?: string; qaTab?: QATab; qaRunId?: string; }; - items: ArchivedItemPageParams & { + [OrgTab.Items]: ArchivedItemPageParams & { itemType?: string; qaTab?: QATab; }; - "browser-profiles": { + [OrgTab.BrowserProfiles]: { browserProfileId?: string; browserId?: string; new?: ResourceName; @@ -78,16 +80,13 @@ export type OrgParams = { navigateUrl?: string; proxyId?: string; }; - collections: ArchivedItemPageParams & { + [OrgTab.Collections]: ArchivedItemPageParams & { collectionTab?: string; }; - settings: { + [OrgTab.Settings]: { settingsTab?: "information" | "members"; }; }; -export type OrgTab = keyof OrgParams; - -const defaultTab = "home"; const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; @@ -110,7 +109,7 @@ export class Org extends BtrixElement { params: OrgParams[OrgTab] = {}; @property({ type: String }) - orgTab: OrgTab = defaultTab; + orgTab?: OrgTab | string; @property({ type: Number }) maxScale: number = DEFAULT_MAX_SCALE; @@ -122,6 +121,12 @@ export class Org extends BtrixElement { private isCreateDialogVisible = false; connectedCallback() { + if ( + !this.orgTab || + !Object.values(OrgTab).includes(this.orgTab as OrgTab) + ) { + this.navigate.to(`${this.navigate.orgBasePath}/${OrgTab.Dashboard}`); + } super.connectedCallback(); this.addEventListener( "btrix-execution-minutes-quota-update", @@ -158,7 +163,9 @@ export class Org extends BtrixElement { // Couldn't find org with slug, redirect to first org const org = this.userInfo.orgs[0] as UserOrg | undefined; if (org) { - this.navigate.to(`/orgs/${org.slug}`); + this.navigate.to( + `/${RouteNamespace.PrivateOrgs}/${org.slug}/${OrgTab.Dashboard}`, + ); } else { this.navigate.to(`/account/settings`); } @@ -281,9 +288,9 @@ export class Org extends BtrixElement { choose( this.orgTab, [ - ["home", this.renderDashboard], + [OrgTab.Dashboard, this.renderDashboard], [ - "items", + OrgTab.Items, () => html` html` html` html` this.appState.isAdmin ? html` @@ -350,37 +357,31 @@ export class Org extends BtrixElement { > @@ -390,22 +391,14 @@ export class Org extends BtrixElement { `; } - private renderNavTab({ - tabName, - label, - path, - }: { - tabName: OrgTab; - label: string; - path: string; - }) { + private renderNavTab({ tabName, label }: { tabName: OrgTab; label: string }) { const isActive = this.orgTab === tabName; return html` @@ -442,7 +435,7 @@ export class Org extends BtrixElement { ?open=${this.openDialogName === "upload"} @request-close=${() => (this.openDialogName = undefined)} @uploaded=${() => { - if (this.orgTab === "home") { + if (this.orgTab === OrgTab.Dashboard) { this.navigate.to(`${this.navigate.orgBasePath}/items/upload`); } }} @@ -664,6 +657,8 @@ export class Org extends BtrixElement { private async onStorageQuotaUpdate(e: CustomEvent) { e.stopPropagation(); + if (!this.org) return; + const { reached } = e.detail; AppStateService.partialUpdateOrg({ @@ -677,6 +672,8 @@ export class Org extends BtrixElement { ) { e.stopPropagation(); + if (!this.org) return; + const { reached } = e.detail; AppStateService.partialUpdateOrg({ diff --git a/frontend/src/pages/org/profile.ts b/frontend/src/pages/org/profile.ts new file mode 100644 index 0000000000..51e9fa326f --- /dev/null +++ b/frontend/src/pages/org/profile.ts @@ -0,0 +1,328 @@ +import { localized, msg, str } from "@lit/localize"; +import { Task } from "@lit/task"; +import { html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { when } from "lit/directives/when.js"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import { pageTitle } from "@/layouts/pageHeader"; +import type { OrgData } from "@/types/org"; + +type OrgProfileData = { + org: { + name: string; + description: string; + url: string; + verified: boolean; + }; + collections: unknown[]; +}; + +type PublicCollection = { + name: string; + description: string; + thumbnailSrc: string; +}; + +@localized() +@customElement("btrix-org-profile") +export class OrgProfile extends BtrixElement { + @property({ type: String }) + slug?: string; + + @state() + private readonly collections: PublicCollection[] = []; + + @state() + private isPrivatePreview = false; + + readonly publicOrg = new Task(this, { + autoRun: false, + task: async ([slug]) => { + if (!slug) return; + const org = await this.fetchOrgProfile(slug); + return org; + }, + args: () => [this.slug] as const, + }); + + protected firstUpdated(): void { + void this.publicOrg.run(); + } + + render() { + if (!this.slug) { + return this.renderError(); + } + + return html` +
+ ${this.publicOrg.render({ + complete: (profile) => + profile + ? html` + ${this.renderProfile(profile)} + ${when(!this.authState, () => + this.renderSignUpCta(profile.org), + )} + ` + : this.renderError(), + error: this.renderError, + })} +
+ `; + } + + private renderPreviewBanner() { + return html` + +
+ `; + } + + private renderProfile({ org }: OrgProfileData) { + return html` + + + ${this.isPrivatePreview ? this.renderPreviewBanner() : nothing} + +
+ +
+
+ ${pageTitle(org.name)} + ${org.verified && + html``} + ${when( + this.appState.isAdmin, + () => + html`
+ + + +
`, + )} +
+ ${when( + org.description, + (description) => html` +
${description}
+ `, + )} + ${when(org.url, (urlStr) => { + let url: URL; + try { + url = new URL(urlStr); + } catch { + return nothing; + } + + return html` + + `; + })} +
+ +
+

${msg("Collections")}

+ ${when( + this.appState.isAdmin, + () => + html` + + `, + )} +
+ +
+ ${this.renderCollections(this.collections)} +
+
+ `; + } + + private renderCollections(collections: PublicCollection[]) { + if (!collections.length) { + return html` +

+ ${msg("This org doesn't have any public collections yet.")} +

+ `; + } + return html` + + `; + } + + private renderSignUpCta(org: OrgProfileData["org"]) { + const { signUpUrl } = this.appState.settings || {}; + + if (!signUpUrl) return; + + return html` +
+

+ ${when( + this.publicOrg.value, + () => html` + + ${msg(str`${org.name} is web archiving with Browsertrix.`)} + +
+ `, + )} + ${msg("Do you have web archives to share?")} + + ${msg("Get started with Browsertrix")} + +

+
+ `; + } + + private renderError() { + return html` +
+ +
+ `; + } + + private async fetchOrgProfile(slug: string): Promise { + const resp = await fetch(`/api/public-collections/${slug}`, { + headers: { "Content-Type": "application/json" }, + }); + + switch (resp.status) { + case 200: + return (await resp.json()) as OrgProfileData; + case 404: { + if (this.authState) { + // Use authenticated org data to render preview + const orgProfile = await this.getUserOrg(); + + if (orgProfile) { + this.isPrivatePreview = true; + + return orgProfile; + } + } + throw resp.status; + } + default: + throw resp.status; + } + } + + private async getUserOrg(): Promise { + try { + const userInfo = this.userInfo || (await this.api.fetch("/users/me")); + const userOrg = userInfo?.orgs.find((org) => org.slug === this.slug); + + if (!userOrg) { + return null; + } + + const org = await this.api.fetch(`/orgs/${userOrg.id}`); + + return { + org: { + name: org.name, + description: org.publicDescription || "", + url: org.publicUrl || "", + verified: false, // TODO + }, + collections: [], + }; + } catch { + return null; + } + } +} diff --git a/frontend/src/pages/org/settings/components/profile.ts b/frontend/src/pages/org/settings/components/profile.ts new file mode 100644 index 0000000000..b5af3a3582 --- /dev/null +++ b/frontend/src/pages/org/settings/components/profile.ts @@ -0,0 +1,158 @@ +import { localized, msg } from "@lit/localize"; +import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js"; +import { html } from "lit"; +import { customElement } from "lit/decorators.js"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import { columns, type Cols } from "@/layouts/columns"; +import { RouteNamespace } from "@/routes"; +import { formValidator, maxLengthValidator } from "@/utils/form"; + +@localized() +@customElement("btrix-org-settings-profile") +export class OrgSettingsProfile extends BtrixElement { + private readonly validateDescriptionMax = maxLengthValidator(150); + + render() { + const orgBaseUrl = `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ""}`; + + const cols: Cols = [ + [ + html` + +
+ + ${msg("Allow anyone to view org")} + +
+ `, + msg( + "If enabled, anyone will be able to view your org's profile page and public collections.", + ), + ], + [ + html` + + `, + msg("Write a short description to introduce your organization."), + ], + [ + html` + + `, + msg("Link to your organization's (or your personal) website."), + ], + [ + html` +
+ +
+ `, + html` + ${msg( + html`To customize this URL, + ${msg("update your Org URL in General settings")}.`, + )} + `, + ], + ]; + + return html` +

${msg("Profile")}

+ +
+
+
${columns(cols)}
+
+ + ${msg("Preview public profile page")} + + + ${msg("Save")} + +
+
+
+ `; + } + + private readonly checkFormValidity = formValidator(this); + + private async onSubmit(e: SubmitEvent) { + e.preventDefault(); + + const form = e.currentTarget as HTMLFormElement; + + if (!(await this.checkFormValidity(form))) return; + + const { enablePublicProfile, publicDescription, publicUrl } = + serialize(form); + + try { + const data = await this.api.fetch<{ updated: boolean }>( + `/orgs/${this.orgId}/public-profile`, + { + method: "POST", + body: JSON.stringify({ + enablePublicProfile: enablePublicProfile === "on", + publicDescription, + publicUrl, + }), + }, + ); + + if (!data.updated) { + throw new Error(); + } + + this.notify.toast({ + message: msg("Org profile has been updated."), + variant: "success", + icon: "check2-circle", + }); + } catch (err) { + console.debug(err); + + this.notify.toast({ + message: msg("Sorry, couldn't update org at this time."), + variant: "danger", + icon: "exclamation-octagon", + }); + } + } +} diff --git a/frontend/src/pages/org/settings/settings.ts b/frontend/src/pages/org/settings/settings.ts index 6c7abe9383..cd70229f3f 100644 --- a/frontend/src/pages/org/settings/settings.ts +++ b/frontend/src/pages/org/settings/settings.ts @@ -13,9 +13,10 @@ import { BtrixElement } from "@/classes/BtrixElement"; import type { APIUser } from "@/index"; import { columns } from "@/layouts/columns"; import { pageHeader } from "@/layouts/pageHeader"; +import { RouteNamespace } from "@/routes"; import type { APIPaginatedList } from "@/types/api"; import { isApiError } from "@/utils/api"; -import { maxLengthValidator } from "@/utils/form"; +import { formValidator, maxLengthValidator } from "@/utils/form"; import { AccessCode, isAdmin, isCrawler } from "@/utils/orgs"; import slugifyStrict from "@/utils/slugify"; import { AppStateService } from "@/utils/state"; @@ -24,6 +25,7 @@ import { formatAPIUser } from "@/utils/user"; import "./components/billing"; import "./components/crawling-defaults"; +import "./components/profile"; const styles = unsafeCSS(stylesheet); @@ -108,6 +110,31 @@ export class OrgSettings extends BtrixElement { } } + // TODO (emma) maybe upstream this into BtrixElement? + handleHashChange = (e: HashChangeEvent) => { + const { hash } = new URL(e.newURL); + if (!hash) return; + + const el = this.shadowRoot?.querySelector(hash); + + el?.focus(); + el?.scrollIntoView({ + behavior: window.matchMedia("prefers-reduced-motion: reduce").matches + ? "instant" + : "smooth", + }); + }; + + connectedCallback() { + super.connectedCallback(); + window.addEventListener("hashchange", this.handleHashChange); + } + + disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener("hashchange", this.handleHashChange); + } + render() { return html` ${pageHeader( msg("Org Settings"), @@ -181,6 +208,8 @@ export class OrgSettings extends BtrixElement { ${this.renderInformation()} + + ${this.renderApi()} ${this.renderMembers()} @@ -226,14 +255,14 @@ export class OrgSettings extends BtrixElement { private renderInformation() { if (!this.userOrg) return; - return html`
+ return html`
${columns([ [ html` `, msg( - "Name of your organization that is visible to all org members.", + "Choose a name that represents your organization, your team, or your personal web archive.", ), ], [ html` + > +
+ ${window.location.hostname}${window.location.port + ? `:${window.location.port}` + : ""}/${RouteNamespace.PrivateOrgs}/ +
+ `, msg( - "Customize your organization's web address for accessing Browsertrix.", + "Customize your org's Browsertrix URL. This will also apply to the URL to your org's public profile page, if you've enabled it.", ), ], - [ - html` - - `, - msg("Use this ID to reference this org in the Browsertrix API."), - ], ])}
@@ -298,11 +315,37 @@ export class OrgSettings extends BtrixElement { variant="primary" ?disabled=${this.isSavingOrgName} ?loading=${this.isSavingOrgName} - >${msg("Save Changes")} + ${msg("Save")} +
-
`; + `; + } + + private renderApi() { + if (!this.userOrg) return; + + return html`

+ ${msg("Developer Tools")} +

+ +
+
+ ${columns([ + [ + html` + + `, + msg("Use this ID to reference your org in the Browsertrix API."), + ], + ])} +
+
`; } private handleSlugInput(e: InputEvent) { @@ -322,12 +365,15 @@ export class OrgSettings extends BtrixElement { if (!this.org?.users) return; const columnWidths = ["1fr", "2fr", "auto", "min-content"]; - const rows = Object.entries(this.org.users).map(([_id, user]) => [ - user.name, - user.email, - this.renderUserRoleSelect(user), - this.renderRemoveMemberButton(user), - ]); + const rows = Object.entries(this.org.users).map( + ([_id, user]) => + [ + user.name, + user.email, + this.renderUserRoleSelect(user), + this.renderRemoveMemberButton(user), + ] as const, + ); return html`
>( diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index d47489923d..35b95497eb 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -1,3 +1,17 @@ +export enum OrgTab { + Dashboard = "dashboard", + Workflows = "workflows", + Items = "items", + Collections = "collections", + BrowserProfiles = "browser-profiles", + Settings = "settings", +} + +export enum RouteNamespace { + PrivateOrgs = "orgs", + PublicOrgs = "profile", +} + export const ROUTES = { home: "/", join: "/join/:token", @@ -9,22 +23,25 @@ export const ROUTES = { forgotPassword: "/log-in/forgot-password", resetPassword: "/reset-password", accountSettings: "/account/settings(/:settingsTab)", - orgs: "/orgs", + orgs: `/${RouteNamespace.PrivateOrgs}(/)`, org: [ - "/orgs/:slug", + `/${RouteNamespace.PrivateOrgs}/:slug(/)`, // Org sections: - "(/workflows(/new)(/:workflowId(/crawls/:itemId(/review/:qaTab))))", - "(/items(/:itemType(/:itemId(/review/:qaTab))))", - "(/collections(/new)(/view/:collectionId(/:collectionTab)))", - "(/browser-profiles(/profile(/browser/:browserId)(/:browserProfileId)))", - "(/settings(/:settingsTab))", + `(/${OrgTab.Dashboard})`, + `(/${OrgTab.Workflows}(/new)(/:workflowId(/crawls/:itemId(/review/:qaTab))))`, + `(/${OrgTab.Items}(/:itemType(/:itemId(/review/:qaTab))))`, + `(/${OrgTab.Collections}(/new)(/view/:collectionId(/:collectionTab)))`, + `(/${OrgTab.BrowserProfiles}(/profile(/browser/:browserId)(/:browserProfileId)))`, + `(/${OrgTab.Settings}(/:settingsTab))`, ].join(""), + publicOrgs: `/${RouteNamespace.PublicOrgs}(/)`, + publicOrgProfile: `/${RouteNamespace.PublicOrgs}/:slug(/)`, users: "/users", usersInvite: "/users/invite", crawls: "/crawls", crawl: "/crawls/crawl/:crawlId", // Redirect for https://github.com/webrecorder/browsertrix-cloud/issues/935 - awpUploadRedirect: "/orgs/:orgId/artifacts/upload/:uploadId", + awpUploadRedirect: `/${RouteNamespace.PrivateOrgs}/:orgId/artifacts/upload/:uploadId`, } as const; export type Routes = typeof ROUTES; diff --git a/frontend/src/theme.stylesheet.css b/frontend/src/theme.stylesheet.css index ee809ecdbf..2acfbacd8a 100644 --- a/frontend/src/theme.stylesheet.css +++ b/frontend/src/theme.stylesheet.css @@ -147,6 +147,10 @@ background-color: theme(colors.primary.500); } + sl-radio-button[checked]::part(button) { + background-color: theme(colors.primary.500); + } + /* Elevate select and buttons */ sl-select::part(combobox), sl-button:not([variant="text"])::part(base) { @@ -212,8 +216,8 @@ } /* Have button group take up whole width */ - sl-radio-group::part(button-group), - sl-radio-group sl-radio-button { + sl-radio-group:not([size="small"])::part(button-group), + sl-radio-group:not([size="small"]) sl-radio-button { width: 100%; min-width: min-content; } diff --git a/frontend/src/types/org.ts b/frontend/src/types/org.ts index e6759ba412..a17f467e68 100644 --- a/frontend/src/types/org.ts +++ b/frontend/src/types/org.ts @@ -93,6 +93,9 @@ export const orgDataSchema = z.object({ crawlingDefaults: crawlingDefaultsSchema.nullable(), allowSharedProxies: z.boolean(), allowedProxies: z.array(z.string()), + enablePublicProfile: z.boolean(), + publicDescription: z.string().nullable(), + publicUrl: z.string().nullable(), }); export type OrgData = z.infer; diff --git a/frontend/src/utils/APIRouter.ts b/frontend/src/utils/APIRouter.ts index c4dd060025..c71a837858 100644 --- a/frontend/src/utils/APIRouter.ts +++ b/frontend/src/utils/APIRouter.ts @@ -1,12 +1,15 @@ import queryString from "query-string"; import UrlPattern from "url-pattern"; -type Paths = { [key: string]: string }; -type Routes = { [key in keyof T]: UrlPattern }; +import type { ROUTES } from "@/routes"; -export type ViewState = { +type RouteName = keyof typeof ROUTES; +type Routes = Record; +type Paths = Record; + +export type ViewState = { // route name, e.g. "admin" - route: keyof T | null; + route: RouteName | null; // path name // e.g. "/dashboard" // e.g. "/users/abc123" @@ -20,18 +23,20 @@ export type ViewState = { data?: { [key: string]: any }; }; -export default class APIRouter { - private readonly routes: Routes; +export default class APIRouter { + private readonly routes: Routes; - constructor(paths: T) { - this.routes = {} as Routes; + constructor(paths: Paths) { + const routes: { [key: string]: UrlPattern } = {}; - for (const [name, route] of Object.entries(paths) as [keyof T, string][]) { - this.routes[name] = new UrlPattern(route); + for (const [name, route] of Object.entries(paths)) { + routes[name] = new UrlPattern(route); } + + this.routes = routes as Routes; } - match(relativePath: string): ViewState { + match(relativePath: string): ViewState { for (const [name, pattern] of Object.entries(this.routes)) { const [path, qs = ""] = relativePath.split("?"); const match = pattern.match(path); @@ -46,7 +51,7 @@ export default class APIRouter { ...match, ...queryParams, }; - return { route: name, pathname: relativePath, params }; + return { route: name as RouteName, pathname: relativePath, params }; } } diff --git a/frontend/src/utils/form.ts b/frontend/src/utils/form.ts index 38a9820eb5..fde88e1827 100644 --- a/frontend/src/utils/form.ts +++ b/frontend/src/utils/form.ts @@ -1,5 +1,7 @@ import { msg, str } from "@lit/localize"; import type { SlInput, SlTextarea } from "@shoelace-style/shoelace"; +import { getFormControls } from "@shoelace-style/shoelace/dist/utilities/form.js"; +import type { LitElement } from "lit"; import localize from "./localize"; @@ -60,3 +62,21 @@ export function maxLengthValidator(maxLength: number): MaxLengthValidator { return { helpText: validityHelpText, validate }; } + +/** + * Validate form based on whether it contains an invalid Shoelace element + * + * @TODO Refactor forms to use utility + */ +export function formValidator(el: LitElement) { + return async function checkFormValidity(form: HTMLFormElement) { + await el.updateComplete; + + const formControls = getFormControls(form); + + return ( + !form.querySelector("[data-invalid]") && + !formControls.some((el) => el.getAttribute("data-invalid") !== null) + ); + }; +} diff --git a/frontend/src/utils/state.ts b/frontend/src/utils/state.ts index c963b12ae2..4784213ef6 100644 --- a/frontend/src/utils/state.ts +++ b/frontend/src/utils/state.ts @@ -20,9 +20,6 @@ import { isAdmin, isCrawler } from "@/utils/orgs"; export { use }; -// Keyed by org ID -type Lookup = Record; - export function makeAppStateService() { // Prevent state updates from any component const { state, unlock } = locked(); @@ -41,7 +38,7 @@ export function makeAppStateService() { // TODO persist here auth: Auth | null = null; - // Store org slug in local storage in order to redirect + // Store user's org slug preference in local storage in order to redirect // to the most recently visited org on next log in // TODO move to `userPreferences` @options(persist(window.localStorage)) @@ -50,8 +47,6 @@ export function makeAppStateService() { // Org details org: OrgData | null | undefined = undefined; - orgIdLookup: Lookup | null = null; - // Since org slug is used to ID an org, use `userOrg` // to retrieve the basic org info like name and ID // before other org details are available From f60a99cc269668de461d688857517477c3e67aed Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 3 Dec 2024 19:45:52 -0800 Subject: [PATCH 04/12] feat: Make collection public (#2208) --- frontend/package.json | 2 +- frontend/src/components/ui/copy-button.ts | 53 +-- frontend/src/components/ui/dialog.ts | 19 +- frontend/src/controllers/clipboard.ts | 61 ++++ .../collections/collection-metadata-dialog.ts | 38 +- frontend/src/features/collections/index.ts | 2 + .../collections/select-collection-access.ts | 95 +++++ .../features/collections/share-collection.ts | 324 ++++++++++++++++++ frontend/src/index.ts | 48 +-- frontend/src/layouts/page.ts | 52 +++ frontend/src/layouts/pageHeader.ts | 3 +- frontend/src/pages/collections/collection.ts | 212 ++++++++++++ frontend/src/pages/collections/index.ts | 1 + frontend/src/pages/index.ts | 1 + .../archived-item-detail.ts | 7 +- frontend/src/pages/org/archived-items.ts | 15 +- frontend/src/pages/org/collection-detail.ts | 288 +++++----------- frontend/src/pages/org/collections-list.ts | 101 ++++-- frontend/src/pages/org/profile.ts | 174 +++++----- frontend/src/pages/org/workflow-detail.ts | 7 +- frontend/src/pages/org/workflows-list.ts | 7 +- frontend/src/routes.ts | 1 + frontend/src/theme.stylesheet.css | 12 + frontend/src/types/events.d.ts | 4 +- frontend/src/types/org.ts | 22 ++ frontend/yarn.lock | 8 +- 26 files changed, 1105 insertions(+), 452 deletions(-) create mode 100644 frontend/src/controllers/clipboard.ts create mode 100644 frontend/src/features/collections/select-collection-access.ts create mode 100644 frontend/src/features/collections/share-collection.ts create mode 100644 frontend/src/layouts/page.ts create mode 100644 frontend/src/pages/collections/collection.ts create mode 100644 frontend/src/pages/collections/index.ts diff --git a/frontend/package.json b/frontend/package.json index 6e9c7f4aa5..66b68a2f5c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,7 @@ "@prettier/plugin-xml": "^3.4.1", "@rollup/plugin-commonjs": "^18.0.0", "@shoelace-style/localize": "^3.2.1", - "@shoelace-style/shoelace": "~2.15.1", + "@shoelace-style/shoelace": "~2.18.0", "@tailwindcss/container-queries": "^0.1.1", "@types/color": "^3.0.2", "@types/diff": "^5.0.9", diff --git a/frontend/src/components/ui/copy-button.ts b/frontend/src/components/ui/copy-button.ts index ef642b6db7..f25b54b021 100644 --- a/frontend/src/components/ui/copy-button.ts +++ b/frontend/src/components/ui/copy-button.ts @@ -1,8 +1,9 @@ import { localized, msg } from "@lit/localize"; import { html } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +import { customElement, property } from "lit/decorators.js"; import { TailwindElement } from "@/classes/TailwindElement"; +import { ClipboardController } from "@/controllers/clipboard"; /** * Copy text to clipboard on click @@ -16,7 +17,7 @@ import { TailwindElement } from "@/classes/TailwindElement"; * value}> * ``` * - * @event on-copied + * @fires btrix-copied */ @customElement("btrix-copy-button") @localized() @@ -42,31 +43,17 @@ export class CopyButton extends TailwindElement { @property({ type: String }) size: "x-small" | "small" | "medium" = "small"; - @state() - private isCopied = false; - - timeoutId?: number; - - static copyToClipboard(value: string) { - void navigator.clipboard.writeText(value); - } - - disconnectedCallback() { - window.clearTimeout(this.timeoutId); - super.disconnectedCallback(); - } + private readonly clipboardController = new ClipboardController(this); render() { return html` @@ -87,25 +78,7 @@ export class CopyButton extends TailwindElement { private onClick() { const value = (this.getValue ? this.getValue() : this.value) || ""; - CopyButton.copyToClipboard(value); - - this.isCopied = true; - - this.dispatchEvent(new CustomEvent("on-copied", { detail: value })); - - this.timeoutId = window.setTimeout(() => { - this.isCopied = false; - const button = this.shadowRoot?.querySelector("sl-icon-button"); - button?.blur(); // Remove focus from the button to set it back to its default state - }, 3000); - } - /** - * Stop propgation of sl-tooltip events. - * Prevents bug where sl-dialog closes when tooltip closes - * https://github.com/shoelace-style/shoelace/issues/170 - */ - private stopProp(e: Event) { - e.stopPropagation(); + void this.clipboardController.copy(value); } } diff --git a/frontend/src/components/ui/dialog.ts b/frontend/src/components/ui/dialog.ts index c74e558592..5bd89544b4 100644 --- a/frontend/src/components/ui/dialog.ts +++ b/frontend/src/components/ui/dialog.ts @@ -69,17 +69,20 @@ export class Dialog extends SlDialog { // optionally re-emitting them as "sl-inner-hide" events protected createRenderRoot() { const root = super.createRenderRoot(); - root.addEventListener("sl-hide", (event: Event) => { - if (!(event.target instanceof Dialog)) { - event.stopPropagation(); - if (this.reEmitInnerSlHideEvents) { - this.dispatchEvent(new CustomEvent("sl-inner-hide", { ...event })); - } - } - }); + root.addEventListener("sl-hide", this.handleSlEvent); + root.addEventListener("sl-after-hide", this.handleSlEvent); return root; } + private readonly handleSlEvent = (event: Event) => { + if (!(event.target instanceof Dialog)) { + event.stopPropagation(); + if (this.reEmitInnerSlHideEvents) { + this.dispatchEvent(new CustomEvent("sl-inner-hide", { ...event })); + } + } + }; + /** * Submit form using external buttons to bypass * incorrect `getRootNode` in Chrome. diff --git a/frontend/src/controllers/clipboard.ts b/frontend/src/controllers/clipboard.ts new file mode 100644 index 0000000000..402baaea69 --- /dev/null +++ b/frontend/src/controllers/clipboard.ts @@ -0,0 +1,61 @@ +import { msg } from "@lit/localize"; +import type { ReactiveController, ReactiveControllerHost } from "lit"; + +export type CopiedEventDetail = string; + +export interface CopiedEventMap { + "btrix-copied": CustomEvent; +} + +/** + * Copy to clipboard + * + * @fires btrix-copied + */ +export class ClipboardController implements ReactiveController { + static readonly text = { + copy: msg("Copy"), + copied: msg("Copied"), + }; + + static copyToClipboard(value: string) { + void navigator.clipboard.writeText(value); + } + + private readonly host: ReactiveControllerHost & EventTarget; + + private timeoutId?: number; + + isCopied = false; + + constructor(host: ClipboardController["host"]) { + this.host = host; + host.addController(this); + } + + hostConnected() {} + + hostDisconnected() { + window.clearTimeout(this.timeoutId); + this.timeoutId = undefined; + } + + async copy(value: string) { + ClipboardController.copyToClipboard(value); + + this.isCopied = true; + + this.timeoutId = window.setTimeout(() => { + this.isCopied = false; + this.host.requestUpdate(); + }, 3000); + + this.host.requestUpdate(); + + await this.host.updateComplete; + + this.host.dispatchEvent( + new CustomEvent("btrix-copied", { detail: value }), + ); + } +} diff --git a/frontend/src/features/collections/collection-metadata-dialog.ts b/frontend/src/features/collections/collection-metadata-dialog.ts index ba70149f5d..ece464b459 100644 --- a/frontend/src/features/collections/collection-metadata-dialog.ts +++ b/frontend/src/features/collections/collection-metadata-dialog.ts @@ -14,6 +14,7 @@ import { when } from "lit/directives/when.js"; import { BtrixElement } from "@/classes/BtrixElement"; import type { Dialog } from "@/components/ui/dialog"; import type { MarkdownEditor } from "@/components/ui/markdown-editor"; +import type { SelectCollectionAccess } from "@/features/collections/select-collection-access"; import { CollectionAccess, type Collection } from "@/types/collection"; import { isApiError } from "@/utils/api"; import { maxLengthValidator } from "@/utils/form"; @@ -43,10 +44,20 @@ export class CollectionMetadataDialog extends BtrixElement { @query("btrix-markdown-editor") private readonly descriptionEditor?: MarkdownEditor | null; + @query("btrix-select-collection-access") + private readonly selectCollectionAccess?: SelectCollectionAccess | null; + @queryAsync("#collectionForm") private readonly form!: Promise; private readonly validateNameMax = maxLengthValidator(50); + + protected firstUpdated(): void { + if (this.open) { + this.isDialogVisible = true; + } + } + render() { return html` html` - + `, )} @@ -172,7 +167,7 @@ export class CollectionMetadataDialog extends BtrixElement { return; } - const { name, isPublic } = serialize(form); + const { name } = serialize(form); const description = this.descriptionEditor.value; this.isSubmitting = true; @@ -180,9 +175,10 @@ export class CollectionMetadataDialog extends BtrixElement { const body = JSON.stringify({ name, description, - access: !isPublic - ? CollectionAccess.Private - : CollectionAccess.Unlisted, + access: + this.selectCollectionAccess?.value || + this.collection?.access || + CollectionAccess.Private, }); let path = `/orgs/${this.orgId}/collections`; let method = "POST"; diff --git a/frontend/src/features/collections/index.ts b/frontend/src/features/collections/index.ts index 228e669a24..949a13dbc5 100644 --- a/frontend/src/features/collections/index.ts +++ b/frontend/src/features/collections/index.ts @@ -2,3 +2,5 @@ import("./collections-add"); import("./collection-items-dialog"); import("./collection-metadata-dialog"); import("./collection-workflow-list"); +import("./select-collection-access"); +import("./share-collection"); diff --git a/frontend/src/features/collections/select-collection-access.ts b/frontend/src/features/collections/select-collection-access.ts new file mode 100644 index 0000000000..b94686c1d9 --- /dev/null +++ b/frontend/src/features/collections/select-collection-access.ts @@ -0,0 +1,95 @@ +import { localized, msg } from "@lit/localize"; +import type { SlIcon, SlSelectEvent } from "@shoelace-style/shoelace"; +import { html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import { CollectionAccess } from "@/types/collection"; + +@localized() +@customElement("btrix-select-collection-access") +export class SelectCollectionAccess extends BtrixElement { + static readonly Options: Record< + CollectionAccess, + { label: string; icon: NonNullable; detail: string } + > = { + [CollectionAccess.Private]: { + label: msg("Private"), + icon: "lock", + detail: msg("Only org members can view"), + }, + [CollectionAccess.Unlisted]: { + label: msg("Unlisted"), + icon: "link-45deg", + detail: msg("Only people with the link can view"), + }, + + [CollectionAccess.Public]: { + label: msg("Public"), + icon: "globe2", + detail: msg("Anyone can view on the org's public profile"), + }, + }; + + @property({ type: String }) + value: CollectionAccess = CollectionAccess.Private; + + @property({ type: Boolean }) + readOnly = false; + + render() { + const selected = SelectCollectionAccess.Options[this.value]; + + if (this.readOnly) { + return html` + + + ${selected.detail} + + `; + } + + return html` +
+
+ ${msg("Visibility")} +
+ { + const { value } = e.detail.item; + this.value = value as CollectionAccess; + }} + > + + + ${selected.label} +
${selected.detail}
+
+ + ${Object.entries(SelectCollectionAccess.Options).map( + ([value, { label, icon, detail }]) => html` + + + ${label} + ${detail} + + `, + )} + +
+
+ `; + } +} diff --git a/frontend/src/features/collections/share-collection.ts b/frontend/src/features/collections/share-collection.ts new file mode 100644 index 0000000000..959f7096e2 --- /dev/null +++ b/frontend/src/features/collections/share-collection.ts @@ -0,0 +1,324 @@ +import { localized, msg, str } from "@lit/localize"; +import type { SlSelectEvent } from "@shoelace-style/shoelace"; +import { html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { when } from "lit/directives/when.js"; + +import { SelectCollectionAccess } from "./select-collection-access"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import { ClipboardController } from "@/controllers/clipboard"; +import { RouteNamespace } from "@/routes"; +import { CollectionAccess, type Collection } from "@/types/collection"; + +export type SelectVisibilityDetail = { + item: { value: CollectionAccess }; +}; + +/** + * @fires btrix-select + */ +@localized() +@customElement("btrix-share-collection") +export class ShareCollection extends BtrixElement { + @property({ type: String }) + collectionId = ""; + + @property({ type: Object }) + collection?: Partial; + + @state() + private showDialog = false; + + @state() + private showEmbedCode = false; + + private readonly clipboardController = new ClipboardController(this); + + private get shareLink() { + const baseUrl = `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ""}`; + if (this.collection) { + return `${baseUrl}/${this.collection.access === CollectionAccess.Private ? `${RouteNamespace.PrivateOrgs}/${this.orgSlug}/collections/view` : `${RouteNamespace.PublicOrgs}/${this.orgSlug}/collections`}/${this.collectionId}`; + } + return ""; + } + + private get publicReplaySrc() { + return new URL( + `/api/orgs/${this.collection?.oid}/collections/${this.collectionId}/public/replay.json`, + window.location.href, + ).href; + } + + public show() { + this.showDialog = true; + } + + render() { + return html` ${this.renderButton()} ${this.renderDialog()}`; + } + + private renderButton() { + if (!this.collection) { + return html` + + `; + } + + if (this.collection.access === CollectionAccess.Private) { + return html` + (this.showDialog = true)} + > + + ${msg("Share")} + + `; + } + + return html` + + + { + void this.clipboardController.copy(this.shareLink); + }} + > + + + ${msg("Copy Link")} + + + + + + + { + this.showEmbedCode = true; + this.showDialog = true; + }} + > + + ${msg("View Embed Code")} + + ${when( + this.authState && + this.collectionId && + this.shareLink !== + window.location.href.slice( + 0, + window.location.href.indexOf(this.collectionId) + + this.collectionId.length, + ), + () => html` + + ${this.collection?.access === CollectionAccess.Unlisted + ? html` + + ${msg("Visit Unlisted Page")} + ` + : html` + + ${msg("Visit Public Page")} + `} + + + { + this.showDialog = true; + }} + > + + ${msg("Change Link Visibility")} + + `, + () => html` + + + ${msg("Download Collection")} + + `, + )} + + + + `; + } + + private renderDialog() { + return html` + { + this.showDialog = false; + }} + @sl-after-hide=${() => { + this.showEmbedCode = false; + }} + style="--width: 32rem;" + > + ${when( + this.authState && this.collection, + (collection) => html` +
+ { + this.dispatchEvent( + new CustomEvent("btrix-select", { + detail: { + item: { + value: (e.target as SelectCollectionAccess).value, + }, + }, + }), + ); + }} + > +
+ `, + )} + ${this.renderShareLink()} ${this.renderEmbedCode()} +
+ (this.showDialog = false)}> + ${msg("Done")} + +
+
+ `; + } + + private readonly renderShareLink = () => { + return html` + + ${msg("Link to Share")} + + + + + + + + `; + }; + + private readonly renderEmbedCode = () => { + const replaySrc = this.publicReplaySrc; + const embedCode = ``; + const importCode = `importScripts("https://replayweb.page/sw.js");`; + + return html` + + ${msg("Embed Code")} + ${when( + this.collection?.access === CollectionAccess.Private, + () => html` + + ${msg("Change the visibility setting to embed this collection.")} + + `, + )} +

+ ${msg( + html`To embed this collection into an existing webpage, add the + following embed code:`, + )} +

+
+ +
+ embedCode} + content=${msg("Copy Embed Code")} + hoist + raised + > +
+
+

+ ${msg( + html`Add the following JavaScript to your + /replay/sw.js:`, + )} +

+
+ +
+ importCode} + content=${msg("Copy JS")} + hoist + raised + > +
+
+

+ ${msg( + html`See + + our embedding guide + for more details.`, + )} +

+
+ `; + }; +} diff --git a/frontend/src/index.ts b/frontend/src/index.ts index fabe7e5014..7d36d52fb6 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -8,6 +8,7 @@ import type { } from "@shoelace-style/shoelace"; import { html, nothing, type TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; import { when } from "lit/directives/when.js"; import isEqual from "lodash/fp/isEqual"; @@ -500,7 +501,7 @@ export class App extends BtrixElement { ` : html` - ${this.viewState.route === "publicOrgProfile" + ${this.viewState.route !== "login" ? html` - ${msg("Sign Up")} - - `; - } - - if (signUpUrl) { - return html` - - ${msg("Sign Up")} - - `; - } - } - private renderOrgs() { const orgs = this.userInfo?.orgs; if (!orgs) return; @@ -742,6 +714,7 @@ export class App extends BtrixElement { `; } + // TODO move into separate component private renderPage() { switch (this.viewState.route) { case "signUp": { @@ -825,6 +798,21 @@ export class App extends BtrixElement { slug=${this.viewState.params.slug} >`; + case "publicCollection": { + const { collectionId, collectionTab } = this.viewState.params; + + if (!collectionId) { + break; + } + + return html``; + } + case "accountSettings": return html` +
+
${pageTitle(title)} ${suffix}
+ ${actions + ? html`
${actions}
` + : nothing} +
+ ${secondary} +
+ `; +} + +export function page( + header: Parameters[0], + render: () => TemplateResult, +) { + return html` + +
+ ${pageHeader(header)} +
${render()}
+
`; +} diff --git a/frontend/src/layouts/pageHeader.ts b/frontend/src/layouts/pageHeader.ts index c0f83829e5..f405a6d919 100644 --- a/frontend/src/layouts/pageHeader.ts +++ b/frontend/src/layouts/pageHeader.ts @@ -84,7 +84,7 @@ export function pageBack({ href, content }: Breadcrumb) { }); } -export function pageTitle(title?: string | TemplateResult) { +export function pageTitle(title?: string | TemplateResult | typeof nothing) { return html`

${title || html``} @@ -100,6 +100,7 @@ export function pageNav(breadcrumbs: Breadcrumb[]) { return pageBreadcrumbs(breadcrumbs); } +// TODO consolidate with page.ts https://github.com/webrecorder/browsertrix/issues/2197 export function pageHeader( title?: string | TemplateResult, suffix?: TemplateResult<1>, diff --git a/frontend/src/pages/collections/collection.ts b/frontend/src/pages/collections/collection.ts new file mode 100644 index 0000000000..ab1fdcb370 --- /dev/null +++ b/frontend/src/pages/collections/collection.ts @@ -0,0 +1,212 @@ +import { localized, msg } from "@lit/localize"; +import { Task } from "@lit/task"; +import { html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { choose } from "lit/directives/choose.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import type { SelectVisibilityDetail } from "@/features/collections/share-collection"; +import { page } from "@/layouts/page"; +import { RouteNamespace } from "@/routes"; +import type { OrgProfileData, PublicCollection } from "@/types/org"; + +enum Tab { + Replay = "replay", + About = "about", +} + +@localized() +@customElement("btrix-collection") +export class Collection extends BtrixElement { + @property({ type: String }) + slug?: string; + + @property({ type: String }) + collectionId?: string; + + @property({ type: String }) + tab: Tab | string = Tab.Replay; + + private readonly tabLabels: Record< + Tab, + { icon: { name: string; library: string }; text: string } + > = { + [Tab.Replay]: { + icon: { name: "replaywebpage", library: "app" }, + text: msg("Replay"), + }, + [Tab.About]: { + icon: { name: "info-square-fill", library: "default" }, + text: msg("About"), + }, + }; + + readonly publicOrg = new Task(this, { + task: async ([slug]) => { + if (!slug) return; + const org = await this.fetchOrgProfile(slug); + return org; + }, + args: () => [this.slug, this.collectionId] as const, + }); + + render() { + return html` + ${this.publicOrg.render({ + complete: (profile) => + profile ? this.renderCollection(profile) : nothing, + })} + `; + } + + private renderCollection({ org, collections }: OrgProfileData) { + const collection = + this.collectionId && + collections.find(({ id }) => id === this.collectionId); + + if (!collection) { + return "TODO"; + } + + return html` + ${page( + { + title: collection.name, + secondary: html` +
+ ${msg("Collection by")} + ${org.name} +
+ `, + actions: html` + ) => { + e.stopPropagation(); + console.log("TODO"); + }} + > + `, + }, + () => html` + + + ${choose( + this.tab, + [ + [Tab.Replay, () => this.renderReplay(collection)], + [Tab.About, () => this.renderAbout(collection)], + ], + () => html``, + )} + `, + )} + `; + } + + private readonly renderTab = (tab: Tab) => { + const isSelected = tab === (this.tab as Tab); + + return html` + + + ${this.tabLabels[tab].text} + `; + }; + + private renderReplay(collection: PublicCollection) { + const replaySource = new URL( + `/api/orgs/${collection.oid}/collections/${this.collectionId}/public/replay.json`, + window.location.href, + ).href; + + return html` +
+ +
+ `; + } + + private renderAbout(collection: PublicCollection) { + return html` +
+
+

+ ${msg("Description")} +

+
+ ${collection.description + ? html` + + ` + : html`

+ ${msg( + "A description has not been provided for this collection.", + )} +

`} +
+
+
+

+ ${msg("Metadata")} +

+ + + ${this.localize.number(collection.crawlCount)} + + + ${this.localize.number(collection.pageCount)} + + + ${this.localize.bytes(collection.totalSize)} + + + TODO + + +
+
+ `; + } + + private async fetchOrgProfile(slug: string): Promise { + const resp = await fetch(`/api/public-collections/${slug}`, { + headers: { "Content-Type": "application/json" }, + }); + + switch (resp.status) { + case 200: + return (await resp.json()) as OrgProfileData; + case 404: { + throw resp.status; + } + default: + throw resp.status; + } + } +} diff --git a/frontend/src/pages/collections/index.ts b/frontend/src/pages/collections/index.ts new file mode 100644 index 0000000000..503f981a79 --- /dev/null +++ b/frontend/src/pages/collections/index.ts @@ -0,0 +1 @@ +import "./collection"; diff --git a/frontend/src/pages/index.ts b/frontend/src/pages/index.ts index 74ddf1566a..142bce53f1 100644 --- a/frontend/src/pages/index.ts +++ b/frontend/src/pages/index.ts @@ -10,3 +10,4 @@ import(/* webpackChunkName: "reset-password" */ "./reset-password"); import(/* webpackChunkName: "users-invite" */ "./users-invite"); import(/* webpackChunkName: "accept-invite" */ "./invite/accept"); import(/* webpackChunkName: "account-settings" */ "./account-settings"); +import(/* webpackChunkName: "collections" */ "./collections"); diff --git a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts index 4678e28389..aaf3d151a8 100644 --- a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts +++ b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts @@ -7,9 +7,9 @@ import { when } from "lit/directives/when.js"; import capitalize from "lodash/fp/capitalize"; import { BtrixElement } from "@/classes/BtrixElement"; -import { CopyButton } from "@/components/ui/copy-button"; import { type Dialog } from "@/components/ui/dialog"; import type { PageChangeEvent } from "@/components/ui/pagination"; +import { ClipboardController } from "@/controllers/clipboard"; import type { CrawlLog } from "@/features/archived-items/crawl-logs"; import { pageBack, pageNav, type Breadcrumb } from "@/layouts/pageHeader"; import type { APIPaginatedList } from "@/types/api"; @@ -666,7 +666,8 @@ export class ArchivedItemDetail extends BtrixElement { ${msg("Go to Workflow")} CopyButton.copyToClipboard(this.item?.cid || "")} + @click=${() => + ClipboardController.copyToClipboard(this.item?.cid || "")} > ${msg("Copy Workflow ID")} @@ -675,7 +676,7 @@ export class ArchivedItemDetail extends BtrixElement { )} - CopyButton.copyToClipboard(this.item!.tags.join(", "))} + ClipboardController.copyToClipboard(this.item!.tags.join(", "))} ?disabled=${!this.item.tags.length} > diff --git a/frontend/src/pages/org/archived-items.ts b/frontend/src/pages/org/archived-items.ts index fded2fc491..4f309fae63 100644 --- a/frontend/src/pages/org/archived-items.ts +++ b/frontend/src/pages/org/archived-items.ts @@ -11,8 +11,8 @@ import queryString from "query-string"; import type { ArchivedItem, Crawl, Workflow } from "./types"; import { BtrixElement } from "@/classes/BtrixElement"; -import { CopyButton } from "@/components/ui/copy-button"; import type { PageChangeEvent } from "@/components/ui/pagination"; +import { ClipboardController } from "@/controllers/clipboard"; import { CrawlStatus } from "@/features/archived-items/crawl-status"; import { pageHeader } from "@/layouts/pageHeader"; import type { APIPaginatedList, APIPaginationQuery } from "@/types/api"; @@ -611,17 +611,13 @@ export class CrawlsList extends BtrixElement { ${msg("Go to Workflow")} { - CopyButton.copyToClipboard(item.cid); - }} + @click=${() => ClipboardController.copyToClipboard(item.cid)} > ${msg("Copy Workflow ID")} { - CopyButton.copyToClipboard(item.id); - }} + @click=${() => ClipboardController.copyToClipboard(item.id)} > ${msg("Copy Crawl ID")} @@ -629,9 +625,8 @@ export class CrawlsList extends BtrixElement { ` : nothing} { - CopyButton.copyToClipboard(item.tags.join(", ")); - }} + @click=${() => + ClipboardController.copyToClipboard(item.tags.join(", "))} ?disabled=${!item.tags.length} > diff --git a/frontend/src/pages/org/collection-detail.ts b/frontend/src/pages/org/collection-detail.ts index edb689e3c5..733586fc15 100644 --- a/frontend/src/pages/org/collection-detail.ts +++ b/frontend/src/pages/org/collection-detail.ts @@ -1,5 +1,4 @@ import { localized, msg, str } from "@lit/localize"; -import type { SlCheckbox } from "@shoelace-style/shoelace"; import { html, nothing, type PropertyValues, type TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; import { choose } from "lit/directives/choose.js"; @@ -11,6 +10,11 @@ import type { Embed as ReplayWebPage } from "replaywebpage"; import { BtrixElement } from "@/classes/BtrixElement"; import type { PageChangeEvent } from "@/components/ui/pagination"; +import { SelectCollectionAccess } from "@/features/collections/select-collection-access"; +import type { + SelectVisibilityDetail, + ShareCollection, +} from "@/features/collections/share-collection"; import { pageNav, type Breadcrumb } from "@/layouts/pageHeader"; import type { APIPaginatedList, @@ -37,9 +41,6 @@ export class CollectionDetail extends BtrixElement { @property({ type: String }) collectionTab?: Tab = TABS[0]; - @property({ type: Boolean }) - isCrawler?: boolean; - @state() private collection?: Collection; @@ -52,9 +53,6 @@ export class CollectionDetail extends BtrixElement { @state() private isDescriptionExpanded = false; - @state() - private showShareInfo = false; - @query(".description") private readonly description?: HTMLElement | null; @@ -64,6 +62,9 @@ export class CollectionDetail extends BtrixElement { @query("replay-web-page") private readonly replayEmbed?: ReplayWebPage | null; + @query("btrix-share-collection") + private readonly shareCollection?: ShareCollection | null; + // Use to cancel requests private getArchivedItemsController: AbortController | null = null; @@ -81,6 +82,10 @@ export class CollectionDetail extends BtrixElement { }, }; + private get isCrawler() { + return this.appState.isCrawler; + } + protected async willUpdate( changedProperties: PropertyValues & Map, ) { @@ -103,40 +108,73 @@ export class CollectionDetail extends BtrixElement {
- ${this.collection?.access === CollectionAccess.Unlisted - ? html` - + ${choose(this.collection?.access, [ + [ + CollectionAccess.Private, + () => html` + - ` - : html` - - + `, + ], + [ + CollectionAccess.Unlisted, + () => html` + + - `} + `, + ], + [ + CollectionAccess.Public, + () => html` + + + + `, + ], + ])}

${this.collection?.name || html``}

- ${when( - this.isCrawler || - this.collection?.access !== CollectionAccess.Private, - () => html` - (this.showShareInfo = true)} - > - - ${msg("Share")} - - `, - )} + ) => { + e.stopPropagation(); + void this.updateVisibility(e.detail.item.value); + }} + > ${when(this.isCrawler, this.renderActions)}
@@ -228,8 +266,7 @@ export class CollectionDetail extends BtrixElement { > `, - )} - ${this.renderShareDialog()}`; + )}`; } private refreshReplay() { @@ -242,141 +279,6 @@ export class CollectionDetail extends BtrixElement { } } - private getPublicReplayURL() { - return new URL( - `/api/orgs/${this.orgId}/collections/${this.collectionId}/public/replay.json`, - window.location.href, - ).href; - } - - private renderShareDialog() { - return html` - (this.showShareInfo = false)} - style="--width: 32rem;" - > - ${ - this.collection?.access === CollectionAccess.Unlisted - ? "" - : html`

- ${msg( - "Make this collection shareable to enable a public viewing link.", - )} -

` - } - ${when( - this.isCrawler, - () => html` -
- - void this.onTogglePublic((e.target as SlCheckbox).checked)} - >${msg("Collection is Shareable")} -
- `, - )} -
- ${when(this.collection?.access === CollectionAccess.Unlisted, this.renderShareInfo)} -
- (this.showShareInfo = false)} - >${msg("Done")} -
- - `; - } - - private readonly renderShareInfo = () => { - const replaySrc = this.getPublicReplayURL(); - const encodedReplaySrc = encodeURIComponent(replaySrc); - const publicReplayUrl = `https://replayweb.page?source=${encodedReplaySrc}`; - const embedCode = ``; - const importCode = `importScripts("https://replayweb.page/sw.js");`; - - return html` ${msg("Link to Share")} -
-

- ${msg("This collection can be viewed by anyone with the link.")} -

- - - - - - - -
- ${msg("Embed Collection")} -
-

- ${msg( - html`Share this collection by embedding it into an existing webpage.`, - )} -

-

- ${msg(html`Add the following embed code to your HTML page:`)} -

-
- -
- embedCode} - content=${msg("Copy Embed Code")} - hoist - raised - > -
-
-

- ${msg( - html`Add the following JavaScript to your - /replay/sw.js:`, - )} -

-
- -
- importCode} - content=${msg("Copy JS")} - hoist - raised - > -
-
-

- ${msg( - html`See - - our embedding guide - for more details.`, - )} -

-
`; - }; - private readonly renderBreadcrumbs = () => { const breadcrumbs: Breadcrumb[] = [ { @@ -433,35 +335,10 @@ export class CollectionDetail extends BtrixElement { ${msg("Select Archived Items")}
- ${this.collection?.access === CollectionAccess.Private - ? html` - void this.onTogglePublic(true)} - > - - ${msg("Make Shareable")} - - ` - : html` - - - - Visit Shareable URL - - - void this.onTogglePublic(false)} - > - - ${msg("Make Private")} - - `} + this.shareCollection?.show()}> + + ${msg("Share Collection")} + ( `/orgs/${this.orgId}/collections/${this.collectionId}`, { @@ -772,8 +646,16 @@ export class CollectionDetail extends BtrixElement { }, ); - if (res.updated && this.collection) { - this.collection = { ...this.collection, access }; + if (res.updated) { + this.notify.toast({ + message: msg("Updated collection visibility."), + variant: "success", + icon: "check2-circle", + }); + + if (this.collection) { + this.collection = { ...this.collection, access }; + } } } diff --git a/frontend/src/pages/org/collections-list.ts b/frontend/src/pages/org/collections-list.ts index d541fe243e..a0ba1ef143 100644 --- a/frontend/src/pages/org/collections-list.ts +++ b/frontend/src/pages/org/collections-list.ts @@ -3,6 +3,7 @@ import type { SlInput, SlMenuItem } from "@shoelace-style/shoelace"; import Fuse from "fuse.js"; import { html, type PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; +import { choose } from "lit/directives/choose.js"; import { guard } from "lit/directives/guard.js"; import { when } from "lit/directives/when.js"; import debounce from "lodash/fp/debounce"; @@ -12,8 +13,11 @@ import type { SelectNewDialogEvent } from "."; import { BtrixElement } from "@/classes/BtrixElement"; import type { PageChangeEvent } from "@/components/ui/pagination"; +import { ClipboardController } from "@/controllers/clipboard"; import type { CollectionSavedEvent } from "@/features/collections/collection-metadata-dialog"; +import { SelectCollectionAccess } from "@/features/collections/select-collection-access"; import { pageHeader } from "@/layouts/pageHeader"; +import { RouteNamespace } from "@/routes"; import type { APIPaginatedList, APIPaginationQuery } from "@/types/api"; import { CollectionAccess, @@ -105,6 +109,10 @@ export class CollectionsList extends BtrixElement { threshold: 0.2, // stricter; default is 0.6 }); + private getShareLink(collection: Collection) { + return `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ""}/${collection.access === CollectionAccess.Private ? `${RouteNamespace.PrivateOrgs}/${this.orgSlug}/collections/view` : `${RouteNamespace.PublicOrgs}/${this.orgSlug}/collections`}/${collection.id}`; + } + private get hasSearchStr() { return this.searchByValue.length >= MIN_SEARCH_LENGTH; } @@ -502,25 +510,58 @@ export class CollectionsList extends BtrixElement { class="cursor-pointer select-none rounded border shadow transition-all focus-within:bg-neutral-50 hover:bg-neutral-50 hover:shadow-none" > - ${col.access === CollectionAccess.Unlisted - ? html` - + ${choose(col.access, [ + [ + CollectionAccess.Private, + () => html` + - ` - : html` - + `, + ], + [ + CollectionAccess.Unlisted, + () => html` + + + + `, + ], + [ + CollectionAccess.Public, + () => html` + - `} + `, + ], + ])} void this.onTogglePublic(col, true)} > - - ${msg("Make Shareable")} + + ${msg("Enable Share Link")}
` : html` - - - - Visit Shareable URL - + { + ClipboardController.copyToClipboard(this.getShareLink(col)); + this.notify.toast({ + message: msg("Link copied"), + }); + }} + > + + ${msg("Copy Share Link")} void this.onTogglePublic(col, false)} > - + ${msg("Make Private")} `} diff --git a/frontend/src/pages/org/profile.ts b/frontend/src/pages/org/profile.ts index 51e9fa326f..94c4104c22 100644 --- a/frontend/src/pages/org/profile.ts +++ b/frontend/src/pages/org/profile.ts @@ -2,22 +2,11 @@ import { localized, msg, str } from "@lit/localize"; import { Task } from "@lit/task"; import { html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; -import { ifDefined } from "lit/directives/if-defined.js"; import { when } from "lit/directives/when.js"; import { BtrixElement } from "@/classes/BtrixElement"; -import { pageTitle } from "@/layouts/pageHeader"; -import type { OrgData } from "@/types/org"; - -type OrgProfileData = { - org: { - name: string; - description: string; - url: string; - verified: boolean; - }; - collections: unknown[]; -}; +import { page } from "@/layouts/page"; +import type { OrgData, OrgProfileData } from "@/types/org"; type PublicCollection = { name: string; @@ -38,7 +27,6 @@ export class OrgProfile extends BtrixElement { private isPrivatePreview = false; readonly publicOrg = new Task(this, { - autoRun: false, task: async ([slug]) => { if (!slug) return; const org = await this.fetchOrgProfile(slug); @@ -47,10 +35,6 @@ export class OrgProfile extends BtrixElement { args: () => [this.slug] as const, }); - protected firstUpdated(): void { - void this.publicOrg.run(); - } - render() { if (!this.slug) { return this.renderError(); @@ -109,94 +93,94 @@ export class OrgProfile extends BtrixElement { private renderProfile({ org }: OrgProfileData) { return html` - - ${this.isPrivatePreview ? this.renderPreviewBanner() : nothing} - -
- -
-
- ${pageTitle(org.name)} - ${org.verified && - html``} + ${page( + { + title: org.name, + suffix: org.verified + ? html`` + : nothing, + actions: when( + this.appState.isAdmin, + () => + html`
+ + + +
`, + ), + secondary: html` ${when( - this.appState.isAdmin, - () => - html`
- - - -
`, + org.description, + (description) => html` +
${description}
+ `, )} -
- ${when( - org.description, - (description) => html` -
${description}
- `, - )} - ${when(org.url, (urlStr) => { - let url: URL; - try { - url = new URL(urlStr); - } catch { - return nothing; - } + ${when(org.url, (urlStr) => { + let url: URL; + try { + url = new URL(urlStr); + } catch { + return nothing; + } - return html` - - `; - })} -
+ + + ${url.href.split("//")[1].replace(/\/$/, "")} + +
+ `; + })} + `, + }, + () => this.renderCollections(), + )} + `; + } -
-

${msg("Collections")}

- ${when( - this.appState.isAdmin, - () => - html` - - `, - )} -
+ private renderCollections() { + return html` +
+

${msg("Collections")}

+ ${when( + this.appState.isAdmin, + () => + html` + + `, + )} +
-
- ${this.renderCollections(this.collections)} -
+
+ ${this.renderCollectionsList(this.collections)}
`; } - private renderCollections(collections: PublicCollection[]) { + private renderCollectionsList(collections: PublicCollection[]) { if (!collections.length) { return html`

diff --git a/frontend/src/pages/org/workflow-detail.ts b/frontend/src/pages/org/workflow-detail.ts index 532c344b24..e9a7497430 100644 --- a/frontend/src/pages/org/workflow-detail.ts +++ b/frontend/src/pages/org/workflow-detail.ts @@ -11,9 +11,9 @@ import queryString from "query-string"; import type { Crawl, Seed, Workflow, WorkflowParams } from "./types"; import { BtrixElement } from "@/classes/BtrixElement"; -import { CopyButton } from "@/components/ui/copy-button"; import type { PageChangeEvent } from "@/components/ui/pagination"; import { type IntersectEvent } from "@/components/utils/observable"; +import { ClipboardController } from "@/controllers/clipboard"; import type { CrawlLog } from "@/features/archived-items/crawl-logs"; import { CrawlStatus } from "@/features/archived-items/crawl-status"; import { ExclusionEditor } from "@/features/crawl-workflows/exclusion-editor"; @@ -753,9 +753,8 @@ export class WorkflowDetail extends BtrixElement { ${msg("Edit Workflow Settings")} { - CopyButton.copyToClipboard(workflow.tags.join(", ")); - }} + @click=${() => + ClipboardController.copyToClipboard(workflow.tags.join(", "))} ?disabled=${!workflow.tags.length} > diff --git a/frontend/src/pages/org/workflows-list.ts b/frontend/src/pages/org/workflows-list.ts index 10455e2760..a807d69a3b 100644 --- a/frontend/src/pages/org/workflows-list.ts +++ b/frontend/src/pages/org/workflows-list.ts @@ -19,9 +19,9 @@ import { } from "./types"; import { BtrixElement } from "@/classes/BtrixElement"; -import { CopyButton } from "@/components/ui/copy-button"; import type { PageChangeEvent } from "@/components/ui/pagination"; import { type SelectEvent } from "@/components/ui/search-combobox"; +import { ClipboardController } from "@/controllers/clipboard"; import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow-dialog"; import { pageHeader } from "@/layouts/pageHeader"; import scopeTypeLabels from "@/strings/crawl-workflows/scopeType"; @@ -625,9 +625,8 @@ export class WorkflowsList extends BtrixElement { `, )} { - CopyButton.copyToClipboard(workflow.tags.join(", ")); - }} + @click=${() => + ClipboardController.copyToClipboard(workflow.tags.join(", "))} ?disabled=${!workflow.tags.length} > diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 35b95497eb..598de394aa 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -36,6 +36,7 @@ export const ROUTES = { ].join(""), publicOrgs: `/${RouteNamespace.PublicOrgs}(/)`, publicOrgProfile: `/${RouteNamespace.PublicOrgs}/:slug(/)`, + publicCollection: `/${RouteNamespace.PublicOrgs}/:slug/collections/:collectionId(/:collectionTab)`, users: "/users", usersInvite: "/users/invite", crawls: "/crawls", diff --git a/frontend/src/theme.stylesheet.css b/frontend/src/theme.stylesheet.css index 2acfbacd8a..1a6131e8d1 100644 --- a/frontend/src/theme.stylesheet.css +++ b/frontend/src/theme.stylesheet.css @@ -376,6 +376,18 @@ sl-drawer::part(footer) { border-top: 1px solid var(--sl-panel-border-color); } + + sl-button.button-card { + @apply w-full; + } + + sl-button.button-card::part(base) { + @apply min-h-20 justify-between leading-none; + } + + sl-button.button-card::part(label) { + @apply flex flex-1 flex-col justify-center gap-2 text-left; + } } /* Following styles won't work with layers */ diff --git a/frontend/src/types/events.d.ts b/frontend/src/types/events.d.ts index 5e41d6f498..7b08caaa2d 100644 --- a/frontend/src/types/events.d.ts +++ b/frontend/src/types/events.d.ts @@ -1,4 +1,5 @@ import { type APIEventMap } from "@/controllers/api"; +import { type CopiedEventMap } from "@/controllers/clipboard"; import { type NavigateEventMap } from "@/controllers/navigate"; import { type NotifyEventMap } from "@/controllers/notify"; import { type UserGuideEventMap } from "@/index"; @@ -14,5 +15,6 @@ declare global { NotifyEventMap, AuthEventMap, APIEventMap, - UserGuideEventMap {} + UserGuideEventMap, + CopiedEventMap {} } diff --git a/frontend/src/types/org.ts b/frontend/src/types/org.ts index a17f467e68..2c26c58168 100644 --- a/frontend/src/types/org.ts +++ b/frontend/src/types/org.ts @@ -101,3 +101,25 @@ export type OrgData = z.infer; export const orgConfigSchema = z.unknown(); export type OrgConfig = z.infer; + +export const publicCollectionSchema = z.object({ + id: z.string(), + oid: z.string(), + name: z.string(), + description: z.string(), + crawlCount: z.number(), + pageCount: z.number(), + totalSize: z.number(), +}); +export type PublicCollection = z.infer; + +export const orgProfileDataSchema = z.object({ + org: z.object({ + name: z.string(), + description: z.string(), + url: z.string(), + verified: z.boolean(), + }), + collections: z.array(publicCollectionSchema), +}); +export type OrgProfileData = z.infer; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a92b29318c..c6e0ef7e78 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1607,10 +1607,10 @@ resolved "https://registry.yarnpkg.com/@shoelace-style/localize/-/localize-3.2.1.tgz#9aa0078bef68a357070b104df95c75701c962c79" integrity sha512-r4C9C/5kSfMBIr0D9imvpRdCNXtUNgyYThc4YlS6K5Hchv1UyxNQ9mxwj+BTRH2i1Neits260sR3OjKMnplsFA== -"@shoelace-style/shoelace@~2.15.1": - version "2.15.1" - resolved "https://registry.yarnpkg.com/@shoelace-style/shoelace/-/shoelace-2.15.1.tgz#2fa6bd8e493801f5b5b4744fab0fa108bbc01934" - integrity sha512-3ecUw8gRwOtcZQ8kWWkjk4FTfObYQ/XIl3aRhxprESoOYV1cYhloYPsmQY38UoL3+pwJiZb5+LzX0l3u3Zl0GA== +"@shoelace-style/shoelace@~2.18.0": + version "2.18.0" + resolved "https://registry.yarnpkg.com/@shoelace-style/shoelace/-/shoelace-2.18.0.tgz#21435ad39c4759210c0b4dab342c548ec989fba7" + integrity sha512-uzpL0+8Qm8aE2ArcXBcKHkaPc6l7ymuVaN6xJM0yd2o3talcoXpuP+gRBsgggSZKuuJEa+JkEuLDdzzFnE/+jw== dependencies: "@ctrl/tinycolor" "^4.0.2" "@floating-ui/dom" "^1.5.3" From a031fab3134ed2a495678cf888e27af51b1130ee Mon Sep 17 00:00:00 2001 From: Tessa Walsh Date: Mon, 23 Dec 2024 14:14:09 -0500 Subject: [PATCH 05/12] Backend work for public collections (#2198) Fixes #2182 This rather large PR adds the rest of what should be needed for public collections work in the frontend. New API endpoints include: - Public collections endpoints: GET, streaming download - Paginated list of URLs in collection with snapshot (page) info for each - Collection endpoint to set home URL - Collection endpoint to upload thumbnail as stream - DELETE endpoint to remove collection thumbnail Changes to existing API endpoints include: - Paginating public collection list results - Several `pages` endpoints that previously only supported `/crawls/` in their path, e.g. `/orgs/{oid}/crawls/all/pages/reAdd`, now support `/uploads/` and `/all-crawls/` namespaces as well. This is necessitated by adding pages for uploads to the database (see below). For `/orgs/{oid}/namespace/all/pages/reAdd`, `crawls` or `uploads` will serve as a filter to only affect crawls of that given type. Other endpoints are more liberal at this point, and will perform the same action regardless of the namespace used in the route (we'll likely want to change this in a follow-up to be more consistent). - `/orgs/{oid}/namespace/all/pages/reAdd` now kicks off a background job rather than doing all of the computation in an asyncio task in the backend container. The background job additionally updates collection date ranges, page/size counts, and tags for each collection in the org after pages have been (re)added. Other big changes: - New uploads will now have their pages read into the database! Collection page counts now also include uploads - A migration was added to start a background job for each org that will add the pages for previously-uploaded WACZ files to the database and update collections accordingly - Adds a new `ImageFile` subclass of `BaseFile` for thumbnails that we can use for other user-uploaded image files moving forward, with separate output models for authenticated and public endpoints --- backend/btrixcloud/background_jobs.py | 68 ++- backend/btrixcloud/basecrawls.py | 24 +- backend/btrixcloud/colls.py | 568 ++++++++++++++++-- backend/btrixcloud/crawlmanager.py | 38 +- backend/btrixcloud/crawls.py | 2 +- backend/btrixcloud/db.py | 11 +- backend/btrixcloud/main.py | 14 +- backend/btrixcloud/main_bg.py | 14 +- backend/btrixcloud/main_op.py | 1 + .../migrations/migration_0037_upload_pages.py | 72 +++ backend/btrixcloud/models.py | 295 ++++++++- backend/btrixcloud/ops.py | 27 +- backend/btrixcloud/pages.py | 139 ++++- backend/btrixcloud/profiles.py | 2 +- backend/btrixcloud/storages.py | 4 +- backend/btrixcloud/uploads.py | 50 +- backend/test/data/thumbnail.jpg | Bin 0 -> 27636 bytes backend/test/test_collections.py | 547 ++++++++++++++++- backend/test/test_uploads.py | 43 ++ chart/app-templates/background_job.yaml | 5 +- 20 files changed, 1732 insertions(+), 192 deletions(-) create mode 100644 backend/btrixcloud/migrations/migration_0037_upload_pages.py create mode 100644 backend/test/data/thumbnail.jpg diff --git a/backend/btrixcloud/background_jobs.py b/backend/btrixcloud/background_jobs.py index 1e0da1e28f..7d20e6b637 100644 --- a/backend/btrixcloud/background_jobs.py +++ b/backend/btrixcloud/background_jobs.py @@ -1,7 +1,6 @@ """k8s background jobs""" import asyncio -import os from datetime import datetime from typing import Optional, Tuple, Union, List, Dict, TYPE_CHECKING, cast from uuid import UUID @@ -22,6 +21,7 @@ DeleteReplicaJob, DeleteOrgJob, RecalculateOrgStatsJob, + ReAddOrgPagesJob, PaginatedBackgroundJobResponse, AnyJob, StorageRef, @@ -301,8 +301,6 @@ async def create_delete_org_job( try: job_id = await self.crawl_manager.run_delete_org_job( oid=str(org.id), - backend_image=os.environ.get("BACKEND_IMAGE", ""), - pull_policy=os.environ.get("BACKEND_IMAGE_PULL_POLICY", ""), existing_job_id=existing_job_id, ) if existing_job_id: @@ -346,8 +344,6 @@ async def create_recalculate_org_stats_job( try: job_id = await self.crawl_manager.run_recalculate_org_stats_job( oid=str(org.id), - backend_image=os.environ.get("BACKEND_IMAGE", ""), - pull_policy=os.environ.get("BACKEND_IMAGE_PULL_POLICY", ""), existing_job_id=existing_job_id, ) if existing_job_id: @@ -381,6 +377,52 @@ async def create_recalculate_org_stats_job( print(f"warning: recalculate org stats job could not be started: {exc}") return None + async def create_re_add_org_pages_job( + self, + oid: UUID, + crawl_type: Optional[str] = None, + existing_job_id: Optional[str] = None, + ): + """Create job to (re)add all pages in an org, optionally filtered by crawl type""" + + try: + job_id = await self.crawl_manager.run_re_add_org_pages_job( + oid=str(oid), + crawl_type=crawl_type, + existing_job_id=existing_job_id, + ) + if existing_job_id: + readd_pages_job = await self.get_background_job(existing_job_id, oid) + previous_attempt = { + "started": readd_pages_job.started, + "finished": readd_pages_job.finished, + } + if readd_pages_job.previousAttempts: + readd_pages_job.previousAttempts.append(previous_attempt) + else: + readd_pages_job.previousAttempts = [previous_attempt] + readd_pages_job.started = dt_now() + readd_pages_job.finished = None + readd_pages_job.success = None + else: + readd_pages_job = ReAddOrgPagesJob( + id=job_id, + oid=oid, + crawl_type=crawl_type, + started=dt_now(), + ) + + await self.jobs.find_one_and_update( + {"_id": job_id}, {"$set": readd_pages_job.to_dict()}, upsert=True + ) + + return job_id + # pylint: disable=broad-exception-caught + except Exception as exc: + # pylint: disable=raise-missing-from + print(f"warning: re-add org pages job could not be started: {exc}") + return None + async def job_finished( self, job_id: str, @@ -430,7 +472,11 @@ async def job_finished( async def get_background_job( self, job_id: str, oid: Optional[UUID] = None ) -> Union[ - CreateReplicaJob, DeleteReplicaJob, DeleteOrgJob, RecalculateOrgStatsJob + CreateReplicaJob, + DeleteReplicaJob, + DeleteOrgJob, + RecalculateOrgStatsJob, + ReAddOrgPagesJob, ]: """Get background job""" query: dict[str, object] = {"_id": job_id} @@ -454,6 +500,9 @@ def _get_job_by_type_from_data(self, data: dict[str, object]): if data["type"] == BgJobType.RECALCULATE_ORG_STATS: return RecalculateOrgStatsJob.from_dict(data) + if data["type"] == BgJobType.READD_ORG_PAGES: + return ReAddOrgPagesJob.from_dict(data) + return DeleteOrgJob.from_dict(data) async def list_background_jobs( @@ -595,6 +644,13 @@ async def retry_background_job( existing_job_id=job_id, ) + if job.type == BgJobType.READD_ORG_PAGES: + await self.create_re_add_org_pages_job( + org.id, + job.crawl_type, + existing_job_id=job_id, + ) + return {"success": True} async def retry_failed_background_jobs( diff --git a/backend/btrixcloud/basecrawls.py b/backend/btrixcloud/basecrawls.py index d913d3362b..01e700f8b5 100644 --- a/backend/btrixcloud/basecrawls.py +++ b/backend/btrixcloud/basecrawls.py @@ -1,6 +1,5 @@ """ base crawl type """ -import os from datetime import timedelta from typing import Optional, List, Union, Dict, Any, Type, TYPE_CHECKING, cast, Tuple from uuid import UUID @@ -29,6 +28,7 @@ UpdatedResponse, DeletedResponseQuota, CrawlSearchValuesResponse, + PRESIGN_DURATION_SECONDS, ) from .pagination import paginated_format, DEFAULT_PAGE_SIZE from .utils import dt_now, date_to_str @@ -47,11 +47,6 @@ CrawlConfigOps = UserManager = OrgOps = CollectionOps = PageOps = object StorageOps = EventWebhookOps = BackgroundJobOps = object -# Presign duration must be less than 604800 seconds (one week), -# so set this one minute short of a week. -PRESIGN_MINUTES_MAX = 10079 -PRESIGN_MINUTES_DEFAULT = PRESIGN_MINUTES_MAX - # ============================================================================ # pylint: disable=too-many-instance-attributes, too-many-public-methods, too-many-lines @@ -93,16 +88,8 @@ def __init__( self.background_job_ops = background_job_ops self.page_ops = cast(PageOps, None) - presign_duration_minutes = int( - os.environ.get("PRESIGN_DURATION_MINUTES") or PRESIGN_MINUTES_DEFAULT - ) - - self.presign_duration_seconds = ( - min(presign_duration_minutes, PRESIGN_MINUTES_MAX) * 60 - ) - # renew when <25% of time remaining - self.expire_at_duration_seconds = int(self.presign_duration_seconds * 0.75) + self.expire_at_duration_seconds = int(PRESIGN_DURATION_SECONDS * 0.75) def set_page_ops(self, page_ops): """set page ops reference""" @@ -336,8 +323,9 @@ async def delete_crawls( status_code=400, detail=f"Error Stopping Crawl: {exc}" ) + await self.page_ops.delete_crawl_pages(crawl_id, org.id) + if type_ == "crawl": - await self.page_ops.delete_crawl_pages(crawl_id, org.id) await self.delete_all_crawl_qa_files(crawl_id, org) crawl_size = await self._delete_crawl_files(crawl, org) @@ -382,7 +370,7 @@ async def _delete_crawl_files( size = 0 for file_ in crawl.files: size += file_.size - if not await self.storage_ops.delete_crawl_file_object(org, file_): + if not await self.storage_ops.delete_file_object(org, file_): raise HTTPException(status_code=400, detail="file_deletion_error") # Not replicating QA run WACZs yet if not isinstance(crawl, QARun): @@ -474,7 +462,7 @@ async def resolve_signed_urls( ): exp = now + delta presigned_url = await self.storage_ops.get_presigned_url( - org, file_, self.presign_duration_seconds + org, file_, PRESIGN_DURATION_SECONDS ) prefix = "files" diff --git a/backend/btrixcloud/colls.py b/backend/btrixcloud/colls.py index 411e659ac9..d9ab766aa9 100644 --- a/backend/btrixcloud/colls.py +++ b/backend/btrixcloud/colls.py @@ -2,14 +2,20 @@ Collections API """ +# pylint: disable=too-many-lines + from collections import Counter from uuid import UUID, uuid4 -from typing import Optional, List, TYPE_CHECKING, cast, Dict +from typing import Optional, List, TYPE_CHECKING, cast, Dict, Tuple, Any, Union +import os +import re +import urllib.parse import asyncio import pymongo from fastapi import Depends, HTTPException, Response from fastapi.responses import StreamingResponse +from starlette.requests import Request from .pagination import DEFAULT_PAGE_SIZE, paginated_format from .models import ( @@ -29,10 +35,21 @@ EmptyResponse, UpdatedResponse, SuccessResponse, + AddedResponse, + DeletedResponse, CollectionSearchValuesResponse, OrgPublicCollections, PublicOrgDetails, CollAccessType, + PageUrlCount, + PageIdTimestamp, + PaginatedPageUrlCountResponse, + UpdateCollHomeUrl, + User, + ImageFile, + ImageFilePreparer, + MIN_UPLOAD_PART_SIZE, + PublicCollOut, ) from .utils import dt_now @@ -45,11 +62,14 @@ OrgOps = StorageOps = EventWebhookOps = CrawlOps = object +THUMBNAIL_MAX_SIZE = 2_000_000 + + # ============================================================================ class CollectionOps: """ops for working with named collections of crawls""" - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments, too-many-instance-attributes, too-many-public-methods orgs: OrgOps storage_ops: StorageOps @@ -60,6 +80,7 @@ def __init__(self, mdb, storage_ops, orgs, event_webhook_ops): self.collections = mdb["collections"] self.crawls = mdb["crawls"] self.crawl_configs = mdb["crawl_configs"] + self.pages = mdb["pages"] self.crawl_ops = cast(CrawlOps, None) self.orgs = orgs @@ -70,6 +91,11 @@ def set_crawl_ops(self, ops): """set crawl ops""" self.crawl_ops = ops + def set_page_ops(self, ops): + """set page ops""" + # pylint: disable=attribute-defined-outside-init + self.page_ops = ops + async def init_index(self): """init lookup index""" await self.collections.create_index( @@ -91,8 +117,11 @@ async def add_collection(self, oid: UUID, coll_in: CollIn): oid=oid, name=coll_in.name, description=coll_in.description, + caption=coll_in.caption, modified=modified, access=coll_in.access, + defaultThumbnailName=coll_in.defaultThumbnailName, + allowPublicDownload=coll_in.allowPublicDownload, ) try: await self.collections.insert_one(coll.to_dict()) @@ -100,6 +129,7 @@ async def add_collection(self, oid: UUID, coll_in: CollIn): if crawl_ids: await self.crawl_ops.add_to_collection(crawl_ids, coll_id, org) await self.update_collection_counts_and_tags(coll_id) + await self.update_collection_dates(coll_id) asyncio.create_task( self.event_webhook_ops.create_added_to_collection_notification( crawl_ids, coll_id, org @@ -153,6 +183,7 @@ async def add_crawls_to_collection( raise HTTPException(status_code=404, detail="collection_not_found") await self.update_collection_counts_and_tags(coll_id) + await self.update_collection_dates(coll_id) asyncio.create_task( self.event_webhook_ops.create_added_to_collection_notification( @@ -160,7 +191,7 @@ async def add_crawls_to_collection( ) ) - return await self.get_collection(coll_id, org) + return await self.get_collection_out(coll_id, org) async def remove_crawls_from_collection( self, coll_id: UUID, crawl_ids: List[str], org: Organization @@ -177,6 +208,7 @@ async def remove_crawls_from_collection( raise HTTPException(status_code=404, detail="collection_not_found") await self.update_collection_counts_and_tags(coll_id) + await self.update_collection_dates(coll_id) asyncio.create_task( self.event_webhook_ops.create_removed_from_collection_notification( @@ -184,29 +216,79 @@ async def remove_crawls_from_collection( ) ) - return await self.get_collection(coll_id, org) + return await self.get_collection_out(coll_id, org) - async def get_collection( - self, coll_id: UUID, org: Organization, resources=False, public_only=False - ) -> CollOut: - """Get collection by id""" + async def get_collection_raw( + self, coll_id: UUID, public_or_unlisted_only: bool = False + ) -> Dict[str, Any]: + """Get collection by id as dict from database""" query: dict[str, object] = {"_id": coll_id} - if public_only: + if public_or_unlisted_only: query["access"] = {"$in": ["public", "unlisted"]} result = await self.collections.find_one(query) if not result: raise HTTPException(status_code=404, detail="collection_not_found") + return result + + async def get_collection( + self, coll_id: UUID, public_or_unlisted_only: bool = False + ) -> Collection: + """Get collection by id""" + result = await self.get_collection_raw(coll_id, public_or_unlisted_only) + return Collection.from_dict(result) + + async def get_collection_out( + self, + coll_id: UUID, + org: Organization, + resources=False, + public_or_unlisted_only=False, + ) -> CollOut: + """Get CollOut by id""" + result = await self.get_collection_raw(coll_id, public_or_unlisted_only) + if resources: - result["resources"] = await self.get_collection_crawl_resources( - coll_id, org + result["resources"] = await self.get_collection_crawl_resources(coll_id) + + thumbnail = result.get("thumbnail") + if thumbnail: + image_file = ImageFile(**thumbnail) + result["thumbnail"] = await image_file.get_image_file_out( + org, self.storage_ops ) + return CollOut.from_dict(result) + async def get_public_collection_out( + self, coll_id: UUID, org: Organization, allow_unlisted: bool = False + ) -> PublicCollOut: + """Get PublicCollOut by id""" + result = await self.get_collection_raw(coll_id) + + allowed_access = [CollAccessType.PUBLIC] + if allow_unlisted: + allowed_access.append(CollAccessType.UNLISTED) + + if result.get("access") not in allowed_access: + raise HTTPException(status_code=404, detail="collection_not_found") + + result["resources"] = await self.get_collection_crawl_resources(coll_id) + + thumbnail = result.get("thumbnail") + if thumbnail: + image_file = ImageFile(**thumbnail) + result["thumbnail"] = await image_file.get_public_image_file_out( + org, self.storage_ops + ) + + return PublicCollOut.from_dict(result) + async def list_collections( self, - oid: UUID, + org: Organization, + public_colls_out: bool = False, page_size: int = DEFAULT_PAGE_SIZE, page: int = 1, sort_by: Optional[str] = None, @@ -216,21 +298,22 @@ async def list_collections( access: Optional[str] = None, ): """List all collections for org""" - # pylint: disable=too-many-locals, duplicate-code + # pylint: disable=too-many-locals, duplicate-code, too-many-branches # Zero-index page for query page = page - 1 skip = page * page_size - match_query: dict[str, object] = {"oid": oid} + match_query: dict[str, object] = {"oid": org.id} if name: match_query["name"] = name - elif name_prefix: regex_pattern = f"^{name_prefix}" match_query["name"] = {"$regex": regex_pattern, "$options": "i"} - if access: + if public_colls_out: + match_query["access"] = CollAccessType.PUBLIC + elif access: match_query["access"] = access aggregate = [{"$match": match_query}] @@ -269,15 +352,35 @@ async def list_collections( except (IndexError, ValueError): total = 0 - collections = [CollOut.from_dict(res) for res in items] + collections: List[Union[CollOut, PublicCollOut]] = [] + + for res in items: + res["resources"] = await self.get_collection_crawl_resources(res["_id"]) + + thumbnail = res.get("thumbnail") + if thumbnail: + image_file = ImageFile(**thumbnail) + + if public_colls_out: + res["thumbnail"] = await image_file.get_public_image_file_out( + org, self.storage_ops + ) + else: + res["thumbnail"] = await image_file.get_image_file_out( + org, self.storage_ops + ) + + if public_colls_out: + collections.append(PublicCollOut.from_dict(res)) + else: + collections.append(CollOut.from_dict(res)) return collections, total - async def get_collection_crawl_resources(self, coll_id: UUID, org: Organization): + async def get_collection_crawl_resources(self, coll_id: UUID): """Return pre-signed resources for all collection crawl files.""" - coll = await self.get_collection(coll_id, org) - if not coll: - raise HTTPException(status_code=404, detail="collection_not_found") + # Ensure collection exists + _ = await self.get_collection_raw(coll_id) all_files = [] @@ -312,6 +415,17 @@ async def get_collection_search_values(self, org: Organization): names = [name for name in names if name] return {"names": names} + async def get_collection_crawl_ids(self, coll_id: UUID) -> List[str]: + """Return list of crawl ids in collection""" + crawl_ids = [] + async for crawl_raw in self.crawls.find( + {"collectionIds": coll_id}, projection=["_id"] + ): + crawl_id = crawl_raw.get("_id") + if crawl_id: + crawl_ids.append(crawl_id) + return crawl_ids + async def delete_collection(self, coll_id: UUID, org: Organization): """Delete collection and remove from associated crawls.""" await self.crawl_ops.remove_collection_from_all_crawls(coll_id) @@ -328,7 +442,7 @@ async def delete_collection(self, coll_id: UUID, org: Organization): async def download_collection(self, coll_id: UUID, org: Organization): """Download all WACZs in collection as streaming nested WACZ""" - coll = await self.get_collection(coll_id, org, resources=True) + coll = await self.get_collection_out(coll_id, org, resources=True) metadata = { "type": "collection", @@ -346,6 +460,15 @@ async def download_collection(self, coll_id: UUID, org: Organization): resp, headers=headers, media_type="application/wacz+zip" ) + async def recalculate_org_collection_counts_tags(self, org: Organization): + """Recalculate counts and tags for collections in org""" + collections, _ = await self.list_collections( + org, + page_size=100_000, + ) + for coll in collections: + await self.update_collection_counts_and_tags(coll.id) + async def update_collection_counts_and_tags(self, collection_id: UUID): """Set current crawl info in config when crawl begins""" crawl_count = 0 @@ -353,6 +476,9 @@ async def update_collection_counts_and_tags(self, collection_id: UUID): total_size = 0 tags = [] + coll = await self.get_collection(collection_id) + org = await self.orgs.get_org_by_id(coll.oid) + async for crawl_raw in self.crawls.find({"collectionIds": collection_id}): crawl = BaseCrawl.from_dict(crawl_raw) if crawl.state not in SUCCESSFUL_STATES: @@ -361,8 +487,16 @@ async def update_collection_counts_and_tags(self, collection_id: UUID): files = crawl.files or [] for file in files: total_size += file.size - if crawl.stats: - page_count += crawl.stats.done + + try: + _, crawl_pages = await self.page_ops.list_pages( + crawl.id, org, page_size=1_000_000 + ) + page_count += crawl_pages + # pylint: disable=broad-exception-caught + except Exception: + pass + if crawl.tags: tags.extend(crawl.tags) @@ -380,6 +514,55 @@ async def update_collection_counts_and_tags(self, collection_id: UUID): }, ) + async def recalculate_org_collection_dates(self, org: Organization): + """Recalculate earliest and latest dates for collections in org""" + collections, _ = await self.list_collections( + org, + page_size=100_000, + ) + for coll in collections: + await self.update_collection_dates(coll.id) + + async def update_collection_dates(self, coll_id: UUID): + """Update collection earliest and latest dates from page timestamps""" + coll = await self.get_collection(coll_id) + crawl_ids = await self.get_collection_crawl_ids(coll_id) + + earliest_ts = None + latest_ts = None + + match_query = { + "oid": coll.oid, + "crawl_id": {"$in": crawl_ids}, + "ts": {"$ne": None}, + } + + cursor = self.pages.find(match_query).sort("ts", 1).limit(1) + pages = await cursor.to_list(length=1) + try: + earliest_page = pages[0] + earliest_ts = earliest_page.get("ts") + except IndexError: + pass + + cursor = self.pages.find(match_query).sort("ts", -1).limit(1) + pages = await cursor.to_list(length=1) + try: + latest_page = pages[0] + latest_ts = latest_page.get("ts") + except IndexError: + pass + + await self.collections.find_one_and_update( + {"_id": coll_id}, + { + "$set": { + "dateEarliest": earliest_ts, + "dateLatest": latest_ts, + } + }, + ) + async def update_crawl_collections(self, crawl_id: str): """Update counts and tags for all collections in crawl""" crawl = await self.crawls.find_one({"_id": crawl_id}) @@ -398,7 +581,14 @@ async def add_successful_crawl_to_collections(self, crawl_id: str, cid: UUID): ) await self.update_crawl_collections(crawl_id) - async def get_org_public_collections(self, org_slug: str): + async def get_org_public_collections( + self, + org_slug: str, + page_size: int = DEFAULT_PAGE_SIZE, + page: int = 1, + sort_by: Optional[str] = None, + sort_direction: int = 1, + ): """List public collections for org""" try: org = await self.orgs.get_org_by_slug(org_slug) @@ -411,7 +601,12 @@ async def get_org_public_collections(self, org_slug: str): raise HTTPException(status_code=404, detail="public_profile_not_found") collections, _ = await self.list_collections( - org.id, access=CollAccessType.PUBLIC + org, + page_size=page_size, + page=page, + sort_by=sort_by, + sort_direction=sort_direction, + public_colls_out=True, ) public_org_details = PublicOrgDetails( @@ -422,10 +617,192 @@ async def get_org_public_collections(self, org_slug: str): return OrgPublicCollections(org=public_org_details, collections=collections) + async def list_urls_in_collection( + self, + coll_id: UUID, + oid: UUID, + url_prefix: Optional[str] = None, + page_size: int = DEFAULT_PAGE_SIZE, + page: int = 1, + ) -> Tuple[List[PageUrlCount], int]: + """List all URLs in collection sorted desc by snapshot count""" + # pylint: disable=duplicate-code, too-many-locals, too-many-branches, too-many-statements + # Zero-index page for query + page = page - 1 + skip = page_size * page + + crawl_ids = await self.get_collection_crawl_ids(coll_id) + + match_query: dict[str, object] = {"oid": oid, "crawl_id": {"$in": crawl_ids}} + + if url_prefix: + url_prefix = urllib.parse.unquote(url_prefix) + regex_pattern = f"^{re.escape(url_prefix)}" + match_query["url"] = {"$regex": regex_pattern, "$options": "i"} + + aggregate = [{"$match": match_query}] + + aggregate.extend( + [ + { + "$group": { + "_id": "$url", + "pages": {"$push": "$$ROOT"}, + "count": {"$sum": 1}, + }, + }, + {"$sort": {"count": -1}}, + {"$set": {"url": "$_id"}}, + { + "$facet": { + "items": [ + {"$skip": skip}, + {"$limit": page_size}, + ], + "total": [{"$count": "count"}], + } + }, + ] + ) + + # Get total + cursor = self.pages.aggregate(aggregate) + results = await cursor.to_list(length=1) + result = results[0] + items = result["items"] + + try: + total = int(result["total"][0]["count"]) + except (IndexError, ValueError): + total = 0 + + return [ + PageUrlCount( + url=data.get("url", ""), + count=data.get("count", 0), + snapshots=[ + PageIdTimestamp( + pageId=p["_id"], ts=p.get("ts"), status=p.get("status", 200) + ) + for p in data.get("pages", []) + ], + ) + for data in items + ], total + + async def set_home_url( + self, coll_id: UUID, update: UpdateCollHomeUrl, org: Organization + ) -> Dict[str, bool]: + """Set home URL for collection and save thumbnail to database""" + if update.pageId: + page = await self.page_ops.get_page(update.pageId, org.id) + update_query = { + "homeUrl": page.url, + "homeUrlTs": page.ts, + "homeUrlPageId": page.id, + } + else: + update_query = { + "homeUrl": None, + "homeUrlTs": None, + "homeUrlPageId": None, + } + + await self.collections.find_one_and_update( + {"_id": coll_id, "oid": org.id}, + {"$set": update_query}, + ) + + return {"updated": True} + + async def upload_thumbnail_stream( + self, stream, filename: str, coll_id: UUID, org: Organization, user: User + ) -> Dict[str, bool]: + """Upload file as stream to use as collection thumbnail""" + coll = await self.get_collection(coll_id) + + _, extension = os.path.splitext(filename) + + image_filename = f"thumbnail-{str(coll_id)}{extension}" + + prefix = org.storage.get_storage_extra_path(str(org.id)) + "images/" + + file_prep = ImageFilePreparer( + prefix, + image_filename, + original_filename=filename, + user=user, + created=dt_now(), + ) + + async def stream_iter(): + """iterate over each chunk and compute and digest + total size""" + async for chunk in stream: + file_prep.add_chunk(chunk) + yield chunk + + print("Collection thumbnail stream upload starting", flush=True) + + if not await self.storage_ops.do_upload_multipart( + org, + file_prep.upload_name, + stream_iter(), + MIN_UPLOAD_PART_SIZE, + ): + print("Collection thumbnail stream upload failed", flush=True) + raise HTTPException(status_code=400, detail="upload_failed") + + print("Collection thumbnail stream upload complete", flush=True) + + thumbnail_file = file_prep.get_image_file(org.storage) + + if thumbnail_file.size > THUMBNAIL_MAX_SIZE: + print( + "Collection thumbnail stream upload failed: max size (2 MB) exceeded", + flush=True, + ) + await self.storage_ops.delete_file_object(org, thumbnail_file) + raise HTTPException(status_code=400, detail="upload_failed") + + if coll.thumbnail: + if not await self.storage_ops.delete_file_object(org, coll.thumbnail): + print( + f"Unable to delete previous collection thumbnail: {coll.thumbnail.filename}" + ) + + coll.thumbnail = thumbnail_file + + # Update entire document to avoid bson.errors.InvalidDocument exception + await self.collections.find_one_and_update( + {"_id": coll_id, "oid": org.id}, + {"$set": coll.to_dict()}, + ) + + return {"added": True} + + async def delete_thumbnail(self, coll_id: UUID, org: Organization): + """Delete collection thumbnail""" + coll = await self.get_collection(coll_id) + + if not coll.thumbnail: + raise HTTPException(status_code=404, detail="thumbnail_not_found") + + if not await self.storage_ops.delete_file_object(org, coll.thumbnail): + print(f"Unable to delete collection thumbnail: {coll.thumbnail.filename}") + raise HTTPException(status_code=400, detail="file_deletion_error") + + # Delete from database + await self.collections.find_one_and_update( + {"_id": coll_id, "oid": org.id}, + {"$set": {"thumbnail": None}}, + ) + + return {"deleted": True} + # ============================================================================ # pylint: disable=too-many-locals -def init_collections_api(app, mdb, orgs, storage_ops, event_webhook_ops): +def init_collections_api(app, mdb, orgs, storage_ops, event_webhook_ops, user_dep): """init collections api""" # pylint: disable=invalid-name, unused-argument, too-many-arguments @@ -461,7 +838,7 @@ async def list_collection_all( access: Optional[str] = None, ): collections, total = await colls.list_collections( - org.id, + org, page_size=pageSize, page=page, sort_by=sortBy, @@ -480,10 +857,10 @@ async def list_collection_all( async def get_collection_all(org: Organization = Depends(org_viewer_dep)): results = {} try: - all_collections, _ = await colls.list_collections(org.id, page_size=10_000) + all_collections, _ = await colls.list_collections(org, page_size=10_000) for collection in all_collections: results[collection.name] = await colls.get_collection_crawl_resources( - collection.id, org + collection.id ) except Exception as exc: # pylint: disable=raise-missing-from @@ -511,7 +888,7 @@ async def get_collection_search_values( async def get_collection( coll_id: UUID, org: Organization = Depends(org_viewer_dep) ): - return await colls.get_collection(coll_id, org) + return await colls.get_collection_out(coll_id, org) @app.get( "/orgs/{oid}/collections/{coll_id}/replay.json", @@ -521,7 +898,7 @@ async def get_collection( async def get_collection_replay( coll_id: UUID, org: Organization = Depends(org_viewer_dep) ): - return await colls.get_collection(coll_id, org, resources=True) + return await colls.get_collection_out(coll_id, org, resources=True) @app.get( "/orgs/{oid}/collections/{coll_id}/public/replay.json", @@ -533,8 +910,8 @@ async def get_collection_public_replay( coll_id: UUID, org: Organization = Depends(org_public), ): - coll = await colls.get_collection( - coll_id, org, resources=True, public_only=True + coll = await colls.get_collection_out( + coll_id, org, resources=True, public_or_unlisted_only=True ) response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Headers"] = "*" @@ -610,11 +987,126 @@ async def download_collection( return await colls.download_collection(coll_id, org) @app.get( - "/public-collections/{org_slug}", - tags=["collections"], + "/public/orgs/{org_slug}/collections", + tags=["collections", "public"], response_model=OrgPublicCollections, ) - async def get_org_public_collections(org_slug: str): - return await colls.get_org_public_collections(org_slug) + async def get_org_public_collections( + org_slug: str, + pageSize: int = DEFAULT_PAGE_SIZE, + page: int = 1, + sortBy: Optional[str] = None, + sortDirection: int = 1, + ): + return await colls.get_org_public_collections( + org_slug, + page_size=pageSize, + page=page, + sort_by=sortBy, + sort_direction=sortDirection, + ) + + @app.get( + "/public/orgs/{org_slug}/collections/{coll_id}", + tags=["collections", "public"], + response_model=PublicCollOut, + ) + async def get_public_collection( + org_slug: str, + coll_id: UUID, + ): + try: + org = await colls.orgs.get_org_by_slug(org_slug) + # pylint: disable=broad-exception-caught + except Exception: + # pylint: disable=raise-missing-from + raise HTTPException(status_code=404, detail="collection_not_found") + + return await colls.get_public_collection_out(coll_id, org, allow_unlisted=True) + + @app.get( + "/public/orgs/{org_slug}/collections/{coll_id}/download", + tags=["collections", "public"], + response_model=bytes, + ) + async def download_public_collection( + org_slug: str, + coll_id: UUID, + ): + try: + org = await colls.orgs.get_org_by_slug(org_slug) + # pylint: disable=broad-exception-caught + except Exception: + # pylint: disable=raise-missing-from + raise HTTPException(status_code=404, detail="collection_not_found") + + # Make sure collection exists and is public/unlisted + coll = await colls.get_collection(coll_id, public_or_unlisted_only=True) + + if coll.allowPublicDownload is False: + raise HTTPException(status_code=403, detail="not_allowed") + + return await colls.download_collection(coll_id, org) + + @app.get( + "/orgs/{oid}/collections/{coll_id}/urls", + tags=["collections"], + response_model=PaginatedPageUrlCountResponse, + ) + async def get_collection_url_list( + coll_id: UUID, + oid: UUID, + urlPrefix: Optional[str] = None, + pageSize: int = DEFAULT_PAGE_SIZE, + page: int = 1, + ): + """Retrieve paginated list of urls in collection sorted by snapshot count""" + pages, total = await colls.list_urls_in_collection( + coll_id=coll_id, + oid=oid, + url_prefix=urlPrefix, + page_size=pageSize, + page=page, + ) + return paginated_format(pages, total, page, pageSize) + + @app.post( + "/orgs/{oid}/collections/{coll_id}/home-url", + tags=["collections"], + response_model=UpdatedResponse, + ) + async def set_collection_home_url( + update: UpdateCollHomeUrl, + coll_id: UUID, + org: Organization = Depends(org_crawl_dep), + ): + return await colls.set_home_url(coll_id, update, org) + + @app.put( + "/orgs/{oid}/collections/{coll_id}/thumbnail", + tags=["collections"], + response_model=AddedResponse, + ) + async def upload_thumbnail_stream( + request: Request, + filename: str, + coll_id: UUID, + org: Organization = Depends(org_crawl_dep), + user: User = Depends(user_dep), + ): + return await colls.upload_thumbnail_stream( + request.stream(), filename, coll_id, org, user + ) + + @app.delete( + "/orgs/{oid}/collections/{coll_id}/thumbnail", + tags=["collections"], + response_model=DeletedResponse, + ) + async def delete_thumbnail_stream( + coll_id: UUID, + org: Organization = Depends(org_crawl_dep), + ): + return await colls.delete_thumbnail(coll_id, org) return colls diff --git a/backend/btrixcloud/crawlmanager.py b/backend/btrixcloud/crawlmanager.py index d91e6de76d..8047d663eb 100644 --- a/backend/btrixcloud/crawlmanager.py +++ b/backend/btrixcloud/crawlmanager.py @@ -118,8 +118,6 @@ async def run_replica_job( async def run_delete_org_job( self, oid: str, - backend_image: str, - pull_policy: str, existing_job_id: Optional[str] = None, ) -> str: """run job to delete org and all of its data""" @@ -130,14 +128,12 @@ async def run_delete_org_job( job_id = f"delete-org-{oid}-{secrets.token_hex(5)}" return await self._run_bg_job_with_ops_classes( - oid, backend_image, pull_policy, job_id, job_type=BgJobType.DELETE_ORG.value + oid, job_id, job_type=BgJobType.DELETE_ORG.value ) async def run_recalculate_org_stats_job( self, oid: str, - backend_image: str, - pull_policy: str, existing_job_id: Optional[str] = None, ) -> str: """run job to recalculate storage stats for the org""" @@ -149,19 +145,32 @@ async def run_recalculate_org_stats_job( return await self._run_bg_job_with_ops_classes( oid, - backend_image, - pull_policy, job_id, job_type=BgJobType.RECALCULATE_ORG_STATS.value, ) - async def _run_bg_job_with_ops_classes( + async def run_re_add_org_pages_job( self, oid: str, - backend_image: str, - pull_policy: str, - job_id: str, - job_type: str, + crawl_type: Optional[str] = None, + existing_job_id: Optional[str] = None, + ) -> str: + """run job to recalculate storage stats for the org""" + + if existing_job_id: + job_id = existing_job_id + else: + job_id = f"org-pages-{oid}-{secrets.token_hex(5)}" + + return await self._run_bg_job_with_ops_classes( + oid, + job_id, + job_type=BgJobType.READD_ORG_PAGES.value, + crawl_type=crawl_type, + ) + + async def _run_bg_job_with_ops_classes( + self, oid: str, job_id: str, job_type: str, **kwargs ) -> str: """run background job with access to ops classes""" @@ -169,8 +178,9 @@ async def _run_bg_job_with_ops_classes( "id": job_id, "oid": oid, "job_type": job_type, - "backend_image": backend_image, - "pull_policy": pull_policy, + "backend_image": os.environ.get("BACKEND_IMAGE", ""), + "pull_policy": os.environ.get("BACKEND_IMAGE_PULL_POLICY", ""), + **kwargs, } data = self.templates.env.get_template("background_job.yaml").render(params) diff --git a/backend/btrixcloud/crawls.py b/backend/btrixcloud/crawls.py index 5a0994fe70..539c408ee6 100644 --- a/backend/btrixcloud/crawls.py +++ b/backend/btrixcloud/crawls.py @@ -918,7 +918,7 @@ async def delete_crawl_qa_run_files( """delete crawl qa wacz files""" qa_run = await self.get_qa_run(crawl_id, qa_run_id, org) for file_ in qa_run.files: - if not await self.storage_ops.delete_crawl_file_object(org, file_): + if not await self.storage_ops.delete_file_object(org, file_): raise HTTPException(status_code=400, detail="file_deletion_error") # Not replicating QA run WACZs yet # await self.background_job_ops.create_delete_replica_jobs( diff --git a/backend/btrixcloud/db.py b/backend/btrixcloud/db.py index f453442191..0723258e40 100644 --- a/backend/btrixcloud/db.py +++ b/backend/btrixcloud/db.py @@ -17,7 +17,7 @@ from .migrations import BaseMigration -CURR_DB_VERSION = "0036" +CURR_DB_VERSION = "0037" # ============================================================================ @@ -82,6 +82,7 @@ async def update_and_prepare_db( invite_ops, storage_ops, page_ops, + background_job_ops, db_inited, ): """Prepare database for application. @@ -94,7 +95,7 @@ async def update_and_prepare_db( """ await ping_db(mdb) print("Database setup started", flush=True) - if await run_db_migrations(mdb, user_manager, page_ops): + if await run_db_migrations(mdb, user_manager, background_job_ops, page_ops): await drop_indexes(mdb) await create_indexes( org_ops, @@ -113,7 +114,7 @@ async def update_and_prepare_db( # ============================================================================ -async def run_db_migrations(mdb, user_manager, page_ops): +async def run_db_migrations(mdb, user_manager, background_job_ops, page_ops): """Run database migrations.""" # if first run, just set version and exit @@ -145,7 +146,9 @@ async def run_db_migrations(mdb, user_manager, page_ops): assert spec.loader migration_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(migration_module) - migration = migration_module.Migration(mdb, page_ops=page_ops) + migration = migration_module.Migration( + mdb, background_job_ops=background_job_ops, page_ops=page_ops + ) if await migration.run(): migrations_run = True except ImportError as err: diff --git a/backend/btrixcloud/main.py b/backend/btrixcloud/main.py index f6b678cb82..927a03dcb8 100644 --- a/backend/btrixcloud/main.py +++ b/backend/btrixcloud/main.py @@ -223,7 +223,9 @@ def main() -> None: profiles, ) - coll_ops = init_collections_api(app, mdb, org_ops, storage_ops, event_webhook_ops) + coll_ops = init_collections_api( + app, mdb, org_ops, storage_ops, event_webhook_ops, current_active_user + ) base_crawl_init = ( app, @@ -243,14 +245,15 @@ def main() -> None: crawls = init_crawls_api(crawl_manager, *base_crawl_init) + upload_ops = init_uploads_api(*base_crawl_init) + page_ops = init_pages_api( - app, mdb, crawls, org_ops, storage_ops, current_active_user + app, mdb, crawls, org_ops, storage_ops, background_job_ops, current_active_user ) base_crawl_ops.set_page_ops(page_ops) crawls.set_page_ops(page_ops) - - init_uploads_api(*base_crawl_init) + upload_ops.set_page_ops(page_ops) org_ops.set_ops(base_crawl_ops, profiles, coll_ops, background_job_ops) @@ -260,6 +263,8 @@ def main() -> None: crawl_config_ops.set_coll_ops(coll_ops) + coll_ops.set_page_ops(page_ops) + # run only in first worker if run_once_lock("btrix-init-db"): asyncio.create_task( @@ -273,6 +278,7 @@ def main() -> None: invites, storage_ops, page_ops, + background_job_ops, db_inited, ) ) diff --git a/backend/btrixcloud/main_bg.py b/backend/btrixcloud/main_bg.py index 2fba05e53f..709139d8d2 100644 --- a/backend/btrixcloud/main_bg.py +++ b/backend/btrixcloud/main_bg.py @@ -12,6 +12,7 @@ job_type = os.environ.get("BG_JOB_TYPE") oid = os.environ.get("OID") +crawl_type = os.environ.get("CRAWL_TYPE") # ============================================================================ @@ -27,7 +28,7 @@ async def main(): ) return 1 - (org_ops, _, _, _, _, _, _, _, _, _, user_manager) = init_ops() + (org_ops, _, _, _, _, page_ops, coll_ops, _, _, _, _, user_manager) = init_ops() if not oid: print("Org id missing, quitting") @@ -57,6 +58,17 @@ async def main(): traceback.print_exc() return 1 + if job_type == BgJobType.READD_ORG_PAGES: + try: + await page_ops.re_add_all_crawl_pages(org, crawl_type=crawl_type) + await coll_ops.recalculate_org_collection_dates(org) + await coll_ops.recalculate_org_collection_counts_tags(org) + return 0 + # pylint: disable=broad-exception-caught + except Exception: + traceback.print_exc() + return 1 + print(f"Provided job type {job_type} not currently supported") return 1 diff --git a/backend/btrixcloud/main_op.py b/backend/btrixcloud/main_op.py index a6f6654be3..af7a2d0956 100644 --- a/backend/btrixcloud/main_op.py +++ b/backend/btrixcloud/main_op.py @@ -31,6 +31,7 @@ def main(): crawl_config_ops, _, crawl_ops, + _, page_ops, coll_ops, _, diff --git a/backend/btrixcloud/migrations/migration_0037_upload_pages.py b/backend/btrixcloud/migrations/migration_0037_upload_pages.py new file mode 100644 index 0000000000..62bfe98237 --- /dev/null +++ b/backend/btrixcloud/migrations/migration_0037_upload_pages.py @@ -0,0 +1,72 @@ +""" +Migration 0037 -- upload pages +""" + +from uuid import UUID + +from btrixcloud.migrations import BaseMigration + + +MIGRATION_VERSION = "0037" + + +class Migration(BaseMigration): + """Migration class.""" + + # pylint: disable=unused-argument + def __init__(self, mdb, **kwargs): + super().__init__(mdb, migration_version=MIGRATION_VERSION) + + self.background_job_ops = kwargs.get("background_job_ops") + self.page_ops = kwargs.get("page_ops") + + async def org_upload_pages_already_added(self, oid: UUID) -> bool: + """Check if upload pages have already been added for this org""" + if self.page_ops is None: + print( + f"page_ops missing, assuming pages need to be added for org {oid}", + flush=True, + ) + return False + + mdb_crawls = self.mdb["crawls"] + async for upload in mdb_crawls.find({"oid": oid, "type": "upload"}): + upload_id = upload["_id"] + _, total = await self.page_ops.list_pages(upload_id) + if total > 0: + return True + return False + + async def migrate_up(self): + """Perform migration up. + + Start background jobs to parse uploads and add their pages to db + """ + if self.background_job_ops is None: + print( + "Unable to start background job, missing background_job_ops", flush=True + ) + return + + mdb_orgs = self.mdb["organizations"] + async for org in mdb_orgs.find(): + oid = org["_id"] + + pages_already_added = await self.org_upload_pages_already_added(oid) + + if pages_already_added: + print( + f"Skipping org {oid}, upload pages already added to db", flush=True + ) + continue + + try: + await self.background_job_ops.create_re_add_org_pages_job( + oid, crawl_type="upload" + ) + # pylint: disable=broad-exception-caught + except Exception as err: + print( + f"Error starting background job to add upload pges to org {oid}: {err}", + flush=True, + ) diff --git a/backend/btrixcloud/models.py b/backend/btrixcloud/models.py index f18412c2df..807628fdc3 100644 --- a/backend/btrixcloud/models.py +++ b/backend/btrixcloud/models.py @@ -5,6 +5,9 @@ from datetime import datetime from enum import Enum, IntEnum from uuid import UUID +import base64 +import hashlib +import mimetypes import os from typing import Optional, List, Dict, Union, Literal, Any, get_args @@ -21,6 +24,7 @@ BeforeValidator, TypeAdapter, ) +from pathvalidate import sanitize_filename # from fastapi_users import models as fastapi_users_models @@ -29,6 +33,20 @@ # crawl scale for constraint MAX_CRAWL_SCALE = int(os.environ.get("MAX_CRAWL_SCALE", 3)) +# Presign duration must be less than 604800 seconds (one week), +# so set this one minute short of a week +PRESIGN_MINUTES_MAX = 10079 +PRESIGN_MINUTES_DEFAULT = PRESIGN_MINUTES_MAX + +# Expire duration seconds for presigned urls +PRESIGN_DURATION_MINUTES = int( + os.environ.get("PRESIGN_DURATION_MINUTES") or PRESIGN_MINUTES_DEFAULT +) +PRESIGN_DURATION_SECONDS = min(PRESIGN_DURATION_MINUTES, PRESIGN_MINUTES_MAX) * 60 + +# Minimum part size for file uploads +MIN_UPLOAD_PART_SIZE = 10000000 + # annotated types # ============================================================================ @@ -779,6 +797,9 @@ class BaseCrawl(CoreCrawlable, BaseMongoModel): reviewStatus: ReviewStatus = None + filePageCount: Optional[int] = 0 + errorPageCount: Optional[int] = 0 + # ============================================================================ class CollIdName(BaseModel): @@ -995,9 +1016,6 @@ class Crawl(BaseCrawl, CrawlConfigCore): qa: Optional[QARun] = None qaFinished: Optional[Dict[str, QARun]] = {} - filePageCount: Optional[int] = 0 - errorPageCount: Optional[int] = 0 - # ============================================================================ class CrawlCompleteIn(BaseModel): @@ -1050,6 +1068,155 @@ class UpdateUpload(UpdateCrawl): """Update modal that also includes name""" +# ============================================================================ +class FilePreparer: + """wrapper to compute digest / name for streaming upload""" + + def __init__(self, prefix, filename): + self.upload_size = 0 + self.upload_hasher = hashlib.sha256() + self.upload_name = prefix + self.prepare_filename(filename) + + def add_chunk(self, chunk): + """add chunk for file""" + self.upload_size += len(chunk) + self.upload_hasher.update(chunk) + + def get_crawl_file(self, storage: StorageRef): + """get crawl file""" + return CrawlFile( + filename=self.upload_name, + hash=self.upload_hasher.hexdigest(), + size=self.upload_size, + storage=storage, + ) + + def prepare_filename(self, filename): + """prepare filename by sanitizing and adding extra string + to avoid duplicates""" + name = sanitize_filename(filename.rsplit("/", 1)[-1]) + parts = name.split(".") + randstr = base64.b32encode(os.urandom(5)).lower() + parts[0] += "-" + randstr.decode("utf-8") + return ".".join(parts) + + +# ============================================================================ + +### USER-UPLOADED IMAGES ### + + +# ============================================================================ +class ImageFileOut(BaseModel): + """output for user-upload imaged file (conformance to Data Resource Spec)""" + + name: str + path: str + hash: str + size: int + + originalFilename: str + mime: str + userid: UUID + userName: str + created: datetime + + +# ============================================================================ +class PublicImageFileOut(BaseModel): + """public output for user-upload imaged file (conformance to Data Resource Spec)""" + + name: str + path: str + hash: str + size: int + + mime: str + + +# ============================================================================ +class ImageFile(BaseFile): + """User-uploaded image file""" + + originalFilename: str + mime: str + userid: UUID + userName: str + created: datetime + + async def get_image_file_out(self, org, storage_ops) -> ImageFileOut: + """Get ImageFileOut with new presigned url""" + presigned_url = await storage_ops.get_presigned_url( + org, self, PRESIGN_DURATION_SECONDS + ) + + return ImageFileOut( + name=self.filename, + path=presigned_url or "", + hash=self.hash, + size=self.size, + originalFilename=self.originalFilename, + mime=self.mime, + userid=self.userid, + userName=self.userName, + created=self.created, + ) + + async def get_public_image_file_out(self, org, storage_ops) -> PublicImageFileOut: + """Get PublicImageFileOut with new presigned url""" + presigned_url = await storage_ops.get_presigned_url( + org, self, PRESIGN_DURATION_SECONDS + ) + + return PublicImageFileOut( + name=self.filename, + path=presigned_url or "", + hash=self.hash, + size=self.size, + mime=self.mime, + ) + + +# ============================================================================ +class ImageFilePreparer(FilePreparer): + """Wrapper for user image streaming uploads""" + + # pylint: disable=too-many-arguments, too-many-function-args + + def __init__( + self, + prefix, + filename, + original_filename: str, + user: User, + created: datetime, + ): + super().__init__(prefix, filename) + + self.original_filename = original_filename + self.mime, _ = mimetypes.guess_type(original_filename) or ("image/jpeg", None) + self.userid = user.id + self.user_name = user.name + self.created = created + + def get_image_file( + self, + storage: StorageRef, + ) -> ImageFile: + """get user-uploaded image file""" + return ImageFile( + filename=self.upload_name, + hash=self.upload_hasher.hexdigest(), + size=self.upload_size, + storage=storage, + originalFilename=self.original_filename, + mime=self.mime, + userid=self.userid, + userName=self.user_name, + created=self.created, + ) + + # ============================================================================ ### COLLECTIONS ### @@ -1071,17 +1238,30 @@ class Collection(BaseMongoModel): name: str = Field(..., min_length=1) oid: UUID description: Optional[str] = None + caption: Optional[str] = None modified: Optional[datetime] = None crawlCount: Optional[int] = 0 pageCount: Optional[int] = 0 totalSize: Optional[int] = 0 + dateEarliest: Optional[datetime] = None + dateLatest: Optional[datetime] = None + # Sorted by count, descending tags: Optional[List[str]] = [] access: CollAccessType = CollAccessType.PRIVATE + homeUrl: Optional[AnyHttpUrl] = None + homeUrlTs: Optional[datetime] = None + homeUrlPageId: Optional[UUID] = None + + thumbnail: Optional[ImageFile] = None + defaultThumbnailName: Optional[str] = None + + allowPublicDownload: Optional[bool] = True + # ============================================================================ class CollIn(BaseModel): @@ -1089,16 +1269,74 @@ class CollIn(BaseModel): name: str = Field(..., min_length=1) description: Optional[str] = None + caption: Optional[str] = None crawlIds: Optional[List[str]] = [] access: CollAccessType = CollAccessType.PRIVATE + defaultThumbnailName: Optional[str] = None + allowPublicDownload: bool = True + # ============================================================================ -class CollOut(Collection): +class CollOut(BaseMongoModel): """Collection output model with annotations.""" + name: str + oid: UUID + description: Optional[str] = None + caption: Optional[str] = None + modified: Optional[datetime] = None + + crawlCount: Optional[int] = 0 + pageCount: Optional[int] = 0 + totalSize: Optional[int] = 0 + + dateEarliest: Optional[datetime] = None + dateLatest: Optional[datetime] = None + + # Sorted by count, descending + tags: Optional[List[str]] = [] + + access: CollAccessType = CollAccessType.PRIVATE + + homeUrl: Optional[AnyHttpUrl] = None + homeUrlTs: Optional[datetime] = None + homeUrlPageId: Optional[UUID] = None + + resources: List[CrawlFileOut] = [] + thumbnail: Optional[ImageFileOut] = None + defaultThumbnailName: Optional[str] = None + + allowPublicDownload: bool = True + + +# ============================================================================ +class PublicCollOut(BaseMongoModel): + """Collection output model with annotations.""" + + name: str + oid: UUID + description: Optional[str] = None + caption: Optional[str] = None + + crawlCount: Optional[int] = 0 + pageCount: Optional[int] = 0 + totalSize: Optional[int] = 0 + + dateEarliest: Optional[datetime] = None + dateLatest: Optional[datetime] = None + + access: CollAccessType = CollAccessType.PUBLIC + + homeUrl: Optional[AnyHttpUrl] = None + homeUrlTs: Optional[datetime] = None + resources: List[CrawlFileOut] = [] + thumbnail: Optional[PublicImageFileOut] = None + defaultThumbnailName: Optional[str] = None + + allowPublicDownload: bool = True # ============================================================================ @@ -1107,7 +1345,17 @@ class UpdateColl(BaseModel): name: Optional[str] = None description: Optional[str] = None + caption: Optional[str] = None access: Optional[CollAccessType] = None + defaultThumbnailName: Optional[str] = None + allowPublicDownload: Optional[bool] = None + + +# ============================================================================ +class UpdateCollHomeUrl(BaseModel): + """Update home url for collection""" + + pageId: Optional[UUID] = None # ============================================================================ @@ -1167,7 +1415,7 @@ class OrgPublicCollections(BaseModel): org: PublicOrgDetails - collections: List[CollOut] = [] + collections: List[PublicCollOut] = [] # ============================================================================ @@ -2066,6 +2314,7 @@ class BgJobType(str, Enum): DELETE_REPLICA = "delete-replica" DELETE_ORG = "delete-org" RECALCULATE_ORG_STATS = "recalculate-org-stats" + READD_ORG_PAGES = "readd-org-pages" # ============================================================================ @@ -2119,6 +2368,14 @@ class RecalculateOrgStatsJob(BackgroundJob): type: Literal[BgJobType.RECALCULATE_ORG_STATS] = BgJobType.RECALCULATE_ORG_STATS +# ============================================================================ +class ReAddOrgPagesJob(BackgroundJob): + """Model for tracking jobs to readd an org's pages""" + + type: Literal[BgJobType.READD_ORG_PAGES] = BgJobType.READD_ORG_PAGES + crawl_type: Optional[str] = None + + # ============================================================================ # Union of all job types, for response model @@ -2129,6 +2386,7 @@ class RecalculateOrgStatsJob(BackgroundJob): BackgroundJob, DeleteOrgJob, RecalculateOrgStatsJob, + ReAddOrgPagesJob, ] ] @@ -2240,7 +2498,7 @@ class PageWithAllQA(Page): class PageOut(Page): """Model for pages output, no QA""" - status: Optional[int] = 200 + status: int = 200 # ============================================================================ @@ -2266,6 +2524,24 @@ class PageNoteUpdatedResponse(BaseModel): data: PageNote +# ============================================================================ +class PageIdTimestamp(BaseModel): + """Simplified model for page info to include in PageUrlCount""" + + pageId: UUID + ts: Optional[datetime] = None + status: int = 200 + + +# ============================================================================ +class PageUrlCount(BaseModel): + """Model for counting pages by URL""" + + url: AnyHttpUrl + count: int = 0 + snapshots: List[PageIdTimestamp] = [] + + # ============================================================================ ### GENERIC RESPONSE MODELS ### @@ -2512,3 +2788,10 @@ class PaginatedUserEmailsResponse(PaginatedResponse): """Response model for user emails with org info""" items: List[UserEmailWithOrgInfo] + + +# ============================================================================ +class PaginatedPageUrlCountResponse(PaginatedResponse): + """Response model for page count by url""" + + items: List[PageUrlCount] diff --git a/backend/btrixcloud/ops.py b/backend/btrixcloud/ops.py index 23629de2aa..bee24d00c5 100644 --- a/backend/btrixcloud/ops.py +++ b/backend/btrixcloud/ops.py @@ -16,6 +16,7 @@ from .pages import PageOps from .profiles import ProfileOps from .storages import StorageOps +from .uploads import UploadOps from .users import UserManager from .webhooks import EventWebhookOps @@ -26,6 +27,7 @@ def init_ops() -> Tuple[ CrawlConfigOps, BaseCrawlOps, CrawlOps, + UploadOps, PageOps, CollectionOps, ProfileOps, @@ -70,7 +72,7 @@ def init_ops() -> Tuple[ coll_ops = CollectionOps(mdb, crawl_manager, org_ops, event_webhook_ops) - base_crawl_ops = BaseCrawlOps( + base_crawl_init = ( mdb, user_manager, org_ops, @@ -81,23 +83,17 @@ def init_ops() -> Tuple[ background_job_ops, ) - crawl_ops = CrawlOps( - crawl_manager, - mdb, - user_manager, - org_ops, - crawl_config_ops, - coll_ops, - storage_ops, - event_webhook_ops, - background_job_ops, - ) + base_crawl_ops = BaseCrawlOps(*base_crawl_init) - page_ops = PageOps(mdb, crawl_ops, org_ops, storage_ops) + crawl_ops = CrawlOps(crawl_manager, *base_crawl_init) - base_crawl_ops.set_page_ops(page_ops) + upload_ops = UploadOps(*base_crawl_init) + page_ops = PageOps(mdb, crawl_ops, org_ops, storage_ops, background_job_ops) + + base_crawl_ops.set_page_ops(page_ops) crawl_ops.set_page_ops(page_ops) + upload_ops.set_page_ops(page_ops) background_job_ops.set_ops(crawl_ops, profile_ops) @@ -109,11 +105,14 @@ def init_ops() -> Tuple[ crawl_config_ops.set_coll_ops(coll_ops) + coll_ops.set_page_ops(page_ops) + return ( org_ops, crawl_config_ops, base_crawl_ops, crawl_ops, + upload_ops, page_ops, coll_ops, profile_ops, diff --git a/backend/btrixcloud/pages.py b/backend/btrixcloud/pages.py index a980567c49..251d959be1 100644 --- a/backend/btrixcloud/pages.py +++ b/backend/btrixcloud/pages.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Optional, Tuple, List, Dict, Any, Union from uuid import UUID, uuid4 -from fastapi import Depends, HTTPException +from fastapi import Depends, HTTPException, Request import pymongo from .models import ( @@ -24,6 +24,7 @@ PageNoteEdit, PageNoteDelete, QARunBucketStats, + StartedResponse, StartedResponseBool, UpdatedResponse, DeletedResponse, @@ -34,11 +35,12 @@ from .utils import str_to_date, str_list_to_bools, dt_now if TYPE_CHECKING: + from .background_jobs import BackgroundJobOps from .crawls import CrawlOps from .orgs import OrgOps from .storages import StorageOps else: - CrawlOps = StorageOps = OrgOps = object + CrawlOps = StorageOps = OrgOps = BackgroundJobOps = object # ============================================================================ @@ -49,18 +51,24 @@ class PageOps: crawl_ops: CrawlOps org_ops: OrgOps storage_ops: StorageOps + background_job_ops: BackgroundJobOps - def __init__(self, mdb, crawl_ops, org_ops, storage_ops): + def __init__(self, mdb, crawl_ops, org_ops, storage_ops, background_job_ops): self.pages = mdb["pages"] self.crawls = mdb["crawls"] self.crawl_ops = crawl_ops self.org_ops = org_ops self.storage_ops = storage_ops + self.background_job_ops = background_job_ops async def init_index(self): """init index for pages db collection""" await self.pages.create_index([("crawl_id", pymongo.HASHED)]) + async def set_ops(self, background_job_ops: BackgroundJobOps): + """Set ops classes as needed""" + self.background_job_ops = background_job_ops + async def add_crawl_pages_to_db_from_wacz(self, crawl_id: str, batch_size=100): """Add pages to database from WACZ files""" pages_buffer: List[Page] = [] @@ -94,9 +102,14 @@ def _get_page_from_dict( self, page_dict: Dict[str, Any], crawl_id: str, oid: UUID ) -> Page: """Return Page object from dict""" - page_id = page_dict.get("id") + page_id = page_dict.get("id", "") if not page_id: - print(f'Page {page_dict.get("url")} has no id - assigning UUID', flush=True) + page_id = uuid4() + + try: + UUID(page_id) + except ValueError: + page_id = uuid4() status = page_dict.get("status") if not status and page_dict.get("loadState"): @@ -199,10 +212,7 @@ async def update_crawl_file_and_error_counts( inc_query["errorPageCount"] = error_count await self.crawls.find_one_and_update( - { - "_id": crawl_id, - "type": "crawl", - }, + {"_id": crawl_id}, {"$inc": inc_query}, ) @@ -554,13 +564,17 @@ async def re_add_crawl_pages(self, crawl_id: str, oid: UUID): print(f"Deleted pages for crawl {crawl_id}", flush=True) await self.add_crawl_pages_to_db_from_wacz(crawl_id) - async def re_add_all_crawl_pages(self, oid: UUID): - """Re-add pages for all crawls in org""" - crawl_ids = await self.crawls.distinct( - "_id", {"type": "crawl", "finished": {"$ne": None}} - ) + async def re_add_all_crawl_pages( + self, org: Organization, crawl_type: Optional[str] = None + ): + """Re-add pages for all crawls and uploads in org""" + match_query: Dict[str, object] = {"finished": {"$ne": None}} + if crawl_type in ("crawl", "upload"): + match_query["type"] = crawl_type + + crawl_ids = await self.crawls.distinct("_id", match_query) for crawl_id in crawl_ids: - await self.re_add_crawl_pages(crawl_id, oid) + await self.re_add_crawl_pages(crawl_id, org.id) async def get_qa_run_aggregate_counts( self, @@ -630,47 +644,102 @@ async def get_qa_run_aggregate_counts( return sorted(return_data, key=lambda bucket: bucket.lowerBoundary) + def get_crawl_type_from_pages_route(self, request: Request): + """Get crawl type to filter on from request route""" + crawl_type = None + + try: + route_path = request.scope["route"].path + type_path = route_path.split("/")[4] + + if type_path == "uploads": + crawl_type = "upload" + if type_path == "crawls": + crawl_type = "crawl" + except (IndexError, AttributeError): + pass + + return crawl_type + # ============================================================================ # pylint: disable=too-many-arguments, too-many-locals, invalid-name, fixme -def init_pages_api(app, mdb, crawl_ops, org_ops, storage_ops, user_dep): +def init_pages_api( + app, mdb, crawl_ops, org_ops, storage_ops, background_job_ops, user_dep +): """init pages API""" # pylint: disable=invalid-name - ops = PageOps(mdb, crawl_ops, org_ops, storage_ops) + ops = PageOps(mdb, crawl_ops, org_ops, storage_ops, background_job_ops) org_crawl_dep = org_ops.org_crawl_dep @app.post( "/orgs/{oid}/crawls/all/pages/reAdd", - tags=["pages"], - response_model=StartedResponseBool, + tags=["pages", "crawls"], + response_model=StartedResponse, + ) + @app.post( + "/orgs/{oid}/uploads/all/pages/reAdd", + tags=["pages", "uploads"], + response_model=StartedResponse, + ) + @app.post( + "/orgs/{oid}/all-crawls/all/pages/reAdd", + tags=["pages", "all-crawls"], + response_model=StartedResponse, ) async def re_add_all_crawl_pages( - org: Organization = Depends(org_crawl_dep), user: User = Depends(user_dep) + request: Request, + org: Organization = Depends(org_crawl_dep), + user: User = Depends(user_dep), ): - """Re-add pages for all crawls in org (superuser only)""" + """Re-add pages for all crawls in org (superuser only, may delete page QA data!)""" if not user.is_superuser: raise HTTPException(status_code=403, detail="Not Allowed") - asyncio.create_task(ops.re_add_all_crawl_pages(org.id)) - return {"started": True} + crawl_type = ops.get_crawl_type_from_pages_route(request) + job_id = await ops.background_job_ops.create_re_add_org_pages_job( + org.id, crawl_type=crawl_type + ) + return {"started": job_id or ""} @app.post( "/orgs/{oid}/crawls/{crawl_id}/pages/reAdd", - tags=["pages"], + tags=["pages", "crawls"], + response_model=StartedResponseBool, + ) + @app.post( + "/orgs/{oid}/uploads/{crawl_id}/pages/reAdd", + tags=["pages", "uploads"], + response_model=StartedResponseBool, + ) + @app.post( + "/orgs/{oid}/all-crawls/{crawl_id}/pages/reAdd", + tags=["pages", "all-crawls"], response_model=StartedResponseBool, ) async def re_add_crawl_pages( - crawl_id: str, org: Organization = Depends(org_crawl_dep) + crawl_id: str, + org: Organization = Depends(org_crawl_dep), ): - """Re-add pages for crawl""" + """Re-add pages for crawl (may delete page QA data!)""" asyncio.create_task(ops.re_add_crawl_pages(crawl_id, org.id)) return {"started": True} @app.get( "/orgs/{oid}/crawls/{crawl_id}/pages/{page_id}", - tags=["pages"], + tags=["pages", "crawls"], + response_model=PageOut, + ) + @app.get( + "/orgs/{oid}/uploads/{crawl_id}/pages/{page_id}", + tags=["pages", "uploads"], + response_model=PageOut, + ) + @app.get( + "/orgs/{oid}/all-crawls/{crawl_id}/pages/{page_id}", + tags=["pages", "all-crawls"], response_model=PageOut, ) async def get_page( @@ -692,7 +761,7 @@ async def get_page_with_qa( page_id: UUID, org: Organization = Depends(org_crawl_dep), ): - """GET single page""" + """GET single page with QA details""" return await ops.get_page_out(page_id, org.id, crawl_id, qa_run_id=qa_run_id) @app.patch( @@ -753,12 +822,22 @@ async def delete_page_notes( delete: PageNoteDelete, org: Organization = Depends(org_crawl_dep), ): - """Edit page note""" + """Delete page note""" return await ops.delete_page_notes(page_id, org.id, delete, crawl_id) @app.get( "/orgs/{oid}/crawls/{crawl_id}/pages", - tags=["pages"], + tags=["pages", "crawls"], + response_model=PaginatedPageOutResponse, + ) + @app.get( + "/orgs/{oid}/uploads/{crawl_id}/pages", + tags=["pages", "uploads"], + response_model=PaginatedPageOutResponse, + ) + @app.get( + "/orgs/{oid}/all-crawls/{crawl_id}/pages", + tags=["pages", "all-crawls"], response_model=PaginatedPageOutResponse, ) async def get_pages_list( diff --git a/backend/btrixcloud/profiles.py b/backend/btrixcloud/profiles.py index ab72422472..9b8ae8da8f 100644 --- a/backend/btrixcloud/profiles.py +++ b/backend/btrixcloud/profiles.py @@ -426,7 +426,7 @@ async def delete_profile( # Delete file from storage if profile.resource: - await self.storage_ops.delete_crawl_file_object(org, profile.resource) + await self.storage_ops.delete_file_object(org, profile.resource) await self.orgs.inc_org_bytes_stored( org.id, -profile.resource.size, "profile" ) diff --git a/backend/btrixcloud/storages.py b/backend/btrixcloud/storages.py index 3c79648529..e167449eb5 100644 --- a/backend/btrixcloud/storages.py +++ b/backend/btrixcloud/storages.py @@ -475,9 +475,7 @@ async def get_presigned_url( return presigned_url - async def delete_crawl_file_object( - self, org: Organization, crawlfile: BaseFile - ) -> bool: + async def delete_file_object(self, org: Organization, crawlfile: BaseFile) -> bool: """delete crawl file from storage.""" return await self._delete_file(org, crawlfile.filename, crawlfile.storage) diff --git a/backend/btrixcloud/uploads.py b/backend/btrixcloud/uploads.py index ded0630719..e95b30427f 100644 --- a/backend/btrixcloud/uploads.py +++ b/backend/btrixcloud/uploads.py @@ -1,9 +1,6 @@ """ handle user uploads into browsertrix """ import uuid -import hashlib -import os -import base64 from urllib.parse import unquote from uuid import UUID @@ -13,7 +10,6 @@ from fastapi import Depends, UploadFile, File from fastapi import HTTPException from starlette.requests import Request -from pathvalidate import sanitize_filename from .basecrawls import BaseCrawlOps from .storages import CHUNK_SIZE @@ -27,18 +23,16 @@ Organization, PaginatedCrawlOutResponse, User, - StorageRef, UpdatedResponse, DeletedResponseQuota, AddedResponseIdQuota, + FilePreparer, + MIN_UPLOAD_PART_SIZE, ) from .pagination import paginated_format, DEFAULT_PAGE_SIZE from .utils import dt_now -MIN_UPLOAD_PART_SIZE = 10000000 - - # ============================================================================ class UploadOps(BaseCrawlOps): """upload ops""" @@ -105,9 +99,10 @@ async def stream_iter(): if prev_upload: try: await self._delete_crawl_files(prev_upload, org) + await self.page_ops.delete_crawl_pages(prev_upload.id, org.id) # pylint: disable=broad-exception-caught except Exception as exc: - print("replace file deletion failed", exc) + print(f"Error handling previous upload: {exc}", flush=True) return await self._create_upload( files, name, description, collections, tags, id_, org, user @@ -195,6 +190,8 @@ async def _create_upload( self.event_webhook_ops.create_upload_finished_notification(crawl_id, org.id) ) + asyncio.create_task(self.page_ops.add_crawl_pages_to_db_from_wacz(crawl_id)) + await self.orgs.inc_org_bytes_stored(org.id, file_size, "upload") quota_reached = self.orgs.storage_quota_reached(org) @@ -224,39 +221,6 @@ async def delete_uploads( return {"deleted": True, "storageQuotaReached": quota_reached} -# ============================================================================ -class FilePreparer: - """wrapper to compute digest / name for streaming upload""" - - def __init__(self, prefix, filename): - self.upload_size = 0 - self.upload_hasher = hashlib.sha256() - self.upload_name = prefix + self.prepare_filename(filename) - - def add_chunk(self, chunk): - """add chunk for file""" - self.upload_size += len(chunk) - self.upload_hasher.update(chunk) - - def get_crawl_file(self, storage: StorageRef): - """get crawl file""" - return CrawlFile( - filename=self.upload_name, - hash=self.upload_hasher.hexdigest(), - size=self.upload_size, - storage=storage, - ) - - def prepare_filename(self, filename): - """prepare filename by sanitizing and adding extra string - to avoid duplicates""" - name = sanitize_filename(filename.rsplit("/", 1)[-1]) - parts = name.split(".") - randstr = base64.b32encode(os.urandom(5)).lower() - parts[0] += "-" + randstr.decode("utf-8") - return ".".join(parts) - - # ============================================================================ class UploadFileReader(BufferedReader): """Compute digest on file upload""" @@ -446,3 +410,5 @@ async def delete_uploads( org: Organization = Depends(org_crawl_dep), ): return await ops.delete_uploads(delete_list, org, user) + + return ops diff --git a/backend/test/data/thumbnail.jpg b/backend/test/data/thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..133cfae2b048546ea31f8eb79ff6dd4517cbee50 GIT binary patch literal 27636 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 Z;PY)4VWAiDY&4|lp(I<8ZQuS*{|5?yN#y_l literal 0 HcmV?d00001 diff --git a/backend/test/test_collections.py b/backend/test/test_collections.py index 4faf42540d..c2999bb5ad 100644 --- a/backend/test/test_collections.py +++ b/backend/test/test_collections.py @@ -1,5 +1,6 @@ import requests import os +from uuid import uuid4 from zipfile import ZipFile, ZIP_STORED from tempfile import TemporaryFile @@ -12,12 +13,24 @@ UPDATED_NAME = "Updated tést cöllection" SECOND_COLLECTION_NAME = "second-collection" DESCRIPTION = "Test description" +CAPTION = "Short caption" +UPDATED_CAPTION = "Updated caption" + +NON_PUBLIC_COLL_FIELDS = ( + "modified", + "tags", + "homeUrlPageId", +) +NON_PUBLIC_IMAGE_FIELDS = ("originalFilename", "userid", "userName", "created") + _coll_id = None _second_coll_id = None _public_coll_id = None +_second_public_coll_id = None upload_id = None modified = None +default_org_slug = None curr_dir = os.path.dirname(os.path.realpath(__file__)) @@ -25,12 +38,16 @@ def test_create_collection( crawler_auth_headers, default_org_id, crawler_crawl_id, admin_crawl_id ): + default_thumbnail_name = "default-thumbnail.jpg" + r = requests.post( f"{API_PREFIX}/orgs/{default_org_id}/collections", headers=crawler_auth_headers, json={ "crawlIds": [crawler_crawl_id], "name": COLLECTION_NAME, + "caption": CAPTION, + "defaultThumbnailName": default_thumbnail_name, }, ) assert r.status_code == 200 @@ -49,6 +66,29 @@ def test_create_collection( assert _coll_id in r.json()["collectionIds"] assert r.json()["collections"] == [{"name": COLLECTION_NAME, "id": _coll_id}] + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_coll_id}", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + + assert data["id"] == _coll_id + assert data["name"] == COLLECTION_NAME + assert data["caption"] == CAPTION + assert data["crawlCount"] == 1 + assert data["pageCount"] > 0 + assert data["totalSize"] > 0 + modified = data["modified"] + assert modified + assert modified.endswith("Z") + + assert data["dateEarliest"] + assert data["dateLatest"] + + assert data["defaultThumbnailName"] == default_thumbnail_name + assert data["allowPublicDownload"] + def test_create_public_collection( crawler_auth_headers, default_org_id, crawler_crawl_id, admin_crawl_id @@ -59,7 +99,9 @@ def test_create_public_collection( json={ "crawlIds": [crawler_crawl_id], "name": PUBLIC_COLLECTION_NAME, + "caption": CAPTION, "access": "public", + "allowPublicDownload": False, }, ) assert r.status_code == 200 @@ -115,6 +157,7 @@ def test_update_collection( headers=crawler_auth_headers, json={ "description": DESCRIPTION, + "caption": UPDATED_CAPTION, }, ) assert r.status_code == 200 @@ -130,6 +173,7 @@ def test_update_collection( assert data["id"] == _coll_id assert data["name"] == COLLECTION_NAME assert data["description"] == DESCRIPTION + assert data["caption"] == UPDATED_CAPTION assert data["crawlCount"] == 1 assert data["pageCount"] > 0 assert data["totalSize"] > 0 @@ -137,6 +181,9 @@ def test_update_collection( modified = data["modified"] assert modified assert modified.endswith("Z") + assert data["dateEarliest"] + assert data["dateLatest"] + assert data["defaultThumbnailName"] def test_rename_collection( @@ -211,6 +258,8 @@ def test_add_remove_crawl_from_collection( assert data["totalSize"] > 0 assert data["modified"] >= modified assert data["tags"] == ["wr-test-2", "wr-test-1"] + assert data["dateEarliest"] + assert data["dateLatest"] # Verify it was added r = requests.get( @@ -233,6 +282,8 @@ def test_add_remove_crawl_from_collection( assert data["totalSize"] == 0 assert data["modified"] >= modified assert data.get("tags", []) == [] + assert data.get("dateEarliest") is None + assert data.get("dateLatest") is None # Verify they were removed r = requests.get( @@ -261,6 +312,8 @@ def test_add_remove_crawl_from_collection( assert data["totalSize"] > 0 assert data["modified"] >= modified assert data["tags"] == ["wr-test-2", "wr-test-1"] + assert data["dateEarliest"] + assert data["dateLatest"] def test_get_collection(crawler_auth_headers, default_org_id): @@ -274,11 +327,15 @@ def test_get_collection(crawler_auth_headers, default_org_id): assert data["name"] == UPDATED_NAME assert data["oid"] == default_org_id assert data["description"] == DESCRIPTION + assert data["caption"] == UPDATED_CAPTION assert data["crawlCount"] == 2 assert data["pageCount"] > 0 assert data["totalSize"] > 0 assert data["modified"] >= modified assert data["tags"] == ["wr-test-2", "wr-test-1"] + assert data["dateEarliest"] + assert data["dateLatest"] + assert data["defaultThumbnailName"] def test_get_collection_replay(crawler_auth_headers, default_org_id): @@ -292,11 +349,15 @@ def test_get_collection_replay(crawler_auth_headers, default_org_id): assert data["name"] == UPDATED_NAME assert data["oid"] == default_org_id assert data["description"] == DESCRIPTION + assert data["caption"] == UPDATED_CAPTION assert data["crawlCount"] == 2 assert data["pageCount"] > 0 assert data["totalSize"] > 0 assert data["modified"] >= modified assert data["tags"] == ["wr-test-2", "wr-test-1"] + assert data["dateEarliest"] + assert data["dateLatest"] + assert data["defaultThumbnailName"] resources = data["resources"] assert resources @@ -413,6 +474,9 @@ def test_add_upload_to_collection(crawler_auth_headers, default_org_id): assert data["totalSize"] > 0 assert data["modified"] assert data["tags"] == ["wr-test-2", "wr-test-1"] + assert data["dateEarliest"] + assert data["dateLatest"] + assert data["defaultThumbnailName"] # Verify it was added r = requests.get( @@ -459,16 +523,20 @@ def test_list_collections( assert len(items) == 3 first_coll = [coll for coll in items if coll["name"] == UPDATED_NAME][0] - assert first_coll["id"] + assert first_coll["id"] == _coll_id assert first_coll["name"] == UPDATED_NAME assert first_coll["oid"] == default_org_id assert first_coll["description"] == DESCRIPTION + assert first_coll["caption"] == UPDATED_CAPTION assert first_coll["crawlCount"] == 3 assert first_coll["pageCount"] > 0 assert first_coll["totalSize"] > 0 assert first_coll["modified"] assert first_coll["tags"] == ["wr-test-2", "wr-test-1"] assert first_coll["access"] == "private" + assert first_coll["dateEarliest"] + assert first_coll["dateLatest"] + assert first_coll["defaultThumbnailName"] second_coll = [coll for coll in items if coll["name"] == SECOND_COLLECTION_NAME][0] assert second_coll["id"] @@ -481,6 +549,8 @@ def test_list_collections( assert second_coll["modified"] assert second_coll["tags"] == ["wr-test-2"] assert second_coll["access"] == "private" + assert second_coll["dateEarliest"] + assert second_coll["dateLatest"] def test_remove_upload_from_collection(crawler_auth_headers, default_org_id): @@ -742,11 +812,14 @@ def test_list_public_collections( json={ "crawlIds": [crawler_crawl_id], "name": "Second public collection", + "description": "Lorem ipsum", "access": "public", }, ) assert r.status_code == 200 - second_public_coll_id = r.json()["id"] + + global _second_public_coll_id + _second_public_coll_id = r.json()["id"] # Get default org slug r = requests.get( @@ -755,7 +828,10 @@ def test_list_public_collections( ) assert r.status_code == 200 data = r.json() - org_slug = data["slug"] + + global default_org_slug + default_org_slug = data["slug"] + org_name = data["name"] # Verify that public profile isn't enabled @@ -764,7 +840,7 @@ def test_list_public_collections( assert data["publicUrl"] == "" # Try listing public collections without org public profile enabled - r = requests.get(f"{API_PREFIX}/public-collections/{org_slug}") + r = requests.get(f"{API_PREFIX}/public/orgs/{default_org_slug}/collections") assert r.status_code == 404 assert r.json()["detail"] == "public_profile_not_found" @@ -795,7 +871,7 @@ def test_list_public_collections( assert data["publicUrl"] == public_url # List public collections with no auth (no public profile) - r = requests.get(f"{API_PREFIX}/public-collections/{org_slug}") + r = requests.get(f"{API_PREFIX}/public/orgs/{default_org_slug}/collections") assert r.status_code == 200 data = r.json() @@ -807,12 +883,19 @@ def test_list_public_collections( collections = data["collections"] assert len(collections) == 2 for collection in collections: - assert collection["id"] in (_public_coll_id, second_public_coll_id) + assert collection["id"] in (_public_coll_id, _second_public_coll_id) + assert collection["oid"] assert collection["access"] == "public" + assert collection["name"] + assert collection["dateEarliest"] + assert collection["dateLatest"] + assert collection["crawlCount"] > 0 + assert collection["pageCount"] > 0 + assert collection["totalSize"] > 0 # Test non-existing slug - it should return a 404 but not reveal # whether or not an org exists with that slug - r = requests.get(f"{API_PREFIX}/public-collections/nonexistentslug") + r = requests.get(f"{API_PREFIX}/public/orgs/nonexistentslug/collections") assert r.status_code == 404 assert r.json()["detail"] == "public_profile_not_found" @@ -820,7 +903,7 @@ def test_list_public_collections( def test_list_public_collections_no_colls(non_default_org_id, admin_auth_headers): # Test existing org that's not public - should return same 404 as # if org doesn't exist - r = requests.get(f"{API_PREFIX}/public-collections/{NON_DEFAULT_ORG_SLUG}") + r = requests.get(f"{API_PREFIX}/public/orgs/{NON_DEFAULT_ORG_SLUG}/collections") assert r.status_code == 404 assert r.json()["detail"] == "public_profile_not_found" @@ -837,13 +920,459 @@ def test_list_public_collections_no_colls(non_default_org_id, admin_auth_headers # List public collections with no auth - should still get profile even # with no public collections - r = requests.get(f"{API_PREFIX}/public-collections/{NON_DEFAULT_ORG_SLUG}") + r = requests.get(f"{API_PREFIX}/public/orgs/{NON_DEFAULT_ORG_SLUG}/collections") assert r.status_code == 200 data = r.json() assert data["org"]["name"] == NON_DEFAULT_ORG_NAME assert data["collections"] == [] +def test_set_collection_home_url( + crawler_auth_headers, default_org_id, crawler_crawl_id +): + # Get a page id from crawler_crawl_id + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/crawls/{crawler_crawl_id}/pages", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + assert data["total"] >= 1 + + page = data["items"][0] + assert page + + page_id = page["id"] + assert page_id + + page_url = page["url"] + page_ts = page["ts"] + + # Set page as home url + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}/home-url", + headers=crawler_auth_headers, + json={"pageId": page_id}, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + # Check that fields were set in collection as expected + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + assert data["homeUrl"] == page_url + assert data["homeUrlTs"] == page_ts + assert data["homeUrlPageId"] == page_id + + +def test_collection_url_list(crawler_auth_headers, default_org_id): + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}/urls", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + + assert data["total"] >= 1 + urls = data["items"] + assert urls + + for url in urls: + assert url["url"] + assert url["count"] >= 1 + + snapshots = url["snapshots"] + assert snapshots + + for snapshot in snapshots: + assert snapshot["pageId"] + assert snapshot["ts"] + assert snapshot["status"] + + +def test_upload_collection_thumbnail(crawler_auth_headers, default_org_id): + with open(os.path.join(curr_dir, "data", "thumbnail.jpg"), "rb") as fh: + r = requests.put( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}/thumbnail?filename=thumbnail.jpg", + headers=crawler_auth_headers, + data=read_in_chunks(fh), + ) + assert r.status_code == 200 + assert r.json()["added"] + + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + thumbnail = r.json()["thumbnail"] + + assert thumbnail["name"] + assert thumbnail["path"] + assert thumbnail["hash"] + assert thumbnail["size"] > 0 + + assert thumbnail["originalFilename"] == "thumbnail.jpg" + assert thumbnail["mime"] == "image/jpeg" + assert thumbnail["userid"] + assert thumbnail["userName"] + assert thumbnail["created"] + + +def test_set_collection_default_thumbnail(crawler_auth_headers, default_org_id): + default_thumbnail_name = "orange-default.avif" + + r = requests.patch( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_second_public_coll_id}", + headers=crawler_auth_headers, + json={"defaultThumbnailName": default_thumbnail_name}, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_second_public_coll_id}", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + + assert data["id"] == _second_public_coll_id + assert data["defaultThumbnailName"] == default_thumbnail_name + + +def test_list_public_colls_home_url_thumbnail(): + # Check we get expected data for each public collection + # and nothing we don't expect + non_public_fields = ( + "oid", + "modified", + "crawlCount", + "pageCount", + "totalSize", + "tags", + "access", + "homeUrlPageId", + ) + non_public_image_fields = ("originalFilename", "userid", "userName", "created") + + r = requests.get(f"{API_PREFIX}/public/orgs/{default_org_slug}/collections") + assert r.status_code == 200 + collections = r.json()["collections"] + assert len(collections) == 2 + + for coll in collections: + assert coll["id"] in (_public_coll_id, _second_public_coll_id) + assert coll["oid"] + assert coll["access"] == "public" + assert coll["name"] + assert coll["resources"] + assert coll["dateEarliest"] + assert coll["dateLatest"] + assert coll["crawlCount"] > 0 + assert coll["pageCount"] > 0 + assert coll["totalSize"] > 0 + + for field in NON_PUBLIC_COLL_FIELDS: + assert field not in coll + + if coll["id"] == _public_coll_id: + assert coll["allowPublicDownload"] is False + + assert coll["caption"] == CAPTION + + assert coll["homeUrl"] + assert coll["homeUrlTs"] + + thumbnail = coll["thumbnail"] + assert thumbnail + + assert thumbnail["name"] + assert thumbnail["path"] + assert thumbnail["hash"] + assert thumbnail["size"] + assert thumbnail["mime"] + + for field in NON_PUBLIC_IMAGE_FIELDS: + assert field not in thumbnail + + if coll["id"] == _second_public_coll_id: + assert coll["description"] + assert coll["defaultThumbnailName"] == "orange-default.avif" + assert coll["allowPublicDownload"] + + +def test_get_public_collection(default_org_id): + r = requests.get( + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_public_coll_id}" + ) + assert r.status_code == 200 + coll = r.json() + + assert coll["id"] == _public_coll_id + assert coll["oid"] == default_org_id + assert coll["access"] == "public" + assert coll["name"] + assert coll["resources"] + assert coll["dateEarliest"] + assert coll["dateLatest"] + assert coll["crawlCount"] > 0 + assert coll["pageCount"] > 0 + assert coll["totalSize"] > 0 + + for field in NON_PUBLIC_COLL_FIELDS: + assert field not in coll + + assert coll["caption"] == CAPTION + + assert coll["homeUrl"] + assert coll["homeUrlTs"] + + assert coll["allowPublicDownload"] is False + + thumbnail = coll["thumbnail"] + assert thumbnail + + assert thumbnail["name"] + assert thumbnail["path"] + assert thumbnail["hash"] + assert thumbnail["size"] + assert thumbnail["mime"] + + for field in NON_PUBLIC_IMAGE_FIELDS: + assert field not in thumbnail + + # Invalid org slug - don't reveal whether org exists or not, use + # same exception as if collection doesn't exist + r = requests.get( + f"{API_PREFIX}/public/orgs/doesntexist/collections/{_public_coll_id}" + ) + assert r.status_code == 404 + assert r.json()["detail"] == "collection_not_found" + + # Invalid collection id + random_uuid = uuid4() + r = requests.get( + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{random_uuid}" + ) + assert r.status_code == 404 + assert r.json()["detail"] == "collection_not_found" + + # Collection isn't public + r = requests.get( + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{ _coll_id}" + ) + assert r.status_code == 404 + assert r.json()["detail"] == "collection_not_found" + + +def test_get_public_collection_unlisted(crawler_auth_headers, default_org_id): + # Make second public coll unlisted + r = requests.patch( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_second_public_coll_id}", + headers=crawler_auth_headers, + json={ + "access": "unlisted", + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + # Verify single public collection GET endpoint works for unlisted collection + r = requests.get( + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_second_public_coll_id}" + ) + assert r.status_code == 200 + coll = r.json() + + assert coll["id"] == _second_public_coll_id + assert coll["oid"] == default_org_id + assert coll["access"] == "unlisted" + assert coll["name"] + assert coll["resources"] + assert coll["dateEarliest"] + assert coll["dateLatest"] + assert coll["crawlCount"] > 0 + assert coll["pageCount"] > 0 + assert coll["totalSize"] > 0 + assert coll["defaultThumbnailName"] == "orange-default.avif" + assert coll["allowPublicDownload"] + + for field in NON_PUBLIC_COLL_FIELDS: + assert field not in coll + + +def test_get_public_collection_unlisted_org_profile_disabled( + admin_auth_headers, default_org_id +): + # Disable org profile + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/public-profile", + headers=admin_auth_headers, + json={ + "enablePublicProfile": False, + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + # Verify we can still get public details for unlisted collection + r = requests.get( + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_second_public_coll_id}" + ) + assert r.status_code == 200 + coll = r.json() + + assert coll["id"] == _second_public_coll_id + assert coll["oid"] == default_org_id + assert coll["access"] == "unlisted" + assert coll["name"] + assert coll["resources"] + assert coll["dateEarliest"] + assert coll["dateLatest"] + assert coll["crawlCount"] > 0 + assert coll["pageCount"] > 0 + assert coll["totalSize"] > 0 + assert coll["defaultThumbnailName"] == "orange-default.avif" + assert coll["allowPublicDownload"] + + for field in NON_PUBLIC_COLL_FIELDS: + assert field not in coll + + # Re-enable org profile + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/public-profile", + headers=admin_auth_headers, + json={ + "enablePublicProfile": True, + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + +def test_delete_thumbnail(crawler_auth_headers, default_org_id): + r = requests.delete( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}/thumbnail", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + assert r.json()["deleted"] + + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + assert r.json().get("thumbnail") is None + + r = requests.delete( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_second_public_coll_id}/thumbnail", + headers=crawler_auth_headers, + ) + assert r.status_code == 404 + assert r.json()["detail"] == "thumbnail_not_found" + + +def test_unset_collection_home_url( + crawler_auth_headers, default_org_id, crawler_crawl_id +): + # Unset home url + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}/home-url", + headers=crawler_auth_headers, + json={"pageId": None}, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + # Check that fields were set in collection as expected + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}", + headers=crawler_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + assert data.get("homeUrl") is None + assert data.get("homeUrlTs") is None + assert data.get("homeUrlPageId") is None + + +def test_download_streaming_public_collection(crawler_auth_headers, default_org_id): + # Check that download is blocked if allowPublicDownload is False + with requests.get( + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_public_coll_id}/download", + stream=True, + ) as r: + assert r.status_code == 403 + + # Set allowPublicDownload to True and then check downloading works + r = requests.patch( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}", + headers=crawler_auth_headers, + json={ + "allowPublicDownload": True, + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + with TemporaryFile() as fh: + with requests.get( + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_public_coll_id}/download", + stream=True, + ) as r: + assert r.status_code == 200 + for chunk in r.iter_content(): + fh.write(chunk) + + fh.seek(0) + with ZipFile(fh, "r") as zip_file: + contents = zip_file.namelist() + + assert len(contents) == 2 + for filename in contents: + assert filename.endswith(".wacz") or filename == "datapackage.json" + assert zip_file.getinfo(filename).compress_type == ZIP_STORED + + +def test_download_streaming_public_collection_profile_disabled( + admin_auth_headers, default_org_id +): + # Disable org public profile and ensure download still works for public collection + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/public-profile", + headers=admin_auth_headers, + json={ + "enablePublicProfile": False, + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + with TemporaryFile() as fh: + with requests.get( + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_public_coll_id}/download", + stream=True, + ) as r: + assert r.status_code == 200 + for chunk in r.iter_content(): + fh.write(chunk) + + fh.seek(0) + with ZipFile(fh, "r") as zip_file: + contents = zip_file.namelist() + + assert len(contents) == 2 + for filename in contents: + assert filename.endswith(".wacz") or filename == "datapackage.json" + assert zip_file.getinfo(filename).compress_type == ZIP_STORED + + def test_delete_collection(crawler_auth_headers, default_org_id, crawler_crawl_id): # Delete second collection r = requests.delete( diff --git a/backend/test/test_uploads.py b/backend/test/test_uploads.py index fb7543d0a2..3fb1c1c44b 100644 --- a/backend/test/test_uploads.py +++ b/backend/test/test_uploads.py @@ -232,6 +232,49 @@ def test_get_upload_replay_json_admin( assert "files" not in data +def test_get_upload_pages(admin_auth_headers, default_org_id, upload_id): + # Give time for pages to finish being uploaded + time.sleep(10) + + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/uploads/{upload_id}/pages", + headers=admin_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + + assert data["total"] > 0 + + pages = data["items"] + for page in pages: + assert page["id"] + assert page["oid"] + assert page["crawl_id"] == upload_id + assert page["url"] + assert page["ts"] + assert page.get("title") or page.get("title") is None + + page_id = pages[0]["id"] + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/uploads/{upload_id}/pages/{page_id}", + headers=admin_auth_headers, + ) + assert r.status_code == 200 + page = r.json() + + assert page["id"] == page_id + assert page["oid"] + assert page["crawl_id"] + assert page["url"] + assert page["ts"] + assert page.get("title") or page.get("title") is None + + assert page["notes"] == [] + assert page.get("userid") is None + assert page.get("modified") is None + assert page.get("approved") is None + + def test_replace_upload( admin_auth_headers, default_org_id, uploads_collection_id, upload_id ): diff --git a/chart/app-templates/background_job.yaml b/chart/app-templates/background_job.yaml index 132d3bf8fe..f47dd2acfd 100644 --- a/chart/app-templates/background_job.yaml +++ b/chart/app-templates/background_job.yaml @@ -8,7 +8,7 @@ metadata: btrix.org: {{ oid }} spec: - ttlSecondsAfterFinished: 0 + ttlSecondsAfterFinished: 90 backoffLimit: 3 template: spec: @@ -38,6 +38,9 @@ spec: - name: OID value: {{ oid }} + - name: CRAWL_TYPE + value: {{ crawl_type }} + envFrom: - configMapRef: name: backend-env-config From 093b11447967e76f28e54bde607c81a6a47281f1 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Mon, 23 Dec 2024 12:38:47 -0800 Subject: [PATCH 06/12] feat: Collection thumbnails, start page, and public view updates (#2209) - Allows user to choose collection replay home page and collection thumbnail (resolves https://github.com/webrecorder/browsertrix/issues/2182) - Displays collection thumbnails on org dashboard and public page - Enables downloading public collection (resolves https://github.com/webrecorder/browsertrix/issues/2233) - Adds caption as "Summary" to metadata dialog - Moves description editor to "About" tab --------- Co-authored-by: Emma Segal-Grossman --- frontend/index.d.ts | 1 + .../images/thumbnails/thumbnail-cyan.avif | Bin 0 -> 71251 bytes .../images/thumbnails/thumbnail-green.avif | Bin 0 -> 92293 bytes .../images/thumbnails/thumbnail-orange.avif | Bin 0 -> 95688 bytes .../images/thumbnails/thumbnail-yellow.avif | Bin 0 -> 134725 bytes frontend/src/components/not-found.ts | 38 +- frontend/src/components/ui/button.ts | 2 +- frontend/src/components/ui/markdown-editor.ts | 18 +- frontend/src/components/ui/markdown-viewer.ts | 12 + .../src/components/ui/overflow-dropdown.ts | 27 +- frontend/src/components/ui/section-heading.ts | 3 +- frontend/src/components/ui/table/table.ts | 3 + frontend/src/controllers/api.ts | 85 ++- frontend/src/controllers/navigate.ts | 6 + .../features/archived-items/file-uploader.ts | 2 + .../collections/collection-items-dialog.ts | 4 +- .../collections/collection-metadata-dialog.ts | 127 ++-- .../collections/collection-replay-dialog.ts | 393 +++++++++++ .../collections/collection-thumbnail.ts | 52 ++ .../features/collections/collections-grid.ts | 140 ++++ frontend/src/features/collections/index.ts | 4 + .../collections/select-collection-access.ts | 31 +- .../select-collection-start-page.ts | 308 +++++++++ .../features/collections/share-collection.ts | 552 +++++++++++---- frontend/src/layouts/page.ts | 71 +- frontend/src/layouts/pageHeader.ts | 3 +- frontend/src/pages/collections/collection.ts | 293 +++++--- .../org/archived-item-qa/archived-item-qa.ts | 3 +- frontend/src/pages/org/collection-detail.ts | 636 +++++++++++------- frontend/src/pages/org/collections-list.ts | 63 +- frontend/src/pages/org/dashboard.ts | 157 ++++- frontend/src/pages/org/index.ts | 5 +- frontend/src/pages/org/profile.ts | 158 ++--- frontend/src/replayWebPage.d.ts | 9 + frontend/src/theme.stylesheet.css | 20 + frontend/src/types/collection.ts | 48 +- frontend/src/types/org.ts | 16 +- frontend/src/utils/replay.ts | 8 + frontend/webpack.config.js | 2 +- frontend/yarn.lock | 14 + 40 files changed, 2595 insertions(+), 719 deletions(-) create mode 100644 frontend/src/assets/images/thumbnails/thumbnail-cyan.avif create mode 100644 frontend/src/assets/images/thumbnails/thumbnail-green.avif create mode 100644 frontend/src/assets/images/thumbnails/thumbnail-orange.avif create mode 100644 frontend/src/assets/images/thumbnails/thumbnail-yellow.avif create mode 100644 frontend/src/features/collections/collection-replay-dialog.ts create mode 100644 frontend/src/features/collections/collection-thumbnail.ts create mode 100644 frontend/src/features/collections/collections-grid.ts create mode 100644 frontend/src/features/collections/select-collection-start-page.ts create mode 100644 frontend/src/utils/replay.ts diff --git a/frontend/index.d.ts b/frontend/index.d.ts index 57f96d4072..a711dad908 100644 --- a/frontend/index.d.ts +++ b/frontend/index.d.ts @@ -1,3 +1,4 @@ +declare module "*.avif"; declare module "*.svg"; declare module "*.webp"; declare module "*.css"; diff --git a/frontend/src/assets/images/thumbnails/thumbnail-cyan.avif b/frontend/src/assets/images/thumbnails/thumbnail-cyan.avif new file mode 100644 index 0000000000000000000000000000000000000000..05092899a414fba32be3809bed941f97ebb4fdb7 GIT binary patch literal 71251 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 aKV+=;_3{hBt@A#%)`yMI-R_y+J3VSBqw*2} literal 0 HcmV?d00001 diff --git a/frontend/src/components/not-found.ts b/frontend/src/components/not-found.ts index 7f64e8904e..3d346e62a6 100644 --- a/frontend/src/components/not-found.ts +++ b/frontend/src/components/not-found.ts @@ -1,5 +1,5 @@ import { localized, msg } from "@lit/localize"; -import { html } from "lit"; +import { html, nothing } from "lit"; import { customElement } from "lit/decorators.js"; import { BtrixElement } from "@/classes/BtrixElement"; @@ -9,27 +9,39 @@ import { BtrixElement } from "@/classes/BtrixElement"; export class NotFound extends BtrixElement { render() { return html` -

-

- ${msg("Page not found")} +

+

+ ${msg("Sorry, we couldn’t find that page")}

-

+

+ ${msg("Check the URL to make sure you’ve entered it correctly.")} +

+
+ ${msg("Go to Home")} +
+

${msg("Did you click a link to get here?")} -
- ${msg("Or")} - - ${msg("Report a Broken Link")} - + ${this.navigate.isPublicPage + ? nothing + : html` +
+ ${msg("Or")} + + ${msg("Report a Broken Link")} + + `}

`; diff --git a/frontend/src/components/ui/button.ts b/frontend/src/components/ui/button.ts index 5ef6a2baf3..f06bd3d4e5 100644 --- a/frontend/src/components/ui/button.ts +++ b/frontend/src/components/ui/button.ts @@ -74,7 +74,7 @@ export class Button extends TailwindElement { small: tw`min-h-6 min-w-6 rounded-md text-base`, medium: tw`min-h-8 min-w-8 rounded-sm text-lg`, }[this.size], - this.raised && tw`border shadow-sm`, + this.raised && tw`shadow ring-1 ring-neutral-200`, this.filled ? [ tw`text-white`, diff --git a/frontend/src/components/ui/markdown-editor.ts b/frontend/src/components/ui/markdown-editor.ts index 5359378b2e..056d868cb1 100644 --- a/frontend/src/components/ui/markdown-editor.ts +++ b/frontend/src/components/ui/markdown-editor.ts @@ -1,4 +1,4 @@ -import { msg, str } from "@lit/localize"; +import { localized, msg, str } from "@lit/localize"; import { wrap, type AwaitableInstance } from "ink-mde"; import { css, html, type PropertyValues } from "lit"; import { customElement, property, query } from "lit/decorators.js"; @@ -16,6 +16,7 @@ export type MarkdownChangeEvent = CustomEvent; * * @fires btrix-change MarkdownChangeEvent */ +@localized() @customElement("btrix-markdown-editor") export class MarkdownEditor extends BtrixElement { static styles = css` @@ -53,11 +54,18 @@ export class MarkdownEditor extends BtrixElement { white-space: nowrap; border-width: 0; } + + .cm-line:only-child { + min-height: 8em; + } `; @property({ type: String }) label = ""; + @property({ type: String }) + placeholder = ""; + @property({ type: String }) initialValue = ""; @@ -76,6 +84,11 @@ export class MarkdownEditor extends BtrixElement { return this.textarea?.checkValidity(); } + public async focus() { + await this.updateComplete; + (await this.editor)?.focus(); + } + protected willUpdate(changedProperties: PropertyValues): void { if ( changedProperties.has("initialValue") && @@ -99,7 +112,7 @@ export class MarkdownEditor extends BtrixElement { const isInvalid = this.maxlength && this.value.length > this.maxlength; return html`
- + ${this.label && html``}

@@ -181,6 +194,7 @@ export class MarkdownEditor extends BtrixElement { taskList: false, upload: false, }, + placeholder: this.placeholder, }); } } diff --git a/frontend/src/components/ui/markdown-viewer.ts b/frontend/src/components/ui/markdown-viewer.ts index f3ee86dc9e..6ea8f94e32 100644 --- a/frontend/src/components/ui/markdown-viewer.ts +++ b/frontend/src/components/ui/markdown-viewer.ts @@ -29,6 +29,18 @@ export class MarkdownViewer extends LitElement { img { max-width: 100%; } + + p { + line-height: inherit; + } + + p:first-child { + margin-top: 0; + } + + p:last-child { + margin-bottom: 0; + } `, ]; diff --git a/frontend/src/components/ui/overflow-dropdown.ts b/frontend/src/components/ui/overflow-dropdown.ts index 533dd8fca6..1bc0ea8f78 100644 --- a/frontend/src/components/ui/overflow-dropdown.ts +++ b/frontend/src/components/ui/overflow-dropdown.ts @@ -3,10 +3,12 @@ import type { SlDropdown, SlMenu } from "@shoelace-style/shoelace"; import { html } from "lit"; import { customElement, + property, query, queryAssignedElements, state, } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; import { TailwindElement } from "@/classes/TailwindElement"; @@ -26,6 +28,9 @@ import { TailwindElement } from "@/classes/TailwindElement"; @customElement("btrix-overflow-dropdown") @localized() export class OverflowDropdown extends TailwindElement { + @property({ type: Boolean }) + raised = false; + @state() private hasMenuItems?: boolean; @@ -37,15 +42,19 @@ export class OverflowDropdown extends TailwindElement { render() { return html` - - - + + + + + (this.hasMenuItems = this.menu.length > 0)} > diff --git a/frontend/src/components/ui/section-heading.ts b/frontend/src/components/ui/section-heading.ts index ffb4808150..08705ffa5b 100644 --- a/frontend/src/components/ui/section-heading.ts +++ b/frontend/src/components/ui/section-heading.ts @@ -19,8 +19,7 @@ export class SectionHeading extends LitElement { gap: 0.5rem; font-size: var(--sl-font-size-medium); color: var(--sl-color-neutral-500); - padding-top: var(--sl-spacing-x-small); - padding-bottom: var(--sl-spacing-x-small); + min-height: 2rem; line-height: 1; border-bottom: 1px solid var(--sl-panel-border-color); margin-bottom: var(--margin); diff --git a/frontend/src/components/ui/table/table.ts b/frontend/src/components/ui/table/table.ts index 091932ce6a..aff04f8ebc 100644 --- a/frontend/src/components/ui/table/table.ts +++ b/frontend/src/components/ui/table/table.ts @@ -48,6 +48,7 @@ tableCSS.split("}").forEach((rule: string) => { * @slot head * @slot * @csspart head + * @cssproperty --btrix-column-gap * @cssproperty --btrix-cell-gap * @cssproperty --btrix-cell-padding-top * @cssproperty --btrix-cell-padding-left @@ -58,6 +59,7 @@ tableCSS.split("}").forEach((rule: string) => { export class Table extends LitElement { static styles = css` :host { + --btrix-column-gap: 0; --btrix-cell-gap: 0; --btrix-cell-padding-top: 0; --btrix-cell-padding-bottom: 0; @@ -65,6 +67,7 @@ export class Table extends LitElement { --btrix-cell-padding-right: 0; display: grid; + column-gap: var(--btrix-column-gap, 0); } `; diff --git a/frontend/src/controllers/api.ts b/frontend/src/controllers/api.ts index 4a43f72ee2..44c9e44030 100644 --- a/frontend/src/controllers/api.ts +++ b/frontend/src/controllers/api.ts @@ -1,5 +1,6 @@ import { msg } from "@lit/localize"; import type { ReactiveController, ReactiveControllerHost } from "lit"; +import throttle from "lodash/fp/throttle"; import { APIError, type Detail } from "@/utils/api"; import AuthService from "@/utils/AuthService"; @@ -12,6 +13,11 @@ export interface APIEventMap { "btrix-storage-quota-update": CustomEvent; } +export enum AbortReason { + UserCancel = "user-canceled", + QuotaReached = "storage_quota_reached", +} + /** * Utilities for interacting with the Browsertrix backend API * @@ -29,13 +35,20 @@ export interface APIEventMap { export class APIController implements ReactiveController { host: ReactiveControllerHost & EventTarget; + uploadProgress = 0; + + private uploadRequest: XMLHttpRequest | null = null; + constructor(host: APIController["host"]) { this.host = host; host.addController(this); } hostConnected() {} - hostDisconnected() {} + + hostDisconnected() { + this.cancelUpload(); + } async fetch(path: string, options?: RequestInit): Promise { const auth = appState.auth; @@ -156,4 +169,74 @@ export class APIController implements ReactiveController { details: errorDetail as Detail[], }); } + + async upload( + path: string, + file: File, + ): Promise<{ id: string; added: boolean; storageQuotaReached: boolean }> { + const auth = appState.auth; + + if (!auth) throw new Error("auth not in state"); + + // TODO handle multiple uploads + if (this.uploadRequest) { + console.debug("upload request exists"); + this.cancelUpload(); + } + + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + + xhr.open("PUT", `/api/${path}`); + xhr.setRequestHeader("Content-Type", "application/octet-stream"); + Object.entries(auth.headers).forEach(([k, v]) => { + xhr.setRequestHeader(k, v); + }); + xhr.addEventListener("load", () => { + if (xhr.status === 200) { + resolve( + JSON.parse(xhr.response as string) as { + id: string; + added: boolean; + storageQuotaReached: boolean; + }, + ); + } + if (xhr.status === 403) { + reject(AbortReason.QuotaReached); + } + }); + xhr.addEventListener("error", () => { + reject( + new APIError({ + message: xhr.statusText, + status: xhr.status, + }), + ); + }); + xhr.addEventListener("abort", () => { + reject(AbortReason.UserCancel); + }); + xhr.upload.addEventListener("progress", this.onUploadProgress); + + xhr.send(file); + + this.uploadRequest = xhr; + }); + } + + readonly onUploadProgress = throttle(100)((e: ProgressEvent) => { + this.uploadProgress = (e.loaded / e.total) * 100; + + this.host.requestUpdate(); + }); + + private cancelUpload() { + if (this.uploadRequest) { + this.uploadRequest.abort(); + this.uploadRequest = null; + } + + this.onUploadProgress.cancel(); + } } diff --git a/frontend/src/controllers/navigate.ts b/frontend/src/controllers/navigate.ts index 6e94f56b45..a20c516976 100644 --- a/frontend/src/controllers/navigate.ts +++ b/frontend/src/controllers/navigate.ts @@ -37,6 +37,12 @@ export class NavigateController implements ReactiveController { return "/"; } + get isPublicPage() { + return window.location.pathname.startsWith( + `/${RouteNamespace.PublicOrgs}/`, + ); + } + constructor(host: NavigateController["host"]) { this.host = host; host.addController(this); diff --git a/frontend/src/features/archived-items/file-uploader.ts b/frontend/src/features/archived-items/file-uploader.ts index 214bf03d83..eb7406680f 100644 --- a/frontend/src/features/archived-items/file-uploader.ts +++ b/frontend/src/features/archived-items/file-uploader.ts @@ -44,6 +44,8 @@ enum AbortReason { * > * ``` * + * @TODO Refactor to use this.api.upload + * * @event request-close * @event upload-start * @event uploaded diff --git a/frontend/src/features/collections/collection-items-dialog.ts b/frontend/src/features/collections/collection-items-dialog.ts index 0f2c2a9ab3..cb2ad9eb98 100644 --- a/frontend/src/features/collections/collection-items-dialog.ts +++ b/frontend/src/features/collections/collection-items-dialog.ts @@ -674,7 +674,7 @@ export class CollectionItemsDialog extends BtrixElement { this.close(); this.dispatchEvent(new CustomEvent("btrix-collection-saved")); this.notify.toast({ - message: msg(str`Successfully saved archived item selection.`), + message: msg(str`Archived item selection updated.`), variant: "success", icon: "check2-circle", id: "archived-item-selection-status", @@ -683,7 +683,7 @@ export class CollectionItemsDialog extends BtrixElement { this.notify.toast({ message: isApiError(e) ? e.message - : msg("Something unexpected went wrong"), + : msg("Sorry, couldn't save archived item selection at this time."), variant: "danger", icon: "exclamation-octagon", id: "archived-item-selection-status", diff --git a/frontend/src/features/collections/collection-metadata-dialog.ts b/frontend/src/features/collections/collection-metadata-dialog.ts index ece464b459..fcbb6b7da4 100644 --- a/frontend/src/features/collections/collection-metadata-dialog.ts +++ b/frontend/src/features/collections/collection-metadata-dialog.ts @@ -1,7 +1,7 @@ import { localized, msg, str } from "@lit/localize"; -import { type SlInput } from "@shoelace-style/shoelace"; +import type { SlInput, SlSelectEvent } from "@shoelace-style/shoelace"; import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js"; -import { html } from "lit"; +import { html, nothing } from "lit"; import { customElement, property, @@ -11,9 +11,10 @@ import { } from "lit/decorators.js"; import { when } from "lit/directives/when.js"; +import { DEFAULT_THUMBNAIL } from "./collection-thumbnail"; + import { BtrixElement } from "@/classes/BtrixElement"; import type { Dialog } from "@/components/ui/dialog"; -import type { MarkdownEditor } from "@/components/ui/markdown-editor"; import type { SelectCollectionAccess } from "@/features/collections/select-collection-access"; import { CollectionAccess, type Collection } from "@/types/collection"; import { isApiError } from "@/utils/api"; @@ -41,8 +42,8 @@ export class CollectionMetadataDialog extends BtrixElement { @state() private isSubmitting = false; - @query("btrix-markdown-editor") - private readonly descriptionEditor?: MarkdownEditor | null; + @state() + private showPublicWarning = false; @query("btrix-select-collection-access") private readonly selectCollectionAccess?: SelectCollectionAccess | null; @@ -51,6 +52,7 @@ export class CollectionMetadataDialog extends BtrixElement { private readonly form!: Promise; private readonly validateNameMax = maxLengthValidator(50); + private readonly validateCaptionMax = maxLengthValidator(150); protected firstUpdated(): void { if (this.open) { @@ -61,12 +63,12 @@ export class CollectionMetadataDialog extends BtrixElement { render() { return html` (this.isDialogVisible = true)} @sl-after-hide=${() => (this.isDialogVisible = false)} - style="--width: 46rem" + class="[--width:40rem]" > ${when(this.isDialogVisible, () => this.renderForm())}

@@ -80,14 +82,6 @@ export class CollectionMetadataDialog extends BtrixElement { }} >${msg("Cancel")} - ${when( - !this.collection, - () => html` - - `, - )} - - + > + + + + ${msg("Summary")} + + + ${msg( + "Write a short description that summarizes this collection. If the collection is public, this description will be visible next to the collection name.", + )} + ${this.collection + ? nothing + : msg( + "You can write a longer description in the 'About' section after creating the collection.", + )} + + + + + ${when( !this.collection, () => html` - + + (this.showPublicWarning = + (e.detail.item.value as CollectionAccess) === + CollectionAccess.Public)} + > + `, + )} + ${when( + this.showPublicWarning && this.org, + (org) => html` + + ${org.enablePublicProfile + ? msg( + "This collection will be visible on the org public profile, even without archived items. You may want to set visibility to 'Unlisted' until archived items have been added.", + ) + : html` + ${msg( + "This collection will be visible on the org profile page, which isn't public yet. To make the org profile and this collection visible to the public, update org profile settings.", + )} + + ${msg("Open org settings")} + + + `} + `, )} - + `; } @@ -160,25 +206,23 @@ export class CollectionMetadataDialog extends BtrixElement { const form = event.target as HTMLFormElement; const nameInput = form.querySelector('sl-input[name="name"]'); - if ( - !nameInput?.checkValidity() || - !this.descriptionEditor?.checkValidity() - ) { + + if (!nameInput?.checkValidity()) { return; } - const { name } = serialize(form); - const description = this.descriptionEditor.value; + const { name, caption } = serialize(form); this.isSubmitting = true; try { const body = JSON.stringify({ name, - description, + caption, access: this.selectCollectionAccess?.value || this.collection?.access || CollectionAccess.Private, + defaultThumbnailName: DEFAULT_THUMBNAIL, }); let path = `/orgs/${this.orgId}/collections`; let method = "POST"; @@ -199,9 +243,9 @@ export class CollectionMetadataDialog extends BtrixElement { }) as CollectionSavedEvent, ); this.notify.toast({ - message: msg( - str`Successfully saved "${data.name || name}" Collection.`, - ), + message: this.collection + ? msg(str`"${data.name || name}" metadata updated`) + : msg(str`Created "${data.name || name}" collection`), variant: "success", icon: "check2-circle", id: "collection-metadata-status", @@ -222,11 +266,4 @@ export class CollectionMetadataDialog extends BtrixElement { this.isSubmitting = false; } - - /** - * https://github.com/shoelace-style/shoelace/issues/170 - */ - private stopProp(e: CustomEvent) { - e.stopPropagation(); - } } diff --git a/frontend/src/features/collections/collection-replay-dialog.ts b/frontend/src/features/collections/collection-replay-dialog.ts new file mode 100644 index 0000000000..41678a5176 --- /dev/null +++ b/frontend/src/features/collections/collection-replay-dialog.ts @@ -0,0 +1,393 @@ +import { localized, msg } from "@lit/localize"; +import type { SlChangeEvent, SlIcon, SlSelect } from "@shoelace-style/shoelace"; +import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js"; +import { html, nothing, type PropertyValues } from "lit"; +import { customElement, property, query, state } from "lit/decorators.js"; +import { when } from "lit/directives/when.js"; +import queryString from "query-string"; + +import type { SelectSnapshotDetail } from "./select-collection-start-page"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import type { Dialog } from "@/components/ui/dialog"; +import { formatRwpTimestamp } from "@/utils/replay"; + +enum HomeView { + Pages = "pages", + URL = "url", +} + +@localized() +@customElement("btrix-collection-replay-dialog") +export class CollectionStartPageDialog extends BtrixElement { + static readonly Options: Record< + HomeView, + { label: string; icon: NonNullable; detail: string } + > = { + [HomeView.Pages]: { + label: msg("Default"), + icon: "list-ul", + detail: `${msg("ReplayWeb.Page default view")}`, + }, + [HomeView.URL]: { + label: msg("Page"), + icon: "file-earmark", + detail: msg("Load a single page URL"), + }, + }; + + @property({ type: String }) + collectionId?: string; + + @property({ type: String }) + homeUrl?: string | null = null; + + @property({ type: String }) + homePageId?: string | null = null; + + @property({ type: String }) + homeTs?: string | null = null; + + @property({ type: Boolean }) + open = false; + + @property({ type: Boolean }) + replayLoaded = false; + + @state() + homeView = HomeView.Pages; + + @state() + private showContent = false; + + @state() + private isSubmitting = false; + + @state() + private selectedSnapshot?: SelectSnapshotDetail["item"]; + + @query("btrix-dialog") + private readonly dialog?: Dialog | null; + + @query("form") + private readonly form?: HTMLFormElement | null; + + @query("#thumbnailPreview") + private readonly thumbnailPreview?: HTMLIFrameElement | null; + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("homeUrl") && this.homeUrl) { + this.homeView = HomeView.URL; + } + } + + render() { + return html` + (this.showContent = true)} + @sl-after-hide=${() => (this.showContent = false)} + > + ${this.showContent ? this.renderContent() : nothing} +
+ void this.dialog?.hide()} + >${msg("Cancel")} + { + this.form?.requestSubmit(); + }} + > + ${msg("Save")} + +
+
+ `; + } + + private renderContent() { + return html` +
+
+

${msg("Preview")}

+ ${this.renderPreview()} +
+
${this.renderForm()}
+
+ `; + } + + private renderPreview() { + let urlPreview = html` +

+ ${msg("Enter a Page URL to preview it")} +

+ `; + const snapshot = + this.selectedSnapshot || + (this.homeUrl + ? { + url: this.homeUrl, + ts: this.homeTs, + pageId: this.homePageId, + } + : null); + + if (snapshot) { + urlPreview = html` + + `; + } + + return html` +
+ ${when( + this.homeView === HomeView.URL && this.replayLoaded, + () => urlPreview, + )} +
+ ${this.renderReplay()} +
+ + ${when( + !this.replayLoaded, + () => html` +
+ +
+ `, + )} +
+ `; + } + + private renderForm() { + const { icon, detail } = CollectionStartPageDialog.Options[this.homeView]; + + return html` +
+ { + this.homeView = (e.currentTarget as SlSelect).value as HomeView; + }} + > + ${this.replayLoaded + ? html`` + : html``} + + ${detail} + + ${Object.values(HomeView).map((homeView) => { + const { label, icon, detail } = + CollectionStartPageDialog.Options[homeView]; + return html` + + + ${label} + ${detail} + + `; + })} + + + ${when( + this.homeView === HomeView.URL, + () => html` + +
+ , + ) => { + this.selectedSnapshot = e.detail.item; + }} + > + + + ${msg("Update collection thumbnail")} + + + + +
+ `, + )} +
+ `; + } + + private renderReplay() { + const replaySource = `/api/orgs/${this.orgId}/collections/${this.collectionId}/replay.json`; + // TODO Get query from replay-web-page embed + const query = queryString.stringify({ + source: replaySource, + customColl: this.collectionId, + embed: "default", + noCache: 1, + noSandbox: 1, + }); + + return html`
+
+ +
+
`; + } + + private async onSubmit(e: SubmitEvent) { + e.preventDefault(); + + const form = e.currentTarget as HTMLFormElement; + const { homeView, useThumbnail } = serialize(form); + + this.isSubmitting = true; + + try { + await this.updateUrl({ + pageId: + (homeView === HomeView.URL && this.selectedSnapshot?.pageId) || null, + }); + + const shouldUpload = + homeView === HomeView.URL && + useThumbnail === "on" && + this.selectedSnapshot && + this.homePageId !== this.selectedSnapshot.pageId; + // TODO get filename from rwp? + const fileName = `page-thumbnail_${this.selectedSnapshot?.pageId}.jpeg`; + let file: File | undefined; + + if (shouldUpload && this.thumbnailPreview?.src) { + const { src } = this.thumbnailPreview; + + // Wait to get the thumbnail image before closing the dialog + try { + const resp = await this.thumbnailPreview.contentWindow!.fetch(src); + const blob = await resp.blob(); + + file = new File([blob], fileName, { + type: blob.type, + }); + } catch (err) { + console.debug(err); + } + } else { + this.notify.toast({ + message: msg("Home view updated."), + variant: "success", + icon: "check2-circle", + id: "home-view-update-status", + }); + } + + this.isSubmitting = false; + this.open = false; + + if (shouldUpload) { + try { + if (!file || !fileName) throw new Error("file or fileName missing"); + await this.api.upload( + `/orgs/${this.orgId}/collections/${this.collectionId}/thumbnail?filename=${fileName}`, + file, + ); + await this.updateThumbnail({ defaultThumbnailName: null }); + + this.notify.toast({ + message: msg("Home view and collection thumbnail updated."), + variant: "success", + icon: "check2-circle", + id: "home-view-update-status", + }); + } catch (err) { + console.debug(err); + + this.notify.toast({ + message: msg( + "Home view updated, but couldn't update collection thumbnail at this time.", + ), + variant: "warning", + icon: "exclamation-triangle", + id: "home-view-update-status", + }); + } + } + } catch (err) { + console.debug(err); + + this.isSubmitting = false; + + this.notify.toast({ + message: msg("Sorry, couldn't update home view at this time."), + variant: "danger", + icon: "exclamation-octagon", + id: "home-view-update-status", + }); + } + } + + private async updateThumbnail({ + defaultThumbnailName, + }: { + defaultThumbnailName: string | null; + }) { + return this.api.fetch<{ updated: boolean }>( + `/orgs/${this.orgId}/collections/${this.collectionId}`, + { + method: "PATCH", + body: JSON.stringify({ defaultThumbnailName }), + }, + ); + } + + private async updateUrl({ pageId }: { pageId: string | null }) { + return this.api.fetch( + `/orgs/${this.orgId}/collections/${this.collectionId}/home-url`, + { + method: "POST", + body: JSON.stringify({ + pageId, + }), + }, + ); + } +} diff --git a/frontend/src/features/collections/collection-thumbnail.ts b/frontend/src/features/collections/collection-thumbnail.ts new file mode 100644 index 0000000000..daebfbc3b6 --- /dev/null +++ b/frontend/src/features/collections/collection-thumbnail.ts @@ -0,0 +1,52 @@ +import { localized } from "@lit/localize"; +import { html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import thumbnailCyanSrc from "~assets/images/thumbnails/thumbnail-cyan.avif"; +import thumbnailGreenSrc from "~assets/images/thumbnails/thumbnail-green.avif"; +import thumbnailOrangeSrc from "~assets/images/thumbnails/thumbnail-orange.avif"; +import thumbnailYellowSrc from "~assets/images/thumbnails/thumbnail-yellow.avif"; + +export enum Thumbnail { + Cyan = "thumbnail-cyan", + Green = "thumbnail-green", + Orange = "thumbnail-orange", + Yellow = "thumbnail-yellow", +} + +export const DEFAULT_THUMBNAIL = Thumbnail.Cyan; + +@localized() +@customElement("btrix-collection-thumbnail") +export class CollectionThumbnail extends BtrixElement { + static readonly Variants: Record = { + [Thumbnail.Cyan]: { + path: thumbnailCyanSrc, + }, + [Thumbnail.Green]: { + path: thumbnailGreenSrc, + }, + [Thumbnail.Orange]: { + path: thumbnailOrangeSrc, + }, + [Thumbnail.Yellow]: { + path: thumbnailYellowSrc, + }, + }; + + @property({ type: String }) + src?: string; + + render() { + return html` + + `; + } +} + +export const DEFAULT_THUMBNAIL_VARIANT = + CollectionThumbnail.Variants[DEFAULT_THUMBNAIL]; diff --git a/frontend/src/features/collections/collections-grid.ts b/frontend/src/features/collections/collections-grid.ts new file mode 100644 index 0000000000..579997f2e5 --- /dev/null +++ b/frontend/src/features/collections/collections-grid.ts @@ -0,0 +1,140 @@ +import { localized, msg } from "@lit/localize"; +import { html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { when } from "lit/directives/when.js"; + +import { CollectionThumbnail } from "./collection-thumbnail"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import { RouteNamespace } from "@/routes"; +import type { PublicCollection } from "@/types/collection"; +import { tw } from "@/utils/tailwind"; + +/** + * Grid view of collections list + * + * @TODO Generalize into collections, just handling public collections for now + */ +@localized() +@customElement("btrix-collections-grid") +export class CollectionsGrid extends BtrixElement { + @property({ type: String }) + slug = ""; + + @property({ type: Array }) + collections?: PublicCollection[]; + + render() { + const gridClassNames = tw`grid flex-1 grid-cols-1 gap-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4`; + + if (!this.collections || !this.slug) { + const thumb = html` + + `; + + return html` +
${thumb}${thumb}${thumb}${thumb}
+ `; + } + + if (!this.collections.length) { + return html` +
+

+ ${msg("No public collections yet.")} +

+ +
+ `; + } + + const showActions = !this.navigate.isPublicPage && this.appState.isCrawler; + + return html` + + `; + } + + private readonly renderActions = (collection: PublicCollection) => html` +
+
+ + + + + ${msg("Visit Public Page")} + + + +
+
+ `; + + private renderDateBadge(collection: PublicCollection) { + if (!collection.dateEarliest || !collection.dateLatest) return; + + const earliestYear = this.localize.date(collection.dateEarliest, { + year: "numeric", + }); + const latestYear = this.localize.date(collection.dateLatest, { + year: "numeric", + }); + + return html` + + ${earliestYear} + ${latestYear !== earliestYear ? html` - ${latestYear} ` : nothing} + + `; + } +} diff --git a/frontend/src/features/collections/index.ts b/frontend/src/features/collections/index.ts index 949a13dbc5..3480cdd694 100644 --- a/frontend/src/features/collections/index.ts +++ b/frontend/src/features/collections/index.ts @@ -1,6 +1,10 @@ import("./collections-add"); +import("./collections-grid"); import("./collection-items-dialog"); import("./collection-metadata-dialog"); +import("./collection-replay-dialog"); import("./collection-workflow-list"); import("./select-collection-access"); +import("./select-collection-start-page"); import("./share-collection"); +import("./collection-thumbnail"); diff --git a/frontend/src/features/collections/select-collection-access.ts b/frontend/src/features/collections/select-collection-access.ts index b94686c1d9..ce12d49292 100644 --- a/frontend/src/features/collections/select-collection-access.ts +++ b/frontend/src/features/collections/select-collection-access.ts @@ -2,6 +2,7 @@ import { localized, msg } from "@lit/localize"; import type { SlIcon, SlSelectEvent } from "@shoelace-style/shoelace"; import { html } from "lit"; import { customElement, property } from "lit/decorators.js"; +import { when } from "lit/directives/when.js"; import { BtrixElement } from "@/classes/BtrixElement"; import { CollectionAccess } from "@/types/collection"; @@ -23,7 +24,6 @@ export class SelectCollectionAccess extends BtrixElement { icon: "link-45deg", detail: msg("Only people with the link can view"), }, - [CollectionAccess.Public]: { label: msg("Public"), icon: "globe2", @@ -83,13 +83,40 @@ export class SelectCollectionAccess extends BtrixElement { > ${label} - ${detail} + + ${detail} + `, )}
+ ${when( + this.value === CollectionAccess.Public, + () => html` + +
+ + ${msg("What information will be visible to the public?")} +
+ ${msg( + "In addition to replay, the following collection details will be visible:", + )} +
    +
  • ${msg("Name")}
  • +
  • ${msg("Summary")}
  • +
  • ${msg("About")}
  • +
  • ${msg("Collection Period")}
  • +
  • ${msg("Total Pages")}
  • +
  • ${msg("Collection Size")}
  • +
+
+ `, + )} `; } } diff --git a/frontend/src/features/collections/select-collection-start-page.ts b/frontend/src/features/collections/select-collection-start-page.ts new file mode 100644 index 0000000000..7b991e20d8 --- /dev/null +++ b/frontend/src/features/collections/select-collection-start-page.ts @@ -0,0 +1,308 @@ +import { localized, msg } from "@lit/localize"; +import { Task } from "@lit/task"; +import type { + SlChangeEvent, + SlInput, + SlSelect, +} from "@shoelace-style/shoelace"; +import { html, type PropertyValues } from "lit"; +import { customElement, property, query, state } from "lit/decorators.js"; +import { when } from "lit/directives/when.js"; +import debounce from "lodash/fp/debounce"; +import sortBy from "lodash/fp/sortBy"; +import queryString from "query-string"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import type { Combobox } from "@/components/ui/combobox"; +import type { APIPaginatedList, APIPaginationQuery } from "@/types/api"; +import type { UnderlyingFunction } from "@/types/utils"; + +type Snapshot = { + pageId: string; + ts: string; + status: number; +}; + +type Page = { + url: string; + count: number; + snapshots: Snapshot[]; +}; + +export type SelectSnapshotDetail = { + item: Snapshot & { url: string }; +}; + +const DEFAULT_PROTOCOL = "http"; + +const sortByTs = sortBy("ts"); + +/** + * @fires btrix-select + */ +@localized() +@customElement("btrix-select-collection-start-page") +export class SelectCollectionStartPage extends BtrixElement { + @property({ type: String }) + collectionId?: string; + + @property({ type: String }) + homeUrl?: string | null = null; + + @property({ type: String }) + homeTs?: string | null = null; + + @state() + private searchQuery = ""; + + @state() + private selectedPage?: Page; + + @state() + private selectedSnapshot?: Snapshot; + + @query("btrix-combobox") + private readonly combobox?: Combobox | null; + + @query("sl-input") + private readonly input?: SlInput | null; + + public get page() { + return this.selectedPage; + } + + public get snapshot() { + return this.selectedSnapshot; + } + + updated(changedProperties: PropertyValues) { + if (changedProperties.has("homeUrl") && this.homeUrl) { + if (this.input) { + this.input.value = this.homeUrl; + } + this.searchQuery = this.homeUrl; + void this.initSelection(); + } + } + + private async initSelection() { + await this.updateComplete; + await this.searchResults.taskComplete; + + if (this.homeUrl && this.searchResults.value) { + this.selectedPage = this.searchResults.value.items.find( + ({ url }) => url === this.homeUrl, + ); + + if (this.selectedPage && this.homeTs) { + this.selectedSnapshot = this.selectedPage.snapshots.find( + ({ ts }) => ts === this.homeTs, + ); + } + } + } + + private readonly searchResults = new Task(this, { + task: async ([searchValue], { signal }) => { + const searchResults = await this.getPageUrls( + { + id: this.collectionId!, + urlPrefix: searchValue, + }, + signal, + ); + + return searchResults; + }, + args: () => [this.searchQuery] as const, + }); + + render() { + return html` +
+ ${this.renderPageSearch()} + { + const { value } = e.currentTarget as SlSelect; + + await this.updateComplete; + + this.selectedSnapshot = this.selectedPage?.snapshots.find( + ({ pageId }) => pageId === value, + ); + + if (this.selectedSnapshot) { + this.dispatchEvent( + new CustomEvent("btrix-select", { + detail: { + item: { + url: this.selectedPage!.url, + ...this.selectedSnapshot, + }, + }, + }), + ); + } + }} + > + ${when( + this.selectedSnapshot, + (snapshot) => html` + ${snapshot.status} + `, + )} + ${when(this.selectedPage, (item) => + item.snapshots.map( + ({ pageId, ts, status }) => html` + + ${this.localize.date(ts)} + ${status} + + `, + ), + )} + +
+ `; + } + + private renderPageSearch() { + return html` + { + this.combobox?.hide(); + }} + > + { + this.combobox?.show(); + }} + @sl-clear=${async () => { + this.searchQuery = ""; + }} + @sl-input=${this.onSearchInput as UnderlyingFunction< + typeof this.onSearchInput + >} + > + + + ${this.renderSearchResults()} + + `; + } + + private renderSearchResults() { + return this.searchResults.render({ + pending: () => html` + + + + `, + complete: ({ items }) => { + if (!items.length) { + return html` + + ${msg("No matching pages found.")} + + `; + } + + return html` + ${items.map((item: Page) => { + return html` + { + if (this.input) { + this.input.value = item.url; + } + + this.selectedPage = { + ...item, + // TODO check if backend can sort + snapshots: sortByTs(item.snapshots).reverse(), + }; + + this.combobox?.hide(); + + this.selectedSnapshot = this.selectedPage.snapshots[0]; + + await this.updateComplete; + this.dispatchEvent( + new CustomEvent("btrix-select", { + detail: { + item: { + url: this.selectedPage.url, + ...this.selectedSnapshot, + }, + }, + }), + ); + }} + >${item.url} + + `; + })} + `; + }, + }); + } + + private readonly onSearchInput = debounce(400)(() => { + const value = this.input?.value; + + if (!value) { + return; + } + + if (value.startsWith(DEFAULT_PROTOCOL)) { + this.combobox?.show(); + } else { + if (value !== DEFAULT_PROTOCOL.slice(0, value.length)) { + this.input.value = `https://${value}`; + + this.combobox?.show(); + } + } + + this.searchQuery = this.input.value; + }); + + private async getPageUrls( + { + id, + urlPrefix, + page = 1, + pageSize = 5, + }: { + id: string; + urlPrefix?: string; + } & APIPaginationQuery, + signal?: AbortSignal, + ) { + const query = queryString.stringify({ + page, + pageSize, + urlPrefix: urlPrefix ? window.encodeURIComponent(urlPrefix) : undefined, + }); + return this.api.fetch>( + `/orgs/${this.orgId}/collections/${id}/urls?${query}`, + { signal }, + ); + } +} diff --git a/frontend/src/features/collections/share-collection.ts b/frontend/src/features/collections/share-collection.ts index 959f7096e2..d0fa0361f8 100644 --- a/frontend/src/features/collections/share-collection.ts +++ b/frontend/src/features/collections/share-collection.ts @@ -1,27 +1,45 @@ import { localized, msg, str } from "@lit/localize"; -import type { SlSelectEvent } from "@shoelace-style/shoelace"; -import { html } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +import type { + SlChangeEvent, + SlSelectEvent, + SlSwitch, + SlTabGroup, +} from "@shoelace-style/shoelace"; +import { html, nothing } from "lit"; +import { customElement, property, query, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { when } from "lit/directives/when.js"; +import { + CollectionThumbnail, + DEFAULT_THUMBNAIL_VARIANT, + Thumbnail, +} from "./collection-thumbnail"; import { SelectCollectionAccess } from "./select-collection-access"; import { BtrixElement } from "@/classes/BtrixElement"; import { ClipboardController } from "@/controllers/clipboard"; import { RouteNamespace } from "@/routes"; -import { CollectionAccess, type Collection } from "@/types/collection"; +import { + CollectionAccess, + type Collection, + type PublicCollection, +} from "@/types/collection"; -export type SelectVisibilityDetail = { - item: { value: CollectionAccess }; -}; +enum Tab { + Link = "link", + Embed = "embed", +} /** - * @fires btrix-select + * @fires btrix-change */ @localized() @customElement("btrix-share-collection") export class ShareCollection extends BtrixElement { + @property({ type: String }) + slug = ""; + @property({ type: String }) collectionId = ""; @@ -31,8 +49,8 @@ export class ShareCollection extends BtrixElement { @state() private showDialog = false; - @state() - private showEmbedCode = false; + @query("sl-tab-group") + private readonly tabGroup?: SlTabGroup | null; private readonly clipboardController = new ClipboardController(this); @@ -87,9 +105,7 @@ export class ShareCollection extends BtrixElement { { - this.showEmbedCode = true; + this.tabGroup?.show(Tab.Embed); this.showDialog = true; }} > @@ -127,14 +143,7 @@ export class ShareCollection extends BtrixElement { ${msg("View Embed Code")} ${when( - this.authState && - this.collectionId && - this.shareLink !== - window.location.href.slice( - 0, - window.location.href.indexOf(this.collectionId) + - this.collectionId.length, - ), + this.authState && !this.navigate.isPublicPage, () => html` - - { - this.showDialog = true; - }} - > - - ${msg("Change Link Visibility")} - - `, - () => html` - - - ${msg("Download Collection")} - + ${this.appState.isCrawler + ? html` + + { + this.showDialog = true; + }} + > + + ${msg("Link Settings")} + + ` + : nothing} `, )} + ${when(this.slug && this.collection, (collection) => + collection.access === CollectionAccess.Public && + collection.allowPublicDownload + ? html` + + + ${msg("Download Collection")} + ${when( + this.collection, + (collection) => html` + ${this.localize.bytes( + collection.totalSize || 0, + )} + `, + )} + + ` + : nothing, + )} @@ -183,6 +214,8 @@ export class ShareCollection extends BtrixElement { } private renderDialog() { + const showSettings = !this.navigate.isPublicPage && this.authState; + return html` { - this.showEmbedCode = false; + this.tabGroup?.show(Tab.Link); }} - style="--width: 32rem;" + class="[--body-spacing:0] [--width:40rem]" > - ${when( - this.authState && this.collection, - (collection) => html` -
- { - this.dispatchEvent( - new CustomEvent("btrix-select", { - detail: { - item: { - value: (e.target as SelectCollectionAccess).value, - }, - }, - }), - ); - }} - > + + ${showSettings ? msg("Link Settings") : msg("Link")} + ${msg("Embed")} + + +
+ ${when( + showSettings && this.collection, + this.renderSettings, + this.renderShareLink, + )}
- `, - )} - ${this.renderShareLink()} ${this.renderEmbedCode()} -
+ + + +
${this.renderEmbedCode()}
+
+ + +
+ ${when(showSettings, this.renderShareLink)} (this.showDialog = false)}> ${msg("Done")} @@ -227,16 +259,145 @@ export class ShareCollection extends BtrixElement { `; } + private readonly renderSettings = (collection: Partial) => { + return html` +
+ { + void this.updateVisibility( + (e.target as SelectCollectionAccess).value, + ); + }} + > + ${when( + this.org && + !this.org.enablePublicProfile && + this.collection?.access === CollectionAccess.Public, + () => html` + + ${msg( + "The org profile page isn't public yet. To make the org profile and this collection visible to the public, update profile visibility in org settings.", + )} + + `, + )} +
+
+
+ ${msg("Thumbnail")} + + + +
+ ${this.renderThumbnails()} +
+
+
+ ${msg("Downloads")} + + + +
+
+ { + void this.updateAllowDownload((e.target as SlSwitch).checked); + }} + >${msg("Show download button")} +
+
+ `; + }; + + private renderThumbnails() { + let selectedImgSrc = DEFAULT_THUMBNAIL_VARIANT.path; + + if (this.collection?.defaultThumbnailName) { + const { defaultThumbnailName } = this.collection; + const variant = Object.entries(CollectionThumbnail.Variants).find( + ([name]) => name === defaultThumbnailName, + ); + + if (variant) { + selectedImgSrc = variant[1].path; + } + } else if (this.collection?.thumbnail) { + selectedImgSrc = this.collection.thumbnail.path; + } + + const thumbnail = ( + thumbnail: Thumbnail | NonNullable, + ) => { + let name: Thumbnail | null = null; + let path = ""; + + if (Object.values(Thumbnail).some((t) => t === thumbnail)) { + name = thumbnail as Thumbnail; + path = CollectionThumbnail.Variants[name].path; + } else { + path = (thumbnail as NonNullable).path; + } + + if (!path) { + console.debug("no path for thumbnail:", thumbnail); + return; + } + + const isSelected = path === selectedImgSrc; + + return html` + + + + `; + }; + + return html` +
+ ${when(this.collection?.thumbnail, (t) => thumbnail(t))} + ${thumbnail(Thumbnail.Cyan)} ${thumbnail(Thumbnail.Green)} + ${thumbnail(Thumbnail.Yellow)} ${thumbnail(Thumbnail.Orange)} +
+ `; + } + private readonly renderShareLink = () => { return html` - - ${msg("Link to Share")} +
+
${msg("Link to Share")}
- +
`; }; @@ -261,64 +422,199 @@ export class ShareCollection extends BtrixElement { const importCode = `importScripts("https://replayweb.page/sw.js");`; return html` - - ${msg("Embed Code")} - ${when( - this.collection?.access === CollectionAccess.Private, - () => html` - - ${msg("Change the visibility setting to embed this collection.")} - - `, + ${when( + this.collection?.access === CollectionAccess.Private, + () => html` + + ${msg("Change the visibility setting to embed this collection.")} + + `, + )} +

+ ${msg( + html`To embed this collection into an existing webpage, add the + following embed code:`, )} -

- ${msg( - html`To embed this collection into an existing webpage, add the - following embed code:`, - )} -

-
- -
- embedCode} - content=${msg("Copy Embed Code")} - hoist - raised - > -
+

+
+ +
+ embedCode} + content=${msg("Copy Embed Code")} + hoist + raised + >
-

- ${msg( - html`Add the following JavaScript to your - /replay/sw.js:`, - )} -

-
- -
- importCode} - content=${msg("Copy JS")} - hoist - raised - > -
+
+

+ ${msg( + html`Add the following JavaScript to your + /replay/sw.js:`, + )} +

+
+ +
+ importCode} + content=${msg("Copy JS")} + hoist + raised + >
-

- ${msg( - html`See - - our embedding guide - for more details.`, - )} -

- +
+

+ ${msg( + html`See + + our embedding guide + for more details.`, + )} +

`; }; + + private async updateVisibility(access: CollectionAccess) { + const prevValue = this.collection?.access; + + // Optimistic update + if (this.collection) { + this.collection = { ...this.collection, access }; + } + + try { + await this.api.fetch<{ updated: boolean }>( + `/orgs/${this.orgId}/collections/${this.collectionId}`, + { + method: "PATCH", + body: JSON.stringify({ access }), + }, + ); + + this.dispatchEvent(new CustomEvent("btrix-change")); + + this.notify.toast({ + id: "collection-visibility-update-status", + message: msg("Collection visibility updated."), + variant: "success", + icon: "check2-circle", + }); + } catch (err) { + console.debug(err); + + // Revert optimistic update + if (this.collection && prevValue !== undefined) { + this.collection = { ...this.collection, access: prevValue }; + } + + this.notify.toast({ + id: "collection-visibility-update-status", + message: msg("Sorry, couldn't update visibility at this time."), + variant: "danger", + icon: "exclamation-octagon", + }); + } + } + + async updateThumbnail({ + defaultThumbnailName, + }: { + defaultThumbnailName: Thumbnail | null; + }) { + const prevValue = this.collection?.defaultThumbnailName; + + // Optimistic update + if (this.collection) { + this.collection = { ...this.collection, defaultThumbnailName }; + } + + try { + await this.api.fetch<{ updated: boolean }>( + `/orgs/${this.orgId}/collections/${this.collectionId}`, + { + method: "PATCH", + body: JSON.stringify({ defaultThumbnailName }), + }, + ); + + this.dispatchEvent(new CustomEvent("btrix-change")); + + this.notify.toast({ + id: "collection-thumbnail-update-status", + message: msg("Thumbnail updated."), + variant: "success", + icon: "check2-circle", + }); + } catch (err) { + console.debug(err); + + // Revert optimistic update + if (this.collection && prevValue !== undefined) { + this.collection = { + ...this.collection, + defaultThumbnailName: prevValue, + }; + } + + this.notify.toast({ + id: "collection-thumbnail-update-status", + message: msg("Sorry, couldn't update thumbnail at this time."), + variant: "danger", + icon: "exclamation-octagon", + }); + } + } + + async updateAllowDownload(allowPublicDownload: boolean) { + const prevValue = this.collection?.allowPublicDownload; + + // Optimistic update + if (this.collection) { + this.collection = { ...this.collection, allowPublicDownload }; + } + + try { + await this.api.fetch<{ updated: boolean }>( + `/orgs/${this.orgId}/collections/${this.collectionId}`, + { + method: "PATCH", + body: JSON.stringify({ allowPublicDownload }), + }, + ); + + this.dispatchEvent(new CustomEvent("btrix-change")); + + this.notify.toast({ + id: "collection-allow-public-download-update-status", + message: allowPublicDownload + ? msg("Download button enabled.") + : msg("Download button hidden."), + variant: "success", + icon: "check2-circle", + }); + } catch (err) { + console.debug(err); + + // Revert optimistic update + if (this.collection && prevValue !== undefined) { + this.collection = { + ...this.collection, + allowPublicDownload: prevValue, + }; + } + + this.notify.toast({ + id: "collection-allow-public-download-update-status", + message: msg("Sorry, couldn't update download button at this time."), + variant: "danger", + icon: "exclamation-octagon", + }); + } + } } diff --git a/frontend/src/layouts/page.ts b/frontend/src/layouts/page.ts index adbc4c1217..652d220f44 100644 --- a/frontend/src/layouts/page.ts +++ b/frontend/src/layouts/page.ts @@ -1,52 +1,93 @@ import clsx from "clsx"; import { html, nothing, type TemplateResult } from "lit"; import { ifDefined } from "lit/directives/if-defined.js"; +import { html as staticHtml, unsafeStatic } from "lit/static-html.js"; -import { pageTitle } from "./pageHeader"; +import { pageNav, pageTitle } from "./pageHeader"; -import type { tw } from "@/utils/tailwind"; +import { tw } from "@/utils/tailwind"; type Content = string | TemplateResult | typeof nothing; +export function pageHeading({ + content, + level = 2, +}: { + content?: string | TemplateResult | typeof nothing; + level?: 2 | 3 | 4; +}) { + const tag = unsafeStatic(`h${level}`); + const classNames = tw`min-w-0 text-lg font-medium leading-8`; + + return staticHtml` + <${tag} class=${classNames}> + ${ + content || + html`` + } + + `; +} + // TODO consolidate with pageHeader.ts https://github.com/webrecorder/browsertrix/issues/2197 -function pageHeader({ +export function pageHeader({ title, + prefix, suffix, secondary, actions, + border = true, classNames, }: { - title: Content; + title?: Content; + prefix?: Content; suffix?: Content; secondary?: Content; actions?: Content; + border?: boolean; classNames?: typeof tw; }) { return html` -
-
-
${pageTitle(title)} ${suffix}
- ${actions - ? html`
${actions}
` - : nothing} +
+
+
+ ${prefix}${pageTitle(title)}${suffix} +
+ ${secondary}
- ${secondary} + + ${actions + ? html`
+ ${actions} +
` + : nothing}
`; } export function page( - header: Parameters[0], + header: Parameters[0] & { + breadcrumbs?: Parameters[0]; + }, render: () => TemplateResult, ) { return html`
+ ${header.breadcrumbs ? html` ${pageNav(header.breadcrumbs)} ` : nothing} ${pageHeader(header)} -
${render()}
+
${render()}
`; } diff --git a/frontend/src/layouts/pageHeader.ts b/frontend/src/layouts/pageHeader.ts index f405a6d919..b8678eea7d 100644 --- a/frontend/src/layouts/pageHeader.ts +++ b/frontend/src/layouts/pageHeader.ts @@ -87,7 +87,8 @@ export function pageBack({ href, content }: Breadcrumb) { export function pageTitle(title?: string | TemplateResult | typeof nothing) { return html`

- ${title || html``} + ${title || + html``}

`; } diff --git a/frontend/src/pages/collections/collection.ts b/frontend/src/pages/collections/collection.ts index ab1fdcb370..1150fb2fba 100644 --- a/frontend/src/pages/collections/collection.ts +++ b/frontend/src/pages/collections/collection.ts @@ -1,15 +1,16 @@ -import { localized, msg } from "@lit/localize"; +import { localized, msg, str } from "@lit/localize"; import { Task } from "@lit/task"; -import { html, nothing } from "lit"; +import { html, type TemplateResult } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { choose } from "lit/directives/choose.js"; import { ifDefined } from "lit/directives/if-defined.js"; +import { when } from "lit/directives/when.js"; import { BtrixElement } from "@/classes/BtrixElement"; -import type { SelectVisibilityDetail } from "@/features/collections/share-collection"; import { page } from "@/layouts/page"; import { RouteNamespace } from "@/routes"; -import type { OrgProfileData, PublicCollection } from "@/types/org"; +import type { PublicCollection } from "@/types/collection"; +import type { PublicOrgCollections } from "@/types/org"; +import { formatRwpTimestamp } from "@/utils/replay"; enum Tab { Replay = "replay", @@ -28,90 +29,139 @@ export class Collection extends BtrixElement { @property({ type: String }) tab: Tab | string = Tab.Replay; + get canEditCollection() { + return this.slug === this.orgSlug && this.appState.isCrawler; + } + private readonly tabLabels: Record< Tab, { icon: { name: string; library: string }; text: string } > = { [Tab.Replay]: { icon: { name: "replaywebpage", library: "app" }, - text: msg("Replay"), + text: msg("Browse Collection"), }, [Tab.About]: { icon: { name: "info-square-fill", library: "default" }, - text: msg("About"), + text: msg("About This Collection"), }, }; - readonly publicOrg = new Task(this, { + private readonly orgCollections = new Task(this, { task: async ([slug]) => { - if (!slug) return; - const org = await this.fetchOrgProfile(slug); + if (!slug) throw new Error("slug required"); + const org = await this.fetchCollections({ slug }); return org; }, + args: () => [this.slug] as const, + }); + + private readonly collection = new Task(this, { + task: async ([slug, collectionId]) => { + if (!slug || !collectionId) + throw new Error("slug and collection required"); + const collection = await this.fetchCollection({ slug, collectionId }); + + if (!collection.crawlCount && (this.tab as unknown) === Tab.Replay) { + this.tab = Tab.About; + } + + return collection; + }, args: () => [this.slug, this.collectionId] as const, }); render() { - return html` - ${this.publicOrg.render({ - complete: (profile) => - profile ? this.renderCollection(profile) : nothing, - })} - `; + return this.collection.render({ + complete: this.renderComplete, + error: this.renderError, + }); } - private renderCollection({ org, collections }: OrgProfileData) { - const collection = - this.collectionId && - collections.find(({ id }) => id === this.collectionId); + private readonly renderComplete = (collection: PublicCollection) => { + const org = this.orgCollections.value?.org; + const header: Parameters[0] = { + breadcrumbs: org + ? [ + { + href: `/${RouteNamespace.PublicOrgs}/${this.slug}`, + content: org.name, + }, + { + href: `/${RouteNamespace.PublicOrgs}/${this.slug}`, + content: msg("Collections"), + }, + { + content: collection.name, + }, + ] + : [], + title: collection.name || "", + actions: html` + ${when( + this.canEditCollection, + () => html` + + + + `, + )} + + + `, + }; - if (!collection) { - return "TODO"; + if (collection.caption) { + header.secondary = html` +
${collection.caption}
+ `; } + const panel = (tab: Tab, content: TemplateResult) => html` +
+ ${content} +
+ `; + return html` ${page( - { - title: collection.name, - secondary: html` -
- ${msg("Collection by")} - ${org.name} -
- `, - actions: html` - ) => { - e.stopPropagation(); - console.log("TODO"); - }} - > - `, - }, + header, () => html` - ${choose( - this.tab, - [ - [Tab.Replay, () => this.renderReplay(collection)], - [Tab.About, () => this.renderAbout(collection)], - ], - () => html``, + ${when(collection.crawlCount, () => + panel(Tab.Replay, this.renderReplay(collection)), )} + ${panel(Tab.About, this.renderAbout(collection))} `, )} `; - } + }; + + private readonly renderError = (error?: unknown) => { + console.log("error", error); + + return html`
+ +
`; + }; private readonly renderTab = (tab: Tab) => { const isSelected = tab === (this.tab as Tab); @@ -139,9 +189,15 @@ export class Collection extends BtrixElement { ).href; return html` -
+
-
-

- ${msg("Description")} -

-
- ${collection.description - ? html` - - ` - : html`

- ${msg( - "A description has not been provided for this collection.", - )} -

`} -
-
-
-

- ${msg("Metadata")} -

- - - ${this.localize.number(collection.crawlCount)} - - - ${this.localize.number(collection.pageCount)} - - - ${this.localize.bytes(collection.totalSize)} - - - TODO - - -
-
+ const dateRange = () => { + if (!collection.dateEarliest || !collection.dateLatest) { + return msg("n/a"); + } + const format: Intl.DateTimeFormatOptions = { + month: "long", + year: "numeric", + }; + const dateEarliest = this.localize.date(collection.dateEarliest, format); + const dateLatest = this.localize.date(collection.dateLatest, format); + + if (dateEarliest === dateLatest) return dateLatest; + + return msg(str`${dateEarliest} to ${dateLatest}`, { + desc: "Date range formatted to show full month name and year", + }); + }; + + const metadata = html` + + + ${dateRange()} + + + ${this.localize.number(collection.pageCount)} + + + ${this.localize.bytes(collection.totalSize)} + + `; + + if (collection.description) { + return html` +
+
+ +
+
+ +

${msg("Metadata")}

+
+
${metadata}
+
+
+ `; + } + + return html`
${metadata}
`; } - private async fetchOrgProfile(slug: string): Promise { - const resp = await fetch(`/api/public-collections/${slug}`, { + private async fetchCollections({ + slug, + }: { + slug: string; + }): Promise { + const resp = await fetch(`/api/public/orgs/${slug}/collections`, { headers: { "Content-Type": "application/json" }, }); switch (resp.status) { case 200: - return (await resp.json()) as OrgProfileData; - case 404: { + return (await resp.json()) as PublicOrgCollections; + default: throw resp.status; - } + } + } + + private async fetchCollection({ + slug, + collectionId, + }: { + slug: string; + collectionId: string; + }): Promise { + const resp = await fetch( + `/api/public/orgs/${slug}/collections/${collectionId}`, + { + headers: { "Content-Type": "application/json" }, + }, + ); + + switch (resp.status) { + case 200: + return (await resp.json()) as PublicCollection; default: throw resp.status; } diff --git a/frontend/src/pages/org/archived-item-qa/archived-item-qa.ts b/frontend/src/pages/org/archived-item-qa/archived-item-qa.ts index 1b44e4bcb3..5d5ea682e3 100644 --- a/frontend/src/pages/org/archived-item-qa/archived-item-qa.ts +++ b/frontend/src/pages/org/archived-item-qa/archived-item-qa.ts @@ -45,6 +45,7 @@ import { type finishedCrawlStates, } from "@/utils/crawler"; import { maxLengthValidator } from "@/utils/form"; +import { formatRwpTimestamp } from "@/utils/replay"; import { tw } from "@/utils/tailwind"; const DEFAULT_PAGE_SIZE = 100; @@ -1314,7 +1315,7 @@ export class ArchivedItemQA extends BtrixElement { return; } - const timestamp = page.ts?.split(".")[0].replace(/\D/g, ""); + const timestamp = formatRwpTimestamp(page.ts) || ""; const pageUrl = page.url; const doLoad = async (isQA: boolean) => { diff --git a/frontend/src/pages/org/collection-detail.ts b/frontend/src/pages/org/collection-detail.ts index 733586fc15..0ce6e42aea 100644 --- a/frontend/src/pages/org/collection-detail.ts +++ b/frontend/src/pages/org/collection-detail.ts @@ -9,12 +9,11 @@ import queryString from "query-string"; import type { Embed as ReplayWebPage } from "replaywebpage"; import { BtrixElement } from "@/classes/BtrixElement"; +import type { MarkdownEditor } from "@/components/ui/markdown-editor"; import type { PageChangeEvent } from "@/components/ui/pagination"; import { SelectCollectionAccess } from "@/features/collections/select-collection-access"; -import type { - SelectVisibilityDetail, - ShareCollection, -} from "@/features/collections/share-collection"; +import type { ShareCollection } from "@/features/collections/share-collection"; +import { pageHeader } from "@/layouts/page"; import { pageNav, type Breadcrumb } from "@/layouts/pageHeader"; import type { APIPaginatedList, @@ -25,12 +24,16 @@ import { CollectionAccess, type Collection } from "@/types/collection"; import type { ArchivedItem, Crawl, Upload } from "@/types/crawler"; import type { CrawlState } from "@/types/crawlState"; import { pluralOf } from "@/utils/pluralize"; +import { formatRwpTimestamp } from "@/utils/replay"; const ABORT_REASON_THROTTLE = "throttled"; -const DESCRIPTION_MAX_HEIGHT_PX = 200; const INITIAL_ITEMS_PAGE_SIZE = 20; -const TABS = ["replay", "items"] as const; -export type Tab = (typeof TABS)[number]; + +export enum Tab { + Replay = "replay", + About = "about", + Items = "items", +} @customElement("btrix-collection-detail") @localized() @@ -39,7 +42,7 @@ export class CollectionDetail extends BtrixElement { collectionId!: string; @property({ type: String }) - collectionTab?: Tab = TABS[0]; + collectionTab: Tab = Tab.Replay; @state() private collection?: Collection; @@ -48,16 +51,17 @@ export class CollectionDetail extends BtrixElement { private archivedItems?: APIPaginatedList; @state() - private openDialogName?: "delete" | "editMetadata" | "editItems"; + private openDialogName?: + | "delete" + | "editMetadata" + | "editItems" + | "editStartPage"; @state() - private isDescriptionExpanded = false; + private isEditingDescription = false; - @query(".description") - private readonly description?: HTMLElement | null; - - @query(".descriptionExpandBtn") - private readonly descriptionExpandBtn?: HTMLElement | null; + @state() + private isRwpLoaded = false; @query("replay-web-page") private readonly replayEmbed?: ReplayWebPage | null; @@ -65,6 +69,9 @@ export class CollectionDetail extends BtrixElement { @query("btrix-share-collection") private readonly shareCollection?: ShareCollection | null; + @query("btrix-markdown-editor") + private readonly descriptionEditor?: MarkdownEditor | null; + // Use to cancel requests private getArchivedItemsController: AbortController | null = null; @@ -72,14 +79,18 @@ export class CollectionDetail extends BtrixElement { Tab, { icon: { name: string; library: string }; text: string } > = { - replay: { + [Tab.Replay]: { icon: { name: "replaywebpage", library: "app" }, text: msg("Replay"), }, - items: { + [Tab.Items]: { icon: { name: "list-ul", library: "default" }, text: msg("Archived Items"), }, + [Tab.About]: { + icon: { name: "info-square-fill", library: "default" }, + text: msg("About"), + }, }; private get isCrawler() { @@ -98,120 +109,130 @@ export class CollectionDetail extends BtrixElement { protected async updated( changedProperties: PropertyValues & Map, ) { - if (changedProperties.has("collection") && this.collection) { - void this.checkTruncateDescription(); + if ( + changedProperties.has("isEditingDescription") && + this.isEditingDescription + ) { + if (this.descriptionEditor) { + // FIXME Focus on editor ready instead of timeout + window.setTimeout(() => { + void this.descriptionEditor?.focus(); + }, 200); + } } } render() { return html`
${this.renderBreadcrumbs()}
-
-
-
- ${choose(this.collection?.access, [ - [ - CollectionAccess.Private, - () => html` - - - - `, - ], - [ - CollectionAccess.Unlisted, - () => html` - - - - `, - ], - [ - CollectionAccess.Public, - () => html` - - - - `, - ], - ])} -
-

- ${this.collection?.name || - html``} -

-
- ) => { - e.stopPropagation(); - void this.updateVisibility(e.detail.item.value); - }} - > - ${when(this.isCrawler, this.renderActions)} -
-
+ ${pageHeader({ + title: this.collection?.name, + border: false, + prefix: this.renderAccessIcon(), + secondary: this.collection?.caption + ? html`
+ ${this.collection.caption} +
` + : nothing, + actions: html` + { + e.stopPropagation(); + void this.fetchCollection(); + }} + > + ${when(this.isCrawler, this.renderActions)} + `, + })} + +
${this.renderInfoBar()}
-
+
${this.renderTabs()} - ${when( - this.isCrawler, - () => html` - (this.openDialogName = "editItems")} - ?disabled=${!this.collection} - > - - ${msg("Select Items")} - - `, + ${when(this.isCrawler, () => + choose(this.collectionTab, [ + [ + Tab.Replay, + () => html` + + (this.openDialogName = "editStartPage")} + ?disabled=${!this.collection?.crawlCount} + > + + ${msg("Configure Home")} + + + `, + ], + [ + Tab.About, + () => + this.isEditingDescription + ? html` +
+ void this.saveDescription()} + ?disabled=${!this.collection} + > + + ${msg("Save")} + + (this.isEditingDescription = false)} + > + ${msg("Cancel")} + +
+ ` + : html` + (this.isEditingDescription = true)} + ?disabled=${!this.collection} + > + + ${msg("Edit")} + + `, + ], + [ + Tab.Items, + () => html` + (this.openDialogName = "editItems")} + ?disabled=${!this.collection} + > + + ${msg("Select Items")} + + `, + ], + ]), )}
- ${choose( - this.collectionTab, + ${choose(this.collectionTab, [ + [Tab.Replay, () => guard([this.collection], this.renderReplay)], [ - ["replay", () => guard([this.collection], this.renderReplay)], - [ - "items", - () => guard([this.archivedItems], this.renderArchivedItems), - ], + Tab.Items, + () => guard([this.archivedItems], this.renderArchivedItems), ], - - () => html``, - )} -
${this.renderDescription()}
+ [Tab.About, () => this.renderDescription()], + ])} + + { + this.openDialogName = undefined; + + // Don't do full refresh of rwp so that rwp-url-change fires + this.isRwpLoaded = false; + + await this.fetchCollection(); + await this.updateComplete; + }} + collectionId=${this.collectionId} + .homeUrl=${this.collection?.homeUrl} + .homePageId=${this.collection?.homeUrlPageId} + .homeTs=${this.collection?.homeUrlTs} + ?replayLoaded=${this.isRwpLoaded} + > + ${when( this.collection, () => html` @@ -269,6 +309,56 @@ export class CollectionDetail extends BtrixElement { )}`; } + private renderAccessIcon() { + return choose(this.collection?.access, [ + [ + CollectionAccess.Private, + () => html` + + + + `, + ], + [ + CollectionAccess.Unlisted, + () => html` + + + + `, + ], + [ + CollectionAccess.Public, + () => html` + + + + `, + ], + ]); + } + private refreshReplay() { if (this.replayEmbed) { try { @@ -296,8 +386,10 @@ export class CollectionDetail extends BtrixElement { private readonly renderTabs = () => { return html`