mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: settings page
This commit is contained in:
@@ -2,10 +2,9 @@ import { Query } from '@appwrite.io/console';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { getLimit, getPage, getQuery, pageToOffset } from '$lib/helpers/load';
|
||||
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
import { queries, queryParamToMap } from '$lib/components/filters';
|
||||
|
||||
export const load: PageLoad = async ({ params, depends, url, route, parent }) => {
|
||||
export const load = async ({ params, depends, url, route, parent }) => {
|
||||
const data = await parent();
|
||||
depends(Dependencies.DEPLOYMENTS);
|
||||
const page = getPage(url);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { Container } from '$lib/layout';
|
||||
import DangerZone from './dangerZone.svelte';
|
||||
import UpdateName from './updateName.svelte';
|
||||
import UpdateVariables from '../../../updateVariables.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Heading } from '$lib/components';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
export let data;
|
||||
|
||||
const sdkCreateVariable = async (key: string, value: string) => {
|
||||
await sdk.forProject.sites.createVariable($page.params.site, key, value);
|
||||
await Promise.all([invalidate(Dependencies.VARIABLES), invalidate(Dependencies.SITE)]);
|
||||
};
|
||||
|
||||
const sdkUpdateVariable = async (variableId: string, key: string, value: string) => {
|
||||
await sdk.forProject.sites.updateVariable($page.params.site, variableId, key, value);
|
||||
await Promise.all([invalidate(Dependencies.VARIABLES), invalidate(Dependencies.SITE)]);
|
||||
};
|
||||
|
||||
const sdkDeleteVariable = async (variableId: string) => {
|
||||
await sdk.forProject.sites.deleteVariable($page.params.site, variableId);
|
||||
await Promise.all([invalidate(Dependencies.VARIABLES), invalidate(Dependencies.SITE)]);
|
||||
};
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Heading tag="h2" size="5">Settings</Heading>
|
||||
<UpdateName site={data.site} />
|
||||
<UpdateVariables
|
||||
{sdkCreateVariable}
|
||||
{sdkUpdateVariable}
|
||||
{sdkDeleteVariable}
|
||||
isGlobal={false}
|
||||
globalVariableList={data.globalVariables}
|
||||
variableList={data.variables} />
|
||||
<DangerZone site={data.site} />
|
||||
</Container>
|
||||
@@ -0,0 +1,37 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
|
||||
export const load = async ({ params, depends, parent }) => {
|
||||
depends(Dependencies.VARIABLES);
|
||||
depends(Dependencies.SITE);
|
||||
const { site } = await parent();
|
||||
|
||||
const [globalVariables, variables] = await Promise.all([
|
||||
sdk.forProject.projectApi.listVariables(),
|
||||
sdk.forProject.sites.listVariables(params.site)
|
||||
]);
|
||||
|
||||
// Conflicting variables first
|
||||
variables.variables = variables.variables.sort((var1, var2) => {
|
||||
const isVar1Global =
|
||||
globalVariables.variables.find((variable) => variable.key === var1.key) !== undefined;
|
||||
const isVar2Global =
|
||||
globalVariables.variables.find((variable) => variable.key === var2.key) !== undefined;
|
||||
|
||||
if (isVar1Global && isVar2Global) {
|
||||
return -var1.$createdAt.localeCompare(var2.$createdAt);
|
||||
} else if (isVar1Global) {
|
||||
return -1;
|
||||
} else if (isVar2Global) {
|
||||
return 1;
|
||||
} else {
|
||||
return -var1.$createdAt.localeCompare(var2.$createdAt);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
variables,
|
||||
globalVariables,
|
||||
site
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { BoxAvatar, CardGrid } from '$lib/components';
|
||||
import Heading from '$lib/components/heading.svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import Delete from './deleteModal.svelte';
|
||||
|
||||
export let site;
|
||||
let showDelete = false;
|
||||
</script>
|
||||
|
||||
<CardGrid danger>
|
||||
<Heading tag="h6" size="7">Delete site</Heading>
|
||||
<p>
|
||||
The function will be permanently deleted, including all deployments associated with it. This
|
||||
action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<BoxAvatar>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{site.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last updated: {toLocaleDateTime(site.$updatedAt)}</p>
|
||||
</BoxAvatar>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
export let showDelete = false;
|
||||
const siteId = $page.params.site;
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await sdk.forProject.sites.delete(siteId);
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Site has been deleted`
|
||||
});
|
||||
await goto(`${base}/project-${$page.params.project}/sites`);
|
||||
trackEvent(Submit.SiteDelete);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.SiteDelete);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
title="Delete site"
|
||||
bind:show={showDelete}
|
||||
onSubmit={handleSubmit}
|
||||
icon="exclamation"
|
||||
state="warning">
|
||||
<p data-private>
|
||||
Are you sure you want to delete this function and all associated deployments from your
|
||||
project?
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button secondary submit>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { func } from '../store';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { Runtime } from '@appwrite.io/console';
|
||||
|
||||
export let show = false;
|
||||
const functionId = $page.params.function;
|
||||
let error = '';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
if (!isValueOfStringEnum(Runtime, $func.runtime)) {
|
||||
throw new Error(`Invalid runtime: ${$func.runtime}`);
|
||||
}
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
$func.name,
|
||||
$func.runtime,
|
||||
$func.execute || undefined,
|
||||
$func.events || undefined,
|
||||
$func.schedule || undefined,
|
||||
$func.timeout || undefined,
|
||||
$func.enabled || undefined,
|
||||
$func.logging || undefined,
|
||||
$func.entrypoint,
|
||||
$func.commands || undefined,
|
||||
$func.scopes || undefined,
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
true,
|
||||
''
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
dispatch('success');
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Repository has been disconnected from your function`
|
||||
});
|
||||
trackEvent(Submit.FunctionDisconnectRepo);
|
||||
show = false;
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.FunctionDisconnectRepo);
|
||||
}
|
||||
};
|
||||
|
||||
$: if (!show) {
|
||||
error = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
title="Disconnect Git repository"
|
||||
bind:show
|
||||
bind:error
|
||||
onSubmit={handleSubmit}
|
||||
icon="exclamation"
|
||||
state="warning"
|
||||
headerDivider={false}>
|
||||
<p data-private>
|
||||
Are you sure you want to disconnect <b>{$func.name}</b>? This will affect future deployments
|
||||
to this function.
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button secondary submit>Disconnect</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { BoxAvatar, EmptySearch, Modal, PaginationInline } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { InputChoice, InputSearch, InputSelect, InputText, Button } from '$lib/elements/forms';
|
||||
import { timeFromNow, toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Runtime, type Models } from '@appwrite.io/console';
|
||||
import { func, repositories } from '../store';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import InputSelectSearch from '$lib/elements/forms/inputSelectSearch.svelte';
|
||||
import { installations } from '$lib/wizards/functions/store';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { sortBranches } from '$lib/stores/vcs';
|
||||
|
||||
export let show: boolean;
|
||||
|
||||
const functionId = $page.params.function;
|
||||
|
||||
let selectedRepoId: string;
|
||||
let selectedInstallationId: string;
|
||||
let step = 1;
|
||||
let search: string;
|
||||
let repositoriesList: Models.ProviderRepository[];
|
||||
let branchesList: Models.BranchList;
|
||||
let offset = 0;
|
||||
let selectedBranch: string;
|
||||
let selectedDir: string;
|
||||
let silentMode = false;
|
||||
let error = '';
|
||||
|
||||
let installationsOptions = $installations.installations.map((installation) => {
|
||||
return {
|
||||
value: installation.$id,
|
||||
label: installation.organization
|
||||
};
|
||||
});
|
||||
|
||||
function getProviderIcon(provider: string) {
|
||||
if (provider === 'github') {
|
||||
return `icon-github`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
if (!isValueOfStringEnum(Runtime, $func.runtime)) {
|
||||
throw new Error(`Invalid runtime: ${$func.runtime}`);
|
||||
}
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
$func.name,
|
||||
$func.runtime,
|
||||
$func.execute || undefined,
|
||||
$func.events || undefined,
|
||||
$func.schedule || undefined,
|
||||
$func.timeout || undefined,
|
||||
$func.enabled || undefined,
|
||||
$func.logging || undefined,
|
||||
$func.entrypoint,
|
||||
$func.commands || undefined,
|
||||
$func.scopes || undefined,
|
||||
selectedInstallationId,
|
||||
selectedRepoId,
|
||||
selectedBranch,
|
||||
silentMode,
|
||||
selectedDir
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$func.name} git configuration has been updated successfully`
|
||||
});
|
||||
trackEvent(Submit.FunctionUpdateConfiguration);
|
||||
show = false;
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.FunctionUpdateConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
async function getRepos() {
|
||||
if (!show || !selectedInstallationId) return;
|
||||
|
||||
if (
|
||||
!$repositories ||
|
||||
$repositories.installationId !== selectedInstallationId ||
|
||||
$repositories.search !== search
|
||||
) {
|
||||
$repositories.repositories = (
|
||||
await sdk.forProject.vcs.listRepositories(
|
||||
selectedInstallationId,
|
||||
search || undefined
|
||||
)
|
||||
).providerRepositories;
|
||||
}
|
||||
|
||||
$repositories.search = search;
|
||||
$repositories.installationId = selectedInstallation.$id;
|
||||
|
||||
repositoriesList = $repositories.repositories;
|
||||
}
|
||||
async function getBranches() {
|
||||
if (!show || !selectedInstallationId) return;
|
||||
branchesList = await sdk.forProject.vcs.listRepositoryBranches(
|
||||
selectedInstallationId,
|
||||
selectedRepoId
|
||||
);
|
||||
|
||||
branchesList.branches = sortBranches(branchesList.branches);
|
||||
|
||||
selectedBranch = branchesList?.branches[0].name;
|
||||
}
|
||||
|
||||
$: if (search !== null) {
|
||||
offset = 0;
|
||||
getRepos();
|
||||
}
|
||||
|
||||
$: selectedRepo = (repositoriesList ?? []).find((repo) => repo.id === selectedRepoId) ?? null;
|
||||
$: selectedInstallation =
|
||||
($installations?.installations ?? []).find(
|
||||
(installation) => installation.$id === selectedInstallationId
|
||||
) ?? null;
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
title="Git configuration"
|
||||
headerDivider={false}
|
||||
bind:show
|
||||
size="big"
|
||||
bind:error
|
||||
onSubmit={handleSubmit}>
|
||||
<p class="text">
|
||||
Configure a Git repository that will trigger your function deployments when updated.
|
||||
</p>
|
||||
{#if step === 1}
|
||||
{#await getRepos()}
|
||||
Fetching repositories..
|
||||
{:then}
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="u-width-full-line">
|
||||
<InputSelect
|
||||
options={installationsOptions}
|
||||
id="installations"
|
||||
label="installation"
|
||||
showLabel={false}
|
||||
bind:value={selectedInstallationId} />
|
||||
</div>
|
||||
<div class="u-width-full-line">
|
||||
<InputSearch placeholder="Search repositories" bind:value={search} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text">
|
||||
Manage organization configuration in your <a
|
||||
class="link"
|
||||
href={`${base}/project-${$page.params.project}/settings`}>project settings</a
|
||||
>.
|
||||
</p>
|
||||
{#if repositoriesList.length}
|
||||
<ul class="table is-remove-outer-styles u-sep-block-start">
|
||||
{#each repositoriesList as repo}
|
||||
<li class="table-row">
|
||||
<label class="table-col u-cursor-pointer">
|
||||
<div class="u-flex u-cross-center u-gap-8">
|
||||
<input
|
||||
class="is-small u-margin-inline-end-8"
|
||||
type="radio"
|
||||
name="repositories"
|
||||
bind:group={selectedRepoId}
|
||||
value={repo.id} />
|
||||
<div class="avatar is-size-x-small">
|
||||
<img src="" alt={repo.name} />
|
||||
</div>
|
||||
<div class="u-flex u-gap-8">
|
||||
<span class="text">{repo.name}</span>
|
||||
{#if repo.private}
|
||||
<span
|
||||
class="icon-lock-closed"
|
||||
style="font-size: var(--icon-size-small)"
|
||||
aria-hidden="true" />
|
||||
{/if}
|
||||
<time class="u-color-text-gray" datetime={repo.pushedAt}>
|
||||
{timeFromNow(repo.pushedAt)}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {repositoriesList.length}</p>
|
||||
<PaginationInline
|
||||
limit={5}
|
||||
bind:offset
|
||||
sum={repositoriesList.length}
|
||||
hidePages />
|
||||
</div>
|
||||
{:else if search}
|
||||
<EmptySearch hidePages>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<b class="body-text-2 u-bold">Sorry we couldn't find "{search}"</b>
|
||||
<p>There are no repositories that match your search.</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 common-section u-main-center">
|
||||
<Button
|
||||
external
|
||||
href="https://appwrite.io/docs/products/functions/deployment"
|
||||
text>Documentation</Button>
|
||||
<Button secondary on:click={() => (search = '')}>Clear search</Button>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{:else}
|
||||
<EmptySearch hidePages>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<p class="text u-line-height-1-5">You have no repositories.</p>
|
||||
<p class="text u-line-height-1-5">
|
||||
Need a hand? Learn more in our <a
|
||||
href="https://appwrite.io/docs/products/functions/deployment"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
documentation</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{/if}
|
||||
{/await}
|
||||
{:else}
|
||||
{#await getBranches()}
|
||||
Fetching branches..
|
||||
{:then}
|
||||
<BoxAvatar>
|
||||
<svelte:fragment slot="image">
|
||||
<div class="avatar">
|
||||
<span class={getProviderIcon(selectedInstallation.provider)} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{selectedRepo.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last updated: {toLocaleDateTime(selectedRepo.pushedAt)}</p>
|
||||
</BoxAvatar>
|
||||
|
||||
<InputSelectSearch
|
||||
required={true}
|
||||
id="branch"
|
||||
label="Branch"
|
||||
placeholder="main"
|
||||
bind:value={selectedBranch}
|
||||
bind:search={selectedBranch}
|
||||
on:select={(event) => {
|
||||
selectedBranch = event.detail.value;
|
||||
}}
|
||||
name="branch"
|
||||
options={branchesList?.branches?.map((branch) => {
|
||||
return {
|
||||
value: branch.name,
|
||||
label: branch.name
|
||||
};
|
||||
}) ?? []} />
|
||||
<InputText
|
||||
id="root"
|
||||
label="Root directory"
|
||||
placeholder="functions/my-function"
|
||||
bind:value={selectedDir} />
|
||||
<InputChoice
|
||||
id="silent"
|
||||
label="Silent mode"
|
||||
tooltip="Don't create comments and checks when pushing to this repository"
|
||||
bind:value={silentMode} />
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
{#if step === 1}
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button secondary disabled={!selectedRepoId} on:click={() => step++}>Next</Button>
|
||||
{:else}
|
||||
<Button secondary on:click={() => step--}>Back</Button>
|
||||
<Button submit>Deploy now</Button>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
|
||||
export let site: Models.Site;
|
||||
let siteName: string = null;
|
||||
|
||||
onMount(async () => {
|
||||
siteName ??= site.name;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdk.forProject.sites.update(
|
||||
site.$id,
|
||||
siteName,
|
||||
site.framework,
|
||||
site.enabled || undefined,
|
||||
site.timeout || undefined,
|
||||
site.installCommand || undefined,
|
||||
site.buildCommand || undefined,
|
||||
site.outputDirectory || undefined,
|
||||
site.buildRuntime || undefined,
|
||||
site.serveRuntime || undefined,
|
||||
site.installationId || undefined,
|
||||
site.providerRepositoryId || undefined,
|
||||
site.providerBranch || undefined,
|
||||
site.providerSilentMode || undefined,
|
||||
site.providerRootDirectory || undefined,
|
||||
site.specification || undefined
|
||||
);
|
||||
await invalidate(Dependencies.SITE);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.SiteUpdateName);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.SiteUpdateName);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updateName}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Name</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={siteName} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={siteName === site.name || !siteName} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
Reference in New Issue
Block a user