mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'cloud-current' of https://github.com/appwrite/console into christy-usage-test
This commit is contained in:
commit
7b64ac16dc
118 files changed
+1519
-947
No files matched your search
@@ -17,9 +17,9 @@ jobs:
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
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
+35
-29
@@ -16,6 +16,7 @@
|
||||
"@sentry/tracing": "^7.36.0",
|
||||
"analytics": "^0.8.1",
|
||||
"echarts": "^5.4.1",
|
||||
"logrocket": "^3.0.1",
|
||||
"pretty-bytes": "^6.1.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
@@ -24,7 +25,7 @@
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.30.0",
|
||||
"@sveltejs/adapter-static": "1.0.6",
|
||||
"@sveltejs/kit": "1.3.10",
|
||||
"@sveltejs/kit": "^1.3.10",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.0.2",
|
||||
"@testing-library/dom": "^8.20.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
@@ -33,7 +34,7 @@
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vitest/ui": "^0.28.4",
|
||||
"@vitest/ui": "^0.28.5",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
@@ -49,7 +50,7 @@
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.1.1",
|
||||
"vitest": "^0.28.4"
|
||||
"vitest": "^0.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
@@ -1982,17 +1983,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.0.2.tgz",
|
||||
"integrity": "sha512-xCEan0/NNpQuL0l5aS42FjwQ6wwskdxC3pW1OeFtEKNZwRg7Evro9lac9HesGP6TdFsTv2xMes5ASQVKbCacxg==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.0.3.tgz",
|
||||
"integrity": "sha512-o+cguBFdwIGtRbNkYOyqTM7KvRUffxh5bfK4oJsWKG2obu+v/cbpT03tJrGl58C7tRXo/aEC0/axN5FVHBj0nA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"deepmerge": "^4.2.2",
|
||||
"deepmerge": "^4.3.0",
|
||||
"kleur": "^4.1.5",
|
||||
"magic-string": "^0.27.0",
|
||||
"magic-string": "^0.29.0",
|
||||
"svelte-hmr": "^0.15.1",
|
||||
"vitefu": "^0.2.3"
|
||||
"vitefu": "^0.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >= 16"
|
||||
@@ -2003,9 +2004,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte/node_modules/magic-string": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
|
||||
"integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
|
||||
"version": "0.29.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.29.0.tgz",
|
||||
"integrity": "sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.13"
|
||||
@@ -6261,6 +6262,11 @@
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/logrocket": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/logrocket/-/logrocket-3.0.1.tgz",
|
||||
"integrity": "sha512-jOWG+jEzobKVxGytzZ+4KGm2kiMQMRTHab2uDWQyVZcHfEF38BlCH1yjQVY4LCmuQUwZitP9biMzJZnyUQ0dtQ=="
|
||||
},
|
||||
"node_modules/loupe": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz",
|
||||
@@ -6449,15 +6455,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.1.0.tgz",
|
||||
"integrity": "sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.1.1.tgz",
|
||||
"integrity": "sha512-Jnlh4W/aI4GySPo6+DyTN17Q75KKbLTyFK8BrGhjNP4rxuUjbRWhE6gHg3bs33URWAF44FRm7gdQA348i3XxRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.8.1",
|
||||
"pathe": "^1.0.0",
|
||||
"acorn": "^8.8.2",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.1",
|
||||
"ufo": "^1.0.1"
|
||||
"ufo": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
@@ -6934,14 +6940,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.1.tgz",
|
||||
"integrity": "sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.2.tgz",
|
||||
"integrity": "sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"mlly": "^1.0.0",
|
||||
"pathe": "^1.0.0"
|
||||
"mlly": "^1.1.1",
|
||||
"pathe": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
@@ -8286,9 +8292,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.0.1.tgz",
|
||||
"integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.1.0.tgz",
|
||||
"integrity": "sha512-LQc2s/ZDMaCN3QLpa+uzHUOQ7SdV0qgv3VBXOolQGXTaaZpIur6PwUclF5nN2hNkiTRcUugXd1zFOW3FLJ135Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/undici": {
|
||||
@@ -8387,9 +8393,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.1.1.tgz",
|
||||
"integrity": "sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.1.4.tgz",
|
||||
"integrity": "sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.16.14",
|
||||
|
||||
+4
-3
@@ -27,6 +27,7 @@
|
||||
"@sentry/tracing": "^7.36.0",
|
||||
"analytics": "^0.8.1",
|
||||
"echarts": "^5.4.1",
|
||||
"logrocket": "^3.0.1",
|
||||
"pretty-bytes": "^6.1.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
@@ -35,7 +36,7 @@
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.30.0",
|
||||
"@sveltejs/adapter-static": "1.0.6",
|
||||
"@sveltejs/kit": "1.3.10",
|
||||
"@sveltejs/kit": "^1.3.10",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.0.2",
|
||||
"@testing-library/dom": "^8.20.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
@@ -44,7 +45,7 @@
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vitest/ui": "^0.28.4",
|
||||
"@vitest/ui": "^0.28.5",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
@@ -60,7 +61,7 @@
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.1.1",
|
||||
"vitest": "^0.28.4"
|
||||
"vitest": "^0.28.5"
|
||||
},
|
||||
"type": "module",
|
||||
"pre-commit": [
|
||||
|
||||
@@ -1,18 +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 { growthEndpoint, Mode } from '$lib/constants';
|
||||
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 isDevelopment =
|
||||
import.meta.env.DEV || import.meta.env?.VITE_VERCEL_ENV?.toString() === 'preview';
|
||||
const analytics = Analytics({
|
||||
app: 'appwrite',
|
||||
plugins: [
|
||||
googleAnalytics({
|
||||
measurementIds: [import.meta.env.VITE_GA_PROJECT?.toString() || 'G-R4YJ9JN8L4']
|
||||
measurementIds: [VARS.GOOGLE_ANALYTICS || 'G-R4YJ9JN8L4']
|
||||
})
|
||||
]
|
||||
});
|
||||
@@ -32,7 +30,7 @@ export function trackEvent(name: string, data: object = null): void {
|
||||
};
|
||||
}
|
||||
|
||||
if (isDevelopment) {
|
||||
if (ENV.DEV || ENV.PREVIEW) {
|
||||
console.debug(`[Analytics] Event ${name} ${path}`, data);
|
||||
} else {
|
||||
analytics.track(name, { ...data, path });
|
||||
@@ -54,7 +52,7 @@ export function trackPageView(path: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDevelopment) {
|
||||
if (ENV.DEV || ENV.PREVIEW) {
|
||||
console.debug(`[Analytics] Pageview ${path}`);
|
||||
} else {
|
||||
analytics.page({
|
||||
@@ -64,14 +62,14 @@ export function trackPageView(path: string) {
|
||||
}
|
||||
|
||||
function sendEventToGrowth(event: string, path: string, data: object = null): void {
|
||||
if (!growthEndpoint) return;
|
||||
if (!VARS.GROWTH_ENDPOINT) return;
|
||||
const userStore = get(user);
|
||||
let email: string, name: string;
|
||||
if (userStore) {
|
||||
email = userStore.email;
|
||||
name = userStore.name;
|
||||
}
|
||||
fetch(`${growthEndpoint}/analytics`, {
|
||||
fetch(`${VARS.GROWTH_ENDPOINT}/analytics`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -80,7 +78,7 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo
|
||||
action: event,
|
||||
label: event,
|
||||
url: window.location.origin + path,
|
||||
account: import.meta.env.VITE_CONSOLE_MODE?.toString() || Mode.SELF_HOSTED,
|
||||
account: MODE,
|
||||
data: {
|
||||
email,
|
||||
name,
|
||||
@@ -90,8 +88,8 @@ function sendEventToGrowth(event: string, path: string, data: object = null): vo
|
||||
});
|
||||
}
|
||||
|
||||
function isTrackingAllowed() {
|
||||
if (import.meta.env?.VITEST) {
|
||||
export function isTrackingAllowed() {
|
||||
if (ENV.TEST) {
|
||||
return;
|
||||
}
|
||||
if (window.navigator?.doNotTrack) {
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<section class="box u-overflow-hidden " class:common-section={!noMargin}>
|
||||
<section class="box u-overflow-hidden" class:common-section={!noMargin}>
|
||||
<div
|
||||
class="controls u-position-absolute u-inset-inline-end-8 u-inset-block-start-8 u-flex u-gap-8">
|
||||
{#if label}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<div class="options-list">
|
||||
<button
|
||||
type="button"
|
||||
class="input-button"
|
||||
class="options-list-button"
|
||||
aria-label="Click to copy."
|
||||
on:click={handleCopy}
|
||||
on:mouseenter={() => setTimeout(() => (content = 'Click to copy'))}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<section
|
||||
class:u-overflow-y-auto={scrollable}
|
||||
class:u-max-height-200={scrollable}
|
||||
class="drop-section ">
|
||||
class="drop-section">
|
||||
<ul class="drop-list">
|
||||
<slot name="list" />
|
||||
</ul>
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await feedback.submitFeedback('feedback-nps', message, name, email, value);
|
||||
console.log(value, message);
|
||||
feedback.switchType('general');
|
||||
} catch (error) {
|
||||
feedback.switchType('general');
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<form class="modal-form" role="form" on:submit|preventDefault>
|
||||
<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}
|
||||
|
||||
@@ -68,13 +68,13 @@
|
||||
{@const isAnonymous = !data.email && !data.phone && isUser}
|
||||
<div class="user-profile">
|
||||
{#if isAnonymous}
|
||||
<div class="avatar is-size-small ">
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
{:else if data.name}
|
||||
<AvatarInitials name={data.name} size={40} />
|
||||
{:else}
|
||||
<div class="avatar is-size-small ">
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-minus-sm" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<div class="u-x-small">{user.$id}</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="avatar is-size-small ">
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-minus-sm" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="u-line-height-1-5">
|
||||
@@ -107,7 +107,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="avatar is-size-small ">
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="u-line-height-1-5">
|
||||
|
||||
@@ -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>
|
||||
@@ -2,13 +2,6 @@ export const PAGE_LIMIT = 12; // default page limit
|
||||
export const CARD_LIMIT = 6; // default card limit
|
||||
export const INTERVAL = 5 * 60000; // default interval to check for feedback
|
||||
|
||||
export enum Mode {
|
||||
CLOUD = 'cloud',
|
||||
SELF_HOSTED = 'self-hosted'
|
||||
}
|
||||
|
||||
export const growthEndpoint = import.meta.env.VITE_APPWRITE_GROWTH_ENDPOINT;
|
||||
|
||||
export enum Dependencies {
|
||||
ORGANIZATION = 'dependency:organization',
|
||||
PROJECT = 'dependency:project',
|
||||
|
||||
@@ -23,3 +23,4 @@ export { default as InputURL } from './inputURL.svelte';
|
||||
export { default as InputId } from './inputId.svelte';
|
||||
export { default as InputSecret } from './inputSecret.svelte';
|
||||
export { default as Helper } from './helper.svelte';
|
||||
export { default as Label } from './label.svelte';
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
@@ -27,12 +27,9 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>
|
||||
<Label {required} {optionalText} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
{#if optionalText}
|
||||
<span class="optional">{optionalText}</span>
|
||||
{/if}
|
||||
</label>
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -48,7 +48,10 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<Label {required} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
{id}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -39,12 +39,9 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>
|
||||
<Label {required} {optionalText} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
<span class:u-hide={!showLabel || !optionalText} class="optional">
|
||||
{optionalText}
|
||||
</span>
|
||||
</label>
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper" style="--amount-of-buttons:1; --button-size: 1rem">
|
||||
<input
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -47,7 +47,10 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<Label {required} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
{id}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -41,7 +41,10 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<Label {required} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
{id}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<p class="text">{label}</p>
|
||||
{/if}
|
||||
<div
|
||||
class="box is-no-shadow u-padding-24"
|
||||
class="box is-no-shadow u-padding-24"
|
||||
style="--box-border-radius:var(--border-radius-xsmall); z-index: 1"
|
||||
class:is-border-dashed={!hovering}
|
||||
class:is-hover-with-file={hovering}
|
||||
@@ -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,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
@@ -52,12 +52,9 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>
|
||||
<Label {required} {optionalText} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
{#if optionalText}
|
||||
<span class="optional">{optionalText}</span>
|
||||
{/if}
|
||||
</label>
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
@@ -47,7 +47,10 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<Label {required} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
{#if showInPlainText}
|
||||
<input
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -46,7 +46,9 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<Label {required} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
{id}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -40,7 +40,9 @@
|
||||
bind:group
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<Label {required} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Label } from '.';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
@@ -11,7 +11,10 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<Label hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper" style=" --amount-of-buttons: 1;">
|
||||
{#if showInPlainText}
|
||||
<div
|
||||
@@ -19,7 +22,7 @@
|
||||
role="textbox"
|
||||
{id}
|
||||
aria-placeholder={placeholder}
|
||||
class="input-text is-resizable"
|
||||
class="input-text is-resizable"
|
||||
bind:textContent={value} />
|
||||
{:else}
|
||||
<input
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
@@ -41,12 +41,9 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>
|
||||
<Label {required} {optionalText} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
{#if optionalText}
|
||||
<span class="optional">{optionalText}</span>
|
||||
{/if}
|
||||
</label>
|
||||
</Label>
|
||||
|
||||
<div class="select">
|
||||
<select
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { last } from '$lib/helpers/array';
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
@@ -77,7 +77,10 @@
|
||||
value={tags.join(',')}
|
||||
{required}
|
||||
on:invalid={handleInvalid} />
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<Label {required} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<div class="tags-input">
|
||||
<div class="tags">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tooltip as tooltipAction } from '$lib/actions/tooltip';
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
@@ -43,20 +42,9 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>
|
||||
<Label {required} {tooltip} {optionalText} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
{#if tooltip}
|
||||
<span
|
||||
class="icon-info"
|
||||
aria-hidden="true"
|
||||
use:tooltipAction={{
|
||||
content: tooltip
|
||||
}} />
|
||||
{/if}
|
||||
{#if optionalText}
|
||||
<span class="optional">{optionalText}</span>
|
||||
{/if}
|
||||
</label>
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
@@ -72,6 +60,13 @@
|
||||
bind:value
|
||||
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>
|
||||
{/if}
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -37,7 +37,10 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<Label {required} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<textarea
|
||||
{id}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -50,7 +50,10 @@
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<Label {required} hide={!showLabel} for={id}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
{id}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
interface $$Props extends Partial<HTMLLabelElement> {
|
||||
required?: boolean;
|
||||
optionalText?: string | undefined;
|
||||
hide?: boolean;
|
||||
tooltip?: string;
|
||||
for?: string;
|
||||
}
|
||||
|
||||
export let required: $$Props['required'] = false;
|
||||
export let optionalText: $$Props['optionalText'] = undefined;
|
||||
export let hide: $$Props['hide'] = false;
|
||||
export let tooltip: $$Props['tooltip'] = null;
|
||||
</script>
|
||||
|
||||
<label class:is-required={required} class:u-hide={hide} class="label" {...$$restProps}>
|
||||
<slot />
|
||||
</label>
|
||||
|
||||
{#if optionalText}
|
||||
<span class="optional">{optionalText}</span>
|
||||
{/if}
|
||||
|
||||
{#if tooltip}
|
||||
<button class="tooltip" aria-label="variables info">
|
||||
<span class="icon-info" aria-hidden="true" />
|
||||
<span class="tooltip-popup" role="tooltip">
|
||||
<p class="text">
|
||||
{tooltip}
|
||||
</p>
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -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">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
export let isSticky = false;
|
||||
</script>
|
||||
|
||||
<div class="table-with-scroll u-margin-block-start-32">
|
||||
<div class="table-with-scroll u-margin-block-start-32" data-private>
|
||||
<div class="table-wrapper">
|
||||
<table class="table" class:is-sticky-scroll={isSticky}>
|
||||
<slot />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] as const;
|
||||
export type Size = (typeof sizes)[number];
|
||||
|
||||
export function calculateSize(bytes: number, decimals = 1) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
@@ -13,18 +14,19 @@ export function calculateSize(bytes: number, decimals = 1) {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
export function sizeToBytes(value: number, unit: string) {
|
||||
export function sizeToBytes(value: number, unit: Size) {
|
||||
const index = sizes.indexOf(unit);
|
||||
return value * Math.pow(1024, index);
|
||||
}
|
||||
export function bytesToSize(value: number, unit: string) {
|
||||
|
||||
export function bytesToSize(value: number, unit: Size) {
|
||||
const index = sizes.indexOf(unit);
|
||||
return value / Math.pow(1024, index);
|
||||
}
|
||||
|
||||
export function humanFileSize(bytes: number): {
|
||||
value: string;
|
||||
unit: string;
|
||||
unit: Size;
|
||||
} {
|
||||
const value = prettyBytes(bytes, {
|
||||
locale: 'en'
|
||||
@@ -32,6 +34,6 @@ export function humanFileSize(bytes: number): {
|
||||
|
||||
return {
|
||||
value: value[0],
|
||||
unit: value[1]
|
||||
unit: value[1] as Size
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import { last } from './array';
|
||||
import { withPrevious } from './withPrevious';
|
||||
|
||||
export type Unit<T = string> = {
|
||||
name: T;
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a value-unit pair. When the unit is changed, the value is updated
|
||||
* to reflect the new unit.
|
||||
*
|
||||
* @export
|
||||
* @template T = string
|
||||
* @param {number} [initialValue=0]
|
||||
* @param {Unit<T>[]} units
|
||||
*/
|
||||
export function createValueUnitPair<T = string>(initialValue = 0, units: Unit<T>[]) {
|
||||
if (!units.some((u) => u.value === 1)) {
|
||||
throw new Error('Units must have a value of 1');
|
||||
}
|
||||
|
||||
if (units.some((u) => u.value < 1)) {
|
||||
throw new Error('Units must have a value greater than 1');
|
||||
}
|
||||
|
||||
// Highest to lowest
|
||||
const sortedUnits = units.sort((a, b) => b.value - a.value);
|
||||
|
||||
const unit = withPrevious(last(sortedUnits).name);
|
||||
const value = writable(initialValue);
|
||||
const baseValue = derived([value, unit], ([$value, $unit]) => {
|
||||
return $value * units.find((u) => u.name === $unit).value;
|
||||
});
|
||||
|
||||
for (const u of sortedUnits) {
|
||||
if (initialValue >= u.value && initialValue % u.value === 0) {
|
||||
unit.set(u.name, { updateAll: true });
|
||||
value.set(initialValue / u.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unit.subscribe((newValue, prev) => {
|
||||
value.update((v) => {
|
||||
const unitInBase = v * units.find((u) => u.name === prev).value;
|
||||
return unitInBase / units.find((u) => u.name === newValue).value;
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
value,
|
||||
unit,
|
||||
baseValue
|
||||
};
|
||||
}
|
||||
|
||||
export function createTimeUnitPair(initialValue = 0) {
|
||||
const units: Unit[] = [
|
||||
{ name: 'Days', value: 86400 },
|
||||
{ name: 'Hours', value: 3600 },
|
||||
{ name: 'Minutes', value: 60 },
|
||||
{ name: 'Seconds', value: 1 }
|
||||
];
|
||||
return { ...createValueUnitPair(initialValue, units), units };
|
||||
}
|
||||
|
||||
export function createByteUnitPair(initialValue = 0) {
|
||||
const units: Unit[] = [
|
||||
{ name: 'Bytes', value: 1 },
|
||||
{ name: 'Kilobytes', value: 1024 },
|
||||
{ name: 'Megabytes', value: 1024 ** 2 },
|
||||
{ name: 'Gigabytes', value: 1024 ** 3 }
|
||||
];
|
||||
return { ...createValueUnitPair(initialValue, units), units };
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
type SetterOptions = {
|
||||
updateAll?: boolean;
|
||||
};
|
||||
|
||||
type Listener<T> = (value: T, previous: T | null) => void;
|
||||
|
||||
export function withPrevious<T>(value: T) {
|
||||
let current = value;
|
||||
let previous: T | null = null;
|
||||
|
||||
let subscribers: Array<Listener<T>> = [];
|
||||
|
||||
const subscribe = (listener: Listener<T>) => {
|
||||
subscribers.push(listener);
|
||||
listener(current, previous);
|
||||
|
||||
return () => {
|
||||
subscribers = subscribers.filter((s) => s !== listener);
|
||||
};
|
||||
};
|
||||
|
||||
const set = (value: T, options?: SetterOptions) => {
|
||||
previous = options?.updateAll ? value : current;
|
||||
current = value;
|
||||
|
||||
subscribers.forEach((s) => s(current, previous));
|
||||
};
|
||||
|
||||
const getPrevious = () => previous;
|
||||
|
||||
const currentStore = {
|
||||
subscribe,
|
||||
set,
|
||||
getPrevious
|
||||
};
|
||||
|
||||
return currentStore;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { EmptySearch, Pagination, Trim } from '$lib/components';
|
||||
import { AvatarInitials, EmptySearch, Pagination, Trim } from '$lib/components';
|
||||
import {
|
||||
TableBody,
|
||||
TableHeader,
|
||||
@@ -11,24 +11,19 @@
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
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;
|
||||
|
||||
const getBrowser = (clientCode: string) => {
|
||||
return sdkForConsole.avatars.getBrowser(clientCode, 40, 40);
|
||||
};
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if logs.total}
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
<TableCellHead width={150}>Client</TableCellHead>
|
||||
<TableCellHead width={100}>User</TableCellHead>
|
||||
<TableCellHead width={100}>Event</TableCellHead>
|
||||
<TableCellHead width={80}>Location</TableCellHead>
|
||||
<TableCellHead width={90}>IP</TableCellHead>
|
||||
@@ -38,28 +33,22 @@
|
||||
{#each logs.logs as log}
|
||||
<TableRow>
|
||||
<TableCell title="Client">
|
||||
{#if log.clientName}
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<div class="avatar is-small">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
src={getBrowser(log.clientCode).toString()}
|
||||
alt={log.clientName} />
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
{#if log.userEmail}
|
||||
{#if log.userName}
|
||||
<AvatarInitials size={32} name={log.userName} />
|
||||
<Trim>{log.userName}</Trim>
|
||||
{:else}
|
||||
<AvatarInitials size={32} name={log.userEmail} />
|
||||
<Trim>{log.userEmail}</Trim>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
<Trim>
|
||||
{log.clientName}
|
||||
{log.clientVersion}
|
||||
on {log.osName}
|
||||
{log.osVersion}
|
||||
</Trim>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<span class="avatar is-color-empty" />
|
||||
<p class="text u-trim">Unknown</p>
|
||||
</div>
|
||||
{/if}
|
||||
<span class="text u-trim">{log.userName ?? 'Anonymous'}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Event">{log.event}</TableCellText>
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
</a>
|
||||
{/if}
|
||||
<span class="text u-trim-1">
|
||||
<span class="text u-trim-1" data-private>
|
||||
<slot />
|
||||
</span>
|
||||
</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/blob/master/CHANGES.md"
|
||||
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(:hover) {
|
||||
color: hsl(var(--color-neutral-50));
|
||||
}
|
||||
</style>
|
||||
@@ -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
|
||||
|
||||
@@ -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,11 +65,17 @@
|
||||
|
||||
<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 {
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { growthEndpoint } from '$lib/constants';
|
||||
import { VARS } from '$lib/system';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export type AppStore = {
|
||||
@@ -64,8 +64,8 @@ function createFeedbackStore() {
|
||||
email?: string,
|
||||
value?: number
|
||||
) => {
|
||||
if (!growthEndpoint) return;
|
||||
const response = await fetch(`${growthEndpoint}/feedback`, {
|
||||
if (!VARS.GROWTH_ENDPOINT) return;
|
||||
const response = await fetch(`${VARS.GROWTH_ENDPOINT}/feedback`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Project } from '$lib/sdk/project';
|
||||
import { VARS } from '$lib/system';
|
||||
import {
|
||||
Account,
|
||||
Avatars,
|
||||
@@ -12,10 +14,8 @@ import {
|
||||
Users
|
||||
} from '@aw-labs/appwrite-console';
|
||||
|
||||
import { Project } from './project';
|
||||
const endpoint = VARS.APPWRITE_ENDPOINT ?? `${globalThis?.location?.origin}/v1`;
|
||||
|
||||
const endpoint =
|
||||
import.meta.env.VITE_APPWRITE_ENDPOINT?.toString() ?? `${globalThis?.location?.origin}/v1`;
|
||||
const clientConsole = new Client();
|
||||
clientConsole.setEndpoint(endpoint).setProject('console');
|
||||
|
||||
@@ -45,7 +45,6 @@ const sdkForProject = {
|
||||
health: new Health(clientProject),
|
||||
locale: new Locale(clientProject),
|
||||
project: new Project(clientProject),
|
||||
projects: new Projects(clientProject),
|
||||
storage: new Storage(clientProject),
|
||||
teams: new Teams(clientProject),
|
||||
users: new Users(clientProject)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
export enum Mode {
|
||||
CLOUD = 'cloud',
|
||||
SELF_HOSTED = 'self-hosted'
|
||||
}
|
||||
|
||||
export const VARS = {
|
||||
APPWRITE_ENDPOINT: import.meta.env?.VITE_APPWRITE_ENDPOINT?.toString() as string | undefined,
|
||||
GROWTH_ENDPOINT: import.meta.env?.VITE_APPWRITE_GROWTH_ENDPOINT?.toString() as
|
||||
| string
|
||||
| undefined,
|
||||
CONSOLE_MODE: import.meta.env?.VITE_CONSOLE_MODE?.toString() as string | undefined,
|
||||
VERCEL_ENV: import.meta.env?.VITE_VERCEL_ENV?.toString() as string | undefined,
|
||||
GOOGLE_ANALYTICS: import.meta.env?.VITE_GA_PROJECT?.toString() as string | undefined
|
||||
};
|
||||
|
||||
export const ENV = {
|
||||
DEV: import.meta.env.DEV,
|
||||
PROD: import.meta.env.PROD,
|
||||
PREVIEW: VARS.VERCEL_ENV === 'preview',
|
||||
TEST: !!import.meta.env?.VITEST
|
||||
};
|
||||
|
||||
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
-19
@@ -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,10 +72,7 @@
|
||||
}
|
||||
loading.set(false);
|
||||
} else {
|
||||
if (
|
||||
acceptedRoutes.includes($page.url.pathname) ||
|
||||
$page.url.pathname.startsWith('/register/invite')
|
||||
) {
|
||||
if (acceptedRoutes.some((n) => $page.url.pathname.startsWith(n))) {
|
||||
await goto(`${base}${$page.url.pathname}${$page.url.search}`);
|
||||
} else {
|
||||
await goto(`${base}/login`, {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import '@appwrite.io/pink';
|
||||
import '@appwrite.io/pink-icons';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import LogRocket from 'logrocket';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
@@ -13,6 +14,11 @@ export const load: LayoutLoad = async ({ depends, url }) => {
|
||||
try {
|
||||
const account = await sdkForConsole.account.get();
|
||||
|
||||
LogRocket.identify(account.$id, {
|
||||
name: account.name,
|
||||
email: account.email
|
||||
});
|
||||
|
||||
return {
|
||||
account,
|
||||
organizations: sdkForConsole.teams.list()
|
||||
@@ -28,10 +34,7 @@ export const load: LayoutLoad = async ({ depends, url }) => {
|
||||
'/auth/oauth2/failure'
|
||||
];
|
||||
|
||||
if (
|
||||
!acceptedRoutes.includes(url.pathname) &&
|
||||
!url.pathname.startsWith('/register/invite')
|
||||
) {
|
||||
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 { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load: LayoutLoad = async () => {
|
||||
const response = await fetch(`${sdkForConsole.client.config.endpoint}/health/version`);
|
||||
const data = await response.json();
|
||||
|
||||
return {
|
||||
version: data?.version ?? null
|
||||
};
|
||||
};
|
||||
@@ -85,13 +85,7 @@
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
autofocus={true}
|
||||
bind:value={name} />
|
||||
<InputText id="name" label="Name" placeholder="Enter name" bind:value={name} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -110,14 +104,12 @@
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autocomplete={false}
|
||||
bind:value={email} />
|
||||
{#if email !== $user.email && email}
|
||||
<InputPassword
|
||||
id="emailPassword"
|
||||
label="Password"
|
||||
placeholder="Enter password"
|
||||
autocomplete={false}
|
||||
showPasswordButton={true}
|
||||
bind:value={emailPassword} />
|
||||
{/if}
|
||||
@@ -144,14 +136,12 @@
|
||||
id="oldPassword"
|
||||
label="Old password"
|
||||
placeholder="Enter password"
|
||||
autocomplete={false}
|
||||
showPasswordButton={true}
|
||||
bind:value={oldPassword} />
|
||||
<InputPassword
|
||||
id="newPassword"
|
||||
label="New password"
|
||||
placeholder="Enter password"
|
||||
autocomplete={false}
|
||||
showPasswordButton={true}
|
||||
bind:value={newPassword} />
|
||||
</FormList>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
{organization.name}
|
||||
</svelte:fragment>
|
||||
{#await avatarList}
|
||||
<span class="avatar is-color-empty" />
|
||||
<span class="avatar is-color-empty" />
|
||||
{:then avatars}
|
||||
<AvatarGroup size={40} {avatars} />
|
||||
{/await}
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
{session.osVersion}
|
||||
</Trim>
|
||||
{:else}
|
||||
<span class="avatar is-color-empty" />
|
||||
<span class="avatar is-color-empty" />
|
||||
<p class="text u-trim">Unknown</p>
|
||||
{/if}
|
||||
<div class="is-only-desktop">
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<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}'?`}
|
||||
|
||||
@@ -63,12 +63,12 @@
|
||||
<AvatarInitials size={32} name={user.name} />
|
||||
<span class="text u-trim">{user.name}</span>
|
||||
{:else}
|
||||
<div class="avatar is-size-small ">
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-minus-sm" aria-hidden="true" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="avatar is-size-small ">
|
||||
<div class="avatar is-size-small">
|
||||
<span class="icon-anonymous" aria-hidden="true" />
|
||||
</div>
|
||||
<span class="text u-trim">{user.name}</span>
|
||||
|
||||
@@ -4,62 +4,18 @@
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, InputNumber, InputSelect } from '$lib/elements/forms';
|
||||
import { secsToUnit, timeToSeconds } from '$lib/helpers/timeConversion';
|
||||
import { createTimeUnitPair } from '$lib/helpers/unit';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { writable } from 'svelte/store';
|
||||
import { project } from '../../store';
|
||||
|
||||
enum Period {
|
||||
Days = 'd',
|
||||
Hours = 'h',
|
||||
Minutes = 'm',
|
||||
Seconds = 's'
|
||||
}
|
||||
|
||||
const projectId = $project.$id;
|
||||
const options = [
|
||||
{ label: 'days', value: Period.Days },
|
||||
{ label: 'hours', value: Period.Hours },
|
||||
{ label: 'minutes', value: Period.Minutes },
|
||||
{ label: 'seconds', value: Period.Seconds }
|
||||
];
|
||||
|
||||
let period = writable(Period.Seconds);
|
||||
|
||||
function getInitialTime() {
|
||||
let t = $project.authDuration;
|
||||
|
||||
if (t >= 86400 && t % 86400 === 0) {
|
||||
$period = Period.Days;
|
||||
return t / 86400;
|
||||
}
|
||||
|
||||
if (t >= 3600 && t % 3600 === 0) {
|
||||
$period = Period.Hours;
|
||||
return t / 3600;
|
||||
}
|
||||
|
||||
if (t >= 60 && t % 60 === 0) {
|
||||
$period = Period.Minutes;
|
||||
return t / 60;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
let time = getInitialTime();
|
||||
let timeInSecs = timeToSeconds(time, $period);
|
||||
|
||||
period.subscribe((p) => {
|
||||
time = secsToUnit(timeInSecs, p);
|
||||
});
|
||||
|
||||
$: timeInSecs = timeToSeconds(time, $period);
|
||||
const { value, unit, baseValue, units } = createTimeUnitPair($project.authDuration);
|
||||
const options = units.map((v) => ({ label: v.name, value: v.name }));
|
||||
|
||||
async function updateSessionLength() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateAuthDuration(projectId, timeInSecs);
|
||||
await sdkForConsole.projects.updateAuthDuration(projectId, $baseValue);
|
||||
invalidate(Dependencies.PROJECT);
|
||||
|
||||
addNotification({
|
||||
@@ -87,15 +43,15 @@
|
||||
<svelte:fragment slot="aside">
|
||||
<form class="form u-grid u-gap-16">
|
||||
<ul class="form-list is-multiple">
|
||||
<InputNumber id="length" label="Length" bind:value={time} />
|
||||
<InputSelect id="period" label="Time Period" bind:value={$period} {options} />
|
||||
<InputNumber id="length" label="Length" bind:value={$value} min={0} />
|
||||
<InputSelect id="period" label="Time Period" bind:value={$unit} {options} />
|
||||
</ul>
|
||||
</form>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={timeToSeconds(time, $period) === $project.authDuration}
|
||||
disabled={$baseValue === $project.authDuration}
|
||||
on:click={() => {
|
||||
updateSessionLength();
|
||||
}}>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
type="radio"
|
||||
bind:group={isLimited}
|
||||
value={false} />
|
||||
<div class="choice-item-content u-cross-child-center">
|
||||
<div class="choice-item-content u-cross-child-center">
|
||||
<div class="choice-item-title">Unlimited</div>
|
||||
</div>
|
||||
<Pill>recommended</Pill>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
<Modal bind:show={showDelete} on:submit={deleteMembership} warning>
|
||||
<svelte:fragment slot="header">Delete Member</svelte:fragment>
|
||||
<p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>{selectedMembership.userName}</b> from '{selectedMembership.teamName}'?
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
|
||||
<Modal bind:show={showDelete} on:submit={deleteTeam} warning>
|
||||
<svelte:fragment slot="header">Delete Team</svelte:fragment>
|
||||
|
||||
<p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>{team.name}</b>?
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${project}/auth/user-${membership.userId}`}>
|
||||
<TableCellText title="Name">
|
||||
<div class="u-flex u-gap-12">
|
||||
<div class="u-flex u-gap-12 u-cross-center">
|
||||
<AvatarInitials size={32} name={membership.userName} />
|
||||
<span>{membership.userName ? membership.userName : 'n/a'}</span>
|
||||
</div>
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">
|
||||
<h6 class="u-bold u-trim-1" data-private>
|
||||
{$user.name || $user.email || $user.phone || 'Anonymous'}
|
||||
</h6>
|
||||
</svelte:fragment>
|
||||
<p class="u-trim-1">
|
||||
<p class="u-trim-1" data-private>
|
||||
{$user.email && $user.phone
|
||||
? [$user.email, $user.phone].join(',')
|
||||
: $user.email || $user.phone}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<Modal bind:show={showDeleteAll} on:submit={deleteAllSessions} warning>
|
||||
<svelte:fragment slot="header">Delete All Sessions</svelte:fragment>
|
||||
<p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>all of {$user.name}'s sessions?</b>
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<Modal bind:show={showDelete} on:submit={deleteMembership} warning>
|
||||
<svelte:fragment slot="header">Delete Member</svelte:fragment>
|
||||
{#if selectedMembership}
|
||||
<p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>{selectedMembership.userName}</b> from '{selectedMembership.teamName}'?
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<Modal bind:show={showDelete} on:submit={deleteUser} warning>
|
||||
<svelte:fragment slot="header">Delete User</svelte:fragment>
|
||||
<p>Are you sure you want to delete <b>{$user.name}</b> from '{$project.name}'?</p>
|
||||
<p data-private>Are you sure you want to delete <b>{$user.name}</b> from '{$project.name}'?</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button secondary submit>Delete</Button>
|
||||
|
||||
+10
-14
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { base } from '$app/paths';
|
||||
import { Empty, AvatarInitials } from '$lib/components';
|
||||
import { AvatarInitials, EmptySearch } from '$lib/components';
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
@@ -41,7 +41,7 @@
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${project}/auth/teams/team-${membership.teamId}`}>
|
||||
<TableCellText title="Name">
|
||||
<div class="u-flex u-gap-12">
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<AvatarInitials size={32} name={membership.teamName} />
|
||||
<span>{membership.teamName ? membership.teamName : 'n/a'}</span>
|
||||
</div>
|
||||
@@ -67,19 +67,15 @@
|
||||
</TableBody>
|
||||
</Table>
|
||||
{:else}
|
||||
<Empty single>
|
||||
<p>No memberships available</p>
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href="https://appwrite.io/docs/server/users?sdk=nodejs-default#usersListMemberships">
|
||||
Documentation
|
||||
</Button>
|
||||
</Empty>
|
||||
<EmptySearch>
|
||||
<div class="u-flex u-flex-vertical u-cross-center u-gap-24">
|
||||
<p class="text u-line-height-1-5">No memberships available</p>
|
||||
<Button external secondary href="https://appwrite.io/docs/client/teams">
|
||||
Documentation
|
||||
</Button>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{/if}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {data.memberships.total}</p>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<DeleteMembership {selectedMembership} bind:showDelete />
|
||||
+13
-14
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Empty } from '$lib/components';
|
||||
import { EmptySearch } from '$lib/components';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<Container>
|
||||
{#if data.sessions.total}
|
||||
<div class="u-flex u-main-end common-section">
|
||||
<div class="u-flex u-main-end common-section">
|
||||
<Button secondary on:click={() => (showDeleteAll = true)}>
|
||||
<span class="text">Delete All</span>
|
||||
</Button>
|
||||
@@ -84,19 +84,18 @@
|
||||
</TableBody>
|
||||
</Table>
|
||||
{:else}
|
||||
<Empty single>
|
||||
<p>No sessions available</p>
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href="https://appwrite.io/docs/server/auth?sdk=nodejs-default#usersGetSessions">
|
||||
Documentation
|
||||
</Button>
|
||||
</Empty>
|
||||
<EmptySearch>
|
||||
<div class="u-flex u-flex-vertical u-cross-center u-gap-24">
|
||||
<p class="text u-line-height-1-5">No sessions available</p>
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href="https://appwrite.io/docs/server/auth?sdk=nodejs-default#usersGetSessions">
|
||||
Documentation
|
||||
</Button>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{/if}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {data.sessions.total}</p>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<DeleteSessions {selectedSessionId} bind:showDelete />
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<Heading tag="h6" size="7">Update Name</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<ul data-private>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16">
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16" data-private>
|
||||
{#if $user.email || $user.phone}
|
||||
{#if $user.name}
|
||||
<AvatarInitials size={48} name={$user.name} />
|
||||
@@ -92,7 +92,7 @@
|
||||
</div>
|
||||
<svelte:fragment slot="aside">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<div>
|
||||
<div data-private>
|
||||
{#if $user.email}
|
||||
<p class="title">{$user.email}</p>
|
||||
{/if}
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@
|
||||
formattedColumn = column;
|
||||
} else if (Array.isArray(column)) {
|
||||
formattedColumn = formatArray(column);
|
||||
} else if (!column) {
|
||||
} else if (column === null) {
|
||||
formattedColumn = 'n/a';
|
||||
} else {
|
||||
formattedColumn = `${column}`;
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@
|
||||
|
||||
<Modal warning={true} bind:show={showDelete} on:submit={handleDelete}>
|
||||
<svelte:fragment slot="header">Delete Attribute</svelte:fragment>
|
||||
<p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>'{selectedAttribute?.key}' from {$collection?.name}</b>?
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
+5
@@ -54,6 +54,11 @@
|
||||
key = null;
|
||||
selectedOption = null;
|
||||
$option = null;
|
||||
data = {
|
||||
required: false,
|
||||
array: false,
|
||||
default: null
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+7
-6
@@ -10,7 +10,7 @@
|
||||
export let attributes: Attributes[] = [];
|
||||
export let formValues: object = {};
|
||||
export let customId: string | null | undefined = undefined;
|
||||
export let gap: '16' | '32' = '32';
|
||||
export let gap: '16' | '24' | '32' = '32';
|
||||
|
||||
let showCustomId = false;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
{#if attributes.length}
|
||||
<ul class={`form-list u-gap-${gap}`}>
|
||||
{#each attributes as attribute}
|
||||
{@const label = attribute.required ? `${attribute.key}*` : attribute.key}
|
||||
{@const label = attribute.key}
|
||||
{#if attribute.array}
|
||||
{#if formValues[attribute.key].length === 0}
|
||||
<div class="u-flex u-cross-center u-main-space-between">
|
||||
@@ -71,6 +71,7 @@
|
||||
</div>
|
||||
<div class="form-item-part u-cross-child-end">
|
||||
<Button
|
||||
noMargin
|
||||
text
|
||||
on:click={() => removeArrayItem(attribute.key, index)}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
@@ -78,11 +79,11 @@
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
<Button text noMargin on:click={() => addArrayItem(attribute.key)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text"> Add item</span>
|
||||
</Button>
|
||||
</ul>
|
||||
<Button text noMargin on:click={() => addArrayItem(attribute.key)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text"> Add item</span>
|
||||
</Button>
|
||||
{/if}
|
||||
{:else}
|
||||
<FormList>
|
||||
|
||||
+1
@@ -13,6 +13,7 @@
|
||||
{id}
|
||||
{label}
|
||||
{optionalText}
|
||||
placeholder="Enter string"
|
||||
showLabel={!!label?.length}
|
||||
required={attribute.required}
|
||||
maxlength={attribute.size}
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@
|
||||
<Modal warning={true} on:submit={handleDelete} bind:show={showDelete}>
|
||||
<svelte:fragment slot="header">Delete Document</svelte:fragment>
|
||||
|
||||
<p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>the document from {$collection.name}</b>?
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@
|
||||
<Modal warning={true} on:submit={handleDelete} bind:show={showDelete}>
|
||||
<svelte:fragment slot="header">Delete Index</svelte:fragment>
|
||||
|
||||
<p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>'{selectedIndex.key}' from {$collection.name}</b>?
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@
|
||||
<Modal warning={true} bind:show={showDelete} on:submit={handleDelete}>
|
||||
<svelte:fragment slot="header">Delete Collection</svelte:fragment>
|
||||
|
||||
<p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>{$collection.name}</b>?
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
+2
-1
@@ -13,5 +13,6 @@
|
||||
<AttributeForm
|
||||
bind:formValues={$createDocument.document}
|
||||
attributes={$createDocument.attributes}
|
||||
bind:customId={$createDocument.id} />
|
||||
bind:customId={$createDocument.id}
|
||||
gap="24" />
|
||||
</WizardStep>
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<Modal warning={true} bind:show={showDelete} on:submit={handleDelete}>
|
||||
<svelte:fragment slot="header">Delete Database</svelte:fragment>
|
||||
<p class="text">
|
||||
<p class="text" data-private>
|
||||
Are you sure you want to delete <b>{$database.name}</b>?
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
+10
-14
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Card, CardGrid, Box, Heading, AvatarInitials } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Button, InputText, Helper } from '$lib/elements/forms';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { database } from '../store';
|
||||
import { onMount } from 'svelte';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Box, Card, CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Helper, InputText } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import Delete from '../delete.svelte';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { database } from '../store';
|
||||
|
||||
let showDelete = false;
|
||||
let showError: false | 'name' | 'email' | 'password' = false;
|
||||
@@ -51,7 +51,6 @@
|
||||
<div class="common-section grid-1-2">
|
||||
<div class="grid-1-2-col-1">
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16">
|
||||
<AvatarInitials size={48} name={$database.name} />
|
||||
<Heading tag="h6" size="7">{$database.name}</Heading>
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,9 +98,6 @@
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="image">
|
||||
<AvatarInitials size={48} name={$database.name} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$database.name}</h6>
|
||||
<span>Last Updated: {toLocaleDateTime($database.$updatedAt)}</span>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<Modal bind:show={showDelete} on:submit={handleSubmit} warning>
|
||||
<svelte:fragment slot="header">Delete Deployment</svelte:fragment>
|
||||
<p>Are you sure you want to delete this deployment?</p>
|
||||
<p data-private>Are you sure you want to delete this deployment?</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button secondary submit>Delete</Button>
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
|
||||
<Modal bind:show={showDelete} on:submit={handleSubmit} warning>
|
||||
<svelte:fragment slot="header">Delete Function</svelte:fragment>
|
||||
<p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete this function and all associated deployments from your
|
||||
project?
|
||||
</p>
|
||||
|
||||
-2
@@ -5,9 +5,7 @@
|
||||
import { BarChart } from '$lib/charts';
|
||||
import { page } from '$app/stores';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
$: count = data.executionsTotal;
|
||||
</script>
|
||||
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@ export const load: PageLoad = async ({ params, parent }) => {
|
||||
);
|
||||
|
||||
return {
|
||||
executionsTotal: response.executionsTotal as unknown as Models.Metric[]
|
||||
count: response.executionsTotal as unknown as Models.Metric[],
|
||||
errors: response.executionsFailure as unknown as Models.Metric[]
|
||||
};
|
||||
};
|
||||
@@ -65,8 +65,7 @@
|
||||
<Onboard {projectId} />
|
||||
{:else}
|
||||
{#if $usage}
|
||||
{@const storage = humanFileSize(total($usage.filesStorage))}
|
||||
|
||||
{@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">
|
||||
@@ -90,14 +89,14 @@
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
{format(total($usage.documentsTotal))}
|
||||
{format(total($usage.documents) ?? 0)}
|
||||
</div>
|
||||
<div>Documents</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text">
|
||||
Databases: {format(total($usage.databasesTotal))}
|
||||
Databases: {format(total($usage.databases) ?? 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,7 +124,7 @@
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text">
|
||||
Buckets: {format(total($usage.bucketsTotal))}
|
||||
Buckets: {format(total($usage.buckets) ?? 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,7 +144,7 @@
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
{format(total($usage.usersTotal))}
|
||||
{format(total($usage.users) ?? 0)}
|
||||
</div>
|
||||
<div>Users</div>
|
||||
</div>
|
||||
@@ -166,7 +165,7 @@
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
{format(total($usage.executionsTotal))}
|
||||
{format(total($usage.executions) ?? 0)}
|
||||
</div>
|
||||
<div>Executions</div>
|
||||
</div>
|
||||
|
||||
@@ -81,5 +81,5 @@
|
||||
</svelte:fragment>
|
||||
</InputSelect>
|
||||
{#if expirationSelect === 'custom'}
|
||||
<InputDateTime id="expire" label="" bind:value={expirationCustom} showLabel={false} />
|
||||
<InputDateTime required id="expire" label="" bind:value={expirationCustom} showLabel={false} />
|
||||
{/if}
|
||||
@@ -97,7 +97,8 @@
|
||||
{#if data.platforms.platforms.length}
|
||||
<div
|
||||
class="grid-box u-margin-block-start-32"
|
||||
style="--grid-gap:1.5rem; --grid-item-size:20rem;">
|
||||
style="--grid-gap:1.5rem; --grid-item-size:20rem;"
|
||||
data-private>
|
||||
{#each data.platforms.platforms as platform}
|
||||
<a class="card" href={`${path}/${platform.$id}`}>
|
||||
<div class="grid-item-1" style="min-block-size: calc(182 / 16 * 1rem)">
|
||||
|
||||
@@ -26,16 +26,7 @@
|
||||
|
||||
async function beforeSubmit() {
|
||||
if ($createPlatform.$id) {
|
||||
await sdkForConsole.projects.updatePlatform(
|
||||
projectId,
|
||||
$createPlatform.$id,
|
||||
$createPlatform.name,
|
||||
$createPlatform.key,
|
||||
$createPlatform.store,
|
||||
$createPlatform.hostname
|
||||
);
|
||||
|
||||
return;
|
||||
await sdkForConsole.projects.deletePlatform(projectId, $createPlatform.$id);
|
||||
}
|
||||
|
||||
const response = await sdkForConsole.projects.createPlatform(
|
||||
@@ -52,6 +43,7 @@
|
||||
});
|
||||
|
||||
$createPlatform.$id = response.$id;
|
||||
$createPlatform.type = platform;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+10
-12
@@ -22,7 +22,13 @@
|
||||
Web = 'flutter-web'
|
||||
}
|
||||
|
||||
let platform: Platform = Platform.Android;
|
||||
function isPlatform(value: string): value is Platform {
|
||||
return Object.values(Platform).includes(value as Platform);
|
||||
}
|
||||
|
||||
let platform: Platform = isPlatform($createPlatform.type)
|
||||
? $createPlatform.type
|
||||
: Platform.Android;
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const suggestions = ['*.vercel.app', '*.netlify.app', '*.gitpod.io'];
|
||||
@@ -81,16 +87,7 @@
|
||||
|
||||
async function beforeSubmit() {
|
||||
if ($createPlatform.$id) {
|
||||
await sdkForConsole.projects.updatePlatform(
|
||||
projectId,
|
||||
$createPlatform.$id,
|
||||
$createPlatform.name,
|
||||
$createPlatform.key,
|
||||
$createPlatform.store,
|
||||
$createPlatform.hostname
|
||||
);
|
||||
|
||||
return;
|
||||
await sdkForConsole.projects.deletePlatform(projectId, $createPlatform.$id);
|
||||
}
|
||||
|
||||
const response = await sdkForConsole.projects.createPlatform(
|
||||
@@ -107,6 +104,7 @@
|
||||
});
|
||||
|
||||
$createPlatform.$id = response.$id;
|
||||
$createPlatform.type = platform;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -136,7 +134,7 @@
|
||||
button
|
||||
on:click={() => (platform = Platform.Macos)}
|
||||
selected={platform === Platform.Macos}>
|
||||
Mac OS
|
||||
macOS
|
||||
</Pill>
|
||||
<Pill
|
||||
button
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { sdkForProject } 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,
|
||||
@@ -10,8 +11,10 @@ export const usage = cachedStore<
|
||||
}
|
||||
>('projectUsage', function ({ set }) {
|
||||
return {
|
||||
load: async (range) => {
|
||||
const usages = await sdkForProject.project.getUsage(range);
|
||||
load: async (projectId, range) => {
|
||||
const usages = isCloud
|
||||
? await sdkForProject.project.getUsage(range)
|
||||
: await sdkForConsole.projects.getUsage(projectId, range);
|
||||
set(usages);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<Modal bind:show={showDelete} on:submit={deleteDomain} warning>
|
||||
<svelte:fragment slot="header">Delete Domain</svelte:fragment>
|
||||
{#if selectedDomain}
|
||||
<p>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b>{selectedDomain.domain}</b> from '{$project.name}'?
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
required
|
||||
bind:value={$domain.domain} />
|
||||
</FormList>
|
||||
<div class="common-section ">
|
||||
<div class="common-section">
|
||||
<p>
|
||||
You can find a list of domain providers and their DNS setting documentation <a
|
||||
class="link"
|
||||
|
||||
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user