mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' into feat-pink-v2
This commit is contained in:
+1
-2
@@ -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(
|
||||
|
||||
@@ -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 />
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { upgradeURL } from '$lib/stores/billing';
|
||||
</script>
|
||||
|
||||
<div class="u-flex-vertical u-gap-16">
|
||||
<p>Additional CPU and RAM are available for Pro and Scale teams.</p>
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
<a
|
||||
class="u-bold u-block"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://appwrite.io/docs/advanced/platform/gb-hours">Learn more</a>
|
||||
<Button href={$upgradeURL} secondary fullWidthMobile event={'specifications_cta_upgrade'}>
|
||||
<span class="text">Upgrade Plan</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
a {
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -19,7 +19,8 @@
|
||||
repositoryBehaviour: 'new',
|
||||
repositoryName: template.id,
|
||||
repositoryPrivate: true,
|
||||
repositoryId: null
|
||||
repositoryId: null,
|
||||
specification: null
|
||||
});
|
||||
wizard.start(CreateTemplate);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -39,6 +53,18 @@
|
||||
{options}
|
||||
required />
|
||||
|
||||
<InputSelect
|
||||
label="CPU and memory"
|
||||
id="specification"
|
||||
placeholder="Select specification"
|
||||
required
|
||||
disabled={specificationOptions.length < 1}
|
||||
options={specificationOptions}
|
||||
popover={isCloud && $organization?.billingPlan === BillingPlan.FREE
|
||||
? SpecificationsTooltip
|
||||
: null}
|
||||
bind:value={$createFunction.specification} />
|
||||
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
@@ -61,6 +86,28 @@
|
||||
{options}
|
||||
bind:value={$templateConfig.runtime} />
|
||||
{/await}
|
||||
{#await loadSpecifications()}
|
||||
<InputSelect
|
||||
label="CPU and memory"
|
||||
id="specification"
|
||||
placeholder="Loading specifications..."
|
||||
required
|
||||
disabled
|
||||
options={[]}
|
||||
value={null} />
|
||||
{:then specificationOptions}
|
||||
<InputSelect
|
||||
label="CPU and memory"
|
||||
id="specification"
|
||||
placeholder="Select specification"
|
||||
required
|
||||
disabled={specificationOptions.length < 1}
|
||||
options={specificationOptions}
|
||||
popover={isCloud && $organization?.billingPlan === BillingPlan.FREE
|
||||
? SpecificationsTooltip
|
||||
: null}
|
||||
bind:value={$templateConfig.specification} />
|
||||
{/await}
|
||||
</FormList>
|
||||
|
||||
<FormList class="u-margin-block-start-24">
|
||||
|
||||
@@ -15,6 +15,7 @@ export const templateConfig = writable<{
|
||||
repositoryId: string;
|
||||
execute?: boolean;
|
||||
scopes?: string[];
|
||||
specification?: string;
|
||||
}>();
|
||||
export const repository = writable<Models.ProviderRepository>();
|
||||
export const installation = writable<Models.Installation>();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
+34
-4
@@ -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;
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updateRuntime}>
|
||||
@@ -83,11 +102,22 @@
|
||||
{options}
|
||||
required
|
||||
hideRequired />
|
||||
<InputSelect
|
||||
label="CPU and memory"
|
||||
id="size"
|
||||
placeholder="Select runtime specification"
|
||||
bind:value={specification}
|
||||
options={specificationOptions}
|
||||
popover={isCloud && $organization?.billingPlan === BillingPlan.FREE
|
||||
? SpecificationsTooltip
|
||||
: null}
|
||||
required
|
||||
hideRequired />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={runtime === $func.runtime} submit>Update</Button>
|
||||
<Button disabled={!isUpdateButtonEnabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
+22
@@ -10,6 +10,8 @@
|
||||
export let data: PageData;
|
||||
$: total = data.executionsTotal;
|
||||
$: count = data.executions;
|
||||
$: gbHoursTotal = data.executionsMbSecondsTotal / 1000 / 3600;
|
||||
$: mbSecondsCount = data.executionsMbSeconds;
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -47,4 +49,24 @@
|
||||
]} />
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
{#if mbSecondsCount}
|
||||
<Card>
|
||||
<Heading tag="h6" size="6">{formatNumberWithCommas(gbHoursTotal)}</Heading>
|
||||
<p>GB Hours</p>
|
||||
<div class="u-margin-block-start-16" />
|
||||
<BarChart
|
||||
series={[
|
||||
{
|
||||
name: 'Count of gbHours over time',
|
||||
data: [
|
||||
...mbSecondsCount.map((e) => [
|
||||
e.date,
|
||||
Math.ceil((e.value / 1000 / 3600) * 1000) / 1000
|
||||
])
|
||||
]
|
||||
}
|
||||
]} />
|
||||
</Card>
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
@@ -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 ?? ''}`);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user