mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge pull request #58 from appwrite/feat-permissions
feat: permissions for 1.0.0
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let disabled = false;
|
||||
export let icon: string = null;
|
||||
</script>
|
||||
|
||||
<li class="drop-list-item">
|
||||
<button class="drop-button" on:click|preventDefault>
|
||||
<button class="drop-button" on:click|preventDefault {disabled}>
|
||||
<span class="text"><slot /></span>
|
||||
{#if icon}
|
||||
<span class={`icon-${icon}`} aria-hidden="true" />
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
};
|
||||
const closeModal = () => {
|
||||
if (closable) {
|
||||
show = false;
|
||||
dispatch('close');
|
||||
show = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let sum: number;
|
||||
export let limit: number;
|
||||
export let offset: number;
|
||||
export let hidePages = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
$: totalPages = Math.ceil(sum / limit);
|
||||
$: currentPage = Math.floor(offset / limit + 1);
|
||||
$: pages = pagination(currentPage, totalPages);
|
||||
@@ -50,6 +53,7 @@
|
||||
{#if totalPages > 1}
|
||||
<nav class="pagination">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => handleButtonPage('prev')}
|
||||
class:is-disabled={currentPage <= 1}
|
||||
class="button is-text"
|
||||
@@ -57,30 +61,34 @@
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
<span class="text">Prev</span>
|
||||
</button>
|
||||
<ol class="pagination-list is-only-desktop">
|
||||
{#each pages as page}
|
||||
{#if typeof page === 'number'}
|
||||
<li class="pagination-item">
|
||||
<button
|
||||
class="button"
|
||||
on:click={() => handleOptionClick(page)}
|
||||
class:is-disabled={currentPage === page}
|
||||
class:is-text={currentPage !== page}
|
||||
aria-label="page">
|
||||
<span class="text">{page}</span>
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li class="li is-text">
|
||||
<span class="icon">...</span>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ol>
|
||||
{#if !hidePages}
|
||||
<ol class="pagination-list is-only-desktop">
|
||||
{#each pages as page}
|
||||
{#if typeof page === 'number'}
|
||||
<li class="pagination-item">
|
||||
<button
|
||||
type="button"
|
||||
class="button"
|
||||
on:click={() => handleOptionClick(page)}
|
||||
class:is-disabled={currentPage === page}
|
||||
class:is-text={currentPage !== page}
|
||||
aria-label="page">
|
||||
<span class="text">{page}</span>
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li class="li is-text">
|
||||
<span class="icon">...</span>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ol>
|
||||
{/if}
|
||||
<button
|
||||
on:click={() => handleButtonPage('next')}
|
||||
class:is-disabled={currentPage === totalPages}
|
||||
class="button is-text"
|
||||
type="button"
|
||||
aria-label="next page">
|
||||
<span class="text">Next</span>
|
||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
||||
@@ -88,18 +96,20 @@
|
||||
</nav>
|
||||
{:else}
|
||||
<nav class="pagination">
|
||||
<button class="button is-text is-disabled" aria-label="prev page">
|
||||
<button type="button" class="button is-text is-disabled" aria-label="prev page">
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
<span class="text">Prev</span>
|
||||
</button>
|
||||
<ol class="pagination-list is-only-desktop">
|
||||
<li class="pagination-item">
|
||||
<button class="button is-disabled" aria-label="page">
|
||||
<span class="text">1</span>
|
||||
</button>
|
||||
</li>
|
||||
</ol>
|
||||
<button class="button is-text is-disabled" aria-label="next page">
|
||||
{#if !hidePages}
|
||||
<ol class="pagination-list is-only-desktop">
|
||||
<li class="pagination-item">
|
||||
<button type="button" class="button is-disabled" aria-label="page">
|
||||
<span class="text">1</span>
|
||||
</button>
|
||||
</li>
|
||||
</ol>
|
||||
{/if}
|
||||
<button type="button" class="button is-text is-disabled" aria-label="next page">
|
||||
<span class="text">Next</span>
|
||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { Button, Form, FormList, Helper, InputText } from '$lib/elements/forms';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { Modal } from '..';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { Permission } from './permissions.svelte';
|
||||
|
||||
export let show: boolean;
|
||||
export let groups: Writable<Map<string, Permission>>;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let value = '';
|
||||
|
||||
function reset() {
|
||||
value = '';
|
||||
show = false;
|
||||
}
|
||||
|
||||
function create() {
|
||||
dispatch('create', [value]);
|
||||
reset();
|
||||
}
|
||||
|
||||
$: disabled = !value || $groups.has(value);
|
||||
</script>
|
||||
|
||||
<Form on:submit={create} noMargin>
|
||||
<Modal bind:show on:close={reset}>
|
||||
<svelte:fragment slot="header">Custom permission</svelte:fragment>
|
||||
|
||||
<FormList>
|
||||
<InputText
|
||||
showLabel={false}
|
||||
id="custom-permission"
|
||||
label="Custom permission"
|
||||
placeholder="user:[USER_ID] or team:[TEAM_ID]/[ROLE]"
|
||||
bind:value />
|
||||
<Helper type="neutral">
|
||||
A permission should be formatted as: user:[USER_ID] or team:[TEAM_ID]/[ROLE]¸
|
||||
</Helper>
|
||||
</FormList>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit {disabled}>Create</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Permissions } from './permissions.svelte';
|
||||
@@ -0,0 +1,252 @@
|
||||
<script context="module" lang="ts">
|
||||
export type Permission = {
|
||||
create: boolean;
|
||||
read: boolean;
|
||||
update: boolean;
|
||||
delete: boolean;
|
||||
};
|
||||
|
||||
export type PermissionsTypes = 'create' | 'read' | 'update' | 'delete';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { difference } from '$lib/helpers/array';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { writable, type Unsubscriber } from 'svelte/store';
|
||||
import { DropList, DropListItem } from '..';
|
||||
import Custom from './custom.svelte';
|
||||
import Row from './row.svelte';
|
||||
import Team from './team.svelte';
|
||||
import User from './user.svelte';
|
||||
|
||||
export let withCreate = false;
|
||||
export let permissions: string[] = [];
|
||||
|
||||
let showUser = false;
|
||||
let showTeam = false;
|
||||
let showCustom = false;
|
||||
let showDropdown = false;
|
||||
let unsubscribe: Unsubscriber;
|
||||
|
||||
const groups = writable<Map<string, Permission>>(new Map());
|
||||
|
||||
onMount(() => {
|
||||
permissions.forEach(fromPermissionString);
|
||||
unsubscribe = groups.subscribe(() => {
|
||||
const current = exportRoles();
|
||||
if (
|
||||
difference(current, permissions).length ||
|
||||
difference(permissions, current).length
|
||||
) {
|
||||
permissions = current;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
function create(event: CustomEvent<string[]>) {
|
||||
for (const role of event.detail) {
|
||||
addRole(role);
|
||||
}
|
||||
|
||||
showTeam = showUser = false;
|
||||
}
|
||||
|
||||
function addRole(role: string) {
|
||||
if ($groups.has(role)) {
|
||||
return;
|
||||
}
|
||||
|
||||
groups.update((n) => {
|
||||
n.set(role, {
|
||||
create: false,
|
||||
read: false,
|
||||
update: false,
|
||||
delete: false
|
||||
});
|
||||
|
||||
return n;
|
||||
});
|
||||
|
||||
showDropdown = false;
|
||||
}
|
||||
|
||||
function fromPermissionString(permission: string): void {
|
||||
const type = permission.slice(0, permission.indexOf('('));
|
||||
const role = permission.slice(permission.indexOf('("') + 2, permission.indexOf('")'));
|
||||
|
||||
groups.update((n) => {
|
||||
if (!n.has(role)) {
|
||||
n.set(role, {
|
||||
create: false,
|
||||
read: false,
|
||||
update: false,
|
||||
delete: false
|
||||
});
|
||||
}
|
||||
n.get(role)[type] = true;
|
||||
|
||||
return n;
|
||||
});
|
||||
}
|
||||
|
||||
function togglePermission(role: string, permission: PermissionsTypes): void {
|
||||
groups.update((n) => {
|
||||
n.get(role)[permission] = !n.get(role)[permission];
|
||||
|
||||
return n;
|
||||
});
|
||||
}
|
||||
|
||||
function deleteRole(role: string): void {
|
||||
groups.update((n) => {
|
||||
n.delete(role);
|
||||
|
||||
return n;
|
||||
});
|
||||
}
|
||||
|
||||
function exportRoles() {
|
||||
return [...$groups].reduce((prev, [role, permission]) => {
|
||||
['create', 'read', 'update', 'delete'].forEach((type) => {
|
||||
if (permission[type] === true) {
|
||||
prev.push(`${type}("${role}")`);
|
||||
}
|
||||
});
|
||||
|
||||
return prev;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function sortRoles([a]: [string, Permission], [b]: [string, Permission]) {
|
||||
if ((a === 'any') !== (b === 'any')) {
|
||||
return a === 'any' ? -1 : 1;
|
||||
}
|
||||
if ((a === 'users') !== (b === 'users')) {
|
||||
return a === 'users' ? -1 : 1;
|
||||
}
|
||||
if ((a === 'guests') !== (b === 'guests')) {
|
||||
return a === 'guests' ? -1 : 1;
|
||||
}
|
||||
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="table-with-scroll">
|
||||
<div class="table-wrapper">
|
||||
<table class="table is-table-layout-auto is-remove-outer-styles">
|
||||
<thead class="table-thead">
|
||||
<tr class="table-row">
|
||||
<th class="table-thead-col">
|
||||
<span class="eyebrow-heading-3">Role</span>
|
||||
</th>
|
||||
{#if withCreate}
|
||||
<th class="table-thead-col" style="--p-col-width:70">
|
||||
<span class="eyebrow-heading-3">Create</span>
|
||||
</th>
|
||||
{/if}
|
||||
<th class="table-thead-col" style="--p-col-width:70">
|
||||
<span class="eyebrow-heading-3">Read</span>
|
||||
</th>
|
||||
<th class="table-thead-col" style="--p-col-width:70">
|
||||
<span class="eyebrow-heading-3">Update</span>
|
||||
</th>
|
||||
<th class="table-thead-col" style="--p-col-width:70">
|
||||
<span class="eyebrow-heading-3">Delete</span>
|
||||
</th>
|
||||
<th class="table-thead-col" style="--p-col-width:40" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-tbody">
|
||||
{#each [...$groups].sort(sortRoles) as [role, permission]}
|
||||
<tr class="table-row">
|
||||
<td class="table-col" data-title="Role">
|
||||
<Row {role} />
|
||||
</td>
|
||||
{#if withCreate}
|
||||
<td class="table-col" data-title="Create">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="icon-check"
|
||||
aria-label="Create"
|
||||
checked={permission.create}
|
||||
on:change={() => togglePermission(role, 'create')} />
|
||||
</td>
|
||||
{/if}
|
||||
<td class="table-col" data-title="Read">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="icon-check"
|
||||
aria-label="Read"
|
||||
checked={permission.read}
|
||||
on:change={() => togglePermission(role, 'read')} />
|
||||
</td>
|
||||
<td class="table-col" data-title="Update">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="icon-check"
|
||||
aria-label="Update"
|
||||
checked={permission.update}
|
||||
on:change={() => togglePermission(role, 'update')} />
|
||||
</td>
|
||||
<td class="table-col" data-title="Delete">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="icon-check"
|
||||
aria-label="Delete"
|
||||
checked={permission.delete}
|
||||
on:change={() => togglePermission(role, 'delete')} />
|
||||
</td>
|
||||
<td class="table-col u-overflow-visible">
|
||||
<div class="u-flex">
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
type="button"
|
||||
aria-label="delete"
|
||||
on:click={() => deleteRole(role)}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<DropList
|
||||
bind:show={showDropdown}
|
||||
position="bottom"
|
||||
horizontal="right"
|
||||
arrow={true}
|
||||
arrowPosition="start">
|
||||
<Button text noMargin on:click={() => (showDropdown = !showDropdown)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Add role</span>
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem disabled={$groups.has('any')} on:click={() => addRole('any')}>
|
||||
Any
|
||||
</DropListItem>
|
||||
<DropListItem disabled={$groups.has('guests')} on:click={() => addRole('guests')}>
|
||||
All guests
|
||||
</DropListItem>
|
||||
<DropListItem disabled={$groups.has('users')} on:click={() => addRole('users')}>
|
||||
All users
|
||||
</DropListItem>
|
||||
<DropListItem on:click={() => (showUser = true)}>Select users</DropListItem>
|
||||
<DropListItem on:click={() => (showTeam = true)}>Select teams</DropListItem>
|
||||
<DropListItem on:click={() => (showCustom = true)}>Custom permission</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
|
||||
<User bind:show={showUser} on:create={create} {groups} />
|
||||
<Team bind:show={showTeam} on:create={create} {groups} />
|
||||
<Custom bind:show={showCustom} on:create={create} {groups} />
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
export let role: string;
|
||||
</script>
|
||||
|
||||
<div class="u-flex u-cross-center u-gap-8">
|
||||
<div>
|
||||
{#if role === 'users'}
|
||||
<div>Users</div>
|
||||
{:else if role === 'guests'}
|
||||
<div>Guests</div>
|
||||
{:else if role === 'any'}
|
||||
<div>Any</div>
|
||||
{:else}
|
||||
<div>{role}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,111 @@
|
||||
<script lang="ts">
|
||||
import { Button, Form, InputSearch } from '$lib/elements/forms';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { Query, type Models } from '@aw-labs/appwrite-console';
|
||||
import { Avatar, Modal, Pagination } from '..';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { Permission } from './permissions.svelte';
|
||||
|
||||
export let show: boolean;
|
||||
export let groups: Writable<Map<string, Permission>>;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let search = '';
|
||||
let offset = 0;
|
||||
let results: Models.TeamList;
|
||||
let selected: Set<string> = new Set();
|
||||
let hasSelection = false;
|
||||
|
||||
function reset() {
|
||||
offset = 0;
|
||||
search = '';
|
||||
selected.clear();
|
||||
}
|
||||
|
||||
function create() {
|
||||
dispatch('create', Array.from(selected));
|
||||
reset();
|
||||
}
|
||||
|
||||
async function request() {
|
||||
if (!show) return;
|
||||
results = await sdkForProject.teams.list([Query.limit(5), Query.offset(offset)], search);
|
||||
}
|
||||
|
||||
function onSelection(event: Event, role: string) {
|
||||
const { checked } = event.currentTarget as HTMLInputElement;
|
||||
|
||||
if (checked) {
|
||||
selected.add(role);
|
||||
} else {
|
||||
selected.delete(role);
|
||||
}
|
||||
|
||||
hasSelection = selected.size > 0;
|
||||
}
|
||||
|
||||
$: if (show) {
|
||||
request();
|
||||
}
|
||||
$: if (offset !== null) {
|
||||
request();
|
||||
}
|
||||
$: if (search !== null) {
|
||||
offset = 0;
|
||||
request();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={create} noMargin>
|
||||
<Modal bind:show on:close={reset} size="big">
|
||||
<svelte:fragment slot="header">Select teams</svelte:fragment>
|
||||
<InputSearch bind:value={search} />
|
||||
{#if results?.teams}
|
||||
<div class="table-wrapper">
|
||||
<table class="table is-table-layout-auto is-remove-outer-styles">
|
||||
<tbody class="table-tbody">
|
||||
{#each results.teams as team (team.$id)}
|
||||
{@const role = `team:${team.$id}`}
|
||||
{@const exists = $groups.has(role)}
|
||||
<tr class="table-row">
|
||||
<td class="table-col" data-title="Enabled" style="--p-col-width:40">
|
||||
<input
|
||||
id={team.$id}
|
||||
type="checkbox"
|
||||
class="icon-check"
|
||||
aria-label="Create"
|
||||
checked={exists || selected.has(role)}
|
||||
disabled={exists}
|
||||
on:change={(event) => onSelection(event, role)} />
|
||||
</td>
|
||||
<td class="table-col" data-title="Team">
|
||||
<label class="u-flex u-cross-center u-gap-8" for={team.$id}>
|
||||
<Avatar
|
||||
src={sdkForProject.avatars
|
||||
.getInitials(team.name, 64, 64)
|
||||
.toString()}
|
||||
size={32}
|
||||
name={team.name} />
|
||||
<div class="u-line-height-1-5">
|
||||
<div class="body-text-2">{team.name}</div>
|
||||
<div class="u-x-small">{team.$id}</div>
|
||||
</div>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {results?.total}</p>
|
||||
<Pagination limit={5} bind:offset sum={results?.total} hidePages />
|
||||
</div>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit disabled={!hasSelection}>Create</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -0,0 +1,111 @@
|
||||
<script lang="ts">
|
||||
import { Button, Form, InputSearch } from '$lib/elements/forms';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { Avatar, Modal, Pagination } from '..';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { Query, type Models } from '@aw-labs/appwrite-console';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { Permission } from './permissions.svelte';
|
||||
|
||||
export let show: boolean;
|
||||
export let groups: Writable<Map<string, Permission>>;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let search = '';
|
||||
let offset = 0;
|
||||
let results: Models.UserList<Record<string, unknown>>;
|
||||
let selected: Set<string> = new Set();
|
||||
let hasSelection = false;
|
||||
|
||||
function reset() {
|
||||
offset = 0;
|
||||
search = '';
|
||||
selected.clear();
|
||||
}
|
||||
|
||||
function create() {
|
||||
dispatch('create', Array.from(selected));
|
||||
reset();
|
||||
}
|
||||
|
||||
async function request() {
|
||||
if (!show) return;
|
||||
results = await sdkForProject.users.list([Query.limit(5), Query.offset(offset)], search);
|
||||
}
|
||||
|
||||
function onSelection(event: Event, role: string) {
|
||||
const { checked } = event.currentTarget as HTMLInputElement;
|
||||
|
||||
if (checked) {
|
||||
selected.add(role);
|
||||
} else {
|
||||
selected.delete(role);
|
||||
}
|
||||
|
||||
hasSelection = selected.size > 0;
|
||||
}
|
||||
|
||||
$: if (show) {
|
||||
request();
|
||||
}
|
||||
$: if (offset !== null) {
|
||||
request();
|
||||
}
|
||||
$: if (search !== null) {
|
||||
offset = 0;
|
||||
request();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={create} noMargin>
|
||||
<Modal bind:show on:close={reset} size="big">
|
||||
<svelte:fragment slot="header">Select users</svelte:fragment>
|
||||
<InputSearch bind:value={search} />
|
||||
{#if results?.users}
|
||||
<div class="table-wrapper">
|
||||
<table class="table is-table-layout-auto is-remove-outer-styles">
|
||||
<tbody class="table-tbody">
|
||||
{#each results.users as user (user.$id)}
|
||||
{@const role = `user:${user.$id}`}
|
||||
{@const exists = $groups.has(role)}
|
||||
<tr class="table-row">
|
||||
<td class="table-col" data-title="Enabled" style="--p-col-width:40">
|
||||
<input
|
||||
id={user.$id}
|
||||
type="checkbox"
|
||||
class="icon-check"
|
||||
aria-label="Create"
|
||||
checked={exists || selected.has(role)}
|
||||
disabled={exists}
|
||||
on:change={(event) => onSelection(event, role)} />
|
||||
</td>
|
||||
<td class="table-col" data-title="User">
|
||||
<label class="u-flex u-cross-center u-gap-8" for={user.$id}>
|
||||
<Avatar
|
||||
src={sdkForProject.avatars
|
||||
.getInitials(user.name, 64, 64)
|
||||
.toString()}
|
||||
size={32}
|
||||
name={user.name} />
|
||||
<div class="u-line-height-1-5">
|
||||
<div class="body-text-2">{user.name}</div>
|
||||
<div class="u-x-small">{user.$id}</div>
|
||||
</div>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {results?.total}</p>
|
||||
<Pagination limit={5} bind:offset sum={results?.total} hidePages />
|
||||
</div>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit disabled={!hasSelection}>Create</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -9,6 +9,7 @@
|
||||
export let href: string = null;
|
||||
export let fullWidth = false;
|
||||
export let ariaLabel: string = null;
|
||||
export let noMargin = false;
|
||||
//TODO: add option to add aria-label to buttons that are only icons
|
||||
</script>
|
||||
|
||||
@@ -24,6 +25,7 @@
|
||||
class:is-text={text}
|
||||
class:is-danger={danger}
|
||||
class:is-full-width={fullWidth}
|
||||
class:u-padding-inline-0={noMargin}
|
||||
aria-label={ariaLabel}>
|
||||
<slot />
|
||||
</a>
|
||||
@@ -37,6 +39,7 @@
|
||||
class:is-danger={danger}
|
||||
class:is-text={text}
|
||||
class:is-full-width={fullWidth}
|
||||
class:u-padding-inline-0={noMargin}
|
||||
type={submit ? 'submit' : 'button'}
|
||||
aria-label={ariaLabel}>
|
||||
<slot />
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script lang="ts">
|
||||
export let noMargin = false;
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-redundant-roles -->
|
||||
<form role="form" class="form common-section" on:submit|preventDefault>
|
||||
<form role="form" class="form" class:common-section={!noMargin} on:submit|preventDefault>
|
||||
<slot />
|
||||
</form>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
|
||||
export let value = '';
|
||||
export let placeholder = '';
|
||||
@@ -18,6 +19,13 @@
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
value = '';
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
});
|
||||
|
||||
const valueChange = (event: Event) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
|
||||
@@ -37,9 +37,9 @@
|
||||
href="https://appwrite.io/support"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="button is-small is-text u-margin-inline-end-16"
|
||||
><span class="text">Support</span></a>
|
||||
<button class="button is-small is-secondary"><span class="text">Upgrade</span></button>
|
||||
class="button is-small is-text">
|
||||
<span class="text">Support</span>
|
||||
</a>
|
||||
</nav>
|
||||
<nav class="user-profile">
|
||||
{#if $user}
|
||||
|
||||
+7
-19
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid, Box, Alert } from '$lib/components';
|
||||
import { CardGrid, Box } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Button, InputTags } from '$lib/elements/forms';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { doc } from './store';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -9,18 +9,17 @@
|
||||
import Document from './_document.svelte';
|
||||
import Delete from './_delete.svelte';
|
||||
import { difference } from '$lib/helpers/array';
|
||||
import { page } from '$app/stores';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
|
||||
let showDelete = false;
|
||||
let permissions = $doc?.$permissions;
|
||||
let arePermsDisabled = true;
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdkForProject.databases.updateDocument(
|
||||
databaseId,
|
||||
$doc.collectionId,
|
||||
$doc.$databaseId,
|
||||
$doc.$collectionId,
|
||||
$doc.$id,
|
||||
$doc.data,
|
||||
permissions
|
||||
@@ -70,20 +69,9 @@
|
||||
<b> Document Level</b>. If collection Level permissions are assigned, permissions
|
||||
applied to individual documents are ignored.
|
||||
</p>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<Alert type="info">
|
||||
<p>
|
||||
Tip: Add role:all for wildcard access. Check out our documentation for more
|
||||
on <a href="#?"> Permissions</a>
|
||||
</p>
|
||||
</Alert>
|
||||
<ul class="common-section">
|
||||
<InputTags
|
||||
id="permissions"
|
||||
label="Permissions"
|
||||
placeholder="User ID, Team ID, or Role"
|
||||
bind:tags={permissions} />
|
||||
</ul>
|
||||
<Permissions bind:permissions />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
+18
-29
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Alert, CardGrid, Box } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Button, InputText, InputTags, InputSwitch, Helper } from '$lib/elements/forms';
|
||||
import { Button, InputText, InputSwitch, Helper } from '$lib/elements/forms';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import { collection } from '../store';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
@@ -20,10 +21,10 @@
|
||||
let enabled: boolean = null,
|
||||
collectionName: string = null,
|
||||
collectionDocumentSecurity: boolean = null,
|
||||
collectionPermissions: string[] = [],
|
||||
collectionPermissions: string[] = null,
|
||||
arePermsDisabled = true;
|
||||
|
||||
onMount(async () => {
|
||||
onMount(() => {
|
||||
enabled ??= $collection.enabled;
|
||||
collectionName ??= $collection.name;
|
||||
collectionPermissions ??= $collection.$permissions;
|
||||
@@ -34,7 +35,10 @@
|
||||
if (collectionDocumentSecurity !== $collection.documentSecurity) {
|
||||
arePermsDisabled = false;
|
||||
} else if (collectionPermissions) {
|
||||
if (difference(collectionPermissions, $collection.$permissions).length) {
|
||||
if (
|
||||
difference(collectionPermissions, $collection.$permissions).length ||
|
||||
difference($collection.$permissions, collectionPermissions).length
|
||||
) {
|
||||
arePermsDisabled = false;
|
||||
} else arePermsDisabled = true;
|
||||
}
|
||||
@@ -92,7 +96,7 @@
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
collectionDocumentSecurity ? collectionPermissions : $collection.$permissions
|
||||
collectionDocumentSecurity ? $collection.$permissions : collectionPermissions
|
||||
);
|
||||
$collection.$permissions = collectionPermissions;
|
||||
$collection.documentSecurity = collectionDocumentSecurity;
|
||||
@@ -118,8 +122,8 @@
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputSwitch
|
||||
label={enabled ? 'Enabled' : 'Disabled'}
|
||||
id="toggle"
|
||||
label={enabled ? 'Enabled' : 'Disabled'}
|
||||
bind:value={enabled} />
|
||||
</ul>
|
||||
<div>
|
||||
@@ -129,8 +133,9 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={enabled === $collection.enabled} on:click={togglecollection}
|
||||
>Update</Button>
|
||||
<Button disabled={enabled === $collection.enabled} on:click={togglecollection}>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
@@ -173,7 +178,7 @@
|
||||
class="is-small"
|
||||
name="level"
|
||||
bind:group={collectionDocumentSecurity}
|
||||
value={true} />
|
||||
value={false} />
|
||||
<span>Collection Level</span>
|
||||
</label>
|
||||
</li>
|
||||
@@ -184,7 +189,7 @@
|
||||
class="is-small"
|
||||
name="level"
|
||||
bind:group={collectionDocumentSecurity}
|
||||
value={false} />
|
||||
value={true} />
|
||||
<span>Document Level</span>
|
||||
</label>
|
||||
</li>
|
||||
@@ -199,28 +204,12 @@
|
||||
<a class="link" href="/#">Permissions</a>
|
||||
</p>
|
||||
</Alert>
|
||||
{:else}
|
||||
<Alert type="info">
|
||||
<p>
|
||||
Tip: Add <b>role:all</b> for wildcards access. Check out our
|
||||
documentation for more on <a class="link" href="/#">Permissions</a>
|
||||
</p>
|
||||
</Alert>
|
||||
<ul class="form-list">
|
||||
<InputTags
|
||||
id="permissions"
|
||||
label="Permissions"
|
||||
placeholder="User ID, Team ID, or Role"
|
||||
bind:tags={collectionPermissions} />
|
||||
</ul>
|
||||
{:else if collectionPermissions !== null}
|
||||
<Permissions bind:permissions={collectionPermissions} withCreate />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={arePermsDisabled}
|
||||
on:click={() => {
|
||||
updatePermissions();
|
||||
}}>Update</Button>
|
||||
<Button disabled={arePermsDisabled} on:click={updatePermissions}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
|
||||
+4
-19
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Alert } from '$lib/components';
|
||||
import { InputTags } from '$lib/elements/forms';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { createDocument } from './store';
|
||||
</script>
|
||||
@@ -12,21 +11,7 @@
|
||||
<b> Document Level</b>. If collection Level permissions are assigned, permissions applied to
|
||||
individual documents are ignored.
|
||||
</svelte:fragment>
|
||||
<Alert type="info">
|
||||
<svelte:fragment slot="title">
|
||||
You have Collection Level permissions enabled
|
||||
</svelte:fragment>
|
||||
<p>
|
||||
If you want to assign permissions specific to this document, you will need to update
|
||||
your Collection Settings to enable Document Level permissions.
|
||||
</p>
|
||||
</Alert>
|
||||
|
||||
<ul class="common-section">
|
||||
<InputTags
|
||||
id="permissions"
|
||||
label="Permissions"
|
||||
placeholder="User ID, Team ID, or Role"
|
||||
bind:tags={$createDocument.permissions} />
|
||||
</ul>
|
||||
<div class="common-section">
|
||||
<Permissions bind:permissions={$createDocument.permissions} />
|
||||
</div>
|
||||
</WizardStep>
|
||||
|
||||
-1
@@ -116,7 +116,6 @@
|
||||
}
|
||||
|
||||
async function updateScopes() {
|
||||
console.log(scopes.filter((scope) => activeScopes[scope]));
|
||||
try {
|
||||
await sdkForConsole.projects.updateKey(
|
||||
$project.$id,
|
||||
|
||||
@@ -112,9 +112,7 @@
|
||||
<button
|
||||
class="button is-only-icon is-text"
|
||||
aria-label="Delete item"
|
||||
on:click|preventDefault={() => {
|
||||
console.log('Feel refreshed?');
|
||||
}}>
|
||||
on:click|preventDefault>
|
||||
<span class="icon-refresh" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { InputTags, InputText, Button, Form, FormList } from '$lib/elements/forms';
|
||||
import { InputText, Button, Form, FormList } from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Modal, Alert, InnerModal } from '$lib/components';
|
||||
import { Modal, InnerModal } from '$lib/components';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { uploader } from '$lib/stores/uploader';
|
||||
import { bucket } from './store';
|
||||
import { calculateSize } from '$lib/helpers/sizeConvertion';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
|
||||
export let showCreate = false;
|
||||
|
||||
@@ -148,19 +149,7 @@
|
||||
</InnerModal>
|
||||
{/if}
|
||||
<p class="heading-level-7">Permissions</p>
|
||||
<Alert type="info">
|
||||
<p>
|
||||
Tip: Add role:all for wildcard access. Check out our documentation for more on <a
|
||||
class="link"
|
||||
href="#?">
|
||||
Permissions</a>
|
||||
</p>
|
||||
</Alert>
|
||||
<InputTags
|
||||
id="permissions"
|
||||
label="Permissions"
|
||||
bind:tags={permissions}
|
||||
placeholder="User ID, Team ID or Role" />
|
||||
<Permissions bind:permissions />
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
|
||||
|
||||
+4
-19
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Alert, CardGrid, Box, Copy } from '$lib/components';
|
||||
import { CardGrid, Box, Copy } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Button, InputTags } from '$lib/elements/forms';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { file } from './store';
|
||||
import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date';
|
||||
@@ -12,6 +12,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { difference } from '$lib/helpers/array';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
|
||||
onMount(async () => {
|
||||
let bucketId = $page.params.bucket;
|
||||
@@ -113,23 +114,7 @@
|
||||
permissions are enabled, file permissions will be ignored.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Alert type="info">
|
||||
<svelte:fragment slot="title">
|
||||
You have Bucket Level permissions enabled
|
||||
</svelte:fragment>
|
||||
<p>
|
||||
If you want to assign permissions specific to this file, you will need to
|
||||
update your <a class="link" href="#/"> Bucket Settings</a> to enable File Level
|
||||
permissions.
|
||||
</p>
|
||||
</Alert>
|
||||
<ul class="common-section">
|
||||
<InputTags
|
||||
id="permissions"
|
||||
label="Permissions"
|
||||
placeholder="User ID, Team ID, or Role"
|
||||
bind:tags={filePermissions} />
|
||||
</ul>
|
||||
<Permissions bind:permissions={filePermissions} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Alert, CardGrid, Box } from '$lib/components';
|
||||
import { CardGrid, Box } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import {
|
||||
Form,
|
||||
@@ -20,6 +20,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import Pill from '$lib/elements/pill.svelte';
|
||||
import { difference } from '$lib/helpers/array';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
|
||||
let showDelete = false;
|
||||
|
||||
@@ -156,7 +157,6 @@
|
||||
}
|
||||
async function updateMaxSize() {
|
||||
let size = sizeToBytes(maxSize, byteUnit);
|
||||
console.log(size);
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
@@ -281,20 +281,8 @@
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<Alert type="info">
|
||||
<p>
|
||||
Tip: Add <b>role:all</b> for wildcards access. Check out our
|
||||
documentation for more on <a href="/#">Permissions</a>
|
||||
</p>
|
||||
</Alert>
|
||||
{#if bucketFileSecurity}
|
||||
<ul class="common-section">
|
||||
<InputTags
|
||||
id="permissions"
|
||||
label="Permissions"
|
||||
placeholder="User ID, Team ID, or Role"
|
||||
bind:tags={bucketPermissions} />
|
||||
</ul>
|
||||
<Permissions bind:permissions={bucketPermissions} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
Reference in New Issue
Block a user