Merge pull request #2010 from appwrite/1.8.x

1.8.x
This commit is contained in:
Jake Barnby
2025-06-12 14:07:40 -04:00
committed by GitHub
22 changed files with 220 additions and 98 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
PUBLIC_CONSOLE_MODE=self-hosted
PUBLIC_CONSOLE_FEATURE_FLAGS=
PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import,attribute-encrypt
PUBLIC_APPWRITE_MULTI_REGION=false
PUBLIC_APPWRITE_ENDPOINT=http://localhost/v1
PUBLIC_STRIPE_KEY=
+3 -3
View File
@@ -79,7 +79,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
"PUBLIC_CONSOLE_MODE=cloud"
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import"
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import,attribute-encrypt"
"PUBLIC_APPWRITE_MULTI_REGION=true"
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY_STAGE }}"
@@ -118,7 +118,7 @@ jobs:
build-args: |
"PUBLIC_CONSOLE_MODE=self-hosted"
"PUBLIC_APPWRITE_MULTI_REGION=false"
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import"
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import,attribute-encrypt"
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
publish-cloud-no-regions:
@@ -156,6 +156,6 @@ jobs:
build-args: |
"PUBLIC_CONSOLE_MODE=cloud"
"PUBLIC_APPWRITE_MULTI_REGION=false"
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import"
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import,attribute-encrypt"
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY_STAGE }}"
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
+1 -1
View File
@@ -22,7 +22,7 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564",
"@appwrite.io/console": "^1.9.0",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "^2.0.0-RC.1",
"@appwrite.io/pink-legacy": "^1.0.3",
+9 -10
View File
@@ -12,8 +12,8 @@ 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@a5e5564
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564
specifier: ^1.9.0
version: 1.9.0
'@appwrite.io/pink-icons':
specifier: 0.25.0
version: 0.25.0
@@ -257,9 +257,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@a5e5564':
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564}
version: 1.8.0
'@appwrite.io/console@1.9.0':
resolution: {integrity: sha512-g8+zfdBF8mz7tRUER4CGVe5FHWQVLp8TlY/UOGCZ2TgUF1qnpNu/DhiQgph8uF+QZ9jDKCLAAZlP5//9t7ckWA==}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870}
@@ -1341,8 +1340,8 @@ packages:
'@types/prop-types@15.7.14':
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
'@types/react@18.3.22':
resolution: {integrity: sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==}
'@types/react@18.3.23':
resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==}
'@types/remarkable@2.0.8':
resolution: {integrity: sha512-eKXqPZfpQl1kOADjdKchHrp2gwn9qMnGXhH/AtZe0UrklzhGJkawJo/Y/D0AlWcdWoWamFNIum8+/nkAISQVGg==}
@@ -3636,7 +3635,7 @@ snapshots:
'@analytics/type-utils@0.6.2': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564': {}
'@appwrite.io/console@1.9.0': {}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870(svelte@5.25.3)':
dependencies:
@@ -4792,7 +4791,7 @@ snapshots:
'@types/prop-types@15.7.14': {}
'@types/react@18.3.22':
'@types/react@18.3.23':
dependencies:
'@types/prop-types': 15.7.14
csstype: 3.1.3
@@ -6753,7 +6752,7 @@ snapshots:
svelte-motion@0.12.2(svelte@5.25.3):
dependencies:
'@types/react': 18.3.22
'@types/react': 18.3.23
framesync: 6.1.2
popmotion: 11.0.5
style-value-types: 5.1.2
+2 -1
View File
@@ -20,5 +20,6 @@ function isFlagEnabled(name: string) {
export const flags = {
showSites: isFlagEnabled('sites'),
showCsvImport: isFlagEnabled('csv-import')
showCsvImport: isFlagEnabled('csv-import'),
showAttributeEncrypt: isFlagEnabled('attribute-encrypt')
};
+1
View File
@@ -44,6 +44,7 @@ export type Column = PinkColumn & {
format?: string;
exclude?: boolean;
elements?: string[] | { value: string | number; label: string }[];
encrypt?: boolean;
};
export function isValueOfStringEnum<T extends Record<string, string>>(
+1
View File
@@ -310,6 +310,7 @@ export type Plan = {
users: number;
teams: number;
databases: number;
databasesAllowEncrypt: boolean;
buckets: number;
fileSize: number;
functions: number;
@@ -13,10 +13,11 @@
Link,
Popover,
Table,
Tooltip,
Typography
} from '@appwrite.io/pink-svelte';
import Create from '../createAttribute.svelte';
import { isRelationship } from '../document-[document]/attributes/store';
import { isRelationship, isString } from '../document-[document]/attributes/store';
import FailedModal from '../failedModal.svelte';
import CreateIndex from '../indexes/createIndex.svelte';
import { attributes, type Attributes, isCsvImportInProgress } from '../store';
@@ -33,7 +34,8 @@
IconPlus,
IconSwitchHorizontal,
IconTrash,
IconViewList
IconViewList,
IconLockClosed
} from '@appwrite.io/pink-icons-svelte';
import type { ComponentProps } from 'svelte';
import { Click, trackEvent } from '$lib/actions/analytics';
@@ -112,25 +114,38 @@
{:else}
<Icon icon={option.icon} size="s" />
{/if}
<span class="text u-trim-1" data-private>{attribute.key}</span>
{#if attribute.status !== 'available'}
<Badge
size="s"
variant="secondary"
content={attribute.status}
type={getAttributeStatusBadge(attribute.status)} />
{#if attribute.error}
<Link.Button
variant="muted"
on:click={(e) => {
e.preventDefault();
error = attribute.error;
showFailed = true;
}}>Details</Link.Button>
<Layout.Stack direction="row" alignItems="center" gap="s">
<Layout.Stack inline direction="row" alignItems="center" gap="xxs">
<span class="text u-trim-1" data-private>{attribute.key}</span>
{#if isString(attribute) && attribute.encrypt}
<Tooltip>
<Icon
size="s"
icon={IconLockClosed}
color="--fgcolor-neutral-tertiary" />
<div slot="tooltip">Encrypted</div>
</Tooltip>
{/if}
</Layout.Stack>
{#if attribute.status !== 'available'}
<Badge
size="s"
variant="secondary"
content={attribute.status}
type={getAttributeStatusBadge(attribute.status)} />
{#if attribute.error}
<Link.Button
variant="muted"
on:click={(e) => {
e.preventDefault();
error = attribute.error;
showFailed = true;
}}>Details</Link.Button>
{/if}
{:else if attribute.required}
<Badge size="xs" variant="secondary" content="required" />
{/if}
{:else if attribute.required}
<Badge size="s" variant="secondary" content="required" />
{/if}
</Layout.Stack>
</Layout.Stack>
</Table.Cell>
<Table.Cell column="type" {root}>
@@ -100,5 +100,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -92,5 +92,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -93,5 +93,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -124,5 +124,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -122,5 +122,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -120,5 +120,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -92,5 +92,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -18,7 +18,8 @@
data.size,
data.required,
data.default,
data.array
data.array,
data.encrypt
);
}
export async function updateString(
@@ -42,20 +43,28 @@
</script>
<script lang="ts">
import { Selector } from '@appwrite.io/pink-svelte';
import { isCloud } from '$lib/system';
import { upgradeURL } from '$lib/stores/billing';
import { currentPlan } from '$lib/stores/organization';
import { createConservative } from '$lib/helpers/stores';
import { ActionMenu, Selector } from '@appwrite.io/pink-svelte';
import { InputNumber, InputText, InputTextarea } from '$lib/elements/forms';
import { Popover, Layout, Tag, Typography, Link } from '@appwrite.io/pink-svelte';
import { flags } from '$lib/flags';
export let data: Partial<Models.AttributeString> = {
required: false,
size: 0,
default: null,
array: false
array: false,
encrypt: false
};
export let editing = false;
let savedDefault = data.default;
let showEncrypt = flags.showAttributeEncrypt(page.data);
function handleDefaultState(hideDefault: boolean) {
if (hideDefault) {
savedDefault = data.default;
@@ -73,48 +82,110 @@
array: false,
...data
});
$: listen(data);
$: handleDefaultState($required || $array);
// Check plan on cloud, always allow on self-hosted
$: supportsStringEncryption = isCloud ? $currentPlan?.databasesAllowEncrypt : true;
</script>
<InputNumber
id="size"
label="Size"
required
placeholder="Enter size"
bind:value={data.size}
required={true} />
{#if data.size >= 50}
<InputTextarea
id="default"
label="Default"
placeholder="Enter string"
disabled={data.required || data.array}
nullable={!data.required && !data.array}
maxlength={data.size}
bind:value={data.default} />
{:else}
<InputText
id="default"
label="Default"
placeholder="Enter string"
disabled={data.required || data.array}
nullable={!data.required && !data.array}
maxlength={data.size}
bind:value={data.default} />
{/if}
min={supportsStringEncryption && data.encrypt ? 150 : undefined}
helper={supportsStringEncryption && data.encrypt
? 'Encrypted string attributes require a minimum size of 150.'
: undefined} />
<svelte:component
this={data.size >= 50 ? InputTextarea : InputText}
id="default"
label="Default"
placeholder="Enter string"
maxlength={data.size}
bind:value={data.default}
disabled={data.required || data.array}
nullable={!data.required && !data.array} />
<Selector.Checkbox
size="s"
id="required"
label="Required"
bind:checked={data.required}
disabled={data.array}
description="Indicate whether this attribute is required" />
description="Indicate whether this attribute is required." />
<Selector.Checkbox
size="s"
id="array"
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
{#if showEncrypt}
<Layout.Stack gap="xs" direction="column">
<div class="popover-holder" class:disabled-checkbox={!supportsStringEncryption || editing}>
<Layout.Stack inline gap="s" alignItems="flex-start" direction="row">
<Popover let:toggle placement="bottom-start">
<Selector.Checkbox
size="s"
id="encrypt"
bind:checked={data.encrypt}
disabled={!supportsStringEncryption || editing} />
<Layout.Stack gap="xxs" direction="column">
<button
type="button"
class="u-cursor-pointer"
on:click={(e) => {
if (!supportsStringEncryption) {
toggle(e);
} else {
data.encrypt = !data.encrypt;
}
}}>
<Layout.Stack inline gap="xxs" direction="row" alignItems="center">
<Typography.Text variant="m-500">Encrypted</Typography.Text>
{#if !supportsStringEncryption}
<Tag variant="default" size="xs" on:click={toggle}>Pro</Tag>
{/if}
</Layout.Stack>
</button>
<Typography.Text color="--fgcolor-neutral-tertiary">
Indicate whether this attribute is encrypted. Encrypted attributes
cannot be queried.
</Typography.Text>
</Layout.Stack>
<ActionMenu.Root width="180px" slot="tooltip">
<Typography.Text variant="m-500">
Available on Pro plan. <Link.Anchor href={$upgradeURL}
>Upgrade</Link.Anchor>
to enable encrypted attributes.
</Typography.Text>
</ActionMenu.Root>
</Popover>
</Layout.Stack>
</div>
</Layout.Stack>
{/if}
<style lang="scss">
.popover-holder {
& :global([role='tooltip']) {
margin-top: 4px;
left: 8rem !important;
}
// no cursor when disabled
&.disabled-checkbox :global(button) {
cursor: unset !important;
}
}
</style>
@@ -93,5 +93,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -15,3 +15,8 @@ export function isRelationship(attribute: Attributes): attribute is Models.Attri
if (!attribute) return false;
return attribute?.type === 'relationship';
}
export function isString(attribute: Attributes): attribute is Models.AttributeString {
if (!attribute) return false;
return attribute?.type === 'string';
}
@@ -79,13 +79,15 @@
{ id: 'type' },
{ id: 'attributes' },
{ id: 'orders' },
{ id: 'lengths' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
<Table.Header.Cell column="attributes" {root}>Attributes</Table.Header.Cell>
<Table.Header.Cell column="orders" {root}>Asc/Desc</Table.Header.Cell>
<Table.Header.Cell column="orders" {root}>Orders</Table.Header.Cell>
<Table.Header.Cell column="lengths" {root}>Lengths</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.collection.indexes as index}
@@ -118,6 +120,9 @@
<Table.Cell column="orders" {root}>
{index.orders}
</Table.Cell>
<Table.Cell column="lengths" {root}>
{index.lengths}
</Table.Cell>
<Table.Cell column="actions" {root}>
<Popover let:toggle padding="none" placement="bottom-end">
<Button text icon ariaLabel="more options" on:click={toggle}>
@@ -5,7 +5,7 @@
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Modal } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, InputSelect, InputText } from '$lib/elements/forms';
import { Button, InputNumber, InputSelect, InputText } from '$lib/elements/forms';
import { remove } from '$lib/helpers/array';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
@@ -36,7 +36,7 @@
label: attribute.key
}));
let attributeList = [{ value: '', order: '' }];
let attributeList = [{ value: '', order: '', length: null }];
function generateIndexKey() {
let indexKeys = $indexes.map((index) => index.key);
@@ -51,8 +51,8 @@
function initialize() {
attributeList = externalAttribute
? [{ value: externalAttribute.key, order: 'ASC' }]
: [{ value: '', order: 'ASC' }];
? [{ value: externalAttribute.key, order: 'ASC', length: null }]
: [{ value: '', order: 'ASC', length: null }];
selectedType = IndexType.Key;
key = `index_${$indexes.length + 1}`;
}
@@ -78,8 +78,10 @@
key,
selectedType,
attributeList.map((a) => a.value),
attributeList.map((a) => a.order)
attributeList.map((a) => a.order),
attributeList.map((a) => (a.length ? Number(a.length) : null))
);
await Promise.allSettled([
invalidate(Dependencies.COLLECTION),
invalidate(Dependencies.DATABASE)
@@ -105,7 +107,7 @@
if (addAttributeDisabled) return;
// We assign instead of pushing to trigger Svelte's reactivity
attributeList = [...attributeList, { value: '', order: '' }];
attributeList = [...attributeList, { value: '', order: '', length: null }];
}
</script>
@@ -134,17 +136,26 @@
label={i === 0 ? 'Attribute' : undefined}
placeholder="Select Attribute"
bind:value={attribute.value} />
<InputSelect
options={[
{ value: 'ASC', label: 'ASC' },
{ value: 'DESC', label: 'DESC' }
]}
required
id={`order-${i}`}
label={i === 0 ? 'Order' : undefined}
bind:value={attribute.order}
placeholder="Select Order" />
<Layout.Stack direction="row" alignItems="flex-end" gap="xs">
<InputSelect
options={[
{ value: 'ASC', label: 'ASC' },
{ value: 'DESC', label: 'DESC' }
]}
required
id={`order-${i}`}
label={i === 0 ? 'Order' : undefined}
bind:value={attribute.order}
placeholder="Select Order" />
{#if selectedType === IndexType.Key}
<InputNumber
id={`length-${i}`}
label={i === 0 ? 'Length' : undefined}
placeholder="Enter Length"
bind:value={attribute.length} />
{/if}
<Button
icon
compact
@@ -39,6 +39,12 @@
id={`value-${selectedIndex.orders[i]}`}
value={selectedIndex.orders[i]}
readonly />
<InputText
required
label={i === 0 ? 'Length' : ''}
id={`value-${selectedIndex.lengths[i]}`}
value={selectedIndex.lengths[i]?.toString() ?? null}
readonly />
</Layout.Stack>
{/each}
{/if}
@@ -12,7 +12,11 @@
import type { Models } from '@appwrite.io/console';
import { afterUpdate, onMount } from 'svelte';
import type { PageData } from './$types';
import { isRelationship, isRelationshipToMany } from './document-[document]/attributes/store';
import {
isRelationship,
isRelationshipToMany,
isString
} from './document-[document]/attributes/store';
import RelationshipsModal from './relationshipsModal.svelte';
import { attributes, collection, columns } from './store';
import type { ColumnType } from '$lib/helpers/types';
@@ -23,11 +27,12 @@
Link,
Badge,
FloatingActionBar,
InteractiveText,
Typography
} from '@appwrite.io/pink-svelte';
import { toLocaleDateTime } from '$lib/helpers/date';
import DualTimeView from '$lib/components/dualTimeView.svelte';
import { flags } from '$lib/flags';
export let data: PageData;
const databaseId = page.params.database;
@@ -163,6 +168,8 @@
) as Models.AttributeRelationship[];
let checked = false;
let showEncrypt = flags.showAttributeEncrypt(data);
</script>
<Table.Root
@@ -251,6 +258,14 @@
<span slot="title">Timestamp</span>
{toLocaleDateTime(formatted.whole, true, 'UTC')}
</DualTimeView>
{:else if isString(attr) && attr.encrypt && showEncrypt}
<button on:click={(e) => e.preventDefault()}>
<InteractiveText
copy={false}
variant="secret"
isVisible={false}
text={formatted.value} />
</button>
{:else if formatted.truncated}
<Tooltip placement="bottom" disabled={!formatted.truncated}>
<Typography.Text truncate>{formatted.value}</Typography.Text>