mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'feat-custom-signup' of github.com:appwrite/console into db-pools-support
This commit is contained in:
+4
-1
@@ -1,4 +1,7 @@
|
||||
VITE_APPWRITE_ENDPOINT=
|
||||
VITE_APPWRITE_GROWTH_ENDPOINT=
|
||||
VITE_GA_PROJECT=
|
||||
VITE_SENTRY_DSN=
|
||||
VITE_CONSOLE_MODE=self-hosted
|
||||
VITE_CONSOLE_MODE=self-hosted
|
||||
VITE_CONSOLE_MODE=cloud
|
||||
VITE_CONSOLE_TIER=base
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
|
||||
Generated
+13
-13
@@ -9,7 +9,7 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@analytics/google-analytics": "^1.0.5",
|
||||
"@aw-labs/appwrite-console": "^13.0.0",
|
||||
"@aw-labs/appwrite-console": "^13.1.0",
|
||||
"@aw-labs/icons": "0.0.0-77",
|
||||
"@aw-labs/ui": "0.0.0-77",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
@@ -145,9 +145,9 @@
|
||||
"integrity": "sha512-1Yw7u/COtxx06BfwlI+kVhsa/upKYzmCNrT4c8QDeCY2KMYlnijkUjtHiPU08HxyTIVB5j6d75O0YWVIHwQS8g=="
|
||||
},
|
||||
"node_modules/@aw-labs/appwrite-console": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.0.0.tgz",
|
||||
"integrity": "sha512-a5illDoFZAhgLP8Ae+KHPzBIUozKXzjyHUw6jTwNPZaL+b2RnTe3oRLZU8l8Kt5pB5m7NZx6kAcZm+NMf8NeCw==",
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.1.0.tgz",
|
||||
"integrity": "sha512-1FyKke5b3vx+D3VfNYd3YnFhzUOlsj9bD+KTRLWA4WRItb55thFVZJMWQQ4XnZtDiLPuJAn2XjbMSfMr+9XYwg==",
|
||||
"dependencies": {
|
||||
"cross-fetch": "3.1.5",
|
||||
"isomorphic-form-data": "2.0.0"
|
||||
@@ -5399,9 +5399,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
@@ -7740,9 +7740,9 @@
|
||||
"integrity": "sha512-1Yw7u/COtxx06BfwlI+kVhsa/upKYzmCNrT4c8QDeCY2KMYlnijkUjtHiPU08HxyTIVB5j6d75O0YWVIHwQS8g=="
|
||||
},
|
||||
"@aw-labs/appwrite-console": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.0.0.tgz",
|
||||
"integrity": "sha512-a5illDoFZAhgLP8Ae+KHPzBIUozKXzjyHUw6jTwNPZaL+b2RnTe3oRLZU8l8Kt5pB5m7NZx6kAcZm+NMf8NeCw==",
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.1.0.tgz",
|
||||
"integrity": "sha512-1FyKke5b3vx+D3VfNYd3YnFhzUOlsj9bD+KTRLWA4WRItb55thFVZJMWQQ4XnZtDiLPuJAn2XjbMSfMr+9XYwg==",
|
||||
"requires": {
|
||||
"cross-fetch": "3.1.5",
|
||||
"isomorphic-form-data": "2.0.0"
|
||||
@@ -11827,9 +11827,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
"dependencies": {
|
||||
"@analytics/google-analytics": "^1.0.5",
|
||||
"analytics": "^0.8.1",
|
||||
"@aw-labs/appwrite-console": "^13.0.0",
|
||||
"@aw-labs/appwrite-console": "^13.1.0",
|
||||
"@aw-labs/icons": "0.0.0-77",
|
||||
"@aw-labs/ui": "0.0.0-77",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
|
||||
@@ -3,7 +3,7 @@ import googleAnalytics from '@analytics/google-analytics';
|
||||
import { get } from 'svelte/store';
|
||||
import { page } from '$app/stores';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { Mode } from '$lib/constants';
|
||||
import { growthEndpoint, Mode } from '$lib/constants';
|
||||
|
||||
const analytics = Analytics({
|
||||
app: 'appwrite',
|
||||
@@ -40,7 +40,7 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo
|
||||
email = userStore.email;
|
||||
name = userStore.name;
|
||||
}
|
||||
fetch('https://growth.appwrite.io/v1/analytics', {
|
||||
fetch(`${growthEndpoint}/analytics`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
import { copy } from '$lib/helpers/copy';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
export let value: string;
|
||||
@@ -9,27 +10,28 @@
|
||||
|
||||
let content = 'Click to copy';
|
||||
|
||||
const copy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
async function handleClick() {
|
||||
const success = await copy(value);
|
||||
|
||||
if (success) {
|
||||
content = 'Copied';
|
||||
} catch (error) {
|
||||
} else {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
message: 'Unable to copy to clipboard',
|
||||
type: 'error'
|
||||
});
|
||||
} finally {
|
||||
if (event) {
|
||||
trackEvent('click_id_tag', {
|
||||
name: event
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (event) {
|
||||
trackEvent('click_id_tag', {
|
||||
name: event
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<span
|
||||
on:click|preventDefault={copy}
|
||||
on:click|preventDefault={handleClick}
|
||||
on:keyup={clickOnEnter}
|
||||
on:mouseenter={() => setTimeout(() => (content = 'Click to copy'))}
|
||||
use:tooltip={{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { copy } from '$lib/helpers/copy';
|
||||
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
@@ -9,13 +10,14 @@
|
||||
|
||||
let content = 'Click to copy';
|
||||
|
||||
const copy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
const handleCopy = async () => {
|
||||
const success = await copy(value);
|
||||
|
||||
if (success) {
|
||||
content = 'Copied';
|
||||
} catch (error) {
|
||||
} else {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
message: 'Unable to copy to clipboard',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
@@ -31,7 +33,7 @@
|
||||
type="button"
|
||||
class="input-button"
|
||||
aria-label="Click to copy."
|
||||
on:click={copy}
|
||||
on:click={handleCopy}
|
||||
on:mouseenter={() => setTimeout(() => (content = 'Click to copy'))}
|
||||
use:tooltip={{
|
||||
content,
|
||||
|
||||
@@ -142,9 +142,9 @@
|
||||
</script>
|
||||
|
||||
{#if [...$groups]?.length}
|
||||
<Table noMargin noStyles noMobile>
|
||||
<Table noMargin noStyles>
|
||||
<TableHeader>
|
||||
<TableCellHead>Role</TableCellHead>
|
||||
<TableCellHead width={70}>Role</TableCellHead>
|
||||
{#if withCreate}
|
||||
<TableCellHead width={70}>Create</TableCellHead>
|
||||
{/if}
|
||||
|
||||
@@ -92,9 +92,9 @@
|
||||
</script>
|
||||
|
||||
{#if [...$groups.keys()]?.length}
|
||||
<Table noMargin noStyles noMobile>
|
||||
<Table noMargin noStyles>
|
||||
<TableHeader>
|
||||
<TableCellHead>Role</TableCellHead>
|
||||
<TableCellHead width={70}>Role</TableCellHead>
|
||||
<TableCellHead width={40} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -7,6 +7,8 @@ export enum Mode {
|
||||
SELF_HOSTED = 'self-hosted'
|
||||
}
|
||||
|
||||
export const growthEndpoint = import.meta.env.VITE_APPWRITE_GROWTH_ENDPOINT;
|
||||
|
||||
export enum Dependencies {
|
||||
ORGANIZATION = 'dependency:organization',
|
||||
PROJECT = 'dependency:project',
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script lang="ts">
|
||||
export let width: number = null;
|
||||
export let onlyDesktop = false;
|
||||
</script>
|
||||
|
||||
<div
|
||||
style={width ? `--p-col-width:${width?.toString()}` : ''}
|
||||
class:is-only-desktop={onlyDesktop}
|
||||
class="table-thead-col"
|
||||
role="columnheader">
|
||||
<span class="eyebrow-heading-3">
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
import { Trim } from '$lib/components';
|
||||
export let title: string;
|
||||
export let showOverflow = false;
|
||||
export let onlyDesktop = false;
|
||||
</script>
|
||||
|
||||
<div class="table-col " class:u-overflow-visible={showOverflow} data-title={title} role="cell">
|
||||
<div
|
||||
class="table-col"
|
||||
class:is-only-desktop={onlyDesktop}
|
||||
class:u-overflow-visible={showOverflow}
|
||||
data-title={title}
|
||||
role="cell">
|
||||
<Trim><slot /></Trim>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let noMargin = false;
|
||||
export let noStyles = false;
|
||||
export let noMobile = false;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="table is-vertical"
|
||||
class:is-vertical={!noMobile}
|
||||
class="table is-selected-columns-mobile"
|
||||
class:u-margin-block-start-32={!noMargin}
|
||||
class:is-remove-outer-styles={noStyles}
|
||||
role="table">
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script lang="ts">
|
||||
export let isSticky = false;
|
||||
</script>
|
||||
|
||||
<div class="table-with-scroll u-margin-block-start-32">
|
||||
<div class="table-wrapper">
|
||||
<div class="table is-sticky-scroll" role="table">
|
||||
<table class="table" class:is-sticky-scroll={isSticky}>
|
||||
<slot />
|
||||
</div>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { EmptySearch, Pagination, Trim } from '$lib/components';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText
|
||||
TableCellText,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
@@ -26,13 +26,13 @@
|
||||
|
||||
<Container>
|
||||
{#if logs.total}
|
||||
<Table>
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
<TableCellHead>Client</TableCellHead>
|
||||
<TableCellHead>Event</TableCellHead>
|
||||
<TableCellHead>Location</TableCellHead>
|
||||
<TableCellHead>IP</TableCellHead>
|
||||
<TableCellHead>Date</TableCellHead>
|
||||
<TableCellHead width={150}>Client</TableCellHead>
|
||||
<TableCellHead width={100}>Event</TableCellHead>
|
||||
<TableCellHead width={80}>Location</TableCellHead>
|
||||
<TableCellHead width={90}>IP</TableCellHead>
|
||||
<TableCellHead width={140}>Date</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each logs.logs as log}
|
||||
@@ -75,7 +75,7 @@
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableScroll>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {logs.total}</p>
|
||||
<Pagination limit={PAGE_LIMIT} {path} {offset} sum={logs.total} />
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<script lang="ts">
|
||||
import AppwriteLogo from '$lib/images/appwrite.svg';
|
||||
import LoginLight from '$lib/images/login/login-light-mode.svg';
|
||||
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';
|
||||
import LoginLight from '$lib/images/login/login-light-mode.svg';
|
||||
import LoginDark from '$lib/images/login/login-dark-mode.svg';
|
||||
|
||||
export let imgLight = LoginLight;
|
||||
export let imgDark = LoginDark;
|
||||
|
||||
const technologies = [
|
||||
'js',
|
||||
@@ -25,7 +29,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>
|
||||
@@ -34,12 +38,12 @@
|
||||
|
||||
<div class="u-margin-block-start-auto is-no-mobile" />
|
||||
<div
|
||||
class="container u-margin-block-start-20 is-no-mobile"
|
||||
class="container u-margin-block-start-20 is-no-mobile u-flex u-main-center"
|
||||
style="--p-container-max-size: var(--container-size-large);">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
<img src={LoginDark} alt="" class="u-only-dark" />
|
||||
<img src={imgDark} alt="" class="u-only-dark" />
|
||||
{:else}
|
||||
<img src={LoginLight} alt="" class="u-only-light" />
|
||||
<img src={imgLight} alt="" class="u-only-light" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { growthEndpoint } from '$lib/constants';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export type AppStore = {
|
||||
@@ -18,6 +19,10 @@ export const app = writable<AppStore>({
|
||||
themeInUse: 'light'
|
||||
});
|
||||
|
||||
//TODO: remove cloud always set to true
|
||||
export const isCloud =
|
||||
(import.meta.env.VITE_CONSOLE_MODE?.toString() ?? 'cloud') === 'cloud' ? true : false;
|
||||
|
||||
function createFeedbackStore() {
|
||||
const { subscribe, update } = writable<Feedback>({
|
||||
elapsed: browser ? parseInt(localStorage.getItem('feedbackElapsed')) : 0,
|
||||
@@ -59,7 +64,7 @@ function createFeedbackStore() {
|
||||
email?: string,
|
||||
value?: number
|
||||
) => {
|
||||
const response = await fetch('https://growth.appwrite.io/v1/feedback', {
|
||||
const response = await fetch(`${growthEndpoint}/feedback`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
@@ -54,6 +54,11 @@ function createServices() {
|
||||
label: 'Users',
|
||||
method: 'users',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'GraphQL',
|
||||
method: 'graphql',
|
||||
value: null
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -107,6 +112,11 @@ function createServices() {
|
||||
label: 'Users',
|
||||
method: 'users',
|
||||
value: project.serviceStatusForUsers
|
||||
},
|
||||
{
|
||||
label: 'GraphQL',
|
||||
method: 'graphql',
|
||||
value: project.serviceStatusForGraphql
|
||||
}
|
||||
];
|
||||
set({ list });
|
||||
|
||||
@@ -51,4 +51,4 @@ const sdkForProject = {
|
||||
users: new Users(clientProject)
|
||||
};
|
||||
|
||||
export { sdkForConsole, sdkForProject, setProject };
|
||||
export { sdkForConsole, sdkForProject, setProject, endpoint };
|
||||
|
||||
@@ -49,13 +49,21 @@
|
||||
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)) {
|
||||
if (
|
||||
acceptedRoutes.includes($page.url.pathname) ||
|
||||
$page.url.pathname.startsWith('/register/invite')
|
||||
) {
|
||||
await goto(`${base}${$page.url.pathname}${$page.url.search}`);
|
||||
} else {
|
||||
await goto(`${base}/login`, {
|
||||
|
||||
@@ -27,7 +27,10 @@ export const load: LayoutLoad = async ({ depends, url }) => {
|
||||
'/auth/oauth2/failure'
|
||||
];
|
||||
|
||||
if (!acceptedRoutes.includes(url.pathname)) {
|
||||
if (
|
||||
!acceptedRoutes.includes(url.pathname) &&
|
||||
!url.pathname.startsWith('/register/invite')
|
||||
) {
|
||||
throw redirect(303, '/login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { Empty, Heading } from '$lib/components';
|
||||
import { Empty, Heading, Trim } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText
|
||||
TableCellText,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
@@ -52,40 +52,39 @@
|
||||
</div>
|
||||
|
||||
{#if data.sessions.total}
|
||||
<Table>
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
<TableCellHead>Client</TableCellHead>
|
||||
<TableCellHead width={100}>Location</TableCellHead>
|
||||
<TableCellHead width={150}>IP</TableCellHead>
|
||||
<TableCellHead width={100} />
|
||||
<TableCellHead width={250}>Client</TableCellHead>
|
||||
<TableCellHead width={70}>Location</TableCellHead>
|
||||
<TableCellHead width={90}>IP</TableCellHead>
|
||||
<TableCellHead width={60} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.sessions.sessions as session}
|
||||
<TableRow>
|
||||
<TableCell title="Client">
|
||||
<div class="u-flex u-gap-12 u-cross-center">
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
{#if session.clientName}
|
||||
<div class="avatar is-small">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
src={getBrowser(session.clientCode).toString()}
|
||||
alt={session.clientName} />
|
||||
</div>
|
||||
<p class="text u-trim">
|
||||
{session.clientName}
|
||||
{session.clientVersion}
|
||||
on {session.osName}
|
||||
{session.osVersion}
|
||||
</p>
|
||||
{:else}
|
||||
<span class="avatar is-color-empty" />
|
||||
<p class="text u-trim">Unknown</p>
|
||||
{/if}
|
||||
{#if session.clientName}
|
||||
<div class="avatar is-small">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
src={getBrowser(session.clientCode).toString()}
|
||||
alt={session.clientName} />
|
||||
</div>
|
||||
<Trim>
|
||||
{session.clientName}
|
||||
{session.clientVersion}
|
||||
on {session.osName}
|
||||
{session.osVersion}
|
||||
</Trim>
|
||||
{:else}
|
||||
<span class="avatar is-color-empty" />
|
||||
<p class="text u-trim">Unknown</p>
|
||||
{/if}
|
||||
<div class="is-only-desktop">
|
||||
<Pill>{session.provider}</Pill>
|
||||
</div>
|
||||
|
||||
<Pill>{session.provider}</Pill>
|
||||
{#if session.current}
|
||||
<Pill success>current session</Pill>
|
||||
{/if}
|
||||
@@ -105,7 +104,7 @@
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableScroll>
|
||||
{:else}
|
||||
<Empty>
|
||||
<div class="u-flex u-flex-vertical u-cross-center">
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText,
|
||||
TableRow
|
||||
TableRow,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { AvatarInitials, Heading, Pagination } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
@@ -64,11 +64,11 @@
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Table>
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Email</TableCellHead>
|
||||
<TableCellHead width={100}>Role</TableCellHead>
|
||||
<TableCellHead width={140}>Name</TableCellHead>
|
||||
<TableCellHead width={120}>Email</TableCellHead>
|
||||
<TableCellHead width={90}>Role</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -112,7 +112,7 @@
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableScroll>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {data.organizationMembers.total}</p>
|
||||
<Pagination
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Identifiers</TableCellHead>
|
||||
<TableCellHead width={130}>Status</TableCellHead>
|
||||
<TableCellHead width={100}>ID</TableCellHead>
|
||||
<TableCellHead>Joined</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Identifiers</TableCellHead>
|
||||
<TableCellHead onlyDesktop width={130}>Status</TableCellHead>
|
||||
<TableCellHead onlyDesktop width={100}>ID</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Joined</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.users.users as user}
|
||||
@@ -75,12 +75,12 @@
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Identifiers">
|
||||
<TableCellText onlyDesktop title="Identifiers">
|
||||
{user.email && user.phone
|
||||
? [user.email, user.phone].join(',')
|
||||
: user.email || user.phone}
|
||||
</TableCellText>
|
||||
<TableCell title="Status">
|
||||
<TableCell onlyDesktop title="Status">
|
||||
{#if user.status}
|
||||
<Pill success={user.emailVerification || user.phoneVerification}>
|
||||
{user.emailVerification && user.phoneVerification
|
||||
@@ -95,7 +95,7 @@
|
||||
<Pill danger>blocked</Pill>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell showOverflow title="ID">
|
||||
<TableCell onlyDesktop showOverflow title="ID">
|
||||
<Copy value={user.$id} event="user">
|
||||
<Pill button>
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
@@ -103,7 +103,7 @@
|
||||
</Pill>
|
||||
</Copy>
|
||||
</TableCell>
|
||||
<TableCellText title="Joined">
|
||||
<TableCellText onlyDesktop title="Joined">
|
||||
{toLocaleDateTime(user.registration)}
|
||||
</TableCellText>
|
||||
</TableRowLink>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -40,21 +40,22 @@
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Members</TableCellHead>
|
||||
<TableCellHead>Created</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Members</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Created</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.teams.teams as team}
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${project}/auth/teams/team-${team.$id}`}>
|
||||
<TableCell title="ID">
|
||||
<TableCell title="Name">
|
||||
<div class="u-flex u-gap-12">
|
||||
<AvatarInitials size={32} name={team.name} />
|
||||
<span class="text u-trim">{team.name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Members">{team.total} members</TableCellText>
|
||||
<TableCellText title="Members">
|
||||
<TableCellText onlyDesktop title="Members"
|
||||
>{team.total} members</TableCellText>
|
||||
<TableCellText onlyDesktop title="Created">
|
||||
{toLocaleDateTime(team.$createdAt)}
|
||||
</TableCellText>
|
||||
</TableRowLink>
|
||||
|
||||
+4
-4
@@ -46,8 +46,8 @@
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Roles</TableCellHead>
|
||||
<TableCellHead>Joined</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Roles</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Joined</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -60,8 +60,8 @@
|
||||
<span>{membership.userName ? membership.userName : 'n/a'}</span>
|
||||
</div>
|
||||
</TableCellText>
|
||||
<TableCellText title="Roles">{membership.roles}</TableCellText>
|
||||
<TableCellText title="Joined">
|
||||
<TableCellText onlyDesktop title="Roles">{membership.roles}</TableCellText>
|
||||
<TableCellText onlyDesktop title="Joined">
|
||||
{toLocaleDateTime(membership.joined)}
|
||||
</TableCellText>
|
||||
<TableCell>
|
||||
|
||||
+4
-4
@@ -32,8 +32,8 @@
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Roles</TableCellHead>
|
||||
<TableCellHead>Joined</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Roles</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Joined</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -46,8 +46,8 @@
|
||||
<span>{membership.teamName ? membership.teamName : 'n/a'}</span>
|
||||
</div>
|
||||
</TableCellText>
|
||||
<TableCellText title="Roles">{membership.roles}</TableCellText>
|
||||
<TableCellText title="Joined">
|
||||
<TableCellText onlyDesktop title="Roles">{membership.roles}</TableCellText>
|
||||
<TableCellText onlyDesktop title="Joined">
|
||||
{toLocaleDateTime(membership.joined)}
|
||||
</TableCellText>
|
||||
<TableCell width={30}>
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@
|
||||
</div>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Browser and Device</TableCellHead>
|
||||
<TableCellHead width={140}>Browser and Device</TableCellHead>
|
||||
<TableCellHead width={140}>Session</TableCellHead>
|
||||
<TableCellHead width={140}>Location</TableCellHead>
|
||||
<TableCellHead width={140}>IP</TableCellHead>
|
||||
|
||||
@@ -53,10 +53,10 @@
|
||||
async function updateStatus() {
|
||||
try {
|
||||
await sdkForProject.users.updateStatus($user.$id, !$user.status);
|
||||
invalidate(Dependencies.USER);
|
||||
await invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.status ? 'blocked' : 'unblocked'
|
||||
$user.status ? 'unblocked' : 'blocked'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@
|
||||
|
||||
{#if $collection?.attributes?.length}
|
||||
{#if data.documents.total}
|
||||
<TableScroll>
|
||||
<TableScroll isSticky>
|
||||
<TableHeader>
|
||||
<TableCellHead>Document ID</TableCellHead>
|
||||
{#each columns as column}
|
||||
|
||||
+9
-5
@@ -40,8 +40,8 @@
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Key</TableCellHead>
|
||||
<TableCellHead>Type</TableCellHead>
|
||||
<TableCellHead>Default Value</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Type</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Default Value</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -63,9 +63,11 @@
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Type">{attribute.type}</TableCellText>
|
||||
<TableCellText title="Default Value">
|
||||
{attribute.default ? attribute.default : '-'}
|
||||
<TableCellText onlyDesktop title="Type">
|
||||
{`${attribute.type}${attribute.array ? '[]' : ''}`}
|
||||
</TableCellText>
|
||||
<TableCellText onlyDesktop title="Default Value">
|
||||
{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"
|
||||
|
||||
+3
-1
@@ -40,6 +40,7 @@
|
||||
default: data.default
|
||||
} = selectedAttribute);
|
||||
}
|
||||
|
||||
$: if (data.required || data.array) {
|
||||
data.default = null;
|
||||
}
|
||||
@@ -50,7 +51,8 @@
|
||||
label="Elements"
|
||||
bind:tags={data.elements}
|
||||
placeholder="Add elements here"
|
||||
readonly={!!selectedAttribute} />
|
||||
readonly={!!selectedAttribute}
|
||||
required />
|
||||
<InputSelect
|
||||
id="default"
|
||||
label="Default value"
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
data.required,
|
||||
data.min,
|
||||
data.max,
|
||||
data.default ? data.default : undefined,
|
||||
typeof data.default === 'number' ? data.default : undefined,
|
||||
data.array
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
data.required,
|
||||
data.min,
|
||||
data.max,
|
||||
data.default ? data.default : undefined,
|
||||
typeof data.default === 'number' ? data.default : undefined,
|
||||
data.array
|
||||
);
|
||||
}
|
||||
|
||||
+6
-6
@@ -50,9 +50,9 @@
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Key</TableCellHead>
|
||||
<TableCellHead>Type</TableCellHead>
|
||||
<TableCellHead>Attributes</TableCellHead>
|
||||
<TableCellHead>Asc/Desc</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Type</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Attributes</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Asc/Desc</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -72,11 +72,11 @@
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Type">{index.type}</TableCellText>
|
||||
<TableCellText title="Attributes">
|
||||
<TableCellText title="Type" onlyDesktop>{index.type}</TableCellText>
|
||||
<TableCellText title="Attributes" onlyDesktop>
|
||||
{index.attributes}
|
||||
</TableCellText>
|
||||
<TableCellText title="ASC/DESC">
|
||||
<TableCellText title="ASC/DESC" onlyDesktop>
|
||||
{index.orders}
|
||||
</TableCellText>
|
||||
<TableCell showOverflow>
|
||||
|
||||
+35
-21
@@ -1,17 +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;
|
||||
@@ -32,26 +32,31 @@
|
||||
label: attribute.key
|
||||
}));
|
||||
let attributeList = [{ value: '', order: '' }];
|
||||
let creating = false;
|
||||
|
||||
$: addAttributeDisabled = !attributeList.at(-1)?.value || !attributeList.at(-1)?.order;
|
||||
|
||||
onMount(() => {
|
||||
if (!externalAttribute) return;
|
||||
attributeList = [{ value: externalAttribute.key, order: 'ASC' }];
|
||||
});
|
||||
|
||||
$: if (showCreateIndex) {
|
||||
attributeList = [{ value: '', order: '' }];
|
||||
function initialize() {
|
||||
attributeList = externalAttribute
|
||||
? [{ value: externalAttribute.key, order: 'ASC' }]
|
||||
: [{ value: '', order: '' }];
|
||||
selectedType = 'key';
|
||||
key = null;
|
||||
}
|
||||
|
||||
$: 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,
|
||||
@@ -61,7 +66,15 @@
|
||||
attributeList.map((a) => a.value),
|
||||
attributeList.map((a) => a.order)
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
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'
|
||||
@@ -74,6 +87,7 @@
|
||||
});
|
||||
} finally {
|
||||
showCreateIndex = false;
|
||||
creating = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +154,6 @@
|
||||
</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>
|
||||
|
||||
+10
-254
@@ -1,263 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid, Box, Heading } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Button, InputText, InputSwitch, Helper } from '$lib/elements/forms';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import { collection } from '../store';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import Delete from './deleteCollection.svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let showDelete = false;
|
||||
let showError: false | 'name' | 'size' = false,
|
||||
errorMessage = 'Something went wrong',
|
||||
errorType: 'error' | 'warning' | 'success' = 'error';
|
||||
let enabled: boolean = null,
|
||||
collectionName: string = null,
|
||||
collectionDocumentSecurity: boolean = null,
|
||||
collectionPermissions: string[] = null,
|
||||
arePermsDisabled = true;
|
||||
|
||||
onMount(() => {
|
||||
enabled ??= $collection.enabled;
|
||||
collectionName ??= $collection.name;
|
||||
collectionPermissions ??= $collection.$permissions;
|
||||
collectionDocumentSecurity ??= $collection.documentSecurity;
|
||||
});
|
||||
|
||||
$: if (
|
||||
collectionPermissions &&
|
||||
symmetricDifference(collectionPermissions, $collection.$permissions).length
|
||||
) {
|
||||
arePermsDisabled = false;
|
||||
} else arePermsDisabled = true;
|
||||
|
||||
function addError(location: typeof showError, message: string, type: typeof errorType) {
|
||||
showError = location;
|
||||
errorMessage = message;
|
||||
errorType = type;
|
||||
}
|
||||
|
||||
async function toggleCollection() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
$collection.$permissions,
|
||||
$collection.documentSecurity,
|
||||
enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
addNotification({
|
||||
message: `${$collection.name} has been updated`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_enabled');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
collectionName,
|
||||
$collection.$permissions,
|
||||
$collection.documentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
showError = false;
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_name');
|
||||
} catch (error) {
|
||||
addError('name', error.message, 'error');
|
||||
}
|
||||
}
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
collectionPermissions,
|
||||
$collection.documentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
arePermsDisabled = true;
|
||||
addNotification({
|
||||
message: 'Permissions have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_permissions');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
$collection.$permissions,
|
||||
collectionDocumentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
arePermsDisabled = true;
|
||||
addNotification({
|
||||
message: 'Security has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_security');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
import DangerZone from './dangerZone.svelte';
|
||||
import UpdateName from './updateName.svelte';
|
||||
import UpdatePermissions from './updatePermissions.svelte';
|
||||
import UpdateSecurity from './updateSecurity.svelte';
|
||||
import UpdateStatus from './updateStatus.svelte';
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if $collection}
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">{$collection.name}</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputSwitch
|
||||
id="toggle"
|
||||
label={enabled ? 'Enabled' : 'Disabled'}
|
||||
bind:value={enabled} />
|
||||
</ul>
|
||||
<div>
|
||||
<p>Created: {toLocaleDateTime($collection.$createdAt)}</p>
|
||||
<p>Last Updated: {toLocaleDateTime($collection.$updatedAt)}</p>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={enabled === $collection.enabled} on:click={toggleCollection}>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Name</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={collectionName} />
|
||||
{#if showError === 'name'}
|
||||
<Helper type={errorType}>{errorMessage}</Helper>
|
||||
{/if}
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={collectionName === $collection.name || !collectionName}
|
||||
on:click={updateName}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Permissions</Heading>
|
||||
<p class="text">
|
||||
Choose who can access your collection and documents. For more information, check out
|
||||
the <a
|
||||
href="https://appwrite.io/docs/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
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>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Document Security</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputSwitch
|
||||
bind:value={collectionDocumentSecurity}
|
||||
id="security"
|
||||
label="Document Security" />
|
||||
</FormList>
|
||||
<p class="text">
|
||||
When document security is enabled, users will be able to access documents for
|
||||
which they have been granted <b>either Document or Collection permissions</b>.
|
||||
</p>
|
||||
<p class="text">
|
||||
If document security is disabled, users can access documents <b
|
||||
>only if they have Collection permissions</b
|
||||
>. Document permissions will be ignored.
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={collectionDocumentSecurity === $collection.documentSecurity}
|
||||
on:click={updateSecurity}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid danger>
|
||||
<Heading tag="h6" size="7">Delete Collection</Heading>
|
||||
<p>
|
||||
The collection will be permanently deleted, including all the documents within it.
|
||||
This action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$collection.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last Updated: {toLocaleDateTime($collection.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<UpdateStatus />
|
||||
<UpdateName />
|
||||
<UpdatePermissions />
|
||||
<UpdateSecurity />
|
||||
<DangerZone />
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { Box, CardGrid, Heading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { collection } from '../store';
|
||||
import Delete from './deleteCollection.svelte';
|
||||
|
||||
let showDelete = false;
|
||||
</script>
|
||||
|
||||
<CardGrid danger>
|
||||
<Heading tag="h6" size="7">Delete Collection</Heading>
|
||||
<p>
|
||||
The collection will be permanently deleted, including all the documents within it. This
|
||||
action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$collection.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last Updated: {toLocaleDateTime($collection.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { collection } from '../store';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let collectionName: string = null;
|
||||
|
||||
onMount(() => {
|
||||
collectionName ??= $collection.name;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
collectionName,
|
||||
$collection.$permissions,
|
||||
$collection.documentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_name');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Name</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={collectionName} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={collectionName === $collection.name || !collectionName} submit
|
||||
>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { collection } from '../store';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let arePermsDisabled = true;
|
||||
|
||||
let collectionPermissions: string[] = null;
|
||||
|
||||
onMount(() => {
|
||||
collectionPermissions ??= $collection.$permissions;
|
||||
});
|
||||
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
collectionPermissions,
|
||||
$collection.documentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
arePermsDisabled = true;
|
||||
addNotification({
|
||||
message: 'Permissions have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_permissions');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: if (
|
||||
collectionPermissions &&
|
||||
symmetricDifference(collectionPermissions, $collection.$permissions).length
|
||||
) {
|
||||
arePermsDisabled = false;
|
||||
} else arePermsDisabled = true;
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Permissions</Heading>
|
||||
<p class="text">
|
||||
Choose who can access your collection and documents. For more information, check out the <a
|
||||
href="https://appwrite.io/docs/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">Permissions Guide</a> in our documentation.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if collectionPermissions}
|
||||
<Permissions bind:permissions={collectionPermissions} withCreate />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={arePermsDisabled} on:click={updatePermissions}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, FormList, InputSwitch } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { collection } from '../store';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let collectionDocumentSecurity: boolean = null;
|
||||
|
||||
onMount(() => {
|
||||
collectionDocumentSecurity ??= $collection.documentSecurity;
|
||||
});
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
$collection.$permissions,
|
||||
collectionDocumentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
addNotification({
|
||||
message: 'Security has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_security');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Document Security</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputSwitch
|
||||
bind:value={collectionDocumentSecurity}
|
||||
id="security"
|
||||
label="Document Security" />
|
||||
</FormList>
|
||||
<p class="text">
|
||||
When document security is enabled, users will be able to access documents for which they
|
||||
have been granted <b>either Document or Collection permissions</b>.
|
||||
</p>
|
||||
<p class="text">
|
||||
If document security is disabled, users can access documents <b
|
||||
>only if they have Collection permissions</b
|
||||
>. Document permissions will be ignored.
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={collectionDocumentSecurity === $collection.documentSecurity}
|
||||
on:click={updateSecurity}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, InputSwitch } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { collection } from '../store';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
let enabled: boolean = null;
|
||||
|
||||
onMount(() => {
|
||||
enabled ??= $collection.enabled;
|
||||
});
|
||||
|
||||
async function toggleCollection() {
|
||||
try {
|
||||
await sdkForProject.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
$collection.$permissions,
|
||||
$collection.documentSecurity,
|
||||
enabled
|
||||
);
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
addNotification({
|
||||
message: `${$collection.name} has been updated`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent('submit_collection_update_enabled');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">{$collection.name}</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputSwitch
|
||||
id="toggle"
|
||||
label={enabled ? 'Enabled' : 'Disabled'}
|
||||
bind:value={enabled} />
|
||||
</ul>
|
||||
<div>
|
||||
<p>Created: {toLocaleDateTime($collection.$createdAt)}</p>
|
||||
<p>Last Updated: {toLocaleDateTime($collection.$updatedAt)}</p>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={enabled === $collection.enabled} on:click={toggleCollection}>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
@@ -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',
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>last accessed</TableCellHead>
|
||||
<TableCellHead>expiration date</TableCellHead>
|
||||
<TableCellHead>scopes</TableCellHead>
|
||||
<TableCellHead onlyDesktop>last accessed</TableCellHead>
|
||||
<TableCellHead onlyDesktop>expiration date</TableCellHead>
|
||||
<TableCellHead onlyDesktop>scopes</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.keys.keys as key}
|
||||
@@ -45,13 +45,13 @@
|
||||
<TableCellText title="Name">
|
||||
{key.name}
|
||||
</TableCellText>
|
||||
<TableCellText title="Last Accessed">
|
||||
<TableCellText onlyDesktop title="Last Accessed">
|
||||
{key.accessedAt ? toLocaleDateTime(key.accessedAt) : 'never'}
|
||||
</TableCellText>
|
||||
<TableCellText title="Expiration Date">
|
||||
<TableCellText onlyDesktop title="Expiration Date">
|
||||
{key.expire ? toLocaleDateTime(key.expire) : 'never'}
|
||||
</TableCellText>
|
||||
<TableCellText title="Expiration Date">
|
||||
<TableCellText onlyDesktop title="Expiration Date">
|
||||
{key.scopes.length} Scopes
|
||||
</TableCellText>
|
||||
</TableRowLink>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Dependencies } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ params, depends }) => {
|
||||
depends(Dependencies.PROJECT);
|
||||
depends(Dependencies.PLATFORM);
|
||||
|
||||
return {
|
||||
header: Header,
|
||||
|
||||
+16
-11
@@ -20,11 +20,8 @@
|
||||
import { project } from '../../../store';
|
||||
import { platform } from './store';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { PageData } from './$types';
|
||||
import { invalidate } from '$app/navigation';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
const types: Record<string, typeof SvelteComponent> = {
|
||||
web: Web,
|
||||
android: Android,
|
||||
@@ -41,20 +38,22 @@
|
||||
|
||||
let showDelete = false;
|
||||
let name: string = null;
|
||||
let updating = false;
|
||||
|
||||
onMount(() => {
|
||||
name ??= data.platform.name;
|
||||
name ??= $platform.name;
|
||||
});
|
||||
|
||||
const updateName = async () => {
|
||||
async function updateName() {
|
||||
updating = true;
|
||||
try {
|
||||
await sdkForConsole.projects.updatePlatform(
|
||||
$project.$id,
|
||||
data.platform.$id,
|
||||
$platform.$id,
|
||||
name,
|
||||
data.platform.key,
|
||||
data.platform.store,
|
||||
data.platform.hostname
|
||||
$platform.key,
|
||||
$platform.store,
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
addNotification({
|
||||
@@ -67,7 +66,13 @@
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$: {
|
||||
// When platform name is updated, finalize the updating flow
|
||||
$platform.name;
|
||||
updating = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -87,7 +92,7 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={name === $platform.name} submit>Update</Button>
|
||||
<Button disabled={name === $platform.name || updating} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
import { project } from '../../../store';
|
||||
import { platform } from './store';
|
||||
|
||||
let hostname: string = null;
|
||||
let key: string = null;
|
||||
|
||||
onMount(() => {
|
||||
hostname ??= $platform.hostname;
|
||||
key ??= $platform.key;
|
||||
});
|
||||
|
||||
const updateHostname = async () => {
|
||||
@@ -22,9 +22,9 @@
|
||||
$project.$id,
|
||||
$platform.$id,
|
||||
$platform.name,
|
||||
$platform.key,
|
||||
key,
|
||||
$platform.store,
|
||||
hostname
|
||||
$platform.hostname
|
||||
);
|
||||
invalidate(Dependencies.PLATFORM);
|
||||
trackEvent('submit_platform_update', {
|
||||
@@ -52,16 +52,16 @@
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="hostname"
|
||||
id="key"
|
||||
label="Package Name"
|
||||
bind:value={hostname}
|
||||
bind:value={key}
|
||||
required
|
||||
placeholder="com.company.appname" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={hostname === $platform.hostname} submit>Update</Button>
|
||||
<Button disabled={key === $platform.hostname} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
projectId,
|
||||
'android',
|
||||
$createPlatform.name,
|
||||
$createPlatform.key,
|
||||
undefined,
|
||||
undefined,
|
||||
$createPlatform.hostname
|
||||
undefined
|
||||
);
|
||||
|
||||
$createPlatform.$id = platform.$id;
|
||||
@@ -50,11 +50,11 @@
|
||||
required
|
||||
bind:value={$createPlatform.name} />
|
||||
<InputText
|
||||
id="hostname"
|
||||
id="key"
|
||||
label="Package Name"
|
||||
placeholder="com.company.appname"
|
||||
tooltip="Your package name is generally the applicationId in your app-level build.gradle file."
|
||||
required
|
||||
bind:value={$createPlatform.hostname} />
|
||||
bind:value={$createPlatform.key} />
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
|
||||
@@ -41,9 +41,9 @@
|
||||
projectId,
|
||||
platform,
|
||||
$createPlatform.name,
|
||||
$createPlatform.key,
|
||||
undefined,
|
||||
undefined,
|
||||
$createPlatform.hostname
|
||||
undefined
|
||||
);
|
||||
|
||||
$createPlatform.$id = response.$id;
|
||||
@@ -95,6 +95,6 @@
|
||||
placeholder="com.company.appname"
|
||||
tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."
|
||||
required
|
||||
bind:value={$createPlatform.hostname} />
|
||||
bind:value={$createPlatform.key} />
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
|
||||
+23
-14
@@ -25,7 +25,7 @@
|
||||
let platform: Platform = Platform.Android;
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const suggestions = ['*.vercel.app', '*.netlify.app', '*.gitpod.app'];
|
||||
const suggestions = ['*.vercel.app', '*.netlify.app', '*.gitpod.io'];
|
||||
const placeholder: Record<
|
||||
Platform,
|
||||
{
|
||||
@@ -97,9 +97,9 @@
|
||||
projectId,
|
||||
platform,
|
||||
$createPlatform.name,
|
||||
platform !== Platform.Web ? $createPlatform.key : undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
$createPlatform.hostname
|
||||
platform === Platform.Web ? $createPlatform.hostname : undefined
|
||||
);
|
||||
|
||||
$createPlatform.$id = response.$id;
|
||||
@@ -160,15 +160,16 @@
|
||||
placeholder={placeholder[platform].name}
|
||||
required
|
||||
bind:value={$createPlatform.name} />
|
||||
<div>
|
||||
<InputText
|
||||
id="hostname"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
tooltip={placeholder[platform].tooltip}
|
||||
required
|
||||
bind:value={$createPlatform.hostname} />
|
||||
{#if platform === Platform.Web}
|
||||
{#if platform === Platform.Web}
|
||||
<div>
|
||||
<InputText
|
||||
id="hostname"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
tooltip={placeholder[platform].tooltip}
|
||||
required
|
||||
bind:value={$createPlatform.hostname} />
|
||||
|
||||
<div class="u-flex u-gap-16 u-margin-block-start-8">
|
||||
{#each suggestions as suggestion}
|
||||
<Pill
|
||||
@@ -179,7 +180,15 @@
|
||||
</Pill>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<InputText
|
||||
id="key"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
tooltip={placeholder[platform].tooltip}
|
||||
required
|
||||
bind:value={$createPlatform.key} />
|
||||
{/if}
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import Dark from './dark.svg';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const suggestions = ['*.vercel.app', '*.netlify.app', '*.gitpod.app'];
|
||||
const suggestions = ['*.vercel.app', '*.netlify.app', '*.gitpod.io'];
|
||||
|
||||
$wizard.media = $app.themeInUse === 'dark' ? Dark : Light;
|
||||
async function beforeSubmit() {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
let name: string = null;
|
||||
let showDelete = false;
|
||||
let updating = false;
|
||||
const endpoint = sdkForConsole.client.config.endpoint;
|
||||
const projectId = $page.params.project;
|
||||
|
||||
@@ -24,7 +25,8 @@
|
||||
name ??= $project.name;
|
||||
});
|
||||
|
||||
const updateName = async () => {
|
||||
async function updateName() {
|
||||
updating = true;
|
||||
try {
|
||||
await sdkForConsole.projects.update($project.$id, name);
|
||||
invalidate(Dependencies.PROJECT);
|
||||
@@ -39,9 +41,15 @@
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const serviceUpdate = async (service: Service) => {
|
||||
$: {
|
||||
// When project name is updated, finalize the updating flow
|
||||
$project.name;
|
||||
updating = false;
|
||||
}
|
||||
|
||||
async function serviceUpdate(service: Service) {
|
||||
try {
|
||||
await sdkForConsole.projects.updateServiceStatus(
|
||||
$project.$id,
|
||||
@@ -65,7 +73,7 @@
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$: services.load($project);
|
||||
</script>
|
||||
@@ -105,7 +113,7 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={name === $project.name} submit>Update</Button>
|
||||
<Button disabled={name === $project.name || updating} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow
|
||||
TableRow,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -68,14 +68,14 @@
|
||||
</Button>
|
||||
</div>
|
||||
{#if data.domains.total}
|
||||
<Table>
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
<TableCellHead>Domain Name</TableCellHead>
|
||||
<TableCellHead width={150}>Domain Name</TableCellHead>
|
||||
<TableCellHead width={100} />
|
||||
<TableCellHead width={60}>Type</TableCellHead>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Value</TableCellHead>
|
||||
<TableCellHead />
|
||||
<TableCellHead width={90}>Name</TableCellHead>
|
||||
<TableCellHead width={90}>Value</TableCellHead>
|
||||
<TableCellHead width={40} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.domains.domains as domain}
|
||||
@@ -131,7 +131,7 @@
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableScroll>
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
|
||||
@@ -1,353 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { CardGrid, Box, Secret, Empty, EventModal, Heading } from '$lib/components';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormList,
|
||||
InputText,
|
||||
InputChoice,
|
||||
InputPassword
|
||||
} from '$lib/elements/forms';
|
||||
import { TableList, TableCellText, TableCell } from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { webhook } from './store';
|
||||
import { page } from '$app/stores';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import Delete from './delete.svelte';
|
||||
import Regenerate from './regenerate.svelte';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
let name: string = null;
|
||||
let url: string = null;
|
||||
|
||||
const eventSet: Writable<Set<string>> = writable(new Set());
|
||||
|
||||
let httpUser: string = null;
|
||||
let httpPass: string = null;
|
||||
let security = false;
|
||||
let showDelete = false;
|
||||
let showCreateEvent = false;
|
||||
let areEventsDisabled = true;
|
||||
let showRegenerate = false;
|
||||
|
||||
onMount(async () => {
|
||||
name ??= $webhook.name;
|
||||
url ??= $webhook.url;
|
||||
$eventSet = new Set($webhook.events);
|
||||
httpUser ??= $webhook.httpUser;
|
||||
httpPass ??= $webhook.httpPass;
|
||||
security = $webhook.security;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
name,
|
||||
$webhook.events,
|
||||
$webhook.url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook name has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_name');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateUrl() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
$webhook.events,
|
||||
url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook url has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_url');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateEvents() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
Array.from($eventSet),
|
||||
$webhook.url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
areEventsDisabled = true;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook events have been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_events');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
$webhook.events,
|
||||
$webhook.url,
|
||||
security,
|
||||
httpUser,
|
||||
httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook security has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_security');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleEvent(event: CustomEvent) {
|
||||
eventSet.set($eventSet.add(event.detail));
|
||||
}
|
||||
|
||||
$: if ($eventSet) {
|
||||
if (symmetricDifference(Array.from($eventSet), $webhook.events).length) {
|
||||
areEventsDisabled = false;
|
||||
} else areEventsDisabled = true;
|
||||
}
|
||||
import DangerZone from './dangerZone.svelte';
|
||||
import UpdateEvents from './updateEvents.svelte';
|
||||
import UpdateName from './updateName.svelte';
|
||||
import UpdateSecurity from './updateSecurity.svelte';
|
||||
import UpdateSignature from './updateSignature.svelte';
|
||||
import UpdateUrl from './updateURL.svelte';
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Signature Key</Heading>
|
||||
<p>
|
||||
Add the Signature Key to the X-Appwrite-Webhook-Signature header to validate your
|
||||
webhooks. <a
|
||||
href="https://appwrite.io/docs/webhooks#verification"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">Learn more about webhook validation.</a>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<div>
|
||||
<p>Key</p>
|
||||
<Secret bind:value={$webhook.signatureKey} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button on:click={() => (showRegenerate = true)} secondary submit>
|
||||
Regenerate Key
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Update Name</Heading>
|
||||
<p>Choose any name that will help you distinguish between Webhooks.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
bind:value={name}
|
||||
required
|
||||
placeholder="Enter name" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={name === $webhook.name || !name} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateUrl}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Update Url</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="url"
|
||||
label="POST URL"
|
||||
bind:value={url}
|
||||
required
|
||||
placeholder="https://example.com/callback" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={url === $webhook.url || !url} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateEvents}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Events</Heading>
|
||||
<p class="text">
|
||||
Set the events that will trigger your webhook. Maximum 100 events allowed.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if $eventSet.size}
|
||||
<TableList>
|
||||
{#each Array.from($eventSet) as event}
|
||||
<li class="table-row">
|
||||
<TableCellText title="id">
|
||||
{event}
|
||||
</TableCellText>
|
||||
<TableCell showOverflow title="options" width={40}>
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
aria-label="delete id"
|
||||
on:click|preventDefault={() => {
|
||||
$eventSet.delete(event);
|
||||
eventSet.set($eventSet);
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</TableCell>
|
||||
</li>
|
||||
{/each}
|
||||
</TableList>
|
||||
<div class="u-flex u-margin-block-start-16">
|
||||
<Button text noMargin on:click={() => (showCreateEvent = true)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="u-text">Add event</span>
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Empty on:click={() => (showCreateEvent = true)}>
|
||||
Add an event to get started
|
||||
</Empty>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={areEventsDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateSecurity}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Security</Heading>
|
||||
<p class="text">
|
||||
Set an optional basic HTTP authentication username and password to protect your
|
||||
endpoint from unauthorized access.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<div>
|
||||
<Heading tag="h3" size="7">HTTP Authentication</Heading>
|
||||
<p class="text">Use to secure your endpoint from untrusted sources.</p>
|
||||
</div>
|
||||
<InputText
|
||||
label="User"
|
||||
id="user"
|
||||
placeholder="Enter username"
|
||||
bind:value={httpUser} />
|
||||
<InputPassword
|
||||
label="Password"
|
||||
id="password"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
placeholder="Enter password"
|
||||
bind:value={httpPass} />
|
||||
|
||||
<InputChoice
|
||||
id="Security"
|
||||
label="Certificate verification (SSL/TLS)"
|
||||
bind:value={security}>
|
||||
<span class="u-error">Warning:</span> Untrusted or self-signed certificates
|
||||
may not be secure.
|
||||
<a
|
||||
href="https://appwrite.io/docs/custom-domains#enjoySSLCert"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
Learn more</a>
|
||||
</InputChoice>
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={httpUser === $webhook.httpUser &&
|
||||
httpPass === $webhook.httpPass &&
|
||||
security === $webhook.security}
|
||||
submit>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<CardGrid danger>
|
||||
<div>
|
||||
<Heading tag="h2" size="7">Delete Webhook</Heading>
|
||||
</div>
|
||||
<p>The webhook will be permanently deleted. This action is irreversible.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$webhook.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last updated: {toLocaleDateTime($webhook.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<UpdateSignature />
|
||||
<UpdateName />
|
||||
<UpdateUrl />
|
||||
<UpdateEvents />
|
||||
<UpdateSecurity />
|
||||
<DangerZone />
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
<Regenerate bind:show={showRegenerate} />
|
||||
|
||||
<EventModal bind:show={showCreateEvent} on:created={handleEvent} />
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { Box, CardGrid, Heading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { webhook } from './store';
|
||||
import Delete from './delete.svelte';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
|
||||
let showDelete = false;
|
||||
</script>
|
||||
|
||||
<CardGrid danger>
|
||||
<div>
|
||||
<Heading tag="h2" size="7">Delete Webhook</Heading>
|
||||
</div>
|
||||
<p>The webhook will be permanently deleted. This action is irreversible.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$webhook.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last updated: {toLocaleDateTime($webhook.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
@@ -0,0 +1,109 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Empty, EventModal, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { TableCell, TableCellText, TableList } from '$lib/elements/table';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { webhook } from './store';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const eventSet: Writable<Set<string>> = writable(new Set());
|
||||
|
||||
let showCreateEvent = false;
|
||||
let areEventsDisabled = true;
|
||||
|
||||
onMount(async () => {
|
||||
$eventSet = new Set($webhook.events);
|
||||
});
|
||||
|
||||
async function updateEvents() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
Array.from($eventSet),
|
||||
$webhook.url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
areEventsDisabled = true;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook events have been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_events');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleEvent(event: CustomEvent) {
|
||||
eventSet.set($eventSet.add(event.detail));
|
||||
}
|
||||
|
||||
$: if ($eventSet) {
|
||||
if (symmetricDifference(Array.from($eventSet), $webhook.events).length) {
|
||||
areEventsDisabled = false;
|
||||
} else areEventsDisabled = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateEvents}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Events</Heading>
|
||||
<p class="text">
|
||||
Set the events that will trigger your webhook. Maximum 100 events allowed.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if $eventSet.size}
|
||||
<TableList>
|
||||
{#each Array.from($eventSet) as event}
|
||||
<li class="table-row">
|
||||
<TableCellText title="id">
|
||||
{event}
|
||||
</TableCellText>
|
||||
<TableCell showOverflow title="options" width={40}>
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
aria-label="delete id"
|
||||
on:click|preventDefault={() => {
|
||||
$eventSet.delete(event);
|
||||
eventSet.set($eventSet);
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</TableCell>
|
||||
</li>
|
||||
{/each}
|
||||
</TableList>
|
||||
<div class="u-flex u-margin-block-start-16">
|
||||
<Button text noMargin on:click={() => (showCreateEvent = true)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="u-text">Add event</span>
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Empty on:click={() => (showCreateEvent = true)}>Add an event to get started</Empty>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={areEventsDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<EventModal bind:show={showCreateEvent} on:created={handleEvent} />
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { webhook } from './store';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
let name: string = null;
|
||||
|
||||
onMount(async () => {
|
||||
name ??= $webhook.name;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
name,
|
||||
$webhook.events,
|
||||
$webhook.url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook name has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_name');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Update Name</Heading>
|
||||
<p>Choose any name that will help you distinguish between Webhooks.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
bind:value={name}
|
||||
required
|
||||
placeholder="Enter name" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={name === $webhook.name || !name} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormList,
|
||||
InputChoice,
|
||||
InputPassword,
|
||||
InputText
|
||||
} from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { webhook } from './store';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
let httpUser: string = null;
|
||||
let httpPass: string = null;
|
||||
let security = false;
|
||||
|
||||
onMount(async () => {
|
||||
httpUser ??= $webhook.httpUser;
|
||||
httpPass ??= $webhook.httpPass;
|
||||
security = $webhook.security;
|
||||
});
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
$webhook.events,
|
||||
$webhook.url,
|
||||
security,
|
||||
httpUser,
|
||||
httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook security has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_security');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateSecurity}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Security</Heading>
|
||||
<p class="text">
|
||||
Set an optional basic HTTP authentication username and password to protect your endpoint
|
||||
from unauthorized access.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<div>
|
||||
<Heading tag="h3" size="7">HTTP Authentication</Heading>
|
||||
<p class="text">Use to secure your endpoint from untrusted sources.</p>
|
||||
</div>
|
||||
<InputText
|
||||
label="User"
|
||||
id="user"
|
||||
placeholder="Enter username"
|
||||
bind:value={httpUser} />
|
||||
<InputPassword
|
||||
label="Password"
|
||||
id="password"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
placeholder="Enter password"
|
||||
bind:value={httpPass} />
|
||||
|
||||
<InputChoice
|
||||
id="Security"
|
||||
label="Certificate verification (SSL/TLS)"
|
||||
bind:value={security}>
|
||||
<span class="u-error">Warning:</span> Untrusted or self-signed certificates may
|
||||
not be secure.
|
||||
<a
|
||||
href="https://appwrite.io/docs/custom-domains#enjoySSLCert"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
Learn more</a>
|
||||
</InputChoice>
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={httpUser === $webhook.httpUser &&
|
||||
httpPass === $webhook.httpPass &&
|
||||
security === $webhook.security}
|
||||
submit>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid, Heading, Secret } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import Regenerate from './regenerate.svelte';
|
||||
import { webhook } from './store';
|
||||
|
||||
let showRegenerate = false;
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Signature Key</Heading>
|
||||
<p>
|
||||
Add the Signature Key to the X-Appwrite-Webhook-Signature header to validate your webhooks. <a
|
||||
href="https://appwrite.io/docs/webhooks#verification"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">Learn more about webhook validation.</a>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<div>
|
||||
<p>Key</p>
|
||||
<Secret bind:value={$webhook.signatureKey} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button on:click={() => (showRegenerate = true)} secondary submit>Regenerate Key</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Regenerate bind:show={showRegenerate} />
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { webhook } from './store';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
let url: string = null;
|
||||
|
||||
onMount(async () => {
|
||||
url ??= $webhook.url;
|
||||
});
|
||||
|
||||
async function updateUrl() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateWebhook(
|
||||
projectId,
|
||||
$webhook.$id,
|
||||
$webhook.name,
|
||||
$webhook.events,
|
||||
url,
|
||||
$webhook.security,
|
||||
$webhook.httpUser,
|
||||
$webhook.httpPass
|
||||
);
|
||||
invalidate(Dependencies.WEBHOOK);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook url has been updated'
|
||||
});
|
||||
trackEvent('submit_webhook_update_url');
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={updateUrl}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="7">Update Url</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="url"
|
||||
label="POST URL"
|
||||
bind:value={url}
|
||||
required
|
||||
placeholder="https://example.com/callback" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={url === $webhook.url || !url} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+11
-11
@@ -86,9 +86,9 @@
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Filename</TableCellHead>
|
||||
<TableCellHead width={140}>Type</TableCellHead>
|
||||
<TableCellHead width={100}>Size</TableCellHead>
|
||||
<TableCellHead width={120}>Created</TableCellHead>
|
||||
<TableCellHead onlyDesktop width={140}>Type</TableCellHead>
|
||||
<TableCellHead onlyDesktop width={100}>Size</TableCellHead>
|
||||
<TableCellHead onlyDesktop width={120}>Created</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -99,17 +99,17 @@
|
||||
<div class="u-flex u-gap-12 u-main-space-between">
|
||||
<span class="avatar is-size-small is-color-empty" />
|
||||
|
||||
<span class="text u-trim"> {file.name}</span>
|
||||
<span class="text u-trim">{file.name}</span>
|
||||
<div>
|
||||
<Pill warning>Pending</Pill>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Type">{file.mimeType}</TableCellText>
|
||||
<TableCellText title="Size">
|
||||
<TableCellText onlyDesktop title="Type">{file.mimeType}</TableCellText>
|
||||
<TableCellText onlyDesktop title="Size">
|
||||
{calculateSize(file.sizeOriginal)}
|
||||
</TableCellText>
|
||||
<TableCellText title="Date Created">
|
||||
<TableCellText onlyDesktop title="Date Created">
|
||||
{toLocaleDate(file.$createdAt)}
|
||||
</TableCellText>
|
||||
<TableCell>
|
||||
@@ -137,14 +137,14 @@
|
||||
<TableCell title="Name">
|
||||
<div class="u-flex u-gap-12">
|
||||
<Avatar size={32} src={getPreview(file.$id)} name={file.name} />
|
||||
<span class="text u-trim"> {file.name}</span>
|
||||
<span class="text u-trim">{file.name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Type">{file.mimeType}</TableCellText>
|
||||
<TableCellText title="Size">
|
||||
<TableCellText onlyDesktop title="Type">{file.mimeType}</TableCellText>
|
||||
<TableCellText onlyDesktop title="Size">
|
||||
{calculateSize(file.sizeOriginal)}
|
||||
</TableCellText>
|
||||
<TableCellText title="Date Created">
|
||||
<TableCellText onlyDesktop title="Date Created">
|
||||
{toLocaleDate(file.$createdAt)}
|
||||
</TableCellText>
|
||||
<TableCell showOverflow>
|
||||
|
||||
+54
-12
@@ -36,8 +36,9 @@
|
||||
arePermsDisabled = true,
|
||||
encryption: boolean = null,
|
||||
antivirus: boolean = null,
|
||||
maxSize: number;
|
||||
let byteUnit = writable('KB');
|
||||
maxSize: number,
|
||||
compression: string = null;
|
||||
let byteUnit = writable('MB');
|
||||
let sizeInBytes: number = null;
|
||||
const options = [
|
||||
{ label: 'Bytes', value: 'Bytes' },
|
||||
@@ -45,6 +46,11 @@
|
||||
{ label: 'Megabytes', value: 'MB' },
|
||||
{ label: 'Gigabytes', value: 'GB' }
|
||||
];
|
||||
const compressionOptions = [
|
||||
{ label: 'None', value: 'none' },
|
||||
{ label: 'Gzip', value: 'gzip' },
|
||||
{ label: 'Zstd', value: 'zstd' }
|
||||
];
|
||||
let suggestedExtensions = ['jpg', 'png', 'svg', 'gif', 'html', 'pdf', 'mp4'];
|
||||
let extensions = $bucket.allowedFileExtensions;
|
||||
let isExtensionsDisabled = true;
|
||||
@@ -57,8 +63,10 @@
|
||||
bucketPermissions ??= $bucket.$permissions;
|
||||
encryption ??= $bucket.encryption;
|
||||
antivirus ??= $bucket.antivirus;
|
||||
maxSize = $bucket.maximumFileSize / 1024;
|
||||
maxSize = $bucket.maximumFileSize / 1024 / 1024;
|
||||
compression ??= $bucket.compression;
|
||||
});
|
||||
|
||||
$: if (bucketPermissions) {
|
||||
if (symmetricDifference(bucketPermissions, $bucket.$permissions).length) {
|
||||
arePermsDisabled = false;
|
||||
@@ -192,6 +200,17 @@
|
||||
);
|
||||
}
|
||||
|
||||
function updateCompression() {
|
||||
updateBucket(
|
||||
{
|
||||
compression
|
||||
},
|
||||
{
|
||||
trackEventName: 'submit_bucket_update_size'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateAllowedExtensions() {
|
||||
updateBucket(
|
||||
{
|
||||
@@ -234,8 +253,8 @@
|
||||
id="toggle"
|
||||
bind:value={enabled} />
|
||||
</FormList>
|
||||
<p>Created: {toLocaleDateTime($bucket.$createdAt)}</p>
|
||||
<p>Last Updated: {toLocaleDateTime($bucket.$updatedAt)}</p>
|
||||
<p class="text">Created: {toLocaleDateTime($bucket.$createdAt)}</p>
|
||||
<p class="text">Last Updated: {toLocaleDateTime($bucket.$updatedAt)}</p>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -320,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>
|
||||
@@ -381,17 +400,40 @@
|
||||
<Button
|
||||
disabled={encryption === $bucket.encryption &&
|
||||
antivirus === $bucket.antivirus}
|
||||
submit
|
||||
>Update
|
||||
submit>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateCompression}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="6">Update Compression Algorithm</Heading>
|
||||
<p class="text">
|
||||
Choose an algorithm for compression. For files larger than 20MB, compression
|
||||
will be skipped even if it's enabled.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputSelect
|
||||
id="compression"
|
||||
label="Compression algorithm"
|
||||
options={compressionOptions}
|
||||
bind:value={compression} />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={compression === $bucket.compression} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateMaxSize}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="6">Update Maximum File Size</Heading>
|
||||
<p>Set the maximum file size allowed in the bucket.</p>
|
||||
<p class="text">Set the maximum file size allowed in the bucket.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul class="u-flex u-gap-12">
|
||||
<InputNumber
|
||||
@@ -414,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>
|
||||
@@ -453,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>
|
||||
@@ -462,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>
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import LoginLight from '$lib/images/login/login-light-mode.svg';
|
||||
import LoginDark from '$lib/images/login/login-dark-mode.svg';
|
||||
|
||||
let imgLight = LoginLight;
|
||||
let imgDark = LoginDark;
|
||||
|
||||
let name: string, mail: string, pass: string, disabled: boolean;
|
||||
let terms = false;
|
||||
@@ -42,7 +47,7 @@
|
||||
<title>Sign up - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
<Unauthenticated>
|
||||
<Unauthenticated {imgLight} {imgDark}>
|
||||
<svelte:fragment slot="title">Sign up</svelte:fragment>
|
||||
<svelte:fragment>
|
||||
<Form on:submit={register}>
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormItem,
|
||||
InputChoice,
|
||||
InputEmail,
|
||||
InputPassword,
|
||||
InputText
|
||||
} from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { endpoint, sdkForConsole } from '$lib/stores/sdk';
|
||||
import { Unauthenticated } from '$lib/layout';
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { isCloud } from '$lib/stores/app';
|
||||
import LoginLight from '$lib/images/login/login-light-mode.svg';
|
||||
import LoginDark from '$lib/images/login/login-dark-mode.svg';
|
||||
|
||||
let slug = $page.params.slug;
|
||||
let imgLight = LoginLight;
|
||||
let imgDark = LoginDark;
|
||||
|
||||
onMount(async () => {
|
||||
if (isCloud) {
|
||||
switch (slug) {
|
||||
case 'mlh':
|
||||
imgDark = (await import('./mlh-dark.svg')).default;
|
||||
imgLight = (await import('./mlh-light.svg')).default;
|
||||
title = 'Welcome MLH Hackers!';
|
||||
break;
|
||||
case 'appwrite':
|
||||
imgDark = (await import('$lib/images/appwrite.svg')).default;
|
||||
imgLight = (await import('$lib/images/appwrite.svg')).default;
|
||||
title = 'Welcome Appwriters!';
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let name: string, mail: string, pass: string, code: string, disabled: boolean;
|
||||
let title = 'Sign up';
|
||||
let terms = false;
|
||||
|
||||
async function invite() {
|
||||
try {
|
||||
disabled = true;
|
||||
const res = await fetch(`${endpoint}/account/invite`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userId: 'unique()',
|
||||
email: mail,
|
||||
password: pass,
|
||||
code: code,
|
||||
name: name ?? ''
|
||||
})
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error((await res.json()).message);
|
||||
} else {
|
||||
await sdkForConsole.account.createEmailSession(mail, pass);
|
||||
await sdkForConsole.account.updatePrefs({ code });
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
await goto(`${base}/console`);
|
||||
trackEvent('submit_account_create', { code: code });
|
||||
}
|
||||
} catch (error) {
|
||||
disabled = false;
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Sign up - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
<Unauthenticated {imgLight} {imgDark}>
|
||||
<svelte:fragment slot="title">{title}</svelte:fragment>
|
||||
<svelte:fragment>
|
||||
<Form on:submit={invite}>
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Your name"
|
||||
autofocus={true}
|
||||
bind:value={name} />
|
||||
<InputEmail
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Your email"
|
||||
required={true}
|
||||
bind:value={mail} />
|
||||
<InputPassword
|
||||
id="password"
|
||||
label="Password"
|
||||
placeholder="Your password"
|
||||
required={true}
|
||||
showPasswordButton={true}
|
||||
bind:value={pass} />
|
||||
<InputText
|
||||
id="Code"
|
||||
label="Code"
|
||||
placeholder="Your code"
|
||||
required={true}
|
||||
bind:value={code} />
|
||||
<InputChoice required value={terms} id="terms" label="terms" showLabel={false}>
|
||||
By registering, you agree that you have read, understand, and acknowledge our <a
|
||||
class="link"
|
||||
href="https://appwrite.io/policy/privacy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Privacy Policy</a>
|
||||
and accept our
|
||||
<a
|
||||
class="link"
|
||||
href="https://appwrite.io/policy/terms"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">General Terms of Use</a
|
||||
>.</InputChoice>
|
||||
<FormItem>
|
||||
<Button fullWidth submit {disabled}>Sign up</Button>
|
||||
</FormItem>
|
||||
</FormList>
|
||||
</Form>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="links">
|
||||
<li class="inline-links-item">
|
||||
<span class="text">
|
||||
Already got an account? <a class="link" href={`${base}/login`}>Sign in</a>
|
||||
</span>
|
||||
</li>
|
||||
</svelte:fragment>
|
||||
</Unauthenticated>
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 63 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 63 KiB |
Reference in New Issue
Block a user