Merge remote-tracking branch 'origin/feat-pink-v2' into feat-pink-v2

This commit is contained in:
ernstmul
2025-05-06 10:56:27 +02:00
20 changed files with 190 additions and 141 deletions
+2 -2
View File
@@ -21,7 +21,8 @@
"e2e:ui": "playwright test --ui"
},
"dependencies": {
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e",
"@ai-sdk/svelte": "^1.1.22",
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1",
"@appwrite.io/pink": "0.25.0",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f",
@@ -32,7 +33,6 @@
"@stripe/stripe-js": "^3.5.0",
"ai": "^2.2.37",
"analytics": "^0.8.16",
"@ai-sdk/svelte": "^1.1.22",
"cron-parser": "^4.9.0",
"dayjs": "^1.11.13",
"deep-equal": "^2.2.3",
+5 -5
View File
@@ -12,8 +12,8 @@ importers:
specifier: ^1.1.22
version: 1.1.22(svelte@5.25.3)(zod@3.24.3)
'@appwrite.io/console':
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1
'@appwrite.io/pink':
specifier: 0.25.0
version: 0.25.0
@@ -254,8 +254,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/appwrite/@appwrite.io/console@e2f082e':
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1':
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1}
version: 1.2.1
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f':
@@ -3625,7 +3625,7 @@ snapshots:
'@analytics/type-utils@0.6.2': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1': {}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f(svelte@5.25.3)':
dependencies:
+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}
+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);
@@ -122,7 +122,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()}
@@ -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.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.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.forProject.proxy.createAPIRule(domainName);
@@ -41,27 +47,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.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.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.proxy.updateRuleVerification(ruleId);
verified = ruleData.status === 'verified';
} else if (isNewDomain && isCloud) {
@@ -44,9 +64,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
@@ -82,14 +102,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>
@@ -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;
@@ -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>
@@ -21,7 +21,7 @@
async function deleteDomain() {
try {
await sdk.forProject.proxy.deleteRule(selectedDomain.$id);
await invalidate(Dependencies.SITES_DOMAINS);
await invalidate(Dependencies.DOMAINS);
show = false;
addNotification({
type: 'success',
@@ -38,11 +38,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>
@@ -21,7 +21,7 @@
async function retryDomain() {
try {
await sdk.forProject.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">
@@ -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/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 '
}
@@ -63,7 +65,21 @@
try {
const usage = await sdk.forProject.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">