Merge branch 'main' into 'add-arrow-navs'.

This commit is contained in:
Darshan
2025-11-10 12:22:59 +05:30
39 changed files with 459 additions and 283 deletions
+4 -4
View File
@@ -12,7 +12,7 @@
"clean": "rm -rf node_modules && rm -rf .svelte_kit && pnpm i --force",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"format": "prettier --write --cache .",
"lint": "prettier --check . && eslint .",
"test": "TZ=EST vitest run",
"test:ui": "TZ=EST vitest --ui",
@@ -22,11 +22,11 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e",
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@4472521",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@4472521",
"@faker-js/faker": "^9.9.0",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
+15 -15
View File
@@ -12,20 +12,20 @@ importers:
specifier: ^1.1.24
version: 1.1.24(svelte@5.25.3)(zod@3.24.3)
'@appwrite.io/console':
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752
'@appwrite.io/pink-icons':
specifier: 0.25.0
version: 0.25.0
'@appwrite.io/pink-icons-svelte':
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e(svelte@5.25.3)
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@4472521
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@4472521(svelte@5.25.3)
'@appwrite.io/pink-legacy':
specifier: ^1.0.3
version: 1.0.3
'@appwrite.io/pink-svelte':
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e(svelte@5.25.3)
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@4472521
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@4472521(svelte@5.25.3)
'@faker-js/faker':
specifier: ^9.9.0
version: 9.9.0
@@ -260,8 +260,8 @@ packages:
'@analytics/type-utils@0.6.2':
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e':
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e}
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752':
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752}
version: 1.10.0
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1':
@@ -269,8 +269,8 @@ packages:
peerDependencies:
svelte: ^4.0.0
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e}
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@4472521':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@4472521}
version: 2.0.0-RC.1
peerDependencies:
svelte: ^4.0.0
@@ -284,8 +284,8 @@ packages:
'@appwrite.io/pink-legacy@1.0.3':
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e}
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@4472521':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@4472521}
version: 2.0.0-RC.2
peerDependencies:
svelte: ^4.0.0
@@ -3703,13 +3703,13 @@ snapshots:
'@analytics/type-utils@0.6.2': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752': {}
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)':
dependencies:
svelte: 5.25.3
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e(svelte@5.25.3)':
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@4472521(svelte@5.25.3)':
dependencies:
svelte: 5.25.3
@@ -3722,7 +3722,7 @@ snapshots:
'@appwrite.io/pink-icons': 1.0.0
the-new-css-reset: 1.11.3
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e(svelte@5.25.3)':
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@4472521(svelte@5.25.3)':
dependencies:
'@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3)
'@floating-ui/dom': 1.6.13
@@ -20,6 +20,7 @@
import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte';
import type { PinkColumn } from '$lib/helpers/types';
import { Card } from '$lib/components';
import { TableScroll } from '$lib/elements/table';
export let withCreate = false;
export let permissions: string[] = [];
@@ -124,7 +125,7 @@
</script>
{#if [...$groups]?.length}
<div class="table-wrapper">
<TableScroll>
<Table.Root {columns} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="role" {root}>Role</Table.Header.Cell>
@@ -139,7 +140,7 @@
{#each [...$groups] as [role, permission] (role)}
<Table.Row.Base {root}>
<Table.Cell column="role" {root}>
<Row {role} />
<Row {role} onNotFound={() => deleteRole(role)} />
</Table.Cell>
<Table.Cell column="create" {root}>
<Selector.Checkbox
@@ -174,7 +175,7 @@
</Table.Row.Base>
{/each}
</Table.Root>
</div>
</TableScroll>
<div>
<Actions
@@ -210,14 +211,3 @@
</Layout.Stack>
</Card>
{/if}
<style lang="scss">
.table-wrapper {
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
</style>
+29 -12
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { type ComponentProps, type Snippet } from 'svelte';
import { type ComponentProps, type Snippet, onMount } from 'svelte';
import { sdk } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { AvatarInitials } from '../';
@@ -34,9 +34,10 @@
role: string;
placement?: ComponentProps<Popover>['placement'];
children?: Snippet;
onNotFound?: (role: string) => void;
}
let { role, placement = 'bottom-start', children }: Props = $props();
let { role, placement = 'bottom-start', children, onNotFound }: Props = $props();
type ParsedPermission = {
type: 'user' | 'team' | 'other';
@@ -111,6 +112,21 @@
return fetchPromise;
}
async function verifyExistence() {
try {
const data = await getData(role);
if (data?.notFound) {
onNotFound?.(role);
}
} catch {
// Intentionally ignore fetch/parse errors; UI handles missing data state
}
}
onMount(() => {
verifyExistence();
});
let isMouseOverTooltip = $state(false);
function hidePopover(hideTooltip: () => void, timeout = true) {
if (!timeout) {
@@ -162,7 +178,7 @@
{:then data}
{formatName(
data.name ?? data?.email ?? data?.phone ?? '-',
$isSmallViewport ? 16 : 18
$isSmallViewport ? 16 : 20
)}
{/await}
</Typography.Text>
@@ -281,20 +297,21 @@
<Divider />
<Layout.Stack gap="xxs" alignItems="flex-start">
{#if data.email}
<Typography.Text
size="xs"
variant="m-400"
<Typography.Caption
variant="400"
color="--fgcolor-neutral-secondary">
Email: {data.email}
</Typography.Text>
Email: {formatName(
data.email,
$isSmallViewport ? 24 : 32
)}
</Typography.Caption>
{/if}
{#if data.phone}
<Typography.Text
size="xs"
variant="m-400"
<Typography.Caption
variant="400"
color="--fgcolor-neutral-secondary">
Phone: {data.phone}
</Typography.Text>
</Typography.Caption>
{/if}
</Layout.Stack>
{/if}
+2 -7
View File
@@ -21,16 +21,17 @@
}[];
export let leadingIcon: ComponentType | undefined = undefined;
let element: HTMLSelectElement;
let error: string;
const handleInvalid = (event: Event) => {
event.preventDefault();
const element = event.target as HTMLInputElement;
if (element.validity.valueMissing) {
error = 'This field is required';
return;
}
error = element.validationMessage;
};
@@ -38,12 +39,6 @@
return typeof value === 'boolean' ? true : !!value;
};
$: if (required && !isNotEmpty(value)) {
element?.setCustomValidity('This field is required');
} else {
element?.setCustomValidity('');
}
$: if (isNotEmpty(value)) {
error = null;
}
+45
View File
@@ -0,0 +1,45 @@
import type { Models } from '@appwrite.io/console';
/**
* Checks if a build has exceeded the maximum build timeout duration
*/
function isBuildTimedOut(createdAt: string, status: string, timeoutSeconds: number): boolean {
if (!['waiting', 'processing', 'building'].includes(status)) {
return false;
}
if (!timeoutSeconds || timeoutSeconds <= 0) {
return false;
}
const created = new Date(createdAt);
const elapsedSeconds = Math.floor((Date.now() - created.getTime()) / 1000);
return elapsedSeconds > timeoutSeconds;
}
/**
* Gets the effective status for a build, considering timeout
*/
export function getEffectiveBuildStatus(
originalStatus: string,
createdAt: string,
consoleVariables: Models.ConsoleVariables | undefined
): string {
const timeoutSeconds = getBuildTimeoutSeconds(consoleVariables);
if (isBuildTimedOut(createdAt, originalStatus, timeoutSeconds)) {
return 'failed';
}
return originalStatus;
}
/**
* Helper to get timeout value from console variables
*/
function getBuildTimeoutSeconds(consoleVariables: Models.ConsoleVariables | undefined): number {
if (!consoleVariables?._APP_COMPUTE_BUILD_TIMEOUT) {
return 0;
}
const timeout = parseInt(String(consoleVariables._APP_COMPUTE_BUILD_TIMEOUT), 10);
return isNaN(timeout) ? 0 : timeout;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

+14 -11
View File
@@ -76,7 +76,7 @@
<Icon size="s" icon={IconCloud} />
{/if}
{#if $version}
{#if $version && !isCloud}
<Link.Anchor
size="s"
variant="quiet"
@@ -137,7 +137,7 @@
</Link.Anchor>
{/if}
{#if $isSmallViewport}
{#if $version}
{#if $version && !isCloud}
<span class="divider-wrapper">
<Divider vertical />
</span>
@@ -151,15 +151,18 @@
style="white-space: nowrap;">
Version {$version}
</Link.Anchor>
{#if isCloud}
<Icon size="s" icon={IconCloud} />
<Badge
size="xs"
type="success"
variant="secondary"
content="Generally Available"
style="white-space: nowrap;" />
{/if}
{/if}
{#if isCloud}
<span class="divider-wrapper">
<Divider vertical />
</span>
<Icon size="s" icon={IconCloud} />
<Badge
size="xs"
type="success"
variant="secondary"
content="Generally Available"
style="white-space: nowrap;" />
{/if}
{/if}
</Layout.Stack>
-1
View File
@@ -111,7 +111,6 @@ if (!building) {
scopedConsoleClient.setProject('console');
clientConsole.setEndpoint(endpoint).setProject('console');
clientRealtime.setEndpoint(endpoint).setProject('console');
clientProject.setEndpoint(endpoint).setMode('admin');
clientRealtime.setEndpoint(endpoint).setProject('console');
}
+10 -10
View File
@@ -1,32 +1,32 @@
import { isCloud } from '$lib/system';
import { isSameDay } from '$lib/helpers/date';
import { type BottomModalAlertItem, showBottomModalAlert } from '$lib/stores/bottom-alerts';
import TransactionsApiDark from '$lib/images/promos/transactions-api-dark.png';
import TransactionsApiLight from '$lib/images/promos/transactions-api-light.png';
import DbOperatorsDark from '$lib/images/promos/db-operators-dark.png';
import DbOperatorsLight from '$lib/images/promos/db-operators-light.png';
const listOfPromotions: BottomModalAlertItem[] = [];
if (isCloud) {
const transactionsApiPromo: BottomModalAlertItem = {
id: 'modal:transactions_api_announcement',
const dbOperatorsPromo: BottomModalAlertItem = {
id: 'modal:db_operators_announcement',
src: {
dark: TransactionsApiDark,
light: TransactionsApiLight
dark: DbOperatorsDark,
light: DbOperatorsLight
},
title: 'Announcing Transactions API',
message: 'Ensure data consistency across tables with atomic, all-or-nothing commits.',
title: 'Announcing DB operators',
message: 'Update multiple fields without fetching the entire row.',
plan: 'free',
importance: 8,
scope: 'project',
cta: {
text: 'Read announcement',
link: () => 'https://appwrite.io/blog/post/announcing-transactions-api',
link: () => 'https://appwrite.io/blog/post/announcing-db-operators',
external: true,
hideOnClick: true
},
show: true
};
listOfPromotions.push(transactionsApiPromo);
listOfPromotions.push(dbOperatorsPromo);
}
export function addBottomModalAlerts() {
@@ -1,5 +1,6 @@
<script lang="ts">
import { Card, Layout, Button } from '@appwrite.io/pink-svelte';
import { Form } from '$lib/elements/forms';
import { isCloud } from '$lib/system';
import { sdk } from '$lib/stores/sdk';
import { ID, Region } from '@appwrite.io/console';
@@ -97,24 +98,22 @@
class="u-only-dark"
alt="Appwrite Logo" />
<Card.Base variant="primary" padding="l">
<CreateProject
showTitle
bind:projectName
bind:id={projectId}
bind:region={projectRegion}
regions={$regionsStore?.regions}>
{#snippet submit()}
<Layout.Stack direction="row" justifyContent="flex-end">
<Button.Button
on:click={createProject}
type="submit"
variant="primary"
size="s">
Create
</Button.Button>
</Layout.Stack>
{/snippet}
</CreateProject>
<Form noStyle onSubmit={createProject}>
<CreateProject
showTitle
bind:projectName
bind:id={projectId}
bind:region={projectRegion}
regions={$regionsStore?.regions}>
{#snippet submit()}
<Layout.Stack direction="row" justifyContent="flex-end">
<Button.Button type="submit" variant="primary" size="s">
Create
</Button.Button>
</Layout.Stack>
{/snippet}
</CreateProject>
</Form>
</Card.Base>
{/if}
</div>
@@ -36,6 +36,7 @@
expandTabs,
databaseRelatedRowSheetOptions,
rowPermissionSheet,
isWaterfallFromFaker,
type Columns
} from './store';
import { addSubPanel, registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
@@ -84,12 +85,6 @@
let selectedOption: Option['name'] = 'String';
let createMoreColumns = false;
/**
* adding a lot of fake data will trigger the realtime below
* and will keep invalidating the `Dependencies.TABLE` making a lot of API noise!
*/
let isWaterfallFromFaker = false;
let columnCreationHandler: ((response: RealtimeResponse) => void) | null = null;
onMount(() => {
@@ -100,21 +95,9 @@
response.events.includes('databases.*.tables.*.columns.*') ||
response.events.includes('databases.*.tables.*.indexes.*')
) {
if (isWaterfallFromFaker) {
if ($isWaterfallFromFaker) {
columnCreationHandler?.(response);
}
// don't invalidate when -
// 1. from faker
// 2. ai columns creation
// 3. ai indexes creation
if (
!isWaterfallFromFaker &&
!$showIndexesSuggestions &&
!$tableColumnSuggestions.table
) {
invalidate(Dependencies.TABLE);
}
}
});
});
@@ -316,7 +299,7 @@
}
async function createFakeData() {
isWaterfallFromFaker = true;
isWaterfallFromFaker.set(true);
$spreadsheetLoading = true;
$randomDataModalState.show = false;
@@ -394,10 +377,8 @@
$randomDataModalState.value = 25;
}
/* api is too fast! */
// await sleep(1250);
$spreadsheetLoading = false;
isWaterfallFromFaker = false;
isWaterfallFromFaker.set(false);
spreadsheetRenderKey.set(hash(rowIds));
}
@@ -26,16 +26,24 @@
import { addNotification } from '$lib/stores/notifications';
import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { isSmallViewport } from '$lib/stores/viewport';
import { IconChevronDown, IconChevronUp, IconPlus } from '@appwrite.io/pink-icons-svelte';
import {
IconChevronDown,
IconChevronUp,
IconPlus,
IconRefresh
} from '@appwrite.io/pink-icons-svelte';
import type { Models } from '@appwrite.io/console';
import EmptySheet from './layout/emptySheet.svelte';
import CreateRow from './rows/create.svelte';
import { onDestroy } from 'svelte';
import { isCloud } from '$lib/system';
import { Empty as SuggestionsEmptySheet, tableColumnSuggestions } from '../(suggestions)';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
export let data: PageData;
let isRefreshing = false;
let showImportCSV = false;
// todo: might need a type fix here.
@@ -147,36 +155,66 @@
<svelte:fragment slot="tooltip">Filters</svelte:fragment>
</Tooltip>
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<Button
secondary
event={Click.DatabaseImportCsv}
disabled={!(hasColumns && hasValidColumns)}
on:click={() => (showImportCSV = true)}>
Import CSV
</Button>
{#if !$isSmallViewport}
<Layout.Stack
direction="row"
alignItems="center"
justifyContent="flex-end"
style="padding-right: 40px;">
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<Button
secondary
event="create_row"
event={Click.DatabaseImportCsv}
disabled={!(hasColumns && hasValidColumns)}
on:click={() => ($showRowCreateSheet.show = true)}>
<Icon icon={IconPlus} slot="start" size="s" />
Create row
on:click={() => (showImportCSV = true)}>
Import CSV
</Button>
{#if !$isSmallViewport}
<Button
secondary
event="create_row"
disabled={!(hasColumns && hasValidColumns)}
on:click={() => ($showRowCreateSheet.show = true)}>
<Icon icon={IconPlus} slot="start" size="s" />
Create row
</Button>
<Button
icon
size="s"
secondary
class="small-button-dimensions"
on:click={() => {
$expandTabs = !$expandTabs;
preferences.setKey('tableHeaderExpanded', $expandTabs);
}}>
<Icon icon={!$expandTabs ? IconChevronDown : IconChevronUp} size="s" />
</Button>
{/if}
<Button
icon
size="s"
secondary
class="small-button-dimensions"
on:click={() => {
$expandTabs = !$expandTabs;
preferences.setKey('tableHeaderExpanded', $expandTabs);
}}>
<Icon
icon={!$expandTabs ? IconChevronDown : IconChevronUp}
size="s" />
</Button>
<Tooltip disabled={isRefreshing || !data.rows.total} placement="top">
<Button
icon
size="s"
secondary
disabled={isRefreshing ||
!data.rows.total ||
!(hasColumns && hasValidColumns)}
class="small-button-dimensions"
on:click={async () => {
isRefreshing = true;
await invalidate(Dependencies.TABLE);
isRefreshing = false;
}}>
<div style:line-height="0px" class:rotating={isRefreshing}>
<Icon icon={IconRefresh} size="s" />
</div>
</Button>
<svelte:fragment slot="tooltip">Refresh</svelte:fragment>
</Tooltip>
{/if}
</Layout.Stack>
</Layout.Stack>
</Layout.Stack>
{#if $isSmallViewport}
@@ -282,4 +320,17 @@
width: 32px !important;
height: 32px !important;
}
:global(.rotating) {
animation: rotate 1s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>
@@ -23,6 +23,7 @@
type ColumnsWidth,
indexes,
isCsvImportInProgress,
isWaterfallFromFaker,
reorderItems,
showCreateIndexSheet
} from '../store';
@@ -55,6 +56,9 @@
import { page } from '$app/state';
import { debounce } from '$lib/helpers/debounce';
import type { PageData } from './$types';
import { realtime } from '$lib/stores/sdk';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
const {
data
@@ -140,6 +144,16 @@
onMount(() => {
columnsOrder = preferences.getColumnOrder(tableId);
columnsWidth = preferences.getColumnWidths(tableId + '#columns');
return realtime.forProject(page.params.region, ['project', 'console'], async (response) => {
if (
response.events.includes('databases.*.tables.*.columns.*.delete') ||
(response.events.includes('databases.*.tables.*.columns.*.update') &&
!$isWaterfallFromFaker)
) {
await invalidate(Dependencies.TABLE);
}
});
});
function getColumnStatusBadge(status: string): ComponentProps<Badge>['type'] {
@@ -1,5 +1,4 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { page } from '$app/state';
import { addNotification } from '$lib/stores/notifications';
import { table } from '../store';
@@ -9,7 +8,6 @@
import { isRelationship } from '../rows/store';
import Confirm from '$lib/components/confirm.svelte';
import { Layout } from '@appwrite.io/pink-svelte';
import { Dependencies } from '$lib/constants';
import type { Models } from '@appwrite.io/console';
let {
@@ -59,7 +57,6 @@
: `${selectedColumns.length} columns have been deleted`
});
await invalidate(Dependencies.TABLE);
showDelete = false;
selectedColumn = Array.isArray(selectedColumn) ? [] : null;
} catch (e) {
@@ -37,6 +37,9 @@
import { showCreateColumnSheet } from '../store';
import { isSmallViewport } from '$lib/stores/viewport';
import { page } from '$app/state';
import { realtime } from '$lib/stores/sdk';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
let {
data
@@ -96,6 +99,12 @@
onMount(() => {
columnsWidth = preferences.getColumnWidths(tableId + '#indexes');
return realtime.forProject(page.params.region, ['project', 'console'], (response) => {
if (response.events.includes('databases.*.tables.*.indexes.*')) {
invalidate(Dependencies.TABLE);
}
});
});
function getColumnStatusBadge(status: string): ComponentProps<Badge>['type'] {
@@ -104,9 +104,12 @@
};
$: rows = writable(data.rows);
$: if (rows) {
$: if ($rows) {
paginatedRows.clear();
paginatedRows.setPage(1, $rows.rows);
/* reset ui when the underlying data changes */
spreadsheetRenderKey.set(hash(Date.now().toString()));
}
// create index map for O(1) row lookups, reactive!
@@ -478,7 +481,7 @@
message: 'Column deleted'
});
invalidate(Dependencies.TABLE);
await invalidate(Dependencies.TABLE);
$databaseColumnSheetOptions.column = null;
} catch (error) {
@@ -28,6 +28,12 @@ export const table = derived(page, ($page) => $page.data.table as Table);
export const columns = derived(page, ($page) => $page.data.table.columns as Columns[]);
export const indexes = derived(page, ($page) => $page.data.table.indexes as Models.ColumnIndex[]);
/**
* adding a lot of fake data will trigger the realtime below
* and will keep invalidating the `Dependencies.TABLE` making a lot of API noise!
*/
export const isWaterfallFromFaker = writable(false);
export const tableColumns = writable<Column[]>([]);
export const isCsvImportInProgress = writable(false);
@@ -15,6 +15,8 @@
import { DeploymentSource, DeploymentCreatedBy, DeploymentDomains } from '$lib/components/git';
import { func } from '../store';
import { capitalize } from '$lib/helpers/string';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import { isCloud } from '$lib/system';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import Link from '$lib/elements/link.svelte';
@@ -36,6 +38,9 @@
footer?: Snippet;
} = $props();
let effectiveStatus = $derived(
getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
);
let totalSize = $derived(humanFileSize(deployment?.totalSize ?? 0));
</script>
@@ -122,11 +127,11 @@
</Layout.Stack>
<Layout.Stack direction="row" gap="xl">
{#if deployment.status === 'failed'}
{#if effectiveStatus === 'failed'}
<Layout.Stack gap="xxs" inline>
{@render titleSnippet('Status')}
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
<Status status={deployment.status} label={deployment.status} />
<Status status={effectiveStatus} label={effectiveStatus} />
</Typography.Text>
</Layout.Stack>
{:else}
@@ -2,7 +2,7 @@
import { page } from '$app/state';
import { SubMenu } from '$lib/components/menu';
import { type Models } from '@appwrite.io/console';
import { IconDownload } from '@appwrite.io/pink-icons-svelte';
import { IconDownload, IconChevronRight } from '@appwrite.io/pink-icons-svelte';
import { ActionMenu } from '@appwrite.io/pink-svelte';
import { getOutputDownload, getSourceDownload } from '../store';
@@ -13,7 +13,8 @@
{#if deployment?.status === 'ready' || deployment?.status === 'failed' || deployment?.status === 'building'}
<SubMenu>
<ActionMenu.Root noPadding>
<ActionMenu.Item.Button trailingIcon={IconDownload}>Download</ActionMenu.Item.Button>
<ActionMenu.Item.Button leadingIcon={IconDownload} trailingIcon={IconChevronRight}
>Download</ActionMenu.Item.Button>
</ActionMenu.Root>
<svelte:fragment slot="menu">
<ActionMenu.Root>
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { goto, invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import Confirm from '$lib/components/confirm.svelte';
import { Dependencies } from '$lib/constants';
@@ -20,6 +21,12 @@
functionId: selectedDeployment.resourceId,
deploymentId: selectedDeployment.$id
});
if (page.route.id?.includes('deployment-[deployment]')) {
goto(
`${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/deployments`
);
return;
}
await invalidate(Dependencies.FUNCTION);
showDelete = false;
addNotification({
@@ -113,7 +113,7 @@
placement={'bottom'}>
<div>
<ActionMenu.Item.Button
trailingIcon={IconRefresh}
leadingIcon={IconRefresh}
disabled={activeDeployment.sourceSize === 0}
on:click={() => {
selectedDeployment = activeDeployment;
@@ -134,7 +134,7 @@
deployment={activeDeployment}
{toggle} />
<ActionMenu.Item.Anchor
trailingIcon={IconTerminal}
leadingIcon={IconTerminal}
href={`${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/deployment-${activeDeployment.$id}`}>
Build logs
</ActionMenu.Item.Anchor>
@@ -1,39 +0,0 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import Confirm from '$lib/components/confirm.svelte';
import { Dependencies } from '$lib/constants';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { page } from '$app/state';
import type { Models } from '@appwrite.io/console';
export let showDelete = false;
export let selectedDeployment: Models.Deployment = null;
let error: string;
async function handleSubmit() {
try {
await sdk
.forProject(page.params.region, page.params.project)
.functions.deleteDeployment({
functionId: selectedDeployment.resourceId,
deploymentId: selectedDeployment.$id
});
await invalidate(Dependencies.FUNCTION);
showDelete = false;
addNotification({
type: 'success',
message: `Deployment has been deleted`
});
trackEvent(Submit.DeploymentDelete);
} catch (e) {
error = e.message;
trackError(e, Submit.DeploymentDelete);
}
}
</script>
<Confirm onSubmit={handleSubmit} title="Delete deployment" bind:open={showDelete} bind:error>
Are you sure you want to delete this deployment?
</Confirm>
@@ -24,9 +24,11 @@
} from '@appwrite.io/pink-svelte';
import { capitalize } from '$lib/helpers/string';
import { formatTimeDetailed } from '$lib/helpers/timeConversion';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import { timer } from '$lib/actions/timer';
import { app } from '$lib/stores/app';
import { IconDotsHorizontal, IconRefresh } from '@appwrite.io/pink-icons-svelte';
import { IconDotsHorizontal, IconRefresh, IconTrash } from '@appwrite.io/pink-icons-svelte';
import { Menu } from '$lib/components/menu';
import { canWriteFunctions } from '$lib/stores/roles';
import { Click, trackEvent } from '$lib/actions/analytics';
@@ -36,22 +38,29 @@
import { readOnly } from '$lib/stores/billing';
import RedeployModal from '../(modals)/redeployModal.svelte';
export let data;
let { data } = $props();
let showDelete = false;
let showCancel = false;
let showActivate = false;
let showRedeploy = false;
let effectiveStatus = $derived(
getEffectiveBuildStatus(
data.deployment.status,
data.deployment.$createdAt,
$regionalConsoleVariables
)
);
let showDelete = $state(false);
let showCancel = $state(false);
let showActivate = $state(false);
let showRedeploy = $state(false);
onMount(() => {
return realtime.forProject(page.params.region, 'console', (response) => {
return realtime.forConsole(page.params.region, 'console', (message) => {
if (
response.events.includes(
message.events.includes(
`functions.${page.params.function}.deployments.${page.params.deployment}.update`
)
) {
const payload = response.payload as Models.Deployment;
if (payload.status === 'ready') {
const payload = message.payload as Models.Deployment;
if (['ready', 'failed'].includes(payload.status)) {
invalidate(Dependencies.DEPLOYMENT);
}
}
@@ -78,7 +87,7 @@
<DeploymentCard proxyRuleList={data.proxyRuleList} deployment={data.deployment}>
{#snippet footer()}
<Layout.Stack direction="row" alignItems="center" inline>
{#if data.deployment.status === 'processing' || data.deployment.status === 'building' || data.deployment.status === 'waiting'}
{#if effectiveStatus === 'processing' || effectiveStatus === 'building' || effectiveStatus === 'waiting'}
<Button
text
on:click={() => {
@@ -98,7 +107,7 @@
placement={'bottom'}>
<div>
<ActionMenu.Item.Button
trailingIcon={IconRefresh}
leadingIcon={IconRefresh}
disabled={data.deployment.sourceSize === 0}
on:click={() => {
showRedeploy = true;
@@ -115,6 +124,18 @@
{#if !!data.deployment?.sourceSize || !!data.deployment?.sourceSize}
<DownloadActionMenuItem deployment={data.deployment} {toggle} />
{/if}
{#if $canWriteFunctions && ['ready', 'failed'].includes(data.deployment.status)}
<ActionMenu.Item.Button
status="danger"
leadingIcon={IconTrash}
on:click={() => {
showDelete = true;
toggle();
}}
style="width: 100%">
Delete
</ActionMenu.Item.Button>
{/if}
</ActionMenu.Root>
</svelte:fragment>
</Menu>
@@ -140,9 +161,9 @@
<Card.Base padding="s">
<Accordion
title="Deployment logs"
badge={capitalize(data.deployment.status)}
badge={capitalize(effectiveStatus)}
open
badgeType={badgeTypeDeployment(data.deployment.status)}
badgeType={badgeTypeDeployment(effectiveStatus)}
hideDivider>
<Layout.Stack gap="xl">
{#key data.deployment.buildLogs}
@@ -155,7 +176,7 @@
<svelte:fragment slot="end">
<Layout.Stack direction="row" alignItems="center" inline>
{#if ['processing', 'building'].includes(data.deployment.status)}
{#if ['processing', 'building'].includes(effectiveStatus)}
<Typography.Code color="--fgcolor-neutral-secondary">
<Layout.Stack direction="row" alignItems="center" inline>
<p use:timer={{ start: data.deployment.$createdAt }}></p>
@@ -28,6 +28,8 @@
import Delete from './(modals)/deleteModal.svelte';
import { capitalize } from '$lib/helpers/string';
import { deploymentStatusConverter } from '$lib/stores/git';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import DownloadActionMenuItem from './(components)/downloadActionMenuItem.svelte';
import { Menu } from '$lib/components/menu';
import { sdk } from '$lib/stores/sdk';
@@ -82,9 +84,13 @@
{/each}
<Table.Header.Cell column="actions" {root} />
{/snippet}
{#snippet children(root)}
{#each data.deploymentList.deployments as deployment (deployment.$id)}
{@const effectiveStatus = getEffectiveBuildStatus(
deployment.status,
deployment.$createdAt,
$regionalConsoleVariables
)}
<Table.Row.Link
{root}
id={deployment.$id}
@@ -96,23 +102,21 @@
<Id value={deployment.$id}>{deployment.$id}</Id>
{/key}
{:else if column.id === 'status'}
{@const status = deployment.status}
{#if data?.activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(status)}
label={capitalize(status)} />
status={deploymentStatusConverter(effectiveStatus)}
label={capitalize(effectiveStatus)} />
{/if}
{:else if column.id === 'type'}
<DeploymentSource {deployment} />
{:else if column.id === '$updatedAt'}
<DeploymentCreatedBy {deployment} />
{:else if column.id === 'buildDuration'}
{#if ['waiting'].includes(deployment.status)}
{#if ['waiting'].includes(effectiveStatus)}
-
{:else if ['processing', 'building'].includes(deployment.status)}
{:else if ['processing', 'building'].includes(effectiveStatus)}
<span use:timer={{ start: deployment.$createdAt }}></span>
{:else}
{formatTimeDetailed(deployment.buildDuration)}
@@ -139,7 +143,7 @@
placement={'bottom'}>
<div>
<ActionMenu.Item.Button
trailingIcon={IconRefresh}
leadingIcon={IconRefresh}
disabled={deployment.sourceSize === 0}
on:click={() => {
selectedDeployment = deployment;
@@ -155,7 +159,7 @@
</Tooltip>
{#if deployment.status === 'ready' && deployment.$id !== $func.deploymentId}
<ActionMenu.Item.Button
trailingIcon={IconLightningBolt}
leadingIcon={IconLightningBolt}
on:click={() => {
selectedDeployment = deployment;
showActivate = true;
@@ -167,7 +171,7 @@
<DownloadActionMenuItem {deployment} {toggle} />
{#if deployment.status === 'processing' || deployment.status === 'building' || deployment.status === 'waiting'}
{#if effectiveStatus === 'processing' || effectiveStatus === 'building' || effectiveStatus === 'waiting'}
<ActionMenu.Item.Button
trailingIcon={IconXCircle}
on:click={() => {
@@ -180,9 +184,9 @@
Cancel
</ActionMenu.Item.Button>
{/if}
{#if deployment.status !== 'building' && deployment.status !== 'processing' && deployment.status !== 'waiting'}
{#if effectiveStatus !== 'building' && effectiveStatus !== 'processing' && effectiveStatus !== 'waiting'}
<ActionMenu.Item.Button
trailingIcon={IconTrash}
leadingIcon={IconTrash}
status="danger"
on:click={() => {
selectedDeployment = deployment;
@@ -15,6 +15,8 @@
IconXCircle
} from '@appwrite.io/pink-icons-svelte';
import { ActionMenu, Icon, Tooltip } from '@appwrite.io/pink-svelte';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
export let selectedDeployment: Models.Deployment;
export let deployment: Models.Deployment;
@@ -51,6 +53,11 @@
</Button>
<svelte:fragment slot="menu" let:toggle>
<ActionMenu.Root>
{@const effectiveStatus = getEffectiveBuildStatus(
deployment.status,
deployment.$createdAt,
$regionalConsoleVariables
)}
{#if !inCard}
<Tooltip disabled={selectedDeployment?.sourceSize !== 0} placement={'bottom'}>
<div>
@@ -70,7 +77,7 @@
<div slot="tooltip">Source is empty</div>
</Tooltip>
{/if}
{#if deployment?.status === 'ready' && deployment?.$id !== activeDeployment}
{#if effectiveStatus === 'ready' && deployment?.$id !== activeDeployment}
<ActionMenu.Item.Button
leadingIcon={IconLightningBolt}
on:click={(e) => {
@@ -82,7 +89,7 @@
Activate
</ActionMenu.Item.Button>
{/if}
{#if deployment?.status === 'ready' || deployment?.status === 'failed' || deployment?.status === 'building'}
{#if effectiveStatus === 'ready' || effectiveStatus === 'failed' || effectiveStatus === 'building'}
<SubMenu>
<ActionMenu.Root noPadding>
<ActionMenu.Item.Button
@@ -101,7 +108,7 @@
</ActionMenu.Item.Anchor>
<ActionMenu.Item.Anchor
disabled={deployment?.status !== 'ready'}
disabled={effectiveStatus !== 'ready'}
on:click={toggle}
href={getOutputDownload(deployment.$id)}
external>
@@ -112,7 +119,7 @@
</SubMenu>
{/if}
{#if deployment?.status === 'processing' || deployment?.status === 'building' || deployment.status === 'waiting'}
{#if effectiveStatus === 'processing' || effectiveStatus === 'building' || effectiveStatus === 'waiting'}
<ActionMenu.Item.Button
leadingIcon={IconXCircle}
status="danger"
@@ -124,7 +131,7 @@
Cancel
</ActionMenu.Item.Button>
{/if}
{#if deployment.status !== 'building' && deployment.status !== 'processing' && deployment?.status !== 'waiting'}
{#if ['ready', 'failed'].includes(deployment.status)}
<ActionMenu.Item.Button
status="danger"
leadingIcon={IconTrash}
@@ -18,6 +18,8 @@
<script lang="ts">
import { capitalize } from '$lib/helpers/string';
import { app } from '$lib/stores/app';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import type { Models } from '@appwrite.io/console';
import { Badge, Card, Layout, Logs, Spinner, Typography } from '@appwrite.io/pink-svelte';
import LogsTimer from './logsTimer.svelte';
@@ -38,15 +40,19 @@
emptyCopy?: string;
} = $props();
let effectiveStatus = $derived(
getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
);
function setCopy() {
if (deployment.status === 'failed') {
if (effectiveStatus === 'failed') {
return 'Your deployment has failed.';
} else if (deployment.status === 'building') {
} else if (effectiveStatus === 'building') {
//Do not remove empty space before the string it's an invisible character
return 'Preparing for build ... \n';
} else if (deployment.status === 'waiting') {
} else if (effectiveStatus === 'waiting') {
return 'Preparing for build ... \n';
} else if (deployment.status === 'processing') {
} else if (effectiveStatus === 'processing') {
return 'Preparing for build ... \n';
} else {
return emptyCopy;
@@ -62,16 +68,16 @@
Deployment logs
</Typography.Text>
<Badge
content={capitalize(deployment.status)}
content={capitalize(effectiveStatus)}
size="xs"
variant="secondary"
type={badgeTypeDeployment(deployment.status)} />
type={badgeTypeDeployment(effectiveStatus)} />
</Layout.Stack>
<LogsTimer status={deployment.status} {deployment} />
<LogsTimer status={effectiveStatus} {deployment} />
</Layout.Stack>
{/if}
{#if ['waiting', 'processing'].includes(deployment.status) || (deployment.status === 'building' && !deployment?.buildLogs?.length)}
{#if ['waiting', 'processing'].includes(effectiveStatus) || (effectiveStatus === 'building' && !deployment?.buildLogs?.length)}
<Card.Base variant="secondary">
<Layout.Stack direction="row" justifyContent="center" gap="s">
<Spinner /> Waiting for build to start...
@@ -1,16 +1,21 @@
<script lang="ts">
import { timer } from '$lib/actions/timer';
import { formatTimeDetailed } from '$lib/helpers/timeConversion';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import type { Models } from '@appwrite.io/console';
import { Layout, Spinner, Typography } from '@appwrite.io/pink-svelte';
export let status: string;
export let deployment: Models.Deployment;
let { status, deployment }: { status: string; deployment: Models.Deployment } = $props();
let effectiveStatus = $derived(
getEffectiveBuildStatus(status, deployment.$createdAt, $regionalConsoleVariables)
);
</script>
<Layout.Stack direction="row" alignItems="center" inline>
{#if ['processing', 'building'].includes(status)}
{#if ['processing', 'building'].includes(effectiveStatus)}
<Typography.Code color="--fgcolor-neutral-secondary">
<Layout.Stack direction="row" alignItems="center" inline>
<p use:timer={{ start: deployment.$createdAt }}></p>
@@ -23,6 +23,8 @@
import { isCloud } from '$lib/system';
import { sdk } from '$lib/stores/sdk';
import { capitalize } from '$lib/helpers/string';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import { regionalProtocol } from '$routes/(console)/project-[region]-[project]/store';
import type { Snippet } from 'svelte';
@@ -40,6 +42,9 @@
footer?: Snippet;
} = $props();
let effectiveStatus = $derived(
getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
);
let show = $state(false);
const totalSize = $derived(humanFileSize(deployment?.totalSize ?? 0));
@@ -113,15 +118,15 @@
</Layout.Stack>
<Layout.Stack direction="row" gap="xl">
{#if deployment.status === 'failed'}
{#if effectiveStatus === 'failed'}
<Layout.Stack gap="xxs" inline>
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
Status
</Typography.Text>
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
<Status
status={deployment.status}
label={capitalize(deployment.status)} />
status={effectiveStatus}
label={capitalize(effectiveStatus)} />
</Typography.Text>
</Layout.Stack>
{:else}
@@ -22,15 +22,16 @@
siteId: selectedDeployment.resourceId,
deploymentId: selectedDeployment.$id
});
if (page.route.id?.includes('deployment-[deployment]')) {
goto(
`${base}/project-${page.params.region}-${page.params.project}/sites/site-${page.params.site}/deployments`
);
return;
}
await Promise.all([
invalidate(Dependencies.DEPLOYMENTS),
invalidate(Dependencies.SITE)
]);
if (page.url.href.includes(`deployment-${selectedDeployment.$id}`)) {
await goto(
`${base}/project-${page.params.region}-${page.params.project}/sites/site-${page.params.site}/deployments`
);
}
showDelete = false;
addNotification({
type: 'success',
@@ -13,6 +13,8 @@
import ActivateDeploymentModal from '../../../activateDeploymentModal.svelte';
import { Accordion, Tooltip } from '@appwrite.io/pink-svelte';
import { capitalize } from '$lib/helpers/string';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import LogsTimer from '../../../(components)/logsTimer.svelte';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
@@ -24,6 +26,9 @@
let { data }: PageProps = $props();
let deployment = $derived(data.deployment);
let effectiveStatus = $derived(
getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
);
let showRedeploy = $state(false);
let showActivate = $state(false);
@@ -46,7 +51,7 @@
<Container>
<SiteCard {deployment} proxyRuleList={data.proxyRuleList}>
{#snippet footer()}
{#if deployment?.status === 'ready' && data.proxyRuleList?.total}
{#if effectiveStatus === 'ready' && data.proxyRuleList?.total}
<Button
external
href={`${$regionalProtocol}${data.proxyRuleList.rules[0]?.domain}`}>
@@ -79,13 +84,13 @@
<Card padding="s">
<Accordion
title="Deployment logs"
badge={capitalize(deployment.status)}
badge={capitalize(effectiveStatus)}
open
badgeType={badgeTypeDeployment(deployment.status)}
badgeType={badgeTypeDeployment(effectiveStatus)}
hideDivider>
<Logs {deployment} hideTitle hideScrollButtons fullHeight />
<Logs {deployment} hideTitle fullHeight />
<svelte:fragment slot="end">
<LogsTimer status={deployment.status} {deployment} />
<LogsTimer status={effectiveStatus} {deployment} />
</svelte:fragment>
</Accordion>
</Card>
@@ -17,6 +17,8 @@
import { capitalize } from '$lib/helpers/string';
import DeploymentActionMenu from '../../(components)/deploymentActionMenu.svelte';
import { deploymentStatusConverter } from '$lib/stores/git';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import { sdk } from '$lib/stores/sdk';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { invalidate } from '$app/navigation';
@@ -69,9 +71,13 @@
{/each}
<Table.Header.Cell column="actions" {root} />
{/snippet}
{#snippet children(root)}
{#each data.deploymentList.deployments as deployment (deployment.$id)}
{@const effectiveStatus = getEffectiveBuildStatus(
deployment.status,
deployment.$createdAt,
$regionalConsoleVariables
)}
<Table.Row.Link
{root}
id={deployment.$id}
@@ -83,22 +89,21 @@
<Id value={deployment.$id}>{deployment.$id}</Id>
{/key}
{:else if column.id === 'status'}
{@const status = deployment.status}
{#if data?.activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(status)}
label={capitalize(status)} />
status={deploymentStatusConverter(effectiveStatus)}
label={capitalize(effectiveStatus)} />
{/if}
{:else if column.id === 'type'}
<DeploymentSource {deployment} />
{:else if column.id === '$updatedAt'}
<DeploymentCreatedBy {deployment} />
{:else if column.id === 'buildDuration'}
{#if ['waiting'].includes(deployment.status)}
{#if ['waiting'].includes(effectiveStatus)}
-
{:else if ['processing', 'building'].includes(deployment.status)}
{:else if ['processing', 'building'].includes(effectiveStatus)}
<span use:timer={{ start: deployment.$createdAt }}></span>
{:else}
{formatTimeDetailed(deployment.buildDuration)}
@@ -127,12 +132,6 @@
</Table.Row.Link>
{/each}
{/snippet}
{#snippet deleteContent(count)}
Are you sure you want to delete <strong>{count}</strong>
{count > 1 ? 'deployments' : 'deployment'} from your site -
<strong>{page.data.site.name}</strong>?
{/snippet}
</MultiSelectionTable>
{#if selectedDeployment}
@@ -11,6 +11,8 @@
import ActivateDeploymentModal from '../activateDeploymentModal.svelte';
import CancelDeploymentModal from './deployments/cancelDeploymentModal.svelte';
import { capitalize } from '$lib/helpers/string';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import DeleteDeploymentModal from './deployments/deleteDeploymentModal.svelte';
import DeploymentActionMenu from '../(components)/deploymentActionMenu.svelte';
import { deploymentStatusConverter } from '$lib/stores/git';
@@ -72,12 +74,17 @@
</Table.Cell>
<Table.Cell {root}>
{@const status = deployment.status}
{@const effectiveStatus = getEffectiveBuildStatus(
status,
deployment.$createdAt,
$regionalConsoleVariables
)}
{#if activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(status)}
label={capitalize(status)} />
status={deploymentStatusConverter(effectiveStatus)}
label={capitalize(effectiveStatus)} />
{/if}
</Table.Cell>
@@ -69,12 +69,12 @@ export const adapterDataList = [
{
framework: 'nextjs',
ssr: {
desc: "Ensure you don't set $ in $ file.",
code: ['output', 'next.config.js'],
desc: 'Set $ in $ file.',
code: ["output: 'standalone'", 'next.config.js'],
url: 'https://nextjs.org/docs/pages/building-your-application/deploying'
},
static: {
desc: 'Set $ in $ file',
desc: 'Set $ in $ file.',
code: ["output: 'export'", 'next.config.js'],
url: 'https://nextjs.org/docs/pages/building-your-application/deploying/static-exports'
}
@@ -82,12 +82,12 @@ export const adapterDataList = [
{
framework: 'analog',
ssr: {
desc: 'Set $ in $ plugin in $',
desc: 'Set $ in $ plugin in $.',
code: ['ssr: true', 'analog', 'vite.config.ts'],
url: 'https://analogjs.org/docs/features/server/server-side-rendering'
},
static: {
desc: 'Set $ in $ plugin in $',
desc: 'Set $ in $ plugin in $.',
code: ['static: true', 'analog', 'vite.config.ts'],
url: 'https://analogjs.org/docs/features/server/static-site-generation'
}
@@ -36,21 +36,34 @@
frameworks.find((framework) => framework.key === site.framework)
);
let showFallback = $derived(adapter === Adapter.Static);
let hasChanges = $derived(
let isUntouched = $derived(
installCommand === site?.installCommand &&
buildCommand === site?.buildCommand &&
outputDirectory === site?.outputDirectory &&
selectedFramework?.key === site?.framework &&
fallback === (site?.fallbackFile || undefined) &&
adapter === site?.adapter
(fallback ?? '') === (site?.fallbackFile ?? '') &&
(adapter ?? '') === (site?.adapter ?? '')
);
let frameworkAdapterData = $derived(
selectedFramework.adapters.find((a) => a.key === adapter) ?? selectedFramework.adapters[0]
);
$effect(() => {
if (adapter) {
const data = selectedFramework.adapters.find((a) => a.key === adapter);
if (data) {
installCommand = data.installCommand;
buildCommand = data.buildCommand;
outputDirectory = data.outputDirectory;
fallback = data.fallbackFile;
}
}
});
$effect(() => {
if (selectedFramework?.key !== site.framework) {
//Update adapter
// Update adapter
const singleAdapter = selectedFramework?.adapters?.length <= 1;
if (singleAdapter) {
const hasSSR = selectedFramework?.adapters?.some((a) => a?.key === Adapter.Ssr);
@@ -65,11 +78,13 @@
}
//Update values
const data = selectedFramework.adapters.find((a) => a.key === adapter);
const data =
selectedFramework.adapters.find((a) => a.key === adapter) ??
selectedFramework.adapters[0];
installCommand = data.installCommand;
buildCommand = data.buildCommand;
outputDirectory = data.outputDirectory;
adapter = selectedFramework.adapters[0].key as Adapter;
adapter = data.key as Adapter;
fallback = data.fallbackFile;
} else {
adapter = site.adapter as Adapter;
@@ -157,11 +172,11 @@
const data = selectedFramework.adapters.find((a) => a.key === adapter);
if (type === 'installCommand') {
installCommand = site?.installCommand ?? data.installCommand;
installCommand = data.installCommand;
} else if (type === 'buildCommand') {
buildCommand = site?.buildCommand ?? data.buildCommand;
buildCommand = data.buildCommand;
} else if (type === 'outputDirectory') {
outputDirectory = site?.outputDirectory ?? data.outputDirectory;
outputDirectory = data.outputDirectory;
}
}
</script>
@@ -216,7 +231,8 @@
{adapterData.ssr.desc}
{/if}
{#if adapterData?.ssr?.url}
<Link external href={adapterData.ssr.url}>Learn more</Link>
<Link variant="muted" size="m" external href={adapterData.ssr.url}
>Learn more</Link>
{/if}
</Card.Selector>
<Card.Selector
@@ -243,7 +259,11 @@
{adapterData.static.desc}
{/if}
{#if adapterData?.static?.url}
<Link external href={adapterData.static.url}>Learn more</Link>
<Link
variant="muted"
size="m"
external
href={adapterData.static.url}>Learn more</Link>
{/if}
</Card.Selector>
</Layout.Grid>
@@ -258,7 +278,11 @@
placeholder={frameworkAdapterData?.installCommand ||
'Enter install command'} />
<Button secondary size="s" on:click={() => reset('installCommand')}>
<Button
secondary
size="s"
on:click={() => reset('installCommand')}
disabled={installCommand === frameworkAdapterData?.installCommand}>
Reset
</Button>
</Layout.Stack>
@@ -269,7 +293,11 @@
bind:value={buildCommand}
placeholder={frameworkAdapterData?.buildCommand ||
'Enter build command'} />
<Button secondary size="s" on:click={() => reset('buildCommand')}>
<Button
secondary
size="s"
disabled={buildCommand === frameworkAdapterData?.buildCommand}
on:click={() => reset('buildCommand')}>
Reset
</Button>
</Layout.Stack>
@@ -280,7 +308,12 @@
bind:value={outputDirectory}
placeholder={frameworkAdapterData?.outputDirectory ||
'Enter output directory'} />
<Button secondary size="s" on:click={() => reset('outputDirectory')}>
<Button
secondary
size="s"
disabled={(outputDirectory ?? '') ===
(frameworkAdapterData?.outputDirectory ?? '')}
on:click={() => reset('outputDirectory')}>
Reset
</Button>
</Layout.Stack>
@@ -305,7 +338,7 @@
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={hasChanges} submit>Update</Button>
<Button disabled={isUntouched} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
+1 -1
View File
@@ -125,11 +125,11 @@
</Layout.Stack>
</Layout.Stack>
<InputSelect
required
id="project"
label="Choose a project"
options={projectOptions ?? []}
bind:value={$supportData.project}
required={false}
placeholder="Select project" />
<InputText
id="subject"