Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions src/lib/helpers/buildTimeout.ts
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 5 additions & 0 deletions src/lib/sdk/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,14 @@ export type OrganizationUsage = {
databasesReads: Array<Models.Metric>;
databasesWrites: Array<Models.Metric>;
imageTransformations: Array<Models.Metric>;
screenshotsGenerated: Array<Models.Metric>;
executionsTotal: number;
filesStorageTotal: number;
buildsStorageTotal: number;
databasesReadsTotal: number;
databasesWritesTotal: number;
imageTransformationsTotal: number;
screenshotsGeneratedTotal: number;
deploymentsStorageTotal: number;
executionsMBSecondsTotal: number;
buildsMBSecondsTotal: number;
Expand All @@ -316,6 +318,7 @@ export type OrganizationUsage = {
authPhoneTotal: number;
authPhoneEstimate: number;
imageTransformations: number;
screenshotsGenerated: number;
}>;
authPhoneTotal: number;
authPhoneEstimate: number;
Expand Down Expand Up @@ -384,6 +387,7 @@ export type Plan = {
bandwidth: number;
storage: number;
imageTransformations: number;
screenshotsGenerated: number;
webhooks: number;
users: number;
teams: number;
Expand Down Expand Up @@ -411,6 +415,7 @@ export type Plan = {
databasesWrites: AdditionalResource;
GBHours: AdditionalResource;
imageTransformations: AdditionalResource;
screenshotsGenerated: AdditionalResource;
};
addons: {
seats: PlanAddon;
Expand Down
10 changes: 10 additions & 0 deletions src/lib/sdk/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
3 changes: 2 additions & 1 deletion src/lib/stores/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
),
Expand Down Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,53 @@
</svelte:fragment>
</CardGrid>

<CardGrid gap="none">
<svelte:fragment slot="title">Screenshots generated</svelte:fragment>
The total number of screenshots generated across all projects in your organization.
<svelte:fragment slot="aside">
{#if data.organizationUsage.screenshotsGeneratedTotal}
{@const current = data.organizationUsage.screenshotsGeneratedTotal}
{@const max = getServiceLimit('screenshotsGenerated', tier, plan)}
<ProgressBarBig
currentUnit="Screenshots"
currentValue={formatNum(current)}
maxValue={max ? `/ ${formatNum(max)}` : undefined}
progressValue={current}
progressMax={max}
showBar={false} />
<BarChart
options={{
yAxis: {
axisLabel: {
formatter: formatNum
}
}
}}
series={[
{
name: 'Screenshots generated',
data: [
...(data.organizationUsage.screenshotsGenerated ?? []).map((e) => [
e.date,
e.value
])
]
}
]} />
{#if projects?.length > 0}
<ProjectBreakdown {projects} metric="screenshotsGenerated" {usageProjects} />
{/if}
{:else}
<Card isDashed>
<Layout.Stack gap="xs" alignItems="center" justifyContent="center">
<Icon icon={IconChartSquareBar} size="l" />
<Typography.Text variant="m-600">No data to show</Typography.Text>
</Layout.Stack>
</Card>
{/if}
</svelte:fragment>
</CardGrid>

<CardGrid gap="none">
<svelte:fragment slot="title">Executions</svelte:fragment>
Calculated for all functions that are executed in all projects in your organization.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
| 'authPhoneTotal'
| 'databasesReads'
| 'databasesWrites'
| 'imageTransformations';
| 'imageTransformations'
| 'screenshotsGenerated';

type Estimate = 'authPhoneEstimate';

Expand Down Expand Up @@ -89,6 +90,7 @@

switch (metric) {
case 'imageTransformations':
case 'screenshotsGenerated':
case 'authPhoneTotal':
return formatNumberWithCommas(value);
case 'executions':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -36,6 +38,9 @@
footer?: Snippet;
} = $props();

let effectiveStatus = $derived(
getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
);
let totalSize = $derived(humanFileSize(deployment?.totalSize ?? 0));
</script>

Expand Down Expand Up @@ -122,11 +127,11 @@
</Layout.Stack>

<Layout.Stack direction="row" gap="xl">
{#if deployment.status === 'failed'}
{#if effectiveStatus === 'failed'}
<Layout.Stack gap="xxs" inline>
{@render titleSnippet('Status')}
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
<Status status={deployment.status} label={deployment.status} />
<Status status={effectiveStatus} label={effectiveStatus} />
</Typography.Text>
</Layout.Stack>
{:else}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
)
);
Comment on lines +43 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Timeout logic never re-runs

Same as in logsTimer.svelte: the derived effectiveStatus has no time-based dependency, so once the component mounts it never re-checks the timeout and deployments stuck in building/processing never graduate to 'failed'. Please introduce a ticking dependency (e.g. a $state updated on an interval or a SvelteDate) so the timeout calculation re-executes after the configured threshold.

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);
}
}
Expand All @@ -78,7 +87,7 @@
<DeploymentCard proxyRuleList={data.proxyRuleList} deployment={data.deployment}>
{#snippet footer()}
<Layout.Stack direction="row" alignItems="center" inline>
{#if data.deployment.status === 'processing' || data.deployment.status === 'building' || data.deployment.status === 'waiting'}
{#if effectiveStatus === 'processing' || effectiveStatus === 'building' || effectiveStatus === 'waiting'}
<Button
text
on:click={() => {
Expand Down Expand Up @@ -152,9 +161,9 @@
<Card.Base padding="s">
<Accordion
title="Deployment logs"
badge={capitalize(data.deployment.status)}
badge={capitalize(effectiveStatus)}
open
badgeType={badgeTypeDeployment(data.deployment.status)}
badgeType={badgeTypeDeployment(effectiveStatus)}
hideDivider>
<Layout.Stack gap="xl">
{#key data.deployment.buildLogs}
Expand All @@ -167,7 +176,7 @@

<svelte:fragment slot="end">
<Layout.Stack direction="row" alignItems="center" inline>
{#if ['processing', 'building'].includes(data.deployment.status)}
{#if ['processing', 'building'].includes(effectiveStatus)}
<Typography.Code color="--fgcolor-neutral-secondary">
<Layout.Stack direction="row" alignItems="center" inline>
<p use:timer={{ start: data.deployment.$createdAt }}></p>
Expand Down
Loading
Loading