Merge branch '1.4.x' of github.com:appwrite/console into usage-rollback

This commit is contained in:
Arman
2023-08-22 19:51:57 +02:00
220 changed files with 8722 additions and 2358 deletions
+7258 -1370
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -21,8 +21,8 @@
"@analytics/google-analytics": "^1.0.5",
"@analytics/google-tag-manager": "^0.5.3",
"@appwrite.io/console": "npm:shimon-appwrite-console@0.1.1",
"@appwrite.io/pink": "0.1.0-next.3",
"@appwrite.io/pink-icons": "^0.1.0-next.3",
"@appwrite.io/pink": "0.1.0-next.4",
"@appwrite.io/pink-icons": "^0.1.0-next.4",
"@popperjs/core": "^2.11.6",
"@sentry/svelte": "^7.44.2",
"@sentry/tracing": "^7.44.2",
+20
View File
@@ -0,0 +1,20 @@
import type { Action } from 'svelte/action';
export type MultiActionArray = Array<(node: HTMLElement) => ReturnType<Action>>;
export function multiAction(node: HTMLElement, arr: MultiActionArray) {
const destroyFns = arr.map((fn) => {
const actionReturn = fn(node);
return actionReturn
? actionReturn.destroy
: () => {
/* noop */
};
});
return {
destroy() {
destroyFns.forEach((fn) => fn());
}
};
}
+18 -2
View File
@@ -33,6 +33,9 @@
import { commandCenterKeyDownHandler, disableCommands, registerCommands } from './commands';
import { RootPanel } from './panels';
import { addSubPanel, clearSubPanels, subPanels } from './subPanels';
import { addNotification } from '$lib/stores/notifications';
let debugOverlayEnabled = false;
$: $registerCommands([
{
@@ -40,6 +43,20 @@
keys: ['k'],
ctrl: true,
forceEnable: true
},
{
label: 'Toggle debug overlay',
callback: () => {
debugOverlayEnabled = !debugOverlayEnabled;
addNotification({
title: 'Debug overlay',
message: debugOverlayEnabled ? 'Enabled' : 'Disabled',
type: 'info'
});
},
keys: ['d', 'o'],
group: 'misc',
disabled: !dev
}
]);
@@ -100,7 +117,7 @@
</div>
{/if}
{#if dev}
{#if dev && debugOverlayEnabled}
<div class="debug-keys" use:portal>
{#each keys as key, i (i)}
<kbd class="kbd" transition:fade|local={{ duration: 150 }}>
@@ -126,7 +143,6 @@
left: 50%;
transform: translateX(-50%);
padding: 0.5rem;
// background-color: hsl(var(--color-neutral-500) / 0.5);
z-index: 9999;
display: flex;
+14 -10
View File
@@ -57,8 +57,8 @@ type KeyedCommand = BaseCommand & {
alt?: boolean;
};
function isKeyedCommand(command: Command): command is KeyedCommand {
return 'keys' in command;
export function isKeyedCommand(command: Command): command is KeyedCommand {
return 'keys' in command && Array.isArray((command as KeyedCommand).keys);
}
export type Command = KeyedCommand | BaseCommand;
@@ -83,7 +83,7 @@ function isInputEvent(event: KeyboardEvent) {
function getCommandRank(command: KeyedCommand) {
const { keys, ctrl: meta, shift, alt } = command;
const modifiers = [meta, shift, alt].filter(Boolean).length;
return keys.length + modifiers * 10;
return (keys?.length || 0) + modifiers * 10;
}
function hasDisputing(command: KeyedCommand, allCommands: Command[]) {
@@ -95,7 +95,7 @@ function hasDisputing(command: KeyedCommand, allCommands: Command[]) {
return false;
}
const keysString = command.keys.join('+');
const otherKeysString = otherCommand.keys.join('+');
const otherKeysString = otherCommand?.keys?.join('+');
const cmdRank = getCommandRank(command);
const otherCmdRank = getCommandRank(otherCommand);
@@ -195,8 +195,10 @@ export const commandCenterKeyDownHandler = derived(
const isShiftPressed = shift ? event.shiftKey : !event.shiftKey;
const isAltPressed = alt ? event.altKey : !event.altKey;
const commandKeyCodes = keys.map((key) => key.toUpperCase().charCodeAt(0));
const allKeysPressed = recentKeyCodes.join('').includes(commandKeyCodes.join(''));
const commandKeyCodes = keys?.map((key) => key.toUpperCase().charCodeAt(0));
const allKeysPressed = commandKeyCodes
? recentKeyCodes.join('').includes(commandKeyCodes.join(''))
: false;
if (allKeysPressed && isMetaPressed && isShiftPressed && isAltPressed) {
event.preventDefault();
@@ -288,10 +290,12 @@ export const commandGroupRanks = derived(groupRanksMap, ($groupRankTransformatio
const initialRanks = {
...Object.fromEntries(groups.map((group) => [group, 0])),
ungrouped: 9999,
databases: 3,
users: 2,
teams: 1,
navigation: -10,
databases: 50,
users: 40,
teams: 30,
projects: 20,
organizations: 10,
navigation: 0,
help: -20,
misc: -30
} as CommandGroupRanks;
+71 -36
View File
@@ -1,13 +1,12 @@
<script lang="ts">
import Template from './template.svelte';
import { AvatarInitials, Code } from '$lib/components';
import { AvatarInitials, Code, LoadingDots, SvgIcon } from '$lib/components';
import { user } from '$lib/stores/user';
import { useCompletion } from 'ai/svelte';
import { subPanels } from '../subPanels';
import { isLanguage, type Language } from '$lib/components/code.svelte';
import CoolerAppwrite from '$lib/images/appwrite-cooler.svg';
import { VARS } from '$lib/system';
const endpoint = VARS.APPWRITE_ENDPOINT ?? `${globalThis?.location?.origin}/v1`;
@@ -62,7 +61,9 @@
answer.push({
type: 'code',
value: nextCodeMatch[2],
value: nextCodeMatch[2].startsWith('\n')
? nextCodeMatch[2].slice(1)
: nextCodeMatch[2],
language: isLanguage(language) ? language : 'js'
});
@@ -82,6 +83,11 @@
}
$: answer = parseCompletion($completion);
function getInitials(name: string) {
const [first, last] = name.split(' ');
return `${first?.[0] ?? ''}${last?.[0] ?? ''}`;
}
</script>
<Template
@@ -98,9 +104,14 @@
};
})}
clearOnCallback={false}
fullheight
--command-panel-max-height="40rem">
<div slot="search" />
on:keydown={(e) => {
e.detail.cancel();
}}
--min-height="40rem"
--max-height="52.5rem">
<div slot="search">
<span class="experimental border-gradient">EXPERIMENTAL</span>
</div>
<div slot="option" let:option class="u-flex u-cross-center u-gap-8">
<i class="icon-question-mark-circle" />
@@ -109,20 +120,28 @@
{#if $isLoading || answer}
<div class="content">
<div class="u-flex u-gap-8">
<div class="u-flex u-gap-8 u-cross-center">
<div class="avatar is-size-x-small">{getInitials($user.name)}</div>
<p class="u-opacity-75">{$input}</p>
</div>
<div class="u-flex u-gap-8 u-margin-block-start-24">
<div class="logo">
<img src={CoolerAppwrite} alt="Appwrite logo" />
<SvgIcon name="sparkles" type="color" />
</div>
<div class="answer">
{#if $isLoading && !$completion}
<p>...</p>
<LoadingDots />
{:else}
{#each answer as part}
{#if part.type === 'text'}
<p>{part.value}</p>
<p>{part.value.trimStart()}</p>
{:else if part.type === 'code'}
{#key part.value}
<Code language={part.language} code={part.value} />
<div
class="u-margin-block-start-8"
style="margin-block-end: 1rem;">
<Code language={part.language} code={part.value} noMargin />
</div>
{/key}
{/if}
{/each}
@@ -174,8 +193,16 @@
</Template>
<style lang="scss">
:global(.theme-dark) .content {
--logo-bg: #282a3b;
}
:global(.theme-light) .content {
--logo-bg: #f2f2f8;
}
.content {
overflow-y: auto;
overflow: auto;
padding: 1rem;
.logo {
@@ -187,7 +214,7 @@
flex-shrink: 0;
border-radius: 0.25rem;
background: #282a3b;
background: var(--logo-bg);
}
.answer {
@@ -197,29 +224,6 @@
white-space: pre-wrap;
}
}
h2 {
color: hsl(var(--color-neutral-70));
}
.examples {
display: flex;
flex-direction: column;
li {
padding: 0.59375rem 0.5rem;
button {
&:hover {
opacity: 0.75;
}
i {
color: hsl(var(--color-neutral-70));
}
}
}
}
}
.footer {
@@ -229,4 +233,35 @@
background-color: hsl(var(--color-neutral-150));
}
}
.experimental {
display: flex;
padding: 0.09375rem 0.25rem;
align-items: center;
color: var(--light-neutrals-30, #e8e9f0);
text-align: center;
font-family: Inter;
font-size: 0.625rem;
font-style: normal;
font-weight: 500;
line-height: 150%; /* 0.9375rem */
letter-spacing: 0.075rem;
text-transform: uppercase;
background: rgba(240, 46, 101, 0.24);
--border-gradient: linear-gradient(
to bottom,
rgba(240, 46, 101, 0.48) 0%,
rgba(240, 46, 101, 0) 150%
)
border-box;
--border-size: 0.03rem;
--border-radius: 0.25rem;
border-radius: var(--border-radius);
}
:global(.theme-light) .experimental {
color: rgba(240, 46, 101, 1);
}
</style>
+2 -2
View File
@@ -4,7 +4,7 @@
<script lang="ts">
import { debounce } from '$lib/helpers/debounce';
import { isMac } from '$lib/helpers/platform';
import { commands, searchers, type Command } from '../commands';
import { commands, searchers, type Command, isKeyedCommand } from '../commands';
import Template from './template.svelte';
let search = '';
@@ -69,7 +69,7 @@
{#if hasAlt(command)}
<kbd class="kbd"> {isMac() ? '⌥' : 'Alt'} </kbd>
{/if}
{#if 'keys' in command}
{#if isKeyedCommand(command)}
{#each command.keys as key, i}
{@const hasNext = command.keys.length - 1 !== i}
+77 -13
View File
@@ -4,13 +4,13 @@
// This is the template for all panels used in the command center.
// Use this component when you want to create a new panel.
import { tick } from 'svelte';
import { createEventDispatcher, tick } from 'svelte';
import { getCommandCenterCtx } from '../commandCenter.svelte';
import { clearSubPanels, popSubPanel, subPanels } from '../subPanels';
type Option = $$Generic<Command>;
type Option = $$Generic<Omit<Command, 'group'> & { group?: string }>;
export let options: Option[] | null = null;
export let search = '';
export let searchPlaceholder = 'Search...';
@@ -18,6 +18,7 @@
export let clearOnCallback = true;
let selected = 0;
let usingKeyboard = false;
let contentEl: HTMLElement;
async function triggerOption(option: Option) {
@@ -28,8 +29,25 @@
}
}
const dispatch = createEventDispatcher<{
keydown: {
originalEvent: KeyboardEvent;
cancel: () => void;
key: string;
};
}>();
function handleKeyDown(event: KeyboardEvent) {
if (!open) return;
usingKeyboard = true;
let canceled = false;
dispatch('keydown', {
originalEvent: event,
cancel: () => (canceled = true),
key: event.key
});
if (canceled) return;
if (options) {
if (event.key === 'ArrowDown') {
@@ -90,8 +108,9 @@
}
}
$: {
options;
$: if (selected > options?.length - 1) {
selected = options?.length - 1;
} else if (usingKeyboard && selected < 0 && options?.length) {
selected = 0;
}
@@ -173,13 +192,30 @@
triggerOption(option);
};
const getOptionFocusHandler = (option: IndexedOption) => () => {
selected = option.index;
const getOptionFocusHandler =
(option: IndexedOption, hover = false) =>
() => {
selected = option.index;
usingKeyboard = hover ? false : usingKeyboard;
};
const getOptionBlurHandler = () => () => {
selected = -1;
usingKeyboard = false;
};
const castOption = (option: IndexedOption) => option as Option;
$: breadcrumbs = $subPanels.filter((panel) => panel.name !== 'root').map((panel) => panel.name);
function getBreadcrumbs(subPanels: typeof $subPanels) {
return subPanels.filter((panel) => panel.name !== 'root').map((panel) => panel.name);
}
let breadcrumbs = getBreadcrumbs($subPanels);
// Avoid clearing subpanels before the closing transition is finished
$: if ($subPanels.length) {
breadcrumbs = getBreadcrumbs($subPanels);
}
const handleCrumbClick = (index: number) => {
if (index === breadcrumbs.length - 1) {
@@ -262,7 +298,8 @@
<button
class="option"
on:click={getOptionClickHandler(item)}
on:mouseover={getOptionFocusHandler(item)}
on:mouseover={getOptionFocusHandler(item, true)}
on:mouseleave={getOptionBlurHandler()}
on:focus={getOptionFocusHandler(item)}>
<slot name="option" option={castOption(item)}>
<div class="u-flex u-gap-8 u-cross-center">
@@ -350,6 +387,9 @@
--result-bg: hsl(var(--color-neutral-10));
--footer-bg: linear-gradient(180deg, #fff 0%, #e8e9f0 100%);
--icon-color: hsl(var(--color-neutral-50));
--label-color: hsl(var(--color-neutral-100));
}
:global(.theme-dark) .card {
@@ -364,15 +404,19 @@
--result-bg: hsl(var(--color-neutral-200));
--footer-bg: linear-gradient(180deg, #1b1b28 0%, #282a3b 100%);
--icon-color: hsl(var(--color-neutral-70));
--label-color: hsl(var(--color-neutral-30));
}
// Elements
.card {
display: flex;
flex-direction: column;
width: var(--command-panel-width, 42.5rem);
width: var(--width, 42.5rem);
max-width: 100%;
max-height: var(--command-panel-max-height, 32rem);
min-height: var(--min-height);
max-height: var(--max-height, 32rem);
overflow: hidden;
padding: 0;
@@ -388,7 +432,7 @@
backdrop-filter: blur(6px);
&.fullheight {
height: var(--command-panel-max-height, 32rem);
height: var(--max-height, 32rem);
}
:global(.kbd) {
@@ -458,6 +502,8 @@
position: relative;
z-index: 10;
font-size: 10px !important;
&:not(:first-child) {
margin-block-start: 1rem;
}
@@ -470,17 +516,35 @@
position: absolute;
inset: 0;
background-color: var(--result-bg);
border-radius: 0.75rem;
border-radius: 0.5rem;
translate: 0 -1px;
}
.option {
padding: 0.5rem 0.75rem;
padding: 0.5rem 9.5px;
font-size: 14px;
position: relative;
z-index: 10;
width: 100%;
box-shadow: none !important;
color: var(--label-color);
:global(i[class^='icon-']) {
font-size: 1rem !important;
width: 1rem !important;
height: 1rem !important;
color: var(--icon-color);
position: relative;
}
:global(i[class^='icon-']::before) {
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
}
}
&.nested {
+2 -1
View File
@@ -4,8 +4,9 @@
export let name: string;
export let size: number;
export let background: string | undefined = undefined;
$: src = sdk.forConsole.avatars.getInitials(name, size * 2, size * 2).toString();
$: src = sdk.forConsole.avatars.getInitials(name, size * 2, size * 2, background).toString();
</script>
<Avatar {name} {size} {src} />
+11 -3
View File
@@ -29,6 +29,7 @@
export let withLineNumbers = false;
export let withCopy = false;
export let noMargin = false;
export let allowScroll = false;
Prism.plugins.customClass.prefix('prism-');
@@ -56,8 +57,10 @@
</Copy>
{/if}
</div>
<pre class={`language-${language}`} class:line-numbers={withLineNumbers}><code
>{code}</code></pre>
<pre
class:with-scroll={allowScroll}
class={`language-${language}`}
class:line-numbers={withLineNumbers}><code>{code}</code></pre>
</section>
<style lang="scss" global>
@@ -76,6 +79,11 @@
}
}
.with-scroll {
height: 100%;
overflow: auto;
}
code,
pre {
&[class*='language-'] {
@@ -108,7 +116,7 @@
:not(pre) > code[class*='language-'],
pre[class*='language-'] {
background: hsl(var(--p-box-background-color));
padding: 0;
padding-block-start: 4%;
margin: 0;
}
.prism-token {
+7 -4
View File
@@ -4,6 +4,7 @@
import Dark from '$lib/images/search-dark.svg';
import PaginationInline from './paginationInline.svelte';
export let hidePagination = false;
export let hidePages = false;
</script>
@@ -18,7 +19,9 @@
</div>
</article>
<div class="u-flex u-margin-block-start-32 u-main-space-between">
<p class="text">Total results: 0</p>
<PaginationInline limit={1} offset={0} sum={0} {hidePages} />
</div>
{#if !hidePagination}
<div class="u-flex u-margin-block-start-32 u-main-space-between">
<p class="text">Total results: 0</p>
<PaginationInline limit={1} offset={0} sum={0} {hidePages} />
</div>
{/if}
+1 -2
View File
@@ -245,8 +245,7 @@
}
</script>
<Modal bind:show onSubmit={create} size="big">
<svelte:fragment slot="header">Create Event</svelte:fragment>
<Modal title="Create event" bind:show onSubmit={create} size="big">
<slot />
<div>
<p class="u-text">Choose a service</p>
@@ -21,6 +21,8 @@
border-radius: 0.5rem;
padding: 0.75rem 1rem;
width: clamp(0px, calc(100vw - 4rem), 32.5rem);
}
:global(.theme-dark) .floating-action-bar {
+1
View File
@@ -59,3 +59,4 @@ export { default as EyebrowHeading } from './eyebrowHeading.svelte';
export { default as SvgIcon } from './svgIcon.svelte';
export { default as MigrationBox } from './migrationBox.svelte';
export { default as FloatingActionBar } from './floatingActionBar.svelte';
export { default as LoadingDots } from './loadingDots.svelte';
+56
View File
@@ -0,0 +1,56 @@
<!-- Loading dots -->
<script>
let className = '';
export { className as class };
</script>
<div class="loading-dots {className}">
<div class="dot" />
<div class="dot" />
<div class="dot" />
</div>
<style>
.loading-dots {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
gap: 0.25rem;
padding-inline: 0.5rem;
}
.dot {
width: 0.25rem;
height: 0.25rem;
border-radius: 50%;
background-color: hsl(var(--p-body-text-color));
animation: loading-dots 1s infinite ease-in-out;
}
.dot:nth-child(1) {
animation-delay: 0.1s;
}
.dot:nth-child(2) {
animation-delay: 0.2s;
}
.dot:nth-child(3) {
animation-delay: 0.3s;
}
@keyframes loading-dots {
0% {
transform: translateY(0.25rem);
}
25% {
transform: translateY(0rem);
}
50%,
100% {
transform: translateY(0.25rem);
}
}
</style>
+11 -1
View File
@@ -15,6 +15,8 @@
export let onSubmit: (e: SubmitEvent) => Promise<void> | void = function () {
return;
};
export let title = '';
export let description = '';
let dialog: HTMLDialogElement;
let alert: HTMLElement;
@@ -102,8 +104,11 @@
<span class={`icon-${icon}`} aria-hidden="true" />
</div>
{/if}
<h4 class="modal-title heading-level-5">
<slot name="header" />
<slot name="title">
{title}
</slot>
</h4>
</div>
{#if closable}
@@ -122,6 +127,11 @@
</button>
{/if}
</div>
<p>
<slot name="description">
{description}
</slot>
</p>
</header>
<div class="modal-content">
{#if error}
+1 -2
View File
@@ -25,8 +25,7 @@
$: disabled = !value || $groups.has(value);
</script>
<Modal bind:show on:close={reset} onSubmit={create}>
<svelte:fragment slot="header">Custom permission</svelte:fragment>
<Modal title="Custom permission" bind:show on:close={reset} onSubmit={create}>
<p class="text">
Custom permissions allow you to grant access to specific users or teams using their ID and
role.
+1 -2
View File
@@ -61,8 +61,7 @@
}
</script>
<Modal bind:show onSubmit={create} on:close={reset} size="big">
<svelte:fragment slot="header">Select teams</svelte:fragment>
<Modal title="Select teams" bind:show onSubmit={create} on:close={reset} size="big">
<p class="text">
Grant access to any member of a specific team. To grant access to team members with specific
roles, you will need to set a <button
+1 -2
View File
@@ -61,8 +61,7 @@
}
</script>
<Modal bind:show onSubmit={create} on:close={reset} size="big">
<svelte:fragment slot="header">Select users</svelte:fragment>
<Modal title="Select users" bind:show onSubmit={create} on:close={reset} size="big">
<p class="text">Grant access to any authenticated or anonymous user.</p>
<InputSearch
autofocus
+6 -2
View File
@@ -3,6 +3,7 @@
import { getContext, hasContext } from 'svelte';
import { readable } from 'svelte/store';
import type { FormContext } from './form.svelte';
import { multiAction, type MultiActionArray } from '$lib/actions/multi-actions';
export let submit = false;
export let secondary = false;
@@ -19,6 +20,7 @@
export let event: string = null;
let classes: string = undefined;
export { classes as class };
export let actions: MultiActionArray = [];
const isSubmitting = hasContext('form')
? getContext<FormContext>('form').isSubmitting
@@ -59,7 +61,8 @@
target={external ? '_blank' : ''}
rel={external ? 'noopener noreferrer' : ''}
class={resolvedClasses}
aria-label={ariaLabel}>
aria-label={ariaLabel}
use:multiAction={actions}>
<slot />
</a>
{:else}
@@ -69,7 +72,8 @@
disabled={internalDisabled}
class={resolvedClasses}
aria-label={ariaLabel}
type={submit === false ? 'button' : undefined}>
type={submit === false ? 'button' : undefined}
use:multiAction={actions}>
<slot />
</button>
{/if}
+1 -1
View File
@@ -11,7 +11,7 @@
export let required = false;
export let disabled = false;
let element: HTMLInputElement;
export let element: HTMLInputElement | undefined = undefined;
let error: string;
const handleInvalid = (event: Event) => {
+18 -19
View File
@@ -40,25 +40,24 @@
on:invalid={handleInvalid} />
<div class="choice-item-content">
<div class="u-flex u-cross-center u-gap-4">
<h6 class:u-hide={!showLabel} class="choice-item-title">
{label}
</h6>
{#if tooltip}
<button class="tooltip" aria-label="variables info">
<span
class="icon-info"
aria-hidden="true"
style="font-size: var(--icon-size-small)" />
<span class="tooltip-popup" role="tooltip">
<p class="text">
{tooltip}
</p>
</span>
</button>
{/if}
</div>
{#if $$slots}
<h6 class:u-hide={!showLabel} class="choice-item-title">
{label}
</h6>
{#if tooltip}
<button class="tooltip" aria-label="variables info">
<span
class="icon-info"
aria-hidden="true"
style="font-size: var(--icon-size-small)" />
<span class="tooltip-popup" role="tooltip">
<p class="text">
{tooltip}
</p>
</span>
</button>
{/if}
{#if $$slots.default}
<p class="choice-item-paragraph"><slot /></p>
{/if}
</div>
+3 -1
View File
@@ -22,7 +22,9 @@
const hasInvalidExt = Array.from(value).some((file) => {
const fileExtension = file.name.split('.').pop();
return !allowedFileExtensions.includes(fileExtension);
return allowedFileExtensions?.length
? !allowedFileExtensions.includes(fileExtension)
: false;
});
if (hasInvalidExt) {
error = 'Invalid file extension';
+1 -1
View File
@@ -28,7 +28,7 @@
{/if}
{#if tooltip}
<button class="tooltip" aria-label="input tooltip">
<button type="button" on:click|preventDefault class="tooltip" aria-label="input tooltip">
<span class="icon-info" aria-hidden="true" style="font-size: var(--icon-size-small)" />
<span class="tooltip-popup" role="tooltip">
<p class="text">
+25 -14
View File
@@ -6,24 +6,35 @@
export let id: string;
export let selectedIds: string[] = [];
let el: HTMLInputElement;
const handleClick = (e: Event) => {
// Prevent the link from being followed
e.preventDefault();
if (!isHTMLInputElement(el)) return;
selectedIds = toggle(selectedIds, id);
// Hack to make sure the checkbox is checked, independent of the
// preventDefault() call above
window.setTimeout(() => {
el.checked = selectedIds.includes(id);
});
};
</script>
<TableCell>
<div class="touch-area" on:click={handleClick} on:keypress={handleClick} />
<InputCheckbox
bind:element={el}
id="select-{id}"
value={selectedIds.includes(id)}
on:click={(e) => {
// Prevent the link from being followed
e.preventDefault();
const el = e.currentTarget;
if (!isHTMLInputElement(el)) return;
selectedIds = toggle(selectedIds, id);
// Hack to make sure the checkbox is checked, independent of the
// preventDefault() call above
window.setTimeout(() => {
el.checked = selectedIds.includes(id);
});
}} />
on:click={handleClick} />
</TableCell>
<style lang="scss">
.touch-area {
position: absolute;
inset: 0;
}
</style>
+2
View File
@@ -1,12 +1,14 @@
<script lang="ts">
export let noMargin = false;
export let noStyles = false;
export let style = '';
</script>
<div
class="table is-selected-columns-mobile"
class:u-margin-block-start-32={!noMargin}
class:is-remove-outer-styles={noStyles}
{style}
role="table"
data-private>
<slot />
+7 -1
View File
@@ -55,7 +55,7 @@ export function empty(arr: unknown[]): boolean {
export function at(array: readonly [], index: number): undefined;
export function at<T>(array: readonly T[], index: number): T;
export function at<T>(array: readonly T[] | [], index: number): T | undefined {
const len = array.length;
const len = array?.length;
if (!len) return undefined;
if (index < 0) index += len;
@@ -96,3 +96,9 @@ export function toggle<T>(arr: T[], elem: T): T[] {
arr.push(elem);
return arr;
}
// TODO: metric type is wrong
export function total(set: Array<number>): number {
if (!set) return 0;
return set.reduce((prev, curr) => prev + curr, 0);
}
+6
View File
@@ -0,0 +1,6 @@
// Easier wrapper over mutation observer, which takes an element, a callback, and returns an unsubscribe fn
export function observeElement(el: HTMLElement, callback: MutationCallback): () => void {
const observer = new MutationObserver(callback);
observer.observe(el, { childList: true, subtree: true });
return () => observer.disconnect();
}
+11
View File
@@ -37,6 +37,17 @@ export function deepEqual<T>(obj1: T, obj2: T): boolean {
return true;
}
/**
* Creates a deep clone of the given object. This function uses the JSON methods for cloning,
* so it may not be suitable for objects with functions, symbols, or other non-JSON-safe data.
*
* @param obj the object to be cloned
* @returns a deep clone of the provided object
*/
export function deepClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
export type DeepObj<T> = {
[K in keyof T]: T[K] extends object ? DeepObj<T[K]> : T[K];
};
+8
View File
@@ -40,3 +40,11 @@ export function camelize(str: string): string {
return firstChar.toLowerCase();
});
}
const formatter = Intl.NumberFormat('en', {
notation: 'compact'
});
export function formatNum(number: number): string {
return formatter.format(number);
}
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
export let overlapCover = false;
export let size: 'small' | 'medium' | 'large' | 'xl' = null;
export let size: 'small' | 'medium' | 'large' | 'xl' | 'xxl' | 'xxxl' = null;
$: style = size
? `--p-container-max-size: var(--container-max-size, var(--container-size-${size}))`
+14 -1
View File
@@ -3,10 +3,12 @@
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Submit, trackEvent } from '$lib/actions/analytics';
import { tooltip } from '$lib/actions/tooltip';
import { toggleCommandCenter } from '$lib/commandCenter/commandCenter.svelte';
import { AvatarInitials, DropList, DropListItem, DropListLink } from '$lib/components';
import { Feedback } from '$lib/components/feedback';
import Button from '$lib/elements/forms/button.svelte';
import { isMac } from '$lib/helpers/platform';
import AppwriteLogo from '$lib/images/appwrite-gray-light.svg';
import DarkMode from '$lib/images/mode/dark-mode.svg';
import LightMode from '$lib/images/mode/light-mode.svg';
@@ -95,7 +97,18 @@
class="button is-small is-text">
<span class="text">Support</span>
</a>
<Button text class="is-small" on:click={toggleCommandCenter}>
<Button
actions={[
(node) => {
return tooltip(node, {
content: isMac() ? '⌘ + K' : 'Ctrl + K',
placement: 'bottom'
});
}
]}
text
class="is-small"
on:click={toggleCommandCenter}>
<i class="icon-search" />
</Button>
</nav>
+10 -12
View File
@@ -1,9 +1,7 @@
<script lang="ts">
import { toLocaleDateTime } from '$lib/helpers/date';
import { log } from '$lib/stores/logs';
import { Alert, Card, Code, Heading, Id, SvgIcon, Tab, Tabs } from '../components';
import { base } from '$app/paths';
import { app } from '$lib/stores/app';
import { Card, Code, Heading, Id, SvgIcon, Tab, Tabs } from '../components';
import { calculateTime } from '$lib/helpers/timeConversion';
import {
TableBody,
@@ -14,7 +12,6 @@
TableScroll
} from '$lib/elements/table';
import { beforeNavigate } from '$app/navigation';
import Table from '$lib/elements/table/table.svelte';
import { Pill } from '$lib/elements';
let selectedRequest = 'parameters';
@@ -56,6 +53,7 @@
$: if (execution?.errors) {
selectedResponse = 'errors';
}
$: host = execution?.requestHeaders?.find((header) => header.name === 'host')?.value;
</script>
<svelte:window on:keydown={handleKeydown} />
@@ -103,11 +101,11 @@
{toLocaleDateTime(execution.$createdAt)}
</time>
</li>
{#if execution?.requestHeaders?.host}
{#if host}
<li class="text">
<b>Host</b>
<b>Host:</b>
<span>
{execution.requestHeaders.host}
{host}
</span>
</li>
{/if}
@@ -203,7 +201,7 @@
use
<b>context.log()</b>.
<a
href="http://#"
href="https://appwrite.io/docs/functions-develop#logging"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more</a
@@ -241,7 +239,7 @@
header data in the Logs tab, use
<b>context.log()</b>.
<a
href="http://#"
href="https://appwrite.io/docs/functions-develop#logging"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more</a
@@ -253,7 +251,7 @@
and privacy. To display body data in the Logs tab, use
<b>context.log()</b>.
<a
href="http://#"
href="https://appwrite.io/docs/functions-develop#logging"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more</a
@@ -331,7 +329,7 @@
header data in the Logs tab, use
<b>context.log()</b>.
<a
href="http://#"
href="https://appwrite.io/docs/functions-develop#logging"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more</a
@@ -343,7 +341,7 @@
and privacy. To display body data in the Logs tab, use
<b>context.log()</b>.
<a
href="http://#"
href="https://appwrite.io/docs/functions-develop#logging"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more</a
+3 -1
View File
@@ -146,7 +146,9 @@
class:is-cloud={isCloud}
style:--url={`url(${AppwriteCloudBg})`}
style:--url-mobile={`url(${AppwriteCloudBgMobile})`}>
<div class="container u-flex u-flex-vertical u-cross-center" class:cloud-contents={isCloud}>
<div
class="container u-flex u-flex-vertical u-cross-center u-main-center"
class:cloud-contents={isCloud}>
{#if isCloud}
<a class="mobile-logo is-only-mobile" href={user ? '/console' : '/'}>
<img
+11 -10
View File
@@ -26,7 +26,6 @@
const dispatch = createEventDispatcher();
let currentStep = 1;
let showExitModal = false;
function handleKeydown(event: KeyboardEvent) {
@@ -55,8 +54,8 @@
function handleStepClick(e: CustomEvent<number>) {
const step = e.detail;
if (step < currentStep) {
currentStep = step;
if (step < $wizard.step) {
$wizard.step = step;
}
}
@@ -79,12 +78,14 @@
dispatch('finish');
} else {
trackEvent('wizard_next');
currentStep++;
$wizard.step++;
}
}
$: sortedSteps = [...steps].sort(([a], [b]) => (a > b ? 1 : -1));
$: isLastStep = currentStep === steps.size;
$: isLastStep = $wizard.step === steps.size;
$: console.log({ sortedSteps, current: $wizard.step });
</script>
<svelte:window on:keydown={handleKeydown} />
@@ -114,7 +115,7 @@
text: label,
optional
}))}
{currentStep} />
currentStep={$wizard.step} />
</aside>
<div class="wizard-media">
{#if $wizard.media}
@@ -124,24 +125,24 @@
<div class="wizard-main">
<Form noStyle onSubmit={submit}>
{#each sortedSteps as [step, { component }]}
{#if currentStep === step}
{#if $wizard.step === step}
<svelte:component this={component} />
{/if}
{/each}
<div class="form-footer">
<div class="u-flex u-main-end u-gap-12">
{#if !isLastStep && sortedSteps[currentStep - 1][1].optional}
{#if !isLastStep && sortedSteps[$wizard.step - 1]?.[1]?.optional}
<Button text on:click={() => dispatch('finish')}>
Skip optional steps
</Button>
{/if}
{#if currentStep === 1}
{#if $wizard.step === 1}
<Button secondary on:click={handleExit}>Cancel</Button>
{:else}
<Button
secondary
on:click={() => currentStep--}
on:click={() => $wizard.step--}
on:click={() => trackEvent('wizard_back')}>Back</Button>
{/if}
+1 -2
View File
@@ -11,8 +11,7 @@
}
</script>
<Modal bind:show onSubmit={handleSubmit} icon="exclamation" state="warning">
<svelte:fragment slot="header">Exit Process</svelte:fragment>
<Modal title="Exit Process" bind:show onSubmit={handleSubmit} icon="exclamation" state="warning">
<p>
Are you sure you want to exit from <slot />? All data will be deleted. This action is
irreversible.
+1 -1
View File
@@ -33,12 +33,12 @@
</script>
<Modal
title="Delete domain"
bind:show={showDelete}
onSubmit={deleteDomain}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete Domain</svelte:fragment>
{#if selectedDomain}
<p data-private>
Are you sure you want to delete <b>{selectedDomain.domain}</b>? You will no longer be
+39 -23
View File
@@ -26,6 +26,7 @@
import Create from './create.svelte';
import Delete from './delete.svelte';
import Retry from './wizard/retry.svelte';
import { Pill } from '$lib/elements';
export let rules: Models.ProxyRuleList;
export let type: ProxyTypes;
@@ -84,40 +85,49 @@
<span class="icon-external-link" aria-hidden="true" />
</span>
</TableCellLink>
<TableCell title="Status">
<TableCell title="Verification">
{#if domain.status === 'created'}
<div class="u-flex u-gap-8 u-cross-center">
<span
class="icon-x-circle u-color-text-danger"
aria-hidden="true" />
<span class="u-text">Failed</span>
<button on:click={() => openRetry(domain)}>
<Pill danger>
<span
class="icon-exclamation-circle u-color-text-danger"
aria-hidden="true" />
<span class="u-text">Failed</span>
</Pill>
<button type="button" on:click={() => openRetry(domain)}>
<span class="link">Retry</span>
</button>
</div>
{:else}
<div class="u-flex u-gap-8 u-cross-center">
<Pill success>
<span
class="icon-check-circle u-color-text-success"
aria-hidden="true" />
<p class="u-stretch">Verified</p>
</div>
<p class="text">Verified</p>
</Pill>
{/if}
</TableCell>
<TableCell title="Name">
<TableCell title="Cartificate">
{#if domain.status === 'unverified'}
<div class="u-flex u-gap-8 u-cross-center">
<span
class="icon-x-circle u-color-text-danger"
aria-hidden="true" />
<p class="u-stretch">Failed</p>
<Pill danger>
<span
class="icon-exclamation-circle u-color-text-danger"
aria-hidden="true" />
<span class="u-text">Failed</span>
</Pill>
<button type="button" on:click={() => openRetry(domain)}>
<span class="link">Retry</span>
</button>
</div>
{:else if domain.status === 'verified'}
<div class="u-flex u-gap-8 u-cross-center">
<span
class="icon-check-circle u-color-text-success"
aria-hidden="true" />
<span>Generated</span>
<Pill success>
<span
class="icon-check-circle u-color-text-success"
aria-hidden="true" />
<span>Generated</span>
</Pill>
{#if domain.renewAt}
<span class="u-text-color-gray">
Auto-renewal: {toLocaleDate(domain.renewAt)}
@@ -126,8 +136,10 @@
</div>
{:else}
<div class="u-flex u-gap-8 u-cross-center">
<span class="icon-clock u-text-color-gray" aria-hidden="true" />
<p class="u-stretch">Waiting to run</p>
<Pill>
<span class="icon-clock u-text-color-gray" aria-hidden="true" />
<p class="text">Waiting to run</p>
</Pill>
</div>
{/if}
</TableCell>
@@ -145,7 +157,9 @@
</Button>
<svelte:fragment slot="list">
<DropListItem icon="refresh" on:click={() => openRetry(domain, i)}>
Retry
{domain.status === 'unverfied'
? 'Retry generation'
: 'Retry verification'}
</DropListItem>
<DropListItem
icon="trash"
@@ -172,8 +186,10 @@
{/if}
<Delete bind:showDelete bind:selectedDomain {dependency} />
<Modal bind:show={showRetry} headerDivider={false} bind:error={retryError}>
<svelte:fragment slot="header">Retry verification</svelte:fragment>
<Modal bind:show={showRetry} headerDivider={false} bind:error={retryError} size="big">
<svelte:fragment slot="title">
Retry {$domain.status === 'unverfied' ? 'certificate generation' : 'verification'}
</svelte:fragment>
<Retry on:error={(e) => (retryError = e.detail)} />
<svelte:fragment slot="footer">
<Button text on:click={() => (showRetry = false)}>Close</Button>
@@ -18,7 +18,7 @@
$: cnameValue = $domain.domain.replace('.' + registerable, '');
</script>
<Table noMargin noStyles>
<Table noMargin noStyles style="--p-table-bg-color: var(--transparent);">
<TableHeader>
<TableCellHead>Type</TableCellHead>
<TableCellHead>Name</TableCellHead>
+46 -23
View File
@@ -4,7 +4,7 @@
import { domain } from './store';
import CnameTable from './cnameTable.svelte';
import { createEventDispatcher } from 'svelte';
import { Box } from '$lib/components';
import { Box, Code, Trim } from '$lib/components';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { addNotification } from '$lib/stores/notifications';
@@ -20,7 +20,10 @@
$domain = await sdk.forProject.proxy.updateRuleVerification($domain.$id);
invalidate(Dependencies.FUNCTION_DOMAINS);
addNotification({
message: 'Domain has been verified successfully',
message:
$domain.status === 'unverfied'
? 'Domain certificate has been generated successfully'
: 'Domain has been verified successfully',
type: 'success'
});
trackEvent(Submit.DomainUpdateVerification);
@@ -33,24 +36,44 @@
}
</script>
<Box radius="small">
<div class="u-flex u-gap-8 u-cross-center">
<span class="icon-exclamation-circle u-color-text-danger" aria-hidden="true" />
<p class="u-stretch">Verification failed</p>
<Button secondary on:click={retry} disabled={retrying}>
{#if retrying}
<div class="loader u-text-color-gray" />
{:else}
Retry
{/if}
</Button>
</div>
<p class="text u-margin-block-start-24">
In order to continue, set the following record on your DNS provider. Find a list of domain
providers and their DNS settings in our documentation. Changes may take time to be
effective.
</p>
<div class="u-margin-block-start-24">
<CnameTable />
</div>
</Box>
{#if $domain.status === 'created'}
<Box radius="small">
<div class="u-flex u-gap-8 u-cross-center">
<span class="icon-exclamation-circle u-color-text-danger" aria-hidden="true" />
<p class="u-stretch">Verification failed</p>
<Button secondary on:click={retry} disabled={retrying}>
{#if retrying}
<div class="loader u-text-color-gray" />
{:else}
Retry
{/if}
</Button>
</div>
<p class="text u-margin-block-start-24">
In order to continue, set the following record on your DNS provider. Find a list of
domain providers and their DNS settings in our documentation. Changes may take time to
be effective.
</p>
<div class="u-margin-block-start-24">
<CnameTable />
</div>
</Box>
{:else if $domain.status === 'unverified'}
<Trim alternativeTrim><b>{$domain.domain}</b></Trim>
<Box radius="small">
<div class="u-flex u-gap-8 u-cross-center">
<span class="icon-exclamation-circle u-color-text-danger" aria-hidden="true" />
<p class="u-stretch">Generation failed</p>
<Button secondary on:click={retry} disabled={retrying}>
{#if retrying}
<div class="loader u-text-color-gray" />
{:else}
Retry
{/if}
</Button>
</div>
{#if $domain?.logs}
<Code language="sh" withCopy code={$domain.logs} />
{/if}
</Box>
{/if}
+8 -10
View File
@@ -17,16 +17,14 @@
<svelte:fragment slot="title">{$domain.domain}</svelte:fragment>
<div class="boxes-wrapper u-margin-block-start-24">
<div class="box">
{#if $domain.status === 'created'}
<Retry on:error={onRetryError} />
{:else}
<div class="u-flex u-gap-8 u-cross-center">
<span class="icon-check u-color-text-success" aria-hidden="true" />
<p class="u-stretch">Domain verified</p>
</div>
{/if}
</div>
{#if $domain.status === 'created'}
<Retry on:error={onRetryError} />
{:else}
<div class="u-flex u-gap-8 u-cross-center">
<span class="icon-check u-color-text-success" aria-hidden="true" />
<p class="u-stretch">Domain verified</p>
</div>
{/if}
<div class="box">
<div class="u-flex u-gap-8 u-cross-center">
{#if $domain.status === 'verifying'}
+9 -5
View File
@@ -2,21 +2,23 @@ import { getProjectId } from '$lib/helpers/project';
import { VARS } from '$lib/system';
import {
Account,
Assistant,
Avatars,
Client,
Console,
Databases,
Functions,
Health,
Locale,
Migrations,
Projects,
Project,
Project as ProjectApi,
Projects,
Proxy,
Storage,
Teams,
Users,
Project as ProjectApi,
Vcs,
Proxy
Vcs
} from '@appwrite.io/console';
const endpoint = VARS.APPWRITE_ENDPOINT ?? `${globalThis?.location?.origin}/v1`;
@@ -63,7 +65,9 @@ export const sdk = {
projects: new Projects(clientConsole),
teams: new Teams(clientConsole),
users: new Users(clientConsole),
migrations: new Migrations(clientConsole)
migrations: new Migrations(clientConsole),
console: new Console(clientConsole),
assistant: new Assistant(clientConsole)
},
get forProject() {
const projectId = getProjectId();
+18 -2
View File
@@ -9,6 +9,7 @@ export type WizardStore = {
cover?: typeof SvelteComponent;
interceptor?: () => Promise<void>;
nextDisabled: boolean;
step: number;
};
function createWizardStore() {
@@ -18,7 +19,8 @@ function createWizardStore() {
cover: null,
interceptor: null,
media: null,
nextDisabled: false
nextDisabled: false,
step: 1
});
return {
@@ -30,6 +32,7 @@ function createWizardStore() {
n.component = component;
n.interceptor = null;
n.media = media;
n.step = 1;
n.cover = null;
trackEvent('wizard_start');
return n;
@@ -52,6 +55,7 @@ function createWizardStore() {
n.component = null;
n.interceptor = null;
n.media = null;
n.step = 1;
n.cover = null;
return n;
}),
@@ -59,7 +63,19 @@ function createWizardStore() {
update((n) => {
n.cover = component;
return n;
})
}),
updateStep: (cb: (prevStep: number) => number) => {
update((n) => {
n.step = cb(n.step);
return n;
});
},
setStep: (step: number) => {
update((n) => {
n.step = step;
return n;
});
}
};
}
@@ -19,7 +19,7 @@
disabled={!!$templateConfig.generateKey} />
<Helper type="neutral">
This API key will allow you to interact with the Appwrite server APIs. <a
href="http://#"
href="https://appwrite.io/docs/keys"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more</a
@@ -1,7 +1,7 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/stores';
import { EmptySearch, PaginationInline } from '$lib/components';
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';
@@ -173,10 +173,10 @@
{/if}
{/each}
</ul>
<div class="u-flex u-margin-block-start-32 u-main-space-between">
<!-- <div class="u-flex u-margin-block-start-32 u-main-space-between">
<p class="text">Total results: {response?.length}</p>
<PaginationInline {limit} bind:offset sum={response?.length} />
</div>
</div> -->
{:else if search}
<EmptySearch hidePages>
<div class="common-section">
@@ -29,7 +29,7 @@
$installation.$id,
$repository.id,
$choices.branch,
$choices.silentMode,
$choices.silentMode || undefined,
$choices.rootDir
);
trackEvent(Submit.FunctionConnectRepo, {
@@ -59,11 +59,11 @@
const stepsComponents: WizardStepsType = new Map();
stepsComponents.set(1, {
label: 'Select repository',
label: 'Repository',
component: SelectRepository
});
stepsComponents.set(2, {
label: 'Git configuration',
label: 'Git',
component: GitConfiguration
});
</script>
+6 -3
View File
@@ -48,7 +48,7 @@
const quickStart = marketplace.find((template) => template.id === 'starter');
const templates = marketplace.filter((template) => template.id !== 'starter').slice(0, 3);
function connect(event: CustomEvent<Models.Repository>) {
function connect(event: CustomEvent<Models.ProviderRepository>) {
repository.set(event.detail);
wizard.start(CreateGit);
}
@@ -82,8 +82,11 @@
class="link"
on:click={() => wizard.start(CreateManual)}>manually</button>
or using the CLI.
<a href="http://#" target="_blank" rel="noopener noreferrer" class="link"
>Learn more</a
<a
href="https://appwrite.io/docs/functions-deploy"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more</a
>.
</p>
</div>
+2 -2
View File
@@ -67,11 +67,11 @@
const stepsComponents: WizardStepsType = new Map();
stepsComponents.set(1, {
label: 'Git configuration',
label: 'Git',
component: GitConfiguration
});
stepsComponents.set(2, {
label: 'Function configuration',
label: 'Configuration',
component: FunctionConfiguration
});
stepsComponents.set(3, {
@@ -81,7 +81,7 @@
const stepsComponents: WizardStepsType = new Map();
stepsComponents.set(1, {
label: 'Template configuration',
label: 'Configuration',
component: TemplateConfiguration
});
stepsComponents.set(2, {
@@ -89,15 +89,15 @@
component: TemplateVariables
});
stepsComponents.set(3, {
label: 'Repository behaviour',
label: 'Connect',
component: RepositoryBehaviour
});
stepsComponents.set(4, {
label: 'Select repository',
label: 'Repository',
component: CreateRepository
});
stepsComponents.set(5, {
label: 'Git configuration',
label: 'Branch',
component: GitConfiguration
});
</script>
@@ -7,7 +7,7 @@
</script>
<WizardStep>
<svelte:fragment slot="title">Function configuration</svelte:fragment>
<svelte:fragment slot="title">Configuration</svelte:fragment>
<svelte:fragment slot="subtitle">
Set your deployment configuration and any build commands here.
</svelte:fragment>
@@ -11,9 +11,9 @@
import Repositories from '../components/repositories.svelte';
import { installation, repository, templateConfig } from '../store';
let selectedInstallationId;
let hasInstallations;
let selectedRepository;
let selectedInstallationId: string;
let hasInstallations: boolean;
let selectedRepository: string;
async function beforeSubmit() {
if (!hasInstallations || !$installation) {
@@ -70,7 +70,7 @@
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Select repository</svelte:fragment>
<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>
@@ -21,7 +21,7 @@
</script>
<WizardStep>
<svelte:fragment slot="title">Function details</svelte:fragment>
<svelte:fragment slot="title">Details</svelte:fragment>
<svelte:fragment slot="subtitle">Create and deploy your function manually.</svelte:fragment>
<FormList>
<InputText
@@ -5,7 +5,7 @@
</script>
<WizardStep>
<svelte:fragment slot="title">Execute access</svelte:fragment>
<svelte:fragment slot="title">Execution permissions</svelte:fragment>
<svelte:fragment slot="subtitle">
Choose who can execute this function using the client API. For more information, check out
the <a
@@ -51,7 +51,7 @@
</script>
<WizardStep>
<svelte:fragment slot="title">Function configuration</svelte:fragment>
<svelte:fragment slot="title">Configuration</svelte:fragment>
<svelte:fragment slot="subtitle">
Set your deployment configuration and any build commands here.
</svelte:fragment>
@@ -3,6 +3,7 @@
import InputSelectSearch from '$lib/elements/forms/inputSelectSearch.svelte';
import { WizardStep } from '$lib/layout';
import { sdk } from '$lib/stores/sdk';
import { sortBranches } from '$routes/console/project-[project]/functions/function-[function]/settings/updateConfiguration.svelte';
import { choices, installation, repository } from '../store';
$choices.rootDir ??= '';
@@ -20,13 +21,7 @@
$installation.$id,
$repository.id
);
const sorted = branches.sort((a, b) => {
if (a.name === 'main' || a.name === 'master') {
return -1;
}
return a.name > b.name ? -1 : 1;
});
const sorted = sortBranches(branches);
$choices.branch = sorted[0]?.name ?? null;
if (!$choices.branch) {
@@ -38,7 +33,7 @@
</script>
<WizardStep>
<svelte:fragment slot="title">Git configuration</svelte:fragment>
<svelte:fragment slot="title">Git</svelte:fragment>
<svelte:fragment slot="subtitle">
Configure the Git repository that will trigger your function deployments when updated.
</svelte:fragment>
@@ -57,6 +52,17 @@
<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
@@ -73,12 +79,7 @@
}}
interactiveOutput
name="branch"
options={branches?.map((branch) => {
return {
value: branch.name,
label: branch.name
};
}) ?? []} />
{options} />
<InputText
id="root"
label="Root directory"
@@ -94,8 +95,8 @@
{/await}
</div>
<p class="text u-margin-block-start-8">
View your configuration in <a
href={$repository.html_url}
Visit your repository on <a
href={`https://github.com/${$repository.organization}/${$repository.name}`}
target="_blank"
rel="noopener noreferrer"
class="link">GitHub</a
@@ -11,18 +11,21 @@
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Repository Behaviour</svelte:fragment>
<svelte:fragment slot="title">Connect</svelte:fragment>
<svelte:fragment slot="subtitle">
Connect function to a new repository or to an existing one within a selected Git
organization.
</svelte:fragment>
<ul class="u-flex u-flex-vertical u-gap-24">
<LabelCard name="test" value="new" bind:group={$templateConfig.repositoryBehaviour}>
<LabelCard name="behaviour" value="new" bind:group={$templateConfig.repositoryBehaviour}>
<svelte:fragment slot="title">Create a new repository</svelte:fragment>
Clone the template and create a new repository in your selected organization.
</LabelCard>
<LabelCard name="test" value="new" bind:group={$templateConfig.repositoryBehaviour}>
<LabelCard
name="behaviour"
value="existing"
bind:group={$templateConfig.repositoryBehaviour}>
<svelte:fragment slot="title">Add to existing repository</svelte:fragment>
Clone the template to an existing repository in your selected organization.
</LabelCard>
@@ -2,8 +2,8 @@
import { WizardStep } from '$lib/layout';
import Repositories from '../components/repositories.svelte';
let hasInstallations;
let selectedRepository;
let hasInstallations: boolean;
let selectedRepository: string;
async function beforeSubmit() {
if (!hasInstallations) {
throw new Error('Please connect a Git provider');
@@ -15,7 +15,7 @@
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Select repository</svelte:fragment>
<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>
@@ -23,7 +23,7 @@
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Environment variables</svelte:fragment>
<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.
+21
View File
@@ -185,4 +185,25 @@
background: var(--separator-color);
}
}
.border-gradient {
position: relative;
}
.border-gradient::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: var(--border-radius);
border: var(--border-size) solid transparent;
background: var(--border-gradient) border-box;
mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
-webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: destination-out;
mask-composite: exclude;
pointer-events: none;
}
</style>
+1 -2
View File
@@ -199,8 +199,7 @@
</div>
</div>
<Modal bind:show={showEmbedCode}>
<svelte:fragment slot="header">Get Embed Code</svelte:fragment>
<Modal title="Get Embed Code" bind:show={showEmbedCode}>
<div class="u-overflow-hidden">
<Code language="html" code={embedCode} noMargin />
</div>
@@ -1,9 +1,9 @@
<script lang="ts">
import { EyebrowHeading } from '$lib/components';
import { Alert, EyebrowHeading } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { deepMap } from '$lib/helpers/object';
import type { WritableValue } from '$lib/helpers/types';
import { sdk } from '$lib/stores/sdk';
import { sdk, type getSdkForProject } from '$lib/stores/sdk';
import { onMount } from 'svelte';
@@ -14,11 +14,11 @@
providerResources,
resourcesToMigrationForm
} from '$lib/stores/migration';
import { addNotification } from '$lib/stores/notifications';
import { wizard } from '$lib/stores/wizard';
export let formData: ReturnType<typeof createMigrationFormStore>;
export let provider: ReturnType<typeof createMigrationProviderStore>;
export let projectSdk: ReturnType<typeof getSdkForProject>;
type ValueOf<T> = T[keyof T];
type FormData = WritableValue<typeof formData>;
@@ -80,12 +80,14 @@
$: version = report?.version || '0.0.0';
let isOpen = false;
let error = false;
onMount(async () => {
isOpen = true;
try {
switch ($provider.provider) {
case 'appwrite': {
const res = await sdk.forProject.migrations.getAppwriteReport(
const res = await projectSdk.migrations.getAppwriteReport(
providerResources.appwrite,
$provider.endpoint,
$provider.projectID,
@@ -95,7 +97,7 @@
break;
}
case 'supabase': {
const res = await sdk.forProject.migrations.getSupabaseReport(
const res = await projectSdk.migrations.getSupabaseReport(
providerResources.supabase,
$provider.endpoint,
$provider.apiKey,
@@ -118,7 +120,7 @@
report = res;
} else if ($provider.serviceAccount) {
// Manual auth
const res = await sdk.forProject.migrations.getFirebaseReport(
const res = await projectSdk.migrations.getFirebaseReport(
providerResources.firebase,
$provider.serviceAccount
);
@@ -128,7 +130,7 @@
break;
}
case 'nhost': {
const res = await sdk.forProject.migrations.getNHostReport(
const res = await projectSdk.migrations.getNHostReport(
providerResources.nhost,
$provider.subdomain,
$provider.region,
@@ -142,10 +144,7 @@
}
} catch (e) {
if (!isOpen) return;
addNotification({
message: e.message,
type: 'error'
});
error = true;
}
return () => {
@@ -154,7 +153,6 @@
});
$: resources = providerResources[$provider.provider];
$: wizard.setNextDisabled(!report);
</script>
@@ -206,6 +204,45 @@
</div>
</div>
{#if report && !isVersionAtLeast(version, '1.4.0')}
<div class="u-margin-block-start-24">
<Alert
type="warning"
isStandalone
buttons={[
{
name: 'Learn more',
method() {
wizard.updateStep((p) => p - 1);
}
}
]}>
<svelte:fragment slot="title">Functions not available for import</svelte:fragment>
To migrate your functions, update the version of the Appwrite instance you're importing from
to a version newer than 1.4
</Alert>
</div>
{/if}
{#if error}
<div class="u-margin-block-start-24">
<Alert
type="error"
isStandalone
buttons={[
{
name: 'Edit credentials',
method() {
wizard.updateStep((p) => p - 1);
}
}
]}>
<svelte:fragment slot="title">Request failed</svelte:fragment>
Please check if your credentials are filled in correctly in the previous step
</Alert>
</div>
{/if}
<ul class="buttons-list u-margin-block-start-32 u-main-end">
<li class="buttons-list-item">
<Button text on:click={deselectAll}>Deselect all</Button>
@@ -216,18 +253,22 @@
</li>
</ul>
<ul class="u-flex u-flex-vertical u-gap-32 u-margin-block-start-16">
<ul class="u-flex u-flex-vertical u-margin-block-start-16">
{#if resources.includes('user')}
<li class="checkbox-field">
<input
type="checkbox"
bind:checked={$formData.users.root}
on:change={handleInputChange('users.root')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Users</span>
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.user ?? '...'}</span>
{#if report?.user !== undefined}
<span class="inline-tag">{report.user}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
@@ -240,10 +281,14 @@
type="checkbox"
bind:checked={$formData.users.teams}
on:change={handleInputChange('users.teams')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Include teams</span>
{#if $provider.provider === 'firebase'}
<span class="inline-tag">{report?.team ?? '...'}</span>
{#if report?.team !== undefined}
<span class="inline-tag">{report.team}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
@@ -260,10 +305,14 @@
type="checkbox"
bind:checked={$formData.databases.root}
on:change={handleInputChange('databases.root')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Databases</span>
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.database ?? '...'}</span>
{#if report?.database !== undefined}
<span class="inline-tag">{report.database}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
@@ -276,10 +325,14 @@
type="checkbox"
bind:checked={$formData.databases.documents}
on:change={handleInputChange('databases.documents')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Include documents</span>
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.document ?? '...'}</span>
{#if report?.document !== undefined}
<span class="inline-tag">{report.document}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
@@ -296,10 +349,14 @@
type="checkbox"
bind:checked={$formData.functions.root}
on:change={handleInputChange('functions.root')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Functions</span>
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.function ?? '...'}</span>
{#if report?.function !== undefined}
<span class="inline-tag">{report.function}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
@@ -341,26 +398,28 @@
type="checkbox"
bind:checked={$formData.storage.root}
on:change={handleInputChange('storage.root')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Storage</span>
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">
{report?.size ? `${report.size.toFixed(2)}MB` : '...'}
</span>
{#if report?.size !== undefined}
<span class="inline-tag">{`${report.size.toFixed(2)}MB`}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
<span>
<p>
Import all buckets
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.bucket ?? '...'}</span>
{#if $provider.provider !== 'firebase' && report?.bucket}
<span class="inline-tag">{report.bucket}</span>
{/if}
and files
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.file ?? '...'}</span>
{#if $provider.provider !== 'firebase' && report?.file}
<span class="inline-tag">{report.file}</span>
{/if}
</span>
</p>
</li>
{/if}
</ul>
@@ -1,11 +1,12 @@
<script lang="ts">
import { WizardStep } from '$lib/layout';
import { getSdkForProject } from '$lib/stores/sdk';
import { formData, provider } from '.';
import { formData, provider, selectedProject } from '.';
import ResourceForm from './resource-form.svelte';
</script>
<WizardStep>
<svelte:fragment slot="title">Select data</svelte:fragment>
<ResourceForm {provider} {formData} />
<ResourceForm {provider} {formData} projectSdk={getSdkForProject($selectedProject)} />
</WizardStep>
@@ -37,11 +37,11 @@
const steps: WizardStepsType = new Map();
steps.set(1, {
label: 'Select project',
label: 'Project',
component: Step1
});
steps.set(2, {
label: 'Select data',
label: 'Resources',
component: Step2
});
</script>
+125 -45
View File
@@ -17,20 +17,53 @@
import { goto } from '$app/navigation';
import { CommandCenter, registerCommands, registerSearchers } from '$lib/commandCenter';
import { AIPanel, OrganizationsPanel, ProjectsPanel } from '$lib/commandCenter/panels';
import { AIPanel } from '$lib/commandCenter/panels';
import { orgSearcher, projectsSearcher } from '$lib/commandCenter/searchers';
import { addSubPanel } from '$lib/commandCenter/subPanels';
import { addNotification } from '$lib/stores/notifications';
import { openMigrationWizard } from './(migration-wizard)';
import { project } from './project-[project]/store';
import { sdk } from '$lib/stores/sdk';
function kebabToSentenceCase(str: string) {
return str
.split('-')
.map((word) => word[0].toUpperCase() + word.slice(1))
.join(' ');
}
let isAssistantEnabled = false;
onMount(async () => {
const vars = await sdk.forConsole.console.variables();
isAssistantEnabled = vars._APP_ASSISTANT_ENABLED === true;
});
$: isOnSettingsLayout = $project?.$id
? $page.url.pathname.includes(`project-${$project.$id}/settings`)
: false;
$: $registerCommands([
{
label: 'Ask AI',
label: 'Go to projects',
callback: () => {
goto('/console');
},
keys: ['g', 'p'],
group: 'navigation',
disabled:
$page.url.pathname.includes('/console/organization-') &&
!$page.url.pathname.endsWith('/members') &&
!$page.url.pathname.endsWith('/settings'),
rank: -1
},
{
label: 'Ask the AI',
callback: () => {
addSubPanel(AIPanel);
},
keys: ['a', 'i'],
icon: 'light-bulb'
icon: 'sparkles',
disabled: !isAssistantEnabled
},
{
label: 'Go to account',
@@ -38,25 +71,8 @@
goto('/console/account');
},
keys: ['i'],
group: 'navigation'
},
{
label: 'Find an organization',
callback: () => {
addSubPanel(OrganizationsPanel);
},
group: 'organizations',
icon: 'search',
keys: ['f', 'o']
},
{
label: 'Find a project',
callback: () => {
addSubPanel(ProjectsPanel);
},
keys: ['f', 'p'],
group: 'projects',
icon: 'search'
group: 'navigation',
rank: -2
},
{
label: 'Create new organization',
@@ -66,14 +82,6 @@
keys: ['c', 'o'],
group: 'organizations'
},
{
label: 'Go to home',
callback: () => {
goto('/console');
},
keys: ['h'],
group: 'navigation'
},
{
label: 'Open documentation',
callback: () => {
@@ -106,24 +114,96 @@
group: 'help',
icon: 'discord'
},
...(['auto', 'dark', 'light'] as const).map((theme) => {
return {
label: `Set theme to ${theme}`,
callback: () => {
$app.theme = theme;
addNotification({
title: 'Theme changed',
message: `Theme changed to ${$app.theme}`,
type: 'success'
});
},
group: 'misc',
icon: 'switch-horizontal',
keys: ['t', theme[0]]
} as const;
}),
// Auth
...[
'users-limit',
'session-length',
'sessions-limit',
'password-history',
'password-dictionary',
'personal-data'
].map(
(heading) =>
({
label: kebabToSentenceCase(heading),
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#${heading}`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
} as const)
),
// Settings
{
label: 'Toggle theme',
label: 'Go to settings overview',
keys: isOnSettingsLayout ? ['g', 'o'] : undefined,
callback: () => {
if ($app.theme === 'auto') {
$app.theme = 'light';
} else if ($app.theme === 'light') {
$app.theme = 'dark';
} else {
$app.theme = 'auto';
}
addNotification({
title: 'Theme changed',
message: `Theme changed to ${$app.theme}`,
type: 'success'
});
goto(`/console/project-${$project.$id}/settings`);
},
group: 'misc',
icon: 'switch-horizontal'
disabled: isOnSettingsLayout && $page.url.pathname.endsWith('settings'),
group: 'navigation',
rank: isOnSettingsLayout ? 40 : -1
},
{
label: 'Go to custom domains',
keys: isOnSettingsLayout ? ['g', 'd'] : undefined,
callback: () => {
goto(`/console/project-${$project.$id}/settings/domains`);
},
disabled: isOnSettingsLayout && $page.url.pathname.includes('domains'),
group: 'navigation',
rank: isOnSettingsLayout ? 30 : -1
},
{
label: 'Go to webhooks',
keys: isOnSettingsLayout ? ['g', 'w'] : undefined,
callback: () => {
goto(`/console/project-${$project.$id}/settings/webhooks`);
},
disabled: isOnSettingsLayout && $page.url.pathname.includes('webhooks'),
group: 'navigation',
rank: isOnSettingsLayout ? 20 : -1
},
{
label: 'Go to migrations',
keys: isOnSettingsLayout ? ['g', 'm'] : undefined,
callback: () => {
goto(`/console/project-${$project.$id}/settings/migrations`);
},
disabled: isOnSettingsLayout && $page.url.pathname.includes('migrations'),
group: 'navigation',
rank: isOnSettingsLayout ? 10 : -1
},
{
label: 'Go to SMTP settings',
keys: isOnSettingsLayout ? ['g', 's'] : undefined,
callback: () => {
goto(`/console/project-${$project.$id}/settings/smtp`);
},
disabled: isOnSettingsLayout && $page.url.pathname.includes('smtp'),
group: 'navigation',
rank: -1
}
]);
let isOpen = false;
+1 -1
View File
@@ -155,7 +155,7 @@
</Form>
<CardGrid danger>
<div>
<Heading tag="h6" size="7">Delete Account</Heading>
<Heading tag="h6" size="7">Delete account</Heading>
</div>
<p>
Your account will be permanently deleted and access will be lost to any of your teams
+1 -1
View File
@@ -30,12 +30,12 @@
</script>
<Modal
title="Delete account"
bind:show={showDelete}
onSubmit={deleteAccount}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete Account</svelte:fragment>
<p>Are you sure you want to delete your account?</p>
<svelte:fragment slot="footer">
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
+1 -2
View File
@@ -42,8 +42,7 @@
}
</script>
<Modal {error} onSubmit={create} size="big" bind:show>
<svelte:fragment slot="header">Create New Organization</svelte:fragment>
<Modal title="Create new organization" {error} onSubmit={create} size="big" bind:show>
<FormList>
<InputText
id="organization-name"
@@ -1,13 +1,39 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { registerCommands } from '$lib/commandCenter';
import { newMemberModal, newOrgModal } from '$lib/stores/organization';
import { requestedMigration } from '$routes/store';
import { openMigrationWizard } from '../(migration-wizard)';
import Create from '../createOrganization.svelte';
import CreateMember from './createMember.svelte';
export let data;
$: if ($requestedMigration) {
openMigrationWizard();
}
$: $registerCommands([
{
label: 'Go to members',
callback: () => {
goto(`/console/organization-${data.organization.$id}/members`);
},
keys: ['g', 'm'],
disabled: $page.url.pathname.endsWith('/members'),
group: 'navigation'
},
{
label: 'Go to settings',
callback: () => {
goto(`/console/organization-${data.organization.$id}/settings`);
},
keys: ['g', 's'],
disabled: $page.url.pathname.endsWith('/settings'),
group: 'navigation'
}
]);
</script>
<svelte:head>
@@ -49,8 +49,7 @@
}
</script>
<Modal {error} size="big" bind:show={showCreate} onSubmit={create}>
<svelte:fragment slot="header">Invite Member</svelte:fragment>
<Modal title="Invite Member" {error} size="big" bind:show={showCreate} onSubmit={create}>
<FormList>
<InputEmail
required
@@ -44,8 +44,7 @@
}
</script>
<Modal {error} onSubmit={create} size="big" bind:show>
<svelte:fragment slot="header">Create Project</svelte:fragment>
<Modal title="Create project" {error} onSubmit={create} size="big" bind:show>
<FormList>
<InputText id="name" label="Name" bind:value={name} required autofocus={true} />
{#if !showCustomId}
@@ -49,8 +49,8 @@
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">
{isUser ? 'Leave Organization' : 'Delete Member'}
<svelte:fragment slot="title">
{isUser ? 'Leave organization' : 'Delete member'}
</svelte:fragment>
<p data-private>
{isUser
@@ -37,12 +37,12 @@
</script>
<Modal
title="Delete organization"
onSubmit={deleteOrg}
bind:show={showDelete}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete Organization</svelte:fragment>
<p>
Are you sure you want to delete <b>{$organization.name}</b>? All projects ({$organization.total})
and data associated with this organization will be deleted. This action is irreversible.
@@ -64,7 +64,7 @@
<CardGrid danger>
<div>
<Heading tag="h6" size="7">Delete Organization</Heading>
<Heading tag="h6" size="7">Delete organization</Heading>
</div>
<p>
The organization will be permanently deleted, including all projects and data
@@ -5,15 +5,8 @@
import { project, stats } from './store';
import { goto } from '$app/navigation';
import { addSubPanel, registerCommands, registerSearchers } from '$lib/commandCenter';
import { registerCommands, registerSearchers } from '$lib/commandCenter';
import {
BucketsPanel,
DatabasesPanel,
FunctionsPanel,
TeamsPanel,
UsersPanel
} from '$lib/commandCenter/panels';
import {
bucketSearcher,
dbSearcher,
@@ -35,24 +28,7 @@
$: $registerCommands([
{
label: 'Go to overview',
callback: () => {
goto(`/console/project-${$project.$id}`);
},
keys: ['o'],
group: 'navigation'
},
{
label: 'Go to auth',
callback: () => {
goto(`/console/project-${$project.$id}/auth`);
},
keys: ['a'],
group: 'navigation'
},
{
label: 'Go to databases',
label: 'Go to Databases',
callback: () => {
goto(`/console/project-${$project.$id}/databases`);
},
@@ -60,7 +36,15 @@
group: 'navigation'
},
{
label: 'Go to functions',
label: 'Go to Auth',
callback: () => {
goto(`/console/project-${$project.$id}/auth`);
},
keys: ['a'],
group: 'navigation'
},
{
label: 'Go to Functions',
callback: () => {
goto(`/console/project-${$project.$id}/functions`);
},
@@ -68,7 +52,7 @@
group: 'navigation'
},
{
label: 'Go to storage',
label: 'Go to Storage',
callback: () => {
goto(`/console/project-${$project.$id}/storage`);
},
@@ -84,50 +68,12 @@
group: 'navigation'
},
{
label: 'Find users',
label: 'Go to overview',
callback: () => {
addSubPanel(UsersPanel);
goto(`/console/project-${$project.$id}`);
},
group: 'users',
icon: 'search',
keys: ['f', 'u'],
rank: 10
},
{
label: 'Find teams',
callback: () => {
addSubPanel(TeamsPanel);
},
group: 'teams',
icon: 'search',
keys: ['f', 't']
},
{
label: 'Find databases',
callback: () => {
addSubPanel(DatabasesPanel);
},
group: 'databases',
icon: 'search',
keys: ['f', 'd']
},
{
label: 'Find functions',
callback: () => {
addSubPanel(FunctionsPanel);
},
group: 'functions',
icon: 'search',
keys: ['f', 'f']
},
{
label: 'Find buckets',
callback: () => {
addSubPanel(BucketsPanel);
},
group: 'buckets',
icon: 'search',
keys: ['f', 'b']
keys: ['o'],
group: 'navigation'
}
]);
@@ -74,51 +74,6 @@
group: 'navigation',
rank: 1,
disabled: $page.url.pathname.endsWith('settings')
},
{
label: 'Users limit',
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#users-limit`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
},
{
label: 'Session length',
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#session-length`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
},
{
label: 'Sessions limit',
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#sessions-limit`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
},
{
label: 'Password history',
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#password-history`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
},
{
label: 'Password dictionary',
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#password-dictionary`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
}
]);
@@ -38,7 +38,7 @@
</script>
<Modal {error} onSubmit={update} size="big" show on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -39,7 +39,7 @@
</script>
<Modal {error} size="big" show onSubmit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -39,7 +39,7 @@
</script>
<Modal {error} size="big" show onSubmit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -41,8 +41,7 @@
}
</script>
<Modal {error} size="big" bind:show={showCreate} onSubmit={create}>
<svelte:fragment slot="header">Create Team</svelte:fragment>
<Modal title="Create team" {error} size="big" bind:show={showCreate} onSubmit={create}>
<FormList>
<InputText
id="name"
@@ -58,8 +58,7 @@
}
</script>
<Modal {error} size="big" bind:show={showCreate} onSubmit={create}>
<svelte:fragment slot="header">Create User</svelte:fragment>
<Modal title="Create user" {error} size="big" bind:show={showCreate} onSubmit={create}>
<FormList>
<InputText
id="name"
@@ -37,7 +37,7 @@
</script>
<Modal {error} size="big" show onSubmit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -35,7 +35,7 @@
</script>
<Modal {error} size="big" show onSubmit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -35,7 +35,7 @@
</script>
<Modal {error} size="big" show onSubmit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -38,7 +38,7 @@
</script>
<Modal {error} onSubmit={update} size="big" show on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -61,7 +61,7 @@
</script>
<Modal {error} onSubmit={update} size="big" show on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -43,7 +43,7 @@
</script>
<Modal {error} onSubmit={update} size="big" show on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -35,13 +35,14 @@
<Form onSubmit={updatePasswordDictionary}>
<CardGrid>
<Heading tag="h2" size="7" id="password-dictionary">Password Dictionary</Heading>
<Heading tag="h2" size="7" id="password-dictionary">Password dictionary</Heading>
<svelte:fragment slot="aside">
<FormList>
<InputSwitch
bind:value={passwordDictionary}
id="passwordDictionary"
label="Password Dictionary" />
label="Password dictionary" />
</FormList>
<p class="text">
Enabling this option prevent users from setting insecure passwords by comparing the
@@ -39,13 +39,13 @@
<Form onSubmit={updatePasswordHistoryLimit}>
<CardGrid>
<Heading tag="h2" size="7" id="password-history">Password History</Heading>
<Heading tag="h2" size="7" id="password-history">Password history</Heading>
<svelte:fragment slot="aside">
<FormList>
<InputSwitch
bind:value={passwordHistoryEnabled}
id="passwordHistoryEnabled"
label="Password History" />
label="Password history" />
</FormList>
<p class="text">
Enabling this option prevents users from reusing recent passwords by comparing the
@@ -35,7 +35,7 @@
<Form onSubmit={updatePersonalDataCheck}>
<CardGrid>
<Heading tag="h2" size="7">Personal Data</Heading>
<Heading tag="h2" size="7" id="personal-data">Personal data</Heading>
<svelte:fragment slot="aside">
<FormList>
<InputSwitch
@@ -44,7 +44,7 @@
label="Disallow Personal Data" />
</FormList>
<p class="text">
Do now allow passwords that contain any part of the user's personal data. This
Do not allow passwords that contain any part of the user's personal data. This
includes the user's <code>name</code>, <code>email</code>, or <code>phone</code>.
</p>
</svelte:fragment>
@@ -34,7 +34,7 @@
<Form onSubmit={updateSessionsLimit}>
<CardGrid>
<Heading tag="h2" size="7" id="sessions-limit">Sessions Limit</Heading>
<Heading tag="h2" size="7" id="sessions-limit">Sessions limit</Heading>
<p>Maximum number of active sessions allowed per user.</p>
<svelte:fragment slot="aside">
<ul>
@@ -46,7 +46,7 @@
</script>
<CardGrid>
<Heading tag="h2" size="7" id="users-limit">Users Limit</Heading>
<Heading tag="h2" size="7" id="users-limit">Users limit</Heading>
<p>
Limit new users from signing up for your project, regardless of authentication method. You
can still create users and team memberships from your Appwrite console.
@@ -44,8 +44,7 @@
}
</script>
<Modal {error} onSubmit={create} size="big" bind:show={showCreate}>
<svelte:fragment slot="header">Create Membership</svelte:fragment>
<Modal title="Create membership" {error} onSubmit={create} size="big" bind:show={showCreate}>
<FormList>
<InputEmail
id="email"
@@ -9,7 +9,7 @@
<CardGrid danger>
<div>
<Heading tag="h6" size="7">Delete Team</Heading>
<Heading tag="h6" size="7">Delete team</Heading>
</div>
<p>
@@ -38,12 +38,12 @@
</script>
<Modal
title="Delete Member"
bind:show={showDelete}
onSubmit={deleteMembership}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete Member</svelte:fragment>
<p data-private>
Are you sure you want to delete <b>{selectedMembership.userName}</b> from '{selectedMembership.teamName}'?
</p>
@@ -29,12 +29,12 @@
</script>
<Modal
title="Delete team"
bind:show={showDelete}
onSubmit={deleteTeam}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete Team</svelte:fragment>
<p data-private>
Are you sure you want to delete <b>{team.name}</b>?
</p>
@@ -105,7 +105,7 @@
<p class="text">
Click to copy variables for the fields below. Learn more <a
class="link"
href="/#">here</a
href="https://appwrite.io/docs/email-and-sms-templates">here</a
>.
<!-- TODO: add link to docs -->
</p>
@@ -45,7 +45,7 @@
{error}
bind:show
headerDivider={false}>
<svelte:fragment slot="header">Reset Email Template?</svelte:fragment>
<svelte:fragment slot="title">Reset Email Template?</svelte:fragment>
<p class="text">
Are you sure you want to reset the email template?
<b>Default values will be set in all inputs.</b>
@@ -45,7 +45,7 @@
{error}
bind:show
headerDivider={false}>
<svelte:fragment slot="header">Reset SMS Template?</svelte:fragment>
<svelte:fragment slot="title">Reset SMS Template?</svelte:fragment>
<p class="text">
Are you sure you want to reset the SMS template?
<b>Default values will be set in all inputs.</b>
@@ -23,7 +23,7 @@
<CardGrid danger>
<div>
<Heading tag="h6" size="7">Delete User</Heading>
<Heading tag="h6" size="7">Delete user</Heading>
</div>
<p>
The user will be permanently deleted, including all data associated with this user. This
@@ -32,12 +32,12 @@
</script>
<Modal
title="Delete all sessions"
bind:show={showDeleteAll}
onSubmit={deleteAllSessions}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete All Sessions</svelte:fragment>
<p data-private>
Are you sure you want to delete <b>all of {$user.name}'s sessions?</b>
</p>

Some files were not shown because too many files have changed in this diff Show More