diff --git a/src/lib/stores/runtimes.ts b/src/lib/stores/runtimes.ts index c42f8b1f7..c30d0ae04 100644 --- a/src/lib/stores/runtimes.ts +++ b/src/lib/stores/runtimes.ts @@ -1,5 +1,7 @@ -export function getIconFromRuntime(runtime: string) { +export function getIconFromRuntime(runtime?: string) { switch (true) { + case !runtime: + return undefined; case runtime.includes('node'): return 'node'; case runtime.includes('php'): @@ -18,6 +20,18 @@ export function getIconFromRuntime(runtime: string) { return 'deno'; case runtime.includes('flutter'): return 'flutter'; + case runtime.includes('dotnet'): + return 'dotnet'; + case runtime.includes('java'): + return 'java'; + case runtime.includes('swift'): + return 'swift'; + case runtime.includes('kotlin'): + return 'kotlin'; + case runtime.includes('cpp'): + return 'cpp'; + case runtime.includes('rust'): + return 'rust'; default: return undefined; } diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/+page.svelte index 42be5ca74..a9c2e00cd 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/+page.svelte @@ -21,6 +21,7 @@ import Wizard from '$lib/layout/wizard.svelte'; import { Link } from '$lib/elements'; import { Button } from '$lib/elements/forms'; + import { getIconFromRuntime } from '$lib/stores/runtimes'; import { regionalConsoleVariables } from '../../store'; export let data; @@ -36,17 +37,17 @@ const starterTemplate = data.templates.find((template) => template.id === 'starter'); - const baseRuntimesList = [ - ...new Map( - data.runtimesList.runtimes.map((runtime) => { - const base = runtime.name.split('-')[0]; - return [base, runtime]; - }) - ).values() - ]; + const latestRuntimesByKey = new Map(); + for (const runtime of data.runtimesList.runtimes) { + latestRuntimesByKey.set(runtime.key, runtime); + } - const starterTemplateRuntimes = starterTemplate.runtimes.filter((r) => - baseRuntimesList.some((base) => base.$id === r.name) + const starterTemplateRuntimeIds = new Set( + starterTemplate?.runtimes.map((runtime) => runtime.name) ?? [] + ); + + const starterTemplateRuntimes = [...latestRuntimesByKey.values()].filter((runtime) => + starterTemplateRuntimeIds.has(runtime.$id) ); function connect(e: Models.ProviderRepository) { @@ -131,11 +132,8 @@ Quick start - {#each starterTemplateRuntimes.slice(0, 6) as template} - {@const iconName = template.name.split('-')[0]} - {@const runtimeDetail = baseRuntimesList.find( - (runtime) => runtime.$id === template.name - )} + {#each starterTemplateRuntimes as runtime} + {@const iconName = getIconFromRuntime(runtime.key)} + ) + `?runtime=${runtime.$id}`}> - - + + {#if iconName} + + {/if} - {runtimeDetail?.name} + {runtime.name} diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte index 26410e3e6..e0e389e91 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte @@ -59,13 +59,16 @@ data.runtimesList?.runtimes?.map((r) => ({ value: r.$id, label: `${r.name} - ${r.version}`, - leadingHtml: `` + leadingHtml: `` })) || [] ); onMount(() => { - const runtimeParam = data.runtime || page.url.searchParams.get('runtime') || 'node-18.0'; - runtime = runtimeParam as Runtime; + const runtimeParam = data.runtime || page.url.searchParams.get('runtime'); + const runtimeOption = data.runtimesList.runtimes.find( + (runtime) => runtime.$id === runtimeParam + ); + runtime = (runtimeOption?.$id ?? data.runtimesList.runtimes[0]?.$id) as Runtime; entrypoint = page.url.searchParams.get('entrypoint') || ''; diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte index c1452020d..d0e10cdf4 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte @@ -32,7 +32,7 @@ const runtimeOptions = data.runtimesList.runtimes.map((runtime) => { const { $id: value, name, version, key } = runtime; const label = `${name} - ${version}`; - const iconName = getIconFromRuntime(key); + const iconName = getIconFromRuntime(key) ?? 'empty'; const iconSrc = iconFor(iconName, 'color'); const leadingHtml = `${iconName}`; diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte index 58d29ad20..7b19202d0 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte @@ -37,7 +37,7 @@ return { value: runtime.$id, label: `${runtime.name} - ${runtime.version}`, - leadingHtml: `` + leadingHtml: `` }; }); diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte index 8aef379d1..cfb1bcd80 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte @@ -11,7 +11,7 @@ import { installation, repository } from '$lib/stores/vcs'; import { Fieldset, Layout, Icon, Divider, Empty, Typography } from '@appwrite.io/pink-svelte'; import { IconGithub } from '@appwrite.io/pink-icons-svelte'; - import { onMount } from 'svelte'; + import { onMount, untrack } from 'svelte'; import { writable } from 'svelte/store'; import ProductionBranch from '$lib/components/git/productionBranchFieldset.svelte'; import Configuration from './configuration.svelte'; @@ -37,64 +37,83 @@ import { Dependencies } from '$lib/constants'; import { getIconFromRuntime } from '$lib/stores/runtimes'; import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; + import type { PageData } from './$types'; - export let data; + let { data }: { data: PageData } = $props(); - const specificationOptions = (data.specificationsList?.specifications ?? []).map((size) => ({ - label: - `${size.cpus} CPU, ${size.memory} MB RAM` + - (!size.enabled ? ` (Upgrade to use this)` : ''), - value: size.slug, - disabled: !size.enabled - })); + const specificationOptions = $derived( + (data.specificationsList?.specifications ?? []).map((size) => ({ + label: + `${size.cpus} CPU, ${size.memory} MB RAM` + + (!size.enabled ? ` (Upgrade to use this)` : ''), + value: size.slug, + disabled: !size.enabled + })) + ); - let showExitModal = false; - let isCreatingRepository = false; + let showExitModal = $state(false); + let isCreatingRepository = $state(false); - let formComponent: Form; - let isSubmitting = writable(false); + let formComponent = $state
(); + let isSubmitting = $state(writable(false)); - let name = data.template.name; - let id: string | null = null; - let runtime: Runtime; - let branch = 'main'; - let rootDir = './'; - let connectBehaviour: 'now' | 'later' = 'now'; - let repositoryBehaviour: 'new' | 'existing' = 'new'; - let repositoryName = undefined; - let repositoryPrivate = true; - let selectedInstallationId = ''; - let selectedRepository = ''; - let showConfig = false; - let silentMode = false; - let entrypoint = ''; - let selectedScopes: Scopes[] = []; - let execute = true; - let variables: Partial[] = []; - let specification = specificationOptions[0]?.value || ''; + let name = $state(untrack(() => data.template.name)); + let id = $state(null); + let runtime = $state(); + let branch = $state('main'); + let rootDir = $state('./'); + let connectBehaviour = $state<'now' | 'later'>('now'); + let repositoryBehaviour = $state<'new' | 'existing'>('new'); + let repositoryName = $state(); + let repositoryPrivate = $state(true); + let selectedInstallationId = $state(''); + let selectedRepository = $state(''); + let showConfig = $state(false); + let silentMode = $state(false); + let entrypoint = $state(''); + let selectedScopes = $state([]); + let execute = $state(true); + let variables = $state[]>([]); + let specification = $state(untrack(() => specificationOptions[0]?.value || '')); + + const availableRuntimes: Models.Runtime[] = $derived( + data.runtimesList.runtimes.filter((runtime) => + data.template.runtimes.some((templateRuntime) => templateRuntime.name === runtime.$id) + ) + ); + + function sortRuntimesByVersionDesc(a: Models.Runtime, b: Models.Runtime) { + return b.version.localeCompare(a.version, undefined, { numeric: true }); + } + + function selectInitialRuntime() { + const runtimeParam = page.url.searchParams.get('runtime'); + const requestedRuntime = availableRuntimes.find((runtime) => runtime.$id === runtimeParam); + + if (requestedRuntime) { + return requestedRuntime.$id as Runtime; + } + + const runtimeById = new Map( + data.runtimesList.runtimes.map((runtime) => [runtime.$id, runtime]) + ); + const preferredRuntime = data.template.runtimes + .map((runtime) => runtimeById.get(runtime.name)) + .find(Boolean); + const preferredRuntimes = preferredRuntime + ? availableRuntimes.filter((runtime) => runtime.key === preferredRuntime.key) + : []; + const runtimes = preferredRuntimes.length ? preferredRuntimes : availableRuntimes; + + return [...runtimes].sort(sortRuntimesByVersionDesc)[0]?.$id as Runtime; + } onMount(async () => { if (!$installation?.$id) { $installation = data.installations.installations[0]; } selectedInstallationId = $installation?.$id; - if (data.template.runtimes && data.template.runtimes.length > 0) { - const targetRuntime = data.template.runtimes[0].name; - const matchingRuntimes = Object.values(Runtime).filter((r) => - r.startsWith(targetRuntime.split('-')[0]) - ); - - matchingRuntimes.sort((a, b) => { - const versionA = a.split('-')[1]; - const versionB = b.split('-')[1]; - return versionB.localeCompare(versionA, undefined, { numeric: true }); - }); - if (page.url.searchParams.has('runtime')) { - runtime = page.url.searchParams.get('runtime') as Runtime; - } else { - runtime = matchingRuntimes[0] as Runtime; - } - } + runtime = selectInitialRuntime(); }); async function createRepository() { @@ -210,18 +229,18 @@ } } - $: if (repositoryBehaviour === 'new') { - selectedInstallationId ??= $installation?.$id; - repositoryName ??= name.split(' ').join('-').toLowerCase(); - } + $effect(() => { + if (repositoryBehaviour === 'new') { + selectedInstallationId ??= $installation?.$id; + repositoryName ??= name.split(' ').join('-').toLowerCase(); + } + }); - $: if (connectBehaviour === 'later') { - selectedRepository = null; - } - - $: availableRuntimes = data.runtimesList.runtimes.filter((runtime) => - data.template.runtimes.some((templateRuntime) => templateRuntime.name === runtime.$id) - ); + $effect(() => { + if (connectBehaviour === 'later') { + selectedRepository = null; + } + }); @@ -259,7 +278,7 @@ return { value: runtime.$id, label: `${runtime.name} - ${runtime.version}`, - leadingHtml: `` + leadingHtml: `` }; })} diff --git a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte index 7309623b5..1209581be 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte @@ -37,7 +37,15 @@ type ScopeDefinition } from '$lib/constants'; import { sdk } from '$lib/stores/sdk'; - import { Accordion, Alert, Badge, Divider, Layout, Selector } from '@appwrite.io/pink-svelte'; + import { + Accordion, + Alert, + Badge, + Divider, + Layout, + Selector, + Typography + } from '@appwrite.io/pink-svelte'; import type { Scopes } from '@appwrite.io/console'; let { scopes = $bindable([]) }: { scopes: Scopes[] } = $props(); @@ -51,68 +59,51 @@ Database: 'Databases' }; - enum Category { - Project = 'Project', - Auth = 'Auth', - Databases = 'Databases', - Functions = 'Functions', - Messaging = 'Messaging', - Sites = 'Sites', - Storage = 'Storage', - Domains = 'Domains', - Other = 'Other' - } - - const categoryOrder = [ - Category.Project, - Category.Auth, - Category.Databases, - Category.Functions, - Category.Storage, - Category.Messaging, - Category.Sites, - Category.Domains, - Category.Other - ]; - function normalizeCategory(category: string): string { return categoryAliasMap[category] ?? category; } const filteredScopes = $derived.by(() => { + const scopesById = new Map(allScopesList.map((scope) => [scope.scope, scope])); + const databasesWriteIndex = allScopesList.findIndex((s) => s.scope === 'databases.write'); if (isCloud && databasesWriteIndex !== -1) { + const normalizedBackupScopes = cloudOnlyBackupScopes.map((scope) => ({ + ...scope, + category: normalizeCategory(scope.category) + })); + const backupScopeIds = new Set(normalizedBackupScopes.map((scope) => scope.scope)); + + for (const scope of normalizedBackupScopes) { + if (!scopesById.has(scope.scope)) { + scopesById.set(scope.scope, scope); + } + } + + const mergedScopes = Array.from(scopesById.values()); + const nonBackupScopes = mergedScopes.filter( + (scope) => !backupScopeIds.has(scope.scope) + ); + const mergedDatabasesWriteIndex = nonBackupScopes.findIndex( + (s) => s.scope === 'databases.write' + ); + return [ - ...allScopesList.slice(0, databasesWriteIndex + 1), - ...cloudOnlyBackupScopes.map((scope) => ({ - ...scope, - category: normalizeCategory(scope.category) - })), - ...allScopesList.slice(databasesWriteIndex + 1) + ...nonBackupScopes.slice(0, mergedDatabasesWriteIndex + 1), + ...normalizedBackupScopes.map((scope) => scopesById.get(scope.scope) ?? scope), + ...nonBackupScopes.slice(mergedDatabasesWriteIndex + 1) ]; } return allScopesList; }); const categories = $derived.by(() => { - const availableCategories = new Set( - filteredScopes.map((scope) => normalizeCategory(scope.category)) + return Array.from( + new Set(filteredScopes.map((scope) => normalizeCategory(scope.category))) ); - - return [ - ...categoryOrder.filter((category) => availableCategories.has(category)), - ...Array.from(availableCategories).filter( - (category) => !categoryOrder.includes(category as Category) - ) - ]; }); - const scopeCatalog = $derived( - new Set([ - ...filteredScopes.map((s) => s.scope), - ...(isCloud ? cloudOnlyBackupScopes.map((s) => s.scope) : []) - ]) - ); + const scopeCatalog = $derived(new Set(filteredScopes.map((s) => s.scope))); let activeScopes: Record = $state({}); @@ -130,13 +121,6 @@ const result = await sdk.forConsole.console.listProjectScopes(); const scopesById = new Map(); - for (const scope of localScopes) { - scopesById.set(scope.scope, { - ...scope, - category: normalizeCategory(scope.category) - }); - } - for (const scope of result.scopes) { scopesById.set(scope.$id, { scope: scope.$id, @@ -147,6 +131,15 @@ }); } + for (const scope of localScopes) { + if (!scopesById.has(scope.scope)) { + scopesById.set(scope.scope, { + ...scope, + category: normalizeCategory(scope.category) + }); + } + } + allScopesList = Array.from(scopesById.values()); for (const s of filteredScopes) { @@ -235,16 +228,36 @@ on:change={(event) => onCategoryChange(event, category)}> {#each filteredScopes.filter((s) => s.category === category) as scope} - + - {#if scope.deprecated} - - {/if} + + + {/each} @@ -252,3 +265,9 @@ {/each} + + diff --git a/src/routes/(public)/functions/deploy/+page.ts b/src/routes/(public)/functions/deploy/+page.ts index 929de33ec..28defa677 100644 --- a/src/routes/(public)/functions/deploy/+page.ts +++ b/src/routes/(public)/functions/deploy/+page.ts @@ -31,6 +31,16 @@ export const load: PageLoad = async ({ parent, url }) => { const envParam = url.searchParams.get('env'); const envKeys = envParam ? envParam.split(',').map((key: string) => key.trim()) : []; + // Get available runtimes + const runtimesList = await sdk.forConsole.functions.listRuntimes(); + + const runtimeOption = runtimesList.runtimes.find((option) => option.$id === runtime); + const runtimeId = runtimeOption?.$id ?? runtimesList.runtimes[0]?.$id; + + if (!runtimeId) { + redirect(302, base + '/'); + } + const deploymentData: { type: 'repo'; repository: { @@ -48,12 +58,9 @@ export const load: PageLoad = async ({ parent, url }) => { rootDirectory: null }, name: name || '', - runtime: runtime || 'node-18.0' + runtime: runtimeId }; - // Get available runtimes - const runtimesList = await sdk.forConsole.functions.listRuntimes(); - const info = getRepositoryInfo(repository); if (!info) { redirect(302, base + '/'); diff --git a/static/icons/dark/color/rust.svg b/static/icons/dark/color/rust.svg new file mode 100644 index 000000000..7364d9ede --- /dev/null +++ b/static/icons/dark/color/rust.svg @@ -0,0 +1 @@ + diff --git a/static/icons/dark/grayscale/rust.svg b/static/icons/dark/grayscale/rust.svg new file mode 100644 index 000000000..af2f8dd01 --- /dev/null +++ b/static/icons/dark/grayscale/rust.svg @@ -0,0 +1 @@ + diff --git a/static/icons/light/color/rust.svg b/static/icons/light/color/rust.svg new file mode 100644 index 000000000..7364d9ede --- /dev/null +++ b/static/icons/light/color/rust.svg @@ -0,0 +1 @@ + diff --git a/static/icons/light/grayscale/rust.svg b/static/icons/light/grayscale/rust.svg new file mode 100644 index 000000000..9c3a86e0c --- /dev/null +++ b/static/icons/light/grayscale/rust.svg @@ -0,0 +1 @@ +