diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index 9d7a72d6d..cf0e0ca9a 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -4,8 +4,8 @@ import Footer from '$lib/layout/footer.svelte'; import Shell from '$lib/layout/shell.svelte'; import { app } from '$lib/stores/app'; - import { newOrgModal, organization, type Organization } from '$lib/stores/organization'; import { database, checkForDatabaseBackupPolicies } from '$lib/stores/database'; + import { newOrgModal, organization, type Organization } from '$lib/stores/organization'; import { wizard } from '$lib/stores/wizard'; import { afterUpdate, onMount } from 'svelte'; import { loading } from '$routes/store'; @@ -36,7 +36,7 @@ import { stripe } from '$lib/stores/stripe'; import MobileSupportModal from './wizard/support/mobileSupportModal.svelte'; import { showSupportModal } from './wizard/support/store'; - import { activeHeaderAlert, consoleVariables } from './store'; + import { activeHeaderAlert, consoleVariables, version } from './store'; import { headerAlert } from '$lib/stores/headerAlert'; import { UsageRates } from '$lib/components/billing'; import { base } from '$app/paths'; @@ -54,6 +54,10 @@ } from '@appwrite.io/pink-icons-svelte'; import type { LayoutData } from './$types'; import type { NavbarProject } from '$lib/components/navbar.svelte'; + import { sdk } from '$lib/stores/sdk'; + import { type Models, Query } from '@appwrite.io/console'; + + export let data: LayoutData; function kebabToSentenceCase(str: string) { return str @@ -62,11 +66,56 @@ .join(' '); } - const isAssistantEnabled = $consoleVariables?._APP_ASSISTANT_ENABLED === true; + let loadingVersion = false; + let loadingConsoleVars = false; + let loadingProjectsForShell = false; - export let data: LayoutData; + function loadConsoleVariables() { + if (!$version && !loadingVersion) { + const { endpoint, project } = sdk.forConsole.client.config; - $: loadedProjects = data.projects.map((project) => { + loadingVersion = true; + fetch(`${endpoint}/health/version`, { + headers: { 'X-Appwrite-Project': project } + }) + .then((res) => res.json().catch(() => null)) + .then((data) => version.set(data?.version)) + .finally(() => (loadingVersion = false)); + } + + if (!$consoleVariables && !loadingConsoleVars) { + loadingConsoleVars = true; + sdk.forConsole.console + .variables() + .then((vars) => consoleVariables.set(vars)) + .finally(() => (loadingConsoleVars = false)); + } + } + + let latestProjects: Models.Project[] = []; + function loadProjectsForShell() { + if (latestProjects.length || loadingProjectsForShell) return; + + loadingProjectsForShell = true; + + sdk.forConsole.projects + .list([ + Query.equal('teamId', data.currentOrgId), + Query.limit(5), + Query.orderDesc('$updatedAt') + ]) + .then((res) => { + latestProjects = res.projects ?? []; + for (const project of latestProjects) { + project.region ??= 'default'; + } + }) + .finally(() => (loadingProjectsForShell = false)); + } + + $: isAssistantEnabled = $consoleVariables?._APP_ASSISTANT_ENABLED === true; + + $: loadedProjects = latestProjects.map((project) => { return { name: project?.name, $id: project.$id, @@ -262,6 +311,11 @@ rank: -1 } ]); + + loadProjectsForShell(); + + loadConsoleVariables(); + onMount(async () => { loading.set(false); if (!localStorage.getItem('feedbackElapsed')) { diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index 249914fc8..eb0de2235 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -1,71 +1,53 @@ -import { Dependencies } from '$lib/constants'; -import type { Plan } from '$lib/sdk/billing'; -import type { Tier } from '$lib/stores/billing'; import { sdk } from '$lib/stores/sdk'; import { isCloud } from '$lib/system'; import type { LayoutLoad } from './$types'; -import { Query, type Models } from '@appwrite.io/console'; +import { Dependencies } from '$lib/constants'; +import type { Tier } from '$lib/stores/billing'; +import type { Plan, PlanList } from '$lib/sdk/billing'; -export const load: LayoutLoad = async ({ params, fetch, depends, parent }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { await parent(); depends(Dependencies.RUNTIMES); depends(Dependencies.CONSOLE_VARIABLES); depends(Dependencies.ORGANIZATION); - const prefs = await sdk.forConsole.account.getPrefs(); + const [preferences, plansArray, organizations] = await loadPromises(); - const { endpoint, project } = sdk.forConsole.client.config; - const versionPromise = fetch(`${endpoint}/health/version`, { - headers: { - 'X-Appwrite-Project': project - } - }).then((response) => response.json() as { version?: string }); + const plansInfo = toPlanMap(plansArray); - const variablesPromise = sdk.forConsole.console.variables(); - - const [data, variables] = await Promise.all([versionPromise, variablesPromise]); - - let plansInfo = new Map(); - if (isCloud) { - const plansArray = await sdk.forConsole.billing.getPlansInfo(); - plansInfo = plansArray.plans.reduce((map, plan) => { - map.set(plan.$id as Tier, plan); - return map; - }, new Map()); - } - - const organizations = !isCloud - ? await sdk.forConsole.teams.list() - : await sdk.forConsole.billing.listOrganization(); - - let projects: Models.Project[] = []; - let currentOrgId = params.organization ? params.organization : prefs.organization; - - if (!currentOrgId && organizations.teams.length > 0) { - currentOrgId = organizations.teams[0].$id; - } - if (currentOrgId) { - const orgProjects = await sdk.forConsole.projects.list([ - Query.equal('teamId', currentOrgId), - Query.limit(5), - Query.orderDesc('$updatedAt') - ]); - projects = orgProjects.projects.length > 0 ? orgProjects.projects : []; - } - - // set `default` if no region! - for (const project of projects) { - project.region ??= 'default'; - } + const currentOrgId = + params.organization ?? + preferences.organization ?? + (organizations.teams.length > 0 ? organizations.teams[0].$id : undefined); return { - consoleVariables: variables, - version: data?.version ?? null, plansInfo, roles: [], scopes: [], - projects: projects, - currentProjectId: params.project ?? '', - organizations: organizations + preferences, + currentOrgId, + organizations, + currentProjectId: params.project ?? '' }; }; + +async function loadPromises() { + return Promise.all([ + sdk.forConsole.account.getPrefs(), + isCloud ? sdk.forConsole.billing.getPlansInfo() : Promise.resolve(null), + isCloud ? sdk.forConsole.billing.listOrganization() : sdk.forConsole.teams.list() + ]); +} + +function toPlanMap(plansArray: PlanList | null): Map { + const map = new Map(); + if (!plansArray?.plans.length) return map; + + const plans = plansArray.plans; + for (let i = 0; i < plans.length; i++) { + const plan = plans[i]; + map.set(plan.$id as Tier, plan); + } + + return map; +} diff --git a/src/routes/(console)/organization-[organization]/+layout.ts b/src/routes/(console)/organization-[organization]/+layout.ts index bda0a6d41..3d944a93e 100644 --- a/src/routes/(console)/organization-[organization]/+layout.ts +++ b/src/routes/(console)/organization-[organization]/+layout.ts @@ -10,12 +10,13 @@ import { headerAlert } from '$lib/stores/headerAlert'; import ProjectsAtRisk from '$lib/components/billing/alerts/projectsAtRisk.svelte'; import { get } from 'svelte/store'; import { preferences } from '$lib/stores/preferences'; -import type { Organization } from '$lib/stores/organization'; import { defaultRoles, defaultScopes } from '$lib/constants'; import type { Plan } from '$lib/sdk/billing'; import { loadAvailableRegions } from '$routes/(console)/regions'; -export const load: LayoutLoad = async ({ params, depends }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { + const { preferences: prefs } = await parent(); + depends(Dependencies.ORGANIZATION); depends(Dependencies.MEMBERS); depends(Dependencies.PAYMENT_METHODS); @@ -41,20 +42,18 @@ export const load: LayoutLoad = async ({ params, depends }) => { } } } - const prefs = await sdk.forConsole.account.getPrefs(); if (prefs.organization !== params.organization) { const newPrefs = { ...prefs, organization: params.organization }; - sdk.forConsole.account.updatePrefs(newPrefs); + void sdk.forConsole.account.updatePrefs(newPrefs); } const [organization, members] = await Promise.all([ - sdk.forConsole.teams.get(params.organization) as Promise, + sdk.forConsole.teams.get(params.organization), sdk.forConsole.teams.listMemberships(params.organization), - preferences.loadTeamPrefs(params.organization) + preferences.loadTeamPrefs(params.organization), + loadAvailableRegions(params.organization) ]); - await loadAvailableRegions(params.organization); - return { header: Header, breadcrumbs: Breadcrumbs, @@ -65,9 +64,8 @@ export const load: LayoutLoad = async ({ params, depends }) => { scopes }; } catch (e) { - const prefs = await sdk.forConsole.account.getPrefs(); const newPrefs = { ...prefs, organization: null }; - sdk.forConsole.account.updatePrefs(newPrefs); + void sdk.forConsole.account.updatePrefs(newPrefs); error(e.code, e.message); } }; diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index 19090c22a..8e2e6f725 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -155,7 +155,8 @@ {@const platforms = filterPlatforms( project.platforms.map((platform) => getPlatformInfo(platform.type)) )} - + {project?.platforms?.length ? project?.platforms?.length : 'No'} apps diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index d9a79cbc1..0c4952633 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -11,16 +11,18 @@ import { get } from 'svelte/store'; import { headerAlert } from '$lib/stores/headerAlert'; import PaymentFailed from '$lib/components/billing/alerts/paymentFailed.svelte'; import { loadAvailableRegions } from '$routes/(console)/regions'; -import type { Organization } from '$lib/stores/organization'; +import { roles as rolesStore, scopes as scopesStore } from '$lib/stores/roles'; +import { organization as orgStore, type Organization } from '$lib/stores/organization'; export const load: LayoutLoad = async ({ params, depends }) => { depends(Dependencies.PROJECT); let currentPlan: Plan = null; + const orgFromStore: Organization = get(orgStore); try { const project = await sdk.forConsole.projects.get(params.project); const [organization, prefs, regionalConsoleVariables, _] = await Promise.all([ - sdk.forConsole.teams.get(project.teamId) as Promise, + orgFromStore ? Promise.resolve(orgFromStore) : sdk.forConsole.teams.get(project.teamId), sdk.forConsole.account.getPrefs(), sdk.forConsoleIn(project.region).console.variables(), loadAvailableRegions(project.teamId) @@ -32,11 +34,18 @@ export const load: LayoutLoad = async ({ params, depends }) => { }); } await preferences.loadTeamPrefs(project.teamId); + + // get values from saved stores if available! + const [rolesStoreValue, scopesStoreValue] = [get(rolesStore), get(scopesStore)]; + const areBothAvailable = !!(rolesStoreValue.length && scopesStoreValue.length); + let roles = isCloud ? [] : defaultRoles; let scopes = isCloud ? [] : defaultScopes; if (isCloud) { currentPlan = await sdk.forConsole.billing.getOrganizationPlan(project.teamId); - const res = await sdk.forConsole.billing.getRoles(project.teamId); + const res = areBothAvailable + ? { roles: rolesStoreValue, scopes: scopesStoreValue } + : await sdk.forConsole.billing.getRoles(project.teamId); roles = res.roles; scopes = res.scopes; if (scopes.includes('billing.read')) { diff --git a/src/routes/(console)/project-[region]-[project]/overview/+page.ts b/src/routes/(console)/project-[region]-[project]/overview/+page.ts index d53943a64..c88763a2e 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/overview/+page.ts @@ -5,9 +5,8 @@ import type { PageLoad } from './$types'; import { selectedTab } from './store'; export const load: PageLoad = async ({ params }) => { - if (get(selectedTab) === 'keys') { - redirect(302, `${base}/project-${params.region}-${params.project}/overview/keys`); - } else { - redirect(302, `${base}/project-${params.region}-${params.project}/overview/platforms`); - } + const tab = get(selectedTab); + const subroute = ['keys', 'dev-keys'].includes(tab) ? tab : 'platforms'; + + redirect(302, `${base}/project-${params.region}-${params.project}/overview/${subroute}`); }; diff --git a/src/routes/(console)/store.ts b/src/routes/(console)/store.ts index 7735d4c29..a02f4faf7 100644 --- a/src/routes/(console)/store.ts +++ b/src/routes/(console)/store.ts @@ -4,11 +4,9 @@ import type { Organization } from '$lib/stores/organization'; import type { Models } from '@appwrite.io/console'; import { derived, writable } from 'svelte/store'; -export const version = derived(page, ($page) => $page.data.version as string | null); -export const consoleVariables = derived( - page, - ($page) => $page.data.consoleVariables as Models.ConsoleVariables -); +export const version = writable(null); +export const consoleVariables = writable(undefined); + export const protocol = derived(page, ($page) => ($page.data.consoleVariables as Models.ConsoleVariables)?._APP_OPTIONS_FORCE_HTTPS === 'enabled' ? 'https://'