diff --git a/.env.example b/.env.example index 4c584201d..e490a6ef4 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -VITE_APPWRITE_ENDPOINT= +VITE_APPWRITE_ENDPOINT=http://localhost/v1 VITE_APPWRITE_GROWTH_ENDPOINT= VITE_GA_PROJECT= VITE_CONSOLE_MODE=self-hosted \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1f4ef8396..aaac70173 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,11 +22,11 @@ jobs: # run: npm audit --audit-level low - name: Install dependencies run: npm ci - - name: Build Console - run: npm run build - name: Svelte Diagnostics run: npm run check - name: Linter run: npm run lint - name: Unit Tests run: npm test + - name: Build Console + run: npm run build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a723620ce..60baa93de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,25 +10,30 @@ If you are worried about or don’t know where to start, check out the next sect ``` ├── src -│ ├── lib // All non-route components, accessible over "import ... from '$lib'" -│ │ ├── components // Re-usable components -│ │ ├── elements // Re-usable elements -│ │ ├── layout // Global components for the layout (Nav/Content/Container) -│ │ └── stores // Global stores (state management) -│ └─── routes -│ ├── console // Routes that need authentication -│ │ ├──[project] -│ │ │ ├── database // Database Service -│ │ │ │ ├── [collection] // Nested Route for the collection "/console/[PROJECT_ID]/database/[COLLECTION_ID]" -│ │ │ │ ├── _create.svelte // Component to Create collections -│ │ │ │ └── index.svelte // Entrypoint for "/console/[PROJECT_ID]/database" -│ │ │ ├── storage // Storage Service "/console/[PROJECT]/storage" -│ │ │ └── auth // Users Service "/console/[PROJECT]/auth" -│ │ └──... -│ ├── login.svelte // Component for Login "/console/login" -│ └── register.svelte // Component for Register "/console/register" -├── build // Compiled application -└── static // Static assets +│ ├── lib // Reusable logic (accessible with '$lib') +│ │ ├── actions // Svelte actions +│ │ ├── charts // Chart components +│ │ ├── components // Re-usable components +│ │ ├── elements // Re-usable elements +│ │ ├── helpers // Small functions used through out the console +│ │ ├── images // Images used in the console +│ │ ├── layout // Global components for the layout (Nav/Content/Container) +│ │ ├── mock // Mock components used for testing +│ │ └── stores // Global stores (state management) +│ └── routes +│ └── console // Routes that need authentication +│ │ └── project-[project] +│ │ │ └── database // Database Service +│ │ │ │ ├── +layout.svelte // Layout head and other logic like realtime events is set here +│ │ │ │ ├── +layout.ts // Layout data is set here (Header, Breadcrumbs, ...) +│ │ │ │ ├── +page.svelte // Page displayed on "/console/project-[PROJECT_ID]/database" +│ │ │ │ ├── +page.ts // Necessary data for the page is fetched here +│ │ │ │ └── create.svelte // Component to create databases +│ │ │ └── ... // Other services +│ │ └── ... +│ └── ... // Routes that don't need authentication +├── build // Compiled application +└── static // Static assets ``` ## Development @@ -41,11 +46,19 @@ git clone https://github.com/appwrite/console.git appwrite-console ### 2. Install dependencies with npm +Navigate to the Appwrite Console repository and install dependencies. + ```bash -npm install +cd appwrite-console && npm install ``` -### 3. Setup environment variables +### 3. Install and run Appwrite locally + +When you run the Appwrite Console locally, it needs to point to a backend as well. The easiest way to do this is to run an Appwrite instance locally. + +Follow the [install instructions](https://appwrite.io/docs/installation) in the Appwrite docs. + +### 4. Setup environment variables Add a `.env` file by copying the `.env.example` file as a template in the project's root directory. diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 84c178cc4..664d86d46 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -118,6 +118,7 @@ export enum Submit { UserCreate = 'submit_user_create', UserDelete = 'submit_user_delete', UserUpdateEmail = 'submit_user_update_email', + UserUpdateLabels = 'submit_user_update_labels', UserUpdateName = 'submit_user_update_name', UserUpdatePassword = 'submit_user_update_password', UserUpdatePhone = 'submit_user_update_phone', @@ -131,6 +132,7 @@ export enum Submit { ProjectCreate = 'submit_project_create', ProjectDelete = 'submit_project_delete', ProjectUpdateName = 'submit_project_update_name', + ProjectUpdateTeam = 'submit_project_update_team', ProjectService = 'submit_project_service', MemberCreate = 'submit_member_create', MemberDelete = 'submit_member_delete', diff --git a/src/lib/components/empty.svelte b/src/lib/components/empty.svelte index ab7d091a4..9dc0c6103 100644 --- a/src/lib/components/empty.svelte +++ b/src/lib/components/empty.svelte @@ -21,7 +21,8 @@ {#if single}
-
+
diff --git a/src/lib/components/feedback/feedbackGeneral.svelte b/src/lib/components/feedback/feedbackGeneral.svelte new file mode 100644 index 000000000..183921a56 --- /dev/null +++ b/src/lib/components/feedback/feedbackGeneral.svelte @@ -0,0 +1,19 @@ + + + + + + + diff --git a/src/lib/components/feedback/feedbackNPS.svelte b/src/lib/components/feedback/feedbackNPS.svelte new file mode 100644 index 000000000..613e10514 --- /dev/null +++ b/src/lib/components/feedback/feedbackNPS.svelte @@ -0,0 +1,30 @@ + + + + How likely are you to recommend Appwrite to a friend or colleague? + +{#if $feedbackData.value} + + + + + +{/if} diff --git a/src/lib/components/feedback/index.ts b/src/lib/components/feedback/index.ts new file mode 100644 index 000000000..15d522683 --- /dev/null +++ b/src/lib/components/feedback/index.ts @@ -0,0 +1,3 @@ +export { default as Feedback } from './feedback.svelte'; +export { default as FeedbackGeneral } from './feedbackGeneral.svelte'; +export { default as FeedbackNPS } from './feedbackNPS.svelte'; diff --git a/src/lib/components/feedbackNPS.svelte b/src/lib/components/feedbackNPS.svelte deleted file mode 100644 index a9276b4a7..000000000 --- a/src/lib/components/feedbackNPS.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - -
-
-

How are we doing?

- -
-
- At Appwrite, we value the feedback of our users. That means you! We'd love to hear what you - think. -
- - - - How likely are you to recommend Appwrite to a friend or colleague? - - {#if value} - - - - - - {/if} -
- - -
- -
diff --git a/src/lib/components/id.svelte b/src/lib/components/id.svelte index e73a5b452..1b618f3d2 100644 --- a/src/lib/components/id.svelte +++ b/src/lib/components/id.svelte @@ -31,7 +31,7 @@ } } } - checkOverflow(); + requestAnimationFrame(checkOverflow); window.addEventListener('resize', checkOverflow); return { @@ -46,22 +46,22 @@ } -
- - - -
- + +
+ + + +
- +
-
+ diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index f891b8255..f2cf6e0ee 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -31,9 +31,6 @@ export { default as Box } from './box.svelte'; export { default as Search } from './search.svelte'; export { default as SearchQuery } from './searchQuery.svelte'; export { default as GridItem1 } from './gridItem1.svelte'; -export { default as FeedbackGeneral } from './feedbackGeneral.svelte'; -export { default as FeedbackNPS } from './feedbackNPS.svelte'; -export { default as Evaluation } from './evaluation.svelte'; export { default as Steps } from './steps.svelte'; export { default as Step } from './step.svelte'; export { default as Code } from './code.svelte'; diff --git a/src/lib/components/permissions/team.svelte b/src/lib/components/permissions/team.svelte index 7b214f534..fdd4c5e9c 100644 --- a/src/lib/components/permissions/team.svelte +++ b/src/lib/components/permissions/team.svelte @@ -134,7 +134,7 @@ You have no teams. Create a team to see them here.

- Need a hand? Check out our diff --git a/src/lib/components/permissions/user.svelte b/src/lib/components/permissions/user.svelte index 4488d8f01..5835023b6 100644 --- a/src/lib/components/permissions/user.svelte +++ b/src/lib/components/permissions/user.svelte @@ -153,7 +153,7 @@ You have no users. Create a user to see them here.

