mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'feat-pink-v2' into 'file-tokens'.
This commit is contained in:
+19
-19
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"backgroundColor": "var(--color-bgcolor-neutral-primary)",
|
||||
"backgroundColor": "var(--bgcolor-neutral-primary)",
|
||||
"categoryAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)",
|
||||
"color": "var(--fgcolor-neutral-tertiary)",
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
@@ -26,12 +26,12 @@
|
||||
},
|
||||
"valueAxis": {
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)",
|
||||
"color": "var(--fgcolor-neutral-tertiary)",
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
"color": ["var(--border-neutral)"]
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
@@ -48,23 +48,23 @@
|
||||
"logAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)",
|
||||
"color": "var(--fgcolor-neutral-tertiary)",
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
"color": ["var(--border-neutral)"]
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
@@ -75,23 +75,23 @@
|
||||
"timeAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)",
|
||||
"color": "var(--fgcolor-neutral-tertiary)",
|
||||
"show": true
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
"color": ["var(--border-neutral)"]
|
||||
},
|
||||
"show": true
|
||||
},
|
||||
@@ -100,18 +100,18 @@
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "var(--color-bgcolor-neutral-primary)",
|
||||
"borderColor": "var(--color-border-neutral)",
|
||||
"backgroundColor": "var(--bgcolor-neutral-primary)",
|
||||
"borderColor": "var(--border-neutral)",
|
||||
"textStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-secondary)"
|
||||
"color": "var(--fgcolor-neutral-secondary)"
|
||||
},
|
||||
"axisPointer": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-border-neutral-strong)",
|
||||
"color": "var(--border-neutral-strong)",
|
||||
"width": 1
|
||||
},
|
||||
"crossStyle": {
|
||||
"color": "var(--color-border-neutral-strong)",
|
||||
"color": "var(--border-neutral-strong)",
|
||||
"width": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,10 +120,10 @@
|
||||
"color": "#444444"
|
||||
}
|
||||
},
|
||||
"backgroundColor": "var(--color-bgcolor-neutral-primary)",
|
||||
"borderColor": "var(--color-border-neutral)",
|
||||
"backgroundColor": "var(--bgcolor-neutral-primary)",
|
||||
"borderColor": "var(--border-neutral)",
|
||||
"textStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-secondary)"
|
||||
"color": "var(--fgcolor-neutral-secondary)"
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
|
||||
+19
-19
@@ -1,82 +1,82 @@
|
||||
{
|
||||
"backgroundColor": "var(--color-bgcolor-neutral-primary)",
|
||||
"backgroundColor": "var(--bgcolor-neutral-primary)",
|
||||
"categoryAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"valueAxis": {
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
"color": ["var(--border-neutral)"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"logAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
"color": ["var(--border-neutral)"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeAxis": {
|
||||
"axisLine": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "var(--color-fgcolor-neutral-tertiary)"
|
||||
"color": "var(--fgcolor-neutral-tertiary)"
|
||||
},
|
||||
"splitLine": {
|
||||
"lineStyle": {
|
||||
"color": ["var(--color-border-neutral)"]
|
||||
"color": ["var(--border-neutral)"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "var(--color-bgcolor-neutral-primary)",
|
||||
"borderColor": "var(--color-border-neutral)",
|
||||
"backgroundColor": "var(--bgcolor-neutral-primary)",
|
||||
"borderColor": "var(--border-neutral)",
|
||||
"textStyle": {
|
||||
"color": "var(--color-fgcolor-neutral-secondary)"
|
||||
"color": "var(--fgcolor-neutral-secondary)"
|
||||
},
|
||||
"axisPointer": {
|
||||
"lineStyle": {
|
||||
"color": "var(--color-border-neutral-strong)"
|
||||
"color": "var(--border-neutral-strong)"
|
||||
},
|
||||
"crossStyle": {
|
||||
"color": "var(--color-border-neutral-strong)"
|
||||
"color": "var(--border-neutral-strong)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,14 +281,6 @@
|
||||
--logo-bg: #f2f2f8;
|
||||
}
|
||||
|
||||
:global(.theme-dark) .footer {
|
||||
--sep-clr: hsl(var(--color-neutral-150));
|
||||
}
|
||||
|
||||
:global(.theme-light) .footer {
|
||||
--sep-clr: hsl(var(--color-neutral-30));
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: auto;
|
||||
padding: 1rem;
|
||||
@@ -324,14 +316,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
.sep {
|
||||
width: 1px;
|
||||
height: 1.5rem;
|
||||
background-color: var(--sep-clr);
|
||||
}
|
||||
}
|
||||
|
||||
.experimental {
|
||||
display: flex;
|
||||
padding: 0.09375rem 0.25rem;
|
||||
|
||||
@@ -68,9 +68,9 @@
|
||||
src={`${base}/icons/${$app.themeInUse}/color/${command.image}.svg`}
|
||||
alt={command.label} />
|
||||
{:else if command.icon}
|
||||
<Icon icon={command.icon} size="s" color="--color-fgcolor-neutral-tertiary" />
|
||||
<Icon icon={command.icon} size="s" color="--fgcolor-neutral-tertiary" />
|
||||
{:else}
|
||||
<Icon icon={IconArrowSmRight} size="s" color="--color-fgcolor-neutral-tertiary" />
|
||||
<Icon icon={IconArrowSmRight} size="s" color="--fgcolor-neutral-tertiary" />
|
||||
{/if}
|
||||
<span>
|
||||
{command.label}
|
||||
@@ -103,7 +103,7 @@
|
||||
|
||||
<style>
|
||||
.then {
|
||||
color: var(--color-fgcolor-neutral-tertiary, #97979b);
|
||||
color: var(--fgcolor-neutral-tertiary, #97979b);
|
||||
font-size: var(--font-size-s, 14px);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@@ -300,12 +300,12 @@
|
||||
<Icon
|
||||
icon={item.icon}
|
||||
size="s"
|
||||
color="--color-fgcolor-neutral-tertiary" />
|
||||
color="--fgcolor-neutral-tertiary" />
|
||||
{:else}
|
||||
<Icon
|
||||
icon={IconArrowSmRight}
|
||||
size="s"
|
||||
color="--color-fgcolor-neutral-tertiary" />
|
||||
color="--fgcolor-neutral-tertiary" />
|
||||
{/if}
|
||||
<span>
|
||||
{item.label}
|
||||
@@ -378,15 +378,15 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
--cmd-center-bg: var(--color-bgcolor-neutral-primary, #fff);
|
||||
--footer-bg: var(--color-bgcolor-neutral-primary, #fff);
|
||||
--cmd-center-border: var(--color-border-neutral, #ededf0);
|
||||
--result-bg: var(--color-overlay-neutral-hover, rgba(25, 25, 28, 0.03));
|
||||
--kbd-bg: var(--color-overlay-on-neutral, rgba(0, 0, 0, 0.06));
|
||||
--kbd-color: var(--color-fgcolor-neutral-secondary, #56565c);
|
||||
--icon-color: var(--color-fgcolor-neutral-tertiary, #97979b);
|
||||
--label-color: var(--color-fgColor-neutral-secondary, #56565c);
|
||||
--crumb-color: var(--color-fgColor-neutral-secondary, #56565c);
|
||||
--cmd-center-bg: var(--bgcolor-neutral-primary);
|
||||
--footer-bg: var(--bgcolor-neutral-primary);
|
||||
--cmd-center-border: var(--border-neutral);
|
||||
--result-bg: var(--color-overlay-neutral-hover);
|
||||
--kbd-bg: var(--color-overlay-on-neutral);
|
||||
--kbd-color: var(--fgcolor-neutral-secondary);
|
||||
--icon-color: var(--fgcolor-neutral-tertiary);
|
||||
--label-color: var(--fgcolor-neutral-secondary);
|
||||
--crumb-color: var(--fgcolor-neutral-secondary);
|
||||
}
|
||||
|
||||
// Elements
|
||||
@@ -432,7 +432,7 @@
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
border-bottom: 1px solid var(--color-border-neutral, #ededf0);
|
||||
border-bottom: 1px solid var(--border-neutral, #ededf0);
|
||||
font-size: 16px;
|
||||
padding: 1rem;
|
||||
|
||||
@@ -480,7 +480,7 @@
|
||||
padding: 1rem;
|
||||
|
||||
.group {
|
||||
color: var(--color-fgcolor-neutral-secondary, #56565c);
|
||||
color: var(--fgcolor-neutral-secondary, #56565c);
|
||||
margin-inline-start: 0.25rem;
|
||||
margin-block-end: 0.25rem;
|
||||
position: relative;
|
||||
@@ -548,7 +548,7 @@
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
border-left: 1px solid var(--color-border-neutral, #ededf0);
|
||||
border-left: 1px solid var(--border-neutral, #ededf0);
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -558,7 +558,7 @@
|
||||
|
||||
.footer {
|
||||
background: var(--footer-bg);
|
||||
border-top: 1px solid var(--color-border-neutral, #ededf0);
|
||||
border-top: 1px solid var(--border-neutral, #ededf0);
|
||||
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
>{#if selectedOrg?.tierName}<Badge
|
||||
variant="secondary"
|
||||
content={selectedOrg?.tierName} />{/if}</span>
|
||||
<Icon icon={IconChevronDown} size="s" color="--color-fgcolor-neutral-secondary" />
|
||||
<Icon icon={IconChevronDown} size="s" color="--fgcolor-neutral-secondary" />
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
@@ -208,7 +208,7 @@
|
||||
>{selectedOrg?.name ?? 'Organization'}</span>
|
||||
<span class="not-mobile"
|
||||
><Badge variant="secondary" content={selectedOrg?.tierName ?? ''} /></span>
|
||||
<Icon icon={IconChevronDown} size="s" color="--color-fgcolor-neutral-secondary" />
|
||||
<Icon icon={IconChevronDown} size="s" color="--fgcolor-neutral-secondary" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -348,7 +348,7 @@
|
||||
|
||||
<style lang="scss">
|
||||
.menu {
|
||||
min-width: 220px;
|
||||
min-width: 244px;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@
|
||||
}
|
||||
|
||||
.subMenu {
|
||||
min-width: 220px;
|
||||
min-width: 244px;
|
||||
margin-inline: -4px;
|
||||
margin-block: -4px;
|
||||
}
|
||||
@@ -372,7 +372,7 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 60px;
|
||||
color: var(--color-fgcolor-neutral-secondary);
|
||||
color: var(--fgcolor-neutral-secondary);
|
||||
|
||||
@media (min-width: 390px) {
|
||||
max-width: 95px;
|
||||
@@ -409,7 +409,7 @@
|
||||
|
||||
transition: color 0.2s ease;
|
||||
|
||||
color: var(--color-fgcolor-neutral-primary, #2d2d31);
|
||||
color: var(--fgcolor-neutral-primary, #2d2d31);
|
||||
border-radius: var(--corner-radius-medium, 8px);
|
||||
|
||||
cursor: default;
|
||||
@@ -427,29 +427,29 @@
|
||||
|
||||
:global(.trigger[data-highlighted]) {
|
||||
outline: none;
|
||||
background: var(--color-bgcolor-neutral-secondary, #f4f4f7);
|
||||
background: var(--bgcolor-neutral-secondary, #f4f4f7);
|
||||
}
|
||||
|
||||
:global(.trigger[data-highlighted]:focus) {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--color-bgcolor-neutral-secondary, #f4f4f7);
|
||||
box-shadow: 0 0 0 2px var(--bgcolor-neutral-secondary, #f4f4f7);
|
||||
}
|
||||
|
||||
.trigger:focus {
|
||||
z-index: 30;
|
||||
box-shadow:
|
||||
var(--shadow-offsetx-0, 0px) var(--shadow-offsety-0, 0px) 0 2px
|
||||
var(--color-bgcolor-neutral-default, #fafafb),
|
||||
0 0 0 4px var(--color-border-focus, #818186);
|
||||
var(--bgcolor-neutral-default, #fafafb),
|
||||
0 0 0 4px var(--border-focus, #818186);
|
||||
}
|
||||
.separator {
|
||||
height: 1px;
|
||||
margin-block: 2px;
|
||||
margin-inline-start: calc(var(--base-4) * -1);
|
||||
width: calc(100% + var(--base-8));
|
||||
background-color: var(--color-border-neutral);
|
||||
background-color: var(--border-neutral);
|
||||
}
|
||||
.breadcrumb-separator {
|
||||
color: var(--color-fgcolor-neutral-tertiary, #97979b);
|
||||
color: var(--fgcolor-neutral-tertiary, #97979b);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<Card.Base
|
||||
variant="secondary"
|
||||
padding="s"
|
||||
--input-background-color="var(--color-bgcolor-neutral-primary)">
|
||||
--input-background-color="var(--bgcolor-neutral-primary)">
|
||||
<Layout.Stack gap="xl">
|
||||
<Layout.Stack gap="s">
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignContent="center">
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { onMount } from 'svelte';
|
||||
import Heading from './heading.svelte';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
import Empty from './empty.svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let show: boolean;
|
||||
export let mimeTypeQuery: string = 'image/';
|
||||
@@ -446,7 +446,7 @@
|
||||
<article
|
||||
style:--card-bg-color="transparent"
|
||||
style:--shadow-small="none"
|
||||
style:--color-border="var(--color-neutral-15)"
|
||||
style:--border="var(--color-neutral-15)"
|
||||
class="card u-grid u-cross-center u-width-full-line common-section is-border-dashed">
|
||||
<div
|
||||
class="u-flex u-flex-vertical u-cross-center u-gap-24 u-overflow-hidden">
|
||||
@@ -476,15 +476,12 @@
|
||||
noMedia
|
||||
--card-bg-color="transparent"
|
||||
--shadow-small="none"
|
||||
--color-border="var(--color-neutral-15)">
|
||||
--border="var(--color-neutral-15)">
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<Heading
|
||||
size="7"
|
||||
tag="h2"
|
||||
trimmed={false}>
|
||||
<Typography.Title size="s">
|
||||
No files found within this bucket.
|
||||
</Heading>
|
||||
</Typography.Title>
|
||||
<p class="text u-line-height-1-5">
|
||||
Need a hand? Learn more in our <a
|
||||
class="link"
|
||||
@@ -508,11 +505,9 @@
|
||||
noMedia
|
||||
--card-bg-color="transparent"
|
||||
--shadow-small="none"
|
||||
--color-border="var(--color-neutral-15)">
|
||||
--border="var(--color-neutral-15)">
|
||||
<div class="u-text-center u-flex-vertical u-cross-center u-gap-24">
|
||||
<Heading size="7" tag="h2" trimmed={false}>
|
||||
No buckets found
|
||||
</Heading>
|
||||
<Typography.Title size="s">No buckets found</Typography.Title>
|
||||
<Button
|
||||
secondary
|
||||
external
|
||||
|
||||
@@ -122,9 +122,7 @@
|
||||
options={column?.elements?.map((e) => ({
|
||||
label: e?.label ?? e,
|
||||
value: e?.value ?? e
|
||||
}))}
|
||||
label="Value"
|
||||
showLabel={false} />
|
||||
}))} />
|
||||
{:else if column.type === 'integer' || column.type === 'double'}
|
||||
<InputNumber id="value" bind:value placeholder="Enter value" />
|
||||
{:else if column.type === 'boolean'}
|
||||
@@ -139,12 +137,7 @@
|
||||
bind:value />
|
||||
{:else if column.type === 'datetime'}
|
||||
{#key value}
|
||||
<InputDateTime
|
||||
id="value"
|
||||
bind:value
|
||||
label="value"
|
||||
showLabel={false}
|
||||
step={60} />
|
||||
<InputDateTime id="value" bind:value step={60} />
|
||||
{/key}
|
||||
{:else}
|
||||
<InputText id="value" bind:value placeholder="Enter value" />
|
||||
@@ -166,17 +159,3 @@
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.selects {
|
||||
:global(> *) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tags {
|
||||
:global(b) {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -225,24 +225,3 @@
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.dropped {
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0px 16px 32px 0px rgba(55, 59, 77, 0.04);
|
||||
|
||||
padding: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
|
||||
width: 37.5rem;
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
width: calc(100% + 2rem);
|
||||
background-color: hsl(var(--color-border));
|
||||
|
||||
margin-block-start: 1rem;
|
||||
margin-inline: -1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
title="No installation was added to the project yet"
|
||||
description="Add an installation to connect repositories">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button size="m" secondary href={connectGitHub().toString()} disabled={!isVcsEnabled}>
|
||||
<Icon slot="start" icon={IconGithub} size="m" />
|
||||
<Button secondary href={connectGitHub().toString()} disabled={!isVcsEnabled}>
|
||||
<Icon slot="start" icon={IconGithub} />
|
||||
Connect to GitHub
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Button, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import { Fieldset, Layout, Selector } from '@appwrite.io/pink-svelte';
|
||||
import SelectRootModal from '../(components)/selectRootModal.svelte';
|
||||
import SelectRootModal from '../../../routes/(console)/project-[project]/sites/(components)/selectRootModal.svelte';
|
||||
|
||||
export let branch: string;
|
||||
export let rootDir: string;
|
||||
@@ -1,9 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { EmptySearch } from '$lib/components';
|
||||
import { Button, InputSearch, InputSelect } from '$lib/elements/forms';
|
||||
import { timeFromNow } from '$lib/helpers/date';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { repositories } from '$routes/(console)/project-[project]/functions/function-[function]/store';
|
||||
import { installation, installations, repository } from '$lib/stores/vcs';
|
||||
@@ -167,36 +165,36 @@
|
||||
size="xs"
|
||||
alt={repo.name}
|
||||
empty={!framework}>
|
||||
<SvgIcon name={getFrameworkIcon(framework)} />
|
||||
<SvgIcon
|
||||
name={getFrameworkIcon(framework)}
|
||||
iconSize="small" />
|
||||
</Avatar>
|
||||
{/await}
|
||||
{:else}
|
||||
<Avatar
|
||||
size="xs"
|
||||
src={repo?.runtime
|
||||
? `${base}/icons/${$app.themeInUse}/color/${
|
||||
repo.runtime.split('-')[0]
|
||||
}.svg`
|
||||
: ''}
|
||||
alt={repo.name} />
|
||||
{@const iconName = repo?.runtime
|
||||
? repo.runtime.split('-')[0]
|
||||
: undefined}
|
||||
<Avatar size="xs" alt={repo.name} empty={!iconName}>
|
||||
<SvgIcon name={iconName} iconSize="small" />
|
||||
</Avatar>
|
||||
{/if}
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
<Typography.Text
|
||||
truncate
|
||||
color="--color-fgcolor-neutral-secondary">
|
||||
color="--fgcolor-neutral-secondary">
|
||||
{repo.name}
|
||||
</Typography.Text>
|
||||
{#if repo.private}
|
||||
<Icon
|
||||
size="s"
|
||||
icon={IconLockClosed}
|
||||
color="--color-fgcolor-neutral-tertiary" />
|
||||
color="--fgcolor-neutral-tertiary" />
|
||||
{/if}
|
||||
<time datetime={repo.pushedAt}>
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
truncate
|
||||
color="--color-fgcolor-neutral-tertiary">
|
||||
color="--fgcolor-neutral-tertiary">
|
||||
{timeFromNow(repo.pushedAt)}
|
||||
</Typography.Caption>
|
||||
</time>
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
<script>
|
||||
import { Card, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Card, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
export let href;
|
||||
</script>
|
||||
|
||||
<Card.Link class="card" {href}>
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3"><slot name="eyebrow" /></div>
|
||||
<Typography.Title size="s"><slot name="title" /></Typography.Title>
|
||||
<div class="u-padding-block-start-4"><slot name="subtitle" /></div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-start-end">
|
||||
<slot name="status" />
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="u-flex u-gap-16 u-flex-wrap">
|
||||
<Layout.Stack height="calc(182 / 16 * 1rem)" justifyContent="space-between">
|
||||
<Layout.Stack direction="row">
|
||||
<Layout.Stack gap="xs">
|
||||
<Typography.Caption color="--fgcolor-neutral-tertiary"
|
||||
><slot name="eyebrow" /></Typography.Caption>
|
||||
<Typography.Title size="s"><slot name="title" /></Typography.Title>
|
||||
<div>
|
||||
<slot name="subtitle" />
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row" justifyContent="flex-end" alignItems="center">
|
||||
<slot name="status" />
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row">
|
||||
<Layout.Stack direction="row">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-item-1-end-end">
|
||||
<ul class="icons u-flex u-gap-8">
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<slot name="icons" />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Card.Link>
|
||||
|
||||
<style>
|
||||
/* TODO: remove this when ui library is updated*/
|
||||
.grid-item-1 {
|
||||
min-block-size: calc(182 / 16 * 1rem);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
type NumSize = 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
type Size = NumSize | `${NumSize}`;
|
||||
|
||||
export let tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
export let size: Size;
|
||||
export let trimmed = true;
|
||||
export let trimmedSecondLine = false;
|
||||
export let id: string = null;
|
||||
let classes = '';
|
||||
export { classes as class };
|
||||
export let style = '';
|
||||
</script>
|
||||
|
||||
<svelte:element
|
||||
this={tag}
|
||||
class={`heading-level-${size} u-min-width-0 ${classes}`}
|
||||
class:u-trim-1={trimmed}
|
||||
class:u-trim-2={trimmedSecondLine}
|
||||
{style}
|
||||
{id}>
|
||||
<slot />
|
||||
</svelte:element>
|
||||
@@ -46,7 +46,6 @@ export { default as Tabs } from './tabs.svelte';
|
||||
export { default as Tab } from './tab.svelte';
|
||||
export { default as EventModal } from './eventModal.svelte';
|
||||
export { default as Status } from './status.svelte';
|
||||
export { default as Heading } from './heading.svelte';
|
||||
export { default as Output } from './output.svelte';
|
||||
export { default as Support } from './support.svelte';
|
||||
export { default as ViewSelector } from './viewSelector.svelte';
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
|
||||
export let factors: Models.MfaFactors & { recoveryCode: boolean };
|
||||
/** If true, the form will be submitted automatically when the code is entered. */
|
||||
export let autoSubmit: boolean = true;
|
||||
export let showVerifyButton: boolean = true;
|
||||
export let disabled: boolean = false;
|
||||
export let challenge: Models.MfaChallenge;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
export let show = false;
|
||||
export let error: string = null;
|
||||
export let closable = true;
|
||||
export let dismissible = true;
|
||||
export let onSubmit: (e: SubmitEvent) => Promise<void> | void = function () {
|
||||
return;
|
||||
|
||||
@@ -94,12 +94,14 @@
|
||||
export let showAccountMenu = false;
|
||||
|
||||
let activeTheme = $app.theme;
|
||||
let shouldAnimateThemeToggle = false;
|
||||
|
||||
$: {
|
||||
if (activeTheme) {
|
||||
updateTheme(activeTheme);
|
||||
}
|
||||
}
|
||||
|
||||
$: currentOrg = organizations.find((org) => org.isSelected);
|
||||
$: selectedProject = currentOrg?.projects.find((project) => project.isSelected);
|
||||
</script>
|
||||
@@ -120,7 +122,7 @@
|
||||
</a>
|
||||
<Breadcrumbs {organizations} />
|
||||
{#if selectedProject && selectedProject.pingCount === 0}
|
||||
<div class="only-desktop" style:margin-inline-start="-10px">
|
||||
<div class="only-desktop" style:margin-inline-start="-16px">
|
||||
<Button.Anchor
|
||||
href={`${base}/project-${selectedProject.$id}/get-started`}
|
||||
variant="secondary"
|
||||
@@ -186,7 +188,8 @@
|
||||
<Button.Button
|
||||
variant="text"
|
||||
aria-label="Toggle Command Center"
|
||||
on:click={toggleCommandCenter}>
|
||||
on:click={toggleCommandCenter}
|
||||
icon>
|
||||
<Icon icon={IconSearch} />
|
||||
</Button.Button>
|
||||
<span slot="tooltip">{isMac() ? '⌘ + K' : 'Ctrl + K'}</span></Tooltip>
|
||||
@@ -194,6 +197,7 @@
|
||||
<Link.Button
|
||||
on:click={() => {
|
||||
showAccountMenu = !showAccountMenu;
|
||||
shouldAnimateThemeToggle = false;
|
||||
if (showAccountMenu) {
|
||||
trackEvent('click_menu_dropdown');
|
||||
}
|
||||
@@ -234,17 +238,31 @@
|
||||
direction="row"
|
||||
alignItems="center">
|
||||
<Typography.Text>Theme</Typography.Text>
|
||||
<ToggleButton
|
||||
bind:active={activeTheme}
|
||||
buttons={[
|
||||
{ id: 'light', label: 'Light', icon: IconSun },
|
||||
{ id: 'dark', label: 'Dark', icon: IconMoon },
|
||||
{
|
||||
id: 'auto',
|
||||
label: 'System',
|
||||
icon: IconMode
|
||||
}
|
||||
]}></ToggleButton>
|
||||
<div
|
||||
class:keepTransformTransition={shouldAnimateThemeToggle}>
|
||||
<ToggleButton
|
||||
bind:active={activeTheme}
|
||||
on:change={(event) => {
|
||||
shouldAnimateThemeToggle = true;
|
||||
}}
|
||||
buttons={[
|
||||
{
|
||||
id: 'light',
|
||||
label: 'Light',
|
||||
icon: IconSun
|
||||
},
|
||||
{
|
||||
id: 'dark',
|
||||
label: 'Dark',
|
||||
icon: IconMoon
|
||||
},
|
||||
{
|
||||
id: 'auto',
|
||||
label: 'System',
|
||||
icon: IconMode
|
||||
}
|
||||
]}></ToggleButton>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
@@ -356,8 +374,8 @@
|
||||
align-items: center;
|
||||
gap: var(--space-4, 8px);
|
||||
border-radius: var(--border-radius-xs, 6px);
|
||||
border: var(--border-width-s, 1px) solid var(--color-border-neutral-strong, #d8d8db);
|
||||
background: var(--color-bgcolor-neutral-primary, #fff);
|
||||
border: var(--border-width-s, 1px) solid var(--border-neutral-strong, #d8d8db);
|
||||
background: var(--bgcolor-neutral-primary, #fff);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -404,4 +422,8 @@
|
||||
/* `desltop` is not a typo—it comes from the `pink2/legacy` module! */
|
||||
inline-size: var(--p-drop-width-size-desltop);
|
||||
}
|
||||
|
||||
:global(.keepTransformTransition span) {
|
||||
transition: transform 0.2s ease-out !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
{#if !hideFooter}
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography.Text variant="m-400" color="--color-fgcolor-neutral-secondary">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-secondary">
|
||||
Total results: {total}
|
||||
</Typography.Text>
|
||||
<div>
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/elements/table';
|
||||
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { onMount } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import Actions from './actions.svelte';
|
||||
import type { Permission } from './permissions.svelte';
|
||||
import Row from './row.svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon, Layout, Table } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus, IconTable, IconX } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let roles: string[] = [];
|
||||
|
||||
@@ -88,32 +80,26 @@
|
||||
</script>
|
||||
|
||||
{#if [...$groups.keys()]?.length}
|
||||
<Table noMargin noStyles>
|
||||
<TableHeader>
|
||||
<TableCellHead width={70}>Role</TableCellHead>
|
||||
<TableCellHead width={40} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each [...$groups.keys()].sort(sortRoles) as role}
|
||||
<TableRow>
|
||||
<TableCell title="Role">
|
||||
<Row {role} />
|
||||
</TableCell>
|
||||
<TableCellText title="Remove">
|
||||
<div class="u-flex u-main-end">
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
type="button"
|
||||
aria-label="delete"
|
||||
on:click={() => deleteRole(role)}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</TableCellText>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Cell>Role</Table.Header.Cell>
|
||||
<Table.Header.Cell width="40px" />
|
||||
</svelte:fragment>
|
||||
{#each [...$groups.keys()].sort(sortRoles) as role}
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Row {role} />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Layout.Stack justifyContent="flex-end">
|
||||
<Button icon on:click={() => deleteRole(role)}>
|
||||
<Icon icon={IconX} size="s" />
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
<Actions
|
||||
bind:showLabel
|
||||
bind:showCustom
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { tick } from 'svelte';
|
||||
import { AvatarInitials } from '../';
|
||||
import Output from '../output.svelte';
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Icon,
|
||||
Layout,
|
||||
Link,
|
||||
Popover,
|
||||
Spinner,
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import Avatar from '../avatar.svelte';
|
||||
import { IconAnonymous, IconExternalLink, IconMinusSm } from '@appwrite.io/pink-icons-svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
export let role: string;
|
||||
|
||||
let content: HTMLDivElement;
|
||||
let data = null;
|
||||
let isFetching = false;
|
||||
|
||||
async function getData(
|
||||
permission: string
|
||||
): Promise<
|
||||
@@ -20,139 +27,85 @@
|
||||
const role = permission.split(':')[0];
|
||||
const id = permission.split(':')[1].split('/')[0];
|
||||
if (role === 'user') {
|
||||
const user = await sdk.forProject.users.get(id);
|
||||
return user;
|
||||
return await sdk.forProject.users.get(id);
|
||||
}
|
||||
if (role === 'team') {
|
||||
const team = await sdk.forProject.teams.get(id);
|
||||
return team;
|
||||
return await sdk.forProject.teams.get(id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="u-flex u-cross-center u-gap-8 tippy-user">
|
||||
<div>
|
||||
{#if role === 'users'}
|
||||
<div>Users</div>
|
||||
{:else if role === 'guests'}
|
||||
<div>Guests</div>
|
||||
{:else if role === 'any'}
|
||||
<div>Any</div>
|
||||
{:else}
|
||||
<div
|
||||
class="u-trim-1"
|
||||
use:tooltip={{
|
||||
interactive: true,
|
||||
allowHTML: true,
|
||||
onShow(instance) {
|
||||
if (isFetching || data) {
|
||||
return;
|
||||
}
|
||||
|
||||
getData(role)
|
||||
.then((n) => {
|
||||
data = n;
|
||||
})
|
||||
.finally(() => {
|
||||
tick().then(() => {
|
||||
instance.setContent(content);
|
||||
});
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{role}
|
||||
</div>
|
||||
<div class="u-hide">
|
||||
<div bind:this={content}>
|
||||
{#if data}
|
||||
{@const isUser = role.startsWith('user')}
|
||||
{@const isTeam = role.startsWith('team')}
|
||||
{@const isAnonymous = !data.email && !data.phone && isUser}
|
||||
<div class="user-profile">
|
||||
{#if role === 'users'}
|
||||
<div>Users</div>
|
||||
{:else if role === 'guests'}
|
||||
<div>Guests</div>
|
||||
{:else if role === 'any'}
|
||||
<div>Any</div>
|
||||
{:else}
|
||||
<Popover let:toggle placement="bottom-start">
|
||||
<Link.Button on:click={toggle}>{role}</Link.Button>
|
||||
<div let:showing slot="tooltip" style:width="200px">
|
||||
{#key showing}
|
||||
{#await getData(role)}
|
||||
<Layout.Stack alignItems="center">
|
||||
<Spinner />
|
||||
</Layout.Stack>
|
||||
{:then data}
|
||||
{@const isUser = role.startsWith('user')}
|
||||
{@const isTeam = role.startsWith('team')}
|
||||
{@const isAnonymous = !data.email && !data.phone && !data.name && isUser}
|
||||
<Layout.Stack>
|
||||
<Layout.Stack direction="row" gap="s" alignItems="center">
|
||||
{#if isAnonymous}
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
<Avatar alt="avatar" size="xs">
|
||||
<Icon icon={IconAnonymous} size="s" />
|
||||
</Avatar>
|
||||
{:else if data.name}
|
||||
<AvatarInitials name={data.name} size="s" />
|
||||
<AvatarInitials name={data.name} size="xs" />
|
||||
{:else}
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-minus-sm" aria-hidden="true" />
|
||||
</div>
|
||||
<Avatar alt="avatar" size="xs">
|
||||
<Icon icon={IconMinusSm} size="s" />
|
||||
</Avatar>
|
||||
{/if}
|
||||
<span class="user-profile-info is-only-desktop">
|
||||
<span class="name">
|
||||
{data.name ?? data?.email ?? data?.phone ?? '-'}
|
||||
</span>
|
||||
<Output value={data.$id}>{role}</Output>
|
||||
</span>
|
||||
{#if (isUser && (data?.email || data?.phone)) || isTeam}
|
||||
<span class="user-profile-sep" />
|
||||
<Typography.Text truncate color="--fgcolor-neutral-primary">
|
||||
{data.name ?? data?.email ?? data?.phone ?? '-'}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
<span class="user-profile-empty-column" />
|
||||
<span class="user-profile-info is-only-desktop">
|
||||
{#if isUser}
|
||||
<div class="u-grid u-gap-4">
|
||||
{#if data?.email}
|
||||
<p class="text u-x-small">Email: {data?.email}</p>
|
||||
{/if}
|
||||
{#if data?.phone}
|
||||
<p class="text u-x-small">Phone: {data?.phone}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if isTeam}
|
||||
<p class="text u-x-small">Members: {data?.total}</p>
|
||||
{/if}
|
||||
</span>
|
||||
<Divider />
|
||||
{#if isUser}
|
||||
{#if data?.email}
|
||||
<Typography.Text truncate>Email: {data?.email}</Typography.Text>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
Not found.
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore css-unused-selector -->
|
||||
<style lang="scss" global>
|
||||
.tippy-user .tippy-box {
|
||||
--p-drop-bg-color: var(--color-neutral-105);
|
||||
--p-drop-border-color: var(--color-neutral-85);
|
||||
|
||||
inset-inline-start: -0.625rem;
|
||||
inset-block-end: calc(100% + 0.625rem);
|
||||
background-color: hsl(var(--p-drop-bg-color));
|
||||
border: solid 0.0625rem hsl(var(--p-drop-border-color));
|
||||
border-radius: var(--border-radius-small);
|
||||
box-shadow: var(--shadow-small);
|
||||
font-size: var(--font-size-0);
|
||||
color: hsl(var(--p-body-text-color));
|
||||
max-inline-size: 32.5rem;
|
||||
margin-inline: auto;
|
||||
line-height: 1.5;
|
||||
|
||||
body.theme-light & {
|
||||
--p-drop-bg-color: var(--color-neutral-0);
|
||||
--p-drop-border-color: var(--color-neutral-10);
|
||||
}
|
||||
|
||||
.tippy-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
&[data-placement^='top'] > .tippy-arrow::before {
|
||||
border-top-color: hsl(var(--p-drop-bg-color));
|
||||
}
|
||||
&[data-placement^='bottom'] > .tippy-arrow::before {
|
||||
border-bottom-color: hsl(var(--p-drop-bg-color));
|
||||
}
|
||||
&[data-placement^='left'] > .tippy-arrow::before {
|
||||
border-left-color: hsl(var(--p-drop-bg-color));
|
||||
}
|
||||
&[data-placement^='right'] > .tippy-arrow::before {
|
||||
border-right-color: hsl(var(--p-drop-bg-color));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{#if data?.phone}
|
||||
<Typography.Text truncate>Phone: {data?.phone}</Typography.Text>
|
||||
{/if}
|
||||
<div>
|
||||
<Button.Anchor
|
||||
href={`${base}/project-${$page.params.project}/auth/user-${data?.$id}`}
|
||||
size="xs"
|
||||
target="_blank"
|
||||
variant="secondary">
|
||||
View user
|
||||
<Icon slot="end" icon={IconExternalLink} size="s" />
|
||||
</Button.Anchor>
|
||||
</div>
|
||||
{:else if isTeam}
|
||||
<Typography.Text>Members: {data?.total}</Typography.Text>
|
||||
<div>
|
||||
<Button.Anchor
|
||||
href={`${base}/project-${$page.params.project}/auth/teams/team-${data?.$id}`}
|
||||
size="s"
|
||||
target="_blank"
|
||||
variant="secondary">
|
||||
View team
|
||||
<Icon slot="end" icon={IconExternalLink} size="s" />
|
||||
</Button.Anchor>
|
||||
</div>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/await}
|
||||
{/key}
|
||||
</div>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
@@ -93,23 +93,14 @@
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<AvatarInitials size="xs" name={team.name} />
|
||||
<Layout.Stack gap="none">
|
||||
<Typography.Caption variant="400">Text</Typography.Caption>
|
||||
<Typography.Caption variant="400">{team.name}</Typography.Caption>
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
color="--color-fgcolor-neutral-tertiary">
|
||||
Secondary Text
|
||||
color="--fgcolor-neutral-tertiary">
|
||||
{team.$id}
|
||||
</Typography.Caption>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<AvatarInitials size="xs" name={team.name} />
|
||||
<span>
|
||||
{team.name}
|
||||
</span>
|
||||
<span>
|
||||
{team.$id}
|
||||
</span>
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
</Table.Button>
|
||||
{/each}
|
||||
|
||||
@@ -17,13 +17,7 @@
|
||||
Table,
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
IconAnonymous,
|
||||
IconChartSquareBar,
|
||||
IconCheck,
|
||||
IconMinus,
|
||||
IconMinusSm
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import { IconAnonymous, IconMinusSm } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let show: boolean;
|
||||
export let groups: Writable<Map<string, Permission>>;
|
||||
@@ -105,7 +99,7 @@
|
||||
>{user.name}</Typography.Caption>
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
color="--color-fgcolor-neutral-tertiary">
|
||||
color="--fgcolor-neutral-tertiary">
|
||||
{user.$id}
|
||||
</Typography.Caption>
|
||||
</Layout.Stack>
|
||||
@@ -120,7 +114,7 @@
|
||||
: user.phone}</Typography.Caption>
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
color="--color-fgcolor-neutral-tertiary">
|
||||
color="--fgcolor-neutral-tertiary">
|
||||
{user.$id}
|
||||
</Typography.Caption>
|
||||
</Layout.Stack>
|
||||
@@ -134,7 +128,7 @@
|
||||
>{user.name ? user.name : '-'}</Typography.Caption>
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
color="--color-fgcolor-neutral-tertiary">
|
||||
color="--fgcolor-neutral-tertiary">
|
||||
{user.$id}
|
||||
</Typography.Caption>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -63,9 +63,8 @@
|
||||
{placeholder}
|
||||
{disabled}
|
||||
{required}
|
||||
type="search"
|
||||
bind:value={search}
|
||||
--color-bgcolor-neutral-default="var(--color-bgcolor-neutral-primary)">
|
||||
--bgcolor-neutral-default="var(--bgcolor-neutral-primary)">
|
||||
<svelte:fragment slot="start">
|
||||
<Icon icon={IconSearch} />
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -70,12 +70,12 @@
|
||||
$: isOnProjectSettings = /^\/console\/project-[a-zA-Z0-9-]+\/settings$/.test(pathname);
|
||||
|
||||
const projectOptions = [
|
||||
{ name: 'Auth', icon: IconUserGroup, slug: 'auth' },
|
||||
{ name: 'Databases', icon: IconDatabase, slug: 'databases' },
|
||||
{ name: 'Functions', icon: IconLightningBolt, slug: 'functions' },
|
||||
{ name: 'Messaging', icon: IconChatBubble, slug: 'messaging' },
|
||||
{ name: 'Sites', icon: IconGlobeAlt, slug: 'sites' },
|
||||
{ name: 'Storage', icon: IconFolder, slug: 'storage' }
|
||||
{ name: 'Auth', icon: IconUserGroup, slug: 'auth', category: 'build' },
|
||||
{ name: 'Databases', icon: IconDatabase, slug: 'databases', category: 'build' },
|
||||
{ name: 'Functions', icon: IconLightningBolt, slug: 'functions', category: 'build' },
|
||||
{ name: 'Messaging', icon: IconChatBubble, slug: 'messaging', category: 'build' },
|
||||
{ name: 'Storage', icon: IconFolder, slug: 'storage', category: 'build' },
|
||||
{ name: 'Sites', icon: IconGlobeAlt, slug: 'sites', category: 'deploy' }
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
state = 'closed';
|
||||
sideBarIsOpen = false;
|
||||
}}>
|
||||
<Icon icon={IconSearch} color="--color-fgcolor-neutral-tertiary" />
|
||||
<Icon icon={IconSearch} color="--fgcolor-neutral-tertiary" />
|
||||
</Button.Button>
|
||||
</div>
|
||||
<Link.Button
|
||||
@@ -121,11 +121,9 @@
|
||||
</div>
|
||||
{#if state !== 'icons'}
|
||||
<div class="info" in:fade={{ delay: 200, duration: 200 }}>
|
||||
<Typography.Text
|
||||
variant="m-500"
|
||||
color="--color-fgcolor-neutral-secondary"
|
||||
<Typography.Text variant="m-500" color="--fgcolor-neutral-secondary"
|
||||
>{progressCard.title}</Typography.Text>
|
||||
<Typography.Text color="--color-fgcolor-neutral-secondary"
|
||||
<Typography.Text color="--fgcolor-neutral-secondary"
|
||||
>{progressCard.percentage}% complete</Typography.Text>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -157,12 +155,45 @@
|
||||
<Divider />
|
||||
</div>
|
||||
<div class="products-label-container">
|
||||
<span class="products-label" class:hidden={state === 'icons'}
|
||||
>Products</span>
|
||||
<span class="products-label" class:hidden={state === 'icons'}>Build</span>
|
||||
<span class="products-label-indicator" class:hidden={state !== 'icons'}
|
||||
></span>
|
||||
</div>
|
||||
{#each projectOptions as projectOption}
|
||||
{@const buildProjectOptions = projectOptions.filter(
|
||||
(projectOption) => projectOption.category === 'build'
|
||||
)}
|
||||
{#each buildProjectOptions as projectOption}
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
href={`/console/project-${project.$id}/${projectOption.slug}`}
|
||||
class="link"
|
||||
class:active={pathname.includes(projectOption.slug)}
|
||||
on:click={() => {
|
||||
trackEvent(`click_menu_${projectOption.slug}`);
|
||||
sideBarIsOpen = false;
|
||||
}}
|
||||
><span class="link-icon"
|
||||
><Icon icon={projectOption.icon} size="s" />
|
||||
</span><span
|
||||
class:no-text={state === 'icons'}
|
||||
class:has-text={state === 'open'}
|
||||
class="link-text">{projectOption.name}</span
|
||||
></a>
|
||||
<span slot="tooltip">{projectOption.name}</span>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
<div class="only-mobile divider">
|
||||
<Divider />
|
||||
</div>
|
||||
<div class="products-label-container">
|
||||
<span class="products-label" class:hidden={state === 'icons'}>Deploy</span>
|
||||
<span class="products-label-indicator" class:hidden={state !== 'icons'}
|
||||
></span>
|
||||
</div>
|
||||
{@const deployProjectOptions = projectOptions.filter(
|
||||
(projectOption) => projectOption.category === 'deploy'
|
||||
)}
|
||||
{#each deployProjectOptions as projectOption}
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
href={`/console/project-${project.$id}/${projectOption.slug}`}
|
||||
@@ -325,47 +356,47 @@
|
||||
|
||||
.link-icon {
|
||||
height: 16px;
|
||||
color: var(--color-fgcolor-neutral-weak);
|
||||
color: var(--fgcolor-neutral-weak);
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-bgcolor-neutral-secondary, #f4f4f7);
|
||||
background: var(--bgcolor-neutral-secondary);
|
||||
|
||||
.link-icon {
|
||||
color: var(--color-fgcolor-neutral-tertiary);
|
||||
color: var(--fgcolor-neutral-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background: var(--color-bgcolor-neutral-secondary, #f4f4f7);
|
||||
background: var(--bgcolor-neutral-secondary);
|
||||
|
||||
/* box-shadow/state/focus */
|
||||
box-shadow:
|
||||
var(--shadow-offsetx-0, 0px) var(--shadow-offsety-0, 0px) 0 2px
|
||||
var(--color-bgcolor-neutral-default, #fafafb),
|
||||
0px 0px 0px 4px var(--color-border-focus, #818186);
|
||||
var(--bgcolor-neutral-default, #fafafb),
|
||||
0px 0px 0px 4px var(--border-focus, #818186);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--color-bgcolor-neutral-secondary, #f4f4f7);
|
||||
background: var(--bgcolor-neutral-secondary, #f4f4f7);
|
||||
|
||||
.link-text {
|
||||
color: var(--color-fgcolor-neutral-primary);
|
||||
color: var(--fgcolor-neutral-primary);
|
||||
}
|
||||
|
||||
.link-icon {
|
||||
color: var(--color-fgcolor-neutral-tertiary);
|
||||
color: var(--fgcolor-neutral-tertiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
.active {
|
||||
background: var(--color-bgcolor-neutral-secondary, #f4f4f7);
|
||||
background: var(--bgcolor-neutral-secondary, #f4f4f7);
|
||||
.link-text {
|
||||
color: var(--color-fgcolor-neutral-primary);
|
||||
color: var(--fgcolor-neutral-primary);
|
||||
}
|
||||
.link-icon {
|
||||
color: var(--color-fgcolor-neutral-tertiary);
|
||||
color: var(--fgcolor-neutral-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +415,7 @@
|
||||
transition-behavior: allow-discrete;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
color: var(--color-fgcolor-neutral-secondary, #56565c);
|
||||
color: var(--fgcolor-neutral-secondary, #56565c);
|
||||
filter: blur(0);
|
||||
|
||||
&.has-text {
|
||||
@@ -424,8 +455,8 @@
|
||||
align-items: center;
|
||||
margin-bottom: var(--gap-s, 8px);
|
||||
|
||||
border-bottom: var(--border-width-s, 1px) solid var(--color-border-neutral, #ededf0);
|
||||
background: var(--color-bgcolor-neutral-primary, #fff);
|
||||
border-bottom: var(--border-width-s, 1px) solid var(--border-neutral, #ededf0);
|
||||
background: var(--bgcolor-neutral-primary, #fff);
|
||||
}
|
||||
|
||||
.products-label-container {
|
||||
@@ -444,11 +475,11 @@
|
||||
|
||||
.products-label {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-fgcolor-neutral-tertiary);
|
||||
color: var(--fgcolor-neutral-tertiary);
|
||||
}
|
||||
|
||||
.products-label-indicator {
|
||||
border-bottom: 1px solid var(--color-border-neutral);
|
||||
border-bottom: 1px solid var(--border-neutral);
|
||||
height: 1px;
|
||||
width: 18px;
|
||||
align-self: center;
|
||||
@@ -493,8 +524,8 @@
|
||||
margin-block-end: var(--space-6, 12px);
|
||||
align-self: stretch;
|
||||
border-radius: var(--border-radius-s, 8px);
|
||||
border: 1px solid var(--color-border-neutral, #ededf0);
|
||||
background: var(--color-bgcolor-neutral-default, #fafafb);
|
||||
border: 1px solid var(--border-neutral, #ededf0);
|
||||
background: var(--bgcolor-neutral-default, #fafafb);
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
@@ -549,7 +580,7 @@
|
||||
height: calc(100vh - 48px);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
background-color: var(--color-bgcolor-neutral-primary, #fff);
|
||||
background-color: var(--bgcolor-neutral-primary, #fff);
|
||||
z-index: 14;
|
||||
position: fixed;
|
||||
top: 48px;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { app } from '$lib/stores/app';
|
||||
import { currentPlan } from '$lib/stores/organization';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let show = false;
|
||||
|
||||
@@ -61,21 +62,18 @@
|
||||
|
||||
<section class="drop-section support-section">
|
||||
{#if showHeader}
|
||||
<h4 class="heading-level-6">Support</h4>
|
||||
<Typography.Title size="s">Support</Typography.Title>
|
||||
{/if}
|
||||
|
||||
{#each supportOptions as option, index}
|
||||
{#if showCloudSupport(index)}
|
||||
<Card
|
||||
isTile
|
||||
class="support-option-card u-flex u-flex-vertical u-gap-16"
|
||||
style="border-radius: var(--border-radius-small, 8px); padding: 0.65rem;">
|
||||
class="support-option-card "
|
||||
style="border-radius: var(--border-radius-small, 8px); --gap-xl: 16px">
|
||||
<div class="u-flex u-flex-vertical u-gap-4">
|
||||
<h4 class="body-text-2 u-bold">{option.label}</h4>
|
||||
|
||||
<p class="u-line-height-1-5">
|
||||
{option.description}
|
||||
</p>
|
||||
<Typography.Text variant="m-500">{option.label}</Typography.Text>
|
||||
<Typography.Text variant="m-400">{option.description}</Typography.Text>
|
||||
</div>
|
||||
|
||||
{#if option.showSupport}
|
||||
@@ -177,12 +175,12 @@
|
||||
|
||||
:global(.theme-dark .support-option-card) {
|
||||
/* override required due to the card's background color */
|
||||
background: var(--color-bgcolor-neutral-default, #19191c) !important;
|
||||
background: var(--bgcolor-neutral-default, #19191c) !important;
|
||||
}
|
||||
|
||||
:global(.theme-light .support-option-card) {
|
||||
border: 1px solid var(--color-border-neutral, #ededf0);
|
||||
border: 1px solid var(--border-neutral, #ededf0);
|
||||
/* override required due to the card's background color */
|
||||
background: var(--color-bgcolor-neutral-default, #fafafb) !important;
|
||||
background: var(--bgcolor-neutral-default, #fafafb) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,5 +4,13 @@
|
||||
</script>
|
||||
|
||||
{#if $uploader?.isOpen}
|
||||
<Upload.Box files={$uploader.files} on:close={() => uploader.close()} />
|
||||
<Upload.Box
|
||||
files={$uploader.files.map((file) => {
|
||||
return {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
status: file.status
|
||||
};
|
||||
})}
|
||||
on:close={() => uploader.close()} />
|
||||
{/if}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
<Card radius="s" padding="xs" isTile>
|
||||
<Layout.Stack gap="xxxs">
|
||||
<Typography.Caption variant="400" color="--color-fgcolor-neutral-tertiary">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
{description}
|
||||
</Typography.Caption>
|
||||
<slot>
|
||||
{#if value !== null && value !== undefined}
|
||||
<Typography.Text size="s" truncate color="--color-fgcolor-neutral-primary"
|
||||
<Typography.Text size="s" truncate color="--fgcolor-neutral-primary"
|
||||
>{value}</Typography.Text>
|
||||
{:else}
|
||||
<Skeleton variant="line" width={100} height={19.5} />
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
export let hideView = false;
|
||||
export let hideColumns = false;
|
||||
export let allowNoColumns = false;
|
||||
export let fullWidthMobile = false;
|
||||
|
||||
onMount(async () => {
|
||||
if (isCustomCollection) {
|
||||
@@ -115,7 +114,7 @@
|
||||
|
||||
{#if !hideView}
|
||||
<ToggleButton
|
||||
--color-bgcolor-neutral-default="var(--color-bgcolor-neutral-primary)"
|
||||
--bgcolor-neutral-default="var(--bgcolor-neutral-primary)"
|
||||
on:change={onViewChange}
|
||||
active={view}
|
||||
buttons={[
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
export let disabled = false;
|
||||
export let tooltip: string = null;
|
||||
export let fullWidth = false;
|
||||
export let description = '';
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
@@ -36,6 +37,7 @@
|
||||
{id}
|
||||
{disabled}
|
||||
{required}
|
||||
description="aspdjasipdj"
|
||||
bind:checked={value}
|
||||
on:change
|
||||
on:invalid={handleInvalid} />
|
||||
@@ -43,6 +45,7 @@
|
||||
<Selector.Checkbox
|
||||
{id}
|
||||
{disabled}
|
||||
{description}
|
||||
size="s"
|
||||
{required}
|
||||
bind:checked={value}
|
||||
|
||||
@@ -6,11 +6,6 @@
|
||||
export let required = false;
|
||||
export let disabled = false;
|
||||
export let readonly = false;
|
||||
export let autofocus = false;
|
||||
export let fullWidth = false;
|
||||
export let autoSubmit = true;
|
||||
|
||||
$: console.log(value);
|
||||
</script>
|
||||
|
||||
<Input.OTP {length} bind:value {required} {disabled} {readonly} {autofocus} size="s" {fullWidth} />
|
||||
<Input.OTP {length} bind:value {required} {disabled} {readonly} size="s" />
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
{required}
|
||||
{autofocus}
|
||||
{label}
|
||||
type="search"
|
||||
bind:value
|
||||
on:input={valueChange}>
|
||||
<svelte:fragment slot="start">
|
||||
|
||||
@@ -144,15 +144,3 @@
|
||||
<Helper class="u-position-relative" type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</div> -->
|
||||
|
||||
<style>
|
||||
.form-item :global(.drop) {
|
||||
translate: 0 4px;
|
||||
}
|
||||
|
||||
.form-item :global(.drop-section) {
|
||||
width: 100%;
|
||||
margin-inline: initial;
|
||||
max-inline-size: initial;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import InputCheckbox from './forms/inputCheckbox.svelte';
|
||||
|
||||
export let value = false;
|
||||
export let padding: number | null = null;
|
||||
</script>
|
||||
|
||||
<ActionMenu.Item.Button on:click>
|
||||
|
||||
@@ -18,10 +18,14 @@ export enum View {
|
||||
Grid = 'grid'
|
||||
}
|
||||
|
||||
export function getView(url: URL, route: Page['route'], fallback: View): View {
|
||||
return (url.searchParams.get('view') ?? preferences.get(route).view) === View.Grid
|
||||
export function getView(url: URL, route: Page['route'], fallback: View, overwrite?: View): View {
|
||||
const view = url.searchParams.get('view') ?? preferences.get(route).view;
|
||||
|
||||
return view === View.Grid
|
||||
? View.Grid
|
||||
: (View.Table ?? fallback);
|
||||
: view === View.Table
|
||||
? View.Table
|
||||
: (overwrite ?? View.Table ?? fallback);
|
||||
}
|
||||
|
||||
export function getColumns(route: Page['route'], fallback: string[]): string[] {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
margin-inline: auto;
|
||||
max-width: min(944px, calc(944px - 5rem));
|
||||
max-width: calc(944px - 11rem);
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
margin-inline: auto;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Alert, DropList, Heading } from '$lib/components';
|
||||
import { Alert, DropList } from '$lib/components';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Link, Pill } from '$lib/elements';
|
||||
import {
|
||||
@@ -17,11 +17,9 @@
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { ContainerButton } from '.';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let title: string;
|
||||
export let titleTag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' = 'h2';
|
||||
export let titleSize: '1' | '2' | '3' | '4' | '5' | '6' | '7' = '5';
|
||||
export let serviceId = title.toLocaleLowerCase() as PlanServices;
|
||||
export let total: number = null;
|
||||
export let alertType: 'info' | 'success' | 'warning' | 'error' | 'default' = 'warning';
|
||||
@@ -117,7 +115,7 @@
|
||||
|
||||
<Layout.Stack direction="row" alignContent="center">
|
||||
<Layout.Stack direction="row">
|
||||
<Heading tag={titleTag} size={titleSize}>{title}</Heading>
|
||||
<Typography.Title size="m">{title}</Typography.Title>
|
||||
{#if isCloud && isLimited}
|
||||
<DropList bind:show={showDropdown} width="16">
|
||||
{#if hasProjectLimitation}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
container-type: inline-size;
|
||||
padding-block-start: var(--base-32);
|
||||
padding-block-end: var(--base-16);
|
||||
border-bottom: 1px solid var(--color-border-neutral, #2d2d31);
|
||||
background: var(--color-bgcolor-neutral-primary, #1d1d21);
|
||||
border-bottom: 1px solid var(--border-neutral, #2d2d31);
|
||||
background: var(--bgcolor-neutral-primary, #1d1d21);
|
||||
margin-left: -190px;
|
||||
padding-left: 190px;
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
margin-inline: auto;
|
||||
max-width: min(944px, calc(944px - 5rem));
|
||||
max-width: calc(944px - 11rem);
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
margin-inline: auto;
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
</Button.Anchor>
|
||||
</span>
|
||||
{/if}
|
||||
<Typography.Title
|
||||
truncate
|
||||
color="--color-fgcolor-neutral-primary"
|
||||
size={$isSmallViewport ? 'm' : 'l'}>
|
||||
<Typography.Title truncate color="--fgcolor-neutral-primary" size={$isSmallViewport ? 'm' : 'l'}>
|
||||
<slot />
|
||||
</Typography.Title>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Heading, ViewSelector } from '$lib/components';
|
||||
import { ViewSelector } from '$lib/components';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
import type { View } from '$lib/helpers/load';
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import type { Writable } from 'svelte/store';
|
||||
@@ -14,7 +15,7 @@
|
||||
</script>
|
||||
|
||||
<header class="grid-header">
|
||||
<Heading tag="h2" size="5">{title}</Heading>
|
||||
<Typography.Title size="m">{title}</Typography.Title>
|
||||
<div class="u-flex u-gap-16 u-contents-mobile">
|
||||
<ViewSelector
|
||||
{view}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { hoursToDays, timeFromNow, toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { log } from '$lib/stores/logs';
|
||||
import { Alert, Card, Code, Copy, Heading, Id, SvgIcon, Tab, Tabs } from '../components';
|
||||
import { Alert, Card, Code, Copy, Id, SvgIcon, Tab, Tabs } from '../components';
|
||||
import { calculateTime } from '$lib/helpers/timeConversion';
|
||||
import {
|
||||
TableBody,
|
||||
@@ -18,7 +18,7 @@
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let selectedRequest = 'parameters';
|
||||
let selectedResponse = 'logs';
|
||||
@@ -201,7 +201,7 @@
|
||||
<div class="code-panel-content grid-1-2">
|
||||
<div
|
||||
class="grid-1-2-col-1 u-flex u-flex-vertical u-gap-16 mobile-only-inline-20-padding">
|
||||
<Heading tag="h3" size="6">Request</Heading>
|
||||
<Typography.Title size="s">Request</Typography.Title>
|
||||
<div class="u-sep-block-end">
|
||||
<Tabs>
|
||||
<Tab
|
||||
@@ -314,7 +314,7 @@
|
||||
<div class="u-sep-block-end is-only-mobile u-padding-block-start-16" />
|
||||
<div
|
||||
class="grid-1-2-col-2 u-flex u-flex-vertical u-gap-16 u-min-width-0 mobile-only-inline-20-padding mobile-only-block-start-20-padding">
|
||||
<Heading tag="h3" size="6">Response</Heading>
|
||||
<Typography.Title size="s">Request</Typography.Title>
|
||||
<div class="u-sep-block-end">
|
||||
<Tabs>
|
||||
<Tab
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { Avatar, AvatarInitials, Card, Heading } from '$lib/components';
|
||||
import { Avatar, AvatarInitials, Card } from '$lib/components';
|
||||
import AppwriteLogoDark from '$lib/images/appwrite-logo-dark.svg';
|
||||
import AppwriteLogoLight from '$lib/images/appwrite-logo-light.svg';
|
||||
import LoginDark from '$lib/images/login/login-dark-mode.png';
|
||||
@@ -93,26 +93,7 @@
|
||||
alt="promo" />
|
||||
|
||||
<div class="u-text-center auth-container">
|
||||
<div class="is-only-mobile u-width-full-line">
|
||||
<Heading
|
||||
size="5"
|
||||
tag="h3"
|
||||
class="u-margin-block-start-16"
|
||||
trimmed={false}
|
||||
style="font-weight:normal">
|
||||
{generateTitle()}
|
||||
</Heading>
|
||||
</div>
|
||||
<div class="is-not-mobile u-width-full-line">
|
||||
<Heading
|
||||
size="4"
|
||||
tag="h3"
|
||||
class="u-margin-block-start-32"
|
||||
trimmed={false}
|
||||
style="font-weight:normal">
|
||||
{generateTitle()}
|
||||
</Heading>
|
||||
</div>
|
||||
<Typography.Title size="m">{generateTitle()}</Typography.Title>
|
||||
<p class="u-margin-block-start-16">
|
||||
{generateDesc()}
|
||||
</p>
|
||||
@@ -122,26 +103,7 @@
|
||||
<section
|
||||
class="u-flex u-flex-vertical u-main-center u-cross-center u-height-100-percent u-width-full-line review-container">
|
||||
<div class="u-text-center">
|
||||
<div class="is-only-mobile u-width-full-line">
|
||||
<Heading
|
||||
size="5"
|
||||
tag="h3"
|
||||
class="u-margin-block-start-48"
|
||||
trimmed={false}
|
||||
style="font-weight:normal">
|
||||
{generateTitle()}
|
||||
</Heading>
|
||||
</div>
|
||||
<div class="is-not-mobile u-width-full-line">
|
||||
<Heading
|
||||
size="3"
|
||||
tag="h3"
|
||||
class="u-margin-block-start-32"
|
||||
trimmed={false}
|
||||
style="font-weight:normal">
|
||||
{generateTitle()}
|
||||
</Heading>
|
||||
</div>
|
||||
<Typography.Title size="m">{generateTitle()}</Typography.Title>
|
||||
</div>
|
||||
|
||||
<p class="body-text-1 u-margin-block-start-16 u-text-center">{generateDesc()}</p>
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
<Layout.Stack gap="s">
|
||||
<div
|
||||
style:max-width="250px"
|
||||
style:--input-background-color="var(--color-bgcolor-neutral-primary)">
|
||||
style:--input-background-color="var(--bgcolor-neutral-primary)">
|
||||
<InputSelect
|
||||
on:change={(e) => goto(`${path}/${e.detail}`)}
|
||||
id="period"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Container } from '$lib/layout';
|
||||
import { BarChart, Legend, type LegendData } from '$lib/charts';
|
||||
import { accumulateFromEndingTotal } from '$lib/layout/usage.svelte';
|
||||
import { Card, Heading, SecondaryTabs, SecondaryTabsItem } from '$lib/components';
|
||||
import { Card, SecondaryTabs, SecondaryTabsItem } from '$lib/components';
|
||||
import { page } from '$app/stores';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
import { formatNumberWithCommas } from '$lib/helpers/numbers';
|
||||
@@ -20,7 +20,7 @@
|
||||
<Container overlapCover={overlapContainerCover}>
|
||||
{#if showHeader}
|
||||
<div class="u-flex u-main-space-between common-section">
|
||||
<Heading tag="h2" size="5">{title}</Heading>
|
||||
<Typography.Title>{title}</Typography.Title>
|
||||
|
||||
{#if path}
|
||||
<SecondaryTabs>
|
||||
|
||||
@@ -60,6 +60,6 @@
|
||||
height: 100%;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background: var(--color-bgcolor-neutral-primary);
|
||||
background: var(--bgcolor-neutral-primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { Heading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import WizardExitModal from './wizardExitModal.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
type $$Props =
|
||||
| {
|
||||
@@ -42,7 +42,8 @@
|
||||
<div class="wizard-secondary-container">
|
||||
<header class="wizard-secondary-header">
|
||||
<div class="u-flex u-main-space-between u-gap-32 u-cross-center">
|
||||
<Heading size={5} tag="h1"><slot name="title" /></Heading>
|
||||
<Typography.Title><slot name="title" /></Typography.Title>
|
||||
|
||||
<Button
|
||||
text
|
||||
icon
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Heading } from '$lib/components';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
|
||||
/**
|
||||
@@ -23,9 +22,7 @@
|
||||
|
||||
<Layout.Stack>
|
||||
<header class="form-header" class:hide-divider={!$$slots.subtitle}>
|
||||
<Heading tag="h1" size="6">
|
||||
<slot name="title" />
|
||||
</Heading>
|
||||
<Typography.Title><slot name="title" /></Typography.Title>
|
||||
{#if $$slots.subtitle}
|
||||
<p>
|
||||
<slot name="subtitle" />
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { page } from '$app/stores';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { derived } from 'svelte/store';
|
||||
|
||||
export const runtimesList = derived(
|
||||
page,
|
||||
async ($page) => (await $page.data.runtimesList) as Models.RuntimeList
|
||||
);
|
||||
|
||||
export const baseRuntimesList = derived(runtimesList, async ($runtimesList) => {
|
||||
const baseRuntimes = new Map<string, Models.Runtime>();
|
||||
for (const runtime of (await $runtimesList).runtimes) {
|
||||
const runtimeBase = runtime.name.split('-')[0];
|
||||
baseRuntimes.set(runtimeBase, runtime);
|
||||
}
|
||||
return { runtimes: [...baseRuntimes.values()] };
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import { page } from '$app/stores';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { derived } from 'svelte/store';
|
||||
|
||||
export const templatesList = derived(
|
||||
page,
|
||||
async ($page) => (await $page.data.templatesList) as Models.TemplateFunctionList
|
||||
);
|
||||
|
||||
export const featuredTemplatesList = derived(templatesList, async ($templatesList) => {
|
||||
return {
|
||||
templates: (await $templatesList).templates
|
||||
.filter((template) => template.id !== 'starter')
|
||||
.slice(0, 2)
|
||||
};
|
||||
});
|
||||
|
||||
export const starterTemplate = derived(templatesList, async ($templatesList) => {
|
||||
return (await $templatesList).templates.find((template) => template.id === 'starter');
|
||||
});
|
||||
@@ -8,6 +8,7 @@ type UploaderFile = {
|
||||
resourceId: string;
|
||||
name: string;
|
||||
progress: number;
|
||||
size: number;
|
||||
status: 'failed' | 'pending' | 'success';
|
||||
error?: string;
|
||||
};
|
||||
@@ -73,6 +74,7 @@ const createUploader = () => {
|
||||
$id: id,
|
||||
resourceId: bucketId,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
progress: 0,
|
||||
status: 'pending'
|
||||
};
|
||||
@@ -104,6 +106,7 @@ const createUploader = () => {
|
||||
$id: '',
|
||||
resourceId: siteId,
|
||||
name: code.name,
|
||||
size: code.size,
|
||||
progress: 0,
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
<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 { timeFromNow } from '$lib/helpers/date';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { repositories } from '$routes/(console)/project-[project]/functions/function-[function]/store';
|
||||
import { installation, installations, repository } from '../store';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { Icon, Layout } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
IconAzure,
|
||||
IconBitBucket,
|
||||
IconGithub,
|
||||
IconGitlab
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let callbackState: Record<string, string> = null;
|
||||
export let selectedRepository: string = null;
|
||||
export let hasInstallations = false;
|
||||
export let action: 'button' | 'select' = 'select';
|
||||
|
||||
$: {
|
||||
hasInstallations = $installations?.total > 0;
|
||||
}
|
||||
|
||||
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 = '';
|
||||
async function loadRepositories(installationId: string, search: string) {
|
||||
if (
|
||||
!$repositories ||
|
||||
$repositories.installationId !== installationId ||
|
||||
$repositories.search !== search
|
||||
) {
|
||||
$repositories.repositories = (
|
||||
await sdk.forProject.vcs.listRepositories(installationId, search || undefined)
|
||||
).providerRepositories;
|
||||
}
|
||||
|
||||
$repositories.search = search;
|
||||
$repositories.installationId = installationId;
|
||||
|
||||
if ($repositories.repositories.length) {
|
||||
selectedRepository = $repositories.repositories[0].id;
|
||||
$repository = $repositories.repositories[0];
|
||||
}
|
||||
|
||||
return $repositories.repositories.slice(0, 4);
|
||||
}
|
||||
|
||||
function connectGitHub() {
|
||||
const redirect = new URL($page.url);
|
||||
if (callbackState) {
|
||||
Object.keys(callbackState).forEach((key) => {
|
||||
redirect.searchParams.append(key, callbackState[key]);
|
||||
});
|
||||
}
|
||||
const target = new URL(`${sdk.forProject.client.config.endpoint}/vcs/github/authorize`);
|
||||
target.searchParams.set('project', $page.params.project);
|
||||
target.searchParams.set('success', redirect.toString());
|
||||
target.searchParams.set('failure', redirect.toString());
|
||||
target.searchParams.set('mode', 'admin');
|
||||
return target;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if hasInstallations}
|
||||
{#await loadInstallations()}
|
||||
<div class="u-flex u-gap-16">
|
||||
<ul class="u-width-full-line">
|
||||
<InputSelect
|
||||
disabled
|
||||
id="installation"
|
||||
label="Select installation"
|
||||
showLabel={false}
|
||||
options={[
|
||||
{
|
||||
label: 'Loading...',
|
||||
value: null
|
||||
}
|
||||
]}
|
||||
value={null} />
|
||||
</ul>
|
||||
<ul class="u-width-full-line">
|
||||
<InputSearch placeholder="Search repositories" disabled />
|
||||
</ul>
|
||||
</div>
|
||||
{:then installations}
|
||||
<Layout.Stack direction="row" alignItems="flex-end">
|
||||
<InputSelect
|
||||
id="installation"
|
||||
label="Select installation"
|
||||
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} />
|
||||
<InputSearch placeholder="Search repositories" bind:value={search} />
|
||||
</Layout.Stack>
|
||||
{/await}
|
||||
<p class="text u-margin-block-start-16">
|
||||
Manage organization configuration in your <a
|
||||
class="link"
|
||||
href={`${base}/project-${$page.params.project}/settings`}>project settings</a
|
||||
>.
|
||||
</p>
|
||||
{#if selectedInstallation}
|
||||
{#await loadRepositories(selectedInstallation, search)}
|
||||
<div class="u-flex u-gap-8 u-cross-center u-main-center" style:height="50vh">
|
||||
<div class="loader u-margin-32" />
|
||||
</div>
|
||||
{:then response}
|
||||
{#if response?.length}
|
||||
<ul class="table is-remove-outer-styles common-section">
|
||||
{#each response as repo, i}
|
||||
<li
|
||||
class="table-row"
|
||||
style:border-block-end={i === response.length - 1 ? 'none' : null}>
|
||||
<div class="table-col">
|
||||
<div
|
||||
class="u-flex u-cross-center u-gap-8"
|
||||
style="margin-block: .75rem;">
|
||||
{#if action === 'select'}
|
||||
<input
|
||||
class="is-small u-margin-inline-end-8"
|
||||
type="radio"
|
||||
name="repositories"
|
||||
bind:group={selectedRepository}
|
||||
on:change={() => repository.set(repo)}
|
||||
value={repo.id} />
|
||||
{/if}
|
||||
<div
|
||||
class="avatar is-size-x-small"
|
||||
style:--p-text-size="1.25rem"
|
||||
class:is-color-empty={!repo.runtime}>
|
||||
{#if repo.runtime}
|
||||
<img
|
||||
src={`${base}/icons/${$app.themeInUse}/color/${
|
||||
repo.runtime.split('-')[0]
|
||||
}.svg`}
|
||||
alt={repo.name} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
<span class="text u-trim-1">{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 u-trim-1"
|
||||
datetime={repo.pushedAt}>
|
||||
{timeFromNow(repo.pushedAt)}
|
||||
</time>
|
||||
</div>
|
||||
{#if action === 'button'}
|
||||
<div class="u-margin-inline-start-auto">
|
||||
<Button
|
||||
secondary
|
||||
on:click={() => dispatch('connect', repo)}>
|
||||
Connect
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</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 secondary on:click={() => (search = '')}>Clear search</Button>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{:else}
|
||||
<EmptySearch hidePages />
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="u-flex u-cross-center u-flex-vertical u-gap-16">
|
||||
<Button href={connectGitHub().toString()} fullWidth secondary>
|
||||
<Icon icon={IconGithub} slot="start" size="s" />
|
||||
GitHub
|
||||
</Button>
|
||||
<Button disabled fullWidth secondary>
|
||||
<Icon icon={IconGitlab} slot="start" size="s" />
|
||||
GitLab (coming soon)
|
||||
</Button>
|
||||
<Button disabled fullWidth secondary>
|
||||
<Icon icon={IconBitBucket} slot="start" size="s" />
|
||||
BitBucket (coming soon)
|
||||
</Button>
|
||||
<Button disabled fullWidth secondary>
|
||||
<Icon icon={IconAzure} slot="start" size="s" />
|
||||
Azure (coming soon)
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.icon-lock-closed {
|
||||
color: hsl(var(--color-neutral-50));
|
||||
}
|
||||
</style>
|
||||
@@ -1,24 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { upgradeURL } from '$lib/stores/billing';
|
||||
</script>
|
||||
|
||||
<div class="u-flex-vertical u-gap-16">
|
||||
<p>Additional CPU and RAM are available for Pro and Scale teams.</p>
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
<a
|
||||
class="u-bold u-block"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://appwrite.io/docs/advanced/platform/gb-hours">Learn more</a>
|
||||
<Button href={$upgradeURL} secondary fullWidthMobile event={'specifications_cta_upgrade'}>
|
||||
<span class="text">Upgrade Plan</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
a {
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -1,81 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { WizardWithSteps } from '$lib/layout';
|
||||
import SelectRepository from './steps/selectRepository.svelte';
|
||||
import GitConfiguration from './steps/gitConfiguration.svelte';
|
||||
import type { WizardStepsType } from '$lib/layout/wizardWithSteps.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 { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { Runtime } from '@appwrite.io/console';
|
||||
|
||||
async function createGitHubInstallation() {
|
||||
try {
|
||||
if (!isValueOfStringEnum(Runtime, $func.runtime)) {
|
||||
throw new Error(`Invalid runtime: ${$func.runtime}`);
|
||||
}
|
||||
await sdk.forProject.functions.update(
|
||||
$func.$id,
|
||||
$func.name,
|
||||
$func.runtime,
|
||||
$func.execute || undefined,
|
||||
$func.events || undefined,
|
||||
$func.schedule || undefined,
|
||||
$func.timeout || undefined,
|
||||
$func.enabled || undefined,
|
||||
$func.logging || undefined,
|
||||
$func.entrypoint || undefined,
|
||||
$func.commands || undefined,
|
||||
$func.scopes || undefined,
|
||||
$installation.$id,
|
||||
$repository.id,
|
||||
$choices.branch,
|
||||
$choices.silentMode || undefined,
|
||||
$choices.rootDir
|
||||
);
|
||||
trackEvent(Submit.FunctionConnectRepo, {
|
||||
customId: !!$func.$id
|
||||
});
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
resetState();
|
||||
wizard.hide();
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.FunctionConnectRepo);
|
||||
}
|
||||
}
|
||||
|
||||
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: 'Repository',
|
||||
component: SelectRepository
|
||||
});
|
||||
stepsComponents.set(2, {
|
||||
label: 'Git',
|
||||
component: GitConfiguration
|
||||
});
|
||||
</script>
|
||||
|
||||
<WizardWithSteps
|
||||
title="Connect Git"
|
||||
steps={stepsComponents}
|
||||
on:finish={createGitHubInstallation}
|
||||
on:exit={resetState} />
|
||||
@@ -1,317 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import CreateTemplate from './createTemplate.svelte';
|
||||
|
||||
export function connectTemplate(
|
||||
template: Models.TemplateFunction,
|
||||
runtime: string | null = null
|
||||
) {
|
||||
const variables: Record<string, string> = {};
|
||||
template.variables.forEach((variable) => {
|
||||
variables[variable.name] = variable.value ?? '';
|
||||
});
|
||||
|
||||
templateStore.set(template);
|
||||
templateConfig.set({
|
||||
$id: null,
|
||||
runtime,
|
||||
name: template.name,
|
||||
variables,
|
||||
repositoryBehaviour: 'new',
|
||||
repositoryName: template.id,
|
||||
repositoryPrivate: true,
|
||||
repositoryId: null,
|
||||
specification: null
|
||||
});
|
||||
wizard.start(CreateTemplate);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { AvatarGroup, Box, Heading } from '$lib/components';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { repository, templateConfig, template as templateStore } from './store';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { page } from '$app/stores';
|
||||
import { baseRuntimesList } from '$lib/stores/runtimes';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import WizardCover from '$lib/layout/wizardCover.svelte';
|
||||
import Repositories from './components/repositories.svelte';
|
||||
import CreateManual from './createManual.svelte';
|
||||
import CreateGit from './createGit.svelte';
|
||||
import { isSelfHosted } from '$lib/system';
|
||||
import { consoleVariables } from '$routes/(console)/store';
|
||||
import { featuredTemplatesList, starterTemplate } from '$lib/stores/templates';
|
||||
import { Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { IconDeno, IconDotnet } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
const isVcsEnabled = $consoleVariables?._APP_VCS_ENABLED === true;
|
||||
let hasInstallations: boolean;
|
||||
let selectedRepository: string;
|
||||
|
||||
function connect(event: CustomEvent<Models.ProviderRepository>) {
|
||||
trackEvent('click_connect_repository', {
|
||||
from: 'cover'
|
||||
});
|
||||
repository.set(event.detail);
|
||||
wizard.start(CreateGit);
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardCover>
|
||||
<svelte:fragment slot="title">Create function</svelte:fragment>
|
||||
<div class="wizard-container container">
|
||||
<div class="git-container u-position-relative">
|
||||
<div class="grid-1-1 u-gap-24">
|
||||
<div class="card u-cross-child-start u-height-100-percent">
|
||||
<Heading size="6" tag="h6">Connect Git repository</Heading>
|
||||
<p class="u-margin-block-start-8">
|
||||
Create and deploy a function with a connected git repository.
|
||||
</p>
|
||||
<div class="u-margin-block-start-24">
|
||||
<Repositories
|
||||
bind:hasInstallations
|
||||
bind:selectedRepository
|
||||
action="button"
|
||||
callbackState={{
|
||||
from: 'github',
|
||||
to: 'cover'
|
||||
}}
|
||||
on:connect={connect} />
|
||||
</div>
|
||||
{#if isSelfHosted && !isVcsEnabled}
|
||||
<div
|
||||
class="overlay u-flex-vertical u-position-absolute u-height-100-percent u-width-full-line u-z-index-1 u-text-center u-inset-0"
|
||||
style="border-radius: var(--border-radius-medium)">
|
||||
<div
|
||||
class="u-flex-vertical u-height-100-percent u-main-center u-cross-center u-gap-16 u-padding-inline-24">
|
||||
<Heading size="7" tag="h6" trimmed={false}>
|
||||
Connect your self-hosted instance to Git
|
||||
</Heading>
|
||||
<p>
|
||||
Configure your self-hosted instance to connect your function to
|
||||
a Git repository.
|
||||
<a
|
||||
href="https://appwrite.io/docs/advanced/self-hosting/functions#git"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">Learn more</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="card u-height-100-percent">
|
||||
<section class="common-section">
|
||||
<Heading size="6" tag="h6">Quick start</Heading>
|
||||
<p class="u-margin-block-start-8">Use a starter template.</p>
|
||||
<ul
|
||||
class="grid-box u-margin-block-start-16"
|
||||
style:--grid-item-size="8rem"
|
||||
style:--grid-item-size-small-screens="9rem"
|
||||
style:--grid-gap=".5rem">
|
||||
{#await Promise.all([$baseRuntimesList, $starterTemplate])}
|
||||
{#each Array(6) as _i}
|
||||
<li>
|
||||
<button
|
||||
disabled
|
||||
class="box u-width-full-line u-flex u-cross-center u-main-center"
|
||||
style:--box-padding="1rem"
|
||||
style:--box-border-radius="var(--border-radius-small)">
|
||||
<div class="avatar is-size-small">
|
||||
<div class="loader u-margin-16" />
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
{:then [response, quickStart]}
|
||||
{@const runtimes = new Map(
|
||||
response.runtimes.map((r) => [r.$id, r])
|
||||
)}
|
||||
{@const templates = quickStart.runtimes.filter((template) =>
|
||||
runtimes.has(template.name)
|
||||
)}
|
||||
{#each templates.slice(0, 6) as template}
|
||||
{@const runtimeDetail = runtimes.get(template.name)}
|
||||
<li>
|
||||
<button
|
||||
on:click={() => {
|
||||
trackEvent('click_connect_template', {
|
||||
from: 'cover',
|
||||
template: quickStart.id,
|
||||
runtime: template.name
|
||||
});
|
||||
}}
|
||||
on:click={() =>
|
||||
connectTemplate(quickStart, template.name)}
|
||||
class="box u-width-full-line u-flex u-cross-center u-gap-8"
|
||||
style:--box-padding="1rem"
|
||||
style:--box-border-radius="var(--border-radius-small)">
|
||||
<div class="avatar is-size-small">
|
||||
<img
|
||||
style:--p-text-size="1.25rem"
|
||||
src={`${base}/icons/${$app.themeInUse}/color/${
|
||||
template.name.split('-')[0]
|
||||
}.svg`}
|
||||
alt={template.name} />
|
||||
</div>
|
||||
<div class="body-text-2">
|
||||
{runtimeDetail.name}
|
||||
{#if runtimeDetail.name.toLowerCase() === 'deno'}
|
||||
<span class="inline-tag">New</span>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
|
||||
{#if templates.length < 6}
|
||||
<Tooltip>
|
||||
<li>
|
||||
<Box
|
||||
class="u-width-full-line u-flex u-cross-center u-gap-8"
|
||||
padding={16}
|
||||
radius="small">
|
||||
<AvatarGroup
|
||||
icons={[IconDotnet, IconDeno]}
|
||||
total={4}
|
||||
size="s" />
|
||||
</Box>
|
||||
</li>
|
||||
<span slot="tooltip">More runtimes coming soon</span>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
{/await}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<Button
|
||||
text
|
||||
class="u-margin-block-start-24 u-margin-inline-start-auto"
|
||||
href={`${base}/project-${$page.params.project}/functions/templates?useCase=starter`}>
|
||||
All starter templates <span class="icon-cheveron-right" />
|
||||
</Button>
|
||||
<div class="u-sep-block-start common-section" />
|
||||
<section class="common-section">
|
||||
<Heading size="6" tag="h6">Templates</Heading>
|
||||
<p class="text u-margin-block-start-8">
|
||||
Find the right template for your use case.
|
||||
</p>
|
||||
|
||||
<ul class="clickable-list u-margin-block-start-16">
|
||||
{#await $featuredTemplatesList}
|
||||
{#each Array(3) as _i}
|
||||
<li>
|
||||
<button
|
||||
disabled
|
||||
class="clickable-list-button u-width-full-line u-flex u-gap-12">
|
||||
<div class="avatar is-size-small">
|
||||
<div class="loader" />
|
||||
</div>
|
||||
<div class="u-flex u-flex-vertical u-gap-4">
|
||||
<div class="body-text-2 u-bold u-trim">
|
||||
<div class="loader" />
|
||||
</div>
|
||||
<div class="u-trim-1 u-color-text-gray">
|
||||
<div class="loader" />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
{:then templatesListWithoutStarter}
|
||||
{#each templatesListWithoutStarter.templates as template}
|
||||
<li class="clickable-list-item u-padding-block-8">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
trackEvent('click_connect_template', {
|
||||
from: 'cover',
|
||||
template: template.id
|
||||
});
|
||||
}}
|
||||
on:click={() => connectTemplate(template)}
|
||||
class="clickable-list-button u-width-full-line u-flex u-gap-12">
|
||||
<div
|
||||
class="avatar is-size-small"
|
||||
style:--p-text-size="1.25rem">
|
||||
<span class={template.icon} />
|
||||
</div>
|
||||
<div class="u-flex u-flex-vertical u-gap-4">
|
||||
<div class="body-text-2 u-bold u-trim">
|
||||
{template.name}
|
||||
</div>
|
||||
<div class="u-trim-1 u-color-text-gray">
|
||||
{template.tagline}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
{/await}
|
||||
</ul>
|
||||
</section>
|
||||
<Button
|
||||
text
|
||||
class="u-margin-inline-start-auto u-margin-block-start-16"
|
||||
href={`${base}/project-${$page.params.project}/functions/templates`}>
|
||||
<span> All templates </span>
|
||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="u-margin-block-start-16">
|
||||
You can also create a function <button
|
||||
class="link"
|
||||
on:click={() => {
|
||||
trackEvent('click_create_function_manual', {
|
||||
from: 'cover'
|
||||
});
|
||||
}}
|
||||
on:click={() => wizard.start(CreateManual)}>manually</button>
|
||||
or using the CLI.
|
||||
<a
|
||||
href="https://appwrite.io/docs/products/functions/deployment"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">Learn more</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</WizardCover>
|
||||
|
||||
<style lang="scss">
|
||||
.git-container .overlay {
|
||||
background: linear-gradient(
|
||||
0,
|
||||
hsl(var(--p-card-bg-color)) 68.91%,
|
||||
hsl(var(--p-card-bg-color) / 0.5) 92.8%
|
||||
);
|
||||
}
|
||||
|
||||
.u-sep-block-start {
|
||||
border-block-start: solid 0.0625rem hsl(var(--color-neutral)) !important;
|
||||
}
|
||||
|
||||
.clickable-list-item:not(:last-child) {
|
||||
border-block-end: solid 0.0625rem hsl(var(--color-neutral)) !important;
|
||||
}
|
||||
|
||||
:global(.theme-light) {
|
||||
--color-neutral: var(--color-neutral-10);
|
||||
}
|
||||
|
||||
:global(.theme-dark) {
|
||||
--color-neutral: var(--color-neutral-85);
|
||||
}
|
||||
|
||||
.inline-tag {
|
||||
line-height: 140%;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -1,91 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ID, Runtime } from '@appwrite.io/console';
|
||||
import { WizardWithSteps } from '$lib/layout';
|
||||
import type { WizardStepsType } from '$lib/layout/wizardWithSteps.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { goto } from '$app/navigation';
|
||||
import { choices, createFunction, installation, repository } from './store';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import ExecuteAccess from './steps/executeAccess.svelte';
|
||||
import GitConfiguration from './steps/gitConfiguration.svelte';
|
||||
import FunctionConfiguration from './steps/functionConfiguration.svelte';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
if (!isValueOfStringEnum(Runtime, $createFunction.runtime)) {
|
||||
throw new Error(`Invalid runtime: ${$createFunction.runtime}`);
|
||||
}
|
||||
const response = await sdk.forProject.functions.create(
|
||||
$createFunction.$id || ID.unique(),
|
||||
$createFunction.name,
|
||||
$createFunction.runtime,
|
||||
$createFunction.execute || undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
$createFunction.entrypoint,
|
||||
$createFunction.commands || undefined,
|
||||
undefined,
|
||||
$installation.$id,
|
||||
$repository.id,
|
||||
$choices.branch,
|
||||
$choices.silentMode || undefined,
|
||||
$choices.rootDir || undefined
|
||||
);
|
||||
goto(`${base}/project-${$page.params.project}/functions/function-${response.$id}`);
|
||||
addNotification({
|
||||
message: `${$createFunction.name} has been created`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.FunctionCreate, {
|
||||
customId: !!$createFunction.$id
|
||||
});
|
||||
resetState();
|
||||
wizard.hide();
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.FunctionCreate);
|
||||
}
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
createFunction.set({
|
||||
$id: null,
|
||||
name: null,
|
||||
entrypoint: null,
|
||||
execute: [],
|
||||
runtime: null
|
||||
});
|
||||
}
|
||||
|
||||
const stepsComponents: WizardStepsType = new Map();
|
||||
stepsComponents.set(1, {
|
||||
label: 'Git',
|
||||
component: GitConfiguration
|
||||
});
|
||||
stepsComponents.set(2, {
|
||||
label: 'Configuration',
|
||||
component: FunctionConfiguration
|
||||
});
|
||||
stepsComponents.set(3, {
|
||||
label: 'Execute access',
|
||||
component: ExecuteAccess,
|
||||
optional: true
|
||||
});
|
||||
</script>
|
||||
|
||||
<WizardWithSteps
|
||||
title="Create Function"
|
||||
steps={stepsComponents}
|
||||
on:finish={create}
|
||||
on:exit={resetState} />
|
||||
@@ -1,109 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ID, Runtime } from '@appwrite.io/console';
|
||||
import { WizardWithSteps } from '$lib/layout';
|
||||
import type { WizardStepsType } from '$lib/layout/wizardWithSteps.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { goto } from '$app/navigation';
|
||||
import {
|
||||
choices,
|
||||
createFunction,
|
||||
createFunctionDeployment,
|
||||
installation,
|
||||
repository
|
||||
} from './store';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import Details from './steps/manualDetails.svelte';
|
||||
import Configuration from './steps/manualConfiguration.svelte';
|
||||
import ExecuteAccess from './steps/executeAccess.svelte';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
if (!isValueOfStringEnum(Runtime, $createFunction.runtime)) {
|
||||
throw new Error(`Invalid runtime: ${$createFunction.runtime}`);
|
||||
}
|
||||
const response = await sdk.forProject.functions.create(
|
||||
$createFunction.$id || ID.unique(),
|
||||
$createFunction.name,
|
||||
$createFunction.runtime,
|
||||
$createFunction.execute || undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
$createFunction.entrypoint,
|
||||
$createFunction.commands || undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
$createFunction.specification
|
||||
);
|
||||
await sdk.forProject.functions.createDeployment(
|
||||
response.$id,
|
||||
$createFunctionDeployment[0],
|
||||
true
|
||||
);
|
||||
goto(`${base}/project-${$page.params.project}/functions/function-${response.$id}`);
|
||||
addNotification({
|
||||
message: `${$createFunction.name} has been created`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.FunctionCreate, {
|
||||
customId: !!$createFunction.$id
|
||||
});
|
||||
resetState();
|
||||
wizard.hide();
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.FunctionCreate);
|
||||
}
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
createFunction.reset();
|
||||
choices.set({
|
||||
branch: null,
|
||||
silentMode: false,
|
||||
rootDir: null
|
||||
});
|
||||
installation.set(null);
|
||||
repository.set(null);
|
||||
createFunctionDeployment.set(null);
|
||||
}
|
||||
|
||||
const stepsComponents: WizardStepsType = new Map();
|
||||
stepsComponents.set(1, {
|
||||
label: 'Details',
|
||||
component: Details
|
||||
});
|
||||
stepsComponents.set(2, {
|
||||
label: 'Configuration',
|
||||
component: Configuration
|
||||
});
|
||||
stepsComponents.set(3, {
|
||||
label: 'Execute access',
|
||||
component: ExecuteAccess,
|
||||
optional: true
|
||||
});
|
||||
</script>
|
||||
|
||||
<WizardWithSteps
|
||||
title="Create Function"
|
||||
steps={stepsComponents}
|
||||
on:finish={create}
|
||||
on:exit={resetState} />
|
||||
@@ -1,128 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ID, Runtime } from '@appwrite.io/console';
|
||||
import { WizardWithSteps } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { goto } from '$app/navigation';
|
||||
import {
|
||||
choices,
|
||||
installation,
|
||||
repository,
|
||||
template,
|
||||
templateConfig,
|
||||
templateStepsComponents
|
||||
} from './store';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import TemplateConfiguration from './steps/templateConfiguration.svelte';
|
||||
import TemplatePermissions from './steps/templatePermissions.svelte';
|
||||
import TemplateVariables from './steps/templateVariables.svelte';
|
||||
import TemplateDeployment from './steps/templateDeployment.svelte';
|
||||
import CreateRepository from './steps/createRepository.svelte';
|
||||
import GitConfiguration from './steps/gitConfiguration.svelte';
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
if (!isValueOfStringEnum(Runtime, $templateConfig.runtime)) {
|
||||
throw new Error(`Invalid runtime: ${$templateConfig.runtime}`);
|
||||
}
|
||||
const runtimeDetail = $template.runtimes.find(
|
||||
(r) => r.name === $templateConfig.runtime
|
||||
);
|
||||
|
||||
const response = await sdk.forProject.functions.create(
|
||||
$templateConfig.$id || ID.unique(),
|
||||
$templateConfig.name,
|
||||
$templateConfig.runtime,
|
||||
$templateConfig?.execute ? $template.permissions || undefined : undefined,
|
||||
$template.events || undefined,
|
||||
$template.cron || undefined,
|
||||
$template.timeout || undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
runtimeDetail.entrypoint,
|
||||
runtimeDetail.commands || undefined,
|
||||
$templateConfig?.scopes?.length ? $templateConfig.scopes : undefined,
|
||||
$templateConfig.repositoryBehaviour === 'manual' ? undefined : $installation.$id,
|
||||
$templateConfig.repositoryBehaviour === 'manual' ? undefined : $repository.id,
|
||||
$templateConfig.repositoryBehaviour === 'manual' ? undefined : $choices.branch,
|
||||
$templateConfig.repositoryBehaviour === 'manual'
|
||||
? undefined
|
||||
: $choices.silentMode || undefined,
|
||||
$templateConfig.repositoryBehaviour === 'manual'
|
||||
? undefined
|
||||
: $choices.rootDir || undefined,
|
||||
$templateConfig.specification || undefined
|
||||
);
|
||||
|
||||
if ($templateConfig.variables) {
|
||||
const promises = Object.entries($templateConfig.variables).map(([key, value]) =>
|
||||
sdk.forProject.functions.createVariable(response.$id, key, value?.toString())
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
goto(`${base}/project-${$page.params.project}/functions/function-${response.$id}`);
|
||||
addNotification({
|
||||
message: `${response.name} has been created`,
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.FunctionCreate, {
|
||||
customId: !!response.$id,
|
||||
runtime: response.runtime,
|
||||
deployment_type: $templateConfig.repositoryBehaviour,
|
||||
scopes: $templateConfig.scopes,
|
||||
specification: $templateConfig.specification
|
||||
});
|
||||
resetState();
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.FunctionCreate);
|
||||
}
|
||||
}
|
||||
function resetState() {
|
||||
wizard.hide();
|
||||
templateConfig.set(null);
|
||||
template.set(null);
|
||||
installation.set(null);
|
||||
}
|
||||
|
||||
$templateStepsComponents.set(1, {
|
||||
label: 'Configuration',
|
||||
component: TemplateConfiguration
|
||||
});
|
||||
$templateStepsComponents.set(2, {
|
||||
label: 'Permissions',
|
||||
component: TemplatePermissions
|
||||
});
|
||||
$templateStepsComponents.set(3, {
|
||||
label: 'Variables',
|
||||
component: TemplateVariables,
|
||||
disabled: !$template?.variables?.length
|
||||
});
|
||||
$templateStepsComponents.set(4, {
|
||||
label: 'Deployment',
|
||||
component: TemplateDeployment
|
||||
});
|
||||
$templateStepsComponents.set(5, {
|
||||
label: 'Repository',
|
||||
component: CreateRepository
|
||||
});
|
||||
$templateStepsComponents.set(6, {
|
||||
label: 'Branch',
|
||||
component: GitConfiguration
|
||||
});
|
||||
</script>
|
||||
|
||||
<WizardWithSteps
|
||||
title="Create Function"
|
||||
steps={$templateStepsComponents}
|
||||
on:finish={create}
|
||||
on:exit={resetState} />
|
||||
@@ -1,179 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import InputChoice from '$lib/elements/forms/inputChoice.svelte';
|
||||
import InputSelect from '$lib/elements/forms/inputSelect.svelte';
|
||||
import InputText from '$lib/elements/forms/inputText.svelte';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import Repositories from '../components/repositories.svelte';
|
||||
import { installation, repository, template, templateConfig } from '../store';
|
||||
import { Box } from '$lib/components';
|
||||
import {
|
||||
IconAzure,
|
||||
IconBitBucket,
|
||||
IconGithub,
|
||||
IconGitlab
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let selectedInstallationId: string;
|
||||
let hasInstallations: boolean;
|
||||
let selectedRepository: string;
|
||||
|
||||
onMount(() => {
|
||||
$templateConfig.repositoryPrivate = true;
|
||||
});
|
||||
|
||||
async function beforeSubmit() {
|
||||
if (!hasInstallations || !$installation) {
|
||||
throw new Error('Please connect a Git provider');
|
||||
}
|
||||
|
||||
if ($templateConfig.repositoryBehaviour === 'new') {
|
||||
const repo = await sdk.forProject.vcs.createRepository(
|
||||
$installation.$id,
|
||||
$templateConfig.repositoryName,
|
||||
$templateConfig.repositoryPrivate
|
||||
);
|
||||
$repository = repo;
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Repository successfully created.'
|
||||
});
|
||||
}
|
||||
|
||||
if (!$repository) {
|
||||
throw new Error('Please select a repository');
|
||||
}
|
||||
}
|
||||
|
||||
function connectGitHub() {
|
||||
const redirect = new URL($page.url);
|
||||
const callbackState = {
|
||||
from: 'github',
|
||||
to: 'template',
|
||||
step: '5',
|
||||
template: $template.id,
|
||||
templateConfig: JSON.stringify($templateConfig)
|
||||
};
|
||||
|
||||
if (callbackState) {
|
||||
Object.keys(callbackState).forEach((key) => {
|
||||
redirect.searchParams.append(key, callbackState[key]);
|
||||
});
|
||||
}
|
||||
const target = new URL(`${sdk.forProject.client.config.endpoint}/vcs/github/authorize`);
|
||||
target.searchParams.set('project', $page.params.project);
|
||||
target.searchParams.set('success', redirect.toString());
|
||||
target.searchParams.set('failure', redirect.toString());
|
||||
target.searchParams.set('mode', 'admin');
|
||||
return target;
|
||||
}
|
||||
|
||||
async function loadInstallations() {
|
||||
const { installations } = await sdk.forProject.vcs.listInstallations();
|
||||
if (installations.length) {
|
||||
$installation = installations[0];
|
||||
hasInstallations = true;
|
||||
}
|
||||
|
||||
return installations;
|
||||
}
|
||||
|
||||
function getProviderIcon(provider: string) {
|
||||
switch (provider) {
|
||||
case 'github':
|
||||
return 'icon-github';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Repository</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Select a Git repository that will trigger your function deployments when updated.
|
||||
</svelte:fragment>
|
||||
|
||||
{#if $templateConfig.repositoryBehaviour === 'existing'}
|
||||
<Repositories bind:hasInstallations bind:selectedRepository />
|
||||
{:else}
|
||||
{#await loadInstallations()}
|
||||
<div class="u-flex u-gap-8 u-cross-center u-main-center">
|
||||
<div class="loader u-margin-32" />
|
||||
</div>
|
||||
{:then installations}
|
||||
{#if hasInstallations}
|
||||
<FormList>
|
||||
<InputSelect
|
||||
id="installation"
|
||||
label="Git organization"
|
||||
options={installations.map((entry) => {
|
||||
return {
|
||||
label: entry.organization,
|
||||
value: entry.$id
|
||||
};
|
||||
})}
|
||||
on:change={() => {
|
||||
$installation = installations.find(
|
||||
(entry) => entry.$id === selectedInstallationId
|
||||
);
|
||||
}}
|
||||
bind:value={selectedInstallationId} />
|
||||
</FormList>
|
||||
{:else}
|
||||
<div class="u-flex u-cross-center u-flex-vertical u-gap-16">
|
||||
<Button href={connectGitHub().toString()} fullWidth secondary>
|
||||
<Icon icon={IconGithub} slot="start" size="s" />
|
||||
GitHub
|
||||
</Button>
|
||||
<Button disabled fullWidth secondary>
|
||||
<Icon icon={IconGitlab} slot="start" size="s" />
|
||||
GitLab (coming soon)
|
||||
</Button>
|
||||
<Button disabled fullWidth secondary>
|
||||
<Icon icon={IconBitBucket} slot="start" size="s" />
|
||||
BitBucket (coming soon)
|
||||
</Button>
|
||||
<Button disabled fullWidth secondary>
|
||||
<Icon icon={IconAzure} slot="start" size="s" />
|
||||
Azure (coming soon)
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $installation}
|
||||
<Box radius="small" class="u-margin-block-start-20">
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="avatar is-size-x-small">
|
||||
<span class={getProviderIcon($installation.provider)} />
|
||||
</div>
|
||||
<div class="u-cross-child-center u-line-height-1-5">
|
||||
<h6 class="u-bold u-trim-1">
|
||||
{$installation.organization}/{$templateConfig.repositoryName}
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-margin-block-start-24">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="repositoryName"
|
||||
label="Repository name"
|
||||
placeholder="my-repository"
|
||||
bind:value={$templateConfig.repositoryName} />
|
||||
<InputChoice
|
||||
id="repositoryPrivate"
|
||||
label="Keep repository private"
|
||||
bind:value={$templateConfig.repositoryPrivate} />
|
||||
</FormList>
|
||||
</div>
|
||||
</Box>
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
</WizardStep>
|
||||
@@ -1,21 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { Roles } from '$lib/components/permissions';
|
||||
import { createFunction } from '../store';
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Permissions</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Choose who can execute this function using the client API. For more information, visit our
|
||||
<a
|
||||
href="https://appwrite.io/docs/advanced/platform/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
Permissions guide</a
|
||||
>.
|
||||
</svelte:fragment>
|
||||
|
||||
<Roles bind:roles={$createFunction.execute} />
|
||||
</WizardStep>
|
||||
@@ -1,120 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible, CollapsibleItem, CustomId } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { FormList, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import InputTextarea from '$lib/elements/forms/inputTextarea.svelte';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { onMount } from 'svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { choices, createFunction, installation, repository } from '../store';
|
||||
import { runtimesList } from '$lib/stores/runtimes';
|
||||
|
||||
let showCustomId = false;
|
||||
|
||||
let detectingRuntime = true;
|
||||
async function loadDetection(
|
||||
installationId: string,
|
||||
repositoryId: string,
|
||||
rootDirectory: string
|
||||
) {
|
||||
const detection = await sdk.forProject.vcs.createRepositoryDetection(
|
||||
installationId,
|
||||
repositoryId,
|
||||
rootDirectory
|
||||
);
|
||||
|
||||
return detection;
|
||||
}
|
||||
|
||||
let options = [];
|
||||
|
||||
onMount(async () => {
|
||||
options = (await $runtimesList).runtimes.map((runtime) => ({
|
||||
label: `${runtime.name} - ${runtime.version}`,
|
||||
value: runtime.$id
|
||||
}));
|
||||
|
||||
try {
|
||||
const detection = await loadDetection(
|
||||
$installation.$id,
|
||||
$repository.id,
|
||||
$choices.rootDir
|
||||
);
|
||||
$createFunction.runtime = detection.runtime;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
detectingRuntime = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Configuration</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Set your deployment configuration and any build commands here.
|
||||
</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
label="Name"
|
||||
id="name"
|
||||
placeholder="Function name"
|
||||
autofocus
|
||||
bind:value={$createFunction.name}
|
||||
required />
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}>
|
||||
<span class="icon-pencil" aria-hidden="true" />
|
||||
<span class="text">Function ID </span>
|
||||
</Pill>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomId
|
||||
autofocus
|
||||
bind:show={showCustomId}
|
||||
name="Function"
|
||||
bind:id={$createFunction.$id}
|
||||
fullWidth />
|
||||
{/if}
|
||||
|
||||
{#if detectingRuntime}
|
||||
<InputSelect
|
||||
disabled
|
||||
label="Runtime"
|
||||
id="runtime"
|
||||
placeholder="Detecting runtime"
|
||||
value={null}
|
||||
options={[]} />
|
||||
{:else}
|
||||
<InputSelect
|
||||
label="Runtime"
|
||||
id="runtime"
|
||||
placeholder="Select runtime"
|
||||
bind:value={$createFunction.runtime}
|
||||
{options}
|
||||
required />
|
||||
{/if}
|
||||
|
||||
<InputText
|
||||
label="Entrypoint"
|
||||
id="entrypoint"
|
||||
placeholder="Entrypoint"
|
||||
bind:value={$createFunction.entrypoint}
|
||||
required />
|
||||
<Collapsible>
|
||||
<CollapsibleItem>
|
||||
<svelte:fragment slot="title">Build commands</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">(optional)</svelte:fragment>
|
||||
<FormList>
|
||||
<InputTextarea
|
||||
label="Commands"
|
||||
placeholder="Enter a build command (e.g. 'npm install')"
|
||||
id="build"
|
||||
tooltip="Enter a single command or chain multiple commands with the && operator"
|
||||
bind:value={$createFunction.commands} />
|
||||
</FormList>
|
||||
</CollapsibleItem>
|
||||
</Collapsible>
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
@@ -1,94 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Box, SvgIcon } from '$lib/components';
|
||||
import { FormList, InputChoice, InputText } from '$lib/elements/forms';
|
||||
import InputSelectSearch from '$lib/elements/forms/inputSelectSearch.svelte';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { sortBranches } from '$lib/stores/vcs';
|
||||
import { choices, installation, repository } from '../store';
|
||||
|
||||
$choices.rootDir ??= '';
|
||||
|
||||
async function loadBranches() {
|
||||
const { branches } = await sdk.forProject.vcs.listRepositoryBranches(
|
||||
$installation.$id,
|
||||
$repository.id
|
||||
);
|
||||
const sorted = sortBranches(branches);
|
||||
$choices.branch = sorted[0]?.name ?? null;
|
||||
|
||||
if (!$choices.branch) {
|
||||
$choices.branch = 'main';
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Branch</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Choose the Git branch that will trigger your function deployments when updated.
|
||||
</svelte:fragment>
|
||||
|
||||
<Box radius="small">
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
<SvgIcon name={$repository.provider} iconSize="large" />
|
||||
<h6 class="u-bold u-trim-1">{$installation.organization}/{$repository.name}</h6>
|
||||
</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}
|
||||
{@const options =
|
||||
branches
|
||||
?.map((branch) => {
|
||||
return {
|
||||
value: branch.name,
|
||||
label: branch.name
|
||||
};
|
||||
})
|
||||
?.sort((a, b) => {
|
||||
return a.label > b.label ? 1 : -1;
|
||||
}) ?? []}
|
||||
<div class="u-margin-block-start-24">
|
||||
<FormList>
|
||||
<InputSelectSearch
|
||||
required={true}
|
||||
id="branch"
|
||||
label="Production branch"
|
||||
placeholder="Select branch"
|
||||
tooltip="Every commit pushed to this branch will activate the deployment after a successful build"
|
||||
hideRequired
|
||||
bind:value={$choices.branch}
|
||||
bind:search={$choices.branch}
|
||||
on:select={(event) => {
|
||||
$choices.branch = event.detail.value;
|
||||
}}
|
||||
interactiveOutput
|
||||
name="branches"
|
||||
{options} />
|
||||
<InputText
|
||||
id="root"
|
||||
label="Root directory"
|
||||
placeholder="functions/my-function"
|
||||
bind:value={$choices.rootDir} />
|
||||
<InputChoice
|
||||
id="silent"
|
||||
label="Silent mode"
|
||||
tooltip="When enabled, comments will not be made on pull requests in this repository"
|
||||
bind:value={$choices.silentMode} />
|
||||
</FormList>
|
||||
</div>
|
||||
{/await}
|
||||
</Box>
|
||||
<p class="text u-margin-block-start-8">
|
||||
Visit your repository on <a
|
||||
href={`https://github.com/${$repository.organization}/${$repository.name}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">GitHub</a
|
||||
>.
|
||||
</p>
|
||||
</WizardStep>
|
||||
@@ -1,42 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible, CollapsibleItem } from '$lib/components';
|
||||
import { FormList, InputFile, InputText } from '$lib/elements/forms';
|
||||
import InputTextarea from '$lib/elements/forms/inputTextarea.svelte';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { createFunction, createFunctionDeployment } from '../store';
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Configuration</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Set your deployment configuration and any build commands here.
|
||||
</svelte:fragment>
|
||||
<FormList>
|
||||
<InputFile
|
||||
label="Upload a zip file (tar.gz) containing your function source code"
|
||||
allowedFileExtensions={['gz']}
|
||||
bind:files={$createFunctionDeployment}
|
||||
required />
|
||||
|
||||
<InputText
|
||||
label="Entrypoint"
|
||||
id="entrypoint"
|
||||
placeholder="Entrypoint"
|
||||
bind:value={$createFunction.entrypoint}
|
||||
required />
|
||||
<Collapsible>
|
||||
<CollapsibleItem>
|
||||
<svelte:fragment slot="title">Build commands</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">(optional)</svelte:fragment>
|
||||
<FormList>
|
||||
<InputTextarea
|
||||
label="Commands"
|
||||
placeholder="Enter a build command (e.g. 'npm install')"
|
||||
id="build"
|
||||
tooltip="Enter a single command or chain multiple commands with the && operator"
|
||||
bind:value={$createFunction.commands} />
|
||||
</FormList>
|
||||
</CollapsibleItem>
|
||||
</Collapsible>
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
@@ -1,84 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { CustomId } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { InputText, InputSelect, FormList } from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { onMount } from 'svelte';
|
||||
import { createFunction } from '../store';
|
||||
import { runtimesList } from '$lib/stores/runtimes';
|
||||
import { specificationsList } from '$lib/stores/specifications';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import SpecificationsTooltip from '../components/specificationsTooltip.svelte';
|
||||
|
||||
let showCustomId = false;
|
||||
|
||||
let options = [];
|
||||
let specificationOptions = [];
|
||||
|
||||
onMount(async () => {
|
||||
options = (await $runtimesList).runtimes.map((runtime) => ({
|
||||
label: `${runtime.name} - ${runtime.version}`,
|
||||
value: runtime.$id
|
||||
}));
|
||||
|
||||
specificationOptions = (await $specificationsList).specifications.map((size) => ({
|
||||
label:
|
||||
`${size.cpus} CPU, ${size.memory} MB RAM` +
|
||||
(!size.enabled ? ` (Upgrade to use this)` : ''),
|
||||
value: size.slug,
|
||||
disabled: !size.enabled
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Details</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">Create and deploy your function manually.</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
label="Name"
|
||||
id="name"
|
||||
placeholder="Function name"
|
||||
autofocus
|
||||
bind:value={$createFunction.name}
|
||||
required />
|
||||
|
||||
<InputSelect
|
||||
label="Runtime"
|
||||
id="runtime"
|
||||
placeholder="Select runtime"
|
||||
bind:value={$createFunction.runtime}
|
||||
{options}
|
||||
required />
|
||||
|
||||
<InputSelect
|
||||
label="CPU and memory"
|
||||
id="specification"
|
||||
placeholder="Select specification"
|
||||
required
|
||||
disabled={specificationOptions.length < 1}
|
||||
options={specificationOptions}
|
||||
popover={isCloud && $organization?.billingPlan === BillingPlan.FREE
|
||||
? SpecificationsTooltip
|
||||
: null}
|
||||
bind:value={$createFunction.specification} />
|
||||
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}>
|
||||
<span class="icon-pencil" aria-hidden="true" />
|
||||
<span class="text">Function ID </span>
|
||||
</Pill>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomId
|
||||
autofocus
|
||||
bind:show={showCustomId}
|
||||
name="Function"
|
||||
bind:id={$createFunction.$id}
|
||||
fullWidth />
|
||||
{/if}
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import Repositories from '../components/repositories.svelte';
|
||||
|
||||
let hasInstallations: boolean;
|
||||
let selectedRepository: string;
|
||||
async function beforeSubmit() {
|
||||
if (!hasInstallations) {
|
||||
throw new Error('Please connect a Git provider');
|
||||
}
|
||||
if (!selectedRepository) {
|
||||
throw new Error('Please select a repository');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Repository</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Select a Git repository that will trigger your function deployments when updated.
|
||||
</svelte:fragment>
|
||||
<Repositories bind:hasInstallations bind:selectedRepository />
|
||||
</WizardStep>
|
||||
@@ -1,128 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { CustomId } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { FormList, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { runtimesList } from '$lib/stores/runtimes';
|
||||
import { specificationsList } from '$lib/stores/specifications';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import SpecificationsTooltip from '../components/specificationsTooltip.svelte';
|
||||
import { template, templateConfig } from '../store';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { isCloud } from '$lib/system';
|
||||
|
||||
let showCustomId = false;
|
||||
|
||||
async function beforeSubmit() {
|
||||
if (!$templateConfig.runtime) {
|
||||
throw new Error('Please select a runtime.');
|
||||
}
|
||||
|
||||
if (!$templateConfig.specification) {
|
||||
throw new Error('Please select a specification.');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRuntimes() {
|
||||
const options = (await $runtimesList).runtimes
|
||||
.map((runtime) => ({
|
||||
label: `${runtime.name} - ${runtime.version}`,
|
||||
value: runtime.$id
|
||||
}))
|
||||
.filter((runtime) => {
|
||||
const allowedRuntimes = $template.runtimes.map((r) => r.name);
|
||||
return allowedRuntimes.includes(runtime.value);
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
async function loadSpecifications() {
|
||||
const specificationOptions = (await $specificationsList).specifications.map((size) => ({
|
||||
label:
|
||||
`${size.cpus} CPU, ${size.memory} MB RAM` +
|
||||
(!size.enabled ? ` (Upgrade to use this)` : ''),
|
||||
value: size.slug,
|
||||
disabled: !size.enabled
|
||||
}));
|
||||
|
||||
if (!$templateConfig.specification && specificationOptions.length > 0) {
|
||||
$templateConfig.specification = specificationOptions[0].value;
|
||||
}
|
||||
|
||||
return specificationOptions;
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">{$template.name}</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
{$template.tagline}
|
||||
</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
label="Name"
|
||||
id="name"
|
||||
placeholder="Function name"
|
||||
autofocus
|
||||
bind:value={$templateConfig.name}
|
||||
required />
|
||||
{#await loadRuntimes()}
|
||||
<InputSelect
|
||||
label="Runtime"
|
||||
id="runtime"
|
||||
placeholder="Loading runtimes..."
|
||||
required
|
||||
disabled
|
||||
options={[]}
|
||||
value={null} />
|
||||
{:then options}
|
||||
<InputSelect
|
||||
label="Runtime"
|
||||
id="runtime"
|
||||
placeholder="Select runtime"
|
||||
required
|
||||
disabled={options.length < 1}
|
||||
{options}
|
||||
bind:value={$templateConfig.runtime} />
|
||||
{/await}
|
||||
{#await loadSpecifications()}
|
||||
<InputSelect
|
||||
label="CPU and memory"
|
||||
id="specification"
|
||||
placeholder="Loading specifications..."
|
||||
required
|
||||
disabled
|
||||
options={[]}
|
||||
value={null} />
|
||||
{:then specificationOptions}
|
||||
<InputSelect
|
||||
label="CPU and memory"
|
||||
id="specification"
|
||||
placeholder="Select specification"
|
||||
required
|
||||
disabled={specificationOptions.length < 1}
|
||||
options={specificationOptions}
|
||||
popover={isCloud && $organization?.billingPlan === BillingPlan.FREE
|
||||
? SpecificationsTooltip
|
||||
: null}
|
||||
bind:value={$templateConfig.specification} />
|
||||
{/await}
|
||||
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}>
|
||||
<span class="icon-pencil" aria-hidden="true" />
|
||||
<span class="text">Function ID</span>
|
||||
</Pill>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomId
|
||||
autofocus
|
||||
bind:show={showCustomId}
|
||||
name="Function"
|
||||
bind:id={$templateConfig.$id}
|
||||
fullWidth />
|
||||
{/if}
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
@@ -1,62 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { LabelCard } from '$lib/components';
|
||||
import { FormList } from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { consoleVariables } from '$routes/(console)/store';
|
||||
import { onMount } from 'svelte';
|
||||
import { templateConfig, templateStepsComponents } from '../store';
|
||||
|
||||
const isVcsEnabled = $consoleVariables?._APP_VCS_ENABLED === true;
|
||||
|
||||
onMount(() => {
|
||||
if (!isVcsEnabled) {
|
||||
$templateConfig.repositoryBehaviour = 'manual';
|
||||
}
|
||||
});
|
||||
|
||||
async function beforeSubmit() {
|
||||
if (!$templateConfig.repositoryBehaviour) {
|
||||
throw new Error('Please select repository behaviour.');
|
||||
}
|
||||
}
|
||||
|
||||
$: if ($templateConfig.repositoryBehaviour === 'manual') {
|
||||
$templateStepsComponents.get(5).disabled = true;
|
||||
$templateStepsComponents.get(6).disabled = true;
|
||||
$templateStepsComponents = $templateStepsComponents;
|
||||
} else {
|
||||
$templateStepsComponents.get(5).disabled = false;
|
||||
$templateStepsComponents.get(6).disabled = false;
|
||||
$templateStepsComponents = $templateStepsComponents;
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Deployment</svelte:fragment>
|
||||
|
||||
<FormList>
|
||||
<h3>Connect with Git <span class="inline-code">Recommended</span></h3>
|
||||
<LabelCard
|
||||
name="behaviour"
|
||||
value="new"
|
||||
bind:group={$templateConfig.repositoryBehaviour}
|
||||
disabled={!isVcsEnabled}>
|
||||
<svelte:fragment slot="title">Create a new repository</svelte:fragment>
|
||||
Clone the template to a newly created repository in your organization.
|
||||
</LabelCard>
|
||||
<LabelCard
|
||||
name="behaviour"
|
||||
value="existing"
|
||||
bind:group={$templateConfig.repositoryBehaviour}
|
||||
disabled={!isVcsEnabled}>
|
||||
<svelte:fragment slot="title">Add to existing repository</svelte:fragment>
|
||||
Clone the template to an existing repository in your organization.
|
||||
</LabelCard>
|
||||
|
||||
<h3>Quick start</h3>
|
||||
<LabelCard name="behaviour" value="manual" bind:group={$templateConfig.repositoryBehaviour}>
|
||||
<svelte:fragment slot="title">Connect later</svelte:fragment>
|
||||
Deploy now and continue development via CLI, or connect Git from your function settings.
|
||||
</LabelCard>
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
@@ -1,90 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { scopes } from '$lib/constants';
|
||||
import { FormList, InputChoice } from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { onMount } from 'svelte';
|
||||
import { template, templateConfig } from '../store';
|
||||
import { Tooltip } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let templateScopes = [];
|
||||
onMount(() => {
|
||||
$templateConfig.execute = $template.permissions.includes('any');
|
||||
templateScopes = scopes
|
||||
// hot fix for perplexity chat template,
|
||||
// backend doesn't have scopes for this template.
|
||||
// TODO: @itznotabug fix on backend too.
|
||||
.filter((scope) => ($template.scopes ?? []).includes(scope.scope))
|
||||
.map((scope) => ({
|
||||
...scope,
|
||||
active: true
|
||||
}));
|
||||
});
|
||||
|
||||
$: $templateConfig.scopes = templateScopes
|
||||
?.filter((scope) => scope.active)
|
||||
?.map((scope) => scope.scope);
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Permissions</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Enable recommended scopes and execute access for when your function is deployed.
|
||||
</svelte:fragment>
|
||||
<h3 class="label">Execute permissions</h3>
|
||||
<FormList class="u-margin-block-start-16">
|
||||
<div class="user-profile">
|
||||
<span class="avatar" style:--p-text-size="1rem">
|
||||
<span class="icon-lock-open" />
|
||||
</span>
|
||||
<span class="user-profile-info u-flex u-main-space-between u-gap-16">
|
||||
<div>
|
||||
<p class="name u-bold">
|
||||
Public (anyone can execute) <Tooltip
|
||||
><span class="icon-info" /><span slot="tooltip"
|
||||
>You can further customize execute permissions in your function
|
||||
settings.</span
|
||||
></Tooltip>
|
||||
</p>
|
||||
<p class="text u-margin-block-start-4">
|
||||
This could include unauthorized users and bots.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<InputChoice
|
||||
type="switchbox"
|
||||
id="execute"
|
||||
label="Execute"
|
||||
showLabel={false}
|
||||
bind:value={$templateConfig.execute}></InputChoice>
|
||||
</span>
|
||||
</div>
|
||||
</FormList>
|
||||
{#if templateScopes.length > 0}
|
||||
<h3 class="label u-margin-block-start-48">Function scopes</h3>
|
||||
<FormList class="u-margin-block-start-16">
|
||||
{#each templateScopes as scope, i}
|
||||
<div class="user-profile">
|
||||
<span class="avatar" style:--p-text-size="1rem">
|
||||
<span class={`icon-${scope.icon}`} />
|
||||
</span>
|
||||
<span class="user-profile-info u-flex u-main-space-between u-gap-16">
|
||||
<div>
|
||||
<p class="name u-bold">{scope.scope}</p>
|
||||
<p class="text u-margin-block-start-4">{scope.description}</p>
|
||||
</div>
|
||||
<InputChoice
|
||||
type="switchbox"
|
||||
id={scope.scope}
|
||||
label={scope.scope}
|
||||
showLabel={false}
|
||||
bind:value={scope.active}>
|
||||
</InputChoice>
|
||||
</span>
|
||||
</div>
|
||||
{#if i < templateScopes.length - 1}
|
||||
<div class="with-separators"></div>
|
||||
{/if}
|
||||
{/each}
|
||||
</FormList>
|
||||
{/if}
|
||||
</WizardStep>
|
||||
@@ -1,124 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { template, templateConfig } from '../store';
|
||||
import {
|
||||
FormList,
|
||||
Helper,
|
||||
InputPassword,
|
||||
InputText,
|
||||
InputURL,
|
||||
InputPhone,
|
||||
InputEmail,
|
||||
InputNumber
|
||||
} from '$lib/elements/forms';
|
||||
import { Card, Collapsible, CollapsibleItem } from '$lib/components';
|
||||
import type { SvelteComponent } from 'svelte';
|
||||
|
||||
async function beforeSubmit() {
|
||||
for (const variable of $template.variables) {
|
||||
if (!variable.required) {
|
||||
continue;
|
||||
}
|
||||
if (!$templateConfig.variables[variable.name]) {
|
||||
throw new Error(`Please set ${variable.name} variable.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: requiredVariables = $template?.variables?.filter((v) => v.required);
|
||||
$: optionalVariables = $template?.variables?.filter((v) => !v.required);
|
||||
|
||||
function selectComponent(variableType: string): typeof SvelteComponent<unknown> {
|
||||
switch (variableType) {
|
||||
case 'password':
|
||||
return InputPassword;
|
||||
case 'text':
|
||||
return InputText;
|
||||
case 'url':
|
||||
return InputURL;
|
||||
case 'phone':
|
||||
return InputPhone;
|
||||
case 'email':
|
||||
return InputEmail;
|
||||
case 'number':
|
||||
return InputNumber;
|
||||
default:
|
||||
return InputPassword;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Variables</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Edit the values of the environment variables that will be passed to your function at
|
||||
runtime.
|
||||
</svelte:fragment>
|
||||
{#if $template?.variables?.length}
|
||||
{#if requiredVariables?.length}
|
||||
<Collapsible>
|
||||
<CollapsibleItem open={true}>
|
||||
<svelte:fragment slot="title">Required variables</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
<span class="inline-tag">{requiredVariables.length}</span>
|
||||
</svelte:fragment>
|
||||
|
||||
<FormList>
|
||||
{#each requiredVariables as variable}
|
||||
<div>
|
||||
<svelte:component
|
||||
this={selectComponent(variable.type)}
|
||||
id={variable.name}
|
||||
label={variable.name}
|
||||
placeholder={variable.placeholder ?? 'Enter value'}
|
||||
required={variable.required}
|
||||
autocomplete={false}
|
||||
minlength={variable.type === 'password' ? 0 : null}
|
||||
showPasswordButton={variable.type === 'password'}
|
||||
bind:value={$templateConfig.variables[variable.name]} />
|
||||
<Helper type="neutral">
|
||||
{@html variable.description}
|
||||
</Helper>
|
||||
</div>
|
||||
{/each}
|
||||
</FormList>
|
||||
</CollapsibleItem>
|
||||
</Collapsible>
|
||||
{/if}
|
||||
|
||||
{#if optionalVariables?.length}
|
||||
<Collapsible>
|
||||
<CollapsibleItem open={!requiredVariables?.length}>
|
||||
<svelte:fragment slot="title">Optional variables</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
<span class="inline-tag">{optionalVariables.length}</span>
|
||||
</svelte:fragment>
|
||||
|
||||
<FormList>
|
||||
{#each optionalVariables as variable}
|
||||
<div>
|
||||
<svelte:component
|
||||
this={selectComponent(variable.type)}
|
||||
id={variable.name}
|
||||
label={variable.name}
|
||||
placeholder={variable.placeholder ?? 'Enter value'}
|
||||
required={variable.required}
|
||||
autocomplete={false}
|
||||
showPasswordButton={variable.type === 'password'}
|
||||
bind:value={$templateConfig.variables[variable.name]} />
|
||||
|
||||
<Helper type="neutral">
|
||||
{@html variable.description}
|
||||
</Helper>
|
||||
</div>
|
||||
{/each}
|
||||
</FormList>
|
||||
</CollapsibleItem>
|
||||
</Collapsible>
|
||||
{/if}
|
||||
{:else}
|
||||
<Card isDashed>
|
||||
<p class="text u-text-center">There are no environment variables to configure.</p>
|
||||
</Card>
|
||||
{/if}
|
||||
</WizardStep>
|
||||
@@ -1,64 +0,0 @@
|
||||
import { page } from '$app/stores';
|
||||
import type { WizardStepsType } from '$lib/layout/wizardWithSteps.svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { derived, writable, type Writable } from 'svelte/store';
|
||||
|
||||
export const template = writable<Models.TemplateFunction>();
|
||||
export const templateConfig = writable<{
|
||||
$id: string;
|
||||
name: string;
|
||||
runtime: string;
|
||||
variables: { [key: string]: unknown };
|
||||
repositoryBehaviour: 'new' | 'existing' | 'manual';
|
||||
repositoryName?: string;
|
||||
repositoryPrivate?: boolean;
|
||||
repositoryId: string;
|
||||
execute?: boolean;
|
||||
scopes?: string[];
|
||||
specification?: string;
|
||||
}>();
|
||||
export const repository = writable<Models.ProviderRepository>();
|
||||
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
|
||||
);
|
||||
|
||||
const initialCreateFunction: Partial<Models.Function> = {
|
||||
$id: null,
|
||||
name: null,
|
||||
entrypoint: null,
|
||||
execute: [],
|
||||
runtime: null,
|
||||
commands: null
|
||||
};
|
||||
|
||||
function createFunctionStore() {
|
||||
const store = writable<Partial<Models.Function>>({
|
||||
...initialCreateFunction
|
||||
});
|
||||
|
||||
const reset = () => {
|
||||
store.set({ ...initialCreateFunction });
|
||||
};
|
||||
|
||||
return {
|
||||
...store,
|
||||
reset
|
||||
};
|
||||
}
|
||||
export const createFunction = createFunctionStore();
|
||||
|
||||
export const createFunctionDeployment = writable<FileList>();
|
||||
|
||||
export const templateStepsComponents: Writable<WizardStepsType> = writable(new Map());
|
||||
@@ -418,7 +418,7 @@
|
||||
height: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
border-radius: 100%;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border: 1px solid hsl(var(--border));
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
@@ -431,7 +431,7 @@
|
||||
}
|
||||
|
||||
.buttons-list {
|
||||
border-block-end: 1px solid hsl(var(--color-border));
|
||||
border-block-end: 1px solid hsl(var(--border));
|
||||
padding-block-end: 0.625rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<script>
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Heading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<div class="u-flex-vertical u-gap-16">
|
||||
<Heading size="1" tag="h3">{$page.error.status || 'Invalid Argument'}</Heading>
|
||||
<Heading size="4" tag="h4" trimmed={false}>{$page.error.message}</Heading>
|
||||
<div>
|
||||
<Typography.Title size="xl">{$page.error.status || 'Invalid Argument'}</Typography.Title>
|
||||
<Typography.Title>{$page.error.message}</Typography.Title>
|
||||
</div>
|
||||
<div>
|
||||
<Button href={base}>Back to the console</Button>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
Empty,
|
||||
AvatarGroup,
|
||||
CardContainer,
|
||||
Heading,
|
||||
PaginationWithLimit
|
||||
} from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
@@ -21,7 +20,7 @@
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Icon, Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { Icon, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let data: PageData;
|
||||
@@ -47,7 +46,7 @@
|
||||
|
||||
<Container>
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<Heading tag="h2" size="5">Organizations</Heading>
|
||||
<Typography.Title>Organizations</Typography.Title>
|
||||
|
||||
<Button on:click={createOrg} event="create_organization">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Heading } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import PaymentMethods from './paymentMethods.svelte';
|
||||
import BillingAddress from './billingAddress.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { confirmPayment } from '$lib/stores/stripe';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let showPayment = false;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<Container>
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<Heading tag="h2" size="5">Payment details</Heading>
|
||||
<Typography.Title size="s">Payment details</Typography.Title>
|
||||
</div>
|
||||
<PaymentMethods bind:showPayment />
|
||||
<BillingAddress />
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, EyebrowHeading, Heading } from '$lib/components';
|
||||
import { CardGrid, EyebrowHeading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, FormList, InputChoice } from '$lib/elements/forms';
|
||||
import { Button, InputChoice } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { Button, Form, FormList, InputPassword } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
@@ -190,18 +190,16 @@
|
||||
</p>
|
||||
<PlanSelection bind:billingPlan {anyOrgFree} isNewOrg />
|
||||
{#if billingPlan !== BillingPlan.FREE}
|
||||
<FormList class="u-margin-block-start-24">
|
||||
<InputTags
|
||||
bind:tags={collaborators}
|
||||
label="Invite members by email"
|
||||
popover={Default}
|
||||
placeholder="Enter email address(es)"
|
||||
validityRegex={emailRegex}
|
||||
validityMessage="Invalid email address"
|
||||
id="members" />
|
||||
<SelectPaymentMethod bind:methods bind:value={paymentMethodId} bind:taxId
|
||||
></SelectPaymentMethod>
|
||||
</FormList>
|
||||
<InputTags
|
||||
bind:tags={collaborators}
|
||||
label="Invite members by email"
|
||||
popover={Default}
|
||||
placeholder="Enter email address(es)"
|
||||
validityRegex={emailRegex}
|
||||
validityMessage="Invalid email address"
|
||||
id="members" />
|
||||
<SelectPaymentMethod bind:methods bind:value={paymentMethodId} bind:taxId
|
||||
></SelectPaymentMethod>
|
||||
{#if !couponData?.code}
|
||||
<Button text on:click={() => (showCreditModal = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
|
||||
@@ -147,9 +147,7 @@
|
||||
<Card.Base variant="primary" padding="l"
|
||||
><form>
|
||||
<Layout.Stack direction="column" gap="xxl">
|
||||
<div style:margin-top="20px">
|
||||
<Typography.Title size="l">Create your project</Typography.Title>
|
||||
</div>
|
||||
<Typography.Title size="l">Create your project</Typography.Title>
|
||||
|
||||
<Layout.Stack direction="column" gap="xxl">
|
||||
<Layout.Stack direction="column" gap="xxl">
|
||||
@@ -191,7 +189,7 @@
|
||||
><Button.Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
size="m"
|
||||
size="s"
|
||||
on:click={createProject}>
|
||||
Create</Button.Button>
|
||||
</Layout.Stack>
|
||||
@@ -204,13 +202,13 @@
|
||||
@use '@appwrite.io/pink-legacy/src/abstract/variables/devices';
|
||||
|
||||
:global(body) {
|
||||
background: var(--color-bgcolor-neutral-default, #19191c);
|
||||
background: var(--bgcolor-neutral-default, #19191c);
|
||||
}
|
||||
.page-container {
|
||||
width: calc(100% - 2rem);
|
||||
margin: 0 1rem;
|
||||
gap: 4.5rem;
|
||||
background: var(--color-bgcolor-neutral-default, #19191c);
|
||||
background: var(--bgcolor-neutral-default, #19191c);
|
||||
|
||||
@media #{devices.$break2open} {
|
||||
width: 700px;
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
|
||||
<style lang="scss">
|
||||
:global(.theme-dark) {
|
||||
--icon-content-background-color: var(--color-bgcolor-neutral-primary, #1d1d21);
|
||||
--icon-content-background-color: var(--bgcolor-neutral-primary, #1d1d21);
|
||||
--icon-content-border-color: var(--color-overlay-skeleton);
|
||||
--icon-container-background-color: #242427;
|
||||
--icon-container-border-color: rgba(255, 255, 255, 0.02);
|
||||
@@ -216,7 +216,7 @@
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-fgcolor-neutral-primary, #2d2d31);
|
||||
color: var(--fgcolor-neutral-primary, #2d2d31);
|
||||
|
||||
--animation-type: cubic-bezier(0.77, 0, 0.175, 1);
|
||||
--icon-colorize-animation-duration: 0.3s; // duration of icons going from gray to colorized
|
||||
@@ -393,10 +393,10 @@
|
||||
}
|
||||
|
||||
.border-right {
|
||||
border-right: 1px solid var(--color-border-neutral);
|
||||
border-right: 1px solid var(--border-neutral);
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid var(--color-border-neutral);
|
||||
border-bottom: 1px solid var(--border-neutral);
|
||||
grid-column: 1/8;
|
||||
@media (min-width: 768px) {
|
||||
grid-column: 1/10;
|
||||
@@ -406,7 +406,7 @@
|
||||
.vertical-fade-top {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--color-bgcolor-neutral-default, #19191c) 0%,
|
||||
var(--bgcolor-neutral-default, #19191c) 0%,
|
||||
transparent 100%
|
||||
);
|
||||
grid-column: 1/8;
|
||||
@@ -417,7 +417,7 @@
|
||||
.vertical-fade-bottom {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
var(--color-bgcolor-neutral-default, #19191c) 0%,
|
||||
var(--bgcolor-neutral-default, #19191c) 0%,
|
||||
transparent 100%
|
||||
);
|
||||
grid-column: 1/8;
|
||||
@@ -428,14 +428,14 @@
|
||||
.horizontal-fade-left {
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
var(--color-bgcolor-neutral-default, #19191c) 0%,
|
||||
var(--bgcolor-neutral-default, #19191c) 0%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
.horizontal-fade-right {
|
||||
background: linear-gradient(
|
||||
to left,
|
||||
var(--color-bgcolor-neutral-default, #19191c) 0%,
|
||||
var(--bgcolor-neutral-default, #19191c) 0%,
|
||||
transparent 100%
|
||||
);
|
||||
grid-column-start: 7;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
DropListItem,
|
||||
Empty,
|
||||
GridItem1,
|
||||
Heading,
|
||||
PaginationWithLimit
|
||||
} from '$lib/components';
|
||||
import { goto } from '$app/navigation';
|
||||
@@ -31,7 +30,7 @@
|
||||
import { onMount, type ComponentType } from 'svelte';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { Badge, Icon, Typography } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
IconAndroid,
|
||||
IconApple,
|
||||
@@ -146,7 +145,7 @@
|
||||
|
||||
<Container>
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<Heading tag="h2" size="5">Projects</Heading>
|
||||
<Typography.Title>Projects</Typography.Title>
|
||||
|
||||
<DropList bind:show={showDropdown} placement="bottom-end">
|
||||
{#if $canWriteProjects}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import AvailableCredit from './availableCredit.svelte';
|
||||
import PaymentHistory from './paymentHistory.svelte';
|
||||
import TaxId from './taxId.svelte';
|
||||
import { Alert, Heading } from '$lib/components';
|
||||
import { Alert } from '$lib/components';
|
||||
import { failedInvoice, paymentMethods, tierToPlan, upgradeURL } from '$lib/stores/billing';
|
||||
import type { PaymentMethodData } from '$lib/sdk/billing';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -21,6 +21,7 @@
|
||||
import { selectedInvoice, showRetryModal } from './store';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -117,7 +118,7 @@
|
||||
</Alert>
|
||||
{/if}
|
||||
<div class="common-section">
|
||||
<Heading tag="h2" size="5">Billing</Heading>
|
||||
<Typography.Title>Billing</Typography.Title>
|
||||
</div>
|
||||
<PlanSummary
|
||||
creditList={data?.creditList}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Alert, CardGrid, Empty, Heading, PaginationInline } from '$lib/components';
|
||||
import { Alert, CardGrid, Empty, PaginationInline } from '$lib/components';
|
||||
import {
|
||||
TableBody,
|
||||
TableCellHead,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, DropList, Heading } from '$lib/components';
|
||||
import { CardGrid, DropList } from '$lib/components';
|
||||
import DropListItem from '$lib/components/dropListItem.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Alert, CardGrid, Heading } from '$lib/components';
|
||||
import { Alert, CardGrid } from '$lib/components';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { tierToPlan, upgradeURL } from '$lib/stores/billing';
|
||||
import { Button, Form, FormList, InputSelectSearch } from '$lib/elements/forms';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Alert, CardGrid, Heading } from '$lib/components';
|
||||
import { Alert, CardGrid } from '$lib/components';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputNumber, InputSwitch } from '$lib/elements/forms';
|
||||
import { showUsageRatesModal, upgradeURL } from '$lib/stores/billing';
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
DropListItem,
|
||||
DropListLink,
|
||||
EmptySearch,
|
||||
Heading,
|
||||
PaginationInline
|
||||
} from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
CreditCardBrandImage,
|
||||
CreditCardInfo,
|
||||
DropList,
|
||||
DropListItem,
|
||||
Heading
|
||||
DropListItem
|
||||
} from '$lib/components';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { Card, CardGrid, Collapsible, CollapsibleItem, Heading } from '$lib/components';
|
||||
import { Card, CardGrid, Collapsible, CollapsibleItem } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { plansInfo, tierToPlan, upgradeURL } from '$lib/stores/billing';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
const backPage = `${base}/project-${$page.params.organization}/domains`;
|
||||
|
||||
let domain = '';
|
||||
let domainData: Domain;
|
||||
let domainName = '';
|
||||
let domain: Domain;
|
||||
|
||||
async function addDomain() {
|
||||
try {
|
||||
domainData = await sdk.forConsole.domains.create($page.params.organization, domain);
|
||||
console.log(domainData);
|
||||
domain = await sdk.forConsole.domains.create($page.params.organization, domainName);
|
||||
console.log(domain);
|
||||
invalidate(Dependencies.DOMAINS);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
@@ -29,41 +29,11 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyStatus() {
|
||||
try {
|
||||
domainData = await sdk.forConsole.domains.updateNameservers(domainData.$id);
|
||||
console.log(domainData);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function back() {
|
||||
try {
|
||||
await sdk.forConsole.domains.delete(domainData.$id);
|
||||
domainData = undefined;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Wizard title="Add domain" href={backPage} column columnSize="s" hideFooter={!domainData}>
|
||||
{#if domainData}
|
||||
<RecordsCard domain={domainData}>
|
||||
<Divider />
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<Button text on:click={back}>Back</Button>
|
||||
<Button secondary on:click={verifyStatus}>Verify</Button>
|
||||
</Layout.Stack>
|
||||
</RecordsCard>
|
||||
<Wizard title="Add domain" href={backPage} column columnSize="s" hideFooter={!domain}>
|
||||
{#if domain}
|
||||
<RecordsCard {domain} />
|
||||
{:else}
|
||||
<Fieldset legend="Configuration">
|
||||
<Form onSubmit={addDomain}>
|
||||
@@ -72,7 +42,7 @@
|
||||
label="Domain"
|
||||
id="domain"
|
||||
name="domain"
|
||||
bind:value={domain}
|
||||
bind:value={domainName}
|
||||
required
|
||||
placeholder="example.com" />
|
||||
|
||||
@@ -87,7 +57,7 @@
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text href={backPage}>Cancel</Button>
|
||||
{#if domainData}
|
||||
{#if domain}
|
||||
<Button secondary href={backPage}>Skip to console</Button>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Heading } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
import ChangeOrganization from './changeOrganization.svelte';
|
||||
import DeleteDomain from './deleteDomain.svelte';
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Heading tag="h2" size="5">Settings</Heading>
|
||||
<Typography.Title>Settings</Typography.Title>
|
||||
<ChangeOrganization domain={data.domain} organizations={data.organizations} />
|
||||
<DeleteDomain domain={data.domain} />
|
||||
</Container>
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { Copy } from '$lib/components';
|
||||
import { Link } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import type { Domain } from '$lib/sdk/domains';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { consoleVariables } from '$routes/(console)/store';
|
||||
import { IconDuplicate, IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Badge, Layout, Typography, Table, Fieldset, Icon } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
Badge,
|
||||
Layout,
|
||||
Typography,
|
||||
Table,
|
||||
Fieldset,
|
||||
Icon,
|
||||
Divider
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let domain: Domain;
|
||||
|
||||
$: console.log($consoleVariables._APP_DOMAINS_NAMESERVERS);
|
||||
let verified = undefined;
|
||||
|
||||
// TODO: split _APP_DOMAINS_NAMESERVERS?
|
||||
let nameservers = $consoleVariables?._APP_DOMAINS_NAMESERVERS ?? [
|
||||
@@ -16,7 +27,32 @@
|
||||
'ns2.appwrite.io'
|
||||
];
|
||||
|
||||
$: verified = domain.nameservers === nameservers;
|
||||
async function verifyStatus() {
|
||||
try {
|
||||
domain = await sdk.forConsole.domains.updateNameservers(domain.$id);
|
||||
verified = domain.nameservers === nameservers;
|
||||
console.log(domain);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function back() {
|
||||
try {
|
||||
await sdk.forConsole.domains.delete(domain.$id);
|
||||
domain = undefined;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: console.log($consoleVariables._APP_DOMAINS_NAMESERVERS);
|
||||
</script>
|
||||
|
||||
<Fieldset legend="Verification">
|
||||
@@ -26,6 +62,9 @@
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
<Typography.Text variant="l-500">{domain.domain}</Typography.Text>
|
||||
|
||||
{#if verified === false}
|
||||
<Badge variant="secondary" type="error" content="Verification failed" />
|
||||
{/if}
|
||||
<Badge variant="secondary" type="warning" content="Pending verification" />
|
||||
</Layout.Stack>
|
||||
<Typography.Text variant="m-400">
|
||||
@@ -55,8 +94,8 @@
|
||||
{/each}
|
||||
</Table.Root>
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
<Icon icon={IconInfo} size="s" color="--color-fgcolor-neutral-secondary" />
|
||||
<Typography.Text variant="m-400" color="--color-fgcolor-neutral-secondary">
|
||||
<Icon icon={IconInfo} size="s" color="--fgcolor-neutral-secondary" />
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-secondary">
|
||||
A list of all domain providers and their DNS setting is available <Link
|
||||
variant="muted"
|
||||
href="#">here</Link
|
||||
@@ -64,6 +103,10 @@
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<slot />
|
||||
<Divider />
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<Button text on:click={back}>Back</Button>
|
||||
<Button secondary on:click={verifyStatus}>Verify</Button>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<svelte:fragment slot="header">
|
||||
<span class="u-flex u-cross-center u-gap-8 u-min-width-0">
|
||||
<span class="u-trim">
|
||||
<Typography.Title color="--color-fgcolor-neutral-primary" size="xl"
|
||||
<Typography.Title color="--fgcolor-neutral-primary" size="xl"
|
||||
>{$organization.name}</Typography.Title>
|
||||
</span>
|
||||
{#if isCloud && $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid, BoxAvatar, AvatarGroup, Heading } from '$lib/components';
|
||||
import { CardGrid, BoxAvatar, AvatarGroup } from '$lib/components';
|
||||
import { InputText, Form, Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Box, CardGrid, Heading } from '$lib/components';
|
||||
import { Box, CardGrid } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import BaaModal from './BAAModal.svelte';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Box, CardGrid, Heading } from '$lib/components';
|
||||
import { Box, CardGrid } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { base } from '$app/paths';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Container } from '$lib/layout';
|
||||
import { Card, CardGrid, Heading, ProgressBarBig } from '$lib/components';
|
||||
import { Card, CardGrid, ProgressBarBig } from '$lib/components';
|
||||
import {
|
||||
getServiceLimit,
|
||||
showUsageRatesModal,
|
||||
@@ -20,6 +20,7 @@
|
||||
import TotalMembers from './totalMembers.svelte';
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { formatCurrency, formatNumberWithCommas } from '$lib/helpers/numbers';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -38,7 +39,7 @@
|
||||
|
||||
<Container>
|
||||
<div class="u-flex u-cross-center u-main-space-between">
|
||||
<Heading tag="h2" size="5">Usage</Heading>
|
||||
<Typography.Title>Usage</Typography.Title>
|
||||
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
<Button
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { AvatarInitials, CardGrid, Heading, Paginator } from '$lib/components';
|
||||
import { AvatarInitials, CardGrid, Paginator } from '$lib/components';
|
||||
import { EmptyCardCloud } from '$lib/components/billing';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user