mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' of github.com:appwrite/console into fix-table-update
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
VITE_APPWRITE_ENDPOINT=
|
||||
VITE_APPWRITE_GROWTH_ENDPOINT=
|
||||
VITE_GA_PROJECT=
|
||||
VITE_SENTRY_DSN=
|
||||
VITE_CONSOLE_MODE=self-hosted
|
||||
Generated
+7
-7
@@ -9,7 +9,7 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@analytics/google-analytics": "^1.0.5",
|
||||
"@aw-labs/appwrite-console": "^13.0.0",
|
||||
"@aw-labs/appwrite-console": "^13.1.0",
|
||||
"@aw-labs/icons": "0.0.0-77",
|
||||
"@aw-labs/ui": "0.0.0-77",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
@@ -145,9 +145,9 @@
|
||||
"integrity": "sha512-1Yw7u/COtxx06BfwlI+kVhsa/upKYzmCNrT4c8QDeCY2KMYlnijkUjtHiPU08HxyTIVB5j6d75O0YWVIHwQS8g=="
|
||||
},
|
||||
"node_modules/@aw-labs/appwrite-console": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.0.0.tgz",
|
||||
"integrity": "sha512-a5illDoFZAhgLP8Ae+KHPzBIUozKXzjyHUw6jTwNPZaL+b2RnTe3oRLZU8l8Kt5pB5m7NZx6kAcZm+NMf8NeCw==",
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.1.0.tgz",
|
||||
"integrity": "sha512-1FyKke5b3vx+D3VfNYd3YnFhzUOlsj9bD+KTRLWA4WRItb55thFVZJMWQQ4XnZtDiLPuJAn2XjbMSfMr+9XYwg==",
|
||||
"dependencies": {
|
||||
"cross-fetch": "3.1.5",
|
||||
"isomorphic-form-data": "2.0.0"
|
||||
@@ -7740,9 +7740,9 @@
|
||||
"integrity": "sha512-1Yw7u/COtxx06BfwlI+kVhsa/upKYzmCNrT4c8QDeCY2KMYlnijkUjtHiPU08HxyTIVB5j6d75O0YWVIHwQS8g=="
|
||||
},
|
||||
"@aw-labs/appwrite-console": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.0.0.tgz",
|
||||
"integrity": "sha512-a5illDoFZAhgLP8Ae+KHPzBIUozKXzjyHUw6jTwNPZaL+b2RnTe3oRLZU8l8Kt5pB5m7NZx6kAcZm+NMf8NeCw==",
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.1.0.tgz",
|
||||
"integrity": "sha512-1FyKke5b3vx+D3VfNYd3YnFhzUOlsj9bD+KTRLWA4WRItb55thFVZJMWQQ4XnZtDiLPuJAn2XjbMSfMr+9XYwg==",
|
||||
"requires": {
|
||||
"cross-fetch": "3.1.5",
|
||||
"isomorphic-form-data": "2.0.0"
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
"dependencies": {
|
||||
"@analytics/google-analytics": "^1.0.5",
|
||||
"analytics": "^0.8.1",
|
||||
"@aw-labs/appwrite-console": "^13.0.0",
|
||||
"@aw-labs/appwrite-console": "^13.1.0",
|
||||
"@aw-labs/icons": "0.0.0-77",
|
||||
"@aw-labs/ui": "0.0.0-77",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
|
||||
@@ -3,7 +3,7 @@ import googleAnalytics from '@analytics/google-analytics';
|
||||
import { get } from 'svelte/store';
|
||||
import { page } from '$app/stores';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { Mode } from '$lib/constants';
|
||||
import { growthEndpoint, Mode } from '$lib/constants';
|
||||
|
||||
const analytics = Analytics({
|
||||
app: 'appwrite',
|
||||
@@ -40,7 +40,7 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo
|
||||
email = userStore.email;
|
||||
name = userStore.name;
|
||||
}
|
||||
fetch('https://growth.appwrite.io/v1/analytics', {
|
||||
fetch(`${growthEndpoint}/analytics`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
import { copy } from '$lib/helpers/copy';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
export let value: string;
|
||||
@@ -9,27 +10,28 @@
|
||||
|
||||
let content = 'Click to copy';
|
||||
|
||||
const copy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
async function handleClick() {
|
||||
const success = await copy(value);
|
||||
|
||||
if (success) {
|
||||
content = 'Copied';
|
||||
} catch (error) {
|
||||
} else {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
message: 'Unable to copy to clipboard',
|
||||
type: 'error'
|
||||
});
|
||||
} finally {
|
||||
if (event) {
|
||||
trackEvent('click_id_tag', {
|
||||
name: event
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (event) {
|
||||
trackEvent('click_id_tag', {
|
||||
name: event
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<span
|
||||
on:click|preventDefault={copy}
|
||||
on:click|preventDefault={handleClick}
|
||||
on:keyup={clickOnEnter}
|
||||
on:mouseenter={() => setTimeout(() => (content = 'Click to copy'))}
|
||||
use:tooltip={{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { copy } from '$lib/helpers/copy';
|
||||
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
@@ -9,13 +10,14 @@
|
||||
|
||||
let content = 'Click to copy';
|
||||
|
||||
const copy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
const handleCopy = async () => {
|
||||
const success = await copy(value);
|
||||
|
||||
if (success) {
|
||||
content = 'Copied';
|
||||
} catch (error) {
|
||||
} else {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
message: 'Unable to copy to clipboard',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
@@ -31,7 +33,7 @@
|
||||
type="button"
|
||||
class="input-button"
|
||||
aria-label="Click to copy."
|
||||
on:click={copy}
|
||||
on:click={handleCopy}
|
||||
on:mouseenter={() => setTimeout(() => (content = 'Click to copy'))}
|
||||
use:tooltip={{
|
||||
content,
|
||||
|
||||
@@ -53,59 +53,61 @@
|
||||
})
|
||||
.finally(() => {
|
||||
tick().then(() => {
|
||||
instance.setContent(content.innerHTML);
|
||||
instance.setContent(content);
|
||||
});
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{role}
|
||||
</div>
|
||||
<div class="u-hide" bind:this={content}>
|
||||
{#if data}
|
||||
{@const isUser = role.startsWith('user')}
|
||||
{@const isTeam = role.startsWith('team')}
|
||||
{@const isAnonymous = !data.email && !data.phone}
|
||||
<div class="user-profile">
|
||||
{#if isAnonymous}
|
||||
<div class="avatar is-size-small ">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
{:else if data.name}
|
||||
<AvatarInitials name={data.name} size={40} />
|
||||
{:else}
|
||||
<div class="avatar is-size-small ">
|
||||
<span class="icon-minus-sm" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
<span class="user-profile-info is-only-desktop">
|
||||
<span class="name">
|
||||
{data.name
|
||||
? data.name
|
||||
: data?.email
|
||||
? data?.email
|
||||
: data?.phone
|
||||
? data?.phone
|
||||
: '-'}
|
||||
</span>
|
||||
<Output value={data.$id}>{role}</Output>
|
||||
</span>
|
||||
{#if (isUser && (data?.email || data?.phone)) || isTeam}
|
||||
<span class="user-profile-sep" />
|
||||
|
||||
<span class="user-profile-empty-column" />
|
||||
<div class="u-hide">
|
||||
<div bind:this={content}>
|
||||
{#if data}
|
||||
{@const isUser = role.startsWith('user')}
|
||||
{@const isTeam = role.startsWith('team')}
|
||||
{@const isAnonymous = !data.email && !data.phone}
|
||||
<div class="user-profile">
|
||||
{#if isAnonymous}
|
||||
<div class="avatar is-size-small ">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
{:else if data.name}
|
||||
<AvatarInitials name={data.name} size={40} />
|
||||
{:else}
|
||||
<div class="avatar is-size-small ">
|
||||
<span class="icon-minus-sm" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
<span class="user-profile-info is-only-desktop">
|
||||
{#if isUser}
|
||||
<p class="text u-x-small">{data?.email}</p>
|
||||
<p class="text u-x-small">{data?.phone}</p>
|
||||
{:else if isTeam}
|
||||
<p class="text u-x-small">Members: {data?.total}</p>
|
||||
{/if}
|
||||
<span class="name">
|
||||
{data.name
|
||||
? data.name
|
||||
: data?.email
|
||||
? data?.email
|
||||
: data?.phone
|
||||
? data?.phone
|
||||
: '-'}
|
||||
</span>
|
||||
<Output value={data.$id}>{role}</Output>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
Not found.
|
||||
{/if}
|
||||
{#if (isUser && (data?.email || data?.phone)) || isTeam}
|
||||
<span class="user-profile-sep" />
|
||||
|
||||
<span class="user-profile-empty-column" />
|
||||
<span class="user-profile-info is-only-desktop">
|
||||
{#if isUser}
|
||||
<p class="text u-x-small">{data?.email}</p>
|
||||
<p class="text u-x-small">{data?.phone}</p>
|
||||
{:else if isTeam}
|
||||
<p class="text u-x-small">Members: {data?.total}</p>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
Not found.
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
import { Pill } from '$lib/elements';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { Avatar } from '$lib/components';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
async function removeFile($id: string, bucketId: string) {
|
||||
const file = await sdkForProject.storage.getFile(bucketId, $id);
|
||||
uploader.removeFile(file);
|
||||
}
|
||||
let hovering = false;
|
||||
|
||||
const getPreview = (fileId: string, bucketId: string) =>
|
||||
sdkForProject.storage.getFilePreview(bucketId, fileId, 32, 32).toString() + '&mode=admin';
|
||||
@@ -28,7 +27,7 @@
|
||||
<span class="icon-cheveron-up" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
on:click={() => uploader.close()}
|
||||
on:click={() => uploader.reset()}
|
||||
class="icon-button"
|
||||
aria-label="close upload box">
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
@@ -37,8 +36,12 @@
|
||||
<div class="upload-box-content" class:is-open={!$uploader.isCollapsed}>
|
||||
<ul class="upload-box-list">
|
||||
{#each $uploader.files as file}
|
||||
{@const progress = Math.round(file.progress)}
|
||||
|
||||
{#if file.completed || file.progress === 100}
|
||||
<li class="upload-box-item">
|
||||
<a
|
||||
class="upload-box-item"
|
||||
href={`${base}/console/project-${$page.params.project}/storage/bucket-${$page.params.bucket}/file-${file.$id}`}>
|
||||
<div class="u-margin-inline-end-16">
|
||||
<Avatar
|
||||
size={32}
|
||||
@@ -46,50 +49,51 @@
|
||||
name={file.name} />
|
||||
</div>
|
||||
|
||||
<label for={file.name} class="file-name">{file.name}</label>
|
||||
<span class="icon-check" />
|
||||
</li>
|
||||
<label for={file.name} class="file-name u-trim">{file.name}</label>
|
||||
|
||||
<button
|
||||
class="icon-button"
|
||||
aria-label="Failed"
|
||||
on:mouseenter={() => (hovering = true)}
|
||||
on:mouseleave={() => (hovering = false)}
|
||||
on:click|preventDefault={() => uploader.removeFromQueue(file.$id)}>
|
||||
<span class:icon-check={!hovering} class:icon-x={hovering} />
|
||||
</button>
|
||||
</a>
|
||||
{:else if file.failed}
|
||||
<li class="upload-box-item">
|
||||
<div class="upload-image u-margin-inline-end-16">
|
||||
<div
|
||||
class="progress"
|
||||
role="progressbar"
|
||||
aria-valuenow={file.progress}
|
||||
aria-valuenow={progress}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100} />
|
||||
<span class="icon">{file.progress}%</span>
|
||||
<span class="icon">{progress}%</span>
|
||||
</div>
|
||||
<label for={file.name} class="file-name">{file.name}</label>
|
||||
<label for={file.name} class="file-name u-trim">{file.name}</label>
|
||||
<Pill danger>Failed</Pill>
|
||||
<button
|
||||
class="icon-button"
|
||||
aria-label="Failed"
|
||||
on:click={() => removeFile(file.$id, file.bucketId)}>
|
||||
on:click|preventDefault={() => uploader.removeFromQueue(file.$id)}>
|
||||
<span class="icon-x" />
|
||||
</button>
|
||||
</li>
|
||||
{:else if file.cancelled}
|
||||
cancelled?
|
||||
{:else}
|
||||
<li class="upload-box-item">
|
||||
<div class="upload-image u-margin-inline-end-16">
|
||||
<div
|
||||
class="progress"
|
||||
role="progressbar"
|
||||
aria-valuenow={file.progress}
|
||||
style={`--progress-value:${progress}`}
|
||||
aria-valuenow={progress}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100} />
|
||||
<span class="icon">{file.progress}%</span>
|
||||
<span class="icon">{progress}%</span>
|
||||
</div>
|
||||
<label for={file.name} class="file-name">{file.name}</label>
|
||||
<label for={file.name} class="file-name u-trim">{file.name}</label>
|
||||
<Pill warning>Pending</Pill>
|
||||
<button
|
||||
class="icon-button"
|
||||
aria-label="Pending"
|
||||
on:click={() => removeFile(file.$id, file.bucketId)}>
|
||||
<span class="icon-x" />
|
||||
</button>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -3,10 +3,12 @@ export const CARD_LIMIT = 6; // default card limit
|
||||
export const INTERVAL = 5 * 60000; // default interval to check for feedback
|
||||
|
||||
export enum Mode {
|
||||
CLOUD = 'mode:cloud',
|
||||
SELF_HOSTED = 'mode:self-hosted'
|
||||
CLOUD = 'cloud',
|
||||
SELF_HOSTED = 'self-hosted'
|
||||
}
|
||||
|
||||
export const growthEndpoint = import.meta.env.VITE_APPWRITE_GROWTH_ENDPOINT;
|
||||
|
||||
export enum Dependencies {
|
||||
ORGANIZATION = 'dependency:organization',
|
||||
PROJECT = 'dependency:project',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Trim } from '$lib/components';
|
||||
import { Button } from '.';
|
||||
|
||||
export let label: string = null;
|
||||
@@ -47,20 +48,23 @@
|
||||
on:dragover|preventDefault={dragOverHandler}
|
||||
on:dragleave|preventDefault={() => (hovering = false)}>
|
||||
<div class="u-flex u-main-center u-cross-center u-gap-32">
|
||||
<div class="avatar is-size-large">
|
||||
<div class="avatar is-size-large u-min-width-0">
|
||||
<span class="icon-folder" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="u-grid u-gap-16">
|
||||
<div class="u-grid u-gap-16 u-min-width-0">
|
||||
<p>Drag and drop files here to upload</p>
|
||||
<div class="u-flex u-gap-8 ">
|
||||
<div class="u-flex u-gap-8 u-min-width-0">
|
||||
<Button secondary on:click={() => input.click()}>
|
||||
<span class="icon-upload" aria-hidden="true" />
|
||||
<span class="text">Choose File</span>
|
||||
</Button>
|
||||
|
||||
{#if files?.length}
|
||||
<div class="u-flex ">
|
||||
{files.item(0).name}
|
||||
{@const fileName = files.item(0).name.split('.')}
|
||||
<div class="u-flex u-cross-center u-min-width-0">
|
||||
<Trim>{fileName[0]}</Trim>
|
||||
<span class="u-min-width-0 u-flex-shrink-0 u-margin-inline-end-16"
|
||||
>.{fileName[1]}</span>
|
||||
<button
|
||||
on:click={() => (files = null)}
|
||||
type="button"
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
export let autofocus = false;
|
||||
export let disabled = false;
|
||||
export let readonly = false;
|
||||
export let required = false;
|
||||
|
||||
let value = '';
|
||||
let element: HTMLInputElement;
|
||||
let hiddenEl: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
onMount(() => {
|
||||
@@ -54,11 +56,11 @@
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (element.validity.valueMissing) {
|
||||
if (hiddenEl.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
error = element.validationMessage;
|
||||
error = hiddenEl.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
@@ -67,6 +69,12 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<input
|
||||
class="u-hide"
|
||||
bind:this={hiddenEl}
|
||||
value={tags.join(',')}
|
||||
{required}
|
||||
on:invalid={handleInvalid} />
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<div class="input-text-wrapper">
|
||||
<div class="tags-input">
|
||||
@@ -98,8 +106,7 @@
|
||||
bind:value
|
||||
bind:this={element}
|
||||
on:keydown={handleInput}
|
||||
on:blur={addValue}
|
||||
on:invalid={handleInvalid} />
|
||||
on:blur={addValue} />
|
||||
</div>
|
||||
</div>
|
||||
{#if error}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
async function securedCopy(value: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function unsecuredCopy(value: string) {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = value;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
let success = true;
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
} catch {
|
||||
success = false;
|
||||
} finally {
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
export async function copy(value: string) {
|
||||
// securedCopy works only in HTTPS environment.
|
||||
// unsecuredCopy works in HTTP and only runs if securedCopy fails.
|
||||
const success = (await securedCopy(value)) || unsecuredCopy(value);
|
||||
|
||||
return success;
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
import LoginDark from '$lib/images/login/login-dark-mode.svg';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { base } from '$app/paths';
|
||||
import { user } from '$lib/stores/user';
|
||||
|
||||
const technologies = [
|
||||
'js',
|
||||
@@ -25,7 +26,7 @@
|
||||
<div
|
||||
class="container u-margin-block-start-20"
|
||||
style="--p-container-max-size: var(--container-size-medium);">
|
||||
<a href="/">
|
||||
<a href={user ? '/console' : '/'}>
|
||||
<img src={AppwriteLogo} width="196" height="47" class="u-block" alt="Appwrite" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { growthEndpoint } from '$lib/constants';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export type AppStore = {
|
||||
@@ -59,7 +60,7 @@ function createFeedbackStore() {
|
||||
email?: string,
|
||||
value?: number
|
||||
) => {
|
||||
const response = await fetch('https://growth.appwrite.io/v1/feedback', {
|
||||
const response = await fetch(`${growthEndpoint}/feedback`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
@@ -54,6 +54,11 @@ function createServices() {
|
||||
label: 'Users',
|
||||
method: 'users',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'GraphQL',
|
||||
method: 'graphql',
|
||||
value: null
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -107,6 +112,11 @@ function createServices() {
|
||||
label: 'Users',
|
||||
method: 'users',
|
||||
value: project.serviceStatusForUsers
|
||||
},
|
||||
{
|
||||
label: 'GraphQL',
|
||||
method: 'graphql',
|
||||
value: project.serviceStatusForGraphql
|
||||
}
|
||||
];
|
||||
set({ list });
|
||||
|
||||
+32
-21
@@ -1,5 +1,6 @@
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable } from 'svelte/store';
|
||||
import { sdkForProject } from './sdk';
|
||||
|
||||
type UploaderFile = {
|
||||
$id: string;
|
||||
@@ -8,7 +9,6 @@ type UploaderFile = {
|
||||
progress: number;
|
||||
completed: boolean;
|
||||
failed: boolean;
|
||||
cancelled: boolean;
|
||||
error?: string;
|
||||
};
|
||||
export type Uploader = {
|
||||
@@ -42,7 +42,6 @@ const createUploader = () => {
|
||||
close: () =>
|
||||
update((n) => {
|
||||
n.isOpen = !n.isOpen;
|
||||
|
||||
return n;
|
||||
}),
|
||||
toggle: () =>
|
||||
@@ -57,22 +56,43 @@ const createUploader = () => {
|
||||
isCollapsed: false,
|
||||
files: []
|
||||
}),
|
||||
addFile: async (file: Models.File, isOpen = true, isCollapsed = false) => {
|
||||
uploadFile: async (bucketId: string, id: string, file: File, permissions: string[]) => {
|
||||
const newFile: UploaderFile = {
|
||||
$id: file.$id,
|
||||
bucketId: file.bucketId,
|
||||
$id: id,
|
||||
bucketId: bucketId,
|
||||
name: file.name,
|
||||
progress: calculateProgress(file),
|
||||
completed: calculateProgress(file) === 100 ? true : false,
|
||||
failed: false,
|
||||
cancelled: false
|
||||
progress: 0,
|
||||
completed: false,
|
||||
failed: false
|
||||
};
|
||||
return update((n) => {
|
||||
n.isOpen = isOpen;
|
||||
n.isCollapsed = isCollapsed;
|
||||
update((n) => {
|
||||
n.isOpen = true;
|
||||
n.isCollapsed = false;
|
||||
n.files.unshift(newFile);
|
||||
return n;
|
||||
});
|
||||
const uploadedFile = await sdkForProject.storage.createFile(
|
||||
bucketId,
|
||||
id ?? 'unique()',
|
||||
file,
|
||||
permissions,
|
||||
(p) => {
|
||||
newFile.$id = p.$id;
|
||||
newFile.progress = p.progress;
|
||||
newFile.completed = p.progress === 100 ? true : false;
|
||||
updateFile(p.$id, newFile);
|
||||
}
|
||||
);
|
||||
newFile.$id = uploadedFile.$id;
|
||||
newFile.progress = 100;
|
||||
newFile.completed = true;
|
||||
updateFile(newFile.$id, newFile);
|
||||
},
|
||||
removeFromQueue: (id: string) => {
|
||||
update((n) => {
|
||||
n.files = n.files.filter((f) => f.$id !== id);
|
||||
return n;
|
||||
});
|
||||
},
|
||||
removeFile: async (file: Models.File) => {
|
||||
if (file.chunksTotal === file.chunksUploaded) {
|
||||
@@ -81,18 +101,9 @@ const createUploader = () => {
|
||||
|
||||
return n;
|
||||
});
|
||||
} else {
|
||||
if (confirm('Are you sure you want to cancel the upload?')) {
|
||||
updateFile(file.$id, { cancelled: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const uploader = createUploader();
|
||||
|
||||
function calculateProgress(file: Models.File) {
|
||||
const progress = file.chunksUploaded / file.chunksTotal;
|
||||
return Math.round(progress * 100);
|
||||
}
|
||||
|
||||
@@ -49,11 +49,16 @@
|
||||
if (!$page.url.pathname.startsWith('/auth')) {
|
||||
const acceptedRoutes = ['/login', '/register', '/recover', '/invite'];
|
||||
if ($user) {
|
||||
if (!$page.url.pathname.startsWith('/console')) {
|
||||
if (
|
||||
!$page.url.pathname.startsWith('/console') &&
|
||||
!$page.url.pathname.startsWith('/invite')
|
||||
) {
|
||||
console.log('test');
|
||||
await goto(`${base}/console`, {
|
||||
replaceState: true
|
||||
});
|
||||
}
|
||||
loading.set(false);
|
||||
} else {
|
||||
if (acceptedRoutes.includes($page.url.pathname)) {
|
||||
await goto(`${base}${$page.url.pathname}${$page.url.search}`);
|
||||
|
||||
@@ -8,9 +8,6 @@ import type { LayoutLoad } from './$types';
|
||||
export const ssr = false;
|
||||
|
||||
export const load: LayoutLoad = async ({ depends, url }) => {
|
||||
if (url.pathname.startsWith('/auth')) {
|
||||
return;
|
||||
}
|
||||
depends(Dependencies.ACCOUNT);
|
||||
try {
|
||||
const account = await sdkForConsole.account.get();
|
||||
@@ -20,7 +17,15 @@ export const load: LayoutLoad = async ({ depends, url }) => {
|
||||
organizations: sdkForConsole.teams.list()
|
||||
};
|
||||
} catch (error) {
|
||||
const acceptedRoutes = ['/login', '/register', '/recover', '/invite'];
|
||||
const acceptedRoutes = [
|
||||
'/login',
|
||||
'/register',
|
||||
'/recover',
|
||||
'/invite',
|
||||
'/auth/magic-url',
|
||||
'/auth/oauth2/success',
|
||||
'/auth/oauth2/failure'
|
||||
];
|
||||
|
||||
if (!acceptedRoutes.includes(url.pathname)) {
|
||||
throw redirect(303, '/login');
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
|
||||
export let showDelete = false;
|
||||
|
||||
const deleteAccount = async () => {
|
||||
try {
|
||||
await sdkForConsole.users.delete($user.$id);
|
||||
await sdkForConsole.account.updateStatus();
|
||||
showDelete = false;
|
||||
invalidate(Dependencies.ACCOUNT);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Account was deleted `
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Pill } from '$lib/elements/';
|
||||
import { Button, InputNumber, InputSelect } from '$lib/elements/forms';
|
||||
import { Button, InputNumber, InputSelect, Form } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { project } from '../../store';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -20,6 +20,7 @@
|
||||
let timeInSec = time;
|
||||
let period = writable('s');
|
||||
let btnActive = false;
|
||||
let maxSessions = $project.authSessionsLimit;
|
||||
|
||||
let options = [
|
||||
{ label: 'days', value: 'd' },
|
||||
@@ -81,6 +82,22 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateSessionsLimit() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateAuthSessionsLimit(projectId, maxSessions);
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Sessions limit has been updated'
|
||||
});
|
||||
trackEvent('submit_sessions_limit_update');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
period.subscribe((p) => {
|
||||
if (p === 'd') {
|
||||
@@ -198,4 +215,20 @@
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Form on:submit={updateSessionsLimit}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="6">Session Limit</Heading>
|
||||
<p>Maximum number of active sessions allowed per user.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputNumber id="max-session" label="Limit" bind:value={maxSessions} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={maxSessions === $project.authSessionsLimit} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
</Container>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.emailVerification ? 'verified' : 'unverified'
|
||||
$user.emailVerification ? 'unverified' : 'verified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -38,7 +38,7 @@
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.phoneVerification ? 'verified' : 'unverified'
|
||||
$user.phoneVerification ? 'unverified' : 'verified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -56,7 +56,7 @@
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.status ? 'unblocked' : 'blocked'
|
||||
$user.status ? 'blocked' : 'unblocked'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
+4
-2
@@ -63,9 +63,11 @@
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText onlyDesktop title="Type">{attribute.type}</TableCellText>
|
||||
<TableCellText onlyDesktop title="Type">
|
||||
{`${attribute.type}${attribute.array ? '[]' : ''}`}
|
||||
</TableCellText>
|
||||
<TableCellText onlyDesktop title="Default Value">
|
||||
{attribute.default ? attribute.default : '-'}
|
||||
{attribute.default !== null ? attribute.default : '-'}
|
||||
</TableCellText>
|
||||
<TableCell showOverflow>
|
||||
<DropList
|
||||
|
||||
+3
-1
@@ -40,6 +40,7 @@
|
||||
default: data.default
|
||||
} = selectedAttribute);
|
||||
}
|
||||
|
||||
$: if (data.required || data.array) {
|
||||
data.default = null;
|
||||
}
|
||||
@@ -50,7 +51,8 @@
|
||||
label="Elements"
|
||||
bind:tags={data.elements}
|
||||
placeholder="Add elements here"
|
||||
readonly={!!selectedAttribute} />
|
||||
readonly={!!selectedAttribute}
|
||||
required />
|
||||
<InputSelect
|
||||
id="default"
|
||||
label="Default value"
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
data.required,
|
||||
data.min,
|
||||
data.max,
|
||||
data.default ? data.default : undefined,
|
||||
typeof data.default === 'number' ? data.default : undefined,
|
||||
data.array
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
data.required,
|
||||
data.min,
|
||||
data.max,
|
||||
data.default ? data.default : undefined,
|
||||
typeof data.default === 'number' ? data.default : undefined,
|
||||
data.array
|
||||
);
|
||||
}
|
||||
|
||||
+10
-253
@@ -1,262 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid, Box, Heading } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Button, InputText, InputSwitch, Helper } from '$lib/elements/forms';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import { collection } from '../store';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import Delete from './deleteCollection.svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let showDelete = false;
|
||||
let showError: false | 'name' | 'size' = false,
|
||||
errorMessage = 'Something went wrong',
|
||||
errorType: 'error' | 'warning' | 'success' = 'error';
|
||||
let enabled: boolean = null,
|
||||
collectionName: string = null,
|
||||
collectionDocumentSecurity: boolean = null,
|
||||
collectionPermissions: string[] = null,
|
||||
arePermsDisabled = true;
|
||||
|
||||
onMount(() => {
|
||||
enabled ??= $collection.enabled;
|
||||
collectionName ??= $collection.name;
|
||||
collectionPermissions ??= $collection.$permissions;
|
||||
collectionDocumentSecurity ??= $collection.documentSecurity;
|
||||
});
|
||||
|
||||
$: if (
|
||||
collectionPermissions &&
|
||||
symmetricDifference(collectionPermissions, $collection.$permissions).length
|
||||
) {
|
||||
arePermsDisabled = false;
|
||||
} else arePermsDisabled = true;
|
||||
|
||||
function addError(location: typeof showError, message: string, type: typeof errorType) {
|
||||
showError = location;
|
||||
errorMessage = message;
|
||||
errorType = type;
|
||||
}
|
||||
|
||||
async function toggleCollection() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
$collection.$permissions,
|
||||
$collection.documentSecurity,
|
||||
enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
addNotification({
|
||||
message: `${$collection.name} has been updated`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_enabled');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
collectionName,
|
||||
$collection.$permissions,
|
||||
$collection.documentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
showError = false;
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_name');
|
||||
} catch (error) {
|
||||
addError('name', error.message, 'error');
|
||||
}
|
||||
}
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
collectionPermissions,
|
||||
$collection.documentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
arePermsDisabled = true;
|
||||
addNotification({
|
||||
message: 'Permissions have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_permissions');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
$collection.$permissions,
|
||||
collectionDocumentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
arePermsDisabled = true;
|
||||
addNotification({
|
||||
message: 'Security has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_security');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
import DangerZone from './dangerZone.svelte';
|
||||
import UpdateName from './updateName.svelte';
|
||||
import UpdatePermissions from './updatePermissions.svelte';
|
||||
import UpdateSecurity from './updateSecurity.svelte';
|
||||
import UpdateStatus from './updateStatus.svelte';
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if $collection}
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">{$collection.name}</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputSwitch
|
||||
id="toggle"
|
||||
label={enabled ? 'Enabled' : 'Disabled'}
|
||||
bind:value={enabled} />
|
||||
</ul>
|
||||
<div>
|
||||
<p>Created: {toLocaleDateTime($collection.$createdAt)}</p>
|
||||
<p>Last Updated: {toLocaleDateTime($collection.$updatedAt)}</p>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={enabled === $collection.enabled} on:click={toggleCollection}>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Name</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={collectionName} />
|
||||
{#if showError === 'name'}
|
||||
<Helper type={errorType}>{errorMessage}</Helper>
|
||||
{/if}
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={collectionName === $collection.name || !collectionName}
|
||||
on:click={updateName}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Permissions</Heading>
|
||||
<p class="text">
|
||||
Choose who can access your collection and documents. For more information, check out
|
||||
the <a
|
||||
href="https://appwrite.io/docs/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">Permissions Guide</a> in our documentation.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if collectionPermissions}
|
||||
<Permissions bind:permissions={collectionPermissions} withCreate />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={arePermsDisabled} on:click={updatePermissions}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Document Security</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputSwitch
|
||||
bind:value={collectionDocumentSecurity}
|
||||
id="security"
|
||||
label="Document Security" />
|
||||
</FormList>
|
||||
<p class="text">
|
||||
When document security is enabled, users will be able to access documents for
|
||||
which they have been granted <b>either Document or Collection permissions</b>.
|
||||
</p>
|
||||
<p class="text">
|
||||
If document security is disabled, users can access documents <b
|
||||
>only if they have Collection permissions</b
|
||||
>. Document permissions will be ignored.
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={collectionDocumentSecurity === $collection.documentSecurity}
|
||||
on:click={updateSecurity}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid danger>
|
||||
<Heading tag="h6" size="7">Delete Collection</Heading>
|
||||
<p>
|
||||
The collection will be permanently deleted, including all the documents within it.
|
||||
This action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$collection.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last Updated: {toLocaleDateTime($collection.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<UpdateStatus />
|
||||
<UpdateName />
|
||||
<UpdatePermissions />
|
||||
<UpdateSecurity />
|
||||
<DangerZone />
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { Box, CardGrid, Heading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { collection } from '../store';
|
||||
import Delete from './deleteCollection.svelte';
|
||||
|
||||
let showDelete = false;
|
||||
</script>
|
||||
|
||||
<CardGrid danger>
|
||||
<Heading tag="h6" size="7">Delete Collection</Heading>
|
||||
<p>
|
||||
The collection will be permanently deleted, including all the documents within it. This
|
||||
action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$collection.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last Updated: {toLocaleDateTime($collection.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { collection } from '../store';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let collectionName: string = null;
|
||||
|
||||
onMount(() => {
|
||||
collectionName ??= $collection.name;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
collectionName,
|
||||
$collection.$permissions,
|
||||
$collection.documentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_name');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Name</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={collectionName} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={collectionName === $collection.name || !collectionName} submit
|
||||
>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { collection } from '../store';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let arePermsDisabled = true;
|
||||
|
||||
let collectionPermissions: string[] = null;
|
||||
|
||||
onMount(() => {
|
||||
collectionPermissions ??= $collection.$permissions;
|
||||
});
|
||||
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
collectionPermissions,
|
||||
$collection.documentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
arePermsDisabled = true;
|
||||
addNotification({
|
||||
message: 'Permissions have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_permissions');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: if (
|
||||
collectionPermissions &&
|
||||
symmetricDifference(collectionPermissions, $collection.$permissions).length
|
||||
) {
|
||||
arePermsDisabled = false;
|
||||
} else arePermsDisabled = true;
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Permissions</Heading>
|
||||
<p class="text">
|
||||
Choose who can access your collection and documents. For more information, check out the <a
|
||||
href="https://appwrite.io/docs/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">Permissions Guide</a> in our documentation.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if collectionPermissions}
|
||||
<Permissions bind:permissions={collectionPermissions} withCreate />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={arePermsDisabled} on:click={updatePermissions}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, FormList, InputSwitch } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { collection } from '../store';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let collectionDocumentSecurity: boolean = null;
|
||||
|
||||
onMount(() => {
|
||||
collectionDocumentSecurity ??= $collection.documentSecurity;
|
||||
});
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
$collection.$permissions,
|
||||
collectionDocumentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
addNotification({
|
||||
message: 'Security has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_security');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Document Security</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputSwitch
|
||||
bind:value={collectionDocumentSecurity}
|
||||
id="security"
|
||||
label="Document Security" />
|
||||
</FormList>
|
||||
<p class="text">
|
||||
When document security is enabled, users will be able to access documents for which they
|
||||
have been granted <b>either Document or Collection permissions</b>.
|
||||
</p>
|
||||
<p class="text">
|
||||
If document security is disabled, users can access documents <b
|
||||
>only if they have Collection permissions</b
|
||||
>. Document permissions will be ignored.
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={collectionDocumentSecurity === $collection.documentSecurity}
|
||||
on:click={updateSecurity}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, InputSwitch } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { collection } from '../store';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let enabled: boolean = null;
|
||||
|
||||
onMount(() => {
|
||||
enabled ??= $collection.enabled;
|
||||
});
|
||||
|
||||
async function toggleCollection() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
$collection.$permissions,
|
||||
$collection.documentSecurity,
|
||||
enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
addNotification({
|
||||
message: `${$collection.name} has been updated`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_enabled');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">{$collection.name}</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputSwitch
|
||||
id="toggle"
|
||||
label={enabled ? 'Enabled' : 'Disabled'}
|
||||
bind:value={enabled} />
|
||||
</ul>
|
||||
<div>
|
||||
<p>Created: {toLocaleDateTime($collection.$createdAt)}</p>
|
||||
<p>Last Updated: {toLocaleDateTime($collection.$updatedAt)}</p>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={enabled === $collection.enabled} on:click={toggleCollection}>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
+2
-1
@@ -12,7 +12,8 @@
|
||||
Choose who can access your collection and documents. For more information, check out the <a
|
||||
href="https://appwrite.io/docs/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">Permissions Guide</a> in our documentation.
|
||||
rel="noopener noreferrer"
|
||||
class="link">Permissions Guide</a> in our documentation.
|
||||
</svelte:fragment>
|
||||
{#if $collection.documentSecurity}
|
||||
<div class="common-section">
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
files = entrypoint = active = null;
|
||||
showCreate = false;
|
||||
dispatch('created');
|
||||
trackEvent('submit_function_create');
|
||||
trackEvent('submit_deployment_create');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
import { project } from '../../../store';
|
||||
import { platform } from './store';
|
||||
|
||||
let hostname: string = null;
|
||||
let key: string = null;
|
||||
|
||||
onMount(() => {
|
||||
hostname ??= $platform.hostname;
|
||||
key ??= $platform.key;
|
||||
});
|
||||
|
||||
const updateHostname = async () => {
|
||||
@@ -22,9 +22,9 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
$platform.key,
|
||||
key,
|
||||
$platform.store,
|
||||
hostname
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
@@ -52,16 +52,16 @@
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="hostname"
|
||||
id="key"
|
||||
label="Package Name"
|
||||
bind:value={hostname}
|
||||
bind:value={key}
|
||||
required
|
||||
placeholder="com.company.appname" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={hostname === $platform.hostname} submit>Update</Button>
|
||||
<Button disabled={key === $platform.hostname} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
projectId,
|
||||
'android',
|
||||
$createPlatform.name,
|
||||
$createPlatform.key,
|
||||
undefined,
|
||||
undefined,
|
||||
$createPlatform.hostname
|
||||
undefined
|
||||
);
|
||||
|
||||
$createPlatform.$id = platform.$id;
|
||||
@@ -50,11 +50,11 @@
|
||||
required
|
||||
bind:value={$createPlatform.name} />
|
||||
<InputText
|
||||
id="hostname"
|
||||
id="key"
|
||||
label="Package Name"
|
||||
placeholder="com.company.appname"
|
||||
tooltip="Your package name is generally the applicationId in your app-level build.gradle file."
|
||||
required
|
||||
bind:value={$createPlatform.hostname} />
|
||||
bind:value={$createPlatform.key} />
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
|
||||
@@ -41,9 +41,9 @@
|
||||
projectId,
|
||||
platform,
|
||||
$createPlatform.name,
|
||||
$createPlatform.key,
|
||||
undefined,
|
||||
undefined,
|
||||
$createPlatform.hostname
|
||||
undefined
|
||||
);
|
||||
|
||||
$createPlatform.$id = response.$id;
|
||||
@@ -95,6 +95,6 @@
|
||||
placeholder="com.company.appname"
|
||||
tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."
|
||||
required
|
||||
bind:value={$createPlatform.hostname} />
|
||||
bind:value={$createPlatform.key} />
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
|
||||
+23
-14
@@ -25,7 +25,7 @@
|
||||
let platform: Platform = Platform.Android;
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const suggestions = ['*.vercel.app', '*.netlify.app', '*.gitpod.app'];
|
||||
const suggestions = ['*.vercel.app', '*.netlify.app', '*.gitpod.io'];
|
||||
const placeholder: Record<
|
||||
Platform,
|
||||
{
|
||||
@@ -97,9 +97,9 @@
|
||||
projectId,
|
||||
platform,
|
||||
$createPlatform.name,
|
||||
platform !== Platform.Web ? $createPlatform.key : undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
$createPlatform.hostname
|
||||
platform === Platform.Web ? $createPlatform.hostname : undefined
|
||||
);
|
||||
|
||||
$createPlatform.$id = response.$id;
|
||||
@@ -160,15 +160,16 @@
|
||||
placeholder={placeholder[platform].name}
|
||||
required
|
||||
bind:value={$createPlatform.name} />
|
||||
<div>
|
||||
<InputText
|
||||
id="hostname"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
tooltip={placeholder[platform].tooltip}
|
||||
required
|
||||
bind:value={$createPlatform.hostname} />
|
||||
{#if platform === Platform.Web}
|
||||
{#if platform === Platform.Web}
|
||||
<div>
|
||||
<InputText
|
||||
id="hostname"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
tooltip={placeholder[platform].tooltip}
|
||||
required
|
||||
bind:value={$createPlatform.hostname} />
|
||||
|
||||
<div class="u-flex u-gap-16 u-margin-block-start-8">
|
||||
{#each suggestions as suggestion}
|
||||
<Pill
|
||||
@@ -179,7 +180,15 @@
|
||||
</Pill>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<InputText
|
||||
id="key"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
tooltip={placeholder[platform].tooltip}
|
||||
required
|
||||
bind:value={$createPlatform.key} />
|
||||
{/if}
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import Dark from './dark.svg';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const suggestions = ['*.vercel.app', '*.netlify.app', '*.gitpod.app'];
|
||||
const suggestions = ['*.vercel.app', '*.netlify.app', '*.gitpod.io'];
|
||||
|
||||
$wizard.media = $app.themeInUse === 'dark' ? Dark : Light;
|
||||
async function beforeSubmit() {
|
||||
|
||||
@@ -1,353 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { CardGrid, Box, Secret, Empty, EventModal, Heading } from '$lib/components';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormList,
|
||||
InputText,
|
||||
InputChoice,
|
||||
InputPassword
|
||||
} from '$lib/elements/forms';
|
||||
import { TableList, TableCellText, TableCell } from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { webhook } from './store';
|
||||
import { page } from '$app/stores';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import Delete from './delete.svelte';
|
||||
import Regenerate from './regenerate.svelte';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
let name: string = null;
|
||||
let url: string = null;
|
||||
|
||||
const eventSet: Writable<Set<string>> = writable(new Set());
|
||||
|
||||
let httpUser: string = null;
|
||||
let httpPass: string = null;
|
||||
let security = false;
|
||||
let showDelete = false;
|
||||
let showCreateEvent = false;
|
||||
let areEventsDisabled = true;
|
||||
let showRegenerate = false;
|
||||
|
||||
onMount(async () => {
|
||||
name ??= $webhook.name;
|
||||
url ??= $webhook.url;
|
||||
$eventSet = new Set($webhook.events);
|
||||
httpUser ??= $webhook.httpUser;
|
||||
httpPass ??= $webhook.httpPass;
|
||||
security = $webhook.security;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
name,
|
||||
$webhook.events,
|
||||
$webhook.url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook name has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_name');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateUrl() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
$webhook.events,
|
||||
url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook url has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_url');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateEvents() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
Array.from($eventSet),
|
||||
$webhook.url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
areEventsDisabled = true;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook events have been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_events');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
$webhook.events,
|
||||
$webhook.url,
|
||||
security,
|
||||
httpUser,
|
||||
httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook security has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_security');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleEvent(event: CustomEvent) {
|
||||
eventSet.set($eventSet.add(event.detail));
|
||||
}
|
||||
|
||||
$: if ($eventSet) {
|
||||
if (symmetricDifference(Array.from($eventSet), $webhook.events).length) {
|
||||
areEventsDisabled = false;
|
||||
} else areEventsDisabled = true;
|
||||
}
|
||||
import DangerZone from './dangerZone.svelte';
|
||||
import UpdateEvents from './updateEvents.svelte';
|
||||
import UpdateName from './updateName.svelte';
|
||||
import UpdateSecurity from './updateSecurity.svelte';
|
||||
import UpdateSignature from './updateSignature.svelte';
|
||||
import UpdateUrl from './updateURL.svelte';
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Signature Key</Heading>
|
||||
<p>
|
||||
Add the Signature Key to the X-Appwrite-Webhook-Signature header to validate your
|
||||
webhooks. <a
|
||||
href="https://appwrite.io/docs/webhooks#verification"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">Learn more about webhook validation.</a>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<div>
|
||||
<p>Key</p>
|
||||
<Secret bind:value={$webhook.signatureKey} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button on:click={() => (showRegenerate = true)} secondary submit>
|
||||
Regenerate Key
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Update Name</Heading>
|
||||
<p>Choose any name that will help you distinguish between Webhooks.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
bind:value={name}
|
||||
required
|
||||
placeholder="Enter name" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={name === $webhook.name || !name} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateUrl}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Update Url</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="url"
|
||||
label="POST URL"
|
||||
bind:value={url}
|
||||
required
|
||||
placeholder="https://example.com/callback" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={url === $webhook.url || !url} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateEvents}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Events</Heading>
|
||||
<p class="text">
|
||||
Set the events that will trigger your webhook. Maximum 100 events allowed.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if $eventSet.size}
|
||||
<TableList>
|
||||
{#each Array.from($eventSet) as event}
|
||||
<li class="table-row">
|
||||
<TableCellText title="id">
|
||||
{event}
|
||||
</TableCellText>
|
||||
<TableCell showOverflow title="options" width={40}>
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
aria-label="delete id"
|
||||
on:click|preventDefault={() => {
|
||||
$eventSet.delete(event);
|
||||
eventSet.set($eventSet);
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</TableCell>
|
||||
</li>
|
||||
{/each}
|
||||
</TableList>
|
||||
<div class="u-flex u-margin-block-start-16">
|
||||
<Button text noMargin on:click={() => (showCreateEvent = true)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="u-text">Add event</span>
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Empty on:click={() => (showCreateEvent = true)}>
|
||||
Add an event to get started
|
||||
</Empty>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={areEventsDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateSecurity}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Security</Heading>
|
||||
<p class="text">
|
||||
Set an optional basic HTTP authentication username and password to protect your
|
||||
endpoint from unauthorized access.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<div>
|
||||
<Heading tag="h3" size="7">HTTP Authentication</Heading>
|
||||
<p class="text">Use to secure your endpoint from untrusted sources.</p>
|
||||
</div>
|
||||
<InputText
|
||||
label="User"
|
||||
id="user"
|
||||
placeholder="Enter username"
|
||||
bind:value={httpUser} />
|
||||
<InputPassword
|
||||
label="Password"
|
||||
id="password"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
placeholder="Enter password"
|
||||
bind:value={httpPass} />
|
||||
|
||||
<InputChoice
|
||||
id="Security"
|
||||
label="Certificate verification (SSL/TLS)"
|
||||
bind:value={security}>
|
||||
<span class="u-error">Warning:</span> Untrusted or self-signed certificates
|
||||
may not be secure.
|
||||
<a
|
||||
href="https://appwrite.io/docs/custom-domains#enjoySSLCert"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
Learn more</a>
|
||||
</InputChoice>
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={httpUser === $webhook.httpUser &&
|
||||
httpPass === $webhook.httpPass &&
|
||||
security === $webhook.security}
|
||||
submit>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<CardGrid danger>
|
||||
<div>
|
||||
<Heading tag="h2" size="7">Delete Webhook</Heading>
|
||||
</div>
|
||||
<p>The webhook will be permanently deleted. This action is irreversible.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$webhook.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last updated: {toLocaleDateTime($webhook.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<UpdateSignature />
|
||||
<UpdateName />
|
||||
<UpdateUrl />
|
||||
<UpdateEvents />
|
||||
<UpdateSecurity />
|
||||
<DangerZone />
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
<Regenerate bind:show={showRegenerate} />
|
||||
|
||||
<EventModal bind:show={showCreateEvent} on:created={handleEvent} />
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { Box, CardGrid, Heading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { webhook } from './store';
|
||||
import Delete from './delete.svelte';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
|
||||
let showDelete = false;
|
||||
</script>
|
||||
|
||||
<CardGrid danger>
|
||||
<div>
|
||||
<Heading tag="h2" size="7">Delete Webhook</Heading>
|
||||
</div>
|
||||
<p>The webhook will be permanently deleted. This action is irreversible.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$webhook.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last updated: {toLocaleDateTime($webhook.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
@@ -0,0 +1,109 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Empty, EventModal, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { TableCell, TableCellText, TableList } from '$lib/elements/table';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { webhook } from './store';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const eventSet: Writable<Set<string>> = writable(new Set());
|
||||
|
||||
let showCreateEvent = false;
|
||||
let areEventsDisabled = true;
|
||||
|
||||
onMount(async () => {
|
||||
$eventSet = new Set($webhook.events);
|
||||
});
|
||||
|
||||
async function updateEvents() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
Array.from($eventSet),
|
||||
$webhook.url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
areEventsDisabled = true;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook events have been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_events');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleEvent(event: CustomEvent) {
|
||||
eventSet.set($eventSet.add(event.detail));
|
||||
}
|
||||
|
||||
$: if ($eventSet) {
|
||||
if (symmetricDifference(Array.from($eventSet), $webhook.events).length) {
|
||||
areEventsDisabled = false;
|
||||
} else areEventsDisabled = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateEvents}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Events</Heading>
|
||||
<p class="text">
|
||||
Set the events that will trigger your webhook. Maximum 100 events allowed.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if $eventSet.size}
|
||||
<TableList>
|
||||
{#each Array.from($eventSet) as event}
|
||||
<li class="table-row">
|
||||
<TableCellText title="id">
|
||||
{event}
|
||||
</TableCellText>
|
||||
<TableCell showOverflow title="options" width={40}>
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
aria-label="delete id"
|
||||
on:click|preventDefault={() => {
|
||||
$eventSet.delete(event);
|
||||
eventSet.set($eventSet);
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</TableCell>
|
||||
</li>
|
||||
{/each}
|
||||
</TableList>
|
||||
<div class="u-flex u-margin-block-start-16">
|
||||
<Button text noMargin on:click={() => (showCreateEvent = true)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="u-text">Add event</span>
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Empty on:click={() => (showCreateEvent = true)}>Add an event to get started</Empty>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={areEventsDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<EventModal bind:show={showCreateEvent} on:created={handleEvent} />
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { webhook } from './store';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
let name: string = null;
|
||||
|
||||
onMount(async () => {
|
||||
name ??= $webhook.name;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
name,
|
||||
$webhook.events,
|
||||
$webhook.url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook name has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_name');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Update Name</Heading>
|
||||
<p>Choose any name that will help you distinguish between Webhooks.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
bind:value={name}
|
||||
required
|
||||
placeholder="Enter name" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={name === $webhook.name || !name} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormList,
|
||||
InputChoice,
|
||||
InputPassword,
|
||||
InputText
|
||||
} from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { webhook } from './store';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
let httpUser: string = null;
|
||||
let httpPass: string = null;
|
||||
let security = false;
|
||||
|
||||
onMount(async () => {
|
||||
httpUser ??= $webhook.httpUser;
|
||||
httpPass ??= $webhook.httpPass;
|
||||
security = $webhook.security;
|
||||
});
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
$webhook.events,
|
||||
$webhook.url,
|
||||
security,
|
||||
httpUser,
|
||||
httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook security has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_security');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateSecurity}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Security</Heading>
|
||||
<p class="text">
|
||||
Set an optional basic HTTP authentication username and password to protect your endpoint
|
||||
from unauthorized access.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<div>
|
||||
<Heading tag="h3" size="7">HTTP Authentication</Heading>
|
||||
<p class="text">Use to secure your endpoint from untrusted sources.</p>
|
||||
</div>
|
||||
<InputText
|
||||
label="User"
|
||||
id="user"
|
||||
placeholder="Enter username"
|
||||
bind:value={httpUser} />
|
||||
<InputPassword
|
||||
label="Password"
|
||||
id="password"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
placeholder="Enter password"
|
||||
bind:value={httpPass} />
|
||||
|
||||
<InputChoice
|
||||
id="Security"
|
||||
label="Certificate verification (SSL/TLS)"
|
||||
bind:value={security}>
|
||||
<span class="u-error">Warning:</span> Untrusted or self-signed certificates may
|
||||
not be secure.
|
||||
<a
|
||||
href="https://appwrite.io/docs/custom-domains#enjoySSLCert"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
Learn more</a>
|
||||
</InputChoice>
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={httpUser === $webhook.httpUser &&
|
||||
httpPass === $webhook.httpPass &&
|
||||
security === $webhook.security}
|
||||
submit>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid, Heading, Secret } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import Regenerate from './regenerate.svelte';
|
||||
import { webhook } from './store';
|
||||
|
||||
let showRegenerate = false;
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Signature Key</Heading>
|
||||
<p>
|
||||
Add the Signature Key to the X-Appwrite-Webhook-Signature header to validate your webhooks. <a
|
||||
href="https://appwrite.io/docs/webhooks#verification"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">Learn more about webhook validation.</a>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<div>
|
||||
<p>Key</p>
|
||||
<Secret bind:value={$webhook.signatureKey} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button on:click={() => (showRegenerate = true)} secondary submit>Regenerate Key</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Regenerate bind:show={showRegenerate} />
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { webhook } from './store';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
let url: string = null;
|
||||
|
||||
onMount(async () => {
|
||||
url ??= $webhook.url;
|
||||
});
|
||||
|
||||
async function updateUrl() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
$webhook.events,
|
||||
url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook url has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_url');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateUrl}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Update Url</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="url"
|
||||
label="POST URL"
|
||||
bind:value={url}
|
||||
required
|
||||
placeholder="https://example.com/callback" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={url === $webhook.url || !url} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -162,11 +162,15 @@
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListLink
|
||||
on:click={() => {
|
||||
showDropdown[index] = false;
|
||||
}}
|
||||
href={`${base}/console/project-${projectId}/storage/bucket-${bucketId}/file-${file.$id}`}
|
||||
icon="pencil">Update</DropListLink>
|
||||
<DropListItem
|
||||
icon="trash"
|
||||
on:click={() => {
|
||||
showDropdown[index] = false;
|
||||
selectedFile = file;
|
||||
showDelete = true;
|
||||
}}>Delete</DropListItem>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Button, FormList, InputFile } from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Modal, CustomId, Heading, Alert } from '$lib/components';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { uploader } from '$lib/stores/uploader';
|
||||
@@ -25,20 +24,15 @@
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
const file = await sdkForProject.storage.createFile(
|
||||
bucketId,
|
||||
id ?? 'unique()',
|
||||
files[0],
|
||||
permissions
|
||||
);
|
||||
files = null;
|
||||
showCreate = false;
|
||||
await uploader.uploadFile(bucketId, id, files[0], permissions);
|
||||
files = null;
|
||||
showCustomId = false;
|
||||
uploader.addFile(file);
|
||||
// uploader.addFile(file);
|
||||
dispatch('created');
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${file.name} has been created`
|
||||
message: `File has been uploaded`
|
||||
});
|
||||
trackEvent('submit_file_create');
|
||||
} catch ({ message }) {
|
||||
@@ -74,8 +68,11 @@
|
||||
<Heading tag="h6" size="7">Update Permissions</Heading>
|
||||
<p class="text">
|
||||
Choose who can access your buckets and files. For more information, check out the
|
||||
<a href="https://appwrite.io/docs/permissions" target="_blank" rel="noopener noreferrer"
|
||||
>Permissions Guide</a> in our documentation.
|
||||
<a
|
||||
href="https://appwrite.io/docs/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">Permissions Guide</a> in our documentation.
|
||||
</p>
|
||||
{#if $bucket.fileSecurity}
|
||||
<div class="common-section">
|
||||
|
||||
+56
-13
@@ -36,8 +36,9 @@
|
||||
arePermsDisabled = true,
|
||||
encryption: boolean = null,
|
||||
antivirus: boolean = null,
|
||||
maxSize: number;
|
||||
let byteUnit = writable('KB');
|
||||
maxSize: number,
|
||||
compression: string = null;
|
||||
let byteUnit = writable('MB');
|
||||
let sizeInBytes: number = null;
|
||||
const options = [
|
||||
{ label: 'Bytes', value: 'Bytes' },
|
||||
@@ -45,6 +46,11 @@
|
||||
{ label: 'Megabytes', value: 'MB' },
|
||||
{ label: 'Gigabytes', value: 'GB' }
|
||||
];
|
||||
const compressionOptions = [
|
||||
{ label: 'None', value: 'none' },
|
||||
{ label: 'Gzip', value: 'gzip' },
|
||||
{ label: 'Zstd', value: 'zstd' }
|
||||
];
|
||||
let suggestedExtensions = ['jpg', 'png', 'svg', 'gif', 'html', 'pdf', 'mp4'];
|
||||
let extensions = $bucket.allowedFileExtensions;
|
||||
let isExtensionsDisabled = true;
|
||||
@@ -57,8 +63,10 @@
|
||||
bucketPermissions ??= $bucket.$permissions;
|
||||
encryption ??= $bucket.encryption;
|
||||
antivirus ??= $bucket.antivirus;
|
||||
maxSize = $bucket.maximumFileSize / 1024;
|
||||
maxSize = $bucket.maximumFileSize / 1024 / 1024;
|
||||
compression ??= $bucket.compression;
|
||||
});
|
||||
|
||||
$: if (bucketPermissions) {
|
||||
if (symmetricDifference(bucketPermissions, $bucket.$permissions).length) {
|
||||
arePermsDisabled = false;
|
||||
@@ -192,6 +200,17 @@
|
||||
);
|
||||
}
|
||||
|
||||
function updateCompression() {
|
||||
updateBucket(
|
||||
{
|
||||
compression
|
||||
},
|
||||
{
|
||||
trackEventName: 'submit_bucket_update_size'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateAllowedExtensions() {
|
||||
updateBucket(
|
||||
{
|
||||
@@ -234,8 +253,8 @@
|
||||
id="toggle"
|
||||
bind:value={enabled} />
|
||||
</FormList>
|
||||
<p>Created: {toLocaleDateTime($bucket.$createdAt)}</p>
|
||||
<p>Last Updated: {toLocaleDateTime($bucket.$updatedAt)}</p>
|
||||
<p class="text">Created: {toLocaleDateTime($bucket.$createdAt)}</p>
|
||||
<p class="text">Last Updated: {toLocaleDateTime($bucket.$updatedAt)}</p>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -275,7 +294,8 @@
|
||||
the <a
|
||||
href="https://appwrite.io/docs/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">Permissions Guide</a> in our documentation.
|
||||
rel="noopener noreferrer"
|
||||
class="link">Permissions Guide</a> in our documentation.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if bucketPermissions}
|
||||
@@ -319,7 +339,7 @@
|
||||
<Form on:submit={updateSecurity}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Update Security Settings</Heading>
|
||||
<p>
|
||||
<p class="text">
|
||||
Enable or disable security services for the bucket including <b>Ecryption</b>
|
||||
and <b>Antivirus scanning.</b>
|
||||
</p>
|
||||
@@ -380,17 +400,40 @@
|
||||
<Button
|
||||
disabled={encryption === $bucket.encryption &&
|
||||
antivirus === $bucket.antivirus}
|
||||
submit
|
||||
>Update
|
||||
submit>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateCompression}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="6">Update Compression Algorithm</Heading>
|
||||
<p class="text">
|
||||
Choose an algorithm for compression. For files larger than 20MB, compression
|
||||
will be skipped even if it's enabled.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputSelect
|
||||
id="compression"
|
||||
label="Compression algorithm"
|
||||
options={compressionOptions}
|
||||
bind:value={compression} />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={compression === $bucket.compression} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateMaxSize}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="6">Update Maximum File Size</Heading>
|
||||
<p>Set the maximum file size allowed in the bucket.</p>
|
||||
<p class="text">Set the maximum file size allowed in the bucket.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul class="u-flex u-gap-12">
|
||||
<InputNumber
|
||||
@@ -413,7 +456,7 @@
|
||||
<Form on:submit={updateAllowedExtensions}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Allowed File Extensions</Heading>
|
||||
<p>
|
||||
<p class="text">
|
||||
A maximum of 100 file extensions can be added. Leave blank to allow all file
|
||||
types.
|
||||
</p>
|
||||
@@ -452,7 +495,7 @@
|
||||
|
||||
<CardGrid danger>
|
||||
<Heading tag="h6" size="7">Delete Bucket</Heading>
|
||||
<p>
|
||||
<p class="text">
|
||||
The bucket will be permanently deleted, including all the files within it. This
|
||||
action is irreversible.
|
||||
</p>
|
||||
@@ -461,7 +504,7 @@
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$bucket.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last Updated: {toLocaleDateTime($bucket.$updatedAt)}</p>
|
||||
<p class="text">Last Updated: {toLocaleDateTime($bucket.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user