fix some more

This commit is contained in:
Torsten Dittmann
2023-07-16 17:54:50 +02:00
parent bfde571747
commit 929bc1660b
11 changed files with 620 additions and 23 deletions
+1 -1
View File
@@ -45,7 +45,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body>
<body data-sveltekit-preload-data="hover">
<div id="svelte">%sveltekit.body%</div>
</body>
</html>
+1
View File
@@ -26,6 +26,7 @@ export enum Dependencies {
FILES = 'dependency:files',
FUNCTION = 'dependency:function',
FUNCTION_DOMAINS = 'dependency:function_domains',
FUNCTION_INSTALLATIONS = 'dependency:function_installations',
FUNCTIONS = 'dependency:functions',
VARIABLES = 'dependency:variables',
DEPLOYMENTS = 'dependency:deployments',
@@ -0,0 +1,66 @@
<script lang="ts">
import { Wizard } from '$lib/layout';
import SelectRepository from './steps/selectRepository.svelte';
import GitConfiguration from './steps/gitConfiguration.svelte';
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
import { sdk } from '$lib/stores/sdk';
import { func } from '$routes/console/project-[project]/functions/function-[function]/store';
import { choices, installation, repository } from './store';
import { wizard } from '$lib/stores/wizard';
import { beforeNavigate, invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
async function createGitHubInstallation() {
await sdk.forProject.functions.update(
$func.$id,
$func.name,
$func.execute || undefined,
$func.entrypoint || undefined,
$func.events || undefined,
$func.schedule || undefined,
$func.timeout || undefined,
$func.enabled,
$func.logging,
$func.buildCommand || undefined,
$func.installCommand || undefined,
$installation.$id,
$repository.id,
$choices.branch,
$choices.silentMode,
$choices.rootDir
);
await invalidate(Dependencies.FUNCTION);
resetState();
wizard.hide();
}
beforeNavigate(() => {
wizard.hide();
});
function resetState() {
choices.set({
branch: null,
silentMode: false,
rootDir: null
});
installation.set(null);
repository.set(null);
}
const stepsComponents: WizardStepsType = new Map();
stepsComponents.set(1, {
label: 'Select repository',
component: SelectRepository
});
stepsComponents.set(2, {
label: 'Git configuration',
component: GitConfiguration
});
</script>
<Wizard
title="Connect Git"
steps={stepsComponents}
on:finish={createGitHubInstallation}
on:exit={resetState} />
@@ -0,0 +1,23 @@
<script lang="ts">
import { WizardStep } from '$lib/layout';
import { FormList, InputCron } from '$lib/elements/forms';
import { createFunction } from './store';
</script>
<WizardStep>
<svelte:fragment slot="title">Schedule</svelte:fragment>
<svelte:fragment slot="subtitle">
Set a Cron schedule to trigger your function. Leave blank for no schedule. <a
href="https://appwrite.io/docs/functions#createFunction"
target="_blank"
rel="noopener noreferrer"
class="link">More details on Cron syntax here</a
>.
</svelte:fragment>
<FormList>
<InputCron
bind:value={$createFunction.schedule}
label="Schedule (Cron Syntax)"
id="schedule" />
</FormList>
</WizardStep>
@@ -0,0 +1,68 @@
<script lang="ts">
import { WizardStep } from '$lib/layout';
import { Button } from '$lib/elements/forms';
import { Empty } from '$lib/components';
import { createFunction } from './store';
import { EventModal } from '$lib/components';
import { TableList, TableCellText, TableCell } from '$lib/elements/table';
let showCreate = false;
let eventSet = new Set<string>();
function handleCreated(event: CustomEvent) {
eventSet.add(event.detail);
$createFunction.events = Array.from(eventSet);
}
</script>
<WizardStep>
<svelte:fragment slot="title">Events</svelte:fragment>
<svelte:fragment slot="subtitle">
Set the events that will trigger your function. Maximum 100 events allowed.
</svelte:fragment>
{#if $createFunction?.events?.length}
<TableList>
{#each $createFunction.events as event}
<li class="table-row">
<TableCellText title="id">
{event}
</TableCellText>
<TableCell showOverflow title="options" width={40}>
<button
class="button is-text is-only-icon"
aria-label="delete id"
on:click|preventDefault={() => {
eventSet.delete(event);
$createFunction.events = Array.from(eventSet);
}}>
<span class="icon-x" aria-hidden="true" />
</button>
</TableCell>
</li>
{/each}
</TableList>
<div class="u-flex u-margin-block-start-16">
<Button text noMargin on:click={() => (showCreate = !showCreate)}>
<span class="icon-plus" aria-hidden="true" />
<span class="u-text">Add event</span>
</Button>
</div>
{:else}
<Empty on:click={() => (showCreate = !showCreate)}>Add an event to get started</Empty>
{/if}
</WizardStep>
{#if showCreate}
<EventModal bind:show={showCreate} on:created={handleCreated}>
<p class="text">
Select events in your Appwrite project that will trigger your function. <a
href="https://appwrite.io/docs/events"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more about Appwrite Events</a
>.
</p>
</EventModal>
{/if}
@@ -0,0 +1,84 @@
<script lang="ts">
import { FormList, InputChoice, InputSelect, InputText } from '$lib/elements/forms';
import { WizardStep } from '$lib/layout';
import { sdk } from '$lib/stores/sdk';
import { choices, installation, repository } from '../store';
$choices.rootDir ??= './';
function getProviderIcon(provider: string) {
switch (provider) {
case 'github':
return 'icon-github';
default:
return '';
}
}
async function loadBranches() {
const { branches } = await sdk.forProject.vcs.listRepositoryBranches(
$installation.$id,
$repository.id
);
const sorted = branches.sort((a, b) => {
if (a.name === 'main' || a.name === 'master') {
return -1;
}
return a.name > b.name ? -1 : 1;
});
$choices.branch = sorted[0].name ?? null;
return sorted;
}
</script>
<WizardStep>
<svelte:fragment slot="title">Execute access</svelte:fragment>
<svelte:fragment slot="subtitle">
Choose who can execute this function using the client API. For more information, check out
the <a
href="https://appwrite.io/docs/permissions"
target="_blank"
rel="noopener noreferrer"
class="link">
Permissions Guide
</a>.
</svelte:fragment>
<div class="box" style:--box-border-radius="var(--border-radius-small)">
<div class="u-flex u-gap-16">
<div class="avatar is-size-x-small">
<span class={getProviderIcon($repository.provider)} />
</div>
<div class="u-cross-child-center u-line-height-1-5">
<h6 class="u-bold u-trim-1">{$installation.organization}/{$repository.name}</h6>
</div>
</div>
{#await loadBranches()}
<div class="u-flex u-gap-8 u-cross-center u-main-center">
<div class="loader u-margin-32" />
</div>
{:then branches}
<div class="u-margin-block-start-24">
<FormList>
<InputSelect
id="branch"
label="Branch"
options={branches?.map((branch) => {
return {
value: branch.name,
label: branch.name
};
}) ?? []}
bind:value={$choices.branch} />
<InputText id="root" label="Root directory" bind:value={$choices.rootDir} />
<InputChoice
id="silent"
label="Silent mode"
tooltip="Don't create comments when pushing to this repository"
bind:value={$choices.silentMode} />
</FormList>
</div>
{/await}
</div>
</WizardStep>
@@ -0,0 +1,204 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/stores';
import { EmptySearch } from '$lib/components';
import { Button, InputSearch, InputSelect } from '$lib/elements/forms';
import { toLocaleDateTime } from '$lib/helpers/date';
import { WizardStep } from '$lib/layout';
import { app } from '$lib/stores/app';
import { sdk } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { installation, installations, repository } from '../store';
let selectedInstallation = null;
async function loadInstallations() {
const { installations } = await sdk.forProject.vcs.listInstallations();
if (installations.length) {
selectedInstallation = installations[0].$id;
installation.set(installations.find((entry) => entry.$id === selectedInstallation));
}
return installations;
}
let search = '';
let selectedRepository = null;
async function loadRepositories(installationId: string, search: string) {
return sdk.forProject.vcs.listRepositories(installationId, search || undefined);
}
const detectionMemory = new Map<string, Models.Detection>();
async function loadDetection(installationId: string, repositoryId: string) {
const key = `${installationId}.${repositoryId}`;
if (detectionMemory.has(key)) {
return detectionMemory.get(key);
}
const detection = await sdk.forProject.vcs.createRepositoryDetection(
installationId,
repositoryId
);
detectionMemory.set(key, detection);
return detection;
}
$: hasInstallations = $installations.total > 0;
async function beforeSubmit() {
if (!hasInstallations) {
throw new Error('Please connect a Git provider');
}
if (!selectedRepository) {
throw new Error('Please select a repository');
}
}
function connectGitHub() {
const target = new URL($page.url);
target.searchParams.append('github-installed', 'true');
sdk.forProject.vcs.createGitHubInstallation(target.toString());
}
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Select repository</svelte:fragment>
<svelte:fragment slot="subtitle">
Select a Git repository that will trigger your function deployments when updated.
</svelte:fragment>
{#if hasInstallations}
{#await loadInstallations()}
<div class="u-flex u-gap-8 u-cross-center u-main-center">
<div class="loader u-margin-32" />
</div>
{:then installations}
<div class="u-flex u-gap-16">
<div class="u-width-full-line">
<InputSelect
id="installation"
label="Select installation"
showLabel={false}
options={installations.map((entry) => {
return {
label: entry.organization,
value: entry.$id
};
})}
on:change={() => {
search = '';
installation.set(
installations.find((entry) => entry.$id === selectedInstallation)
);
}}
bind:value={selectedInstallation} />
</div>
<div class="u-width-full-line">
<InputSearch placeholder="Search repositories" bind:value={search} />
</div>
</div>
{/await}
{#if selectedInstallation}
<p class="text common-section">
Manage organization configuration in your <a
class="link"
href={`${base}/console/project-${$page.params.project}/settings`}
>project settings</a
>.
</p>
{#await loadRepositories(selectedInstallation, search)}
<div class="card-git card is-border-dashed is-no-shadow common-section">
<div class="u-flex u-gap-8 u-cross-center u-main-center">
<div class="loader u-margin-16" />
Loading repositories...
</div>
</div>
{:then response}
{#if response?.total}
<ul class="table is-remove-outer-styles u-sep-block-start common-section">
{#each response.repositories 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={repo.name}
bind:group={selectedRepository}
on:change={() => repository.set(repo)}
value={repo.id} />
{#await loadDetection(selectedInstallation, repo.id)}
<div class="avatar is-size-x-small is-color-empty" />
{:then detection}
{#if detection.runtime}
<div class="avatar is-size-x-small">
<img
src={`${base}/icons/${$app.themeInUse}/color/${detection.runtime}.svg`}
alt={repo.name} />
</div>
{:else}
<div
class="avatar is-size-x-small is-color-empty" />
{/if}
{/await}
<div class="u-flex u-gap-8">
<span class="text">{repo.name}</span>
<time
class="u-color-text-gray"
datetime={repo.pushedAt}>
{toLocaleDateTime(repo.pushedAt)}
</time>
</div>
</div>
</label>
</li>
{/each}
</ul>
{: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/client/teams" 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/client/teams"
target="_blank"
rel="noopener noreferrer">
documentation</a
>.
</p>
</div>
</div>
</EmptySearch>
{/if}
{/await}
{/if}
{:else}
<div class="u-flex u-cross-center u-flex-vertical u-gap-16">
<Button on:click={() => connectGitHub()} fullWidth secondary>
<span class="icon-github" aria-hidden="true" />
<span class="text">Continue with GitHub</span>
</Button>
<Button disabled fullWidth secondary>
<span class="icon-gitlab" aria-hidden="true" />
<span class="text">GitLab (coming soon)</span>
</Button>
<Button disabled fullWidth secondary>
<span class="icon-bitBucket" aria-hidden="true" />
<span class="text">BitBucket (comong soon)</span>
</Button>
</div>
{/if}
</WizardStep>
@@ -0,0 +1,121 @@
<script lang="ts">
import { WizardStep } from '$lib/layout';
import { createFunction } from './store';
import Create from '../../createVariable.svelte';
import { DropList, DropListItem, Secret, Empty, Output } from '$lib/components';
import type { Models } from '@appwrite.io/console';
import {
Table,
TableBody,
TableCell,
TableCellHead,
TableHeader,
TableRow
} from '$lib/elements/table';
import { Button } from '$lib/elements/forms';
let showCreate = false;
let selectedVar: Partial<Models.Variable> = null;
let showDropdown = [];
function handleCreated(dispatchEvent: CustomEvent) {
createFunction.update((n) => {
if (n.vars.length) {
n.vars.map((v) => {
if (v.key !== dispatchEvent.detail.key) {
n.vars.push(dispatchEvent.detail);
}
});
} else {
n.vars.push(dispatchEvent.detail);
}
return n;
});
}
function handleUpdated(dispatchEvent: CustomEvent) {
createFunction.update((n) => {
n.vars = n.vars.map((v) => (v.key === selectedVar.key ? dispatchEvent.detail : v));
return n;
});
}
</script>
<WizardStep>
<svelte:fragment slot="title">Variables</svelte:fragment>
<svelte:fragment slot="subtitle">
Create the environment variables or secret keys that will be passed to your function
</svelte:fragment>
{#if $createFunction.vars.length}
<Table noStyles>
<TableHeader>
<TableCellHead>Key</TableCellHead>
<TableCellHead>Value</TableCellHead>
<TableCellHead width={30} />
</TableHeader>
<TableBody>
{#each $createFunction.vars as variable, i}
<TableRow>
<TableCell title="key">
<Output value={variable.key}>
{variable.key}
</Output>
</TableCell>
<TableCell showOverflow title="value">
<Secret copyEvent="variable" value={variable.value} />
</TableCell>
<TableCell showOverflow title="options">
<DropList bind:show={showDropdown[i]} placement="bottom-start" noArrow>
<Button
text
round
ariaLabel="more options"
on:click={() => (showDropdown[i] = !showDropdown[i])}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</Button>
<svelte:fragment slot="list">
<DropListItem
icon="pencil"
on:click={() => {
selectedVar = $createFunction.vars[i];
showDropdown[i] = false;
showCreate = true;
}}>
Edit
</DropListItem>
<DropListItem
icon="trash"
on:click={() => {
$createFunction.vars.splice(i, 1);
$createFunction = $createFunction;
showDropdown[i] = false;
}}>
Delete
</DropListItem>
</svelte:fragment>
</DropList>
</TableCell>
</TableRow>
{/each}
</TableBody>
</Table>
<div class="u-flex u-margin-block-start-16">
<Button text noMargin on:click={() => (showCreate = !showCreate)}>
<span class="icon-plus" aria-hidden="true" />
<span class="u-text">Create variable</span>
</Button>
</div>
{:else}
<Empty on:click={() => (showCreate = !showCreate)}>Create a variable to get started</Empty>
{/if}
</WizardStep>
{#if showCreate}
<Create
bind:selectedVar
bind:showCreate
on:created={handleCreated}
on:updated={handleUpdated} />
{/if}
+20
View File
@@ -0,0 +1,20 @@
import { page } from '$app/stores';
import type { Models } from '@appwrite.io/console';
import { derived, writable } from 'svelte/store';
export const repository = writable<Models.Repository>();
export const installation = writable<Models.Installation>();
export const choices = writable<{
branch: string;
rootDir: string;
silentMode: boolean;
}>({
branch: null,
rootDir: null,
silentMode: null
});
export const installations = derived(
page,
($page) => $page.data.installations as Models.InstallationList
);
@@ -5,6 +5,7 @@ import { Query } from '@appwrite.io/console';
export const load: PageLoad = async ({ params, depends, parent }) => {
depends(Dependencies.VARIABLES);
depends(Dependencies.FUNCTION_INSTALLATIONS);
const data = await parent();
@@ -21,6 +21,9 @@
import { toLocaleDateTime } from '$lib/helpers/date';
import { onMount } from 'svelte';
import DisconnectRepo from './disconnectRepo.svelte';
import { Pill } from '$lib/elements';
import { wizard } from '$lib/stores/wizard';
import ConnectExisting from '$lib/wizards/functions/connectExisting.svelte';
export let installations: Models.InstallationList;
export let repository: Models.Repository | null;
@@ -43,30 +46,34 @@
selectedBranch = $func?.vcsBranch;
silentMode = $func?.vcsSilentMode ?? false;
selectedDir = $func?.vcsRootDirectory;
if ($page.url.searchParams.has('github-installed')) {
wizard.start(ConnectExisting);
}
});
function getProviderIcon(provider: string) {
if (provider === 'github') {
return `icon-github`;
}
return '';
enum ProviderNames {
github = 'GitHub',
gitlab = 'GitLab',
bitBucket = 'BitBucket'
}
function getProviderName(provider: string) {
if (provider === 'github') {
return `GitHub`;
function getProviderIcon(provider: string) {
switch (provider) {
case 'github':
return 'icon-github';
default:
return '';
}
return '';
}
function getRepositoryLink(repository: Models.Repository) {
if (repository.provider === 'github') {
return `https://github.com/${repository.organization}/${repository.name}`;
switch (repository.provider) {
case 'github':
return `https://github.com/${repository.organization}/${repository.name}`;
default:
return '';
}
return '';
}
async function updateConfiguration() {
@@ -148,7 +155,12 @@
</FormList>
<Collapsible>
<CollapsibleItem>
<svelte:fragment slot="title">Git settings</svelte:fragment>
<svelte:fragment slot="title">
<span class="u-margin-inline-end-16">Git settings</span>
{#if !repository}
<Pill>NOT CONNECTED</Pill>
{/if}
</svelte:fragment>
{#if repository}
<div class="box" style:--box-border-radius="var(--border-radius-small)">
<div class="u-flex u-gap-16">
@@ -187,7 +199,8 @@
<Button text on:click={() => (showDisconnect = true)}
>Disconnect repository</Button>
<Button secondary href={getRepositoryLink(repository)} external>
View on {getProviderName(repository.provider)}
View on {ProviderNames[repository.provider] ??
'unknown provider'}
<span class="icon-external-link" />
</Button>
</div>
@@ -202,11 +215,7 @@
<div class="avatar"><span class="icon-server" /></div>
</div>
<Button
secondary
on:click={() => {
showGit = true;
}}>
<Button secondary on:click={() => wizard.start(ConnectExisting)}>
<span class="text">Connect Git</span>
</Button>
</div>