mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'feat-pink-v2' into fix-dualtimstamp
This commit is contained in:
+2
-2
@@ -19,11 +19,11 @@
|
||||
"e2e:ui": "playwright test tests/e2e --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@74f9464",
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08b7400",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@9738320",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@9738320",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@11e0653",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/sveltekit": "^8.38.0",
|
||||
"@stripe/stripe-js": "^3.5.0",
|
||||
|
||||
Generated
+18
-17
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@appwrite.io/console':
|
||||
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@74f9464
|
||||
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@74f9464
|
||||
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08b7400
|
||||
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08b7400
|
||||
'@appwrite.io/pink-icons':
|
||||
specifier: 0.25.0
|
||||
version: 0.25.0
|
||||
@@ -21,8 +21,8 @@ importers:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
'@appwrite.io/pink-svelte':
|
||||
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@9738320
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@9738320(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)
|
||||
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@11e0653
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@11e0653(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)
|
||||
'@popperjs/core':
|
||||
specifier: ^2.11.8
|
||||
version: 2.11.8
|
||||
@@ -211,18 +211,18 @@ 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@74f9464':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@74f9464}
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08b7400':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08b7400}
|
||||
version: 1.2.1
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@9738320':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@9738320}
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@11e065399ceac01b462887282f593271ec8b29a4':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@11e065399ceac01b462887282f593271ec8b29a4}
|
||||
version: 1.0.0-next.7
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@9738320c3694272255ba4cc7639586ed8bbcc094':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@9738320c3694272255ba4cc7639586ed8bbcc094}
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6}
|
||||
version: 1.0.0-next.7
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
@@ -236,8 +236,9 @@ packages:
|
||||
'@appwrite.io/pink-legacy@1.0.3':
|
||||
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@9738320':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@9738320}
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@11e0653':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@11e0653}
|
||||
version: 1.0.0-next.85
|
||||
peerDependencies:
|
||||
react-dom: ^18.0.0
|
||||
@@ -4241,13 +4242,13 @@ snapshots:
|
||||
|
||||
'@analytics/type-utils@0.6.2': {}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@74f9464': {}
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@08b7400': {}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@9738320(svelte@4.2.19)':
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@11e065399ceac01b462887282f593271ec8b29a4(svelte@4.2.19)':
|
||||
dependencies:
|
||||
svelte: 4.2.19
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@9738320c3694272255ba4cc7639586ed8bbcc094(svelte@4.2.19)':
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6(svelte@4.2.19)':
|
||||
dependencies:
|
||||
svelte: 4.2.19
|
||||
|
||||
@@ -4260,9 +4261,9 @@ snapshots:
|
||||
'@appwrite.io/pink-icons': 1.0.0
|
||||
the-new-css-reset: 1.11.3
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@9738320(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)':
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@11e0653(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)':
|
||||
dependencies:
|
||||
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@9738320c3694272255ba4cc7639586ed8bbcc094(svelte@4.2.19)
|
||||
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@11e065399ceac01b462887282f593271ec8b29a4(svelte@4.2.19)
|
||||
'@floating-ui/dom': 1.6.13
|
||||
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.3(svelte@4.2.19))(svelte@4.2.19)
|
||||
'@melt-ui/svelte': 0.86.3(svelte@4.2.19)
|
||||
|
||||
@@ -5,8 +5,16 @@
|
||||
import { onMount } from 'svelte';
|
||||
import PaymentModal from './paymentModal.svelte';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { Alert, Fieldset, Icon, Layout, Selector } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import {
|
||||
Alert,
|
||||
Fieldset,
|
||||
Icon,
|
||||
Layout,
|
||||
Selector,
|
||||
Card,
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { IconExclamationCircle, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import InputSelect from '$lib/elements/forms/inputSelect.svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
@@ -33,48 +41,55 @@
|
||||
$: selectedPaymentMethod = methods?.paymentMethods?.find((method) => method.$id === value);
|
||||
</script>
|
||||
|
||||
<Fieldset legend="Payment">
|
||||
<Layout.Stack>
|
||||
{#if filteredMethods?.length}
|
||||
{#if selectedPaymentMethod?.country?.toLowerCase() === 'in'}
|
||||
<Alert.Inline status="warning">
|
||||
<svelte:fragment slot="title"
|
||||
>Indian credit or debit card-holders</svelte:fragment>
|
||||
To comply with RBI regulations in India, Appwrite will ask for verification to charge
|
||||
up to $150 USD on your payment method. We will never charge more than the cost of
|
||||
your plan and the resources you use, or your budget cap limit. For higher usage limits,
|
||||
please contact us.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<InputSelect
|
||||
id="method"
|
||||
required
|
||||
label="Payment method"
|
||||
placeholder="Select payment method"
|
||||
bind:value
|
||||
options={filteredMethods.map((method) => {
|
||||
return {
|
||||
value: method.$id,
|
||||
label: `${capitalize(method.brand)} ending in ${method.last4}`,
|
||||
data: [method.brand]
|
||||
};
|
||||
})} />
|
||||
<div>
|
||||
<Layout.Stack gap="s">
|
||||
{#if filteredMethods?.length}
|
||||
{#if selectedPaymentMethod?.country?.toLowerCase() === 'in'}
|
||||
<Alert.Inline status="warning">
|
||||
<svelte:fragment slot="title">Indian credit or debit card-holders</svelte:fragment>
|
||||
To comply with RBI regulations in India, Appwrite will ask for verification to charge
|
||||
up to $150 USD on your payment method. We will never charge more than the cost of your
|
||||
plan and the resources you use, or your budget cap limit. For higher usage limits, please
|
||||
contact us.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<InputSelect
|
||||
id="method"
|
||||
required
|
||||
label="Payment method"
|
||||
placeholder="Select payment method"
|
||||
bind:value
|
||||
options={filteredMethods.map((method) => {
|
||||
return {
|
||||
value: method.$id,
|
||||
label: `${capitalize(method.brand)} ending in ${method.last4}`,
|
||||
data: [method.brand]
|
||||
};
|
||||
})} />
|
||||
<div>
|
||||
<Button on:click={() => (showPaymentModal = true)} compact size="s">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add payment method
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Card.Base variant="secondary" radius="s" padding="xs">
|
||||
<Layout.Stack direction="row">
|
||||
<Layout.Stack direction="row" gap="xxs" alignItems="center">
|
||||
<Icon
|
||||
icon={IconExclamationCircle}
|
||||
size="m"
|
||||
color="--fgcolor-neutral-tertiary" />
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary"
|
||||
>No saved payment methods</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Button secondary on:click={() => (showPaymentModal = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add new payment method
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Alert.Inline title="No saved payment methods">
|
||||
<Button slot="actions" secondary on:click={() => (showPaymentModal = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add
|
||||
</Button>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
{#if showPaymentModal && isCloud && hasStripePublicKey}
|
||||
<PaymentModal bind:show={showPaymentModal} on:submit={cardSaved}>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { sortBranches } from '$lib/stores/vcs';
|
||||
|
||||
export let branch: string;
|
||||
export let branch = 'main';
|
||||
export let rootDir: string;
|
||||
export let silentMode: boolean;
|
||||
export let installationId: string;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import { page } from '$app/stores';
|
||||
import { app } from '$lib/stores/app';
|
||||
import {
|
||||
Icon,
|
||||
Sidebar,
|
||||
@@ -82,7 +83,11 @@
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class:only-mobile-tablet={project === undefined}>
|
||||
<div
|
||||
class:only-mobile-tablet={project === undefined}
|
||||
style:--overlay-on-neutral={$app.themeInUse === 'dark'
|
||||
? 'var(--neutral-750)'
|
||||
: 'var(--neutral-100)'}>
|
||||
<Sidebar.Base
|
||||
{...$$props}
|
||||
bind:state
|
||||
|
||||
@@ -96,14 +96,16 @@
|
||||
<ActionMenu.Root>
|
||||
<Layout.Stack>
|
||||
{#each $columns as column}
|
||||
<InputCheckbox
|
||||
id={column.id}
|
||||
label={column.title}
|
||||
on:change={() => (column.hide = !column.hide)}
|
||||
checked={!column.hide}
|
||||
disabled={allowNoColumns
|
||||
? false
|
||||
: selectedColumnsNumber <= 1 && column.hide !== true} />
|
||||
{#if !column?.exclude}
|
||||
<InputCheckbox
|
||||
id={column.id}
|
||||
label={column.title}
|
||||
on:change={() => (column.hide = !column.hide)}
|
||||
checked={!column.hide}
|
||||
disabled={allowNoColumns
|
||||
? false
|
||||
: selectedColumnsNumber <= 1 && column.hide !== true} />
|
||||
{/if}
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</ActionMenu.Root>
|
||||
|
||||
@@ -380,13 +380,13 @@ export const scopes: {
|
||||
},
|
||||
{
|
||||
scope: 'log.read',
|
||||
description: "Access to read your sites's logs",
|
||||
description: "Access to read your sites's logs",
|
||||
category: 'Sites',
|
||||
icon: 'globe'
|
||||
},
|
||||
{
|
||||
scope: 'log.write',
|
||||
description: "Access to execute your project's sites",
|
||||
description: "Access to delete your site's logs",
|
||||
category: 'Sites',
|
||||
icon: 'globe'
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
export let placeholder = '';
|
||||
export let disabled = false;
|
||||
export let helper: string | undefined = undefined;
|
||||
export let pattern: string | undefined = undefined;
|
||||
|
||||
let value = '';
|
||||
let error: string;
|
||||
@@ -33,7 +34,8 @@
|
||||
{id}
|
||||
{placeholder}
|
||||
{disabled}
|
||||
{pattern}
|
||||
bind:value={tags}
|
||||
helper={error || helper}
|
||||
on:invalid={handleInvalid}
|
||||
state={error ? 'error' : 'default'} />
|
||||
state={error ? 'error' : 'default'}><slot name="info" slot="info" /></Input.Tags>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
export let autocomplete = false;
|
||||
export let maxlength: number = null;
|
||||
|
||||
let error: string;
|
||||
export let error: string = null;
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -48,3 +48,8 @@ const formatter = Intl.NumberFormat('en', {
|
||||
export function formatNum(number: number): string {
|
||||
return formatter.format(number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a regex to check hostname validity. Supports wildcards too!
|
||||
*/
|
||||
export const hostnameRegex = String.raw`(\*)|(\*\.)?(?!-)[A-Za-z0-9\-]+([\-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,18}|localhost`;
|
||||
|
||||
@@ -114,3 +114,29 @@ export function formatTimeDetailed(time: number): string {
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the time difference between a starting date/time and the current time.
|
||||
* Returns the difference formatted in seconds, minutes, or hours depending on the magnitude.
|
||||
*
|
||||
* @export
|
||||
* @param {Date | number | string} startTime - The starting date/time, timestamp in milliseconds, or ISO date string (e.g., '2025-03-20T15:50:43.495+00:00')
|
||||
* @returns {string} The formatted time difference
|
||||
*/
|
||||
export function timer(startTime: Date | number | string): string {
|
||||
let start: number;
|
||||
|
||||
if (startTime instanceof Date) {
|
||||
start = startTime.getTime();
|
||||
} else if (typeof startTime === 'number') {
|
||||
start = startTime;
|
||||
} else {
|
||||
// Handle ISO date string format
|
||||
start = new Date(startTime).getTime();
|
||||
}
|
||||
|
||||
const now = new Date().getTime();
|
||||
const diffInSeconds = (now - start) / 1000;
|
||||
|
||||
return calculateTime(diffInSeconds);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export type Column = PinkColumn & {
|
||||
array?: boolean;
|
||||
format?: string;
|
||||
show?: boolean;
|
||||
exclude?: boolean;
|
||||
elements?: string[] | { value: string | number; label: string }[];
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { Avatar, AvatarInitials, Card } from '$lib/components';
|
||||
import { AvatarInitials, Card } from '$lib/components';
|
||||
import AppwriteLogoDark from '$lib/images/appwrite-logo-dark.svg';
|
||||
import AppwriteLogoLight from '$lib/images/appwrite-logo-light.svg';
|
||||
import LoginDark from '$lib/images/login/login-dark-mode.png';
|
||||
@@ -9,7 +9,7 @@
|
||||
import { app } from '$lib/stores/app';
|
||||
import type { Campaign } from '$lib/stores/campaigns';
|
||||
import { getApiEndpoint } from '$lib/stores/sdk';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Typography, Layout, Avatar } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export const imgLight = LoginLight;
|
||||
export const imgDark = LoginDark;
|
||||
@@ -85,60 +85,74 @@
|
||||
<p>Build like a team of hundreds<span class="underscore">_</span></p>
|
||||
</div>
|
||||
{:else if variation === 'card'}
|
||||
<section
|
||||
class="u-flex u-flex-vertical u-main-center u-cross-center u-height-100-percent u-width-full-line">
|
||||
<img
|
||||
src={getImage(campaign?.image[$app.themeInUse])}
|
||||
class="u-block u-image-object-fit-cover side-bg-img"
|
||||
alt="promo" />
|
||||
<div style:max-inline-size="30rem" style:height="100%">
|
||||
<Layout.Stack justifyContent="center" alignItems="center" height="100%" gap="xxxl">
|
||||
<img
|
||||
src={getImage(campaign?.image[$app.themeInUse])}
|
||||
class="u-block u-image-object-fit-cover side-bg-img"
|
||||
alt="promo" />
|
||||
|
||||
<div class="u-text-center auth-container">
|
||||
<Typography.Title size="m">{generateTitle()}</Typography.Title>
|
||||
<p class="u-margin-block-start-16">
|
||||
{generateDesc()}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<Layout.Stack gap="s">
|
||||
<Typography.Title size="s" color="--fgcolor-neutral-primary" align="center"
|
||||
>{generateTitle()}</Typography.Title>
|
||||
<Typography.Text
|
||||
variant="l-400"
|
||||
color="--fgcolor-neutral-secondary"
|
||||
align="center">
|
||||
{generateDesc()}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
{:else if variation === 'review'}
|
||||
<section
|
||||
class="u-flex u-flex-vertical u-main-center u-cross-center u-height-100-percent u-width-full-line review-container">
|
||||
<div class="u-text-center">
|
||||
<Typography.Title size="m">{generateTitle()}</Typography.Title>
|
||||
</div>
|
||||
|
||||
<p class="body-text-1 u-margin-block-start-16 u-text-center">{generateDesc()}</p>
|
||||
<div class="review-container u-margin-block-start-64">
|
||||
<Card style="--p-card-padding: 1.25rem">
|
||||
<p class="body-text-1">
|
||||
{currentReview.review}
|
||||
</p>
|
||||
<div class="u-margin-block-start-16 u-flex u-gap-16">
|
||||
{#if currentReview?.image}
|
||||
<Avatar
|
||||
src={getImage(currentReview?.image)}
|
||||
alt={currentReview.name}
|
||||
size="m" />
|
||||
{:else}
|
||||
<AvatarInitials size="m" name={currentReview.name} />
|
||||
{/if}
|
||||
<div>
|
||||
<p class="body-text-2 u-bold">{currentReview.name}</p>
|
||||
<p class="body-text-2">{currentReview.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style:max-inline-size="30rem" style:height="100%">
|
||||
<Layout.Stack justifyContent="center" alignItems="center" height="100%" gap="xxxl">
|
||||
<Card radius="m" padding="s">
|
||||
<Layout.Stack gap="xl">
|
||||
<Typography.Text variant="l-400" color="--fgcolor-neutral-secondary">
|
||||
{currentReview.review}</Typography.Text>
|
||||
<Layout.Stack gap="s" direction="row">
|
||||
{#if currentReview?.image}
|
||||
<Avatar
|
||||
src={getImage(currentReview?.image)}
|
||||
alt={currentReview.name}
|
||||
size="m" />
|
||||
{:else}
|
||||
<AvatarInitials size="m" name={currentReview.name} />
|
||||
{/if}
|
||||
<Layout.Stack direction="column" gap="xxxs">
|
||||
<Typography.Text
|
||||
variant="m-500"
|
||||
color="--fgcolor-neutral-primary"
|
||||
>{currentReview.name}</Typography.Text>
|
||||
<Typography.Text
|
||||
variant="m-400"
|
||||
color="--fgcolor-neutral-secondary"
|
||||
>{currentReview.description}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
</div>
|
||||
{#if campaign?.footer}
|
||||
<div
|
||||
class="u-flex u-gap-16 u-cross-center u-main-center review-footer-container">
|
||||
<p class="u-bold" style:text-transform="uppercase">provided to you by</p>
|
||||
<img
|
||||
style:max-block-size="2.5rem"
|
||||
src={getImage(campaign?.image[$app.themeInUse])}
|
||||
alt={coupon?.campaign ?? campaign.$id} />
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
<Layout.Stack gap="s">
|
||||
<Typography.Title size="s" align="center" color="--fgcolor-neutral-primary"
|
||||
>{generateTitle()}</Typography.Title>
|
||||
<Typography.Text variant="l-400" align="center"
|
||||
>{generateDesc()}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
{#if campaign?.footer}
|
||||
<div
|
||||
class="u-flex u-gap-16 u-cross-center u-main-center review-footer-container">
|
||||
<p class="u-bold" style:text-transform="uppercase">
|
||||
provided to you by
|
||||
</p>
|
||||
<img
|
||||
style:max-block-size="2.5rem"
|
||||
src={getImage(campaign?.image[$app.themeInUse])}
|
||||
alt={coupon?.campaign ?? campaign.$id} />
|
||||
</div>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
<section class="grid-1-1-col-2 u-flex u-flex-vertical u-cross-center u-main-center">
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { plansInfo } from '$lib/stores/billing';
|
||||
import { Fieldset, Icon, Layout, Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -70,6 +72,11 @@
|
||||
let couponData = data?.couponData;
|
||||
let campaign = data?.campaign;
|
||||
let billingPlan = BillingPlan.PRO;
|
||||
let tempOrgId = null;
|
||||
|
||||
$: onlyNewOrgs = (campaign && campaign.onlyNewOrgs) || (couponData && couponData.onlyNewOrgs);
|
||||
|
||||
$: selectedOrgId = tempOrgId;
|
||||
|
||||
onMount(async () => {
|
||||
await loadPaymentMethods();
|
||||
@@ -83,6 +90,10 @@
|
||||
if (campaign?.plan) {
|
||||
billingPlan = campaign.plan;
|
||||
}
|
||||
|
||||
if ($organizationList.total > 0 && tempOrgId === null) {
|
||||
tempOrgId = $organizationList.teams[0].$id;
|
||||
}
|
||||
});
|
||||
|
||||
async function loadPaymentMethods() {
|
||||
@@ -204,75 +215,87 @@
|
||||
<WizardSecondaryContainer href={previousPage} bind:showExitModal confirmExit>
|
||||
<svelte:fragment slot="title">Apply credits</svelte:fragment>
|
||||
<WizardSecondaryContent>
|
||||
<Form bind:this={formComponent} onSubmit={handleSubmit} bind:isSubmitting>
|
||||
{#if $organizationList?.total && !campaign?.onlyNewOrgs && canSelectOrg}
|
||||
<InputSelect
|
||||
bind:value={selectedOrgId}
|
||||
label="Select organization"
|
||||
{options}
|
||||
required
|
||||
placeholder="Select organization"
|
||||
id="organization" />
|
||||
{/if}
|
||||
{#if selectedOrgId && (selectedOrg?.billingPlan !== BillingPlan.PRO || !selectedOrg?.paymentMethodId)}
|
||||
{#if selectedOrgId === newOrgId}
|
||||
<InputText
|
||||
label="Organization name"
|
||||
placeholder="Enter organization name"
|
||||
id="name"
|
||||
required
|
||||
bind:value={name} />
|
||||
{/if}
|
||||
<InputTags
|
||||
bind:tags={collaborators}
|
||||
label="Invite members by email"
|
||||
tooltip="Invited members will have access to all services and payment data within your organization"
|
||||
placeholder="Enter email address(es)"
|
||||
validityRegex={emailRegex}
|
||||
validityMessage="Invalid email address"
|
||||
id="members" />
|
||||
<SelectPaymentMethod bind:methods bind:value={paymentMethodId} bind:taxId />
|
||||
{/if}
|
||||
</Form>
|
||||
<Form bind:this={couponForm} onSubmit={addCoupon}>
|
||||
{#if !data?.couponData?.code && selectedOrgId}
|
||||
<InputText
|
||||
required
|
||||
disabled={!!couponData?.credits}
|
||||
bind:value={coupon}
|
||||
placeholder="Enter coupon code"
|
||||
id="code"
|
||||
label="Coupon code">
|
||||
<Button submit secondary disabled={!!couponData?.credits}>
|
||||
<span class="text">Apply</span>
|
||||
</Button>
|
||||
</InputText>
|
||||
{/if}
|
||||
</Form>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if campaign?.template === 'card'}
|
||||
<div
|
||||
class="box card-container u-position-relative"
|
||||
style:--box-border-radius="var(--border-radius-small)">
|
||||
<div class="card-bg"></div>
|
||||
<div class="u-flex u-flex-vertical u-gap-24 u-cross-center u-position-relative">
|
||||
<img
|
||||
src={campaign?.image[$app.themeInUse]}
|
||||
class="u-block u-image-object-fit-cover card-img"
|
||||
alt="promo" />
|
||||
<p class="text">
|
||||
{#if couponData?.credits}
|
||||
{campaign.title.replace('VALUE', couponData.credits.toString())}
|
||||
{:else}
|
||||
{campaign.title}
|
||||
<Layout.Stack gap="l">
|
||||
<Form bind:this={formComponent} onSubmit={handleSubmit} bind:isSubmitting>
|
||||
<Layout.Stack gap="xl">
|
||||
<Fieldset legend="Setup">
|
||||
<Layout.Stack gap="l">
|
||||
{#if $organizationList?.total && !onlyNewOrgs && canSelectOrg}
|
||||
<InputSelect
|
||||
bind:value={selectedOrgId}
|
||||
label="Organization"
|
||||
{options}
|
||||
required
|
||||
placeholder="Select organization"
|
||||
id="organization" />
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if selectedOrgId && (selectedOrg?.billingPlan !== BillingPlan.PRO || !selectedOrg?.paymentMethodId)}
|
||||
{#if selectedOrgId === newOrgId}
|
||||
<InputText
|
||||
label="Organization name"
|
||||
placeholder="Enter name"
|
||||
id="name"
|
||||
required
|
||||
bind:value={name} />
|
||||
{/if}
|
||||
<InputTags
|
||||
bind:tags={collaborators}
|
||||
label="Invite members by email"
|
||||
placeholder="Enter email address(es)"
|
||||
pattern={emailRegex.toString()}
|
||||
id="members">
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span slot="tooltip">
|
||||
Invited members will have access to all services and
|
||||
payment data within your organization
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputTags>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{#if (selectedOrgId && (selectedOrg?.billingPlan !== BillingPlan.PRO || !selectedOrg?.paymentMethodId)) || (!data?.couponData?.code && selectedOrgId)}
|
||||
<Fieldset legend="Billing">
|
||||
<Layout.Stack gap="xl">
|
||||
{#if selectedOrgId && (selectedOrg?.billingPlan !== BillingPlan.PRO || !selectedOrg?.paymentMethodId)}
|
||||
<SelectPaymentMethod
|
||||
bind:methods
|
||||
bind:value={paymentMethodId}
|
||||
bind:taxId />
|
||||
{/if}
|
||||
<Form bind:this={couponForm} onSubmit={addCoupon}>
|
||||
{#if !data?.couponData?.code && selectedOrgId}
|
||||
<Layout.Stack gap="s" direction="row" alignItems="flex-end">
|
||||
<InputText
|
||||
required
|
||||
disabled={!!couponData?.credits}
|
||||
bind:value={coupon}
|
||||
placeholder="Enter code"
|
||||
id="code"
|
||||
label="Coupon code">
|
||||
</InputText>
|
||||
|
||||
<Button
|
||||
submit
|
||||
secondary
|
||||
size="s"
|
||||
disabled={!!couponData?.credits}>
|
||||
<span class="text">Add</span>
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Form>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
</Layout.Stack>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if selectedOrg?.$id && selectedOrg?.billingPlan !== BillingPlan.FREE && selectedOrg?.billingPlan !== BillingPlan.GITHUB_EDUCATION}
|
||||
<section
|
||||
class="card u-margin-block-start-24"
|
||||
class="card"
|
||||
style:--p-card-padding="1.5rem"
|
||||
style:--p-card-border-radius="var(--border-radius-small)">
|
||||
{#if couponData?.code && couponData?.status === 'active'}
|
||||
@@ -286,7 +309,7 @@
|
||||
{/if}
|
||||
</section>
|
||||
{:else if selectedOrgId}
|
||||
<div class:u-margin-block-start-24={campaign?.template === 'card'}>
|
||||
<div>
|
||||
<EstimatedTotalBox
|
||||
fixedCoupon={!!data?.couponData?.code}
|
||||
{billingPlan}
|
||||
@@ -326,9 +349,9 @@
|
||||
<span class="loader is-small is-transparent u-line-height-1-5" aria-hidden="true" />
|
||||
{/if}
|
||||
{#if selectedOrgId === newOrgId}
|
||||
Create Organization
|
||||
Create organization
|
||||
{:else}
|
||||
Apply Credits
|
||||
Apply
|
||||
{/if}
|
||||
</Button>
|
||||
</WizardSecondaryFooter>
|
||||
|
||||
+4
-2
@@ -59,7 +59,9 @@
|
||||
<Icon
|
||||
icon={IconTable}
|
||||
size="s"
|
||||
color="--fgcolor-neutral-weak" />
|
||||
color={isSelected
|
||||
? '--fgcolor-neutral-tertiary'
|
||||
: '--fgcolor-neutral-weak'} />
|
||||
<span class="text collection-name" data-private
|
||||
>{collection.name}</span>
|
||||
</a>
|
||||
@@ -187,7 +189,7 @@
|
||||
li:hover {
|
||||
color: var(--fgcolor-neutral-primary);
|
||||
border-radius: var(--border-radius-xs, 4px);
|
||||
background: var(--bgcolor-neutral-tertiary, #fafafb);
|
||||
background: var(--bgcolor-neutral-secondary);
|
||||
}
|
||||
|
||||
.collection-name {
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@
|
||||
required
|
||||
placeholder="Enter name" />
|
||||
{#if showCustomId}
|
||||
<CustomId bind:id bind:show={showCustomId} name="Function" fullWidth />
|
||||
<CustomId bind:id bind:show={showCustomId} name="Function" />
|
||||
{:else}
|
||||
<div>
|
||||
<Tag size="s" on:click={() => (showCustomId = !showCustomId)}>
|
||||
|
||||
+22
-2
@@ -67,7 +67,17 @@
|
||||
</Layout.Stack>
|
||||
{#if requestTab === 'parameters'}
|
||||
{#if parameters?.length}
|
||||
<Table.Root columns={2} let:root>
|
||||
<Table.Root
|
||||
columns={[
|
||||
{
|
||||
id: 'key',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
id: 'value'
|
||||
}
|
||||
]}
|
||||
let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Value</Table.Header.Cell>
|
||||
@@ -86,7 +96,17 @@
|
||||
{/if}
|
||||
{:else if requestTab === 'headers'}
|
||||
{#if selectedLog.requestHeaders?.length}
|
||||
<Table.Root columns={2} let:root>
|
||||
<Table.Root
|
||||
columns={[
|
||||
{
|
||||
id: 'key',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
id: 'value'
|
||||
}
|
||||
]}
|
||||
let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Value</Table.Header.Cell>
|
||||
|
||||
+11
-1
@@ -81,7 +81,17 @@
|
||||
{/if}
|
||||
{:else if responseTab === 'headers'}
|
||||
{#if selectedLog.responseHeaders?.length}
|
||||
<Table.Root columns={2} let:root>
|
||||
<Table.Root
|
||||
columns={[
|
||||
{
|
||||
id: 'key',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
id: 'value'
|
||||
}
|
||||
]}
|
||||
let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Value</Table.Header.Cell>
|
||||
|
||||
+9
-8
@@ -2,12 +2,12 @@ import type { Column } from '$lib/helpers/types';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const columns = writable<Column[]>([
|
||||
{ id: '$id', title: 'Execution ID', type: 'string', width: 200 },
|
||||
{ id: '$id', title: 'Execution ID', type: 'string', width: { min: 200, max: 250 } },
|
||||
{
|
||||
id: 'requestPath',
|
||||
title: 'Path',
|
||||
type: 'string',
|
||||
width: 90,
|
||||
width: { min: 100, max: 200 },
|
||||
format: 'string'
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ export const columns = writable<Column[]>([
|
||||
title: 'Trigger',
|
||||
type: 'string',
|
||||
hide: true,
|
||||
width: 90,
|
||||
width: { min: 90, max: 200 },
|
||||
array: true,
|
||||
format: 'enum',
|
||||
elements: [
|
||||
@@ -30,7 +30,7 @@ export const columns = writable<Column[]>([
|
||||
title: 'Method',
|
||||
type: 'string',
|
||||
hide: true,
|
||||
width: 70,
|
||||
width: { min: 90, max: 200 },
|
||||
array: true,
|
||||
format: 'enum',
|
||||
elements: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||
@@ -40,7 +40,7 @@ export const columns = writable<Column[]>([
|
||||
title: 'Status code',
|
||||
type: 'integer',
|
||||
hide: true,
|
||||
width: 100,
|
||||
width: { min: 100, max: 200 },
|
||||
format: 'integer',
|
||||
elements: [
|
||||
{
|
||||
@@ -66,7 +66,7 @@ export const columns = writable<Column[]>([
|
||||
id: 'status',
|
||||
title: 'Status',
|
||||
type: 'enum',
|
||||
width: 130,
|
||||
width: { min: 130, max: 200 },
|
||||
array: true,
|
||||
format: 'enum',
|
||||
elements: ['completed', 'failed', 'waiting', 'scheduled', 'processing', 'cancelled']
|
||||
@@ -75,7 +75,8 @@ export const columns = writable<Column[]>([
|
||||
id: 'duration',
|
||||
title: 'Duration',
|
||||
type: 'integer',
|
||||
width: 80,
|
||||
width: { min: 80, max: 200 },
|
||||
|
||||
format: 'integer',
|
||||
elements: [
|
||||
{
|
||||
@@ -96,7 +97,7 @@ export const columns = writable<Column[]>([
|
||||
id: '$createdAt',
|
||||
title: 'Created',
|
||||
type: 'datetime',
|
||||
width: 120,
|
||||
width: { min: 120, max: 200 },
|
||||
format: 'datetime',
|
||||
elements: [
|
||||
{
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
Fieldset,
|
||||
InlineCode,
|
||||
Card,
|
||||
Button
|
||||
Button,
|
||||
Input,
|
||||
Tooltip
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { Form } from '$lib/elements/forms';
|
||||
import { Form, InputText } from '$lib/elements/forms';
|
||||
import {
|
||||
IconVue,
|
||||
IconAppwrite,
|
||||
@@ -44,6 +46,7 @@
|
||||
AngularFrameworkIcon,
|
||||
JavascriptFrameworkIcon
|
||||
} from './components/index';
|
||||
import { hostnameRegex } from '$lib/helpers/string';
|
||||
|
||||
export let key;
|
||||
|
||||
@@ -69,6 +72,8 @@ ${prefix}APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject.client.config.endpoint}"
|
||||
};
|
||||
export let platform: PlatformType = PlatformType.Flutterandroid;
|
||||
export let selectedFrameworkKey: string | undefined = key ? key : undefined;
|
||||
let hostname;
|
||||
let hostnameError = false;
|
||||
|
||||
let frameworks: Array<FrameworkType> = [
|
||||
{
|
||||
@@ -140,6 +145,11 @@ ${prefix}APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject.client.config.endpoint}"
|
||||
$: selectedFrameworkIcon = selectedFramework ? selectedFramework.icon : NoFrameworkIcon;
|
||||
|
||||
async function createWebPlatform() {
|
||||
hostnameError = !new RegExp(hostnameRegex).test(hostname);
|
||||
if (hostnameError) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
isCreatingPlatform = true;
|
||||
await sdk.forConsole.projects.createPlatform(
|
||||
@@ -148,7 +158,7 @@ ${prefix}APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject.client.config.endpoint}"
|
||||
`${selectedFramework.label} app`,
|
||||
selectedFrameworkKey,
|
||||
undefined,
|
||||
undefined
|
||||
hostname
|
||||
);
|
||||
|
||||
isPlatformCreated = true;
|
||||
@@ -211,20 +221,38 @@ ${prefix}APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject.client.config.endpoint}"
|
||||
imageRadius="s" />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
{#if isChangingFramework}
|
||||
<Button.Button
|
||||
disabled={!selectedFramework}
|
||||
on:click={() => (isChangingFramework = false)}>
|
||||
Save</Button.Button>
|
||||
{:else}
|
||||
<Button.Button type="submit" disabled={!selectedFramework}
|
||||
>Create platform</Button.Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{#if !isChangingFramework}
|
||||
<Fieldset legend="Details">
|
||||
<InputText
|
||||
id="hostname"
|
||||
label="Hostname"
|
||||
placeholder="localhost"
|
||||
error={hostnameError && 'Please enter a valid hostname'}
|
||||
bind:value={hostname}>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span slot="tooltip">
|
||||
The hostname that your website will use to interact with the
|
||||
Appwrite APIs in production or development environments. No
|
||||
protocol or port number required.
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputText></Fieldset>
|
||||
<Layout.Stack direction="row" justifyContent="flex-end"
|
||||
><Button.Button type="submit" disabled={!selectedFramework}
|
||||
>Create platform</Button.Button
|
||||
></Layout.Stack>
|
||||
{/if}
|
||||
{:else}
|
||||
<Card.Base padding="s"
|
||||
><Layout.Stack
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
export let hideScrollButtons = false;
|
||||
export let height = 'auto';
|
||||
export let fullHeight = false;
|
||||
export let emptyCopy = 'No logs available';
|
||||
|
||||
let { status, buildLogs } = deployment;
|
||||
|
||||
@@ -56,6 +57,18 @@
|
||||
});
|
||||
return () => unsubscribe();
|
||||
});
|
||||
|
||||
function setCopy() {
|
||||
if (status === 'failed') {
|
||||
return 'Your deployment has failed.';
|
||||
} else if (status === 'building') {
|
||||
return 'Build is starting.';
|
||||
} else if (status === 'processing') {
|
||||
return 'Your deployment is processing.';
|
||||
} else {
|
||||
return emptyCopy;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Layout.Stack gap="xl">
|
||||
@@ -79,7 +92,7 @@
|
||||
{fullHeight}
|
||||
{height}
|
||||
showScrollButton={!hideScrollButtons}
|
||||
logs={buildLogs || 'No logs available'}
|
||||
logs={buildLogs || setCopy()}
|
||||
bind:theme={$app.themeInUse} />
|
||||
{/key}
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
import { columns } from './store';
|
||||
import { View } from '$lib/helpers/load';
|
||||
import Table from './table.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
export let data;
|
||||
let show = false;
|
||||
|
||||
@@ -41,6 +45,14 @@
|
||||
]);
|
||||
|
||||
$updateCommandGroupRanks({ sites: 1000 });
|
||||
|
||||
onMount(() => {
|
||||
return sdk.forConsole.client.subscribe('console', (response) => {
|
||||
if (response.events.includes(`sites.*`)) {
|
||||
invalidate(Dependencies.SITES);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -60,7 +72,7 @@
|
||||
</Layout.Stack>
|
||||
{#if data.siteList.total}
|
||||
{#if data.view === View.Grid}
|
||||
<Grid deployments={data.deployments} siteList={data.siteList} />
|
||||
<Grid siteList={data.siteList} />
|
||||
{:else}
|
||||
<Table siteList={data.siteList} />
|
||||
{/if}
|
||||
|
||||
@@ -17,35 +17,11 @@ export const load = async ({ url, depends, route }) => {
|
||||
search || undefined
|
||||
);
|
||||
|
||||
// TODO: @Meldiron Discuss better solution for getting screnshot IDs
|
||||
const deployments: Models.Deployment[] = [];
|
||||
await Promise.all(
|
||||
siteList.sites.map(async (site) => {
|
||||
if (!site.deploymentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: @Meldiron Tripple-check if this can ever happened
|
||||
// It happened to me but on QA that might have corrupted data
|
||||
try {
|
||||
const deployment = await sdk.forProject.sites.getDeployment(
|
||||
site.$id,
|
||||
site.deploymentId
|
||||
);
|
||||
deployments.push(deployment);
|
||||
} catch (err) {
|
||||
// Active deployment no longer available (deleted?)
|
||||
console.warn(err);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
offset,
|
||||
limit,
|
||||
search,
|
||||
siteList,
|
||||
deployments,
|
||||
view
|
||||
};
|
||||
};
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
export let frameworks: Models.Framework[];
|
||||
export let selectedFramework: Models.Framework;
|
||||
$: frameworkData = frameworks.find((framework) => framework.key === selectedFramework?.key);
|
||||
$: ssr = frameworkData?.adapters.find((adapter) => adapter.key === 'ssr');
|
||||
|
||||
export let variables: Partial<Models.Variable>[] = [];
|
||||
export let installCommand = '';
|
||||
@@ -54,9 +55,9 @@
|
||||
let frameworkId = selectedFramework.key;
|
||||
|
||||
$: if (!installCommand || !buildCommand || !outputDirectory) {
|
||||
installCommand ||= frameworkData?.adapters?.ssr?.installCommand;
|
||||
buildCommand ||= frameworkData?.adapters?.ssr?.buildCommand;
|
||||
outputDirectory ||= frameworkData?.adapters?.ssr?.outputDirectory;
|
||||
installCommand ||= ssr?.installCommand;
|
||||
buildCommand ||= ssr?.buildCommand;
|
||||
outputDirectory ||= ssr?.outputDirectory;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -86,15 +87,12 @@
|
||||
id="installCommand"
|
||||
label="Install command"
|
||||
bind:value={installCommand}
|
||||
placeholder={frameworkData?.adapters?.ssr?.installCommand} />
|
||||
placeholder={ssr?.installCommand} />
|
||||
<Button
|
||||
secondary
|
||||
size="s"
|
||||
disabled={frameworkData?.adapters?.ssr?.installCommand ===
|
||||
installCommand}
|
||||
on:click={() =>
|
||||
(installCommand =
|
||||
frameworkData?.adapters?.ssr?.installCommand)}>
|
||||
disabled={ssr?.installCommand === installCommand}
|
||||
on:click={() => (installCommand = ssr?.installCommand)}>
|
||||
Reset
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
@@ -103,14 +101,12 @@
|
||||
id="buildCommand"
|
||||
label="Build command"
|
||||
bind:value={buildCommand}
|
||||
placeholder={frameworkData?.adapters?.ssr?.buildCommand} />
|
||||
placeholder={ssr?.buildCommand} />
|
||||
<Button
|
||||
secondary
|
||||
size="s"
|
||||
disabled={frameworkData?.adapters?.ssr?.buildCommand ===
|
||||
buildCommand}
|
||||
on:click={() =>
|
||||
(buildCommand = frameworkData?.adapters?.ssr?.buildCommand)}>
|
||||
disabled={ssr?.buildCommand === buildCommand}
|
||||
on:click={() => (buildCommand = ssr?.buildCommand)}>
|
||||
Reset
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
@@ -119,15 +115,12 @@
|
||||
id="outputDirectory"
|
||||
label="Output directory"
|
||||
bind:value={outputDirectory}
|
||||
placeholder={frameworkData?.adapters?.ssr?.outputDirectory} />
|
||||
placeholder={ssr?.outputDirectory} />
|
||||
<Button
|
||||
secondary
|
||||
size="s"
|
||||
disabled={frameworkData?.adapters?.ssr?.outputDirectory ===
|
||||
outputDirectory}
|
||||
on:click={() =>
|
||||
(outputDirectory =
|
||||
frameworkData?.adapters?.ssr?.outputDirectory)}>
|
||||
disabled={ssr?.outputDirectory === outputDirectory}
|
||||
on:click={() => (outputDirectory = ssr?.outputDirectory)}>
|
||||
Reset
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
bind:deployment={data.deployment}
|
||||
bind:site={data.site}
|
||||
hideScrollButtons
|
||||
height="calc(100dvh - 430px)" />
|
||||
height="calc(100dvh - 430px)"
|
||||
emptyCopy="No logs available yet..." />
|
||||
</Fieldset>
|
||||
</Layout.Stack>
|
||||
<svelte:fragment slot="aside">
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
}[] = [];
|
||||
|
||||
let showCustomId = false;
|
||||
|
||||
$: console.log(options);
|
||||
</script>
|
||||
|
||||
<Fieldset legend="Details">
|
||||
@@ -32,7 +30,7 @@
|
||||
required
|
||||
placeholder="Enter name" />
|
||||
{#if showCustomId}
|
||||
<CustomId bind:id bind:show={showCustomId} name="Site" fullWidth />
|
||||
<CustomId bind:id bind:show={showCustomId} name="Site" />
|
||||
{:else}
|
||||
<div>
|
||||
<Tag size="s" on:click={() => (showCustomId = !showCustomId)}>
|
||||
|
||||
@@ -1,83 +1,74 @@
|
||||
<script lang="ts">
|
||||
import Card from '$lib/components/card.svelte';
|
||||
import { Button, InputText } from '$lib/elements/forms';
|
||||
import { InputText } from '$lib/elements/forms';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import { debounce } from '$lib/helpers/debounce';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { consoleVariables } from '$routes/(console)/store';
|
||||
import { ConsoleResourceType } from '@appwrite.io/console';
|
||||
import { Fieldset, Layout, Divider, Status, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Fieldset, Layout, Status, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let domain: string;
|
||||
export let domainIsValid = true;
|
||||
const orginalDomain = domain.split('').join('');
|
||||
let showConfig = false;
|
||||
const originalDomain = domain;
|
||||
let newDomain = domain;
|
||||
|
||||
let domainStatus: 'complete' | 'failed' | 'pending' = 'complete';
|
||||
|
||||
onMount(async () => {});
|
||||
|
||||
function setDomainLabel(status: typeof domainStatus) {
|
||||
switch (status) {
|
||||
case 'complete':
|
||||
return 'Domain is available';
|
||||
case 'failed':
|
||||
return 'Domain is not available';
|
||||
case 'pending':
|
||||
return 'Checking domain availability';
|
||||
}
|
||||
}
|
||||
|
||||
const checkDomain = debounce(async (value: string) => {
|
||||
if (!value) {
|
||||
domainStatus = 'failed';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await sdk.forConsole.console.getResource(
|
||||
`${value}.${$consoleVariables._APP_DOMAIN_SITES}`,
|
||||
ConsoleResourceType.Rules
|
||||
);
|
||||
|
||||
domainStatus = 'complete';
|
||||
domainIsValid = true;
|
||||
} catch {
|
||||
domainStatus = 'failed';
|
||||
domainIsValid = false;
|
||||
} finally {
|
||||
domain = newDomain;
|
||||
}
|
||||
}, 300);
|
||||
}, 500);
|
||||
|
||||
$: if (domain) {
|
||||
checkDomain(domain);
|
||||
$: if (domain !== newDomain) {
|
||||
domainStatus = 'pending';
|
||||
checkDomain(newDomain);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if showConfig}
|
||||
<Fieldset legend="Domains">
|
||||
<Layout.Stack>
|
||||
<Layout.Stack gap="s">
|
||||
<InputText id="domain" placeholder="my-domain" bind:value={domain}>
|
||||
<svelte:fragment slot="end">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
.{$consoleVariables._APP_DOMAIN_SITES}
|
||||
</Typography.Text>
|
||||
</svelte:fragment>
|
||||
</InputText>
|
||||
<Status
|
||||
status={domainIsValid ? 'complete' : 'failed'}
|
||||
label={domainIsValid ? 'Domain is available' : 'Domain is not available'}>
|
||||
</Status>
|
||||
</Layout.Stack>
|
||||
<Divider />
|
||||
<Layout.Stack direction="row" justifyContent="flex-end" gap="s">
|
||||
<Button
|
||||
text
|
||||
on:click={() => {
|
||||
domain = orginalDomain;
|
||||
showConfig = false;
|
||||
}}>Cancel</Button>
|
||||
|
||||
<Button secondary disabled={!domainIsValid} on:click={() => (showConfig = false)}>
|
||||
Update
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{:else}
|
||||
<Card padding="s" radius="s">
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
|
||||
<Layout.Stack direction="row" gap="s" alignItems="center">
|
||||
<span class="icon-globe-alt"></span>
|
||||
{domain}.{$consoleVariables._APP_DOMAIN_SITES}
|
||||
</Layout.Stack>
|
||||
</Typography.Text>
|
||||
<Fieldset legend="Domains">
|
||||
<Layout.Stack gap="s">
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
<InputText id="domain" placeholder="my-domain" bind:value={newDomain}>
|
||||
<svelte:fragment slot="end">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
.{$consoleVariables._APP_DOMAIN_SITES}
|
||||
</Typography.Text>
|
||||
</svelte:fragment>
|
||||
</InputText>
|
||||
<Button
|
||||
size="s"
|
||||
secondary
|
||||
on:click={() => {
|
||||
showConfig = true;
|
||||
}}>
|
||||
Update
|
||||
</Button>
|
||||
disabled={originalDomain === newDomain}
|
||||
on:click={() => (newDomain = originalDomain)}>Reset</Button>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
{/if}
|
||||
<Status status={domainStatus} label={setDomainLabel(domainStatus)}></Status>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
domain = await buildVerboseDomain(name, $project.name, $organization.name, id);
|
||||
domain = await buildVerboseDomain(name, $organization.name, $project.name, id);
|
||||
|
||||
const fr = Object.values(Framework).find((f) => f === framework.key);
|
||||
const buildRuntime = Object.values(BuildRuntime).find(
|
||||
@@ -125,13 +125,10 @@
|
||||
<title>Create site - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
<!-- TODO: re enable aside -->
|
||||
<Wizard
|
||||
title="Create site"
|
||||
bind:showExitModal
|
||||
href={`${base}/project-${$page.params.project}/sites/`}
|
||||
column
|
||||
columnSize="s"
|
||||
confirmExit>
|
||||
<Form bind:this={formComponent} onSubmit={create} bind:isSubmitting>
|
||||
<Layout.Stack gap="xl">
|
||||
@@ -184,7 +181,7 @@
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
<svelte:fragment slot="aside">
|
||||
<Aside />
|
||||
<Aside template={data.template} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
export const load = async ({ parent }) => {
|
||||
const { frameworks } = await parent();
|
||||
|
||||
return {
|
||||
frameworks
|
||||
frameworks,
|
||||
template: await sdk.forProject.sites.getTemplate('starter-for-js')
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Card } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { IconDownload } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon, Image, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Image, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let template: Models.TemplateSite;
|
||||
</script>
|
||||
|
||||
<Card padding="xs">
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Don't have a site yet?</Typography.Text>
|
||||
<Typography.Text variant="m-500" color="--fgcolor-neutral-primary"
|
||||
>Don't have a site yet?</Typography.Text>
|
||||
<Typography.Text variant="m-400">
|
||||
Download our starter kit, and deploy in seconds.
|
||||
Deploy the JavaScript starter template in seconds.
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
<Layout.Stack>
|
||||
<Image
|
||||
ratio="16/9"
|
||||
src={$app.themeInUse === 'dark'
|
||||
? `${base}/images/sites/screenshot-placeholder-dark.svg`
|
||||
: `${base}/images/sites/screenshot-placeholder-light.svg`}
|
||||
src={$app.themeInUse === 'dark' ? template.screenshotDark : template.screenshotLight}
|
||||
alt="preview" />
|
||||
|
||||
<Button fullWidth secondary href="#">
|
||||
<Icon icon={IconDownload} slot="start" /> Starter kit
|
||||
<Button
|
||||
fullWidth
|
||||
secondary
|
||||
href={`${base}/project-${$page.params.project}/sites/create-site/templates/template-${template.key}`}>
|
||||
Deploy
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
|
||||
+12
-12
@@ -26,8 +26,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import Configuration from '../../configuration.svelte';
|
||||
import { consoleVariables } from '$routes/(console)/store';
|
||||
import { buildVerboseDomain } from '../../store';
|
||||
import { project } from '$routes/(console)/project-[project]/store';
|
||||
import Domain from '../../domain.svelte';
|
||||
|
||||
export let data;
|
||||
let showExitModal = false;
|
||||
@@ -46,7 +45,8 @@
|
||||
let outputDirectory = adapter?.outputDirectory;
|
||||
let variables: Partial<Models.Variable>[] = [];
|
||||
let silentMode = false;
|
||||
let domain = id;
|
||||
let domain = data.domain;
|
||||
let domainIsValid = true;
|
||||
|
||||
onMount(async () => {
|
||||
installation.set(data.installation);
|
||||
@@ -76,18 +76,18 @@
|
||||
} catch (error) {
|
||||
framework = data.frameworks.frameworks.find((f) => f.key === 'other');
|
||||
trackError(error, Submit.FrameworkDetect);
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function create() {
|
||||
if (!domainIsValid) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: 'Domain is not valid'
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
domain = await buildVerboseDomain(
|
||||
data.repository.name,
|
||||
data.repository.organization,
|
||||
$project.name,
|
||||
id
|
||||
);
|
||||
const fr = Object.values(Framework).find((f) => f === framework.key);
|
||||
const buildRuntime = Object.values(BuildRuntime).find(
|
||||
(f) => f === framework.buildRuntime
|
||||
@@ -151,8 +151,6 @@
|
||||
trackError(e, Submit.SiteCreate);
|
||||
}
|
||||
}
|
||||
|
||||
$: console.log(framework);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -204,6 +202,8 @@
|
||||
bind:variables
|
||||
frameworks={data.frameworks.frameworks} />
|
||||
{/key}
|
||||
|
||||
<Domain bind:domain bind:domainIsValid />
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
<svelte:fragment slot="aside">
|
||||
|
||||
+13
-3
@@ -1,18 +1,28 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import { buildVerboseDomain } from '../../store';
|
||||
|
||||
export const load = async ({ parent, params, url }) => {
|
||||
const { installations, frameworks } = await parent();
|
||||
|
||||
const { installations, frameworks, project, organization, consoleVariables } = await parent();
|
||||
const [repository] = await Promise.all([
|
||||
sdk.forProject.vcs.getRepository(url.searchParams.get('installation'), params.repository)
|
||||
]);
|
||||
|
||||
const domain = await buildVerboseDomain(
|
||||
consoleVariables._APP_DOMAIN_SITES,
|
||||
repository.name,
|
||||
organization.name,
|
||||
project.name,
|
||||
ID.unique()
|
||||
);
|
||||
|
||||
return {
|
||||
installations,
|
||||
installation: installations.installations.find(
|
||||
(installation) => installation.$id === url.searchParams.get('installation')
|
||||
),
|
||||
repository,
|
||||
frameworks
|
||||
frameworks,
|
||||
domain
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { consoleVariables } from '$routes/(console)/store';
|
||||
import { ConsoleResourceType, ID } from '@appwrite.io/console';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
function toURLSafe(s: string) {
|
||||
return s.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
}
|
||||
|
||||
export async function checkDomain(domain: string) {
|
||||
export async function checkDomain(domain: string, apex: string) {
|
||||
try {
|
||||
await sdk.forConsole.console.getResource(
|
||||
`${domain}.${get(consoleVariables)._APP_DOMAIN_SITES}`,
|
||||
ConsoleResourceType.Rules
|
||||
);
|
||||
await sdk.forConsole.console.getResource(`${domain}.${apex}`, ConsoleResourceType.Rules);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
@@ -20,6 +15,7 @@ export async function checkDomain(domain: string) {
|
||||
}
|
||||
|
||||
export async function buildVerboseDomain(
|
||||
apex: string,
|
||||
name: string,
|
||||
specifier: string,
|
||||
secondarySpecifier?: string,
|
||||
@@ -32,21 +28,21 @@ export async function buildVerboseDomain(
|
||||
: '';
|
||||
const safeUnique = unique ? toURLSafe(unique).toLowerCase() : ID.unique();
|
||||
let domain = `${safeName}`;
|
||||
if (await checkDomain(domain)) {
|
||||
if (await checkDomain(domain, apex)) {
|
||||
return domain;
|
||||
}
|
||||
domain += '-' + safeSpecifier;
|
||||
if (await checkDomain(domain)) {
|
||||
if (await checkDomain(domain, apex)) {
|
||||
return domain;
|
||||
}
|
||||
if (secondarySpecifier) {
|
||||
domain += '-' + safeSecondarySpecifier;
|
||||
if (await checkDomain(domain)) {
|
||||
if (await checkDomain(domain, apex)) {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
domain += '-' + safeUnique;
|
||||
if (await checkDomain(domain)) {
|
||||
if (await checkDomain(domain, apex)) {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ export const load = async ({ url, route }) => {
|
||||
|
||||
const siteTemplatesList = await sdk.forProject.sites.listTemplates(undefined, undefined, 100);
|
||||
|
||||
console.log(siteTemplatesList);
|
||||
|
||||
const [frameworksSet, useCasesSet] = siteTemplatesList.templates.reduce(
|
||||
([fr, uc], next) => {
|
||||
next.useCases.forEach((useCase) => uc.add(useCase));
|
||||
|
||||
+12
-16
@@ -35,10 +35,8 @@
|
||||
import { getFrameworkIcon } from '../../../store';
|
||||
import { app, iconPath } from '$lib/stores/app';
|
||||
import { consoleVariables } from '$routes/(console)/store';
|
||||
import { project } from '$routes/(console)/project-[project]/store';
|
||||
import { buildVerboseDomain } from '../../store';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { connectGitHub } from '$lib/stores/git';
|
||||
import Domain from '../../domain.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -51,7 +49,8 @@
|
||||
|
||||
let name = data.template.name;
|
||||
let id = ID.unique();
|
||||
let domain = id;
|
||||
let domain = data.domain;
|
||||
let domainIsValid = true;
|
||||
let framework = data?.template?.frameworks[0];
|
||||
let branch = 'main';
|
||||
let rootDir = './';
|
||||
@@ -100,15 +99,14 @@
|
||||
message: 'Please select a repository'
|
||||
});
|
||||
return;
|
||||
} else if (!domainIsValid) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: 'Domain is not valid'
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
try {
|
||||
domain = await buildVerboseDomain(
|
||||
data.template.name,
|
||||
$project.name,
|
||||
$organization.name,
|
||||
id
|
||||
);
|
||||
|
||||
const fr = Object.values(Framework).find((f) => f === framework.key);
|
||||
const buildRuntime = Object.values(BuildRuntime).find(
|
||||
(f) => f === framework.buildRuntime
|
||||
@@ -186,11 +184,6 @@
|
||||
$: if (connectBehaviour === 'later') {
|
||||
selectedRepository = null;
|
||||
}
|
||||
|
||||
$: console.log(repositoryName);
|
||||
|
||||
$: console.log(data.template);
|
||||
$: console.log(variables);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -319,6 +312,9 @@
|
||||
<Configuration bind:variables templateVariables={data.template.variables} />
|
||||
{/if}
|
||||
{/if}
|
||||
{#if connectBehaviour === 'later' || selectedRepository}
|
||||
<Domain bind:domain bind:domainIsValid />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
<svelte:fragment slot="aside">
|
||||
|
||||
+12
-2
@@ -1,12 +1,22 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { buildVerboseDomain } from '../../store.js';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
|
||||
export const load = async ({ parent, params }) => {
|
||||
const { installations, frameworks } = await parent();
|
||||
const { installations, frameworks, project, organization, consoleVariables } = await parent();
|
||||
const template = await sdk.forProject.sites.getTemplate(params.template);
|
||||
const domain = await buildVerboseDomain(
|
||||
consoleVariables._APP_DOMAIN_SITES,
|
||||
template.name,
|
||||
organization.name,
|
||||
project.name,
|
||||
ID.unique()
|
||||
);
|
||||
|
||||
return {
|
||||
installations,
|
||||
frameworks,
|
||||
template
|
||||
template,
|
||||
domain
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import { timeFromNow } from '$lib/helpers/date';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Card, Layout, Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { Card, Icon, Layout, Popover, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { getFrameworkIcon } from './store';
|
||||
import { SvgIcon } from '$lib/components';
|
||||
import { app } from '$lib/stores/app';
|
||||
@@ -11,25 +11,24 @@
|
||||
import AddCollaboratorModal from './(components)/addCollaboratorModal.svelte';
|
||||
import SitesActionMenu from './sitesActionMenu.svelte';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { timer } from '$lib/helpers/timeConversion';
|
||||
import { IconExclamation } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Link } from '$lib/elements';
|
||||
|
||||
export let siteList: Models.SiteList;
|
||||
export let deployments: Models.Deployment[];
|
||||
|
||||
let showAddCollaborator = false;
|
||||
let selectedSite: Models.Site = null;
|
||||
|
||||
function getScreenshot(theme: string, site: Models.Site) {
|
||||
const deployment =
|
||||
deployments.find((d) => site.deploymentId && d.$id === site.deploymentId) ?? null;
|
||||
|
||||
if (theme === 'dark') {
|
||||
return deployment?.screenshotDark
|
||||
? getFilePreview(deployment.screenshotDark)
|
||||
return site?.deploymentScreenshotDark
|
||||
? getFilePreview(site?.deploymentScreenshotDark)
|
||||
: `${base}/images/sites/screenshot-placeholder-dark.svg`;
|
||||
}
|
||||
|
||||
return deployment?.screenshotLight
|
||||
? getFilePreview(deployment.screenshotLight)
|
||||
return site?.deploymentScreenshotLight
|
||||
? getFilePreview(site?.deploymentScreenshotLight)
|
||||
: `${base}/images/sites/screenshot-placeholder-light.svg`;
|
||||
}
|
||||
|
||||
@@ -38,6 +37,14 @@
|
||||
const endpoint = getApiEndpoint();
|
||||
return endpoint + `/storage/buckets/screenshots/files/${fileId}/view?project=console`;
|
||||
}
|
||||
|
||||
function generateDesc(site: Models.Site) {
|
||||
if (site.latestDeploymentStatus === 'building') {
|
||||
return `Deployment building ${timer(site.latestDeploymentCreatedAt)}`;
|
||||
} else {
|
||||
return `Deployed ${timeFromNow(site.deploymentCreatedAt)}`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Layout.Grid columns={3} columnsXS={1} columnsXXS={1}>
|
||||
@@ -47,7 +54,7 @@
|
||||
padding="xxs">
|
||||
<Card.Media
|
||||
title={site.name}
|
||||
description={`Updated ${timeFromNow(site.$updatedAt)}`}
|
||||
description={generateDesc(site)}
|
||||
src={getScreenshot($app.themeInUse, site)}
|
||||
alt={site.name}
|
||||
avatar>
|
||||
@@ -60,6 +67,31 @@
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
<SitesActionMenu {site} bind:showAddCollaborator bind:selectedSite />
|
||||
|
||||
<svelte:fragment slot="description-end">
|
||||
{#if site.latestDeploymentStatus === 'failed'}
|
||||
<Popover let:toggle portal>
|
||||
<button on:mouseenter={(e) => toggle(e)}>
|
||||
<Layout.Stack alignItems="center">
|
||||
<Icon
|
||||
icon={IconExclamation}
|
||||
size="s"
|
||||
color="--bgcolor-warning" />
|
||||
</Layout.Stack>
|
||||
</button>
|
||||
<svelte:fragment slot="tooltip">
|
||||
<Typography.Text variant="m-400">
|
||||
Last deployment failed {timeFromNow(
|
||||
site.latestDeploymentCreatedAt
|
||||
)}. <Link
|
||||
href={`${base}/project-${$page.params.project}/sites/site-${site.$id}/deployments/deployment-${site.latestDeploymentId}`}>
|
||||
View logs
|
||||
</Link>
|
||||
</Typography.Text>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Card.Media>
|
||||
</Card.Link>
|
||||
{/each}
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
}
|
||||
if (message.events.includes('sites.*.deployments.*.update')) {
|
||||
invalidate(Dependencies.DEPLOYMENTS);
|
||||
invalidate(Dependencies.FUNCTION);
|
||||
|
||||
invalidate(Dependencies.SITE);
|
||||
console.log(message);
|
||||
return;
|
||||
}
|
||||
if (message.events.includes('sites.*.deployments.*.delete')) {
|
||||
|
||||
@@ -4,12 +4,12 @@ import type { Column } from '$lib/helpers/types';
|
||||
export const showCreateDeployment: Writable<boolean> = writable(false);
|
||||
|
||||
export const columns = writable<Column[]>([
|
||||
{ id: '$id', title: 'Deployment ID', type: 'string', width: 200 },
|
||||
{ id: '$id', title: 'Deployment ID', type: 'string', width: { min: 200, max: 250 } },
|
||||
{
|
||||
id: 'status',
|
||||
title: 'Status',
|
||||
type: 'enum',
|
||||
width: 110,
|
||||
width: { min: 90, max: 130 },
|
||||
array: true,
|
||||
format: 'enum',
|
||||
elements: ['ready', 'processing', 'building', 'waiting', 'cancelled', 'failed']
|
||||
@@ -19,7 +19,8 @@ export const columns = writable<Column[]>([
|
||||
id: 'buildDuration',
|
||||
title: 'Build duration',
|
||||
type: 'integer',
|
||||
width: 90,
|
||||
width: { min: 110, max: 190 },
|
||||
|
||||
elements: [
|
||||
{
|
||||
value: 15,
|
||||
@@ -36,12 +37,32 @@ export const columns = writable<Column[]>([
|
||||
],
|
||||
filter: false
|
||||
},
|
||||
{
|
||||
id: 'totalSize',
|
||||
title: 'Total size',
|
||||
type: 'integer',
|
||||
width: { min: 110, max: 190 },
|
||||
elements: [
|
||||
{
|
||||
value: 2 * 1000 * 1000,
|
||||
label: 'more than 2MB'
|
||||
},
|
||||
{
|
||||
value: 10 * 1000 * 1000,
|
||||
label: 'more than 10MB'
|
||||
},
|
||||
{
|
||||
value: 50 * 1000 * 1000,
|
||||
label: 'more than 50MB'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'sourceSize',
|
||||
title: 'Source size',
|
||||
type: 'integer',
|
||||
hide: true,
|
||||
width: 140,
|
||||
width: { min: 110, max: 190 },
|
||||
elements: [
|
||||
{
|
||||
value: 2 * 1000 * 1000,
|
||||
@@ -61,14 +82,15 @@ export const columns = writable<Column[]>([
|
||||
id: 'buildSize',
|
||||
title: 'Build size',
|
||||
type: 'integer',
|
||||
hide: true,
|
||||
filter: false,
|
||||
width: 80
|
||||
width: { min: 90, max: 190 }
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
title: 'Source',
|
||||
type: 'string',
|
||||
width: 90,
|
||||
width: { min: 90, max: 160 },
|
||||
array: true,
|
||||
format: 'enum',
|
||||
elements: [
|
||||
@@ -81,7 +103,8 @@ export const columns = writable<Column[]>([
|
||||
id: '$updatedAt',
|
||||
title: 'Updated',
|
||||
type: 'datetime',
|
||||
width: 150,
|
||||
width: { min: 150, max: 180 },
|
||||
|
||||
format: 'datetime'
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
{:else}
|
||||
{formatTimeDetailed(deployment.buildDuration)}
|
||||
{/if}
|
||||
{:else if column.id === 'totalSize'}
|
||||
{calculateSize(deployment.totalSize)}
|
||||
{:else if column.id === 'sourceSize'}
|
||||
{calculateSize(deployment.sourceSize)}
|
||||
{:else if column.id === 'buildSize'}
|
||||
|
||||
@@ -43,7 +43,15 @@
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
{#if deploymentList?.total}
|
||||
<Table.Root columns={5} let:root>
|
||||
<Table.Root
|
||||
columns={[
|
||||
{ id: '$id', width: 200 },
|
||||
{ id: 'status' },
|
||||
{ id: 'source' },
|
||||
{ id: 'createdBy' },
|
||||
{ id: 'actions' }
|
||||
]}
|
||||
let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Deployment ID</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Status</Table.Header.Cell>
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
import Table from './table.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
$: console.log(data.domains);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
|
||||
+131
-149
@@ -5,120 +5,77 @@
|
||||
import { Wizard } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import {
|
||||
Fieldset,
|
||||
Layout,
|
||||
Tooltip,
|
||||
Icon,
|
||||
Input,
|
||||
Divider,
|
||||
Typography,
|
||||
Card
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { Fieldset, Layout, Tooltip, Icon, Input, Alert } from '@appwrite.io/pink-svelte';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { sortBranches } from '$lib/stores/vcs';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { consoleVariables, protocol } from '$routes/(console)/store';
|
||||
import { IconGlobeAlt, IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { LabelCard } from '$lib/components';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { StatusCode } from '@appwrite.io/console';
|
||||
import VerificationFieldset from './verificationFieldset.svelte';
|
||||
import type { Domain } from '$lib/sdk/domains';
|
||||
import { statusCodeOptions } from '$lib/stores/domains';
|
||||
import ConnectRepoModal from '../../../(components)/connectRepoModal.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const backPage = `${base}/project-${$page.params.project}/sites/site-${$page.params.site}/domains`;
|
||||
const routeBase = `${base}/project-${$page.params.project}/sites/site-${$page.params.site}/domains`;
|
||||
|
||||
export let data;
|
||||
|
||||
let formComponent: Form;
|
||||
let isSubmitting = writable(false);
|
||||
|
||||
let showConnectRepo = false;
|
||||
|
||||
let behaviour: 'REDIRECT' | 'BRANCH' | 'ACTIVE' = 'ACTIVE';
|
||||
let step: 'add' | 'verify' = 'add';
|
||||
let domainName = '';
|
||||
let redirect: string = null;
|
||||
let statusCode: number = null;
|
||||
let branch = null;
|
||||
|
||||
let domainData: Domain;
|
||||
let selectedTab: 'cname' | 'nameserver';
|
||||
|
||||
const redirectOptions = data.domains.rules
|
||||
const redirectOptions = data.rules.rules
|
||||
.filter((d) => !d.domain.endsWith($consoleVariables._APP_DOMAIN_SITES))
|
||||
.map((domain) => ({
|
||||
label: domain.domain,
|
||||
value: domain.domain
|
||||
}));
|
||||
|
||||
async function addDomain() {
|
||||
const isNewDomain =
|
||||
data.domains.rules.findIndex((rule) => rule.domain === domainName) === -1;
|
||||
try {
|
||||
if (isNewDomain && isCloud) {
|
||||
domainData = await sdk.forConsole.domains.create($organization.$id, domainName);
|
||||
}
|
||||
onMount(() => {
|
||||
if (
|
||||
$page.url.searchParams.has('connectRepo') &&
|
||||
$page.url.searchParams.get('connectRepo') === 'true'
|
||||
) {
|
||||
showConnectRepo = true;
|
||||
}
|
||||
});
|
||||
|
||||
async function addDomain() {
|
||||
try {
|
||||
if (behaviour === 'BRANCH') {
|
||||
await sdk.forProject.proxy.createSiteRule(domainName, $page.params.site, branch);
|
||||
} else if (behaviour === 'REDIRECT') {
|
||||
const sc = Object.values(StatusCode).find((code) => parseInt(code) === statusCode);
|
||||
console.log(statusCode, sc);
|
||||
await sdk.forProject.proxy.createRedirectRule(domainName, $protocol + redirect, sc);
|
||||
} else if (behaviour === 'ACTIVE') {
|
||||
await sdk.forProject.proxy.createSiteRule(domainName, $page.params.site);
|
||||
}
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Domain added successfully'
|
||||
});
|
||||
await goto(backPage);
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
await goto(`${routeBase}/add-domain/verify-${domainName}`);
|
||||
await invalidate(Dependencies.SITES_DOMAINS);
|
||||
} catch (error) {
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: isVerified = domainData?.nameservers
|
||||
? domainData?.nameservers.toLocaleLowerCase() === 'appwrite'
|
||||
: undefined;
|
||||
</script>
|
||||
|
||||
<Wizard title="Add custom domain" href={backPage} column columnSize="s" hideFooter={step === 'add'}>
|
||||
<Form onSubmit={() => (step = 'verify')}>
|
||||
{#if step === 'add'}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Layout.Grid columns={3} columnsXS={1}>
|
||||
<LabelCard value="ACTIVE" bind:group={behaviour} title="Active deployment">
|
||||
Point this domain to the latest deployed version.
|
||||
</LabelCard>
|
||||
<Tooltip disabled={!!data.site?.providerRepositoryId}>
|
||||
<div>
|
||||
<LabelCard
|
||||
value="BRANCH"
|
||||
bind:group={behaviour}
|
||||
title="Branch"
|
||||
disabled={!data.site?.providerRepositoryId}>
|
||||
Point this domain to a specific branch in your repository.
|
||||
</LabelCard>
|
||||
</div>
|
||||
<svelte:fragment slot="tooltip">
|
||||
No repository connected to your site.
|
||||
</svelte:fragment>
|
||||
</Tooltip>
|
||||
<LabelCard value="REDIRECT" bind:group={behaviour} title="Redirect">
|
||||
Forward all traffic from this domain to another URL.
|
||||
</LabelCard>
|
||||
</Layout.Grid>
|
||||
|
||||
<Fieldset legend="Settings">
|
||||
<Layout.Stack gap="xl">
|
||||
<!-- <Input.ComboBox
|
||||
<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"
|
||||
@@ -129,93 +86,118 @@
|
||||
}))}
|
||||
required
|
||||
placeholder="appwrite.example.com" /> -->
|
||||
<InputDomain
|
||||
label="Domain"
|
||||
id="domain"
|
||||
bind:value={domainName}
|
||||
required
|
||||
placeholder="appwrite.example.com" />
|
||||
<InputDomain
|
||||
label="Domain"
|
||||
id="domain"
|
||||
bind:value={domainName}
|
||||
required
|
||||
placeholder="appwrite.example.com" />
|
||||
</Fieldset>
|
||||
|
||||
{#if behaviour === 'BRANCH'}
|
||||
{#if data.site?.providerRepositoryId}
|
||||
{@const sortedBranches = sortBranches(data.branches.branches)}
|
||||
{@const options = sortedBranches.map((branch) => ({
|
||||
label: branch.name,
|
||||
value: branch.name
|
||||
}))}
|
||||
<Layout.Stack gap="s">
|
||||
<InputSelect
|
||||
{options}
|
||||
label="Production branch"
|
||||
id="branch"
|
||||
required
|
||||
bind:value={branch}
|
||||
placeholder="Select branch" />
|
||||
{#if !data.branches?.total}
|
||||
<Input.Helper state="default">
|
||||
No branches found in the selected repository. Create a
|
||||
branch to see it here.
|
||||
</Input.Helper>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{:else if behaviour === 'REDIRECT'}
|
||||
<Layout.Grid columns={3} columnsXS={1}>
|
||||
<LabelCard value="ACTIVE" bind:group={behaviour} title="Active deployment">
|
||||
Point this domain to the latest deployed version.
|
||||
</LabelCard>
|
||||
<LabelCard value="BRANCH" bind:group={behaviour} title="Git branch">
|
||||
Point this domain to a specific branch in your repository.
|
||||
</LabelCard>
|
||||
<LabelCard value="REDIRECT" bind:group={behaviour} title="Redirect">
|
||||
Forward all traffic from this domain to another URL.
|
||||
</LabelCard>
|
||||
</Layout.Grid>
|
||||
|
||||
{#if behaviour === 'BRANCH'}
|
||||
<Fieldset legend="Settings">
|
||||
<Layout.Stack gap="xl">
|
||||
{#if data.site?.providerRepositoryId}
|
||||
{@const sortedBranches = sortBranches(data.branches.branches)}
|
||||
{@const options = sortedBranches.map((branch) => ({
|
||||
label: branch.name,
|
||||
value: branch.name
|
||||
}))}
|
||||
<Layout.Stack gap="s">
|
||||
<InputSelect
|
||||
{options}
|
||||
label="Production branch"
|
||||
id="branch"
|
||||
required
|
||||
bind:value={branch}
|
||||
placeholder="Select branch" />
|
||||
{#if !data.branches?.total}
|
||||
<Input.Helper state="default">
|
||||
No branches found in the selected repository. Create a
|
||||
branch to see it here.
|
||||
</Input.Helper>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{:else}
|
||||
<InputSelect
|
||||
label="Redirect to"
|
||||
id="redirect"
|
||||
placeholder="Select domain"
|
||||
options={redirectOptions}
|
||||
bind:value={redirect}
|
||||
required>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span slot="tooltip">
|
||||
Redirect this domain. Domains added to your project will be
|
||||
listed here.
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputSelect>
|
||||
<InputSelect
|
||||
options={statusCodeOptions}
|
||||
label="Status code"
|
||||
id="code"
|
||||
disabled
|
||||
options={[{ label: 'main', value: 'main' }]}
|
||||
label="Production branch"
|
||||
id="branch"
|
||||
required
|
||||
bind:value={statusCode}
|
||||
placeholder="Select status code" />
|
||||
value="main"
|
||||
placeholder="Select branch" />
|
||||
<Alert.Inline title=" There is no repository connected to your site">
|
||||
<Layout.Stack>
|
||||
<p>
|
||||
The domain will be connected to your active deployment.
|
||||
Connect your Git repository to link a production branch.
|
||||
</p>
|
||||
<div>
|
||||
<Button compact on:click={() => (showConnectRepo = true)}>
|
||||
Connect repository
|
||||
</Button>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<Divider />
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<Button submit>Add</Button>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
</Layout.Stack>
|
||||
{:else if step === 'verify'}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card.Base radius="s" padding="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="xs">
|
||||
<Icon icon={IconGlobeAlt} color="--fgcolor-neutral-primary" />
|
||||
|
||||
<Typography.Text variation="m-500" color="--fgcolor-neutral-primary">
|
||||
{domainName}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Button secondary on:click={() => (step = 'add')}>Change</Button>
|
||||
{:else if behaviour === 'REDIRECT'}
|
||||
<Fieldset legend="Settings">
|
||||
<Layout.Stack gap="xl">
|
||||
<InputSelect
|
||||
label="Redirect to"
|
||||
id="redirect"
|
||||
placeholder="Select domain"
|
||||
options={redirectOptions}
|
||||
bind:value={redirect}
|
||||
required>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span slot="tooltip">
|
||||
Redirect this domain. Domains added to your project will be
|
||||
listed here.
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputSelect>
|
||||
<InputSelect
|
||||
options={statusCodeOptions}
|
||||
label="Status code"
|
||||
id="code"
|
||||
required
|
||||
bind:value={statusCode}
|
||||
placeholder="Select status code" />
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
|
||||
<VerificationFieldset domain={domainName} verified={isVerified} bind:selectedTab />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary href={backPage}>Cancel</Button>
|
||||
<Button on:click={addDomain}>Verify</Button>
|
||||
<Button secondary href={routeBase}>Cancel</Button>
|
||||
<Button on:click={() => formComponent.triggerSubmit()} bind:disabled={$isSubmitting}>
|
||||
Add
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Wizard>
|
||||
|
||||
{#if showConnectRepo}
|
||||
<ConnectRepoModal
|
||||
bind:show={showConnectRepo}
|
||||
site={data.site}
|
||||
onlyExisting
|
||||
callbackState={{ connectRepo: 'true' }} />
|
||||
{/if}
|
||||
|
||||
+3
-3
@@ -5,9 +5,9 @@ import { Dependencies } from '$lib/constants.js';
|
||||
|
||||
export const load = async ({ parent, depends }) => {
|
||||
const { site } = await parent();
|
||||
depends(Dependencies.DOMAINS);
|
||||
depends(Dependencies.DOMAINS, Dependencies.SITES_DOMAINS);
|
||||
|
||||
const [domains, installations] = await Promise.all([
|
||||
const [rules, installations] = await Promise.all([
|
||||
sdk.forProject.proxy.listRules([
|
||||
Query.equal('type', RuleType.DEPLOYMENT),
|
||||
Query.equal('trigger', RuleTrigger.MANUAL)
|
||||
@@ -17,7 +17,7 @@ export const load = async ({ parent, depends }) => {
|
||||
|
||||
return {
|
||||
site,
|
||||
domains,
|
||||
rules,
|
||||
installations,
|
||||
branches:
|
||||
site?.installationId && site?.providerRepositoryId
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<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 { Button, Form } from '$lib/elements/forms';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { page } from '$app/stores';
|
||||
import Wizard from '$lib/layout/wizard.svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export let data;
|
||||
let selectedTab: 'cname' | 'nameserver';
|
||||
let domainData = data.domain;
|
||||
|
||||
let routeBase = `${base}/project-${$page.params.project}/sites/site-${$page.params.site}/domains`;
|
||||
let formComponent: Form;
|
||||
let isSubmitting = writable(false);
|
||||
|
||||
async function addDomain() {
|
||||
const isNewDomain =
|
||||
data.domainsList.domains.findIndex((rule) => rule.domain === $page.params.domain) ===
|
||||
-1;
|
||||
try {
|
||||
if (isNewDomain && isCloud) {
|
||||
domainData = await sdk.forConsole.domains.create(
|
||||
$organization.$id,
|
||||
$page.params.domain
|
||||
);
|
||||
}
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Domain added successfully'
|
||||
});
|
||||
await goto(routeBase);
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
await invalidate(Dependencies.SITES_DOMAINS);
|
||||
} catch (error) {
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function back() {}
|
||||
|
||||
$: isVerified = domainData?.nameservers
|
||||
? domainData?.nameservers.toLocaleLowerCase() === 'appwrite'
|
||||
: undefined;
|
||||
</script>
|
||||
|
||||
<Wizard title="Add domain" href={routeBase} column columnSize="s">
|
||||
<Form bind:this={formComponent} onSubmit={addDomain} bind:isSubmitting>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card.Base radius="s" padding="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="xs">
|
||||
<Icon icon={IconGlobeAlt} color="--fgcolor-neutral-primary" />
|
||||
|
||||
<Typography.Text variation="m-500" color="--fgcolor-neutral-primary">
|
||||
{$page.params.domain}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Button secondary on:click={back}>Change</Button>
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
|
||||
<VerificationFieldset
|
||||
domain={$page.params.domain}
|
||||
verified={isVerified}
|
||||
bind:selectedTab />
|
||||
|
||||
<Divider />
|
||||
<Layout.Stack>
|
||||
<Button submit disabled={$isSubmitting}>Verify</Button>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
</Wizard>
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
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 }) => {
|
||||
const { site } = await parent();
|
||||
depends(Dependencies.DOMAINS);
|
||||
|
||||
let domain: Domain;
|
||||
let domainsList: DomainsList;
|
||||
if (isCloud) {
|
||||
[domain, domainsList] = await Promise.all([
|
||||
sdk.forConsole.domains.get(params.domain),
|
||||
sdk.forConsole.domains.list()
|
||||
]);
|
||||
}
|
||||
|
||||
return {
|
||||
site,
|
||||
domain,
|
||||
domainsList
|
||||
};
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Copy } from '$lib/components';
|
||||
import { Link } from '$lib/elements';
|
||||
import type { Domain } from '$lib/sdk/domains';
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Badge, Layout, Typography, Table, Fieldset, Icon } from '@appwrite.io/pink-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let domain: Domain | Models.ProxyRule;
|
||||
</script>
|
||||
|
||||
<Fieldset legend="Verification">
|
||||
<Layout.Stack gap="xl">
|
||||
<Layout.Stack gap="xl">
|
||||
<Layout.Stack gap="s">
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
<Typography.Text variant="l-500">{domain?.domain}</Typography.Text>
|
||||
<Badge variant="secondary" type="warning" content="Pending verification" />
|
||||
</Layout.Stack>
|
||||
<Typography.Text variant="m-400">
|
||||
Add the following nameservers on your DNS provider. Note that DNS changes may
|
||||
take time to propagate fully.
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
<Table.Root columns={3} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Type</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Value</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell {root}>CNAME</Table.Cell>
|
||||
<Table.Cell {root}>{domain?.domain}</Table.Cell>
|
||||
<Table.Cell {root}>
|
||||
<Copy value={globalThis?.location?.origin}
|
||||
>{globalThis?.location?.origin}</Copy>
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
</Table.Root>
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
<Icon icon={IconInfo} size="s" color="--fgcolor-neutral-secondary" />
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-secondary">
|
||||
A list of all domain providers and their DNS setting is available <Link
|
||||
variant="muted"
|
||||
href="#">here</Link
|
||||
>.
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
+1
-2
@@ -6,12 +6,11 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { Domain } from '$lib/sdk/domains';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import CnameTable from '$lib/components/domains/cnameTable.svelte';
|
||||
|
||||
export let show = false;
|
||||
export let selectedDomain: Domain | Models.ProxyRule;
|
||||
export let selectedDomain: Models.ProxyRule;
|
||||
|
||||
let error = null;
|
||||
async function retryDomain() {
|
||||
|
||||
@@ -7,19 +7,19 @@ export const columns = writable<Column[]>([
|
||||
title: 'Domain',
|
||||
type: 'string',
|
||||
format: 'string',
|
||||
width: { min: 120, max: 400 }
|
||||
width: { min: 200, max: 550 }
|
||||
},
|
||||
|
||||
{
|
||||
id: 'redirectUrl',
|
||||
title: 'Redirect to',
|
||||
type: 'string',
|
||||
width: { min: 120, max: 200 }
|
||||
width: { min: 120, max: 400 }
|
||||
},
|
||||
{
|
||||
id: 'deploymentVcsProviderBranch',
|
||||
title: 'Production branch',
|
||||
type: 'string',
|
||||
width: { min: 70, max: 150 }
|
||||
width: { min: 90, max: 220 }
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<Typography.Text variant="l-500" color="--fgcolor-neutral-primary">
|
||||
Domains
|
||||
</Typography.Text>
|
||||
<Badge variant="secondary" content={proxyRuleList?.total.toString()} size="s" />
|
||||
<Badge variant="secondary" content={(rules?.length ?? 0).toString()} size="s" />
|
||||
</Layout.Stack>
|
||||
<Button
|
||||
secondary
|
||||
|
||||
+11
-1
@@ -86,7 +86,17 @@
|
||||
{/if}
|
||||
{:else if requestTab === 'headers'}
|
||||
{#if selectedLog.requestHeaders?.length}
|
||||
<Table.Root columns={2} let:root>
|
||||
<Table.Root
|
||||
columns={[
|
||||
{
|
||||
id: 'key',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
id: 'value'
|
||||
}
|
||||
]}
|
||||
let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Value</Table.Header.Cell>
|
||||
|
||||
+11
-1
@@ -73,7 +73,17 @@
|
||||
{/if}
|
||||
{:else if responseTab === 'headers'}
|
||||
{#if selectedLog.responseHeaders?.length}
|
||||
<Table.Root columns={2} let:root>
|
||||
<Table.Root
|
||||
columns={[
|
||||
{
|
||||
id: 'key',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
id: 'value'
|
||||
}
|
||||
]}
|
||||
let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Value</Table.Header.Cell>
|
||||
|
||||
@@ -2,12 +2,12 @@ import type { Column } from '$lib/helpers/types';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const columns = writable<Column[]>([
|
||||
{ id: '$id', title: 'Log ID', type: 'string', width: 200 },
|
||||
{ id: '$id', title: 'Log ID', type: 'string', width: { min: 200, max: 250 } },
|
||||
{
|
||||
id: 'requestPath',
|
||||
title: 'Path',
|
||||
type: 'string',
|
||||
width: 90,
|
||||
width: { min: 500, max: 900 },
|
||||
format: 'string'
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ export const columns = writable<Column[]>([
|
||||
id: 'trigger',
|
||||
title: 'Trigger',
|
||||
type: 'string',
|
||||
hide: true,
|
||||
exclude: true,
|
||||
width: 90,
|
||||
array: true,
|
||||
format: 'enum',
|
||||
@@ -30,7 +30,7 @@ export const columns = writable<Column[]>([
|
||||
id: 'requestMethod',
|
||||
title: 'Method',
|
||||
type: 'string',
|
||||
hide: true,
|
||||
exclude: true,
|
||||
width: 70,
|
||||
array: true,
|
||||
format: 'enum',
|
||||
@@ -40,7 +40,7 @@ export const columns = writable<Column[]>([
|
||||
id: 'responseStatusCode',
|
||||
title: 'Status code',
|
||||
type: 'integer',
|
||||
hide: true,
|
||||
exclude: true,
|
||||
width: 100,
|
||||
format: 'integer',
|
||||
elements: [
|
||||
@@ -84,13 +84,14 @@ export const columns = writable<Column[]>([
|
||||
label: 'more than 30 seconds'
|
||||
}
|
||||
],
|
||||
filter: false
|
||||
filter: false,
|
||||
exclude: true
|
||||
},
|
||||
{
|
||||
id: '$createdAt',
|
||||
title: 'Created',
|
||||
width: { min: 150, max: 180 },
|
||||
type: 'datetime',
|
||||
width: 120,
|
||||
format: 'datetime',
|
||||
elements: [
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<Table.Root {columns} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
{#each columns as { id, title }}
|
||||
{#each columns.filter((c) => !c.exclude) as { id, title }}
|
||||
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
@@ -26,7 +26,7 @@
|
||||
openSheet = true;
|
||||
selectedLogId = log.$id;
|
||||
}}>
|
||||
{#each columns as column}
|
||||
{#each columns.filter((c) => !c.exclude) as column}
|
||||
<Table.Cell column={column.id} {root}>
|
||||
{#if column.id === '$id'}
|
||||
{#key column.id}
|
||||
@@ -38,10 +38,12 @@
|
||||
{log.responseStatusCode}
|
||||
{:else if column.id === 'requestPath'}
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
type={log.responseStatusCode >= 400 ? 'error' : 'success'}
|
||||
content={log.responseStatusCode.toString()} />
|
||||
<div>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
type={log.responseStatusCode >= 400 ? 'error' : 'success'}
|
||||
content={log.responseStatusCode.toString()} />
|
||||
</div>
|
||||
<Typography.Code size="m">
|
||||
{log.requestMethod}
|
||||
</Typography.Code>
|
||||
|
||||
@@ -52,8 +52,6 @@
|
||||
showConnectRepo.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
$: console.log(data);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
|
||||
+13
-20
@@ -28,15 +28,12 @@
|
||||
let fallback = site.fallbackFile;
|
||||
let isButtonDisabled = true;
|
||||
let showFallback = site.adapter === Adapter.Static;
|
||||
$: frameworkAdapterData = selectedFramework.adapters.find((a) => a.key === adapter);
|
||||
|
||||
onMount(async () => {
|
||||
installCommand =
|
||||
site?.installCommand ?? selectedFramework.adapters[site.adapter].defaultInstallCommand;
|
||||
buildCommand =
|
||||
site?.buildCommand ?? selectedFramework.adapters[site.adapter].defaultBuildCommand;
|
||||
outputDirectory =
|
||||
site?.outputDirectory ??
|
||||
selectedFramework.adapters[site.adapter].defaultOutputDirectory;
|
||||
installCommand = site?.installCommand ?? frameworkAdapterData?.installCommand;
|
||||
buildCommand = site?.buildCommand ?? frameworkAdapterData?.buildCommand;
|
||||
outputDirectory = site?.outputDirectory ?? frameworkAdapterData?.outputDirectory;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
@@ -89,8 +86,7 @@
|
||||
|
||||
$: if (adapter === Adapter.Static) {
|
||||
showFallback = true;
|
||||
fallback ||= selectedFramework.adapters.static.fallbackFile;
|
||||
console.log(fallback, selectedFramework.adapters.static);
|
||||
fallback ||= selectedFramework.adapters.find((a) => a.key === Adapter.Static).fallbackFile;
|
||||
} else {
|
||||
showFallback = false;
|
||||
fallback = undefined;
|
||||
@@ -100,15 +96,12 @@
|
||||
fallback = null;
|
||||
}
|
||||
|
||||
$: frameworkDataAdapter = selectedFramework.adapters?.length
|
||||
? selectedFramework.adapters[site.adapter]
|
||||
: frameworks[0].adapters[site.adapter];
|
||||
|
||||
//TODO: fix after backend type is fixed
|
||||
$: if (selectedFramework?.adapters?.length <= 1 || !selectedFramework?.adapters?.ssr?.key) {
|
||||
$: hasSSR = selectedFramework?.adapters?.some((a) => a?.key === Adapter.Ssr);
|
||||
$: hasStatic = selectedFramework?.adapters?.some((a) => a?.key === Adapter.Static);
|
||||
$: if (selectedFramework?.adapters?.length <= 1 || !hasSSR) {
|
||||
adapter = Adapter.Static;
|
||||
}
|
||||
$: if (selectedFramework?.adapters?.length <= 1 || !selectedFramework?.adapters?.static?.key) {
|
||||
$: if (selectedFramework?.adapters?.length <= 1 || !hasStatic) {
|
||||
adapter = Adapter.Ssr;
|
||||
}
|
||||
</script>
|
||||
@@ -139,7 +132,7 @@
|
||||
(framework) => framework.key === frameworkKey
|
||||
);
|
||||
}} />
|
||||
{#if selectedFramework.adapters?.length || (selectedFramework.adapters?.ssr?.key && selectedFramework.adapters?.static?.key)}
|
||||
{#if selectedFramework.adapters?.length > 2}
|
||||
<Layout.Grid columnsXS={1} columns={2} gap="l">
|
||||
<Card.Selector
|
||||
title="Server side rendering"
|
||||
@@ -202,7 +195,7 @@
|
||||
id="installCommand"
|
||||
label="Install command"
|
||||
bind:value={installCommand}
|
||||
placeholder={frameworkDataAdapter?.defaultInstallCommand} />
|
||||
placeholder={frameworkAdapterData?.installCommand} />
|
||||
|
||||
<Button
|
||||
secondary
|
||||
@@ -220,7 +213,7 @@
|
||||
id="buildCommand"
|
||||
label="Build command"
|
||||
bind:value={buildCommand}
|
||||
placeholder={frameworkDataAdapter?.defaultBuildCommand} />
|
||||
placeholder={frameworkAdapterData?.buildCommand} />
|
||||
<Button
|
||||
secondary
|
||||
size="s"
|
||||
@@ -237,7 +230,7 @@
|
||||
id="outputDirectory"
|
||||
label="Output directory"
|
||||
bind:value={outputDirectory}
|
||||
placeholder={frameworkDataAdapter?.defaultOutputDirectory} />
|
||||
placeholder={frameworkAdapterData?.outputDirectory} />
|
||||
<Button
|
||||
secondary
|
||||
size="s"
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { BuildRuntime, Framework, type Models } from '@appwrite.io/console';
|
||||
import { Adapter, BuildRuntime, Framework, type Models } from '@appwrite.io/console';
|
||||
|
||||
export let site: Models.Site;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
site.buildCommand || undefined,
|
||||
site.outputDirectory || undefined,
|
||||
(site?.buildRuntime as BuildRuntime) || undefined,
|
||||
site.adapter,
|
||||
site.adapter as Adapter,
|
||||
site.fallbackFile || undefined,
|
||||
site.installationId || undefined,
|
||||
site.providerRepositoryId || undefined,
|
||||
|
||||
@@ -309,7 +309,11 @@
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<Table.Root
|
||||
columns={[{ id: 'key' }, { id: 'value' }, { id: 'actions', width: 30 }]}
|
||||
columns={[
|
||||
{ id: 'key', width: { min: 200, max: 400 } },
|
||||
{ id: 'value', width: { min: 200, max: 400 } },
|
||||
{ id: 'actions', width: 50 }
|
||||
]}
|
||||
let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
try {
|
||||
disabled = true;
|
||||
await sdk.forConsole.account.createEmailPasswordSession(mail, pass);
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
|
||||
if ($user) {
|
||||
trackEvent(Submit.AccountLogin, { mfa_used: 'none' });
|
||||
addNotification({
|
||||
@@ -30,12 +30,7 @@
|
||||
message: 'Successfully logged in.'
|
||||
});
|
||||
}
|
||||
if ($redirectTo) {
|
||||
window.location.href = $redirectTo;
|
||||
return;
|
||||
}
|
||||
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
if (data?.couponData?.code) {
|
||||
trackEvent(Submit.AccountCreate, { campaign_name: data?.couponData?.code });
|
||||
await goto(`${base}/apply-credit?code=${data?.couponData?.code}`);
|
||||
@@ -45,6 +40,13 @@
|
||||
await goto(`${base}/apply-credit?campaign=${data.campaign?.$id}`);
|
||||
return;
|
||||
}
|
||||
if ($redirectTo) {
|
||||
window.location.href = $redirectTo;
|
||||
return;
|
||||
}
|
||||
|
||||
// no specific redirect, so redirect will happen through invalidating the account
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
} catch (error) {
|
||||
disabled = false;
|
||||
addNotification({
|
||||
|
||||
Reference in New Issue
Block a user