mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
@@ -1,17 +1,18 @@
|
||||
<script lang="ts">
|
||||
import Template from './template.svelte';
|
||||
|
||||
import { AvatarInitials, Code, LoadingDots, SvgIcon } from '$lib/components';
|
||||
import { Alert, AvatarInitials, Code, LoadingDots, SvgIcon } from '$lib/components';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { useCompletion } from 'ai/svelte';
|
||||
import { subPanels } from '../subPanels';
|
||||
|
||||
import { isLanguage, type Language } from '$lib/components/code.svelte';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { VARS } from '$lib/system';
|
||||
|
||||
const endpoint = VARS.APPWRITE_ENDPOINT ?? `${globalThis?.location?.origin}/v1`;
|
||||
|
||||
const { input, handleSubmit, completion, isLoading, complete } = useCompletion({
|
||||
const { input, handleSubmit, completion, isLoading, complete, error } = useCompletion({
|
||||
api: endpoint + '/console/assistant',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
@@ -105,7 +106,9 @@
|
||||
})}
|
||||
clearOnCallback={false}
|
||||
on:keydown={(e) => {
|
||||
e.detail.cancel();
|
||||
if (e.detail.key !== 'Escape') {
|
||||
e.detail.cancel();
|
||||
}
|
||||
}}
|
||||
--min-height="40rem"
|
||||
--max-height="52.5rem">
|
||||
@@ -118,6 +121,21 @@
|
||||
<span>{option.label}</span>
|
||||
</div>
|
||||
|
||||
{#if !$preferences.hideAiDisclaimer}
|
||||
<div style="padding: 1rem; padding-block-end: 0;">
|
||||
<Alert
|
||||
type="default"
|
||||
dismissible
|
||||
on:dismiss={() => {
|
||||
$preferences.hideAiDisclaimer = true;
|
||||
}}>
|
||||
<span slot="title">
|
||||
We collect user responses to refine our experimental AI feature.
|
||||
</span>
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $isLoading || answer}
|
||||
<div class="content">
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
@@ -140,7 +158,13 @@
|
||||
<div
|
||||
class="u-margin-block-start-8"
|
||||
style="margin-block-end: 1rem;">
|
||||
<Code language={part.language} code={part.value} noMargin />
|
||||
<Code
|
||||
label={part.language}
|
||||
language={part.language}
|
||||
code={part.value}
|
||||
noMargin
|
||||
noBoxPadding
|
||||
withCopy />
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
@@ -151,6 +175,18 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $error}
|
||||
<div style="padding: 1rem; padding-block-end: 0;">
|
||||
<Alert type="error">
|
||||
<span slot="title">Something went wrong</span>
|
||||
<p>
|
||||
An unexpected error occurred while handling your request. Please try again
|
||||
later.
|
||||
</p>
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="footer" slot="footer">
|
||||
<div class="u-flex u-cross-center u-gap-4">
|
||||
<AvatarInitials size={32} name={$user.name} />
|
||||
@@ -201,6 +237,14 @@
|
||||
--logo-bg: #f2f2f8;
|
||||
}
|
||||
|
||||
:global(.theme-dark) .footer {
|
||||
--sep-clr: hsl(var(--color-neutral-150));
|
||||
}
|
||||
|
||||
:global(.theme-light) .footer {
|
||||
--sep-clr: hsl(var(--color-neutral-30));
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: auto;
|
||||
padding: 1rem;
|
||||
@@ -230,7 +274,7 @@
|
||||
.sep {
|
||||
width: 1px;
|
||||
height: 1.5rem;
|
||||
background-color: hsl(var(--color-neutral-150));
|
||||
background-color: var(--sep-clr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { commandGroupRanks, type CommandGroup, type Command } from '../commands';
|
||||
|
||||
import { commandGroupRanks, type Command, type CommandGroup } 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.
|
||||
|
||||
@@ -82,19 +81,9 @@
|
||||
tick().then(() => {
|
||||
if (!cardEl) return;
|
||||
|
||||
const resultEls = Array.from(contentEl.querySelectorAll('.result'));
|
||||
const selectedEl = contentEl.querySelector('[data-selected]');
|
||||
const selectedIdx = resultEls.indexOf(selectedEl);
|
||||
|
||||
if (selectedIdx === 0) {
|
||||
contentEl.scrollTo({
|
||||
top: 0
|
||||
});
|
||||
} else if (selectedIdx === resultEls.length - 1) {
|
||||
contentEl.scrollTo({
|
||||
top: contentEl.scrollHeight
|
||||
});
|
||||
} else if (selectedEl) {
|
||||
if (selectedEl) {
|
||||
selectedEl.scrollIntoView({
|
||||
block: 'nearest'
|
||||
});
|
||||
@@ -276,6 +265,7 @@
|
||||
</div>
|
||||
|
||||
<div class="content" bind:this={contentEl}>
|
||||
<slot />
|
||||
{#if groupsAndOptions}
|
||||
<ul class="options">
|
||||
{#each groupsAndOptions as item, i}
|
||||
@@ -320,8 +310,6 @@
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -386,7 +374,7 @@
|
||||
--crumb-color: hsl(var(--color-neutral-100));
|
||||
|
||||
--result-bg: hsl(var(--color-neutral-10));
|
||||
--footer-bg: linear-gradient(180deg, #fff 0%, #e8e9f0 100%);
|
||||
--footer-bg: linear-gradient(180deg, #fff 49.38%, #e8e9f0 100%);
|
||||
|
||||
--icon-color: hsl(var(--color-neutral-50));
|
||||
--label-color: hsl(var(--color-neutral-100));
|
||||
@@ -512,6 +500,7 @@
|
||||
|
||||
.result {
|
||||
position: relative;
|
||||
scroll-margin-block: 0.5rem;
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type { Buttons } from '../stores/notifications';
|
||||
|
||||
export let dismissible = false;
|
||||
export let type: 'info' | 'success' | 'warning' | 'error' = 'info';
|
||||
export let type: 'info' | 'success' | 'warning' | 'error' | 'default' = 'info';
|
||||
export let buttons: Buttons[] = [];
|
||||
export let isAction = false;
|
||||
export let isStandalone = false;
|
||||
@@ -33,7 +33,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
<span
|
||||
class:icon-info={type === 'info'}
|
||||
class:icon-info={type === 'info' || type === 'default'}
|
||||
class:icon-check-circle={type === 'success'}
|
||||
class:icon-exclamation={type === 'warning'}
|
||||
class:icon-exclamation-circle={type === 'error'}
|
||||
@@ -44,7 +44,9 @@
|
||||
<slot name="title" />
|
||||
</h6>
|
||||
{/if}
|
||||
<p class="alert-message"><slot /></p>
|
||||
{#if $$slots.default}
|
||||
<p class="alert-message"><slot /></p>
|
||||
{/if}
|
||||
{#if ($$slots.buttons || buttons?.length) && !isAction}
|
||||
<div class="alert-buttons u-flex">
|
||||
<slot name="buttons">
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
export let withLineNumbers = false;
|
||||
export let withCopy = false;
|
||||
export let noMargin = false;
|
||||
export let noBoxPadding = false;
|
||||
export let allowScroll = false;
|
||||
|
||||
Prism.plugins.customClass.prefix('prism-');
|
||||
@@ -38,7 +39,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<section class="box u-overflow-hidden" class:common-section={!noMargin}>
|
||||
<section class="box u-overflow-hidden" class:common-section={!noMargin} class:noBoxPadding>
|
||||
<div
|
||||
class="controls u-position-absolute u-inset-inline-end-8 u-inset-block-start-8 u-flex u-gap-8">
|
||||
{#if label}
|
||||
@@ -79,11 +80,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.noBoxPadding {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.with-scroll {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding-inline-end: 7rem !important; // Add space for label and copy btn
|
||||
}
|
||||
|
||||
code,
|
||||
pre {
|
||||
&[class*='language-'] {
|
||||
@@ -116,7 +125,7 @@
|
||||
:not(pre) > code[class*='language-'],
|
||||
pre[class*='language-'] {
|
||||
background: hsl(var(--p-box-background-color));
|
||||
padding-block-start: 4%;
|
||||
|
||||
margin: 0;
|
||||
}
|
||||
.prism-token {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { get, writable } from 'svelte/store';
|
||||
import { sdk } from './sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { organization } from './organization';
|
||||
import { onDestroy } from 'svelte';
|
||||
|
||||
type Preferences = {
|
||||
limit?: number;
|
||||
@@ -27,31 +28,39 @@ type PreferencesStore = {
|
||||
[key: string]: TeamPreferences['names'];
|
||||
};
|
||||
};
|
||||
};
|
||||
} & { hideAiDisclaimer?: boolean };
|
||||
|
||||
function createPreferences() {
|
||||
const { subscribe, set, update } = writable<PreferencesStore>({});
|
||||
let preferences: PreferencesStore = {};
|
||||
|
||||
if (browser) {
|
||||
set(JSON.parse(globalThis.localStorage.getItem('preferences') ?? '{}'));
|
||||
}
|
||||
|
||||
subscribe((v) => {
|
||||
preferences = v;
|
||||
if (browser) {
|
||||
globalThis.localStorage.setItem('preferences', JSON.stringify(v));
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
get: (route: Page['route']): Preferences => {
|
||||
let preferences: PreferencesStore;
|
||||
subscribe((n) => (preferences = n))();
|
||||
|
||||
set,
|
||||
update,
|
||||
get: (route?: Page['route']): Preferences => {
|
||||
const parsedRoute = route ?? get(page).route;
|
||||
return (
|
||||
preferences[sdk.forProject.client.config.project]?.[route.id] ?? {
|
||||
preferences[sdk.forProject.client.config.project]?.[parsedRoute.id] ?? {
|
||||
limit: null,
|
||||
view: null,
|
||||
columns: null
|
||||
}
|
||||
);
|
||||
},
|
||||
getCustomCollectionColumns: (collectionId: string): Preferences['columns'] => {
|
||||
let preferences: PreferencesStore;
|
||||
subscribe((n) => (preferences = n))();
|
||||
|
||||
getCustomCollectionColumns: (collectionId: string): Preferences['columns'] => {
|
||||
return (
|
||||
preferences[sdk.forProject.client.config.project]?.collections?.[collectionId] ??
|
||||
null
|
||||
@@ -122,9 +131,7 @@ function createPreferences() {
|
||||
getDisplayNames: () => {
|
||||
const id = get(organization)?.$id;
|
||||
if (!id) return {};
|
||||
let preferences: PreferencesStore;
|
||||
|
||||
subscribe((n) => (preferences = n))();
|
||||
return preferences?.[id]?.displayNames ?? {};
|
||||
},
|
||||
setDisplayNames: async (collectionId: string, names: TeamPreferences['names']) => {
|
||||
@@ -147,7 +154,3 @@ function createPreferences() {
|
||||
}
|
||||
|
||||
export const preferences = createPreferences();
|
||||
|
||||
if (browser) {
|
||||
preferences.subscribe((n) => globalThis.localStorage.setItem('preferences', JSON.stringify(n)));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,29 @@
|
||||
<script>
|
||||
import { addSubPanel, registerCommands } from '$lib/commandCenter';
|
||||
import { TeamsPanel, UsersPanel } from '$lib/commandCenter/panels';
|
||||
import { Container } from '$lib/layout';
|
||||
import { loading } from '../store';
|
||||
|
||||
loading.set(false);
|
||||
|
||||
$registerCommands([
|
||||
{
|
||||
label: 'Find users',
|
||||
callback: () => {
|
||||
addSubPanel(UsersPanel);
|
||||
},
|
||||
group: 'users',
|
||||
rank: -1
|
||||
},
|
||||
{
|
||||
label: 'Find teams',
|
||||
callback: () => {
|
||||
addSubPanel(TeamsPanel);
|
||||
},
|
||||
group: 'teams',
|
||||
rank: -1
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { CommandCenter, registerCommands, registerSearchers } from '$lib/commandCenter';
|
||||
import { AIPanel } from '$lib/commandCenter/panels';
|
||||
import { AIPanel, OrganizationsPanel, ProjectsPanel } from '$lib/commandCenter/panels';
|
||||
import { orgSearcher, projectsSearcher } from '$lib/commandCenter/searchers';
|
||||
import { addSubPanel } from '$lib/commandCenter/subPanels';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -205,6 +205,23 @@
|
||||
disabled: isOnSettingsLayout && $page.url.pathname.includes('smtp'),
|
||||
group: isOnSettingsLayout ? 'navigation' : 'settings',
|
||||
rank: -1
|
||||
},
|
||||
// Searcher panels
|
||||
{
|
||||
label: 'Find organizations',
|
||||
callback: () => {
|
||||
addSubPanel(OrganizationsPanel);
|
||||
},
|
||||
group: 'organizations',
|
||||
rank: -1
|
||||
},
|
||||
{
|
||||
label: 'Find projects',
|
||||
callback: () => {
|
||||
addSubPanel(ProjectsPanel);
|
||||
},
|
||||
group: 'projects',
|
||||
rank: -1
|
||||
}
|
||||
]);
|
||||
let isOpen = false;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
|
||||
import { addSubPanel, registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
|
||||
import { DatabasesPanel } from '$lib/commandCenter/panels';
|
||||
import { project } from '../store';
|
||||
|
||||
$: $registerCommands([
|
||||
@@ -14,6 +15,14 @@
|
||||
disabled:
|
||||
$page.url.pathname.includes('usage') || $page.url.pathname.includes('database-'),
|
||||
group: 'databases'
|
||||
},
|
||||
{
|
||||
label: 'Find databases',
|
||||
callback: () => {
|
||||
addSubPanel(DatabasesPanel);
|
||||
},
|
||||
group: 'databases',
|
||||
rank: -1
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import {
|
||||
addSubPanel,
|
||||
registerCommands,
|
||||
registerSearchers,
|
||||
updateCommandGroupRanks
|
||||
@@ -12,6 +13,7 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import CreateCollection from './createCollection.svelte';
|
||||
import { showCreate } from './store';
|
||||
import { CollectionsPanel } from '$lib/commandCenter/panels';
|
||||
|
||||
const project = $page.params.project;
|
||||
const databaseId = $page.params.database;
|
||||
@@ -69,6 +71,14 @@
|
||||
$page.url.pathname.includes('collection-'),
|
||||
keys: ['g', 's'],
|
||||
group: 'collections'
|
||||
},
|
||||
{
|
||||
label: 'Find collections',
|
||||
callback: () => {
|
||||
addSubPanel(CollectionsPanel);
|
||||
},
|
||||
group: 'collections',
|
||||
rank: -1
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { addSubPanel, registerCommands } from '$lib/commandCenter';
|
||||
import { FunctionsPanel } from '$lib/commandCenter/panels';
|
||||
|
||||
$registerCommands([
|
||||
{
|
||||
label: 'Find functions',
|
||||
callback: () => {
|
||||
addSubPanel(FunctionsPanel);
|
||||
},
|
||||
group: 'functions',
|
||||
rank: -1
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Functions - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { onMount } from 'svelte';
|
||||
import Initial from '$lib/wizards/functions/cover.svelte';
|
||||
import { registerCommands } from '$lib/commandCenter';
|
||||
import { registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
|
||||
import CreateTemplate from '$lib/wizards/functions/createTemplate.svelte';
|
||||
import {
|
||||
templateConfig as templateConfigStore,
|
||||
@@ -63,9 +63,12 @@
|
||||
callback: openWizard,
|
||||
keys: ['c'],
|
||||
disabled: $wizard.show,
|
||||
icon: 'plus'
|
||||
icon: 'plus',
|
||||
group: 'functions'
|
||||
}
|
||||
]);
|
||||
|
||||
$updateCommandGroupRanks({ functions: 1000 });
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
|
||||
import { addSubPanel, registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
|
||||
import { BucketsPanel } from '$lib/commandCenter/panels';
|
||||
import { project } from '../store';
|
||||
import { showCreateBucket } from './+page.svelte';
|
||||
|
||||
@@ -28,6 +29,14 @@
|
||||
$page.url.pathname.endsWith('usage') || $page.url.pathname.includes('bucket-'),
|
||||
group: 'navigation',
|
||||
rank: 10
|
||||
},
|
||||
{
|
||||
label: 'Find buckets',
|
||||
callback: () => {
|
||||
addSubPanel(BucketsPanel);
|
||||
},
|
||||
group: 'storage',
|
||||
rank: -1
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user