mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
storage commands
This commit is contained in:
@@ -24,7 +24,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { dev } from '$app/environment';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { portal } from '$lib/actions/portal';
|
||||
import { last } from '$lib/helpers/array';
|
||||
import { debounce } from '$lib/helpers/debounce';
|
||||
@@ -61,10 +60,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
afterNavigate(() => {
|
||||
clearSubPanels();
|
||||
});
|
||||
|
||||
const ctx = setCommandCenterCtx({
|
||||
isInitialPanel: true,
|
||||
open: false
|
||||
@@ -95,6 +90,8 @@
|
||||
}
|
||||
$commandCenterKeyDownHandler(e);
|
||||
};
|
||||
|
||||
$: console.log($subPanels);
|
||||
</script>
|
||||
|
||||
<svelte:window on:mousedown={handleBlur} on:keydown={handleKeydown} />
|
||||
|
||||
@@ -25,7 +25,9 @@ const groups = [
|
||||
'indexes',
|
||||
'documents',
|
||||
'teams',
|
||||
'security'
|
||||
'security',
|
||||
'buckets',
|
||||
'files'
|
||||
] as const;
|
||||
|
||||
export type CommandGroup = (typeof groups)[number];
|
||||
@@ -39,6 +41,7 @@ type BaseCommand = {
|
||||
icon?: string;
|
||||
rank?: number;
|
||||
nested?: boolean;
|
||||
keepOpen?: boolean;
|
||||
};
|
||||
|
||||
type KeyedCommand = BaseCommand & {
|
||||
@@ -152,7 +155,7 @@ export const commandCenterKeyDownHandler = derived(
|
||||
|
||||
const rankAndExecute = debounce(() => {
|
||||
const command = getHighestPriorityCommand();
|
||||
command.callback();
|
||||
command?.callback();
|
||||
reset.immediate();
|
||||
}, 200);
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { initSearcher } from '../commands';
|
||||
import { bucketSearcher } from '../searchers';
|
||||
import Template from './template.svelte';
|
||||
|
||||
const { search, results } = initSearcher(bucketSearcher);
|
||||
</script>
|
||||
|
||||
<Template options={$results} bind:search={$search} />
|
||||
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { initSearcher } from '../commands';
|
||||
import { fileSearcher } from '../searchers';
|
||||
import Template from './template.svelte';
|
||||
|
||||
const { search, results } = initSearcher(fileSearcher);
|
||||
</script>
|
||||
|
||||
<Template options={$results} bind:search={$search} />
|
||||
@@ -66,3 +66,15 @@ export const FunctionsPanel: SubPanel = {
|
||||
name: 'Functions',
|
||||
component: Functions
|
||||
};
|
||||
|
||||
import Buckets from './buckets.svelte';
|
||||
export const BucketsPanel: SubPanel = {
|
||||
name: 'Buckets',
|
||||
component: Buckets
|
||||
};
|
||||
|
||||
import Files from './files.svelte';
|
||||
export const FilesPanel: SubPanel = {
|
||||
name: 'Files',
|
||||
component: Files
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { commandGroupRanks, type CommandGroup } from '../commands';
|
||||
import { commandGroupRanks, type CommandGroup, type Command } from '../commands';
|
||||
|
||||
// This is the template for all panels used in the command center.
|
||||
// Use this component when you want to create a new panel.
|
||||
@@ -10,18 +10,7 @@
|
||||
|
||||
import { clearSubPanels, popSubPanel, subPanels } from '../subPanels';
|
||||
|
||||
import { quadOut } from 'svelte/easing';
|
||||
import { crossfade } from 'svelte/transition';
|
||||
|
||||
type BaseOption = {
|
||||
callback: () => void;
|
||||
group?: string;
|
||||
rank?: number;
|
||||
nested?: boolean;
|
||||
icon?: string;
|
||||
label?: string;
|
||||
};
|
||||
type Option = $$Generic<BaseOption>;
|
||||
type Option = $$Generic<Command>;
|
||||
export let options: Option[] | null = null;
|
||||
export let search = '';
|
||||
export let searchPlaceholder = 'Search...';
|
||||
@@ -31,10 +20,10 @@
|
||||
let selected = 0;
|
||||
let contentEl: HTMLElement;
|
||||
|
||||
function triggerOption(option: Option) {
|
||||
async function triggerOption(option: Option) {
|
||||
const prevPanels = $subPanels.length;
|
||||
option.callback();
|
||||
if (prevPanels === $subPanels.length && clearOnCallback) {
|
||||
if (prevPanels === $subPanels.length && clearOnCallback && !option.keepOpen) {
|
||||
clearSubPanels();
|
||||
}
|
||||
}
|
||||
@@ -106,11 +95,6 @@
|
||||
selected = 0;
|
||||
}
|
||||
|
||||
const [send, receive] = crossfade({
|
||||
duration: 100,
|
||||
easing: quadOut
|
||||
});
|
||||
|
||||
const commandCenterCtx = getCommandCenterCtx();
|
||||
|
||||
let cardEl: HTMLElement;
|
||||
@@ -237,10 +221,14 @@
|
||||
class:scale-up={$commandCenterCtx.isInitialPanel && $commandCenterCtx.open}>
|
||||
<div class="search-wrapper">
|
||||
{#each breadcrumbs as crumb, i}
|
||||
{@const isLast = i === breadcrumbs.length - 1}
|
||||
<button class="crumb" on:click={() => handleCrumbClick(i)}>
|
||||
<span>{crumb}</span>
|
||||
<i class="icon-x" />
|
||||
</button>
|
||||
{#if !isLast}
|
||||
<span style="opacity: 50%">/</span>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<slot name="search">
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { project } from '$routes/console/project-[project]/store';
|
||||
import { Query, type Models } from '@appwrite.io/console';
|
||||
import { get } from 'svelte/store';
|
||||
import type { Command, Searcher } from '../commands';
|
||||
import { addSubPanel } from '../subPanels';
|
||||
import { FilesPanel } from '../panels';
|
||||
|
||||
const getBucketCommand = (bucket: Models.Bucket, projectId: string) => {
|
||||
return {
|
||||
label: `${bucket.name}`,
|
||||
callback() {
|
||||
goto(`/console/project-${projectId}/storage/bucket-${bucket.$id}`);
|
||||
},
|
||||
group: 'buckets',
|
||||
icon: 'folder'
|
||||
} satisfies Command;
|
||||
};
|
||||
|
||||
export const bucketSearcher = (async (query: string) => {
|
||||
const { buckets } = await sdk.forProject.storage.listBuckets([Query.orderDesc('$createdAt')]);
|
||||
|
||||
const $project = get(project);
|
||||
const filtered = buckets.filter((bucket) => bucket.name.includes(query));
|
||||
|
||||
if (filtered.length === 1) {
|
||||
const bucket = filtered[0];
|
||||
return [
|
||||
getBucketCommand(bucket, $project.$id),
|
||||
{
|
||||
label: 'Find files',
|
||||
async callback() {
|
||||
await goto(`/console/project-${$project.$id}/storage/bucket-${bucket.$id}`);
|
||||
addSubPanel(FilesPanel);
|
||||
},
|
||||
group: 'buckets',
|
||||
nested: true,
|
||||
icon: 'search',
|
||||
keepOpen: true
|
||||
},
|
||||
{
|
||||
label: 'Permissions',
|
||||
async callback() {
|
||||
await goto(
|
||||
`/console/project-${$project.$id}/storage/bucket-${bucket.$id}/settings#permissions`
|
||||
);
|
||||
scrollBy({ top: -100 });
|
||||
},
|
||||
group: 'buckets',
|
||||
nested: true,
|
||||
icon: 'key'
|
||||
},
|
||||
{
|
||||
label: 'Extensions',
|
||||
async callback() {
|
||||
await goto(
|
||||
`/console/project-${$project.$id}/storage/bucket-${bucket.$id}/settings#extensions`
|
||||
);
|
||||
},
|
||||
group: 'buckets',
|
||||
nested: true,
|
||||
icon: 'puzzle'
|
||||
},
|
||||
{
|
||||
label: 'File Security',
|
||||
async callback() {
|
||||
await goto(
|
||||
`/console/project-${$project.$id}/storage/bucket-${bucket.$id}/settings#file-security`
|
||||
);
|
||||
scrollBy({ top: -100 });
|
||||
},
|
||||
group: 'buckets',
|
||||
nested: true,
|
||||
icon: 'lock-closed'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return filtered.map((bucket) => getBucketCommand(bucket, $project.$id));
|
||||
}) satisfies Searcher;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { get } from 'svelte/store';
|
||||
import type { Searcher } from '../commands';
|
||||
import { bucket } from '$routes/console/project-[project]/storage/bucket-[bucket]/store';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import { goto } from '$app/navigation';
|
||||
import { project } from '$routes/console/project-[project]/store';
|
||||
|
||||
export const fileSearcher = (async (query: string) => {
|
||||
const $bucket = get(bucket);
|
||||
const $project = get(project);
|
||||
|
||||
const { files } = await sdk.forProject.storage.listFiles(
|
||||
$bucket.$id,
|
||||
[Query.orderDesc('$createdAt')],
|
||||
query || undefined
|
||||
);
|
||||
|
||||
return files.map((file) => ({
|
||||
label: file.name,
|
||||
callback: () => {
|
||||
goto(`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/file-${file.$id}`);
|
||||
},
|
||||
icon: 'document',
|
||||
group: 'files'
|
||||
}));
|
||||
}) satisfies Searcher;
|
||||
@@ -5,3 +5,5 @@ export * from './collections';
|
||||
export * from './projects';
|
||||
export * from './teams';
|
||||
export * from './functions';
|
||||
export * from './buckets';
|
||||
export * from './files';
|
||||
|
||||
@@ -17,7 +17,7 @@ const getUserCommand = (user: Models.User<Models.Preferences>, projectId: string
|
||||
} satisfies Command);
|
||||
|
||||
export const userSearcher = (async (query: string) => {
|
||||
const { users } = await sdk.forProject.users.list([], query);
|
||||
const { users } = await sdk.forProject.users.list([], query || undefined);
|
||||
const projectId = get(project).$id;
|
||||
|
||||
if (users.length === 1) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
} from '$lib/commandCenter';
|
||||
|
||||
import {
|
||||
bucketSearcher,
|
||||
dbSearcher,
|
||||
functionsSearcher,
|
||||
teamSearcher,
|
||||
@@ -23,7 +24,8 @@
|
||||
UsersPanel,
|
||||
TeamsPanel,
|
||||
DatabasesPanel,
|
||||
FunctionsPanel
|
||||
FunctionsPanel,
|
||||
BucketsPanel
|
||||
} from '$lib/commandCenter/panels';
|
||||
|
||||
onMount(async () => {
|
||||
@@ -122,12 +124,19 @@
|
||||
group: 'functions',
|
||||
icon: 'search',
|
||||
keys: ['f', 'f']
|
||||
},
|
||||
{
|
||||
label: 'Find Buckets',
|
||||
callback: () => {
|
||||
addSubPanel(BucketsPanel);
|
||||
},
|
||||
group: 'buckets',
|
||||
icon: 'search',
|
||||
keys: ['f', 'b']
|
||||
}
|
||||
]);
|
||||
|
||||
$registerSearchers(userSearcher, teamSearcher, dbSearcher, functionsSearcher);
|
||||
|
||||
$updateCommandGroupRanks({ functions: 10 });
|
||||
$registerSearchers(userSearcher, teamSearcher, dbSearcher, functionsSearcher, bucketSearcher);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
]);
|
||||
|
||||
// To prioritize the groups!
|
||||
$: $updateCommandGroupRanks({ users: 300, teams: 200, security: 100 });
|
||||
$: $updateCommandGroupRanks({ users: 300, teams: 200, security: 100, navigation: 50 });
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
]);
|
||||
|
||||
$: $updateCommandGroupRanks({ databases: 100 });
|
||||
$: $updateCommandGroupRanks({ databases: 200, navigation: 100 });
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
$registerSearchers(collectionsSearcher);
|
||||
|
||||
$: $updateCommandGroupRanks((p) => ({ ...p, collections: 10 }));
|
||||
$: $updateCommandGroupRanks({ collections: 10 });
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
+2
-3
@@ -147,13 +147,12 @@
|
||||
}
|
||||
]);
|
||||
|
||||
$updateCommandGroupRanks((p) => ({
|
||||
...p,
|
||||
$updateCommandGroupRanks({
|
||||
attributes: 1000,
|
||||
documents: 900,
|
||||
collections: 800,
|
||||
indexes: 700
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -80,10 +80,9 @@
|
||||
}
|
||||
]);
|
||||
|
||||
$: $updateCommandGroupRanks((prev) => ({
|
||||
...prev,
|
||||
$: $updateCommandGroupRanks({
|
||||
integrations: 10
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -71,11 +71,10 @@
|
||||
}
|
||||
]);
|
||||
|
||||
$: $updateCommandGroupRanks((prev) => ({
|
||||
...prev,
|
||||
$updateCommandGroupRanks({
|
||||
domains: 20,
|
||||
webhooks: 10
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
} from '$lib/elements/table';
|
||||
import Table from '$lib/elements/table/table.svelte';
|
||||
import { isSameDay, toLocaleDate } from '$lib/helpers/date';
|
||||
import { parseIfString } from '$lib/helpers/object';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { isSelfHosted } from '$lib/system';
|
||||
@@ -71,10 +69,7 @@
|
||||
}
|
||||
]);
|
||||
|
||||
$: $updateCommandGroupRanks((prev) => ({
|
||||
...prev,
|
||||
migrations: 100
|
||||
}));
|
||||
$updateCommandGroupRanks({ migrations: 100 });
|
||||
|
||||
const getCurrentEndpoint = () => {
|
||||
// Remove subpaths and query strings from the current URL. Add a /v1 suffix
|
||||
|
||||
@@ -39,11 +39,7 @@
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
$: $updateCommandGroupRanks((prev) => ({
|
||||
...prev,
|
||||
webhooks: 20,
|
||||
domains: 10
|
||||
}));
|
||||
$: $updateCommandGroupRanks({ webhooks: 20, domains: 10 });
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
$showCreateBucket = true;
|
||||
},
|
||||
keys: $page.url.pathname.endsWith('storage') ? ['c'] : ['c', 'b'],
|
||||
icon: 'plus'
|
||||
icon: 'plus',
|
||||
group: 'buckets'
|
||||
},
|
||||
{
|
||||
label: 'Go to usage',
|
||||
@@ -23,12 +24,14 @@
|
||||
goto(`/console/project-${$project.$id}/storage/usage`);
|
||||
},
|
||||
keys: ['g', 'u'],
|
||||
disabled: $page.url.pathname.endsWith('usage'),
|
||||
group: 'storage'
|
||||
disabled:
|
||||
$page.url.pathname.endsWith('usage') || $page.url.pathname.includes('bucket-'),
|
||||
group: 'navigation',
|
||||
rank: 10
|
||||
}
|
||||
]);
|
||||
|
||||
$: $updateCommandGroupRanks((prev) => ({ ...prev, storage: 10 }));
|
||||
$: $updateCommandGroupRanks({ buckets: 200, files: 150, navigation: 100 });
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -1,3 +1,109 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import {
|
||||
addSubPanel,
|
||||
registerCommands,
|
||||
registerSearchers,
|
||||
updateCommandGroupRanks
|
||||
} from '$lib/commandCenter';
|
||||
import { FilesPanel } from '$lib/commandCenter/panels';
|
||||
import { fileSearcher } from '$lib/commandCenter/searchers';
|
||||
import { project } from '../../store';
|
||||
import { showCreateFile } from './+page.svelte';
|
||||
import { bucket } from './store';
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Search files',
|
||||
callback() {
|
||||
addSubPanel(FilesPanel);
|
||||
},
|
||||
keys: ['f', 'l'],
|
||||
group: 'files',
|
||||
icon: 'search'
|
||||
},
|
||||
{
|
||||
label: 'Create file',
|
||||
async callback() {
|
||||
if (!$page.url.pathname.endsWith($bucket.$id)) {
|
||||
goto(`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}`);
|
||||
}
|
||||
showCreateFile();
|
||||
},
|
||||
keys: $page.url.pathname.endsWith($bucket.$id) ? ['c'] : ['c', 'f'],
|
||||
group: 'files',
|
||||
icon: 'plus'
|
||||
},
|
||||
{
|
||||
label: 'Permissions',
|
||||
async callback() {
|
||||
await goto(
|
||||
`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/settings#permissions`
|
||||
);
|
||||
scrollBy({ top: -100 });
|
||||
},
|
||||
group: 'buckets',
|
||||
icon: 'key'
|
||||
},
|
||||
{
|
||||
label: 'Extensions',
|
||||
async callback() {
|
||||
await goto(
|
||||
`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/settings#extensions`
|
||||
);
|
||||
},
|
||||
group: 'buckets',
|
||||
icon: 'puzzle'
|
||||
},
|
||||
{
|
||||
label: 'File Security',
|
||||
async callback() {
|
||||
await goto(
|
||||
`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/settings#file-security`
|
||||
);
|
||||
scrollBy({ top: -100 });
|
||||
},
|
||||
group: 'buckets',
|
||||
icon: 'lock-closed'
|
||||
},
|
||||
{
|
||||
label: 'Go to files',
|
||||
callback() {
|
||||
goto(`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}`);
|
||||
},
|
||||
disabled: $page.url.pathname.endsWith($bucket.$id),
|
||||
keys: ['g', 'f'],
|
||||
group: 'navigation',
|
||||
rank: 10
|
||||
},
|
||||
{
|
||||
label: 'Go to usage',
|
||||
callback() {
|
||||
goto(`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/usage`);
|
||||
},
|
||||
disabled: $page.url.pathname.endsWith('usage'),
|
||||
keys: ['g', 'u'],
|
||||
group: 'navigation',
|
||||
rank: 10
|
||||
},
|
||||
{
|
||||
label: 'Go to settings',
|
||||
callback() {
|
||||
goto(`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/settings`);
|
||||
},
|
||||
disabled: $page.url.pathname.endsWith('settings'),
|
||||
keys: ['g', 's'],
|
||||
group: 'navigation',
|
||||
rank: 10
|
||||
}
|
||||
]);
|
||||
|
||||
$: $updateCommandGroupRanks({ files: 300 });
|
||||
|
||||
$registerSearchers(fileSearcher);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Bucket - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
<script lang="ts" context="module">
|
||||
let showCreate = writable(false);
|
||||
|
||||
export const showCreateFile = () => {
|
||||
showCreate.set(true);
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -36,10 +44,10 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let showCreate = false;
|
||||
let showDelete = false;
|
||||
let showDropdown = [];
|
||||
let selectedFile: Models.File = null;
|
||||
@@ -50,7 +58,7 @@
|
||||
sdk.forProject.storage.getFilePreview(bucketId, fileId, 32, 32).toString() + '&mode=admin';
|
||||
|
||||
async function fileCreated() {
|
||||
showCreate = false;
|
||||
$showCreate = false;
|
||||
await invalidate(Dependencies.FILES);
|
||||
}
|
||||
|
||||
@@ -78,7 +86,7 @@
|
||||
|
||||
<Container>
|
||||
<SearchQuery search={data.search} placeholder="Search by filename">
|
||||
<Button on:click={() => (showCreate = true)} event="create_file">
|
||||
<Button on:click={() => ($showCreate = true)} event="create_file">
|
||||
<span class="icon-plus" aria-hidden="true" /> <span class="text">Create file</span>
|
||||
</Button>
|
||||
</SearchQuery>
|
||||
@@ -211,11 +219,11 @@
|
||||
single
|
||||
href="https://appwrite.io/docs/storage#createFile"
|
||||
target="file"
|
||||
on:click={() => (showCreate = true)} />
|
||||
on:click={() => ($showCreate = true)} />
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<Create bind:showCreate on:created={fileCreated} />
|
||||
<Create bind:showCreate={$showCreate} on:created={fileCreated} />
|
||||
{#if selectedFile}
|
||||
<Delete file={selectedFile} bind:showDelete on:deleted={fileDeleted} />
|
||||
{/if}
|
||||
|
||||
@@ -258,7 +258,7 @@
|
||||
|
||||
<Form onSubmit={updatePermissions}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">Permissions</Heading>
|
||||
<Heading tag="h6" size="7" id="permissions">Permissions</Heading>
|
||||
<p class="text">
|
||||
Choose who can access your buckets and files. For more information, check out
|
||||
the <a
|
||||
@@ -282,7 +282,7 @@
|
||||
|
||||
<Form onSubmit={updateFileSecurity}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">File Security</Heading>
|
||||
<Heading tag="h6" size="7" id="file-security">File Security</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputSwitch
|
||||
@@ -406,7 +406,7 @@
|
||||
|
||||
<Form onSubmit={updateAllowedExtensions}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7">File Extensions</Heading>
|
||||
<Heading tag="h6" size="7" id="extensions">File Extensions</Heading>
|
||||
<p class="text">
|
||||
Allowed file extensions. A maximum of 100 file extensions can be added. Leave
|
||||
blank to allow all file types.
|
||||
|
||||
Reference in New Issue
Block a user