diff --git a/package.json b/package.json index 5a2e77773d..94dbeaf00e 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@0aa6399", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82b9a6afc8..0ca9702012 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.25.3)(zod@3.24.3) '@appwrite.io/console': - specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e - version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@0aa6399 + version: https://pkg.vc/-/@appwrite/@appwrite.io/console@0aa6399 '@appwrite.io/pink-icons': specifier: 0.25.0 version: 0.25.0 @@ -260,8 +260,8 @@ packages: '@analytics/type-utils@0.6.2': resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e': - resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@0aa6399': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@0aa6399} version: 1.10.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -3703,7 +3703,7 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e': {} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@0aa6399': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: diff --git a/src/lib/helpers/buildTimeout.ts b/src/lib/helpers/buildTimeout.ts new file mode 100644 index 0000000000..3915811c2e --- /dev/null +++ b/src/lib/helpers/buildTimeout.ts @@ -0,0 +1,45 @@ +import type { Models } from '@appwrite.io/console'; + +/** + * Checks if a build has exceeded the maximum build timeout duration + */ +function isBuildTimedOut(createdAt: string, status: string, timeoutSeconds: number): boolean { + if (!['waiting', 'processing', 'building'].includes(status)) { + return false; + } + + if (!timeoutSeconds || timeoutSeconds <= 0) { + return false; + } + + const created = new Date(createdAt); + const elapsedSeconds = Math.floor((Date.now() - created.getTime()) / 1000); + + return elapsedSeconds > timeoutSeconds; +} + +/** + * Gets the effective status for a build, considering timeout + */ +export function getEffectiveBuildStatus( + originalStatus: string, + createdAt: string, + consoleVariables: Models.ConsoleVariables | undefined +): string { + const timeoutSeconds = getBuildTimeoutSeconds(consoleVariables); + if (isBuildTimedOut(createdAt, originalStatus, timeoutSeconds)) { + return 'failed'; + } + return originalStatus; +} + +/** + * Helper to get timeout value from console variables + */ +function getBuildTimeoutSeconds(consoleVariables: Models.ConsoleVariables | undefined): number { + if (!consoleVariables?._APP_COMPUTE_BUILD_TIMEOUT) { + return 0; + } + const timeout = parseInt(String(consoleVariables._APP_COMPUTE_BUILD_TIMEOUT), 10); + return isNaN(timeout) ? 0 : timeout; +} diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index 0361f59101..9d3328f5fb 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -292,12 +292,14 @@ export type OrganizationUsage = { databasesReads: Array; databasesWrites: Array; imageTransformations: Array; + screenshotsGenerated: Array; executionsTotal: number; filesStorageTotal: number; buildsStorageTotal: number; databasesReadsTotal: number; databasesWritesTotal: number; imageTransformationsTotal: number; + screenshotsGeneratedTotal: number; deploymentsStorageTotal: number; executionsMBSecondsTotal: number; buildsMBSecondsTotal: number; @@ -316,6 +318,7 @@ export type OrganizationUsage = { authPhoneTotal: number; authPhoneEstimate: number; imageTransformations: number; + screenshotsGenerated: number; }>; authPhoneTotal: number; authPhoneEstimate: number; @@ -384,6 +387,7 @@ export type Plan = { bandwidth: number; storage: number; imageTransformations: number; + screenshotsGenerated: number; webhooks: number; users: number; teams: number; @@ -411,6 +415,7 @@ export type Plan = { databasesWrites: AdditionalResource; GBHours: AdditionalResource; imageTransformations: AdditionalResource; + screenshotsGenerated: AdditionalResource; }; addons: { seats: PlanAddon; diff --git a/src/lib/sdk/usage.ts b/src/lib/sdk/usage.ts index b446bfa50f..268f785234 100644 --- a/src/lib/sdk/usage.ts +++ b/src/lib/sdk/usage.ts @@ -332,4 +332,14 @@ export type UsageProject = { * Aggregated statistics of total number of image transformations. */ imageTransformationsTotal: number; + + /** + * Array of screenshots generated per period. + */ + screenshotsGenerated: Metric[]; + + /** + * Aggregated statistics of total number of screenshots generated. + */ + screenshotsGeneratedTotal: number; }; diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 46134dde33..d5af1fe764 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -150,7 +150,8 @@ export type PlanServices = | 'webhooks' | 'sites' | 'authPhone' - | 'imageTransformations'; + | 'imageTransformations' + | 'screenshotsGenerated'; export function getServiceLimit(serviceId: PlanServices, tier: Tier = null, plan?: Plan): number { if (!isCloud) return 0; diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index 6a4a43f01c..64907af63f 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -127,6 +127,9 @@ imageTransformations: projectData?.resources?.find( (resource) => resource.resourceId === 'imageTransformations' ), + screenshotsGenerated: projectData?.resources?.find( + (resource) => resource.resourceId === 'screenshotsGenerated' + ), bandwidth: projectData?.resources?.find( (resource) => resource.resourceId === 'bandwidth' ), @@ -280,6 +283,19 @@ ), maxValue: currentPlan?.imageTransformations }, + { + id: `screenshots-generated`, + cells: { + item: 'Screenshots generated', + usage: `${formatNum(project.screenshotsGenerated.value || 0)} / ${currentPlan?.screenshotsGenerated ? formatNum(currentPlan.screenshotsGenerated) : 'Unlimited'}`, + price: formatCurrency(project.screenshotsGenerated.amount || 0) + }, + progressData: createProgressData( + project.screenshotsGenerated.value || 0, + currentPlan?.screenshotsGenerated + ), + maxValue: currentPlan?.screenshotsGenerated + }, { id: `gb-hours`, cells: { diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte index a9422df60b..cd8399e4fe 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte @@ -322,6 +322,53 @@ + + Screenshots generated + The total number of screenshots generated across all projects in your organization. + + {#if data.organizationUsage.screenshotsGeneratedTotal} + {@const current = data.organizationUsage.screenshotsGeneratedTotal} + {@const max = getServiceLimit('screenshotsGenerated', tier, plan)} + + [ + e.date, + e.value + ]) + ] + } + ]} /> + {#if projects?.length > 0} + + {/if} + {:else} + + + + No data to show + + + {/if} + + + Executions Calculated for all functions that are executed in all projects in your organization. diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts index 8fd36fadfc..8143bcdc96 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts @@ -34,7 +34,9 @@ export const load: PageLoad = async ({ params, parent }) => { databasesReadsTotal: null, databasesWritesTotal: null, imageTransformations: null, - imageTransformationsTotal: null + imageTransformationsTotal: null, + screenshotsGenerated: null, + screenshotsGeneratedTotal: null } }; } diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte index 98a0282821..1772e6a4f5 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte @@ -16,7 +16,8 @@ | 'authPhoneTotal' | 'databasesReads' | 'databasesWrites' - | 'imageTransformations'; + | 'imageTransformations' + | 'screenshotsGenerated'; type Estimate = 'authPhoneEstimate'; @@ -89,6 +90,7 @@ switch (metric) { case 'imageTransformations': + case 'screenshotsGenerated': case 'authPhoneTotal': return formatNumberWithCommas(value); case 'executions': diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(components)/deploymentCard.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(components)/deploymentCard.svelte index 1add28d37b..2376107203 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(components)/deploymentCard.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(components)/deploymentCard.svelte @@ -15,6 +15,8 @@ import { DeploymentSource, DeploymentCreatedBy, DeploymentDomains } from '$lib/components/git'; import { func } from '../store'; import { capitalize } from '$lib/helpers/string'; + import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout'; + import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import { isCloud } from '$lib/system'; import { IconInfo } from '@appwrite.io/pink-icons-svelte'; import Link from '$lib/elements/link.svelte'; @@ -36,6 +38,9 @@ footer?: Snippet; } = $props(); + let effectiveStatus = $derived( + getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables) + ); let totalSize = $derived(humanFileSize(deployment?.totalSize ?? 0)); @@ -122,11 +127,11 @@ - {#if deployment.status === 'failed'} + {#if effectiveStatus === 'failed'} {@render titleSnippet('Status')} - + {:else} diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/deployment-[deployment]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/deployment-[deployment]/+page.svelte index 4a4f3970ec..9a9b47b2e2 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/deployment-[deployment]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/deployment-[deployment]/+page.svelte @@ -24,6 +24,8 @@ } from '@appwrite.io/pink-svelte'; import { capitalize } from '$lib/helpers/string'; import { formatTimeDetailed } from '$lib/helpers/timeConversion'; + import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout'; + import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import { timer } from '$lib/actions/timer'; import { app } from '$lib/stores/app'; import { IconDotsHorizontal, IconRefresh, IconTrash } from '@appwrite.io/pink-icons-svelte'; @@ -36,22 +38,29 @@ import { readOnly } from '$lib/stores/billing'; import RedeployModal from '../(modals)/redeployModal.svelte'; - export let data; + let { data } = $props(); - let showDelete = false; - let showCancel = false; - let showActivate = false; - let showRedeploy = false; + let effectiveStatus = $derived( + getEffectiveBuildStatus( + data.deployment.status, + data.deployment.$createdAt, + $regionalConsoleVariables + ) + ); + let showDelete = $state(false); + let showCancel = $state(false); + let showActivate = $state(false); + let showRedeploy = $state(false); onMount(() => { - return realtime.forProject(page.params.region, 'console', (response) => { + return realtime.forConsole(page.params.region, 'console', (message) => { if ( - response.events.includes( + message.events.includes( `functions.${page.params.function}.deployments.${page.params.deployment}.update` ) ) { - const payload = response.payload as Models.Deployment; - if (payload.status === 'ready') { + const payload = message.payload as Models.Deployment; + if (['ready', 'failed'].includes(payload.status)) { invalidate(Dependencies.DEPLOYMENT); } } @@ -78,7 +87,7 @@ {#snippet footer()} - {#if data.deployment.status === 'processing' || data.deployment.status === 'building' || data.deployment.status === 'waiting'} + {#if effectiveStatus === 'processing' || effectiveStatus === 'building' || effectiveStatus === 'waiting'} + {@const effectiveStatus = getEffectiveBuildStatus( + deployment.status, + deployment.$createdAt, + $regionalConsoleVariables + )} {#if !inCard}
@@ -70,7 +77,7 @@
Source is empty
{/if} - {#if deployment?.status === 'ready' && deployment?.$id !== activeDeployment} + {#if effectiveStatus === 'ready' && deployment?.$id !== activeDeployment} { @@ -82,7 +89,7 @@ Activate {/if} - {#if deployment?.status === 'ready' || deployment?.status === 'failed' || deployment?.status === 'building'} + {#if effectiveStatus === 'ready' || effectiveStatus === 'failed' || effectiveStatus === 'building'} @@ -112,7 +119,7 @@ {/if} - {#if deployment?.status === 'processing' || deployment?.status === 'building' || deployment.status === 'waiting'} + {#if effectiveStatus === 'processing' || effectiveStatus === 'building' || effectiveStatus === 'waiting'} import { capitalize } from '$lib/helpers/string'; import { app } from '$lib/stores/app'; + import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout'; + import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import type { Models } from '@appwrite.io/console'; import { Badge, Card, Layout, Logs, Spinner, Typography } from '@appwrite.io/pink-svelte'; import LogsTimer from './logsTimer.svelte'; @@ -38,15 +40,19 @@ emptyCopy?: string; } = $props(); + let effectiveStatus = $derived( + getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables) + ); + function setCopy() { - if (deployment.status === 'failed') { + if (effectiveStatus === 'failed') { return 'Your deployment has failed.'; - } else if (deployment.status === 'building') { + } else if (effectiveStatus === 'building') { //Do not remove empty space before the string it's an invisible character return 'Preparing for build ... \n'; - } else if (deployment.status === 'waiting') { + } else if (effectiveStatus === 'waiting') { return 'Preparing for build ... \n'; - } else if (deployment.status === 'processing') { + } else if (effectiveStatus === 'processing') { return 'Preparing for build ... \n'; } else { return emptyCopy; @@ -62,16 +68,16 @@ Deployment logs + type={badgeTypeDeployment(effectiveStatus)} /> - + {/if} - {#if ['waiting', 'processing'].includes(deployment.status) || (deployment.status === 'building' && !deployment?.buildLogs?.length)} + {#if ['waiting', 'processing'].includes(effectiveStatus) || (effectiveStatus === 'building' && !deployment?.buildLogs?.length)} Waiting for build to start... diff --git a/src/routes/(console)/project-[region]-[project]/sites/(components)/logsTimer.svelte b/src/routes/(console)/project-[region]-[project]/sites/(components)/logsTimer.svelte index 204ce91ec1..47bc28da3f 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/(components)/logsTimer.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/(components)/logsTimer.svelte @@ -1,16 +1,21 @@ - {#if ['processing', 'building'].includes(status)} + {#if ['processing', 'building'].includes(effectiveStatus)}

diff --git a/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte b/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte index 6bf425f7d7..78ad644f49 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte @@ -23,6 +23,8 @@ import { isCloud } from '$lib/system'; import { sdk } from '$lib/stores/sdk'; import { capitalize } from '$lib/helpers/string'; + import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout'; + import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import { regionalProtocol } from '$routes/(console)/project-[region]-[project]/store'; import type { Snippet } from 'svelte'; @@ -40,6 +42,9 @@ footer?: Snippet; } = $props(); + let effectiveStatus = $derived( + getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables) + ); let show = $state(false); const totalSize = $derived(humanFileSize(deployment?.totalSize ?? 0)); @@ -113,15 +118,15 @@
- {#if deployment.status === 'failed'} + {#if effectiveStatus === 'failed'} Status + status={effectiveStatus} + label={capitalize(effectiveStatus)} /> {:else} diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/deployment-[deployment]/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/deployment-[deployment]/+page.svelte index eb073c718d..6bf2062613 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/deployment-[deployment]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/deployment-[deployment]/+page.svelte @@ -13,6 +13,8 @@ import ActivateDeploymentModal from '../../../activateDeploymentModal.svelte'; import { Accordion, Tooltip } from '@appwrite.io/pink-svelte'; import { capitalize } from '$lib/helpers/string'; + import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout'; + import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import LogsTimer from '../../../(components)/logsTimer.svelte'; import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; @@ -24,6 +26,9 @@ let { data }: PageProps = $props(); let deployment = $derived(data.deployment); + let effectiveStatus = $derived( + getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables) + ); let showRedeploy = $state(false); let showActivate = $state(false); @@ -46,7 +51,7 @@ {#snippet footer()} - {#if deployment?.status === 'ready' && data.proxyRuleList?.total} + {#if effectiveStatus === 'ready' && data.proxyRuleList?.total}