diff --git a/.env.example b/.env.example index 897876704..e9457b89f 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ VITE_APPWRITE_ENDPOINT= -VITE_GOOGLE_ANALYTICS= -VITE_SENTRY_DSN= \ No newline at end of file +VITE_APPWRITE_GROWTH_ENDPOINT= +VITE_GA_PROJECT= +VITE_SENTRY_DSN= +VITE_CONSOLE_MODE=self-hosted \ No newline at end of file diff --git a/README.md b/README.md index eceed7586..0dd094894 100644 --- a/README.md +++ b/README.md @@ -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) -

-Console 2.0 - Backend as a Service, reimagined by Appwrite | Product Hunt -

- ![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. diff --git a/package-lock.json b/package-lock.json index 0a7d57f97..766f48f5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "0.0.1", "dependencies": { "@analytics/google-analytics": "^1.0.5", - "@aw-labs/appwrite-console": "^13.0.0", - "@aw-labs/icons": "0.0.0-78", - "@aw-labs/ui": "0.0.0-78", + "@aw-labs/appwrite-console": "^13.1.0", + "@aw-labs/icons": "0.0.0-82", + "@aw-labs/ui": "0.0.0-82", "@popperjs/core": "^2.11.6", "@sentry/svelte": "^7.19.0", "@sentry/tracing": "^7.19.0", @@ -145,23 +145,23 @@ "integrity": "sha512-1Yw7u/COtxx06BfwlI+kVhsa/upKYzmCNrT4c8QDeCY2KMYlnijkUjtHiPU08HxyTIVB5j6d75O0YWVIHwQS8g==" }, "node_modules/@aw-labs/appwrite-console": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.0.0.tgz", - "integrity": "sha512-a5illDoFZAhgLP8Ae+KHPzBIUozKXzjyHUw6jTwNPZaL+b2RnTe3oRLZU8l8Kt5pB5m7NZx6kAcZm+NMf8NeCw==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.1.0.tgz", + "integrity": "sha512-1FyKke5b3vx+D3VfNYd3YnFhzUOlsj9bD+KTRLWA4WRItb55thFVZJMWQQ4XnZtDiLPuJAn2XjbMSfMr+9XYwg==", "dependencies": { "cross-fetch": "3.1.5", "isomorphic-form-data": "2.0.0" } }, "node_modules/@aw-labs/icons": { - "version": "0.0.0-78", - "resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-78.tgz", - "integrity": "sha512-M1yH0EUanjuHw1McVJfVKrkbHOoSd4PF8ZOYqMXOhSwuwk37MFR8jUapYb2NPu3WdfLTGI6H3N+HwaILePotCg==" + "version": "0.0.0-82", + "resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-82.tgz", + "integrity": "sha512-TQ3abZGBjisd4WpeBVSUihY98drljdlcdw++z8icNpQVYohmUejRHU59mhmIfXCkhpA1s0tW+5xEMp0WmLNp9Q==" }, "node_modules/@aw-labs/ui": { - "version": "0.0.0-78", - "resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-78.tgz", - "integrity": "sha512-YjIqkwAqZLvjJ6MQBxTEbAuotXHL1E/LXwmHOH9/Tu6TvF5p8e83LlyHVG6d8rXwsmsGfUcXLJEvEcc8RMEOMQ==", + "version": "0.0.0-82", + "resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-82.tgz", + "integrity": "sha512-zZDTP117uxMrwzAacOcpSXI77FMFcfv6+Da1R5sDWoqxhvZ0Wyx0Z6cn3dakV0M3zJfYZQ49LmSnU/5wqHPc7A==", "dependencies": { "@aw-labs/icons": "*", "normalize.css": "^8.0.1", @@ -7740,23 +7740,23 @@ "integrity": "sha512-1Yw7u/COtxx06BfwlI+kVhsa/upKYzmCNrT4c8QDeCY2KMYlnijkUjtHiPU08HxyTIVB5j6d75O0YWVIHwQS8g==" }, "@aw-labs/appwrite-console": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.0.0.tgz", - "integrity": "sha512-a5illDoFZAhgLP8Ae+KHPzBIUozKXzjyHUw6jTwNPZaL+b2RnTe3oRLZU8l8Kt5pB5m7NZx6kAcZm+NMf8NeCw==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@aw-labs/appwrite-console/-/appwrite-console-13.1.0.tgz", + "integrity": "sha512-1FyKke5b3vx+D3VfNYd3YnFhzUOlsj9bD+KTRLWA4WRItb55thFVZJMWQQ4XnZtDiLPuJAn2XjbMSfMr+9XYwg==", "requires": { "cross-fetch": "3.1.5", "isomorphic-form-data": "2.0.0" } }, "@aw-labs/icons": { - "version": "0.0.0-78", - "resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-78.tgz", - "integrity": "sha512-M1yH0EUanjuHw1McVJfVKrkbHOoSd4PF8ZOYqMXOhSwuwk37MFR8jUapYb2NPu3WdfLTGI6H3N+HwaILePotCg==" + "version": "0.0.0-82", + "resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-82.tgz", + "integrity": "sha512-TQ3abZGBjisd4WpeBVSUihY98drljdlcdw++z8icNpQVYohmUejRHU59mhmIfXCkhpA1s0tW+5xEMp0WmLNp9Q==" }, "@aw-labs/ui": { - "version": "0.0.0-78", - "resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-78.tgz", - "integrity": "sha512-YjIqkwAqZLvjJ6MQBxTEbAuotXHL1E/LXwmHOH9/Tu6TvF5p8e83LlyHVG6d8rXwsmsGfUcXLJEvEcc8RMEOMQ==", + "version": "0.0.0-82", + "resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-82.tgz", + "integrity": "sha512-zZDTP117uxMrwzAacOcpSXI77FMFcfv6+Da1R5sDWoqxhvZ0Wyx0Z6cn3dakV0M3zJfYZQ49LmSnU/5wqHPc7A==", "requires": { "@aw-labs/icons": "*", "normalize.css": "^8.0.1", diff --git a/package.json b/package.json index 0a6855b61..0ff88f4b4 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,9 @@ "dependencies": { "@analytics/google-analytics": "^1.0.5", "analytics": "^0.8.1", - "@aw-labs/appwrite-console": "^13.0.0", - "@aw-labs/icons": "0.0.0-78", - "@aw-labs/ui": "0.0.0-78", + "@aw-labs/appwrite-console": "^13.1.0", + "@aw-labs/icons": "0.0.0-82", + "@aw-labs/ui": "0.0.0-82", "@popperjs/core": "^2.11.6", "@sentry/svelte": "^7.19.0", "@sentry/tracing": "^7.19.0", diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 1f77d0cc3..a57000024 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -3,12 +3,13 @@ import googleAnalytics from '@analytics/google-analytics'; import { get } from 'svelte/store'; import { page } from '$app/stores'; import { user } from '$lib/stores/user'; +import { growthEndpoint, Mode } from '$lib/constants'; const analytics = Analytics({ app: 'appwrite', plugins: [ googleAnalytics({ - measurementIds: ['G-R4YJ9JN8L4'] + measurementIds: [import.meta.env.VITE_GA_PROJECT?.toString() || 'G-R4YJ9JN8L4'] }) ] }); @@ -39,7 +40,7 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo email = userStore.email; name = userStore.name; } - fetch('https://growth.appwrite.io/v1/analytics', { + fetch(`${growthEndpoint}/analytics`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -48,6 +49,7 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo action: event, label: event, url: window.location.origin + path, + account: import.meta.env.VITE_CONSOLE_MODE?.toString() || Mode.SELF_HOSTED, data: { email, name, diff --git a/src/lib/components/copy.svelte b/src/lib/components/copy.svelte index 1f0e5dfd8..0e859e79c 100644 --- a/src/lib/components/copy.svelte +++ b/src/lib/components/copy.svelte @@ -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 + }); + } + } setTimeout(() => (content = 'Click to copy'))} use:tooltip={{ diff --git a/src/lib/components/copyInput.svelte b/src/lib/components/copyInput.svelte index 65bd6bb81..b06f1ae36 100644 --- a/src/lib/components/copyInput.svelte +++ b/src/lib/components/copyInput.svelte @@ -1,5 +1,6 @@ +
@@ -98,8 +106,7 @@ bind:value bind:this={element} on:keydown={handleInput} - on:blur={addValue} - on:invalid={handleInvalid} /> + on:blur={addValue} />
{#if error} diff --git a/src/lib/elements/table/cellText.svelte b/src/lib/elements/table/cellText.svelte index 59b8fe2e3..94f36b389 100644 --- a/src/lib/elements/table/cellText.svelte +++ b/src/lib/elements/table/cellText.svelte @@ -4,6 +4,6 @@ export let showOverflow = false; -
+
diff --git a/src/lib/helpers/array.ts b/src/lib/helpers/array.ts index cd7a923fb..12cde8c4b 100644 --- a/src/lib/helpers/array.ts +++ b/src/lib/helpers/array.ts @@ -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(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)]; +} diff --git a/src/lib/helpers/copy.ts b/src/lib/helpers/copy.ts new file mode 100644 index 000000000..6399995ea --- /dev/null +++ b/src/lib/helpers/copy.ts @@ -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; +} diff --git a/src/lib/layout/unauthenticated.svelte b/src/lib/layout/unauthenticated.svelte index 62d9e923b..1028e4f0a 100644 --- a/src/lib/layout/unauthenticated.svelte +++ b/src/lib/layout/unauthenticated.svelte @@ -4,6 +4,7 @@ import LoginDark from '$lib/images/login/login-dark-mode.svg'; import { app } from '$lib/stores/app'; import { base } from '$app/paths'; + import { user } from '$lib/stores/user'; const technologies = [ 'js', @@ -25,7 +26,7 @@ diff --git a/src/lib/stores/app.ts b/src/lib/stores/app.ts index 555d36415..f3b03e36b 100644 --- a/src/lib/stores/app.ts +++ b/src/lib/stores/app.ts @@ -1,4 +1,5 @@ import { browser } from '$app/environment'; +import { growthEndpoint } from '$lib/constants'; import { writable } from 'svelte/store'; export type AppStore = { @@ -59,7 +60,7 @@ function createFeedbackStore() { email?: string, value?: number ) => { - const response = await fetch('https://growth.appwrite.io/v1/feedback', { + const response = await fetch(`${growthEndpoint}/feedback`, { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/src/lib/stores/oauth-providers.ts b/src/lib/stores/oauth-providers.ts index 36100ed16..4ed398e67 100644 --- a/src/lib/stores/oauth-providers.ts +++ b/src/lib/stores/oauth-providers.ts @@ -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; diff --git a/src/lib/stores/project-services.ts b/src/lib/stores/project-services.ts index 6f4a8cb0d..ef8a1c444 100644 --- a/src/lib/stores/project-services.ts +++ b/src/lib/stores/project-services.ts @@ -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 }); diff --git a/src/lib/stores/uploader.ts b/src/lib/stores/uploader.ts index 6ca846214..9538d0b79 100644 --- a/src/lib/stores/uploader.ts +++ b/src/lib/stores/uploader.ts @@ -1,5 +1,6 @@ import type { Models } from '@aw-labs/appwrite-console'; import { writable } from 'svelte/store'; +import { sdkForProject } from './sdk'; type UploaderFile = { $id: string; @@ -8,7 +9,6 @@ type UploaderFile = { progress: number; completed: boolean; failed: boolean; - cancelled: boolean; error?: string; }; export type Uploader = { @@ -42,7 +42,6 @@ const createUploader = () => { close: () => update((n) => { n.isOpen = !n.isOpen; - return n; }), toggle: () => @@ -57,22 +56,43 @@ const createUploader = () => { isCollapsed: false, files: [] }), - addFile: async (file: Models.File, isOpen = true, isCollapsed = false) => { + uploadFile: async (bucketId: string, id: string, file: File, permissions: string[]) => { const newFile: UploaderFile = { - $id: file.$id, - bucketId: file.bucketId, + $id: id, + bucketId: bucketId, name: file.name, - progress: calculateProgress(file), - completed: calculateProgress(file) === 100 ? true : false, - failed: false, - cancelled: false + progress: 0, + completed: false, + failed: false }; - return update((n) => { - n.isOpen = isOpen; - n.isCollapsed = isCollapsed; + update((n) => { + n.isOpen = true; + n.isCollapsed = false; n.files.unshift(newFile); return n; }); + const uploadedFile = await sdkForProject.storage.createFile( + bucketId, + id ?? 'unique()', + file, + permissions, + (p) => { + newFile.$id = p.$id; + newFile.progress = p.progress; + newFile.completed = p.progress === 100 ? true : false; + updateFile(p.$id, newFile); + } + ); + newFile.$id = uploadedFile.$id; + newFile.progress = 100; + newFile.completed = true; + updateFile(newFile.$id, newFile); + }, + removeFromQueue: (id: string) => { + update((n) => { + n.files = n.files.filter((f) => f.$id !== id); + return n; + }); }, removeFile: async (file: Models.File) => { if (file.chunksTotal === file.chunksUploaded) { @@ -81,18 +101,9 @@ const createUploader = () => { return n; }); - } else { - if (confirm('Are you sure you want to cancel the upload?')) { - updateFile(file.$id, { cancelled: true }); - } } } }; }; export const uploader = createUploader(); - -function calculateProgress(file: Models.File) { - const progress = file.chunksUploaded / file.chunksTotal; - return Math.round(progress * 100); -} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 43965143d..bdaa9a1ad 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -49,11 +49,16 @@ if (!$page.url.pathname.startsWith('/auth')) { const acceptedRoutes = ['/login', '/register', '/recover', '/invite']; if ($user) { - if (!$page.url.pathname.startsWith('/console')) { + if ( + !$page.url.pathname.startsWith('/console') && + !$page.url.pathname.startsWith('/invite') + ) { + console.log('test'); await goto(`${base}/console`, { replaceState: true }); } + loading.set(false); } else { if (acceptedRoutes.includes($page.url.pathname)) { await goto(`${base}${$page.url.pathname}${$page.url.search}`); diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 4544b5beb..84e164569 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -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'); diff --git a/src/routes/auth/oauth/failure/+page.svelte b/src/routes/auth/oauth2/failure/+page.svelte similarity index 100% rename from src/routes/auth/oauth/failure/+page.svelte rename to src/routes/auth/oauth2/failure/+page.svelte diff --git a/src/routes/auth/oauth/success/+page.svelte b/src/routes/auth/oauth2/success/+page.svelte similarity index 100% rename from src/routes/auth/oauth/success/+page.svelte rename to src/routes/auth/oauth2/success/+page.svelte diff --git a/src/routes/console/account/delete.svelte b/src/routes/console/account/delete.svelte index a64b1321f..bf1a7d2a3 100644 --- a/src/routes/console/account/delete.svelte +++ b/src/routes/console/account/delete.svelte @@ -1,17 +1,19 @@ + + + {provider.name} OAuth2 Settings + +

+ To use {provider.name} authentication in your application, first fill in this form. For more + info you can + visit the docs. +

+ + + + + + To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. + +
+

URI

+ +
+
+ + + + +
diff --git a/src/routes/console/project-[project]/auth/gitlabOAuth.svelte b/src/routes/console/project-[project]/auth/gitlabOAuth.svelte new file mode 100644 index 000000000..f179f74bc --- /dev/null +++ b/src/routes/console/project-[project]/auth/gitlabOAuth.svelte @@ -0,0 +1,106 @@ + + + + {provider.name} OAuth2 Settings + +

+ To use {provider.name} authentication in your application, first fill in this form. For more + info you can + visit the docs. +

+ + + + + + To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. + +
+

URI

+ +
+
+ + + + +
diff --git a/src/routes/console/project-[project]/auth/mainOAuth.svelte b/src/routes/console/project-[project]/auth/mainOAuth.svelte index 43efe29dd..b3be6f62a 100644 --- a/src/routes/console/project-[project]/auth/mainOAuth.svelte +++ b/src/routes/console/project-[project]/auth/mainOAuth.svelte @@ -84,7 +84,7 @@ + }/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
diff --git a/src/routes/console/project-[project]/auth/microsoftOAuth.svelte b/src/routes/console/project-[project]/auth/microsoftOAuth.svelte index caeffe743..fc0b38d0a 100644 --- a/src/routes/console/project-[project]/auth/microsoftOAuth.svelte +++ b/src/routes/console/project-[project]/auth/microsoftOAuth.svelte @@ -93,7 +93,7 @@ + }/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} /> diff --git a/src/routes/console/project-[project]/auth/oktaOAuth.svelte b/src/routes/console/project-[project]/auth/oktaOAuth.svelte index c8ff2593a..2fa6dbcb8 100644 --- a/src/routes/console/project-[project]/auth/oktaOAuth.svelte +++ b/src/routes/console/project-[project]/auth/oktaOAuth.svelte @@ -35,7 +35,8 @@ projectId, provider.name.toLowerCase(), appId, - secret + secret, + enabled ); addNotification({ type: 'success', @@ -102,7 +103,7 @@ + }/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} /> diff --git a/src/routes/console/project-[project]/auth/security/+page.svelte b/src/routes/console/project-[project]/auth/security/+page.svelte index 5392e0db5..6168b6002 100644 --- a/src/routes/console/project-[project]/auth/security/+page.svelte +++ b/src/routes/console/project-[project]/auth/security/+page.svelte @@ -1,7 +1,7 @@ - -
- {#if $user.email || $user.phone} - {#if $user.name} - - {$user.name} - {:else} -
-
- {/if} - {:else} -
-
- {/if} -
- -
-
- {#if $user.email} -

{$user.email}

- {/if} - {#if $user.phone} -

{$user.phone}

- {/if} -

Joined: {toLocaleDateTime($user.registration)}

-
- {#if !$user.status} - blocked - {:else if $user.email && $user.phone} - - {$user.emailVerification && $user.phoneVerification - ? 'verified' - : $user.emailVerification - ? 'verified email' - : $user.phoneVerification - ? 'verified phone' - : 'unverified'} - - {:else} - - {$user.emailVerification - ? 'verified ' - : $user.phoneVerification - ? 'verified ' - : 'unverified'} - - {/if} -
-
- - - - {#if $user.status} - {#if $user.phone && $user.email} - - - - updateVerificationEmail()}> - {$user.emailVerification ? 'Unverify' : 'Verify'} email - - updateVerificationPhone()}> - {$user.phoneVerification ? 'Unverify' : 'Verify'} phone - - - - {:else if $user.email} - - {:else if $user.phone} - - {/if} - {/if} - -
- -
- - Update Name - - -
    - -
-
- - - - -
-
- -
- - Update Email - -
    - -
-
- - - - -
-
- -
- - Update Phone - -
    - -
-
- - - - -
-
- -
- -
- Update Password -
- -

- Enter a new password. A password must contain at least 8 characters. -

- -
    - -
-
- - - - -
-
- -
- - User Preferences -

- You can update your user preferences by storing information on the user's objects so - they can easily be shared across devices and sessions. -

- - -
    - {#if prefs} - {#each prefs as [key, value], index} -
  • -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
  • - {/each} - {/if} -
- - -
- - - - -
- - - -
- Danger Zone -
-

- The user will be permanently deleted, including all data associated with this user. This - action is irreversible. -

- - - - {#if $user.email || $user.phone} - {#if $user.name} - - {:else} -
-
- {/if} - {:else} -
-
- {/if} -
- -
- {$user.name || $user.email || $user.phone || 'Anonymous'} -
-
-

- {$user.email && $user.phone - ? [$user.email, $user.phone].join(',') - : $user.email || $user.phone} -

-
-
- - - - -
+ + + + + + +
- - diff --git a/src/routes/console/project-[project]/auth/user-[user]/dangerZone.svelte b/src/routes/console/project-[project]/auth/user-[user]/dangerZone.svelte new file mode 100644 index 000000000..207f1badd --- /dev/null +++ b/src/routes/console/project-[project]/auth/user-[user]/dangerZone.svelte @@ -0,0 +1,53 @@ + + + +
+ Danger Zone +
+

+ The user will be permanently deleted, including all data associated with this user. This + action is irreversible. +

+ + + + {#if $user.email || $user.phone} + {#if $user.name} + + {:else} +
+
+ {/if} + {:else} +
+
+ {/if} +
+ +
+ {$user.name || $user.email || $user.phone || 'Anonymous'} +
+
+

+ {$user.email && $user.phone + ? [$user.email, $user.phone].join(',') + : $user.email || $user.phone} +

+
+
+ + + + +
+ + diff --git a/src/routes/console/project-[project]/auth/user-[user]/memberships/[[page]]/+page.svelte b/src/routes/console/project-[project]/auth/user-[user]/memberships/[[page]]/+page.svelte index a5f8b2fc6..b822f838a 100644 --- a/src/routes/console/project-[project]/auth/user-[user]/memberships/[[page]]/+page.svelte +++ b/src/routes/console/project-[project]/auth/user-[user]/memberships/[[page]]/+page.svelte @@ -72,7 +72,7 @@ diff --git a/src/routes/console/project-[project]/auth/user-[user]/updateEmail.svelte b/src/routes/console/project-[project]/auth/user-[user]/updateEmail.svelte new file mode 100644 index 000000000..5eecbbbed --- /dev/null +++ b/src/routes/console/project-[project]/auth/user-[user]/updateEmail.svelte @@ -0,0 +1,53 @@ + + +
+ + Update Email + +
    + +
+
+ + + + +
+
diff --git a/src/routes/console/project-[project]/auth/user-[user]/updateName.svelte b/src/routes/console/project-[project]/auth/user-[user]/updateName.svelte new file mode 100644 index 000000000..1b4119579 --- /dev/null +++ b/src/routes/console/project-[project]/auth/user-[user]/updateName.svelte @@ -0,0 +1,54 @@ + + +
+ + Update Name + + +
    + +
+
+ + + + +
+
diff --git a/src/routes/console/project-[project]/auth/user-[user]/updatePassword.svelte b/src/routes/console/project-[project]/auth/user-[user]/updatePassword.svelte new file mode 100644 index 000000000..66116f2c6 --- /dev/null +++ b/src/routes/console/project-[project]/auth/user-[user]/updatePassword.svelte @@ -0,0 +1,55 @@ + + +
+ +
+ Update Password +
+ +

+ Enter a new password. A password must contain at least 8 characters. +

+ +
    + +
+
+ + + + +
+
diff --git a/src/routes/console/project-[project]/auth/user-[user]/updatePhone.svelte b/src/routes/console/project-[project]/auth/user-[user]/updatePhone.svelte new file mode 100644 index 000000000..d77cd778b --- /dev/null +++ b/src/routes/console/project-[project]/auth/user-[user]/updatePhone.svelte @@ -0,0 +1,53 @@ + + +
+ + Update Phone + +
    + +
+
+ + + + +
+
diff --git a/src/routes/console/project-[project]/auth/user-[user]/updatePrefs.svelte b/src/routes/console/project-[project]/auth/user-[user]/updatePrefs.svelte new file mode 100644 index 000000000..c000793bc --- /dev/null +++ b/src/routes/console/project-[project]/auth/user-[user]/updatePrefs.svelte @@ -0,0 +1,129 @@ + + +
+ + User Preferences +

+ You can update your user preferences by storing information on the user's objects so + they can easily be shared across devices and sessions. +

+ + +
    + {#if prefs} + {#each prefs as [key, value], index} +
  • +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
  • + {/each} + {/if} +
+ + +
+ + + + +
+ diff --git a/src/routes/console/project-[project]/auth/user-[user]/updateStatus.svelte b/src/routes/console/project-[project]/auth/user-[user]/updateStatus.svelte new file mode 100644 index 000000000..54f6df3a7 --- /dev/null +++ b/src/routes/console/project-[project]/auth/user-[user]/updateStatus.svelte @@ -0,0 +1,157 @@ + + + +
+ {#if $user.email || $user.phone} + {#if $user.name} + + {$user.name} + {:else} +
+
+ {/if} + {:else} +
+
+ {/if} +
+ +
+
+ {#if $user.email} +

{$user.email}

+ {/if} + {#if $user.phone} +

{$user.phone}

+ {/if} +

Joined: {toLocaleDateTime($user.registration)}

+
+ {#if !$user.status} + blocked + {:else if $user.email && $user.phone} + + {$user.emailVerification && $user.phoneVerification + ? 'verified' + : $user.emailVerification + ? 'verified email' + : $user.phoneVerification + ? 'verified phone' + : 'unverified'} + + {:else} + + {$user.emailVerification + ? 'verified ' + : $user.phoneVerification + ? 'verified ' + : 'unverified'} + + {/if} +
+
+ + + + {#if $user.status} + {#if $user.phone && $user.email} + + + + updateVerificationEmail()}> + {$user.emailVerification ? 'Unverify' : 'Verify'} email + + updateVerificationPhone()}> + {$user.phoneVerification ? 'Unverify' : 'Verify'} phone + + + + {:else if $user.email} + + {:else if $user.phone} + + {/if} + {/if} + +
diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/+page.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/+page.svelte index 1b7c7501e..4b9d85eb9 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/+page.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/+page.svelte @@ -63,9 +63,11 @@ {/if} - {attribute.type} + + {`${attribute.type}${attribute.array ? '[]' : ''}`} + - {attribute.default ? attribute.default : '-'} + {attribute.default !== null ? attribute.default : '-'} { selectedAttribute = attribute; showOverview = true; + showDropdown[index] = false; }}>Overview { selectedAttribute = attribute; showCreateIndex = true; + showDropdown[index] = false; }}>Create Index + readonly={!!selectedAttribute} + required /> - import { Modal } from '$lib/components'; - import { Button, InputText, FormList, InputSelect } from '$lib/elements/forms'; - import { collection } from '../store'; - import { onMount } from 'svelte'; - import { sdkForProject } from '$lib/stores/sdk'; - import { addNotification } from '$lib/stores/notifications'; + import { goto, invalidate } from '$app/navigation'; + import { base } from '$app/paths'; import { page } from '$app/stores'; - import type { Attributes } from '../store'; - import { invalidate } from '$app/navigation'; - import { Dependencies } from '$lib/constants'; - import Select from './select.svelte'; import { trackEvent } from '$lib/actions/analytics'; + import { Modal } from '$lib/components'; + import { Dependencies } from '$lib/constants'; + import { Button, FormList, InputSelect, InputText } from '$lib/elements/forms'; + import { remove } from '$lib/helpers/array'; + import { addNotification } from '$lib/stores/notifications'; + import { sdkForProject } from '$lib/stores/sdk'; + import type { Attributes } from '../store'; + import { collection } from '../store'; + import Select from './select.svelte'; export let showCreateIndex = false; export let externalAttribute: Attributes = null; @@ -24,115 +25,91 @@ { value: 'unique', label: 'Unique' }, { value: 'fulltext', label: 'FullText' } ]; - let newAttr = false; let selectedType = 'key'; - $: attributeOptions = $collection.attributes.map((attribute: Attributes) => ({ + + let attributeOptions = $collection.attributes.map((attribute: Attributes) => ({ value: attribute.key, label: attribute.key })); - $: attributeList = []; + let attributeList = [{ value: '', order: '' }]; + let creating = false; - let selectedAttribute = ''; - let selectedOrder = ''; - - onMount(() => { - if (externalAttribute) { - attributeList = [{ value: externalAttribute.key, order: 'ASC' }]; - } - }); - - $: if (showCreateIndex) { - attributeList = []; - selectedOrder = selectedAttribute = ''; + function initialize() { + attributeList = externalAttribute + ? [{ value: externalAttribute.key, order: 'ASC' }] + : [{ value: '', order: '' }]; selectedType = 'key'; key = null; } - const created = async () => { - if (key && selectedAttribute && selectedOrder && selectedType) { - if (selectedAttribute && selectedOrder) { - attributeList.push({ value: selectedAttribute, order: selectedOrder }); - selectedAttribute = selectedOrder = ''; - } - try { - await sdkForProject.databases.createIndex( - databaseId, - $collection.$id, - key, - selectedType, - attributeList.map((a) => a.value), - attributeList.map((a) => a.order) - ); - invalidate(Dependencies.COLLECTION); - addNotification({ - message: 'Index has been created', - type: 'success' - }); - trackEvent('submit_index_create'); - } catch (error) { - addNotification({ - message: error.message, - type: 'error' - }); - } + $: if (showCreateIndex) { + initialize(); + } + + $: addAttributeDisabled = + !attributeList.at(-1)?.value || !attributeList.at(-1)?.order || creating; + + async function create() { + if (!(key && selectedType && !addAttributeDisabled)) { + error = 'All fields are required'; + return; + } + + creating = true; + + try { + await sdkForProject.databases.createIndex( + databaseId, + $collection.$id, + key, + selectedType, + attributeList.map((a) => a.value), + attributeList.map((a) => a.order) + ); + await Promise.allSettled([ + invalidate(Dependencies.COLLECTION), + invalidate(Dependencies.DATABASE) + ]); + + goto( + `${base}/console/project-${$page.params.project}/databases/database-${databaseId}/collection-${$collection.$id}/indexes` + ); + + addNotification({ + message: 'Index has been created', + type: 'success' + }); + trackEvent('submit_index_create'); + } catch (error) { + addNotification({ + message: error.message, + type: 'error' + }); + } finally { showCreateIndex = false; - } else error = 'All fields are required'; - }; + creating = false; + } + } + + function addAttribute() { + if (addAttributeDisabled) return; + + // We assign instead of pushing to trigger Svelte's reactivity + attributeList = [...attributeList, { value: '', order: '' }]; + } - + Create Index - {#if attributeList?.length} - {#each attributeList as index, i} -
  • -
    - -
    -
    - -
    - -
    - -
    -
  • - {/each} - {/if} - {#if !attributeList?.length || newAttr} + {#each attributeList as attribute, i}
  • -
    - + @@ -141,9 +118,7 @@ {#each attributeOptions as option} - {/each} @@ -151,42 +126,34 @@
    - +
    +
  • - {/if} -
    - +
    diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/+page.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/+page.svelte index d64073937..707864dc4 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/+page.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/+page.svelte @@ -1,257 +1,19 @@ {#if $collection} - - {$collection.name} - - -
      - -
    -
    -

    Created: {toLocaleDateTime($collection.$createdAt)}

    -

    Last Updated: {toLocaleDateTime($collection.$updatedAt)}

    -
    -
    - - - - -
    - - - Update Name - - -
      - - {#if showError === 'name'} - {errorMessage} - {/if} -
    -
    - - - - -
    - - - Update Permissions -

    - Choose who can access your collection and documents. For more information, check out - the Permissions Guide in our documentation. -

    - - {#if collectionPermissions} - - {/if} - - - - -
    - - - Update Document Security - - - - -

    - When document security is enabled, users will be able to access documents for - which they have been granted either Document or Collection permissions. -

    -

    - If document security is disabled, users can access documents only if they have Collection permissions. Document permissions will be ignored. -

    -
    - - - -
    - - - Delete Collection -

    - The collection will be permanently deleted, including all the documents within it. - This action is irreversible. -

    - - - -
    {$collection.name}
    -
    -

    Last Updated: {toLocaleDateTime($collection.$updatedAt)}

    -
    -
    - - - - -
    + + + + + {/if}
    - - diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/dangerZone.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/dangerZone.svelte new file mode 100644 index 000000000..53653a2d4 --- /dev/null +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/dangerZone.svelte @@ -0,0 +1,31 @@ + + + + Delete Collection +

    + The collection will be permanently deleted, including all the documents within it. This + action is irreversible. +

    + + + +
    {$collection.name}
    +
    +

    Last Updated: {toLocaleDateTime($collection.$updatedAt)}

    +
    +
    + + + + +
    + + diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updateName.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updateName.svelte new file mode 100644 index 000000000..e98422453 --- /dev/null +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updateName.svelte @@ -0,0 +1,66 @@ + + +
    + + Update Name + + +
      + +
    +
    + + + + +
    +
    diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updatePermissions.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updatePermissions.svelte new file mode 100644 index 000000000..6589c22c0 --- /dev/null +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updatePermissions.svelte @@ -0,0 +1,75 @@ + + + + Update Permissions +

    + Choose who can access your collection and documents. For more information, check out the Permissions Guide in our documentation. +

    + + {#if collectionPermissions} + + {/if} + + + + +
    diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updateSecurity.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updateSecurity.svelte new file mode 100644 index 000000000..87d601be6 --- /dev/null +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updateSecurity.svelte @@ -0,0 +1,70 @@ + + + + Update Document Security + + + + +

    + When document security is enabled, users will be able to access documents for which they + have been granted either Document or Collection permissions. +

    +

    + If document security is disabled, users can access documents only if they have Collection permissions. Document permissions will be ignored. +

    +
    + + + +
    diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updateStatus.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updateStatus.svelte new file mode 100644 index 000000000..6c1353320 --- /dev/null +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/settings/updateStatus.svelte @@ -0,0 +1,68 @@ + + + + {$collection.name} + + +
      + +
    +
    +

    Created: {toLocaleDateTime($collection.$createdAt)}

    +

    Last Updated: {toLocaleDateTime($collection.$updatedAt)}

    +
    +
    + + + + +
    diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/wizard/step2.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/wizard/step2.svelte index 0768461c1..1024dcba2 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/wizard/step2.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/wizard/step2.svelte @@ -12,7 +12,8 @@ Choose who can access your collection and documents. For more information, check out the Permissions Guide in our documentation. + rel="noopener noreferrer" + class="link">Permissions Guide in our documentation.
    {#if $collection.documentSecurity}
    diff --git a/src/routes/console/project-[project]/functions/createVariable.svelte b/src/routes/console/project-[project]/functions/createVariable.svelte index 2654e31c6..0fa4a6706 100644 --- a/src/routes/console/project-[project]/functions/createVariable.svelte +++ b/src/routes/console/project-[project]/functions/createVariable.svelte @@ -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(); + } @@ -48,7 +53,7 @@ required /> - + diff --git a/src/routes/console/project-[project]/functions/function-[function]/create.svelte b/src/routes/console/project-[project]/functions/function-[function]/create.svelte index 14373c646..c8e2f9e08 100644 --- a/src/routes/console/project-[project]/functions/function-[function]/create.svelte +++ b/src/routes/console/project-[project]/functions/function-[function]/create.svelte @@ -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', diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/+page.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/+page.svelte index 28ead1b22..9313c97a0 100644 --- a/src/routes/console/project-[project]/functions/function-[function]/settings/+page.svelte +++ b/src/routes/console/project-[project]/functions/function-[function]/settings/+page.svelte @@ -62,7 +62,15 @@ async function updateName() { try { - await sdkForProject.functions.update(functionId, functionName, $func.execute); + await sdkForProject.functions.update( + functionId, + functionName, + $func.execute, + $func.events, + $func.schedule, + $func.timeout, + $func.enabled + ); invalidate(Dependencies.FUNCTION); addNotification({ message: 'Name has been updated', @@ -79,7 +87,15 @@ async function updatePermissions() { try { - await sdkForProject.functions.update(functionId, $func.name, permissions); + await sdkForProject.functions.update( + functionId, + $func.name, + permissions, + $func.events, + $func.schedule, + $func.timeout, + $func.enabled + ); invalidate(Dependencies.FUNCTION); addNotification({ message: 'Permissions have been updated', @@ -100,7 +116,10 @@ functionId, $func.name, $func.execute, - Array.from($eventSet) + Array.from($eventSet), + $func.schedule, + $func.timeout, + $func.enabled ); invalidate(Dependencies.FUNCTION); addNotification({ @@ -124,7 +143,8 @@ $func.execute, $func.events, functionSchedule, - timeout + $func.timeout, + $func.enabled ); invalidate(Dependencies.FUNCTION); @@ -149,9 +169,11 @@ $func.execute, $func.events, $func.schedule, - timeout + timeout, + $func.enabled ); + invalidate(Dependencies.FUNCTION); addNotification({ type: 'success', message: 'Timeout has been updated' @@ -570,10 +592,12 @@ - +{#if showVariablesModal} + +{/if} diff --git a/src/routes/console/project-[project]/functions/function-[function]/store.ts b/src/routes/console/project-[project]/functions/function-[function]/store.ts index 726e6cd0e..0dd72124b 100644 --- a/src/routes/console/project-[project]/functions/function-[function]/store.ts +++ b/src/routes/console/project-[project]/functions/function-[function]/store.ts @@ -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 = writable(); diff --git a/src/routes/console/project-[project]/overview/keys/[key]/+page@project-[project].svelte b/src/routes/console/project-[project]/overview/keys/[key]/+page@project-[project].svelte index 70d32ee50..ff3f5b24b 100644 --- a/src/routes/console/project-[project]/overview/keys/[key]/+page@project-[project].svelte +++ b/src/routes/console/project-[project]/overview/keys/[key]/+page@project-[project].svelte @@ -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' diff --git a/src/routes/console/project-[project]/overview/keys/wizard.svelte b/src/routes/console/project-[project]/overview/keys/wizard.svelte index 9217ccd88..e302d0b74 100644 --- a/src/routes/console/project-[project]/overview/keys/wizard.svelte +++ b/src/routes/console/project-[project]/overview/keys/wizard.svelte @@ -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({ diff --git a/src/routes/console/project-[project]/overview/platforms/[platform]/+page@project-[project].svelte b/src/routes/console/project-[project]/overview/platforms/[platform]/+page@project-[project].svelte index baec0ded4..b66a71d9e 100644 --- a/src/routes/console/project-[project]/overview/platforms/[platform]/+page@project-[project].svelte +++ b/src/routes/console/project-[project]/overview/platforms/[platform]/+page@project-[project].svelte @@ -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', diff --git a/src/routes/console/project-[project]/overview/platforms/[platform]/android.svelte b/src/routes/console/project-[project]/overview/platforms/[platform]/android.svelte index de95db47c..fa23169a1 100644 --- a/src/routes/console/project-[project]/overview/platforms/[platform]/android.svelte +++ b/src/routes/console/project-[project]/overview/platforms/[platform]/android.svelte @@ -1,5 +1,6 @@ diff --git a/src/routes/console/project-[project]/overview/platforms/wizard/web/step3.svelte b/src/routes/console/project-[project]/overview/platforms/wizard/web/step3.svelte index e13329c53..c9685da2e 100644 --- a/src/routes/console/project-[project]/overview/platforms/wizard/web/step3.svelte +++ b/src/routes/console/project-[project]/overview/platforms/wizard/web/step3.svelte @@ -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}');`; diff --git a/src/routes/console/project-[project]/settings/webhooks/[webhook]/+page.svelte b/src/routes/console/project-[project]/settings/webhooks/[webhook]/+page.svelte index 3a3383fa1..ab3bd3c63 100644 --- a/src/routes/console/project-[project]/settings/webhooks/[webhook]/+page.svelte +++ b/src/routes/console/project-[project]/settings/webhooks/[webhook]/+page.svelte @@ -1,346 +1,18 @@ - - Signature Key -

    - Add the Signature Key to the X-Appwrite-Webhook-Signature header to validate your - webhooks. Learn more about webhook validation. -

    - -
    -

    Key

    - -
    -
    - - - -
    -
    - - Update Name -

    Choose any name that will help you distinguish between Webhooks.

    - - - - - - - - - -
    -
    - -
    - - Update Url - - - - - - - - - - - -
    - -
    - - Update Events -

    - Set the events that will trigger your webhook. Maximum 100 events allowed. -

    - - {#if $eventSet.size} - - {#each Array.from($eventSet) as event} -
  • - - {event} - - - - -
  • - {/each} -
    -
    - -
    - {:else} - (showCreateEvent = true)}> - Add an event to get started - - {/if} -
    - - - - -
    -
    - -
    - - Security -

    - Set an optional basic HTTP authentication username and password to protect your - endpoint from unauthorized access. -

    - - -
    - HTTP Authentication -

    Use to secure your endpoint from untrusted sources.

    -
    - - - - - Warning: Untrusted or self-signed certificates - may not be secure. - - Learn more - -
    -
    - - - - -
    -
    - - -
    - Delete Webhook -
    -

    The webhook will be permanently deleted. This action is irreversible.

    - - - -
    {$webhook.name}
    -
    -

    Last updated: {toLocaleDateTime($webhook.$updatedAt)}

    -
    -
    - - - - -
    + + + + + +
    - - - - - diff --git a/src/routes/console/project-[project]/settings/webhooks/[webhook]/dangerZone.svelte b/src/routes/console/project-[project]/settings/webhooks/[webhook]/dangerZone.svelte new file mode 100644 index 000000000..c531b91a4 --- /dev/null +++ b/src/routes/console/project-[project]/settings/webhooks/[webhook]/dangerZone.svelte @@ -0,0 +1,30 @@ + + + +
    + Delete Webhook +
    +

    The webhook will be permanently deleted. This action is irreversible.

    + + + +
    {$webhook.name}
    +
    +

    Last updated: {toLocaleDateTime($webhook.$updatedAt)}

    +
    +
    + + + + +
    + + diff --git a/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateEvents.svelte b/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateEvents.svelte new file mode 100644 index 000000000..919d21402 --- /dev/null +++ b/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateEvents.svelte @@ -0,0 +1,109 @@ + + +
    + + Update Events +

    + Set the events that will trigger your webhook. Maximum 100 events allowed. +

    + + {#if $eventSet.size} + + {#each Array.from($eventSet) as event} +
  • + + {event} + + + + +
  • + {/each} +
    +
    + +
    + {:else} + (showCreateEvent = true)}>Add an event to get started + {/if} +
    + + + + +
    +
    + + diff --git a/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateName.svelte b/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateName.svelte new file mode 100644 index 000000000..1d4fcd0cf --- /dev/null +++ b/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateName.svelte @@ -0,0 +1,66 @@ + + +
    + + Update Name +

    Choose any name that will help you distinguish between Webhooks.

    + + + + + + + + + +
    +
    diff --git a/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateSecurity.svelte b/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateSecurity.svelte new file mode 100644 index 000000000..dc1d1d8ce --- /dev/null +++ b/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateSecurity.svelte @@ -0,0 +1,110 @@ + + +
    + + Security +

    + Set an optional basic HTTP authentication username and password to protect your endpoint + from unauthorized access. +

    + + +
    + HTTP Authentication +

    Use to secure your endpoint from untrusted sources.

    +
    + + + + + Warning: Untrusted or self-signed certificates may + not be secure. + + Learn more + +
    +
    + + + + +
    +
    diff --git a/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateSignature.svelte b/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateSignature.svelte new file mode 100644 index 000000000..7f65c7677 --- /dev/null +++ b/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateSignature.svelte @@ -0,0 +1,30 @@ + + + + Signature Key +

    + Add the Signature Key to the X-Appwrite-Webhook-Signature header to validate your webhooks. Learn more about webhook validation. +

    + +
    +

    Key

    + +
    +
    + + + +
    + + diff --git a/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateURL.svelte b/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateURL.svelte new file mode 100644 index 000000000..ff775045d --- /dev/null +++ b/src/routes/console/project-[project]/settings/webhooks/[webhook]/updateURL.svelte @@ -0,0 +1,66 @@ + + +
    + + Update Url + + + + + + + + + + + +
    diff --git a/src/routes/console/project-[project]/storage/bucket-[bucket]/[[page]]/+page.svelte b/src/routes/console/project-[project]/storage/bucket-[bucket]/[[page]]/+page.svelte index 600677d32..0592ac359 100644 --- a/src/routes/console/project-[project]/storage/bucket-[bucket]/[[page]]/+page.svelte +++ b/src/routes/console/project-[project]/storage/bucket-[bucket]/[[page]]/+page.svelte @@ -162,11 +162,15 @@ { + showDropdown[index] = false; + }} href={`${base}/console/project-${projectId}/storage/bucket-${bucketId}/file-${file.$id}`} icon="pencil">Update { + showDropdown[index] = false; selectedFile = file; showDelete = true; }}>Delete diff --git a/src/routes/console/project-[project]/storage/bucket-[bucket]/create.svelte b/src/routes/console/project-[project]/storage/bucket-[bucket]/create.svelte index 5570b62f1..da6046ee3 100644 --- a/src/routes/console/project-[project]/storage/bucket-[bucket]/create.svelte +++ b/src/routes/console/project-[project]/storage/bucket-[bucket]/create.svelte @@ -2,7 +2,6 @@ import { Button, FormList, InputFile } from '$lib/elements/forms'; import { Pill } from '$lib/elements'; import { Modal, CustomId, Heading, Alert } from '$lib/components'; - import { sdkForProject } from '$lib/stores/sdk'; import { createEventDispatcher } from 'svelte'; import { page } from '$app/stores'; import { uploader } from '$lib/stores/uploader'; @@ -25,20 +24,15 @@ async function create() { try { - const file = await sdkForProject.storage.createFile( - bucketId, - id ?? 'unique()', - files[0], - permissions - ); - files = null; showCreate = false; + await uploader.uploadFile(bucketId, id, files[0], permissions); + files = null; showCustomId = false; - uploader.addFile(file); + // uploader.addFile(file); dispatch('created'); addNotification({ type: 'success', - message: `${file.name} has been created` + message: `File has been uploaded` }); trackEvent('submit_file_create'); } catch ({ message }) { @@ -74,8 +68,11 @@ Update Permissions

    Choose who can access your buckets and files. For more information, check out the - Permissions Guide in our documentation. + Permissions Guide in our documentation.

    {#if $bucket.fileSecurity}
    diff --git a/src/routes/console/project-[project]/storage/bucket-[bucket]/settings/+page.svelte b/src/routes/console/project-[project]/storage/bucket-[bucket]/settings/+page.svelte index 8e611b5fc..5d1aa0c48 100644 --- a/src/routes/console/project-[project]/storage/bucket-[bucket]/settings/+page.svelte +++ b/src/routes/console/project-[project]/storage/bucket-[bucket]/settings/+page.svelte @@ -24,6 +24,8 @@ import { Dependencies } from '$lib/constants'; import Delete from '../deleteBucket.svelte'; import { trackEvent } from '$lib/actions/analytics'; + import { writable } from 'svelte/store'; + import type { Models } from '@aw-labs/appwrite-console'; let showDelete = false; @@ -34,14 +36,21 @@ arePermsDisabled = true, encryption: boolean = null, antivirus: boolean = null, - maxSize: number; - let byteUnit: 'Bytes' | 'KB' | 'MB' | 'GB' = 'MB', - options = [ - { label: 'Bytes', value: 'Bytes' }, - { label: 'Kilobytes', value: 'KB' }, - { label: 'Megabytes', value: 'MB' }, - { label: 'Gigabytes', value: 'GB' } - ]; + maxSize: number, + compression: string = null; + let byteUnit = writable('MB'); + let sizeInBytes: number = null; + const options = [ + { label: 'Bytes', value: 'Bytes' }, + { label: 'Kilobytes', value: 'KB' }, + { label: 'Megabytes', value: 'MB' }, + { label: 'Gigabytes', value: 'GB' } + ]; + const compressionOptions = [ + { label: 'None', value: 'none' }, + { label: 'Gzip', value: 'gzip' }, + { label: 'Zstd', value: 'zstd' } + ]; let suggestedExtensions = ['jpg', 'png', 'svg', 'gif', 'html', 'pdf', 'mp4']; let extensions = $bucket.allowedFileExtensions; let isExtensionsDisabled = true; @@ -54,7 +63,10 @@ bucketPermissions ??= $bucket.$permissions; encryption ??= $bucket.encryption; antivirus ??= $bucket.antivirus; + maxSize = $bucket.maximumFileSize / 1024 / 1024; + compression ??= $bucket.compression; }); + $: if (bucketPermissions) { if (symmetricDifference(bucketPermissions, $bucket.$permissions).length) { arePermsDisabled = false; @@ -66,78 +78,44 @@ } else isExtensionsDisabled = true; } - async function toggleBucket() { + type TUpdateBucketMisc = { + successMessage?: string; + trackEventName: string; + trackEventData?: { value: boolean }; + arePermsDisabled?: boolean; + }; + + async function updateBucket(updates: Partial, misc: TUpdateBucketMisc) { + const values = { ...$bucket, ...updates }; + try { await sdkForProject.storage.updateBucket( - $bucket.$id, - $bucket.name, - undefined, - undefined, - enabled + values.$id, + values.name, + values.$permissions, + values.fileSecurity, + values.enabled, + values.maximumFileSize, + values.allowedFileExtensions, + values.compression, + values.encryption, + values.antivirus ); + invalidate(Dependencies.BUCKET); + + if (misc.arePermsDisabled !== undefined) { + arePermsDisabled = misc.arePermsDisabled; + } + addNotification({ - message: `${$bucket.name} has been updated`, + message: misc.successMessage ?? `${$bucket.name} has been updated`, type: 'success' }); - trackEvent('submit_bucket_enable', { - value: enabled - }); - } catch (error) { - addNotification({ - message: error.message, - type: 'error' - }); - } - } - async function updateName() { - try { - await sdkForProject.storage.updateBucket($bucket.$id, bucketName); - invalidate(Dependencies.BUCKET); - addNotification({ - message: 'Name has been updated', - type: 'success' - }); - trackEvent('submit_bucket_update_name'); - } catch (error) { - addNotification({ - message: error.message, - type: 'error' - }); - } - } - async function updatePermissions() { - try { - await sdkForProject.storage.updateBucket($bucket.$id, $bucket.name, bucketPermissions); - invalidate(Dependencies.BUCKET); - arePermsDisabled = true; - addNotification({ - message: 'Permissions have been updated', - type: 'success' - }); - trackEvent('submit_bucket_update_permissions'); - } catch (error) { - addNotification({ - message: error.message, - type: 'error' - }); - } - } - async function updateFileSecurity() { - try { - await sdkForProject.storage.updateBucket( - $bucket.$id, - $bucket.name, - $bucket.$permissions, - bucketFileSecurity - ); - invalidate(Dependencies.BUCKET); - arePermsDisabled = true; - addNotification({ - message: 'Security has been updated', - type: 'success' - }); - trackEvent('submit_bucket_update_file_security'); + + if (misc.trackEventName) { + trackEvent(misc.trackEventName, misc.trackEventData); + } } catch (error) { addNotification({ message: error.message, @@ -146,81 +124,118 @@ } } - async function updateSecurity() { - try { - await sdkForProject.storage.updateBucket( - $bucket.$id, - $bucket.name, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, + function toggleBucket() { + updateBucket( + { + enabled + }, + { + trackEventName: 'submit_bucket_enable', + trackEventData: { + value: enabled + } + } + ); + } + + function updateName() { + updateBucket( + { + name: bucketName + }, + { + successMessage: 'Name has been updated', + trackEventName: 'submit_bucket_update_name' + } + ); + } + + function updatePermissions() { + updateBucket( + { + $permissions: bucketPermissions + }, + { + successMessage: 'Permissions have been updated', + trackEventName: 'submit_bucket_update_permissions', + arePermsDisabled: true + } + ); + } + + function updateFileSecurity() { + updateBucket( + { + fileSecurity: bucketFileSecurity + }, + { + successMessage: 'Security has been updated', + trackEventName: 'submit_bucket_update_file_security', + arePermsDisabled: true + } + ); + } + + function updateSecurity() { + updateBucket( + { encryption, antivirus - ); - invalidate(Dependencies.BUCKET); - addNotification({ - message: `${$bucket.name} has been updated`, - type: 'success' - }); - trackEvent('submit_bucket_update_security'); - } catch (error) { - addNotification({ - message: error.message, - type: 'error' - }); - } - } - async function updateMaxSize() { - let size = sizeToBytes(maxSize, byteUnit); - try { - await sdkForProject.storage.updateBucket( - $bucket.$id, - $bucket.name, - undefined, - undefined, - undefined, - size - ); - invalidate(Dependencies.BUCKET); - addNotification({ - message: `${$bucket.name} has been updated`, - type: 'success' - }); - trackEvent('submit_bucket_update_size'); - } catch (error) { - addNotification({ - message: error.message, - type: 'error' - }); - } + }, + { + trackEventName: 'submit_bucket_update_security' + } + ); } - async function updateAllowedExtensions() { - try { - await sdkForProject.storage.updateBucket( - $bucket.$id, - $bucket.name, - undefined, - undefined, - undefined, - undefined, - extensions - ); - invalidate(Dependencies.BUCKET); - addNotification({ - message: `${$bucket.name} has been updated`, - type: 'success' - }); - trackEvent('submit_bucket_update_extensions'); - } catch (error) { - addNotification({ - message: error.message, - type: 'error' - }); + function updateMaxSize() { + const size = sizeToBytes(maxSize, $byteUnit); + updateBucket( + { + maximumFileSize: size + }, + { + trackEventName: 'submit_bucket_update_size' + } + ); + } + + function updateCompression() { + updateBucket( + { + compression + }, + { + trackEventName: 'submit_bucket_update_size' + } + ); + } + + function updateAllowedExtensions() { + updateBucket( + { + allowedFileExtensions: extensions + }, + { + trackEventName: 'submit_bucket_update_extensions' + } + ); + } + + byteUnit.subscribe((b) => { + if (b === 'Bytes') { + maxSize = sizeInBytes; + } else if (b === 'KB') { + maxSize = sizeInBytes / 1024; + } else if (b === 'MB') { + maxSize = sizeInBytes / 1024 / 1024; + } else if (b === 'GB') { + maxSize = sizeInBytes / 1024 / 1024 / 1024; } + }); + + $: if (maxSize) { + sizeInBytes = sizeToBytes(maxSize, $byteUnit); } @@ -238,8 +253,8 @@ id="toggle" bind:value={enabled} /> -

    Created: {toLocaleDateTime($bucket.$createdAt)}

    -

    Last Updated: {toLocaleDateTime($bucket.$updatedAt)}

    +

    Created: {toLocaleDateTime($bucket.$createdAt)}

    +

    Last Updated: {toLocaleDateTime($bucket.$updatedAt)}

    @@ -279,7 +294,8 @@ the Permissions Guide in our documentation. + rel="noopener noreferrer" + class="link">Permissions Guide in our documentation.

    {#if bucketPermissions} @@ -323,7 +339,7 @@
    Update Security Settings -

    +

    Enable or disable security services for the bucket including Ecryption and Antivirus scanning.

    @@ -384,7 +400,32 @@ + submit> + Update + + +
    +
    + +
    + + Update Compression Algorithm +

    + Choose an algorithm for compression. For files larger than 20MB, compression + will be skipped even if it's enabled. +

    + + + + + + + +
    @@ -392,20 +433,22 @@
    Update Maximum File Size -

    Set the maximum file size allowed in the bucket.

    +

    Set the maximum file size allowed in the bucket.

      - +
    - +
    @@ -413,7 +456,7 @@
    Update Allowed File Extensions -

    +

    A maximum of 100 file extensions can be added. Leave blank to allow all file types.

    @@ -452,7 +495,7 @@ Delete Bucket -

    +

    The bucket will be permanently deleted, including all the files within it. This action is irreversible.

    @@ -461,7 +504,7 @@
    {$bucket.name}
    -

    Last Updated: {toLocaleDateTime($bucket.$updatedAt)}

    +

    Last Updated: {toLocaleDateTime($bucket.$updatedAt)}

    diff --git a/static/icons/dark/color/cpp.svg b/static/icons/dark/color/cpp.svg new file mode 100644 index 000000000..e224115f3 --- /dev/null +++ b/static/icons/dark/color/cpp.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/static/icons/dark/color/dotnet.svg b/static/icons/dark/color/dotnet.svg new file mode 100644 index 000000000..8800d2d72 --- /dev/null +++ b/static/icons/dark/color/dotnet.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/dark/grayscale/cpp.svg b/static/icons/dark/grayscale/cpp.svg new file mode 100644 index 000000000..4824c7f47 --- /dev/null +++ b/static/icons/dark/grayscale/cpp.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/dark/grayscale/dotnet.svg b/static/icons/dark/grayscale/dotnet.svg new file mode 100644 index 000000000..3f8694d65 --- /dev/null +++ b/static/icons/dark/grayscale/dotnet.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/light/color/cpp.svg b/static/icons/light/color/cpp.svg new file mode 100644 index 000000000..a1251ac73 --- /dev/null +++ b/static/icons/light/color/cpp.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/static/icons/light/color/dotnet.svg b/static/icons/light/color/dotnet.svg new file mode 100644 index 000000000..8a366f68b --- /dev/null +++ b/static/icons/light/color/dotnet.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/light/grayscale/cpp.svg b/static/icons/light/grayscale/cpp.svg new file mode 100644 index 000000000..9879612ca --- /dev/null +++ b/static/icons/light/grayscale/cpp.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/light/grayscale/dotnet.svg b/static/icons/light/grayscale/dotnet.svg new file mode 100644 index 000000000..f49950efb --- /dev/null +++ b/static/icons/light/grayscale/dotnet.svg @@ -0,0 +1,3 @@ + + +