diff --git a/src/hooks.client.ts b/src/hooks.client.ts index c6404ee81..9c8679912 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -8,8 +8,7 @@ Sentry.init({ dsn: 'https://c7ce178bdedd486480317b72f282fd39@o1063647.ingest.us.sentry.io/4504158071422976', tracesSampleRate: 1, replaysSessionSampleRate: 0, - replaysOnErrorSampleRate: 1, - integrations: [Sentry.replayIntegration()] + replaysOnErrorSampleRate: 0 }); export const handleError: HandleClientError = Sentry.handleErrorWithSentry( diff --git a/src/lib/elements/forms/inputSelect.svelte b/src/lib/elements/forms/inputSelect.svelte index 844d3e898..75b7ea2ae 100644 --- a/src/lib/elements/forms/inputSelect.svelte +++ b/src/lib/elements/forms/inputSelect.svelte @@ -17,6 +17,7 @@ export let options: { value: string | boolean | number | null; label: string; + disabled?: boolean; }[]; export let isMultiple = false; export let fullWidth = false; @@ -64,4 +65,4 @@ on:invalid={handleInvalid} on:input on:change - bind:value /> + bind:value /> \ No newline at end of file diff --git a/src/lib/helpers/pricingRedirect.ts b/src/lib/helpers/pricingRedirect.ts new file mode 100644 index 000000000..5de04212f --- /dev/null +++ b/src/lib/helpers/pricingRedirect.ts @@ -0,0 +1,13 @@ +import { goto } from '$app/navigation'; +import { base } from '$app/paths'; + +export function checkPricingRefAndRedirect(searchParams: URLSearchParams, shouldRegister = false) { + if (searchParams.has('type')) { + const paramType = searchParams.get('type'); + if (paramType === 'createPro') { + shouldRegister + ? goto(`${base}/register?type=createPro`) + : goto(`${base}/create-organization?type=createPro`); + } + } +} diff --git a/src/lib/stores/specifications.ts b/src/lib/stores/specifications.ts new file mode 100644 index 000000000..30bc5eabb --- /dev/null +++ b/src/lib/stores/specifications.ts @@ -0,0 +1,8 @@ +import { page } from '$app/stores'; +import type { Models } from '@appwrite.io/console'; +import { derived } from 'svelte/store'; + +export const specificationsList = derived( + page, + async ($page) => (await $page.data.specificationsList) as Models.SpecificationList +); diff --git a/src/lib/wizards/functions/components/specificationsTooltip.svelte b/src/lib/wizards/functions/components/specificationsTooltip.svelte new file mode 100644 index 000000000..279f5a0f1 --- /dev/null +++ b/src/lib/wizards/functions/components/specificationsTooltip.svelte @@ -0,0 +1,24 @@ + + +
+

Additional CPU and RAM are available for Pro and Scale teams.

+
+ Learn more + +
+
+ + diff --git a/src/lib/wizards/functions/cover.svelte b/src/lib/wizards/functions/cover.svelte index e88803c42..bf4484b8b 100644 --- a/src/lib/wizards/functions/cover.svelte +++ b/src/lib/wizards/functions/cover.svelte @@ -19,7 +19,8 @@ repositoryBehaviour: 'new', repositoryName: template.id, repositoryPrivate: true, - repositoryId: null + repositoryId: null, + specification: null }); wizard.start(CreateTemplate); } diff --git a/src/lib/wizards/functions/createManual.svelte b/src/lib/wizards/functions/createManual.svelte index c8fe529ef..586b580c6 100644 --- a/src/lib/wizards/functions/createManual.svelte +++ b/src/lib/wizards/functions/createManual.svelte @@ -37,7 +37,18 @@ undefined, undefined, $createFunction.entrypoint, - $createFunction.commands || undefined + $createFunction.commands || undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + $createFunction.specification ); await sdk.forProject.functions.createDeployment( response.$id, diff --git a/src/lib/wizards/functions/createTemplate.svelte b/src/lib/wizards/functions/createTemplate.svelte index 63aa03ab5..e41bf78dc 100644 --- a/src/lib/wizards/functions/createTemplate.svelte +++ b/src/lib/wizards/functions/createTemplate.svelte @@ -58,7 +58,8 @@ $template.providerRepositoryId || undefined, $template.providerOwner || undefined, runtimeDetail.providerRootDirectory || undefined, - $template.providerVersion || undefined + $template.providerVersion || undefined, + $templateConfig.specification || undefined ); if ($templateConfig.variables) { @@ -78,7 +79,8 @@ customId: !!response.$id, runtime: response.runtime, deployment_type: $templateConfig.repositoryBehaviour, - scopes: $templateConfig.scopes + scopes: $templateConfig.scopes, + specification: $templateConfig.specification }); resetState(); } catch (error) { diff --git a/src/lib/wizards/functions/steps/manualDetails.svelte b/src/lib/wizards/functions/steps/manualDetails.svelte index 55fe5655c..9167474d0 100644 --- a/src/lib/wizards/functions/steps/manualDetails.svelte +++ b/src/lib/wizards/functions/steps/manualDetails.svelte @@ -6,16 +6,30 @@ import { onMount } from 'svelte'; import { createFunction } from '../store'; import { runtimesList } from '$lib/stores/runtimes'; + import { specificationsList } from '$lib/stores/specifications'; + import { isCloud } from '$lib/system'; + import { organization } from '$lib/stores/organization'; + import { BillingPlan } from '$lib/constants'; + import SpecificationsTooltip from '../components/specificationsTooltip.svelte'; let showCustomId = false; let options = []; + let specificationOptions = []; onMount(async () => { options = (await $runtimesList).runtimes.map((runtime) => ({ label: `${runtime.name} - ${runtime.version}`, value: runtime.$id })); + + specificationOptions = (await $specificationsList).specifications.map((size) => ({ + label: + `${size.cpus} CPU, ${size.memory} MB RAM` + + (!size.enabled ? ` (Upgrade to use this)` : ''), + value: size.slug, + disabled: !size.enabled + })); }); @@ -39,6 +53,18 @@ {options} required /> + + {#if !showCustomId}
(showCustomId = !showCustomId)}> diff --git a/src/lib/wizards/functions/steps/templateConfiguration.svelte b/src/lib/wizards/functions/steps/templateConfiguration.svelte index 481cd5026..fbce477fb 100644 --- a/src/lib/wizards/functions/steps/templateConfiguration.svelte +++ b/src/lib/wizards/functions/steps/templateConfiguration.svelte @@ -4,7 +4,12 @@ import { FormList, InputSelect, InputText } from '$lib/elements/forms'; import { WizardStep } from '$lib/layout'; import { runtimesList } from '$lib/stores/runtimes'; + import { specificationsList } from '$lib/stores/specifications'; + import { BillingPlan } from '$lib/constants'; + import SpecificationsTooltip from '../components/specificationsTooltip.svelte'; import { template, templateConfig } from '../store'; + import { organization } from '$lib/stores/organization'; + import { isCloud } from '$lib/system'; let showCustomId = false; @@ -12,6 +17,10 @@ if (!$templateConfig.runtime) { throw new Error('Please select a runtime.'); } + + if (!$templateConfig.specification) { + throw new Error('Please select a specification.'); + } } async function loadRuntimes() { @@ -27,6 +36,22 @@ return options; } + + async function loadSpecifications() { + const specificationOptions = (await $specificationsList).specifications.map((size) => ({ + label: + `${size.cpus} CPU, ${size.memory} MB RAM` + + (!size.enabled ? ` (Upgrade to use this)` : ''), + value: size.slug, + disabled: !size.enabled + })); + + if (!$templateConfig.specification && specificationOptions.length > 0) { + $templateConfig.specification = specificationOptions[0].value; + } + + return specificationOptions; + } @@ -61,6 +86,28 @@ {options} bind:value={$templateConfig.runtime} /> {/await} + {#await loadSpecifications()} + + {:then specificationOptions} + + {/await} diff --git a/src/lib/wizards/functions/store.ts b/src/lib/wizards/functions/store.ts index 90cfdc55b..6613438a2 100644 --- a/src/lib/wizards/functions/store.ts +++ b/src/lib/wizards/functions/store.ts @@ -15,6 +15,7 @@ export const templateConfig = writable<{ repositoryId: string; execute?: boolean; scopes?: string[]; + specification?: string; }>(); export const repository = writable(); export const installation = writable(); diff --git a/src/routes/(console)/create-organization/+page.svelte b/src/routes/(console)/create-organization/+page.svelte index c463a5d4a..63cc1f210 100644 --- a/src/routes/(console)/create-organization/+page.svelte +++ b/src/routes/(console)/create-organization/+page.svelte @@ -79,7 +79,11 @@ billingPlan = plan as BillingPlan; } } - if (anyOrgFree) { + if ( + anyOrgFree || + ($page.url.searchParams.has('type') && + $page.url.searchParams.get('type') === 'createPro') + ) { billingPlan = BillingPlan.PRO; } }); diff --git a/src/routes/(console)/onboarding/+page.svelte b/src/routes/(console)/onboarding/+page.svelte index 3f112011c..b4a9b4aba 100644 --- a/src/routes/(console)/onboarding/+page.svelte +++ b/src/routes/(console)/onboarding/+page.svelte @@ -17,6 +17,7 @@ import { tierToPlan, type Tier, plansInfo } from '$lib/stores/billing'; import { formatCurrency } from '$lib/helpers/numbers'; import { base } from '$app/paths'; + import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; let name: string; let id: string; @@ -42,12 +43,7 @@ onMount(() => { if (isCloud) { - if ($page.url.searchParams.has('type')) { - const paramType = $page.url.searchParams.get('type'); - if (paramType === 'createPro') { - goto(`${base}/create-organization`); - } - } + checkPricingRefAndRedirect($page.url.searchParams); } }); diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index d16339072..76607b429 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -32,6 +32,7 @@ import { onMount } from 'svelte'; import { organization } from '$lib/stores/organization'; import { canWriteProjects } from '$lib/stores/roles'; + import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; export let data; @@ -123,12 +124,7 @@ onMount(async () => { if (isCloud) { regions = await sdk.forConsole.billing.listRegions(); - if ($page.url.searchParams.has('type')) { - const paramType = $page.url.searchParams.get('type'); - if (paramType === 'createPro') { - goto(`${base}/create-organization`); - } - } + checkPricingRefAndRedirect($page.url.searchParams); } }); diff --git a/src/routes/(console)/project-[project]/functions/+layout.ts b/src/routes/(console)/project-[project]/functions/+layout.ts index cab65d7c3..8d7b68a16 100644 --- a/src/routes/(console)/project-[project]/functions/+layout.ts +++ b/src/routes/(console)/project-[project]/functions/+layout.ts @@ -8,10 +8,11 @@ import type { LayoutLoad } from './$types'; export const load: LayoutLoad = async ({ depends }) => { depends(Dependencies.FUNCTION_INSTALLATIONS); - const [runtimesList, installations, templatesList] = await Promise.all([ + const [runtimesList, installations, templatesList, specificationsList] = await Promise.all([ sdk.forProject.functions.listRuntimes(), sdk.forProject.vcs.listInstallations([Query.limit(100)]), - sdk.forProject.functions.listTemplates(undefined, undefined, 100) + sdk.forProject.functions.listTemplates(undefined, undefined, 100), + sdk.forProject.functions.listSpecifications() ]); return { @@ -19,6 +20,7 @@ export const load: LayoutLoad = async ({ depends }) => { breadcrumbs: Breadcrumbs, runtimesList, installations, - templatesList + templatesList, + specificationsList }; }; diff --git a/src/routes/(console)/project-[project]/functions/function-[function]/settings/updateRuntime.svelte b/src/routes/(console)/project-[project]/functions/function-[function]/settings/updateRuntime.svelte index d7925fea5..1f06dbe9c 100644 --- a/src/routes/(console)/project-[project]/functions/function-[function]/settings/updateRuntime.svelte +++ b/src/routes/(console)/project-[project]/functions/function-[function]/settings/updateRuntime.svelte @@ -3,30 +3,46 @@ import { page } from '$app/stores'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { CardGrid, Heading } from '$lib/components'; - import { Dependencies } from '$lib/constants'; + import { BillingPlan, Dependencies } from '$lib/constants'; import { Button, Form, FormList } from '$lib/elements/forms'; import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; import { onMount } from 'svelte'; import { func } from '../store'; import InputSelect from '$lib/elements/forms/inputSelect.svelte'; + import { specificationsList } from '$lib/stores/specifications'; import { runtimesList } from '$lib/stores/runtimes'; import { isValueOfStringEnum } from '$lib/helpers/types'; import { Runtime } from '@appwrite.io/console'; + import { isCloud } from '$lib/system'; + import { organization } from '$lib/stores/organization'; + import SpecificationsTooltip from '$lib/wizards/functions/components/specificationsTooltip.svelte'; const functionId = $page.params.function; let runtime: string = null; + let specification: string = null; let options = []; + let specificationOptions = []; onMount(async () => { runtime ??= $func.runtime; + specification ??= $func.specification; let runtimes = await $runtimesList; + let allowedSpecifications = (await $specificationsList).specifications; options = runtimes.runtimes.map((runtime) => ({ label: `${runtime.name} - ${runtime.version}`, value: runtime.$id })); + + specificationOptions = allowedSpecifications.map((size) => ({ + label: + `${size.cpus} CPU, ${size.memory} MB RAM` + + (!size.enabled ? ` (Upgrade to use this)` : ''), + value: size.slug, + disabled: !size.enabled + })); }); async function updateRuntime() { @@ -51,11 +67,12 @@ $func.providerRepositoryId || undefined, $func.providerBranch || undefined, $func.providerSilentMode || undefined, - $func.providerRootDirectory || undefined + $func.providerRootDirectory || undefined, + specification ); await invalidate(Dependencies.FUNCTION); addNotification({ - message: 'Runtime has been updated', + message: 'Runtime settings have been updated', type: 'success' }); trackEvent(Submit.FunctionUpdateName); @@ -67,6 +84,8 @@ trackError(error, Submit.FunctionUpdateName); } } + + $: isUpdateButtonEnabled = runtime !== $func?.runtime || specification !== $func?.specification;
@@ -83,11 +102,22 @@ {options} required hideRequired /> + - + diff --git a/src/routes/(console)/project-[project]/functions/function-[function]/usage/[[period]]/+page.svelte b/src/routes/(console)/project-[project]/functions/function-[function]/usage/[[period]]/+page.svelte index dcd003a74..95e9e8a25 100644 --- a/src/routes/(console)/project-[project]/functions/function-[function]/usage/[[period]]/+page.svelte +++ b/src/routes/(console)/project-[project]/functions/function-[function]/usage/[[period]]/+page.svelte @@ -10,6 +10,8 @@ export let data: PageData; $: total = data.executionsTotal; $: count = data.executions; + $: gbHoursTotal = data.executionsMbSecondsTotal / 1000 / 3600; + $: mbSecondsCount = data.executionsMbSeconds; @@ -47,4 +49,24 @@ ]} /> {/if} + + {#if mbSecondsCount} + + {formatNumberWithCommas(gbHoursTotal)} +

GB Hours

+
+ [ + e.date, + Math.ceil((e.value / 1000 / 3600) * 1000) / 1000 + ]) + ] + } + ]} /> + + {/if} diff --git a/src/routes/(public)/(guest)/register/+page.svelte b/src/routes/(public)/(guest)/register/+page.svelte index f8fab2e6f..96fe43ae0 100644 --- a/src/routes/(public)/(guest)/register/+page.svelte +++ b/src/routes/(public)/(guest)/register/+page.svelte @@ -20,6 +20,7 @@ import { isCloud } from '$lib/system'; import { page } from '$app/stores'; import { redirectTo } from '$routes/store'; + import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; export let data; @@ -52,6 +53,8 @@ $page.url.searchParams.delete('redirect'); if (redirect) { await goto(`${redirect}${$page.url.search}`); + } else if (isCloud) { + checkPricingRefAndRedirect($page.url.searchParams); } else { await goto(`${base}/${$page.url.search ?? ''}`); } diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 77fd3e8b1..134ef1e22 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -9,6 +9,8 @@ import { redirectTo } from './store'; import { base } from '$app/paths'; import type { Account } from '$lib/stores/user'; import type { AppwriteException } from '@appwrite.io/console'; +import { isCloud } from '$lib/system'; +import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; export const ssr = false; @@ -46,6 +48,10 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { } if (!isPublicRoute) { + if (isCloud) { + checkPricingRefAndRedirect(url.searchParams, true); + } + redirect(303, withParams(`${base}/login`, url.searchParams)); } };