Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Svelte 5 format.
  2. productLabel should have proper union type, site | function | global.

Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,29 @@
import { page } from '$app/state';
import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte';

export let show = false;
export let variables: Partial<Models.Variable>[];
export type ProductLabel = 'site' | 'function';

let newVariables: Partial<Models.Variable>[] = [{ key: '', value: '' }];
let secret = false;
let error = '';
let {
show = $bindable(false),
variables = $bindable(),
productLabel = 'site'
}: {
show: boolean;
variables: Partial<Models.Variable>[];
productLabel?: ProductLabel;
} = $props();

let newVariables = $state<Partial<Models.Variable>[]>([{ key: '', value: '' }]);
let secret = $state(false);
let error = $state('');

$effect(() => {
if (!show) {
newVariables = [{ key: '', value: '' }];
secret = false;
error = '';
}
});

function handleVariable() {
try {
Expand Down Expand Up @@ -54,19 +71,17 @@

function removeVariable(index: number) {
if (newVariables.length === 1) {
newVariables[0].key = '';
newVariables[0].value = '';
newVariables = [{ key: '', value: '' }];
} else {
newVariables.splice(index, 1);
newVariables = [...newVariables];
newVariables = newVariables.filter((_, i) => i !== index);
}
}
</script>

<Modal bind:show onSubmit={handleVariable} title="Create variables" bind:error>
<span slot="description">
Set the environment variables or secret that will be passed to your site. Global variables
can be set in <Link
Set the environment variables or secret that will be passed to your {productLabel}. Global
variables can be set in <Link
variant="muted"
href={`${base}/project-${page.params.region}-${page.params.project}/settings`}
>project settings</Link
Expand Down Expand Up @@ -95,7 +110,7 @@
type="button"
size="s"
disabled={newVariables.length === 1 && !pair.key && !pair.value}
on:click={() => removeVariable(i)}>
onclick={() => removeVariable(i)}>
<Icon icon={IconX} />
</PinkButton.Button>
</Layout.Stack>
Expand Down
271 changes: 271 additions & 0 deletions src/lib/components/variables/environmentVariables.svelte
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

svelte 5

Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
<script lang="ts">
import { Empty, Paginator } from '$lib/components';
import { Button } from '$lib/elements/forms';
import {
ActionMenu,
Accordion,
Badge,
InteractiveText,
Icon,
Layout,
Popover,
Skeleton,
Table,
Tooltip,
Button as PinkButton
} from '@appwrite.io/pink-svelte';
import {
IconDotsHorizontal,
IconCode,
IconUpload,
IconPlus,
IconTrash,
IconEyeOff,
IconPencil
} from '@appwrite.io/pink-icons-svelte';
import type { Models } from '@appwrite.io/console';
import VariableEditorModal from './variableEditorModal.svelte';
import SecretVariableModal from './secretVariableModal.svelte';
import ImportVariablesModal from './importVariablesModal.svelte';
import CreateVariableModal, { type ProductLabel } from './createVariableModal.svelte';
import DeleteVariableModal from './deleteVariableModal.svelte';
import UpdateVariableModal from './updateVariableModal.svelte';
import { Click, trackEvent } from '$lib/actions/analytics';

const DOCS_LINKS: Record<ProductLabel, string> = {
site: 'https://appwrite.io/docs/products/sites/develop#accessing-environment-variables',
function: 'https://appwrite.io/docs/products/functions/develop#environment-variables'
};

let {
variables = $bindable([]),
productLabel = 'site',
analyticsSource = 'site_configuration',
analyticsCreateSource = 'site_settings',
isLoading = false
}: {
variables: Partial<Models.Variable>[];
productLabel?: ProductLabel;
analyticsSource?: string;
analyticsCreateSource?: string;
isLoading?: boolean;
} = $props();

let showEditorModal = $state(false);
let showImportModal = $state(false);
let showSecretModal = $state(false);
let showCreate = $state(false);
let showUpdate = $state(false);
let showDelete = $state(false);
let currentVariable = $state<Partial<Models.Variable>>(undefined);

const createSource = $derived(analyticsCreateSource || analyticsSource);
const docsLink = $derived(DOCS_LINKS[productLabel]);

const tableColumns = [
{ id: 'key', width: { min: 300 } },
{ id: 'value', width: { min: 280 } },
{ id: 'actions', width: 40 }
];
</script>

<Accordion title="Environment variables" badge="Optional" hideDivider>
<Layout.Stack gap="xl">
Set up environment variables to securely manage keys and settings for your project.
<Layout.Stack gap="l">
<Layout.Stack direction="row">
<Layout.Stack direction="row" gap="s">
<Button
secondary
size="s"
on:click={() => {
showEditorModal = true;
trackEvent(Click.VariablesUpdateClick, {
source: analyticsSource
});
}}>
<Icon slot="start" icon={IconCode} /> Editor
</Button>
<Button
secondary
size="s"
on:click={() => {
showImportModal = true;
trackEvent(Click.VariablesImportClick, {
source: analyticsSource
});
}}>
<Icon slot="start" icon={IconUpload} /> Import .env
</Button>
</Layout.Stack>
{#if variables?.length}
<Button
secondary
size="s"
on:click={() => {
showCreate = true;
trackEvent(Click.VariablesCreateClick, {
source: createSource
});
}}>
<Icon slot="start" icon={IconPlus} /> Create variable
</Button>
{/if}
</Layout.Stack>

{#if isLoading && !variables?.length}
<Table.Root class="responsive-table" let:root columns={tableColumns}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
<Table.Header.Cell column="value" {root}>Value</Table.Header.Cell>
<Table.Header.Cell column="actions" {root}></Table.Header.Cell>
</svelte:fragment>
{#each Array(3) as _}
<Table.Row.Base {root}>
<Table.Cell column="key" {root}>
<Skeleton variant="line" width={120} height={14} />
</Table.Cell>
<Table.Cell column="value" {root}>
<Skeleton variant="line" width="100%" height={14} />
</Table.Cell>
<Table.Cell column="actions" {root}>
<Skeleton variant="line" width={24} height={14} />
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{:else if variables?.length}
<Paginator items={variables} limit={6} hideFooter={variables.length <= 6}>
{#snippet children(paginatedItems)}
<Table.Root let:root columns={tableColumns}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
<Table.Header.Cell column="value" {root}>Value</Table.Header.Cell>
<Table.Header.Cell column="actions" {root}></Table.Header.Cell>
</svelte:fragment>
{#each paginatedItems as variable}
<Table.Row.Base {root}>
<Table.Cell column="key" {root}>{variable.key}</Table.Cell>
<Table.Cell column="value" {root}>
<!-- TODO: fix max width -->
<div style="max-width: 100%">
{#if variable.secret}
<Tooltip maxWidth="26rem">
<Badge
content="Secret"
variant="secondary"
size="s" />
<svelte:fragment slot="tooltip">
This value is secret, you cannot see its
value.
</svelte:fragment>
</Tooltip>
{:else}
<InteractiveText
variant="secret"
isVisible={true}
text={variable.value} />
{/if}
</div>
</Table.Cell>
<Table.Cell column="actions" {root}>
<div style="margin-inline-start: auto">
<Popover
padding="none"
placement="bottom-end"
let:toggle>
<PinkButton.Button
icon
variant="text"
size="s"
aria-label="More options"
onclick={(e) => {
e.preventDefault();
toggle(e);
}}>
<Icon icon={IconDotsHorizontal} size="s" />
</PinkButton.Button>

<svelte:fragment slot="tooltip" let:toggle>
<ActionMenu.Root>
{#if !variable?.secret}
<ActionMenu.Item.Button
leadingIcon={IconPencil}
onclick={(e) => {
toggle(e);
currentVariable = variable;
showUpdate = true;
}}>
Update
</ActionMenu.Item.Button>
{/if}
{#if !variable?.secret}
<ActionMenu.Item.Button
leadingIcon={IconEyeOff}
onclick={(e) => {
toggle(e);
currentVariable = variable;
showSecretModal = true;
}}>
Secret
</ActionMenu.Item.Button>
{/if}
<ActionMenu.Item.Button
status="danger"
leadingIcon={IconTrash}
onclick={(e) => {
toggle(e);
currentVariable = variable;
showDelete = true;
}}>
Delete
</ActionMenu.Item.Button>
</ActionMenu.Root>
</svelte:fragment>
</Popover>
</div>
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{/snippet}
</Paginator>
{:else}
<Empty
on:click={() => {
showCreate = true;
trackEvent(Click.VariablesCreateClick, {
source: createSource
});
}}>Create variables to get started</Empty>
{/if}
</Layout.Stack>
</Layout.Stack>
</Accordion>

{#if showEditorModal}
<VariableEditorModal bind:variables bind:showEditor={showEditorModal} {docsLink} />
{/if}

{#if showSecretModal}
<SecretVariableModal bind:show={showSecretModal} bind:currentVariable bind:variables />
{/if}

{#if showImportModal}
<ImportVariablesModal bind:show={showImportModal} bind:variables />
{/if}

{#if showCreate}
<CreateVariableModal bind:show={showCreate} bind:variables {productLabel} />
{/if}
{#if showUpdate}
<UpdateVariableModal
bind:show={showUpdate}
bind:variables
bind:selectedVar={currentVariable}
{productLabel} />
{/if}

{#if showDelete}
<DeleteVariableModal bind:show={showDelete} bind:variables bind:currentVariable />
{/if}
7 changes: 7 additions & 0 deletions src/lib/components/variables/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { default as CreateVariableModal, type ProductLabel } from './createVariableModal.svelte';
export { default as DeleteVariableModal } from './deleteVariableModal.svelte';
export { default as EnvironmentVariables } from './environmentVariables.svelte';
export { default as ImportVariablesModal } from './importVariablesModal.svelte';
export { default as SecretVariableModal } from './secretVariableModal.svelte';
export { default as UpdateVariableModal } from './updateVariableModal.svelte';
export { default as VariableEditorModal } from './variableEditorModal.svelte';
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
export let show = false;
export let selectedVar: Partial<Models.Variable>;
export let variables: Partial<Models.Variable>[];
export let productLabel = 'site';

let pair = {
$id: selectedVar?.$id,
Expand Down Expand Up @@ -40,7 +41,8 @@

<Modal bind:show onSubmit={handleVariable} title="Update variable">
<span slot="description">
Update the environment variable for your site. Global variables can be set in <Link
Update the environment variable for your {productLabel}. Global variables can be set in
<Link
variant="muted"
href={`${base}/project-${page.params.region}-${page.params.project}/settings`}
>project settings</Link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

export let showEditor = false;
export let variables: Partial<Models.Variable>[];
export let docsLink =
'https://appwrite.io/docs/products/sites/develop#accessing-environment-variables';

const editableVariables = variables.filter((variable) => !variable.secret);
const secretVariables = variables.filter((variable) => variable.secret);
Expand Down Expand Up @@ -122,11 +124,7 @@
{#if secretVariables?.length > 0}
<Alert.Inline status="info">
{secretVariables.length} secret variables are hidden from the editor. Their values will
remain unchanged. <Link
href="https://appwrite.io/docs/products/sites/develop#accessing-environment-variables"
external
variant="muted">Learn more</Link
>.
remain unchanged. <Link href={docsLink} external variant="muted">Learn more</Link>.
</Alert.Inline>
{/if}
<Layout.Stack gap="s">
Expand Down
Loading
Loading