mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' into usability-internal
This commit is contained in:
Generated
+14
-14
@@ -9,8 +9,8 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@aw-labs/appwrite-console": "^6.0.0",
|
||||
"@aw-labs/icons": "0.0.0-57",
|
||||
"@aw-labs/ui": "0.0.0-57",
|
||||
"@aw-labs/icons": "0.0.0-58",
|
||||
"@aw-labs/ui": "0.0.0-58",
|
||||
"echarts": "^5.4.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"web-vitals": "^2.1.4"
|
||||
@@ -77,14 +77,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aw-labs/icons": {
|
||||
"version": "0.0.0-57",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-57.tgz",
|
||||
"integrity": "sha512-HmXSTSP3GEBi5awFk0APTTNdM10DDIBCTnxi3BJGlsXo+SXnpVikoMk6RZltNLH7MRfPQNx+VfJ2bOC+hlW8ZQ=="
|
||||
"version": "0.0.0-58",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-58.tgz",
|
||||
"integrity": "sha512-xUD5DQcYVNiKhDSpxMO24G/4l7txgAdwochK01tJA6wXhuPAcp5Yjuofjm2bGkMzCuze4Vs+SVvhsBxzVXRPrA=="
|
||||
},
|
||||
"node_modules/@aw-labs/ui": {
|
||||
"version": "0.0.0-57",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-57.tgz",
|
||||
"integrity": "sha512-a/nKqu9nHysTF7bIOLo+ZN1zKpCHfv+jHGixU0SGXchTNAmj2OXClZNk3XfMmwnPD6CiF4cG6YRdbmpx+TTXgA==",
|
||||
"version": "0.0.0-58",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-58.tgz",
|
||||
"integrity": "sha512-W3bTvAPX4ig5qLBvGn/VpNaDk62RqO4oDa0poube2WuElkVvaGwFAR0wCwmisdi2NeXyGJqt20g3Y/Mz4K2yMA==",
|
||||
"dependencies": {
|
||||
"@aw-labs/icons": "*"
|
||||
}
|
||||
@@ -8134,14 +8134,14 @@
|
||||
}
|
||||
},
|
||||
"@aw-labs/icons": {
|
||||
"version": "0.0.0-57",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-57.tgz",
|
||||
"integrity": "sha512-HmXSTSP3GEBi5awFk0APTTNdM10DDIBCTnxi3BJGlsXo+SXnpVikoMk6RZltNLH7MRfPQNx+VfJ2bOC+hlW8ZQ=="
|
||||
"version": "0.0.0-58",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/icons/-/icons-0.0.0-58.tgz",
|
||||
"integrity": "sha512-xUD5DQcYVNiKhDSpxMO24G/4l7txgAdwochK01tJA6wXhuPAcp5Yjuofjm2bGkMzCuze4Vs+SVvhsBxzVXRPrA=="
|
||||
},
|
||||
"@aw-labs/ui": {
|
||||
"version": "0.0.0-57",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-57.tgz",
|
||||
"integrity": "sha512-a/nKqu9nHysTF7bIOLo+ZN1zKpCHfv+jHGixU0SGXchTNAmj2OXClZNk3XfMmwnPD6CiF4cG6YRdbmpx+TTXgA==",
|
||||
"version": "0.0.0-58",
|
||||
"resolved": "https://registry.npmjs.org/@aw-labs/ui/-/ui-0.0.0-58.tgz",
|
||||
"integrity": "sha512-W3bTvAPX4ig5qLBvGn/VpNaDk62RqO4oDa0poube2WuElkVvaGwFAR0wCwmisdi2NeXyGJqt20g3Y/Mz4K2yMA==",
|
||||
"requires": {
|
||||
"@aw-labs/icons": "*"
|
||||
}
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@aw-labs/appwrite-console": "^6.0.0",
|
||||
"@aw-labs/icons": "0.0.0-57",
|
||||
"@aw-labs/ui": "0.0.0-57",
|
||||
"@aw-labs/icons": "0.0.0-58",
|
||||
"@aw-labs/ui": "0.0.0-58",
|
||||
"echarts": "^5.4.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"web-vitals": "^2.1.4"
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
<ul class="collapsible">
|
||||
<li class="collapsible-item">
|
||||
<details class="collapsible-wrapper">
|
||||
<summary class="collapsible-button">
|
||||
<span class="text">
|
||||
<slot name="header" />
|
||||
</span>
|
||||
<span class="collapsible-button-optional">
|
||||
<slot name="subheader" />
|
||||
</span>
|
||||
<div class="icon">
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="icon-minus" aria-hidden="true" />
|
||||
</div>
|
||||
</summary>
|
||||
<p class="collapsible-content">
|
||||
<slot />
|
||||
</p>
|
||||
</details>
|
||||
</li>
|
||||
<slot />
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<li class="collapsible-item">
|
||||
<details class="collapsible-wrapper">
|
||||
<summary class="collapsible-button">
|
||||
<span class="text"><slot name="title" /></span>
|
||||
<span class="collapsible-button-optional"><slot name="subtitle" /></span>
|
||||
<div class="icon">
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</div>
|
||||
</summary>
|
||||
<div class="collapsible-content">
|
||||
<slot />
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
@@ -16,6 +16,7 @@ export { default as DropList } from './dropList.svelte';
|
||||
export { default as DropListItem } from './dropListItem.svelte';
|
||||
export { default as DropListLink } from './dropListLink.svelte';
|
||||
export { default as Collapsible } from './collapsible.svelte';
|
||||
export { default as CollapsibleItem } from './collapsibleItem.svelte';
|
||||
export { default as DropTabs } from './dropTabs.svelte';
|
||||
export { default as DropTabsItem } from './dropTabsItem.svelte';
|
||||
export { default as Avatar } from './avatar.svelte';
|
||||
@@ -27,3 +28,4 @@ export { default as GridItem1 } from './gridItem1.svelte';
|
||||
export { default as Steps } from './steps.svelte';
|
||||
export { default as Step } from './step.svelte';
|
||||
export { default as CustomId } from './customId.svelte';
|
||||
export { default as Secret } from './secret.svelte';
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { Copy } from '.';
|
||||
|
||||
export let show = false;
|
||||
export let value: string;
|
||||
</script>
|
||||
|
||||
<div class="interactive-text-output" class:is-textarea={show}>
|
||||
{#if show}
|
||||
<span class="text u-line-height-1-5 u-break-word">{value}</span>
|
||||
{:else}
|
||||
<span class="text">••••••••••••</span>
|
||||
{/if}
|
||||
<div class="u-flex u-cross-child-start u-gap-8">
|
||||
<button
|
||||
class="interactive-text-output-button"
|
||||
aria-label="show hidden text"
|
||||
type="button"
|
||||
on:click={() => (show = !show)}>
|
||||
<span class:icon-eye-off={show} class:icon-eye={!show} aria-hidden="true" />
|
||||
</button>
|
||||
<Copy {value}>
|
||||
<button class="interactive-text-output-button" aria-label="copy text" type="button">
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
</button>
|
||||
</Copy>
|
||||
</div>
|
||||
</div>
|
||||
+128
-78
@@ -1,80 +1,130 @@
|
||||
export const scopes = [
|
||||
'users.read',
|
||||
'users.write',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
'collections.read',
|
||||
'collections.write',
|
||||
'attributes.read',
|
||||
'attributes.write',
|
||||
'indexes.read',
|
||||
'indexes.write',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'files.read',
|
||||
'files.write',
|
||||
'buckets.read',
|
||||
'buckets.write',
|
||||
'functions.read',
|
||||
'functions.write',
|
||||
'execution.read',
|
||||
'execution.write',
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'health.read'
|
||||
];
|
||||
|
||||
export const events = [
|
||||
'account.create',
|
||||
'account.update.email',
|
||||
'account.update.name',
|
||||
'account.update.password',
|
||||
'users.update.email',
|
||||
'users.update.name',
|
||||
'users.update.password',
|
||||
'account.update.prefs',
|
||||
'account.recovery.create',
|
||||
'account.recovery.update',
|
||||
'account.verification.create',
|
||||
'account.verification.update',
|
||||
'account.delete',
|
||||
'account.sessions.create',
|
||||
'account.sessions.delete',
|
||||
'account.sessions.update',
|
||||
'database.collections.create',
|
||||
'database.collections.update',
|
||||
'database.collections.delete',
|
||||
'database.attributes.create',
|
||||
'database.attributes.delete',
|
||||
'database.indexes.create',
|
||||
'database.indexes.delete',
|
||||
'database.documents.create',
|
||||
'database.documents.update',
|
||||
'database.documents.delete',
|
||||
'functions.create',
|
||||
'functions.update',
|
||||
'functions.delete',
|
||||
'functions.deployments.create',
|
||||
'functions.deployments.update',
|
||||
'functions.deployments.delete',
|
||||
'functions.executions.create',
|
||||
'functions.executions.update',
|
||||
'storage.files.create',
|
||||
'storage.files.update',
|
||||
'storage.files.delete',
|
||||
'storage.buckets.create',
|
||||
'storage.buckets.update',
|
||||
'storage.buckets.delete',
|
||||
'users.create',
|
||||
'users.update.prefs',
|
||||
'users.update.status',
|
||||
'users.delete',
|
||||
'users.sessions.delete',
|
||||
'teams.create',
|
||||
'teams.update',
|
||||
'teams.delete',
|
||||
'teams.memberships.create',
|
||||
'teams.memberships.update',
|
||||
'teams.memberships.update.status',
|
||||
'teams.memberships.delete'
|
||||
{
|
||||
scope: 'users.read',
|
||||
description: "Access to read your project's users",
|
||||
category: 'Authentication'
|
||||
},
|
||||
{
|
||||
scope: 'users.write',
|
||||
description: "Access to create, update, and delete your project's users",
|
||||
category: 'Authentication'
|
||||
},
|
||||
{
|
||||
scope: 'teams.read',
|
||||
description: "Access to read your project's teams",
|
||||
category: 'Authentication'
|
||||
},
|
||||
{
|
||||
scope: 'teams.write',
|
||||
description: "Access to create, update, and delete your project's teams",
|
||||
category: 'Authentication'
|
||||
},
|
||||
{
|
||||
scope: 'databases.read',
|
||||
description: "Access to read your project's databases",
|
||||
category: 'Database'
|
||||
},
|
||||
{
|
||||
scope: 'databases.write',
|
||||
description: "Access to create, update, and delete your project's databases",
|
||||
category: 'Database'
|
||||
},
|
||||
{
|
||||
scope: 'collections.read',
|
||||
description: "Access to read your project's database collections",
|
||||
category: 'Database'
|
||||
},
|
||||
{
|
||||
scope: 'collections.write',
|
||||
description: "Access to create, update, and delete your project's database collections",
|
||||
category: 'Database'
|
||||
},
|
||||
{
|
||||
scope: 'attributes.read',
|
||||
description: "Access to read your project's database collection's attributes",
|
||||
category: 'Database'
|
||||
},
|
||||
{
|
||||
scope: 'attributes.write',
|
||||
description:
|
||||
"Access to create, update, and delete your project's database collection's attributes",
|
||||
category: 'Database'
|
||||
},
|
||||
{
|
||||
scope: 'indexes.read',
|
||||
description: "Access to read your project's database collection's indexes",
|
||||
category: 'Database'
|
||||
},
|
||||
{
|
||||
scope: 'indexes.write',
|
||||
description:
|
||||
"Access to create, update, and delete your project's database collection's indexes",
|
||||
category: 'Database'
|
||||
},
|
||||
{
|
||||
scope: 'documents.read',
|
||||
description: "Access to read your project's database documents",
|
||||
category: 'Database'
|
||||
},
|
||||
{
|
||||
scope: 'documents.write',
|
||||
description: "Access to create, update, and delete your project's database documents",
|
||||
category: 'Database'
|
||||
},
|
||||
{
|
||||
scope: 'files.read',
|
||||
description: "Access to read your project's storage files and preview images",
|
||||
category: 'Storage'
|
||||
},
|
||||
{
|
||||
scope: 'files.write',
|
||||
description: "Access to create, update, and delete your project's storage files",
|
||||
category: 'Storage'
|
||||
},
|
||||
{
|
||||
scope: 'buckets.read',
|
||||
description: "Access to read your project's storage buckets",
|
||||
category: 'Storage'
|
||||
},
|
||||
{
|
||||
scope: 'buckets.write',
|
||||
description: "Access to create, update, and delete your project's storage buckets",
|
||||
category: 'Storage'
|
||||
},
|
||||
{
|
||||
scope: 'functions.read',
|
||||
description: "Access to read your project's functions and code deployments",
|
||||
category: 'Functions'
|
||||
},
|
||||
{
|
||||
scope: 'functions.write',
|
||||
description:
|
||||
"Access to create, update, and delete your project's functions and code deployments",
|
||||
category: 'Functions'
|
||||
},
|
||||
{
|
||||
scope: 'execution.read',
|
||||
description: "Access to read your project's execution logs",
|
||||
category: 'Functions'
|
||||
},
|
||||
{
|
||||
scope: 'execution.write',
|
||||
description: "Access to execute your project's functions",
|
||||
category: 'Functions'
|
||||
},
|
||||
{
|
||||
scope: 'locale.read',
|
||||
description: "Access to access your project's Locale service",
|
||||
category: 'Other'
|
||||
},
|
||||
{
|
||||
scope: 'avatars.read',
|
||||
description: "Access to access your project's Avatars service",
|
||||
category: 'Other'
|
||||
},
|
||||
{
|
||||
scope: 'health.read',
|
||||
description: "Access to read your project's health status",
|
||||
category: 'Other'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -11,6 +11,7 @@ export { default as InputSwitch } from './inputSwitch.svelte';
|
||||
export { default as InputTags } from './inputTags.svelte';
|
||||
export { default as InputFile } from './inputFile.svelte';
|
||||
export { default as InputCustomId } from './inputCustomId.svelte';
|
||||
export { default as InputDateTime } from './inputDateTime.svelte';
|
||||
export { default as InputSearch } from './inputSearch.svelte';
|
||||
export { default as InputRadio } from './inputRadio.svelte';
|
||||
export { default as InputSelect } from './inputSelect.svelte';
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
export let id: string;
|
||||
export let value = '';
|
||||
export let required = false;
|
||||
export let disabled = false;
|
||||
export let readonly = false;
|
||||
export let autofocus = false;
|
||||
export let autocomplete = false;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
onMount(() => {
|
||||
if (element && autofocus) {
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
{id}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{required}
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
type="datetime-local"
|
||||
class="input-text"
|
||||
bind:value
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
@@ -1,22 +0,0 @@
|
||||
<script>
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { updateLayout } from '$lib/stores/layout';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(handle);
|
||||
afterNavigate(handle);
|
||||
|
||||
function handle(event = null) {
|
||||
updateLayout({
|
||||
navigate: event,
|
||||
title: 'API Keys',
|
||||
level: 3,
|
||||
breadcrumbs: {
|
||||
href: 'keys',
|
||||
title: 'API Keys'
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
@@ -1,62 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { Empty } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCellHead,
|
||||
TableCellLink,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { project } from '../store';
|
||||
import Create from './_create.svelte';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
let showCreate = false;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Appwrite - API Keys</title>
|
||||
</svelte:head>
|
||||
<Container>
|
||||
{#if $project}
|
||||
{#if $project.keys}
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Scopes</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each $project.keys as key}
|
||||
<TableRow>
|
||||
<TableCellLink
|
||||
href={`${base}/console/project-${projectId}/keys/key/${key.$id}`}
|
||||
title="Name">
|
||||
{key.name}
|
||||
</TableCellLink>
|
||||
<TableCellText title="Scopes">{key.scopes.length}</TableCellText>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{:else}
|
||||
<Empty>
|
||||
<div class="u-flex u-flex-vertical">
|
||||
<div class="common-section">No API Keys Found</div>
|
||||
<div class="common-section">
|
||||
You haven't created any API keys for your project yet.
|
||||
</div>
|
||||
</div>
|
||||
</Empty>
|
||||
{/if}
|
||||
<Button on:click={() => (showCreate = true)}>Add API Key</Button>
|
||||
{/if}
|
||||
</Container>
|
||||
<Create bind:show={showCreate} />
|
||||
@@ -1,69 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { InputText, Button, Form, InputCheckbox } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { project } from '../store';
|
||||
import { scopes } from '$lib/constants';
|
||||
export let show = false;
|
||||
|
||||
let name: string;
|
||||
const activeScopes = scopes.reduce((prev, next) => {
|
||||
prev[next] = false;
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
const create = async () => {
|
||||
try {
|
||||
await sdkForConsole.projects.createKey(
|
||||
$project.$id,
|
||||
name,
|
||||
scopes.filter((scope) => activeScopes[scope])
|
||||
);
|
||||
name = null;
|
||||
for (const scope in activeScopes) {
|
||||
activeScopes[scope] = false;
|
||||
}
|
||||
|
||||
await project.load($project.$id);
|
||||
show = false;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const selectAll = () => {
|
||||
for (const scope in activeScopes) {
|
||||
activeScopes[scope] = true;
|
||||
}
|
||||
};
|
||||
|
||||
const unselectAll = () => {
|
||||
for (const scope in activeScopes) {
|
||||
activeScopes[scope] = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={create}>
|
||||
<Modal bind:show>
|
||||
<svelte:fragment slot="header">Add API Key</svelte:fragment>
|
||||
<p>
|
||||
<span class="link" on:click={selectAll}>Select All</span><span
|
||||
class="link"
|
||||
on:click={unselectAll}>Unselect All</span>
|
||||
</p>
|
||||
<InputText id="name" label="Name" bind:value={name} required />
|
||||
{#each scopes as scope}
|
||||
<InputCheckbox id={scope} label={scope} bind:value={activeScopes[scope]} />
|
||||
{/each}
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit>Create</Button>
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -1,35 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Card } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { project } from '../../../store';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const keyId = $page.params.key;
|
||||
const request = sdkForConsole.projects.getKey(projectId, keyId);
|
||||
const deleteKey = async () => {
|
||||
await sdkForConsole.projects.deleteKey(projectId, keyId);
|
||||
addNotification({
|
||||
message: 'API key deleted.',
|
||||
type: 'success'
|
||||
});
|
||||
project.load(projectId);
|
||||
await goto(`${base}/console/project-${projectId}/keys`);
|
||||
};
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Card>
|
||||
{#await request}
|
||||
loading
|
||||
{:then response}
|
||||
<p>{response.name}</p>
|
||||
<Button danger on:click={deleteKey}>Delete</Button>
|
||||
{/await}
|
||||
</Card>
|
||||
</Container>
|
||||
@@ -276,7 +276,7 @@
|
||||
</div>
|
||||
<div
|
||||
class="card is-2-columns-medium-screen is-2-columns-large-screen is-2-rows-large-screen is-location-row-2-end-large-screen">
|
||||
<div class="heading-level-4">10</div>
|
||||
<div class="heading-level-4">XX</div>
|
||||
<div>Realtime Connections</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
{key.name}
|
||||
</TableCellText>
|
||||
<TableCellText title="Last Accessed">
|
||||
{toLocaleDateTime(key.$createdAt)}
|
||||
{key.accessedAt ? toLocaleDateTime(key.accessedAt) : 'never'}
|
||||
</TableCellText>
|
||||
<TableCellText title="Expiration Date">
|
||||
{toLocaleDateTime(key.$updatedAt)}
|
||||
|
||||
+22
-60
@@ -2,16 +2,10 @@
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { scopes } from '$lib/constants';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormList,
|
||||
InputCheckbox,
|
||||
InputPassword,
|
||||
InputText
|
||||
} from '$lib/elements/forms';
|
||||
import { CardGrid, Secret } from '$lib/components';
|
||||
import { Button, Form, FormList, InputText } from '$lib/elements/forms';
|
||||
import InputDateTime from '$lib/elements/forms/inputDateTime.svelte';
|
||||
import { difference } from '$lib/helpers/array';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { Container } from '$lib/layout';
|
||||
import { updateLayout } from '$lib/stores/layout';
|
||||
@@ -19,38 +13,30 @@
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { project } from '../../../store';
|
||||
import Scopes from '../scopes.svelte';
|
||||
import Delete from './delete.svelte';
|
||||
import { key } from './store';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const keyId = $page.params.key;
|
||||
const activeScopes = scopes.reduce((prev, next) => {
|
||||
prev[next] = false;
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
let loaded = false;
|
||||
let showDelete = false;
|
||||
let name: string = null;
|
||||
let secret: string = null;
|
||||
let expire: string = null;
|
||||
let scopes: string[] = null;
|
||||
|
||||
onMount(handle);
|
||||
afterNavigate(handle);
|
||||
|
||||
async function handle(event = null) {
|
||||
if ($key?.$id !== keyId) {
|
||||
await key.load(projectId, keyId);
|
||||
}
|
||||
await key.load(projectId, keyId);
|
||||
|
||||
name ??= $key.name;
|
||||
secret ??= $key.secret;
|
||||
expire ??= $key.expire;
|
||||
unselectAll();
|
||||
$key.scopes.forEach((scope) => {
|
||||
activeScopes[scope] = true;
|
||||
});
|
||||
scopes ??= $key.scopes;
|
||||
|
||||
updateLayout({
|
||||
navigate: event,
|
||||
@@ -117,12 +103,8 @@
|
||||
|
||||
async function updateScopes() {
|
||||
try {
|
||||
await sdkForConsole.projects.updateKey(
|
||||
$project.$id,
|
||||
$key.$id,
|
||||
$key.name,
|
||||
scopes.filter((scope) => activeScopes[scope])
|
||||
);
|
||||
await sdkForConsole.projects.updateKey($project.$id, $key.$id, $key.name, scopes);
|
||||
$key.scopes = scopes;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'API Key scopes has been updated'
|
||||
@@ -134,12 +116,6 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function unselectAll() {
|
||||
for (const scope in activeScopes) {
|
||||
activeScopes[scope] = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -148,13 +124,14 @@
|
||||
|
||||
<Container>
|
||||
{#if loaded}
|
||||
{@const accessedAt = $key.accessedAt ? toLocaleDateTime($key.accessedAt) : 'never'}
|
||||
<CardGrid>
|
||||
<div>
|
||||
<h6 class="heading-level-7">{$key.name}</h6>
|
||||
</div>
|
||||
<svelte:fragment slot="aside">
|
||||
<p>
|
||||
Last accessed: {toLocaleDateTime($key.$updatedAt)}<br />
|
||||
Last accessed: {accessedAt}<br />
|
||||
Scopes granted: {$key.scopes.length}
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
@@ -164,18 +141,9 @@
|
||||
<h6 class="heading-level-7">API Key Secret</h6>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="API Key Secret"
|
||||
bind:value={secret}
|
||||
showPasswordButton
|
||||
required />
|
||||
<Secret bind:value={secret} />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
<Form on:submit={updateName}>
|
||||
@@ -206,18 +174,16 @@
|
||||
practice to allow only the permissions you need to meet your project goals.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
{#each scopes as scope}
|
||||
<InputCheckbox
|
||||
id={scope}
|
||||
label={scope}
|
||||
bind:value={activeScopes[scope]} />
|
||||
{/each}
|
||||
</FormList>
|
||||
<Scopes bind:scopes />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button submit>Update</Button>
|
||||
<Button
|
||||
submit
|
||||
disabled={!(
|
||||
difference(scopes, $key.scopes).length !== 0 ||
|
||||
difference($key.scopes, scopes).length !== 0
|
||||
)}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -227,11 +193,7 @@
|
||||
<p class="text">Choose any name that will help you distinguish between API keys.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputText
|
||||
id="expire"
|
||||
label="Expiration Date"
|
||||
bind:value={expire}
|
||||
required />
|
||||
<InputDateTime id="expire" label="Expiration Date" bind:value={expire} />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -250,7 +212,7 @@
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="u-cross-child-center u-line-height-1-5">
|
||||
<h6 class="u-bold">{$key.name}</h6>
|
||||
<p>Last accessed: {toLocaleDateTime($key.$updatedAt)}</p>
|
||||
<p>Last accessed: {accessedAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { scopes } from '$lib/constants';
|
||||
import { InputText, Button, Form, FormList, InputCheckbox } from '$lib/elements/forms';
|
||||
import { InputText, Button, Form, FormList } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { project } from '../../.../../store';
|
||||
import Scopes from './scopes.svelte';
|
||||
|
||||
export let show = false;
|
||||
|
||||
const activeScopes = scopes.reduce((prev, next) => {
|
||||
prev[next] = false;
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
let name: string;
|
||||
let scopes: string[] = [];
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
await sdkForConsole.projects.createKey(
|
||||
$project.$id,
|
||||
name,
|
||||
scopes.filter((scope) => activeScopes[scope])
|
||||
);
|
||||
await sdkForConsole.projects.createKey($project.$id, name, scopes);
|
||||
name = null;
|
||||
for (const scope in activeScopes) {
|
||||
activeScopes[scope] = false;
|
||||
}
|
||||
scopes = [];
|
||||
project.load($project.$id);
|
||||
show = false;
|
||||
} catch (error) {
|
||||
@@ -39,13 +28,11 @@
|
||||
</script>
|
||||
|
||||
<Form on:submit={create}>
|
||||
<Modal bind:show>
|
||||
<Modal bind:show size="big">
|
||||
<svelte:fragment slot="header">Create API Key</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText id="name" label="Name" bind:value={name} autofocus required />
|
||||
{#each scopes as scope}
|
||||
<InputCheckbox id={scope} label={scope} bind:value={activeScopes[scope]} />
|
||||
{/each}
|
||||
<Scopes bind:scopes />
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit>Register</Button>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible, CollapsibleItem } from '$lib/components';
|
||||
import { Button, FormList, InputChoice } from '$lib/elements/forms';
|
||||
import { scopes as allScopes } from '$lib/constants';
|
||||
import { onMount } from 'svelte';
|
||||
import { difference } from '$lib/helpers/array';
|
||||
|
||||
export let scopes: string[];
|
||||
|
||||
let mounted = false;
|
||||
|
||||
onMount(() => {
|
||||
scopes.forEach((scope) => {
|
||||
activeScopes[scope] = true;
|
||||
});
|
||||
mounted = true;
|
||||
});
|
||||
|
||||
function selectAll() {
|
||||
for (const scope in activeScopes) {
|
||||
activeScopes[scope] = true;
|
||||
}
|
||||
}
|
||||
|
||||
function unselectAll() {
|
||||
for (const scope in activeScopes) {
|
||||
activeScopes[scope] = false;
|
||||
}
|
||||
}
|
||||
|
||||
const activeScopes = allScopes.reduce((prev, next) => {
|
||||
prev[next.scope] = false;
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
$: {
|
||||
if (mounted) {
|
||||
const newScopes = allScopes
|
||||
.filter((scope) => activeScopes[scope.scope])
|
||||
.map(({ scope }) => scope);
|
||||
if (
|
||||
difference(scopes, newScopes).length !== 0 ||
|
||||
difference(newScopes, scopes).length !== 0
|
||||
) {
|
||||
scopes = newScopes;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="u-flex u-cross-center u-main-end">
|
||||
<Button text on:click={unselectAll}>Unselect all</Button>
|
||||
<Button text on:click={selectAll}>Select all</Button>
|
||||
</div>
|
||||
<Collapsible>
|
||||
{#each ['Authentication', 'Database', 'Functions', 'Storage', 'Other'] as category}
|
||||
<CollapsibleItem>
|
||||
<svelte:fragment slot="title">{category}</svelte:fragment>
|
||||
<FormList>
|
||||
{#each allScopes.filter((s) => s.category === category) as scope}
|
||||
<InputChoice
|
||||
id={scope.scope}
|
||||
label={scope.scope}
|
||||
bind:value={activeScopes[scope.scope]}>
|
||||
{scope.description}
|
||||
</InputChoice>
|
||||
{/each}
|
||||
</FormList>
|
||||
</CollapsibleItem>
|
||||
{/each}
|
||||
</Collapsible>
|
||||
@@ -0,0 +1,48 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { vi } from 'vitest';
|
||||
import { render, fireEvent } from '@testing-library/svelte';
|
||||
import { Secret } from '../../../src/lib/components';
|
||||
|
||||
const value = 'This is a secret';
|
||||
|
||||
test('shows Secret component', () => {
|
||||
const { container } = render(Secret, { value });
|
||||
const secret = container.querySelector('span.text');
|
||||
const toggle = container.querySelector('[aria-label="show hidden text"]');
|
||||
const copy = container.querySelector('[aria-label="copy text"]');
|
||||
|
||||
expect(secret).toBeInTheDocument();
|
||||
expect(toggle).toBeInTheDocument();
|
||||
expect(copy).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('toggle secret', async () => {
|
||||
const { container } = render(Secret, { value });
|
||||
const toggle = container.querySelector('[aria-label="show hidden text"]');
|
||||
|
||||
let secret = container.querySelector('span.text');
|
||||
expect(secret).not.toContainEqual(value);
|
||||
await fireEvent.click(toggle);
|
||||
|
||||
secret = container.querySelector('span.text');
|
||||
expect(secret.textContent).toEqual(value);
|
||||
await fireEvent.click(toggle);
|
||||
|
||||
secret = container.querySelector('span.text');
|
||||
expect(secret.textContent).not.toEqual(value);
|
||||
});
|
||||
|
||||
test('copy to clipboard on click', async () => {
|
||||
const { container } = render(Secret, { value });
|
||||
const copy = container.querySelector('[aria-label="copy text"]');
|
||||
|
||||
Object.assign(window.navigator, {
|
||||
clipboard: {
|
||||
writeText: vi.fn().mockImplementation(() => Promise.resolve())
|
||||
}
|
||||
});
|
||||
|
||||
await fireEvent.click(copy);
|
||||
|
||||
expect(window.navigator.clipboard.writeText).toHaveBeenCalledWith(value);
|
||||
});
|
||||
Reference in New Issue
Block a user