Merge branch 'main' of github.com:appwrite/console into feat-custom-id-pattern
@@ -1,3 +1,5 @@
|
||||
VITE_APPWRITE_ENDPOINT=
|
||||
VITE_GOOGLE_ANALYTICS=
|
||||
VITE_SENTRY_DSN=
|
||||
VITE_APPWRITE_GROWTH_ENDPOINT=
|
||||
VITE_GA_PROJECT=
|
||||
VITE_SENTRY_DSN=
|
||||
VITE_CONSOLE_MODE=self-hosted
|
||||
@@ -4,10 +4,6 @@
|
||||
[](https://twitter.com/appwrite)
|
||||
[](https://appwrite.io)
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.producthunt.com/posts/console-2-0?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-console-2-0" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=367206&theme=light" alt="Console 2.0 - Backend as a Service, reimagined by Appwrite | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
[Appwrite](https://appwrite.io) Console is the Graphical User Interface that developers interact with when accessing their Appwrite instance in the web browser.
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@analytics/google-analytics": "^1.0.5",
|
||||
"@aw-labs/appwrite-console": "^13.0.0",
|
||||
"@aw-labs/icons": "0.0.0-78",
|
||||
"@aw-labs/ui": "0.0.0-78",
|
||||
"@aw-labs/appwrite-console": "^13.1.0",
|
||||
"@aw-labs/icons": "0.0.0-82",
|
||||
"@aw-labs/ui": "0.0.0-82",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@sentry/svelte": "^7.19.0",
|
||||
"@sentry/tracing": "^7.19.0",
|
||||
@@ -145,23 +145,23 @@
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/@aw-labs/icons": {
|
||||
"version": "0.0.0-78",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-78.tgz",
|
||||
"integrity": "sha512-M1yH0EUanjuHw1McVJfVKrkbHOoSd4PF8ZOYqMXOhSwuwk37MFR8jUapYb2NPu3WdfLTGI6H3N+HwaILePotCg=="
|
||||
"version": "0.0.0-82",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-82.tgz",
|
||||
"integrity": "sha512-TQ3abZGBjisd4WpeBVSUihY98drljdlcdw++z8icNpQVYohmUejRHU59mhmIfXCkhpA1s0tW+5xEMp0WmLNp9Q=="
|
||||
},
|
||||
"node_modules/@aw-labs/ui": {
|
||||
"version": "0.0.0-78",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-78.tgz",
|
||||
"integrity": "sha512-YjIqkwAqZLvjJ6MQBxTEbAuotXHL1E/LXwmHOH9/Tu6TvF5p8e83LlyHVG6d8rXwsmsGfUcXLJEvEcc8RMEOMQ==",
|
||||
"version": "0.0.0-82",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-82.tgz",
|
||||
"integrity": "sha512-zZDTP117uxMrwzAacOcpSXI77FMFcfv6+Da1R5sDWoqxhvZ0Wyx0Z6cn3dakV0M3zJfYZQ49LmSnU/5wqHPc7A==",
|
||||
"dependencies": {
|
||||
"@aw-labs/icons": "*",
|
||||
"normalize.css": "^8.0.1",
|
||||
@@ -7740,23 +7740,23 @@
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"@aw-labs/icons": {
|
||||
"version": "0.0.0-78",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-78.tgz",
|
||||
"integrity": "sha512-M1yH0EUanjuHw1McVJfVKrkbHOoSd4PF8ZOYqMXOhSwuwk37MFR8jUapYb2NPu3WdfLTGI6H3N+HwaILePotCg=="
|
||||
"version": "0.0.0-82",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-82.tgz",
|
||||
"integrity": "sha512-TQ3abZGBjisd4WpeBVSUihY98drljdlcdw++z8icNpQVYohmUejRHU59mhmIfXCkhpA1s0tW+5xEMp0WmLNp9Q=="
|
||||
},
|
||||
"@aw-labs/ui": {
|
||||
"version": "0.0.0-78",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-78.tgz",
|
||||
"integrity": "sha512-YjIqkwAqZLvjJ6MQBxTEbAuotXHL1E/LXwmHOH9/Tu6TvF5p8e83LlyHVG6d8rXwsmsGfUcXLJEvEcc8RMEOMQ==",
|
||||
"version": "0.0.0-82",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-82.tgz",
|
||||
"integrity": "sha512-zZDTP117uxMrwzAacOcpSXI77FMFcfv6+Da1R5sDWoqxhvZ0Wyx0Z6cn3dakV0M3zJfYZQ49LmSnU/5wqHPc7A==",
|
||||
"requires": {
|
||||
"@aw-labs/icons": "*",
|
||||
"normalize.css": "^8.0.1",
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
"dependencies": {
|
||||
"@analytics/google-analytics": "^1.0.5",
|
||||
"analytics": "^0.8.1",
|
||||
"@aw-labs/appwrite-console": "^13.0.0",
|
||||
"@aw-labs/icons": "0.0.0-78",
|
||||
"@aw-labs/ui": "0.0.0-78",
|
||||
"@aw-labs/appwrite-console": "^13.1.0",
|
||||
"@aw-labs/icons": "0.0.0-82",
|
||||
"@aw-labs/ui": "0.0.0-82",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@sentry/svelte": "^7.19.0",
|
||||
"@sentry/tracing": "^7.19.0",
|
||||
|
||||
@@ -3,12 +3,13 @@ import googleAnalytics from '@analytics/google-analytics';
|
||||
import { get } from 'svelte/store';
|
||||
import { page } from '$app/stores';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { growthEndpoint, Mode } from '$lib/constants';
|
||||
|
||||
const analytics = Analytics({
|
||||
app: 'appwrite',
|
||||
plugins: [
|
||||
googleAnalytics({
|
||||
measurementIds: ['G-R4YJ9JN8L4']
|
||||
measurementIds: [import.meta.env.VITE_GA_PROJECT?.toString() || 'G-R4YJ9JN8L4']
|
||||
})
|
||||
]
|
||||
});
|
||||
@@ -39,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'
|
||||
@@ -48,6 +49,7 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo
|
||||
action: event,
|
||||
label: event,
|
||||
url: window.location.origin + path,
|
||||
account: import.meta.env.VITE_CONSOLE_MODE?.toString() || Mode.SELF_HOSTED,
|
||||
data: {
|
||||
email,
|
||||
name,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -2,6 +2,13 @@ export const PAGE_LIMIT = 12; // default page limit
|
||||
export const CARD_LIMIT = 6; // default card limit
|
||||
export const INTERVAL = 5 * 60000; // default interval to check for feedback
|
||||
|
||||
export enum Mode {
|
||||
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}
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
export let showOverflow = false;
|
||||
</script>
|
||||
|
||||
<div class="table-col " class:u-overflow-visible={showOverflow} data-title={title} role="cell">
|
||||
<div class="table-col" class:u-overflow-visible={showOverflow} data-title={title} role="cell">
|
||||
<Trim><slot /></Trim>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ export function intersection(arr1: unknown[], arr2: unknown[]) {
|
||||
const intersection = new Set(arr1.filter((elem) => set.has(elem)));
|
||||
return Array.from(intersection);
|
||||
}
|
||||
|
||||
export function difference(arr1: unknown[], arr2: unknown[]) {
|
||||
const set = new Set(arr2);
|
||||
const intersection = new Set(arr1.filter((elem) => !set.has(elem)));
|
||||
@@ -12,3 +13,17 @@ export function difference(arr1: unknown[], arr2: unknown[]) {
|
||||
export function symmetricDifference(arr1: unknown[], arr2: unknown[]) {
|
||||
return difference(arr1, arr2).concat(difference(arr2, arr1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the element at the specified index from the array, and returns a new array.
|
||||
*
|
||||
* @export
|
||||
* @template T
|
||||
* @param {T[]} arr
|
||||
* @param {number} index
|
||||
* @returns {T[]}
|
||||
*/
|
||||
export function remove<T>(arr: T[], index: number): T[] {
|
||||
// Remove the element at the given index, return a new array
|
||||
return [...arr.slice(0, index), ...arr.slice(index + 1)];
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -5,6 +5,8 @@ import Apple from '../../routes/console/project-[project]/auth/appleOAuth.svelte
|
||||
import Microsoft from '../../routes/console/project-[project]/auth/microsoftOAuth.svelte';
|
||||
import Okta from '../../routes/console/project-[project]/auth/oktaOAuth.svelte';
|
||||
import Auth0 from '../../routes/console/project-[project]/auth/auth0OAuth.svelte';
|
||||
import Authentik from '../../routes/console/project-[project]/auth/authentikOAuth.svelte';
|
||||
import GitLab from '../../routes/console/project-[project]/auth/gitlabOAuth.svelte';
|
||||
import Main from '../../routes/console/project-[project]/auth/mainOAuth.svelte';
|
||||
|
||||
export type Provider = Models.Provider & {
|
||||
@@ -36,6 +38,13 @@ const setProviders = (project: Models.Project): Provider[] => {
|
||||
docs = 'https://auth0.com/developers';
|
||||
component = Auth0;
|
||||
break;
|
||||
case 'authentik':
|
||||
docs = 'https://goauthentik.io/integrations/sources/oauth/';
|
||||
component = Authentik;
|
||||
break;
|
||||
case 'autodesk':
|
||||
docs = 'https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/';
|
||||
break;
|
||||
case 'bitbucket':
|
||||
docs = 'https://developer.atlassian.com/bitbucket';
|
||||
break;
|
||||
@@ -45,12 +54,21 @@ const setProviders = (project: Models.Project): Provider[] => {
|
||||
case 'box':
|
||||
docs = 'https://developer.box.com/reference/';
|
||||
break;
|
||||
case 'dailymotion':
|
||||
docs = 'https://developers.dailymotion.com/api/';
|
||||
break;
|
||||
case 'discord':
|
||||
docs = 'https://discordapp.com/developers/docs/topics/oauth2';
|
||||
break;
|
||||
case 'disqus':
|
||||
docs = 'https://disqus.com/api/docs/auth/';
|
||||
break;
|
||||
case 'dropbox':
|
||||
docs = 'https://www.dropbox.com/developers/documentation';
|
||||
break;
|
||||
case 'etsy':
|
||||
docs = 'https://developers.etsy.com/';
|
||||
break;
|
||||
case 'facebook':
|
||||
docs = 'https://developers.facebook.com/';
|
||||
break;
|
||||
@@ -59,6 +77,7 @@ const setProviders = (project: Models.Project): Provider[] => {
|
||||
break;
|
||||
case 'gitlab':
|
||||
docs = 'https://docs.gitlab.com/ee/api/';
|
||||
component = GitLab;
|
||||
break;
|
||||
case 'google':
|
||||
docs = 'https://support.google.com/googleapi/answer/6158849';
|
||||
@@ -84,6 +103,9 @@ const setProviders = (project: Models.Project): Provider[] => {
|
||||
icon = 'paypal';
|
||||
docs = 'https://developer.paypal.com/docs/api/overview/';
|
||||
break;
|
||||
case 'podio':
|
||||
docs = 'https://developers.podio.com/doc/oauth-authorization';
|
||||
break;
|
||||
case 'salesforce':
|
||||
docs = 'https://developer.salesforce.com/docs/';
|
||||
break;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 `
|
||||
|
||||
@@ -55,7 +55,9 @@
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
{#each $organizationList.teams as org}
|
||||
<DropListLink href={`${base}/console/organization-${org.$id}`}>
|
||||
<DropListLink
|
||||
href={`${base}/console/organization-${org.$id}`}
|
||||
on:click={() => (showDropdown = false)}>
|
||||
{org.name}
|
||||
</DropListLink>
|
||||
{/each}
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdkForConsole.client.config.endpoint
|
||||
}/account/session/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdkForConsole.client.config.endpoint
|
||||
}/account/session/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Modal, CopyInput, Alert } from '$lib/components';
|
||||
import { Button, InputPassword, InputText, InputSwitch, FormList } from '$lib/elements/forms';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { onMount } from 'svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
export let provider: Provider;
|
||||
|
||||
let appId: string = null;
|
||||
let enabled: boolean = null;
|
||||
let clientSecret: string = null;
|
||||
let authentikDomain: string = null;
|
||||
let error: string;
|
||||
|
||||
onMount(() => {
|
||||
appId ??= provider.appId;
|
||||
enabled ??= provider.enabled;
|
||||
if (provider.secret) ({ clientSecret, authentikDomain } = JSON.parse(provider.secret));
|
||||
});
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const update = async () => {
|
||||
try {
|
||||
await sdkForConsole.projects.updateOAuth2(
|
||||
projectId,
|
||||
provider.name.toLowerCase(),
|
||||
appId,
|
||||
secret,
|
||||
enabled
|
||||
);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${provider.name} authentication has been ${
|
||||
provider.enabled ? 'enabled' : 'disabled'
|
||||
}`
|
||||
});
|
||||
trackEvent('submit_provider_update', {
|
||||
provider,
|
||||
enabled
|
||||
});
|
||||
provider = null;
|
||||
invalidate(Dependencies.PROJECT);
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
|
||||
$: secret =
|
||||
clientSecret && authentikDomain
|
||||
? JSON.stringify({ clientSecret, authentikDomain })
|
||||
: provider.secret;
|
||||
</script>
|
||||
|
||||
<Modal {error} size="big" show on:submit={update} on:close>
|
||||
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer"
|
||||
>visit the docs.</a>
|
||||
</p>
|
||||
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
|
||||
<InputText
|
||||
id="clientID"
|
||||
label="Client ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
bind:value={clientSecret} />
|
||||
<InputText
|
||||
id="domain"
|
||||
label="Authentik Base-Domain"
|
||||
placeholder="Your Authentik domain"
|
||||
bind:value={authentikDomain} />
|
||||
<Alert type="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdkForConsole.client.config.endpoint
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && clientSecret && authentikDomain)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -0,0 +1,106 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Modal, CopyInput, Alert } from '$lib/components';
|
||||
import { Button, InputPassword, InputText, InputSwitch, FormList } from '$lib/elements/forms';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { onMount } from 'svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
export let provider: Provider;
|
||||
|
||||
let appId: string = null;
|
||||
let enabled: boolean = null;
|
||||
let clientSecret: string = null;
|
||||
let endpoint: string = null;
|
||||
let error: string;
|
||||
|
||||
onMount(() => {
|
||||
appId ??= provider.appId;
|
||||
enabled ??= provider.enabled;
|
||||
if (provider.secret) ({ clientSecret, endpoint } = JSON.parse(provider.secret));
|
||||
});
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const update = async () => {
|
||||
try {
|
||||
await sdkForConsole.projects.updateOAuth2(
|
||||
projectId,
|
||||
provider.name.toLowerCase(),
|
||||
appId,
|
||||
secret,
|
||||
enabled
|
||||
);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${provider.name} authentication has been ${
|
||||
provider.enabled ? 'enabled' : 'disabled'
|
||||
}`
|
||||
});
|
||||
trackEvent('submit_provider_update', {
|
||||
provider,
|
||||
enabled
|
||||
});
|
||||
provider = null;
|
||||
invalidate(Dependencies.PROJECT);
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
|
||||
$: secret =
|
||||
clientSecret && endpoint ? JSON.stringify({ clientSecret, endpoint }) : provider.secret;
|
||||
</script>
|
||||
|
||||
<Modal {error} size="big" show on:submit={update} on:close>
|
||||
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer"
|
||||
>visit the docs.</a>
|
||||
</p>
|
||||
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
|
||||
<InputText
|
||||
id="appID"
|
||||
label="App ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="App Secret"
|
||||
placeholder="Enter App Secret"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
bind:value={clientSecret} />
|
||||
<InputText
|
||||
id="endpoint"
|
||||
label="Endpoint (optional)"
|
||||
placeholder="Your endpoint"
|
||||
bind:value={endpoint} />
|
||||
<Alert type="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdkForConsole.client.config.endpoint
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && clientSecret && endpoint)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -84,7 +84,7 @@
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdkForConsole.client.config.endpoint
|
||||
}/account/session/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdkForConsole.client.config.endpoint
|
||||
}/account/session/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
projectId,
|
||||
provider.name.toLowerCase(),
|
||||
appId,
|
||||
secret
|
||||
secret,
|
||||
enabled
|
||||
);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
@@ -102,7 +103,7 @@
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdkForConsole.client.config.endpoint
|
||||
}/account/session/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,500 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import {
|
||||
CardGrid,
|
||||
Box,
|
||||
DropList,
|
||||
DropListItem,
|
||||
Heading,
|
||||
AvatarInitials
|
||||
} from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
InputText,
|
||||
InputEmail,
|
||||
InputPassword,
|
||||
InputPhone
|
||||
} from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import DeleteUser from './deleteUser.svelte';
|
||||
import { user } from './store';
|
||||
import { onMount } from 'svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
$: if (prefs) {
|
||||
if (JSON.stringify(prefs) !== JSON.stringify(Object.entries($user.prefs))) {
|
||||
if (!!prefs[prefs.length - 1][0] && !!prefs[prefs.length - 1][1]) {
|
||||
arePrefsDisabled = false;
|
||||
} else {
|
||||
arePrefsDisabled = true;
|
||||
}
|
||||
} else {
|
||||
arePrefsDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
let showDelete = false;
|
||||
let userName: string = null,
|
||||
userEmail: string = null,
|
||||
userPhone: string = null,
|
||||
newPassword: string = null;
|
||||
let prefs: [string, unknown][] = null;
|
||||
let arePrefsDisabled = true;
|
||||
let showVerifcationDropdown = false;
|
||||
|
||||
onMount(async () => {
|
||||
prefs = Object.entries($user.prefs);
|
||||
if (!prefs?.length) {
|
||||
prefs.push(['', '']);
|
||||
}
|
||||
userName ??= $user.name;
|
||||
userEmail ??= $user.email;
|
||||
userPhone ??= $user.phone;
|
||||
});
|
||||
|
||||
async function updateVerificationEmail() {
|
||||
showVerifcationDropdown = false;
|
||||
try {
|
||||
await sdkForProject.users.updateEmailVerification($user.$id, !$user.emailVerification);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.emailVerification ? 'verified' : 'unverified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_verification_email');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateVerificationPhone() {
|
||||
showVerifcationDropdown = false;
|
||||
try {
|
||||
await sdkForProject.users.updatePhoneVerification($user.$id, !$user.phoneVerification);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.phoneVerification ? 'verified' : 'unverified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_verification_phone');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateStatus() {
|
||||
try {
|
||||
await sdkForProject.users.updateStatus($user.$id, !$user.status);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.status ? 'unblocked' : 'blocked'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_status');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.users.updateName($user.$id, userName);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_name');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateEmail() {
|
||||
try {
|
||||
await sdkForProject.users.updateEmail($user.$id, userEmail);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: 'Email has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_email');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updatePhone() {
|
||||
try {
|
||||
await sdkForProject.users.updatePhone($user.$id, userPhone);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: 'Phone has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_phone');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updatePassword() {
|
||||
try {
|
||||
await sdkForProject.users.updatePassword($user.$id, newPassword);
|
||||
newPassword = null;
|
||||
addNotification({
|
||||
message: 'Password has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_password');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePrefs() {
|
||||
try {
|
||||
let updatedPrefs = Object.fromEntries(prefs);
|
||||
|
||||
await sdkForProject.users.updatePrefs($user.$id, updatedPrefs);
|
||||
invalidate(Dependencies.USER);
|
||||
arePrefsDisabled = true;
|
||||
|
||||
addNotification({
|
||||
message: 'Preferences have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_preferences');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
import DangerZone from './dangerZone.svelte';
|
||||
import UpdateEmail from './updateEmail.svelte';
|
||||
import UpdateName from './updateName.svelte';
|
||||
import UpdatePassword from './updatePassword.svelte';
|
||||
import UpdatePhone from './updatePhone.svelte';
|
||||
import UpdatePrefs from './updatePrefs.svelte';
|
||||
import UpdateStatus from './updateStatus.svelte';
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<CardGrid>
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16">
|
||||
{#if $user.email || $user.phone}
|
||||
{#if $user.name}
|
||||
<AvatarInitials size={48} name={$user.name} />
|
||||
<Heading tag="h6" size="7">{$user.name}</Heading>
|
||||
{:else}
|
||||
<div class="avatar">
|
||||
<span class="icon-minus-sm" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="avatar">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<svelte:fragment slot="aside">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<div>
|
||||
{#if $user.email}
|
||||
<p class="title">{$user.email}</p>
|
||||
{/if}
|
||||
{#if $user.phone}
|
||||
<p class="title">{$user.phone}</p>
|
||||
{/if}
|
||||
<p>Joined: {toLocaleDateTime($user.registration)}</p>
|
||||
</div>
|
||||
{#if !$user.status}
|
||||
<Pill danger>blocked</Pill>
|
||||
{:else if $user.email && $user.phone}
|
||||
<Pill success={$user.emailVerification || $user.phoneVerification}>
|
||||
{$user.emailVerification && $user.phoneVerification
|
||||
? 'verified'
|
||||
: $user.emailVerification
|
||||
? 'verified email'
|
||||
: $user.phoneVerification
|
||||
? 'verified phone'
|
||||
: 'unverified'}
|
||||
</Pill>
|
||||
{:else}
|
||||
<Pill success={$user.emailVerification || $user.phoneVerification}>
|
||||
{$user.emailVerification
|
||||
? 'verified '
|
||||
: $user.phoneVerification
|
||||
? 'verified '
|
||||
: 'unverified'}
|
||||
</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button text={$user.status} secondary={!$user.status} on:click={() => updateStatus()}>
|
||||
{$user.status ? 'Block account' : 'Unblock account'}
|
||||
</Button>
|
||||
{#if $user.status}
|
||||
{#if $user.phone && $user.email}
|
||||
<DropList bind:show={showVerifcationDropdown} placement="top-start">
|
||||
<Button
|
||||
secondary
|
||||
on:click={() => (showVerifcationDropdown = !showVerifcationDropdown)}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem icon="mail" on:click={() => updateVerificationEmail()}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} email
|
||||
</DropListItem>
|
||||
<DropListItem icon="phone" on:click={() => updateVerificationPhone()}>
|
||||
{$user.phoneVerification ? 'Unverify' : 'Verify'} phone
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{:else if $user.email}
|
||||
<Button secondary on:click={() => updateVerificationEmail()}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>
|
||||
{:else if $user.phone}
|
||||
<Button secondary on:click={() => updateVerificationPhone()}>
|
||||
{$user.phoneVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<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={userName} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={userName === $user.name} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateEmail}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Email</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputEmail
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autocomplete={false}
|
||||
bind:value={userEmail} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={userEmail === $user.email} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updatePhone}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Phone</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputPhone
|
||||
id="phone"
|
||||
label="Phone"
|
||||
placeholder="Enter phone number"
|
||||
autocomplete={false}
|
||||
bind:value={userPhone} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={userPhone === $user.phone} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updatePassword}>
|
||||
<CardGrid>
|
||||
<div>
|
||||
<Heading tag="h6" size="7">Update Password</Heading>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Enter a new password. A password must contain <b>at least 8 characters.</b>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputPassword
|
||||
id="newPassword"
|
||||
label="New Password"
|
||||
placeholder="Enter new password"
|
||||
autocomplete={false}
|
||||
meter={false}
|
||||
showPasswordButton={true}
|
||||
bind:value={newPassword} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={!newPassword} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updatePrefs}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">User Preferences</Heading>
|
||||
<p>
|
||||
You can update your user preferences by storing information on the user's objects so
|
||||
they can easily be shared across devices and sessions.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<form class="form u-grid u-gap-16">
|
||||
<ul class="form-list">
|
||||
{#if prefs}
|
||||
{#each prefs as [key, value], index}
|
||||
<li class="form-item is-multiple">
|
||||
<div class="form-item-part u-stretch">
|
||||
<label class="label" for={`key-${index}`}>Key</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
id={`key-${key}`}
|
||||
placeholder="Enter key"
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value={key} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item-part u-stretch">
|
||||
<label class="label" for={`value-${index}`}> Value </label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
id={`value-${value}`}
|
||||
placeholder="Enter value"
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item-part u-cross-child-end">
|
||||
<Button
|
||||
text
|
||||
disabled={(!key || !value) && index === 0}
|
||||
on:click={() => {
|
||||
if (index === 0) {
|
||||
prefs = [['', '']];
|
||||
} else {
|
||||
prefs.splice(index, 1);
|
||||
prefs = prefs;
|
||||
}
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
<Button
|
||||
noMargin
|
||||
text
|
||||
on:click={() => {
|
||||
if (prefs[prefs.length - 1][0] && prefs[prefs.length - 1][1]) {
|
||||
prefs.push(['', '']);
|
||||
prefs = prefs;
|
||||
}
|
||||
}}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Add Preference</span>
|
||||
</Button>
|
||||
</form>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={arePrefsDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<CardGrid danger>
|
||||
<div>
|
||||
<Heading tag="h6" size="7">Danger Zone</Heading>
|
||||
</div>
|
||||
<p>
|
||||
The user will be permanently deleted, including all data associated with this user. This
|
||||
action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="image">
|
||||
{#if $user.email || $user.phone}
|
||||
{#if $user.name}
|
||||
<AvatarInitials size={48} name={$user.name} />
|
||||
{:else}
|
||||
<div class="avatar">
|
||||
<span class="icon-minus-sm" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="avatar">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">
|
||||
{$user.name || $user.email || $user.phone || 'Anonymous'}
|
||||
</h6>
|
||||
</svelte:fragment>
|
||||
<p class="u-trim-1">
|
||||
{$user.email && $user.phone
|
||||
? [$user.email, $user.phone].join(',')
|
||||
: $user.email || $user.phone}
|
||||
</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)} event="delete_user"
|
||||
>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<UpdateStatus />
|
||||
<UpdateName />
|
||||
<UpdateEmail />
|
||||
<UpdatePhone />
|
||||
<UpdatePassword />
|
||||
<UpdatePrefs />
|
||||
<DangerZone />
|
||||
</Container>
|
||||
|
||||
<DeleteUser bind:showDelete />
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid, Box, Heading, AvatarInitials } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import DeleteUser from './deleteUser.svelte';
|
||||
import { user } from './store';
|
||||
|
||||
let showDelete = false;
|
||||
</script>
|
||||
|
||||
<CardGrid danger>
|
||||
<div>
|
||||
<Heading tag="h6" size="7">Danger Zone</Heading>
|
||||
</div>
|
||||
<p>
|
||||
The user will be permanently deleted, including all data associated with this user. This
|
||||
action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="image">
|
||||
{#if $user.email || $user.phone}
|
||||
{#if $user.name}
|
||||
<AvatarInitials size={48} name={$user.name} />
|
||||
{:else}
|
||||
<div class="avatar">
|
||||
<span class="icon-minus-sm" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="avatar">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">
|
||||
{$user.name || $user.email || $user.phone || 'Anonymous'}
|
||||
</h6>
|
||||
</svelte:fragment>
|
||||
<p class="u-trim-1">
|
||||
{$user.email && $user.phone
|
||||
? [$user.email, $user.phone].join(',')
|
||||
: $user.email || $user.phone}
|
||||
</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)} event="delete_user">Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<DeleteUser bind:showDelete />
|
||||
@@ -72,7 +72,7 @@
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href="https://appwrite.io/docs/server/auth?sdk=nodejs-default#usersGetMemberships">
|
||||
href="https://appwrite.io/docs/server/users?sdk=nodejs-default#usersListMemberships">
|
||||
Documentation
|
||||
</Button>
|
||||
</Empty>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputEmail } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { user } from './store';
|
||||
|
||||
let userEmail: string = null;
|
||||
onMount(async () => {
|
||||
userEmail ??= $user.email;
|
||||
});
|
||||
|
||||
async function updateEmail() {
|
||||
try {
|
||||
await sdkForProject.users.updateEmail($user.$id, userEmail);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: 'Email has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_email');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateEmail}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Email</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputEmail
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autocomplete={false}
|
||||
bind:value={userEmail} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={userEmail === $user.email} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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 { user } from './store';
|
||||
|
||||
let userName: string = null;
|
||||
onMount(async () => {
|
||||
userName ??= $user.name;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.users.updateName($user.$id, userName);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_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={userName} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={userName === $user.name} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script lang="ts">
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Button, Form, InputPassword } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { user } from './store';
|
||||
|
||||
let newPassword: string = null;
|
||||
|
||||
async function updatePassword() {
|
||||
try {
|
||||
await sdkForProject.users.updatePassword($user.$id, newPassword);
|
||||
newPassword = null;
|
||||
addNotification({
|
||||
message: 'Password has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_password');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updatePassword}>
|
||||
<CardGrid>
|
||||
<div>
|
||||
<Heading tag="h6" size="7">Update Password</Heading>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Enter a new password. A password must contain <b>at least 8 characters.</b>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputPassword
|
||||
id="newPassword"
|
||||
label="New Password"
|
||||
placeholder="Enter new password"
|
||||
autocomplete={false}
|
||||
meter={false}
|
||||
showPasswordButton={true}
|
||||
bind:value={newPassword} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={!newPassword} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputPhone } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { user } from './store';
|
||||
|
||||
let userPhone: string = null;
|
||||
onMount(async () => {
|
||||
userPhone ??= $user.phone;
|
||||
});
|
||||
|
||||
async function updatePhone() {
|
||||
try {
|
||||
await sdkForProject.users.updatePhone($user.$id, userPhone);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: 'Phone has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_phone');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updatePhone}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Phone</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputPhone
|
||||
id="phone"
|
||||
label="Phone"
|
||||
placeholder="Enter phone number"
|
||||
autocomplete={false}
|
||||
bind:value={userPhone} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={userPhone === $user.phone} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -0,0 +1,129 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { user } from './store';
|
||||
|
||||
$: if (prefs) {
|
||||
if (JSON.stringify(prefs) !== JSON.stringify(Object.entries($user.prefs))) {
|
||||
if (!!prefs[prefs.length - 1][0] && !!prefs[prefs.length - 1][1]) {
|
||||
arePrefsDisabled = false;
|
||||
} else {
|
||||
arePrefsDisabled = true;
|
||||
}
|
||||
} else {
|
||||
arePrefsDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
let prefs: [string, unknown][] = null;
|
||||
let arePrefsDisabled = true;
|
||||
|
||||
onMount(async () => {
|
||||
prefs = Object.entries($user.prefs);
|
||||
if (!prefs?.length) {
|
||||
prefs.push(['', '']);
|
||||
}
|
||||
});
|
||||
|
||||
async function updatePrefs() {
|
||||
try {
|
||||
let updatedPrefs = Object.fromEntries(prefs);
|
||||
|
||||
await sdkForProject.users.updatePrefs($user.$id, updatedPrefs);
|
||||
invalidate(Dependencies.USER);
|
||||
arePrefsDisabled = true;
|
||||
|
||||
addNotification({
|
||||
message: 'Preferences have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_preferences');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updatePrefs}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">User Preferences</Heading>
|
||||
<p>
|
||||
You can update your user preferences by storing information on the user's objects so
|
||||
they can easily be shared across devices and sessions.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<form class="form u-grid u-gap-16">
|
||||
<ul class="form-list">
|
||||
{#if prefs}
|
||||
{#each prefs as [key, value], index}
|
||||
<li class="form-item is-multiple">
|
||||
<div class="form-item-part u-stretch">
|
||||
<label class="label" for={`key-${index}`}>Key</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
id={`key-${key}`}
|
||||
placeholder="Enter key"
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value={key} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item-part u-stretch">
|
||||
<label class="label" for={`value-${index}`}> Value </label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
id={`value-${value}`}
|
||||
placeholder="Enter value"
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item-part u-cross-child-end">
|
||||
<Button
|
||||
text
|
||||
disabled={(!key || !value) && index === 0}
|
||||
on:click={() => {
|
||||
if (index === 0) {
|
||||
prefs = [['', '']];
|
||||
} else {
|
||||
prefs.splice(index, 1);
|
||||
prefs = prefs;
|
||||
}
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
<Button
|
||||
noMargin
|
||||
text
|
||||
on:click={() => {
|
||||
if (prefs[prefs.length - 1][0] && prefs[prefs.length - 1][1]) {
|
||||
prefs.push(['', '']);
|
||||
prefs = prefs;
|
||||
}
|
||||
}}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Add Preference</span>
|
||||
</Button>
|
||||
</form>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={arePrefsDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -0,0 +1,157 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { AvatarInitials, CardGrid, DropList, DropListItem, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { user } from './store';
|
||||
|
||||
let showVerifcationDropdown = false;
|
||||
|
||||
async function updateVerificationEmail() {
|
||||
showVerifcationDropdown = false;
|
||||
try {
|
||||
await sdkForProject.users.updateEmailVerification($user.$id, !$user.emailVerification);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.emailVerification ? 'unverified' : 'verified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_verification_email');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateVerificationPhone() {
|
||||
showVerifcationDropdown = false;
|
||||
try {
|
||||
await sdkForProject.users.updatePhoneVerification($user.$id, !$user.phoneVerification);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.phoneVerification ? 'unverified' : 'verified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_verification_phone');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateStatus() {
|
||||
try {
|
||||
await sdkForProject.users.updateStatus($user.$id, !$user.status);
|
||||
invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.status ? 'blocked' : 'unblocked'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_user_update_status');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16">
|
||||
{#if $user.email || $user.phone}
|
||||
{#if $user.name}
|
||||
<AvatarInitials size={48} name={$user.name} />
|
||||
<Heading tag="h6" size="7">{$user.name}</Heading>
|
||||
{:else}
|
||||
<div class="avatar">
|
||||
<span class="icon-minus-sm" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="avatar">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<svelte:fragment slot="aside">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<div>
|
||||
{#if $user.email}
|
||||
<p class="title">{$user.email}</p>
|
||||
{/if}
|
||||
{#if $user.phone}
|
||||
<p class="title">{$user.phone}</p>
|
||||
{/if}
|
||||
<p>Joined: {toLocaleDateTime($user.registration)}</p>
|
||||
</div>
|
||||
{#if !$user.status}
|
||||
<Pill danger>blocked</Pill>
|
||||
{:else if $user.email && $user.phone}
|
||||
<Pill success={$user.emailVerification || $user.phoneVerification}>
|
||||
{$user.emailVerification && $user.phoneVerification
|
||||
? 'verified'
|
||||
: $user.emailVerification
|
||||
? 'verified email'
|
||||
: $user.phoneVerification
|
||||
? 'verified phone'
|
||||
: 'unverified'}
|
||||
</Pill>
|
||||
{:else}
|
||||
<Pill success={$user.emailVerification || $user.phoneVerification}>
|
||||
{$user.emailVerification
|
||||
? 'verified '
|
||||
: $user.phoneVerification
|
||||
? 'verified '
|
||||
: 'unverified'}
|
||||
</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button text={$user.status} secondary={!$user.status} on:click={() => updateStatus()}>
|
||||
{$user.status ? 'Block account' : 'Unblock account'}
|
||||
</Button>
|
||||
{#if $user.status}
|
||||
{#if $user.phone && $user.email}
|
||||
<DropList bind:show={showVerifcationDropdown} placement="top-start">
|
||||
<Button
|
||||
secondary
|
||||
on:click={() => (showVerifcationDropdown = !showVerifcationDropdown)}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem icon="mail" on:click={() => updateVerificationEmail()}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} email
|
||||
</DropListItem>
|
||||
<DropListItem icon="phone" on:click={() => updateVerificationPhone()}>
|
||||
{$user.phoneVerification ? 'Unverify' : 'Verify'} phone
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{:else if $user.email}
|
||||
<Button secondary on:click={() => updateVerificationEmail()}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>
|
||||
{:else if $user.phone}
|
||||
<Button secondary on:click={() => updateVerificationPhone()}>
|
||||
{$user.phoneVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
@@ -63,9 +63,11 @@
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Type">{attribute.type}</TableCellText>
|
||||
<TableCellText title="Type">
|
||||
{`${attribute.type}${attribute.array ? '[]' : ''}`}
|
||||
</TableCellText>
|
||||
<TableCellText title="Default Value">
|
||||
{attribute.default ? attribute.default : '-'}
|
||||
{attribute.default !== null ? attribute.default : '-'}
|
||||
</TableCellText>
|
||||
<TableCell showOverflow>
|
||||
<DropList
|
||||
@@ -86,12 +88,14 @@
|
||||
on:click={() => {
|
||||
selectedAttribute = attribute;
|
||||
showOverview = true;
|
||||
showDropdown[index] = false;
|
||||
}}>Overview</DropListItem>
|
||||
<DropListItem
|
||||
icon="plus"
|
||||
on:click={() => {
|
||||
selectedAttribute = attribute;
|
||||
showCreateIndex = true;
|
||||
showDropdown[index] = false;
|
||||
}}>Create Index</DropListItem>
|
||||
<DropListItem
|
||||
icon="trash"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
data.required,
|
||||
data.min,
|
||||
data.max,
|
||||
data.default ? data.default : undefined,
|
||||
typeof data.default === 'number' ? data.default : undefined,
|
||||
data.array
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
data.required,
|
||||
data.min,
|
||||
data.max,
|
||||
data.default ? data.default : undefined,
|
||||
typeof data.default === 'number' ? data.default : undefined,
|
||||
data.array
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import { createDocument } from './wizard/store';
|
||||
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
const collectionId = $page.params.collection;
|
||||
@@ -42,6 +43,7 @@
|
||||
message: 'Document has been created',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_document_create');
|
||||
invalidate(Dependencies.DOCUMENTS);
|
||||
wizard.hide();
|
||||
} catch (error) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import Attribute from './attribute.svelte';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
let disableUpdate = true;
|
||||
let currentDoc: string;
|
||||
@@ -61,6 +62,9 @@
|
||||
|
||||
currentDoc = JSON.stringify($work);
|
||||
invalidate(Dependencies.DOCUMENT);
|
||||
trackEvent('submit_document_update', {
|
||||
type: 'android'
|
||||
});
|
||||
disableUpdate = true;
|
||||
addNotification({
|
||||
message: 'Document was updated!',
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button, InputText, FormList, InputSelect } from '$lib/elements/forms';
|
||||
import { collection } from '../store';
|
||||
import { onMount } from 'svelte';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import type { Attributes } from '../store';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import Select from './select.svelte';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, FormList, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import { remove } from '$lib/helpers/array';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import type { Attributes } from '../store';
|
||||
import { collection } from '../store';
|
||||
import Select from './select.svelte';
|
||||
|
||||
export let showCreateIndex = false;
|
||||
export let externalAttribute: Attributes = null;
|
||||
@@ -24,115 +25,91 @@
|
||||
{ value: 'unique', label: 'Unique' },
|
||||
{ value: 'fulltext', label: 'FullText' }
|
||||
];
|
||||
let newAttr = false;
|
||||
let selectedType = 'key';
|
||||
$: attributeOptions = $collection.attributes.map((attribute: Attributes) => ({
|
||||
|
||||
let attributeOptions = $collection.attributes.map((attribute: Attributes) => ({
|
||||
value: attribute.key,
|
||||
label: attribute.key
|
||||
}));
|
||||
$: attributeList = [];
|
||||
let attributeList = [{ value: '', order: '' }];
|
||||
let creating = false;
|
||||
|
||||
let selectedAttribute = '';
|
||||
let selectedOrder = '';
|
||||
|
||||
onMount(() => {
|
||||
if (externalAttribute) {
|
||||
attributeList = [{ value: externalAttribute.key, order: 'ASC' }];
|
||||
}
|
||||
});
|
||||
|
||||
$: if (showCreateIndex) {
|
||||
attributeList = [];
|
||||
selectedOrder = selectedAttribute = '';
|
||||
function initialize() {
|
||||
attributeList = externalAttribute
|
||||
? [{ value: externalAttribute.key, order: 'ASC' }]
|
||||
: [{ value: '', order: '' }];
|
||||
selectedType = 'key';
|
||||
key = null;
|
||||
}
|
||||
const created = async () => {
|
||||
if (key && selectedAttribute && selectedOrder && selectedType) {
|
||||
if (selectedAttribute && selectedOrder) {
|
||||
attributeList.push({ value: selectedAttribute, order: selectedOrder });
|
||||
selectedAttribute = selectedOrder = '';
|
||||
}
|
||||
try {
|
||||
await sdkForProject.databases.createIndex(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
key,
|
||||
selectedType,
|
||||
attributeList.map((a) => a.value),
|
||||
attributeList.map((a) => a.order)
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
addNotification({
|
||||
message: 'Index has been created',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_index_create');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
$: if (showCreateIndex) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
$: addAttributeDisabled =
|
||||
!attributeList.at(-1)?.value || !attributeList.at(-1)?.order || creating;
|
||||
|
||||
async function create() {
|
||||
if (!(key && selectedType && !addAttributeDisabled)) {
|
||||
error = 'All fields are required';
|
||||
return;
|
||||
}
|
||||
|
||||
creating = true;
|
||||
|
||||
try {
|
||||
await sdkForProject.databases.createIndex(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
key,
|
||||
selectedType,
|
||||
attributeList.map((a) => a.value),
|
||||
attributeList.map((a) => a.order)
|
||||
);
|
||||
await Promise.allSettled([
|
||||
invalidate(Dependencies.COLLECTION),
|
||||
invalidate(Dependencies.DATABASE)
|
||||
]);
|
||||
|
||||
goto(
|
||||
`${base}/console/project-${$page.params.project}/databases/database-${databaseId}/collection-${$collection.$id}/indexes`
|
||||
);
|
||||
|
||||
addNotification({
|
||||
message: 'Index has been created',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_index_create');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
} finally {
|
||||
showCreateIndex = false;
|
||||
} else error = 'All fields are required';
|
||||
};
|
||||
creating = false;
|
||||
}
|
||||
}
|
||||
|
||||
function addAttribute() {
|
||||
if (addAttributeDisabled) return;
|
||||
|
||||
// We assign instead of pushing to trigger Svelte's reactivity
|
||||
attributeList = [...attributeList, { value: '', order: '' }];
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:error size="big" on:submit={created} bind:show={showCreateIndex}>
|
||||
<Modal bind:error size="big" on:submit={create} bind:show={showCreateIndex}>
|
||||
<svelte:fragment slot="header">Create Index</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText id="key" label="Index Key" placeholder="Enter Key" bind:value={key} autofocus />
|
||||
<InputSelect options={types} id="type" label="Index type" bind:value={selectedType} />
|
||||
|
||||
{#if attributeList?.length}
|
||||
{#each attributeList as index, i}
|
||||
<li class="form-item is-multiple">
|
||||
<div class="form-item-part u-stretch">
|
||||
<Select id="attribute" label="Attribute" bind:value={index.value}>
|
||||
<optgroup label="Internal">
|
||||
<option value="$id">$id</option>
|
||||
<option value="$createdAt">$createdAt</option>
|
||||
<option value="$updatedAt">$updatedAt</option>
|
||||
</optgroup>
|
||||
<optgroup label="Attributes">
|
||||
{#each attributeOptions as option}
|
||||
<option
|
||||
value={option.value}
|
||||
selected={option.value === selectedAttribute}>
|
||||
{option.label}
|
||||
</option>
|
||||
{/each}
|
||||
</optgroup>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="form-item-part u-stretch">
|
||||
<Select id="order" label="Order" bind:value={index.order}>
|
||||
<option value="ASC"> ASC </option>
|
||||
<option value="DESC"> DESC </option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="form-item-part u-cross-child-end">
|
||||
<Button
|
||||
text
|
||||
disabled={externalAttribute && i === 0}
|
||||
on:click={() => {
|
||||
if (i === 0) attributeList = [];
|
||||
attributeList = attributeList.splice(i, 1);
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if !attributeList?.length || newAttr}
|
||||
{#each attributeList as attribute, i}
|
||||
<li class="form-item is-multiple">
|
||||
<div class="form-item-part u-stretch" style="align-items: flex-start;">
|
||||
<Select id="attribute" label="Attribute" bind:value={selectedAttribute}>
|
||||
<option value="" disabled selected hidden>Select Attribute</option>
|
||||
<div class="form-item-part u-stretch">
|
||||
<Select id={`attribute-${i}`} label="Attribute" bind:value={attribute.value}>
|
||||
<option value="" disabled hidden>Select Attribute</option>
|
||||
|
||||
<optgroup label="Internal">
|
||||
<option value="$id">$id</option>
|
||||
@@ -141,9 +118,7 @@
|
||||
</optgroup>
|
||||
<optgroup label="Attributes">
|
||||
{#each attributeOptions as option}
|
||||
<option
|
||||
value={option.value}
|
||||
selected={option.value === selectedAttribute}>
|
||||
<option value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
{/each}
|
||||
@@ -151,42 +126,34 @@
|
||||
</Select>
|
||||
</div>
|
||||
<div class="form-item-part u-stretch">
|
||||
<Select id="order" label="Order" bind:value={selectedOrder}>
|
||||
<option value="" disabled selected hidden>Select Order</option>
|
||||
<Select id={`order-${i}`} label="Order" bind:value={attribute.order}>
|
||||
<option value="" disabled hidden>Select Order</option>
|
||||
|
||||
<option value="ASC"> ASC </option>
|
||||
<option value="DESC"> DESC </option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="form-item-part u-cross-child-end">
|
||||
<Button
|
||||
text
|
||||
disabled={false}
|
||||
disabled={attributeList.length <= 1}
|
||||
on:click={() => {
|
||||
newAttr = false;
|
||||
selectedAttribute = selectedOrder = '';
|
||||
attributeList = remove(attributeList, i);
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
{/if}
|
||||
<Button
|
||||
text
|
||||
noMargin
|
||||
on:click={() => {
|
||||
newAttr = true;
|
||||
if (selectedAttribute && selectedOrder) {
|
||||
attributeList.push({ value: selectedAttribute, order: selectedOrder });
|
||||
selectedAttribute = selectedOrder = '';
|
||||
}
|
||||
}}>
|
||||
{/each}
|
||||
|
||||
<Button text noMargin on:click={addAttribute} disabled={addAttributeDisabled}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Add attribute</span>
|
||||
</Button>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showCreateIndex = false)}>Cancel</Button>
|
||||
<Button submit>Create</Button>
|
||||
<Button submit disabled={creating}>Create</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -1,257 +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
|
||||
);
|
||||
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
|
||||
);
|
||||
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
|
||||
);
|
||||
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 />
|
||||
|
||||
@@ -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 />
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -16,14 +16,19 @@
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const handleVariable = () => {
|
||||
function close() {
|
||||
showCreate = false;
|
||||
selectedVar = null;
|
||||
}
|
||||
|
||||
function handleVariable() {
|
||||
if (selectedVar) {
|
||||
dispatch('updated', pair);
|
||||
} else {
|
||||
dispatch('created', pair);
|
||||
}
|
||||
showCreate = false;
|
||||
};
|
||||
close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show={showCreate} on:submit={handleVariable} size="big">
|
||||
@@ -48,7 +53,7 @@
|
||||
required />
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
|
||||
<Button secondary on:click={close}>Cancel</Button>
|
||||
<Button submit>{selectedVar ? 'Update' : 'Create'}</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -62,7 +62,15 @@
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.functions.update(functionId, functionName, $func.execute);
|
||||
await sdkForProject.functions.update(
|
||||
functionId,
|
||||
functionName,
|
||||
$func.execute,
|
||||
$func.events,
|
||||
$func.schedule,
|
||||
$func.timeout,
|
||||
$func.enabled
|
||||
);
|
||||
invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
@@ -79,7 +87,15 @@
|
||||
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdkForProject.functions.update(functionId, $func.name, permissions);
|
||||
await sdkForProject.functions.update(
|
||||
functionId,
|
||||
$func.name,
|
||||
permissions,
|
||||
$func.events,
|
||||
$func.schedule,
|
||||
$func.timeout,
|
||||
$func.enabled
|
||||
);
|
||||
invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
message: 'Permissions have been updated',
|
||||
@@ -100,7 +116,10 @@
|
||||
functionId,
|
||||
$func.name,
|
||||
$func.execute,
|
||||
Array.from($eventSet)
|
||||
Array.from($eventSet),
|
||||
$func.schedule,
|
||||
$func.timeout,
|
||||
$func.enabled
|
||||
);
|
||||
invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
@@ -124,7 +143,8 @@
|
||||
$func.execute,
|
||||
$func.events,
|
||||
functionSchedule,
|
||||
timeout
|
||||
$func.timeout,
|
||||
$func.enabled
|
||||
);
|
||||
invalidate(Dependencies.FUNCTION);
|
||||
|
||||
@@ -149,9 +169,11 @@
|
||||
$func.execute,
|
||||
$func.events,
|
||||
$func.schedule,
|
||||
timeout
|
||||
timeout,
|
||||
$func.enabled
|
||||
);
|
||||
|
||||
invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Timeout has been updated'
|
||||
@@ -570,10 +592,12 @@
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
<Variable
|
||||
bind:selectedVar
|
||||
bind:showCreate={showVariablesModal}
|
||||
on:created={handleVariableCreated}
|
||||
on:updated={handleVariableUpdated} />
|
||||
{#if showVariablesModal}
|
||||
<Variable
|
||||
bind:selectedVar
|
||||
bind:showCreate={showVariablesModal}
|
||||
on:created={handleVariableCreated}
|
||||
on:updated={handleVariableUpdated} />
|
||||
{/if}
|
||||
<!-- <Upload bind:show={showVariablesUpload} on:uploaded={handleVariableCreated} /> -->
|
||||
<EventModal bind:show={showEvents} on:created={handleEvent} />
|
||||
|
||||
@@ -2,5 +2,5 @@ import { page } from '$app/stores';
|
||||
import { derived, writable, type Writable } from 'svelte/store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
export const func = derived(page, ($page) => $page.data.function);
|
||||
export const func = derived(page, ($page) => $page.data.function as Models.Function);
|
||||
export const execute: Writable<Models.Function> = writable();
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { key } from './store';
|
||||
import Scopes from '../scopes.svelte';
|
||||
import Delete from './delete.svelte';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
let showDelete = false;
|
||||
let name: string = null;
|
||||
@@ -37,6 +38,7 @@
|
||||
$key.expire
|
||||
);
|
||||
invalidate(Dependencies.KEY);
|
||||
trackEvent('submit_key_update_name');
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'API Key name has been updated'
|
||||
@@ -59,6 +61,7 @@
|
||||
expire
|
||||
);
|
||||
invalidate(Dependencies.KEY);
|
||||
trackEvent('submit_key_update_expire');
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'API Key expiration has been updated'
|
||||
@@ -73,8 +76,15 @@
|
||||
|
||||
async function updateScopes() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateKey($project.$id, $key.$id, $key.name, scopes);
|
||||
await sdkForConsole.projects.updateKey(
|
||||
$project.$id,
|
||||
$key.$id,
|
||||
$key.name,
|
||||
scopes,
|
||||
$key.expire
|
||||
);
|
||||
invalidate(Dependencies.KEY);
|
||||
trackEvent('submit_key_update_scopes');
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'API Key scopes has been updated'
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import { onDestroy } from 'svelte';
|
||||
import { onboarding } from '../../store';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
async function onFinish() {
|
||||
try {
|
||||
@@ -24,6 +25,7 @@
|
||||
if ($onboarding) {
|
||||
invalidate(Dependencies.PROJECT);
|
||||
}
|
||||
trackEvent('submit_key_create');
|
||||
goto(`/console/project-${$page.params.project}/overview/keys/${$id}`);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
|
||||
@@ -48,7 +48,14 @@
|
||||
|
||||
const updateName = async () => {
|
||||
try {
|
||||
await sdkForConsole.projects.updatePlatform($project.$id, data.platform.$id, name);
|
||||
await sdkForConsole.projects.updatePlatform(
|
||||
$project.$id,
|
||||
data.platform.$id,
|
||||
name,
|
||||
data.platform.key,
|
||||
data.platform.store,
|
||||
data.platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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';
|
||||
@@ -9,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 () => {
|
||||
@@ -21,11 +22,14 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
undefined,
|
||||
undefined,
|
||||
hostname
|
||||
key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
type: 'android'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Platform Package Name has been updated'
|
||||
@@ -48,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>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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';
|
||||
@@ -21,9 +22,14 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
key
|
||||
key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
type: 'apple-ios'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Platform Bundle ID has been updated'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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';
|
||||
@@ -21,9 +22,14 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
key
|
||||
key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
type: 'apple-macos'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Platform Bundle ID has been updated'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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';
|
||||
@@ -21,9 +22,14 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
key
|
||||
key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
type: 'apple-tvos'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Platform Bundle ID has been updated'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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';
|
||||
@@ -21,9 +22,14 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
key
|
||||
key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
type: 'apple-watchos'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Platform Bundle ID has been updated'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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';
|
||||
@@ -21,9 +22,14 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
key
|
||||
key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
type: 'flutter-android'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Platform Package Name has been updated'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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';
|
||||
@@ -21,9 +22,14 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
key
|
||||
key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
type: 'flutter-ios'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Platform Bundle ID has been updated'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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';
|
||||
@@ -21,9 +22,14 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
key
|
||||
key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
type: 'flutter-linux'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Platform Package Name has been updated'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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';
|
||||
@@ -21,9 +22,14 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
key
|
||||
key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
type: 'flutter-macos'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Platform Bundle ID has been updated'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
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';
|
||||
@@ -21,9 +22,14 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
key
|
||||
key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
type: 'flutter-windows'
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Platform Package Name has been updated'
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
undefined,
|
||||
undefined,
|
||||
$platform.key,
|
||||
$platform.store,
|
||||
hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
projectId,
|
||||
$createPlatform.$id,
|
||||
$createPlatform.name,
|
||||
undefined,
|
||||
undefined,
|
||||
$createPlatform.key,
|
||||
$createPlatform.store,
|
||||
$createPlatform.hostname
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
projectId,
|
||||
$createPlatform.$id,
|
||||
$createPlatform.name,
|
||||
undefined,
|
||||
undefined,
|
||||
$createPlatform.key,
|
||||
$createPlatform.store,
|
||||
$createPlatform.hostname
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
{
|
||||
@@ -85,8 +85,8 @@
|
||||
projectId,
|
||||
$createPlatform.$id,
|
||||
$createPlatform.name,
|
||||
undefined,
|
||||
undefined,
|
||||
$createPlatform.key,
|
||||
$createPlatform.store,
|
||||
$createPlatform.hostname
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -8,6 +8,8 @@ function createPlatformStore() {
|
||||
$id: null,
|
||||
name: null,
|
||||
hostname: null,
|
||||
key: null,
|
||||
store: null,
|
||||
type: null
|
||||
});
|
||||
|
||||
@@ -20,6 +22,8 @@ function createPlatformStore() {
|
||||
$id: null,
|
||||
name: null,
|
||||
hostname: null,
|
||||
key: null,
|
||||
store: null,
|
||||
type: null
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
@@ -21,8 +21,8 @@
|
||||
projectId,
|
||||
$createPlatform.$id,
|
||||
$createPlatform.name,
|
||||
undefined,
|
||||
undefined,
|
||||
$createPlatform.key,
|
||||
$createPlatform.store,
|
||||
$createPlatform.hostname
|
||||
);
|
||||
|
||||
|
||||
@@ -12,8 +12,12 @@
|
||||
|
||||
let method: Method = Method.NPM;
|
||||
|
||||
const example1 = `import { Appwrite } from 'appwrite';`;
|
||||
const example2 = `<script src="https://cdn.jsdelivr.net/npm/appwrite@${$versions['client-web']}" />`;
|
||||
const example1 = `import { Client } from 'appwrite';`;
|
||||
const example2 =
|
||||
`<script src="https://cdn.jsdelivr.net/npm/appwrite@${$versions['client-web']}" />
|
||||
<script>
|
||||
const { Client } = Appwrite;
|
||||
</script` + `>`; // Prevent svelte from processing the closing script tag
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
|
||||
const { endpoint, project } = sdkForProject.client.config;
|
||||
const code = `const appwrite = new Appwrite();
|
||||
const code = `const client = new Client();
|
||||
|
||||
appwrite
|
||||
client
|
||||
.setEndpoint('${endpoint}')
|
||||
.setProject('${project}');`;
|
||||
</script>
|
||||
|
||||
@@ -1,346 +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
|
||||
);
|
||||
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
|
||||
);
|
||||
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
|
||||
);
|
||||
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"
|
||||
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>
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import Delete from '../deleteBucket.svelte';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
let showDelete = false;
|
||||
|
||||
@@ -34,14 +36,21 @@
|
||||
arePermsDisabled = true,
|
||||
encryption: boolean = null,
|
||||
antivirus: boolean = null,
|
||||
maxSize: number;
|
||||
let byteUnit: 'Bytes' | 'KB' | 'MB' | 'GB' = 'MB',
|
||||
options = [
|
||||
{ label: 'Bytes', value: 'Bytes' },
|
||||
{ label: 'Kilobytes', value: 'KB' },
|
||||
{ label: 'Megabytes', value: 'MB' },
|
||||
{ label: 'Gigabytes', value: 'GB' }
|
||||
];
|
||||
maxSize: number,
|
||||
compression: string = null;
|
||||
let byteUnit = writable('MB');
|
||||
let sizeInBytes: number = null;
|
||||
const options = [
|
||||
{ label: 'Bytes', value: 'Bytes' },
|
||||
{ label: 'Kilobytes', value: 'KB' },
|
||||
{ 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;
|
||||
@@ -54,7 +63,10 @@
|
||||
bucketPermissions ??= $bucket.$permissions;
|
||||
encryption ??= $bucket.encryption;
|
||||
antivirus ??= $bucket.antivirus;
|
||||
maxSize = $bucket.maximumFileSize / 1024 / 1024;
|
||||
compression ??= $bucket.compression;
|
||||
});
|
||||
|
||||
$: if (bucketPermissions) {
|
||||
if (symmetricDifference(bucketPermissions, $bucket.$permissions).length) {
|
||||
arePermsDisabled = false;
|
||||
@@ -66,78 +78,44 @@
|
||||
} else isExtensionsDisabled = true;
|
||||
}
|
||||
|
||||
async function toggleBucket() {
|
||||
type TUpdateBucketMisc = {
|
||||
successMessage?: string;
|
||||
trackEventName: string;
|
||||
trackEventData?: { value: boolean };
|
||||
arePermsDisabled?: boolean;
|
||||
};
|
||||
|
||||
async function updateBucket(updates: Partial<Models.Bucket>, misc: TUpdateBucketMisc) {
|
||||
const values = { ...$bucket, ...updates };
|
||||
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
$bucket.name,
|
||||
undefined,
|
||||
undefined,
|
||||
enabled
|
||||
values.$id,
|
||||
values.name,
|
||||
values.$permissions,
|
||||
values.fileSecurity,
|
||||
values.enabled,
|
||||
values.maximumFileSize,
|
||||
values.allowedFileExtensions,
|
||||
values.compression,
|
||||
values.encryption,
|
||||
values.antivirus
|
||||
);
|
||||
|
||||
invalidate(Dependencies.BUCKET);
|
||||
|
||||
if (misc.arePermsDisabled !== undefined) {
|
||||
arePermsDisabled = misc.arePermsDisabled;
|
||||
}
|
||||
|
||||
addNotification({
|
||||
message: `${$bucket.name} has been updated`,
|
||||
message: misc.successMessage ?? `${$bucket.name} has been updated`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_bucket_enable', {
|
||||
value: enabled
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket($bucket.$id, bucketName);
|
||||
invalidate(Dependencies.BUCKET);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_bucket_update_name');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket($bucket.$id, $bucket.name, bucketPermissions);
|
||||
invalidate(Dependencies.BUCKET);
|
||||
arePermsDisabled = true;
|
||||
addNotification({
|
||||
message: 'Permissions have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_bucket_update_permissions');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateFileSecurity() {
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
$bucket.name,
|
||||
$bucket.$permissions,
|
||||
bucketFileSecurity
|
||||
);
|
||||
invalidate(Dependencies.BUCKET);
|
||||
arePermsDisabled = true;
|
||||
addNotification({
|
||||
message: 'Security has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_bucket_update_file_security');
|
||||
|
||||
if (misc.trackEventName) {
|
||||
trackEvent(misc.trackEventName, misc.trackEventData);
|
||||
}
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
@@ -146,81 +124,118 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
$bucket.name,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
function toggleBucket() {
|
||||
updateBucket(
|
||||
{
|
||||
enabled
|
||||
},
|
||||
{
|
||||
trackEventName: 'submit_bucket_enable',
|
||||
trackEventData: {
|
||||
value: enabled
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateName() {
|
||||
updateBucket(
|
||||
{
|
||||
name: bucketName
|
||||
},
|
||||
{
|
||||
successMessage: 'Name has been updated',
|
||||
trackEventName: 'submit_bucket_update_name'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updatePermissions() {
|
||||
updateBucket(
|
||||
{
|
||||
$permissions: bucketPermissions
|
||||
},
|
||||
{
|
||||
successMessage: 'Permissions have been updated',
|
||||
trackEventName: 'submit_bucket_update_permissions',
|
||||
arePermsDisabled: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateFileSecurity() {
|
||||
updateBucket(
|
||||
{
|
||||
fileSecurity: bucketFileSecurity
|
||||
},
|
||||
{
|
||||
successMessage: 'Security has been updated',
|
||||
trackEventName: 'submit_bucket_update_file_security',
|
||||
arePermsDisabled: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateSecurity() {
|
||||
updateBucket(
|
||||
{
|
||||
encryption,
|
||||
antivirus
|
||||
);
|
||||
invalidate(Dependencies.BUCKET);
|
||||
addNotification({
|
||||
message: `${$bucket.name} has been updated`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_bucket_update_security');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateMaxSize() {
|
||||
let size = sizeToBytes(maxSize, byteUnit);
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
$bucket.name,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
size
|
||||
);
|
||||
invalidate(Dependencies.BUCKET);
|
||||
addNotification({
|
||||
message: `${$bucket.name} has been updated`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_bucket_update_size');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
trackEventName: 'submit_bucket_update_security'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function updateAllowedExtensions() {
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
$bucket.name,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
extensions
|
||||
);
|
||||
invalidate(Dependencies.BUCKET);
|
||||
addNotification({
|
||||
message: `${$bucket.name} has been updated`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_bucket_update_extensions');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
function updateMaxSize() {
|
||||
const size = sizeToBytes(maxSize, $byteUnit);
|
||||
updateBucket(
|
||||
{
|
||||
maximumFileSize: size
|
||||
},
|
||||
{
|
||||
trackEventName: 'submit_bucket_update_size'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateCompression() {
|
||||
updateBucket(
|
||||
{
|
||||
compression
|
||||
},
|
||||
{
|
||||
trackEventName: 'submit_bucket_update_size'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateAllowedExtensions() {
|
||||
updateBucket(
|
||||
{
|
||||
allowedFileExtensions: extensions
|
||||
},
|
||||
{
|
||||
trackEventName: 'submit_bucket_update_extensions'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
byteUnit.subscribe((b) => {
|
||||
if (b === 'Bytes') {
|
||||
maxSize = sizeInBytes;
|
||||
} else if (b === 'KB') {
|
||||
maxSize = sizeInBytes / 1024;
|
||||
} else if (b === 'MB') {
|
||||
maxSize = sizeInBytes / 1024 / 1024;
|
||||
} else if (b === 'GB') {
|
||||
maxSize = sizeInBytes / 1024 / 1024 / 1024;
|
||||
}
|
||||
});
|
||||
|
||||
$: if (maxSize) {
|
||||
sizeInBytes = sizeToBytes(maxSize, $byteUnit);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -238,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>
|
||||
|
||||
@@ -279,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}
|
||||
@@ -323,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>
|
||||
@@ -384,7 +400,32 @@
|
||||
<Button
|
||||
disabled={encryption === $bucket.encryption &&
|
||||
antivirus === $bucket.antivirus}
|
||||
submit>Update</Button>
|
||||
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>
|
||||
@@ -392,20 +433,22 @@
|
||||
<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
|
||||
id="size"
|
||||
label="Size"
|
||||
placeholder="256"
|
||||
placeholder={$bucket.maximumFileSize.toString()}
|
||||
bind:value={maxSize} />
|
||||
<InputSelect id="bytes" label="Bytes" {options} bind:value={byteUnit} />
|
||||
<InputSelect id="bytes" label="Bytes" {options} bind:value={$byteUnit} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={!maxSize} submit>Update</Button>
|
||||
<Button disabled={sizeInBytes === $bucket.maximumFileSize} submit
|
||||
>Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.5466 17C20.686 16.7584 20.7727 16.4863 20.7727 16.2419V7.75808C20.7727 7.51367 20.6862 7.24157 20.5466 7.00001L11.8864 12L20.5466 17Z" fill="#00599C"/>
|
||||
<path d="M12.6559 21.8167L20.0031 17.5748C20.2147 17.4526 20.4071 17.2416 20.5465 17L11.8863 12L3.22607 17.0001C3.36553 17.2416 3.55795 17.4527 3.76954 17.5748L11.1167 21.8167C11.54 22.0611 12.2326 22.0611 12.6559 21.8167Z" fill="#004482"/>
|
||||
<path d="M20.5466 6.99999C20.4071 6.75838 20.2147 6.54737 20.0031 6.42517L12.6559 2.1833C12.2327 1.9389 11.54 1.9389 11.1168 2.1833L3.76957 6.42517C3.34628 6.66951 3 7.26937 3 7.75812V16.2419C3 16.4863 3.0866 16.7585 3.22611 17L11.8863 12L20.5466 6.99999Z" fill="#659AD2"/>
|
||||
<path d="M11.8864 17.9242C8.61976 17.9242 5.96216 15.2666 5.96216 12C5.96216 8.73336 8.61976 6.07576 11.8864 6.07576C13.9943 6.07576 15.9601 7.21008 17.0167 9.03602L14.4529 10.5196C13.924 9.60567 12.9406 9.03787 11.8864 9.03787C10.253 9.03787 8.92427 10.3666 8.92427 12C8.92427 13.6333 10.253 14.9621 11.8864 14.9621C12.9407 14.9621 13.9241 14.3943 14.453 13.4802L17.0169 14.9637C15.9603 16.7898 13.9944 17.9242 11.8864 17.9242Z" fill="white"/>
|
||||
<path d="M17.8106 11.671H17.1523V11.0128H16.4941V11.671H15.8358V12.3292H16.4941V12.9875H17.1523V12.3292H17.8106V11.671Z" fill="white"/>
|
||||
<path d="M20.2791 11.671H19.6208V11.0128H18.9626V11.671H18.3043V12.3292H18.9626V12.9875H19.6208V12.3292H20.2791V11.671Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="21" height="9" viewBox="0 0 21 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.5367 6.77066C1.44246 6.76892 1.35244 6.789 1.27079 6.82299L1.27077 6.82297L1.26809 6.82415C1.18619 6.86013 1.11066 6.91165 1.04778 6.97763C0.984416 7.04106 0.933719 7.11661 0.900246 7.20081L0.900241 7.2008L0.899816 7.2019C0.867868 7.28413 0.850324 7.37148 0.850116 7.46019M1.5367 6.77066C1.62683 6.77086 1.71694 6.78819 1.80221 6.82566C1.88344 6.86135 1.95839 6.91232 2.02096 6.97752C2.08783 7.04102 2.13744 7.11653 2.17282 7.19799L2.17349 7.19953L2.17348 7.19954C2.20882 7.28349 2.22627 7.37286 2.22627 7.4633C2.22627 7.55374 2.20882 7.64311 2.17348 7.72707C2.13908 7.80877 2.08878 7.88483 2.02167 7.94834C1.95873 8.01224 1.88363 8.06296 1.80079 8.09756L1.74298 7.95915L1.80079 8.09756C1.7175 8.13234 1.62807 8.15013 1.5378 8.1499M1.5367 6.77066C1.53724 6.77067 1.53778 6.77068 1.53832 6.7707L1.53515 6.92066V6.77066C1.53567 6.77066 1.53619 6.77066 1.5367 6.77066ZM1.5378 8.1499C1.44662 8.15149 1.35607 8.13418 1.27193 8.09904C1.1879 8.06394 1.11207 8.01181 1.0493 7.94594C0.985862 7.88244 0.935939 7.80764 0.901383 7.72555C0.866254 7.6421 0.848375 7.55224 0.850116 7.46019M1.5378 8.1499C1.53715 8.1499 1.5365 8.1499 1.53585 8.14989L1.53667 7.99989L1.53973 8.14986C1.53908 8.14988 1.53844 8.14989 1.5378 8.1499ZM0.850116 7.46019C0.850126 7.45965 0.850137 7.45912 0.850148 7.45858L1.00011 7.46179H0.850114C0.850114 7.46126 0.850115 7.46072 0.850116 7.46019ZM4.46673 7.90618V2.91575C4.46994 2.80356 4.47033 2.69143 4.46804 2.57937L7.96337 7.9876L8.0077 8.05618H8.08935H9.08495H9.23495V7.90618V1V0.85H9.08495H8.27631H8.12631V1V5.85487C8.12306 6.00128 8.12426 6.14793 8.12979 6.29447L4.68624 0.919087L4.64198 0.85H4.55993H3.5081H3.3581V1V7.90618V8.05618H3.5081H4.31673H4.46673V7.90618ZM14.7176 7.14829L14.7185 6.99739H14.5676H11.86V4.90464H14.2104H14.3604V4.75464V4.02608V3.87608H14.2104H11.86V1.88158H14.3928H14.5428V1.73158V1V0.85H14.3928H10.8816H10.7316V1V7.90618V8.05618H10.8816H14.563H14.7121L14.713 7.90708L14.7176 7.14829ZM15.1862 0.85H15.0362V1V1.73158V1.88158H15.1862H17.0274V7.90618V8.05618H17.1774H17.986H18.136V7.90618V1.88158H20H20.15V1.73158V1V0.85H20H15.1862Z" fill="white" stroke="white" stroke-width="0.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5466 6.99999C20.4071 6.75838 20.2147 6.54737 20.0031 6.42517L12.6559 2.1833C12.2327 1.9389 11.54 1.9389 11.1168 2.1833L3.76957 6.42517C3.34628 6.66951 3 7.26937 3 7.75812V16.2419C3 16.4863 3.0866 16.7585 3.22611 17L3.22607 17C3.36553 17.2416 3.55795 17.4527 3.76954 17.5748L11.1167 21.8167C11.54 22.0611 12.2326 22.0611 12.6559 21.8167L20.0031 17.5748C20.2147 17.4526 20.4071 17.2416 20.5465 17L15.76 14.2365L17.0169 14.9638C15.9603 16.7899 13.9944 17.9243 11.8864 17.9243C8.61976 17.9243 5.96216 15.2667 5.96216 12C5.96216 8.7334 8.61976 6.07581 11.8864 6.07581C13.9943 6.07581 15.9601 7.21012 17.0167 9.03606L14.4529 10.5197C13.924 9.60572 12.9406 9.03792 11.8864 9.03792C10.253 9.03792 8.92427 10.3667 8.92427 12C8.92427 13.6333 10.253 14.9622 11.8864 14.9622C12.9402 14.9622 13.9232 14.3949 14.4523 13.4815L11.8863 12L11.8864 12L14.4523 13.4815L14.453 13.4802L15.7254 14.2165L20.5466 17C20.686 16.7584 20.7727 16.4863 20.7727 16.2419V7.75807C20.7727 7.51367 20.6862 7.24156 20.5466 7L18.6993 8.06657L20.5466 6.99999ZM17.8106 11.6709H17.1523V11.0127H16.4941V11.6709H15.8358V12.3292H16.4941V12.9874H17.1523V12.3292H17.8106V11.6709ZM19.6208 11.0127V11.6709H20.2791V12.3292H19.6208V12.9874H18.9626V12.3292H18.3043V11.6709H18.9626V11.0127H19.6208Z" fill="#C4C6D7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.53673 13.7707C2.44248 13.7689 2.35245 13.789 2.27079 13.823L2.27078 13.823L2.26809 13.8241C2.18619 13.8601 2.11067 13.9117 2.04778 13.9776C1.98442 14.0411 1.93372 14.1166 1.90025 14.2008L1.90024 14.2008L1.89982 14.2019C1.86787 14.2841 1.85032 14.3715 1.85012 14.4602M2.53673 13.7707C2.62685 13.7709 2.71695 13.7882 2.80221 13.8257C2.88344 13.8613 2.9584 13.9123 3.02097 13.9775C3.08783 14.041 3.13744 14.1165 3.17282 14.198L3.17349 14.1995L3.17348 14.1995C3.20882 14.2835 3.22627 14.3729 3.22627 14.4633C3.22627 14.5537 3.20882 14.6431 3.17348 14.7271C3.13908 14.8088 3.08879 14.8848 3.02168 14.9483C2.95873 15.0122 2.88363 15.063 2.80079 15.0976C2.71749 15.1323 2.62805 15.1501 2.53777 15.1499C2.4466 15.1515 2.35607 15.1342 2.27193 15.099C2.1879 15.0639 2.11208 15.0118 2.0493 14.9459C1.98587 14.8824 1.93594 14.8076 1.90138 14.7256C1.86625 14.6421 1.84837 14.5522 1.85012 14.4602M2.53673 13.7707C2.53726 13.7707 2.53779 13.7707 2.53832 13.7707L2.53515 13.9207V13.7707C2.53568 13.7707 2.5362 13.7707 2.53673 13.7707ZM1.85012 14.4602C1.85013 14.4597 1.85014 14.4591 1.85015 14.4586L2.00011 14.4618H1.85011C1.85011 14.4613 1.85011 14.4607 1.85012 14.4602ZM5.46673 14.9062V9.91575C5.46994 9.80356 5.47033 9.69143 5.46804 9.57937L8.96337 14.9876L9.0077 15.0562H9.08935H10.0849H10.2349V14.9062V8V7.85H10.0849H9.27631H9.12631V8V12.8549C9.12306 13.0013 9.12426 13.1479 9.12979 13.2945L5.68624 7.91909L5.64198 7.85H5.55993H4.5081H4.3581V8V14.9062V15.0562H4.5081H5.31673H5.46673V14.9062ZM15.7176 14.1483L15.7185 13.9974H15.5676H12.86V11.9046H15.2104H15.3604V11.7546V11.0261V10.8761H15.2104H12.86V8.88158H15.3928H15.5428V8.73158V8V7.85H15.3928H11.8816H11.7316V8V14.9062V15.0562H11.8816H15.563H15.7121L15.713 14.9071L15.7176 14.1483ZM16.1862 7.85H16.0362V8V8.73158V8.88158H16.1862H18.0274V14.9062V15.0562H18.1774H18.986H19.136V14.9062V8.88158H21H21.15V8.73158V8V7.85H21H16.1862Z" fill="#C4C6D7" stroke="#C4C6D7" stroke-width="0.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.5466 17C20.686 16.7584 20.7727 16.4863 20.7727 16.2419V7.75807C20.7727 7.51367 20.6862 7.24156 20.5466 7L11.8864 12L20.5466 17Z" fill="#00599C"/>
|
||||
<path d="M12.6559 21.8167L20.0031 17.5748C20.2147 17.4526 20.4071 17.2416 20.5465 17L11.8863 12L3.22607 17C3.36553 17.2416 3.55795 17.4527 3.76954 17.5748L11.1167 21.8167C11.54 22.0611 12.2326 22.0611 12.6559 21.8167Z" fill="#004482"/>
|
||||
<path d="M20.5466 6.99999C20.4071 6.75838 20.2147 6.54737 20.0031 6.42517L12.6559 2.1833C12.2327 1.9389 11.54 1.9389 11.1168 2.1833L3.76957 6.42517C3.34628 6.66951 3 7.26937 3 7.75812V16.2419C3 16.4863 3.0866 16.7585 3.22611 17L11.8863 12L20.5466 6.99999Z" fill="#659AD2"/>
|
||||
<path d="M11.8864 17.9242C8.61976 17.9242 5.96216 15.2666 5.96216 12C5.96216 8.73334 8.61976 6.07574 11.8864 6.07574C13.9943 6.07574 15.9601 7.21006 17.0167 9.036L14.4529 10.5196C13.924 9.60566 12.9406 9.03786 11.8864 9.03786C10.253 9.03786 8.92427 10.3666 8.92427 12C8.92427 13.6333 10.253 14.9621 11.8864 14.9621C12.9407 14.9621 13.9241 14.3943 14.453 13.4802L17.0169 14.9637C15.9603 16.7898 13.9944 17.9242 11.8864 17.9242Z" fill="white"/>
|
||||
<path d="M17.8106 11.671H17.1523V11.0128H16.4941V11.671H15.8358V12.3292H16.4941V12.9875H17.1523V12.3292H17.8106V11.671Z" fill="white"/>
|
||||
<path d="M20.2791 11.671H19.6208V11.0128H18.9626V11.671H18.3043V12.3292H18.9626V12.9875H19.6208V12.3292H20.2791V11.671Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="21" height="9" viewBox="0 0 21 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.5367 6.77066C1.44246 6.76892 1.35244 6.789 1.27079 6.82299L1.27077 6.82297L1.26809 6.82415C1.18619 6.86013 1.11066 6.91165 1.04778 6.97763C0.984416 7.04106 0.933719 7.11661 0.900246 7.20081L0.900241 7.2008L0.899816 7.2019C0.867868 7.28413 0.850324 7.37148 0.850116 7.46019M1.5367 6.77066C1.62683 6.77086 1.71694 6.78819 1.80221 6.82566C1.88344 6.86135 1.95839 6.91232 2.02096 6.97752C2.08783 7.04102 2.13744 7.11653 2.17282 7.19799L2.17349 7.19953L2.17348 7.19954C2.20882 7.28349 2.22627 7.37286 2.22627 7.4633C2.22627 7.55374 2.20882 7.64311 2.17348 7.72707C2.13908 7.80877 2.08878 7.88483 2.02167 7.94834C1.95873 8.01224 1.88363 8.06296 1.80079 8.09756L1.74298 7.95915L1.80079 8.09756C1.7175 8.13234 1.62807 8.15013 1.5378 8.1499M1.5367 6.77066C1.53724 6.77067 1.53778 6.77068 1.53832 6.7707L1.53515 6.92066V6.77066C1.53567 6.77066 1.53619 6.77066 1.5367 6.77066ZM1.5378 8.1499C1.44662 8.15149 1.35607 8.13418 1.27193 8.09904C1.1879 8.06394 1.11207 8.01181 1.0493 7.94594C0.985862 7.88244 0.935939 7.80764 0.901383 7.72555C0.866254 7.6421 0.848375 7.55224 0.850116 7.46019M1.5378 8.1499C1.53715 8.1499 1.5365 8.1499 1.53585 8.14989L1.53667 7.99989L1.53973 8.14986C1.53908 8.14988 1.53844 8.14989 1.5378 8.1499ZM0.850116 7.46019C0.850126 7.45965 0.850137 7.45912 0.850148 7.45858L1.00011 7.46179H0.850114C0.850114 7.46126 0.850115 7.46072 0.850116 7.46019ZM4.46673 7.90618V2.91575C4.46994 2.80356 4.47033 2.69143 4.46804 2.57937L7.96337 7.9876L8.0077 8.05618H8.08935H9.08495H9.23495V7.90618V1V0.85H9.08495H8.27631H8.12631V1V5.85487C8.12306 6.00128 8.12426 6.14793 8.12979 6.29447L4.68624 0.919087L4.64198 0.85H4.55993H3.5081H3.3581V1V7.90618V8.05618H3.5081H4.31673H4.46673V7.90618ZM14.7176 7.14829L14.7185 6.99739H14.5676H11.86V4.90464H14.2104H14.3604V4.75464V4.02608V3.87608H14.2104H11.86V1.88158H14.3928H14.5428V1.73158V1V0.85H14.3928H10.8816H10.7316V1V7.90618V8.05618H10.8816H14.563H14.7121L14.713 7.90708L14.7176 7.14829ZM15.1862 0.85H15.0362V1V1.73158V1.88158H15.1862H17.0274V7.90618V8.05618H17.1774H17.986H18.136V7.90618V1.88158H20H20.15V1.73158V1V0.85H20H15.1862Z" fill="#512BD4" stroke="#512BD4" stroke-width="0.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5466 6.99999C20.4071 6.75838 20.2147 6.54737 20.0031 6.42517L12.6559 2.1833C12.2327 1.9389 11.54 1.9389 11.1168 2.1833L3.76957 6.42517C3.34628 6.66951 3 7.26937 3 7.75812V16.2419C3 16.4863 3.0866 16.7585 3.22611 17L3.22607 17C3.36553 17.2416 3.55795 17.4527 3.76954 17.5748L11.1167 21.8167C11.54 22.0611 12.2326 22.0611 12.6559 21.8167L20.0031 17.5748C20.2147 17.4526 20.4071 17.2416 20.5465 17L15.76 14.2365L17.0169 14.9638C15.9603 16.7899 13.9944 17.9243 11.8864 17.9243C8.61976 17.9243 5.96216 15.2667 5.96216 12C5.96216 8.7334 8.61976 6.07581 11.8864 6.07581C13.9943 6.07581 15.9601 7.21012 17.0167 9.03606L14.4529 10.5197C13.924 9.60572 12.9406 9.03792 11.8864 9.03792C10.253 9.03792 8.92427 10.3667 8.92427 12C8.92427 13.6333 10.253 14.9622 11.8864 14.9622C12.9402 14.9622 13.9232 14.3949 14.4523 13.4815L11.8863 12L11.8864 12L14.4523 13.4815L14.453 13.4802L15.7254 14.2165L20.5466 17C20.686 16.7584 20.7727 16.4863 20.7727 16.2419V7.75807C20.7727 7.51367 20.6862 7.24156 20.5466 7L18.6993 8.06657L20.5466 6.99999ZM17.8106 11.6709H17.1523V11.0127H16.4941V11.6709H15.8358V12.3292H16.4941V12.9874H17.1523V12.3292H17.8106V11.6709ZM19.6208 11.0127V11.6709H20.2791V12.3292H19.6208V12.9874H18.9626V12.3292H18.3043V11.6709H18.9626V11.0127H19.6208Z" fill="#373B4D"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.53673 13.7707C2.44248 13.7689 2.35245 13.789 2.27079 13.823L2.27078 13.823L2.26809 13.8241C2.18619 13.8601 2.11067 13.9117 2.04778 13.9776C1.98442 14.0411 1.93372 14.1166 1.90025 14.2008L1.90024 14.2008L1.89982 14.2019C1.86787 14.2841 1.85032 14.3715 1.85012 14.4602M2.53673 13.7707C2.62685 13.7709 2.71695 13.7882 2.80221 13.8257C2.88344 13.8613 2.9584 13.9123 3.02097 13.9775C3.08783 14.041 3.13744 14.1165 3.17282 14.198L3.17349 14.1995L3.17348 14.1995C3.20882 14.2835 3.22627 14.3729 3.22627 14.4633C3.22627 14.5537 3.20882 14.6431 3.17348 14.7271C3.13908 14.8088 3.08879 14.8848 3.02168 14.9483C2.95873 15.0122 2.88363 15.063 2.80079 15.0976C2.71749 15.1323 2.62805 15.1501 2.53777 15.1499C2.4466 15.1515 2.35607 15.1342 2.27193 15.099C2.1879 15.0639 2.11208 15.0118 2.0493 14.9459C1.98587 14.8824 1.93594 14.8076 1.90138 14.7256C1.86625 14.6421 1.84837 14.5522 1.85012 14.4602M2.53673 13.7707C2.53726 13.7707 2.53779 13.7707 2.53832 13.7707L2.53515 13.9207V13.7707C2.53568 13.7707 2.5362 13.7707 2.53673 13.7707ZM1.85012 14.4602C1.85013 14.4597 1.85014 14.4591 1.85015 14.4586L2.00011 14.4618H1.85011C1.85011 14.4613 1.85011 14.4607 1.85012 14.4602ZM5.46673 14.9062V9.91575C5.46994 9.80356 5.47033 9.69143 5.46804 9.57937L8.96337 14.9876L9.0077 15.0562H9.08935H10.0849H10.2349V14.9062V8V7.85H10.0849H9.27631H9.12631V8V12.8549C9.12306 13.0013 9.12426 13.1479 9.12979 13.2945L5.68624 7.91909L5.64198 7.85H5.55993H4.5081H4.3581V8V14.9062V15.0562H4.5081H5.31673H5.46673V14.9062ZM15.7176 14.1483L15.7185 13.9974H15.5676H12.86V11.9046H15.2104H15.3604V11.7546V11.0261V10.8761H15.2104H12.86V8.88158H15.3928H15.5428V8.73158V8V7.85H15.3928H11.8816H11.7316V8V14.9062V15.0562H11.8816H15.563H15.7121L15.713 14.9071L15.7176 14.1483ZM16.1862 7.85H16.0362V8V8.73158V8.88158H16.1862H18.0274V14.9062V15.0562H18.1774H18.986H19.136V14.9062V8.88158H21H21.15V8.73158V8V7.85H21H16.1862Z" fill="#373B4D" stroke="#373B4D" stroke-width="0.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |