mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' of github.com:appwrite/appwrite-console-poc into feat-grid-item-option-overflow-counter
This commit is contained in:
Generated
+14
-14
@@ -9,8 +9,8 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@aw-labs/appwrite-console": "^1.0.0-0",
|
||||
"@aw-labs/icons": "0.0.0-53",
|
||||
"@aw-labs/ui": "0.0.0-53",
|
||||
"@aw-labs/icons": "0.0.0-54",
|
||||
"@aw-labs/ui": "0.0.0-54",
|
||||
"echarts": "^5.3.3",
|
||||
"tippy.js": "^6.3.7",
|
||||
"web-vitals": "^2.1.4"
|
||||
@@ -77,14 +77,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aw-labs/icons": {
|
||||
"version": "0.0.0-53",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-53.tgz",
|
||||
"integrity": "sha512-9UhHTGajh2HJun2uYbcMyxRtPsgIQ5CXf8mn/sCiRyZJIw5FYtuxTSkP/35hv2zJsVq/mY09HvfdX3w4XVKItg=="
|
||||
"version": "0.0.0-54",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-54.tgz",
|
||||
"integrity": "sha512-lk+uikRag3Y5Rm4Ys5lHBhknhCVpA/oTaIBBDgB6nidqtJdqLpa+Nur4BdsBXOEZTlo2yOiWU2NNAl6ZzUzFGQ=="
|
||||
},
|
||||
"node_modules/@aw-labs/ui": {
|
||||
"version": "0.0.0-53",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-53.tgz",
|
||||
"integrity": "sha512-5ftq05SZ9lVdXGHgHIyxwtGznfKsCa1fKPpZhS+279IUTIB+H7GIq0g2U2EzRBj7ht0PXNZJNO4n9hO2ItDJwA==",
|
||||
"version": "0.0.0-54",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-54.tgz",
|
||||
"integrity": "sha512-1Dj216qPaVKdBScW+1XIp6IOVBdcv/kYjiWO4YiFtoQ/IaayoSO6njlAPTwyQzclmX5A/9suEGadOVa3NZqbgQ==",
|
||||
"dependencies": {
|
||||
"@aw-labs/icons": "*"
|
||||
}
|
||||
@@ -7698,14 +7698,14 @@
|
||||
}
|
||||
},
|
||||
"@aw-labs/icons": {
|
||||
"version": "0.0.0-53",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-53.tgz",
|
||||
"integrity": "sha512-9UhHTGajh2HJun2uYbcMyxRtPsgIQ5CXf8mn/sCiRyZJIw5FYtuxTSkP/35hv2zJsVq/mY09HvfdX3w4XVKItg=="
|
||||
"version": "0.0.0-54",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-54.tgz",
|
||||
"integrity": "sha512-lk+uikRag3Y5Rm4Ys5lHBhknhCVpA/oTaIBBDgB6nidqtJdqLpa+Nur4BdsBXOEZTlo2yOiWU2NNAl6ZzUzFGQ=="
|
||||
},
|
||||
"@aw-labs/ui": {
|
||||
"version": "0.0.0-53",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-53.tgz",
|
||||
"integrity": "sha512-5ftq05SZ9lVdXGHgHIyxwtGznfKsCa1fKPpZhS+279IUTIB+H7GIq0g2U2EzRBj7ht0PXNZJNO4n9hO2ItDJwA==",
|
||||
"version": "0.0.0-54",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-54.tgz",
|
||||
"integrity": "sha512-1Dj216qPaVKdBScW+1XIp6IOVBdcv/kYjiWO4YiFtoQ/IaayoSO6njlAPTwyQzclmX5A/9suEGadOVa3NZqbgQ==",
|
||||
"requires": {
|
||||
"@aw-labs/icons": "*"
|
||||
}
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@aw-labs/appwrite-console": "^1.0.0-0",
|
||||
"@aw-labs/icons": "0.0.0-53",
|
||||
"@aw-labs/ui": "0.0.0-53",
|
||||
"@aw-labs/icons": "0.0.0-54",
|
||||
"@aw-labs/ui": "0.0.0-54",
|
||||
"echarts": "^5.3.3",
|
||||
"tippy.js": "^6.3.7",
|
||||
"web-vitals": "^2.1.4"
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
export let value: string;
|
||||
export let label: string = null;
|
||||
export let showLabel = false;
|
||||
|
||||
let content = 'Click to copy';
|
||||
|
||||
@@ -21,17 +23,20 @@
|
||||
</script>
|
||||
|
||||
<div class="input-text-wrapper is-with-end-button">
|
||||
<input {value} type="text" class="input-text" readonly />
|
||||
<button
|
||||
type="button"
|
||||
class="input-button"
|
||||
aria-label="Click to copy."
|
||||
on:click={copy}
|
||||
on:mouseenter={() => setTimeout(() => (content = 'Click to copy'))}
|
||||
use:tooltip={{
|
||||
content,
|
||||
hideOnClick: false
|
||||
}}>
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
</button>
|
||||
<label class:u-hide={!showLabel} class="label" for={label}>{label}</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input {value} id={label} type="text" class="input-text" readonly />
|
||||
<button
|
||||
type="button"
|
||||
class="input-button"
|
||||
aria-label="Click to copy."
|
||||
on:click={copy}
|
||||
on:mouseenter={() => setTimeout(() => (content = 'Click to copy'))}
|
||||
use:tooltip={{
|
||||
content,
|
||||
hideOnClick: false
|
||||
}}>
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,22 +26,32 @@
|
||||
</script>
|
||||
|
||||
<a class="card" {href}>
|
||||
<div class="grid-item-1" bind:offsetWidth={itemWidth}>
|
||||
<div class="eyebrow-heading-3"><slot name="eyebrow" /></div>
|
||||
<h2 class="heading-level-6"><slot name="title" /></h2>
|
||||
<div class="status">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3"><slot name="eyebrow" /></div>
|
||||
<h2 class="heading-level-6"><slot name="title" /></h2>
|
||||
</div>
|
||||
<div class="grid-item-1-start-end">
|
||||
<slot name="status" />
|
||||
</div>
|
||||
<div class="options" style="flex-wrap: nowrap !important;" bind:this={options}>
|
||||
<slot />
|
||||
{#if more}
|
||||
<Pill>
|
||||
+{more}
|
||||
</Pill>
|
||||
{/if}
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div
|
||||
class="u-flex u-gap-16 u-flex-wrap"
|
||||
style="flex-wrap: nowrap !important;"
|
||||
bind:this={options}>
|
||||
<slot />
|
||||
{#if more}
|
||||
<Pill>
|
||||
+{more}
|
||||
</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-item-1-end-end">
|
||||
<ul class="icons u-flex u-gap-8">
|
||||
<slot name="icons" />
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="icons u-flex u-gap-8">
|
||||
<slot name="icons" />
|
||||
</ul>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let error: string = null;
|
||||
export let closable = true;
|
||||
|
||||
let alert: HTMLElement;
|
||||
const dispatch = createEventDispatcher();
|
||||
const transitionFly: FlyParams = {
|
||||
duration: 150,
|
||||
@@ -48,6 +49,10 @@
|
||||
document.body.classList.remove('u-overflow-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
$: if (error) {
|
||||
alert?.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' });
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
@@ -81,14 +86,16 @@
|
||||
</header>
|
||||
<div class="modal-content">
|
||||
{#if error}
|
||||
<Alert
|
||||
dismissible
|
||||
type="warning"
|
||||
on:dismiss={() => {
|
||||
error = null;
|
||||
}}>
|
||||
{error}
|
||||
</Alert>
|
||||
<div bind:this={alert}>
|
||||
<Alert
|
||||
dismissible
|
||||
type="warning"
|
||||
on:dismiss={() => {
|
||||
error = null;
|
||||
}}>
|
||||
{error}
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -15,5 +15,6 @@ export { default as InputSearch } from './inputSearch.svelte';
|
||||
export { default as InputRadio } from './inputRadio.svelte';
|
||||
export { default as InputSelect } from './inputSelect.svelte';
|
||||
export { default as InputCheckbox } from './inputCheckbox.svelte';
|
||||
export { default as InputChoice } from './inputChoice.svelte';
|
||||
export { default as InputPhone } from './inputPhone.svelte';
|
||||
export { default as Helper } from './helper.svelte';
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { FormItem } from '.';
|
||||
|
||||
export let type: 'checkbox' | 'switchbox' = 'checkbox';
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
export let id: string;
|
||||
export let value = false;
|
||||
export let required = false;
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class="choice-item" for={id}>
|
||||
<input
|
||||
{id}
|
||||
{disabled}
|
||||
{required}
|
||||
type="checkbox"
|
||||
class:switch={type === 'switchbox'}
|
||||
bind:checked={value} />
|
||||
|
||||
<div class="choice-item-content">
|
||||
<div class:u-hide={!showLabel} class="choice-item-title">{label}</div>
|
||||
{#if $$slots}
|
||||
<p class="choice-item-paragraph"><slot /></p>
|
||||
{/if}
|
||||
</div>
|
||||
</label>
|
||||
</FormItem>
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let title: string;
|
||||
export let showOverflow = false;
|
||||
let data: HTMLSpanElement;
|
||||
</script>
|
||||
|
||||
<div class="table-col " class:u-overflow-visible={showOverflow} data-title={title} role="cell">
|
||||
<span class="text u-trim"><slot /></span>
|
||||
<span bind:this={data} class="text u-trim" title={data?.innerText}><slot /></span>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { browser } from '$app/env';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
|
||||
export function cachedStore<TModel, TMethods = Record<string, unknown>>(
|
||||
id: string,
|
||||
callback: (store: Writable<TModel>) => TMethods
|
||||
): TMethods & Writable<TModel> {
|
||||
const store = writable<TModel>(browser ? JSON.parse(sessionStorage.getItem(id)) : null);
|
||||
|
||||
if (browser) {
|
||||
store.subscribe((n) => sessionStorage?.setItem(id, JSON.stringify(n ?? '')));
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
set: store.set,
|
||||
update: store.update,
|
||||
...callback(store)
|
||||
};
|
||||
}
|
||||
@@ -86,7 +86,9 @@
|
||||
showDropdown = false;
|
||||
newOrgModal.set(true);
|
||||
}}>New organization</DropListItem>
|
||||
<DropListLink href="/console/$me">Your Account</DropListLink>
|
||||
<DropListLink
|
||||
href={`${base}/console/account`}
|
||||
on:click={() => (showDropdown = false)}>Your Account</DropListLink>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="drop-section">
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
</script>
|
||||
|
||||
{#if $notifications}
|
||||
<section class="toaster">
|
||||
<ul class="toaster-list">
|
||||
<section>
|
||||
<ul class="u-flex u-flex-vertical u-gap-16">
|
||||
{#each $notifications as notification (notification.id)}
|
||||
<Notification
|
||||
type={notification.type}
|
||||
|
||||
@@ -13,10 +13,14 @@
|
||||
} from '$lib/stores/organization';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { base } from '$app/paths';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
export let isOpen = false;
|
||||
export let showSideNavigation = false;
|
||||
|
||||
let y: number;
|
||||
let tabsList: HTMLUListElement;
|
||||
let showLeft = false;
|
||||
let showRight = false;
|
||||
@@ -55,6 +59,11 @@
|
||||
}
|
||||
});
|
||||
|
||||
const logout = async () => {
|
||||
await user.logout();
|
||||
await goto(`${base}/login`);
|
||||
};
|
||||
|
||||
const onScroll = () => {
|
||||
if (!tabsList) {
|
||||
return;
|
||||
@@ -77,9 +86,21 @@
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const toggleMenu = () => {
|
||||
y = 0;
|
||||
isOpen = !isOpen;
|
||||
if (browser) {
|
||||
if (isOpen) {
|
||||
document.body.classList.add('u-overflow-hidden');
|
||||
} else {
|
||||
document.body.classList.remove('u-overflow-hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={throttle(onScroll, 25)} />
|
||||
<svelte:window bind:scrollY={y} on:resize={throttle(onScroll, 25)} />
|
||||
|
||||
<main
|
||||
class:grid-with-side={showSideNavigation}
|
||||
@@ -87,9 +108,10 @@
|
||||
class:is-open={isOpen}>
|
||||
<header class="main-header">
|
||||
<button
|
||||
class:u-hide={!showSideNavigation}
|
||||
class="icon-button is-no-desktop"
|
||||
aria-label="Open Menu"
|
||||
on:click={() => (isOpen = !isOpen)}>
|
||||
on:click={toggleMenu}>
|
||||
<span class:icon-x={isOpen} class:icon-menu={!isOpen} aria-hidden="true" />
|
||||
</button>
|
||||
<slot name="header" />
|
||||
@@ -159,6 +181,12 @@
|
||||
<span class="text"> {$title}</span>
|
||||
</h1>
|
||||
{/if}
|
||||
{#if $page.url.pathname.includes('/console/account')}
|
||||
<div class="u-margin-inline-start-auto">
|
||||
<Button secondary on:click={logout}>Logout</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $copyData?.value}
|
||||
<Copy value={$copyData.value}>
|
||||
<Pill button>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import LoginLight from '$lib/images/login/login-light-mode.svg';
|
||||
import LoginDark from '$lib/images/login/login-dark-mode.svg';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
const technologies = [
|
||||
'js',
|
||||
@@ -31,8 +32,9 @@
|
||||
|
||||
<div class="u-margin-block-start-auto" />
|
||||
|
||||
<div class="u-margin-block-start-auto is-no-mobile" />
|
||||
<div
|
||||
class="container u-margin-block-start-20"
|
||||
class="container u-margin-block-start-20 is-no-mobile"
|
||||
style="--p-container-max-size: var(--container-size-large);">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
<img src={LoginDark} alt="" class="u-only-dark" />
|
||||
@@ -43,16 +45,18 @@
|
||||
|
||||
<div class="u-margin-block-start-auto" />
|
||||
|
||||
<div class="u-margin-block-start-auto is-no-mobile" />
|
||||
<div
|
||||
class="container u-text-color-light-gray is-only-desktop"
|
||||
class="container u-text-color-light-gray is-no-mobile"
|
||||
style="--p-container-max-size:var(--container-size-small); --p-container-padding-inline:1rem;">
|
||||
<p>Integrate with your favourite technologies</p>
|
||||
<ul
|
||||
class="u-flex u-main-center u-flex-wrap u-gap-16 u-margin-block-start-32 u-line-height-1 ">
|
||||
class="u-flex u-main-center u-flex-wrap u-gap-16 u-margin-block-start-32 u-line-height-1 u-opacity-0-5">
|
||||
{#each technologies as tech}
|
||||
<li>
|
||||
<span
|
||||
class={`icon-${tech} u-font-size-32`}
|
||||
<img
|
||||
src={`${base}/icons/${$app.themeInUse}/grayscale/${tech}.svg`}
|
||||
alt={tech}
|
||||
aria-hidden="true"
|
||||
aria-label={tech} />
|
||||
</li>
|
||||
@@ -62,7 +66,7 @@
|
||||
<div class="u-margin-block-start-40" />
|
||||
</section>
|
||||
<section class="grid-1-1-col-2 u-flex u-main-center u-cross-center">
|
||||
<div class="container u-flex u-flex-vertical u-cross-center">
|
||||
<div class="container u-flex u-flex-vertical u-cross-center u-height-100-percents">
|
||||
<div class="u-margin-block-start-auto" />
|
||||
|
||||
<div class="u-max-width-500 u-width-full-line">
|
||||
|
||||
@@ -1,52 +1,50 @@
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/env';
|
||||
import { get } from 'svelte/store';
|
||||
import { base } from '$app/paths';
|
||||
import { goto } from '$app/navigation';
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
|
||||
function createOrganizationList() {
|
||||
const { subscribe, set } = writable<Models.TeamList>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('organizationList')) : null
|
||||
);
|
||||
export const newOrgModal = writable<boolean>(false);
|
||||
export const newMemberModal = writable<boolean>(false);
|
||||
|
||||
export const organizationList = cachedStore<
|
||||
Models.TeamList,
|
||||
{
|
||||
load: () => Promise<void>;
|
||||
}
|
||||
>('organizationList', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async () => {
|
||||
const response = await sdkForConsole.teams.list();
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
function createOrganization() {
|
||||
const { subscribe, set } = writable<Models.Team>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('organization')) : null
|
||||
);
|
||||
});
|
||||
|
||||
export const organization = cachedStore<
|
||||
Models.Team,
|
||||
{
|
||||
load: (teamId: string) => Promise<void>;
|
||||
}
|
||||
>('organization', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (teamId: string) => {
|
||||
load: async (teamId) => {
|
||||
const response = await sdkForConsole.teams.get(teamId);
|
||||
set(response);
|
||||
},
|
||||
deleteCache: () => {
|
||||
sessionStorage.removeItem('organization');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createProjectList() {
|
||||
const { subscribe, set } = writable<Models.ProjectList>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('projectList')) : null
|
||||
);
|
||||
});
|
||||
|
||||
export const projectList = cachedStore<
|
||||
Models.ProjectList,
|
||||
{
|
||||
load: (search: string, limit: number, offset: number) => Promise<void>;
|
||||
}
|
||||
>('projectList', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (search: string, limit: number, offset: number) => {
|
||||
load: async (search, limit, offset) => {
|
||||
const response = await sdkForConsole.projects.list(
|
||||
search,
|
||||
limit,
|
||||
@@ -58,17 +56,16 @@ function createProjectList() {
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createMemberList() {
|
||||
const { subscribe, set } = writable<Models.MembershipList>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('memberList')) : null
|
||||
);
|
||||
});
|
||||
|
||||
export const memberList = cachedStore<
|
||||
Models.MembershipList,
|
||||
{
|
||||
load: (teamId: string, search: string, limit: number, offset: number) => Promise<void>;
|
||||
}
|
||||
>('memberList', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (teamId: string, search: string, limit: number, offset: number) => {
|
||||
load: async (teamId, search, limit, offset) => {
|
||||
const response = await sdkForConsole.teams.getMemberships(
|
||||
teamId,
|
||||
search,
|
||||
@@ -78,23 +75,7 @@ function createMemberList() {
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const organizationList = createOrganizationList();
|
||||
export const organization = createOrganization();
|
||||
export const projectList = createProjectList();
|
||||
export const memberList = createMemberList();
|
||||
export const newOrgModal = writable<boolean>(false);
|
||||
export const newMemberModal = writable<boolean>(false);
|
||||
|
||||
if (browser) {
|
||||
organizationList.subscribe((n) =>
|
||||
sessionStorage?.setItem('organizationList', JSON.stringify(n ?? ''))
|
||||
);
|
||||
organization.subscribe((n) => sessionStorage?.setItem('organization', JSON.stringify(n ?? '')));
|
||||
projectList.subscribe((n) => sessionStorage?.setItem('projectList', JSON.stringify(n ?? '')));
|
||||
memberList.subscribe((n) => sessionStorage?.setItem('memberList', JSON.stringify(n ?? '')));
|
||||
}
|
||||
});
|
||||
|
||||
export const redirectTo = async () => {
|
||||
let org = get(organization);
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
export type Service = {
|
||||
label: string;
|
||||
method: string;
|
||||
value: boolean | null;
|
||||
};
|
||||
|
||||
function createServices() {
|
||||
const { subscribe, set } = writable({
|
||||
list: [
|
||||
{
|
||||
label: 'Account',
|
||||
method: 'account',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Avatars',
|
||||
method: 'avatars',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Databases',
|
||||
method: 'databases',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Functions',
|
||||
method: 'functions',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Health',
|
||||
method: 'health',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Locale',
|
||||
method: 'locale',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Storage',
|
||||
method: 'storage',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Teams',
|
||||
method: 'teams',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Users',
|
||||
method: 'users',
|
||||
value: null
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: (project: Models.Project) => {
|
||||
const list = [
|
||||
{
|
||||
label: 'Account',
|
||||
method: 'account',
|
||||
value: project.serviceStatusForAccount
|
||||
},
|
||||
{
|
||||
label: 'Avatars',
|
||||
method: 'avatars',
|
||||
value: project.serviceStatusForAvatars
|
||||
},
|
||||
{
|
||||
label: 'Databases',
|
||||
method: 'databases',
|
||||
value: project.serviceStatusForDatabases
|
||||
},
|
||||
{
|
||||
label: 'Functions',
|
||||
method: 'functions',
|
||||
value: project.serviceStatusForFunctions
|
||||
},
|
||||
{
|
||||
label: 'Health',
|
||||
method: 'health',
|
||||
value: project.serviceStatusForHealth
|
||||
},
|
||||
{
|
||||
label: 'Locale',
|
||||
method: 'locale',
|
||||
value: project.serviceStatusForLocale
|
||||
},
|
||||
{
|
||||
label: 'Storage',
|
||||
method: 'storage',
|
||||
value: project.serviceStatusForStorage
|
||||
},
|
||||
{
|
||||
label: 'Teams',
|
||||
method: 'teams',
|
||||
value: project.serviceStatusForTeams
|
||||
},
|
||||
{
|
||||
label: 'Users',
|
||||
method: 'users',
|
||||
value: project.serviceStatusForUsers
|
||||
}
|
||||
];
|
||||
set({ list });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const services = createServices();
|
||||
@@ -10,6 +10,9 @@ function createUserStore() {
|
||||
set,
|
||||
fetchUser: async () => {
|
||||
set(await sdkForConsole.account.get());
|
||||
},
|
||||
logout: async () => {
|
||||
await sdkForConsole.account.deleteSession('current');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
window.GOOGLE_ANALYTICS = import.meta.env.VITE_GOOGLE_ANALYTICS?.toString() ?? false;
|
||||
}
|
||||
|
||||
const acceptedRoutes = ['/login', '/register', '/recover', '/invite'];
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
if (!$user) {
|
||||
@@ -33,7 +35,9 @@
|
||||
await redirectTo();
|
||||
}
|
||||
} catch (error) {
|
||||
await goto(`${base}/login`);
|
||||
if (acceptedRoutes.includes($page.url.pathname)) {
|
||||
await goto(`${base}${$page.url.pathname}${$page.url.search}`);
|
||||
} else await goto(`${base}/login`);
|
||||
} finally {
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
|
||||
const logout = async () => {
|
||||
await sdkForConsole.account.deleteSession('current');
|
||||
await goto(`${base}/login`);
|
||||
};
|
||||
const toggleTheme = () => {
|
||||
$app.theme = $app.theme === 'light' ? 'dark' : 'light';
|
||||
};
|
||||
</script>
|
||||
|
||||
<h1>hey</h1>
|
||||
<Button on:click={toggleTheme}>toggle Theme</Button>
|
||||
<Button on:click={logout}>Logout</Button>
|
||||
@@ -36,7 +36,10 @@
|
||||
<title>Appwrite - Console</title>
|
||||
</svelte:head>
|
||||
|
||||
<Shell showSideNavigation={$page.url.pathname !== '/console' && !$page?.params.organization}>
|
||||
<Shell
|
||||
showSideNavigation={$page.url.pathname !== '/console' &&
|
||||
!$page?.params.organization &&
|
||||
!$page.url.pathname.includes('/console/account')}>
|
||||
<svelte:fragment slot="header">
|
||||
<Header />
|
||||
</svelte:fragment>
|
||||
@@ -47,4 +50,6 @@
|
||||
<footer class="main-footer" />
|
||||
</Shell>
|
||||
|
||||
<Create bind:show={$newOrgModal} closable={!!$organizationList?.total} />
|
||||
{#if $newOrgModal}
|
||||
<Create bind:show={$newOrgModal} closable={!!$organizationList?.total} />
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { updateLayout } from '$lib/stores/layout';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const path = 'console/account';
|
||||
|
||||
onMount(handle);
|
||||
afterNavigate(handle);
|
||||
|
||||
async function handle(event = null) {
|
||||
if (!$user) {
|
||||
await user.fetchUser();
|
||||
}
|
||||
updateLayout({
|
||||
navigate: event,
|
||||
title: $user?.name,
|
||||
level: 0,
|
||||
customBase: '',
|
||||
breadcrumbs: {
|
||||
title: `${$user.name}`,
|
||||
href: path
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
href: path,
|
||||
title: 'Overview'
|
||||
},
|
||||
{
|
||||
href: `${path}/sessions`,
|
||||
title: 'Sessions'
|
||||
},
|
||||
{
|
||||
href: `${path}/activity`,
|
||||
title: 'Activity'
|
||||
},
|
||||
{
|
||||
href: `${path}/organizations`,
|
||||
title: 'Organizations'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Appwrite - User</title>
|
||||
</svelte:head>
|
||||
|
||||
<slot />
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
|
||||
export let showDelete = false;
|
||||
|
||||
const deleteAccount = async () => {
|
||||
try {
|
||||
await sdkForConsole.users.delete($user.$id);
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Account was deleted `
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={deleteAccount}>
|
||||
<Modal bind:show={showDelete} warning>
|
||||
<svelte:fragment slot="header">Delete Account</svelte:fragment>
|
||||
<p>Are you sure you want to delete your account?</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button secondary submit>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -0,0 +1,108 @@
|
||||
<script lang="ts">
|
||||
import { Empty, Pagination } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { pageLimit } from '$lib/stores/layout';
|
||||
import { onMount } from 'svelte';
|
||||
import { accountActivity } from './store';
|
||||
|
||||
let offset = 0;
|
||||
|
||||
onMount(async () => {
|
||||
await accountActivity.load($pageLimit, offset);
|
||||
});
|
||||
|
||||
$: accountActivity.load($pageLimit, offset);
|
||||
|
||||
const getBrowser = (clientCode: string) => {
|
||||
return sdkForConsole.avatars.getBrowser(clientCode, 40, 40);
|
||||
};
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if $accountActivity?.total}
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Date</TableCellHead>
|
||||
<TableCellHead>Event</TableCellHead>
|
||||
<TableCellHead>Client</TableCellHead>
|
||||
<TableCellHead>Location</TableCellHead>
|
||||
<TableCellHead>IP</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each $accountActivity.logs as log}
|
||||
<TableRow>
|
||||
<TableCellText title="Date">{toLocaleDateTime(log.time)}</TableCellText>
|
||||
<TableCellText title="Event">{log.event}</TableCellText>
|
||||
|
||||
<TableCell title="Client">
|
||||
{#if log.clientName}
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<div class="avatar is-small">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
src={getBrowser(log.clientCode).toString()}
|
||||
alt={log.clientName} />
|
||||
</div>
|
||||
<p
|
||||
class="text u-trim"
|
||||
title={`${log.clientName} ${log.clientVersion} on ${log.osName} ${log.osVersion}`}>
|
||||
{log.clientName}
|
||||
{log.clientVersion}
|
||||
on {log.osName}
|
||||
{log.osVersion}
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<span class="avatar is-color-empty" />
|
||||
<p class="text u-trim">Unknown</p>
|
||||
</div>
|
||||
{/if}
|
||||
</TableCell>
|
||||
|
||||
<TableCellText title="Location">
|
||||
{#if log.countryCode !== '--'}
|
||||
{log.countryName}
|
||||
{:else}
|
||||
Unknown
|
||||
{/if}
|
||||
</TableCellText>
|
||||
<TableCellText title="IP">{log.ip}</TableCellText>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{:else}
|
||||
<Empty centered>
|
||||
<div class="u-flex u-flex-vertical u-cross-center">
|
||||
<div class="common-section">
|
||||
<p>No logs available</p>
|
||||
</div>
|
||||
<div class="common-section">
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href="https://appwrite.io/docs/server/authentication?sdk=nodejs-default#usersGetLogs"
|
||||
>Documentation</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Empty>
|
||||
{/if}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {$accountActivity?.total}</p>
|
||||
<Pagination limit={$pageLimit} bind:offset sum={$accountActivity?.total} />
|
||||
</div>
|
||||
</Container>
|
||||
@@ -0,0 +1,187 @@
|
||||
<script lang="ts">
|
||||
import { Button, Form, FormList, InputText, InputPassword } from '$lib/elements/forms';
|
||||
import { CardGrid, Box, Avatar } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { onMount } from 'svelte';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { title, breadcrumbs } from '$lib/stores/layout';
|
||||
import { base } from '$app/paths';
|
||||
import Delete from './_delete.svelte';
|
||||
|
||||
let name: string = null,
|
||||
email: string = null,
|
||||
emailPassword: string = null,
|
||||
newPassword: string = null,
|
||||
oldPassword: string = null;
|
||||
let showDelete = false;
|
||||
|
||||
onMount(async () => {
|
||||
name ??= $user.name;
|
||||
email ??= $user.email;
|
||||
});
|
||||
|
||||
const getAvatar = (name: string) => sdkForConsole.avatars.getInitials(name, 96, 96).toString();
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForConsole.account.updateName(name);
|
||||
$user.name = name;
|
||||
title.set(name);
|
||||
const breadcrumb = $breadcrumbs.get(0);
|
||||
breadcrumb.title = name;
|
||||
$breadcrumbs = $breadcrumbs.set($breadcrumbs.size, breadcrumb);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateEmail() {
|
||||
try {
|
||||
await sdkForConsole.account.updateEmail(email, emailPassword);
|
||||
$user.email = email;
|
||||
addNotification({
|
||||
message: 'Email has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePassword() {
|
||||
try {
|
||||
await sdkForConsole.account.updatePassword(newPassword, oldPassword);
|
||||
addNotification({
|
||||
message: 'Password has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Name</h6>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
autofocus={true}
|
||||
bind:value={name} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={name === $user.name || !name} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
<Form on:submit={updateEmail}>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Email</h6>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autocomplete={false}
|
||||
bind:value={email} />
|
||||
{#if email !== $user.email && email}
|
||||
<InputPassword
|
||||
id="emailPassword"
|
||||
label="Password"
|
||||
placeholder="Enter password"
|
||||
autocomplete={false}
|
||||
showPasswordButton={true}
|
||||
bind:value={emailPassword} />
|
||||
{/if}
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={email === $user.email || !email || !emailPassword} submit
|
||||
>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
<Form on:submit={updatePassword}>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Password</h6>
|
||||
<p class="text">
|
||||
Forgot your password? <a class="link" href={`${base}/recover`}
|
||||
>Recover your password</a>
|
||||
</p>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputPassword
|
||||
id="newPassword"
|
||||
label="New password"
|
||||
placeholder="Enter password"
|
||||
autocomplete={false}
|
||||
showPasswordButton={true}
|
||||
bind:value={newPassword} />
|
||||
<InputPassword
|
||||
id="oldPassword"
|
||||
label="Old password"
|
||||
placeholder="Enter password"
|
||||
autocomplete={false}
|
||||
showPasswordButton={true}
|
||||
bind:value={oldPassword} />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={!newPassword || !oldPassword} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
<CardGrid>
|
||||
<div>
|
||||
<h6 class="heading-level-7">Delete Account</h6>
|
||||
</div>
|
||||
<p>
|
||||
Your account will be permanently deleted and access will be lost to any of your teams
|
||||
and data. This action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="image">
|
||||
<Avatar size={48} name={$user.name} src={getAvatar($user.name)} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold">{$user.name}</h6>
|
||||
</svelte:fragment>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { GridItem1, EmptyGridItem, Empty, Pagination, AvatarGroup } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import CreateOrganization from '../_createOrganization.svelte';
|
||||
import { organizationList } from '$lib/stores/organization';
|
||||
import { onMount } from 'svelte';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
|
||||
onMount(async () => {
|
||||
await organizationList.load();
|
||||
});
|
||||
|
||||
const getAvatar = (name: string) => {
|
||||
return sdkForConsole.avatars.getInitials(name, 80, 80).toString();
|
||||
};
|
||||
|
||||
const getMemberships = async (teamId: string) => {
|
||||
const memberships = await sdkForConsole.teams.getMemberships(teamId);
|
||||
return memberships.memberships.map((team) => {
|
||||
return {
|
||||
name: team.userName,
|
||||
img: getAvatar(team.userName)
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
let addOrganization = false;
|
||||
let offset = 0;
|
||||
const limit = 6;
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<h2 class="heading-level-5">Organizations</h2>
|
||||
|
||||
<Button
|
||||
on:click={() => {
|
||||
addOrganization = true;
|
||||
}}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Create organization</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if $organizationList?.teams?.length}
|
||||
<ul
|
||||
class="grid-box common-section u-margin-block-start-32"
|
||||
style={`--grid-gap:1.5rem; --grid-item-size:${
|
||||
$organizationList.total > 3 ? '22rem' : '25rem'
|
||||
};`}>
|
||||
{#each $organizationList.teams as organization, index}
|
||||
{@const avatarList = getMemberships(organization.$id)}
|
||||
{#if index >= offset && index < limit + offset}
|
||||
<GridItem1 href={`${base}/console/organization-${organization.$id}`}>
|
||||
<svelte:fragment slot="eyebrow"
|
||||
>{organization?.total ? organization?.total : 'No'} projects</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
{organization.name}
|
||||
</svelte:fragment>
|
||||
{#await avatarList}
|
||||
<span class="avatar is-color-empty" />
|
||||
{:then avatars}
|
||||
<AvatarGroup size={40} {avatars} />
|
||||
{/await}
|
||||
</GridItem1>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if $organizationList?.total < limit + offset && ($organizationList?.total % 2 !== 0 || $organizationList?.total % 4 === 0)}
|
||||
<EmptyGridItem on:click={() => (addOrganization = true)}>
|
||||
<div class="common-section">
|
||||
<Button secondary round>
|
||||
<i class="icon-plus" />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="common-section">
|
||||
<p>Create a new organization</p>
|
||||
</div>
|
||||
</EmptyGridItem>
|
||||
{/if}
|
||||
</ul>
|
||||
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {$organizationList?.total}</p>
|
||||
<Pagination {limit} bind:offset sum={$organizationList?.total} />
|
||||
</div>
|
||||
{:else}
|
||||
<Empty dashed centered>
|
||||
<div class="grid-item-1">
|
||||
<div class="u-flex u-flex-vertical u-cross-center ">
|
||||
<div class="common-section">
|
||||
<Button secondary round on:click={() => (addOrganization = true)}>
|
||||
<i class="icon-plus" />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="common-section">
|
||||
<p>Create a new organization</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Empty>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {$organizationList?.total}</p>
|
||||
<Pagination {limit} bind:offset sum={$organizationList?.total} />
|
||||
</div>
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<CreateOrganization bind:show={addOrganization} />
|
||||
@@ -0,0 +1,125 @@
|
||||
<script lang="ts">
|
||||
import { Empty, Pagination } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { pageLimit } from '$lib/stores/layout';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { accountSession } from './store';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let offset = 0;
|
||||
|
||||
onMount(async () => {
|
||||
await accountSession.load();
|
||||
});
|
||||
|
||||
const getBrowser = (clientCode: string) => {
|
||||
return sdkForConsole.avatars.getBrowser(clientCode, 40, 40);
|
||||
};
|
||||
|
||||
const logout = async (session: Models.Session) => {
|
||||
await sdkForConsole.account.deleteSession(session.$id);
|
||||
await accountSession.load();
|
||||
};
|
||||
const logoutAll = async () => {
|
||||
await sdkForConsole.account.deleteSessions();
|
||||
await accountSession.load();
|
||||
};
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<h2 class="heading-level-5">Sessions</h2>
|
||||
|
||||
<Button secondary on:click={logoutAll}>
|
||||
<span class="text">Logout all sessions</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if $accountSession?.total}
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Client</TableCellHead>
|
||||
<TableCellHead width={100}>Location</TableCellHead>
|
||||
<TableCellHead width={150}>IP</TableCellHead>
|
||||
<TableCellHead width={100} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each $accountSession.sessions as session}
|
||||
<TableRow>
|
||||
<TableCell title="Client">
|
||||
<div class="u-flex u-gap-12">
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
{#if session.clientName}
|
||||
<div class="avatar is-small">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
src={getBrowser(session.clientCode).toString()}
|
||||
alt={session.clientName} />
|
||||
</div>
|
||||
<p class="text u-trim">
|
||||
{session.clientName}
|
||||
{session.clientVersion}
|
||||
on {session.osName}
|
||||
{session.osVersion}
|
||||
</p>
|
||||
{:else}
|
||||
<span class="avatar is-color-empty" />
|
||||
<p class="text u-trim">Unknown</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Pill>{session.provider}</Pill>
|
||||
{#if session.current}
|
||||
<Pill success>current session</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Location">
|
||||
{#if session.countryCode !== '--'}
|
||||
{session.countryName}
|
||||
{:else}
|
||||
Unknown
|
||||
{/if}
|
||||
</TableCellText>
|
||||
<TableCellText title="IP">{session.ip}</TableCellText>
|
||||
<TableCell title="Client">
|
||||
<Button secondary on:click={() => logout(session)}>Logout</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{:else}
|
||||
<Empty centered>
|
||||
<div class="u-flex u-flex-vertical u-cross-center">
|
||||
<div class="common-section">
|
||||
<p>No sessions available</p>
|
||||
</div>
|
||||
<div class="common-section">
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href="https://appwrite.io/docs/server/authentication?sdk=nodejs-default#usersGetsessions"
|
||||
>Documentation</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Empty>
|
||||
{/if}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {$accountSession?.total}</p>
|
||||
<Pagination limit={$pageLimit} bind:offset sum={$accountSession?.total} />
|
||||
</div>
|
||||
</Container>
|
||||
@@ -0,0 +1,30 @@
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
export const accountSession = cachedStore<
|
||||
Models.SessionList,
|
||||
{
|
||||
load: () => Promise<void>;
|
||||
}
|
||||
>('accountSession', function ({ set }) {
|
||||
return {
|
||||
load: async () => {
|
||||
const response = await sdkForConsole.account.getSessions();
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
});
|
||||
export const accountActivity = cachedStore<
|
||||
Models.LogList,
|
||||
{
|
||||
load: (limit: number, offset: number) => Promise<void>;
|
||||
}
|
||||
>('accountActivity', function ({ set }) {
|
||||
return {
|
||||
load: async (limit, offset) => {
|
||||
const response = await sdkForConsole.account.getLogs(limit, offset);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -20,7 +20,7 @@
|
||||
afterNavigate(handle);
|
||||
|
||||
async function handle(event = null) {
|
||||
if ($organization.$id !== organizationId) {
|
||||
if ($organization?.$id !== organizationId) {
|
||||
await organization.load(organizationId);
|
||||
await memberList.load(organizationId, '', 12, 0);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let email: string, name: string, error: string;
|
||||
const url = `${$page.url.origin}/console/`;
|
||||
const url = `${$page.url.origin}/invite`;
|
||||
|
||||
const create = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -15,8 +17,14 @@
|
||||
const deleteMembership = async () => {
|
||||
try {
|
||||
await sdkForConsole.teams.deleteMembership(selectedMember.teamId, selectedMember.$id);
|
||||
|
||||
if (isUser) {
|
||||
await user.logout();
|
||||
await goto(`${base}/login`);
|
||||
} else {
|
||||
dispatch('deleted');
|
||||
}
|
||||
showDelete = false;
|
||||
dispatch('deleted');
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${selectedMember.userName} was deleted from ${selectedMember.teamName}`
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { pageLimit } from '$lib/stores/layout';
|
||||
import { page } from '$app/stores';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
let search = '';
|
||||
let offset: number = null;
|
||||
@@ -29,14 +30,26 @@
|
||||
const getAvatar = (name: string) =>
|
||||
sdkForConsole.avatars.getInitials(name, 120, 120).toString();
|
||||
const deleted = () => memberList.load($organization.$id, search, $pageLimit, offset ?? 0);
|
||||
const resend = async (member: Models.Membership) =>
|
||||
await sdkForConsole.teams.createMembership(
|
||||
$organization.$id,
|
||||
member.userEmail,
|
||||
member.roles,
|
||||
url,
|
||||
member.userName
|
||||
);
|
||||
const resend = async (member: Models.Membership) => {
|
||||
try {
|
||||
await sdkForConsole.teams.createMembership(
|
||||
$organization.$id,
|
||||
member.userEmail,
|
||||
member.roles,
|
||||
url,
|
||||
member.userName
|
||||
);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Invite has been sent to ${member.userEmail}`
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$: if (search) offset = 0;
|
||||
$: memberList.load($organization.$id, search, $pageLimit, offset ?? 0);
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
function createUsersListStore() {
|
||||
const { subscribe, set } = writable<Models.UserList<Record<string, unknown>>>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('users')) : null
|
||||
);
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
|
||||
export const usersList = cachedStore<
|
||||
Models.UserList<Record<string, unknown>>,
|
||||
{
|
||||
load: (search: string, limit: number, offset: number) => Promise<void>;
|
||||
}
|
||||
>('users', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (search: string, limit: number, offset: number) => {
|
||||
load: async (search, limit, offset) => {
|
||||
const response = await sdkForProject.users.list(
|
||||
search,
|
||||
limit,
|
||||
@@ -23,17 +21,16 @@ function createUsersListStore() {
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createTeamsListStore() {
|
||||
const { subscribe, set } = writable<Models.TeamList>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('teams')) : null
|
||||
);
|
||||
});
|
||||
|
||||
export const teamsList = cachedStore<
|
||||
Models.TeamList,
|
||||
{
|
||||
load: (search: string, limit: number, offset: number) => Promise<void>;
|
||||
}
|
||||
>('teams', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (search: string, limit: number, offset: number) => {
|
||||
load: async (search, limit, offset) => {
|
||||
const response = await sdkForProject.teams.list(
|
||||
search,
|
||||
limit,
|
||||
@@ -45,28 +42,18 @@ function createTeamsListStore() {
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
function createUsersUsageStore() {
|
||||
const { subscribe, set } = writable(
|
||||
browser ? JSON.parse(sessionStorage.getItem('usersUsage')) : null
|
||||
);
|
||||
});
|
||||
|
||||
export const usersUsage = cachedStore<
|
||||
Models.UsageUsers,
|
||||
{
|
||||
load: (range: string) => Promise<void>;
|
||||
}
|
||||
>('usersUsage', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (range: string) => {
|
||||
load: async (range) => {
|
||||
const response = await sdkForProject.users.getUsage(range);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const usersList = createUsersListStore();
|
||||
export const teamsList = createTeamsListStore();
|
||||
export const usersUsage = createUsersUsageStore();
|
||||
|
||||
if (browser) {
|
||||
usersList.subscribe((n) => sessionStorage?.setItem('users', JSON.stringify(n ?? '')));
|
||||
teamsList.subscribe((n) => sessionStorage?.setItem('teams', JSON.stringify(n ?? '')));
|
||||
usersUsage.subscribe((n) => sessionStorage?.setItem('usersUsage', JSON.stringify(n ?? '')));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
function createTeamStore() {
|
||||
const { subscribe, set } = writable<Models.Team>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('team')) : null
|
||||
);
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
|
||||
export const team = cachedStore<
|
||||
Models.Team,
|
||||
{
|
||||
load: (teamId: string) => Promise<void>;
|
||||
}
|
||||
>('team', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (teamId: string) => {
|
||||
const response = await sdkForProject.teams.get(teamId);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
function createMembershipStore() {
|
||||
const { subscribe, set } = writable<Models.MembershipList>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('memberships')) : null
|
||||
);
|
||||
|
||||
});
|
||||
export const memberships = cachedStore<
|
||||
Models.MembershipList,
|
||||
{
|
||||
load: (teamId: string, search: string, limit: number, offset: number) => Promise<void>;
|
||||
}
|
||||
>('memberships', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (teamId: string, search: string, limit: number, offset: number) => {
|
||||
load: async (teamId, search, limit, offset) => {
|
||||
const response = await sdkForProject.teams.getMemberships(
|
||||
teamId,
|
||||
search,
|
||||
@@ -38,12 +35,4 @@ function createMembershipStore() {
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const memberships = createMembershipStore();
|
||||
export const team = createTeamStore();
|
||||
|
||||
if (browser) {
|
||||
team.subscribe((n) => sessionStorage?.setItem('team', JSON.stringify(n ?? '')));
|
||||
memberships.subscribe((n) => sessionStorage?.setItem('memberships', JSON.stringify(n ?? '')));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
function createUserStore() {
|
||||
const { subscribe, set } = writable<Models.User<Record<string, unknown>>>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('selectedUser')) : null
|
||||
);
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
|
||||
export const user = cachedStore<
|
||||
Models.User<Record<string, unknown>>,
|
||||
{
|
||||
load: (userId: string) => Promise<void>;
|
||||
}
|
||||
>('selectedUser', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (userId: string) => {
|
||||
const response = await sdkForProject.users.get(userId);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const user = createUserStore();
|
||||
|
||||
if (browser) {
|
||||
user.subscribe((n) => sessionStorage?.setItem('selectedUser', JSON.stringify(n ?? '')));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -29,10 +29,6 @@
|
||||
{
|
||||
href: `${path}/domains`,
|
||||
title: 'Custom Domains'
|
||||
},
|
||||
{
|
||||
href: `${path}/members`,
|
||||
title: 'Members'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button, Form, FormList, InputPassword, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { project } from '../store';
|
||||
|
||||
export let showDelete = false;
|
||||
|
||||
const projectId = $page.params.project;
|
||||
let password: string = null;
|
||||
let name: string = null;
|
||||
|
||||
onMount(async () => {
|
||||
if (projectId !== $project.$id) await project.load(projectId);
|
||||
});
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await sdkForConsole.projects.delete($project.$id, password);
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$project.name} has been deleted`
|
||||
});
|
||||
await goto(`${base}/console`);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={handleDelete}>
|
||||
<Modal bind:show={showDelete} warning>
|
||||
<svelte:fragment slot="header">Delete Project</svelte:fragment>
|
||||
<p>
|
||||
<b>This project will be deleted</b>, along with all of its metadata, stats, and other
|
||||
resources. <b>This action is irreversible</b>.
|
||||
</p>
|
||||
|
||||
<FormList>
|
||||
<InputText
|
||||
label={`Enter "${$project.name}" to continue`}
|
||||
placeholder="Enter name"
|
||||
id="name"
|
||||
autofocus
|
||||
required
|
||||
bind:value={name} />
|
||||
<InputPassword
|
||||
label="To verify, enter your password"
|
||||
placeholder="Enter password"
|
||||
id="password"
|
||||
required
|
||||
showPasswordButton={true}
|
||||
bind:value={password} />
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button disabled={!name || !password || name !== $project.name} secondary submit
|
||||
>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -1,15 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { Card } from '$lib/components';
|
||||
import { Button, Form, FormItem, InputText } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { project } from '../store';
|
||||
import { services, type Service } from '$lib/stores/project-services';
|
||||
import { CardGrid, CopyInput, Box } from '$lib/components';
|
||||
import { Button, Form, FormList, InputText, InputSwitch } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import Delete from './_deleteProject.svelte';
|
||||
|
||||
const update = async () => {
|
||||
let name: string = null;
|
||||
let showDelete = false;
|
||||
const endpoint = sdkForConsole.client.config.endpoint;
|
||||
|
||||
onMount(async () => {
|
||||
await project.load($page.params.project);
|
||||
|
||||
name ??= $project.name;
|
||||
});
|
||||
|
||||
const updateName = async () => {
|
||||
try {
|
||||
await sdkForConsole.projects.update($project.$id, $project.name);
|
||||
await project.load($project.$id);
|
||||
await sdkForConsole.projects.update($project.$id, name);
|
||||
$project.name = name;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Project name has been updated'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
@@ -17,18 +36,115 @@
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const serviceUpdate = async (service: Service) => {
|
||||
try {
|
||||
await sdkForConsole.projects.updateServiceStatus(
|
||||
$project.$id,
|
||||
service.method,
|
||||
service.value
|
||||
);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${service.label} service has been ${
|
||||
service.value ? 'enabled' : 'disabled'
|
||||
}`
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$: services.load($project);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<h1>Overview</h1>
|
||||
{#if $project}
|
||||
<Card>
|
||||
<Form on:submit={update}>
|
||||
<InputText id="name" label="Name" bind:value={$project.name} required />
|
||||
<FormItem>
|
||||
<Button submit>Update</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Card>
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Name</h6>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
bind:value={name}
|
||||
required
|
||||
placeholder="Enter name" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={name === $project.name} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">API Credentials</h6>
|
||||
<p class="text">
|
||||
Access Appwrite services using your API Endpoint and Project ID. You can connect
|
||||
Appwrite to your applications and server-side code by <a href="#/" class="link"
|
||||
>integrating a new platform</a>
|
||||
or
|
||||
<a href="#/" class="link">creating an API key</a>.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<CopyInput label="Project ID" showLabel={true} value={$project.$id} />
|
||||
<CopyInput label="API Endpoint" showLabel={true} value={endpoint} />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Services</h6>
|
||||
<p class="text">Choose services you wish to enable or disable.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<form class="form">
|
||||
<ul class="form-list is-multiple">
|
||||
{#each $services.list as service}
|
||||
<InputSwitch
|
||||
label={service.label}
|
||||
id={service.method}
|
||||
bind:value={service.value}
|
||||
on:change={() => {
|
||||
serviceUpdate(service);
|
||||
}} />
|
||||
{/each}
|
||||
</ul>
|
||||
</form>
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<div>
|
||||
<h6 class="heading-level-7">Delete Project</h6>
|
||||
</div>
|
||||
<p>
|
||||
The project will be permanently deleted, including all the metadata, resources and
|
||||
stats within it. This action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold">{$project.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last update: {toLocaleDateTime($project.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { List, ListItem } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { project } from '../store';
|
||||
|
||||
$: request = sdkForConsole.teams.getMemberships($project?.teamId ?? undefined);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<h1>Members</h1>
|
||||
{#await request}
|
||||
<div aria-busy="true" />
|
||||
{:then response}
|
||||
{#if response}
|
||||
<List>
|
||||
{#each response.memberships as membership}
|
||||
<ListItem
|
||||
avatar={sdkForConsole.avatars
|
||||
.getInitials(membership.userName, 64, 64)
|
||||
.toString()}>
|
||||
<svelte:fragment slot="header">
|
||||
<h2 class="sessions-item-title">
|
||||
<span class="text">
|
||||
{membership.userName}
|
||||
</span>
|
||||
</h2>
|
||||
{#each membership.roles as role}
|
||||
<Pill>{role}</Pill>
|
||||
{/each}
|
||||
{#if !membership.confirm}
|
||||
<Pill danger>Pending Approval</Pill>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="info">
|
||||
{membership.userEmail}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="action">
|
||||
<Button danger>Remove</Button>
|
||||
</svelte:fragment>
|
||||
</ListItem>
|
||||
{:else}
|
||||
<p>No sessions available.</p>
|
||||
{/each}
|
||||
</List>
|
||||
{/if}
|
||||
{/await}
|
||||
</Container>
|
||||
@@ -1,25 +1,17 @@
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
function createFileStore() {
|
||||
const { subscribe, set } = writable<Models.File>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('file')) : null
|
||||
);
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
|
||||
export const file = cachedStore<
|
||||
Models.File,
|
||||
{
|
||||
load: (bucketId: string, fileId: string) => Promise<void>;
|
||||
}
|
||||
>('file', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (bucketId: string, fileId: string) => {
|
||||
load: async (bucketId, fileId) => {
|
||||
const response = await sdkForProject.storage.getFile(bucketId, fileId);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const file = createFileStore();
|
||||
|
||||
if (browser) {
|
||||
file.subscribe((n) => sessionStorage?.setItem('file', JSON.stringify(n ?? '')));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
function createBucketListStore() {
|
||||
const { subscribe, set } = writable<Models.BucketList>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('bucketList')) : null
|
||||
);
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
|
||||
export const bucketList = cachedStore<
|
||||
Models.BucketList,
|
||||
{
|
||||
load: (search: string, limit: number, offset: number) => Promise<void>;
|
||||
}
|
||||
>('bucketList', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (search: string, limit: number, offset: number) => {
|
||||
load: async (search, limit, offset) => {
|
||||
const response = await sdkForProject.storage.listBuckets(search, limit, offset);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const bucketList = createBucketListStore();
|
||||
|
||||
if (browser) {
|
||||
bucketList.subscribe((n) => sessionStorage?.setItem('bucketList', JSON.stringify(n ?? '')));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
function createProject() {
|
||||
const { subscribe, set } = writable<Models.Project>();
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
|
||||
export const project = cachedStore<
|
||||
Models.Project,
|
||||
{
|
||||
load: (projectId: string) => Promise<void>;
|
||||
}
|
||||
>('project', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (projectId: string) => {
|
||||
const project = await sdkForConsole.projects.get(projectId);
|
||||
set(project);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const project = createProject();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { Button, Form, FormList, InputChoice } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { Unauthenticated } from '$lib/layout';
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let teamId: string, membershipId: string, userId: string, secret: string;
|
||||
let terms = false;
|
||||
|
||||
onMount(() => {
|
||||
userId = $page.url.searchParams.get('userId');
|
||||
secret = $page.url.searchParams.get('secret');
|
||||
teamId = $page.url.searchParams.get('teamId');
|
||||
membershipId = $page.url.searchParams.get('membershipId');
|
||||
});
|
||||
|
||||
const acceptInvite = async () => {
|
||||
try {
|
||||
await sdkForConsole.teams.updateMembershipStatus(teamId, membershipId, userId, secret);
|
||||
await user.fetchUser();
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Successfully logged in.'
|
||||
});
|
||||
await goto(`${base}/console`);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Appwrite - Accept invite</title>
|
||||
</svelte:head>
|
||||
|
||||
<Unauthenticated>
|
||||
<svelte:fragment slot="title">Invite</svelte:fragment>
|
||||
<svelte:fragment>
|
||||
{#if !userId || !secret || !membershipId || !teamId}
|
||||
<p class="text">Invalid invite link.</p>
|
||||
{:else}
|
||||
<p class="text">You have been invited to join a team project on Appwrite</p>
|
||||
<Form on:submit={acceptInvite}>
|
||||
<FormList>
|
||||
<InputChoice
|
||||
required
|
||||
bind:value={terms}
|
||||
id="terms"
|
||||
label="terms"
|
||||
showLabel={false}>
|
||||
By accepting the invitation, you agree to the <a
|
||||
class="link"
|
||||
href="https://appwrite.io/policy/terms"
|
||||
target="_blank">Terms and Conditions</a>
|
||||
and
|
||||
<a class="link" href="https://appwrite.io/policy/privacy" target="_blank">
|
||||
Privacy Policy</a
|
||||
>.</InputChoice>
|
||||
|
||||
<div class="u-flex u-main-end u-gap-12">
|
||||
<Button secondary href={`${base}/login`}>Cancel</Button>
|
||||
<Button submit>Accept</Button>
|
||||
</div>
|
||||
</FormList>
|
||||
</Form>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Unauthenticated>
|
||||
Reference in New Issue
Block a user