mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge remote-tracking branch 'origin/main' into fix-SER-428-Quality-Check-root-drectory-modal-issue
This commit is contained in:
@@ -6,13 +6,11 @@
|
||||
"name": "@appwrite/console",
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@c6f60aa",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@de65a99",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc",
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@plausible-analytics/tracker": "^0.4.4",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
@@ -109,7 +107,7 @@
|
||||
|
||||
"@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="],
|
||||
|
||||
"@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@c6f60aa", { "dependencies": { "bignumber.js": "9.0.0", "json-bigint": "1.0.0" } }],
|
||||
"@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@de65a99", { "dependencies": { "json-bigint": "1.0.0" } }],
|
||||
|
||||
"@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="],
|
||||
|
||||
|
||||
+1
-3
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@c6f60aa",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@de65a99",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
@@ -38,8 +38,6 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"deep-equal": "^2.2.3",
|
||||
"echarts": "^5.6.0",
|
||||
"https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@a4067bf": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@a4067bf",
|
||||
"https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@a4067bf": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@a4067bf",
|
||||
"ignore": "^6.0.2",
|
||||
"nanoid": "^5.1.5",
|
||||
"nanotar": "^0.1.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { currentPlan, organization } from '$lib/stores/organization';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { getChangePlanUrl } from '$lib/stores/billing';
|
||||
@@ -17,7 +17,7 @@
|
||||
</script>
|
||||
|
||||
{#if $showPolicyAlert && isCloud && $organization?.$id && page.url.pathname.match(/\/databases\/database-[^/]+$/)}
|
||||
{@const areBackupsAvailable = $organization?.billingPlanDetails.backupsEnabled}
|
||||
{@const areBackupsAvailable = $currentPlan?.backupsEnabled}
|
||||
|
||||
{@const subtitle = !areBackupsAvailable
|
||||
? 'Upgrade your plan to ensure your data stays safe and backed up'
|
||||
|
||||
@@ -54,8 +54,6 @@
|
||||
case 'cname':
|
||||
if (service === 'sites') {
|
||||
return $regionalConsoleVariables._APP_DOMAIN_SITES;
|
||||
} else if (service === 'functions') {
|
||||
return $regionalConsoleVariables._APP_DOMAIN_FUNCTIONS;
|
||||
} else {
|
||||
return $regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
import { Uniform, MeshBasicMaterial, Vector2 } from 'three';
|
||||
import noise from './threlte/shaders/noise.glsl?raw';
|
||||
import { onMount } from 'svelte';
|
||||
import { useViewport } from '@threlte/extras';
|
||||
|
||||
const { invalidate } = useThrelte();
|
||||
const viewport = useViewport();
|
||||
const { invalidate, size } = useThrelte();
|
||||
|
||||
let uAspect: Uniform;
|
||||
let uOpacity: Uniform;
|
||||
@@ -14,8 +11,8 @@
|
||||
let uViewportSize: Uniform;
|
||||
|
||||
$effect(() => {
|
||||
const width = $viewport.width;
|
||||
const height = $viewport.height;
|
||||
const width = $size.width || 1;
|
||||
const height = $size.height || 1;
|
||||
if (uAspect) {
|
||||
uAspect.value = width / height;
|
||||
invalidate();
|
||||
@@ -30,10 +27,13 @@
|
||||
let material = $state<MeshBasicMaterial>(new MeshBasicMaterial());
|
||||
|
||||
onMount(() => {
|
||||
uAspect = new Uniform($viewport.width / $viewport.height);
|
||||
const width = $size.width || 1;
|
||||
const height = $size.height || 1;
|
||||
|
||||
uAspect = new Uniform(width / height);
|
||||
uOpacity = new Uniform(0);
|
||||
uTime = new Uniform(0);
|
||||
uViewportSize = new Uniform(new Vector2($viewport.width, $viewport.height));
|
||||
uViewportSize = new Uniform(new Vector2(width, height));
|
||||
|
||||
material.onBeforeCompile = (shader) => {
|
||||
shader.uniforms.uTime = uTime;
|
||||
@@ -104,6 +104,6 @@
|
||||
</script>
|
||||
|
||||
<T.OrthographicCamera position={[0, 0, 10]} fov={10} near={0.1} far={1000} makeDefault />
|
||||
<T.Mesh scale={[$viewport.width, $viewport.height, 1]} {material}>
|
||||
<T.Mesh scale={[$size.width || 1, $size.height || 1, 1]} {material}>
|
||||
<T.PlaneGeometry args={[1, 1]} />
|
||||
</T.Mesh>
|
||||
|
||||
+27
-12
@@ -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 {
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
@@ -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';
|
||||
+3
-1
@@ -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,
|
||||
@@ -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
|
||||
+3
-5
@@ -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);
|
||||
@@ -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">
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export function normalizeDetectedVariables(
|
||||
detected: Models.DetectionVariable[] = []
|
||||
): Partial<Models.Variable>[] {
|
||||
const normalized: Partial<Models.Variable>[] = [];
|
||||
for (const variable of detected) {
|
||||
const key = variable.name?.trim();
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
normalized.push({
|
||||
key,
|
||||
value: variable.value ?? '',
|
||||
secret: false
|
||||
});
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function mergeVariables(
|
||||
existing: Partial<Models.Variable>[],
|
||||
detected: Partial<Models.Variable>[]
|
||||
) {
|
||||
const map = new Map(existing.map((variable) => [variable.key, variable]));
|
||||
detected.forEach((variable) => {
|
||||
if (!variable.key) {
|
||||
return;
|
||||
}
|
||||
if (!map.has(variable.key)) {
|
||||
map.set(variable.key, variable);
|
||||
}
|
||||
});
|
||||
return Array.from(map.values());
|
||||
}
|
||||
@@ -105,8 +105,6 @@ function createFeedbackStore() {
|
||||
return feedback;
|
||||
});
|
||||
},
|
||||
// TODO: update growth server to accept `billingPlan`.
|
||||
// TODO: update growth server to accept `source` key to know the feedback source area.
|
||||
submitFeedback: async (
|
||||
subject: string,
|
||||
message: string,
|
||||
@@ -122,18 +120,6 @@ function createFeedbackStore() {
|
||||
if (!VARS.GROWTH_ENDPOINT) return;
|
||||
trackEvent(Submit.FeedbackSubmit);
|
||||
|
||||
const customFields: Array<{ id: string; value: string | number }> = [
|
||||
{ id: '47364', value: currentPage }
|
||||
];
|
||||
|
||||
if (value) {
|
||||
customFields.push({ id: '40655', value });
|
||||
}
|
||||
|
||||
if (billingPlan) {
|
||||
customFields.push({ id: '56109', value: billingPlan });
|
||||
}
|
||||
|
||||
const response = await fetch(`${VARS.GROWTH_ENDPOINT}/feedback`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -143,9 +129,11 @@ function createFeedbackStore() {
|
||||
subject,
|
||||
message,
|
||||
email,
|
||||
customFields,
|
||||
firstname: (name || 'Unknown').slice(0, 40),
|
||||
metaFields: {
|
||||
currentPage,
|
||||
npsScore: value,
|
||||
billingPlan,
|
||||
source: get(feedback).source,
|
||||
orgId,
|
||||
projectId,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { base } from '$app/paths';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import Upgrade from '$lib/components/roles/upgrade.svelte';
|
||||
import { getRoleLabel } from '$lib/stores/billing';
|
||||
import { getServiceLimit, readOnly, getRoleLabel } from '$lib/stores/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { currentPlan, newMemberModal, organization } from '$lib/stores/organization';
|
||||
import { isOwner } from '$lib/stores/roles';
|
||||
@@ -14,7 +14,7 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import Delete from '../deleteMember.svelte';
|
||||
import Edit from './edit.svelte';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { isCloud, GRACE_PERIOD_OVERRIDE } from '$lib/system';
|
||||
import {
|
||||
IconDotsHorizontal,
|
||||
IconInfo,
|
||||
@@ -45,8 +45,10 @@
|
||||
// Calculate if button should be disabled and tooltip should show
|
||||
$: memberCount = data.organizationMembers?.total ?? 0;
|
||||
$: supportsMembers = $organization?.billingPlanDetails?.addons?.seats;
|
||||
$: isFreeWithMembers = !supportsMembers && memberCount >= 1;
|
||||
$: isButtonDisabled = isCloud ? isFreeWithMembers : false;
|
||||
$: limit = getServiceLimit('members', null, $currentPlan) || Infinity;
|
||||
$: isLimited = limit !== 0 && limit < Infinity;
|
||||
$: isButtonDisabled =
|
||||
isCloud && (($readOnly && !GRACE_PERIOD_OVERRIDE) || (isLimited && memberCount >= limit));
|
||||
|
||||
const resend = async (member: Models.Membership) => {
|
||||
try {
|
||||
|
||||
@@ -67,13 +67,11 @@
|
||||
firstName: ($user?.name ?? '').slice(0, 40),
|
||||
message: `BAA request for ${$organization?.name ?? ''} (${$organization?.$id ?? ''})`,
|
||||
tags: ['cloud'],
|
||||
customFields: [
|
||||
{ id: '41612', value: 'BAA' },
|
||||
{ id: '48493', value: $user?.name ?? '' },
|
||||
{ id: '48492', value: $organization?.$id ?? '' },
|
||||
{ id: '48490', value: $user?.$id ?? '' }
|
||||
],
|
||||
metaFields: {
|
||||
category: 'BAA',
|
||||
userName: $user?.name ?? '',
|
||||
orgId: $organization?.$id ?? '',
|
||||
userId: $user?.$id ?? '',
|
||||
employees: employees,
|
||||
country: country,
|
||||
role: role,
|
||||
|
||||
@@ -68,13 +68,11 @@
|
||||
firstName: ($user?.name ?? '').slice(0, 40),
|
||||
message: `SOC-2 request for ${$organization?.name ?? ''} (${$organization?.$id ?? ''})`,
|
||||
tags: ['cloud'],
|
||||
customFields: [
|
||||
{ id: '41612', value: 'SOC-2' },
|
||||
{ id: '48493', value: $user?.name ?? '' },
|
||||
{ id: '48492', value: $organization?.$id ?? '' },
|
||||
{ id: '48490', value: $user?.$id ?? '' }
|
||||
],
|
||||
metaFields: {
|
||||
category: 'SOC-2',
|
||||
userName: $user?.name ?? '',
|
||||
orgId: $organization?.$id ?? '',
|
||||
userId: $user?.$id ?? '',
|
||||
employees: employees,
|
||||
country: country,
|
||||
role: role,
|
||||
|
||||
+2
-2
@@ -23,12 +23,12 @@
|
||||
try {
|
||||
const template = await loadEmailTemplate(
|
||||
project.$id,
|
||||
EmailTemplateType.Mfachallenge,
|
||||
EmailTemplateType.MfaChallenge,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Mfachallenge });
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.MfaChallenge });
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
+2
-2
@@ -23,12 +23,12 @@
|
||||
try {
|
||||
const template = await loadEmailTemplate(
|
||||
project.$id,
|
||||
EmailTemplateType.Magicsession,
|
||||
EmailTemplateType.MagicSession,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Magicsession });
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.MagicSession });
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
+2
-2
@@ -23,12 +23,12 @@
|
||||
try {
|
||||
const template = await loadEmailTemplate(
|
||||
project.$id,
|
||||
EmailTemplateType.Otpsession,
|
||||
EmailTemplateType.OtpSession,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Otpsession });
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.OtpSession });
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
+2
-2
@@ -23,12 +23,12 @@
|
||||
try {
|
||||
const template = await loadEmailTemplate(
|
||||
project.$id,
|
||||
EmailTemplateType.Sessionalert,
|
||||
EmailTemplateType.SessionAlert,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Sessionalert });
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.SessionAlert });
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
@@ -51,13 +51,13 @@ export const templates = [
|
||||
component: EmailVerificationTemplate
|
||||
},
|
||||
{
|
||||
key: EmailTemplateType.Magicsession,
|
||||
key: EmailTemplateType.MagicSession,
|
||||
title: 'Magic URL',
|
||||
description: 'Send an email to users that sign in with a magic URL.',
|
||||
component: EmailMagicUrlTemplate
|
||||
},
|
||||
{
|
||||
key: EmailTemplateType.Otpsession,
|
||||
key: EmailTemplateType.OtpSession,
|
||||
title: 'OTP session',
|
||||
description: 'Send an email to users that sign in with a email OTP.',
|
||||
component: EmailOtpSessionTemplate
|
||||
@@ -75,13 +75,13 @@ export const templates = [
|
||||
component: EmailInviteTemplate
|
||||
},
|
||||
{
|
||||
key: EmailTemplateType.Mfachallenge,
|
||||
key: EmailTemplateType.MfaChallenge,
|
||||
title: '2FA verification',
|
||||
description: 'Send a two-factor authentication email to a user.',
|
||||
component: Email2FaTemplate
|
||||
},
|
||||
{
|
||||
key: EmailTemplateType.Sessionalert,
|
||||
key: EmailTemplateType.SessionAlert,
|
||||
title: 'Session alert',
|
||||
description: 'Send an email to users when a new session is created.',
|
||||
component: EmailSessionAlertTemplate,
|
||||
|
||||
+15
-8
@@ -9,7 +9,7 @@
|
||||
import deepEqual from 'deep-equal';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { type Columns, columnsOrder, databaseColumnSheetOptions } from '../store';
|
||||
import { columnOptions, type Option } from './store';
|
||||
import { columnOptions, STRING_COLUMN_NAME, type Option } from './store';
|
||||
import { onMount } from 'svelte';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
@@ -34,14 +34,21 @@
|
||||
}
|
||||
});
|
||||
|
||||
$: option = columnOptions.find((option) => {
|
||||
if (selectedColumn) {
|
||||
if ('format' in selectedColumn && selectedColumn.format) {
|
||||
return option?.format === selectedColumn?.format;
|
||||
} else {
|
||||
return option?.type === selectedColumn?.type;
|
||||
}
|
||||
$: option = columnOptions.find((opt) => {
|
||||
if (!selectedColumn) return false;
|
||||
|
||||
// format match when present
|
||||
if ('format' in selectedColumn && selectedColumn.format) {
|
||||
return opt?.format === selectedColumn.format;
|
||||
}
|
||||
|
||||
// Legacy string columns (no format)
|
||||
if (selectedColumn.type === 'string') {
|
||||
return opt.name === STRING_COLUMN_NAME;
|
||||
}
|
||||
|
||||
// Fallback: match by type
|
||||
return opt.type === selectedColumn.type;
|
||||
}) as Option;
|
||||
|
||||
export async function submit() {
|
||||
|
||||
+3
-1
@@ -85,6 +85,8 @@ export type Option = {
|
||||
icon: ComponentType;
|
||||
};
|
||||
|
||||
export const STRING_COLUMN_NAME = 'String (deprecated)';
|
||||
|
||||
export const columnOptions: Option[] = [
|
||||
{
|
||||
name: 'Text',
|
||||
@@ -235,7 +237,7 @@ export const columnOptions: Option[] = [
|
||||
icon: IconRelationship
|
||||
},
|
||||
{
|
||||
name: 'String (deprecated)',
|
||||
name: STRING_COLUMN_NAME,
|
||||
sentenceName: 'string',
|
||||
component: String,
|
||||
type: 'string',
|
||||
|
||||
+10
-1
@@ -22,6 +22,7 @@
|
||||
import RepoCard from './repoCard.svelte';
|
||||
import { getIconFromRuntime } from '$lib/stores/runtimes';
|
||||
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
|
||||
import { normalizeDetectedVariables, mergeVariables } from '$lib/helpers/variables';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -82,6 +83,10 @@
|
||||
entrypoint = detections.entrypoint;
|
||||
buildCommand = detections.commands;
|
||||
runtime = detections.runtime as Runtime;
|
||||
const detectedVariables = normalizeDetectedVariables(detections?.variables);
|
||||
if (detectedVariables.length) {
|
||||
variables = mergeVariables(variables, detectedVariables);
|
||||
}
|
||||
|
||||
trackEvent(Submit.FrameworkDetect, { runtime, source: 'repository' });
|
||||
} catch (error) {
|
||||
@@ -189,7 +194,11 @@
|
||||
installationId={data.installation.$id}
|
||||
repositoryId={data.repository.id} />
|
||||
|
||||
<Configuration bind:buildCommand bind:roles />
|
||||
<Configuration
|
||||
bind:buildCommand
|
||||
bind:roles
|
||||
bind:variables
|
||||
isLoading={detectingRuntime} />
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
<svelte:fragment slot="aside">
|
||||
|
||||
+10
@@ -3,9 +3,13 @@
|
||||
import { Link } from '$lib/elements';
|
||||
import { InputText } from '$lib/elements/forms';
|
||||
import { Accordion, Fieldset, Layout } from '@appwrite.io/pink-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { EnvironmentVariables } from '$lib/components/variables';
|
||||
|
||||
export let buildCommand = '';
|
||||
export let roles: string[] = [];
|
||||
export let variables: Partial<Models.Variable>[] = [];
|
||||
export let isLoading = false;
|
||||
</script>
|
||||
|
||||
<Fieldset legend="Settings">
|
||||
@@ -31,5 +35,11 @@
|
||||
<Roles bind:roles />
|
||||
</Layout.Stack>
|
||||
</Accordion>
|
||||
<EnvironmentVariables
|
||||
bind:variables
|
||||
productLabel="function"
|
||||
analyticsSource="function_configuration"
|
||||
analyticsCreateSource="function_configuration"
|
||||
{isLoading} />
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
|
||||
+2
-2
@@ -29,8 +29,8 @@
|
||||
const ruleId = page.url.searchParams.get('rule');
|
||||
|
||||
const showCNAMETab = $derived(
|
||||
Boolean($regionalConsoleVariables._APP_DOMAIN_FUNCTIONS) &&
|
||||
$regionalConsoleVariables._APP_DOMAIN_FUNCTIONS !== 'localhost'
|
||||
Boolean($regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME) &&
|
||||
$regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME !== 'localhost'
|
||||
);
|
||||
const showATab = $derived(
|
||||
!isCloud &&
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@
|
||||
} = $props();
|
||||
|
||||
const showCNAMETab = $derived(
|
||||
Boolean($regionalConsoleVariables._APP_DOMAIN_FUNCTIONS) &&
|
||||
$regionalConsoleVariables._APP_DOMAIN_FUNCTIONS !== 'localhost'
|
||||
Boolean($regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME) &&
|
||||
$regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME !== 'localhost'
|
||||
);
|
||||
const showATab = $derived(
|
||||
!isCloud &&
|
||||
|
||||
+4
-230
@@ -1,38 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Empty, Paginator } from '$lib/components';
|
||||
import { Button, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import {
|
||||
Fieldset,
|
||||
Layout,
|
||||
Popover,
|
||||
Icon,
|
||||
Table,
|
||||
Badge,
|
||||
InteractiveText,
|
||||
ActionMenu,
|
||||
Accordion,
|
||||
Tooltip,
|
||||
Button as PinkButton
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
IconDotsHorizontal,
|
||||
IconCode,
|
||||
IconUpload,
|
||||
IconPlus,
|
||||
IconTrash,
|
||||
IconEyeOff,
|
||||
IconPencil
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import { Fieldset, Layout, Accordion } from '@appwrite.io/pink-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { iconPath } from '$lib/stores/app';
|
||||
import VariableEditorModal from './variableEditorModal.svelte';
|
||||
import SecretVariableModal from './secretVariableModal.svelte';
|
||||
import ImportSiteVariablesModal from './importSiteVariablesModal.svelte';
|
||||
import CreateVariableModal from './createVariableModal.svelte';
|
||||
import DeleteVariableModal from './deleteVariableModal.svelte';
|
||||
import UpdateVariableModal from './updateVariableModal.svelte';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import { getFrameworkIcon } from '$lib/stores/sites';
|
||||
import { EnvironmentVariables } from '$lib/components/variables';
|
||||
|
||||
export let frameworks: Models.Framework[];
|
||||
export let selectedFramework: Models.Framework;
|
||||
@@ -42,18 +14,11 @@
|
||||
frameworkData?.adapters.find((adapter) => adapter.key === 'static');
|
||||
|
||||
export let variables: Partial<Models.Variable>[] = [];
|
||||
export let isLoading = false;
|
||||
export let installCommand = '';
|
||||
export let buildCommand = '';
|
||||
export let outputDirectory = '';
|
||||
|
||||
let showEditorModal = false;
|
||||
let showImportModal = false;
|
||||
let showSecretModal = false;
|
||||
let showCreate = false;
|
||||
let showUpdate = false;
|
||||
let showDelete = false;
|
||||
|
||||
let currentVariable: Partial<Models.Variable>;
|
||||
let frameworkId = selectedFramework.key;
|
||||
|
||||
$: if (!installCommand || !buildCommand || !outputDirectory) {
|
||||
@@ -138,198 +103,7 @@
|
||||
</Layout.Stack>
|
||||
</Accordion>
|
||||
|
||||
<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:mousedown={() => {
|
||||
showEditorModal = true;
|
||||
trackEvent(Click.VariablesUpdateClick, {
|
||||
source: 'site_configuration'
|
||||
});
|
||||
}}>
|
||||
<Icon slot="start" icon={IconCode} /> Editor
|
||||
</Button>
|
||||
<Button
|
||||
secondary
|
||||
size="s"
|
||||
on:mousedown={() => {
|
||||
showImportModal = true;
|
||||
trackEvent(Click.VariablesImportClick, {
|
||||
source: 'site_configuration'
|
||||
});
|
||||
}}>
|
||||
<Icon slot="start" icon={IconUpload} /> Import .env
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
{#if variables?.length}
|
||||
<Button
|
||||
secondary
|
||||
size="s"
|
||||
on:mousedown={() => {
|
||||
showCreate = true;
|
||||
trackEvent(Click.VariablesCreateClick, {
|
||||
source: 'site_settings'
|
||||
});
|
||||
}}>
|
||||
<Icon slot="start" icon={IconPlus} /> Create variable
|
||||
</Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
{#if variables?.length}
|
||||
<Paginator
|
||||
items={variables}
|
||||
limit={6}
|
||||
hideFooter={variables.length <= 6}>
|
||||
{#snippet children(paginatedItems)}
|
||||
<Table.Root
|
||||
let:root
|
||||
columns={[
|
||||
{ id: 'key', width: 200 },
|
||||
{ id: 'value' },
|
||||
{ id: 'actions', width: 40 }
|
||||
]}>
|
||||
<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: 20rem">
|
||||
{#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={false}
|
||||
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"
|
||||
on:click={(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}
|
||||
on:click={(e) => {
|
||||
toggle(e);
|
||||
currentVariable =
|
||||
variable;
|
||||
showUpdate = true;
|
||||
}}>
|
||||
Update
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
{#if !variable?.secret}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconEyeOff}
|
||||
on:click={(e) => {
|
||||
toggle(e);
|
||||
|
||||
currentVariable =
|
||||
variable;
|
||||
showSecretModal = true;
|
||||
}}>
|
||||
Secret
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
<ActionMenu.Item.Button
|
||||
status="danger"
|
||||
leadingIcon={IconTrash}
|
||||
on:click={(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)}
|
||||
>Create variables to get started</Empty>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Accordion>
|
||||
<EnvironmentVariables bind:variables {isLoading} />
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
|
||||
{#if showEditorModal}
|
||||
<VariableEditorModal bind:variables bind:showEditor={showEditorModal} />
|
||||
{/if}
|
||||
|
||||
{#if showSecretModal}
|
||||
<SecretVariableModal bind:show={showSecretModal} bind:currentVariable bind:variables />
|
||||
{/if}
|
||||
|
||||
{#if showImportModal}
|
||||
<ImportSiteVariablesModal bind:show={showImportModal} bind:variables />
|
||||
{/if}
|
||||
|
||||
{#if showCreate}
|
||||
<CreateVariableModal bind:show={showCreate} bind:variables />
|
||||
{/if}
|
||||
{#if showUpdate}
|
||||
<UpdateVariableModal bind:show={showUpdate} bind:variables bind:selectedVar={currentVariable} />
|
||||
{/if}
|
||||
|
||||
{#if showDelete}
|
||||
<DeleteVariableModal bind:show={showDelete} bind:variables bind:currentVariable />
|
||||
{/if}
|
||||
|
||||
+10
@@ -27,6 +27,7 @@
|
||||
import Configuration from '../../configuration.svelte';
|
||||
import Domain from '../../domain.svelte';
|
||||
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
|
||||
import { normalizeDetectedVariables, mergeVariables } from '$lib/helpers/variables';
|
||||
|
||||
export let data;
|
||||
let showExitModal = false;
|
||||
@@ -47,6 +48,7 @@
|
||||
let silentMode = false;
|
||||
let domain = data.domain;
|
||||
let domainIsValid = true;
|
||||
let isVariablesLoading = true;
|
||||
|
||||
onMount(async () => {
|
||||
installation.set(data.installation);
|
||||
@@ -58,6 +60,7 @@
|
||||
|
||||
async function detectFramework() {
|
||||
try {
|
||||
isVariablesLoading = true;
|
||||
const response = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.vcs.createRepositoryDetection({
|
||||
@@ -71,6 +74,10 @@
|
||||
installCommand = adapter?.installCommand;
|
||||
buildCommand = adapter?.buildCommand;
|
||||
outputDirectory = adapter?.outputDirectory;
|
||||
const detectedVariables = normalizeDetectedVariables(response?.variables);
|
||||
if (detectedVariables.length) {
|
||||
variables = mergeVariables(variables, detectedVariables);
|
||||
}
|
||||
trackEvent(Submit.FrameworkDetect, {
|
||||
source: 'repository',
|
||||
framework: framework.key
|
||||
@@ -78,6 +85,8 @@
|
||||
} catch (error) {
|
||||
framework = data.frameworks.frameworks.find((f) => f.key === 'other');
|
||||
trackError(error, Submit.FrameworkDetect);
|
||||
} finally {
|
||||
isVariablesLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +210,7 @@
|
||||
bind:outputDirectory
|
||||
bind:selectedFramework={framework}
|
||||
bind:variables
|
||||
isLoading={isVariablesLoading}
|
||||
frameworks={data.frameworks.frameworks} />
|
||||
{/key}
|
||||
|
||||
|
||||
@@ -136,14 +136,14 @@
|
||||
formData.append('message', $supportData.message);
|
||||
formData.append('tags[]', categoryTopicTag);
|
||||
formData.append(
|
||||
'customFields',
|
||||
JSON.stringify([
|
||||
{ id: '41612', value: $supportData.category },
|
||||
{ id: '48492', value: $organization?.$id ?? '' },
|
||||
{ id: '48491', value: $supportData?.project ?? '' },
|
||||
{ id: '56023', value: $supportData?.severity ?? '' },
|
||||
{ id: '56024', value: $organization?.billingPlanId ?? '' }
|
||||
])
|
||||
'metaFields',
|
||||
JSON.stringify({
|
||||
category: $supportData.category,
|
||||
orgId: $organization?.$id ?? '',
|
||||
projectId: $supportData?.project ?? '',
|
||||
severity: $supportData?.severity ?? '',
|
||||
billingPlan: $organization?.billingPlanId ?? ''
|
||||
})
|
||||
);
|
||||
if (files && files.length > 0) {
|
||||
formData.append('attachment', files[0]);
|
||||
|
||||
Reference in New Issue
Block a user