diff --git a/package.json b/package.json index f0ef3aee1..80a1d1e8a 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f", "@appwrite.io/pink-legacy": "^1.0.3", - "@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@504c653", + "@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@de27ca4", "@popperjs/core": "^2.11.8", "@sentry/sveltekit": "^8.38.0", "@stripe/stripe-js": "^3.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78e5ecd08..4f473f04d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,8 +27,8 @@ importers: specifier: ^1.0.3 version: 1.0.3 '@appwrite.io/pink-svelte': - specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@504c653 - version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@504c653(react-dom@18.3.1(react@18.3.1))(svelte@5.25.3) + specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@de27ca4 + version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@de27ca4(react-dom@18.3.1(react@18.3.1))(svelte@5.25.3) '@popperjs/core': specifier: ^2.11.8 version: 2.11.8 @@ -258,14 +258,14 @@ packages: resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1} version: 1.2.1 - '@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@504c653542de13ba8b4815805806f301df92790a': - resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@504c653542de13ba8b4815805806f301df92790a} + '@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f': + resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f} version: 1.0.0-next.7 peerDependencies: svelte: ^4.0.0 - '@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f': - resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f} + '@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@de27ca407599e51b378814b421fb63625cb98c10': + resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@de27ca407599e51b378814b421fb63625cb98c10} version: 1.0.0-next.7 peerDependencies: svelte: ^4.0.0 @@ -279,8 +279,8 @@ packages: '@appwrite.io/pink-legacy@1.0.3': resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==} - '@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@504c653': - resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@504c653} + '@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@de27ca4': + resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@de27ca4} version: 1.0.0-next.85 peerDependencies: react-dom: ^18.0.0 @@ -3627,11 +3627,11 @@ snapshots: '@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1': {} - '@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@504c653542de13ba8b4815805806f301df92790a(svelte@5.25.3)': + '@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f(svelte@5.25.3)': dependencies: svelte: 5.25.3 - '@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f(svelte@5.25.3)': + '@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@de27ca407599e51b378814b421fb63625cb98c10(svelte@5.25.3)': dependencies: svelte: 5.25.3 @@ -3644,9 +3644,9 @@ snapshots: '@appwrite.io/pink-icons': 1.0.0 the-new-css-reset: 1.11.3 - '@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@504c653(react-dom@18.3.1(react@18.3.1))(svelte@5.25.3)': + '@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@de27ca4(react-dom@18.3.1(react@18.3.1))(svelte@5.25.3)': dependencies: - '@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@504c653542de13ba8b4815805806f301df92790a(svelte@5.25.3) + '@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@de27ca407599e51b378814b421fb63625cb98c10(svelte@5.25.3) '@floating-ui/dom': 1.6.13 '@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.6(svelte@5.25.3))(svelte@5.25.3) '@melt-ui/svelte': 0.86.6(svelte@5.25.3) diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 1ec486dd5..431d8a370 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -353,6 +353,9 @@ export enum Submit { FileCreate = 'submit_file_create', FileDelete = 'submit_file_delete', FileUpdatePermissions = 'submit_file_update_permissions', + FileTokenCreate = 'submit_file_token', + FileTokenDelete = 'submit_file_delete', + FileTokenUpdate = 'submit_file_update_expiry', BudgetCapUpdate = 'submit_budget_cap_update', BudgetAlertsUpdate = 'submit_budget_alert_conditions_update', CreditRedeem = 'submit_credit_redeem', diff --git a/src/lib/components/expirationInput.svelte b/src/lib/components/expirationInput.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index b20b38d1f..4e509c671 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -82,3 +82,4 @@ export { default as BottomSheet } from './bottom-sheet/index'; export { default as Confirm } from './confirm.svelte'; export { default as UsageCard } from './usageCard.svelte'; export { default as ViewToggle } from './viewToggle.svelte'; +export { default as ExpirationInput } from './expirationInput.svelte'; diff --git a/src/lib/components/permissions/actions.svelte b/src/lib/components/permissions/actions.svelte index 4319e3d74..4bdbb7637 100644 --- a/src/lib/components/permissions/actions.svelte +++ b/src/lib/components/permissions/actions.svelte @@ -12,6 +12,7 @@ export let showTeam: boolean; export let showLabel: boolean; export let showCustom: boolean; + export let hideOnClick: boolean = false; export let groups: Writable>; const dispatch = createEventDispatcher(); @@ -19,31 +20,52 @@ - + dispatch('create', ['any'])}> + on:click={(e) => { + if (hideOnClick) hide(e); + dispatch('create', ['any']); + }}> Any dispatch('create', ['guests'])}> + on:click={(e) => { + if (hideOnClick) hide(e); + dispatch('create', ['guests']); + }}> All guests dispatch('create', ['users'])}> + on:click={(e) => { + if (hideOnClick) hide(e); + dispatch('create', ['users']); + }}> All users - (showUser = true)} - >Select users - (showTeam = true)} - >Select teams - (showLabel = true)} - >Label - (showCustom = true)} - >Custom permission + { + showUser = true; + if (hideOnClick) hide(e); + }}>Select users + { + showTeam = true; + if (hideOnClick) hide(e); + }}>Select teams + { + showLabel = true; + if (hideOnClick) hide(e); + }}>Label + { + showCustom = true; + if (hideOnClick) hide(e); + }}>Custom permission diff --git a/src/lib/components/permissions/permissions.svelte b/src/lib/components/permissions/permissions.svelte index 1dabdb0ba..efefa7f1a 100644 --- a/src/lib/components/permissions/permissions.svelte +++ b/src/lib/components/permissions/permissions.svelte @@ -21,6 +21,7 @@ import type { PinkColumn } from '$lib/helpers/types'; export let withCreate = false; + export let hideOnClick = false; export let permissions: string[] = []; let showUser = false; @@ -127,11 +128,11 @@ } const columns: PinkColumn[] = [ - { id: 'role', width: { min: 100 } }, - { id: 'create', width: { min: 100 }, hide: !withCreate }, - { id: 'read', width: { min: 100 } }, - { id: 'update', width: { min: 100 } }, - { id: 'delete', width: { min: 100 } }, + { id: 'role', width: { min: 80 } }, + { id: 'create', width: { min: 80 }, hide: !withCreate }, + { id: 'read', width: { min: 80 } }, + { id: 'update', width: { min: 80 } }, + { id: 'delete', width: { min: 80 } }, { id: 'action', width: 40 } ]; @@ -195,6 +196,7 @@ bind:showTeam bind:showUser {groups} + {hideOnClick} on:create={create} let:toggle> + + + + + {#each $columns as { id, title } (id)} + {title} + {/each} + + + {#each $tokens.tokens as token} + + {toLocaleDate(token.$createdAt)} + + {token.expire + ? cleanFormattedDate(token.expire) + : 'never'} + + {token.accessedAt + ? cleanFormattedDate(token.accessedAt, true) + : 'never'} + + + {#if token.$permissions.length} + + {getPermissionGroups(token.$permissions)} + + {:else} + none + {/if} + + + + + + + + + + + + + { + toggle(e); + showCopyUrlModal = true; + selectedFileToken = token; + }} + leadingIcon={IconDuplicate}> + Copy URL + + + { + toggle(e); + showManageToken = true; + selectedFileToken = token; + }}> + Edit expiry + + { + toggle(e); + showManageToken = true; + tokenPermissionsMode = true; + selectedFileToken = token; + }}> + Edit Permissions + + { + toggle(e); + tokenDeleteMode = true; + showManageToken = true; + selectedFileToken = token; + }}> + Delete + + + + + + + + {/each} + + {:else} + (showManageToken = true)} + >Create new file token + {/if} + + + + Permissions Assign read or write permissions at the bucket level or file level. If bucket level permissions @@ -135,8 +365,8 @@ {:else} - If you want to assign document permissions. Go to Bucket settings and - enable file security. Otherwise, only Bucket permissions will be used. + If you want to assign file permissions. Go to Bucket settings and enable + file security. Otherwise, only Bucket permissions will be used. {/if} @@ -175,3 +405,20 @@ + + createFileToken(expiry)} + on:updated={({ detail: token }) => updateFileToken(token)} + on:deleted={async () => await deleteFileToken(selectedFileToken)} /> + + + + diff --git a/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/deleteFile.svelte b/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/deleteFile.svelte index e4ba0b043..a21f60728 100644 --- a/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/deleteFile.svelte +++ b/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/deleteFile.svelte @@ -13,12 +13,13 @@ let error: string; const deleteFile = async () => { try { + const fileName = $file.name; await sdk.forProject.storage.deleteFile($file.bucketId, $file.$id); showDelete = false; await goto(`${base}/project-${page.params.project}/storage/bucket-${$file.bucketId}`); addNotification({ type: 'success', - message: `${$file.name} has been deleted` + message: `${fileName} has been deleted` }); trackEvent(Submit.FileDelete); } catch (error) { diff --git a/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/fileTokensCopyUrl.svelte b/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/fileTokensCopyUrl.svelte new file mode 100644 index 000000000..8f13ac33c --- /dev/null +++ b/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/fileTokensCopyUrl.svelte @@ -0,0 +1,141 @@ + + + + Use the token-based URL below to access this file securely. + + {#if error} + + Something went wrong, could not generate the URL. + + {:else} + {@const tokenUrl = codeSnippets[selectedTab]} + + + {#each ['Preview', 'View', 'Download'] as cat} + (selectedTab = cat)} + active={selectedTab === cat}> + {cat} + + {/each} + + + + {getMoreInfo()} + + {#if !token.expire} + + This token doesn't expire. Be cautious when sharing links. + + {/if} + + + {/if} + + + + diff --git a/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/manageFileToken.svelte b/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/manageFileToken.svelte new file mode 100644 index 000000000..e51d93204 --- /dev/null +++ b/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/manageFileToken.svelte @@ -0,0 +1,101 @@ + + + + +{#if isDelete} + + {@const formattedDate = cleanFormattedDate(fileToken.$createdAt)} +

+ Are you sure you want to delete the file token created on {formattedDate}? +

+ +

+ {#if fileToken.accessedAt} + {@const formattedDate = cleanFormattedDate(fileToken.accessedAt, true)} + + This token was last accessed on {formattedDate} + {:else} + This token has never been accessed. + {/if} +

+
+{:else} + + + + {#if fileToken} + {@const formattedDate = cleanFormattedDate(fileToken.$createdAt)} + Edit the expiry of the file token created on {formattedDate} + {:else} + Create a file token to grant access to a file. + Learn more. + {/if} + + + {#if isUpdatePermissions} + + {:else} + + {/if} + + + + + + +{/if} diff --git a/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/store.ts b/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/store.ts index 7d42b415a..8042caf2a 100644 --- a/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/store.ts +++ b/src/routes/(console)/project-[project]/storage/bucket-[bucket]/file-[file]/store.ts @@ -1,5 +1,21 @@ -import { derived } from 'svelte/store'; +import { derived, writable } from 'svelte/store'; import { page } from '$app/stores'; import type { Models } from '@appwrite.io/console'; +import type { Column } from '$lib/helpers/types'; export const file = derived(page, ($page) => $page.data.file as Models.File); + +export const tokens = derived(page, ($page) => $page.data.tokens as Models.ResourceTokenList); + +/** + * Caches JWTs in memory by `token.$id` to avoid regenerating them on each URL copy. + */ +export const cachedJwts = writable>(new Map()); + +export const columns = writable([ + { id: 'created', title: 'Created', type: 'datetime', width: 200 }, + { id: 'expiry', title: 'Expiration', type: 'datetime', width: 200 }, + { id: 'last_accessed', title: 'Last accessed', type: 'datetime', width: 200 }, + { id: 'permissions', title: 'Permissions', type: 'string', width: 200 }, + { id: 'actions', title: '', type: 'string', width: 60 } +]); diff --git a/src/routes/(console)/project-[project]/storage/bucket-[bucket]/settings/+page.svelte b/src/routes/(console)/project-[project]/storage/bucket-[bucket]/settings/+page.svelte index 8d9e5409d..44d3d37d4 100644 --- a/src/routes/(console)/project-[project]/storage/bucket-[bucket]/settings/+page.svelte +++ b/src/routes/(console)/project-[project]/storage/bucket-[bucket]/settings/+page.svelte @@ -113,15 +113,12 @@ let isExtensionsDisabled = true; $: if (permissions) { - if (symmetricDifference(permissions, data.bucket.$permissions).length) { - $arePermsDisabled = false; - } else $arePermsDisabled = true; + $arePermsDisabled = !symmetricDifference(permissions, data.bucket.$permissions).length; } $: if (extensions) { - if (JSON.stringify(extensions) !== JSON.stringify(data.bucket.allowedFileExtensions)) { - isExtensionsDisabled = false; - } else isExtensionsDisabled = true; + isExtensionsDisabled = + JSON.stringify(extensions) === JSON.stringify(data.bucket.allowedFileExtensions); } function toggleBucket() {