mirror of
https://github.com/appwrite/console.git
synced 2026-04-07 19:17:46 +00:00
Merge branch 'main' into add-image-transformation-stats
This commit is contained in:
@@ -42,6 +42,7 @@ jobs:
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY }}"
|
||||
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
|
||||
"SENTRY_RELEASE=${{ github.event.release.tag_name }}"
|
||||
publish-cloud-stage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -24,12 +24,14 @@ ARG PUBLIC_APPWRITE_ENDPOINT
|
||||
ARG PUBLIC_GROWTH_ENDPOINT
|
||||
ARG PUBLIC_STRIPE_KEY
|
||||
ARG SENTRY_AUTH_TOKEN
|
||||
ARG SENTRY_RELEASE
|
||||
|
||||
ENV PUBLIC_APPWRITE_ENDPOINT=$PUBLIC_APPWRITE_ENDPOINT
|
||||
ENV PUBLIC_GROWTH_ENDPOINT=$PUBLIC_GROWTH_ENDPOINT
|
||||
ENV PUBLIC_CONSOLE_MODE=$PUBLIC_CONSOLE_MODE
|
||||
ENV PUBLIC_STRIPE_KEY=$PUBLIC_STRIPE_KEY
|
||||
ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN
|
||||
ENV SENTRY_RELEASE=$SENTRY_RELEASE
|
||||
ENV NODE_OPTIONS=--max_old_space_size=8192
|
||||
|
||||
RUN pnpm run sync && pnpm run build
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
{#if $isLoading || answer}
|
||||
<div class="content">
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
<div class="avatar is-size-x-small">{getInitials($user.name)}</div>
|
||||
<div class="avatar is-size-x-small">{getInitials($user.name || $user.email)}</div>
|
||||
<p class="u-opacity-75">{previousQuestion}</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-8 u-margin-block-start-24">
|
||||
@@ -229,7 +229,7 @@
|
||||
|
||||
<div class="footer" slot="footer">
|
||||
<div class="u-flex u-cross-center u-gap-4">
|
||||
<AvatarInitials size={32} name={$user.name} />
|
||||
<AvatarInitials size={32} name={$user.name || $user.email} />
|
||||
<form
|
||||
class="input-text-wrapper u-width-full-line"
|
||||
style="--amount-of-buttons: 1;"
|
||||
|
||||
@@ -50,6 +50,13 @@
|
||||
];
|
||||
|
||||
$: isFree = org.billingPlan === BillingPlan.FREE;
|
||||
|
||||
// equal or above means unlimited!
|
||||
$: getCorrectSeatsCountValue = (count: number): string | number => {
|
||||
// php int max is always larger than js
|
||||
const exceedsSafeLimit = count >= Number.MAX_SAFE_INTEGER;
|
||||
return exceedsSafeLimit ? 'Unlimited' : count || 0;
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal bind:show size="big" headerDivider={false} title="Usage rates">
|
||||
@@ -81,7 +88,7 @@
|
||||
<TableRow>
|
||||
<TableCellText title="resource">{usage.resource}</TableCellText>
|
||||
<TableCellText title="limit">
|
||||
{plan.addons.seats.limit || 0}
|
||||
{getCorrectSeatsCountValue(plan.addons.seats.limit)}
|
||||
</TableCellText>
|
||||
{#if !isFree}
|
||||
<TableCellText title="rate">
|
||||
|
||||
@@ -79,7 +79,9 @@
|
||||
on:cancel|preventDefault
|
||||
{style}>
|
||||
{#if show}
|
||||
<slot close={closeModal} />
|
||||
<div class="content">
|
||||
<slot close={closeModal} />
|
||||
</div>
|
||||
{/if}
|
||||
</dialog>
|
||||
|
||||
|
||||
@@ -12,13 +12,10 @@
|
||||
|
||||
export function getFlag(country: string, width: number, height: number, quality: number) {
|
||||
if (!isValueOfStringEnum(Flag, country)) return '';
|
||||
let flag = sdk.forConsole.avatars
|
||||
return sdk.forConsole.avatars
|
||||
.getFlag(country, width * 2, height * 2, quality)
|
||||
?.toString();
|
||||
flag?.includes('&project=')
|
||||
? (flag = flag.replace('&project=', '&mode=admin'))
|
||||
: flag + '&mode=admin';
|
||||
return flag;
|
||||
?.toString()
|
||||
?.replace('&project=console', '&mode=admin');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ export type Plan = {
|
||||
backupsEnabled: boolean;
|
||||
backupPolicies: number;
|
||||
emailBranding: boolean;
|
||||
supportsCredit: boolean;
|
||||
supportsCredits: boolean;
|
||||
};
|
||||
|
||||
export type PlanList = {
|
||||
|
||||
@@ -160,9 +160,9 @@
|
||||
</div>
|
||||
<div class="body-text-2">
|
||||
{runtimeDetail.name}
|
||||
{#if runtimeDetail.name.toLowerCase() === 'deno'}
|
||||
<span class="inline-tag">New</span>
|
||||
{/if}
|
||||
<!--{#if runtimeDetail.name.toLowerCase() === 'deno'}-->
|
||||
<!-- <span class="inline-tag">New</span>-->
|
||||
<!--{/if}-->
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
<svelte:fragment slot="aside">
|
||||
<BoxAvatar>
|
||||
<svelte:fragment slot="image">
|
||||
<AvatarInitials size={48} name={$user.name} />
|
||||
<AvatarInitials size={48} name={$user.name || $user.email} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<span class="u-bold u-trim-1" data-private>{$user.name}</span>
|
||||
<span class="u-bold u-trim-1" data-private>{$user.name || 'User'}</span>
|
||||
</svelte:fragment>
|
||||
</BoxAvatar>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
let name: string = null;
|
||||
|
||||
onMount(async () => {
|
||||
name ??= $user.name;
|
||||
name ??= $user.name ?? '';
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
|
||||
trackEvent(Submit.OrganizationCreate, {
|
||||
plan: tierToPlan(billingPlan)?.name,
|
||||
budget_cap_enabled: !!billingBudget,
|
||||
budget_cap_enabled: billingBudget !== null,
|
||||
members_invited: collaborators?.length
|
||||
});
|
||||
|
||||
|
||||
@@ -28,16 +28,16 @@
|
||||
import { ID, Region } from '@appwrite.io/console';
|
||||
import { openImportWizard } from '../project-[project]/settings/migrations/(import)';
|
||||
import { readOnly } from '$lib/stores/billing';
|
||||
import type { RegionList } from '$lib/sdk/billing';
|
||||
import { onMount } from 'svelte';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
|
||||
import { regions as regionsStore } from '$routes/(console)/organization-[organization]/store';
|
||||
|
||||
export let data;
|
||||
|
||||
let addOrganization = false;
|
||||
let showCreate = false;
|
||||
let addOrganization = false;
|
||||
|
||||
const getPlatformInfo = (platform: string) => {
|
||||
let name: string, icon: string;
|
||||
@@ -85,6 +85,7 @@
|
||||
if (isCloud) wizard.start(Create);
|
||||
else showCreate = true;
|
||||
}
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Create project',
|
||||
@@ -120,17 +121,16 @@
|
||||
}
|
||||
};
|
||||
|
||||
let regions: RegionList;
|
||||
onMount(async () => {
|
||||
if (isCloud) {
|
||||
regions = await sdk.forConsole.billing.listRegions();
|
||||
const regions = await sdk.forConsole.billing.listRegions();
|
||||
regionsStore.set(regions);
|
||||
checkPricingRefAndRedirect($page.url.searchParams);
|
||||
}
|
||||
});
|
||||
|
||||
function findRegion(project: Models.Project) {
|
||||
const region = regions.regions.find((region) => region.$id === project.region);
|
||||
return region;
|
||||
return $regionsStore?.regions?.find((region) => region.$id === project.region);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
</Pill>
|
||||
{/if}
|
||||
<svelte:fragment slot="icons">
|
||||
{#if isCloud && regions}
|
||||
{#if isCloud && $regionsStore?.regions}
|
||||
{@const region = findRegion(project)}
|
||||
<span class="u-color-text-gray u-medium u-line-height-2">
|
||||
{region?.name}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Container } from '$lib/layout';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import BudgetAlert from './budgetAlert.svelte';
|
||||
import BudgetCap from './budgetCap.svelte';
|
||||
import PlanSummary from './planSummary.svelte';
|
||||
import BillingAddress from './billingAddress.svelte';
|
||||
@@ -128,7 +127,6 @@
|
||||
<BillingAddress billingAddress={data?.billingAddress} />
|
||||
<TaxId />
|
||||
<BudgetCap />
|
||||
<BudgetAlert />
|
||||
<AvailableCredit />
|
||||
</Container>
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let alertsEnabled = false;
|
||||
|
||||
let search: string;
|
||||
let selectedAlert: number;
|
||||
let alerts: number[] = [];
|
||||
@@ -74,7 +76,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: isButtonDisabled = symmetricDifference(alerts, $organization.budgetAlerts).length === 0;
|
||||
$: isButtonDisabled =
|
||||
symmetricDifference(alerts, $organization.budgetAlerts).length === 0 || !alertsEnabled;
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updateBudget}>
|
||||
@@ -107,6 +110,7 @@
|
||||
|
||||
<div class="u-flex u-gap-16">
|
||||
<InputSelectSearch
|
||||
disabled={!alertsEnabled}
|
||||
label="Percentage (%) of budget cap"
|
||||
placeholder="Select a percentage"
|
||||
id="alerts"
|
||||
@@ -118,7 +122,9 @@
|
||||
<div style="align-self: flex-end">
|
||||
<Button
|
||||
secondary
|
||||
disabled={alerts.length > 3 || (!search && !selectedAlert)}
|
||||
disabled={alerts.length > 3 ||
|
||||
(!search && !selectedAlert) ||
|
||||
!alertsEnabled}
|
||||
on:click={addAlert}>
|
||||
Add alert
|
||||
</Button>
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
import { organization, currentPlan } from '$lib/stores/organization';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import BudgetAlert from './budgetAlert.svelte';
|
||||
|
||||
let capActive = false;
|
||||
let budget: number;
|
||||
|
||||
onMount(() => {
|
||||
budget = $organization?.billingBudget;
|
||||
capActive = !!$organization?.billingBudget;
|
||||
capActive = $organization?.billingBudget !== null;
|
||||
});
|
||||
|
||||
async function updateBudget() {
|
||||
@@ -44,7 +45,7 @@
|
||||
}
|
||||
|
||||
$: if (!capActive) {
|
||||
budget = 0;
|
||||
budget = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -113,3 +114,5 @@
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<BudgetAlert alertsEnabled={capActive && budget > 0} />
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
</CollapsibleItem>
|
||||
{/if}
|
||||
|
||||
{#if currentPlan.supportsCredit && availableCredit > 0}
|
||||
{#if currentPlan.supportsCredits && availableCredit > 0}
|
||||
<CollapsibleItem noContent gap={4}>
|
||||
<span class="body-text-2 u-flex u-cross-center u-gap-2"
|
||||
><svg
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { logout } from '$lib/helpers/logout';
|
||||
import { checkForUsageLimit } from '$lib/stores/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { isCloud } from '$lib/system';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { checkForUsageLimit } from '$lib/stores/billing';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { logout } from '$lib/helpers/logout';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${selectedMember.userName} was deleted from ${selectedMember.teamName}`
|
||||
message: `${selectedMember.userName || 'User'} was deleted from ${selectedMember.teamName}`
|
||||
});
|
||||
trackEvent(Submit.MemberDelete);
|
||||
} catch (error) {
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
size={40}
|
||||
name={member.userName || member.userEmail} />
|
||||
<span class="text u-trim">
|
||||
{member.userName ? member.userName : 'n/a'}
|
||||
{member.userName || 'n/a'}
|
||||
</span>
|
||||
{#if member.invited && !member.joined}
|
||||
<Pill warning>Pending</Pill>
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import { page } from '$app/stores';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { derived } from 'svelte/store';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import type { RegionList } from '$lib/sdk/billing';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
export const regions = writable<RegionList | undefined>(undefined);
|
||||
export const regionFlagUrls = derived(regions, ($regions) => {
|
||||
if (!$regions?.regions?.length) return [];
|
||||
|
||||
return $regions?.regions?.map((region) => {
|
||||
return `${sdk.forConsole.client.config.endpoint}/avatars/flags/${region.flag}?width=80&height=60&quality=100&mode=admin`;
|
||||
});
|
||||
});
|
||||
|
||||
export const projects = derived(page, ($page) => $page.data?.projects as Models.ProjectList);
|
||||
|
||||
@@ -514,7 +514,6 @@
|
||||
class="link">pricing page</a
|
||||
>.
|
||||
</p>
|
||||
<p>You will not be charged for Phone OTPs before February 10th.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if data.organizationUsage.authPhoneTotal}
|
||||
<div class="u-flex u-main-space-between">
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@
|
||||
size={32}
|
||||
name={member.userName || member.userEmail} />
|
||||
<span class="text u-trim">
|
||||
{member.userName ? member.userName : member.userEmail}
|
||||
{member.userName || member.userEmail}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
@@ -4,13 +4,22 @@
|
||||
import { InputText, FormList } from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { createProject, regions } from './store';
|
||||
import { createProject } from './store';
|
||||
import { regionFlagUrls, regions } from '$routes/(console)/organization-[organization]/store';
|
||||
|
||||
let showCustomId = false;
|
||||
|
||||
sdk.forConsole.billing.listRegions().then(regions.set);
|
||||
if (!$regions?.regions) {
|
||||
sdk.forConsole.billing.listRegions().then(regions.set);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#each $regionFlagUrls as image}
|
||||
<link rel="preload" as="image" href={image} />
|
||||
{/each}
|
||||
</svelte:head>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Details</svelte:fragment>
|
||||
<FormList>
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { createProject, regions } from './store';
|
||||
import { createProject } from './store';
|
||||
import type { Region } from '$lib/sdk/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { page } from '$app/stores';
|
||||
import { regions } from '$routes/(console)/organization-[organization]/store';
|
||||
|
||||
let prefs: Models.Preferences;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { RegionList } from '$lib/sdk/billing';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const createProject = writable<{
|
||||
@@ -10,5 +9,3 @@ export const createProject = writable<{
|
||||
name: null,
|
||||
region: 'fra'
|
||||
});
|
||||
|
||||
export const regions = writable<RegionList | undefined>(undefined);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
@@ -39,7 +39,7 @@
|
||||
state="warning"
|
||||
headerDivider={false}>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>all of {$user.name}'s sessions?</b>
|
||||
Are you sure you want to delete <b>all of {$user.name || 'User'}'s sessions?</b>
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDeleteAll = false)}>Cancel</Button>
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { user } from './store';
|
||||
import { project } from '../../store';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { user } from './store';
|
||||
|
||||
export let showDelete = false;
|
||||
let error: string;
|
||||
@@ -37,7 +37,9 @@
|
||||
state="warning"
|
||||
headerDivider={false}
|
||||
bind:error>
|
||||
<p data-private>Are you sure you want to delete <b>{$user.name}</b> from '{$project.name}'?</p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>{$user.name || 'User'}</b> from '{$project.name}'?
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button secondary submit>Delete</Button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputText } from '$lib/elements/forms';
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
let userName: string = null;
|
||||
onMount(async () => {
|
||||
userName ??= $user.name;
|
||||
userName ??= $user.name ?? '';
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
|
||||
+5
@@ -11,6 +11,7 @@
|
||||
import { func } from '../store';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { Runtime } from '@appwrite.io/console';
|
||||
import { parseExpression } from 'cron-parser';
|
||||
|
||||
const functionId = $page.params.function;
|
||||
let functionSchedule: string = null;
|
||||
@@ -24,6 +25,10 @@
|
||||
if (!isValueOfStringEnum(Runtime, $func.runtime)) {
|
||||
throw new Error(`Invalid runtime: ${$func.runtime}`);
|
||||
}
|
||||
|
||||
// an error is shown if invalid.
|
||||
parseExpression(functionSchedule);
|
||||
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
$func.name,
|
||||
|
||||
@@ -485,7 +485,6 @@
|
||||
Calculated for all Phone OTP sent across your project. Resets at the start of each
|
||||
billing cycle.
|
||||
</p>
|
||||
<p>You will not be charged for Phone OTPs before February 10th.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if data.usage.authPhoneTotal}
|
||||
<div class="u-flex u-main-space-between">
|
||||
|
||||
Reference in New Issue
Block a user