mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'bulk-deletion' into filtering
This commit is contained in:
Vendored
-1
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -118,6 +118,7 @@ export enum Submit {
|
||||
UserCreate = 'submit_user_create',
|
||||
UserDelete = 'submit_user_delete',
|
||||
UserUpdateEmail = 'submit_user_update_email',
|
||||
UserUpdateLabels = 'submit_user_update_labels',
|
||||
UserUpdateName = 'submit_user_update_name',
|
||||
UserUpdatePassword = 'submit_user_update_password',
|
||||
UserUpdatePhone = 'submit_user_update_phone',
|
||||
@@ -131,6 +132,7 @@ export enum Submit {
|
||||
ProjectCreate = 'submit_project_create',
|
||||
ProjectDelete = 'submit_project_delete',
|
||||
ProjectUpdateName = 'submit_project_update_name',
|
||||
ProjectUpdateTeam = 'submit_project_update_team',
|
||||
ProjectService = 'submit_project_service',
|
||||
MemberCreate = 'submit_member_create',
|
||||
MemberDelete = 'submit_member_delete',
|
||||
|
||||
@@ -6,7 +6,7 @@ export type PortalConfig = string | HTMLElement | undefined;
|
||||
export const portal: Action<HTMLElement, PortalConfig> = (el, target = 'body') => {
|
||||
let targetEl;
|
||||
|
||||
async function update(newTarget: HTMLElement | string | undefined) {
|
||||
async function update(newTarget: PortalConfig) {
|
||||
target = newTarget;
|
||||
if (typeof target === 'string') {
|
||||
targetEl = document.querySelector(target);
|
||||
|
||||
@@ -90,8 +90,6 @@
|
||||
}
|
||||
$commandCenterKeyDownHandler(e);
|
||||
};
|
||||
|
||||
$: console.log($subPanels);
|
||||
</script>
|
||||
|
||||
<svelte:window on:mousedown={handleBlur} on:keydown={handleKeydown} />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { debounce } from '$lib/helpers/debounce';
|
||||
import { isMac } from '$lib/helpers/platform';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { onMount } from 'svelte';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
@@ -106,8 +107,8 @@ function hasDisputing(command: KeyedCommand, allCommands: Command[]) {
|
||||
}
|
||||
|
||||
export const commandCenterKeyDownHandler = derived(
|
||||
[commandMap, commandsEnabled],
|
||||
([$commandMap, enabled]) => {
|
||||
[commandMap, commandsEnabled, wizard],
|
||||
([$commandMap, enabled, $wizard]) => {
|
||||
const commandsArr = Array.from($commandMap.values()).flat();
|
||||
let recentKeyCodes: number[] = [];
|
||||
let validCommands: KeyedCommand[] = [];
|
||||
@@ -178,9 +179,9 @@ export const commandCenterKeyDownHandler = derived(
|
||||
for (const command of commandsArr) {
|
||||
if (!isKeyedCommand(command)) continue;
|
||||
if (!command.forceEnable) {
|
||||
if (command.disabled) continue;
|
||||
if (!enabled) continue;
|
||||
if (isInputEvent(event)) continue;
|
||||
if (command.disabled || !enabled || isInputEvent(event) || $wizard.show) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const { keys, ctrl: meta, shift, alt } = command;
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
|
||||
{#if single}
|
||||
<article class="card u-grid u-cross-center u-width-full-line common-section">
|
||||
<div class="u-flex u-flex-vertical u-cross-center u-gap-24">
|
||||
<div
|
||||
class="u-flex u-flex-vertical u-cross-center u-gap-24 u-width-full-line u-overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
on:click|preventDefault
|
||||
@@ -40,12 +41,14 @@
|
||||
</button>
|
||||
<slot>
|
||||
<div class="u-text-center">
|
||||
<Heading size="7" tag="h2">Create your first {target} to get started.</Heading>
|
||||
<Heading size="7" tag="h2" trimmed={false}>
|
||||
Create your first {target} to get started.
|
||||
</Heading>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">
|
||||
Need a hand? Learn more in our documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 u-main-center">
|
||||
<div class="u-flex u-flex-wrap u-gap-16 u-main-center">
|
||||
<Button
|
||||
external
|
||||
{href}
|
||||
|
||||
+21
-30
@@ -1,40 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import {
|
||||
Form,
|
||||
FormList,
|
||||
InputTextarea,
|
||||
Button,
|
||||
InputText,
|
||||
InputEmail
|
||||
} from '$lib/elements/forms';
|
||||
import { feedback } from '$lib/stores/app';
|
||||
feedback,
|
||||
selectedFeedback,
|
||||
feedbackOptions,
|
||||
feedbackData
|
||||
} from '$lib/stores/feedback';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
let message: string;
|
||||
let name: string;
|
||||
let email: string;
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await feedback.submitFeedback('feedback-general', message, name, email);
|
||||
$: $selectedFeedback = feedbackOptions.find((option) => option.type === $feedback.type);
|
||||
|
||||
async function submit() {
|
||||
try {
|
||||
await feedback.submitFeedback(
|
||||
`feedback-${$feedback.type}`,
|
||||
$feedbackData.message,
|
||||
$feedbackData.name,
|
||||
$feedbackData.email
|
||||
);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Feedback submitted successfully'
|
||||
});
|
||||
feedback.toggleFeedback();
|
||||
feedbackData.reset();
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
} finally {
|
||||
feedback.toggleFeedback();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="drop-section">
|
||||
<header class="u-flex u-main-space-between u-gap-16">
|
||||
<h4 class="body-text-1 u-bold">How can we improve?</h4>
|
||||
<h4 class="body-text-1 u-bold">{$selectedFeedback.title}</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-text is-only-icon u-margin-inline-start-auto"
|
||||
@@ -46,21 +47,11 @@
|
||||
</button>
|
||||
</header>
|
||||
<div class="u-margin-block-start-8 u-line-height-1-5">
|
||||
Your feedback is important to us. Please be honest and tell us what you think.
|
||||
{$selectedFeedback.desc}
|
||||
</div>
|
||||
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormList>
|
||||
<InputText label="Name" id="name" bind:value={name} placeholder="Enter name" />
|
||||
<InputEmail label="Email" id="email" bind:value={email} placeholder="Enter email" />
|
||||
<InputTextarea
|
||||
id="feedback"
|
||||
placeholder="Your message here"
|
||||
label="Message"
|
||||
required
|
||||
bind:value={message} />
|
||||
</FormList>
|
||||
|
||||
<Form onSubmit={submit}>
|
||||
<svelte:component this={$selectedFeedback.component} />
|
||||
<div class="u-flex u-main-end u-gap-16 u-margin-block-start-24">
|
||||
<Button text on:click={() => feedback.toggleFeedback()}>Cancel</Button>
|
||||
<Button secondary submit>Submit</Button>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { FormList, InputTextarea, InputText, InputEmail } from '$lib/elements/forms';
|
||||
import { feedbackData } from '$lib/stores/feedback';
|
||||
</script>
|
||||
|
||||
<FormList>
|
||||
<InputText label="Name" id="name" bind:value={$feedbackData.name} placeholder="Enter name" />
|
||||
<InputEmail
|
||||
label="Email"
|
||||
id="email"
|
||||
bind:value={$feedbackData.email}
|
||||
placeholder="Enter email" />
|
||||
<InputTextarea
|
||||
id="feedback"
|
||||
placeholder="Your message here"
|
||||
label="Message"
|
||||
required
|
||||
bind:value={$feedbackData.message} />
|
||||
</FormList>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { FormList, InputTextarea, InputText, InputEmail } from '$lib/elements/forms';
|
||||
import { feedbackData } from '$lib/stores/feedback';
|
||||
import Evaluation from './evaluation.svelte';
|
||||
</script>
|
||||
|
||||
<Evaluation bind:value={$feedbackData.value}>
|
||||
How likely are you to recommend Appwrite to a friend or colleague?
|
||||
</Evaluation>
|
||||
{#if $feedbackData.value}
|
||||
<FormList>
|
||||
<InputTextarea
|
||||
id="feedback"
|
||||
placeholder="Your message here"
|
||||
label="Message"
|
||||
required
|
||||
bind:value={$feedbackData.message}
|
||||
showLabel={false} />
|
||||
<InputText
|
||||
label="Name"
|
||||
id="name"
|
||||
bind:value={$feedbackData.name}
|
||||
placeholder="Enter name" />
|
||||
<InputEmail
|
||||
label="Email"
|
||||
id="email"
|
||||
bind:value={$feedbackData.email}
|
||||
placeholder="Enter email" />
|
||||
</FormList>
|
||||
{/if}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as Feedback } from './feedback.svelte';
|
||||
export { default as FeedbackGeneral } from './feedbackGeneral.svelte';
|
||||
export { default as FeedbackNPS } from './feedbackNPS.svelte';
|
||||
@@ -1,78 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Form,
|
||||
FormList,
|
||||
InputTextarea,
|
||||
Button,
|
||||
InputText,
|
||||
InputEmail
|
||||
} from '$lib/elements/forms';
|
||||
import { feedback } from '$lib/stores/app';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import Evaluation from './evaluation.svelte';
|
||||
|
||||
let value: number = null;
|
||||
let message: string;
|
||||
let email: string;
|
||||
let name: string;
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await feedback.submitFeedback('feedback-nps', message, name, email, value);
|
||||
feedback.switchType('general');
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Thank you for your feedback!'
|
||||
});
|
||||
} catch (error) {
|
||||
feedback.switchType('general');
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
} finally {
|
||||
feedback.toggleFeedback();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="drop-section">
|
||||
<header class="u-flex u-main-space-between u-gap-16">
|
||||
<h4 class="body-text-1 u-bold">How are we doing?</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-text is-only-icon u-margin-inline-start-auto"
|
||||
style="--button-size:1.5rem;"
|
||||
aria-label="Close Modal"
|
||||
title="Close Modal"
|
||||
on:click={() => feedback.toggleFeedback()}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</header>
|
||||
<div class="u-margin-block-start-8 u-line-height-1-5">
|
||||
At Appwrite, we value the feedback of our users. That means you! We'd love to hear what you
|
||||
think.
|
||||
</div>
|
||||
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Evaluation bind:value>
|
||||
How likely are you to recommend Appwrite to a friend or colleague?
|
||||
</Evaluation>
|
||||
{#if value}
|
||||
<FormList>
|
||||
<InputTextarea
|
||||
id="feedback"
|
||||
placeholder="Your message here"
|
||||
label="Message"
|
||||
required
|
||||
bind:value={message}
|
||||
showLabel={false} />
|
||||
<InputText label="Name" id="name" bind:value={name} placeholder="Enter name" />
|
||||
<InputEmail label="Email" id="email" bind:value={email} placeholder="Enter email" />
|
||||
</FormList>
|
||||
{/if}
|
||||
<div class="u-flex u-main-end u-gap-16 u-margin-block-start-24">
|
||||
<Button text on:click={() => feedback.toggleFeedback()}>No thanks</Button>
|
||||
<Button disabled={!value} secondary submit>Submit</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</section>
|
||||
@@ -32,9 +32,6 @@ export { default as Box } from './box.svelte';
|
||||
export { default as Search } from './search.svelte';
|
||||
export { default as SearchQuery } from './searchQuery.svelte';
|
||||
export { default as GridItem1 } from './gridItem1.svelte';
|
||||
export { default as FeedbackGeneral } from './feedbackGeneral.svelte';
|
||||
export { default as FeedbackNPS } from './feedbackNPS.svelte';
|
||||
export { default as Evaluation } from './evaluation.svelte';
|
||||
export { default as Steps } from './steps.svelte';
|
||||
export { default as Step } from './step.svelte';
|
||||
export { default as Code } from './code.svelte';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { Alert } from '$lib/components';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { Form } from '$lib/elements/forms';
|
||||
import { disableCommands } from '$lib/commandCenter';
|
||||
|
||||
export let show = false;
|
||||
export let size: 'small' | 'big' = null;
|
||||
@@ -70,6 +71,8 @@
|
||||
closeModal();
|
||||
}
|
||||
|
||||
$: $disableCommands(show);
|
||||
|
||||
$: if (error) {
|
||||
alert?.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' });
|
||||
}
|
||||
|
||||
@@ -1,19 +1,95 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { last } from '$lib/helpers/array';
|
||||
import { getElementDir } from '$lib/helpers/style';
|
||||
import { waitUntil } from '$lib/helpers/waitUntil';
|
||||
|
||||
export let selected = false;
|
||||
export let href: string = null;
|
||||
export let event: string = null;
|
||||
|
||||
function track() {
|
||||
if (!event) return;
|
||||
trackEvent(`click_select_tab_${event}`);
|
||||
async function handleClick(e: Event) {
|
||||
if (event) {
|
||||
trackEvent(`click_select_tab_${event}`);
|
||||
}
|
||||
|
||||
if (href) {
|
||||
let el = e.target as HTMLElement;
|
||||
if (el.tagName !== 'A') {
|
||||
el = el.closest('a');
|
||||
}
|
||||
|
||||
await goto(href);
|
||||
await waitUntil(() => {
|
||||
console.log('tickUntil', el);
|
||||
return el.classList.contains('is-selected');
|
||||
}, 1000);
|
||||
el.focus();
|
||||
}
|
||||
}
|
||||
|
||||
const keysMap = {
|
||||
ltr: {
|
||||
next: 'ArrowRight',
|
||||
prev: 'ArrowLeft'
|
||||
},
|
||||
rtl: {
|
||||
next: 'ArrowLeft',
|
||||
prev: 'ArrowRight'
|
||||
}
|
||||
};
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
const tabBtn = e.target as HTMLElement;
|
||||
const tabItem = tabBtn.closest('.tabs-item') as HTMLElement;
|
||||
const tabsList = tabItem.closest('.tabs') as HTMLElement;
|
||||
const tabItems = Array.from(tabsList.querySelectorAll('.tabs-item'));
|
||||
const currentIdx = tabItems.indexOf(tabItem);
|
||||
|
||||
const dir = getElementDir(tabsList);
|
||||
|
||||
switch (e.key) {
|
||||
case 'Home': {
|
||||
e.preventDefault();
|
||||
const firstTabBtn = tabItems[0].querySelector('.tabs-button') as HTMLElement;
|
||||
firstTabBtn.focus();
|
||||
break;
|
||||
}
|
||||
case 'End': {
|
||||
e.preventDefault();
|
||||
const lastTabBtn = last(tabItems).querySelector('.tabs-button') as HTMLElement;
|
||||
lastTabBtn.focus();
|
||||
break;
|
||||
}
|
||||
case keysMap[dir].next: {
|
||||
e.preventDefault();
|
||||
const nextIdx = currentIdx === tabItems.length - 1 ? 0 : currentIdx + 1;
|
||||
const nextTabBtn = tabItems[nextIdx].querySelector('.tabs-button') as HTMLElement;
|
||||
nextTabBtn.focus();
|
||||
break;
|
||||
}
|
||||
case keysMap[dir].prev: {
|
||||
e.preventDefault();
|
||||
const prevIdx = currentIdx === 0 ? tabItems.length - 1 : currentIdx - 1;
|
||||
const prevTabBtn = tabItems[prevIdx].querySelector('.tabs-button') as HTMLElement;
|
||||
prevTabBtn.focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<li class="tabs-item">
|
||||
{#if href}
|
||||
<a class="tabs-button" {href} class:is-selected={selected} on:click={track}>
|
||||
<a
|
||||
class="tabs-button"
|
||||
{href}
|
||||
class:is-selected={selected}
|
||||
on:click={handleClick}
|
||||
tabindex={selected ? 0 : -1}
|
||||
on:keydown={handleKeyDown}
|
||||
role="tab">
|
||||
<span class="text"><slot /></span>
|
||||
</a>
|
||||
{:else}
|
||||
@@ -22,7 +98,10 @@
|
||||
class="tabs-button"
|
||||
class:is-selected={selected}
|
||||
on:click|preventDefault
|
||||
on:click={track}>
|
||||
on:click={handleClick}
|
||||
tabindex={selected ? 0 : -1}
|
||||
on:keydown={handleKeyDown}
|
||||
role="tab">
|
||||
<span class="text"><slot /></span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { toggle } from '$lib/helpers/array';
|
||||
import { isHTMLInputElement } from '$lib/helpers/types';
|
||||
import { TableCell } from '.';
|
||||
import { InputCheckbox } from '../forms';
|
||||
|
||||
export let id: string;
|
||||
export let selectedIds: string[] = [];
|
||||
</script>
|
||||
|
||||
<TableCell>
|
||||
<InputCheckbox
|
||||
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);
|
||||
});
|
||||
}} />
|
||||
</TableCell>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { isHTMLInputElement } from '$lib/helpers/types';
|
||||
|
||||
import { TableCellHead } from '.';
|
||||
import { InputCheckbox } from '../forms';
|
||||
|
||||
export let selected: string[] = [];
|
||||
export let pageItemsIds: string[] = [];
|
||||
|
||||
function handleClick(e: MouseEvent) {
|
||||
if (!isHTMLInputElement(e.target)) return;
|
||||
if (e.target.checked) {
|
||||
const set = new Set(selected);
|
||||
pageItemsIds.forEach((id) => set.add(id));
|
||||
selected = Array.from(set);
|
||||
} else {
|
||||
selected = selected.filter((id) => {
|
||||
return !pageItemsIds.includes(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: someSelected = pageItemsIds.some((id) => selected.includes(id));
|
||||
$: allSelected = pageItemsIds.every((id) => selected.includes(id));
|
||||
</script>
|
||||
|
||||
<TableCellHead width={10}>
|
||||
<InputCheckbox
|
||||
id="select-all"
|
||||
indeterminate={someSelected && !allSelected}
|
||||
value={allSelected}
|
||||
on:click={handleClick} />
|
||||
</TableCellHead>
|
||||
@@ -9,6 +9,8 @@ export { default as TableRowLink } from './rowLink.svelte';
|
||||
export { default as TableRowButton } from './rowButton.svelte';
|
||||
export { default as TableCell } from './cell.svelte';
|
||||
export { default as TableCellHead } from './cellHead.svelte';
|
||||
export { default as TableCellHeadCheck } from './cellHeadCheck.svelte';
|
||||
export { default as TableCellLink } from './cellLink.svelte';
|
||||
export { default as TableCellAvatar } from './cellAvatar.svelte';
|
||||
export { default as TableCellText } from './cellText.svelte';
|
||||
export { default as TableCellCheck } from './cellCheck.svelte';
|
||||
|
||||
@@ -3,11 +3,28 @@
|
||||
|
||||
export let isSticky = false;
|
||||
export let noMargin = false;
|
||||
let isOverflowing = false;
|
||||
|
||||
const hasOverflow: Action<HTMLDivElement, (value: boolean) => void> = (node, callback) => {
|
||||
const hasOverflow: Action<HTMLDivElement> = (node) => {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
callback(entry.contentRect.width < entry.target.scrollWidth);
|
||||
let overflowing = false;
|
||||
if (entry.contentRect.width < entry.target.scrollWidth) {
|
||||
overflowing = true;
|
||||
}
|
||||
|
||||
const cols = entry.target.querySelectorAll('.table-thead-col');
|
||||
for (let i = 0; i < cols.length; i++) {
|
||||
const col = cols[i];
|
||||
const cs = getComputedStyle(col);
|
||||
const innerWidth =
|
||||
col.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight);
|
||||
if (innerWidth < 32) {
|
||||
overflowing = true;
|
||||
}
|
||||
}
|
||||
|
||||
isOverflowing = overflowing;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,8 +36,6 @@
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let isOverflowing = false;
|
||||
</script>
|
||||
|
||||
<div class="table-with-scroll {noMargin ? '' : 'u-margin-block-start-32'}" data-private>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
export const toLocaleDate = (datetime: string) => {
|
||||
const date = new Date(datetime);
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
return 'n/a';
|
||||
}
|
||||
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
@@ -11,6 +16,11 @@ export const toLocaleDate = (datetime: string) => {
|
||||
|
||||
export const toLocaleDateTime = (datetime: string | number) => {
|
||||
const date = new Date(datetime);
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
return 'n/a';
|
||||
}
|
||||
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
type Direction = 'rtl' | 'ltr';
|
||||
|
||||
function isDirection(dir: string): dir is Direction {
|
||||
return dir === 'rtl' || dir === 'ltr';
|
||||
}
|
||||
|
||||
function parseDirection(dir: string): Direction {
|
||||
return isDirection(dir) ? dir : 'ltr';
|
||||
}
|
||||
|
||||
export function getElementDir(el: HTMLElement): Direction {
|
||||
if (window.getComputedStyle) {
|
||||
return parseDirection(window.getComputedStyle(el, null).getPropertyValue('direction'));
|
||||
}
|
||||
return parseDirection(el.style.direction);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export async function waitUntil(condition: () => boolean, timeout = 1000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const start = Date.now();
|
||||
const interval = setInterval(() => {
|
||||
if (condition()) {
|
||||
clearInterval(interval);
|
||||
resolve(undefined);
|
||||
} else if (Date.now() - start > timeout) {
|
||||
clearInterval(interval);
|
||||
reject(new Error('Timeout'));
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<svg width="236" height="175" viewBox="0 0 236 175" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M57.4121 116.254C57.4121 140.493 88.4892 145.072 93.5568 119.917" stroke="url(#paint0_radial_571_90123)" stroke-width="12"/>
|
||||
<path d="M107.204 44.209L35.9817 91.6489C30.5629 100.178 37.1467 119.049 45.0168 119.528C52.887 120.008 137.394 126.511 137.394 126.511L107.204 44.209Z" fill="url(#paint1_linear_571_90123)" fill-opacity="0.4"/>
|
||||
<g filter="url(#filter0_d_571_90123)">
|
||||
<path d="M108.82 33.6465L39.8891 88.9015C34.7605 98.6703 42.5145 119.368 49.9632 119.918C57.412 120.467 137.394 127.916 137.394 127.916L108.82 33.6465Z" fill="url(#paint2_linear_571_90123)"/>
|
||||
</g>
|
||||
<path d="M142.595 74.954C146.53 88.0347 148.092 100.432 147.467 109.883C147.154 114.613 146.297 118.55 144.956 121.454C143.615 124.36 141.847 126.123 139.755 126.752C137.664 127.381 135.217 126.886 132.495 125.202C129.775 123.519 126.888 120.709 124.018 116.936C118.283 109.398 112.747 98.1959 108.812 85.1152C104.878 72.0345 103.315 59.6372 103.941 50.1857C104.254 45.456 105.111 41.5194 106.451 38.6151C107.792 35.7094 109.561 33.9466 111.652 33.3175C113.744 32.6884 116.191 33.1832 118.912 34.8671C121.633 36.55 124.519 39.3605 127.389 43.1328C133.125 50.6711 138.661 61.8733 142.595 74.954Z" fill="url(#paint3_linear_571_90123)" stroke="url(#paint4_linear_571_90123)" stroke-width="2"/>
|
||||
<path d="M114.593 98.8532C114.593 98.8532 121.351 95.3299 122.591 91.7119C123.832 88.0939 122.287 81.9135 120.565 76.1934C118.843 70.4732 105.786 69.9484 105.786 69.9484C108.725 83.3674 110.852 89.2767 114.593 98.8532Z" fill="url(#paint5_linear_571_90123)" fill-opacity="0.3"/>
|
||||
<ellipse cx="120.194" cy="82.5991" rx="2.00454" ry="10.2947" transform="rotate(-14.7165 120.194 82.5991)" fill="url(#paint6_linear_571_90123)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M172.267 33.0303C159.653 27.4164 144.876 33.0914 139.262 45.7057L138.929 46.4536C133.316 59.0679 138.991 73.8448 151.605 79.4587L156.662 81.7093C156.179 82.3785 155.895 83.211 155.916 84.1363L155.978 86.7736C156.057 90.1179 159.961 91.8943 162.534 89.7561L164.563 88.0699C165.299 87.4579 165.745 86.6643 165.921 85.8298L170.525 87.8787C183.139 93.4926 197.916 87.8176 203.53 75.2033L203.862 74.4554C209.476 61.8411 203.801 47.0642 191.187 41.4503L172.267 33.0303Z" fill="url(#paint7_linear_571_90123)"/>
|
||||
<path d="M165.517 87.4798L160.655 91.5071C159.16 92.7454 157.442 93.639 155.506 93.776C154.556 93.8432 154.265 93.1232 154 92.9993C155.828 92.8779 157.297 92.4752 157.68 91.9631L163.801 86.7594L165.517 87.4798Z" fill="#8B2A56"/>
|
||||
<path d="M158.141 26.648C152.649 24.3428 146.941 27.6001 144.975 29.3993C149.627 26.5875 154.943 27.6946 157.019 28.5997L202.118 48.1322C208.529 51.7113 207.777 59.0198 206.599 62.2266L194.603 88.8868C191.478 94.6007 186.913 95.5851 185.021 95.3631C191.256 96.2841 195.705 91.8728 197.15 89.552L209.506 62.0336C212.455 53.6616 208.162 49.1214 205.648 47.8979C191.967 41.8535 163.634 28.9532 158.141 26.648Z" fill="url(#paint8_linear_571_90123)"/>
|
||||
<mask id="path-10-inside-1_571_90123" fill="white">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M155.683 27.5397C150.638 25.2942 144.727 27.5642 142.481 32.6099L129.951 60.7661C127.705 65.8118 129.975 71.7225 135.021 73.9681L152.971 81.9568L153.195 91.517C153.235 93.1892 155.187 94.0774 156.473 93.0083L163.912 86.8257L181.349 94.5859C186.395 96.8315 192.305 94.5615 194.551 89.5158L207.082 61.3596C209.327 56.3139 207.057 50.4031 202.011 48.1576L155.683 27.5397Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M155.683 27.5397C150.638 25.2942 144.727 27.5642 142.481 32.6099L129.951 60.7661C127.705 65.8118 129.975 71.7225 135.021 73.9681L152.971 81.9568L153.195 91.517C153.235 93.1892 155.187 94.0774 156.473 93.0083L163.912 86.8257L181.349 94.5859C186.395 96.8315 192.305 94.5615 194.551 89.5158L207.082 61.3596C209.327 56.3139 207.057 50.4031 202.011 48.1576L155.683 27.5397Z" fill="url(#paint9_linear_571_90123)"/>
|
||||
<path d="M142.481 32.6099L141.568 32.2033L142.481 32.6099ZM155.683 27.5397L156.09 26.6261L155.683 27.5397ZM129.951 60.7661L129.037 60.3595L129.951 60.7661ZM135.021 73.9681L135.428 73.0545L135.021 73.9681ZM152.971 81.9568L153.971 81.9333L153.956 81.3005L153.378 81.0431L152.971 81.9568ZM153.195 91.517L152.196 91.5405L153.195 91.517ZM156.473 93.0083L157.112 93.7773L156.473 93.0083ZM163.912 86.8257L164.318 85.9121L163.751 85.6594L163.273 86.0566L163.912 86.8257ZM181.349 94.5859L180.942 95.4995L181.349 94.5859ZM194.551 89.5158L193.637 89.1092L194.551 89.5158ZM207.082 61.3596L206.168 60.953L206.168 60.953L207.082 61.3596ZM202.011 48.1576L202.418 47.2439L202.418 47.2439L202.011 48.1576ZM143.395 33.0165C145.416 28.4753 150.736 26.4323 155.277 28.4533L156.09 26.6261C150.54 24.156 144.038 26.653 141.568 32.2033L143.395 33.0165ZM130.864 61.1727L143.395 33.0165L141.568 32.2033L129.037 60.3595L130.864 61.1727ZM135.428 73.0545C130.886 71.0335 128.843 65.7138 130.864 61.1727L129.037 60.3595C126.567 65.9098 129.064 72.4116 134.614 74.8817L135.428 73.0545ZM153.378 81.0431L135.428 73.0545L134.614 74.8817L152.565 82.8704L153.378 81.0431ZM154.195 91.4936L153.971 81.9333L151.972 81.9802L152.196 91.5405L154.195 91.4936ZM155.834 92.2392C155.191 92.7738 154.215 92.3297 154.195 91.4936L152.196 91.5405C152.254 94.0487 155.183 95.381 157.112 93.7773L155.834 92.2392ZM163.273 86.0566L155.834 92.2392L157.112 93.7773L164.551 87.5947L163.273 86.0566ZM181.756 93.6723L164.318 85.9121L163.505 87.7393L180.942 95.4995L181.756 93.6723ZM193.637 89.1092C191.616 93.6503 186.297 95.6933 181.756 93.6723L180.942 95.4995C186.493 97.9696 192.994 95.4727 195.465 89.9224L193.637 89.1092ZM206.168 60.953L193.637 89.1092L195.465 89.9224L207.995 61.7662L206.168 60.953ZM201.605 49.0712C206.146 51.0922 208.189 56.4118 206.168 60.953L207.995 61.7662C210.465 56.2159 207.968 49.7141 202.418 47.2439L201.605 49.0712ZM155.277 28.4533L201.605 49.0712L202.418 47.2439L156.09 26.6261L155.277 28.4533Z" fill="url(#paint10_linear_571_90123)" mask="url(#path-10-inside-1_571_90123)"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 149.662 47.835)" fill="#892450"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 148.198 47.8135)" fill="url(#paint11_linear_571_90123)"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 166.811 55.4512)" fill="#892450"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 165.348 55.4297)" fill="url(#paint12_linear_571_90123)"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 183.96 63.0664)" fill="#892450"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 182.498 63.0449)" fill="url(#paint13_linear_571_90123)"/>
|
||||
<g filter="url(#filter1_f_571_90123)">
|
||||
<path d="M63.2684 28.0889L65.3066 31.8156L69.0334 33.8538L65.3066 35.8921L63.2684 39.6188L61.2302 35.8921L57.5034 33.8538L61.2302 31.8156L63.2684 28.0889Z" fill="url(#paint14_radial_571_90123)" fill-opacity="0.6"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_f_571_90123)">
|
||||
<path d="M225.574 13.8984L228.553 19.3452L234 22.3242L228.553 25.3031L225.574 30.7499L222.595 25.3031L217.148 22.3242L222.595 19.3452L225.574 13.8984Z" fill="url(#paint15_radial_571_90123)" fill-opacity="0.6"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_f_571_90123)">
|
||||
<path d="M171.472 111.903L173.04 114.77L175.907 116.338L173.04 117.906L171.472 120.772L169.904 117.906L167.038 116.338L169.904 114.77L171.472 111.903Z" fill="url(#paint16_radial_571_90123)" fill-opacity="0.6"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_571_90123" x="0.233643" y="0.646484" width="179.161" height="174.27" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="2" dy="7"/>
|
||||
<feGaussianBlur stdDeviation="20"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.245833 0 0 0 0 0.255044 0 0 0 0 0.333333 0 0 0 1 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_571_90123"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_571_90123" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_571_90123" x="55.5034" y="26.0889" width="15.53" height="15.5303" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_571_90123"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_571_90123" x="215.148" y="11.8984" width="20.8516" height="20.8516" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_571_90123"/>
|
||||
</filter>
|
||||
<filter id="filter3_f_571_90123" x="165.038" y="109.903" width="12.8691" height="12.8691" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_571_90123"/>
|
||||
</filter>
|
||||
<radialGradient id="paint0_radial_571_90123" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(78.2319 119.062) rotate(53.6887) scale(22.2166 32.3419)">
|
||||
<stop stop-color="#202130"/>
|
||||
<stop offset="0.317708" stop-color="#33374B"/>
|
||||
<stop offset="0.484375" stop-color="#33374B"/>
|
||||
<stop offset="0.905609" stop-color="#20202F"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint1_linear_571_90123" x1="41.4436" y1="121.838" x2="52.7291" y2="75.4951" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#292B3D"/>
|
||||
<stop offset="0.630208" stop-color="#4B506A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_571_90123" x1="71.3936" y1="52.3904" x2="98.8316" y2="131.923" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.274562" stop-color="#33364A"/>
|
||||
<stop offset="1" stop-color="#13141D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_571_90123" x1="121.215" y1="-12.9999" x2="137.25" y2="140.807" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.263084" stop-color="#101118"/>
|
||||
<stop offset="0.562167" stop-color="#3E4156"/>
|
||||
<stop offset="0.984375" stop-color="#303044"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_571_90123" x1="118.956" y1="-24.173" x2="206.164" y2="211.881" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.264071" stop-color="#33374B"/>
|
||||
<stop offset="1" stop-color="#585B7A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_571_90123" x1="115.07" y1="99.4437" x2="111.855" y2="69.0231" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.0252272" stop-color="#222332"/>
|
||||
<stop offset="0.242539" stop-color="#424661"/>
|
||||
<stop offset="0.588639" stop-color="#212230"/>
|
||||
<stop offset="0.844082" stop-color="#363B58"/>
|
||||
<stop offset="1" stop-color="#34374B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_571_90123" x1="121.604" y1="109.753" x2="120.275" y2="67.1411" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#222433"/>
|
||||
<stop offset="1" stop-color="#767D9E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_571_90123" x1="199.971" y1="95.4039" x2="141.478" y2="-6.07794" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E70041"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_571_90123" x1="147.137" y1="21.1212" x2="210.784" y2="96.99" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#BE2764"/>
|
||||
<stop offset="1" stop-color="#812A53"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear_571_90123" x1="206.807" y1="129.438" x2="142.953" y2="21.5313" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#823359"/>
|
||||
<stop offset="1" stop-color="#D02E60"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint10_linear_571_90123" x1="306.52" y1="53.9106" x2="132.142" y2="70.9057" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8A2A56"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint11_linear_571_90123" x1="4.40454" y1="0" x2="4.40454" y2="8.80908" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5B6074" stop-opacity="0.97"/>
|
||||
<stop offset="1" stop-color="#373B4D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint12_linear_571_90123" x1="4.40454" y1="0" x2="4.40454" y2="8.80908" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5B6074" stop-opacity="0.97"/>
|
||||
<stop offset="1" stop-color="#373B4D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint13_linear_571_90123" x1="4.40454" y1="0" x2="4.40454" y2="8.80908" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5B6074" stop-opacity="0.97"/>
|
||||
<stop offset="1" stop-color="#373B4D"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint14_radial_571_90123" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(63.2684 33.8538) rotate(90) scale(6.65188)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="0.947917" stop-color="#282A3B"/>
|
||||
<stop offset="1" stop-color="#F02E65"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint15_radial_571_90123" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(225.574 22.3242) rotate(90) scale(15.0776)">
|
||||
<stop stop-color="#DE2E62"/>
|
||||
<stop offset="0.598958" stop-color="#282A3B"/>
|
||||
<stop offset="0.75" stop-color="#282A3B"/>
|
||||
<stop offset="1" stop-color="#F02E65"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint16_radial_571_90123" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(171.472 116.338) rotate(90) scale(6.43015)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="0.729167" stop-color="#282A3B"/>
|
||||
<stop offset="1" stop-color="#F02E65"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,126 @@
|
||||
<svg width="222" height="175" viewBox="0 0 222 175" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M43.4121 116.254C43.4121 140.493 74.4892 145.072 79.5568 119.917" stroke="url(#paint0_radial_537_80284)" stroke-width="12"/>
|
||||
<path d="M93.2039 44.209L21.9817 91.6489C16.5629 100.178 23.1467 119.049 31.0168 119.528C38.887 120.008 123.394 126.511 123.394 126.511L93.2039 44.209Z" fill="url(#paint1_linear_537_80284)" fill-opacity="0.4"/>
|
||||
<g filter="url(#filter0_d_537_80284)">
|
||||
<path d="M94.8205 33.6465L25.8891 88.9015C20.7605 98.6703 28.5145 119.368 35.9632 119.918C43.412 120.467 123.394 127.916 123.394 127.916L94.8205 33.6465Z" fill="url(#paint2_linear_537_80284)"/>
|
||||
</g>
|
||||
<path d="M128.595 74.954C132.53 88.0347 134.092 100.432 133.467 109.883C133.154 114.613 132.297 118.55 130.956 121.454C129.615 124.36 127.847 126.123 125.755 126.752C123.664 127.381 121.217 126.886 118.495 125.202C115.775 123.519 112.888 120.709 110.018 116.936C104.283 109.398 98.7468 98.1959 94.8124 85.1152C90.878 72.0345 89.3155 59.6372 89.9408 50.1857C90.2537 45.456 91.111 41.5194 92.4513 38.6151C93.7924 35.7094 95.5605 33.9466 97.6521 33.3175C99.7437 32.6884 102.191 33.1832 104.912 34.8671C107.633 36.55 110.519 39.3605 113.389 43.1328C119.125 50.6711 124.661 61.8733 128.595 74.954Z" fill="url(#paint3_linear_537_80284)" stroke="url(#paint4_linear_537_80284)" stroke-width="2"/>
|
||||
<path d="M100.593 98.8532C100.593 98.8532 107.351 95.3299 108.591 91.7119C109.831 88.0939 108.286 81.9135 106.565 76.1934C104.843 70.4732 91.7854 69.9484 91.7854 69.9484C94.7244 83.3674 96.8517 89.2767 100.593 98.8532Z" fill="url(#paint5_linear_537_80284)" fill-opacity="0.3"/>
|
||||
<ellipse cx="106.194" cy="82.5991" rx="2.00454" ry="10.2947" transform="rotate(-14.7165 106.194 82.5991)" fill="url(#paint6_linear_537_80284)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M158.267 33.0303C145.653 27.4164 130.876 33.0914 125.262 45.7057L124.929 46.4536C119.316 59.0679 124.991 73.8448 137.605 79.4587L142.662 81.7093C142.179 82.3785 141.895 83.211 141.916 84.1363L141.978 86.7736C142.057 90.1179 145.961 91.8943 148.534 89.7561L150.563 88.0699C151.299 87.4579 151.745 86.6643 151.921 85.8298L156.525 87.8787C169.139 93.4926 183.916 87.8176 189.53 75.2033L189.862 74.4554C195.476 61.8411 189.801 47.0642 177.187 41.4503L158.267 33.0303Z" fill="url(#paint7_linear_537_80284)"/>
|
||||
<path d="M151.516 87.4798L146.655 91.5071C145.16 92.7454 143.442 93.639 141.506 93.776C140.556 93.8432 140.264 93.1232 140 92.9993C141.827 92.8779 143.296 92.4752 143.68 91.9631L149.8 86.7594L151.516 87.4798Z" fill="#EB2B62"/>
|
||||
<path d="M144.141 26.648C138.649 24.3428 132.941 27.6001 130.975 29.3993C135.627 26.5875 140.943 27.6946 143.019 28.5997L188.118 48.1322C194.529 51.7113 193.777 59.0198 192.599 62.2266L180.603 88.8868C177.478 94.6007 172.913 95.5851 171.021 95.3631C177.256 96.2841 181.705 91.8728 183.15 89.552L195.506 62.0336C198.455 53.6616 194.162 49.1214 191.648 47.8979C177.967 41.8535 149.634 28.9532 144.141 26.648Z" fill="url(#paint8_linear_537_80284)"/>
|
||||
<mask id="path-10-inside-1_537_80284" fill="white">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M141.683 27.5397C136.638 25.2942 130.727 27.5642 128.481 32.6099L115.951 60.7661C113.705 65.8118 115.975 71.7225 121.021 73.9681L138.971 81.9568L139.195 91.517C139.234 93.1892 141.187 94.0774 142.473 93.0083L149.912 86.8257L167.349 94.5859C172.394 96.8315 178.305 94.5615 180.551 89.5158L193.081 61.3596C195.327 56.3139 193.057 50.4031 188.011 48.1576L141.683 27.5397Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M141.683 27.5397C136.638 25.2942 130.727 27.5642 128.481 32.6099L115.951 60.7661C113.705 65.8118 115.975 71.7225 121.021 73.9681L138.971 81.9568L139.195 91.517C139.234 93.1892 141.187 94.0774 142.473 93.0083L149.912 86.8257L167.349 94.5859C172.394 96.8315 178.305 94.5615 180.551 89.5158L193.081 61.3596C195.327 56.3139 193.057 50.4031 188.011 48.1576L141.683 27.5397Z" fill="url(#paint9_linear_537_80284)"/>
|
||||
<path d="M128.481 32.6099L127.568 32.2033L128.481 32.6099ZM141.683 27.5397L142.09 26.6261L141.683 27.5397ZM115.951 60.7661L115.037 60.3595L115.951 60.7661ZM121.021 73.9681L121.427 73.0545L121.021 73.9681ZM138.971 81.9568L139.971 81.9333L139.956 81.3005L139.378 81.0431L138.971 81.9568ZM139.195 91.517L138.195 91.5405L139.195 91.517ZM142.473 93.0083L143.112 93.7773L142.473 93.0083ZM149.912 86.8257L150.318 85.9121L149.75 85.6594L149.272 86.0566L149.912 86.8257ZM167.349 94.5859L166.942 95.4995L167.349 94.5859ZM180.551 89.5158L179.637 89.1092L180.551 89.5158ZM193.081 61.3596L192.168 60.953L192.168 60.953L193.081 61.3596ZM188.011 48.1576L188.418 47.2439L188.418 47.2439L188.011 48.1576ZM129.395 33.0165C131.416 28.4753 136.735 26.4323 141.277 28.4533L142.09 26.6261C136.54 24.156 130.038 26.653 127.568 32.2033L129.395 33.0165ZM116.864 61.1727L129.395 33.0165L127.568 32.2033L115.037 60.3595L116.864 61.1727ZM121.427 73.0545C116.886 71.0335 114.843 65.7138 116.864 61.1727L115.037 60.3595C112.567 65.9098 115.064 72.4116 120.614 74.8817L121.427 73.0545ZM139.378 81.0431L121.427 73.0545L120.614 74.8817L138.565 82.8704L139.378 81.0431ZM140.195 91.4936L139.971 81.9333L137.971 81.9802L138.195 91.5405L140.195 91.4936ZM141.834 92.2392C141.191 92.7738 140.214 92.3297 140.195 91.4936L138.195 91.5405C138.254 94.0487 141.183 95.381 143.112 93.7773L141.834 92.2392ZM149.272 86.0566L141.834 92.2392L143.112 93.7773L150.551 87.5947L149.272 86.0566ZM167.755 93.6723L150.318 85.9121L149.505 87.7393L166.942 95.4995L167.755 93.6723ZM179.637 89.1092C177.616 93.6503 172.296 95.6933 167.755 93.6723L166.942 95.4995C172.492 97.9696 178.994 95.4727 181.464 89.9224L179.637 89.1092ZM192.168 60.953L179.637 89.1092L181.464 89.9224L193.995 61.7662L192.168 60.953ZM187.605 49.0712C192.146 51.0922 194.189 56.4118 192.168 60.953L193.995 61.7662C196.465 56.2159 193.968 49.7141 188.418 47.2439L187.605 49.0712ZM141.277 28.4533L187.605 49.0712L188.418 47.2439L142.09 26.6261L141.277 28.4533Z" fill="url(#paint10_linear_537_80284)" mask="url(#path-10-inside-1_537_80284)"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 135.661 47.835)" fill="#F3779A"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 134.198 47.8135)" fill="#F2F2F8"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 152.811 55.4512)" fill="#F3779A"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 151.348 55.4297)" fill="#F2F2F8"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 169.96 63.0664)" fill="#F3779A"/>
|
||||
<ellipse cx="4.40454" cy="4.40454" rx="4.40454" ry="4.40454" transform="matrix(0.894697 0.397317 -0.36312 0.950578 168.497 63.0449)" fill="#F2F2F8"/>
|
||||
<g filter="url(#filter1_f_537_80284)">
|
||||
<path d="M49.2684 28.0889L51.3066 31.8156L55.0334 33.8538L51.3066 35.8921L49.2684 39.6188L47.2302 35.8921L43.5034 33.8538L47.2302 31.8156L49.2684 28.0889Z" fill="url(#paint11_radial_537_80284)" fill-opacity="0.6"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_f_537_80284)">
|
||||
<path d="M211.574 13.8984L214.553 19.3452L220 22.3242L214.553 25.3031L211.574 30.7499L208.595 25.3031L203.148 22.3242L208.595 19.3452L211.574 13.8984Z" fill="url(#paint12_radial_537_80284)" fill-opacity="0.6"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_f_537_80284)">
|
||||
<path d="M157.472 111.903L159.04 114.77L161.907 116.338L159.04 117.906L157.472 120.772L155.904 117.906L153.038 116.338L155.904 114.77L157.472 111.903Z" fill="url(#paint13_radial_537_80284)" fill-opacity="0.6"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_537_80284" x="-13.7664" y="0.646484" width="179.161" height="174.27" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="2" dy="7"/>
|
||||
<feGaussianBlur stdDeviation="20"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.768627 0 0 0 0 0.768627 0 0 0 0 0.768627 0 0 0 0.3 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_537_80284"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_537_80284" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_537_80284" x="41.5034" y="26.0889" width="15.53" height="15.5303" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_537_80284"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_537_80284" x="201.148" y="11.8984" width="20.8516" height="20.8516" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_537_80284"/>
|
||||
</filter>
|
||||
<filter id="filter3_f_537_80284" x="151.038" y="109.903" width="12.8691" height="12.8691" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_537_80284"/>
|
||||
</filter>
|
||||
<radialGradient id="paint0_radial_537_80284" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(64.2319 119.062) rotate(53.6887) scale(22.2166 32.3419)">
|
||||
<stop stop-color="#A3A8BD"/>
|
||||
<stop offset="0.317708" stop-color="#F4F4F8"/>
|
||||
<stop offset="0.484375" stop-color="white"/>
|
||||
<stop offset="0.905609" stop-color="#A3A8BD"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint1_linear_537_80284" x1="27.4436" y1="121.838" x2="38.7291" y2="75.4951" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A3A8BD"/>
|
||||
<stop offset="0.633096" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_537_80284" x1="57.3936" y1="52.3904" x2="84.8316" y2="131.923" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.274562" stop-color="white"/>
|
||||
<stop offset="1" stop-color="#D0D0E3"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_537_80284" x1="107.215" y1="-12.9999" x2="123.25" y2="140.807" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.263084" stop-color="#A3A8BD"/>
|
||||
<stop offset="0.562167" stop-color="#E8E8F1"/>
|
||||
<stop offset="0.984375" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_537_80284" x1="104.956" y1="-24.173" x2="192.164" y2="211.881" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.264071" stop-color="#FCFCFF"/>
|
||||
<stop offset="1" stop-color="#A3A8BD"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_537_80284" x1="101.07" y1="99.4437" x2="97.8544" y2="69.0231" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.0252272" stop-color="#B5B8C7"/>
|
||||
<stop offset="0.242539" stop-color="white"/>
|
||||
<stop offset="0.588639" stop-color="#BFC2CF"/>
|
||||
<stop offset="0.844082" stop-color="white"/>
|
||||
<stop offset="1" stop-color="#CACCD8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_537_80284" x1="107.604" y1="109.753" x2="106.275" y2="67.1411" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A3A8BD"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_537_80284" x1="185.971" y1="95.4039" x2="127.478" y2="-6.07794" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E70041"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_537_80284" x1="133.137" y1="21.1212" x2="196.784" y2="96.99" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#E90043"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear_537_80284" x1="192.807" y1="129.438" x2="128.953" y2="21.5313" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint10_linear_537_80284" x1="292.52" y1="53.9106" x2="118.141" y2="70.9057" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint11_radial_537_80284" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(49.2684 33.8538) rotate(90) scale(6.65188)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="0.947917" stop-color="white"/>
|
||||
<stop offset="1" stop-color="#F02E65"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint12_radial_537_80284" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(211.574 22.3242) rotate(90) scale(15.0776)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="0.598958" stop-color="white"/>
|
||||
<stop offset="0.75" stop-color="white"/>
|
||||
<stop offset="1" stop-color="#F02E65"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint13_radial_537_80284" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(157.472 116.338) rotate(90) scale(6.43015)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="0.729167" stop-color="white"/>
|
||||
<stop offset="1" stop-color="#F02E65"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -1,27 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import {
|
||||
AvatarInitials,
|
||||
DropList,
|
||||
DropListItem,
|
||||
DropListLink,
|
||||
FeedbackGeneral
|
||||
} from '$lib/components';
|
||||
import { app, feedback } from '$lib/stores/app';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { organizationList, organization, newOrgModal } from '$lib/stores/organization';
|
||||
import AppwriteLogo from '$lib/images/appwrite-gray-light.svg';
|
||||
import LightMode from '$lib/images/mode/light-mode.svg';
|
||||
import DarkMode from '$lib/images/mode/dark-mode.svg';
|
||||
import SystemMode from '$lib/images/mode/system-mode.svg';
|
||||
import { FeedbackNPS } from '$lib/components';
|
||||
|
||||
import { slide } from 'svelte/transition';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackEvent } from '$lib/actions/analytics';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { goto } from '$app/navigation';
|
||||
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 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';
|
||||
import SystemMode from '$lib/images/mode/system-mode.svg';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { feedback } from '$lib/stores/feedback';
|
||||
import { newOrgModal, organization, organizationList } from '$lib/stores/organization';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
let showDropdown = false;
|
||||
let droplistElement: HTMLDivElement;
|
||||
@@ -78,11 +73,7 @@
|
||||
<span class="text">Feedback</span>
|
||||
</button>
|
||||
<svelte:fragment slot="other">
|
||||
{#if $feedback.type === 'nps'}
|
||||
<FeedbackNPS />
|
||||
{:else}
|
||||
<FeedbackGeneral />
|
||||
{/if}
|
||||
<Feedback />
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
<a
|
||||
@@ -92,9 +83,9 @@
|
||||
class="button is-small is-text">
|
||||
<span class="text">Support</span>
|
||||
</a>
|
||||
<button class="button is-text is-small" on:click={toggleCommandCenter}>
|
||||
<Button text class="is-small" on:click={toggleCommandCenter}>
|
||||
<i class="icon-search" />
|
||||
</button>
|
||||
</Button>
|
||||
</nav>
|
||||
<nav class="u-flex u-height-100-percent u-sep-inline-start">
|
||||
{#if $user}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
function isDeployment(data: Models.Deployment | Models.Execution): data is Models.Deployment {
|
||||
if ('buildId' in data) {
|
||||
selectedTab = 'logs';
|
||||
rawData = `${sdk.forConsole.client.config.endpoint}/functions/${$log.func.$id}/deployment/${$log.data.$id}?mode=admin&project=${$page.params.project}`;
|
||||
rawData = `${sdk.forConsole.client.config.endpoint}/functions/${$log.func.$id}/deployments/${$log.data.$id}?mode=admin&project=${$page.params.project}`;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
function isExecution(data: Models.Deployment | Models.Execution): data is Models.Execution {
|
||||
if ('trigger' in data) {
|
||||
selectedTab = 'response';
|
||||
rawData = `${sdk.forConsole.client.config.endpoint}/functions/${$log.func.$id}/execution/${$log.data.$id}?mode=admin&project=${$page.params.project}`;
|
||||
rawData = `${sdk.forConsole.client.config.endpoint}/functions/${$log.func.$id}/executions/${$log.data.$id}?mode=admin&project=${$page.params.project}`;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { isMac } from '$lib/helpers/platform';
|
||||
import { slide } from '$lib/helpers/transition';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import Create from '$routes/console/feedbackWizard.svelte';
|
||||
|
||||
export let isOpen = false;
|
||||
|
||||
$: project = $page.params.project;
|
||||
$: projectPath = `${base}/console/project-${project}`;
|
||||
@@ -27,6 +32,15 @@
|
||||
narrow = !narrow;
|
||||
}
|
||||
}
|
||||
|
||||
function openWizard() {
|
||||
isOpen = false;
|
||||
wizard.start(Create);
|
||||
}
|
||||
|
||||
beforeNavigate(() => {
|
||||
wizard.hide();
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeyDown} />
|
||||
@@ -122,6 +136,24 @@
|
||||
<span class="text">Storage</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item is-only-mobile">
|
||||
<a
|
||||
class="drop-button"
|
||||
href={`${projectPath}/settings`}
|
||||
on:click={() => trackEvent('click_menu_settings')}
|
||||
class:is-selected={$page.url.pathname.startsWith(
|
||||
`${projectPath}/settings`
|
||||
)}
|
||||
title="Settings"
|
||||
use:tooltip={{
|
||||
content: 'Settings',
|
||||
placement: 'right',
|
||||
disabled: !narrow
|
||||
}}>
|
||||
<span class="icon-cog" aria-hidden="true" />
|
||||
<span class="text">Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
@@ -129,7 +161,7 @@
|
||||
<div class="side-nav-bottom">
|
||||
<section class="drop-section">
|
||||
<a
|
||||
class="drop-button"
|
||||
class="drop-button is-only-desktop"
|
||||
href={`${projectPath}/settings`}
|
||||
on:click={() => trackEvent('click_menu_settings')}
|
||||
class:is-selected={$page.url.pathname.startsWith(`${projectPath}/settings`)}
|
||||
@@ -142,6 +174,13 @@
|
||||
<span class="icon-cog" aria-hidden="true" />
|
||||
<span class="text">Settings</span>
|
||||
</a>
|
||||
<ul class="drop-list is-only-mobile">
|
||||
<li class="drop-list-item">
|
||||
<button class="drop-button" on:click={openWizard}>
|
||||
<span class="text">Feedback</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { VARS } from '$lib/system';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import type { Feedback } from './feedback';
|
||||
|
||||
export type AppStore = {
|
||||
theme: 'light' | 'dark' | 'auto';
|
||||
themeInUse: 'light' | 'dark';
|
||||
};
|
||||
|
||||
export type Feedback = {
|
||||
elapsed: number;
|
||||
visualized: number;
|
||||
notification: boolean;
|
||||
type: 'nps' | 'general';
|
||||
show: boolean;
|
||||
};
|
||||
|
||||
export const app = writable<AppStore>({
|
||||
theme: 'auto',
|
||||
themeInUse: 'light'
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { VARS } from '$lib/system';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { SvelteComponent } from 'svelte';
|
||||
import FeedbackGeneral from '$lib/components/feedback/feedbackGeneral.svelte';
|
||||
import FeedbackNps from '$lib/components/feedback/feedbackNPS.svelte';
|
||||
|
||||
export type Feedback = {
|
||||
elapsed: number;
|
||||
visualized: number;
|
||||
notification: boolean;
|
||||
type: 'nps' | 'general';
|
||||
show: boolean;
|
||||
};
|
||||
|
||||
export type FeedbackData = {
|
||||
message: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
value?: number;
|
||||
};
|
||||
|
||||
export type FeedbackOption = {
|
||||
type: Feedback['type'];
|
||||
title: string;
|
||||
desc: string;
|
||||
component: typeof SvelteComponent;
|
||||
};
|
||||
|
||||
export const feedbackOptions: FeedbackOption[] = [
|
||||
{
|
||||
type: 'general',
|
||||
title: 'How can we improve?',
|
||||
desc: 'Your feedback is important to us. Please be honest and tell us what you think.',
|
||||
component: FeedbackGeneral
|
||||
},
|
||||
{
|
||||
type: 'nps',
|
||||
title: 'How are we doing?',
|
||||
desc: "At Appwrite, we value the feedback of our users. That means you! We'd love to hear what you think.",
|
||||
component: FeedbackNps
|
||||
}
|
||||
];
|
||||
|
||||
export const selectedFeedback = writable<FeedbackOption>();
|
||||
|
||||
function createFeedbackDataStore() {
|
||||
const { subscribe, update } = writable<FeedbackData>({
|
||||
message: '',
|
||||
name: '',
|
||||
email: '',
|
||||
value: 0
|
||||
});
|
||||
return {
|
||||
subscribe,
|
||||
update,
|
||||
reset: () => {
|
||||
update((feedbackData) => {
|
||||
feedbackData.message = '';
|
||||
feedbackData.name = '';
|
||||
feedbackData.email = '';
|
||||
feedbackData.value = 0;
|
||||
return feedbackData;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const feedbackData = createFeedbackDataStore();
|
||||
|
||||
function createFeedbackStore() {
|
||||
const { subscribe, update } = writable<Feedback>({
|
||||
elapsed: browser ? parseInt(localStorage.getItem('feedbackElapsed')) : 0,
|
||||
visualized: browser ? parseInt(localStorage.getItem('feedbackVisualized')) : 0,
|
||||
notification: false,
|
||||
type: 'general',
|
||||
show: false
|
||||
});
|
||||
return {
|
||||
subscribe,
|
||||
update,
|
||||
toggleFeedback: () => {
|
||||
update((feedback) => {
|
||||
feedback.show = !feedback.show;
|
||||
return feedback;
|
||||
});
|
||||
},
|
||||
toggleNotification: () =>
|
||||
update((feedback) => {
|
||||
feedback.notification = !feedback.notification;
|
||||
return feedback;
|
||||
}),
|
||||
switchType: (feedType: Feedback['type']) =>
|
||||
update((feedback) => {
|
||||
feedback.type = feedType;
|
||||
return feedback;
|
||||
}),
|
||||
addVisualization: () =>
|
||||
update((feedback) => {
|
||||
feedback.visualized += 1;
|
||||
localStorage.setItem('feedbackVisualized', feedback.visualized.toString());
|
||||
return feedback;
|
||||
}),
|
||||
|
||||
increaseElapsed: (time: number) => {
|
||||
update((feedback) => {
|
||||
feedback.elapsed += time;
|
||||
localStorage.setItem('feedbackElapsed', feedback.elapsed.toString());
|
||||
return feedback;
|
||||
});
|
||||
},
|
||||
submitFeedback: async (
|
||||
subject: string,
|
||||
message: string,
|
||||
firstname?: string,
|
||||
email?: string,
|
||||
value?: number
|
||||
) => {
|
||||
if (!VARS.GROWTH_ENDPOINT) return;
|
||||
const response = await fetch(`${VARS.GROWTH_ENDPOINT}/feedback`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
subject,
|
||||
message,
|
||||
email,
|
||||
firstname: firstname ? firstname : undefined,
|
||||
customFields: value ? [{ id: '40655', value }] : undefined
|
||||
})
|
||||
});
|
||||
if (response.status >= 400) {
|
||||
throw new Error('Failed to submit feedback');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
export const feedback = createFeedbackStore();
|
||||
@@ -177,6 +177,7 @@ type AppwriteInput = {
|
||||
type FirebaseInput = {
|
||||
provider: 'firebase';
|
||||
serviceAccount?: string;
|
||||
projectId?: string;
|
||||
};
|
||||
|
||||
type SupabaseInput = {
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import type { SvelteComponent } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import Apple from '../../routes/console/project-[project]/auth/appleOAuth.svelte';
|
||||
import Microsoft from '../../routes/console/project-[project]/auth/microsoftOAuth.svelte';
|
||||
import Okta from '../../routes/console/project-[project]/auth/oktaOAuth.svelte';
|
||||
import Auth0 from '../../routes/console/project-[project]/auth/auth0OAuth.svelte';
|
||||
import Authentik from '../../routes/console/project-[project]/auth/authentikOAuth.svelte';
|
||||
import GitLab from '../../routes/console/project-[project]/auth/gitlabOAuth.svelte';
|
||||
import Google from '../../routes/console/project-[project]/auth/googleOAuth.svelte';
|
||||
import Main from '../../routes/console/project-[project]/auth/mainOAuth.svelte';
|
||||
import Microsoft from '../../routes/console/project-[project]/auth/microsoftOAuth.svelte';
|
||||
import Oidc from '../../routes/console/project-[project]/auth/oidcOAuth.svelte';
|
||||
import Okta from '../../routes/console/project-[project]/auth/oktaOAuth.svelte';
|
||||
|
||||
export type Provider = Models.Provider & {
|
||||
key: string;
|
||||
icon: string;
|
||||
docs?: string;
|
||||
component?: typeof SvelteComponent;
|
||||
@@ -22,11 +25,12 @@ export type Providers = {
|
||||
const setProviders = (project: Models.Project): Provider[] => {
|
||||
return (
|
||||
project?.providers.map((n) => {
|
||||
const p = n as Models.Provider & { key: string };
|
||||
let docs: Provider['docs'];
|
||||
let icon: Provider['icon'] = n.name.toLowerCase();
|
||||
let icon: Provider['icon'] = p.key.toLowerCase();
|
||||
let component: Provider['component'] = Main;
|
||||
|
||||
switch (n.name.toLowerCase()) {
|
||||
switch (p.key.toLowerCase()) {
|
||||
case 'amazon':
|
||||
docs = 'https://developer.amazon.com/apps-and-games/services-and-apis';
|
||||
break;
|
||||
@@ -81,6 +85,7 @@ const setProviders = (project: Models.Project): Provider[] => {
|
||||
break;
|
||||
case 'google':
|
||||
docs = 'https://support.google.com/googleapi/answer/6158849';
|
||||
component = Google;
|
||||
break;
|
||||
case 'linkedin':
|
||||
docs = 'https://developer.linkedin.com/';
|
||||
@@ -92,6 +97,10 @@ const setProviders = (project: Models.Project): Provider[] => {
|
||||
case 'notion':
|
||||
docs = 'https://developers.notion.com/docs';
|
||||
break;
|
||||
case 'oidc':
|
||||
docs = 'https://openid.net/connect/faq/';
|
||||
component = Oidc;
|
||||
break;
|
||||
case 'okta':
|
||||
docs = 'https://developer.okta.com';
|
||||
component = Okta;
|
||||
@@ -149,7 +158,7 @@ const setProviders = (project: Models.Project): Provider[] => {
|
||||
}
|
||||
|
||||
return {
|
||||
...n,
|
||||
...p,
|
||||
icon,
|
||||
docs,
|
||||
component
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import type { SvelteComponent } from 'svelte';
|
||||
import type { ComponentType } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export type WizardStore = {
|
||||
show: boolean;
|
||||
media?: string;
|
||||
component?: typeof SvelteComponent;
|
||||
component?: ComponentType;
|
||||
interceptor?: () => Promise<void>;
|
||||
nextDisabled: boolean;
|
||||
};
|
||||
@@ -22,10 +22,11 @@ function createWizardStore() {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
start: (component: typeof SvelteComponent, media: string = null) =>
|
||||
start: (component: ComponentType, media: string = null) =>
|
||||
update((n) => {
|
||||
n.show = true;
|
||||
n.component = component;
|
||||
n.interceptor = null;
|
||||
n.media = media;
|
||||
trackEvent('wizard_start');
|
||||
return n;
|
||||
@@ -46,6 +47,7 @@ function createWizardStore() {
|
||||
update((n) => {
|
||||
n.show = false;
|
||||
n.component = null;
|
||||
n.interceptor = null;
|
||||
n.media = null;
|
||||
|
||||
return n;
|
||||
|
||||
@@ -2,20 +2,56 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Heading } from '$lib/components';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(async () => {
|
||||
const project = $page.url.searchParams.get('project');
|
||||
if (project) {
|
||||
await goto(`appwrite-callback-${project}://${$page.url.search}`);
|
||||
const project = $page.url.searchParams.get('project');
|
||||
const link = `appwrite-callback-${project}://${$page.url.search}`;
|
||||
|
||||
const redirect = new Promise((resolve, reject) => {
|
||||
if (!project) {
|
||||
reject('no-project');
|
||||
}
|
||||
// this timeout is needed because goto does not
|
||||
// throw an exception if the redirect does not work
|
||||
setTimeout(() => reject('timeout'), 500);
|
||||
// goto will resolve on successful redirect
|
||||
goto(link).then(resolve);
|
||||
});
|
||||
</script>
|
||||
|
||||
<Heading tag="h1" size="1">Missing Redirect URL</Heading>
|
||||
<p class="text">
|
||||
Your OAuth login flow is missing a proper redirect URL. Please check the
|
||||
<a class="link" href="https://appwrite.io/docs/client/account?sdk=web#createOAuth2Session"
|
||||
>OAuth docs</a>
|
||||
and send request for new session with a valid callback URL.
|
||||
</p>
|
||||
{#await redirect then}
|
||||
<article class="card u-padding-16">
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Heading tag="h1" size="4">Login failed</Heading>
|
||||
<p class="text">You will be automatically redirected back to your app shortly.</p>
|
||||
<p class="text">
|
||||
If you are not redirected, please click on the following
|
||||
<a class="link" href={`appwrite-callback-${project}://${$page.url.search}`}>link</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
{:catch}
|
||||
<article class="card u-padding-16">
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Heading tag="h1" size="4">Missing Redirect URL</Heading>
|
||||
<p class="text">
|
||||
Your OAuth login flow is missing a proper redirect URL. Please check the
|
||||
<a
|
||||
class="link"
|
||||
href="https://appwrite.io/docs/client/account?sdk=web#createOAuth2Session"
|
||||
>OAuth docs</a>
|
||||
and send request for new session with a valid callback URL.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
{/await}
|
||||
|
||||
<style lang="scss">
|
||||
@import '@appwrite.io/pink/src/abstract/variables/_devices.scss';
|
||||
// override padding for screens bigger than mobile
|
||||
@media #{$break2open} {
|
||||
article.card {
|
||||
padding: 2rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,20 +2,56 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Heading } from '$lib/components';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(async () => {
|
||||
const project = $page.url.searchParams.get('project');
|
||||
if (project) {
|
||||
await goto(`appwrite-callback-${project}://${$page.url.search}`);
|
||||
const project = $page.url.searchParams.get('project');
|
||||
const link = `appwrite-callback-${project}://${$page.url.search}`;
|
||||
|
||||
const redirect = new Promise((resolve, reject) => {
|
||||
if (!project) {
|
||||
reject('no-project');
|
||||
}
|
||||
// this timeout is needed because goto does not
|
||||
// throw an exception if the redirect does not work
|
||||
setTimeout(() => reject('timeout'), 500);
|
||||
// goto will resolve on successful redirect
|
||||
goto(link).then(resolve);
|
||||
});
|
||||
</script>
|
||||
|
||||
<Heading tag="h1" size="1">Missing Redirect URL</Heading>
|
||||
<p class="text">
|
||||
Your OAuth login flow is missing a proper redirect URL. Please check the
|
||||
<a class="link" href="https://appwrite.io/docs/client/account?sdk=web#createOAuth2Session"
|
||||
>OAuth docs</a>
|
||||
and send request for new session with a valid callback URL.
|
||||
</p>
|
||||
{#await redirect then}
|
||||
<article class="card u-padding-16">
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Heading tag="h1" size="4">You're now logged in</Heading>
|
||||
<p class="text">You will be automatically redirected back to your app shortly.</p>
|
||||
<p class="text">
|
||||
If you are not redirected, please click on the following
|
||||
<a class="link" href={`appwrite-callback-${project}://${$page.url.search}`}>link</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
{:catch}
|
||||
<article class="card u-padding-16">
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Heading tag="h1" size="4">Missing Redirect URL</Heading>
|
||||
<p class="text">
|
||||
Your OAuth login flow is missing a proper redirect URL. Please check the
|
||||
<a
|
||||
class="link"
|
||||
href="https://appwrite.io/docs/client/account?sdk=web#createOAuth2Session"
|
||||
>OAuth docs</a>
|
||||
and send request for new session with a valid callback URL.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
{/await}
|
||||
|
||||
<style lang="scss">
|
||||
@import '@appwrite.io/pink/src/abstract/variables/_devices.scss';
|
||||
// override padding for screens bigger than mobile
|
||||
@media #{$break2open} {
|
||||
article.card {
|
||||
padding: 2rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,8 +7,7 @@ import Wizard from './wizard.svelte';
|
||||
export const formData = createMigrationFormStore();
|
||||
|
||||
export function openMigrationWizard() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
wizard.start(Wizard as any);
|
||||
wizard.start(Wizard);
|
||||
const migData = get(requestedMigration);
|
||||
provider.set({
|
||||
provider: 'appwrite',
|
||||
|
||||
@@ -98,11 +98,17 @@
|
||||
break;
|
||||
}
|
||||
case 'firebase': {
|
||||
const res = await sdk.forProject.migrations.getFirebaseReport(
|
||||
providerResources.firebase,
|
||||
$provider.serviceAccount
|
||||
);
|
||||
report = res;
|
||||
if ($provider.projectId) {
|
||||
// OAuth
|
||||
} else if ($provider.serviceAccount) {
|
||||
// Manual auth
|
||||
const res = await sdk.forProject.migrations.getFirebaseReport(
|
||||
providerResources.firebase,
|
||||
$provider.serviceAccount
|
||||
);
|
||||
report = res;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'nhost': {
|
||||
@@ -131,7 +137,6 @@
|
||||
};
|
||||
});
|
||||
|
||||
$: console.log(report);
|
||||
$: resources = providerResources[$provider.provider];
|
||||
|
||||
// $: wizard.setNextDisabled(!report);
|
||||
@@ -158,15 +163,30 @@
|
||||
<p>Make sure to have enough storage in your project plan when importing files</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-currency-dollar" />
|
||||
{#if $provider.provider === 'firebase'}
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-exclamation u-color-text-warning" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Possible charges by Firebase</p>
|
||||
<p>
|
||||
Appwrite does not impose charges for importing data, but please note that
|
||||
Firebase may have its own pricing for this service
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Transfer is free of charge</p>
|
||||
<p>You won't be charged for Appwrite bandwidth usage for importing data</p>
|
||||
{:else}
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-currency-dollar" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Transfer is free of charge</p>
|
||||
<p>You won't be charged for Appwrite bandwidth usage for importing data</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -46,4 +46,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Create Function" {steps} on:exit={onExit} on:finish={onFinish} />
|
||||
<Wizard title="Create Project" {steps} on:exit={onExit} on:finish={onFinish} />
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
import { AIPanel, OrganizationsPanel, ProjectsPanel } from '$lib/commandCenter/panels';
|
||||
import { orgSearcher, projectsSearcher } from '$lib/commandCenter/searchers';
|
||||
import { addSubPanel } from '$lib/commandCenter/subPanels';
|
||||
import { openMigrationWizard } from './(migration-wizard)';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { openMigrationWizard } from './(migration-wizard)';
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
@@ -34,7 +34,7 @@
|
||||
icon: 'light-bulb'
|
||||
},
|
||||
{
|
||||
label: 'Go to Account',
|
||||
label: 'Go to account',
|
||||
callback: () => {
|
||||
goto('/console/account');
|
||||
},
|
||||
@@ -42,7 +42,7 @@
|
||||
group: 'navigation'
|
||||
},
|
||||
{
|
||||
label: 'Find an Organization',
|
||||
label: 'Find an organization',
|
||||
callback: () => {
|
||||
addSubPanel(OrganizationsPanel);
|
||||
},
|
||||
@@ -60,7 +60,7 @@
|
||||
icon: 'search'
|
||||
},
|
||||
{
|
||||
label: 'Create new Organization',
|
||||
label: 'Create new organization',
|
||||
callback: () => {
|
||||
newOrgModal.set(true);
|
||||
},
|
||||
@@ -68,7 +68,7 @@
|
||||
group: 'organizations'
|
||||
},
|
||||
{
|
||||
label: 'Go to Home',
|
||||
label: 'Go to home',
|
||||
callback: () => {
|
||||
goto('/console');
|
||||
},
|
||||
@@ -127,6 +127,7 @@
|
||||
icon: 'switch-horizontal'
|
||||
}
|
||||
]);
|
||||
let isOpen = false;
|
||||
|
||||
onMount(() => {
|
||||
loading.set(false);
|
||||
@@ -168,12 +169,13 @@
|
||||
<CommandCenter />
|
||||
|
||||
<Shell
|
||||
bind:isOpen
|
||||
showSideNavigation={$page.url.pathname !== '/console' &&
|
||||
!$page?.params.organization &&
|
||||
!$page.url.pathname.includes('/console/account') &&
|
||||
!$page.url.pathname.includes('/console/onboarding')}>
|
||||
<Header slot="header" />
|
||||
<SideNavigation slot="side" />
|
||||
<SideNavigation slot="side" bind:isOpen />
|
||||
<slot />
|
||||
<Footer slot="footer" />
|
||||
</Shell>
|
||||
|
||||
@@ -3,7 +3,12 @@ import { sdk } from '$lib/stores/sdk';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load: LayoutLoad = async () => {
|
||||
const response = await fetch(`${sdk.forConsole.client.config.endpoint}/health/version`);
|
||||
const { endpoint, project } = sdk.forConsole.client.config;
|
||||
const response = await fetch(`${endpoint}/health/version`, {
|
||||
headers: {
|
||||
'X-Appwrite-Project': project
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { Wizard } from '$lib/layout';
|
||||
import { onDestroy } from 'svelte';
|
||||
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
|
||||
import { feedbackData } from '$lib/stores/feedback';
|
||||
import Step1 from './wizardFeedback/step1.svelte';
|
||||
import Step2 from './wizardFeedback/step2.svelte';
|
||||
|
||||
onDestroy(() => {
|
||||
feedbackData.reset();
|
||||
});
|
||||
|
||||
const stepsComponents: WizardStepsType = new Map();
|
||||
stepsComponents.set(1, {
|
||||
label: 'Feedback',
|
||||
component: Step1
|
||||
});
|
||||
stepsComponents.set(2, {
|
||||
label: 'Thank you',
|
||||
component: Step2,
|
||||
optional: true
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Feedback" steps={stepsComponents} finalAction="Close" />
|
||||
@@ -14,16 +14,18 @@
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Container } from '$lib/layout';
|
||||
import { services } from '$lib/stores/project-services';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { loading } from '$routes/store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import CreateOrganization from '../createOrganization.svelte';
|
||||
import { openImportWizard } from '../project-[project]/settings/migrations/(import)';
|
||||
import type { PageData } from './$types';
|
||||
import CreateProject from './createProject.svelte';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { goto } from '$app/navigation';
|
||||
import { openImportWizard } from '../project-[project]/settings/migrations/(import)';
|
||||
import { loading } from '$routes/store';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -54,6 +56,17 @@
|
||||
return { name, icon };
|
||||
};
|
||||
|
||||
function allServiceDisabled(project: Models.Project): boolean {
|
||||
let disabled = true;
|
||||
services.load(project);
|
||||
$services.list.forEach((service) => {
|
||||
if (service.value) {
|
||||
disabled = false;
|
||||
}
|
||||
});
|
||||
return disabled;
|
||||
}
|
||||
|
||||
function filterPlatforms(platforms: { name: string; icon: string }[]) {
|
||||
return platforms.filter(
|
||||
(value, index, self) => index === self.findIndex((t) => t.name === value.name)
|
||||
@@ -129,6 +142,11 @@
|
||||
<svelte:fragment slot="title">
|
||||
{project.name}
|
||||
</svelte:fragment>
|
||||
{#if allServiceDisabled(project)}
|
||||
<p>
|
||||
<span class="icon-pause" aria-hidden="true" /> All services are disabled.
|
||||
</p>
|
||||
{/if}
|
||||
{@const platforms = filterPlatforms(
|
||||
project.platforms.map((platform) => getPlatformInfo(platform.type))
|
||||
)}
|
||||
|
||||
@@ -5,13 +5,15 @@
|
||||
import { project, stats } from './store';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import {
|
||||
addSubPanel,
|
||||
registerCommands,
|
||||
registerSearchers,
|
||||
updateCommandGroupRanks
|
||||
} from '$lib/commandCenter';
|
||||
import { addSubPanel, registerCommands, registerSearchers } from '$lib/commandCenter';
|
||||
|
||||
import {
|
||||
BucketsPanel,
|
||||
DatabasesPanel,
|
||||
FunctionsPanel,
|
||||
TeamsPanel,
|
||||
UsersPanel
|
||||
} from '$lib/commandCenter/panels';
|
||||
import {
|
||||
bucketSearcher,
|
||||
dbSearcher,
|
||||
@@ -20,13 +22,6 @@
|
||||
userSearcher
|
||||
} from '$lib/commandCenter/searchers';
|
||||
import { MigrationBox } from '$lib/components';
|
||||
import {
|
||||
UsersPanel,
|
||||
TeamsPanel,
|
||||
DatabasesPanel,
|
||||
FunctionsPanel,
|
||||
BucketsPanel
|
||||
} from '$lib/commandCenter/panels';
|
||||
|
||||
onMount(async () => {
|
||||
return sdk.forConsole.client.subscribe(['project', 'console'], (response) => {
|
||||
@@ -40,7 +35,7 @@
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Go to Overview',
|
||||
label: 'Go to overview',
|
||||
callback: () => {
|
||||
goto(`/console/project-${$project.$id}`);
|
||||
},
|
||||
@@ -49,7 +44,7 @@
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Go to Auth',
|
||||
label: 'Go to auth',
|
||||
callback: () => {
|
||||
goto(`/console/project-${$project.$id}/auth`);
|
||||
},
|
||||
@@ -57,7 +52,7 @@
|
||||
group: 'navigation'
|
||||
},
|
||||
{
|
||||
label: 'Go to Databases',
|
||||
label: 'Go to databases',
|
||||
callback: () => {
|
||||
goto(`/console/project-${$project.$id}/databases`);
|
||||
},
|
||||
@@ -65,7 +60,7 @@
|
||||
group: 'navigation'
|
||||
},
|
||||
{
|
||||
label: 'Go to Functions',
|
||||
label: 'Go to functions',
|
||||
callback: () => {
|
||||
goto(`/console/project-${$project.$id}/functions`);
|
||||
},
|
||||
@@ -73,7 +68,7 @@
|
||||
group: 'navigation'
|
||||
},
|
||||
{
|
||||
label: 'Go to Storage',
|
||||
label: 'Go to storage',
|
||||
callback: () => {
|
||||
goto(`/console/project-${$project.$id}/storage`);
|
||||
},
|
||||
@@ -81,7 +76,7 @@
|
||||
group: 'navigation'
|
||||
},
|
||||
{
|
||||
label: 'Go to Settings',
|
||||
label: 'Go to settings',
|
||||
callback: () => {
|
||||
goto(`/console/project-${$project.$id}/settings`);
|
||||
},
|
||||
@@ -89,7 +84,7 @@
|
||||
group: 'navigation'
|
||||
},
|
||||
{
|
||||
label: 'Find Users',
|
||||
label: 'Find users',
|
||||
callback: () => {
|
||||
addSubPanel(UsersPanel);
|
||||
},
|
||||
@@ -99,7 +94,7 @@
|
||||
rank: 10
|
||||
},
|
||||
{
|
||||
label: 'Find Teams',
|
||||
label: 'Find teams',
|
||||
callback: () => {
|
||||
addSubPanel(TeamsPanel);
|
||||
},
|
||||
@@ -108,7 +103,7 @@
|
||||
keys: ['f', 't']
|
||||
},
|
||||
{
|
||||
label: 'Find Databases',
|
||||
label: 'Find databases',
|
||||
callback: () => {
|
||||
addSubPanel(DatabasesPanel);
|
||||
},
|
||||
@@ -117,7 +112,7 @@
|
||||
keys: ['f', 'd']
|
||||
},
|
||||
{
|
||||
label: 'Find Functions',
|
||||
label: 'Find functions',
|
||||
callback: () => {
|
||||
addSubPanel(FunctionsPanel);
|
||||
},
|
||||
@@ -126,7 +121,7 @@
|
||||
keys: ['f', 'f']
|
||||
},
|
||||
{
|
||||
label: 'Find Buckets',
|
||||
label: 'Find buckets',
|
||||
callback: () => {
|
||||
addSubPanel(BucketsPanel);
|
||||
},
|
||||
|
||||
@@ -25,15 +25,22 @@
|
||||
TableHeader,
|
||||
TableRowLink
|
||||
} from '$lib/elements/table';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { Container } from '$lib/layout';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { PageData } from './$types';
|
||||
import Create from './createUser.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
// TODO: Remove this when the console SDK is updated
|
||||
const users = data.users.users.map((user) => {
|
||||
const labels: string[] = [];
|
||||
const accessedAt = '';
|
||||
return { accessedAt, labels, ...user };
|
||||
});
|
||||
|
||||
const projectId = $page.params.project;
|
||||
async function userCreated(event: CustomEvent<Models.User<Record<string, unknown>>>) {
|
||||
await goto(`${base}/console/project-${projectId}/auth/user-${event.detail.$id}`);
|
||||
@@ -53,10 +60,12 @@
|
||||
<TableCellHead onlyDesktop>Identifiers</TableCellHead>
|
||||
<TableCellHead onlyDesktop width={130}>Status</TableCellHead>
|
||||
<TableCellHead onlyDesktop width={100}>ID</TableCellHead>
|
||||
<TableCellHead onlyDesktop width={100}>Labels</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Joined</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Last Activity</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.users.users as user}
|
||||
{#each users as user}
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${projectId}/auth/user-${user.$id}`}>
|
||||
<TableCell title="Name">
|
||||
@@ -106,9 +115,15 @@
|
||||
</Pill>
|
||||
</Copy>
|
||||
</TableCell>
|
||||
<TableCellText onlyDesktop title="Labels">
|
||||
{user.labels.join(', ')}
|
||||
</TableCellText>
|
||||
<TableCellText onlyDesktop title="Joined">
|
||||
{toLocaleDateTime(user.registration)}
|
||||
</TableCellText>
|
||||
<TableCellText onlyDesktop title="Last Activity">
|
||||
{user.accessedAt ? toLocaleDate(user.accessedAt) : 'never'}
|
||||
</TableCellText>
|
||||
</TableRowLink>
|
||||
{/each}
|
||||
</TableBody>
|
||||
|
||||
@@ -63,9 +63,7 @@
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdk.forConsole.client.config.endpoint
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -72,9 +72,7 @@
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdk.forConsole.client.config.endpoint
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -72,9 +72,7 @@
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdk.forConsole.client.config.endpoint
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -70,9 +70,7 @@
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdk.forConsole.client.config.endpoint
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Alert, CopyInput, Modal } from '$lib/components';
|
||||
import { Button, FormList, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { updateOAuth } from './updateOAuth';
|
||||
|
||||
export let provider: Provider;
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
let enabled: boolean = null;
|
||||
let appId: string = null;
|
||||
let secret: string = null;
|
||||
|
||||
onMount(() => {
|
||||
enabled ??= provider.enabled;
|
||||
appId ??= provider.appId;
|
||||
secret ??= provider.secret;
|
||||
});
|
||||
|
||||
let error: string;
|
||||
|
||||
const update = async () => {
|
||||
const result = await updateOAuth({ projectId, provider, secret, appId, enabled });
|
||||
|
||||
if (result.status === 'error') {
|
||||
error = result.message;
|
||||
} else {
|
||||
provider = null;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal {error} size="big" show onSubmit={update} on:close>
|
||||
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer"
|
||||
>visit the docs.</a>
|
||||
</p>
|
||||
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
|
||||
<InputText
|
||||
id="appID"
|
||||
label="App ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="App Secret"
|
||||
placeholder="Enter App Secret"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
bind:value={secret} />
|
||||
<Alert type="info">
|
||||
To complete the setup, create an OAuth2 client ID with "Web application" as the
|
||||
application type, then add this redirect URI to your {provider.name} configuration.
|
||||
</Alert>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdk.forConsole.client.config.endpoint
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={!appId ||
|
||||
!secret ||
|
||||
(appId === provider.appId &&
|
||||
secret === provider.secret &&
|
||||
enabled === provider.enabled)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -63,9 +63,7 @@
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdk.forConsole.client.config.endpoint
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -72,9 +72,7 @@
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdk.forConsole.client.config.endpoint
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Alert, CopyInput, Modal } from '$lib/components';
|
||||
import { Button, FormList, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { updateOAuth } from './updateOAuth';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
export let provider: Provider;
|
||||
|
||||
let appId: string = null;
|
||||
let enabled: boolean = null;
|
||||
let clientSecret: string = null;
|
||||
let wellKnownEndpoint: string = null;
|
||||
let authorizationEndpoint: string = null;
|
||||
let tokenEndpoint: string = null;
|
||||
let userinfoEndpoint: string = null;
|
||||
let error: string;
|
||||
|
||||
// secret is valid if clientSecret is set and either wellKnownEndpoint or all of the other endpoints are set
|
||||
$: isValidSecret =
|
||||
clientSecret &&
|
||||
(wellKnownEndpoint || (authorizationEndpoint && tokenEndpoint && userinfoEndpoint));
|
||||
|
||||
onMount(() => {
|
||||
appId ??= provider.appId;
|
||||
enabled ??= provider.enabled;
|
||||
if (provider.secret) {
|
||||
({
|
||||
clientSecret,
|
||||
wellKnownEndpoint,
|
||||
authorizationEndpoint,
|
||||
tokenEndpoint,
|
||||
userinfoEndpoint
|
||||
} = JSON.parse(provider.secret));
|
||||
}
|
||||
});
|
||||
|
||||
const update = async () => {
|
||||
const result = await updateOAuth({ projectId, provider, secret, appId, enabled });
|
||||
|
||||
if (result.status === 'error') {
|
||||
error = result.message;
|
||||
} else {
|
||||
provider = null;
|
||||
}
|
||||
};
|
||||
|
||||
$: secret = isValidSecret
|
||||
? JSON.stringify({
|
||||
clientSecret,
|
||||
wellKnownEndpoint,
|
||||
authorizationEndpoint,
|
||||
tokenEndpoint,
|
||||
userinfoEndpoint
|
||||
})
|
||||
: provider.secret;
|
||||
</script>
|
||||
|
||||
<Modal {error} onSubmit={update} size="big" show on:close>
|
||||
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer"
|
||||
>visit the docs.</a>
|
||||
</p>
|
||||
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
|
||||
<InputText
|
||||
id="appID"
|
||||
label="Client ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
bind:value={clientSecret} />
|
||||
<InputText
|
||||
id="well-known-endpoint"
|
||||
label="Well-Known Endpoint"
|
||||
placeholder="https://example.com/.well-known/openid-configuration"
|
||||
bind:value={wellKnownEndpoint} />
|
||||
<InputText
|
||||
id="authorization-endpoint"
|
||||
label="Authorization Endpoint"
|
||||
placeholder="https://example.com/authorize"
|
||||
bind:value={authorizationEndpoint} />
|
||||
<InputText
|
||||
id="token-endpoint"
|
||||
label="Token Endpoint"
|
||||
placeholder="https://example.com/token"
|
||||
bind:value={tokenEndpoint} />
|
||||
<InputText
|
||||
id="userinfo-endpoint"
|
||||
label="User Info Endpoint"
|
||||
placeholder="https://example.com/userinfo"
|
||||
bind:value={userinfoEndpoint} />
|
||||
|
||||
<Alert type="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && isValidSecret)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -82,9 +82,7 @@
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${
|
||||
sdk.forConsole.client.config.endpoint
|
||||
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { InputSwitch } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { authMethods, type AuthMethod } from '$lib/stores/auth-methods';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { OAuthProviders } from '$lib/stores/oauth-providers';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { project } from '../../store';
|
||||
import { authMethods, type AuthMethod } from '$lib/stores/auth-methods';
|
||||
import { OAuthProviders } from '$lib/stores/oauth-providers';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { page } from '$app/stores';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
@@ -75,10 +75,10 @@
|
||||
on:click={() => {
|
||||
selectedProvider = provider;
|
||||
trackEvent(`click_select_provider`, {
|
||||
provider: provider.name.toLowerCase()
|
||||
provider: provider.key.toLowerCase()
|
||||
});
|
||||
}}>
|
||||
<div class="image-item">
|
||||
<div class="avatar">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function updateOAuth({
|
||||
try {
|
||||
await sdk.forConsole.projects.updateOAuth2(
|
||||
projectId,
|
||||
provider.name.toLowerCase(),
|
||||
provider.key,
|
||||
appId || undefined,
|
||||
secret || undefined,
|
||||
enabled
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Container } from '$lib/layout';
|
||||
import DangerZone from './dangerZone.svelte';
|
||||
import UpdateEmail from './updateEmail.svelte';
|
||||
import UpdateLabels from './updateLabels.svelte';
|
||||
import UpdateName from './updateName.svelte';
|
||||
import UpdatePassword from './updatePassword.svelte';
|
||||
import UpdatePhone from './updatePhone.svelte';
|
||||
@@ -15,6 +16,7 @@
|
||||
<UpdateEmail />
|
||||
<UpdatePhone />
|
||||
<UpdatePassword />
|
||||
<UpdateLabels />
|
||||
<UpdatePrefs />
|
||||
<DangerZone />
|
||||
</Container>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import { user } from './store';
|
||||
import { goto } from '$app/navigation';
|
||||
import { project } from '../../store';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
$: accessedAt = ($user as unknown as { accessedAt: string }).accessedAt;
|
||||
</script>
|
||||
|
||||
<CardGrid danger>
|
||||
@@ -54,6 +56,7 @@
|
||||
? [$user.email, $user.phone].join(',')
|
||||
: $user.email || $user.phone}
|
||||
</p>
|
||||
<p>Last activity: {accessedAt ? toLocaleDate(accessedAt) : 'never'}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
try {
|
||||
await sdk.forProject.users.deleteSession($page.params.user, selectedSessionId);
|
||||
await invalidate(Dependencies.SESSIONS);
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Session has been deleted'
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { EmptySearch } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/elements/table';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import DeleteAllSessions from '../deleteAllSessions.svelte';
|
||||
@@ -47,7 +47,7 @@
|
||||
<TableRow>
|
||||
<TableCell title="Client">
|
||||
<div class="u-flex u-gap-12 u-cross-center">
|
||||
<div class="image-item">
|
||||
<div class="avatar">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
<Form onSubmit={updateEmail}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Email</Heading>
|
||||
<p>
|
||||
Update user's email. An Email should be formatted as: <span class="inline-code"
|
||||
>name@example.com</span
|
||||
>.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputEmail
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, Helper, InputTags } from '$lib/elements/forms';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { user } from './store';
|
||||
import { Pill } from '$lib/elements';
|
||||
|
||||
const alphaNumericRegExp = /^[a-zA-Z0-9]+$/;
|
||||
let suggestedLabels = ['admin', 'premium', 'mvp'];
|
||||
let labels: string[] = [];
|
||||
let error = '';
|
||||
|
||||
onMount(async () => {
|
||||
// TODO: Remove type cast when console SDK is updated
|
||||
labels = [...($user as unknown as { labels: string[] }).labels];
|
||||
});
|
||||
|
||||
async function updateLabels() {
|
||||
try {
|
||||
// TODO: Use SDK method when console SDK is updated
|
||||
// await sdk.forProject.users.updateLabels($user.$id, labels);
|
||||
const path = `/users/${$user.$id}/labels`;
|
||||
await sdk.forProject.client.call(
|
||||
'PUT',
|
||||
new URL(sdk.forConsole.client.config.endpoint + path),
|
||||
{
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
{
|
||||
labels: labels
|
||||
}
|
||||
);
|
||||
await invalidate(Dependencies.USER);
|
||||
isDisabled = true;
|
||||
|
||||
addNotification({
|
||||
message: 'User labels have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.UserUpdateLabels);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.UserUpdateLabels);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove type cast when console SDK is updated
|
||||
$: isDisabled =
|
||||
!!error ||
|
||||
!symmetricDifference(labels, ($user as unknown as { labels: string[] }).labels).length;
|
||||
$: if (labels) {
|
||||
const invalidLabels = [];
|
||||
|
||||
labels.forEach((label) => {
|
||||
if (!alphaNumericRegExp.test(label)) {
|
||||
invalidLabels.push(label);
|
||||
}
|
||||
});
|
||||
|
||||
if (invalidLabels.length) {
|
||||
error = `Invalid labels: ${invalidLabels.join(', ')}`;
|
||||
} else {
|
||||
error = '';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updateLabels}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Labels</Heading>
|
||||
<p class="text">
|
||||
Categorize and manage your users based on specific criteria by assigning them
|
||||
customizable labels. New label-based roles will be assigned.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul class="common-section">
|
||||
<InputTags
|
||||
id="user-labels"
|
||||
label="Labels"
|
||||
placeholder="Select or tyype user labels"
|
||||
bind:tags={labels} />
|
||||
<li>
|
||||
<Helper type={error ? 'warning' : 'neutral'}
|
||||
>{error ? error : 'Only alphanumeric characters are allowed'}</Helper>
|
||||
</li>
|
||||
<li class="u-flex u-gap-12 u-margin-block-start-8">
|
||||
{#each suggestedLabels as suggestedLabel}
|
||||
<Pill
|
||||
selected={labels.includes(suggestedLabel)}
|
||||
button
|
||||
on:click={() => {
|
||||
if (!labels.includes(suggestedLabel)) {
|
||||
labels = [...labels, suggestedLabel];
|
||||
} else {
|
||||
labels = labels.filter((e) => e !== suggestedLabel);
|
||||
}
|
||||
}}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
{suggestedLabel}
|
||||
</Pill>
|
||||
{/each}
|
||||
</li>
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={isDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -33,9 +33,7 @@
|
||||
<Heading tag="h6" size="7">Password</Heading>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Enter a new password. A password must contain <b>at least 8 characters.</b>
|
||||
</p>
|
||||
<p>Enter a new password. A password must contain at least 8 characters.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputPassword
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
<Form onSubmit={updatePhone}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Phone</Heading>
|
||||
<p>
|
||||
Update user's phone. A phone number must contain leading '+' and maximum of 15 digits.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputPhone
|
||||
|
||||
@@ -56,11 +56,8 @@
|
||||
|
||||
<Form onSubmit={updatePrefs}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">User Preferences</Heading>
|
||||
<p>
|
||||
You can update your user preferences by storing information on the user's objects so
|
||||
they can easily be shared across devices and sessions.
|
||||
</p>
|
||||
<Heading tag="h6" size="7">Preferences</Heading>
|
||||
<p>Add custom user preferences to share them across devices and sessions.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<form class="form u-grid u-gap-16">
|
||||
<ul class="form-list">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { user } from './store';
|
||||
@@ -71,6 +71,9 @@
|
||||
trackError(error, Submit.UserUpdateStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove this when the console SDK is updated
|
||||
$: accessedAt = ($user as unknown as { accessedAt: string }).accessedAt;
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
@@ -100,6 +103,7 @@
|
||||
<p class="title">{$user.phone}</p>
|
||||
{/if}
|
||||
<p>Joined: {toLocaleDateTime($user.registration)}</p>
|
||||
<p>Last activity: {accessedAt ? toLocaleDate(accessedAt) : 'never'}</p>
|
||||
</div>
|
||||
{#if !$user.status}
|
||||
<Pill danger>blocked</Pill>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Find Collections',
|
||||
label: 'Find collections',
|
||||
callback() {
|
||||
addSubPanel(CollectionsPanel);
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
icon: 'search'
|
||||
},
|
||||
{
|
||||
label: 'Create Collection',
|
||||
label: 'Create collection',
|
||||
callback() {
|
||||
$showCreate = true;
|
||||
if (!$page.url.pathname.endsWith(databaseId)) {
|
||||
|
||||
+9
-9
@@ -53,7 +53,7 @@
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Create Document',
|
||||
label: 'Create document',
|
||||
keys: $page.url.pathname.endsWith($collection.$id) ? ['c'] : ['c', 'd'],
|
||||
callback() {
|
||||
wizard.start(CreateDocument);
|
||||
@@ -62,7 +62,7 @@
|
||||
group: 'documents'
|
||||
},
|
||||
{
|
||||
label: 'Create Attribute',
|
||||
label: 'Create attribute',
|
||||
keys: $page.url.pathname.endsWith('attributes') ? ['c'] : ['c', 'a'],
|
||||
callback() {
|
||||
addSubPanel(CreateAttributePanel);
|
||||
@@ -71,7 +71,7 @@
|
||||
group: 'attributes'
|
||||
},
|
||||
{
|
||||
label: 'Go to Documents',
|
||||
label: 'Go to documents',
|
||||
keys: ['g', 'd'],
|
||||
callback() {
|
||||
goto(
|
||||
@@ -82,7 +82,7 @@
|
||||
group: 'collections'
|
||||
},
|
||||
{
|
||||
label: 'Go to Attributes',
|
||||
label: 'Go to attributes',
|
||||
keys: ['g', 'a'],
|
||||
callback() {
|
||||
goto(
|
||||
@@ -93,7 +93,7 @@
|
||||
group: 'collections'
|
||||
},
|
||||
{
|
||||
label: 'Go to Indexes',
|
||||
label: 'Go to indexes',
|
||||
keys: ['g', 'i'],
|
||||
callback() {
|
||||
goto(
|
||||
@@ -104,7 +104,7 @@
|
||||
group: 'collections'
|
||||
},
|
||||
{
|
||||
label: 'Go to Activity',
|
||||
label: 'Go to activity',
|
||||
keys: ['g', 'c'],
|
||||
callback() {
|
||||
goto(
|
||||
@@ -115,7 +115,7 @@
|
||||
group: 'collections'
|
||||
},
|
||||
{
|
||||
label: 'Go to Usage',
|
||||
label: 'Go to usage',
|
||||
keys: ['g', 'u'],
|
||||
callback() {
|
||||
goto(
|
||||
@@ -126,7 +126,7 @@
|
||||
group: 'collections'
|
||||
},
|
||||
{
|
||||
label: 'Go to Settings',
|
||||
label: 'Go to settings',
|
||||
keys: ['g', 's'],
|
||||
callback() {
|
||||
goto(
|
||||
@@ -137,7 +137,7 @@
|
||||
group: 'collections'
|
||||
},
|
||||
{
|
||||
label: 'Create Index',
|
||||
label: 'Create index',
|
||||
keys: $page.url.pathname.endsWith('indexes') ? ['c'] : ['c', 'i'],
|
||||
callback() {
|
||||
initCreateIndex();
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@
|
||||
aria-hidden="true" />
|
||||
{/if}
|
||||
</div>
|
||||
<span class="text u-trim" data-private>{attribute.key}</span>
|
||||
<span class="text u-trim-1" data-private>{attribute.key}</span>
|
||||
</div>
|
||||
{#if attribute.status !== 'available'}
|
||||
<Pill
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
import type { Attributes } from './store';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { feedback } from '$lib/stores/app';
|
||||
import { feedback } from '$lib/stores/feedback';
|
||||
|
||||
export let showCreate = false;
|
||||
export let selectedOption: Option['name'] = null;
|
||||
|
||||
+24
-54
@@ -2,37 +2,32 @@
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { Id } from '$lib/components';
|
||||
import { Alert, FloatingActionBar, Id, Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, InputChoice } from '$lib/elements/forms';
|
||||
import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellCheck,
|
||||
TableCellHead,
|
||||
TableCellHeadCheck,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableRowLink,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { onMount } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { isRelationship, isRelationshipToMany } from './document-[document]/attributes/store';
|
||||
import RelationshipsModal from './relationshipsModal.svelte';
|
||||
import { attributes, collection, columns } from './store';
|
||||
import InputCheckbox from '$lib/elements/forms/inputCheckbox.svelte';
|
||||
import { isHTMLInputElement } from '$lib/helpers/types';
|
||||
import { toggle } from '$lib/helpers/array';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import FloatingActionBar from '$lib/components/floatingActionBar.svelte';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import Modal from '$lib/components/modal.svelte';
|
||||
import Alert from '$lib/components/alert.svelte';
|
||||
import InputChoice from '$lib/elements/forms/inputChoice.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -88,14 +83,16 @@
|
||||
|
||||
$: selected = preferences.getCustomCollectionColumns($page.params.collection);
|
||||
|
||||
$: columns.set(
|
||||
$collection.attributes.map((attribute) => ({
|
||||
id: attribute.key,
|
||||
title: attribute.key,
|
||||
type: attribute.type,
|
||||
show: selected?.includes(attribute.key) ?? true
|
||||
}))
|
||||
);
|
||||
$: {
|
||||
columns.set(
|
||||
$collection.attributes.map((attribute) => ({
|
||||
id: attribute.key,
|
||||
title: attribute.key,
|
||||
type: attribute.type,
|
||||
show: selected?.includes(attribute.key) ?? true
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
let selectedDb: string[] = [];
|
||||
let showDelete = false;
|
||||
@@ -112,7 +109,7 @@
|
||||
trackEvent(Submit.DocumentDelete);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${selected.length} document${selected.length > 1 ? 's' : ''} deleted`
|
||||
message: `${selectedDb.length} document${selectedDb.length > 1 ? 's' : ''} deleted`
|
||||
});
|
||||
invalidate(Dependencies.DOCUMENTS);
|
||||
} catch (error) {
|
||||
@@ -122,7 +119,7 @@
|
||||
});
|
||||
trackError(error, Submit.DocumentDelete);
|
||||
} finally {
|
||||
selected = [];
|
||||
selectedDb = [];
|
||||
showDelete = false;
|
||||
}
|
||||
}
|
||||
@@ -142,18 +139,9 @@
|
||||
|
||||
<TableScroll isSticky>
|
||||
<TableHeader>
|
||||
<TableCellHead width={10}>
|
||||
<InputCheckbox
|
||||
id="select-all"
|
||||
indeterminate={selectedDb.length > 0 && selectedDb.length < data.documents.total}
|
||||
value={selectedDb.length === data.documents.total}
|
||||
on:click={(e) => {
|
||||
if (!isHTMLInputElement(e.target)) return;
|
||||
selectedDb = e.target.checked
|
||||
? data.documents.documents.map((database) => database.$id)
|
||||
: [];
|
||||
}} />
|
||||
</TableCellHead>
|
||||
<TableCellHeadCheck
|
||||
bind:selected={selectedDb}
|
||||
pageItemsIds={data.documents.documents.map((d) => d.$id)} />
|
||||
<TableCellHead width={150} eyebrow={false}>Document ID</TableCellHead>
|
||||
{#each $columns.filter((n) => n.show) as column}
|
||||
{#if column.show}
|
||||
@@ -165,25 +153,7 @@
|
||||
{#each data.documents.documents as document}
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${projectId}/databases/database-${databaseId}/collection-${$collection.$id}/document-${document.$id}`}>
|
||||
<TableCell>
|
||||
<InputCheckbox
|
||||
id="select-{document.$id}"
|
||||
value={selectedDb.includes(document.$id)}
|
||||
on:click={(e) => {
|
||||
// Prevent the link from being followed
|
||||
e.preventDefault();
|
||||
const el = e.currentTarget;
|
||||
if (!isHTMLInputElement(el)) return;
|
||||
|
||||
selectedDb = toggle(selectedDb, document.$id);
|
||||
|
||||
// Hack to make sure the checkbox is checked, independent of the
|
||||
// preventDefault() call above
|
||||
window.setTimeout(() => {
|
||||
el.checked = selectedDb.includes(document.$id);
|
||||
});
|
||||
}} />
|
||||
</TableCell>
|
||||
<TableCellCheck bind:selectedIds={selectedDb} id={document.$id} />
|
||||
|
||||
<TableCell width={150}>
|
||||
<Id value={document.$id}>
|
||||
|
||||
@@ -7,19 +7,18 @@
|
||||
import FloatingActionBar from '$lib/components/floatingActionBar.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import InputCheckbox from '$lib/elements/forms/inputCheckbox.svelte';
|
||||
import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellCheck,
|
||||
TableCellHead,
|
||||
TableCellHeadCheck,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRowLink,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { toggle } from '$lib/helpers/array';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { isHTMLInputElement } from '$lib/helpers/types';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { PageData } from './$types';
|
||||
@@ -62,18 +61,9 @@
|
||||
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
<TableCellHead width={10}>
|
||||
<InputCheckbox
|
||||
id="select-all"
|
||||
indeterminate={selected.length > 0 && selected.length < data.collections.total}
|
||||
value={selected.length === data.collections.total}
|
||||
on:click={(e) => {
|
||||
if (!isHTMLInputElement(e.target)) return;
|
||||
selected = e.target.checked
|
||||
? data.collections.collections.map((database) => database.$id)
|
||||
: [];
|
||||
}} />
|
||||
</TableCellHead>
|
||||
<TableCellHeadCheck
|
||||
bind:selected
|
||||
pageItemsIds={data.collections.collections.map((c) => c.$id)} />
|
||||
{#each $columns as column}
|
||||
{#if column.show}
|
||||
<TableCellHead width={column.width}>{column.title}</TableCellHead>
|
||||
@@ -84,25 +74,7 @@
|
||||
{#each data.collections.collections as collection}
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${projectId}/databases/database-${databaseId}/collection-${collection.$id}`}>
|
||||
<TableCell>
|
||||
<InputCheckbox
|
||||
id="select-{collection.$id}"
|
||||
value={selected.includes(collection.$id)}
|
||||
on:click={(e) => {
|
||||
// Prevent the link from being followed
|
||||
e.preventDefault();
|
||||
const el = e.currentTarget;
|
||||
if (!isHTMLInputElement(el)) return;
|
||||
|
||||
selected = toggle(selected, collection.$id);
|
||||
|
||||
// Hack to make sure the checkbox is checked, independent of the
|
||||
// preventDefault() call above
|
||||
window.setTimeout(() => {
|
||||
el.checked = selected.includes(collection.$id);
|
||||
});
|
||||
}} />
|
||||
</TableCell>
|
||||
<TableCellCheck bind:selectedIds={selected} id={collection.$id} />
|
||||
{#each $columns as column}
|
||||
{#if column.show}
|
||||
{#if column.id === '$id'}
|
||||
|
||||
@@ -6,20 +6,19 @@
|
||||
import { Id, Modal } from '$lib/components';
|
||||
import FloatingActionBar from '$lib/components/floatingActionBar.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import InputCheckbox from '$lib/elements/forms/inputCheckbox.svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableCellHeadCheck,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRowLink,
|
||||
TableScroll
|
||||
TableScroll,
|
||||
TableCellCheck
|
||||
} from '$lib/elements/table';
|
||||
import { toggle } from '$lib/helpers/array';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { isHTMLInputElement } from '$lib/helpers/types';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { PageData } from './$types';
|
||||
@@ -59,18 +58,9 @@
|
||||
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
<TableCellHead width={10}>
|
||||
<InputCheckbox
|
||||
id="select-all"
|
||||
indeterminate={selected.length > 0 && selected.length < data.databases.total}
|
||||
value={selected.length === data.databases.total}
|
||||
on:click={(e) => {
|
||||
if (!isHTMLInputElement(e.target)) return;
|
||||
selected = e.target.checked
|
||||
? data.databases.databases.map((database) => database.$id)
|
||||
: [];
|
||||
}} />
|
||||
</TableCellHead>
|
||||
<TableCellHeadCheck
|
||||
bind:selected
|
||||
pageItemsIds={data.databases.databases.map((c) => c.$id)} />
|
||||
{#each $columns as column}
|
||||
{#if column.show}
|
||||
<TableCellHead width={column.width}>{column.title}</TableCellHead>
|
||||
@@ -81,25 +71,7 @@
|
||||
{#each data.databases.databases as database}
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${projectId}/databases/database-${database.$id}`}>
|
||||
<TableCell>
|
||||
<InputCheckbox
|
||||
id="select-{database.$id}"
|
||||
value={selected.includes(database.$id)}
|
||||
on:click={(e) => {
|
||||
// Prevent the link from being followed
|
||||
e.preventDefault();
|
||||
const el = e.currentTarget;
|
||||
if (!isHTMLInputElement(el)) return;
|
||||
|
||||
selected = toggle(selected, database.$id);
|
||||
|
||||
// Hack to make sure the checkbox is checked, independent of the
|
||||
// preventDefault() call above
|
||||
window.setTimeout(() => {
|
||||
el.checked = selected.includes(database.$id);
|
||||
});
|
||||
}} />
|
||||
</TableCell>
|
||||
<TableCellCheck bind:selectedIds={selected} id={database.$id} />
|
||||
{#each $columns as column}
|
||||
{#if column.show}
|
||||
{#if column.id === '$id'}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let id;
|
||||
</script>
|
||||
|
||||
<input type="checkbox" {id} on:click />
|
||||
@@ -54,8 +54,8 @@
|
||||
},
|
||||
{
|
||||
label: 'Timeout',
|
||||
async callback() {
|
||||
await goto(
|
||||
callback() {
|
||||
goto(
|
||||
`/console/project-${$project.$id}/functions/function-${$func.$id}/settings#timeout`
|
||||
);
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
import type { PageData } from './$types';
|
||||
import Delete from './delete.svelte';
|
||||
import Create from './create.svelte';
|
||||
import Rebuild from './rebuild.svelte';
|
||||
import Activate from './activate.svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -51,6 +52,7 @@
|
||||
let showDropdown = [];
|
||||
let showDelete = false;
|
||||
let showActivate = false;
|
||||
let showRebuild = false;
|
||||
|
||||
let selectedDeployment: Models.Deployment = null;
|
||||
|
||||
@@ -58,6 +60,10 @@
|
||||
invalidate(Dependencies.DEPLOYMENTS);
|
||||
}
|
||||
|
||||
function handleRebuild() {
|
||||
invalidate(Dependencies.DEPLOYMENTS);
|
||||
}
|
||||
|
||||
$: activeDeployment = data.deployments.deployments.find((d) => d.$id === $func?.deployment);
|
||||
|
||||
if (browser) {
|
||||
@@ -222,6 +228,17 @@
|
||||
}}>
|
||||
Activate
|
||||
</DropListItem>
|
||||
{#if 'failed' === deployment.status}
|
||||
<DropListItem
|
||||
icon="refresh"
|
||||
on:click={() => {
|
||||
selectedDeployment = deployment;
|
||||
showRebuild = true;
|
||||
showDropdown = [];
|
||||
}}>
|
||||
Retry Build
|
||||
</DropListItem>
|
||||
{/if}
|
||||
<DropListItem
|
||||
icon="terminal"
|
||||
on:click={() => {
|
||||
@@ -288,4 +305,5 @@
|
||||
{#if selectedDeployment}
|
||||
<Delete {selectedDeployment} bind:showDelete />
|
||||
<Activate {selectedDeployment} bind:showActivate on:activated={handleActivate} />
|
||||
<Rebuild {selectedDeployment} bind:showRebuild on:rebuild={handleRebuild} />
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let showRebuild = false;
|
||||
export let selectedDeployment: Models.Deployment = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await sdk.forProject.functions.createBuild(
|
||||
selectedDeployment.resourceId,
|
||||
selectedDeployment.$id,
|
||||
selectedDeployment.buildId
|
||||
);
|
||||
showRebuild = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Retrying build`
|
||||
});
|
||||
dispatch('rebuild');
|
||||
trackEvent(Submit.DeploymentUpdate);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.DeploymentUpdate);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal bind:show={showRebuild} onSubmit={handleSubmit}>
|
||||
<svelte:fragment slot="header">Retry build</svelte:fragment>
|
||||
<p>Are you sure you want to retry building this deployment?</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showRebuild = false)}>Cancel</Button>
|
||||
<Button secondary submit>Retry build</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
+1
-1
@@ -49,7 +49,7 @@
|
||||
<Heading tag="h6" size="7" id="schedule">Schedule</Heading>
|
||||
<p>
|
||||
Set a Cron schedule to trigger your function. Leave blank for no schedule. <a
|
||||
href="https://en.wikipedia.org/wiki/Cron"
|
||||
href="https://appwrite.io/docs/functions#scheduled-execution"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
TableHeader,
|
||||
TableRowLink
|
||||
} from '$lib/elements/table';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import type { PageData } from './$types';
|
||||
import Wizard from './wizard.svelte';
|
||||
@@ -48,7 +48,7 @@
|
||||
{key.name}
|
||||
</TableCellText>
|
||||
<TableCellText onlyDesktop title="Last Accessed">
|
||||
{key.accessedAt ? toLocaleDateTime(key.accessedAt) : 'never'}
|
||||
{key.accessedAt ? toLocaleDate(key.accessedAt) : 'never'}
|
||||
</TableCellText>
|
||||
<TableCellText onlyDesktop title="Expiration Date">
|
||||
{key.expire ? toLocaleDateTime(key.expire) : 'never'}
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -83,7 +83,7 @@
|
||||
</svelte:head>
|
||||
|
||||
<Container>
|
||||
{@const accessedAt = $key.accessedAt ? toLocaleDateTime($key.accessedAt) : 'never'}
|
||||
{@const accessedAt = $key.accessedAt ? toLocaleDate($key.accessedAt) : 'never'}
|
||||
<CardGrid>
|
||||
<div>
|
||||
<Heading tag="h6" size="7">{$key.name}</Heading>
|
||||
|
||||
@@ -70,15 +70,6 @@
|
||||
return 'unknown';
|
||||
}
|
||||
};
|
||||
|
||||
// $: $registerCommands([
|
||||
// {
|
||||
// label: 'Create Web App',
|
||||
// callback: () => addPlatform(Platform.Web),
|
||||
// keys: ['c'],
|
||||
// icon: 'plus'
|
||||
// }
|
||||
// ]);
|
||||
</script>
|
||||
|
||||
<div class="common-section u-flex u-gap-12">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
let client = Client()
|
||||
.setEndpoint("${endpoint}")
|
||||
.setProject("${project}")
|
||||
.setSelfSigned(status: true) // For self signed certificates, only use for development`;
|
||||
.setSelfSigned(true) // For self signed certificates, only use for development`;
|
||||
|
||||
let showAlert = true;
|
||||
</script>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
},
|
||||
[Platform.Web]: {
|
||||
name: 'My Web App',
|
||||
hostname: 'com.company.appname',
|
||||
hostname: 'localhost',
|
||||
tooltip:
|
||||
'The hostname that your website will use to interact with the Appwrite APIs in production or development environments. No protocol or port number required.'
|
||||
},
|
||||
|
||||
@@ -56,8 +56,12 @@ export const versions = cachedStore<
|
||||
>('versions', function ({ set }) {
|
||||
return {
|
||||
load: async () => {
|
||||
const { endpoint } = sdk.forConsole.client.config;
|
||||
const response = await fetch(`${endpoint}/../versions`);
|
||||
const { endpoint, project } = sdk.forConsole.client.config;
|
||||
const response = await fetch(`${endpoint}/../versions`, {
|
||||
headers: {
|
||||
'X-Appwrite-Project': project
|
||||
}
|
||||
});
|
||||
set(await response.json());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
group: 'webhooks'
|
||||
},
|
||||
{
|
||||
label: 'Go to Settings Overview',
|
||||
label: 'Go to settings overview',
|
||||
|
||||
keys: ['g', 'o'],
|
||||
callback: () => {
|
||||
@@ -37,7 +37,7 @@
|
||||
rank: 40
|
||||
},
|
||||
{
|
||||
label: 'Go to Custom domains',
|
||||
label: 'Go to custom domains',
|
||||
|
||||
keys: ['g', 'd'],
|
||||
callback: () => {
|
||||
@@ -48,7 +48,7 @@
|
||||
rank: 30
|
||||
},
|
||||
{
|
||||
label: 'Go to Webhooks',
|
||||
label: 'Go to webhooks',
|
||||
keys: ['g', 'w'],
|
||||
callback: () => {
|
||||
goto(`/console/project-${$project.$id}/settings/webhooks`);
|
||||
@@ -59,7 +59,7 @@
|
||||
rank: 20
|
||||
},
|
||||
{
|
||||
label: 'Go to Migrations',
|
||||
label: 'Go to migrations',
|
||||
keys: ['g', 'm'],
|
||||
callback: () => {
|
||||
goto(`/console/project-${$project.$id}/settings/migrations`);
|
||||
|
||||
@@ -3,10 +3,18 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { organizationList } from '$lib/stores/organization';
|
||||
import { project } from '../store';
|
||||
import { services, type Service } from '$lib/stores/project-services';
|
||||
import { CardGrid, CopyInput, Box, Heading } from '$lib/components';
|
||||
import { Button, Form, FormList, InputText, InputSwitch } from '$lib/elements/forms';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormList,
|
||||
InputText,
|
||||
InputSwitch,
|
||||
InputSelect
|
||||
} from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
@@ -14,14 +22,22 @@
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import EnableAllServices from './enableAllServices.svelte';
|
||||
import DisableAllServices from './disableAllServices.svelte';
|
||||
import Transfer from './transferProject.svelte';
|
||||
|
||||
let name: string = null;
|
||||
let teamId: string = null;
|
||||
let showDelete = false;
|
||||
let showTransfer = false;
|
||||
const endpoint = sdk.forConsole.client.config.endpoint;
|
||||
const projectId = $page.params.project;
|
||||
let showDisableAll = false;
|
||||
let showEnableAll = false;
|
||||
|
||||
onMount(async () => {
|
||||
name ??= $project.name;
|
||||
teamId ??= $project.teamId;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
@@ -42,6 +58,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleAllServices(status: boolean) {
|
||||
if (status && !showEnableAll) {
|
||||
showEnableAll = true;
|
||||
return;
|
||||
}
|
||||
if (!status && !showDisableAll) {
|
||||
showDisableAll = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const path = '/projects/' + $project.$id + '/service/all';
|
||||
await sdk.forConsole.client.call(
|
||||
'PATCH',
|
||||
new URL(sdk.forConsole.client.config.endpoint + path),
|
||||
{
|
||||
'X-Appwrite-Project': sdk.forConsole.client.config.project,
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
{
|
||||
status: status
|
||||
}
|
||||
);
|
||||
invalidate(Dependencies.PROJECT);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message:
|
||||
'All services for ' +
|
||||
$project.name +
|
||||
' has been ' +
|
||||
(status ? 'enabled.' : 'disabled.')
|
||||
});
|
||||
trackEvent(Submit.ProjectService);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.ProjectService);
|
||||
} finally {
|
||||
showDisableAll = false;
|
||||
showEnableAll = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function serviceUpdate(service: Service) {
|
||||
try {
|
||||
await sdk.forConsole.projects.updateServiceStatus(
|
||||
@@ -120,8 +181,18 @@
|
||||
services are not accessible to client SDKs but remain accessible to server SDKs.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul class="buttons-list u-main-end">
|
||||
<li class="buttons-list-item">
|
||||
<Button text={true} on:click={() => toggleAllServices(true)}
|
||||
>Enable all</Button>
|
||||
</li>
|
||||
<li class="buttons-list-item">
|
||||
<Button text={true} on:click={() => toggleAllServices(false)}
|
||||
>Disable all</Button>
|
||||
</li>
|
||||
</ul>
|
||||
<FormList>
|
||||
<form class="form">
|
||||
<form class="form card-separator">
|
||||
<ul class="form-list is-multiple">
|
||||
{#each $services.list as service}
|
||||
<InputSwitch
|
||||
@@ -137,7 +208,30 @@
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Transfer project</Heading>
|
||||
<p class="text">Transfer your project to another organization that you own.</p>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputSelect
|
||||
id="organization"
|
||||
label="Available organizations"
|
||||
bind:value={teamId}
|
||||
options={$organizationList.teams.map((team) => ({
|
||||
value: team.$id,
|
||||
label: team.name
|
||||
}))} />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
secondary
|
||||
disabled={teamId === $project.teamId}
|
||||
on:click={() => (showTransfer = true)}>Transfer</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid danger>
|
||||
<div>
|
||||
<Heading tag="h6" size="7">Delete Project</Heading>
|
||||
@@ -163,3 +257,11 @@
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
<DisableAllServices handleDisableAll={() => toggleAllServices(false)} bind:show={showDisableAll} />
|
||||
<EnableAllServices handleEnableAll={() => toggleAllServices(true)} bind:show={showEnableAll} />
|
||||
{#if teamId}
|
||||
<Transfer
|
||||
bind:teamId
|
||||
teamName={$organizationList.teams.find((t) => t.$id == teamId).name}
|
||||
bind:show={showTransfer} />
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
|
||||
export let show = false;
|
||||
export let handleDisableAll;
|
||||
</script>
|
||||
|
||||
<Modal icon="exclamation" state="warning" bind:show onSubmit={handleDisableAll}>
|
||||
<svelte:fragment slot="header">Disable all services</svelte:fragment>
|
||||
<p class="text" data-private>
|
||||
Are you sure you want to disable all services? This will disable API requests to your
|
||||
project.
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button secondary submit>Disable All</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
|
||||
export let show = false;
|
||||
export let handleEnableAll;
|
||||
</script>
|
||||
|
||||
<Modal bind:show onSubmit={handleEnableAll}>
|
||||
<svelte:fragment slot="header">Enable all services</svelte:fragment>
|
||||
<p class="text" data-private>All project services will be enabled.</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button secondary submit>Enable all</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -1,12 +1,21 @@
|
||||
<script lang="ts">
|
||||
import SvgIcon from '$lib/components/svgIcon.svelte';
|
||||
import { Button, FormList, InputNumber, InputPassword, InputText } from '$lib/elements/forms';
|
||||
import {
|
||||
Button,
|
||||
FormList,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
InputSelect,
|
||||
InputText
|
||||
} from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import type { Provider } from '$lib/stores/migration';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { page } from '$app/stores';
|
||||
import { provider } from '.';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
|
||||
const providers: Record<Provider, string> = {
|
||||
appwrite: 'Appwrite Self-hosted',
|
||||
@@ -20,19 +29,13 @@
|
||||
const target = new URL(
|
||||
`${sdk.forProject.client.config.endpoint}/migrations/firebase/connect`
|
||||
);
|
||||
target.searchParams.set('projectId', $page.params.project);
|
||||
target.searchParams.set('redirect', redirect.toString());
|
||||
target.searchParams.set('projectId', $page.params.project);
|
||||
return target;
|
||||
}
|
||||
|
||||
function getFirebaseProjects() {
|
||||
sdk.forProject.migrations.listFirebaseProjects().then((res) => {
|
||||
firebaseProjects = res.projects;
|
||||
});
|
||||
}
|
||||
|
||||
function deauthorizeGoogle() {
|
||||
sdk.forProject.migrations.firebaseDeauthorize().then(() => {
|
||||
sdk.forProject.migrations.deleteFirebaseAuth().then(() => {
|
||||
firebaseProjects = [];
|
||||
});
|
||||
|
||||
@@ -40,23 +43,22 @@
|
||||
type: 'success',
|
||||
message: 'Signed out of Google successfully'
|
||||
});
|
||||
|
||||
invalidate(Dependencies.MIGRATIONS);
|
||||
}
|
||||
|
||||
let firebaseProjects = [];
|
||||
$: firebaseProjects = $page.data.firebaseProjects;
|
||||
|
||||
$: console.log(firebaseProjects);
|
||||
|
||||
let showAuth = false;
|
||||
|
||||
$: if ($provider.provider === 'firebase') {
|
||||
getFirebaseProjects();
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Choose provider</svelte:fragment>
|
||||
<div class="cards">
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
{#each Object.entries(providers) as [key, value]}
|
||||
<label
|
||||
class="card is-allow-focus u-height-100-percent u-flex u-flex-vertical u-cursor-pointer">
|
||||
<label class="u-flex u-cross-center u-cursor-pointer u-gap-8">
|
||||
<input
|
||||
class="is-small"
|
||||
type="radio"
|
||||
@@ -93,24 +95,45 @@
|
||||
bind:value={$provider.apiKey} />
|
||||
</FormList>
|
||||
{:else if $provider.provider === 'firebase'}
|
||||
{#if firebaseProjects.length == 0}
|
||||
{#if !firebaseProjects?.length}
|
||||
<div class="box u-flex u-flex-vertical u-gap-16 u-cross-center u-margin-block-start-24">
|
||||
<p class="u-text-center u-bold">Sign in with Google to get started</p>
|
||||
<Button secondary href={connectGoogle().toString()}>
|
||||
<SvgIcon name="google" />Sign in
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
{#each firebaseProjects as project}
|
||||
<div
|
||||
class="card is-allow-focus u-height-100-percent u-flex u-flex-vertical u-cursor-pointer u-margin-block-start-24">
|
||||
<div class="content">
|
||||
<p>{project.displayName}</p>
|
||||
<span>{project.projectId}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<button
|
||||
class="tag u-margin-block-start-16"
|
||||
type="button"
|
||||
on:click={() => (showAuth = !showAuth)}
|
||||
class:is-selected={showAuth}>
|
||||
<span class="icon-lock-closed" aria-hidden="true" />
|
||||
<span class="text">Manual authentication</span>
|
||||
</button>
|
||||
|
||||
{#if showAuth}
|
||||
<div class="u-margin-block-start-16">
|
||||
<InputText
|
||||
id="credentials"
|
||||
label="Account credentials"
|
||||
required
|
||||
bind:value={$provider.serviceAccount}
|
||||
placeholder="Enter account credentials" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<FormList class="u-margin-block-start-24">
|
||||
<InputSelect
|
||||
id="firebase-project"
|
||||
label="Firebase project"
|
||||
required
|
||||
bind:value={$provider.projectId}
|
||||
options={firebaseProjects.map((project) => ({
|
||||
label: project.displayName,
|
||||
value: project.projectId
|
||||
}))} />
|
||||
</FormList>
|
||||
<p class="u-text-center u-margin-block-start-24">
|
||||
Signed in
|
||||
<button class="u-bold" on:click|preventDefault={deauthorizeGoogle}>
|
||||
@@ -118,25 +141,6 @@
|
||||
</button>
|
||||
</p>
|
||||
{/if}
|
||||
<button
|
||||
class="tag u-margin-block-start-16"
|
||||
type="button"
|
||||
on:click={() => (showAuth = !showAuth)}
|
||||
class:is-selected={showAuth}>
|
||||
<span class="icon-lock-closed" aria-hidden="true" />
|
||||
<span class="text">Manual authentication</span>
|
||||
</button>
|
||||
|
||||
{#if showAuth}
|
||||
<div class="u-margin-block-start-16">
|
||||
<InputText
|
||||
id="credentials"
|
||||
label="Account credentials"
|
||||
required
|
||||
bind:value={$provider.serviceAccount}
|
||||
placeholder="Enter account credentials" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else if $provider.provider === 'supabase'}
|
||||
<FormList class="u-margin-block-start-24">
|
||||
<p class="body-text-1 u-bold">Postgres credentials</p>
|
||||
@@ -225,40 +229,3 @@
|
||||
</FormList>
|
||||
{/if}
|
||||
</WizardStep>
|
||||
|
||||
<style lang="scss">
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(9.125rem, 1fr));
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
--card-padding: 0;
|
||||
--card-border-radius: var(--border-radius-small);
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
block-size: auto;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
text-align: center;
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 9.125rem;
|
||||
height: 5.5rem;
|
||||
|
||||
padding: 0 2rem;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
switch ($provider.provider) {
|
||||
case 'appwrite': {
|
||||
const res = await sdk.forProject.migrations.migrateAppwrite(
|
||||
const res = await sdk.forProject.migrations.createAppwriteMigration(
|
||||
resources,
|
||||
$provider.endpoint,
|
||||
$provider.projectID,
|
||||
@@ -35,7 +35,7 @@
|
||||
break;
|
||||
}
|
||||
case 'supabase': {
|
||||
const res = await sdk.forProject.migrations.migrateSupabase(
|
||||
const res = await sdk.forProject.migrations.createSupabaseMigration(
|
||||
resources,
|
||||
$provider.endpoint,
|
||||
$provider.apiKey,
|
||||
@@ -50,16 +50,21 @@
|
||||
}
|
||||
case 'firebase': {
|
||||
console.log('firebase', $provider.serviceAccount);
|
||||
const res = await sdk.forProject.migrations.migrateFirebase(
|
||||
resources,
|
||||
$provider.serviceAccount
|
||||
);
|
||||
console.log('Firebase', res);
|
||||
if ($provider.projectId) {
|
||||
// OAuth
|
||||
} else if ($provider.serviceAccount) {
|
||||
// Manual auth
|
||||
const res = await sdk.forProject.migrations.createFirebaseMigration(
|
||||
resources,
|
||||
$provider.serviceAccount
|
||||
);
|
||||
console.log('Firebase', res);
|
||||
}
|
||||
invalidate(Dependencies.MIGRATIONS);
|
||||
break;
|
||||
}
|
||||
case 'nhost': {
|
||||
const res = await sdk.forProject.migrations.migrateNHost(
|
||||
const res = await sdk.forProject.migrations.createNHostMigration(
|
||||
resources,
|
||||
$provider.subdomain,
|
||||
$provider.region,
|
||||
@@ -100,4 +105,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Create Function" {steps} on:exit={onExit} on:finish={onFinish} />
|
||||
<Wizard title="Create Project" {steps} on:exit={onExit} on:finish={onFinish} />
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
import { Dependencies } from '$lib/constants.js';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
async function getFirebaseProjects() {
|
||||
try {
|
||||
const res = await sdk.forProject.migrations.listFirebaseProjects();
|
||||
return res.projects;
|
||||
} catch (e) {
|
||||
if (e.code === 401) {
|
||||
return [];
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function load({ depends }) {
|
||||
depends(Dependencies.MIGRATIONS);
|
||||
|
||||
try {
|
||||
const { migrations } = await sdk.forProject.migrations.list();
|
||||
|
||||
console.log(migrations);
|
||||
console.log('migrations', migrations);
|
||||
return {
|
||||
migrations
|
||||
migrations,
|
||||
firebaseProjects: getFirebaseProjects()
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { project } from '../store';
|
||||
|
||||
export let show = false;
|
||||
export let teamName;
|
||||
export let teamId;
|
||||
|
||||
const handleTransfer = async () => {
|
||||
try {
|
||||
await sdk.forConsole.client.call(
|
||||
'PATCH',
|
||||
new URL(
|
||||
sdk.forConsole.client.config.endpoint + '/projects/' + $project.$id + '/team'
|
||||
),
|
||||
{
|
||||
'X-Appwrite-Project': sdk.forConsole.client.config.project,
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
{
|
||||
teamId: teamId
|
||||
}
|
||||
);
|
||||
// await sdk.forConsole.projects.update($project.$id, password);
|
||||
show = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$project.name} has been transfered to ${teamName}`
|
||||
});
|
||||
trackEvent(Submit.ProjectUpdateTeam);
|
||||
await goto(`${base}/console/organization-${teamId}`);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.ProjectUpdateTeam);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal bind:show onSubmit={handleTransfer} headerDivider={false}>
|
||||
<svelte:fragment slot="header">Transfer project</svelte:fragment>
|
||||
<p>Are you sure you want to transfer <b>{$project.name}</b> to <b>{teamName}</b>?</p>
|
||||
<p>
|
||||
Members who are not part of the destination organization must be invited to gain access to
|
||||
this project.
|
||||
</p>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button secondary submit>Transfer</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -57,7 +57,7 @@
|
||||
icon: 'puzzle'
|
||||
},
|
||||
{
|
||||
label: 'File Security',
|
||||
label: 'File security',
|
||||
async callback() {
|
||||
await goto(
|
||||
`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/settings#file-security`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { page } from '$app/stores';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import { derived } from 'svelte/store';
|
||||
|
||||
export const version = derived(page, ($page) => $page.data.version as string | null);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import {
|
||||
selectedFeedback,
|
||||
feedbackData,
|
||||
feedbackOptions,
|
||||
feedback
|
||||
} from '$lib/stores/feedback';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
$: $selectedFeedback = feedbackOptions.find((option) => option.type === $feedback.type);
|
||||
|
||||
async function beforeSubmit() {
|
||||
try {
|
||||
await feedback.submitFeedback(
|
||||
`feedback-${$feedback.type}`,
|
||||
$feedbackData.message,
|
||||
$feedbackData.name,
|
||||
$feedbackData.email
|
||||
);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Feedback submitted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">{$selectedFeedback.title}</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">{$selectedFeedback.desc}</svelte:fragment>
|
||||
|
||||
<svelte:component this={$selectedFeedback.component} />
|
||||
</WizardStep>
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { app } from '$lib/stores/app';
|
||||
import imgDark from '$lib/images/feedback/feedback-dark.svg';
|
||||
import imgLight from '$lib/images/feedback/feedback-light.svg';
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Thanks for the feedback!</svelte:fragment>
|
||||
|
||||
<div class="u-flex u-main-center">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
<img src={imgDark} alt="" class="u-only-dark" />
|
||||
{:else}
|
||||
<img src={imgLight} alt="" class="u-only-light" />
|
||||
{/if}
|
||||
</div>
|
||||
</WizardStep>
|
||||
@@ -54,9 +54,11 @@
|
||||
|
||||
async function invite() {
|
||||
try {
|
||||
const res = await fetch(`${sdk.forConsole.client.config.endpoint}/account/invite`, {
|
||||
const { endpoint, project } = sdk.forConsole.client.config;
|
||||
const res = await fetch(`${endpoint}/account/invite`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Appwrite-Project': project,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 3H18.75C19.9926 3 21 4.00736 21 5.25V18.75C21 19.9926 19.9926 21 18.75 21H5.25C4.00736 21 3 19.9926 3 18.75V5.25C3 4.00736 4.00736 3 5.25 3ZM13.5467 9.2378C14.8834 9.40936 16.2798 9.84718 17.6601 10.6272L19.0312 9.71202V12.3053H15.0701L16.2892 11.5423C15.3212 11.1022 14.4026 10.8227 13.5467 10.6742V17.0335L11.4138 18.4063C1.04033 16.518 4.80411 9.69874 11.4138 9.18233V6.96653L13.5467 5.59371V9.2378ZM11.4137 10.5686V17.0335C4.98567 16.1994 6.46334 10.9884 11.4137 10.5686Z" fill="#F68423"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 652 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 3H18.75C19.9926 3 21 4.00736 21 5.25V18.75C21 19.9926 19.9926 21 18.75 21H5.25C4.00736 21 3 19.9926 3 18.75V5.25C3 4.00736 4.00736 3 5.25 3ZM13.5467 9.2378C14.8834 9.40936 16.2798 9.84718 17.6601 10.6272L19.0312 9.71202V12.3053H15.0701L16.2892 11.5423C15.3212 11.1022 14.4026 10.8227 13.5467 10.6742V17.0335L11.4138 18.4063C1.04033 16.518 4.80411 9.69874 11.4138 9.18233V6.96653L13.5467 5.59371V9.2378ZM11.4137 10.5686V17.0335C4.98567 16.1994 6.46334 10.9884 11.4137 10.5686Z" fill="#C4C6D7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 652 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 3H18.75C19.9926 3 21 4.00736 21 5.25V18.75C21 19.9926 19.9926 21 18.75 21H5.25C4.00736 21 3 19.9926 3 18.75V5.25C3 4.00736 4.00736 3 5.25 3ZM13.5467 9.2378C14.8834 9.40936 16.2798 9.84718 17.6601 10.6272L19.0312 9.71202V12.3053H15.0701L16.2892 11.5423C15.3212 11.1022 14.4026 10.8227 13.5467 10.6742V17.0335L11.4138 18.4063C1.04033 16.518 4.80411 9.69874 11.4138 9.18233V6.96653L13.5467 5.59371V9.2378ZM11.4137 10.5686V17.0335C4.98567 16.1994 6.46334 10.9884 11.4137 10.5686Z" fill="#F68423"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 652 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 3H18.75C19.9926 3 21 4.00736 21 5.25V18.75C21 19.9926 19.9926 21 18.75 21H5.25C4.00736 21 3 19.9926 3 18.75V5.25C3 4.00736 4.00736 3 5.25 3ZM13.5467 9.2378C14.8834 9.40936 16.2798 9.84718 17.6601 10.6272L19.0312 9.71202V12.3053H15.0701L16.2892 11.5423C15.3212 11.1022 14.4026 10.8227 13.5467 10.6742V17.0335L11.4138 18.4063C1.04033 16.518 4.80411 9.69874 11.4138 9.18233V6.96653L13.5467 5.59371V9.2378ZM11.4137 10.5686V17.0335C4.98567 16.1994 6.46334 10.9884 11.4137 10.5686Z" fill="#373B4D"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 652 B |
@@ -6,12 +6,12 @@ import { Tab } from '../../../src/lib/components';
|
||||
test('shows tab', () => {
|
||||
const { getByRole } = render(Tab);
|
||||
|
||||
expect(getByRole('button')).toBeInTheDocument();
|
||||
expect(getByRole('tab')).toBeInTheDocument();
|
||||
});
|
||||
test('shows tab - is selected', () => {
|
||||
const { getByRole } = render(Tab, { selected: true });
|
||||
|
||||
expect(getByRole('button')).toHaveClass('is-selected');
|
||||
expect(getByRole('tab')).toHaveClass('is-selected');
|
||||
});
|
||||
|
||||
test('shows tab - is link', () => {
|
||||
@@ -23,7 +23,7 @@ test('shows tab - is link', () => {
|
||||
|
||||
test('shows tab - on:click', async () => {
|
||||
const { getByRole, component } = render(Tab);
|
||||
const tab = getByRole('button');
|
||||
const tab = getByRole('tab');
|
||||
const callback = vi.fn();
|
||||
component.$on('click', callback);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user