mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' of github.com:appwrite/console into feat-4840-allow-retrying-failed-builds
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITE_APPWRITE_ENDPOINT=
|
||||
VITE_APPWRITE_ENDPOINT=http://localhost/v1
|
||||
VITE_APPWRITE_GROWTH_ENDPOINT=
|
||||
VITE_GA_PROJECT=
|
||||
VITE_CONSOLE_MODE=self-hosted
|
||||
@@ -22,11 +22,11 @@ jobs:
|
||||
# run: npm audit --audit-level low
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build Console
|
||||
run: npm run build
|
||||
- name: Svelte Diagnostics
|
||||
run: npm run check
|
||||
- name: Linter
|
||||
run: npm run lint
|
||||
- name: Unit Tests
|
||||
run: npm test
|
||||
- name: Build Console
|
||||
run: npm run build
|
||||
|
||||
+34
-21
@@ -10,25 +10,30 @@ If you are worried about or don’t know where to start, check out the next sect
|
||||
|
||||
```
|
||||
├── src
|
||||
│ ├── lib // All non-route components, accessible over "import ... from '$lib'"
|
||||
│ │ ├── components // Re-usable components
|
||||
│ │ ├── elements // Re-usable elements
|
||||
│ │ ├── layout // Global components for the layout (Nav/Content/Container)
|
||||
│ │ └── stores // Global stores (state management)
|
||||
│ └─── routes
|
||||
│ ├── console // Routes that need authentication
|
||||
│ │ ├──[project]
|
||||
│ │ │ ├── database // Database Service
|
||||
│ │ │ │ ├── [collection] // Nested Route for the collection "/console/[PROJECT_ID]/database/[COLLECTION_ID]"
|
||||
│ │ │ │ ├── _create.svelte // Component to Create collections
|
||||
│ │ │ │ └── index.svelte // Entrypoint for "/console/[PROJECT_ID]/database"
|
||||
│ │ │ ├── storage // Storage Service "/console/[PROJECT]/storage"
|
||||
│ │ │ └── auth // Users Service "/console/[PROJECT]/auth"
|
||||
│ │ └──...
|
||||
│ ├── login.svelte // Component for Login "/console/login"
|
||||
│ └── register.svelte // Component for Register "/console/register"
|
||||
├── build // Compiled application
|
||||
└── static // Static assets
|
||||
│ ├── lib // Reusable logic (accessible with '$lib')
|
||||
│ │ ├── actions // Svelte actions
|
||||
│ │ ├── charts // Chart components
|
||||
│ │ ├── components // Re-usable components
|
||||
│ │ ├── elements // Re-usable elements
|
||||
│ │ ├── helpers // Small functions used through out the console
|
||||
│ │ ├── images // Images used in the console
|
||||
│ │ ├── layout // Global components for the layout (Nav/Content/Container)
|
||||
│ │ ├── mock // Mock components used for testing
|
||||
│ │ └── stores // Global stores (state management)
|
||||
│ └── routes
|
||||
│ └── console // Routes that need authentication
|
||||
│ │ └── project-[project]
|
||||
│ │ │ └── database // Database Service
|
||||
│ │ │ │ ├── +layout.svelte // Layout head and other logic like realtime events is set here
|
||||
│ │ │ │ ├── +layout.ts // Layout data is set here (Header, Breadcrumbs, ...)
|
||||
│ │ │ │ ├── +page.svelte // Page displayed on "/console/project-[PROJECT_ID]/database"
|
||||
│ │ │ │ ├── +page.ts // Necessary data for the page is fetched here
|
||||
│ │ │ │ └── create.svelte // Component to create databases
|
||||
│ │ │ └── ... // Other services
|
||||
│ │ └── ...
|
||||
│ └── ... // Routes that don't need authentication
|
||||
├── build // Compiled application
|
||||
└── static // Static assets
|
||||
```
|
||||
|
||||
## Development
|
||||
@@ -41,11 +46,19 @@ git clone https://github.com/appwrite/console.git appwrite-console
|
||||
|
||||
### 2. Install dependencies with npm
|
||||
|
||||
Navigate to the Appwrite Console repository and install dependencies.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
cd appwrite-console && npm install
|
||||
```
|
||||
|
||||
### 3. Setup environment variables
|
||||
### 3. Install and run Appwrite locally
|
||||
|
||||
When you run the Appwrite Console locally, it needs to point to a backend as well. The easiest way to do this is to run an Appwrite instance locally.
|
||||
|
||||
Follow the [install instructions](https://appwrite.io/docs/installation) in the Appwrite docs.
|
||||
|
||||
### 4. Setup environment variables
|
||||
|
||||
Add a `.env` file by copying the `.env.example` file as a template in the project's root directory.
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<div class="u-text-center">
|
||||
<Heading size="7" tag="h2">Create your first {target} to get started.</Heading>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">
|
||||
Need a hand? Check out our documentation.
|
||||
Need a hand? Learn more in our documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 u-main-center">
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
checkOverflow();
|
||||
requestAnimationFrame(checkOverflow);
|
||||
window.addEventListener('resize', checkOverflow);
|
||||
|
||||
return {
|
||||
@@ -46,22 +46,22 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="interactive-text-output is-buttons-on-top"
|
||||
style:min-inline-size="0"
|
||||
style:display="inline-flex">
|
||||
<span
|
||||
style:white-space="nowrap"
|
||||
class="text u-line-height-1-5"
|
||||
style:overflow="hidden"
|
||||
use:truncateText>
|
||||
<slot />
|
||||
</span>
|
||||
<div class="interactive-text-output-buttons">
|
||||
<Copy {value} {event}>
|
||||
<Copy {value} {event}>
|
||||
<div
|
||||
class="interactive-text-output is-buttons-on-top"
|
||||
style:min-inline-size="0"
|
||||
style:display="inline-flex">
|
||||
<span
|
||||
style:white-space="nowrap"
|
||||
class="text u-line-height-1-5"
|
||||
style:overflow="hidden"
|
||||
use:truncateText>
|
||||
<slot />
|
||||
</span>
|
||||
<div class="interactive-text-output-buttons">
|
||||
<button class="interactive-text-output-button is-hidden" aria-label="copy text">
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
</button>
|
||||
</Copy>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Copy>
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
You have no teams. Create a team to see them here.
|
||||
</p>
|
||||
<p class="text u-line-height-1-5">
|
||||
Need a hand? Check out our <a
|
||||
Need a hand? Learn more in our <a
|
||||
href="https://appwrite.io/docs/client/teams"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
You have no users. Create a user to see them here.
|
||||
</p>
|
||||
<p class="text u-line-height-1-5">
|
||||
Need a hand? Check out our <a
|
||||
Need a hand? Learn more in our <a
|
||||
href="https://appwrite.io/docs/server/users"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
bind:value
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid}
|
||||
style:--amount-of-buttons={required ? 0 : 1.75} />
|
||||
style:--amount-of-buttons={nullable && !required ? 1.75 : 0} />
|
||||
<ul
|
||||
class="buttons-list u-cross-center u-gap-8 u-position-absolute u-inset-block-start-8 u-inset-block-end-8 u-inset-inline-end-12">
|
||||
{#if nullable && !required}
|
||||
|
||||
@@ -27,15 +27,17 @@
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (element && required && !value) {
|
||||
element.setCustomValidity('This field is required');
|
||||
const isNotEmpty = (value: string | number | boolean) => {
|
||||
return typeof value === 'boolean' ? true : !!value;
|
||||
};
|
||||
|
||||
$: if (required && !isNotEmpty(value)) {
|
||||
element?.setCustomValidity('This field is required');
|
||||
} else {
|
||||
element?.setCustomValidity('');
|
||||
}
|
||||
|
||||
$: if (element && required && value) {
|
||||
element.setCustomValidity('');
|
||||
}
|
||||
|
||||
$: if (value) {
|
||||
$: if (isNotEmpty(value)) {
|
||||
error = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
value = prevValue;
|
||||
}
|
||||
}
|
||||
|
||||
$: showTextCounter = !!maxlength;
|
||||
$: showNullCheckbox = nullable && !required;
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -75,19 +78,21 @@
|
||||
class:u-padding-inline-end-56={typeof maxlength === 'number'}
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
<ul
|
||||
class="buttons-list u-cross-center u-gap-8 u-position-absolute u-inset-block-start-8 u-inset-block-end-8 u-inset-inline-end-12">
|
||||
{#if maxlength}
|
||||
<li class="buttons-list-item">
|
||||
<TextCounter max={maxlength} count={value?.length ?? 0} />
|
||||
</li>
|
||||
{/if}
|
||||
{#if nullable && !required}
|
||||
<li class="buttons-list-item">
|
||||
<NullCheckbox checked={value === null} on:change={handleNullChange} />
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{#if showTextCounter || showNullCheckbox}
|
||||
<ul
|
||||
class="buttons-list u-cross-center u-gap-8 u-position-absolute u-inset-block-start-8 u-inset-block-end-8 u-inset-inline-end-12">
|
||||
{#if showTextCounter}
|
||||
<li class="buttons-list-item">
|
||||
<TextCounter max={maxlength} count={value?.length ?? 0} />
|
||||
</li>
|
||||
{/if}
|
||||
{#if showNullCheckbox}
|
||||
<li class="buttons-list-item">
|
||||
<NullCheckbox checked={value === null} on:change={handleNullChange} />
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
value = prevValue;
|
||||
}
|
||||
}
|
||||
|
||||
$: showTextCounter = !!maxlength;
|
||||
$: showNullCheckbox = nullable && !required;
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -72,20 +75,22 @@
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid}
|
||||
style:--amount-of-buttons={required ? undefined : 0.25} />
|
||||
<ul
|
||||
class="buttons-list u-gap-8 u-cross-center u-position-absolute d u-inset-block-end-1 u-inset-inline-end-1 u-padding-block-8 u-padding-inline-12"
|
||||
style="border-end-end-radius:0.0625rem;">
|
||||
{#if maxlength}
|
||||
<li class="buttons-list-item">
|
||||
<TextCounter max={maxlength} count={value?.length ?? 0} />
|
||||
</li>
|
||||
{/if}
|
||||
{#if nullable && !required}
|
||||
<li class="buttons-list-item">
|
||||
<NullCheckbox checked={value === null} on:change={handleNullChange} />
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{#if showTextCounter || showNullCheckbox}
|
||||
<ul
|
||||
class="buttons-list u-gap-8 u-cross-center u-position-absolute d u-inset-block-end-1 u-inset-inline-end-1 u-padding-block-8 u-padding-inline-12"
|
||||
style="border-end-end-radius:0.0625rem;">
|
||||
{#if showTextCounter}
|
||||
<li class="buttons-list-item">
|
||||
<TextCounter max={maxlength} count={value?.length ?? 0} />
|
||||
</li>
|
||||
{/if}
|
||||
{#if showNullCheckbox}
|
||||
<li class="buttons-list-item">
|
||||
<NullCheckbox checked={value === null} on:change={handleNullChange} />
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper, Label } from '.';
|
||||
import NullCheckbox from './nullCheckbox.svelte';
|
||||
import TextCounter from './textCounter.svelte';
|
||||
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
@@ -9,6 +11,7 @@
|
||||
export let value = '';
|
||||
export let placeholder = '';
|
||||
export let required = false;
|
||||
export let nullable = false;
|
||||
export let disabled = false;
|
||||
export let readonly = false;
|
||||
export let autofocus = false;
|
||||
@@ -39,15 +42,20 @@
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
const handleInput = () => {
|
||||
if (value === '') {
|
||||
value = null;
|
||||
}
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
|
||||
let prevValue = '';
|
||||
function handleNullChange(e: CustomEvent<boolean>) {
|
||||
const isNull = e.detail;
|
||||
if (isNull) {
|
||||
prevValue = value;
|
||||
value = null;
|
||||
} else {
|
||||
value = prevValue;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -66,9 +74,21 @@
|
||||
type="url"
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
bind:value
|
||||
on:input={handleInput}
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
<ul
|
||||
class="buttons-list u-cross-center u-gap-8 u-position-absolute u-inset-block-start-8 u-inset-block-end-8 u-inset-inline-end-12">
|
||||
{#if maxlength}
|
||||
<li class="buttons-list-item">
|
||||
<TextCounter max={maxlength} count={value?.length ?? 0} />
|
||||
</li>
|
||||
{/if}
|
||||
{#if nullable && !required}
|
||||
<li class="buttons-list-item">
|
||||
<NullCheckbox checked={value === null} on:change={handleNullChange} />
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
|
||||
@@ -2,11 +2,28 @@
|
||||
import type { Action } from 'svelte/action';
|
||||
|
||||
export let isSticky = false;
|
||||
let isOverflowing = false;
|
||||
|
||||
const hasOverflow: Action<HTMLDivElement, (value: boolean) => void> = (node, callback) => {
|
||||
const hasOverflow: Action<HTMLDivElement> = (node) => {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
callback(entry.contentRect.width < entry.target.scrollWidth);
|
||||
let overflowing = false;
|
||||
if (entry.contentRect.width < entry.target.scrollWidth) {
|
||||
overflowing = true;
|
||||
}
|
||||
|
||||
const cols = entry.target.querySelectorAll('.table-thead-col');
|
||||
for (let i = 0; i < cols.length; i++) {
|
||||
const col = cols[i];
|
||||
const cs = getComputedStyle(col);
|
||||
const innerWidth =
|
||||
col.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight);
|
||||
if (innerWidth < 32) {
|
||||
overflowing = true;
|
||||
}
|
||||
}
|
||||
|
||||
isOverflowing = overflowing;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,12 +35,10 @@
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let isOverflowing = false;
|
||||
</script>
|
||||
|
||||
<div class="table-with-scroll u-margin-block-start-32" data-private>
|
||||
<div class="table-wrapper" use:hasOverflow={(v) => (isOverflowing = v)}>
|
||||
<div class="table-wrapper" use:hasOverflow>
|
||||
<table class="table" class:is-sticky-scroll={isSticky && isOverflowing}>
|
||||
<slot />
|
||||
</table>
|
||||
|
||||
@@ -11,6 +11,12 @@ async function securedCopy(value: string) {
|
||||
function unsecuredCopy(value: string) {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = value;
|
||||
|
||||
// Avoid scrolling to bottom
|
||||
textArea.style.top = '0';
|
||||
textArea.style.left = '0';
|
||||
textArea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && clientSecret && endpoint)}
|
||||
!(appId && clientSecret)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { InputSwitch } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { authMethods, type AuthMethod } from '$lib/stores/auth-methods';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { OAuthProviders } from '$lib/stores/oauth-providers';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { project } from '../../store';
|
||||
import { authMethods, type AuthMethod } from '$lib/stores/auth-methods';
|
||||
import { OAuthProviders } from '$lib/stores/oauth-providers';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { page } from '$app/stores';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
provider: provider.name.toLowerCase()
|
||||
});
|
||||
}}>
|
||||
<div class="image-item">
|
||||
<div class="avatar">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
try {
|
||||
await sdk.forProject.users.deleteSession($page.params.user, selectedSessionId);
|
||||
await invalidate(Dependencies.SESSIONS);
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Session has been deleted'
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { EmptySearch } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/elements/table';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import DeleteAllSessions from '../deleteAllSessions.svelte';
|
||||
@@ -47,7 +47,7 @@
|
||||
<TableRow>
|
||||
<TableCell title="Client">
|
||||
<div class="u-flex u-gap-12 u-cross-center">
|
||||
<div class="image-item">
|
||||
<div class="avatar">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
await invalidate(Dependencies.USER);
|
||||
addNotification({
|
||||
message: `${$user.name || $user.email || $user.phone || 'The account'} has been ${
|
||||
$user.emailVerification ? 'unverified' : 'verified'
|
||||
!$user.emailVerification ? 'unverified' : 'verified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@
|
||||
<div class="u-text-center">
|
||||
<Heading size="7" tag="h2">Create your first attribute to get started.</Heading>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">
|
||||
Need a hand? Check out our documentation.
|
||||
Need a hand? Learn more in our documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 u-main-center">
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@
|
||||
<div class="u-text-center">
|
||||
<Heading size="7" tag="h2">Create your first attribute to get started.</Heading>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">
|
||||
Need a hand? Check out our documentation.
|
||||
Need a hand? Learn more in our documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 u-main-center">
|
||||
|
||||
+10
-12
@@ -34,8 +34,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { InputChoice } from '$lib/elements/forms';
|
||||
import Boolean from '../document-[document]/attributes/boolean.svelte';
|
||||
import { InputChoice, InputSelect } from '$lib/elements/forms';
|
||||
|
||||
export let editing = false;
|
||||
export let data: Partial<Models.AttributeBoolean> = {
|
||||
@@ -49,18 +48,17 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Boolean
|
||||
<InputSelect
|
||||
id="default"
|
||||
label="Default value"
|
||||
bind:value={data.default}
|
||||
attribute={{
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
status: 'enabled',
|
||||
type: 'boolean',
|
||||
array: data.array
|
||||
}}
|
||||
disabled={data.array || data.required} />
|
||||
placeholder="Select a value"
|
||||
disabled={data.required || data.array}
|
||||
options={[
|
||||
{ label: 'NULL', value: null },
|
||||
{ label: 'True', value: true },
|
||||
{ label: 'False', value: false }
|
||||
]}
|
||||
bind:value={data.default} />
|
||||
<InputChoice id="required" label="Required" bind:value={data.required} disabled={data.array}>
|
||||
Indicate whether this is a required attribute
|
||||
</InputChoice>
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@
|
||||
id="default"
|
||||
label="Default value"
|
||||
bind:value={data.default}
|
||||
disabled={data.required} />
|
||||
disabled={data.required || data.array} />
|
||||
<InputChoice id="required" label="Required" bind:value={data.required} disabled={data.array}>
|
||||
Indicate whether this is a required attribute
|
||||
</InputChoice>
|
||||
|
||||
+8
-20
@@ -71,31 +71,19 @@
|
||||
</svelte:fragment>
|
||||
<FormList>
|
||||
{#if selectedAttribute?.type !== 'relationship'}
|
||||
<div>
|
||||
<InputText
|
||||
id="key"
|
||||
label="Attribute Key"
|
||||
placeholder="Enter Key"
|
||||
bind:value={selectedAttribute.key}
|
||||
autofocus
|
||||
required
|
||||
readonly />
|
||||
|
||||
<div class="u-flex u-gap-4 u-margin-block-start-8 u-small">
|
||||
<span
|
||||
class="icon-info u-cross-center u-margin-block-start-2 u-line-height-1 u-icon-small"
|
||||
aria-hidden="true" />
|
||||
<span class="text u-line-height-1-5">
|
||||
Allowed characters: alphanumeric, hyphen, non-leading underscore, period
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<InputText
|
||||
id="key"
|
||||
label="Attribute Key"
|
||||
placeholder="Enter Key"
|
||||
bind:value={selectedAttribute.key}
|
||||
autofocus
|
||||
readonly />
|
||||
{/if}
|
||||
{#if option}
|
||||
<svelte:component
|
||||
this={option.component}
|
||||
bind:data={selectedAttribute}
|
||||
editing
|
||||
bind:data={selectedAttribute}
|
||||
on:close={() => (option = null)} />
|
||||
{/if}
|
||||
</FormList>
|
||||
|
||||
+2
-1
@@ -50,7 +50,8 @@
|
||||
label="Default value"
|
||||
placeholder="Enter value"
|
||||
bind:value={data.default}
|
||||
disabled={data.required} />
|
||||
disabled={data.required || data.array}
|
||||
nullable={!data.required && !data.array} />
|
||||
<InputChoice id="required" label="Required" bind:value={data.required} disabled={data.array}>
|
||||
Indicate whether this is a required attribute
|
||||
</InputChoice>
|
||||
|
||||
+19
-13
@@ -36,8 +36,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { InputChoice, InputTags } from '$lib/elements/forms';
|
||||
import Enum from '../document-[document]/attributes/enum.svelte';
|
||||
import { InputChoice, InputSelect, InputTags } from '$lib/elements/forms';
|
||||
|
||||
export let editing = false;
|
||||
export let data: Partial<Models.AttributeEnum>;
|
||||
@@ -45,6 +44,20 @@
|
||||
$: if (data.required || data.array) {
|
||||
data.default = null;
|
||||
}
|
||||
|
||||
$: options = [
|
||||
...(data?.elements ?? []).map((element) => {
|
||||
return {
|
||||
label: element,
|
||||
value: element
|
||||
};
|
||||
}),
|
||||
!data.required &&
|
||||
!data.array && {
|
||||
label: 'NULL',
|
||||
value: null
|
||||
}
|
||||
].filter(Boolean);
|
||||
</script>
|
||||
|
||||
<InputTags
|
||||
@@ -53,19 +66,12 @@
|
||||
bind:tags={data.elements}
|
||||
placeholder="Add elements here"
|
||||
required />
|
||||
|
||||
<Enum
|
||||
<InputSelect
|
||||
id="default"
|
||||
label="Default value"
|
||||
attribute={{
|
||||
key: data.key,
|
||||
type: 'string',
|
||||
status: 'enabled',
|
||||
format: 'enum',
|
||||
elements: data.elements ?? [],
|
||||
required: data.required,
|
||||
default: data.default
|
||||
}}
|
||||
disabled={data.array || data.required}
|
||||
placeholder="Select a value"
|
||||
{options}
|
||||
bind:value={data.default} />
|
||||
<InputChoice id="required" label="Required" bind:value={data.required} disabled={data.array}>
|
||||
Indicate whether this is a required attribute
|
||||
|
||||
+15
-3
@@ -54,9 +54,20 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputNumber id="min" label="Min" placeholder="Enter size" bind:value={data.min} />
|
||||
<InputNumber id="max" label="Max" placeholder="Enter size" bind:value={data.max} />
|
||||
|
||||
<InputNumber
|
||||
id="min"
|
||||
label="Min"
|
||||
placeholder="Enter size"
|
||||
bind:value={data.min}
|
||||
step="any"
|
||||
required={editing} />
|
||||
<InputNumber
|
||||
id="max"
|
||||
label="Max"
|
||||
placeholder="Enter size"
|
||||
bind:value={data.max}
|
||||
step="any"
|
||||
required={editing} />
|
||||
<InputNumber
|
||||
id="default"
|
||||
label="Default value"
|
||||
@@ -65,6 +76,7 @@
|
||||
max={data.max}
|
||||
bind:value={data.default}
|
||||
disabled={data.required || data.array}
|
||||
nullable={!data.required && !data.array}
|
||||
step="any" />
|
||||
<InputChoice id="required" label="Required" bind:value={data.required} disabled={data.array}>
|
||||
Indicate whether this is a required attribute
|
||||
|
||||
+14
-4
@@ -54,9 +54,18 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputNumber id="min" label="Min" placeholder="Enter size" bind:value={data.min} />
|
||||
<InputNumber id="max" label="Max" placeholder="Enter size" bind:value={data.max} />
|
||||
|
||||
<InputNumber
|
||||
id="min"
|
||||
label="Min"
|
||||
placeholder="Enter size"
|
||||
bind:value={data.min}
|
||||
required={editing} />
|
||||
<InputNumber
|
||||
id="max"
|
||||
label="Max"
|
||||
placeholder="Enter size"
|
||||
bind:value={data.max}
|
||||
required={editing} />
|
||||
<InputNumber
|
||||
id="default"
|
||||
label="Default value"
|
||||
@@ -64,7 +73,8 @@
|
||||
min={data.min}
|
||||
max={data.max}
|
||||
bind:value={data.default}
|
||||
disabled={data.required || data.array} />
|
||||
disabled={data.required || data.array}
|
||||
nullable={!data.required && !data.array} />
|
||||
<InputChoice id="required" label="Required" bind:value={data.required} disabled={data.array}>
|
||||
Indicate whether this is a required attribute
|
||||
</InputChoice>
|
||||
|
||||
+2
-1
@@ -48,7 +48,8 @@
|
||||
label="Default value"
|
||||
placeholder="Enter value"
|
||||
bind:value={data.default}
|
||||
disabled={data.required || data.array} />
|
||||
disabled={data.required || data.array}
|
||||
nullable={!data.required && !data.array} />
|
||||
<InputChoice id="required" label="Required" bind:value={data.required} disabled={data.array}>
|
||||
Indicate whether this is a required attribute
|
||||
</InputChoice>
|
||||
|
||||
-2
@@ -98,7 +98,6 @@
|
||||
});
|
||||
|
||||
// Reactive statements
|
||||
|
||||
$: getCollections(search).then((res) => (collectionList = res));
|
||||
$: collections = collectionList?.collections?.filter((n) => n.$id !== $collection.$id) ?? [];
|
||||
|
||||
@@ -219,7 +218,6 @@
|
||||
placeholder="Select a relation"
|
||||
options={relationshipType}
|
||||
disabled={editing} />
|
||||
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<div class="box">
|
||||
<div class="u-flex u-align u-cross-center u-main-center u-gap-32">
|
||||
|
||||
+21
-17
@@ -34,8 +34,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { InputChoice, InputNumber } from '$lib/elements/forms';
|
||||
import String from '../document-[document]/attributes/string.svelte';
|
||||
import { InputChoice, InputNumber, InputText, InputTextarea } from '$lib/elements/forms';
|
||||
|
||||
export let data: Partial<Models.AttributeString> = {
|
||||
required: false,
|
||||
@@ -55,22 +54,27 @@
|
||||
label="Size"
|
||||
placeholder="Enter size"
|
||||
bind:value={data.size}
|
||||
required
|
||||
required={!editing}
|
||||
readonly={editing} />
|
||||
<String
|
||||
id="default"
|
||||
label="Default value"
|
||||
attribute={{
|
||||
key: 'default',
|
||||
type: 'string',
|
||||
required: data.required,
|
||||
array: data.array,
|
||||
size: data.size,
|
||||
default: data.default,
|
||||
status: 'enabled'
|
||||
}}
|
||||
disabled={data.required || data.array}
|
||||
bind:value={data.default} />
|
||||
{#if data.size >= 50}
|
||||
<InputTextarea
|
||||
id="default"
|
||||
label="Default"
|
||||
placeholder="Enter string"
|
||||
disabled={data.required || data.array}
|
||||
nullable={!data.required && !data.array}
|
||||
maxlength={data.size}
|
||||
bind:value={data.default} />
|
||||
{:else}
|
||||
<InputText
|
||||
id="default"
|
||||
label="Default"
|
||||
placeholder="Enter string"
|
||||
disabled={data.required || data.array}
|
||||
nullable={!data.required && !data.array}
|
||||
maxlength={data.size}
|
||||
bind:value={data.default} />
|
||||
{/if}
|
||||
<InputChoice id="required" label="Required" bind:value={data.required} disabled={data.array}>
|
||||
Indicate whether this is a required attribute
|
||||
</InputChoice>
|
||||
|
||||
+2
-1
@@ -49,7 +49,8 @@
|
||||
label="Default value"
|
||||
placeholder="Enter value"
|
||||
bind:value={data.default}
|
||||
disabled={data.required || data.array} />
|
||||
disabled={data.required || data.array}
|
||||
nullable={!data.required && !data.array} />
|
||||
<InputChoice id="required" label="Required" bind:value={data.required} disabled={data.array}>
|
||||
Indicate whether this is a required attribute
|
||||
</InputChoice>
|
||||
|
||||
+1
-3
@@ -7,7 +7,6 @@
|
||||
export let value: boolean;
|
||||
export let attribute: Models.AttributeBoolean;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
||||
<InputSelect
|
||||
@@ -22,5 +21,4 @@
|
||||
{ label: 'True', value: true },
|
||||
{ label: 'False', value: false }
|
||||
].filter(Boolean)}
|
||||
bind:value
|
||||
{disabled} />
|
||||
bind:value />
|
||||
|
||||
+1
-3
@@ -7,7 +7,6 @@
|
||||
export let value: string;
|
||||
export let attribute: Models.AttributeEnum;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let disabled = false;
|
||||
|
||||
$: options = [
|
||||
...attribute.elements.map((element) => {
|
||||
@@ -31,5 +30,4 @@
|
||||
{optionalText}
|
||||
required={attribute.required}
|
||||
placeholder="Select a value"
|
||||
showLabel={!!label?.length}
|
||||
{disabled} />
|
||||
showLabel={!!label?.length} />
|
||||
|
||||
+2
-2
@@ -17,5 +17,5 @@
|
||||
required={attribute.required}
|
||||
min={attribute.min}
|
||||
max={attribute.max}
|
||||
bind:value
|
||||
step={attribute.type === 'double' ? 'any' : 1} />
|
||||
step={attribute.type === 'double' ? 'any' : 1}
|
||||
bind:value />
|
||||
|
||||
+2
-2
@@ -3,12 +3,12 @@
|
||||
import { PaginationInline } from '$lib/components';
|
||||
import { SelectSearchItem } from '$lib/elements';
|
||||
import { Button, InputSelectSearch, Label } from '$lib/elements/forms';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query, type Models } from '@appwrite.io/console';
|
||||
import { onMount } from 'svelte';
|
||||
import { doc } from '../store';
|
||||
import { isRelationshipToMany } from './store';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
@@ -32,7 +32,7 @@
|
||||
onMount(async () => {
|
||||
if (value) {
|
||||
if (isRelationshipToMany(attribute)) {
|
||||
relatedList = value as string[];
|
||||
relatedList = (value as string[]).slice();
|
||||
} else {
|
||||
singleRel = value as string;
|
||||
}
|
||||
|
||||
+2
-5
@@ -7,30 +7,27 @@
|
||||
export let value: string;
|
||||
export let attribute: Models.AttributeString;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
||||
{#if attribute.size >= 50}
|
||||
<InputTextarea
|
||||
{id}
|
||||
{label}
|
||||
nullable
|
||||
nullable={!attribute.required}
|
||||
placeholder="Enter string"
|
||||
showLabel={!!label?.length}
|
||||
required={attribute.required}
|
||||
maxlength={attribute.size}
|
||||
{disabled}
|
||||
bind:value />
|
||||
{:else}
|
||||
<InputText
|
||||
{id}
|
||||
{label}
|
||||
{optionalText}
|
||||
nullable
|
||||
nullable={!attribute.required}
|
||||
placeholder="Enter string"
|
||||
showLabel={!!label?.length}
|
||||
required={attribute.required}
|
||||
maxlength={attribute.size}
|
||||
{disabled}
|
||||
bind:value />
|
||||
{/if}
|
||||
|
||||
+1
@@ -15,5 +15,6 @@
|
||||
{optionalText}
|
||||
placeholder="Enter URL"
|
||||
showLabel={!!label?.length}
|
||||
nullable={!attribute.required}
|
||||
required={attribute.required}
|
||||
bind:value />
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@
|
||||
<div class="u-text-center">
|
||||
<Heading size="7" tag="h2">Create your first attribute to get started.</Heading>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">
|
||||
Need a hand? Check out our documentation.
|
||||
Need a hand? Learn more in our documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 u-main-center">
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import { page } from '$app/stores';
|
||||
import { Id } from '$lib/components';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRowLink
|
||||
TableRowLink,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import type { PageData } from './$types';
|
||||
@@ -20,7 +20,7 @@
|
||||
const databaseId = $page.params.database;
|
||||
</script>
|
||||
|
||||
<Table>
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
{#each $columns as column}
|
||||
{#if column.show}
|
||||
@@ -54,4 +54,4 @@
|
||||
</TableRowLink>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableScroll>
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import { page } from '$app/stores';
|
||||
import { Id } from '$lib/components';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRowLink
|
||||
TableRowLink,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import type { PageData } from './$types';
|
||||
@@ -19,7 +19,7 @@
|
||||
const projectId = $page.params.project;
|
||||
</script>
|
||||
|
||||
<Table>
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
{#each $columns as column}
|
||||
{#if column.show}
|
||||
@@ -55,4 +55,4 @@
|
||||
</TableRowLink>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableScroll>
|
||||
|
||||
@@ -62,10 +62,10 @@
|
||||
function setCodeSnippets(lang: string) {
|
||||
return {
|
||||
Unix: {
|
||||
code: `appwrite functions createDeployment \\
|
||||
--functionId=${functionId} \\
|
||||
--entrypoint='index.${lang}' \\
|
||||
--code="." \\
|
||||
code: `appwrite functions createDeployment \\
|
||||
--functionId=${functionId} \\
|
||||
--entrypoint='index.${lang}' \\
|
||||
--code="." \\
|
||||
--activate=true`,
|
||||
language: 'bash'
|
||||
},
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@
|
||||
<p class="text u-line-height-1-5">
|
||||
You have no execution logs. Create and activate a deployment to see it here.
|
||||
</p>
|
||||
<p class="text u-line-height-1-5">Need a hand? Check out our documentation</p>
|
||||
<p class="text u-line-height-1-5">Need a hand? Learn more in our documentation</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16">
|
||||
<Button text external href="https://appwrite.io/docs/functions#execute">
|
||||
|
||||
@@ -40,7 +40,9 @@
|
||||
<CoverTitle href={`/console/project-${projectId}/functions`}>
|
||||
{$func?.name}
|
||||
</CoverTitle>
|
||||
<Id value={$func?.$id} event="function">Function ID</Id>
|
||||
{#if $func?.$id}
|
||||
<Id value={$func.$id} event="function">{$func.$id}</Id>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<Tabs>
|
||||
|
||||
+16
-511
@@ -1,520 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import {
|
||||
Box,
|
||||
CardGrid,
|
||||
DropList,
|
||||
DropListItem,
|
||||
Empty,
|
||||
Output,
|
||||
PaginationInline,
|
||||
Secret
|
||||
} from '$lib/components';
|
||||
import Heading from '$lib/components/heading.svelte';
|
||||
import { Roles } from '$lib/components/permissions';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputCron, InputNumber, InputText } from '$lib/elements/forms';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { Container } from '$lib/layout';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { onMount } from 'svelte';
|
||||
import Variable from '../../createVariable.svelte';
|
||||
import { execute, func } from '../store';
|
||||
import UploadVariables from './uploadVariables.svelte';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/elements/table';
|
||||
import type { PageData } from './$types';
|
||||
import Delete from './delete.svelte';
|
||||
|
||||
import UpdateEvents from './updateEvents.svelte';
|
||||
import ExecuteFunction from './executeFunction.svelte';
|
||||
import UpdateName from './updateName.svelte';
|
||||
import UpdatePermissions from './updatePermissions.svelte';
|
||||
import UpdateSchedule from './updateSchedule.svelte';
|
||||
import UpdateVariables from './updateVariables.svelte';
|
||||
import UpdateTimeout from './updateTimeout.svelte';
|
||||
import DangerZone from './dangerZone.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
const functionId = $page.params.function;
|
||||
let showDelete = false;
|
||||
let selectedVar: Models.Variable = null;
|
||||
let showVariablesUpload = false;
|
||||
let showVariablesModal = false;
|
||||
let showVariablesDropdown = [];
|
||||
let timeout: number = null;
|
||||
let functionName: string = null;
|
||||
let functionSchedule: string = null;
|
||||
let permissions: string[] = [];
|
||||
let arePermsDisabled = true;
|
||||
let offset = 0;
|
||||
|
||||
onMount(async () => {
|
||||
timeout ??= $func.timeout;
|
||||
functionName ??= $func.name;
|
||||
functionSchedule ??= $func.schedule;
|
||||
permissions = $func.execute;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
functionName,
|
||||
$func.execute || undefined,
|
||||
$func.events || undefined,
|
||||
$func.schedule || undefined,
|
||||
$func.timeout || undefined,
|
||||
$func.enabled
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.FunctionUpdateName);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.FunctionUpdateName);
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
$func.name,
|
||||
permissions,
|
||||
$func.events || undefined,
|
||||
$func.schedule || undefined,
|
||||
$func.timeout || undefined,
|
||||
$func.enabled
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
message: 'Permissions have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.FunctionUpdatePermissions);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.FunctionUpdatePermissions);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSchedule() {
|
||||
try {
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
$func.name,
|
||||
$func.execute || undefined,
|
||||
$func.events || undefined,
|
||||
functionSchedule,
|
||||
$func.timeout || undefined,
|
||||
$func.enabled
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Cron Schedule has been updated'
|
||||
});
|
||||
trackEvent(Submit.FunctionUpdateSchedule);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.FunctionUpdateSchedule);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTimeout() {
|
||||
try {
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
$func.name,
|
||||
$func.execute || undefined,
|
||||
$func.events || undefined,
|
||||
$func.schedule || undefined,
|
||||
timeout,
|
||||
$func.enabled
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Timeout has been updated'
|
||||
});
|
||||
trackEvent(Submit.FunctionUpdateTimeout);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.FunctionUpdateTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleVariableCreated(event: CustomEvent<Models.Variable>) {
|
||||
const variable = event.detail;
|
||||
|
||||
try {
|
||||
await sdk.forProject.functions.createVariable(functionId, variable.key, variable.value);
|
||||
await invalidate(Dependencies.VARIABLES);
|
||||
showVariablesModal = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$func.name} variables have been updated`
|
||||
});
|
||||
trackEvent(Submit.VariableCreate);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.VariableCreate);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleVariableUpdated(event: CustomEvent<Models.Variable>) {
|
||||
const variable = event.detail;
|
||||
try {
|
||||
await sdk.forProject.functions.updateVariable(
|
||||
functionId,
|
||||
variable.$id,
|
||||
variable.key,
|
||||
variable.value
|
||||
);
|
||||
await invalidate(Dependencies.VARIABLES);
|
||||
selectedVar = null;
|
||||
showVariablesModal = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$func.name} variables have been updated`
|
||||
});
|
||||
trackEvent(Submit.VariableUpdate);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.VariableUpdate);
|
||||
}
|
||||
}
|
||||
async function handleVariableDeleted(variable: Models.Variable) {
|
||||
try {
|
||||
await sdk.forProject.functions.deleteVariable(variable.functionId, variable.$id);
|
||||
await invalidate(Dependencies.VARIABLES);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Variable has been deleted`
|
||||
});
|
||||
trackEvent(Submit.VariableDelete);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.VariableDelete);
|
||||
}
|
||||
}
|
||||
|
||||
function downloadVariables() {
|
||||
if (data.variables.total) {
|
||||
let content = data.variables.variables
|
||||
.map((variable) => `${variable.key}=${variable.value}`)
|
||||
.join('\n');
|
||||
const file = new File([content], '.env', {
|
||||
type: 'application/x-envoy'
|
||||
});
|
||||
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(file);
|
||||
|
||||
link.href = url;
|
||||
link.download = file.name;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
$: if (permissions) {
|
||||
if (symmetricDifference(permissions, $func.execute).length) {
|
||||
arePermsDisabled = false;
|
||||
} else arePermsDisabled = true;
|
||||
}
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<CardGrid>
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16">
|
||||
<div class="avatar is-medium">
|
||||
<img
|
||||
src={`${base}/icons/${$app.themeInUse}/color/${
|
||||
$func.runtime.split('-')[0]
|
||||
}.svg`}
|
||||
alt="technology" />
|
||||
</div>
|
||||
<div>
|
||||
<Heading tag="h6" size="7">{$func.name}</Heading>
|
||||
|
||||
<p class="text u-capitalize">{$func.runtime}</p>
|
||||
</div>
|
||||
</div>
|
||||
<svelte:fragment slot="aside">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<div>
|
||||
<p>Function ID: {$func.$id}</p>
|
||||
<p>Created at: {toLocaleDateTime($func.$createdAt)}</p>
|
||||
<p>Updated at: {toLocaleDateTime($func.$updatedAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => ($execute = $func)}>Execute now</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Form onSubmit={updateName}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Name</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={functionName} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={functionName === $func.name || !functionName} submit>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form onSubmit={updatePermissions}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Execute Access</Heading>
|
||||
<p>
|
||||
Choose who can execute this function using the client API. For more information,
|
||||
check out the <a
|
||||
href="https://appwrite.io/docs/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
Permissions Guide
|
||||
</a>.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Roles bind:roles={permissions} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={arePermsDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<ExecuteFunction />
|
||||
<UpdateName />
|
||||
<UpdatePermissions />
|
||||
<UpdateEvents />
|
||||
|
||||
<Form onSubmit={updateSchedule}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Schedule</Heading>
|
||||
<p>
|
||||
Set a Cron schedule to trigger your function. Leave blank for no schedule. <a
|
||||
href="https://en.wikipedia.org/wiki/Cron"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
More details on Cron syntax here.</a>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputCron
|
||||
bind:value={functionSchedule}
|
||||
label="Schedule (Cron Syntax)"
|
||||
id="schedule" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={$func.schedule === functionSchedule} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Variables</Heading>
|
||||
<p>Set the variables (or secret keys) that will be passed to your function at runtime.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<div class="u-flex u-margin-inline-start-auto u-gap-16">
|
||||
<Button
|
||||
secondary
|
||||
event="download_env"
|
||||
disabled={!data.variables.total}
|
||||
on:click={downloadVariables}>
|
||||
<span class="icon-download" />
|
||||
<span class="text">Download .env file</span>
|
||||
</Button>
|
||||
<Button secondary on:click={() => (showVariablesUpload = true)}>
|
||||
<span class="icon-upload" />
|
||||
<span class="text">Import .env file</span>
|
||||
</Button>
|
||||
</div>
|
||||
{@const limit = 10}
|
||||
{@const sum = data.variables.total}
|
||||
{#if sum}
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Table noMargin noStyles>
|
||||
<TableHeader>
|
||||
<TableCellHead>Key</TableCellHead>
|
||||
<TableCellHead width={180}>Value</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.variables.variables.slice(offset, offset + limit) as variable, i}
|
||||
<TableRow>
|
||||
<TableCell title="key">
|
||||
<Output value={variable.key} hideCopyIcon>
|
||||
{variable.key}
|
||||
</Output>
|
||||
</TableCell>
|
||||
|
||||
<TableCell showOverflow title="value">
|
||||
<Secret copyEvent="variable" value={variable.value} />
|
||||
</TableCell>
|
||||
<TableCell showOverflow title="options">
|
||||
<DropList
|
||||
bind:show={showVariablesDropdown[i]}
|
||||
placement="bottom-start"
|
||||
noArrow>
|
||||
<Button
|
||||
text
|
||||
round
|
||||
ariaLabel="more options"
|
||||
on:click={() =>
|
||||
(showVariablesDropdown[i] =
|
||||
!showVariablesDropdown[i])}>
|
||||
<span
|
||||
class="icon-dots-horizontal"
|
||||
aria-hidden="true" />
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem
|
||||
icon="pencil"
|
||||
on:click={() => {
|
||||
selectedVar = variable;
|
||||
showVariablesDropdown[i] = false;
|
||||
showVariablesModal = true;
|
||||
}}>
|
||||
Edit
|
||||
</DropListItem>
|
||||
<DropListItem
|
||||
icon="trash"
|
||||
on:click={async () => {
|
||||
handleVariableDeleted(variable);
|
||||
showVariablesDropdown[i] = false;
|
||||
}}>
|
||||
Delete
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Button text noMargin on:click={() => (showVariablesModal = true)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Create variable</span>
|
||||
</Button>
|
||||
<div class="u-flex u-main-space-between">
|
||||
<p class="text">Total variables: {sum}</p>
|
||||
<PaginationInline {sum} {limit} bind:offset hidePages />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<Empty on:click={() => (showVariablesModal = !showVariablesModal)}>
|
||||
Create a variable to get started
|
||||
</Empty>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Form onSubmit={updateTimeout}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Timeout</Heading>
|
||||
<p>
|
||||
Limit the execution time of your function. Maximum value is 900 seconds (15
|
||||
minutes).
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={900}
|
||||
id="time"
|
||||
label="Time (in seconds)"
|
||||
bind:value={timeout} />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={$func.timeout === timeout || timeout < 1} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<CardGrid danger>
|
||||
<Heading tag="h6" size="7">Delete Function</Heading>
|
||||
<p>
|
||||
The function will be permanently deleted, including all deployments associated with it.
|
||||
This action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$func.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last Updated: {toLocaleDateTime($func.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<UpdateSchedule />
|
||||
<UpdateVariables variableList={data.variables} />
|
||||
<UpdateTimeout />
|
||||
<DangerZone />
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
{#if showVariablesModal}
|
||||
<Variable
|
||||
bind:selectedVar
|
||||
bind:showCreate={showVariablesModal}
|
||||
on:created={handleVariableCreated}
|
||||
on:updated={handleVariableUpdated} />
|
||||
{/if}
|
||||
{#if showVariablesUpload}
|
||||
<UploadVariables bind:show={showVariablesUpload} />
|
||||
{/if}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { Box, CardGrid } from '$lib/components';
|
||||
import Heading from '$lib/components/heading.svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import Delete from './deleteModal.svelte';
|
||||
|
||||
import { func } from '../store';
|
||||
|
||||
let showDelete = false;
|
||||
</script>
|
||||
|
||||
<CardGrid danger>
|
||||
<Heading tag="h6" size="7">Delete Function</Heading>
|
||||
<p>
|
||||
The function will be permanently deleted, including all deployments associated with it. This
|
||||
action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$func.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last Updated: {toLocaleDateTime($func.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { execute, func } from '../store';
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16">
|
||||
<div class="avatar is-medium">
|
||||
<img
|
||||
src={`${base}/icons/${$app.themeInUse}/color/${$func.runtime.split('-')[0]}.svg`}
|
||||
alt="technology" />
|
||||
</div>
|
||||
<div>
|
||||
<Heading tag="h6" size="7">{$func.name}</Heading>
|
||||
|
||||
<p class="text u-capitalize">{$func.runtime}</p>
|
||||
</div>
|
||||
</div>
|
||||
<svelte:fragment slot="aside">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<div>
|
||||
<p>Function ID: {$func.$id}</p>
|
||||
<p>Created at: {toLocaleDateTime($func.$createdAt)}</p>
|
||||
<p>Updated at: {toLocaleDateTime($func.$updatedAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => ($execute = $func)}>Execute now</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { func } from '../store';
|
||||
|
||||
const functionId = $page.params.function;
|
||||
let functionName: string = null;
|
||||
|
||||
onMount(async () => {
|
||||
functionName ??= $func.name;
|
||||
});
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
functionName,
|
||||
$func.execute || undefined,
|
||||
$func.events || undefined,
|
||||
$func.schedule || undefined,
|
||||
$func.timeout || undefined,
|
||||
$func.enabled
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.FunctionUpdateName);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.FunctionUpdateName);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updateName}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Name</Heading>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={functionName} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={functionName === $func.name || !functionName} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { func } from '../store';
|
||||
import { Roles } from '$lib/components/permissions';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
|
||||
const functionId = $page.params.function;
|
||||
|
||||
let arePermsDisabled = true;
|
||||
let permissions: string[] = [];
|
||||
|
||||
onMount(async () => {
|
||||
permissions = $func.execute;
|
||||
});
|
||||
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
$func.name,
|
||||
permissions,
|
||||
$func.events || undefined,
|
||||
$func.schedule || undefined,
|
||||
$func.timeout || undefined,
|
||||
$func.enabled
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
message: 'Permissions have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.FunctionUpdatePermissions);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.FunctionUpdatePermissions);
|
||||
}
|
||||
}
|
||||
|
||||
$: if (permissions) {
|
||||
if (symmetricDifference(permissions, $func.execute).length) {
|
||||
arePermsDisabled = false;
|
||||
} else arePermsDisabled = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updatePermissions}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Execute Access</Heading>
|
||||
<p>
|
||||
Choose who can execute this function using the client API. For more information, check
|
||||
out the <a
|
||||
href="https://appwrite.io/docs/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
Permissions Guide
|
||||
</a>.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Roles bind:roles={permissions} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={arePermsDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputCron } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { func } from '../store';
|
||||
|
||||
const functionId = $page.params.function;
|
||||
let functionSchedule: string = null;
|
||||
|
||||
onMount(async () => {
|
||||
functionSchedule ??= $func.schedule;
|
||||
});
|
||||
|
||||
async function updateSchedule() {
|
||||
try {
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
$func.name,
|
||||
$func.execute || undefined,
|
||||
$func.events || undefined,
|
||||
functionSchedule,
|
||||
$func.timeout || undefined,
|
||||
$func.enabled
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Cron Schedule has been updated'
|
||||
});
|
||||
trackEvent(Submit.FunctionUpdateSchedule);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.FunctionUpdateSchedule);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updateSchedule}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Schedule</Heading>
|
||||
<p>
|
||||
Set a Cron schedule to trigger your function. Leave blank for no schedule. <a
|
||||
href="https://en.wikipedia.org/wiki/Cron"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
More details on Cron syntax here.</a>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputCron
|
||||
bind:value={functionSchedule}
|
||||
label="Schedule (Cron Syntax)"
|
||||
id="schedule" />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={$func.schedule === functionSchedule} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputNumber } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { func } from '../store';
|
||||
|
||||
const functionId = $page.params.function;
|
||||
let timeout: number = null;
|
||||
|
||||
onMount(async () => {
|
||||
timeout ??= $func.timeout;
|
||||
});
|
||||
|
||||
async function updateTimeout() {
|
||||
try {
|
||||
await sdk.forProject.functions.update(
|
||||
functionId,
|
||||
$func.name,
|
||||
$func.execute || undefined,
|
||||
$func.events || undefined,
|
||||
$func.schedule || undefined,
|
||||
timeout,
|
||||
$func.enabled
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Timeout has been updated'
|
||||
});
|
||||
trackEvent(Submit.FunctionUpdateTimeout);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.FunctionUpdateTimeout);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updateTimeout}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Timeout</Heading>
|
||||
<p>Limit the execution time of your function. Maximum value is 900 seconds (15 minutes).</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={900}
|
||||
id="time"
|
||||
label="Time (in seconds)"
|
||||
bind:value={timeout} />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={$func.timeout === timeout || timeout < 1} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
<script lang="ts">
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/elements/table';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
CardGrid,
|
||||
Heading,
|
||||
DropList,
|
||||
DropListItem,
|
||||
Empty,
|
||||
Output,
|
||||
PaginationInline,
|
||||
Secret
|
||||
} from '$lib/components';
|
||||
import Variable from '../../createVariable.svelte';
|
||||
import UploadVariables from './uploadVariablesModal.svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { func } from '../store';
|
||||
|
||||
export let variableList: Models.VariableList;
|
||||
const functionId = $page.params.function;
|
||||
let showVariablesDropdown = [];
|
||||
let selectedVar: Models.Variable = null;
|
||||
let showVariablesUpload = false;
|
||||
let showVariablesModal = false;
|
||||
let offset = 0;
|
||||
|
||||
async function handleVariableCreated(event: CustomEvent<Models.Variable>) {
|
||||
const variable = event.detail;
|
||||
|
||||
try {
|
||||
await sdk.forProject.functions.createVariable(functionId, variable.key, variable.value);
|
||||
await invalidate(Dependencies.VARIABLES);
|
||||
showVariablesModal = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$func.name} variables have been updated`
|
||||
});
|
||||
trackEvent(Submit.VariableCreate);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.VariableCreate);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleVariableUpdated(event: CustomEvent<Models.Variable>) {
|
||||
const variable = event.detail;
|
||||
try {
|
||||
await sdk.forProject.functions.updateVariable(
|
||||
functionId,
|
||||
variable.$id,
|
||||
variable.key,
|
||||
variable.value
|
||||
);
|
||||
await invalidate(Dependencies.VARIABLES);
|
||||
selectedVar = null;
|
||||
showVariablesModal = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$func.name} variables have been updated`
|
||||
});
|
||||
trackEvent(Submit.VariableUpdate);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.VariableUpdate);
|
||||
}
|
||||
}
|
||||
async function handleVariableDeleted(variable: Models.Variable) {
|
||||
try {
|
||||
await sdk.forProject.functions.deleteVariable(variable.functionId, variable.$id);
|
||||
await invalidate(Dependencies.VARIABLES);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Variable has been deleted`
|
||||
});
|
||||
trackEvent(Submit.VariableDelete);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.VariableDelete);
|
||||
}
|
||||
}
|
||||
|
||||
function downloadVariables() {
|
||||
if (variableList.total) {
|
||||
let content = variableList.variables
|
||||
.map((variable) => `${variable.key}=${variable.value}`)
|
||||
.join('\n');
|
||||
const file = new File([content], '.env', {
|
||||
type: 'application/x-envoy'
|
||||
});
|
||||
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(file);
|
||||
|
||||
link.href = url;
|
||||
link.download = file.name;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Variables</Heading>
|
||||
<p>Set the variables (or secret keys) that will be passed to your function at runtime.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<div class="u-flex u-margin-inline-start-auto u-gap-16">
|
||||
<Button
|
||||
secondary
|
||||
event="download_env"
|
||||
disabled={!variableList.total}
|
||||
on:click={downloadVariables}>
|
||||
<span class="icon-download" />
|
||||
<span class="text">Download .env file</span>
|
||||
</Button>
|
||||
<Button secondary on:click={() => (showVariablesUpload = true)}>
|
||||
<span class="icon-upload" />
|
||||
<span class="text">Import .env file</span>
|
||||
</Button>
|
||||
</div>
|
||||
{@const limit = 10}
|
||||
{@const sum = variableList.total}
|
||||
{#if sum}
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Table noMargin noStyles>
|
||||
<TableHeader>
|
||||
<TableCellHead>Key</TableCellHead>
|
||||
<TableCellHead width={180}>Value</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each variableList.variables.slice(offset, offset + limit) as variable, i}
|
||||
<TableRow>
|
||||
<TableCell title="key">
|
||||
<Output value={variable.key} hideCopyIcon>
|
||||
{variable.key}
|
||||
</Output>
|
||||
</TableCell>
|
||||
|
||||
<TableCell showOverflow title="value">
|
||||
<Secret copyEvent="variable" value={variable.value} />
|
||||
</TableCell>
|
||||
<TableCell showOverflow title="options">
|
||||
<DropList
|
||||
bind:show={showVariablesDropdown[i]}
|
||||
placement="bottom-start"
|
||||
noArrow>
|
||||
<Button
|
||||
text
|
||||
round
|
||||
ariaLabel="more options"
|
||||
on:click={() =>
|
||||
(showVariablesDropdown[i] =
|
||||
!showVariablesDropdown[i])}>
|
||||
<span class="icon-dots-horizontal" aria-hidden="true" />
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem
|
||||
icon="pencil"
|
||||
on:click={() => {
|
||||
selectedVar = variable;
|
||||
showVariablesDropdown[i] = false;
|
||||
showVariablesModal = true;
|
||||
}}>
|
||||
Edit
|
||||
</DropListItem>
|
||||
<DropListItem
|
||||
icon="trash"
|
||||
on:click={async () => {
|
||||
handleVariableDeleted(variable);
|
||||
showVariablesDropdown[i] = false;
|
||||
}}>
|
||||
Delete
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Button text noMargin on:click={() => (showVariablesModal = true)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Create variable</span>
|
||||
</Button>
|
||||
<div class="u-flex u-main-space-between">
|
||||
<p class="text">Total variables: {sum}</p>
|
||||
<PaginationInline {sum} {limit} bind:offset hidePages />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<Empty on:click={() => (showVariablesModal = !showVariablesModal)}>
|
||||
Create a variable to get started
|
||||
</Empty>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
{#if showVariablesModal}
|
||||
<Variable
|
||||
bind:selectedVar
|
||||
bind:showCreate={showVariablesModal}
|
||||
on:created={handleVariableCreated}
|
||||
on:updated={handleVariableUpdated} />
|
||||
{/if}
|
||||
{#if showVariablesUpload}
|
||||
<UploadVariables bind:show={showVariablesUpload} />
|
||||
{/if}
|
||||
@@ -10,6 +10,8 @@ export const load: PageLoad = async ({ params, depends }) => {
|
||||
const key = await sdk.forConsole.projects.getKey(params.project, params.key);
|
||||
if (key.expire) {
|
||||
key.expire = new Date(key.expire).toISOString().slice(0, 23);
|
||||
} else {
|
||||
key.expire = undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
<div class="u-text-center">
|
||||
<Heading size="7" tag="h4">Create your first platform to get started.</Heading>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">
|
||||
Need a hand? Check out our documentation.
|
||||
Need a hand? Learn more in our documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 u-main-center">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
const example1 = `dependencies:
|
||||
appwrite: ^${$versions['client-flutter']}`;
|
||||
const example2 = `pub get appwrite`;
|
||||
const example2 = `flutter pub add appwrite`;
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
|
||||
@@ -35,8 +35,12 @@
|
||||
</svelte:fragment>
|
||||
{#if method === Method.NPM}
|
||||
<p>
|
||||
Use <a href="https://npmjs.org" target="_blank" rel="noopener noreferrer" class="link"
|
||||
>NPM (node package manager)</a> from your command line to add Appwrite SDK to your project.
|
||||
Use <a
|
||||
href="https://npmjs.com/package/appwrite"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">NPM (node package manager)</a> from your command line to add Appwrite SDK
|
||||
to your project.
|
||||
</p>
|
||||
<Code label="Bash" language="sh" code="npm install appwrite" withCopy />
|
||||
<p class="common-section">
|
||||
|
||||
@@ -115,7 +115,10 @@
|
||||
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Services</Heading>
|
||||
<p class="text">Choose services you wish to enable or disable.</p>
|
||||
<p class="text">
|
||||
Choose services you wish to enable or disable for the client API. When disabled, the
|
||||
services are not accessible to client SDKs but remain accessible to server SDKs.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<form class="form">
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableRowLink,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText,
|
||||
TableHeader
|
||||
TableHeader,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
@@ -46,10 +46,10 @@
|
||||
</div>
|
||||
|
||||
{#if data.webhooks.total}
|
||||
<Table>
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>POST URL</TableCellHead>
|
||||
<TableCellHead width={200}>Name</TableCellHead>
|
||||
<TableCellHead width={180}>POST URL</TableCellHead>
|
||||
<TableCellHead width={80}>Events</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -57,7 +57,7 @@
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${projectId}/settings/webhooks/${webhook.$id}`}>
|
||||
<TableCell title="Name">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<div class="u-flex u-main-space-between u-cross-center">
|
||||
{webhook.name}
|
||||
{#if webhook.security === false}
|
||||
<Pill>SLL/TLS disabled</Pill>
|
||||
@@ -69,7 +69,7 @@
|
||||
</TableRowLink>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableScroll>
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
|
||||
@@ -34,7 +34,9 @@
|
||||
<CoverTitle href={`/console/project-${projectId}/storage`}>
|
||||
{$bucket?.name}
|
||||
</CoverTitle>
|
||||
<Id value={$bucket?.$id} event="bucket">Bucket ID</Id>
|
||||
{#if $bucket?.$id}
|
||||
<Id value={$bucket.$id} event="bucket">{$bucket.$id}</Id>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<Tabs>
|
||||
|
||||
Reference in New Issue
Block a user