- Need a hand? Check out our diff --git a/src/lib/components/tab.svelte b/src/lib/components/tab.svelte index 246d9db0d..f3825acbf 100644 --- a/src/lib/components/tab.svelte +++ b/src/lib/components/tab.svelte @@ -1,19 +1,95 @@

  • {#if href} - + {:else} @@ -22,7 +98,10 @@ class="tabs-button" class:is-selected={selected} on:click|preventDefault - on:click={track}> + on:click={handleClick} + tabindex={selected ? 0 : -1} + on:keydown={handleKeyDown} + role="tab"> {/if} diff --git a/src/lib/elements/forms/inputNumber.svelte b/src/lib/elements/forms/inputNumber.svelte index 7c36615e0..5110e4eea 100644 --- a/src/lib/elements/forms/inputNumber.svelte +++ b/src/lib/elements/forms/inputNumber.svelte @@ -84,7 +84,7 @@ bind:value bind:this={element} on:invalid={handleInvalid} - style:--amount-of-buttons={required ? 0 : 1.75} /> + style:--amount-of-buttons={nullable && !required ? 1.75 : 0} />
      {#if nullable && !required} diff --git a/src/lib/elements/forms/inputSelect.svelte b/src/lib/elements/forms/inputSelect.svelte index 0b3663529..fd37a958c 100644 --- a/src/lib/elements/forms/inputSelect.svelte +++ b/src/lib/elements/forms/inputSelect.svelte @@ -27,15 +27,17 @@ error = element.validationMessage; }; - $: if (element && required && !value) { - element.setCustomValidity('This field is required'); + const isNotEmpty = (value: string | number | boolean) => { + return typeof value === 'boolean' ? true : !!value; + }; + + $: if (required && !isNotEmpty(value)) { + element?.setCustomValidity('This field is required'); + } else { + element?.setCustomValidity(''); } - $: if (element && required && value) { - element.setCustomValidity(''); - } - - $: if (value) { + $: if (isNotEmpty(value)) { error = null; } diff --git a/src/lib/elements/forms/inputText.svelte b/src/lib/elements/forms/inputText.svelte index a7082a4b0..28123a30f 100644 --- a/src/lib/elements/forms/inputText.svelte +++ b/src/lib/elements/forms/inputText.svelte @@ -53,6 +53,9 @@ value = prevValue; } } + + $: showTextCounter = !!maxlength; + $: showNullCheckbox = nullable && !required; @@ -75,19 +78,21 @@ class:u-padding-inline-end-56={typeof maxlength === 'number'} bind:this={element} on:invalid={handleInvalid} /> -
        - {#if maxlength} -
      • - -
      • - {/if} - {#if nullable && !required} -
      • - -
      • - {/if} -
      + {#if showTextCounter || showNullCheckbox} +
        + {#if showTextCounter} +
      • + +
      • + {/if} + {#if showNullCheckbox} +
      • + +
      • + {/if} +
      + {/if}
  • {#if error} {error} diff --git a/src/lib/elements/forms/inputTextarea.svelte b/src/lib/elements/forms/inputTextarea.svelte index 7a9c121a6..a5a096b35 100644 --- a/src/lib/elements/forms/inputTextarea.svelte +++ b/src/lib/elements/forms/inputTextarea.svelte @@ -50,6 +50,9 @@ value = prevValue; } } + + $: showTextCounter = !!maxlength; + $: showNullCheckbox = nullable && !required; @@ -72,20 +75,22 @@ bind:this={element} on:invalid={handleInvalid} style:--amount-of-buttons={required ? undefined : 0.25} /> -
      - {#if maxlength} -
    • - -
    • - {/if} - {#if nullable && !required} -
    • - -
    • - {/if} -
    + {#if showTextCounter || showNullCheckbox} +
      + {#if showTextCounter} +
    • + +
    • + {/if} + {#if showNullCheckbox} +
    • + +
    • + {/if} +
    + {/if}
    {#if error} diff --git a/src/lib/elements/forms/inputURL.svelte b/src/lib/elements/forms/inputURL.svelte index 848bc5532..5c7a1178a 100644 --- a/src/lib/elements/forms/inputURL.svelte +++ b/src/lib/elements/forms/inputURL.svelte @@ -1,6 +1,8 @@ @@ -66,9 +74,21 @@ type="url" autocomplete={autocomplete ? 'on' : 'off'} bind:value - on:input={handleInput} bind:this={element} on:invalid={handleInvalid} /> +
      + {#if maxlength} +
    • + +
    • + {/if} + {#if nullable && !required} +
    • + +
    • + {/if} +
    {#if error} {error} diff --git a/src/lib/elements/table/tableScroll.svelte b/src/lib/elements/table/tableScroll.svelte index 814fe00e5..6d30f8aff 100644 --- a/src/lib/elements/table/tableScroll.svelte +++ b/src/lib/elements/table/tableScroll.svelte @@ -2,11 +2,28 @@ import type { Action } from 'svelte/action'; export let isSticky = false; + let isOverflowing = false; - const hasOverflow: Action void> = (node, callback) => { + const hasOverflow: Action = (node) => { const observer = new ResizeObserver((entries) => { for (const entry of entries) { - callback(entry.contentRect.width < entry.target.scrollWidth); + let overflowing = false; + if (entry.contentRect.width < entry.target.scrollWidth) { + overflowing = true; + } + + const cols = entry.target.querySelectorAll('.table-thead-col'); + for (let i = 0; i < cols.length; i++) { + const col = cols[i]; + const cs = getComputedStyle(col); + const innerWidth = + col.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight); + if (innerWidth < 32) { + overflowing = true; + } + } + + isOverflowing = overflowing; } }); @@ -18,12 +35,10 @@ } }; }; - - let isOverflowing = false;
    -
    (isOverflowing = v)}> +
    diff --git a/src/lib/helpers/copy.ts b/src/lib/helpers/copy.ts index 6399995ea..4c3779ffc 100644 --- a/src/lib/helpers/copy.ts +++ b/src/lib/helpers/copy.ts @@ -11,6 +11,12 @@ async function securedCopy(value: string) { function unsecuredCopy(value: string) { const textArea = document.createElement('textarea'); textArea.value = value; + + // Avoid scrolling to bottom + textArea.style.top = '0'; + textArea.style.left = '0'; + textArea.style.position = 'fixed'; + document.body.appendChild(textArea); textArea.focus(); textArea.select(); diff --git a/src/lib/helpers/date.ts b/src/lib/helpers/date.ts index 8558f315f..68f9c31a7 100644 --- a/src/lib/helpers/date.ts +++ b/src/lib/helpers/date.ts @@ -1,5 +1,10 @@ export const toLocaleDate = (datetime: string) => { const date = new Date(datetime); + + if (isNaN(date.getTime())) { + return 'n/a'; + } + const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', @@ -11,6 +16,11 @@ export const toLocaleDate = (datetime: string) => { export const toLocaleDateTime = (datetime: string | number) => { const date = new Date(datetime); + + if (isNaN(date.getTime())) { + return 'n/a'; + } + const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', diff --git a/src/lib/helpers/style.ts b/src/lib/helpers/style.ts new file mode 100644 index 000000000..fef15c8cc --- /dev/null +++ b/src/lib/helpers/style.ts @@ -0,0 +1,16 @@ +type Direction = 'rtl' | 'ltr'; + +function isDirection(dir: string): dir is Direction { + return dir === 'rtl' || dir === 'ltr'; +} + +function parseDirection(dir: string): Direction { + return isDirection(dir) ? dir : 'ltr'; +} + +export function getElementDir(el: HTMLElement): Direction { + if (window.getComputedStyle) { + return parseDirection(window.getComputedStyle(el, null).getPropertyValue('direction')); + } + return parseDirection(el.style.direction); +} diff --git a/src/lib/helpers/waitUntil.ts b/src/lib/helpers/waitUntil.ts new file mode 100644 index 000000000..c3b3a85e4 --- /dev/null +++ b/src/lib/helpers/waitUntil.ts @@ -0,0 +1,14 @@ +export async function waitUntil(condition: () => boolean, timeout = 1000) { + return new Promise((resolve, reject) => { + const start = Date.now(); + const interval = setInterval(() => { + if (condition()) { + clearInterval(interval); + resolve(undefined); + } else if (Date.now() - start > timeout) { + clearInterval(interval); + reject(new Error('Timeout')); + } + }, 10); + }); +} diff --git a/src/lib/images/feedback/feedback-dark.svg b/src/lib/images/feedback/feedback-dark.svg new file mode 100644 index 000000000..8f0b553d6 --- /dev/null +++ b/src/lib/images/feedback/feedback-dark.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lib/images/feedback/feedback-light.svg b/src/lib/images/feedback/feedback-light.svg new file mode 100644 index 000000000..d7ace402f --- /dev/null +++ b/src/lib/images/feedback/feedback-light.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lib/layout/header.svelte b/src/lib/layout/header.svelte index e9ec88e7f..099c1411f 100644 --- a/src/lib/layout/header.svelte +++ b/src/lib/layout/header.svelte @@ -1,26 +1,20 @@ @@ -122,6 +136,24 @@ Storage +
  • + trackEvent('click_menu_settings')} + class:is-selected={$page.url.pathname.startsWith( + `${projectPath}/settings` + )} + title="Settings" + use:tooltip={{ + content: 'Settings', + placement: 'right', + disabled: !narrow + }}> + +
  • @@ -129,7 +161,7 @@ {/if} diff --git a/src/lib/stores/app.ts b/src/lib/stores/app.ts index 11afe9d6a..7c4b2a184 100644 --- a/src/lib/stores/app.ts +++ b/src/lib/stores/app.ts @@ -1,5 +1,4 @@ import { browser } from '$app/environment'; -import { VARS } from '$lib/system'; import { writable } from 'svelte/store'; export type AppStore = { @@ -7,89 +6,11 @@ export type AppStore = { themeInUse: 'light' | 'dark'; }; -export type Feedback = { - elapsed: number; - visualized: number; - notification: boolean; - type: 'nps' | 'general'; - show: boolean; -}; - export const app = writable({ theme: 'auto', themeInUse: 'light' }); -function createFeedbackStore() { - const { subscribe, update } = writable({ - elapsed: browser ? parseInt(localStorage.getItem('feedbackElapsed')) : 0, - visualized: browser ? parseInt(localStorage.getItem('feedbackVisualized')) : 0, - notification: false, - type: 'general', - show: false - }); - return { - subscribe, - update, - toggleFeedback: () => { - update((feedback) => { - feedback.show = !feedback.show; - return feedback; - }); - }, - toggleNotification: () => - update((feedback) => { - feedback.notification = !feedback.notification; - return feedback; - }), - switchType: (feedType: Feedback['type']) => - update((feedback) => { - feedback.type = feedType; - return feedback; - }), - addVisualization: () => - update((feedback) => { - feedback.visualized += 1; - localStorage.setItem('feedbackVisualized', feedback.visualized.toString()); - return feedback; - }), - - increaseElapsed: (time: number) => { - update((feedback) => { - feedback.elapsed += time; - localStorage.setItem('feedbackElapsed', feedback.elapsed.toString()); - return feedback; - }); - }, - submitFeedback: async ( - subject: string, - message: string, - firstname?: string, - email?: string, - value?: number - ) => { - if (!VARS.GROWTH_ENDPOINT) return; - const response = await fetch(`${VARS.GROWTH_ENDPOINT}/feedback`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - subject, - message, - email, - firstname: firstname ? firstname : undefined, - customFields: value ? [{ id: '40655', value }] : undefined - }) - }); - if (response.status >= 400) { - throw new Error('Failed to submit feedback'); - } - } - }; -} -export const feedback = createFeedbackStore(); - if (browser) { app.update((n) => ({ ...n, diff --git a/src/lib/stores/feedback.ts b/src/lib/stores/feedback.ts new file mode 100644 index 000000000..d63e1d2ec --- /dev/null +++ b/src/lib/stores/feedback.ts @@ -0,0 +1,139 @@ +import { browser } from '$app/environment'; +import { VARS } from '$lib/system'; +import { writable } from 'svelte/store'; +import type { SvelteComponent } from 'svelte'; +import FeedbackGeneral from '$lib/components/feedback/feedbackGeneral.svelte'; +import FeedbackNps from '$lib/components/feedback/feedbackNPS.svelte'; + +export type Feedback = { + elapsed: number; + visualized: number; + notification: boolean; + type: 'nps' | 'general'; + show: boolean; +}; + +export type FeedbackData = { + message: string; + name?: string; + email?: string; + value?: number; +}; + +export type FeedbackOption = { + type: Feedback['type']; + title: string; + desc: string; + component: typeof SvelteComponent; +}; + +export const feedbackOptions: FeedbackOption[] = [ + { + type: 'general', + title: 'How can we improve?', + desc: 'Your feedback is important to us. Please be honest and tell us what you think.', + component: FeedbackGeneral + }, + { + type: 'nps', + title: 'How are we doing?', + desc: "At Appwrite, we value the feedback of our users. That means you! We'd love to hear what you think.", + component: FeedbackNps + } +]; + +export const selectedFeedback = writable(); + +function createFeedbackDataStore() { + const { subscribe, update } = writable({ + message: '', + name: '', + email: '', + value: 0 + }); + return { + subscribe, + update, + reset: () => { + update((feedbackData) => { + feedbackData.message = ''; + feedbackData.name = ''; + feedbackData.email = ''; + feedbackData.value = 0; + return feedbackData; + }); + } + }; +} + +export const feedbackData = createFeedbackDataStore(); + +function createFeedbackStore() { + const { subscribe, update } = writable({ + elapsed: browser ? parseInt(localStorage.getItem('feedbackElapsed')) : 0, + visualized: browser ? parseInt(localStorage.getItem('feedbackVisualized')) : 0, + notification: false, + type: 'general', + show: false + }); + return { + subscribe, + update, + toggleFeedback: () => { + update((feedback) => { + feedback.show = !feedback.show; + return feedback; + }); + }, + toggleNotification: () => + update((feedback) => { + feedback.notification = !feedback.notification; + return feedback; + }), + switchType: (feedType: Feedback['type']) => + update((feedback) => { + feedback.type = feedType; + return feedback; + }), + addVisualization: () => + update((feedback) => { + feedback.visualized += 1; + localStorage.setItem('feedbackVisualized', feedback.visualized.toString()); + return feedback; + }), + + increaseElapsed: (time: number) => { + update((feedback) => { + feedback.elapsed += time; + localStorage.setItem('feedbackElapsed', feedback.elapsed.toString()); + return feedback; + }); + }, + submitFeedback: async ( + subject: string, + message: string, + firstname?: string, + email?: string, + value?: number + ) => { + if (!VARS.GROWTH_ENDPOINT) return; + const response = await fetch(`${VARS.GROWTH_ENDPOINT}/feedback`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + subject, + message, + email, + firstname: firstname ? firstname : undefined, + customFields: value ? [{ id: '40655', value }] : undefined + }) + }); + if (response.status >= 400) { + throw new Error('Failed to submit feedback'); + } + } + }; +} +export const feedback = createFeedbackStore(); diff --git a/src/lib/stores/oauth-providers.ts b/src/lib/stores/oauth-providers.ts index 8047d6bdb..c43a597df 100644 --- a/src/lib/stores/oauth-providers.ts +++ b/src/lib/stores/oauth-providers.ts @@ -1,15 +1,18 @@ -import { writable } from 'svelte/store'; import type { Models } from '@appwrite.io/console'; import type { SvelteComponent } from 'svelte'; +import { writable } from 'svelte/store'; 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 Google from '../../routes/console/project-[project]/auth/googleOAuth.svelte'; import Main from '../../routes/console/project-[project]/auth/mainOAuth.svelte'; +import Microsoft from '../../routes/console/project-[project]/auth/microsoftOAuth.svelte'; +import Oidc from '../../routes/console/project-[project]/auth/oidcOAuth.svelte'; +import Okta from '../../routes/console/project-[project]/auth/oktaOAuth.svelte'; export type Provider = Models.Provider & { + key: string; icon: string; docs?: string; component?: typeof SvelteComponent; @@ -22,11 +25,12 @@ export type Providers = { const setProviders = (project: Models.Project): Provider[] => { return ( project?.providers.map((n) => { + const p = n as Models.Provider & { key: string }; let docs: Provider['docs']; - let icon: Provider['icon'] = n.name.toLowerCase(); + let icon: Provider['icon'] = p.key.toLowerCase(); let component: Provider['component'] = Main; - switch (n.name.toLowerCase()) { + switch (p.key.toLowerCase()) { case 'amazon': docs = 'https://developer.amazon.com/apps-and-games/services-and-apis'; break; @@ -81,6 +85,7 @@ const setProviders = (project: Models.Project): Provider[] => { break; case 'google': docs = 'https://support.google.com/googleapi/answer/6158849'; + component = Google; break; case 'linkedin': docs = 'https://developer.linkedin.com/'; @@ -92,6 +97,10 @@ const setProviders = (project: Models.Project): Provider[] => { case 'notion': docs = 'https://developers.notion.com/docs'; break; + case 'oidc': + docs = 'https://openid.net/connect/faq/'; + component = Oidc; + break; case 'okta': docs = 'https://developer.okta.com'; component = Okta; @@ -149,7 +158,7 @@ const setProviders = (project: Models.Project): Provider[] => { } return { - ...n, + ...p, icon, docs, component diff --git a/src/lib/stores/wizard.ts b/src/lib/stores/wizard.ts index 355eb7afc..0eccf0be0 100644 --- a/src/lib/stores/wizard.ts +++ b/src/lib/stores/wizard.ts @@ -1,11 +1,11 @@ import { trackEvent } from '$lib/actions/analytics'; -import type { SvelteComponent } from 'svelte'; +import type { ComponentType } from 'svelte'; import { writable } from 'svelte/store'; export type WizardStore = { show: boolean; media?: string; - component?: typeof SvelteComponent; + component?: ComponentType; interceptor?: () => Promise; }; @@ -20,10 +20,11 @@ function createWizardStore() { return { subscribe, set, - start: (component: typeof SvelteComponent, media: string = null) => + start: (component: ComponentType, media: string = null) => update((n) => { n.show = true; n.component = component; + n.interceptor = null; n.media = media; trackEvent('wizard_start'); return n; @@ -39,6 +40,7 @@ function createWizardStore() { update((n) => { n.show = false; n.component = null; + n.interceptor = null; n.media = null; return n; diff --git a/src/routes/auth/oauth2/failure/+page.svelte b/src/routes/auth/oauth2/failure/+page.svelte index 359c6ffdc..928ad82c4 100644 --- a/src/routes/auth/oauth2/failure/+page.svelte +++ b/src/routes/auth/oauth2/failure/+page.svelte @@ -2,20 +2,56 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { Heading } from '$lib/components'; - import { onMount } from 'svelte'; - onMount(async () => { - const project = $page.url.searchParams.get('project'); - if (project) { - await goto(`appwrite-callback-${project}://${$page.url.search}`); + const project = $page.url.searchParams.get('project'); + const link = `appwrite-callback-${project}://${$page.url.search}`; + + const redirect = new Promise((resolve, reject) => { + if (!project) { + reject('no-project'); } + // this timeout is needed because goto does not + // throw an exception if the redirect does not work + setTimeout(() => reject('timeout'), 500); + // goto will resolve on successful redirect + goto(link).then(resolve); }); -Missing Redirect URL -

    - Your OAuth login flow is missing a proper redirect URL. Please check the - OAuth docs - and send request for new session with a valid callback URL. -

    +{#await redirect then} +
    +
    + Login failed +

    You will be automatically redirected back to your app shortly.

    +

    + If you are not redirected, please click on the following + link. +

    +
    +
    +{:catch} +
    +
    + Missing Redirect URL +

    + Your OAuth login flow is missing a proper redirect URL. Please check the + OAuth docs + and send request for new session with a valid callback URL. +

    +
    +
    +{/await} + + diff --git a/src/routes/auth/oauth2/success/+page.svelte b/src/routes/auth/oauth2/success/+page.svelte index 359c6ffdc..b67ad0ce6 100644 --- a/src/routes/auth/oauth2/success/+page.svelte +++ b/src/routes/auth/oauth2/success/+page.svelte @@ -2,20 +2,56 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { Heading } from '$lib/components'; - import { onMount } from 'svelte'; - onMount(async () => { - const project = $page.url.searchParams.get('project'); - if (project) { - await goto(`appwrite-callback-${project}://${$page.url.search}`); + const project = $page.url.searchParams.get('project'); + const link = `appwrite-callback-${project}://${$page.url.search}`; + + const redirect = new Promise((resolve, reject) => { + if (!project) { + reject('no-project'); } + // this timeout is needed because goto does not + // throw an exception if the redirect does not work + setTimeout(() => reject('timeout'), 500); + // goto will resolve on successful redirect + goto(link).then(resolve); }); -Missing Redirect URL -

    - Your OAuth login flow is missing a proper redirect URL. Please check the - OAuth docs - and send request for new session with a valid callback URL. -

    +{#await redirect then} +
    +
    + You're now logged in +

    You will be automatically redirected back to your app shortly.

    +

    + If you are not redirected, please click on the following + link. +

    +
    +
    +{:catch} +
    +
    + Missing Redirect URL +

    + Your OAuth login flow is missing a proper redirect URL. Please check the + OAuth docs + and send request for new session with a valid callback URL. +

    +
    +
    +{/await} + + diff --git a/src/routes/console/+layout.svelte b/src/routes/console/+layout.svelte index 870794b40..38e74b324 100644 --- a/src/routes/console/+layout.svelte +++ b/src/routes/console/+layout.svelte @@ -7,7 +7,7 @@ import Header from '$lib/layout/header.svelte'; import SideNavigation from '$lib/layout/navigation.svelte'; import Shell from '$lib/layout/shell.svelte'; - import { feedback } from '$lib/stores/app'; + import { feedback } from '$lib/stores/feedback'; import { log } from '$lib/stores/logs'; import { newOrgModal } from '$lib/stores/organization'; import { wizard } from '$lib/stores/wizard'; @@ -15,6 +15,8 @@ import { loading } from '../store'; import Create from './createOrganization.svelte'; + let isOpen = false; + onMount(() => { loading.set(false); @@ -47,12 +49,13 @@
    - +
    diff --git a/src/routes/console/+layout.ts b/src/routes/console/+layout.ts index 65de11ecd..184409719 100644 --- a/src/routes/console/+layout.ts +++ b/src/routes/console/+layout.ts @@ -2,7 +2,12 @@ import { sdk } from '$lib/stores/sdk'; import type { LayoutLoad } from './$types'; export const load: LayoutLoad = async () => { - const response = await fetch(`${sdk.forConsole.client.config.endpoint}/health/version`); + const { endpoint, project } = sdk.forConsole.client.config; + const response = await fetch(`${endpoint}/health/version`, { + headers: { + 'X-Appwrite-Project': project + } + }); const data = await response.json(); return { diff --git a/src/routes/console/feedbackWizard.svelte b/src/routes/console/feedbackWizard.svelte new file mode 100644 index 000000000..fd55199c3 --- /dev/null +++ b/src/routes/console/feedbackWizard.svelte @@ -0,0 +1,25 @@ + + + diff --git a/src/routes/console/organization-[organization]/+page.svelte b/src/routes/console/organization-[organization]/+page.svelte index d039ac7d3..2b5d3759b 100644 --- a/src/routes/console/organization-[organization]/+page.svelte +++ b/src/routes/console/organization-[organization]/+page.svelte @@ -8,6 +8,8 @@ import CreateOrganization from '../createOrganization.svelte'; import type { PageData } from './$types'; import { page } from '$app/stores'; + import { services } from '$lib/stores/project-services'; + import type { Models } from '@appwrite.io/console'; export let data: PageData; @@ -38,6 +40,17 @@ return { name, icon }; }; + function allServiceDisabled(project: Models.Project): boolean { + let disabled = true; + services.load(project); + $services.list.forEach((service) => { + if (service.value) { + disabled = false; + } + }); + return disabled; + } + function filterPlatforms(platforms: { name: string; icon: string }[]) { return platforms.filter( (value, index, self) => index === self.findIndex((t) => t.name === value.name) @@ -67,6 +80,11 @@ {project.name} + {#if allServiceDisabled(project)} +

    +

    + {/if} {@const platforms = filterPlatforms( project.platforms.map((platform) => getPlatformInfo(platform.type)) )} diff --git a/src/routes/console/project-[project]/auth/+page.svelte b/src/routes/console/project-[project]/auth/+page.svelte index 0063f7ea1..2dee0dbfb 100644 --- a/src/routes/console/project-[project]/auth/+page.svelte +++ b/src/routes/console/project-[project]/auth/+page.svelte @@ -19,7 +19,7 @@ TableRowLink } from '$lib/elements/table'; import { Pill } from '$lib/elements'; - import { toLocaleDateTime } from '$lib/helpers/date'; + import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date'; import { Container } from '$lib/layout'; import { base } from '$app/paths'; import { goto } from '$app/navigation'; @@ -29,6 +29,13 @@ export let data: PageData; + // TODO: Remove this when the console SDK is updated + const users = data.users.users.map((user) => { + const labels: string[] = []; + const accessedAt = ''; + return { accessedAt, labels, ...user }; + }); + let showCreate = false; const projectId = $page.params.project; async function userCreated(event: CustomEvent>>) { @@ -49,10 +56,12 @@ Identifiers Status ID + Labels Joined + Last Activity - {#each data.users.users as user} + {#each users as user} @@ -102,9 +111,15 @@ + + {user.labels.join(', ')} + {toLocaleDateTime(user.registration)} + + {user.accessedAt ? toLocaleDate(user.accessedAt) : 'never'} + {/each} diff --git a/src/routes/console/project-[project]/auth/appleOAuth.svelte b/src/routes/console/project-[project]/auth/appleOAuth.svelte index 88b48040c..850ae6721 100644 --- a/src/routes/console/project-[project]/auth/appleOAuth.svelte +++ b/src/routes/console/project-[project]/auth/appleOAuth.svelte @@ -63,9 +63,7 @@

    URI

    + value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
    diff --git a/src/routes/console/project-[project]/auth/auth0OAuth.svelte b/src/routes/console/project-[project]/auth/auth0OAuth.svelte index b8e0fa35c..52e46a680 100644 --- a/src/routes/console/project-[project]/auth/auth0OAuth.svelte +++ b/src/routes/console/project-[project]/auth/auth0OAuth.svelte @@ -72,9 +72,7 @@

    URI

    + value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
    diff --git a/src/routes/console/project-[project]/auth/authentikOAuth.svelte b/src/routes/console/project-[project]/auth/authentikOAuth.svelte index 350fac230..e204be4e0 100644 --- a/src/routes/console/project-[project]/auth/authentikOAuth.svelte +++ b/src/routes/console/project-[project]/auth/authentikOAuth.svelte @@ -72,9 +72,7 @@

    URI

    + value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
    diff --git a/src/routes/console/project-[project]/auth/gitlabOAuth.svelte b/src/routes/console/project-[project]/auth/gitlabOAuth.svelte index 2b5aea8a6..510e4a5c6 100644 --- a/src/routes/console/project-[project]/auth/gitlabOAuth.svelte +++ b/src/routes/console/project-[project]/auth/gitlabOAuth.svelte @@ -70,9 +70,7 @@

    URI

    + value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
    @@ -81,7 +79,7 @@ disabled={(secret === provider.secret && enabled === provider.enabled && appId === provider.appId) || - !(appId && clientSecret && endpoint)} + !(appId && clientSecret)} submit>Update diff --git a/src/routes/console/project-[project]/auth/googleOAuth.svelte b/src/routes/console/project-[project]/auth/googleOAuth.svelte new file mode 100644 index 000000000..c8fd0390b --- /dev/null +++ b/src/routes/console/project-[project]/auth/googleOAuth.svelte @@ -0,0 +1,82 @@ + + + + {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 the setup, create an OAuth2 client ID with "Web application" as the + application type, then add this redirect URI to your {provider.name} configuration. + +
    +

    URI

    + +
    +
    + + + + +
    diff --git a/src/routes/console/project-[project]/auth/mainOAuth.svelte b/src/routes/console/project-[project]/auth/mainOAuth.svelte index 684e8ff81..36d0aa89f 100644 --- a/src/routes/console/project-[project]/auth/mainOAuth.svelte +++ b/src/routes/console/project-[project]/auth/mainOAuth.svelte @@ -63,9 +63,7 @@

    URI

    + value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
    diff --git a/src/routes/console/project-[project]/auth/microsoftOAuth.svelte b/src/routes/console/project-[project]/auth/microsoftOAuth.svelte index e404fec84..10925962a 100644 --- a/src/routes/console/project-[project]/auth/microsoftOAuth.svelte +++ b/src/routes/console/project-[project]/auth/microsoftOAuth.svelte @@ -72,9 +72,7 @@

    URI

    + value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
    diff --git a/src/routes/console/project-[project]/auth/oidcOAuth.svelte b/src/routes/console/project-[project]/auth/oidcOAuth.svelte new file mode 100644 index 000000000..6fade4f7f --- /dev/null +++ b/src/routes/console/project-[project]/auth/oidcOAuth.svelte @@ -0,0 +1,125 @@ + + + + {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/oktaOAuth.svelte b/src/routes/console/project-[project]/auth/oktaOAuth.svelte index 98f607df1..548a99576 100644 --- a/src/routes/console/project-[project]/auth/oktaOAuth.svelte +++ b/src/routes/console/project-[project]/auth/oktaOAuth.svelte @@ -82,9 +82,7 @@

    URI

    + value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
    diff --git a/src/routes/console/project-[project]/auth/settings/+page.svelte b/src/routes/console/project-[project]/auth/settings/+page.svelte index eba59cea4..afacc54eb 100644 --- a/src/routes/console/project-[project]/auth/settings/+page.svelte +++ b/src/routes/console/project-[project]/auth/settings/+page.svelte @@ -1,17 +1,17 @@ @@ -42,6 +46,7 @@ ? [$user.email, $user.phone].join(',') : $user.email || $user.phone}

    +

    Last activity: {accessedAt ? toLocaleDate(accessedAt) : 'never'}

    diff --git a/src/routes/console/project-[project]/auth/user-[user]/deleteSession.svelte b/src/routes/console/project-[project]/auth/user-[user]/deleteSession.svelte index 29a8a09dc..d30ccd96d 100644 --- a/src/routes/console/project-[project]/auth/user-[user]/deleteSession.svelte +++ b/src/routes/console/project-[project]/auth/user-[user]/deleteSession.svelte @@ -15,6 +15,7 @@ try { await sdk.forProject.users.deleteSession($page.params.user, selectedSessionId); await invalidate(Dependencies.SESSIONS); + showDelete = false; addNotification({ type: 'success', message: 'Session has been deleted' diff --git a/src/routes/console/project-[project]/auth/user-[user]/sessions/+page.svelte b/src/routes/console/project-[project]/auth/user-[user]/sessions/+page.svelte index 29aef6a74..eaa1e1cc2 100644 --- a/src/routes/console/project-[project]/auth/user-[user]/sessions/+page.svelte +++ b/src/routes/console/project-[project]/auth/user-[user]/sessions/+page.svelte @@ -1,16 +1,16 @@ + +
    + + Labels +

    + Categorize and manage your users based on specific criteria by assigning them + customizable labels. New label-based roles will be assigned. +

    + +
      + +
    • + {error ? error : 'Only alphanumeric characters are allowed'} +
    • +
    • + {#each suggestedLabels as suggestedLabel} + { + if (!labels.includes(suggestedLabel)) { + labels = [...labels, suggestedLabel]; + } else { + labels = labels.filter((e) => e !== suggestedLabel); + } + }}> + + {/each} +
    • +
    +
    + + + + +
    +
    diff --git a/src/routes/console/project-[project]/auth/user-[user]/updatePassword.svelte b/src/routes/console/project-[project]/auth/user-[user]/updatePassword.svelte index 044408912..0045cb093 100644 --- a/src/routes/console/project-[project]/auth/user-[user]/updatePassword.svelte +++ b/src/routes/console/project-[project]/auth/user-[user]/updatePassword.svelte @@ -33,9 +33,7 @@ Password
    -

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

    +

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

      Phone +

      + Update user's phone. A phone number must contain leading '+' and maximum of 15 digits. +

        - 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. -

        + Preferences +

        Add custom user preferences to share them across devices and sessions.

          diff --git a/src/routes/console/project-[project]/auth/user-[user]/updateStatus.svelte b/src/routes/console/project-[project]/auth/user-[user]/updateStatus.svelte index 34f4c81b0..80fe968c8 100644 --- a/src/routes/console/project-[project]/auth/user-[user]/updateStatus.svelte +++ b/src/routes/console/project-[project]/auth/user-[user]/updateStatus.svelte @@ -5,7 +5,7 @@ import { Dependencies } from '$lib/constants'; import { Pill } from '$lib/elements'; import { Button } from '$lib/elements/forms'; - import { toLocaleDateTime } from '$lib/helpers/date'; + import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date'; import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; import { user } from './store'; @@ -19,7 +19,7 @@ await invalidate(Dependencies.USER); addNotification({ message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${ - $user.emailVerification ? 'unverified' : 'verified' + !$user.emailVerification ? 'unverified' : 'verified' }`, type: 'success' }); @@ -71,6 +71,9 @@ trackError(error, Submit.UserUpdateStatus); } } + + // TODO: Remove this when the console SDK is updated + $: accessedAt = ($user as unknown as { accessedAt: string }).accessedAt; @@ -100,6 +103,7 @@

          {$user.phone}

          {/if}

          Joined: {toLocaleDateTime($user.registration)}

          +

          Last activity: {accessedAt ? toLocaleDate(accessedAt) : 'never'}

    {#if !$user.status} blocked diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/+page.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/+page.svelte index 232e0be6d..d99f0bbf7 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/+page.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/+page.svelte @@ -75,7 +75,7 @@
    Create your first attribute to get started.

    - Need a hand? Check out our documentation. + Need a hand? Learn more in our documentation.

    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 68b72fb5c..75a629bc9 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 @@ -84,7 +84,7 @@ aria-hidden="true" /> {/if}
    - {attribute.key} + {attribute.key} {#if attribute.status !== 'available'} Create your first attribute to get started.

    - Need a hand? Check out our documentation. + Need a hand? Learn more in our documentation.

    diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/boolean.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/boolean.svelte index 7dea3b464..ffa3e61f4 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/boolean.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/boolean.svelte @@ -34,8 +34,7 @@ - + placeholder="Select a value" + disabled={data.required || data.array} + options={[ + { label: 'NULL', value: null }, + { label: 'True', value: true }, + { label: 'False', value: false } + ]} + bind:value={data.default} /> Indicate whether this is a required attribute diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/datetime.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/datetime.svelte index 4315f1319..21886dc1f 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/datetime.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/datetime.svelte @@ -48,7 +48,7 @@ id="default" label="Default value" bind:value={data.default} - disabled={data.required} /> + disabled={data.required || data.array} /> Indicate whether this is a required attribute diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/edit.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/edit.svelte index 72bb2eaf7..e4ea11990 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/edit.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/edit.svelte @@ -71,31 +71,19 @@ {#if selectedAttribute?.type !== 'relationship'} -
    - - -
    -
    -
    + {/if} {#if option} (option = null)} /> {/if}
    diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/email.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/email.svelte index 6da9089f5..3222edaf6 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/email.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/email.svelte @@ -50,7 +50,8 @@ label="Default value" placeholder="Enter value" bind:value={data.default} - disabled={data.required} /> + disabled={data.required || data.array} + nullable={!data.required && !data.array} /> Indicate whether this is a required attribute diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/enum.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/enum.svelte index a8e166a2a..5996689ea 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/enum.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/enum.svelte @@ -36,8 +36,7 @@ - - Indicate whether this is a required attribute diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/float.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/float.svelte index 9e07b3e87..b641880d0 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/float.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/float.svelte @@ -54,9 +54,20 @@ } - - - + + Indicate whether this is a required attribute diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/integer.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/integer.svelte index d68ef0976..450fbac14 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/integer.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/integer.svelte @@ -54,9 +54,18 @@ } - - - + + + disabled={data.required || data.array} + nullable={!data.required && !data.array} /> Indicate whether this is a required attribute diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/ip.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/ip.svelte index b59473958..3bba15ad6 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/ip.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/ip.svelte @@ -48,7 +48,8 @@ label="Default value" placeholder="Enter value" bind:value={data.default} - disabled={data.required || data.array} /> + disabled={data.required || data.array} + nullable={!data.required && !data.array} /> Indicate whether this is a required attribute diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/relationship.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/relationship.svelte index f847e0ebd..501ee1dc5 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/relationship.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/relationship.svelte @@ -98,7 +98,6 @@ }); // Reactive statements - $: getCollections(search).then((res) => (collectionList = res)); $: collections = collectionList?.collections?.filter((n) => n.$id !== $collection.$id) ?? []; @@ -219,7 +218,6 @@ placeholder="Select a relation" options={relationshipType} disabled={editing} /> -
    diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/string.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/string.svelte index bbf9175bd..a52135d8d 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/string.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/attributes/string.svelte @@ -34,8 +34,7 @@ + bind:value /> diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/enum.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/enum.svelte index 7a5103da8..9f88aaa57 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/enum.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/enum.svelte @@ -7,7 +7,6 @@ export let value: string; export let attribute: Models.AttributeEnum; export let optionalText: string | undefined = undefined; - export let disabled = false; $: options = [ ...attribute.elements.map((element) => { @@ -31,5 +30,4 @@ {optionalText} required={attribute.required} placeholder="Select a value" - showLabel={!!label?.length} - {disabled} /> + showLabel={!!label?.length} /> diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/integer.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/integer.svelte index d490f0e3d..22360ff6f 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/integer.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/integer.svelte @@ -17,5 +17,5 @@ required={attribute.required} min={attribute.min} max={attribute.max} - bind:value - step={attribute.type === 'double' ? 'any' : 1} /> + step={attribute.type === 'double' ? 'any' : 1} + bind:value /> diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/relationship.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/relationship.svelte index c12654564..f01e54ca5 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/relationship.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/relationship.svelte @@ -3,12 +3,12 @@ import { PaginationInline } from '$lib/components'; import { SelectSearchItem } from '$lib/elements'; import { Button, InputSelectSearch, Label } from '$lib/elements/forms'; + import { preferences } from '$lib/stores/preferences'; import { sdk } from '$lib/stores/sdk'; import { Query, type Models } from '@appwrite.io/console'; import { onMount } from 'svelte'; import { doc } from '../store'; import { isRelationshipToMany } from './store'; - import { preferences } from '$lib/stores/preferences'; export let id: string; export let label: string; @@ -32,7 +32,7 @@ onMount(async () => { if (value) { if (isRelationshipToMany(attribute)) { - relatedList = value as string[]; + relatedList = (value as string[]).slice(); } else { singleRel = value as string; } diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/string.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/string.svelte index ecb1c613e..d5c32fa59 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/string.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/string.svelte @@ -7,30 +7,27 @@ export let value: string; export let attribute: Models.AttributeString; export let optionalText: string | undefined = undefined; - export let disabled = false; {#if attribute.size >= 50} {:else} {/if} diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/url.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/url.svelte index cbff896fa..b745348d9 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/url.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/document-[document]/attributes/url.svelte @@ -15,5 +15,6 @@ {optionalText} placeholder="Enter URL" showLabel={!!label?.length} + nullable={!attribute.required} required={attribute.required} bind:value /> diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/indexes/+page.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/indexes/+page.svelte index 605450998..81cc43c89 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/indexes/+page.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/indexes/+page.svelte @@ -125,7 +125,7 @@
    Create your first attribute to get started.

    - Need a hand? Check out our documentation. + Need a hand? Learn more in our documentation.

    diff --git a/src/routes/console/project-[project]/databases/database-[database]/table.svelte b/src/routes/console/project-[project]/databases/database-[database]/table.svelte index 317956ae7..458baaa62 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/table.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/table.svelte @@ -3,13 +3,13 @@ import { page } from '$app/stores'; import { Id } from '$lib/components'; import { - Table, TableBody, TableCell, TableCellHead, TableCellText, TableHeader, - TableRowLink + TableRowLink, + TableScroll } from '$lib/elements/table'; import { toLocaleDateTime } from '$lib/helpers/date'; import type { PageData } from './$types'; @@ -20,7 +20,7 @@ const databaseId = $page.params.database; - + {#each $columns as column} {#if column.show} @@ -54,4 +54,4 @@ {/each} -
    + diff --git a/src/routes/console/project-[project]/databases/table.svelte b/src/routes/console/project-[project]/databases/table.svelte index 401364401..ac63aa184 100644 --- a/src/routes/console/project-[project]/databases/table.svelte +++ b/src/routes/console/project-[project]/databases/table.svelte @@ -3,13 +3,13 @@ import { page } from '$app/stores'; import { Id } from '$lib/components'; import { - Table, TableBody, TableCell, TableCellHead, TableCellText, TableHeader, - TableRowLink + TableRowLink, + TableScroll } from '$lib/elements/table'; import { toLocaleDateTime } from '$lib/helpers/date'; import type { PageData } from './$types'; @@ -19,7 +19,7 @@ const projectId = $page.params.project; - + {#each $columns as column} {#if column.show} @@ -55,4 +55,4 @@ {/each} -
    + diff --git a/src/routes/console/project-[project]/functions/function-[function]/+page.svelte b/src/routes/console/project-[project]/functions/function-[function]/+page.svelte index 4bf39c827..119a813a4 100644 --- a/src/routes/console/project-[project]/functions/function-[function]/+page.svelte +++ b/src/routes/console/project-[project]/functions/function-[function]/+page.svelte @@ -32,6 +32,7 @@ import type { PageData } from './$types'; import Delete from './delete.svelte'; import Create from './create.svelte'; + import Rebuild from './rebuild.svelte'; import Activate from './activate.svelte'; import { browser } from '$app/environment'; import { sdk } from '$lib/stores/sdk'; @@ -44,6 +45,7 @@ let showDropdown = []; let showDelete = false; let showActivate = false; + let showRebuild = false; let selectedDeployment: Models.Deployment = null; @@ -51,6 +53,10 @@ invalidate(Dependencies.DEPLOYMENTS); } + function handleRebuild() { + invalidate(Dependencies.DEPLOYMENTS); + } + $: activeDeployment = data.deployments.deployments.find((d) => d.$id === $func?.deployment); if (browser) { @@ -215,6 +221,17 @@ }}> Activate + {#if 'failed' === deployment.status} + { + selectedDeployment = deployment; + showRebuild = true; + showDropdown = []; + }}> + Retry Build + + {/if} { @@ -281,4 +298,5 @@ {#if selectedDeployment} + {/if} 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 806ff23ca..4d65e044b 100644 --- a/src/routes/console/project-[project]/functions/function-[function]/create.svelte +++ b/src/routes/console/project-[project]/functions/function-[function]/create.svelte @@ -62,10 +62,10 @@ function setCodeSnippets(lang: string) { return { Unix: { - code: `appwrite functions createDeployment \\ - --functionId=${functionId} \\ - --entrypoint='index.${lang}' \\ - --code="." \\ + code: `appwrite functions createDeployment \\ + --functionId=${functionId} \\ + --entrypoint='index.${lang}' \\ + --code="." \\ --activate=true`, language: 'bash' }, diff --git a/src/routes/console/project-[project]/functions/function-[function]/executions/+page.svelte b/src/routes/console/project-[project]/functions/function-[function]/executions/+page.svelte index fb36561ff..1e783436d 100644 --- a/src/routes/console/project-[project]/functions/function-[function]/executions/+page.svelte +++ b/src/routes/console/project-[project]/functions/function-[function]/executions/+page.svelte @@ -110,7 +110,7 @@

    You have no execution logs. Create and activate a deployment to see it here.

    -

    Need a hand? Check out our documentation

    +

    Need a hand? Learn more in our documentation

    + + + 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 94aadbc6c..85f8265db 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 @@ -1,520 +1,25 @@ - -
    -
    - technology -
    -
    - {$func.name} - -

    {$func.runtime}

    -
    -
    - -
    -
    -

    Function ID: {$func.$id}

    -

    Created at: {toLocaleDateTime($func.$createdAt)}

    -

    Updated at: {toLocaleDateTime($func.$updatedAt)}

    -
    -
    -
    - - - - -
    - - - - Name - - -
      - -
    -
    - - - - -
    - - -
    - - Execute Access -

    - Choose who can execute this function using the client API. For more information, - check out the - Permissions Guide - . -

    - - - - - - - -
    -
    - + + + - -
    - - Schedule -

    - Set a Cron schedule to trigger your function. Leave blank for no schedule. - More details on Cron syntax here. -

    - - - - - - - - - -
    -
    - - - Variables -

    Set the variables (or secret keys) that will be passed to your function at runtime.

    - -
    - - -
    - {@const limit = 10} - {@const sum = data.variables.total} - {#if sum} -
    - - - Key - Value - - - - {#each data.variables.variables.slice(offset, offset + limit) as variable, i} - - - - {variable.key} - - - - - - - - - - - { - selectedVar = variable; - showVariablesDropdown[i] = false; - showVariablesModal = true; - }}> - Edit - - { - handleVariableDeleted(variable); - showVariablesDropdown[i] = false; - }}> - Delete - - - - - - {/each} - -
    - -
    -

    Total variables: {sum}

    - -
    -
    - {:else} - (showVariablesModal = !showVariablesModal)}> - Create a variable to get started - - {/if} -
    -
    - -
    - - Timeout -

    - Limit the execution time of your function. Maximum value is 900 seconds (15 - minutes). -

    - - - - - - - - - -
    -
    - - - Delete Function -

    - The function will be permanently deleted, including all deployments associated with it. - This action is irreversible. -

    - - - -
    {$func.name}
    -
    -

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

    -
    -
    - - - - -
    + + + +
    - - -{#if showVariablesModal} - -{/if} -{#if showVariablesUpload} - -{/if} diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/dangerZone.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/dangerZone.svelte new file mode 100644 index 000000000..d5ec9fbf4 --- /dev/null +++ b/src/routes/console/project-[project]/functions/function-[function]/settings/dangerZone.svelte @@ -0,0 +1,33 @@ + + + + Delete Function +

    + The function will be permanently deleted, including all deployments associated with it. This + action is irreversible. +

    + + + +
    {$func.name}
    +
    +

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

    +
    +
    + + + + +
    + + diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/delete.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/deleteModal.svelte similarity index 100% rename from src/routes/console/project-[project]/functions/function-[function]/settings/delete.svelte rename to src/routes/console/project-[project]/functions/function-[function]/settings/deleteModal.svelte diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/executeFunction.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/executeFunction.svelte new file mode 100644 index 000000000..194b18bed --- /dev/null +++ b/src/routes/console/project-[project]/functions/function-[function]/settings/executeFunction.svelte @@ -0,0 +1,36 @@ + + + +
    +
    + technology +
    +
    + {$func.name} + +

    {$func.runtime}

    +
    +
    + +
    +
    +

    Function ID: {$func.$id}

    +

    Created at: {toLocaleDateTime($func.$createdAt)}

    +

    Updated at: {toLocaleDateTime($func.$updatedAt)}

    +
    +
    +
    + + + + +
    diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/updateName.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/updateName.svelte new file mode 100644 index 000000000..431d12e5f --- /dev/null +++ b/src/routes/console/project-[project]/functions/function-[function]/settings/updateName.svelte @@ -0,0 +1,66 @@ + + +
    + + Name + + +
      + +
    +
    + + + + +
    +
    diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/updatePermissions.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/updatePermissions.svelte new file mode 100644 index 000000000..369f936a9 --- /dev/null +++ b/src/routes/console/project-[project]/functions/function-[function]/settings/updatePermissions.svelte @@ -0,0 +1,78 @@ + + +
    + + Execute Access +

    + Choose who can execute this function using the client API. For more information, check + out the + Permissions Guide + . +

    + + + + + + + +
    +
    diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/updateSchedule.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/updateSchedule.svelte new file mode 100644 index 000000000..699b727b1 --- /dev/null +++ b/src/routes/console/project-[project]/functions/function-[function]/settings/updateSchedule.svelte @@ -0,0 +1,71 @@ + + +
    + + Schedule +

    + Set a Cron schedule to trigger your function. Leave blank for no schedule. + More details on Cron syntax here. +

    + + + + + + + + + +
    +
    diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/updateTimeout.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/updateTimeout.svelte new file mode 100644 index 000000000..94489f929 --- /dev/null +++ b/src/routes/console/project-[project]/functions/function-[function]/settings/updateTimeout.svelte @@ -0,0 +1,66 @@ + + +
    + + Timeout +

    Limit the execution time of your function. Maximum value is 900 seconds (15 minutes).

    + + + + + + + + + +
    +
    diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/updateVariables.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/updateVariables.svelte new file mode 100644 index 000000000..c3915babc --- /dev/null +++ b/src/routes/console/project-[project]/functions/function-[function]/settings/updateVariables.svelte @@ -0,0 +1,233 @@ + + + + Variables +

    Set the variables (or secret keys) that will be passed to your function at runtime.

    + +
    + + +
    + {@const limit = 10} + {@const sum = variableList.total} + {#if sum} +
    + + + Key + Value + + + + {#each variableList.variables.slice(offset, offset + limit) as variable, i} + + + + {variable.key} + + + + + + + + + + + { + selectedVar = variable; + showVariablesDropdown[i] = false; + showVariablesModal = true; + }}> + Edit + + { + handleVariableDeleted(variable); + showVariablesDropdown[i] = false; + }}> + Delete + + + + + + {/each} + +
    + +
    +

    Total variables: {sum}

    + +
    +
    + {:else} + (showVariablesModal = !showVariablesModal)}> + Create a variable to get started + + {/if} +
    +
    + +{#if showVariablesModal} + +{/if} +{#if showVariablesUpload} + +{/if} diff --git a/src/routes/console/project-[project]/functions/function-[function]/settings/uploadVariables.svelte b/src/routes/console/project-[project]/functions/function-[function]/settings/uploadVariablesModal.svelte similarity index 100% rename from src/routes/console/project-[project]/functions/function-[function]/settings/uploadVariables.svelte rename to src/routes/console/project-[project]/functions/function-[function]/settings/uploadVariablesModal.svelte diff --git a/src/routes/console/project-[project]/overview/keys/+page.svelte b/src/routes/console/project-[project]/overview/keys/+page.svelte index 714ccea79..92cc16287 100644 --- a/src/routes/console/project-[project]/overview/keys/+page.svelte +++ b/src/routes/console/project-[project]/overview/keys/+page.svelte @@ -9,7 +9,7 @@ TableHeader, TableRowLink } from '$lib/elements/table'; - import { toLocaleDateTime } from '$lib/helpers/date'; + import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date'; import { wizard } from '$lib/stores/wizard'; import type { PageData } from './$types'; import Wizard from './wizard.svelte'; @@ -46,7 +46,7 @@ {key.name} - {key.accessedAt ? toLocaleDateTime(key.accessedAt) : 'never'} + {key.accessedAt ? toLocaleDate(key.accessedAt) : 'never'} {key.expire ? toLocaleDateTime(key.expire) : 'never'} 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 e82edaeb4..62d563158 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 @@ -5,7 +5,7 @@ import { Dependencies } from '$lib/constants'; import { Button, Form, FormList, InputText } from '$lib/elements/forms'; import { symmetricDifference } from '$lib/helpers/array'; - import { toLocaleDateTime } from '$lib/helpers/date'; + import { toLocaleDate } from '$lib/helpers/date'; import { Container } from '$lib/layout'; import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; @@ -83,7 +83,7 @@ - {@const accessedAt = $key.accessedAt ? toLocaleDateTime($key.accessedAt) : 'never'} + {@const accessedAt = $key.accessedAt ? toLocaleDate($key.accessedAt) : 'never'}
    {$key.name} diff --git a/src/routes/console/project-[project]/overview/platforms/+page.svelte b/src/routes/console/project-[project]/overview/platforms/+page.svelte index 328e7d88d..5c2332b05 100644 --- a/src/routes/console/project-[project]/overview/platforms/+page.svelte +++ b/src/routes/console/project-[project]/overview/platforms/+page.svelte @@ -142,7 +142,7 @@
    Create your first platform to get started.

    - Need a hand? Check out our documentation. + Need a hand? Learn more in our documentation.

    diff --git a/src/routes/console/project-[project]/overview/platforms/wizard/apple/step3.svelte b/src/routes/console/project-[project]/overview/platforms/wizard/apple/step3.svelte index fc5b28077..d0394946f 100644 --- a/src/routes/console/project-[project]/overview/platforms/wizard/apple/step3.svelte +++ b/src/routes/console/project-[project]/overview/platforms/wizard/apple/step3.svelte @@ -10,7 +10,7 @@ let client = Client() .setEndpoint("${endpoint}") .setProject("${project}") - .setSelfSigned(status: true) // For self signed certificates, only use for development`; + .setSelfSigned(true) // For self signed certificates, only use for development`; let showAlert = true; diff --git a/src/routes/console/project-[project]/overview/platforms/wizard/flutter/step1.svelte b/src/routes/console/project-[project]/overview/platforms/wizard/flutter/step1.svelte index ca0c62f7e..f8e2936ff 100644 --- a/src/routes/console/project-[project]/overview/platforms/wizard/flutter/step1.svelte +++ b/src/routes/console/project-[project]/overview/platforms/wizard/flutter/step1.svelte @@ -65,7 +65,7 @@ }, [Platform.Web]: { name: 'My Web App', - hostname: 'com.company.appname', + hostname: 'localhost', tooltip: 'The hostname that your website will use to interact with the Appwrite APIs in production or development environments. No protocol or port number required.' }, diff --git a/src/routes/console/project-[project]/overview/platforms/wizard/flutter/step2.svelte b/src/routes/console/project-[project]/overview/platforms/wizard/flutter/step2.svelte index 7d7dc3551..a47e93a8d 100644 --- a/src/routes/console/project-[project]/overview/platforms/wizard/flutter/step2.svelte +++ b/src/routes/console/project-[project]/overview/platforms/wizard/flutter/step2.svelte @@ -5,7 +5,7 @@ const example1 = `dependencies: appwrite: ^${$versions['client-flutter']}`; - const example2 = `pub get appwrite`; + const example2 = `flutter pub add appwrite`; diff --git a/src/routes/console/project-[project]/overview/platforms/wizard/store.ts b/src/routes/console/project-[project]/overview/platforms/wizard/store.ts index 8a00d0d35..f8e7b1b88 100644 --- a/src/routes/console/project-[project]/overview/platforms/wizard/store.ts +++ b/src/routes/console/project-[project]/overview/platforms/wizard/store.ts @@ -56,8 +56,12 @@ export const versions = cachedStore< >('versions', function ({ set }) { return { load: async () => { - const { endpoint } = sdk.forConsole.client.config; - const response = await fetch(`${endpoint}/../versions`); + const { endpoint, project } = sdk.forConsole.client.config; + const response = await fetch(`${endpoint}/../versions`, { + headers: { + 'X-Appwrite-Project': project + } + }); set(await response.json()); } }; diff --git a/src/routes/console/project-[project]/overview/platforms/wizard/web/step2.svelte b/src/routes/console/project-[project]/overview/platforms/wizard/web/step2.svelte index 9699669ff..bff1c05dc 100644 --- a/src/routes/console/project-[project]/overview/platforms/wizard/web/step2.svelte +++ b/src/routes/console/project-[project]/overview/platforms/wizard/web/step2.svelte @@ -35,8 +35,12 @@ {#if method === Method.NPM}

    - Use NPM (node package manager) from your command line to add Appwrite SDK to your project. + Use NPM (node package manager) from your command line to add Appwrite SDK + to your project.

    diff --git a/src/routes/console/project-[project]/settings/+page.svelte b/src/routes/console/project-[project]/settings/+page.svelte index ccce2b50b..be38f8bfc 100644 --- a/src/routes/console/project-[project]/settings/+page.svelte +++ b/src/routes/console/project-[project]/settings/+page.svelte @@ -3,10 +3,18 @@ import { onMount } from 'svelte'; import { toLocaleDateTime } from '$lib/helpers/date'; import { addNotification } from '$lib/stores/notifications'; + import { organizationList } from '$lib/stores/organization'; import { project } from '../store'; import { services, type Service } from '$lib/stores/project-services'; import { CardGrid, CopyInput, Box, Heading } from '$lib/components'; - import { Button, Form, FormList, InputText, InputSwitch } from '$lib/elements/forms'; + import { + Button, + Form, + FormList, + InputText, + InputSwitch, + InputSelect + } from '$lib/elements/forms'; import { Container } from '$lib/layout'; import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; @@ -14,14 +22,22 @@ import { base } from '$app/paths'; import { page } from '$app/stores'; import { Submit, trackEvent, trackError } from '$lib/actions/analytics'; + import EnableAllServices from './enableAllServices.svelte'; + import DisableAllServices from './disableAllServices.svelte'; + import Transfer from './transferProject.svelte'; let name: string = null; + let teamId: string = null; let showDelete = false; + let showTransfer = false; const endpoint = sdk.forConsole.client.config.endpoint; const projectId = $page.params.project; + let showDisableAll = false; + let showEnableAll = false; onMount(async () => { name ??= $project.name; + teamId ??= $project.teamId; }); async function updateName() { @@ -42,6 +58,51 @@ } } + async function toggleAllServices(status: boolean) { + if (status && !showEnableAll) { + showEnableAll = true; + return; + } + if (!status && !showDisableAll) { + showDisableAll = true; + return; + } + + try { + const path = '/projects/' + $project.$id + '/service/all'; + await sdk.forConsole.client.call( + 'PATCH', + new URL(sdk.forConsole.client.config.endpoint + path), + { + 'X-Appwrite-Project': sdk.forConsole.client.config.project, + 'content-type': 'application/json' + }, + { + status: status + } + ); + invalidate(Dependencies.PROJECT); + addNotification({ + type: 'success', + message: + 'All services for ' + + $project.name + + ' has been ' + + (status ? 'enabled.' : 'disabled.') + }); + trackEvent(Submit.ProjectService); + } catch (error) { + addNotification({ + type: 'error', + message: error.message + }); + trackError(error, Submit.ProjectService); + } finally { + showDisableAll = false; + showEnableAll = false; + } + } + async function serviceUpdate(service: Service) { try { await sdk.forConsole.projects.updateServiceStatus( @@ -115,10 +176,23 @@ Services -

    Choose services you wish to enable or disable.

    +

    + Choose services you wish to enable or disable for the client API. When disabled, the + services are not accessible to client SDKs but remain accessible to server SDKs. +

    +
      +
    • + +
    • +
    • + +
    • +
    -
    +
      {#each $services.list as service} + + Transfer project +

      Transfer your project to another organization that you own.

      + + + ({ + value: team.$id, + label: team.name + }))} /> + + + + + + +
      Delete Project @@ -160,3 +257,11 @@ + toggleAllServices(false)} bind:show={showDisableAll} /> + toggleAllServices(true)} bind:show={showEnableAll} /> +{#if teamId} + t.$id == teamId).name} + bind:show={showTransfer} /> +{/if} diff --git a/src/routes/console/project-[project]/settings/disableAllServices.svelte b/src/routes/console/project-[project]/settings/disableAllServices.svelte new file mode 100644 index 000000000..1cf11235e --- /dev/null +++ b/src/routes/console/project-[project]/settings/disableAllServices.svelte @@ -0,0 +1,19 @@ + + + + Disable all services +

      + Are you sure you want to disable all services? This will disable API requests to your + project. +

      + + + + +
      diff --git a/src/routes/console/project-[project]/settings/enableAllServices.svelte b/src/routes/console/project-[project]/settings/enableAllServices.svelte new file mode 100644 index 000000000..e3189a739 --- /dev/null +++ b/src/routes/console/project-[project]/settings/enableAllServices.svelte @@ -0,0 +1,16 @@ + + + + Enable all services +

      All project services will be enabled.

      + + + + +
      diff --git a/src/routes/console/project-[project]/settings/transferProject.svelte b/src/routes/console/project-[project]/settings/transferProject.svelte new file mode 100644 index 000000000..cba255afd --- /dev/null +++ b/src/routes/console/project-[project]/settings/transferProject.svelte @@ -0,0 +1,60 @@ + + + + Transfer project +

      Are you sure you want to transfer {$project.name} to {teamName}?

      +

      + Members who are not part of the destination organization must be invited to gain access to + this project. +

      + + + + + +
      diff --git a/src/routes/console/project-[project]/settings/webhooks/+page.svelte b/src/routes/console/project-[project]/settings/webhooks/+page.svelte index fde1c291e..1e22b243a 100644 --- a/src/routes/console/project-[project]/settings/webhooks/+page.svelte +++ b/src/routes/console/project-[project]/settings/webhooks/+page.svelte @@ -6,13 +6,13 @@ import { Button } from '$lib/elements/forms'; import { Pill } from '$lib/elements'; import { - Table, TableBody, TableRowLink, TableCellHead, TableCell, TableCellText, - TableHeader + TableHeader, + TableScroll } from '$lib/elements/table'; import { Container } from '$lib/layout'; import { wizard } from '$lib/stores/wizard'; @@ -46,10 +46,10 @@
      {#if data.webhooks.total} - + - Name - POST URL + Name + POST URL Events @@ -57,7 +57,7 @@ -
      +
      {webhook.name} {#if webhook.security === false} SLL/TLS disabled @@ -69,7 +69,7 @@ {/each} -
      + {:else} {$bucket?.name} - {$bucket?.$id ?? 'Bucket ID'} + {#if $bucket?.$id} + {$bucket.$id} + {/if} diff --git a/src/routes/console/wizardFeedback/step1.svelte b/src/routes/console/wizardFeedback/step1.svelte new file mode 100644 index 000000000..c3a665590 --- /dev/null +++ b/src/routes/console/wizardFeedback/step1.svelte @@ -0,0 +1,39 @@ + + + + {$selectedFeedback.title} + {$selectedFeedback.desc} + + + diff --git a/src/routes/console/wizardFeedback/step2.svelte b/src/routes/console/wizardFeedback/step2.svelte new file mode 100644 index 000000000..1e91272cf --- /dev/null +++ b/src/routes/console/wizardFeedback/step2.svelte @@ -0,0 +1,18 @@ + + + + Thanks for the feedback! + +
      + {#if $app.themeInUse === 'dark'} + + {:else} + + {/if} +
      +
      diff --git a/src/routes/register/invite/[slug]/+page.svelte b/src/routes/register/invite/[slug]/+page.svelte index 346f737c0..c2d9deb61 100644 --- a/src/routes/register/invite/[slug]/+page.svelte +++ b/src/routes/register/invite/[slug]/+page.svelte @@ -54,9 +54,11 @@ async function invite() { try { - const res = await fetch(`${sdk.forConsole.client.config.endpoint}/account/invite`, { + const { endpoint, project } = sdk.forConsole.client.config; + const res = await fetch(`${endpoint}/account/invite`, { method: 'POST', headers: { + 'X-Appwrite-Project': project, 'Content-Type': 'application/json' }, body: JSON.stringify({ diff --git a/static/icons/dark/color/oidc.svg b/static/icons/dark/color/oidc.svg new file mode 100644 index 000000000..3e45a505d --- /dev/null +++ b/static/icons/dark/color/oidc.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/dark/grayscale/oidc.svg b/static/icons/dark/grayscale/oidc.svg new file mode 100644 index 000000000..d4720bbca --- /dev/null +++ b/static/icons/dark/grayscale/oidc.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/light/color/oidc.svg b/static/icons/light/color/oidc.svg new file mode 100644 index 000000000..3e45a505d --- /dev/null +++ b/static/icons/light/color/oidc.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/light/grayscale/oidc.svg b/static/icons/light/grayscale/oidc.svg new file mode 100644 index 000000000..ec5d3f9dd --- /dev/null +++ b/static/icons/light/grayscale/oidc.svg @@ -0,0 +1,3 @@ + + + diff --git a/tests/unit/components/tab.test.ts b/tests/unit/components/tab.test.ts index a0d7b0dcb..9e75e25e8 100644 --- a/tests/unit/components/tab.test.ts +++ b/tests/unit/components/tab.test.ts @@ -6,12 +6,12 @@ import { Tab } from '../../../src/lib/components'; test('shows tab', () => { const { getByRole } = render(Tab); - expect(getByRole('button')).toBeInTheDocument(); + expect(getByRole('tab')).toBeInTheDocument(); }); test('shows tab - is selected', () => { const { getByRole } = render(Tab, { selected: true }); - expect(getByRole('button')).toHaveClass('is-selected'); + expect(getByRole('tab')).toHaveClass('is-selected'); }); test('shows tab - is link', () => { @@ -23,7 +23,7 @@ test('shows tab - is link', () => { test('shows tab - on:click', async () => { const { getByRole, component } = render(Tab); - const tab = getByRole('button'); + const tab = getByRole('tab'); const callback = vi.fn(); component.$on('click', callback); diff --git a/tests/unit/helpers/date.test.ts b/tests/unit/helpers/date.test.ts index 16b5ff3f4..fab13619a 100644 --- a/tests/unit/helpers/date.test.ts +++ b/tests/unit/helpers/date.test.ts @@ -16,6 +16,10 @@ describe('local date', () => { expect(toLocaleDate(value)).toBe(expected); }); }); + + it('invalid date', () => { + expect(toLocaleDate('')).toBe('n/a'); + }); }); describe('local date time', () => { @@ -27,6 +31,10 @@ describe('local date time', () => { expect(toLocaleDateTime(value)).toBe(expected); }); }); + + it('invalid date', () => { + expect(toLocaleDateTime('')).toBe('n/a'); + }); }); describe('is same day', () => {