Merge branch 'main' of github.com:appwrite/appwrite-console-poc into fix-function-review

This commit is contained in:
Arman
2022-11-25 15:37:22 +01:00
65 changed files with 1272 additions and 814 deletions
+3 -2
View File
@@ -1,3 +1,4 @@
VITE_APPWRITE_ENDPOINT=
VITE_GOOGLE_ANALYTICS=
VITE_SENTRY_DSN=
VITE_GA_PROJECT=
VITE_SENTRY_DSN=
VITE_CONSOLE_MODE=self-hosted
-4
View File
@@ -4,10 +4,6 @@
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
[![appwrite.io](https://img.shields.io/badge/appwrite-.io-f02e65?style=flat-square)](https://appwrite.io)
<p align="center">
<a href="https://www.producthunt.com/posts/console-2-0?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-console&#0045;2&#0045;0" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=367206&theme=light" alt="Console&#0032;2&#0046;0 - Backend&#0032;as&#0032;a&#0032;Service&#0044;&#0032;reimagined&#0032;by&#0032;Appwrite | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</p>
![Appwrite](github.png)
[Appwrite](https://appwrite.io) Console is the Graphical User Interface that developers interact with when accessing their Appwrite instance in the web browser.
+3 -1
View File
@@ -3,12 +3,13 @@ import googleAnalytics from '@analytics/google-analytics';
import { get } from 'svelte/store';
import { page } from '$app/stores';
import { user } from '$lib/stores/user';
import { Mode } from '$lib/constants';
const analytics = Analytics({
app: 'appwrite',
plugins: [
googleAnalytics({
measurementIds: ['G-R4YJ9JN8L4']
measurementIds: [import.meta.env.VITE_GA_PROJECT?.toString() || 'G-R4YJ9JN8L4']
})
]
});
@@ -48,6 +49,7 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo
action: event,
label: event,
url: window.location.origin + path,
account: import.meta.env.VITE_CONSOLE_MODE?.toString() || Mode.SELF_HOSTED,
data: {
email,
name,
+5
View File
@@ -2,6 +2,11 @@ export const PAGE_LIMIT = 12; // default page limit
export const CARD_LIMIT = 6; // default card limit
export const INTERVAL = 5 * 60000; // default interval to check for feedback
export enum Mode {
CLOUD = 'mode:cloud',
SELF_HOSTED = 'mode:self-hosted'
}
export enum Dependencies {
ORGANIZATION = 'dependency:organization',
PROJECT = 'dependency:project',
+15
View File
@@ -3,6 +3,7 @@ export function intersection(arr1: unknown[], arr2: unknown[]) {
const intersection = new Set(arr1.filter((elem) => set.has(elem)));
return Array.from(intersection);
}
export function difference(arr1: unknown[], arr2: unknown[]) {
const set = new Set(arr2);
const intersection = new Set(arr1.filter((elem) => !set.has(elem)));
@@ -12,3 +13,17 @@ export function difference(arr1: unknown[], arr2: unknown[]) {
export function symmetricDifference(arr1: unknown[], arr2: unknown[]) {
return difference(arr1, arr2).concat(difference(arr2, arr1));
}
/**
* Removes the element at the specified index from the array, and returns a new array.
*
* @export
* @template T
* @param {T[]} arr
* @param {number} index
* @returns {T[]}
*/
export function remove<T>(arr: T[], index: number): T[] {
// Remove the element at the given index, return a new array
return [...arr.slice(0, index), ...arr.slice(index + 1)];
}
+22
View File
@@ -5,6 +5,8 @@ import Apple from '../../routes/console/project-[project]/auth/appleOAuth.svelte
import Microsoft from '../../routes/console/project-[project]/auth/microsoftOAuth.svelte';
import Okta from '../../routes/console/project-[project]/auth/oktaOAuth.svelte';
import Auth0 from '../../routes/console/project-[project]/auth/auth0OAuth.svelte';
import Authentik from '../../routes/console/project-[project]/auth/authentikOAuth.svelte';
import GitLab from '../../routes/console/project-[project]/auth/gitlabOAuth.svelte';
import Main from '../../routes/console/project-[project]/auth/mainOAuth.svelte';
export type Provider = Models.Provider & {
@@ -36,6 +38,13 @@ const setProviders = (project: Models.Project): Provider[] => {
docs = 'https://auth0.com/developers';
component = Auth0;
break;
case 'authentik':
docs = 'https://goauthentik.io/integrations/sources/oauth/';
component = Authentik;
break;
case 'autodesk':
docs = 'https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/';
break;
case 'bitbucket':
docs = 'https://developer.atlassian.com/bitbucket';
break;
@@ -45,12 +54,21 @@ const setProviders = (project: Models.Project): Provider[] => {
case 'box':
docs = 'https://developer.box.com/reference/';
break;
case 'dailymotion':
docs = 'https://developers.dailymotion.com/api/';
break;
case 'discord':
docs = 'https://discordapp.com/developers/docs/topics/oauth2';
break;
case 'disqus':
docs = 'https://disqus.com/api/docs/auth/';
break;
case 'dropbox':
docs = 'https://www.dropbox.com/developers/documentation';
break;
case 'etsy':
docs = 'https://developers.etsy.com/';
break;
case 'facebook':
docs = 'https://developers.facebook.com/';
break;
@@ -59,6 +77,7 @@ const setProviders = (project: Models.Project): Provider[] => {
break;
case 'gitlab':
docs = 'https://docs.gitlab.com/ee/api/';
component = GitLab;
break;
case 'google':
docs = 'https://support.google.com/googleapi/answer/6158849';
@@ -84,6 +103,9 @@ const setProviders = (project: Models.Project): Provider[] => {
icon = 'paypal';
docs = 'https://developer.paypal.com/docs/api/overview/';
break;
case 'podio':
docs = 'https://developers.podio.com/doc/oauth-authorization';
break;
case 'salesforce':
docs = 'https://developer.salesforce.com/docs/';
break;
+9 -4
View File
@@ -8,9 +8,6 @@ import type { LayoutLoad } from './$types';
export const ssr = false;
export const load: LayoutLoad = async ({ depends, url }) => {
if (url.pathname.startsWith('/auth')) {
return;
}
depends(Dependencies.ACCOUNT);
try {
const account = await sdkForConsole.account.get();
@@ -20,7 +17,15 @@ export const load: LayoutLoad = async ({ depends, url }) => {
organizations: sdkForConsole.teams.list()
};
} catch (error) {
const acceptedRoutes = ['/login', '/register', '/recover', '/invite'];
const acceptedRoutes = [
'/login',
'/register',
'/recover',
'/invite',
'/auth/magic-url',
'/auth/oauth2/success',
'/auth/oauth2/failure'
];
if (!acceptedRoutes.includes(url.pathname)) {
throw redirect(303, '/login');
+4 -2
View File
@@ -1,17 +1,19 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { Modal } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdkForConsole } from '$lib/stores/sdk';
import { user } from '$lib/stores/user';
export let showDelete = false;
const deleteAccount = async () => {
try {
await sdkForConsole.users.delete($user.$id);
await sdkForConsole.account.updateStatus();
showDelete = false;
invalidate(Dependencies.ACCOUNT);
addNotification({
type: 'success',
message: `Account was deleted `
@@ -55,7 +55,9 @@
</button>
<svelte:fragment slot="list">
{#each $organizationList.teams as org}
<DropListLink href={`${base}/console/organization-${org.$id}`}>
<DropListLink
href={`${base}/console/organization-${org.$id}`}
on:click={() => (showDropdown = false)}>
{org.name}
</DropListLink>
{/each}
@@ -84,7 +84,7 @@
<CopyInput
value={`${
sdkForConsole.client.config.endpoint
}/account/session/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -93,7 +93,7 @@
<CopyInput
value={`${
sdkForConsole.client.config.endpoint
}/account/session/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -0,0 +1,108 @@
<script lang="ts">
import { page } from '$app/stores';
import { Modal, CopyInput, Alert } from '$lib/components';
import { Button, InputPassword, InputText, InputSwitch, FormList } from '$lib/elements/forms';
import { sdkForConsole } from '$lib/stores/sdk';
import type { Provider } from '$lib/stores/oauth-providers';
import { addNotification } from '$lib/stores/notifications';
import { onMount } from 'svelte';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
export let provider: Provider;
let appId: string = null;
let enabled: boolean = null;
let clientSecret: string = null;
let authentikDomain: string = null;
let error: string;
onMount(() => {
appId ??= provider.appId;
enabled ??= provider.enabled;
if (provider.secret) ({ clientSecret, authentikDomain } = JSON.parse(provider.secret));
});
const projectId = $page.params.project;
const update = async () => {
try {
await sdkForConsole.projects.updateOAuth2(
projectId,
provider.name.toLowerCase(),
appId,
secret,
enabled
);
addNotification({
type: 'success',
message: `${provider.name} authentication has been ${
provider.enabled ? 'enabled' : 'disabled'
}`
});
trackEvent('submit_provider_update', {
provider,
enabled
});
provider = null;
invalidate(Dependencies.PROJECT);
} catch ({ message }) {
error = message;
}
};
$: secret =
clientSecret && authentikDomain
? JSON.stringify({ clientSecret, authentikDomain })
: provider.secret;
</script>
<Modal {error} size="big" show on:submit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
info you can
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer"
>visit the docs.</a>
</p>
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
<InputText
id="clientID"
label="Client ID"
autofocus={true}
placeholder="Enter ID"
bind:value={appId} />
<InputPassword
id="secret"
label="Client Secret"
placeholder="Enter Client Secret"
minlength={0}
showPasswordButton
bind:value={clientSecret} />
<InputText
id="domain"
label="Authentik Base-Domain"
placeholder="Your Authentik domain"
bind:value={authentikDomain} />
<Alert type="info">
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
</Alert>
<div>
<p>URI</p>
<CopyInput
value={`${
sdkForConsole.client.config.endpoint
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
<Button
disabled={(secret === provider.secret &&
enabled === provider.enabled &&
appId === provider.appId) ||
!(appId && clientSecret && authentikDomain)}
submit>Update</Button>
</svelte:fragment>
</Modal>
@@ -0,0 +1,106 @@
<script lang="ts">
import { page } from '$app/stores';
import { Modal, CopyInput, Alert } from '$lib/components';
import { Button, InputPassword, InputText, InputSwitch, FormList } from '$lib/elements/forms';
import { sdkForConsole } from '$lib/stores/sdk';
import type { Provider } from '$lib/stores/oauth-providers';
import { addNotification } from '$lib/stores/notifications';
import { onMount } from 'svelte';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
export let provider: Provider;
let appId: string = null;
let enabled: boolean = null;
let clientSecret: string = null;
let endpoint: string = null;
let error: string;
onMount(() => {
appId ??= provider.appId;
enabled ??= provider.enabled;
if (provider.secret) ({ clientSecret, endpoint } = JSON.parse(provider.secret));
});
const projectId = $page.params.project;
const update = async () => {
try {
await sdkForConsole.projects.updateOAuth2(
projectId,
provider.name.toLowerCase(),
appId,
secret,
enabled
);
addNotification({
type: 'success',
message: `${provider.name} authentication has been ${
provider.enabled ? 'enabled' : 'disabled'
}`
});
trackEvent('submit_provider_update', {
provider,
enabled
});
provider = null;
invalidate(Dependencies.PROJECT);
} catch ({ message }) {
error = message;
}
};
$: secret =
clientSecret && endpoint ? JSON.stringify({ clientSecret, endpoint }) : provider.secret;
</script>
<Modal {error} size="big" show on:submit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
info you can
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer"
>visit the docs.</a>
</p>
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
<InputText
id="appID"
label="App ID"
autofocus={true}
placeholder="Enter ID"
bind:value={appId} />
<InputPassword
id="secret"
label="App Secret"
placeholder="Enter App Secret"
minlength={0}
showPasswordButton
bind:value={clientSecret} />
<InputText
id="endpoint"
label="Endpoint (optional)"
placeholder="Your endpoint"
bind:value={endpoint} />
<Alert type="info">
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
</Alert>
<div>
<p>URI</p>
<CopyInput
value={`${
sdkForConsole.client.config.endpoint
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
<Button
disabled={(secret === provider.secret &&
enabled === provider.enabled &&
appId === provider.appId) ||
!(appId && clientSecret && endpoint)}
submit>Update</Button>
</svelte:fragment>
</Modal>
@@ -84,7 +84,7 @@
<CopyInput
value={`${
sdkForConsole.client.config.endpoint
}/account/session/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -93,7 +93,7 @@
<CopyInput
value={`${
sdkForConsole.client.config.endpoint
}/account/session/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -35,7 +35,8 @@
projectId,
provider.name.toLowerCase(),
appId,
secret
secret,
enabled
);
addNotification({
type: 'success',
@@ -102,7 +103,7 @@
<CopyInput
value={`${
sdkForConsole.client.config.endpoint
}/account/session/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -1,500 +1,20 @@
<script lang="ts">
import { toLocaleDateTime } from '$lib/helpers/date';
import {
CardGrid,
Box,
DropList,
DropListItem,
Heading,
AvatarInitials
} from '$lib/components';
import { Pill } from '$lib/elements';
import {
Button,
Form,
InputText,
InputEmail,
InputPassword,
InputPhone
} from '$lib/elements/forms';
import { Container } from '$lib/layout';
import { sdkForProject } from '$lib/stores/sdk';
import { addNotification } from '$lib/stores/notifications';
import DeleteUser from './deleteUser.svelte';
import { user } from './store';
import { onMount } from 'svelte';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
$: if (prefs) {
if (JSON.stringify(prefs) !== JSON.stringify(Object.entries($user.prefs))) {
if (!!prefs[prefs.length - 1][0] && !!prefs[prefs.length - 1][1]) {
arePrefsDisabled = false;
} else {
arePrefsDisabled = true;
}
} else {
arePrefsDisabled = true;
}
}
let showDelete = false;
let userName: string = null,
userEmail: string = null,
userPhone: string = null,
newPassword: string = null;
let prefs: [string, unknown][] = null;
let arePrefsDisabled = true;
let showVerifcationDropdown = false;
onMount(async () => {
prefs = Object.entries($user.prefs);
if (!prefs?.length) {
prefs.push(['', '']);
}
userName ??= $user.name;
userEmail ??= $user.email;
userPhone ??= $user.phone;
});
async function updateVerificationEmail() {
showVerifcationDropdown = false;
try {
await sdkForProject.users.updateEmailVerification($user.$id, !$user.emailVerification);
invalidate(Dependencies.USER);
addNotification({
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
$user.emailVerification ? 'verified' : 'unverified'
}`,
type: 'success'
});
trackEvent('submit_user_update_verification_email');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updateVerificationPhone() {
showVerifcationDropdown = false;
try {
await sdkForProject.users.updatePhoneVerification($user.$id, !$user.phoneVerification);
invalidate(Dependencies.USER);
addNotification({
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
$user.phoneVerification ? 'verified' : 'unverified'
}`,
type: 'success'
});
trackEvent('submit_user_update_verification_phone');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updateStatus() {
try {
await sdkForProject.users.updateStatus($user.$id, !$user.status);
invalidate(Dependencies.USER);
addNotification({
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
$user.status ? 'unblocked' : 'blocked'
}`,
type: 'success'
});
trackEvent('submit_user_update_status');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updateName() {
try {
await sdkForProject.users.updateName($user.$id, userName);
invalidate(Dependencies.USER);
addNotification({
message: 'Name has been updated',
type: 'success'
});
trackEvent('submit_user_update_name');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updateEmail() {
try {
await sdkForProject.users.updateEmail($user.$id, userEmail);
invalidate(Dependencies.USER);
addNotification({
message: 'Email has been updated',
type: 'success'
});
trackEvent('submit_user_update_email');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updatePhone() {
try {
await sdkForProject.users.updatePhone($user.$id, userPhone);
invalidate(Dependencies.USER);
addNotification({
message: 'Phone has been updated',
type: 'success'
});
trackEvent('submit_user_update_phone');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updatePassword() {
try {
await sdkForProject.users.updatePassword($user.$id, newPassword);
newPassword = null;
addNotification({
message: 'Password has been updated',
type: 'success'
});
trackEvent('submit_user_update_password');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updatePrefs() {
try {
let updatedPrefs = Object.fromEntries(prefs);
await sdkForProject.users.updatePrefs($user.$id, updatedPrefs);
invalidate(Dependencies.USER);
arePrefsDisabled = true;
addNotification({
message: 'Preferences have been updated',
type: 'success'
});
trackEvent('submit_user_update_preferences');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
import DangerZone from './dangerZone.svelte';
import UpdateEmail from './updateEmail.svelte';
import UpdateName from './updateName.svelte';
import UpdatePassword from './updatePassword.svelte';
import UpdatePhone from './updatePhone.svelte';
import UpdatePrefs from './updatePrefs.svelte';
import UpdateStatus from './updateStatus.svelte';
</script>
<Container>
<CardGrid>
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16">
{#if $user.email || $user.phone}
{#if $user.name}
<AvatarInitials size={48} name={$user.name} />
<Heading tag="h6" size="7">{$user.name}</Heading>
{:else}
<div class="avatar">
<span class="icon-minus-sm" aria-hidden="true" />
</div>
{/if}
{:else}
<div class="avatar">
<span class="icon-anonymous" aria-hidden="true" />
</div>
{/if}
</div>
<svelte:fragment slot="aside">
<div class="u-flex u-main-space-between">
<div>
{#if $user.email}
<p class="title">{$user.email}</p>
{/if}
{#if $user.phone}
<p class="title">{$user.phone}</p>
{/if}
<p>Joined: {toLocaleDateTime($user.registration)}</p>
</div>
{#if !$user.status}
<Pill danger>blocked</Pill>
{:else if $user.email && $user.phone}
<Pill success={$user.emailVerification || $user.phoneVerification}>
{$user.emailVerification && $user.phoneVerification
? 'verified'
: $user.emailVerification
? 'verified email'
: $user.phoneVerification
? 'verified phone'
: 'unverified'}
</Pill>
{:else}
<Pill success={$user.emailVerification || $user.phoneVerification}>
{$user.emailVerification
? 'verified '
: $user.phoneVerification
? 'verified '
: 'unverified'}
</Pill>
{/if}
</div>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button text={$user.status} secondary={!$user.status} on:click={() => updateStatus()}>
{$user.status ? 'Block account' : 'Unblock account'}
</Button>
{#if $user.status}
{#if $user.phone && $user.email}
<DropList bind:show={showVerifcationDropdown} placement="top-start">
<Button
secondary
on:click={() => (showVerifcationDropdown = !showVerifcationDropdown)}>
{$user.emailVerification ? 'Unverify' : 'Verify'} account
</Button>
<svelte:fragment slot="list">
<DropListItem icon="mail" on:click={() => updateVerificationEmail()}>
{$user.emailVerification ? 'Unverify' : 'Verify'} email
</DropListItem>
<DropListItem icon="phone" on:click={() => updateVerificationPhone()}>
{$user.phoneVerification ? 'Unverify' : 'Verify'} phone
</DropListItem>
</svelte:fragment>
</DropList>
{:else if $user.email}
<Button secondary on:click={() => updateVerificationEmail()}>
{$user.emailVerification ? 'Unverify' : 'Verify'} account
</Button>
{:else if $user.phone}
<Button secondary on:click={() => updateVerificationPhone()}>
{$user.phoneVerification ? 'Unverify' : 'Verify'} account
</Button>
{/if}
{/if}
</svelte:fragment>
</CardGrid>
<Form on:submit={updateName}>
<CardGrid>
<Heading tag="h6" size="7">Update Name</Heading>
<svelte:fragment slot="aside">
<ul>
<InputText
id="name"
label="Name"
placeholder="Enter name"
autocomplete={false}
bind:value={userName} />
</ul>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={userName === $user.name} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
<Form on:submit={updateEmail}>
<CardGrid>
<Heading tag="h6" size="7">Update Email</Heading>
<svelte:fragment slot="aside">
<ul>
<InputEmail
id="email"
label="Email"
placeholder="Enter email"
autocomplete={false}
bind:value={userEmail} />
</ul>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={userEmail === $user.email} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
<Form on:submit={updatePhone}>
<CardGrid>
<Heading tag="h6" size="7">Update Phone</Heading>
<svelte:fragment slot="aside">
<ul>
<InputPhone
id="phone"
label="Phone"
placeholder="Enter phone number"
autocomplete={false}
bind:value={userPhone} />
</ul>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={userPhone === $user.phone} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
<Form on:submit={updatePassword}>
<CardGrid>
<div>
<Heading tag="h6" size="7">Update Password</Heading>
</div>
<p>
Enter a new password. A password must contain <b>at least 8 characters.</b>
</p>
<svelte:fragment slot="aside">
<ul>
<InputPassword
id="newPassword"
label="New Password"
placeholder="Enter new password"
autocomplete={false}
meter={false}
showPasswordButton={true}
bind:value={newPassword} />
</ul>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={!newPassword} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
<Form on:submit={updatePrefs}>
<CardGrid>
<Heading tag="h6" size="7">User Preferences</Heading>
<p>
You can update your user preferences by storing information on the user's objects so
they can easily be shared across devices and sessions.
</p>
<svelte:fragment slot="aside">
<form class="form u-grid u-gap-16">
<ul class="form-list">
{#if prefs}
{#each prefs as [key, value], index}
<li class="form-item is-multiple">
<div class="form-item-part u-stretch">
<label class="label" for={`key-${index}`}>Key</label>
<div class="input-text-wrapper">
<input
id={`key-${key}`}
placeholder="Enter key"
type="text"
class="input-text"
bind:value={key} />
</div>
</div>
<div class="form-item-part u-stretch">
<label class="label" for={`value-${index}`}> Value </label>
<div class="input-text-wrapper">
<input
id={`value-${value}`}
placeholder="Enter value"
type="text"
class="input-text"
bind:value />
</div>
</div>
<div class="form-item-part u-cross-child-end">
<Button
text
disabled={(!key || !value) && index === 0}
on:click={() => {
if (index === 0) {
prefs = [['', '']];
} else {
prefs.splice(index, 1);
prefs = prefs;
}
}}>
<span class="icon-x" aria-hidden="true" />
</Button>
</div>
</li>
{/each}
{/if}
</ul>
<Button
noMargin
text
on:click={() => {
if (prefs[prefs.length - 1][0] && prefs[prefs.length - 1][1]) {
prefs.push(['', '']);
prefs = prefs;
}
}}>
<span class="icon-plus" aria-hidden="true" />
<span class="text">Add Preference</span>
</Button>
</form>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={arePrefsDisabled} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
<CardGrid danger>
<div>
<Heading tag="h6" size="7">Danger Zone</Heading>
</div>
<p>
The user will be permanently deleted, including all data associated with this user. This
action is irreversible.
</p>
<svelte:fragment slot="aside">
<Box>
<svelte:fragment slot="image">
{#if $user.email || $user.phone}
{#if $user.name}
<AvatarInitials size={48} name={$user.name} />
{:else}
<div class="avatar">
<span class="icon-minus-sm" aria-hidden="true" />
</div>
{/if}
{:else}
<div class="avatar">
<span class="icon-anonymous" aria-hidden="true" />
</div>
{/if}
</svelte:fragment>
<svelte:fragment slot="title">
<h6 class="u-bold u-trim-1">
{$user.name || $user.email || $user.phone || 'Anonymous'}
</h6>
</svelte:fragment>
<p class="u-trim-1">
{$user.email && $user.phone
? [$user.email, $user.phone].join(',')
: $user.email || $user.phone}
</p>
</Box>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button secondary on:click={() => (showDelete = true)} event="delete_user"
>Delete</Button>
</svelte:fragment>
</CardGrid>
<UpdateStatus />
<UpdateName />
<UpdateEmail />
<UpdatePhone />
<UpdatePassword />
<UpdatePrefs />
<DangerZone />
</Container>
<DeleteUser bind:showDelete />
@@ -0,0 +1,53 @@
<script lang="ts">
import { CardGrid, Box, Heading, AvatarInitials } from '$lib/components';
import { Button } from '$lib/elements/forms';
import DeleteUser from './deleteUser.svelte';
import { user } from './store';
let showDelete = false;
</script>
<CardGrid danger>
<div>
<Heading tag="h6" size="7">Danger Zone</Heading>
</div>
<p>
The user will be permanently deleted, including all data associated with this user. This
action is irreversible.
</p>
<svelte:fragment slot="aside">
<Box>
<svelte:fragment slot="image">
{#if $user.email || $user.phone}
{#if $user.name}
<AvatarInitials size={48} name={$user.name} />
{:else}
<div class="avatar">
<span class="icon-minus-sm" aria-hidden="true" />
</div>
{/if}
{:else}
<div class="avatar">
<span class="icon-anonymous" aria-hidden="true" />
</div>
{/if}
</svelte:fragment>
<svelte:fragment slot="title">
<h6 class="u-bold u-trim-1">
{$user.name || $user.email || $user.phone || 'Anonymous'}
</h6>
</svelte:fragment>
<p class="u-trim-1">
{$user.email && $user.phone
? [$user.email, $user.phone].join(',')
: $user.email || $user.phone}
</p>
</Box>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button secondary on:click={() => (showDelete = true)} event="delete_user">Delete</Button>
</svelte:fragment>
</CardGrid>
<DeleteUser bind:showDelete />
@@ -72,7 +72,7 @@
<Button
external
secondary
href="https://appwrite.io/docs/server/auth?sdk=nodejs-default#usersGetMemberships">
href="https://appwrite.io/docs/server/users?sdk=nodejs-default#usersListMemberships">
Documentation
</Button>
</Empty>
@@ -0,0 +1,53 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputEmail } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdkForProject } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import { user } from './store';
let userEmail: string = null;
onMount(async () => {
userEmail ??= $user.email;
});
async function updateEmail() {
try {
await sdkForProject.users.updateEmail($user.$id, userEmail);
invalidate(Dependencies.USER);
addNotification({
message: 'Email has been updated',
type: 'success'
});
trackEvent('submit_user_update_email');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
</script>
<Form on:submit={updateEmail}>
<CardGrid>
<Heading tag="h6" size="7">Update Email</Heading>
<svelte:fragment slot="aside">
<ul>
<InputEmail
id="email"
label="Email"
placeholder="Enter email"
autocomplete={false}
bind:value={userEmail} />
</ul>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={userEmail === $user.email} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -0,0 +1,54 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputText } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdkForProject } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import { user } from './store';
let userName: string = null;
onMount(async () => {
userName ??= $user.name;
});
async function updateName() {
try {
await sdkForProject.users.updateName($user.$id, userName);
invalidate(Dependencies.USER);
addNotification({
message: 'Name has been updated',
type: 'success'
});
trackEvent('submit_user_update_name');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
</script>
<Form on:submit={updateName}>
<CardGrid>
<Heading tag="h6" size="7">Update Name</Heading>
<svelte:fragment slot="aside">
<ul>
<InputText
id="name"
label="Name"
placeholder="Enter name"
autocomplete={false}
bind:value={userName} />
</ul>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={userName === $user.name} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -0,0 +1,55 @@
<script lang="ts">
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Button, Form, InputPassword } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdkForProject } from '$lib/stores/sdk';
import { user } from './store';
let newPassword: string = null;
async function updatePassword() {
try {
await sdkForProject.users.updatePassword($user.$id, newPassword);
newPassword = null;
addNotification({
message: 'Password has been updated',
type: 'success'
});
trackEvent('submit_user_update_password');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
</script>
<Form on:submit={updatePassword}>
<CardGrid>
<div>
<Heading tag="h6" size="7">Update Password</Heading>
</div>
<p>
Enter a new password. A password must contain <b>at least 8 characters.</b>
</p>
<svelte:fragment slot="aside">
<ul>
<InputPassword
id="newPassword"
label="New Password"
placeholder="Enter new password"
autocomplete={false}
meter={false}
showPasswordButton={true}
bind:value={newPassword} />
</ul>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={!newPassword} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -0,0 +1,53 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputPhone } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdkForProject } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import { user } from './store';
let userPhone: string = null;
onMount(async () => {
userPhone ??= $user.phone;
});
async function updatePhone() {
try {
await sdkForProject.users.updatePhone($user.$id, userPhone);
invalidate(Dependencies.USER);
addNotification({
message: 'Phone has been updated',
type: 'success'
});
trackEvent('submit_user_update_phone');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
</script>
<Form on:submit={updatePhone}>
<CardGrid>
<Heading tag="h6" size="7">Update Phone</Heading>
<svelte:fragment slot="aside">
<ul>
<InputPhone
id="phone"
label="Phone"
placeholder="Enter phone number"
autocomplete={false}
bind:value={userPhone} />
</ul>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={userPhone === $user.phone} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -0,0 +1,129 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdkForProject } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import { user } from './store';
$: if (prefs) {
if (JSON.stringify(prefs) !== JSON.stringify(Object.entries($user.prefs))) {
if (!!prefs[prefs.length - 1][0] && !!prefs[prefs.length - 1][1]) {
arePrefsDisabled = false;
} else {
arePrefsDisabled = true;
}
} else {
arePrefsDisabled = true;
}
}
let prefs: [string, unknown][] = null;
let arePrefsDisabled = true;
onMount(async () => {
prefs = Object.entries($user.prefs);
if (!prefs?.length) {
prefs.push(['', '']);
}
});
async function updatePrefs() {
try {
let updatedPrefs = Object.fromEntries(prefs);
await sdkForProject.users.updatePrefs($user.$id, updatedPrefs);
invalidate(Dependencies.USER);
arePrefsDisabled = true;
addNotification({
message: 'Preferences have been updated',
type: 'success'
});
trackEvent('submit_user_update_preferences');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
</script>
<Form on:submit={updatePrefs}>
<CardGrid>
<Heading tag="h6" size="7">User Preferences</Heading>
<p>
You can update your user preferences by storing information on the user's objects so
they can easily be shared across devices and sessions.
</p>
<svelte:fragment slot="aside">
<form class="form u-grid u-gap-16">
<ul class="form-list">
{#if prefs}
{#each prefs as [key, value], index}
<li class="form-item is-multiple">
<div class="form-item-part u-stretch">
<label class="label" for={`key-${index}`}>Key</label>
<div class="input-text-wrapper">
<input
id={`key-${key}`}
placeholder="Enter key"
type="text"
class="input-text"
bind:value={key} />
</div>
</div>
<div class="form-item-part u-stretch">
<label class="label" for={`value-${index}`}> Value </label>
<div class="input-text-wrapper">
<input
id={`value-${value}`}
placeholder="Enter value"
type="text"
class="input-text"
bind:value />
</div>
</div>
<div class="form-item-part u-cross-child-end">
<Button
text
disabled={(!key || !value) && index === 0}
on:click={() => {
if (index === 0) {
prefs = [['', '']];
} else {
prefs.splice(index, 1);
prefs = prefs;
}
}}>
<span class="icon-x" aria-hidden="true" />
</Button>
</div>
</li>
{/each}
{/if}
</ul>
<Button
noMargin
text
on:click={() => {
if (prefs[prefs.length - 1][0] && prefs[prefs.length - 1][1]) {
prefs.push(['', '']);
prefs = prefs;
}
}}>
<span class="icon-plus" aria-hidden="true" />
<span class="text">Add Preference</span>
</Button>
</form>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={arePrefsDisabled} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -0,0 +1,157 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { AvatarInitials, CardGrid, DropList, DropListItem, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Pill } from '$lib/elements';
import { Button } from '$lib/elements/forms';
import { toLocaleDateTime } from '$lib/helpers/date';
import { addNotification } from '$lib/stores/notifications';
import { sdkForProject } from '$lib/stores/sdk';
import { user } from './store';
let showVerifcationDropdown = false;
async function updateVerificationEmail() {
showVerifcationDropdown = false;
try {
await sdkForProject.users.updateEmailVerification($user.$id, !$user.emailVerification);
invalidate(Dependencies.USER);
addNotification({
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
$user.emailVerification ? 'verified' : 'unverified'
}`,
type: 'success'
});
trackEvent('submit_user_update_verification_email');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updateVerificationPhone() {
showVerifcationDropdown = false;
try {
await sdkForProject.users.updatePhoneVerification($user.$id, !$user.phoneVerification);
invalidate(Dependencies.USER);
addNotification({
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
$user.phoneVerification ? 'verified' : 'unverified'
}`,
type: 'success'
});
trackEvent('submit_user_update_verification_phone');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updateStatus() {
try {
await sdkForProject.users.updateStatus($user.$id, !$user.status);
invalidate(Dependencies.USER);
addNotification({
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
$user.status ? 'unblocked' : 'blocked'
}`,
type: 'success'
});
trackEvent('submit_user_update_status');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
</script>
<CardGrid>
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16">
{#if $user.email || $user.phone}
{#if $user.name}
<AvatarInitials size={48} name={$user.name} />
<Heading tag="h6" size="7">{$user.name}</Heading>
{:else}
<div class="avatar">
<span class="icon-minus-sm" aria-hidden="true" />
</div>
{/if}
{:else}
<div class="avatar">
<span class="icon-anonymous" aria-hidden="true" />
</div>
{/if}
</div>
<svelte:fragment slot="aside">
<div class="u-flex u-main-space-between">
<div>
{#if $user.email}
<p class="title">{$user.email}</p>
{/if}
{#if $user.phone}
<p class="title">{$user.phone}</p>
{/if}
<p>Joined: {toLocaleDateTime($user.registration)}</p>
</div>
{#if !$user.status}
<Pill danger>blocked</Pill>
{:else if $user.email && $user.phone}
<Pill success={$user.emailVerification || $user.phoneVerification}>
{$user.emailVerification && $user.phoneVerification
? 'verified'
: $user.emailVerification
? 'verified email'
: $user.phoneVerification
? 'verified phone'
: 'unverified'}
</Pill>
{:else}
<Pill success={$user.emailVerification || $user.phoneVerification}>
{$user.emailVerification
? 'verified '
: $user.phoneVerification
? 'verified '
: 'unverified'}
</Pill>
{/if}
</div>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button text={$user.status} secondary={!$user.status} on:click={() => updateStatus()}>
{$user.status ? 'Block account' : 'Unblock account'}
</Button>
{#if $user.status}
{#if $user.phone && $user.email}
<DropList bind:show={showVerifcationDropdown} placement="top-start">
<Button
secondary
on:click={() => (showVerifcationDropdown = !showVerifcationDropdown)}>
{$user.emailVerification ? 'Unverify' : 'Verify'} account
</Button>
<svelte:fragment slot="list">
<DropListItem icon="mail" on:click={() => updateVerificationEmail()}>
{$user.emailVerification ? 'Unverify' : 'Verify'} email
</DropListItem>
<DropListItem icon="phone" on:click={() => updateVerificationPhone()}>
{$user.phoneVerification ? 'Unverify' : 'Verify'} phone
</DropListItem>
</svelte:fragment>
</DropList>
{:else if $user.email}
<Button secondary on:click={() => updateVerificationEmail()}>
{$user.emailVerification ? 'Unverify' : 'Verify'} account
</Button>
{:else if $user.phone}
<Button secondary on:click={() => updateVerificationPhone()}>
{$user.phoneVerification ? 'Unverify' : 'Verify'} account
</Button>
{/if}
{/if}
</svelte:fragment>
</CardGrid>
@@ -12,6 +12,7 @@
import { createDocument } from './wizard/store';
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
import { Dependencies } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
const databaseId = $page.params.database;
const collectionId = $page.params.collection;
@@ -42,6 +43,7 @@
message: 'Document has been created',
type: 'success'
});
trackEvent('submit_document_create');
invalidate(Dependencies.DOCUMENTS);
wizard.hide();
} catch (error) {
@@ -12,6 +12,7 @@
import { Dependencies } from '$lib/constants';
import { invalidate } from '$app/navigation';
import Attribute from './attribute.svelte';
import { trackEvent } from '$lib/actions/analytics';
let disableUpdate = true;
let currentDoc: string;
@@ -61,6 +62,9 @@
currentDoc = JSON.stringify($work);
invalidate(Dependencies.DOCUMENT);
trackEvent('submit_document_update', {
type: 'android'
});
disableUpdate = true;
addNotification({
message: 'Document was updated!',
@@ -11,6 +11,7 @@
import { Dependencies } from '$lib/constants';
import Select from './select.svelte';
import { trackEvent } from '$lib/actions/analytics';
import { remove } from '$lib/helpers/array';
export let showCreateIndex = false;
export let externalAttribute: Attributes = null;
@@ -24,115 +25,77 @@
{ value: 'unique', label: 'Unique' },
{ value: 'fulltext', label: 'FullText' }
];
let newAttr = false;
let selectedType = 'key';
$: attributeOptions = $collection.attributes.map((attribute: Attributes) => ({
let attributeOptions = $collection.attributes.map((attribute: Attributes) => ({
value: attribute.key,
label: attribute.key
}));
$: attributeList = [];
let attributeList = [{ value: '', order: '' }];
let selectedAttribute = '';
let selectedOrder = '';
$: addAttributeDisabled = !attributeList.at(-1)?.value || !attributeList.at(-1)?.order;
onMount(() => {
if (externalAttribute) {
attributeList = [{ value: externalAttribute.key, order: 'ASC' }];
}
if (!externalAttribute) return;
attributeList = [{ value: externalAttribute.key, order: 'ASC' }];
});
$: if (showCreateIndex) {
attributeList = [];
selectedOrder = selectedAttribute = '';
attributeList = [{ value: '', order: '' }];
selectedType = 'key';
key = null;
}
const created = async () => {
if (key && selectedAttribute && selectedOrder && selectedType) {
if (selectedAttribute && selectedOrder) {
attributeList.push({ value: selectedAttribute, order: selectedOrder });
selectedAttribute = selectedOrder = '';
}
try {
await sdkForProject.databases.createIndex(
databaseId,
$collection.$id,
key,
selectedType,
attributeList.map((a) => a.value),
attributeList.map((a) => a.order)
);
invalidate(Dependencies.COLLECTION);
addNotification({
message: 'Index has been created',
type: 'success'
});
trackEvent('submit_index_create');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
async function create() {
if (!(key && selectedType && !addAttributeDisabled)) {
error = 'All fields are required';
return;
}
try {
await sdkForProject.databases.createIndex(
databaseId,
$collection.$id,
key,
selectedType,
attributeList.map((a) => a.value),
attributeList.map((a) => a.order)
);
invalidate(Dependencies.COLLECTION);
addNotification({
message: 'Index has been created',
type: 'success'
});
trackEvent('submit_index_create');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
} finally {
showCreateIndex = false;
} else error = 'All fields are required';
};
}
}
function addAttribute() {
if (addAttributeDisabled) return;
// We assign instead of pushing to trigger Svelte's reactivity
attributeList = [...attributeList, { value: '', order: '' }];
}
</script>
<Modal bind:error size="big" on:submit={created} bind:show={showCreateIndex}>
<Modal bind:error size="big" on:submit={create} bind:show={showCreateIndex}>
<svelte:fragment slot="header">Create Index</svelte:fragment>
<FormList>
<InputText id="key" label="Index Key" placeholder="Enter Key" bind:value={key} autofocus />
<InputSelect options={types} id="type" label="Index type" bind:value={selectedType} />
{#if attributeList?.length}
{#each attributeList as index, i}
<li class="form-item is-multiple">
<div class="form-item-part u-stretch">
<Select id="attribute" label="Attribute" bind:value={index.value}>
<optgroup label="Internal">
<option value="$id">$id</option>
<option value="$createdAt">$createdAt</option>
<option value="$updatedAt">$updatedAt</option>
</optgroup>
<optgroup label="Attributes">
{#each attributeOptions as option}
<option
value={option.value}
selected={option.value === selectedAttribute}>
{option.label}
</option>
{/each}
</optgroup>
</Select>
</div>
<div class="form-item-part u-stretch">
<Select id="order" label="Order" bind:value={index.order}>
<option value="ASC"> ASC </option>
<option value="DESC"> DESC </option>
</Select>
</div>
<div class="form-item-part u-cross-child-end">
<Button
text
disabled={externalAttribute && i === 0}
on:click={() => {
if (i === 0) attributeList = [];
attributeList = attributeList.splice(i, 1);
}}>
<span class="icon-x" aria-hidden="true" />
</Button>
</div>
</li>
{/each}
{/if}
{#if !attributeList?.length || newAttr}
{#each attributeList as attribute, i}
<li class="form-item is-multiple">
<div class="form-item-part u-stretch" style="align-items: flex-start;">
<Select id="attribute" label="Attribute" bind:value={selectedAttribute}>
<option value="" disabled selected hidden>Select Attribute</option>
<div class="form-item-part u-stretch">
<Select id={`attribute-${i}`} label="Attribute" bind:value={attribute.value}>
<option value="" disabled hidden>Select Attribute</option>
<optgroup label="Internal">
<option value="$id">$id</option>
@@ -141,9 +104,7 @@
</optgroup>
<optgroup label="Attributes">
{#each attributeOptions as option}
<option
value={option.value}
selected={option.value === selectedAttribute}>
<option value={option.value}>
{option.label}
</option>
{/each}
@@ -151,36 +112,28 @@
</Select>
</div>
<div class="form-item-part u-stretch">
<Select id="order" label="Order" bind:value={selectedOrder}>
<option value="" disabled selected hidden>Select Order</option>
<Select id={`order-${i}`} label="Order" bind:value={attribute.order}>
<option value="" disabled hidden>Select Order</option>
<option value="ASC"> ASC </option>
<option value="DESC"> DESC </option>
</Select>
</div>
<div class="form-item-part u-cross-child-end">
<Button
text
disabled={false}
disabled={attributeList.length <= 1}
on:click={() => {
newAttr = false;
selectedAttribute = selectedOrder = '';
attributeList = remove(attributeList, i);
}}>
<span class="icon-x" aria-hidden="true" />
</Button>
</div>
</li>
{/if}
<Button
text
noMargin
on:click={() => {
newAttr = true;
if (selectedAttribute && selectedOrder) {
attributeList.push({ value: selectedAttribute, order: selectedOrder });
selectedAttribute = selectedOrder = '';
}
}}>
{/each}
<Button text noMargin on:click={addAttribute} disabled={addAttributeDisabled}>
<span class="icon-plus" aria-hidden="true" />
<span class="text">Add attribute</span>
</Button>
@@ -77,7 +77,9 @@
databaseId,
$collection.$id,
collectionName,
$collection.$permissions
$collection.$permissions,
$collection.documentSecurity,
$collection.enabled
);
invalidate(Dependencies.COLLECTION);
showError = false;
@@ -96,7 +98,9 @@
databaseId,
$collection.$id,
$collection.name,
collectionPermissions
collectionPermissions,
$collection.documentSecurity,
$collection.enabled
);
invalidate(Dependencies.COLLECTION);
arePermsDisabled = true;
@@ -120,7 +124,8 @@
$collection.$id,
$collection.name,
$collection.$permissions,
collectionDocumentSecurity
collectionDocumentSecurity,
$collection.enabled
);
invalidate(Dependencies.COLLECTION);
arePermsDisabled = true;
@@ -16,14 +16,19 @@
const dispatch = createEventDispatcher();
const handleVariable = () => {
function close() {
showCreate = false;
selectedVar = null;
}
function handleVariable() {
if (selectedVar) {
dispatch('updated', pair);
} else {
dispatch('created', pair);
}
showCreate = false;
};
close();
}
</script>
<Modal bind:show={showCreate} on:submit={handleVariable} size="big">
@@ -48,7 +53,7 @@
required />
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
<Button secondary on:click={close}>Cancel</Button>
<Button submit>{selectedVar ? 'Update' : 'Create'}</Button>
</svelte:fragment>
</Modal>
@@ -70,7 +70,15 @@
async function updateName() {
try {
await sdkForProject.functions.update(functionId, functionName, $func.execute);
await sdkForProject.functions.update(
functionId,
functionName,
$func.execute,
$func.events,
$func.schedule,
$func.timeout,
$func.enabled
);
invalidate(Dependencies.FUNCTION);
addNotification({
message: 'Name has been updated',
@@ -87,7 +95,15 @@
async function updatePermissions() {
try {
await sdkForProject.functions.update(functionId, $func.name, permissions);
await sdkForProject.functions.update(
functionId,
$func.name,
permissions,
$func.events,
$func.schedule,
$func.timeout,
$func.enabled
);
invalidate(Dependencies.FUNCTION);
addNotification({
message: 'Permissions have been updated',
@@ -108,7 +124,10 @@
functionId,
$func.name,
$func.execute,
Array.from($eventSet)
Array.from($eventSet),
$func.schedule,
$func.timeout,
$func.enabled
);
invalidate(Dependencies.FUNCTION);
addNotification({
@@ -132,7 +151,8 @@
$func.execute,
$func.events,
functionSchedule,
timeout
$func.timeout,
$func.enabled
);
invalidate(Dependencies.FUNCTION);
@@ -157,9 +177,11 @@
$func.execute,
$func.events,
$func.schedule,
timeout
timeout,
$func.enabled
);
invalidate(Dependencies.FUNCTION);
addNotification({
type: 'success',
message: 'Timeout has been updated'
@@ -557,10 +579,12 @@
</Container>
<Delete bind:showDelete />
<Variable
bind:selectedVar
bind:showCreate={showVariablesModal}
on:created={handleVariableCreated}
on:updated={handleVariableUpdated} />
{#if showVariablesModal}
<Variable
bind:selectedVar
bind:showCreate={showVariablesModal}
on:created={handleVariableCreated}
on:updated={handleVariableUpdated} />
{/if}
<!-- <Upload bind:show={showVariablesUpload} on:uploaded={handleVariableCreated} /> -->
<EventModal bind:show={showEvents} on:created={handleEvent} />
@@ -2,5 +2,5 @@ import { page } from '$app/stores';
import { derived, writable, type Writable } from 'svelte/store';
import type { Models } from '@aw-labs/appwrite-console';
export const func = derived(page, ($page) => $page.data.function);
export const func = derived(page, ($page) => $page.data.function as Models.Function);
export const execute: Writable<Models.Function> = writable();
@@ -13,6 +13,7 @@
import { key } from './store';
import Scopes from '../scopes.svelte';
import Delete from './delete.svelte';
import { trackEvent } from '$lib/actions/analytics';
let showDelete = false;
let name: string = null;
@@ -37,6 +38,7 @@
$key.expire
);
invalidate(Dependencies.KEY);
trackEvent('submit_key_update_name');
addNotification({
type: 'success',
message: 'API Key name has been updated'
@@ -59,6 +61,7 @@
expire
);
invalidate(Dependencies.KEY);
trackEvent('submit_key_update_expire');
addNotification({
type: 'success',
message: 'API Key expiration has been updated'
@@ -73,8 +76,15 @@
async function updateScopes() {
try {
await sdkForConsole.projects.updateKey($project.$id, $key.$id, $key.name, scopes);
await sdkForConsole.projects.updateKey(
$project.$id,
$key.$id,
$key.name,
scopes,
$key.expire
);
invalidate(Dependencies.KEY);
trackEvent('submit_key_update_scopes');
addNotification({
type: 'success',
message: 'API Key scopes has been updated'
@@ -12,6 +12,7 @@
import { onDestroy } from 'svelte';
import { onboarding } from '../../store';
import { Dependencies } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
async function onFinish() {
try {
@@ -24,6 +25,7 @@
if ($onboarding) {
invalidate(Dependencies.PROJECT);
}
trackEvent('submit_key_create');
goto(`/console/project-${$page.params.project}/overview/keys/${$id}`);
} catch (error) {
addNotification({
@@ -48,7 +48,14 @@
const updateName = async () => {
try {
await sdkForConsole.projects.updatePlatform($project.$id, data.platform.$id, name);
await sdkForConsole.projects.updatePlatform(
$project.$id,
data.platform.$id,
name,
data.platform.key,
data.platform.store,
data.platform.hostname
);
invalidate(Dependencies.PLATFORM);
addNotification({
type: 'success',
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
@@ -21,11 +22,14 @@
$project.$id,
$platform.$id,
$platform.name,
undefined,
undefined,
$platform.key,
$platform.store,
hostname
);
invalidate(Dependencies.PLATFORM);
trackEvent('submit_platform_update', {
type: 'android'
});
addNotification({
type: 'success',
message: 'Platform Package Name has been updated'
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
@@ -21,9 +22,14 @@
$project.$id,
$platform.$id,
$platform.name,
key
key,
$platform.store,
$platform.hostname
);
invalidate(Dependencies.PLATFORM);
trackEvent('submit_platform_update', {
type: 'apple-ios'
});
addNotification({
type: 'success',
message: 'Platform Bundle ID has been updated'
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
@@ -21,9 +22,14 @@
$project.$id,
$platform.$id,
$platform.name,
key
key,
$platform.store,
$platform.hostname
);
invalidate(Dependencies.PLATFORM);
trackEvent('submit_platform_update', {
type: 'apple-macos'
});
addNotification({
type: 'success',
message: 'Platform Bundle ID has been updated'
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
@@ -21,9 +22,14 @@
$project.$id,
$platform.$id,
$platform.name,
key
key,
$platform.store,
$platform.hostname
);
invalidate(Dependencies.PLATFORM);
trackEvent('submit_platform_update', {
type: 'apple-tvos'
});
addNotification({
type: 'success',
message: 'Platform Bundle ID has been updated'
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
@@ -21,9 +22,14 @@
$project.$id,
$platform.$id,
$platform.name,
key
key,
$platform.store,
$platform.hostname
);
invalidate(Dependencies.PLATFORM);
trackEvent('submit_platform_update', {
type: 'apple-watchos'
});
addNotification({
type: 'success',
message: 'Platform Bundle ID has been updated'
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
@@ -21,9 +22,14 @@
$project.$id,
$platform.$id,
$platform.name,
key
key,
$platform.store,
$platform.hostname
);
invalidate(Dependencies.PLATFORM);
trackEvent('submit_platform_update', {
type: 'flutter-android'
});
addNotification({
type: 'success',
message: 'Platform Package Name has been updated'
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
@@ -21,9 +22,14 @@
$project.$id,
$platform.$id,
$platform.name,
key
key,
$platform.store,
$platform.hostname
);
invalidate(Dependencies.PLATFORM);
trackEvent('submit_platform_update', {
type: 'flutter-ios'
});
addNotification({
type: 'success',
message: 'Platform Bundle ID has been updated'
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
@@ -21,9 +22,14 @@
$project.$id,
$platform.$id,
$platform.name,
key
key,
$platform.store,
$platform.hostname
);
invalidate(Dependencies.PLATFORM);
trackEvent('submit_platform_update', {
type: 'flutter-linux'
});
addNotification({
type: 'success',
message: 'Platform Package Name has been updated'
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
@@ -21,9 +22,14 @@
$project.$id,
$platform.$id,
$platform.name,
key
key,
$platform.store,
$platform.hostname
);
invalidate(Dependencies.PLATFORM);
trackEvent('submit_platform_update', {
type: 'flutter-macos'
});
addNotification({
type: 'success',
message: 'Platform Bundle ID has been updated'
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
@@ -21,9 +22,14 @@
$project.$id,
$platform.$id,
$platform.name,
key
key,
$platform.store,
$platform.hostname
);
invalidate(Dependencies.PLATFORM);
trackEvent('submit_platform_update', {
type: 'flutter-windows'
});
addNotification({
type: 'success',
message: 'Platform Package Name has been updated'
@@ -21,8 +21,8 @@
$project.$id,
$platform.$id,
$platform.name,
undefined,
undefined,
$platform.key,
$platform.store,
hostname
);
invalidate(Dependencies.PLATFORM);
@@ -19,8 +19,8 @@
projectId,
$createPlatform.$id,
$createPlatform.name,
undefined,
undefined,
$createPlatform.key,
$createPlatform.store,
$createPlatform.hostname
);
@@ -29,8 +29,8 @@
projectId,
$createPlatform.$id,
$createPlatform.name,
undefined,
undefined,
$createPlatform.key,
$createPlatform.store,
$createPlatform.hostname
);
@@ -85,8 +85,8 @@
projectId,
$createPlatform.$id,
$createPlatform.name,
undefined,
undefined,
$createPlatform.key,
$createPlatform.store,
$createPlatform.hostname
);
@@ -8,6 +8,8 @@ function createPlatformStore() {
$id: null,
name: null,
hostname: null,
key: null,
store: null,
type: null
});
@@ -20,6 +22,8 @@ function createPlatformStore() {
$id: null,
name: null,
hostname: null,
key: null,
store: null,
type: null
});
}
@@ -21,8 +21,8 @@
projectId,
$createPlatform.$id,
$createPlatform.name,
undefined,
undefined,
$createPlatform.key,
$createPlatform.store,
$createPlatform.hostname
);
@@ -12,8 +12,12 @@
let method: Method = Method.NPM;
const example1 = `import { Appwrite } from 'appwrite';`;
const example2 = `<script src="https://cdn.jsdelivr.net/npm/appwrite@${$versions['client-web']}" />`;
const example1 = `import { Client } from 'appwrite';`;
const example2 =
`<script src="https://cdn.jsdelivr.net/npm/appwrite@${$versions['client-web']}" />
<script>
const { Client } = Appwrite;
</script` + `>`; // Prevent svelte from processing the closing script tag
</script>
<WizardStep>
@@ -4,9 +4,9 @@
import { sdkForProject } from '$lib/stores/sdk';
const { endpoint, project } = sdkForProject.client.config;
const code = `const appwrite = new Appwrite();
const code = `const client = new Client();
appwrite
client
.setEndpoint('${endpoint}')
.setProject('${project}');`;
</script>
@@ -55,7 +55,9 @@
name,
$webhook.events,
$webhook.url,
$webhook.security
$webhook.security,
$webhook.httpUser,
$webhook.httpPass
);
invalidate(Dependencies.WEBHOOK);
addNotification({
@@ -78,7 +80,9 @@
$webhook.name,
$webhook.events,
url,
$webhook.security
$webhook.security,
$webhook.httpUser,
$webhook.httpPass
);
invalidate(Dependencies.WEBHOOK);
addNotification({
@@ -101,7 +105,9 @@
$webhook.name,
Array.from($eventSet),
$webhook.url,
$webhook.security
$webhook.security,
$webhook.httpUser,
$webhook.httpPass
);
invalidate(Dependencies.WEBHOOK);
areEventsDisabled = true;
@@ -288,6 +294,7 @@
<InputPassword
label="Password"
id="password"
minlength={0}
showPasswordButton
placeholder="Enter password"
bind:value={httpPass} />
@@ -24,6 +24,8 @@
import { Dependencies } from '$lib/constants';
import Delete from '../deleteBucket.svelte';
import { trackEvent } from '$lib/actions/analytics';
import { writable } from 'svelte/store';
import type { Models } from '@aw-labs/appwrite-console';
let showDelete = false;
@@ -35,13 +37,14 @@
encryption: boolean = null,
antivirus: boolean = null,
maxSize: number;
let byteUnit: 'Bytes' | 'KB' | 'MB' | 'GB' = 'MB',
options = [
{ label: 'Bytes', value: 'Bytes' },
{ label: 'Kilobytes', value: 'KB' },
{ label: 'Megabytes', value: 'MB' },
{ label: 'Gigabytes', value: 'GB' }
];
let byteUnit = writable('KB');
let sizeInBytes: number = null;
const options = [
{ label: 'Bytes', value: 'Bytes' },
{ label: 'Kilobytes', value: 'KB' },
{ label: 'Megabytes', value: 'MB' },
{ label: 'Gigabytes', value: 'GB' }
];
let suggestedExtensions = ['jpg', 'png', 'svg', 'gif', 'html', 'pdf', 'mp4'];
let extensions = $bucket.allowedFileExtensions;
let isExtensionsDisabled = true;
@@ -54,6 +57,7 @@
bucketPermissions ??= $bucket.$permissions;
encryption ??= $bucket.encryption;
antivirus ??= $bucket.antivirus;
maxSize = $bucket.maximumFileSize / 1024;
});
$: if (bucketPermissions) {
if (symmetricDifference(bucketPermissions, $bucket.$permissions).length) {
@@ -66,78 +70,44 @@
} else isExtensionsDisabled = true;
}
async function toggleBucket() {
type TUpdateBucketMisc = {
successMessage?: string;
trackEventName: string;
trackEventData?: { value: boolean };
arePermsDisabled?: boolean;
};
async function updateBucket(updates: Partial<Models.Bucket>, misc: TUpdateBucketMisc) {
const values = { ...$bucket, ...updates };
try {
await sdkForProject.storage.updateBucket(
$bucket.$id,
$bucket.name,
undefined,
undefined,
enabled
values.$id,
values.name,
values.$permissions,
values.fileSecurity,
values.enabled,
values.maximumFileSize,
values.allowedFileExtensions,
values.compression,
values.encryption,
values.antivirus
);
invalidate(Dependencies.BUCKET);
if (misc.arePermsDisabled !== undefined) {
arePermsDisabled = misc.arePermsDisabled;
}
addNotification({
message: `${$bucket.name} has been updated`,
message: misc.successMessage ?? `${$bucket.name} has been updated`,
type: 'success'
});
trackEvent('submit_bucket_enable', {
value: enabled
});
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updateName() {
try {
await sdkForProject.storage.updateBucket($bucket.$id, bucketName);
invalidate(Dependencies.BUCKET);
addNotification({
message: 'Name has been updated',
type: 'success'
});
trackEvent('submit_bucket_update_name');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updatePermissions() {
try {
await sdkForProject.storage.updateBucket($bucket.$id, $bucket.name, bucketPermissions);
invalidate(Dependencies.BUCKET);
arePermsDisabled = true;
addNotification({
message: 'Permissions have been updated',
type: 'success'
});
trackEvent('submit_bucket_update_permissions');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updateFileSecurity() {
try {
await sdkForProject.storage.updateBucket(
$bucket.$id,
$bucket.name,
$bucket.$permissions,
bucketFileSecurity
);
invalidate(Dependencies.BUCKET);
arePermsDisabled = true;
addNotification({
message: 'Security has been updated',
type: 'success'
});
trackEvent('submit_bucket_update_file_security');
if (misc.trackEventName) {
trackEvent(misc.trackEventName, misc.trackEventData);
}
} catch (error) {
addNotification({
message: error.message,
@@ -146,81 +116,107 @@
}
}
async function updateSecurity() {
try {
await sdkForProject.storage.updateBucket(
$bucket.$id,
$bucket.name,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
function toggleBucket() {
updateBucket(
{
enabled
},
{
trackEventName: 'submit_bucket_enable',
trackEventData: {
value: enabled
}
}
);
}
function updateName() {
updateBucket(
{
name: bucketName
},
{
successMessage: 'Name has been updated',
trackEventName: 'submit_bucket_update_name'
}
);
}
function updatePermissions() {
updateBucket(
{
$permissions: bucketPermissions
},
{
successMessage: 'Permissions have been updated',
trackEventName: 'submit_bucket_update_permissions',
arePermsDisabled: true
}
);
}
function updateFileSecurity() {
updateBucket(
{
fileSecurity: bucketFileSecurity
},
{
successMessage: 'Security has been updated',
trackEventName: 'submit_bucket_update_file_security',
arePermsDisabled: true
}
);
}
function updateSecurity() {
updateBucket(
{
encryption,
antivirus
);
invalidate(Dependencies.BUCKET);
addNotification({
message: `${$bucket.name} has been updated`,
type: 'success'
});
trackEvent('submit_bucket_update_security');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
async function updateMaxSize() {
let size = sizeToBytes(maxSize, byteUnit);
try {
await sdkForProject.storage.updateBucket(
$bucket.$id,
$bucket.name,
undefined,
undefined,
undefined,
size
);
invalidate(Dependencies.BUCKET);
addNotification({
message: `${$bucket.name} has been updated`,
type: 'success'
});
trackEvent('submit_bucket_update_size');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
},
{
trackEventName: 'submit_bucket_update_security'
}
);
}
async function updateAllowedExtensions() {
try {
await sdkForProject.storage.updateBucket(
$bucket.$id,
$bucket.name,
undefined,
undefined,
undefined,
undefined,
extensions
);
invalidate(Dependencies.BUCKET);
addNotification({
message: `${$bucket.name} has been updated`,
type: 'success'
});
trackEvent('submit_bucket_update_extensions');
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
function updateMaxSize() {
const size = sizeToBytes(maxSize, $byteUnit);
updateBucket(
{
maximumFileSize: size
},
{
trackEventName: 'submit_bucket_update_size'
}
);
}
function updateAllowedExtensions() {
updateBucket(
{
allowedFileExtensions: extensions
},
{
trackEventName: 'submit_bucket_update_extensions'
}
);
}
byteUnit.subscribe((b) => {
if (b === 'Bytes') {
maxSize = sizeInBytes;
} else if (b === 'KB') {
maxSize = sizeInBytes / 1024;
} else if (b === 'MB') {
maxSize = sizeInBytes / 1024 / 1024;
} else if (b === 'GB') {
maxSize = sizeInBytes / 1024 / 1024 / 1024;
}
});
$: if (maxSize) {
sizeInBytes = sizeToBytes(maxSize, $byteUnit);
}
</script>
@@ -384,7 +380,9 @@
<Button
disabled={encryption === $bucket.encryption &&
antivirus === $bucket.antivirus}
submit>Update</Button>
submit
>Update
</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -398,14 +396,16 @@
<InputNumber
id="size"
label="Size"
placeholder="256"
placeholder={$bucket.maximumFileSize.toString()}
bind:value={maxSize} />
<InputSelect id="bytes" label="Bytes" {options} bind:value={byteUnit} />
<InputSelect id="bytes" label="Bytes" {options} bind:value={$byteUnit} />
</ul>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={!maxSize} submit>Update</Button>
<Button disabled={sizeInBytes === $bucket.maximumFileSize} submit
>Update
</Button>
</svelte:fragment>
</CardGrid>
</Form>
+8
View File
@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.5466 17C20.686 16.7584 20.7727 16.4863 20.7727 16.2419V7.75808C20.7727 7.51367 20.6862 7.24157 20.5466 7.00001L11.8864 12L20.5466 17Z" fill="#00599C"/>
<path d="M12.6559 21.8167L20.0031 17.5748C20.2147 17.4526 20.4071 17.2416 20.5465 17L11.8863 12L3.22607 17.0001C3.36553 17.2416 3.55795 17.4527 3.76954 17.5748L11.1167 21.8167C11.54 22.0611 12.2326 22.0611 12.6559 21.8167Z" fill="#004482"/>
<path d="M20.5466 6.99999C20.4071 6.75838 20.2147 6.54737 20.0031 6.42517L12.6559 2.1833C12.2327 1.9389 11.54 1.9389 11.1168 2.1833L3.76957 6.42517C3.34628 6.66951 3 7.26937 3 7.75812V16.2419C3 16.4863 3.0866 16.7585 3.22611 17L11.8863 12L20.5466 6.99999Z" fill="#659AD2"/>
<path d="M11.8864 17.9242C8.61976 17.9242 5.96216 15.2666 5.96216 12C5.96216 8.73336 8.61976 6.07576 11.8864 6.07576C13.9943 6.07576 15.9601 7.21008 17.0167 9.03602L14.4529 10.5196C13.924 9.60567 12.9406 9.03787 11.8864 9.03787C10.253 9.03787 8.92427 10.3666 8.92427 12C8.92427 13.6333 10.253 14.9621 11.8864 14.9621C12.9407 14.9621 13.9241 14.3943 14.453 13.4802L17.0169 14.9637C15.9603 16.7898 13.9944 17.9242 11.8864 17.9242Z" fill="white"/>
<path d="M17.8106 11.671H17.1523V11.0128H16.4941V11.671H15.8358V12.3292H16.4941V12.9875H17.1523V12.3292H17.8106V11.671Z" fill="white"/>
<path d="M20.2791 11.671H19.6208V11.0128H18.9626V11.671H18.3043V12.3292H18.9626V12.9875H19.6208V12.3292H20.2791V11.671Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="21" height="9" viewBox="0 0 21 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5367 6.77066C1.44246 6.76892 1.35244 6.789 1.27079 6.82299L1.27077 6.82297L1.26809 6.82415C1.18619 6.86013 1.11066 6.91165 1.04778 6.97763C0.984416 7.04106 0.933719 7.11661 0.900246 7.20081L0.900241 7.2008L0.899816 7.2019C0.867868 7.28413 0.850324 7.37148 0.850116 7.46019M1.5367 6.77066C1.62683 6.77086 1.71694 6.78819 1.80221 6.82566C1.88344 6.86135 1.95839 6.91232 2.02096 6.97752C2.08783 7.04102 2.13744 7.11653 2.17282 7.19799L2.17349 7.19953L2.17348 7.19954C2.20882 7.28349 2.22627 7.37286 2.22627 7.4633C2.22627 7.55374 2.20882 7.64311 2.17348 7.72707C2.13908 7.80877 2.08878 7.88483 2.02167 7.94834C1.95873 8.01224 1.88363 8.06296 1.80079 8.09756L1.74298 7.95915L1.80079 8.09756C1.7175 8.13234 1.62807 8.15013 1.5378 8.1499M1.5367 6.77066C1.53724 6.77067 1.53778 6.77068 1.53832 6.7707L1.53515 6.92066V6.77066C1.53567 6.77066 1.53619 6.77066 1.5367 6.77066ZM1.5378 8.1499C1.44662 8.15149 1.35607 8.13418 1.27193 8.09904C1.1879 8.06394 1.11207 8.01181 1.0493 7.94594C0.985862 7.88244 0.935939 7.80764 0.901383 7.72555C0.866254 7.6421 0.848375 7.55224 0.850116 7.46019M1.5378 8.1499C1.53715 8.1499 1.5365 8.1499 1.53585 8.14989L1.53667 7.99989L1.53973 8.14986C1.53908 8.14988 1.53844 8.14989 1.5378 8.1499ZM0.850116 7.46019C0.850126 7.45965 0.850137 7.45912 0.850148 7.45858L1.00011 7.46179H0.850114C0.850114 7.46126 0.850115 7.46072 0.850116 7.46019ZM4.46673 7.90618V2.91575C4.46994 2.80356 4.47033 2.69143 4.46804 2.57937L7.96337 7.9876L8.0077 8.05618H8.08935H9.08495H9.23495V7.90618V1V0.85H9.08495H8.27631H8.12631V1V5.85487C8.12306 6.00128 8.12426 6.14793 8.12979 6.29447L4.68624 0.919087L4.64198 0.85H4.55993H3.5081H3.3581V1V7.90618V8.05618H3.5081H4.31673H4.46673V7.90618ZM14.7176 7.14829L14.7185 6.99739H14.5676H11.86V4.90464H14.2104H14.3604V4.75464V4.02608V3.87608H14.2104H11.86V1.88158H14.3928H14.5428V1.73158V1V0.85H14.3928H10.8816H10.7316V1V7.90618V8.05618H10.8816H14.563H14.7121L14.713 7.90708L14.7176 7.14829ZM15.1862 0.85H15.0362V1V1.73158V1.88158H15.1862H17.0274V7.90618V8.05618H17.1774H17.986H18.136V7.90618V1.88158H20H20.15V1.73158V1V0.85H20H15.1862Z" fill="white" stroke="white" stroke-width="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5466 6.99999C20.4071 6.75838 20.2147 6.54737 20.0031 6.42517L12.6559 2.1833C12.2327 1.9389 11.54 1.9389 11.1168 2.1833L3.76957 6.42517C3.34628 6.66951 3 7.26937 3 7.75812V16.2419C3 16.4863 3.0866 16.7585 3.22611 17L3.22607 17C3.36553 17.2416 3.55795 17.4527 3.76954 17.5748L11.1167 21.8167C11.54 22.0611 12.2326 22.0611 12.6559 21.8167L20.0031 17.5748C20.2147 17.4526 20.4071 17.2416 20.5465 17L15.76 14.2365L17.0169 14.9638C15.9603 16.7899 13.9944 17.9243 11.8864 17.9243C8.61976 17.9243 5.96216 15.2667 5.96216 12C5.96216 8.7334 8.61976 6.07581 11.8864 6.07581C13.9943 6.07581 15.9601 7.21012 17.0167 9.03606L14.4529 10.5197C13.924 9.60572 12.9406 9.03792 11.8864 9.03792C10.253 9.03792 8.92427 10.3667 8.92427 12C8.92427 13.6333 10.253 14.9622 11.8864 14.9622C12.9402 14.9622 13.9232 14.3949 14.4523 13.4815L11.8863 12L11.8864 12L14.4523 13.4815L14.453 13.4802L15.7254 14.2165L20.5466 17C20.686 16.7584 20.7727 16.4863 20.7727 16.2419V7.75807C20.7727 7.51367 20.6862 7.24156 20.5466 7L18.6993 8.06657L20.5466 6.99999ZM17.8106 11.6709H17.1523V11.0127H16.4941V11.6709H15.8358V12.3292H16.4941V12.9874H17.1523V12.3292H17.8106V11.6709ZM19.6208 11.0127V11.6709H20.2791V12.3292H19.6208V12.9874H18.9626V12.3292H18.3043V11.6709H18.9626V11.0127H19.6208Z" fill="#C4C6D7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.53673 13.7707C2.44248 13.7689 2.35245 13.789 2.27079 13.823L2.27078 13.823L2.26809 13.8241C2.18619 13.8601 2.11067 13.9117 2.04778 13.9776C1.98442 14.0411 1.93372 14.1166 1.90025 14.2008L1.90024 14.2008L1.89982 14.2019C1.86787 14.2841 1.85032 14.3715 1.85012 14.4602M2.53673 13.7707C2.62685 13.7709 2.71695 13.7882 2.80221 13.8257C2.88344 13.8613 2.9584 13.9123 3.02097 13.9775C3.08783 14.041 3.13744 14.1165 3.17282 14.198L3.17349 14.1995L3.17348 14.1995C3.20882 14.2835 3.22627 14.3729 3.22627 14.4633C3.22627 14.5537 3.20882 14.6431 3.17348 14.7271C3.13908 14.8088 3.08879 14.8848 3.02168 14.9483C2.95873 15.0122 2.88363 15.063 2.80079 15.0976C2.71749 15.1323 2.62805 15.1501 2.53777 15.1499C2.4466 15.1515 2.35607 15.1342 2.27193 15.099C2.1879 15.0639 2.11208 15.0118 2.0493 14.9459C1.98587 14.8824 1.93594 14.8076 1.90138 14.7256C1.86625 14.6421 1.84837 14.5522 1.85012 14.4602M2.53673 13.7707C2.53726 13.7707 2.53779 13.7707 2.53832 13.7707L2.53515 13.9207V13.7707C2.53568 13.7707 2.5362 13.7707 2.53673 13.7707ZM1.85012 14.4602C1.85013 14.4597 1.85014 14.4591 1.85015 14.4586L2.00011 14.4618H1.85011C1.85011 14.4613 1.85011 14.4607 1.85012 14.4602ZM5.46673 14.9062V9.91575C5.46994 9.80356 5.47033 9.69143 5.46804 9.57937L8.96337 14.9876L9.0077 15.0562H9.08935H10.0849H10.2349V14.9062V8V7.85H10.0849H9.27631H9.12631V8V12.8549C9.12306 13.0013 9.12426 13.1479 9.12979 13.2945L5.68624 7.91909L5.64198 7.85H5.55993H4.5081H4.3581V8V14.9062V15.0562H4.5081H5.31673H5.46673V14.9062ZM15.7176 14.1483L15.7185 13.9974H15.5676H12.86V11.9046H15.2104H15.3604V11.7546V11.0261V10.8761H15.2104H12.86V8.88158H15.3928H15.5428V8.73158V8V7.85H15.3928H11.8816H11.7316V8V14.9062V15.0562H11.8816H15.563H15.7121L15.713 14.9071L15.7176 14.1483ZM16.1862 7.85H16.0362V8V8.73158V8.88158H16.1862H18.0274V14.9062V15.0562H18.1774H18.986H19.136V14.9062V8.88158H21H21.15V8.73158V8V7.85H21H16.1862Z" fill="#C4C6D7" stroke="#C4C6D7" stroke-width="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.5466 17C20.686 16.7584 20.7727 16.4863 20.7727 16.2419V7.75807C20.7727 7.51367 20.6862 7.24156 20.5466 7L11.8864 12L20.5466 17Z" fill="#00599C"/>
<path d="M12.6559 21.8167L20.0031 17.5748C20.2147 17.4526 20.4071 17.2416 20.5465 17L11.8863 12L3.22607 17C3.36553 17.2416 3.55795 17.4527 3.76954 17.5748L11.1167 21.8167C11.54 22.0611 12.2326 22.0611 12.6559 21.8167Z" fill="#004482"/>
<path d="M20.5466 6.99999C20.4071 6.75838 20.2147 6.54737 20.0031 6.42517L12.6559 2.1833C12.2327 1.9389 11.54 1.9389 11.1168 2.1833L3.76957 6.42517C3.34628 6.66951 3 7.26937 3 7.75812V16.2419C3 16.4863 3.0866 16.7585 3.22611 17L11.8863 12L20.5466 6.99999Z" fill="#659AD2"/>
<path d="M11.8864 17.9242C8.61976 17.9242 5.96216 15.2666 5.96216 12C5.96216 8.73334 8.61976 6.07574 11.8864 6.07574C13.9943 6.07574 15.9601 7.21006 17.0167 9.036L14.4529 10.5196C13.924 9.60566 12.9406 9.03786 11.8864 9.03786C10.253 9.03786 8.92427 10.3666 8.92427 12C8.92427 13.6333 10.253 14.9621 11.8864 14.9621C12.9407 14.9621 13.9241 14.3943 14.453 13.4802L17.0169 14.9637C15.9603 16.7898 13.9944 17.9242 11.8864 17.9242Z" fill="white"/>
<path d="M17.8106 11.671H17.1523V11.0128H16.4941V11.671H15.8358V12.3292H16.4941V12.9875H17.1523V12.3292H17.8106V11.671Z" fill="white"/>
<path d="M20.2791 11.671H19.6208V11.0128H18.9626V11.671H18.3043V12.3292H18.9626V12.9875H19.6208V12.3292H20.2791V11.671Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="21" height="9" viewBox="0 0 21 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5367 6.77066C1.44246 6.76892 1.35244 6.789 1.27079 6.82299L1.27077 6.82297L1.26809 6.82415C1.18619 6.86013 1.11066 6.91165 1.04778 6.97763C0.984416 7.04106 0.933719 7.11661 0.900246 7.20081L0.900241 7.2008L0.899816 7.2019C0.867868 7.28413 0.850324 7.37148 0.850116 7.46019M1.5367 6.77066C1.62683 6.77086 1.71694 6.78819 1.80221 6.82566C1.88344 6.86135 1.95839 6.91232 2.02096 6.97752C2.08783 7.04102 2.13744 7.11653 2.17282 7.19799L2.17349 7.19953L2.17348 7.19954C2.20882 7.28349 2.22627 7.37286 2.22627 7.4633C2.22627 7.55374 2.20882 7.64311 2.17348 7.72707C2.13908 7.80877 2.08878 7.88483 2.02167 7.94834C1.95873 8.01224 1.88363 8.06296 1.80079 8.09756L1.74298 7.95915L1.80079 8.09756C1.7175 8.13234 1.62807 8.15013 1.5378 8.1499M1.5367 6.77066C1.53724 6.77067 1.53778 6.77068 1.53832 6.7707L1.53515 6.92066V6.77066C1.53567 6.77066 1.53619 6.77066 1.5367 6.77066ZM1.5378 8.1499C1.44662 8.15149 1.35607 8.13418 1.27193 8.09904C1.1879 8.06394 1.11207 8.01181 1.0493 7.94594C0.985862 7.88244 0.935939 7.80764 0.901383 7.72555C0.866254 7.6421 0.848375 7.55224 0.850116 7.46019M1.5378 8.1499C1.53715 8.1499 1.5365 8.1499 1.53585 8.14989L1.53667 7.99989L1.53973 8.14986C1.53908 8.14988 1.53844 8.14989 1.5378 8.1499ZM0.850116 7.46019C0.850126 7.45965 0.850137 7.45912 0.850148 7.45858L1.00011 7.46179H0.850114C0.850114 7.46126 0.850115 7.46072 0.850116 7.46019ZM4.46673 7.90618V2.91575C4.46994 2.80356 4.47033 2.69143 4.46804 2.57937L7.96337 7.9876L8.0077 8.05618H8.08935H9.08495H9.23495V7.90618V1V0.85H9.08495H8.27631H8.12631V1V5.85487C8.12306 6.00128 8.12426 6.14793 8.12979 6.29447L4.68624 0.919087L4.64198 0.85H4.55993H3.5081H3.3581V1V7.90618V8.05618H3.5081H4.31673H4.46673V7.90618ZM14.7176 7.14829L14.7185 6.99739H14.5676H11.86V4.90464H14.2104H14.3604V4.75464V4.02608V3.87608H14.2104H11.86V1.88158H14.3928H14.5428V1.73158V1V0.85H14.3928H10.8816H10.7316V1V7.90618V8.05618H10.8816H14.563H14.7121L14.713 7.90708L14.7176 7.14829ZM15.1862 0.85H15.0362V1V1.73158V1.88158H15.1862H17.0274V7.90618V8.05618H17.1774H17.986H18.136V7.90618V1.88158H20H20.15V1.73158V1V0.85H20H15.1862Z" fill="#512BD4" stroke="#512BD4" stroke-width="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5466 6.99999C20.4071 6.75838 20.2147 6.54737 20.0031 6.42517L12.6559 2.1833C12.2327 1.9389 11.54 1.9389 11.1168 2.1833L3.76957 6.42517C3.34628 6.66951 3 7.26937 3 7.75812V16.2419C3 16.4863 3.0866 16.7585 3.22611 17L3.22607 17C3.36553 17.2416 3.55795 17.4527 3.76954 17.5748L11.1167 21.8167C11.54 22.0611 12.2326 22.0611 12.6559 21.8167L20.0031 17.5748C20.2147 17.4526 20.4071 17.2416 20.5465 17L15.76 14.2365L17.0169 14.9638C15.9603 16.7899 13.9944 17.9243 11.8864 17.9243C8.61976 17.9243 5.96216 15.2667 5.96216 12C5.96216 8.7334 8.61976 6.07581 11.8864 6.07581C13.9943 6.07581 15.9601 7.21012 17.0167 9.03606L14.4529 10.5197C13.924 9.60572 12.9406 9.03792 11.8864 9.03792C10.253 9.03792 8.92427 10.3667 8.92427 12C8.92427 13.6333 10.253 14.9622 11.8864 14.9622C12.9402 14.9622 13.9232 14.3949 14.4523 13.4815L11.8863 12L11.8864 12L14.4523 13.4815L14.453 13.4802L15.7254 14.2165L20.5466 17C20.686 16.7584 20.7727 16.4863 20.7727 16.2419V7.75807C20.7727 7.51367 20.6862 7.24156 20.5466 7L18.6993 8.06657L20.5466 6.99999ZM17.8106 11.6709H17.1523V11.0127H16.4941V11.6709H15.8358V12.3292H16.4941V12.9874H17.1523V12.3292H17.8106V11.6709ZM19.6208 11.0127V11.6709H20.2791V12.3292H19.6208V12.9874H18.9626V12.3292H18.3043V11.6709H18.9626V11.0127H19.6208Z" fill="#373B4D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.53673 13.7707C2.44248 13.7689 2.35245 13.789 2.27079 13.823L2.27078 13.823L2.26809 13.8241C2.18619 13.8601 2.11067 13.9117 2.04778 13.9776C1.98442 14.0411 1.93372 14.1166 1.90025 14.2008L1.90024 14.2008L1.89982 14.2019C1.86787 14.2841 1.85032 14.3715 1.85012 14.4602M2.53673 13.7707C2.62685 13.7709 2.71695 13.7882 2.80221 13.8257C2.88344 13.8613 2.9584 13.9123 3.02097 13.9775C3.08783 14.041 3.13744 14.1165 3.17282 14.198L3.17349 14.1995L3.17348 14.1995C3.20882 14.2835 3.22627 14.3729 3.22627 14.4633C3.22627 14.5537 3.20882 14.6431 3.17348 14.7271C3.13908 14.8088 3.08879 14.8848 3.02168 14.9483C2.95873 15.0122 2.88363 15.063 2.80079 15.0976C2.71749 15.1323 2.62805 15.1501 2.53777 15.1499C2.4466 15.1515 2.35607 15.1342 2.27193 15.099C2.1879 15.0639 2.11208 15.0118 2.0493 14.9459C1.98587 14.8824 1.93594 14.8076 1.90138 14.7256C1.86625 14.6421 1.84837 14.5522 1.85012 14.4602M2.53673 13.7707C2.53726 13.7707 2.53779 13.7707 2.53832 13.7707L2.53515 13.9207V13.7707C2.53568 13.7707 2.5362 13.7707 2.53673 13.7707ZM1.85012 14.4602C1.85013 14.4597 1.85014 14.4591 1.85015 14.4586L2.00011 14.4618H1.85011C1.85011 14.4613 1.85011 14.4607 1.85012 14.4602ZM5.46673 14.9062V9.91575C5.46994 9.80356 5.47033 9.69143 5.46804 9.57937L8.96337 14.9876L9.0077 15.0562H9.08935H10.0849H10.2349V14.9062V8V7.85H10.0849H9.27631H9.12631V8V12.8549C9.12306 13.0013 9.12426 13.1479 9.12979 13.2945L5.68624 7.91909L5.64198 7.85H5.55993H4.5081H4.3581V8V14.9062V15.0562H4.5081H5.31673H5.46673V14.9062ZM15.7176 14.1483L15.7185 13.9974H15.5676H12.86V11.9046H15.2104H15.3604V11.7546V11.0261V10.8761H15.2104H12.86V8.88158H15.3928H15.5428V8.73158V8V7.85H15.3928H11.8816H11.7316V8V14.9062V15.0562H11.8816H15.563H15.7121L15.713 14.9071L15.7176 14.1483ZM16.1862 7.85H16.0362V8V8.73158V8.88158H16.1862H18.0274V14.9062V15.0562H18.1774H18.986H19.136V14.9062V8.88158H21H21.15V8.73158V8V7.85H21H16.1862Z" fill="#373B4D" stroke="#373B4D" stroke-width="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB