Merge branch 'feat-pink-v2' of github.com:appwrite/console into feat-v2-regions

This commit is contained in:
Arman
2025-05-06 14:59:30 +02:00
36 changed files with 461 additions and 317 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { Card, Tooltip } from '@appwrite.io/pink-svelte';
import { Card, Tooltip, Icon } from '@appwrite.io/pink-svelte';
import { type ComponentProps } from 'svelte';
import type Selector from '@appwrite.io/pink-svelte/dist/card/Selector.svelte';
+9 -8
View File
@@ -32,6 +32,12 @@
let responseTab: 'logs' | 'errors' | 'headers' | 'body' = $state('logs');
let href = $derived(
product === 'site'
? 'https://appwrite.io/docs/products/sites/logs#log-details'
: 'https://appwrite.io/docs/products/functions/develop#logging'
);
onMount(() => {
if (selectedLog?.errors) {
responseTab = 'errors';
@@ -128,8 +134,8 @@
<Input.Helper state="default">
<span>
Missing headers? Check the <Link variant="muted" href="#" external>docs</Link> to
see the supported data and how to log it.
Missing headers? Check the <Link variant="muted" {href} external>docs</Link> to see
the supported data and how to log it.
</span>
</Input.Helper>
{:else}
@@ -146,12 +152,7 @@
Body data is not captured by Appwrite for your user's security and privacy. To
display body data in the Logs tab, use <InlineCode
code="context.log()"
size="s" />. <Link
external
href="https://appwrite.io/docs/products/functions/develop#logging"
variant="muted">
Learn more</Link
>.
size="s" />. <Link external {href} variant="muted">Learn more</Link>.
</Typography.Text>
</Card>
{/if}
+18 -8
View File
@@ -12,7 +12,8 @@
Button,
Layout,
Avatar,
Typography
Typography,
Badge
} from '@appwrite.io/pink-svelte';
import {
@@ -77,7 +78,7 @@
{ name: 'Functions', icon: IconLightningBolt, slug: 'functions', category: 'build' },
{ name: 'Messaging', icon: IconChatBubble, slug: 'messaging', category: 'build' },
{ name: 'Storage', icon: IconFolder, slug: 'storage', category: 'build' },
{ name: 'Sites', icon: IconGlobeAlt, slug: 'sites', category: 'deploy' }
{ name: 'Sites', icon: IconGlobeAlt, slug: 'sites', category: 'deploy', badge: 'New' }
];
</script>
@@ -208,14 +209,23 @@
on:click={() => {
trackEvent(`click_menu_${projectOption.slug}`);
sideBarIsOpen = false;
}}
><span class="link-icon"
><Icon icon={projectOption.icon} size="s" />
</span><span
}}>
<span class="link-icon">
<Icon icon={projectOption.icon} size="s" />
</span>
<span
class:no-text={state === 'icons'}
class:has-text={state === 'open'}
class="link-text">{projectOption.name}</span
></a>
class="link-text">
{projectOption.name}
{#if projectOption?.badge}
<Badge
variant="secondary"
content={projectOption.badge}
size="s" />
{/if}
</span>
</a>
<span slot="tooltip">{projectOption.name}</span>
</Tooltip>
{/each}
+6
View File
@@ -101,6 +101,12 @@ export function secsToUnit(time: number, unit: Unit) {
* @returns Formatted string like "3d 4h 30m 23s"
*/
export function formatTimeDetailed(time: number): string {
if (typeof time !== 'number' || time === undefined || time === null) {
return '-';
}
if (time <= 0) {
return '0s';
}
const seconds = Math.floor(time % 60);
const minutes = Math.floor((time / 60) % 60);
const hours = Math.floor((time / (60 * 60)) % 24);
@@ -103,11 +103,13 @@
Email templates <Badge variant="secondary" content="Experimental" />
</svelte:fragment>
Use templates to send and process account management emails.
<Link.Anchor href="https://appwrite.io/docs/advanced/platform/message-templates">
<Link.Anchor
target="_blank"
href="https://appwrite.io/docs/advanced/platform/message-templates">
Learn more
</Link.Anchor>
<svelte:fragment slot="aside">
<Layout.Stack>
<Layout.Stack gap="none">
<Accordion
title="Verification"
bind:open={emailVerificationOpen}
@@ -127,8 +129,12 @@
e.preventDefault();
openEmail('magicSession');
}}>
Send an email to users that sign in with a magic URL.
<EmailMagicUrlTemplate />
<Layout.Stack>
<Typography.Text>
Send an email to users that sign in with a magic URL.
</Typography.Text>
<EmailMagicUrlTemplate />
</Layout.Stack>
</Accordion>
<Accordion
title="OTP session"
@@ -137,8 +143,11 @@
e.preventDefault();
openEmail('otpSession');
}}>
Send an email to users that sign in with a email OTP.
<EmailMagicUrlTemplate />
<Layout.Stack>
<Typography.Text>
Send an email to users that sign in with a email OTP.</Typography.Text>
<EmailMagicUrlTemplate />
</Layout.Stack>
</Accordion>
<Accordion
title="Reset password"
@@ -147,8 +156,10 @@
e.preventDefault();
openEmail('recovery');
}}>
Send a recovery email to users that forget their password.
<EmailRecoveryTemplate />
<Layout.Stack>
<Typography.Text>
Send a recovery email to users that forget their password.</Typography.Text>
<EmailRecoveryTemplate /></Layout.Stack>
</Accordion>
<Accordion
title="Invite user"
@@ -157,8 +168,11 @@
e.preventDefault();
openEmail('invitation');
}}>
Send an invitation email to become a member of your project.
<EmailInviteTemplate />
<Layout.Stack>
<Typography.Text>
Send an invitation email to become a member of your project.</Typography.Text>
<EmailInviteTemplate />
</Layout.Stack>
</Accordion>
<Accordion
title="2FA verification"
@@ -167,8 +181,10 @@
e.preventDefault();
openEmail('mfaChallenge');
}}>
Send a two-factor authentication email to a user.
<Email2FaTemplate />
<Layout.Stack>
<Typography.Text>
Send a two-factor authentication email to a user.</Typography.Text>
<Email2FaTemplate /></Layout.Stack>
</Accordion>
<Accordion
title="Session alert"
@@ -177,8 +193,10 @@
e.preventDefault();
openEmail('sessionAlert');
}}>
Send an email to users when a new session is created.
<EmailSessionAlertTemplate />
<Layout.Stack>
<Typography.Text>
Send an email to users when a new session is created.</Typography.Text>
<EmailSessionAlertTemplate /></Layout.Stack>
</Accordion>
</Layout.Stack>
</svelte:fragment>
@@ -7,7 +7,7 @@
import { addNotification } from '$lib/stores/notifications';
import { Id } from '$lib/components';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Layout } from '@appwrite.io/pink-svelte';
import { Layout, Card } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
let locale = 'en';
@@ -35,11 +35,13 @@
}
</script>
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{otp}}'}>{'{{otp}}'}</Id>
</EmailTemplate>
</Layout.Stack>
<Card.Base variant="secondary" padding="s">
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{otp}}'}>{'{{otp}}'}</Id>
</EmailTemplate>
</Layout.Stack>
</Card.Base>
@@ -7,7 +7,7 @@
import { addNotification } from '$lib/stores/notifications';
import { Id } from '$lib/components';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Layout } from '@appwrite.io/pink-svelte';
import { Layout, Card } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
let locale = 'en';
@@ -35,12 +35,14 @@
}
</script>
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{team}}'}>{'{{team}}'}</Id>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{redirect}}'}>{'{{redirect}}'}</Id>
</EmailTemplate>
</Layout.Stack>
<Card.Base variant="secondary" padding="s">
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{team}}'}>{'{{team}}'}</Id>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{redirect}}'}>{'{{redirect}}'}</Id>
</EmailTemplate>
</Layout.Stack>
</Card.Base>
@@ -7,7 +7,7 @@
import { addNotification } from '$lib/stores/notifications';
import { Id } from '$lib/components';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Layout } from '@appwrite.io/pink-svelte';
import { Layout, Card } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
@@ -37,11 +37,13 @@
}
</script>
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{redirect}}'}>{'{{redirect}}'}</Id>
</EmailTemplate>
</Layout.Stack>
<Card.Base variant="secondary" padding="s">
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{redirect}}'}>{'{{redirect}}'}</Id>
</EmailTemplate>
</Layout.Stack>
</Card.Base>
@@ -7,7 +7,7 @@
import { addNotification } from '$lib/stores/notifications';
import { Id } from '$lib/components';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Layout } from '@appwrite.io/pink-svelte';
import { Layout, Card } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
@@ -36,11 +36,13 @@
}
</script>
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{redirect}}'}>{'{{redirect}}'}</Id>
</EmailTemplate>
</Layout.Stack>
<Card.Base variant="secondary" padding="s">
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{redirect}}'}>{'{{redirect}}'}</Id>
</EmailTemplate>
</Layout.Stack>
</Card.Base>
@@ -7,7 +7,7 @@
import { addNotification } from '$lib/stores/notifications';
import { Id } from '$lib/components';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Layout } from '@appwrite.io/pink-svelte';
import { Layout, Card } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
let locale = 'en';
@@ -35,13 +35,15 @@
}
</script>
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{device}}'}>{'{{device}}'}</Id>
<Id value={'{{ipAddress'}>{'{{ipAddress}}'}</Id>
<Id value={'{{country'}>{'{{country}}'}</Id>
</EmailTemplate>
</Layout.Stack>
<Card.Base variant="secondary" padding="s">
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{device}}'}>{'{{device}}'}</Id>
<Id value={'{{ipAddress'}>{'{{ipAddress}}'}</Id>
<Id value={'{{country'}>{'{{country}}'}</Id>
</EmailTemplate>
</Layout.Stack>
</Card.Base>
@@ -83,74 +83,72 @@
$: isSmtpEnabled = $project?.smtpEnabled;
</script>
<Card.Base variant="secondary" padding="s">
{#if loading}
<div
class="u-position-absolute u-width-full-line u-flex u-flex-vertical u-main-center u-cross-center u-gap-16 u-margin-block-start-32"
style="inset-inline-start: 0;">
<div class="loader"></div>
<p class="text">Loading template...</p>
</div>
{/if}
<div class:u-opacity-0={loading} style={loading ? 'pointer-events: none' : ''}>
<Form onSubmit={saveEmailTemplate}>
<Layout.Stack>
<InputText
id="senderName"
label="Sender name"
bind:value={$emailTemplate.senderName}
placeholder="Enter sender name"
disabled={!isSmtpEnabled} />
<InputEmail
bind:value={$emailTemplate.senderEmail}
id="senderEmail"
label="Sender email"
placeholder="Enter sender email"
disabled={!isSmtpEnabled} />
<InputEmail
bind:value={$emailTemplate.replyTo}
id="replyTo"
label="Reply to"
placeholder="noreply@appwrite.io" />
{#if $$slots.default}
<p class="text">
Click to copy variables for the fields below. Learn more <a
class="link"
href="https://appwrite.io/docs/advanced/platform/message-templates"
>here</a
>.
</p>
<Layout.Stack direction="row">
<slot />
</Layout.Stack>
{/if}
<InputText
bind:value={$emailTemplate.subject}
id="subject"
label="Subject"
placeholder="Enter subject" />
<InputTextarea
bind:value={$emailTemplate.message}
id="message"
label="Message"
placeholder="Enter your message"
readonly={!isSmtpEnabled}>
<Tooltip slot="info" maxWidth="15rem">
<Icon icon={IconInfo} size="s" />
<Typography.Caption variant="400" slot="tooltip">
Set up an SMTP server to edit the message body
</Typography.Caption>
</Tooltip>
</InputTextarea>
</Layout.Stack>
<div class="u-sep-block-start u-margin-block-start-24"></div>
<div class="u-flex u-gap-16 u-main-end u-margin-block-start-24">
<Button on:click={() => (openResetModal = true)} text>Reset changes</Button>
<Button submit disabled={isButtonDisabled}>Update</Button>
</div>
</Form>
{#if loading}
<div
class="u-position-absolute u-width-full-line u-flex u-flex-vertical u-main-center u-cross-center u-gap-16 u-margin-block-start-32"
style="inset-inline-start: 0;">
<div class="loader"></div>
<p class="text">Loading template...</p>
</div>
</Card.Base>
{/if}
<div class:u-opacity-0={loading} style={loading ? 'pointer-events: none' : ''}>
<Form onSubmit={saveEmailTemplate}>
<Layout.Stack>
<InputText
id="senderName"
label="Sender name"
bind:value={$emailTemplate.senderName}
placeholder="Enter sender name"
disabled={!isSmtpEnabled} />
<InputEmail
bind:value={$emailTemplate.senderEmail}
id="senderEmail"
label="Sender email"
placeholder="Enter sender email"
disabled={!isSmtpEnabled} />
<InputEmail
bind:value={$emailTemplate.replyTo}
id="replyTo"
label="Reply to"
placeholder="noreply@appwrite.io" />
{#if $$slots.default}
<p class="text">
Click to copy variables for the fields below. Learn more <a
class="link"
href="https://appwrite.io/docs/advanced/platform/message-templates">here</a
>.
</p>
<Layout.Stack direction="row">
<slot />
</Layout.Stack>
{/if}
<InputText
bind:value={$emailTemplate.subject}
id="subject"
label="Subject"
placeholder="Enter subject" />
<InputTextarea
bind:value={$emailTemplate.message}
id="message"
label="Message"
placeholder="Enter your message"
readonly={!isSmtpEnabled}
rows={8}>
<Tooltip slot="info" maxWidth="15rem">
<Icon icon={IconInfo} size="s" />
<Typography.Caption variant="400" slot="tooltip">
Set up an SMTP server to edit the message body
</Typography.Caption>
</Tooltip>
</InputTextarea>
</Layout.Stack>
<div class="u-sep-block-start u-margin-block-start-24"></div>
<div class="u-flex u-gap-16 u-main-end u-margin-block-start-24">
<Button on:click={() => (openResetModal = true)} text>Reset changes</Button>
<Button submit disabled={isButtonDisabled}>Update</Button>
</div>
</Form>
</div>
<ResetEmail bind:show={openResetModal} />
@@ -7,7 +7,7 @@
import { Id } from '$lib/components';
import { addNotification } from '$lib/stores/notifications';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Layout } from '@appwrite.io/pink-svelte';
import { Layout, Card } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
@@ -36,11 +36,13 @@
}
</script>
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{redirect}}'}>{'{{redirect}}'}</Id>
</EmailTemplate>
</Layout.Stack>
<Card.Base variant="secondary" padding="s">
<Layout.Stack gap="l">
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{redirect}}'}>{'{{redirect}}'}</Id>
</EmailTemplate>
</Layout.Stack>
</Card.Base>
@@ -1,5 +1,5 @@
<script lang="ts">
import { Card, Input } from '@appwrite.io/pink-svelte';
import { Input } from '@appwrite.io/pink-svelte';
import { localeCodes } from './store';
export let value = 'en';
@@ -10,14 +10,12 @@
}));
</script>
<Card.Base variant="secondary" padding="s">
<Input.ComboBox
label="Template language"
on:change
id="language"
placeholder="Select a language"
name="locale"
bind:value
required
{options} />
</Card.Base>
<Input.ComboBox
label="Template language"
on:change
id="language"
placeholder="Select a language"
name="locale"
bind:value
required
{options} />
@@ -8,6 +8,7 @@
import { project } from '../../store';
import { loadEmailTemplate } from './+page.svelte';
import { baseEmailTemplate, emailTemplate } from './store';
import { Dialog, Layout, Alert } from '@appwrite.io/pink-svelte';
export let show = false;
@@ -44,15 +45,24 @@
}
</script>
<Modal onSubmit={reset} {error} bind:show>
<Dialog bind:open={show} title="Reset changes">
<svelte:fragment slot="title">Reset email template?</svelte:fragment>
<Layout.Stack>
<p class="text">
Are you sure you want to reset the email template?
<b>Default values will be set in all inputs.</b>
</p>
{#if error}
<Alert.Inline status="warning">{error}</Alert.Inline>
{/if}
</Layout.Stack>
<svelte:fragment slot="footer">
<Button text secondary on:click={() => (show = false)}>Cancel</Button>
<Button secondary submit>Reset</Button>
<Layout.Stack direction="row" gap="s" justifyContent="flex-end">
<Button text secondary on:click={() => (show = false)}>Cancel</Button>
<Button on:click={reset}>Reset</Button>
</Layout.Stack>
</svelte:fragment>
</Modal>
</Dialog>
@@ -324,7 +324,7 @@
variant="secondary"
padding="s"
--input-background-color="var(--bgcolor-neutral-primary)">
<div class="modal-form">
<div>
<div class="u-flex-vertical u-gap-24">
<div class="u-flex-vertical u-gap-4">
<Layout.Grid columns={4}>
@@ -127,7 +127,9 @@
<Modal title="Create Git deployment" bind:show onSubmit={createDeployment} bind:error>
<span slot="description">
Enter a valid commit reference to create a new deployment. <Link href="#">Learn more</Link>
Enter a valid commit reference to create a new deployment. <Link
href="https://appwrite.io/docs/products/functions/deployments#create-deployment"
external>Learn more</Link>
</span>
{#if hasRepository}
{#await load()}
@@ -3,7 +3,7 @@ import { Dependencies } from '$lib/constants.js';
import { isCloud } from '$lib/system';
import type { Domain, DomainsList } from '$lib/sdk/domains.js';
export const load = async ({ parent, depends, params }) => {
export const load = async ({ depends, params }) => {
depends(Dependencies.DOMAINS);
let domain: Domain;
@@ -33,5 +33,5 @@
</script>
<Confirm onSubmit={deleteProvider} title="Delete provider" bind:open={showDelete} bind:error>
Are you sure you want to delete <b>{$provider.name}</b> from '{$project.name}'?
<span>Are you sure you want to delete <b>{$provider.name}</b> from '{$project.name}'?</span>
</Confirm>
@@ -13,6 +13,7 @@
import { Typography } from '@appwrite.io/pink-svelte';
import { getProviderText } from '../../helper';
import { page } from '$app/state';
import { Layout } from '@appwrite.io/pink-svelte';
let enabled: boolean = null;
@@ -132,9 +133,9 @@
bind:value={enabled} />
</ul>
<div>
<p class="title">
Provider: <Provider noIcon provider={$providerData.provider} />
</p>
<Layout.Stack direction="row" gap="xs">
<span>Provider:</span><Provider noIcon provider={$providerData.provider} />
</Layout.Stack>
<p class="title">Type: {getProviderText($providerData.type)}</p>
<p>Created: {toLocaleDateTime($providerData.$createdAt)}</p>
</div>
@@ -25,8 +25,12 @@
APNSProviderParams
} from './store';
import TooltipPopover from './tooltipPopover.svelte';
import { Icon, Tooltip, Upload, Layout, Typography } from '@appwrite.io/pink-svelte';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import { removeFile } from '$lib/helpers/files';
export let files: Record<string, FileList>;
export let input: ProviderInput;
export let params: Partial<
| TwilioProviderParams
@@ -100,14 +104,40 @@
<TooltipPopover slot="info" {popover} {popoverProps} tooltip={input.tooltip} />
</InputPhone>
{:else if input.type === 'file'}
<InputFile
label={input.label}
allowedFileExtensions={input.allowedFileExtensions}
required={!input.optional}
tooltip={input.tooltip}
{popover}
{popoverProps}
bind:files={files[input.name]} />
<Upload.Dropzone
extensions={['json']}
bind:files={files[input.name]}
required={!input.optional}>
<Layout.Stack alignItems="center" gap="s">
<Layout.Stack alignItems="center" gap="s">
<Layout.Stack alignItems="center" justifyContent="center" direction="row" gap="s">
<Typography.Text variant="l-500">
Drag and drop service account JSON here or click to upload
</Typography.Text>
<Tooltip>
<Layout.Stack alignItems="center" justifyContent="center" inline>
<Icon icon={IconInfo} size="s" />
</Layout.Stack>
<svelte:fragment slot="tooltip">Only .json files allowed</svelte:fragment>
</Tooltip>
</Layout.Stack>
<Typography.Caption variant="400">Max file size 10MB</Typography.Caption>
</Layout.Stack>
</Layout.Stack>
</Upload.Dropzone>
{#if files[input.name]?.length}
<Upload.List
bind:files={files[input.name]}
on:remove={(e) => (files[input.name] = removeFile(e.detail, files[input.name]))} />
{/if}
<!-- <InputFile-->
<!-- label={input.label}-->
<!-- allowedFileExtensions={input.allowedFileExtensions}-->
<!-- required={!input.optional}-->
<!-- tooltip={input.tooltip}-->
<!-- {popover}-->
<!-- {popoverProps}-->
<!-- bind:files={files[input.name]} />-->
{:else if input.type === 'switch'}
<InputSwitch label={input.label} id={input.name} bind:value={params[input.name]}>
<svelte:fragment slot="description">
@@ -12,6 +12,14 @@
let showCustomId = false;
let id: string = null;
$: {
name;
id;
$provider;
beforeSubmit();
}
async function beforeSubmit() {
switch ($provider) {
case Providers.Twilio:
@@ -133,9 +141,6 @@
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Provider</svelte:fragment>
<div class="u-sep-block-start u-padding-block-end-32"></div>
<InputText
id="name"
label="Name"
@@ -16,9 +16,16 @@
import { getProviderText } from '../../helper';
let files: Record<string, FileList> = {};
const inputs = providers[$providerType].providers[$provider].configure;
$: inputs = providers[$providerType].providers[$provider].configure;
let open = false;
$: {
files;
$providerType;
$provider;
beforeSubmit();
}
onMount(() => {
const flattened = [];
for (const i of inputs) {
@@ -29,7 +36,11 @@
}
}
for (const input of flattened) {
if (input.type === 'file' && $providerParams[$provider][input.name].length > 0) {
if (
input.type === 'file' &&
$providerParams[$provider] &&
$providerParams[$provider][input.name].length > 0
) {
const dataTransfer = new DataTransfer();
const f = new File(
[$providerParams[$provider][input.name]],
@@ -138,7 +149,9 @@
</div>
</WizardStep>
<CreateMember bind:showCreate={$newMemberModal} />
{#if $newMemberModal}
<CreateMember bind:showCreate={$newMemberModal} />
{/if}
<style lang="scss">
.need-a-hand {
@@ -5,18 +5,21 @@
import { Button } from '$lib/elements/forms';
import Container from '$lib/layout/container.svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import { Card, Empty, Icon, Layout } from '@appwrite.io/pink-svelte';
import SearchQuery from '$lib/components/searchQuery.svelte';
import { Card, Empty, Icon } from '@appwrite.io/pink-svelte';
import { app } from '$lib/stores/app';
import { Click, trackEvent } from '$lib/actions/analytics';
import Table from './table.svelte';
import { ResponsiveContainerHeader } from '$lib/layout';
let { data } = $props();
</script>
<Container>
<Layout.Stack direction="row" justifyContent="space-between">
<SearchQuery placeholder="Search domain" />
<ResponsiveContainerHeader
hasSearch
hideView
searchPlaceholder="Search by domain"
analyticsSource="settings_domain_overview">
<Button
href={`${base}/project-${page.params.region}-${page.params.project}/settings/domains/add-domain`}
on:click={() => {
@@ -27,8 +30,7 @@
<Icon icon={IconPlus} size="s" />
Add domain
</Button>
</Layout.Stack>
</ResponsiveContainerHeader>
{#if data.rules.total}
<Table domains={data.rules} />
@@ -5,21 +5,27 @@
import { Wizard } from '$lib/layout';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Fieldset, Layout } from '@appwrite.io/pink-svelte';
import { Fieldset } from '@appwrite.io/pink-svelte';
import { goto, invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { writable } from 'svelte/store';
import { onMount } from 'svelte';
const routeBase = `${base}/project-${page.params.region}-${page.params.project}/settings/domains`;
//TODO: enable once combo box is fixed
// let { data } = $props();
let formComponent: Form;
let isSubmitting = $state(writable(false));
let showConnectRepo = $state(false);
let domainName = $state('');
onMount(() => {
if (page.url.searchParams.has('domain')) {
domainName = page.url.searchParams.get('domain');
}
});
async function addDomain() {
try {
const rule = await sdk
@@ -43,27 +49,12 @@
<Wizard title="Add domain" href={routeBase} column columnSize="s" confirmExit>
<Form bind:this={formComponent} onSubmit={addDomain} bind:isSubmitting>
<Layout.Stack gap="xxl">
<Fieldset legend="Domain">
<!-- <Input.ComboBox
label="Domain"
id="domain"
name="domain"
bind:value={domain}
options={data.domains.rules.map((domain) => ({
label: domain.domain,
value: domain.domain
}))}
required
placeholder="appwrite.example.com" /> -->
<InputDomain
label="Domain"
id="domain"
bind:value={domainName}
required
placeholder="appwrite.example.com" />
</Fieldset>
</Layout.Stack>
<InputDomain
label="Domain"
id="domain"
bind:value={domainName}
required
placeholder="appwrite.example.com" />
</Form>
<svelte:fragment slot="footer">
@@ -1,7 +1,14 @@
<script lang="ts">
import { IconGlobeAlt } from '@appwrite.io/pink-icons-svelte';
import { Card, Divider, Icon, Layout, Typography } from '@appwrite.io/pink-svelte';
import VerificationFieldset from './verificationFieldset.svelte';
import {
Card,
Divider,
Fieldset,
Icon,
Layout,
Tabs,
Typography
} from '@appwrite.io/pink-svelte';
import { Button, Form } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { organization } from '$lib/stores/organization';
@@ -13,21 +20,34 @@
import Wizard from '$lib/layout/wizard.svelte';
import { base } from '$app/paths';
import { writable } from 'svelte/store';
import { consoleVariables } from '$routes/(console)/store';
import NameserverTable from '$lib/components/domains/nameserverTable.svelte';
import RecordTable from '$lib/components/domains/recordTable.svelte';
let { data } = $props();
const ruleId = page.url.searchParams.get('rule');
const routeBase = `${base}/project-${page.params.region}-${page.params.project}/settings/domains`;
let selectedTab: 'cname' | 'nameserver' = $state();
let isSubDomain = $derived(page.params.domain?.split('.')?.length >= 3);
let selectedTab: 'cname' | 'nameserver' | 'a' | 'aaaa' = $state(
!!$consoleVariables._APP_DOMAIN_TARGET_CNAME && isSubDomain
? 'cname'
: !!$consoleVariables._APP_DOMAIN_TARGET_A
? 'a'
: !!$consoleVariables._APP_DOMAIN_TARGET_AAAA
? 'aaaa'
: 'nameserver'
);
let verified = $state(false);
let routeBase = `${base}/project-${page.params.region}-${page.params.project}/settings/domains`;
let isSubmitting = $state(writable(false));
async function verify() {
const isNewDomain =
data.domainsList.domains.findIndex((rule) => rule.domain === page.params.domain) === -1;
try {
if (selectedTab === 'cname') {
if (selectedTab !== 'nameserver') {
const ruleData = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification(ruleId);
@@ -46,9 +66,9 @@
});
await goto(routeBase);
await invalidate(Dependencies.DOMAINS);
await invalidate(Dependencies.SITES_DOMAINS);
} catch (error) {
verified = false;
isSubmitting.set(false);
addNotification({
type: 'error',
message: error.message
@@ -84,14 +104,58 @@
</Layout.Stack>
</Card.Base>
<VerificationFieldset domain={page.params.domain} {verified} bind:selectedTab>
<Divider />
<Layout.Stack direction="row" justifyContent="flex-end">
<Fieldset legend="Verification">
<Layout.Stack gap="xl">
<div>
<Button submit disabled={$isSubmitting}>Verify</Button>
<Tabs.Root variant="secondary" let:root>
{#if isSubDomain && !!$consoleVariables._APP_DOMAIN_TARGET_CNAME && $consoleVariables._APP_DOMAIN_TARGET_CNAME !== 'localhost'}
<Tabs.Item.Button
{root}
on:click={() => (selectedTab = 'cname')}
active={selectedTab === 'cname'}>
CNAME
</Tabs.Item.Button>
{/if}
{#if isCloud}
<Tabs.Item.Button
{root}
on:click={() => (selectedTab = 'nameserver')}
active={selectedTab === 'nameserver'}>
Nameservers
</Tabs.Item.Button>
{/if}
{#if !!$consoleVariables._APP_DOMAIN_TARGET_A && $consoleVariables._APP_DOMAIN_TARGET_A !== '127.0.0.1'}
<Tabs.Item.Button
{root}
on:click={() => (selectedTab = 'a')}
active={selectedTab === 'a'}>
A
</Tabs.Item.Button>
{/if}
{#if !!$consoleVariables._APP_DOMAIN_TARGET_AAAA && $consoleVariables._APP_DOMAIN_TARGET_AAAA !== '::1'}
<Tabs.Item.Button
{root}
on:click={() => (selectedTab = 'aaaa')}
active={selectedTab === 'aaaa'}>
AAAA
</Tabs.Item.Button>
{/if}
</Tabs.Root>
<Divider />
</div>
{#if selectedTab === 'nameserver'}
<NameserverTable domain={page.params.domain} {verified} />
{:else}
<RecordTable domain={page.params.domain} {verified} variant={selectedTab} />
{/if}
<Divider />
<Layout.Stack direction="row" justifyContent="flex-end">
<div>
<Button submit disabled={$isSubmitting}>Verify</Button>
</div>
</Layout.Stack>
</Layout.Stack>
</VerificationFieldset>
</Fieldset>
</Layout.Stack>
</Form>
</Wizard>
@@ -1,45 +0,0 @@
<script lang="ts">
import CnameTable from '$lib/components/domains/cnameTable.svelte';
import NameserverTable from '$lib/components/domains/nameserverTable.svelte';
import { isCloud, isSelfHosted } from '$lib/system';
import { Divider, Fieldset, Layout, Tabs } from '@appwrite.io/pink-svelte';
export let domain: string;
export let verified: boolean;
$: isSubDomain = domain?.split('.')?.length >= 3;
export let selectedTab: 'cname' | 'nameserver' =
isSubDomain || isSelfHosted ? 'cname' : 'nameserver';
</script>
<Fieldset legend="Verification">
<Layout.Stack gap="xl">
<div>
<Tabs.Root variant="secondary" let:root>
{#if isSubDomain}
<Tabs.Item.Button
{root}
on:click={() => (selectedTab = 'cname')}
active={selectedTab === 'cname'}>
CNAME
</Tabs.Item.Button>
{/if}
{#if isCloud}
<Tabs.Item.Button
{root}
on:click={() => (selectedTab = 'nameserver')}
active={selectedTab === 'nameserver'}>
Nameservers
</Tabs.Item.Button>
{/if}
</Tabs.Root>
<Divider />
</div>
{#if selectedTab === 'cname' && isSubDomain}
<CnameTable {domain} {verified} />
{:else if isCloud}
<NameserverTable {domain} {verified} />
{/if}
<slot />
</Layout.Stack>
</Fieldset>
@@ -24,7 +24,7 @@
await sdk
.forProject(page.params.region, page.params.project)
.proxy.deleteRule(selectedDomain.$id);
await invalidate(Dependencies.SITES_DOMAINS);
await invalidate(Dependencies.DOMAINS);
show = false;
addNotification({
type: 'success',
@@ -41,11 +41,7 @@
<Confirm title="Delete domain" bind:open={show} onSubmit={deleteDomain} bind:error confirmDeletion>
{#if selectedDomain}
<Typography.Text variant="m-400">
Are you sure you want to delete this domain? You will no longer be able to view your
site by visiting:
</Typography.Text>
<Typography.Text variant="m-500">
{selectedDomain.domain}
Are you sure you want to delete this domain?
</Typography.Text>
{/if}
</Confirm>
@@ -24,7 +24,7 @@
await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification(selectedDomain.$id);
await invalidate(Dependencies.SITES_DOMAINS);
await invalidate(Dependencies.DOMAINS);
show = false;
addNotification({
type: 'success',
@@ -5,7 +5,15 @@
import { protocol } from '$routes/(console)/store';
import type { Models } from '@appwrite.io/console';
import { IconDotsHorizontal, IconRefresh, IconTrash } from '@appwrite.io/pink-icons-svelte';
import { ActionMenu, Icon, Layout, Popover, Table, Typography } from '@appwrite.io/pink-svelte';
import {
ActionMenu,
Badge,
Icon,
Layout,
Popover,
Table,
Typography
} from '@appwrite.io/pink-svelte';
import DeleteDomainModal from './deleteDomainModal.svelte';
import RetryDomainModal from './retryDomainModal.svelte';
import { columns } from './store';
@@ -19,7 +27,6 @@
let showDelete = $state(false);
let showRetry = $state(false);
let selectedDomain: Models.ProxyRule = $state(null);
let showPreviewDomainModal = $state(false);
</script>
<Table.Root columns={[...$columns, { id: 'actions', width: 40 }]} let:root>
@@ -39,14 +46,15 @@
<Link external href={`${$protocol}${domain.domain}`} variant="quiet" icon>
<Typography.Text truncate>
{domain.domain}
{#if domain.status !== 'verified'}
<Badge
variant="secondary"
type="error"
content="Verification failed"
size="s" />
{/if}
</Typography.Text>
</Link>
{:else if column.id === 'redirectUrl'}
<!-- TODO design redirect status code -->
{domain?.redirectUrl || 'No redirect'}
{domain?.redirectStatusCode ? `(${domain.redirectStatusCode})` : ''}
{:else if column.id === 'deploymentVcsProviderBranch'}
{domain.deploymentVcsProviderBranch || '-'}
{/if}
</Table.Cell>
{/each}
@@ -69,7 +77,6 @@
<ActionMenu.Item.Button
leadingIcon={IconRefresh}
on:click={(e) => {
e.preventDefault();
selectedDomain = domain;
showRetry = true;
toggle(e);
@@ -81,12 +88,11 @@
status="danger"
leadingIcon={IconTrash}
on:click={(e) => {
e.preventDefault();
selectedDomain = domain;
showDelete = true;
toggle(e);
trackEvent(Click.DomainDeleteClick, {
source: 'sites_domain_overview'
source: 'settings_domain_overview'
});
}}>
Delete
@@ -122,7 +122,11 @@
{#if secretVariables?.length > 0}
<Alert.Inline status="info">
{secretVariables.length} secret variables are hidden from the editor. Their values will
remain unchanged. <Link href="#" variant="muted">Learn more</Link>.
remain unchanged. <Link
href="https://appwrite.io/docs/products/sites/develop#accessing-environment-variables"
external
variant="muted">Learn more</Link
>.
</Alert.Inline>
{/if}
<Layout.Stack gap="s">
@@ -132,7 +132,9 @@
<Modal title="Create Git deployment" bind:show onSubmit={createDeployment} bind:error>
<span slot="description">
Enter a valid commit reference to create a new deployment. <Link href="#">Learn more</Link>
Enter a valid commit reference to create a new deployment. <Link
href="https://appwrite.io/docs/products/sites/deployments#create-deployment"
external>Learn more</Link>
</span>
{#if hasRepository}
{#await load()}
@@ -7,6 +7,8 @@
import { onMount } from 'svelte';
import { page } from '$app/state';
import { UsageCard } from '$lib/components';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { formatTimeDetailed } from '$lib/helpers/timeConversion';
const now = new Date();
const rangeOptions = [
@@ -33,17 +35,17 @@
description: 'Total builds time'
},
{
id: 'avgTime',
id: 'buildsTimeAverage',
value: null,
description: 'Avg. builds time'
},
{
id: 'success',
id: 'buildsSuccessTotal',
value: null,
description: 'Successful'
},
{
id: 'failed',
id: 'buildsFailedTotal',
value: null,
description: 'Failed '
}
@@ -65,7 +67,21 @@
.forProject(page.params.region, page.params.project)
.sites.getUsage(page.params.site, range);
metrics = metrics.map((metric) => {
metric.value = usage[metric.id] ?? '-';
if (metric.id === 'buildsStorageTotal') {
const size = humanFileSize(usage[metric.id]);
metric.value = parseInt(size?.value) > 0 ? size.value + size.unit : '-';
} else if (
metric.id === 'buildsTimeTotal' ||
metric.id === 'buildsTimeAverage'
) {
const seconds = usage[metric.id] > 0 ? usage[metric.id] / 1000 : 0;
const time = formatTimeDetailed(seconds);
metric.value = time !== '0s' ? time : '-';
} else {
metric.value = usage[metric.id] ?? '-';
}
return metric;
});
metrics = metrics;
@@ -58,7 +58,7 @@
<svelte:fragment slot="actions">
<Button
external
href="#/"
href="https://appwrite.io/docs/products/sites/domains#add-a-custom-domain"
text
event="empty_documentation"
size="s"
@@ -54,7 +54,8 @@
<Modal title="Instant rollback" bind:show onSubmit={handleSubmit}>
<svelte:fragment slot="description">
Rollback is only available for deployments that are <b>ready and previously active</b>. <Link
href="#/">Learn more</Link
href="https://appwrite.io/docs/products/sites/deployments#instant-rollback"
external>Learn more</Link
>.
</svelte:fragment>
<Layout.Stack gap="xl">
@@ -55,7 +55,7 @@
<svelte:fragment slot="actions">
<Button
external
href="#"
href="https://appwrite.io/docs/products/sites/logs"
secondary
event="empty_documentation"
size="s"
@@ -121,14 +121,15 @@
</svelte:head>
<Modal
title={state === 'authenticated' ? 'Request access' : 'Sign up or log in to view this preview'}
title={state === 'authenticated' ? 'Access required' : 'Sign up or log in to view this preview'}
dismissible={false}
show
hideFooter={state !== 'authenticated'}
hideFooter
{error}>
<span slot="description">
{#if state === 'authenticated'}
Request access to view this preview. You'll gain access once your request is approved.
Access is required to view this preview. You'll be able to see it once an admin invites
you to the organization.
{/if}
</span>
<Form onSubmit={handleSubmit}>
@@ -225,21 +226,23 @@
{data.account.email}
</Typography.Text>
</Layout.Stack>
<Link
variant="muted"
on:click={() => {
logout(false);
state = 'login';
}}>Switch account</Link>
<div>
<Link
variant="muted"
on:click={() => {
logout(false);
state = 'login';
}}>Switch account</Link>
</div>
</Layout.Stack>
</Card>
{/if}
</Layout.Stack>
</Form>
<svelte:fragment slot="footer">
<!-- <svelte:fragment slot="footer">
<Button on:click={requestAccess}>Request access</Button>
</svelte:fragment>
</svelte:fragment> -->
</Modal>
<style lang="scss">