mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Project } from '$lib/sdk/project';
|
||||
import { VARS } from '$lib/system';
|
||||
import {
|
||||
Account,
|
||||
@@ -42,10 +43,10 @@ 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 { sdkForConsole, sdkForProject, setProject, endpoint };
|
||||
|
||||
@@ -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>
|
||||
@@ -59,7 +59,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`, {
|
||||
|
||||
@@ -28,7 +28,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');
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Container } from '$lib/layout';
|
||||
import { Card, SecondaryTabsItem, SecondaryTabs, Heading } from '$lib/components';
|
||||
import { last } from '$lib/layout/usage.svelte';
|
||||
import { total } from '$lib/layout/usage.svelte';
|
||||
import { BarChart } from '$lib/charts';
|
||||
import { page } from '$app/stores';
|
||||
import type { PageData } from './$types';
|
||||
@@ -35,7 +35,7 @@
|
||||
</div>
|
||||
{#if count}
|
||||
<Card>
|
||||
<Heading tag="h6" size="6">{last(count).value}</Heading>
|
||||
<Heading tag="h6" size="6">{total(count)}</Heading>
|
||||
<p>Executions</p>
|
||||
<div class="u-margin-block-start-16" />
|
||||
<BarChart
|
||||
@@ -49,7 +49,7 @@
|
||||
{/if}
|
||||
{#if errors}
|
||||
<Card>
|
||||
<Heading tag="h6" size="6">{last(errors).value}</Heading>
|
||||
<Heading tag="h6" size="6">{total(errors)}</Heading>
|
||||
<p>Errors</p>
|
||||
<div class="u-margin-block-start-16" />
|
||||
<BarChart
|
||||
|
||||
+1
-1
@@ -11,6 +11,6 @@ export const load: PageLoad = async ({ params, parent }) => {
|
||||
|
||||
return {
|
||||
count: response.executionsTotal as unknown as Models.Metric[],
|
||||
errors: response.buildsFailure as unknown as Models.Metric[]
|
||||
errors: response.executionsFailure as unknown as Models.Metric[]
|
||||
};
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<Onboard {projectId} />
|
||||
{:else}
|
||||
{#if $usage}
|
||||
{@const storage = humanFileSize(last($usage.storage)?.value ?? 0)}
|
||||
{@const storage = humanFileSize(total($usage.storage) ?? 0)}
|
||||
<section class="common-section">
|
||||
<div class="grid-dashboard-1s-2m-6l">
|
||||
<div class="card is-2-columns-medium-screen is-3-columns-large-screen">
|
||||
@@ -91,14 +91,14 @@
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
{format(last($usage.documents)?.value ?? 0)}
|
||||
{format(total($usage.documents) ?? 0)}
|
||||
</div>
|
||||
<div>Documents</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text">
|
||||
Databases: {format(last($usage.databases)?.value ?? 0)}
|
||||
Databases: {format(total($usage.databases) ?? 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text">
|
||||
Buckets: {format(last($usage.buckets)?.value ?? 0)}
|
||||
Buckets: {format(total($usage.buckets) ?? 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,7 +146,7 @@
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
{format(last($usage.users)?.value ?? 0)}
|
||||
{format(total($usage.users) ?? 0)}
|
||||
</div>
|
||||
<div>Users</div>
|
||||
</div>
|
||||
@@ -167,7 +167,7 @@
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
{format(last($usage.executions)?.value ?? 0)}
|
||||
{format(total($usage.executions) ?? 0)}
|
||||
</div>
|
||||
<div>Executions</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { sdkForConsole, sdkForProject } from '$lib/stores/sdk';
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { isCloud } from '$lib/system';
|
||||
|
||||
export const usage = cachedStore<
|
||||
Models.UsageProject,
|
||||
@@ -11,7 +12,9 @@ export const usage = cachedStore<
|
||||
>('projectUsage', function ({ set }) {
|
||||
return {
|
||||
load: async (projectId, range) => {
|
||||
const usages = await sdkForConsole.projects.getUsage(projectId, range);
|
||||
const usages = isCloud
|
||||
? await sdkForProject.project.getUsage(range)
|
||||
: await sdkForConsole.projects.getUsage(projectId, range);
|
||||
set(usages);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { isSelfHosted } from '$lib/system';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load: LayoutLoad = async () => {
|
||||
if (isSelfHosted) {
|
||||
throw error(501, 'Not available in self hosted.');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,147 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormItem,
|
||||
InputChoice,
|
||||
InputEmail,
|
||||
InputPassword,
|
||||
InputText
|
||||
} from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole, endpoint } from '$lib/stores/sdk';
|
||||
import { Unauthenticated } from '$lib/layout';
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import LoginLight from '$lib/images/login/login-light-mode.svg';
|
||||
import LoginDark from '$lib/images/login/login-dark-mode.svg';
|
||||
import { isCloud } from '$lib/system';
|
||||
|
||||
let slug = $page.params.slug;
|
||||
let imgLight = LoginLight;
|
||||
let imgDark = LoginDark;
|
||||
|
||||
onMount(async () => {
|
||||
if (isCloud) {
|
||||
switch (slug) {
|
||||
case 'mlh':
|
||||
imgDark = (await import('./mlh-dark.svg')).default;
|
||||
imgLight = (await import('./mlh-light.svg')).default;
|
||||
title = 'Welcome MLH Hackers!';
|
||||
break;
|
||||
case 'appwrite':
|
||||
imgDark = (await import('$lib/images/appwrite.svg')).default;
|
||||
imgLight = (await import('$lib/images/appwrite.svg')).default;
|
||||
title = 'Welcome Appwriters!';
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let name: string, mail: string, pass: string, code: string, disabled: boolean;
|
||||
let title = 'Sign up';
|
||||
let terms = false;
|
||||
|
||||
async function invite() {
|
||||
try {
|
||||
disabled = true;
|
||||
const res = await fetch(`${endpoint}/account/invite`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userId: 'unique()',
|
||||
email: mail,
|
||||
password: pass,
|
||||
code: code,
|
||||
name: name ?? ''
|
||||
})
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error((await res.json()).message);
|
||||
} else {
|
||||
await sdkForConsole.account.createEmailSession(mail, pass);
|
||||
await sdkForConsole.account.updatePrefs({ code });
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
await goto(`${base}/console`);
|
||||
trackEvent('submit_account_create', { code: code });
|
||||
}
|
||||
} catch (error) {
|
||||
disabled = false;
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Sign up - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
<Unauthenticated {imgLight} {imgDark}>
|
||||
<svelte:fragment slot="title">{title}</svelte:fragment>
|
||||
<svelte:fragment>
|
||||
<Form on:submit={invite}>
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Your name"
|
||||
autofocus={true}
|
||||
bind:value={name} />
|
||||
<InputEmail
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Your email"
|
||||
required={true}
|
||||
bind:value={mail} />
|
||||
<InputPassword
|
||||
id="password"
|
||||
label="Password"
|
||||
placeholder="Your password"
|
||||
required={true}
|
||||
showPasswordButton={true}
|
||||
bind:value={pass} />
|
||||
<InputText
|
||||
id="Code"
|
||||
label="Code"
|
||||
placeholder="Your code"
|
||||
required={true}
|
||||
bind:value={code} />
|
||||
<InputChoice required value={terms} id="terms" label="terms" showLabel={false}>
|
||||
By registering, you agree that you have read, understand, and acknowledge our <a
|
||||
class="link"
|
||||
href="https://appwrite.io/policy/privacy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Privacy Policy</a>
|
||||
and accept our
|
||||
<a
|
||||
class="link"
|
||||
href="https://appwrite.io/policy/terms"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">General Terms of Use</a
|
||||
>.</InputChoice>
|
||||
<FormItem>
|
||||
<Button fullWidth submit {disabled}>Sign up</Button>
|
||||
</FormItem>
|
||||
</FormList>
|
||||
</Form>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="links">
|
||||
<li class="inline-links-item">
|
||||
<span class="text">
|
||||
Already got an account? <a class="link" href={`${base}/login`}>Sign in</a>
|
||||
</span>
|
||||
</li>
|
||||
</svelte:fragment>
|
||||
</Unauthenticated>
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 66 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 66 KiB |
Reference in New Issue
Block a user