Skip to content
Merged
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
4 changes: 2 additions & 2 deletions bun.lock

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@6be9e62",
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@ed09983",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3",
"@appwrite.io/pink-legacy": "^1.0.3",
Expand Down
2 changes: 2 additions & 0 deletions src/lib/actions/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ export enum Submit {
ProjectService = 'submit_project_service',
ProjectUpdateSMTP = 'submit_project_update_smtp',
ProjectUpdateOAuth2Server = 'submit_project_update_oauth2_server',
ProjectUsageExecutionsBreakdown = 'submit_project_usage_executions_breakdown',
ProjectResume = 'submit_project_resume',
MemberCreate = 'submit_member_create',
MemberDelete = 'submit_member_delete',
Expand All @@ -283,6 +284,7 @@ export enum Submit {
AuthCorporateEmailsUpdate = 'submit_auth_corporate_emails_update',
AuthSessionAlertsUpdate = 'submit_auth_session_alerts_update',
AuthMembershipPrivacyUpdate = 'submit_auth_membership_privacy_update',
AuthMfaFactorsUpdate = 'submit_auth_mfa_factors_update',
AuthMockNumbersUpdate = 'submit_auth_mock_numbers_update',
AuthInvalidateSession = 'submit_auth_invalidate_session',
SessionsLengthUpdate = 'submit_sessions_length_update',
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/csvImportBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@
if (importData.source.toLowerCase() !== 'csv') return;

const status = importData.status;
const resourceId = importData.resourceId ?? '';
const [databaseId, tableId] = resourceId.split(':') ?? [];
const databaseId = importData.parentResourceId ?? '';
const tableId = importData.resourceId ?? '';

const current = importItems.get(importData.$id);
let tableName = current?.table ?? null;
Expand Down
3 changes: 3 additions & 0 deletions src/lib/helpers/oauth2-cimd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function cimdDocumentToApp(clientId: string, document: unknown): Models.A
: [],
tagline: '',
tags: [],
labels: [],
images: [],
supportUrl: '',
dataDeletionUrl: '',
Expand All @@ -83,6 +84,8 @@ export function cimdDocumentToApp(clientId: string, document: unknown): Models.A
deviceFlow: Array.isArray(doc.grant_types) && doc.grant_types.includes(DEVICE_GRANT_TYPE),
teamId: '',
userId: '',
installationScopes: [],
installationRedirectUrl: '',
secrets: []
};
}
Expand Down
68 changes: 67 additions & 1 deletion src/lib/sdk/usage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import type { Models } from '@appwrite.io/console';
import {
Query,
UsageEventDimension,
UsageEventMetric,
UsageOrderBy,
UsageOrderDirection,
type Models
} from '@appwrite.io/console';
import { sdk } from '$lib/stores/sdk';

