mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' into refactor/dismissible-self-hosted-alerts
This commit is contained in:
@@ -15,11 +15,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Audit dependencies
|
||||
run: npm audit --audit-level low
|
||||
node-version: 18
|
||||
# - name: Audit dependencies
|
||||
# run: npm audit --audit-level low
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build Console
|
||||
@@ -30,7 +30,3 @@ jobs:
|
||||
run: npm run lint
|
||||
- name: Unit Tests
|
||||
run: npm test
|
||||
# - name: E2E Tests
|
||||
# run: |
|
||||
# npx playwright install
|
||||
# npm run e2e
|
||||
|
||||
+15
-1
@@ -33,7 +33,21 @@ If you are worried about or don’t know where to start, check out the next sect
|
||||
|
||||
## Development
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install`, create a `.env` file using `.env.example` as a template.
|
||||
### 1. Clone the repository with git
|
||||
|
||||
```bash
|
||||
git clone https://github.com/appwrite/console.git appwrite-console
|
||||
```
|
||||
|
||||
### 2. Install dependencies with npm
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 3. Setup environment variables
|
||||
|
||||
Add a `.env` file by copying the `.env.example` file as a template in the project's root directory.
|
||||
|
||||
Finally, start a development server:
|
||||
|
||||
|
||||
Generated
+875
-842
File diff suppressed because it is too large
Load Diff
+24
-23
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "@appwrite/console",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
@@ -20,47 +19,49 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@analytics/google-analytics": "^1.0.5",
|
||||
"@appwrite.io/pink": "^0.0.4",
|
||||
"@appwrite.io/pink": "^0.0.6-rc.4",
|
||||
"@aw-labs/appwrite-console": "^13.1.0",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@sentry/svelte": "^7.36.0",
|
||||
"@sentry/tracing": "^7.36.0",
|
||||
"@sentry/svelte": "^7.44.2",
|
||||
"@sentry/tracing": "^7.44.2",
|
||||
"analytics": "^0.8.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"echarts": "^5.4.1",
|
||||
"logrocket": "^3.0.1",
|
||||
"pretty-bytes": "^6.1.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"web-vitals": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.30.0",
|
||||
"@sveltejs/adapter-static": "1.0.6",
|
||||
"@sveltejs/kit": "1.3.10",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.0.2",
|
||||
"@testing-library/dom": "^8.20.0",
|
||||
"@playwright/test": "^1.31.2",
|
||||
"@sveltejs/adapter-static": "2.0.1",
|
||||
"@sveltejs/kit": "1.13.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.0.3",
|
||||
"@testing-library/dom": "^9.0.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/svelte": "^3.2.2",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vitest/ui": "^0.28.4",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
||||
"@typescript-eslint/parser": "^5.56.0",
|
||||
"@vitest/ui": "^0.29.7",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"jsdom": "^21.1.0",
|
||||
"jsdom": "^21.1.1",
|
||||
"pre-commit": "^1.2.2",
|
||||
"prettier": "^2.8.3",
|
||||
"prettier": "^2.8.6",
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"sass": "^1.58.0",
|
||||
"svelte": "^3.55.1",
|
||||
"svelte-check": "^3.0.3",
|
||||
"sass": "^1.59.3",
|
||||
"svelte": "^3.57.0",
|
||||
"svelte-check": "^3.1.4",
|
||||
"svelte-jester": "^2.3.2",
|
||||
"svelte-preprocess": "^5.0.1",
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.1.1",
|
||||
"vitest": "^0.28.4"
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.2.1",
|
||||
"vitest": "^0.29.7"
|
||||
},
|
||||
"type": "module",
|
||||
"pre-commit": [
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import Analytics from 'analytics';
|
||||
import googleAnalytics from '@analytics/google-analytics';
|
||||
import { get } from 'svelte/store';
|
||||
import { page } from '$app/stores';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { AppwriteException } from '@aw-labs/appwrite-console';
|
||||
import { ENV, MODE, VARS } from '$lib/system';
|
||||
import googleAnalytics from '@analytics/google-analytics';
|
||||
import { AppwriteException } from '@aw-labs/appwrite-console';
|
||||
import Analytics from 'analytics';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
const analytics = Analytics({
|
||||
app: 'appwrite',
|
||||
plugins: [
|
||||
googleAnalytics({
|
||||
measurementIds: [VARS.GOOGLE_ANALYTICS ?? 'G-R4YJ9JN8L4']
|
||||
measurementIds: [VARS.GOOGLE_ANALYTICS || 'G-R4YJ9JN8L4']
|
||||
})
|
||||
]
|
||||
});
|
||||
@@ -88,7 +88,7 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo
|
||||
});
|
||||
}
|
||||
|
||||
function isTrackingAllowed() {
|
||||
export function isTrackingAllowed() {
|
||||
if (ENV.TEST) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
class:icon-exclamation={type === 'warning'}
|
||||
class:icon-exclamation-circle={type === 'error'}
|
||||
aria-hidden="true" />
|
||||
<div class="alert-content">
|
||||
<div class="alert-content" data-private>
|
||||
{#if $$slots.title}
|
||||
<h6 class="alert-title">
|
||||
<slot name="title" />
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import Avatar from './avatar.svelte';
|
||||
|
||||
export let name: string;
|
||||
export let size: number;
|
||||
|
||||
$: src = sdkForConsole.avatars.getInitials(name, size * 2, size * 2).toString();
|
||||
$: src = sdk.forConsole.avatars.getInitials(name, size * 2, size * 2).toString();
|
||||
</script>
|
||||
|
||||
<Avatar {name} {size} {src} />
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
|
||||
type BaseProps = {
|
||||
isTile?: boolean;
|
||||
isDashed?: boolean;
|
||||
danger?: boolean;
|
||||
};
|
||||
|
||||
type ButtonProps = {
|
||||
isButton: true;
|
||||
href?: never;
|
||||
};
|
||||
|
||||
type AnchorProps = {
|
||||
href: string;
|
||||
isButton?: never;
|
||||
};
|
||||
|
||||
type $$Props = BaseProps & (ButtonProps | AnchorProps | BaseProps);
|
||||
|
||||
export let isTile = false;
|
||||
export let isDashed = false;
|
||||
export let isButton = false;
|
||||
export let danger = false;
|
||||
export let href: string = null;
|
||||
|
||||
//allows to add the href attribute without throwing an error
|
||||
let attributes = { href } as Record<string, string>;
|
||||
|
||||
function getElement(): string {
|
||||
function getElement() {
|
||||
switch (true) {
|
||||
case !!href:
|
||||
return 'a';
|
||||
@@ -28,8 +43,9 @@
|
||||
class:common-section={!isTile}
|
||||
class:is-border-dashed={isDashed}
|
||||
class:is-danger={danger}
|
||||
class:is-allowed-focus={href}
|
||||
on:click
|
||||
on:keyup={clickOnEnter}
|
||||
{...attributes}>
|
||||
{href}>
|
||||
<slot />
|
||||
</svelte:element>
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
|
||||
<ul
|
||||
class="grid-box common-section u-margin-block-start-32"
|
||||
style={`--grid-gap:1.5rem; --grid-item-size:${total > 3 ? '22rem' : '25rem'};`}>
|
||||
style={`--grid-gap:1.5rem; --grid-item-size:${total > 3 ? '22rem' : '25rem'};`}
|
||||
data-private>
|
||||
<slot />
|
||||
|
||||
{#if total > 3 ? total < limit + offset : total % 2 !== 0}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
</script>
|
||||
|
||||
<span
|
||||
style:cursor="pointer"
|
||||
on:click|preventDefault={handleClick}
|
||||
on:keyup={clickOnEnter}
|
||||
on:mouseenter={() => setTimeout(() => (content = 'Click to copy'))}
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
export let href: string;
|
||||
export let icon: string = null;
|
||||
export let disabled = false;
|
||||
|
||||
//allows to add the disabled attribute to <a> tag without throwing an error
|
||||
let attributes = { disabled } as Record<string, boolean>;
|
||||
</script>
|
||||
|
||||
<li class="drop-list-item" on:click on:keyup={clickOnEnter}>
|
||||
<a {href} class="drop-button" {...attributes}>
|
||||
<a {href} class="drop-button" class:is-disabled={disabled}>
|
||||
<span class="text"><slot /></span>
|
||||
{#if icon}
|
||||
<span class={`icon-${icon}`} aria-hidden="true" />
|
||||
|
||||
@@ -245,7 +245,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show on:submit={create} size="big">
|
||||
<Modal bind:show onSubmit={create} size="big">
|
||||
<svelte:fragment slot="header">Create Event</svelte:fragment>
|
||||
<slot />
|
||||
<div>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
Your feedback is important to us. Please be honest and tell us what you think.
|
||||
</div>
|
||||
|
||||
<Form on:submit={handleSubmit}>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormList>
|
||||
<InputText
|
||||
label="name"
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
think.
|
||||
</div>
|
||||
|
||||
<Form on:submit={handleSubmit}>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Evaluation bind:value>
|
||||
How likely are you to recommend Appwrite to a friend or colleague?
|
||||
</Evaluation>
|
||||
|
||||
@@ -46,3 +46,5 @@ export { default as EventModal } from './eventModal.svelte';
|
||||
export { default as Status } from './status.svelte';
|
||||
export { default as Heading } from './heading.svelte';
|
||||
export { default as Output } from './output.svelte';
|
||||
export { default as Limit } from './limit.svelte';
|
||||
export { default as ViewSelector } from './viewSelector.svelte';
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page as pageStore } from '$app/stores';
|
||||
import { InputSelect } from '$lib/elements/forms';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
|
||||
export let sum: number;
|
||||
export let limit: number;
|
||||
export let name: string;
|
||||
|
||||
const options = [
|
||||
{ label: '6', value: 6 },
|
||||
{ label: '12', value: 12 },
|
||||
{ label: '24', value: 24 },
|
||||
{ label: '48', value: 48 },
|
||||
{ label: '96', value: 96 }
|
||||
];
|
||||
|
||||
async function limitChange() {
|
||||
const url = new URL($pageStore.url);
|
||||
const previousLimit = Number(url.searchParams.get('limit'));
|
||||
url.searchParams.set('limit', limit.toString());
|
||||
preferences.setLimit(limit);
|
||||
|
||||
if (url.searchParams.has('page')) {
|
||||
const page = Number(url.searchParams.get('page'));
|
||||
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
}
|
||||
|
||||
await goto(url.toString());
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="u-flex u-gap-12 u-cross-center">
|
||||
<InputSelect
|
||||
id="rows"
|
||||
label="Rows per page"
|
||||
showLabel={false}
|
||||
{options}
|
||||
bind:value={limit}
|
||||
on:change={limitChange} />
|
||||
<p class="text">{name} per page. Total results: {sum}</p>
|
||||
</div>
|
||||
@@ -2,12 +2,16 @@
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||
import { Alert } from '$lib/components';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { Form } from '$lib/elements/forms';
|
||||
|
||||
export let show = false;
|
||||
export let size: 'small' | 'big' = null;
|
||||
export let warning = false;
|
||||
export let error: string = null;
|
||||
export let closable = true;
|
||||
export let onSubmit: () => Promise<void> | void = function () {
|
||||
return;
|
||||
};
|
||||
|
||||
let dialog: HTMLDialogElement;
|
||||
let alert: HTMLElement;
|
||||
@@ -77,11 +81,10 @@
|
||||
class:is-big={size === 'big'}
|
||||
bind:this={dialog}>
|
||||
{#if show}
|
||||
<!-- svelte-ignore a11y-no-redundant-roles -->
|
||||
<form class="modal-form" role="form" on:submit|preventDefault>
|
||||
<Form isModal {onSubmit}>
|
||||
<header class="modal-header">
|
||||
{#if warning}
|
||||
<div class="avatar is-color-orange is-medium">
|
||||
<div class="avatar is-warning is-medium">
|
||||
<span class="icon-exclamation" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
@@ -127,6 +130,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
</Form>
|
||||
{/if}
|
||||
</dialog>
|
||||
|
||||
@@ -2,16 +2,21 @@
|
||||
import Copy from './copy.svelte';
|
||||
|
||||
export let value: string;
|
||||
export let hideCopyIcon = false;
|
||||
</script>
|
||||
|
||||
<Copy {value}>
|
||||
<div class="interactive-text-output is-textarea">
|
||||
<div
|
||||
class="interactive-text-output is-textarea"
|
||||
style:min-inline-size="0"
|
||||
style:display="inline-block">
|
||||
<span class="text u-line-height-1-5 u-break-all"><slot /></span>
|
||||
|
||||
<div class="u-flex u-cross-child-start u-gap-8">
|
||||
<button class="interactive-text-output-button" aria-label="copy text">
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{#if !hideCopyIcon}
|
||||
<div class="u-flex u-cross-child-start u-gap-8">
|
||||
<button class="interactive-text-output-button" aria-label="copy text">
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Copy>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { page as pageStore } from '$app/stores';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
|
||||
export let sum: number;
|
||||
export let limit: number;
|
||||
export let offset: number;
|
||||
export let path: string;
|
||||
export let hidePages = false;
|
||||
|
||||
$: totalPages = Math.ceil(sum / limit);
|
||||
@@ -27,68 +27,87 @@
|
||||
...(end < total - 1 ? ['...', total] : end < total ? [total] : [])
|
||||
];
|
||||
}
|
||||
|
||||
function getLink(page: number): string {
|
||||
const url = new URL($pageStore.url);
|
||||
if (page === 1) {
|
||||
url.searchParams.delete('page');
|
||||
} else {
|
||||
url.searchParams.set('page', page.toString());
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if totalPages > 1}
|
||||
{@const search = $page.url.search}
|
||||
<nav class="pagination">
|
||||
<a
|
||||
class:is-disabled={currentPage <= 1}
|
||||
class="button is-text"
|
||||
aria-label="prev page"
|
||||
href={`${path}/${currentPage - 1}${search}`}>
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
<span class="text">Prev</span>
|
||||
</a>
|
||||
{#if !hidePages}
|
||||
<ol class="pagination-list is-only-desktop">
|
||||
{#each pages as page}
|
||||
{#if typeof page === 'number'}
|
||||
<li class="pagination-item">
|
||||
<a
|
||||
href={`${path}/${page}${search}`}
|
||||
class="button"
|
||||
class:is-disabled={currentPage === page}
|
||||
class:is-text={currentPage !== page}
|
||||
aria-label="page">
|
||||
<span class="text">{page}</span>
|
||||
</a>
|
||||
</li>
|
||||
{:else}
|
||||
<li class="li is-text">
|
||||
<span class="icon">...</span>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ol>
|
||||
{/if}
|
||||
<a
|
||||
class:is-disabled={currentPage === totalPages}
|
||||
class="button is-text"
|
||||
href={`${path}/${currentPage + 1}${search}`}
|
||||
aria-label="next page">
|
||||
<span class="text">Next</span>
|
||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
||||
</a>
|
||||
</nav>
|
||||
{#key $pageStore.url}
|
||||
<nav class="pagination">
|
||||
{#if currentPage <= 1}
|
||||
<Button disabled text ariaLabel="prev page">
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
<span class="text">Prev</span>
|
||||
</Button>
|
||||
{:else}
|
||||
<Button text ariaLabel="prev page" href={getLink(currentPage - 1)}>
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
<span class="text">Prev</span>
|
||||
</Button>
|
||||
{/if}
|
||||
{#if !hidePages}
|
||||
<ol class="pagination-list is-only-desktop">
|
||||
{#each pages as page}
|
||||
{#if typeof page === 'number'}
|
||||
<li class="pagination-item">
|
||||
{#if currentPage === page}
|
||||
<Button ariaLabel="page" disabled>
|
||||
<span class="text">{page}</span>
|
||||
</Button>
|
||||
{:else}
|
||||
<Button text ariaLabel="page" href={getLink(page)}>
|
||||
<span class="text">{page}</span>
|
||||
</Button>
|
||||
{/if}
|
||||
</li>
|
||||
{:else}
|
||||
<li class="li is-text">
|
||||
<span class="icon">...</span>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ol>
|
||||
{/if}
|
||||
{#if currentPage >= totalPages}
|
||||
<Button disabled text ariaLabel="next page">
|
||||
<span class="text">Next</span>
|
||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
||||
</Button>
|
||||
{:else}
|
||||
<Button text ariaLabel="next page" href={getLink(currentPage + 1)}>
|
||||
<span class="text">Next</span>
|
||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
||||
</Button>
|
||||
{/if}
|
||||
</nav>
|
||||
{/key}
|
||||
{:else}
|
||||
<nav class="pagination">
|
||||
<button type="button" class="button is-text is-disabled" aria-label="prev page">
|
||||
<Button text disabled ariaLabel="prev page">
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
<span class="text">Prev</span>
|
||||
</button>
|
||||
</Button>
|
||||
{#if !hidePages}
|
||||
<ol class="pagination-list is-only-desktop">
|
||||
<li class="pagination-item">
|
||||
<button type="button" class="button is-disabled" aria-label="page">
|
||||
<Button disabled ariaLabel="page">
|
||||
<span class="text">1</span>
|
||||
</button>
|
||||
</Button>
|
||||
</li>
|
||||
</ol>
|
||||
{/if}
|
||||
<button type="button" class="button is-text is-disabled" aria-label="next page">
|
||||
<Button text disabled ariaLabel="next page">
|
||||
<span class="text">Next</span>
|
||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
</nav>
|
||||
{/if}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
$: disabled = !value || $groups.has(value);
|
||||
</script>
|
||||
|
||||
<Modal bind:show on:close={reset} on:submit={create}>
|
||||
<Modal bind:show on:close={reset} onSubmit={create}>
|
||||
<svelte:fragment slot="header">Custom permission</svelte:fragment>
|
||||
<p class="text">
|
||||
Custom permissions allow you to grant access to specific users or teams using their ID and
|
||||
@@ -44,6 +44,6 @@
|
||||
</FormList>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit {disabled}>Create</Button>
|
||||
<Button submit {disabled}>Add</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<Row {role} />
|
||||
</TableCell>
|
||||
<TableCellText title="Remove">
|
||||
<div class="u-flex">
|
||||
<div class="u-flex u-main-end">
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
type="button"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { tick } from 'svelte';
|
||||
import { AvatarInitials } from '../';
|
||||
@@ -18,11 +18,11 @@
|
||||
const role = permission.split(':')[0];
|
||||
const id = permission.split(':')[1].split('/')[0];
|
||||
if (role === 'user') {
|
||||
const user = await sdkForProject.users.get(id);
|
||||
const user = await sdk.forProject.users.get(id);
|
||||
return user;
|
||||
}
|
||||
if (role === 'team') {
|
||||
const team = await sdkForProject.teams.get(id);
|
||||
const team = await sdk.forProject.teams.get(id);
|
||||
return team;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Button, InputSearch } from '$lib/elements/forms';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query, type Models } from '@aw-labs/appwrite-console';
|
||||
import { AvatarInitials, EmptySearch, Modal, PaginationInline } from '..';
|
||||
import type { Writable } from 'svelte/store';
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
async function request() {
|
||||
if (!show) return;
|
||||
results = await sdkForProject.teams.list([Query.limit(5), Query.offset(offset)], search);
|
||||
results = await sdk.forProject.teams.list([Query.limit(5), Query.offset(offset)], search);
|
||||
}
|
||||
|
||||
function onSelection(event: Event, role: string) {
|
||||
@@ -58,7 +58,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show on:submit={create} on:close={reset} size="big">
|
||||
<Modal bind:show onSubmit={create} on:close={reset} size="big">
|
||||
<svelte:fragment slot="header">Select teams</svelte:fragment>
|
||||
<p class="text">
|
||||
Grant access to any member of a specific team. To grant access to team members with specific
|
||||
@@ -144,6 +144,6 @@
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit disabled={!hasSelection}>Create</Button>
|
||||
<Button submit disabled={!hasSelection}>Add</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Button, InputSearch } from '$lib/elements/forms';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { AvatarInitials, EmptySearch, Modal, PaginationInline } from '..';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query, type Models } from '@aw-labs/appwrite-console';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { Permission } from './permissions.svelte';
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
async function request() {
|
||||
if (!show) return;
|
||||
results = await sdkForProject.users.list([Query.limit(5), Query.offset(offset)], search);
|
||||
results = await sdk.forProject.users.list([Query.limit(5), Query.offset(offset)], search);
|
||||
}
|
||||
|
||||
function onSelection(event: Event, role: string) {
|
||||
@@ -58,7 +58,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show on:submit={create} on:close={reset} size="big">
|
||||
<Modal bind:show onSubmit={create} on:close={reset} size="big">
|
||||
<svelte:fragment slot="header">Select users</svelte:fragment>
|
||||
<p class="text">Grant access to any authenticated or anonymous user.</p>
|
||||
<InputSearch
|
||||
@@ -163,6 +163,6 @@
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit disabled={!hasSelection}>Create</Button>
|
||||
<Button submit disabled={!hasSelection}>Add</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -29,23 +29,28 @@
|
||||
}
|
||||
});
|
||||
|
||||
$: valueChange(search);
|
||||
$: valueChange(search ?? '');
|
||||
|
||||
function valueChange(value: string) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(async () => {
|
||||
timer = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (url.search === value) {
|
||||
const previous = url.searchParams.get('search') ?? '';
|
||||
|
||||
if (previous === value) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Reset to first page if search changes.
|
||||
*/
|
||||
|
||||
if ($page.data.page > 1) {
|
||||
url.pathname = url.pathname.replace(new RegExp(`/${$page.data.page}*$`), '/');
|
||||
url.searchParams.delete('page');
|
||||
}
|
||||
|
||||
if (value === '') {
|
||||
url.searchParams.delete('search');
|
||||
} else {
|
||||
url.searchParams.set('search', value);
|
||||
}
|
||||
|
||||
url.search = value;
|
||||
trackEvent('search');
|
||||
goto(url, { keepFocus: true });
|
||||
}, debounce);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
export let copyEvent: string = null;
|
||||
</script>
|
||||
|
||||
<div class="interactive-text-output" class:is-textarea={show}>
|
||||
<div class="interactive-text-output" class:is-textarea={show} data-private>
|
||||
{#if show}
|
||||
<span class="text u-line-height-1-5 u-break-all">{value}</span>
|
||||
{:else}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<ul class="grid-box common-section" style="--grid-item-size:25rem;">
|
||||
<ul class="grid-box common-section" style="--grid-item-size:25rem;" data-private>
|
||||
<slot />
|
||||
</ul>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { uploader } from '$lib/stores/uploader';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Avatar } from '$lib/components';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
@@ -9,7 +9,7 @@
|
||||
let hovering = false;
|
||||
|
||||
const getPreview = (fileId: string, bucketId: string) =>
|
||||
sdkForProject.storage.getFilePreview(bucketId, fileId, 32, 32).toString() + '&mode=admin';
|
||||
sdk.forProject.storage.getFilePreview(bucketId, fileId, 32, 32).toString() + '&mode=admin';
|
||||
</script>
|
||||
|
||||
{#if $uploader?.isOpen}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<script context="module" lang="ts">
|
||||
export type Column = {
|
||||
id: string;
|
||||
title: string;
|
||||
show: boolean;
|
||||
width?: number;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Button, InputChoice } from '$lib/elements/forms';
|
||||
import { DropList } from '.';
|
||||
import { page } from '$app/stores';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { onMount } from 'svelte';
|
||||
import { View } from '$lib/helpers/load';
|
||||
|
||||
export let columns: Writable<Column[]>;
|
||||
export let view: View;
|
||||
export let isCustomCollection = false;
|
||||
export let hideView = false;
|
||||
export let hideColumns = false;
|
||||
|
||||
let showSelectColumns = false;
|
||||
|
||||
onMount(async () => {
|
||||
if (isCustomCollection) {
|
||||
const prefs = preferences.getCustomCollectionColumns($page.params.collection);
|
||||
columns.set(
|
||||
$columns.map((column) => {
|
||||
column.show = prefs?.includes(column.id) ?? true;
|
||||
return column;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const prefs = preferences.get($page.route);
|
||||
|
||||
columns.set(
|
||||
$columns.map((column) => {
|
||||
column.show = prefs.columns?.includes(column.id) ?? true;
|
||||
return column;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return columns.subscribe((ctx) => {
|
||||
const columns = ctx.filter((n) => n.show).map((n) => n.id);
|
||||
|
||||
if (isCustomCollection) {
|
||||
preferences.setCustomCollectionColumns(columns);
|
||||
} else {
|
||||
preferences.setColumns(columns);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function getViewLink(view: View): string {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('view', view);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function updateViewPreferences(view: View) {
|
||||
preferences.setView(view);
|
||||
}
|
||||
|
||||
$: selectedColumnsNumber = $columns.reduce((acc, column) => {
|
||||
if (column.show) {
|
||||
acc++;
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
</script>
|
||||
|
||||
{#if !hideColumns && view === View.Table}
|
||||
{#if $columns?.length}
|
||||
<DropList bind:show={showSelectColumns} scrollable={true}>
|
||||
<Button secondary on:click={() => (showSelectColumns = !showSelectColumns)}>
|
||||
<span
|
||||
class="icon-view-boards u-opacity-50"
|
||||
aria-hidden="true"
|
||||
aria-label="columns" />
|
||||
<span class="text">Columns</span>
|
||||
<span class="inline-tag">{selectedColumnsNumber}</span>
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
{#each $columns as column}
|
||||
<InputChoice
|
||||
id={column.id}
|
||||
label={column.title}
|
||||
bind:value={column.show}
|
||||
disabled={selectedColumnsNumber <= 1 && column.show} />
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if !hideView}
|
||||
<div class="toggle-button">
|
||||
<ul class="toggle-button-list">
|
||||
{#key $page.url}
|
||||
<li class="toggle-button-item">
|
||||
<a
|
||||
href={getViewLink(View.Table)}
|
||||
on:click={() => updateViewPreferences(View.Table)}
|
||||
class="toggle-button-element"
|
||||
aria-label="List View"
|
||||
type="button"
|
||||
class:is-selected={view === View.Table}>
|
||||
<span class="icon-view-list" aria-hidden="true" />
|
||||
</a>
|
||||
</li>
|
||||
<li class="toggle-button-item">
|
||||
<a
|
||||
href={getViewLink(View.Grid)}
|
||||
on:click={() => updateViewPreferences(View.Grid)}
|
||||
class="toggle-button-element"
|
||||
aria-label="Grid View"
|
||||
type="button"
|
||||
class:is-selected={view === View.Grid}>
|
||||
<span class="icon-view-grid" aria-hidden="true" />
|
||||
</a>
|
||||
</li>
|
||||
{/key}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,5 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { getContext, hasContext } from 'svelte';
|
||||
import { readable } from 'svelte/store';
|
||||
import type { FormContext } from './form.svelte';
|
||||
|
||||
export let submit = false;
|
||||
export let secondary = false;
|
||||
@@ -14,8 +17,11 @@
|
||||
export let noMargin = false;
|
||||
export let event: string = null;
|
||||
|
||||
//allows to add the disabled attribute to <a> tag without throwing an error
|
||||
let attributes = { disabled } as Record<string, boolean>;
|
||||
const isSubmitting = hasContext('form')
|
||||
? getContext<FormContext>('form').isSubmitting
|
||||
: readable(false);
|
||||
|
||||
$: internalDisabled = (submit && $isSubmitting) || disabled;
|
||||
|
||||
function track() {
|
||||
if (!event) {
|
||||
@@ -31,11 +37,11 @@
|
||||
{#if href}
|
||||
<a
|
||||
on:click={track}
|
||||
{...attributes}
|
||||
{href}
|
||||
target={external ? '_blank' : ''}
|
||||
rel={external ? 'noopener noreferrer' : ''}
|
||||
class="button"
|
||||
class:is-disabled={disabled}
|
||||
class:is-only-icon={round}
|
||||
class:is-secondary={secondary}
|
||||
class:is-text={text}
|
||||
@@ -49,7 +55,7 @@
|
||||
<button
|
||||
on:click
|
||||
on:click={track}
|
||||
{disabled}
|
||||
disabled={internalDisabled}
|
||||
class="button"
|
||||
class:is-only-icon={round}
|
||||
class:is-secondary={secondary}
|
||||
|
||||
@@ -1,9 +1,35 @@
|
||||
<script lang="ts">
|
||||
export let noMargin = false;
|
||||
export let noStyle = false;
|
||||
<script context="module" lang="ts">
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export type FormContext = {
|
||||
isSubmitting: Writable<boolean>;
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-redundant-roles -->
|
||||
<form role="form" class:form={!noStyle} class:common-section={!noMargin} on:submit|preventDefault>
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export let noMargin = false;
|
||||
export let noStyle = false;
|
||||
export let isModal = false;
|
||||
export let onSubmit: () => Promise<void> | void;
|
||||
|
||||
const { isSubmitting } = setContext<FormContext>('form', {
|
||||
isSubmitting: writable(false)
|
||||
});
|
||||
|
||||
async function submit() {
|
||||
isSubmitting.set(true);
|
||||
await onSubmit();
|
||||
isSubmitting.set(false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<form
|
||||
class:form={!noStyle}
|
||||
class:common-section={!noMargin}
|
||||
class:modal-form={isModal}
|
||||
on:submit|preventDefault={submit}>
|
||||
<slot />
|
||||
</form>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
</div>
|
||||
<div class="u-min-width-0 u-text-center">
|
||||
<h5 class="upload-file-box-title heading-level-7 u-inline">
|
||||
<span class="is-only-desktop">Drag and drop files here to upload</span>
|
||||
<span class="is-only-desktop">Drag and drop a file here to upload</span>
|
||||
<span class="is-not-desktop">Upload a File</span>
|
||||
</h5>
|
||||
{#if allowedFileExtensions?.length}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem } from '.';
|
||||
import TextCounter from './textCounter.svelte';
|
||||
|
||||
export let value = '';
|
||||
|
||||
@@ -42,11 +43,7 @@
|
||||
bind:value
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
<span class="text-counter">
|
||||
<span class="text-counter-count">{value?.length ?? 0}</span>
|
||||
<span class="text-counter-separator" />
|
||||
<span class="text-counter-max">36</span>
|
||||
</span>
|
||||
<TextCounter count={value?.length ?? 0} max={36} />
|
||||
</div>
|
||||
</FormItem>
|
||||
<div
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
{disabled}
|
||||
bind:this={element}
|
||||
bind:value
|
||||
on:invalid={handleInvalid}>
|
||||
on:invalid={handleInvalid}
|
||||
on:change>
|
||||
{#if placeholder}
|
||||
<option value={null} disabled selected hidden>{placeholder}</option>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
import TextCounter from './textCounter.svelte';
|
||||
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
@@ -58,14 +59,11 @@
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value
|
||||
class:u-padding-inline-end-56={typeof maxlength === 'number'}
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
{#if maxlength}
|
||||
<span class="text-counter">
|
||||
<span class="text-counter-count">{value?.length ?? 0}</span>
|
||||
<span class="text-counter-separator" />
|
||||
<span class="text-counter-max">{maxlength}</span>
|
||||
</span>
|
||||
<TextCounter max={maxlength} count={value?.length ?? 0} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if error}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
import TextCounter from './textCounter.svelte';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -12,6 +13,8 @@
|
||||
export let readonly = false;
|
||||
export let autofocus = false;
|
||||
export let maxlength: number = null;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let tooltip: string = null;
|
||||
|
||||
let element: HTMLTextAreaElement;
|
||||
let error: string;
|
||||
@@ -37,7 +40,7 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<Label {required} hide={!showLabel} for={id}>
|
||||
<Label {required} {tooltip} {optionalText} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
@@ -53,6 +56,9 @@
|
||||
bind:value
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
{#if maxlength}
|
||||
<TextCounter max={maxlength} count={value?.length ?? 0} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
export let count: number;
|
||||
export let max: number;
|
||||
</script>
|
||||
|
||||
<span class="text-counter" class:active={count > 0}>
|
||||
<span class="text-counter-count">{count}</span>
|
||||
<span class="text-counter-separator" />
|
||||
<span class="text-counter-max">{max}</span>
|
||||
</span>
|
||||
|
||||
<style lang="scss">
|
||||
.text-counter {
|
||||
--p-text-counter-color: var(--color-neutral-50);
|
||||
|
||||
&.active {
|
||||
--p-text-counter-color: var(--color-neutral-70);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-dark) .text-counter {
|
||||
--p-text-counter-color: var(--color-neutral-70);
|
||||
|
||||
&.active {
|
||||
--p-text-counter-color: var(--color-neutral-50);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -11,9 +11,6 @@
|
||||
export let href: string = null;
|
||||
export let trim = false;
|
||||
|
||||
//allows to add the disabled attribute to <a> tag without throwing an error
|
||||
let attributes = { disabled } as Record<string, boolean>;
|
||||
|
||||
let element: HTMLButtonElement;
|
||||
|
||||
const isOverflowing = (elem: HTMLButtonElement, iterator = 1) => {
|
||||
@@ -44,7 +41,6 @@
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
{...attributes}
|
||||
{href}
|
||||
target={external ? '_blank' : '_self'}
|
||||
rel={external ? 'noopener noreferrer' : ''}
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
export let title: string;
|
||||
</script>
|
||||
|
||||
<div class="table-col" data-title={title} role="cell">
|
||||
<div class="table-col" data-title={title} role="cell" data-private>
|
||||
<a role="button" tabindex="0" class="link" {href}><slot /></a>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
class:is-only-desktop={onlyDesktop}
|
||||
class:u-overflow-visible={showOverflow}
|
||||
data-title={title}
|
||||
role="cell">
|
||||
role="cell"
|
||||
data-private>
|
||||
<Trim><slot /></Trim>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
class="table is-selected-columns-mobile"
|
||||
class:u-margin-block-start-32={!noMargin}
|
||||
class:is-remove-outer-styles={noStyles}
|
||||
role="table">
|
||||
role="table"
|
||||
data-private>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- TODO: is 100% height needed? -->
|
||||
<div class="table-with-scroll">
|
||||
<div class="table-with-scroll" data-private>
|
||||
<div class="table-wrapper">
|
||||
<div class="table is-remove-outer-styles">
|
||||
<ul class="table-thead">
|
||||
|
||||
@@ -1,10 +1,30 @@
|
||||
<script lang="ts">
|
||||
import type { Action } from 'svelte/action';
|
||||
|
||||
export let isSticky = false;
|
||||
|
||||
const hasOverflow: Action<HTMLDivElement, (value: boolean) => void> = (node, callback) => {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
callback(entry.contentRect.width < entry.target.scrollWidth);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(node);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
observer.disconnect();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let isOverflowing = false;
|
||||
</script>
|
||||
|
||||
<div class="table-with-scroll u-margin-block-start-32">
|
||||
<div class="table-wrapper">
|
||||
<table class="table" class:is-sticky-scroll={isSticky}>
|
||||
<div class="table-with-scroll u-margin-block-start-32" data-private>
|
||||
<div class="table-wrapper" use:hasOverflow={(v) => (isOverflowing = v)}>
|
||||
<table class="table" class:is-sticky-scroll={isSticky && isOverflowing}>
|
||||
<slot />
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import type { Page } from '@sveltejs/kit';
|
||||
|
||||
export function pageToOffset(page: number, limit: number): number {
|
||||
return page ? page * limit - limit : 0;
|
||||
}
|
||||
|
||||
export function getPage(url: URL): number {
|
||||
return Number(url.searchParams.get('page'));
|
||||
}
|
||||
|
||||
export function getLimit(url: URL, route: Page['route'], fallback: number): number {
|
||||
return Number(url.searchParams.get('limit') ?? preferences.get(route).limit ?? fallback);
|
||||
}
|
||||
|
||||
export enum View {
|
||||
Table = 'table',
|
||||
Grid = 'grid'
|
||||
}
|
||||
|
||||
export function getView(url: URL, route: Page['route'], fallback: View): View {
|
||||
return (url.searchParams.get('view') ?? preferences.get(route).view) === View.Grid
|
||||
? View.Grid
|
||||
: View.Table ?? fallback;
|
||||
}
|
||||
|
||||
export function getColumns(route: Page['route'], fallback: string[]): string[] {
|
||||
return preferences.get(route).columns ?? fallback;
|
||||
}
|
||||
|
||||
export function getSearch(url: URL): string | undefined {
|
||||
return url.searchParams.get('search') ?? undefined;
|
||||
}
|
||||
|
||||
type TabElement = { href: string; title: string; hasChildren?: boolean };
|
||||
|
||||
export function isTabSelected(
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function isMac(): boolean {
|
||||
return window.navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export function getProjectId() {
|
||||
const pathname = window.location.pathname;
|
||||
const projectMatch = pathname.match(/\/project-(.*?)\//);
|
||||
return projectMatch?.[1] || null;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export async function sleep(ms: number): Promise<void> {
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import type { EasingFunction, TransitionConfig } from 'svelte/transition';
|
||||
|
||||
export interface SlideParams {
|
||||
delay?: number;
|
||||
duration?: number;
|
||||
easing?: EasingFunction;
|
||||
axis?: 'x' | 'y';
|
||||
}
|
||||
|
||||
export function slide(
|
||||
node: Element,
|
||||
{ delay = 0, duration = 400, easing = cubicOut, axis = 'y' }: SlideParams = {}
|
||||
): TransitionConfig {
|
||||
const style = getComputedStyle(node);
|
||||
const opacity = +style.opacity;
|
||||
const primary_property = axis === 'y' ? 'height' : 'width';
|
||||
const primary_property_value = parseFloat(style[primary_property]);
|
||||
const secondary_properties = axis === 'y' ? ['top', 'bottom'] : ['left', 'right'];
|
||||
const capitalized_secondary_properties = secondary_properties.map(
|
||||
(e) => `${e[0].toUpperCase()}${e.slice(1)}`
|
||||
);
|
||||
const padding_start_value = parseFloat(style[`padding${capitalized_secondary_properties[0]}`]);
|
||||
const padding_end_value = parseFloat(style[`padding${capitalized_secondary_properties[1]}`]);
|
||||
const margin_start_value = parseFloat(style[`margin${capitalized_secondary_properties[0]}`]);
|
||||
const margin_end_value = parseFloat(style[`margin${capitalized_secondary_properties[1]}`]);
|
||||
const border_width_start_value = parseFloat(
|
||||
style[`border${capitalized_secondary_properties[0]}Width`]
|
||||
);
|
||||
const border_width_end_value = parseFloat(
|
||||
style[`border${capitalized_secondary_properties[1]}Width`]
|
||||
);
|
||||
return {
|
||||
delay,
|
||||
duration,
|
||||
easing,
|
||||
css: (t) =>
|
||||
'overflow: hidden;' +
|
||||
`opacity: ${Math.min(t * 20, 1) * opacity};` +
|
||||
`${primary_property}: ${t * primary_property_value}px;` +
|
||||
`padding-${secondary_properties[0]}: ${t * padding_start_value}px;` +
|
||||
`padding-${secondary_properties[1]}: ${t * padding_end_value}px;` +
|
||||
`margin-${secondary_properties[0]}: ${t * margin_start_value}px;` +
|
||||
`margin-${secondary_properties[1]}: ${t * margin_end_value}px;` +
|
||||
`border-${secondary_properties[0]}-width: ${t * border_width_start_value}px;` +
|
||||
`border-${secondary_properties[1]}-width: ${t * border_width_end_value}px;`
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { AvatarInitials, EmptySearch, Pagination, Trim } from '$lib/components';
|
||||
import { AvatarInitials, EmptySearch, Limit, Pagination, Trim } from '$lib/components';
|
||||
import {
|
||||
TableBody,
|
||||
TableHeader,
|
||||
@@ -11,12 +11,11 @@
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { PAGE_LIMIT } from '$lib/constants';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
export let logs: Models.LogList;
|
||||
export let path: string;
|
||||
export let offset = 0;
|
||||
export let limit = 0;
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -66,8 +65,8 @@
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {logs.total}</p>
|
||||
<Pagination limit={PAGE_LIMIT} {path} {offset} sum={logs.total} />
|
||||
<Limit {limit} sum={logs.total} name="Logs" />
|
||||
<Pagination {limit} {offset} sum={logs.total} />
|
||||
</div>
|
||||
{:else}
|
||||
<EmptySearch>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<nav class="breadcrumbs is-only-desktop" aria-label="breadcrumb">
|
||||
<ol class="breadcrumbs-list">
|
||||
{#each breadcrumbs as breadcrumb}
|
||||
<li class="breadcrumbs-item">
|
||||
<li class="breadcrumbs-item" data-private>
|
||||
{#if breadcrumb.href}
|
||||
<a href={breadcrumb.href} title={breadcrumb.title} on:click={track}>
|
||||
{breadcrumb.title}
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
</script>
|
||||
|
||||
<Heading size="4" tag="h1" trimmed={false}>
|
||||
{#if href}
|
||||
<a class="button is-text is-only-icon" {href} aria-label="page back">
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
</a>
|
||||
{/if}
|
||||
<span class="text u-trim-1">
|
||||
<slot />
|
||||
</span>
|
||||
<div class="u-flex u-cross-center">
|
||||
{#if href}
|
||||
<a class="button is-text is-only-icon" {href} aria-label="page back">
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
</a>
|
||||
{/if}
|
||||
<span class="text u-trim-1" data-private>
|
||||
<slot />
|
||||
</span>
|
||||
</div>
|
||||
</Heading>
|
||||
|
||||
@@ -1,47 +1,68 @@
|
||||
<footer class="main-footer">
|
||||
<div class="container">
|
||||
<ul class="inline-links is-center">
|
||||
<script>
|
||||
import { Mode, MODE } from '$lib/system';
|
||||
|
||||
import { version } from '$routes/console/store';
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
</script>
|
||||
|
||||
<footer class="main-footer u-cross-center">
|
||||
<div class="main-footer-start">
|
||||
<ul class="inline-links is-no-padding-first-and-last u-x-small">
|
||||
<li class="inline-links-item">
|
||||
<a
|
||||
class="link"
|
||||
href="https://github.com/appwrite/appwrite"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<span class="icon-github-circled" aria-hidden="true" />
|
||||
<span class="text">GitHub</span>
|
||||
<div class="u-flex u-cross-center u-gap-8">
|
||||
{#if MODE !== Mode.CLOUD}
|
||||
<span class="icon-cloud" />
|
||||
{/if}
|
||||
{#if $version}
|
||||
<a
|
||||
class="text"
|
||||
href="https://github.com/appwrite/appwrite/releases"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
Version {$version}
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
<li class="inline-links-item">
|
||||
<a href="https://appwrite.io/policy/terms" target="_blank" rel="noreferrer">
|
||||
<span class="text">Terms</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="inline-links-item">
|
||||
<a
|
||||
class="link"
|
||||
href="https://appwrite.io/discord"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<span class="icon-discord" aria-hidden="true" />
|
||||
<span class="text">Discord</span>
|
||||
<a href="https://appwrite.io/policy/privacy" target="_blank" rel="noreferrer">
|
||||
<span class="text">Privacy</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="main-footer-end">
|
||||
<ul class="inline-links is-no-padding-first-and-last u-x-small">
|
||||
<li class="inline-links-item">
|
||||
<a
|
||||
class="link"
|
||||
href="https://github.com/appwrite/appwrite/issues/new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<span class="text">Open an Issue</span>
|
||||
</a>
|
||||
<span class="text">ⓒ {currentYear} Appwrite. All rights reserved.</span>
|
||||
</li>
|
||||
<li class="inline-links-item">
|
||||
<a
|
||||
class="link"
|
||||
href="https://appwrite.io/docs"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<span class="text">Docs</span>
|
||||
<li class="inline-links-item u-flex u-gap-8">
|
||||
<a href="https://github.com/appwrite/appwrite" target="_blank" rel="noreferrer">
|
||||
<span class="icon-github" aria-hidden="true" aria-label="Appwrite on Github" />
|
||||
</a>
|
||||
<a href="https://appwrite.io/discord" target="_blank" rel="noreferrer">
|
||||
<span
|
||||
class="icon-discord"
|
||||
aria-hidden="true"
|
||||
aria-label="Appwrite on Discord" />
|
||||
</a>
|
||||
</li>
|
||||
<li class="inline-links-item">
|
||||
<span class="text">API:0.13.2</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style lang="scss">
|
||||
.main-footer {
|
||||
margin-block-start: auto;
|
||||
}
|
||||
|
||||
[class^='icon-']:not(.icon-cloud):not(:hover) {
|
||||
color: hsl(var(--color-neutral-50));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import { slide } from 'svelte/transition';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackEvent } from '$lib/actions/analytics';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
let showDropdown = false;
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
await sdkForConsole.account.deleteSession('current');
|
||||
await sdk.forConsole.account.deleteSession('current');
|
||||
trackEvent(Submit.AccountLogout);
|
||||
await goto(`${base}/login`);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { base } from '$app/paths';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { page } from '$app/stores';
|
||||
import { calculateTime } from '$lib/helpers/timeConversion';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
@@ -17,7 +17,7 @@
|
||||
function isDeployment(data: Models.Deployment | Models.Execution): data is Models.Deployment {
|
||||
if ('buildId' in data) {
|
||||
selectedTab = 'logs';
|
||||
rawData = `${sdkForConsole.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}/deployment/${$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 = `${sdkForConsole.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}/execution/${$log.data.$id}?mode=admin&project=${$page.params.project}`;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
|
||||
{#if $log.data}
|
||||
<section class="cover-frame">
|
||||
<section class="cover-frame" data-private>
|
||||
<header class="cover-frame-header u-flex u-gap-16 u-main-space-between u-cross-center">
|
||||
<h1 class="body-text-1">Function ID: {$log.func.$id}</h1>
|
||||
<button
|
||||
|
||||
@@ -2,81 +2,171 @@
|
||||
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';
|
||||
|
||||
$: project = $page.params.project;
|
||||
$: path = `${base}/console/project-${project}`;
|
||||
$: projectPath = `${base}/console/project-${project}`;
|
||||
|
||||
$: subNavigation = $page.data.subNavigation;
|
||||
// We need to have this second variable, because we only want narrow
|
||||
// to change automatically if we change from having a second side nav to
|
||||
// not having one, not when the second side nav changes to a different value.
|
||||
$: hasSubNavigation = !!subNavigation;
|
||||
|
||||
let narrow = false;
|
||||
$: {
|
||||
narrow = hasSubNavigation;
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
// If Alt + S is pressed
|
||||
if (hasSubNavigation && event.altKey && event.keyCode === 83) {
|
||||
event.preventDefault();
|
||||
narrow = !narrow;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="side-nav">
|
||||
{#if project}
|
||||
<div class="side-nav-main">
|
||||
<section class="drop-section">
|
||||
<ul class="drop-list">
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(`${path}/overview`)}
|
||||
on:click={() => trackEvent('click_menu_overview')}
|
||||
href={path}>
|
||||
<span class="icon-chart-bar" aria-hidden="true" />
|
||||
<span class="text">Overview</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(`${path}/auth`)}
|
||||
on:click={() => trackEvent('click_menu_auth')}
|
||||
href={`${path}/auth`}>
|
||||
<span class="icon-user-group" aria-hidden="true" />
|
||||
<span class="text">Auth</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(`${path}/databases`)}
|
||||
on:click={() => trackEvent('click_menu_databases')}
|
||||
href={`${path}/databases`}>
|
||||
<span class="icon-database" aria-hidden="true" />
|
||||
<span class="text">Databases</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(`${path}/functions`)}
|
||||
on:click={() => trackEvent('click_menu_functions')}
|
||||
href={`${path}/functions`}>
|
||||
<span class="icon-lightning-bolt" aria-hidden="true" />
|
||||
<span class="text">Functions</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(`${path}/storage`)}
|
||||
on:click={() => trackEvent('click_menu_storage')}
|
||||
href={`${path}/storage`}>
|
||||
<span class="icon-folder" aria-hidden="true" />
|
||||
<span class="text">Storage</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
<svelte:window on:keydown={handleKeyDown} />
|
||||
|
||||
<div class="side-nav-bottom">
|
||||
<section class="drop-section">
|
||||
<a
|
||||
class="drop-button"
|
||||
href={`${path}/settings`}
|
||||
on:click={() => trackEvent('click_menu_settings')}
|
||||
class:is-selected={$page.url.pathname.startsWith(`${path}/settings`)}>
|
||||
<span class="icon-cog" aria-hidden="true" />
|
||||
<span class="text">Settings</span>
|
||||
</a>
|
||||
</section>
|
||||
<div class="side-nav" class:is-open-level-2={hasSubNavigation}>
|
||||
<div class="side-nav-level-1" class:is-narrow={narrow}>
|
||||
{#if project}
|
||||
<div class="side-nav-main">
|
||||
<section class="drop-section">
|
||||
<ul class="drop-list">
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(
|
||||
`${projectPath}/overview`
|
||||
)}
|
||||
on:click={() => trackEvent('click_menu_overview')}
|
||||
href={projectPath}
|
||||
use:tooltip={{
|
||||
content: 'Overview',
|
||||
placement: 'right',
|
||||
disabled: !narrow
|
||||
}}>
|
||||
<span class="icon-chart-bar" aria-hidden="true" />
|
||||
<span class="text">Overview</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(
|
||||
`${projectPath}/auth`
|
||||
)}
|
||||
on:click={() => trackEvent('click_menu_auth')}
|
||||
href={`${projectPath}/auth`}
|
||||
use:tooltip={{
|
||||
content: 'Auth',
|
||||
placement: 'right',
|
||||
disabled: !narrow
|
||||
}}>
|
||||
<span class="icon-user-group" aria-hidden="true" />
|
||||
<span class="text">Auth</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(
|
||||
`${projectPath}/databases`
|
||||
)}
|
||||
on:click={() => trackEvent('click_menu_databases')}
|
||||
href={`${projectPath}/databases`}
|
||||
use:tooltip={{
|
||||
content: 'Databases',
|
||||
placement: 'right',
|
||||
disabled: !narrow
|
||||
}}>
|
||||
<span class="icon-database" aria-hidden="true" />
|
||||
<span class="text">Databases</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(
|
||||
`${projectPath}/functions`
|
||||
)}
|
||||
on:click={() => trackEvent('click_menu_functions')}
|
||||
href={`${projectPath}/functions`}
|
||||
use:tooltip={{
|
||||
content: 'Functions',
|
||||
placement: 'right',
|
||||
disabled: !narrow
|
||||
}}>
|
||||
<span class="icon-lightning-bolt" aria-hidden="true" />
|
||||
<span class="text">Functions</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(
|
||||
`${projectPath}/storage`
|
||||
)}
|
||||
on:click={() => trackEvent('click_menu_storage')}
|
||||
href={`${projectPath}/storage`}
|
||||
use:tooltip={{
|
||||
content: 'Storage',
|
||||
placement: 'right',
|
||||
disabled: !narrow
|
||||
}}>
|
||||
<span class="icon-folder" aria-hidden="true" />
|
||||
<span class="text">Storage</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="side-nav-bottom">
|
||||
<section class="drop-section">
|
||||
<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>
|
||||
</section>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if subNavigation}
|
||||
<button
|
||||
class="side-nav-button button is-small is-secondary is-not-mobile"
|
||||
aria-label="resize menu"
|
||||
on:click={() => (narrow = !narrow)}
|
||||
use:tooltip={{
|
||||
content: isMac() ? '⌥ + S' : 'Alt + S'
|
||||
}}>
|
||||
<span
|
||||
class:icon-cheveron-right={narrow}
|
||||
class:icon-cheveron-left={!narrow}
|
||||
aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if subNavigation}
|
||||
<div class="side-nav-level-2 is-open" transition:slide={{ axis: 'x', duration: 250 }}>
|
||||
<div class="side-nav-main">
|
||||
<svelte:component this={subNavigation} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import type { Notification } from '../stores/notifications';
|
||||
|
||||
export let type: Notification['type'] = 'info';
|
||||
@@ -9,12 +10,13 @@
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<li
|
||||
<div
|
||||
class="alert-sticky"
|
||||
class:is-success={type === 'success'}
|
||||
class:is-warning={type === 'warning'}
|
||||
class:is-danger={type === 'error'}
|
||||
class:is-info={type === 'info'}>
|
||||
class:is-info={type === 'info'}
|
||||
transition:fly={{ x: 50 }}>
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
style="--button-size:1.5rem;"
|
||||
@@ -45,4 +47,4 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</li>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
<script lang="ts">
|
||||
import Notification from './notification.svelte';
|
||||
import { dismissNotification, notifications } from '../stores/notifications';
|
||||
import { flip } from 'svelte/animate';
|
||||
</script>
|
||||
|
||||
{#if $notifications}
|
||||
<section>
|
||||
<ul class="u-flex u-flex-vertical u-gap-16">
|
||||
{#each $notifications as notification (notification.id)}
|
||||
<Notification
|
||||
type={notification.type}
|
||||
title={notification.title}
|
||||
on:dismiss={() => dismissNotification(notification.id)}
|
||||
buttons={notification?.buttons}>
|
||||
{notification.message}
|
||||
</Notification>
|
||||
<li animate:flip={{ duration: 500 }}>
|
||||
<Notification
|
||||
type={notification.type}
|
||||
title={notification.title}
|
||||
on:dismiss={() => dismissNotification(notification.id)}
|
||||
buttons={notification?.buttons}>
|
||||
{notification.message}
|
||||
</Notification>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
@@ -22,8 +25,15 @@
|
||||
<style lang="scss">
|
||||
section {
|
||||
position: fixed;
|
||||
top: calc(var(--main-header-height) + 24px);
|
||||
right: 24px;
|
||||
top: calc(var(--main-header-height) + 12px);
|
||||
right: 12px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
section {
|
||||
top: calc(var(--main-header-height) + 24px);
|
||||
right: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
|
||||
<main
|
||||
class:grid-with-side={showSideNavigation}
|
||||
class:grid={!showSideNavigation}
|
||||
class:is-open={isOpen}
|
||||
class:u-hide={$wizard.show || $log.show}>
|
||||
<header class="main-header u-padding-inline-end-0">
|
||||
@@ -54,9 +53,11 @@
|
||||
</button>
|
||||
<slot name="header" />
|
||||
</header>
|
||||
<nav class="main-side">
|
||||
<slot name="side" />
|
||||
</nav>
|
||||
{#if showSideNavigation}
|
||||
<nav class="main-side">
|
||||
<slot name="side" />
|
||||
</nav>
|
||||
{/if}
|
||||
<section class="main-content">
|
||||
{#if $page.data?.header}
|
||||
<svelte:component this={$page.data.header} />
|
||||
@@ -64,19 +65,35 @@
|
||||
|
||||
<slot />
|
||||
</section>
|
||||
<slot name="footer" />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.grid-with-side {
|
||||
<style lang="scss">
|
||||
main {
|
||||
min-height: 100vh;
|
||||
|
||||
&:not(.grid-with-side) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.main-side {
|
||||
z-index: 25;
|
||||
}
|
||||
|
||||
@media (max-width: 550.99px), (min-width: 551px) and (max-width: 1198.99px) {
|
||||
.main-side {
|
||||
top: 4.5rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1199px) {
|
||||
.grid-with-side {
|
||||
grid-template-columns: auto 1fr !important;
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { base } from '$app/paths';
|
||||
import { user } from '$lib/stores/user';
|
||||
|
||||
export let imgLight = LoginLight;
|
||||
export let imgDark = LoginDark;
|
||||
|
||||
const technologies = [
|
||||
'js',
|
||||
'flutter',
|
||||
@@ -38,9 +41,9 @@
|
||||
class="container u-margin-block-start-20 is-not-mobile"
|
||||
style="--p-container-max-size: var(--container-size-large);">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
<img src={LoginDark} alt="" class="u-only-dark" />
|
||||
<img src={imgDark} alt="" class="u-only-dark" />
|
||||
{:else}
|
||||
<img src={LoginLight} alt="" class="u-only-light" />
|
||||
<img src={imgLight} alt="" class="u-only-light" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
</div>
|
||||
<Card>
|
||||
{#if count}
|
||||
<Heading tag="h6" size="6">{last(count).value}</Heading>
|
||||
<Heading tag="h6" size="6">{total(count)}</Heading>
|
||||
<p>{countMetadata.title}</p>
|
||||
<div class="u-margin-block-start-16" />
|
||||
<div class="chart-container">
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="wizard-main">
|
||||
<Form noStyle on:submit={submit}>
|
||||
<Form noStyle onSubmit={submit}>
|
||||
{#each sortedSteps as [step, { component }]}
|
||||
{#if currentStep === step}
|
||||
<svelte:component this={component} />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show on:submit={handleSubmit} warning>
|
||||
<Modal bind:show onSubmit={handleSubmit} warning>
|
||||
<svelte:fragment slot="header">Exit Process</svelte:fragment>
|
||||
<p>
|
||||
Are you sure you want to exit from <slot />? All data will be deleted. This action is
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import Form from '../elements/forms/form.svelte';
|
||||
import Button from '../elements/forms/button.svelte';
|
||||
|
||||
export let onSubmit: () => Promise<void> | void;
|
||||
</script>
|
||||
|
||||
<Form {onSubmit}>
|
||||
<Button submit>Submit</Button>
|
||||
<Button>Button</Button>
|
||||
</Form>
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { Models, Payload } from '@aw-labs/appwrite-console';
|
||||
import { Service } from './service';
|
||||
|
||||
export class Project extends Service {
|
||||
/**
|
||||
* Get usage stats for a project
|
||||
*
|
||||
*
|
||||
* @param {string} range
|
||||
* @throws {AppwriteException}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async getUsage(range?: string): Promise<Models.UsageProject> {
|
||||
const path = '/project/usage';
|
||||
const payload: Payload = {};
|
||||
|
||||
if (typeof range !== 'undefined') {
|
||||
payload['range'] = range;
|
||||
}
|
||||
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return await this.client.call(
|
||||
'get',
|
||||
uri,
|
||||
{
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
payload
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { Client, Payload } from '@aw-labs/appwrite-console';
|
||||
|
||||
export class Service {
|
||||
static CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
client: Client;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
static flatten(data: Payload, prefix = ''): Payload {
|
||||
let output: Payload = {};
|
||||
|
||||
for (const key in data) {
|
||||
const value = data[key];
|
||||
const finalKey = prefix ? `${prefix}[${key}]` : key;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
output = Object.assign(output, this.flatten(value, finalKey));
|
||||
} else {
|
||||
output[finalKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ function createFeedbackStore() {
|
||||
customFields: value ? [{ id: '40655', value }] : undefined
|
||||
})
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
if (response.status >= 400) {
|
||||
throw new Error('Failed to submit feedback');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,7 @@ export const addNotification = (notification: Omit<Notification, 'id'>) => {
|
||||
|
||||
const n = { ...defaults, ...notification };
|
||||
notifications.update((all) => {
|
||||
if (all.length < 3) {
|
||||
return [n, ...all];
|
||||
} else {
|
||||
return [n, ...all.slice(0, 2)];
|
||||
}
|
||||
return [n, ...all.slice(0, 4)];
|
||||
});
|
||||
|
||||
if (n.timeout) setTimeout(() => dismissNotification(n.id), n.timeout);
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { page } from '$app/stores';
|
||||
import type { View } from '$lib/helpers/load';
|
||||
import type { Page } from '@sveltejs/kit';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import { sdk } from './sdk';
|
||||
|
||||
type Preferences = {
|
||||
limit?: number;
|
||||
view?: View;
|
||||
columns?: string[];
|
||||
};
|
||||
|
||||
type PreferencesStore = {
|
||||
[key: string]: {
|
||||
[key: string]: Preferences;
|
||||
collections?: {
|
||||
[key: string]: Preferences['columns'];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function createPreferences() {
|
||||
const { subscribe, set, update } = writable<PreferencesStore>({});
|
||||
if (browser) {
|
||||
set(JSON.parse(globalThis.localStorage.getItem('preferences') ?? '{}'));
|
||||
}
|
||||
return {
|
||||
subscribe,
|
||||
get: (route: Page['route']): Preferences => {
|
||||
let preferences: PreferencesStore;
|
||||
subscribe((n) => (preferences = n))();
|
||||
|
||||
return (
|
||||
preferences[sdk.forProject.client.config.project]?.[route.id] ?? {
|
||||
limit: null,
|
||||
view: null,
|
||||
columns: null
|
||||
}
|
||||
);
|
||||
},
|
||||
getCustomCollectionColumns: (collectionId: string): Preferences['columns'] => {
|
||||
let preferences: PreferencesStore;
|
||||
subscribe((n) => (preferences = n))();
|
||||
|
||||
return (
|
||||
preferences[sdk.forProject.client.config.project]?.collections?.[collectionId] ??
|
||||
null
|
||||
);
|
||||
},
|
||||
setLimit: (limit: Preferences['limit']) =>
|
||||
update((n) => {
|
||||
const path = get(page).route.id;
|
||||
const project = sdk.forProject.client.config.project;
|
||||
if (!n[project]?.[path]) {
|
||||
n[project] ??= {};
|
||||
n[project][path] ??= {};
|
||||
}
|
||||
|
||||
n[project][path].limit = limit;
|
||||
|
||||
return n;
|
||||
}),
|
||||
setView: (view: Preferences['view']) =>
|
||||
update((n) => {
|
||||
const path = get(page).route.id;
|
||||
const project = sdk.forProject.client.config.project;
|
||||
if (!n[project]?.[path]) {
|
||||
n[project] ??= {};
|
||||
n[project][path] ??= {};
|
||||
}
|
||||
|
||||
n[project][path].view = view;
|
||||
|
||||
return n;
|
||||
}),
|
||||
setColumns: (columns: Preferences['columns']) =>
|
||||
update((n) => {
|
||||
const path = get(page).route.id;
|
||||
const project = sdk.forProject.client.config.project;
|
||||
if (!n[project]?.[path]) {
|
||||
n[project] ??= {};
|
||||
n[project][path] ??= {};
|
||||
}
|
||||
|
||||
n[project][path].columns = columns;
|
||||
|
||||
return n;
|
||||
}),
|
||||
setCustomCollectionColumns: (columns: Preferences['columns']) =>
|
||||
update((n) => {
|
||||
const current = get(page);
|
||||
const project = sdk.forProject.client.config.project;
|
||||
const collection = current.params.collection;
|
||||
if (!n[project]?.collections?.[collection]) {
|
||||
n[project] ??= {};
|
||||
n[project].collections ??= {};
|
||||
}
|
||||
|
||||
n[project].collections[collection] = columns;
|
||||
|
||||
return n;
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
export const preferences = createPreferences();
|
||||
|
||||
if (browser) {
|
||||
preferences.subscribe((n) => globalThis.localStorage.setItem('preferences', JSON.stringify(n)));
|
||||
}
|
||||
+25
-17
@@ -1,3 +1,5 @@
|
||||
import { getProjectId } from '$lib/helpers/project';
|
||||
import { Project } from '$lib/sdk/project';
|
||||
import { VARS } from '$lib/system';
|
||||
import {
|
||||
Account,
|
||||
@@ -14,26 +16,12 @@ import {
|
||||
} from '@aw-labs/appwrite-console';
|
||||
|
||||
const endpoint = VARS.APPWRITE_ENDPOINT ?? `${globalThis?.location?.origin}/v1`;
|
||||
|
||||
const clientConsole = new Client();
|
||||
clientConsole.setEndpoint(endpoint).setProject('console');
|
||||
|
||||
const clientProject = new Client();
|
||||
clientProject.setEndpoint(endpoint).setMode('admin');
|
||||
|
||||
const setProject = (projectId: string): Client => clientProject.setProject(projectId);
|
||||
|
||||
const sdkForConsole = {
|
||||
client: clientConsole,
|
||||
account: new Account(clientConsole),
|
||||
avatars: new Avatars(clientConsole),
|
||||
functions: new Functions(clientConsole),
|
||||
health: new Health(clientConsole),
|
||||
locale: new Locale(clientConsole),
|
||||
projects: new Projects(clientConsole),
|
||||
teams: new Teams(clientConsole),
|
||||
users: new Users(clientConsole)
|
||||
};
|
||||
|
||||
const sdkForProject = {
|
||||
client: clientProject,
|
||||
account: new Account(clientProject),
|
||||
@@ -42,10 +30,30 @@ const sdkForProject = {
|
||||
functions: new Functions(clientProject),
|
||||
health: new Health(clientProject),
|
||||
locale: new Locale(clientProject),
|
||||
projects: new Projects(clientProject),
|
||||
project: new Project(clientProject),
|
||||
storage: new Storage(clientProject),
|
||||
teams: new Teams(clientProject),
|
||||
users: new Users(clientProject)
|
||||
};
|
||||
|
||||
export { sdkForConsole, sdkForProject, setProject };
|
||||
export const sdk = {
|
||||
forConsole: {
|
||||
client: clientConsole,
|
||||
account: new Account(clientConsole),
|
||||
avatars: new Avatars(clientConsole),
|
||||
functions: new Functions(clientConsole),
|
||||
health: new Health(clientConsole),
|
||||
locale: new Locale(clientConsole),
|
||||
projects: new Projects(clientConsole),
|
||||
teams: new Teams(clientConsole),
|
||||
users: new Users(clientConsole)
|
||||
},
|
||||
get forProject() {
|
||||
const projectId = getProjectId();
|
||||
if (projectId && projectId !== clientProject.config.project) {
|
||||
clientProject.setProject(projectId);
|
||||
}
|
||||
|
||||
return sdkForProject;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable } from 'svelte/store';
|
||||
import { sdkForProject } from './sdk';
|
||||
import { sdk } from './sdk';
|
||||
|
||||
type UploaderFile = {
|
||||
$id: string;
|
||||
@@ -71,7 +71,7 @@ const createUploader = () => {
|
||||
n.files.unshift(newFile);
|
||||
return n;
|
||||
});
|
||||
const uploadedFile = await sdkForProject.storage.createFile(
|
||||
const uploadedFile = await sdk.forProject.storage.createFile(
|
||||
bucketId,
|
||||
id ?? 'unique()',
|
||||
file,
|
||||
|
||||
@@ -21,3 +21,5 @@ export const ENV = {
|
||||
};
|
||||
|
||||
export const MODE = VARS.CONSOLE_MODE === Mode.CLOUD ? Mode.CLOUD : Mode.SELF_HOSTED;
|
||||
export const isCloud = MODE === Mode.CLOUD;
|
||||
export const isSelfHosted = MODE === Mode.SELF_HOSTED;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import { Heading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Unauthenticated } from '$lib/layout';
|
||||
</script>
|
||||
|
||||
<Unauthenticated>
|
||||
<Heading size="1" tag="h3">{$page.status}</Heading>
|
||||
<Heading size="3" tag="h4">{$page.error.message}</Heading>
|
||||
<Button href="/">Back to the console</Button>
|
||||
</Unauthenticated>
|
||||
+29
-16
@@ -1,19 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { afterNavigate, goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { onCLS, onFID, onLCP, onFCP, onINP, onTTFB } from 'web-vitals';
|
||||
import { reportWebVitals } from '$lib/helpers/vitals';
|
||||
import { onMount } from 'svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { browser, dev } from '$app/environment';
|
||||
import { page } from '$app/stores';
|
||||
import { isTrackingAllowed, trackPageView } from '$lib/actions/analytics';
|
||||
import { reportWebVitals } from '$lib/helpers/vitals';
|
||||
import { Notifications, Progress } from '$lib/layout';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { Progress, Notifications } from '$lib/layout';
|
||||
import { loading } from './store';
|
||||
import Loading from './loading.svelte';
|
||||
import { trackPageView } from '$lib/actions/analytics';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { ENV, isCloud } from '$lib/system';
|
||||
import * as Sentry from '@sentry/svelte';
|
||||
import LogRocket from 'logrocket';
|
||||
import { BrowserTracing } from '@sentry/tracing';
|
||||
import { onMount } from 'svelte';
|
||||
import { onCLS, onFCP, onFID, onINP, onLCP, onTTFB } from 'web-vitals';
|
||||
import Loading from './loading.svelte';
|
||||
import { loading } from './store';
|
||||
|
||||
if (browser) {
|
||||
window.VERCEL_ANALYTICS_ID = import.meta.env.VERCEL_ANALYTICS_ID?.toString() ?? false;
|
||||
@@ -23,7 +25,7 @@
|
||||
/**
|
||||
* Reporting Web Vitals.
|
||||
*/
|
||||
if (!dev && window.VERCEL_ANALYTICS_ID) {
|
||||
if (ENV.PROD && window.VERCEL_ANALYTICS_ID) {
|
||||
onCLS(reportWebVitals);
|
||||
onFID(reportWebVitals);
|
||||
onLCP(reportWebVitals);
|
||||
@@ -32,15 +34,26 @@
|
||||
onTTFB(reportWebVitals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sentry Error Logging
|
||||
*/
|
||||
if (!dev) {
|
||||
if (ENV.PROD) {
|
||||
/**
|
||||
* Sentry Error Logging
|
||||
*/
|
||||
Sentry.init({
|
||||
dsn: 'https://c7ce178bdedd486480317b72f282fd39@o1063647.ingest.sentry.io/4504158071422976',
|
||||
integrations: [new BrowserTracing()],
|
||||
tracesSampleRate: 1.0
|
||||
});
|
||||
|
||||
/**
|
||||
* LogRocket
|
||||
*/
|
||||
if (isCloud && isTrackingAllowed()) {
|
||||
LogRocket.init('rgthvf/appwrite', {
|
||||
dom: {
|
||||
inputSanitizer: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +72,7 @@
|
||||
}
|
||||
loading.set(false);
|
||||
} else {
|
||||
if (acceptedRoutes.includes($page.url.pathname)) {
|
||||
if (acceptedRoutes.some((n) => $page.url.pathname.startsWith(n))) {
|
||||
await goto(`${base}${$page.url.pathname}${$page.url.search}`);
|
||||
} else {
|
||||
await goto(`${base}/login`, {
|
||||
|
||||
+10
-4
@@ -1,7 +1,8 @@
|
||||
import '@appwrite.io/pink';
|
||||
import '@appwrite.io/pink-icons';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import LogRocket from 'logrocket';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { LayoutLoad } from './$types';
|
||||
@@ -11,11 +12,16 @@ export const ssr = false;
|
||||
export const load: LayoutLoad = async ({ depends, url }) => {
|
||||
depends(Dependencies.ACCOUNT);
|
||||
try {
|
||||
const account = await sdkForConsole.account.get();
|
||||
const account = await sdk.forConsole.account.get();
|
||||
|
||||
LogRocket.identify(account.$id, {
|
||||
name: account.name,
|
||||
email: account.email
|
||||
});
|
||||
|
||||
return {
|
||||
account,
|
||||
organizations: sdkForConsole.teams.list()
|
||||
organizations: sdk.forConsole.teams.list()
|
||||
};
|
||||
} catch (error) {
|
||||
const acceptedRoutes = [
|
||||
@@ -28,7 +34,7 @@ export const load: LayoutLoad = async ({ depends, url }) => {
|
||||
'/auth/oauth2/failure'
|
||||
];
|
||||
|
||||
if (!acceptedRoutes.includes(url.pathname)) {
|
||||
if (!acceptedRoutes.some((n) => url.pathname.startsWith(n))) {
|
||||
throw redirect(303, '/login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<script lang="ts">
|
||||
import Shell from '$lib/layout/shell.svelte';
|
||||
import SideNavigation from '$lib/layout/navigation.svelte';
|
||||
import Header from '$lib/layout/header.svelte';
|
||||
import { newOrgModal } from '$lib/stores/organization';
|
||||
import Create from './createOrganization.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { page } from '$app/stores';
|
||||
import { INTERVAL } from '$lib/constants';
|
||||
import { Logs } from '$lib/layout';
|
||||
import Footer from '$lib/layout/footer.svelte';
|
||||
import Header from '$lib/layout/header.svelte';
|
||||
import SideNavigation from '$lib/layout/navigation.svelte';
|
||||
import Shell from '$lib/layout/shell.svelte';
|
||||
import { feedback } from '$lib/stores/app';
|
||||
import { log } from '$lib/stores/logs';
|
||||
import { newOrgModal } from '$lib/stores/organization';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { onMount } from 'svelte';
|
||||
import { loading } from '../store';
|
||||
import { feedback } from '$lib/stores/app';
|
||||
import { INTERVAL } from '$lib/constants';
|
||||
import Create from './createOrganization.svelte';
|
||||
|
||||
onMount(() => {
|
||||
loading.set(false);
|
||||
@@ -50,14 +51,10 @@
|
||||
!$page?.params.organization &&
|
||||
!$page.url.pathname.includes('/console/account') &&
|
||||
!$page.url.pathname.includes('/console/onboarding')}>
|
||||
<svelte:fragment slot="header">
|
||||
<Header />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="side">
|
||||
<SideNavigation />
|
||||
</svelte:fragment>
|
||||
<Header slot="header" />
|
||||
<SideNavigation slot="side" />
|
||||
<slot />
|
||||
<footer class="main-footer" />
|
||||
<Footer slot="footer" />
|
||||
</Shell>
|
||||
|
||||
{#if $wizard.show && $wizard.component}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
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 data = await response.json();
|
||||
|
||||
return {
|
||||
version: data?.version ?? null
|
||||
};
|
||||
};
|
||||
@@ -4,7 +4,7 @@
|
||||
import { Container } from '$lib/layout';
|
||||
import { onMount } from 'svelte';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { base } from '$app/paths';
|
||||
import Delete from './delete.svelte';
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForConsole.account.updateName(name);
|
||||
await sdk.forConsole.account.updateName(name);
|
||||
invalidate(Dependencies.ACCOUNT);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
async function updateEmail() {
|
||||
try {
|
||||
await sdkForConsole.account.updateEmail(email, emailPassword);
|
||||
await sdk.forConsole.account.updateEmail(email, emailPassword);
|
||||
invalidate(Dependencies.ACCOUNT);
|
||||
addNotification({
|
||||
message: 'Email has been updated',
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
async function updatePassword() {
|
||||
try {
|
||||
await sdkForConsole.account.updatePassword(newPassword, oldPassword);
|
||||
await sdk.forConsole.account.updatePassword(newPassword, oldPassword);
|
||||
newPassword = oldPassword = null;
|
||||
addNotification({
|
||||
message: 'Password has been updated',
|
||||
@@ -79,9 +79,9 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Form on:submit={updateName}>
|
||||
<Form onSubmit={updateName}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Name</Heading>
|
||||
<Heading tag="h6" size="7">Name</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
@@ -94,9 +94,9 @@
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
<Form on:submit={updateEmail}>
|
||||
<Form onSubmit={updateEmail}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Email</Heading>
|
||||
<Heading tag="h6" size="7">Email</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
@@ -122,9 +122,9 @@
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
<Form on:submit={updatePassword}>
|
||||
<Form onSubmit={updatePassword}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Update Password</Heading>
|
||||
<Heading tag="h6" size="7">Password</Heading>
|
||||
<p class="text">
|
||||
Forgot your password? <a class="link" href={`${base}/recover`}
|
||||
>Recover your password</a>
|
||||
|
||||
+1
-2
@@ -3,7 +3,6 @@
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
const path = '/console/account/activity';
|
||||
</script>
|
||||
|
||||
<Activity {path} logs={data.logs} offset={data.offset} />
|
||||
<Activity {...data} />
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Query } from '@aw-labs/appwrite-console';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { getLimit, getPage, pageToOffset } from '$lib/helpers/load';
|
||||
import { PAGE_LIMIT } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ url, route }) => {
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, PAGE_LIMIT);
|
||||
const offset = pageToOffset(page, limit);
|
||||
|
||||
return {
|
||||
offset,
|
||||
limit,
|
||||
logs: await sdk.forConsole.account.listLogs([Query.offset(offset), Query.limit(limit)])
|
||||
};
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Query } from '@aw-labs/appwrite-console';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { pageToOffset } from '$lib/helpers/load';
|
||||
import { PAGE_LIMIT } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ params }) => {
|
||||
const page = Number(params.page);
|
||||
const offset = pageToOffset(page, PAGE_LIMIT);
|
||||
|
||||
return {
|
||||
offset,
|
||||
logs: await sdkForConsole.account.listLogs([Query.offset(offset), Query.limit(PAGE_LIMIT)])
|
||||
};
|
||||
};
|
||||
@@ -5,13 +5,13 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
export let showDelete = false;
|
||||
|
||||
const deleteAccount = async () => {
|
||||
try {
|
||||
await sdkForConsole.account.updateStatus();
|
||||
await sdk.forConsole.account.updateStatus();
|
||||
showDelete = false;
|
||||
invalidate(Dependencies.ACCOUNT);
|
||||
addNotification({
|
||||
@@ -29,7 +29,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal bind:show={showDelete} on:submit={deleteAccount} warning>
|
||||
<Modal bind:show={showDelete} onSubmit={deleteAccount} warning>
|
||||
<svelte:fragment slot="header">Delete Account</svelte:fragment>
|
||||
<p>Are you sure you want to delete your account?</p>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { isTabSelected } from '$lib/helpers/load';
|
||||
import { Cover, CoverTitle } from '$lib/layout';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
|
||||
const path = `/console/account`;
|
||||
@@ -38,7 +38,7 @@
|
||||
];
|
||||
|
||||
async function logout() {
|
||||
await sdkForConsole.account.deleteSession('current');
|
||||
await sdk.forConsole.account.deleteSession('current');
|
||||
trackEvent(Submit.AccountLogout);
|
||||
await goto(`${base}/login`);
|
||||
}
|
||||
|
||||
+7
-11
@@ -6,19 +6,19 @@
|
||||
Pagination,
|
||||
AvatarGroup,
|
||||
CardContainer,
|
||||
Heading
|
||||
Heading,
|
||||
Limit
|
||||
} from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import CreateOrganization from '../../../createOrganization.svelte';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { CARD_LIMIT } from '$lib/constants';
|
||||
import CreateOrganization from '../../createOrganization.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
const getMemberships = async (teamId: string) => {
|
||||
const memberships = await sdkForConsole.teams.listMemberships(teamId);
|
||||
const memberships = await sdk.forConsole.teams.listMemberships(teamId);
|
||||
return memberships.memberships.map((team) => team.userName);
|
||||
};
|
||||
|
||||
@@ -67,12 +67,8 @@
|
||||
</Empty>
|
||||
{/if}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {data.organizations.total}</p>
|
||||
<Pagination
|
||||
limit={CARD_LIMIT}
|
||||
path="/console/account/organizations"
|
||||
offset={data.offset}
|
||||
sum={data.organizations.total} />
|
||||
<Limit limit={data.limit} sum={data.organizations.total} name="Organizations" />
|
||||
<Pagination limit={data.limit} offset={data.offset} sum={data.organizations.total} />
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Query } from '@aw-labs/appwrite-console';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { getLimit, getPage, pageToOffset } from '$lib/helpers/load';
|
||||
import { CARD_LIMIT } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ url, route }) => {
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, CARD_LIMIT);
|
||||
const offset = pageToOffset(page, limit);
|
||||
|
||||
return {
|
||||
offset,
|
||||
limit,
|
||||
organizations: await sdk.forConsole.teams.list([
|
||||
Query.offset(offset),
|
||||
Query.limit(limit),
|
||||
Query.orderDesc('$createdAt')
|
||||
])
|
||||
};
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Query } from '@aw-labs/appwrite-console';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { pageToOffset } from '$lib/helpers/load';
|
||||
import { CARD_LIMIT } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ params }) => {
|
||||
const page = Number(params.page);
|
||||
const offset = pageToOffset(page, CARD_LIMIT);
|
||||
|
||||
return {
|
||||
offset,
|
||||
organizations: await sdkForConsole.teams.list([
|
||||
Query.offset(offset),
|
||||
Query.limit(CARD_LIMIT),
|
||||
Query.orderDesc('$createdAt')
|
||||
])
|
||||
};
|
||||
};
|
||||
+5
-6
@@ -12,7 +12,7 @@
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
@@ -23,11 +23,11 @@
|
||||
export let data: PageData;
|
||||
|
||||
const getBrowser = (clientCode: string) => {
|
||||
return sdkForConsole.avatars.getBrowser(clientCode, 40, 40);
|
||||
return sdk.forConsole.avatars.getBrowser(clientCode, 40, 40);
|
||||
};
|
||||
|
||||
const logout = async (session: Models.Session) => {
|
||||
await sdkForConsole.account.deleteSession(session.$id);
|
||||
await sdk.forConsole.account.deleteSession(session.$id);
|
||||
trackEvent(Submit.AccountDeleteSession);
|
||||
if (session.current) {
|
||||
await goto(`${base}/login`);
|
||||
@@ -35,7 +35,7 @@
|
||||
invalidate(Dependencies.ACCOUNT_SESSIONS);
|
||||
};
|
||||
const logoutAll = async () => {
|
||||
await sdkForConsole.account.deleteSessions();
|
||||
await sdk.forConsole.account.deleteSessions();
|
||||
trackEvent(Submit.AccountDeleteAllSessions);
|
||||
await goto(`${base}/login`);
|
||||
invalidate(Dependencies.ACCOUNT_SESSIONS);
|
||||
@@ -115,7 +115,7 @@
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href="https://appwrite.io/docs/server/auth?sdk=nodejs-default#usersGetsessions">
|
||||
href="https://appwrite.io/docs/client/account#accountCreateEmailSession">
|
||||
Documentation
|
||||
</Button>
|
||||
</div>
|
||||
@@ -124,6 +124,5 @@
|
||||
{/if}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {data.sessions.total}</p>
|
||||
<!-- <Pagination limit={PAGE_LIMIT} offset={data.offset} sum={data.sessions.total} /> -->
|
||||
</div>
|
||||
</Container>
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ depends }) => {
|
||||
depends(Dependencies.ACCOUNT_SESSIONS);
|
||||
|
||||
return {
|
||||
sessions: await sdkForConsole.account.listSessions()
|
||||
sessions: await sdk.forConsole.account.listSessions()
|
||||
};
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Pill } from '$lib/elements';
|
||||
import { InputText, Button, FormList } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
const create = async () => {
|
||||
try {
|
||||
const org = await sdkForConsole.teams.create(id ?? ID.unique(), name);
|
||||
const org = await sdk.forConsole.teams.create(id ?? ID.unique(), name);
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
dispatch('created');
|
||||
await goto(`/console/organization-${org.$id}`);
|
||||
@@ -42,7 +42,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal {error} on:submit={create} size="big" bind:show>
|
||||
<Modal {error} onSubmit={create} size="big" bind:show>
|
||||
<svelte:fragment slot="header">Create New Organization</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
|
||||
@@ -9,19 +9,17 @@
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { ID } from '@aw-labs/appwrite-console';
|
||||
|
||||
let name: string;
|
||||
let id: string;
|
||||
let showCustomId = false;
|
||||
let loading = false;
|
||||
|
||||
async function createProject() {
|
||||
try {
|
||||
loading = true;
|
||||
const org = await createOrganization();
|
||||
const project = await sdkForConsole.projects.create(
|
||||
const project = await sdk.forConsole.projects.create(
|
||||
id ?? ID.unique(),
|
||||
name,
|
||||
org.$id,
|
||||
@@ -34,7 +32,6 @@
|
||||
teamId: org.$id
|
||||
});
|
||||
} catch (error) {
|
||||
loading = false;
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
@@ -44,20 +41,19 @@
|
||||
}
|
||||
|
||||
async function createOrganization() {
|
||||
return await sdkForConsole.teams.create(ID.unique(), 'Personal Projects');
|
||||
return await sdk.forConsole.teams.create(ID.unique(), 'Personal Projects');
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container overlapCover size="large">
|
||||
<Card>
|
||||
<Form on:submit={createProject}>
|
||||
<Form onSubmit={createProject}>
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Project name"
|
||||
placeholder="First Appwrite Project"
|
||||
required
|
||||
disabled={loading}
|
||||
bind:value={name} />
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
@@ -70,7 +66,7 @@
|
||||
{:else}
|
||||
<CustomId bind:show={showCustomId} name="Project" bind:id />
|
||||
{/if}
|
||||
<Button fullWidth submit disabled={loading || name === ''} event="create_project">
|
||||
<Button fullWidth submit disabled={name === ''} event="create_project">
|
||||
Create project
|
||||
</Button>
|
||||
</FormList>
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import Header from './header.svelte';
|
||||
import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, parent, depends }) => {
|
||||
await parent();
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
depends(Dependencies.ORGANIZATION);
|
||||
|
||||
try {
|
||||
return {
|
||||
header: Header,
|
||||
breadcrumbs: Breadcrumbs,
|
||||
organization: await sdkForConsole.teams.get(params.organization),
|
||||
members: await sdkForConsole.teams.listMemberships(params.organization)
|
||||
organization: await sdk.forConsole.teams.get(params.organization),
|
||||
members: await sdk.forConsole.teams.listMemberships(params.organization)
|
||||
};
|
||||
} catch (e) {
|
||||
localStorage.removeItem('organization');
|
||||
|
||||
+5
-10
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { GridItem1, Heading, Empty, CardContainer, Pagination } from '$lib/components';
|
||||
import { GridItem1, Heading, Empty, CardContainer, Pagination, Limit } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import CreateProject from '../createProject.svelte';
|
||||
import CreateOrganization from '../../createOrganization.svelte';
|
||||
import CreateProject from './createProject.svelte';
|
||||
import CreateOrganization from '../createOrganization.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { CARD_LIMIT } from '$lib/constants';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
export let data: PageData;
|
||||
@@ -96,12 +95,8 @@
|
||||
</Empty>
|
||||
{/if}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {data.projects.total}</p>
|
||||
<Pagination
|
||||
path={`/console/organization-${$page.params.organization}`}
|
||||
limit={CARD_LIMIT}
|
||||
offset={data.offset}
|
||||
sum={data.projects.total} />
|
||||
<Limit limit={data.limit} sum={data.projects.total} name="Projects" />
|
||||
<Pagination limit={data.limit} offset={data.offset} sum={data.projects.total} />
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Query } from '@aw-labs/appwrite-console';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { getLimit, getPage, pageToOffset } from '$lib/helpers/load';
|
||||
import { CARD_LIMIT } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ params, url, route }) => {
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, CARD_LIMIT);
|
||||
const offset = pageToOffset(page, limit);
|
||||
|
||||
return {
|
||||
offset,
|
||||
limit,
|
||||
projects: await sdk.forConsole.projects.list([
|
||||
Query.offset(offset),
|
||||
Query.limit(limit),
|
||||
Query.equal('teamId', params.organization),
|
||||
Query.orderDesc('$createdAt')
|
||||
])
|
||||
};
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Query } from '@aw-labs/appwrite-console';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { pageToOffset } from '$lib/helpers/load';
|
||||
import { CARD_LIMIT } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ params }) => {
|
||||
const page = Number(params.page);
|
||||
const offset = pageToOffset(page, CARD_LIMIT);
|
||||
|
||||
return {
|
||||
offset,
|
||||
projects: await sdkForConsole.projects.list([
|
||||
Query.offset(offset),
|
||||
Query.limit(CARD_LIMIT),
|
||||
Query.equal('teamId', params.organization),
|
||||
Query.orderDesc('$createdAt')
|
||||
])
|
||||
};
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Modal } from '$lib/components';
|
||||
import { InputText, InputEmail, Button, FormList } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { invalidate } from '$app/navigation';
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
const create = async () => {
|
||||
try {
|
||||
const team = await sdkForConsole.teams.createMembership(
|
||||
const team = await sdk.forConsole.teams.createMembership(
|
||||
$organization.$id,
|
||||
email,
|
||||
['owner'],
|
||||
@@ -47,7 +47,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal {error} size="big" bind:show={showCreate} on:submit={create}>
|
||||
<Modal {error} size="big" bind:show={showCreate} onSubmit={create}>
|
||||
<svelte:fragment slot="header">Invite Member</svelte:fragment>
|
||||
<FormList>
|
||||
<InputEmail
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { Pill } from '$lib/elements';
|
||||
import { InputText, Button, FormList } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { ID } from '@aw-labs/appwrite-console';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
@@ -18,12 +18,10 @@
|
||||
let name: string;
|
||||
let showCustomId = false;
|
||||
let error: string;
|
||||
let isCreating = false;
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
isCreating = true;
|
||||
const project = await sdkForConsole.projects.create(
|
||||
const project = await sdk.forConsole.projects.create(
|
||||
id ?? ID.unique(),
|
||||
name,
|
||||
teamId,
|
||||
@@ -40,23 +38,16 @@
|
||||
});
|
||||
await goto(`/console/project-${project.$id}`);
|
||||
} catch (e) {
|
||||
isCreating = false;
|
||||
error = e.message;
|
||||
trackError(e, Submit.ProjectCreate);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal {error} on:submit={create} size="big" bind:show>
|
||||
<Modal {error} onSubmit={create} size="big" bind:show>
|
||||
<svelte:fragment slot="header">Create Project</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
bind:value={name}
|
||||
required
|
||||
autofocus={true}
|
||||
disabled={isCreating} />
|
||||
<InputText id="name" label="Name" bind:value={name} required autofocus={true} />
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}>
|
||||
@@ -71,6 +62,6 @@
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button submit disabled={isCreating}>Create</Button>
|
||||
<Button submit>Create</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { user } from '$lib/stores/user';
|
||||
@@ -17,10 +17,10 @@
|
||||
|
||||
const deleteMembership = async () => {
|
||||
try {
|
||||
await sdkForConsole.teams.deleteMembership(selectedMember.teamId, selectedMember.$id);
|
||||
await sdk.forConsole.teams.deleteMembership(selectedMember.teamId, selectedMember.$id);
|
||||
|
||||
if (isUser) {
|
||||
await sdkForConsole.account.deleteSession('current');
|
||||
await sdk.forConsole.account.deleteSession('current');
|
||||
await goto(`${base}/login`);
|
||||
} else {
|
||||
dispatch('deleted');
|
||||
@@ -43,11 +43,11 @@
|
||||
$: isUser = selectedMember?.userId === $user?.$id;
|
||||
</script>
|
||||
|
||||
<Modal bind:show={showDelete} on:submit={deleteMembership} warning>
|
||||
<Modal bind:show={showDelete} onSubmit={deleteMembership} warning>
|
||||
<svelte:fragment slot="header">
|
||||
{isUser ? 'Leave Organization' : 'Delete Member'}
|
||||
</svelte:fragment>
|
||||
<p>
|
||||
<p data-private>
|
||||
{isUser
|
||||
? `Are you sure you want to leave '${selectedMember?.teamName}'?`
|
||||
: `Are you sure you want to delete ${selectedMember?.userName} from '${selectedMember?.teamName}'?`}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { organization, organizationList } from '$lib/stores/organization';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
const deleteOrg = async () => {
|
||||
try {
|
||||
await sdkForConsole.teams.delete($organization.$id);
|
||||
await sdk.forConsole.teams.delete($organization.$id);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$organization.name} has been deleted`
|
||||
@@ -37,7 +37,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal on:submit={deleteOrg} bind:show={showDelete} warning>
|
||||
<Modal onSubmit={deleteOrg} bind:show={showDelete} warning>
|
||||
<svelte:fragment slot="header">Delete Organization</svelte:fragment>
|
||||
<p>
|
||||
Are you sure you want to delete <b>{$organization.name}</b>? All projects ({$organization.total})
|
||||
|
||||
@@ -72,7 +72,9 @@
|
||||
</DropList>
|
||||
<div class="u-margin-inline-start-auto">
|
||||
<div class="u-flex u-gap-16">
|
||||
<AvatarGroup size={40} {avatars} total={$members?.total ?? 0} />
|
||||
<a href={`${path}/members`}>
|
||||
<AvatarGroup size={40} {avatars} total={$members?.total ?? 0} />
|
||||
</a>
|
||||
<Button secondary on:click={() => newMemberModal.set(true)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Invite</span>
|
||||
|
||||
+17
-25
@@ -1,38 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { AvatarInitials, Heading, Limit, Pagination } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { AvatarInitials, Heading, Pagination } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { organization, members, newMemberModal } from '$lib/stores/organization';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { page } from '$app/stores';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
|
||||
import Delete from '../../deleteMember.svelte';
|
||||
import { members, newMemberModal, organization } from '$lib/stores/organization';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import type { PageData } from './$types';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import Delete from '../deleteMember.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let selectedMember: Models.Membership;
|
||||
let showDelete = false;
|
||||
|
||||
const url = `${$page.url.origin}/console/`;
|
||||
const deleted = () => invalidate(Dependencies.ACCOUNT);
|
||||
const resend = async (member: Models.Membership) => {
|
||||
try {
|
||||
await sdkForConsole.teams.createMembership(
|
||||
await sdk.forConsole.teams.createMembership(
|
||||
$organization.$id,
|
||||
member.userEmail,
|
||||
member.roles,
|
||||
@@ -69,7 +68,7 @@
|
||||
<TableHeader>
|
||||
<TableCellHead width={140}>Name</TableCellHead>
|
||||
<TableCellHead width={120}>Email</TableCellHead>
|
||||
<TableCellHead width={90}>Role</TableCellHead>
|
||||
<TableCellHead width={90} />
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -87,17 +86,14 @@
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Email">{member.userEmail}</TableCellText>
|
||||
<TableCell title="Roles">
|
||||
<TableCell>
|
||||
{#if member.invited && !member.joined}
|
||||
<Button
|
||||
secondary
|
||||
event="invite_resend"
|
||||
on:click={() => resend(member)}>Resend</Button>
|
||||
{:else}
|
||||
{member.roles}
|
||||
{/if}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<button
|
||||
class="button is-only-icon is-text"
|
||||
@@ -115,12 +111,8 @@
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {data.organizationMembers.total}</p>
|
||||
<Pagination
|
||||
offset={data.offset}
|
||||
limit={PAGE_LIMIT}
|
||||
path={`/console/organization-${$page.params.organization}/members`}
|
||||
sum={data.members.total} />
|
||||
<Limit limit={data.limit} sum={data.members.total} name="Members" />
|
||||
<Pagination limit={data.limit} offset={data.offset} sum={data.members.total} />
|
||||
</div>
|
||||
{/if}
|
||||
</Container>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { PAGE_LIMIT } from '$lib/constants';
|
||||
import { getLimit, getPage, pageToOffset } from '$lib/helpers/load';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query } from '@aw-labs/appwrite-console';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ url, params, route }) => {
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, PAGE_LIMIT);
|
||||
const offset = pageToOffset(page, limit);
|
||||
|
||||
return {
|
||||
offset,
|
||||
limit,
|
||||
organizationMembers: await sdk.forConsole.teams.listMemberships(params.organization, [
|
||||
Query.limit(limit),
|
||||
Query.offset(offset)
|
||||
])
|
||||
};
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user