Merge pull request #301 from appwrite/feat-cloud

feat: implement cloud
This commit is contained in:
Torsten Dittmann
2023-02-24 16:20:23 +05:30
committed by GitHub
17 changed files with 832 additions and 19 deletions
+5 -2
View File
@@ -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>
+1 -1
View File
@@ -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">
+31
View File
@@ -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
);
}
}
+28
View File
@@ -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;
}
}
+3 -2
View File
@@ -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 };
+2
View 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;
+12
View File
@@ -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>
+1 -1
View File
@@ -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`, {
+1 -1
View File
@@ -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');
}
}
@@ -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
@@ -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);
}
};
+9
View File
@@ -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