Skip to content

Commit 38d4805

Browse files
authored
Merge pull request #2851 from appwrite/feat-detection-vars
feat: auto-fill detected env variables for sites/functions
2 parents 16f9572 + 4dfee7b commit 38d4805

13 files changed

Lines changed: 380 additions & 249 deletions

File tree

src/routes/(console)/project-[region]-[project]/sites/create-site/createVariableModal.svelte renamed to src/lib/components/variables/createVariableModal.svelte

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,29 @@
99
import { page } from '$app/state';
1010
import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte';
1111
12-
export let show = false;
13-
export let variables: Partial<Models.Variable>[];
12+
export type ProductLabel = 'site' | 'function';
1413
15-
let newVariables: Partial<Models.Variable>[] = [{ key: '', value: '' }];
16-
let secret = false;
17-
let error = '';
14+
let {
15+
show = $bindable(false),
16+
variables = $bindable(),
17+
productLabel = 'site'
18+
}: {
19+
show: boolean;
20+
variables: Partial<Models.Variable>[];
21+
productLabel?: ProductLabel;
22+
} = $props();
23+
24+
let newVariables = $state<Partial<Models.Variable>[]>([{ key: '', value: '' }]);
25+
let secret = $state(false);
26+
let error = $state('');
27+
28+
$effect(() => {
29+
if (!show) {
30+
newVariables = [{ key: '', value: '' }];
31+
secret = false;
32+
error = '';
33+
}
34+
});
1835
1936
function handleVariable() {
2037
try {
@@ -54,19 +71,17 @@
5471
5572
function removeVariable(index: number) {
5673
if (newVariables.length === 1) {
57-
newVariables[0].key = '';
58-
newVariables[0].value = '';
74+
newVariables = [{ key: '', value: '' }];
5975
} else {
60-
newVariables.splice(index, 1);
61-
newVariables = [...newVariables];
76+
newVariables = newVariables.filter((_, i) => i !== index);
6277
}
6378
}
6479
</script>
6580

6681
<Modal bind:show onSubmit={handleVariable} title="Create variables" bind:error>
6782
<span slot="description">
68-
Set the environment variables or secret that will be passed to your site. Global variables
69-
can be set in <Link
83+
Set the environment variables or secret that will be passed to your {productLabel}. Global
84+
variables can be set in <Link
7085
variant="muted"
7186
href={`${base}/project-${page.params.region}-${page.params.project}/settings`}
7287
>project settings</Link
@@ -95,7 +110,7 @@
95110
type="button"
96111
size="s"
97112
disabled={newVariables.length === 1 && !pair.key && !pair.value}
98-
on:click={() => removeVariable(i)}>
113+
onclick={() => removeVariable(i)}>
99114
<Icon icon={IconX} />
100115
</PinkButton.Button>
101116
</Layout.Stack>

src/routes/(console)/project-[region]-[project]/sites/create-site/deleteVariableModal.svelte renamed to src/lib/components/variables/deleteVariableModal.svelte

File renamed without changes.
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
<script lang="ts">
2+
import { Empty, Paginator } from '$lib/components';
3+
import { Button } from '$lib/elements/forms';
4+
import {
5+
ActionMenu,
6+
Accordion,
7+
Badge,
8+
InteractiveText,
9+
Icon,
10+
Layout,
11+
Popover,
12+
Skeleton,
13+
Table,
14+
Tooltip,
15+
Button as PinkButton
16+
} from '@appwrite.io/pink-svelte';
17+
import {
18+
IconDotsHorizontal,
19+
IconCode,
20+
IconUpload,
21+
IconPlus,
22+
IconTrash,
23+
IconEyeOff,
24+
IconPencil
25+
} from '@appwrite.io/pink-icons-svelte';
26+
import type { Models } from '@appwrite.io/console';
27+
import VariableEditorModal from './variableEditorModal.svelte';
28+
import SecretVariableModal from './secretVariableModal.svelte';
29+
import ImportVariablesModal from './importVariablesModal.svelte';
30+
import CreateVariableModal, { type ProductLabel } from './createVariableModal.svelte';
31+
import DeleteVariableModal from './deleteVariableModal.svelte';
32+
import UpdateVariableModal from './updateVariableModal.svelte';
33+
import { Click, trackEvent } from '$lib/actions/analytics';
34+
35+
const DOCS_LINKS: Record<ProductLabel, string> = {
36+
site: 'https://appwrite.io/docs/products/sites/develop#accessing-environment-variables',
37+
function: 'https://appwrite.io/docs/products/functions/develop#environment-variables'
38+
};
39+
40+
let {
41+
variables = $bindable([]),
42+
productLabel = 'site',
43+
analyticsSource = 'site_configuration',
44+
analyticsCreateSource = 'site_settings',
45+
isLoading = false
46+
}: {
47+
variables: Partial<Models.Variable>[];
48+
productLabel?: ProductLabel;
49+
analyticsSource?: string;
50+
analyticsCreateSource?: string;
51+
isLoading?: boolean;
52+
} = $props();
53+
54+
let showEditorModal = $state(false);
55+
let showImportModal = $state(false);
56+
let showSecretModal = $state(false);
57+
let showCreate = $state(false);
58+
let showUpdate = $state(false);
59+
let showDelete = $state(false);
60+
let currentVariable = $state<Partial<Models.Variable>>(undefined);
61+
62+
const createSource = $derived(analyticsCreateSource || analyticsSource);
63+
const docsLink = $derived(DOCS_LINKS[productLabel]);
64+
65+
const tableColumns = [
66+
{ id: 'key', width: { min: 300 } },
67+
{ id: 'value', width: { min: 280 } },
68+
{ id: 'actions', width: 40 }
69+
];
70+
</script>
71+
72+
<Accordion title="Environment variables" badge="Optional" hideDivider>
73+
<Layout.Stack gap="xl">
74+
Set up environment variables to securely manage keys and settings for your project.
75+
<Layout.Stack gap="l">
76+
<Layout.Stack direction="row">
77+
<Layout.Stack direction="row" gap="s">
78+
<Button
79+
secondary
80+
size="s"
81+
on:click={() => {
82+
showEditorModal = true;
83+
trackEvent(Click.VariablesUpdateClick, {
84+
source: analyticsSource
85+
});
86+
}}>
87+
<Icon slot="start" icon={IconCode} /> Editor
88+
</Button>
89+
<Button
90+
secondary
91+
size="s"
92+
on:click={() => {
93+
showImportModal = true;
94+
trackEvent(Click.VariablesImportClick, {
95+
source: analyticsSource
96+
});
97+
}}>
98+
<Icon slot="start" icon={IconUpload} /> Import .env
99+
</Button>
100+
</Layout.Stack>
101+
{#if variables?.length}
102+
<Button
103+
secondary
104+
size="s"
105+
on:click={() => {
106+
showCreate = true;
107+
trackEvent(Click.VariablesCreateClick, {
108+
source: createSource
109+
});
110+
}}>
111+
<Icon slot="start" icon={IconPlus} /> Create variable
112+
</Button>
113+
{/if}
114+
</Layout.Stack>
115+
116+
{#if isLoading && !variables?.length}
117+
<Table.Root class="responsive-table" let:root columns={tableColumns}>
118+
<svelte:fragment slot="header" let:root>
119+
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
120+
<Table.Header.Cell column="value" {root}>Value</Table.Header.Cell>
121+
<Table.Header.Cell column="actions" {root}></Table.Header.Cell>
122+
</svelte:fragment>
123+
{#each Array(3) as _}
124+
<Table.Row.Base {root}>
125+
<Table.Cell column="key" {root}>
126+
<Skeleton variant="line" width={120} height={14} />
127+
</Table.Cell>
128+
<Table.Cell column="value" {root}>
129+
<Skeleton variant="line" width="100%" height={14} />
130+
</Table.Cell>
131+
<Table.Cell column="actions" {root}>
132+
<Skeleton variant="line" width={24} height={14} />
133+
</Table.Cell>
134+
</Table.Row.Base>
135+
{/each}
136+
</Table.Root>
137+
{:else if variables?.length}
138+
<Paginator items={variables} limit={6} hideFooter={variables.length <= 6}>
139+
{#snippet children(paginatedItems)}
140+
<Table.Root let:root columns={tableColumns}>
141+
<svelte:fragment slot="header" let:root>
142+
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
143+
<Table.Header.Cell column="value" {root}>Value</Table.Header.Cell>
144+
<Table.Header.Cell column="actions" {root}></Table.Header.Cell>
145+
</svelte:fragment>
146+
{#each paginatedItems as variable}
147+
<Table.Row.Base {root}>
148+
<Table.Cell column="key" {root}>{variable.key}</Table.Cell>
149+
<Table.Cell column="value" {root}>
150+
<!-- TODO: fix max width -->
151+
<div style="max-width: 100%">
152+
{#if variable.secret}
153+
<Tooltip maxWidth="26rem">
154+
<Badge
155+
content="Secret"
156+
variant="secondary"
157+
size="s" />
158+
<svelte:fragment slot="tooltip">
159+
This value is secret, you cannot see its
160+
value.
161+
</svelte:fragment>
162+
</Tooltip>
163+
{:else}
164+
<InteractiveText
165+
variant="secret"
166+
isVisible={true}
167+
text={variable.value} />
168+
{/if}
169+
</div>
170+
</Table.Cell>
171+
<Table.Cell column="actions" {root}>
172+
<div style="margin-inline-start: auto">
173+
<Popover
174+
padding="none"
175+
placement="bottom-end"
176+
let:toggle>
177+
<PinkButton.Button
178+
icon
179+
variant="text"
180+
size="s"
181+
aria-label="More options"
182+
onclick={(e) => {
183+
e.preventDefault();
184+
toggle(e);
185+
}}>
186+
<Icon icon={IconDotsHorizontal} size="s" />
187+
</PinkButton.Button>
188+
189+
<svelte:fragment slot="tooltip" let:toggle>
190+
<ActionMenu.Root>
191+
{#if !variable?.secret}
192+
<ActionMenu.Item.Button
193+
leadingIcon={IconPencil}
194+
onclick={(e) => {
195+
toggle(e);
196+
currentVariable = variable;
197+
showUpdate = true;
198+
}}>
199+
Update
200+
</ActionMenu.Item.Button>
201+
{/if}
202+
{#if !variable?.secret}
203+
<ActionMenu.Item.Button
204+
leadingIcon={IconEyeOff}
205+
onclick={(e) => {
206+
toggle(e);
207+
currentVariable = variable;
208+
showSecretModal = true;
209+
}}>
210+
Secret
211+
</ActionMenu.Item.Button>
212+
{/if}
213+
<ActionMenu.Item.Button
214+
status="danger"
215+
leadingIcon={IconTrash}
216+
onclick={(e) => {
217+
toggle(e);
218+
currentVariable = variable;
219+
showDelete = true;
220+
}}>
221+
Delete
222+
</ActionMenu.Item.Button>
223+
</ActionMenu.Root>
224+
</svelte:fragment>
225+
</Popover>
226+
</div>
227+
</Table.Cell>
228+
</Table.Row.Base>
229+
{/each}
230+
</Table.Root>
231+
{/snippet}
232+
</Paginator>
233+
{:else}
234+
<Empty
235+
on:click={() => {
236+
showCreate = true;
237+
trackEvent(Click.VariablesCreateClick, {
238+
source: createSource
239+
});
240+
}}>Create variables to get started</Empty>
241+
{/if}
242+
</Layout.Stack>
243+
</Layout.Stack>
244+
</Accordion>
245+
246+
{#if showEditorModal}
247+
<VariableEditorModal bind:variables bind:showEditor={showEditorModal} {docsLink} />
248+
{/if}
249+
250+
{#if showSecretModal}
251+
<SecretVariableModal bind:show={showSecretModal} bind:currentVariable bind:variables />
252+
{/if}
253+
254+
{#if showImportModal}
255+
<ImportVariablesModal bind:show={showImportModal} bind:variables />
256+
{/if}
257+
258+
{#if showCreate}
259+
<CreateVariableModal bind:show={showCreate} bind:variables {productLabel} />
260+
{/if}
261+
{#if showUpdate}
262+
<UpdateVariableModal
263+
bind:show={showUpdate}
264+
bind:variables
265+
bind:selectedVar={currentVariable}
266+
{productLabel} />
267+
{/if}
268+
269+
{#if showDelete}
270+
<DeleteVariableModal bind:show={showDelete} bind:variables bind:currentVariable />
271+
{/if}

src/routes/(console)/project-[region]-[project]/sites/create-site/importSiteVariablesModal.svelte renamed to src/lib/components/variables/importVariablesModal.svelte

File renamed without changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export { default as CreateVariableModal, type ProductLabel } from './createVariableModal.svelte';
2+
export { default as DeleteVariableModal } from './deleteVariableModal.svelte';
3+
export { default as EnvironmentVariables } from './environmentVariables.svelte';
4+
export { default as ImportVariablesModal } from './importVariablesModal.svelte';
5+
export { default as SecretVariableModal } from './secretVariableModal.svelte';
6+
export { default as UpdateVariableModal } from './updateVariableModal.svelte';
7+
export { default as VariableEditorModal } from './variableEditorModal.svelte';

src/routes/(console)/project-[region]-[project]/sites/create-site/secretVariableModal.svelte renamed to src/lib/components/variables/secretVariableModal.svelte

File renamed without changes.

src/routes/(console)/project-[region]-[project]/sites/create-site/updateVariableModal.svelte renamed to src/lib/components/variables/updateVariableModal.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
export let show = false;
1212
export let selectedVar: Partial<Models.Variable>;
1313
export let variables: Partial<Models.Variable>[];
14+
export let productLabel = 'site';
1415
1516
let pair = {
1617
$id: selectedVar?.$id,
@@ -40,7 +41,8 @@
4041

4142
<Modal bind:show onSubmit={handleVariable} title="Update variable">
4243
<span slot="description">
43-
Update the environment variable for your site. Global variables can be set in <Link
44+
Update the environment variable for your {productLabel}. Global variables can be set in
45+
<Link
4446
variant="muted"
4547
href={`${base}/project-${page.params.region}-${page.params.project}/settings`}
4648
>project settings</Link

src/routes/(console)/project-[region]-[project]/sites/create-site/variableEditorModal.svelte renamed to src/lib/components/variables/variableEditorModal.svelte

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
1414
export let showEditor = false;
1515
export let variables: Partial<Models.Variable>[];
16+
export let docsLink =
17+
'https://appwrite.io/docs/products/sites/develop#accessing-environment-variables';
1618
1719
const editableVariables = variables.filter((variable) => !variable.secret);
1820
const secretVariables = variables.filter((variable) => variable.secret);
@@ -122,11 +124,7 @@
122124
{#if secretVariables?.length > 0}
123125
<Alert.Inline status="info">
124126
{secretVariables.length} secret variables are hidden from the editor. Their values will
125-
remain unchanged. <Link
126-
href="https://appwrite.io/docs/products/sites/develop#accessing-environment-variables"
127-
external
128-
variant="muted">Learn more</Link
129-
>.
127+
remain unchanged. <Link href={docsLink} external variant="muted">Learn more</Link>.
130128
</Alert.Inline>
131129
{/if}
132130
<Layout.Stack gap="s">

0 commit comments

Comments
 (0)