mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
update: file tokens api.
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@7f8fe13",
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@c2f54c6",
|
||||
"@appwrite.io/pink": "0.25.0",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@d68e5e4",
|
||||
|
||||
Generated
+5
-5
@@ -12,8 +12,8 @@ importers:
|
||||
specifier: ^1.1.24
|
||||
version: 1.1.24(svelte@5.25.3)(zod@3.24.3)
|
||||
'@appwrite.io/console':
|
||||
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@7f8fe13
|
||||
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@7f8fe13
|
||||
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@c2f54c6
|
||||
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@c2f54c6
|
||||
'@appwrite.io/pink':
|
||||
specifier: 0.25.0
|
||||
version: 0.25.0
|
||||
@@ -254,8 +254,8 @@ packages:
|
||||
'@analytics/type-utils@0.6.2':
|
||||
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@7f8fe13':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@7f8fe13}
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@c2f54c6':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@c2f54c6}
|
||||
version: 1.2.1
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@d68e5e4':
|
||||
@@ -3625,7 +3625,7 @@ snapshots:
|
||||
|
||||
'@analytics/type-utils@0.6.2': {}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@7f8fe13': {}
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@c2f54c6': {}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@d68e5e4(svelte@5.25.3)':
|
||||
dependencies:
|
||||
|
||||
+7
-52
@@ -52,7 +52,6 @@
|
||||
let showManageToken = false;
|
||||
let tokenDeleteMode = false;
|
||||
let showCopyUrlModal = false;
|
||||
let tokenPermissionsMode = false;
|
||||
let selectedFileToken: Models.ResourceToken | null = null;
|
||||
|
||||
const getPreview = (fileId: string) =>
|
||||
@@ -126,9 +125,7 @@
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.tokens.createFileToken($file.bucketId, $file.$id, expiry, [
|
||||
Permission.read(Role.any())
|
||||
]);
|
||||
.tokens.createFileToken($file.bucketId, $file.$id, expiry);
|
||||
|
||||
await invalidate(Dependencies.FILE_TOKENS);
|
||||
addNotification({
|
||||
@@ -151,11 +148,7 @@
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.tokens.update(
|
||||
selectedFileToken.$id,
|
||||
fileToken.expire ? fileToken.expire : null,
|
||||
fileToken.$permissions
|
||||
);
|
||||
.tokens.update(selectedFileToken.$id, fileToken.expire ? fileToken.expire : null);
|
||||
await invalidate(Dependencies.FILE_TOKENS);
|
||||
addNotification({
|
||||
message: 'File token updated',
|
||||
@@ -173,23 +166,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getPermissionGroups(token: Models.ResourceToken): string {
|
||||
const map = {
|
||||
read: 'Read',
|
||||
update: 'Update',
|
||||
delete: 'Delete'
|
||||
};
|
||||
|
||||
const groups = token.$permissions.reduce((set, perm) => {
|
||||
const match = perm.match(/^(\w+)\(/);
|
||||
const key = match?.[1];
|
||||
if (key && map[key]) set.add(map[key]);
|
||||
return set;
|
||||
}, new Set<string>());
|
||||
|
||||
return [...groups].join(', ');
|
||||
}
|
||||
|
||||
function getExpiryDetails(token: Models.ResourceToken): {
|
||||
status: 'success' | 'warning' | 'error' | null;
|
||||
message: string | null;
|
||||
@@ -301,16 +277,6 @@
|
||||
? cleanFormattedDate(token.accessedAt, true)
|
||||
: 'Never'}</Table.Cell>
|
||||
|
||||
<Table.Cell column="permissions" {root}>
|
||||
{#if token.$permissions.length}
|
||||
<Typography.Text truncate slot="tooltip">
|
||||
{getPermissionGroups(token)}
|
||||
</Typography.Text>
|
||||
{:else}
|
||||
none
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
|
||||
<Table.Cell column="actions" {root}>
|
||||
<Layout.Stack alignItems="flex-end">
|
||||
<Menu>
|
||||
@@ -322,8 +288,8 @@
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Root noPadding>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => {
|
||||
toggle();
|
||||
on:click={(e) => {
|
||||
toggle(e);
|
||||
showCopyUrlModal = true;
|
||||
selectedFileToken = token;
|
||||
}}
|
||||
@@ -333,28 +299,18 @@
|
||||
</ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconPencil}
|
||||
on:click={() => {
|
||||
toggle();
|
||||
on:click={(e) => {
|
||||
toggle(e);
|
||||
showManageToken = true;
|
||||
selectedFileToken = token;
|
||||
}}>
|
||||
Edit expiry
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconKey}
|
||||
on:click={() => {
|
||||
toggle();
|
||||
showManageToken = true;
|
||||
tokenPermissionsMode = true;
|
||||
selectedFileToken = token;
|
||||
}}>
|
||||
Edit permissions
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
status="danger"
|
||||
leadingIcon={IconTrash}
|
||||
on:click={async (e) => {
|
||||
toggle();
|
||||
toggle(e);
|
||||
tokenDeleteMode = true;
|
||||
showManageToken = true;
|
||||
selectedFileToken = token;
|
||||
@@ -439,7 +395,6 @@
|
||||
bind:show={showManageToken}
|
||||
bind:isDelete={tokenDeleteMode}
|
||||
bind:fileToken={selectedFileToken}
|
||||
bind:isUpdatePermissions={tokenPermissionsMode}
|
||||
on:created={({ detail: expiry }) => createFileToken(expiry)}
|
||||
on:updated={({ detail: token }) => updateFileToken(token)}
|
||||
on:deleted={async () => await deleteFileToken(selectedFileToken)} />
|
||||
|
||||
+49
-86
@@ -1,79 +1,47 @@
|
||||
<script lang="ts">
|
||||
import { cachedJwts } from './store';
|
||||
import { page } from '$app/state';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Modal, Card, CopyInput } from '$lib/components';
|
||||
import { Button, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Alert, Layout, Tabs, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { page } from '$app/state';
|
||||
|
||||
export let show = false;
|
||||
export let file: Models.File;
|
||||
export let token: Models.ResourceToken;
|
||||
|
||||
let error = null;
|
||||
|
||||
let codeSnippets = {};
|
||||
let tabContents = {};
|
||||
let selectedTab = 'Preview';
|
||||
|
||||
async function getJwtForToken(): Promise<string> {
|
||||
// Return cached, if it's valid based on the token's expiration.
|
||||
if ($cachedJwts.has(token.$id)) {
|
||||
const cachedJwt = $cachedJwts.get(token.$id);
|
||||
const isExpired = !!token.expire && new Date(token.expire) < new Date();
|
||||
|
||||
if (!isExpired) return cachedJwt;
|
||||
}
|
||||
|
||||
const { jwt } = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.tokens.getJWT(token.$id);
|
||||
|
||||
// update the cached object!
|
||||
cachedJwts.update((map) => {
|
||||
map.set(token.$id, jwt);
|
||||
return map;
|
||||
});
|
||||
|
||||
return jwt;
|
||||
}
|
||||
|
||||
function copyPreviewWithToken(
|
||||
token: string,
|
||||
method: 'preview' | 'view' | 'download' = 'preview'
|
||||
) {
|
||||
error = false;
|
||||
|
||||
try {
|
||||
let url: string;
|
||||
if (method === 'view') {
|
||||
url = sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.storage.getFileView(file.bucketId, file.$id);
|
||||
} else if (method === 'download') {
|
||||
url = sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.storage.getFileDownload(file.bucketId, file.$id);
|
||||
} else {
|
||||
url = sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.storage.getFilePreview(file.bucketId, file.$id);
|
||||
}
|
||||
|
||||
url = `${url}&token=${token}`;
|
||||
|
||||
return url;
|
||||
} catch (ignore) {
|
||||
error = true;
|
||||
let url: string;
|
||||
if (method === 'view') {
|
||||
url = sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.storage.getFileView(file.bucketId, file.$id);
|
||||
} else if (method === 'download') {
|
||||
url = sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.storage.getFileDownload(file.bucketId, file.$id);
|
||||
} else {
|
||||
url = sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.storage.getFilePreview(file.bucketId, file.$id);
|
||||
}
|
||||
|
||||
url = `${url}&token=${token}`;
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
async function setCodeSnippets() {
|
||||
const jwt = await getJwtForToken();
|
||||
return {
|
||||
Preview: `${copyPreviewWithToken(jwt)}`,
|
||||
View: `${copyPreviewWithToken(jwt, 'view')}`,
|
||||
Download: `${copyPreviewWithToken(jwt, 'download')}`
|
||||
function setUrlSnippets() {
|
||||
tabContents = {
|
||||
Preview: `${copyPreviewWithToken(token.secret)}`,
|
||||
View: `${copyPreviewWithToken(token.secret, 'view')}`,
|
||||
Download: `${copyPreviewWithToken(token.secret, 'download')}`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -98,42 +66,37 @@
|
||||
}
|
||||
|
||||
$: if (show) {
|
||||
setCodeSnippets().then((snippets) => (codeSnippets = snippets));
|
||||
setUrlSnippets();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Copy File URL" bind:show hideFooter>
|
||||
{@const tokenUrl = tabContents[selectedTab]}
|
||||
<span slot="description"> Use the token-based URL below to access this file securely. </span>
|
||||
|
||||
{#if error}
|
||||
<Alert.Inline title="Error" status="error">
|
||||
Something went wrong, could not generate the URL.
|
||||
</Alert.Inline>
|
||||
{:else}
|
||||
{@const tokenUrl = codeSnippets[selectedTab]}
|
||||
<Layout.Stack gap="xl">
|
||||
<Tabs.Root let:root stretch>
|
||||
{#each ['Preview', 'View', 'Download'] as cat}
|
||||
<Tabs.Item.Button
|
||||
{root}
|
||||
on:click={() => (selectedTab = cat)}
|
||||
active={selectedTab === cat}>
|
||||
{cat}
|
||||
</Tabs.Item.Button>
|
||||
{/each}
|
||||
</Tabs.Root>
|
||||
<Layout.Stack gap="xl">
|
||||
<Tabs.Root let:root stretch>
|
||||
{#each ['Preview', 'View', 'Download'] as cat}
|
||||
<Tabs.Item.Button
|
||||
{root}
|
||||
on:click={() => (selectedTab = cat)}
|
||||
active={selectedTab === cat}>
|
||||
{cat}
|
||||
</Tabs.Item.Button>
|
||||
{/each}
|
||||
</Tabs.Root>
|
||||
|
||||
<Typography.Text variant="m-400">
|
||||
{getMoreInfo()}
|
||||
</Typography.Text>
|
||||
{#if !token.expire}
|
||||
<Alert.Inline title="No expiration date" status="warning">
|
||||
This token doesn't expire. Be cautious when sharing links.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<CopyInput value={tokenUrl} />
|
||||
</Layout.Stack>
|
||||
|
||||
<Typography.Text variant="m-400">
|
||||
{getMoreInfo()}
|
||||
</Typography.Text>
|
||||
{#if !token.expire}
|
||||
<Alert.Inline title="No expiration date" status="warning">
|
||||
This token doesn't expire. Be cautious when sharing links.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<CopyInput value={tokenUrl} />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (show = false)}>Close</Button>
|
||||
</svelte:fragment>
|
||||
|
||||
+15
-31
@@ -13,27 +13,24 @@
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { Link } from '@appwrite.io/pink-svelte';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import { Confirm, ExpirationInput, Modal } from '$lib/components';
|
||||
|
||||
export let show = false;
|
||||
export let isDelete = false;
|
||||
export let isUpdatePermissions = false;
|
||||
export let fileToken: Models.ResourceToken | null = null;
|
||||
|
||||
$: expire = fileToken?.expire ?? null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function close() {
|
||||
function close(): void {
|
||||
show = false;
|
||||
expire = null;
|
||||
isDelete = false;
|
||||
fileToken = null;
|
||||
isUpdatePermissions = false;
|
||||
}
|
||||
|
||||
function handleFileToken() {
|
||||
function handleFileToken(): void {
|
||||
if (isDelete) {
|
||||
dispatch('deleted');
|
||||
} else {
|
||||
@@ -45,6 +42,10 @@
|
||||
close();
|
||||
}
|
||||
|
||||
function getModalTitle(): string {
|
||||
return `${fileToken ? 'Edit' : 'Create'} token${fileToken ? ' expiry' : ''}`;
|
||||
}
|
||||
|
||||
$: if (!show) {
|
||||
close();
|
||||
}
|
||||
@@ -68,41 +69,24 @@
|
||||
</p>
|
||||
</Confirm>
|
||||
{:else}
|
||||
<Modal
|
||||
bind:show
|
||||
size="s"
|
||||
onSubmit={handleFileToken}
|
||||
title={`${fileToken ? 'Edit' : 'Create'} file token`}>
|
||||
<Modal bind:show size="s" onSubmit={handleFileToken} title={getModalTitle()}>
|
||||
<!-- TODO: docs link needed-->
|
||||
<svelte:fragment slot="description">
|
||||
{#if fileToken}
|
||||
{#if isUpdatePermissions}
|
||||
Choose who can access or update your file using this token.
|
||||
<Link.Anchor
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://appwrite.io/docs/advanced/platform/permissions">
|
||||
Learn more
|
||||
</Link.Anchor>.
|
||||
{:else}
|
||||
{@const formattedDate = cleanFormattedDate(fileToken.$createdAt)}
|
||||
Edit the expiry of the file token created on <b>{formattedDate}</b>.
|
||||
{/if}
|
||||
{@const formattedDate = cleanFormattedDate(fileToken.$createdAt)}
|
||||
Edit the expiry of the file token created on <b>{formattedDate}</b>.
|
||||
{:else}
|
||||
Create a file token to grant access to a file.
|
||||
<!-- TODO: @itznotabug, @adityaoberai need documentations link -->
|
||||
<Link.Anchor href="https://appwrite.com/docs/">Learn more</Link.Anchor>.
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
{#if isUpdatePermissions}
|
||||
<Permissions bind:permissions={fileToken.$permissions} hideOnClick />
|
||||
{:else}
|
||||
<ExpirationInput
|
||||
bind:value={expire}
|
||||
resourceType="token"
|
||||
selectorLabel="Expiry"
|
||||
dateSelectorLabel="Expiry date" />
|
||||
{/if}
|
||||
<ExpirationInput
|
||||
bind:value={expire}
|
||||
resourceType="token"
|
||||
selectorLabel="Expiry"
|
||||
dateSelectorLabel="Expiry date" />
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={close}>Cancel</Button>
|
||||
|
||||
-6
@@ -7,15 +7,9 @@ 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<Map<string, string>>(new Map<string, string>());
|
||||
|
||||
export const columns = writable<Column[]>([
|
||||
{ id: 'created', title: 'Created', type: 'datetime', width: 200 },
|
||||
{ id: 'expire', 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 }
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user