export function accumulateUsage(usage: Models.Metric[], base: number): Models.Metric[] {
const accumulation = usage.reduce(
Expand All @@ -18,6 +26,64 @@ export function accumulateUsage(usage: Models.Metric[], base: number): Models.Me
return accumulation.metrics;
}

export type ExecutionsBreakdown = {
resourceId: string;
name?: string;
value: number;
};

const executionsBreakdownLimit = 25;

/**
* `UsageProject.executionsBreakdown` was dropped in SDK 16; the per-resource split now comes from
* the dimensional usage API, which returns bare resource IDs, so names are resolved separately.
*
* Scoped to `functions.executions` rather than the umbrella `executions` metric, which also counts
* site executions — those resolve to no name here and their rows link to a function that does not exist.
* Omitting `interval` makes each point a whole-window aggregate per resource, so the limit is a
* top-N-by-total rather than a truncation of the underlying data.
*/
export async function listExecutionsBreakdown(
region: string,
projectId: string,
startAt: string,
endAt: string
): Promise<ExecutionsBreakdown[]> {
const project = sdk.forProject(region, projectId);

const events = await project.usage.listEvents({
metrics: [UsageEventMetric.FunctionsExecutions],
dimensions: [UsageEventDimension.ResourceId],
startAt,
endAt,
orderBy: UsageOrderBy.Value,
orderDir: UsageOrderDirection.Desc,
limit: executionsBreakdownLimit
});

const totals = new Map<string, number>();
for (const metric of events.metrics) {
for (const point of metric.points) {
if (!point.resourceId) continue;
totals.set(point.resourceId, (totals.get(point.resourceId) ?? 0) + point.value);
}
}

if (totals.size === 0) return [];

const resourceIds = [...totals.keys()];
const functions = await project.functions.list({
queries: [Query.equal('$id', resourceIds), Query.limit(resourceIds.length)]
});
const names = new Map(functions.functions.map((func) => [func.$id, func.name]));

return resourceIds.map((resourceId) => ({
resourceId,
name: names.get(resourceId),
value: totals.get(resourceId)
}));
}

export type Metric = {
/**
* The value of this metric at the timestamp.
Expand Down
7 changes: 4 additions & 3 deletions src/lib/stores/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
Backups,
Client,
Console,
Embeddings,
Functions,
Health,
Locale,
Messaging,
Migrations,
Expand All @@ -31,6 +31,7 @@ import {
Webhooks,
Realtime,
Organizations,
Usage,
VectorsDB
} from '@appwrite.io/console';
import { buildRegionalV1Endpoint } from '$lib/helpers/apiEndpoint';
Expand All @@ -54,7 +55,6 @@ function createConsoleSdk(client: Client) {
oauth2: new Oauth2(client),
avatars: new Avatars(client),
functions: new Functions(client),
health: new Health(client),
locale: new Locale(client),
projects: new Projects(client),
teams: new Teams(client),
Expand Down Expand Up @@ -115,7 +115,6 @@ const sdkForProject = {
avatars: new Avatars(clientProject),
backups: new Backups(clientProject),
functions: new Functions(clientProject),
health: new Health(clientProject),
locale: new Locale(clientProject),
messaging: new Messaging(clientProject),
project: new Project(clientProject),
Expand All @@ -131,6 +130,8 @@ const sdkForProject = {
tablesDB: new TablesDB(clientProject),
documentsDB: new DocumentsDB(clientProject),
vectorsDB: new VectorsDB(clientProject),
embeddings: new Embeddings(clientProject),
usage: new Usage(clientProject),
webhooks: new Webhooks(clientProject),
console: new Console(clientProject) // for suggestions API
};
Expand Down
2 changes: 1 addition & 1 deletion src/routes/(console)/account/payments/addressModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import type { Models } from '@appwrite.io/console';

export let show = false;
export let locale: Models.CloudLocale;
export let locale: Models.Locale;
export let organization: string = null;
export let countryList: Models.CountryList;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

export let data: PageData;

const locale: Models.CloudLocale = data.locale;
const locale: Models.Locale = data.locale;
const countryList: Models.CountryList = data.countryList;

let show = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import type { Models } from '@appwrite.io/console';

export let show = false;
export let locale: Models.CloudLocale;
export let locale: Models.Locale;
export let countryList: Models.CountryList;
export let selectedAddress: Models.BillingAddress;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
} from '@appwrite.io/pink-icons-svelte';
import type { Models } from '@appwrite.io/console';

export let locale: Models.CloudLocale;
export let locale: Models.Locale;
export let countryList: Models.CountryList;
export let organization: Models.Organization;
export let billingAddress: Models.BillingAddress;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import type { Models } from '@appwrite.io/console';

export let show = false;
export let locale: Models.CloudLocale;
export let locale: Models.Locale;
export let countryList: Models.CountryList;

let loading = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type { Models } from '@appwrite.io/console';

let show = false;
export let locale: Models.CloudLocale;
export let locale: Models.Locale;
export let countryList: Models.CountryList;
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import type { Models } from '@appwrite.io/console';

export let show = false;
export let locale: Models.CloudLocale;
export let locale: Models.Locale;
export let countryList: Models.CountryList;

let email = '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import UpdateSessionsLimit from './updateSessionsLimit.svelte';
import PasswordPolicies from './passwordPolicies.svelte';
import PasswordStrengthPolicy from './passwordStrengthPolicy.svelte';
import UpdateMfaFactors from './updateMfaFactors.svelte';
import SessionSecurity from './sessionSecurity.svelte';
import UpdateSignupEmailSecurity from './updateSignupEmailSecurity.svelte';
import { isCloud } from '$lib/system';
Expand All @@ -25,6 +26,9 @@
dictionaryPolicy={data.passwordDictionaryPolicy}
historyPolicy={data.passwordHistoryPolicy}
personalDataPolicy={data.passwordPersonalDataPolicy} />
{#if data.mfaFactorsPolicy}
<UpdateMfaFactors project={data.project} policy={data.mfaFactorsPolicy} />
{/if}
{#if isCloud}
<UpdateSignupEmailSecurity
project={data.project}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export const load: PageLoad = async ({ depends, params }) => {
) as Partial<Record<ProjectPolicyId | EmailPolicyId, ProjectPolicy>>;

return {
mfaFactorsPolicy: policiesById[ProjectPolicyId.Mfafactors] as
| Models.PolicyMfaFactors
| undefined,
membershipPrivacyPolicy: policiesById[
ProjectPolicyId.Membershipprivacy
] as Models.PolicyMembershipPrivacy,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { canWriteProjects } from '$lib/stores/roles';
import { sdk } from '$lib/stores/sdk';
import { Selector } from '@appwrite.io/pink-svelte';
import type { Models } from '@appwrite.io/console';

const {
project,
policy
}: {
project: Models.Project;
policy: Models.PolicyMfaFactors;
} = $props();

let totp = $state(policy.totp);
let email = $state(policy.email);
let phone = $state(policy.phone);
let custom = $state(policy.custom);

const isSubmitDisabled = $derived(
totp === policy.totp &&
email === policy.email &&
phone === policy.phone &&
custom === policy.custom
);

async function updateMfaFactors() {
try {
await sdk.forProject(project.region, project.$id).project.updateMFAFactorsPolicy({
totp,
email,
phone,
custom
});
await invalidate(Dependencies.PROJECT);
addNotification({
type: 'success',
message: 'Updated MFA factors'
});
trackEvent(Submit.AuthMfaFactorsUpdate);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AuthMfaFactorsUpdate);
}
}
</script>

<Form onSubmit={updateMfaFactors}>
<CardGrid>
<svelte:fragment slot="title">MFA factors</svelte:fragment>
Choose which factors your users can use to complete a multi-factor authentication challenge. Recovery
codes always remain available as a fallback.
<svelte:fragment slot="aside">
<Selector.Checkbox
id="mfaFactorTotp"
label="TOTP"
description="Time-based codes from an authenticator app"
disabled={!$canWriteProjects}
bind:checked={totp} />
<Selector.Checkbox
id="mfaFactorEmail"
label="Email"
description="Codes sent to the user's verified email address"
disabled={!$canWriteProjects}
bind:checked={email} />
<Selector.Checkbox
id="mfaFactorPhone"
label="Phone"
description="Codes sent to the user's verified phone number over SMS"
disabled={!$canWriteProjects}
bind:checked={phone} />
<Selector.Checkbox
id="mfaFactorCustom"
label="Custom"
description="Appwrite generates and verifies the code, and you deliver it through your own channel"
disabled={!$canWriteProjects}
bind:checked={custom} />
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={!$canWriteProjects || isSubmitDisabled} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { timeFromNow } from '$lib/helpers/date';
import type { PageLoad, RouteParams } from './$types';
import { isSelfHosted } from '$lib/system';
import { isCloud } from '$lib/system';
import { useDatabaseSdk } from '$database/(entity)';
import { toDatabaseType, useDatabaseSdk } from '$database/(entity)';

export const load: PageLoad = async ({ url, route, depends, params, parent }) => {
depends(Dependencies.DATABASES);
Expand Down Expand Up @@ -62,7 +62,7 @@ async function fetchDatabasesAndBackups(
databases.databases.map(async ({ $id, type }) => {
const res = await databaseSdk.listEntities({
databaseId: $id,
databaseType: type,
databaseType: toDatabaseType(type),
queries: [Query.limit(1), Query.orderDesc('')]
});

Expand Down
Loading
Loading