feat(meilisearch): add index creation
This commit is contained in:
18 files changed
+304
-4
No files matched your search
@@ -1,19 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Activity, Database, FileText, KeyRound, RefreshCcw, Tags } from "@lucide/vue";
|
||||
import { Activity, Database, FileText, KeyRound, Loader2, Plus, RefreshCcw, Tags } from "@lucide/vue";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import ErrorBanner from "@/components/ui/ErrorBanner.vue";
|
||||
import QueryLoadingState from "@/components/common/QueryLoadingState.vue";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { formatBytes } from "@/lib/database/serverMetrics";
|
||||
import type { MeilisearchSystemOverview, OverviewSection } from "@/types/meilisearchManagement";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { connectionIsEffectivelyReadOnly } from "@/lib/database/readOnlyWriteAccess";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
|
||||
const props = defineProps<{ connectionId: string }>();
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const connectionStore = useConnectionStore();
|
||||
const loading = ref(false);
|
||||
const error = ref("");
|
||||
const overview = ref<MeilisearchSystemOverview | null>(null);
|
||||
const createOpen = ref(false);
|
||||
const createWorking = ref(false);
|
||||
const createError = ref("");
|
||||
const createUid = ref("");
|
||||
const createPrimaryKey = ref("");
|
||||
const readOnly = computed(() => connectionIsEffectivelyReadOnly(connectionStore.getConfig(props.connectionId)));
|
||||
|
||||
const stats = computed(() => overview.value?.stats.data ?? null);
|
||||
const cards = computed(() => [
|
||||
@@ -42,6 +55,42 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
createUid.value = "";
|
||||
createPrimaryKey.value = "";
|
||||
createError.value = "";
|
||||
createOpen.value = true;
|
||||
}
|
||||
|
||||
async function createIndex() {
|
||||
const uid = createUid.value.trim();
|
||||
if (!uid) {
|
||||
createError.value = t("meilisearch.createIndexUidRequired");
|
||||
return;
|
||||
}
|
||||
|
||||
createWorking.value = true;
|
||||
createError.value = "";
|
||||
try {
|
||||
await api.meilisearchCreateIndex(props.connectionId, {
|
||||
uid,
|
||||
...(createPrimaryKey.value.trim() ? { primaryKey: createPrimaryKey.value.trim() } : {}),
|
||||
});
|
||||
createOpen.value = false;
|
||||
toast(t("meilisearch.indexCreated"));
|
||||
await load();
|
||||
try {
|
||||
await connectionStore.loadElasticsearchIndices(props.connectionId);
|
||||
} catch (refreshError) {
|
||||
console.warn("[DBX][meilisearch-index-refresh:error]", refreshError);
|
||||
}
|
||||
} catch (cause: any) {
|
||||
createError.value = cause?.message || String(cause);
|
||||
} finally {
|
||||
createWorking.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => void load());
|
||||
</script>
|
||||
|
||||
@@ -52,7 +101,10 @@ onMounted(() => void load());
|
||||
<h2 class="text-base font-semibold">{{ t("meilisearch.overview") }}</h2>
|
||||
<p class="text-xs text-muted-foreground">{{ t("meilisearch.overviewDescription") }}</p>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" :disabled="loading" @click="load"><RefreshCcw class="mr-1 h-3.5 w-3.5" />{{ t("meilisearch.refresh") }}</Button>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button size="sm" variant="outline" :disabled="readOnly" @click="openCreate"><Plus class="mr-1 h-3.5 w-3.5" />{{ t("meilisearch.createIndex") }}</Button>
|
||||
<Button size="sm" variant="outline" :disabled="loading" @click="load"><RefreshCcw class="mr-1 h-3.5 w-3.5" />{{ t("meilisearch.refresh") }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
<QueryLoadingState v-if="loading && !overview" class="py-12" />
|
||||
<ErrorBanner v-else-if="error && !overview" :message="error" />
|
||||
@@ -120,5 +172,27 @@ onMounted(() => void load());
|
||||
<p v-else class="p-4 text-xs text-muted-foreground">{{ sectionLabel(overview.topIndexes) }}</p>
|
||||
</section>
|
||||
</template>
|
||||
<Dialog v-model:open="createOpen">
|
||||
<DialogContent class="max-w-md">
|
||||
<DialogHeader
|
||||
><DialogTitle>{{ t("meilisearch.createIndex") }}</DialogTitle></DialogHeader
|
||||
>
|
||||
<div class="grid gap-3 text-xs">
|
||||
<label class="grid gap-1"
|
||||
><span class="font-medium">{{ t("meilisearch.uid") }}</span
|
||||
><Input v-model="createUid" :disabled="createWorking" :placeholder="t('meilisearch.createIndexUidPlaceholder')"
|
||||
/></label>
|
||||
<label class="grid gap-1"
|
||||
><span class="font-medium">{{ t("meilisearch.primaryKey") }}</span
|
||||
><Input v-model="createPrimaryKey" :disabled="createWorking" :placeholder="t('meilisearch.createIndexPrimaryKeyPlaceholder')" /><span class="text-[11px] text-muted-foreground">{{ t("meilisearch.createIndexPrimaryKeyHelp") }}</span></label
|
||||
>
|
||||
<ErrorBanner v-if="createError" :message="createError" />
|
||||
</div>
|
||||
<DialogFooter
|
||||
><Button variant="outline" :disabled="createWorking" @click="createOpen = false">{{ t("common.cancel") }}</Button
|
||||
><Button :disabled="createWorking || readOnly" @click="createIndex"><Loader2 v-if="createWorking" class="mr-1 h-3.5 w-3.5 animate-spin" />{{ t("meilisearch.createIndex") }}</Button></DialogFooter
|
||||
>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -10,6 +10,7 @@ import JsonTree from "@/components/common/JsonTree.vue";
|
||||
import RedisJsonEditor from "@/components/redis/RedisJsonEditor.vue";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -23,6 +24,7 @@ const emit = defineEmits<{
|
||||
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const connectionStore = useConnectionStore();
|
||||
const queryStore = useQueryStore();
|
||||
|
||||
const settingsText = ref("");
|
||||
@@ -116,6 +118,11 @@ async function confirmDeleteIndex() {
|
||||
await api.meilisearchDeleteIndex(props.connectionId, props.index);
|
||||
toast(t("meilisearch.indexDeleted"));
|
||||
deleteIndexConfirmOpen.value = false;
|
||||
try {
|
||||
await connectionStore.loadElasticsearchIndices(props.connectionId);
|
||||
} catch (refreshError) {
|
||||
console.warn("[DBX][meilisearch-index-refresh:error]", refreshError);
|
||||
}
|
||||
// The tab may have been closed while the delete was in flight.
|
||||
if (ownerTabId && queryStore.tabs.some((tab) => tab.id === ownerTabId)) queryStore.closeTab(ownerTabId);
|
||||
} catch (e: any) {
|
||||
|
||||
+2
@@ -7,6 +7,7 @@ const mocks = vi.hoisted(() => ({
|
||||
deleteIndex: vi.fn(),
|
||||
getIndexSettings: vi.fn(),
|
||||
closeTab: vi.fn(),
|
||||
refreshTree: vi.fn(),
|
||||
toast: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -51,6 +52,7 @@ vi.mock("@/components/common/QueryLoadingState.vue", () => ({ default: passthrou
|
||||
vi.mock("@/components/common/JsonTree.vue", () => ({ default: passthrough("div") }));
|
||||
vi.mock("@/components/redis/RedisJsonEditor.vue", () => ({ default: passthrough("div") }));
|
||||
vi.mock("@/composables/useToast", () => ({ useToast: () => ({ toast: mocks.toast }) }));
|
||||
vi.mock("@/stores/connectionStore", () => ({ useConnectionStore: () => ({ loadElasticsearchIndices: mocks.refreshTree }) }));
|
||||
vi.mock("@/lib/backend/api", () => ({
|
||||
meilisearchGetIndexSettings: mocks.getIndexSettings,
|
||||
meilisearchDeleteIndex: mocks.deleteIndex,
|
||||
|
||||
@@ -113,6 +113,12 @@ const {
|
||||
cloneMongoCollectionLoading,
|
||||
confirmCloneMongoCollection,
|
||||
showCreateMongoIndexDialog,
|
||||
showCreateMeilisearchIndexDialog,
|
||||
meilisearchCreateIndexUid,
|
||||
meilisearchCreateIndexPrimaryKey,
|
||||
meilisearchCreateIndexError,
|
||||
meilisearchCreateIndexLoading,
|
||||
confirmCreateMeilisearchIndex,
|
||||
mongoCreateIndexForm,
|
||||
mongoCreateIndexFieldOptions,
|
||||
mongoCreateIndexError,
|
||||
@@ -208,6 +214,7 @@ watch(
|
||||
showRenameMongoCollectionDialog,
|
||||
showCloneMongoCollectionDialog,
|
||||
showCreateMongoIndexDialog,
|
||||
showCreateMeilisearchIndexDialog,
|
||||
showMongoIndexManagerDialog,
|
||||
showRedisDatabaseAliasDialog,
|
||||
showCreateSchemaDialog,
|
||||
@@ -401,6 +408,33 @@ watch(
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="showCreateMeilisearchIndexDialog">
|
||||
<DialogContent class="sm:max-w-[420px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("meilisearch.createIndex") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="grid gap-3">
|
||||
<label class="grid gap-1.5 text-sm font-medium">
|
||||
{{ t("meilisearch.createIndexUid") }}
|
||||
<Input v-model="meilisearchCreateIndexUid" :disabled="meilisearchCreateIndexLoading" :placeholder="t('meilisearch.createIndexUidPlaceholder')" @keydown.enter.prevent="confirmCreateMeilisearchIndex" />
|
||||
</label>
|
||||
<label class="grid gap-1.5 text-sm font-medium">
|
||||
{{ t("meilisearch.createIndexPrimaryKey") }}
|
||||
<Input v-model="meilisearchCreateIndexPrimaryKey" :disabled="meilisearchCreateIndexLoading" :placeholder="t('meilisearch.createIndexPrimaryKeyPlaceholder')" />
|
||||
<span class="text-xs font-normal text-muted-foreground">{{ t("meilisearch.createIndexPrimaryKeyHelp") }}</span>
|
||||
</label>
|
||||
<p v-if="meilisearchCreateIndexError" class="text-sm text-destructive">{{ meilisearchCreateIndexError }}</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" :disabled="meilisearchCreateIndexLoading" @click="showCreateMeilisearchIndexDialog = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
<Button :disabled="meilisearchCreateIndexLoading || !meilisearchCreateIndexUid.trim()" @click="confirmCreateMeilisearchIndex">
|
||||
<Loader2 v-if="meilisearchCreateIndexLoading" class="mr-2 h-4 w-4 animate-spin" />
|
||||
{{ t("meilisearch.createIndex") }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="showRedisDatabaseAliasDialog">
|
||||
<DialogContent class="sm:max-w-[420px]">
|
||||
<DialogHeader>
|
||||
|
||||
@@ -316,6 +316,11 @@ import {
|
||||
showDropAllMongoIndexesConfirm,
|
||||
dropAllMongoIndexesLoading,
|
||||
showCreateMongoIndexDialog,
|
||||
showCreateMeilisearchIndexDialog,
|
||||
meilisearchCreateIndexUid,
|
||||
meilisearchCreateIndexPrimaryKey,
|
||||
meilisearchCreateIndexError,
|
||||
meilisearchCreateIndexLoading,
|
||||
mongoCreateIndexForm,
|
||||
mongoCreateIndexFieldOptions,
|
||||
mongoCreateIndexError,
|
||||
@@ -566,6 +571,9 @@ const {
|
||||
mongoCreateIndexCanSubmit,
|
||||
mongoCreateIndexCanAddField,
|
||||
prepareCreateMongoIndexDialog,
|
||||
canCreateMeilisearchIndex,
|
||||
prepareCreateMeilisearchIndexDialog,
|
||||
confirmCreateMeilisearchIndex,
|
||||
addMongoCreateIndexField,
|
||||
removeMongoCreateIndexField,
|
||||
confirmCreateMongoIndex,
|
||||
@@ -1446,6 +1454,12 @@ function openCreateMongoIndexDialog() {
|
||||
prepareCreateMongoIndexDialog();
|
||||
}
|
||||
|
||||
function openCreateMeilisearchIndexDialog() {
|
||||
claimTreeItemDialogOwnership();
|
||||
routeTreeItemDialogController();
|
||||
prepareCreateMeilisearchIndexDialog();
|
||||
}
|
||||
|
||||
function openMongoIndexManagerDialog() {
|
||||
claimTreeItemDialogOwnership();
|
||||
routeTreeItemDialogController();
|
||||
@@ -5311,6 +5325,12 @@ function databaseSpecificDialogCapabilities() {
|
||||
cloneMongoCollectionLoading,
|
||||
confirmCloneMongoCollection,
|
||||
showCreateMongoIndexDialog,
|
||||
showCreateMeilisearchIndexDialog,
|
||||
meilisearchCreateIndexUid,
|
||||
meilisearchCreateIndexPrimaryKey,
|
||||
meilisearchCreateIndexError,
|
||||
meilisearchCreateIndexLoading,
|
||||
confirmCreateMeilisearchIndex,
|
||||
mongoCreateIndexForm,
|
||||
mongoCreateIndexFieldOptions,
|
||||
mongoCreateIndexError,
|
||||
@@ -5549,6 +5569,9 @@ function buildConnectionSidebarMenu(context: SidebarMenuFactoryContext): boolean
|
||||
items.push(addToAiMenuItem(node));
|
||||
}
|
||||
}
|
||||
if (canCreateMeilisearchIndex.value) {
|
||||
items.push({ label: t("meilisearch.createIndex"), action: openCreateMeilisearchIndexDialog, icon: Plus });
|
||||
}
|
||||
const connectionWorkspace = node.connectionId ? driverProfileDatabaseWorkspace(connectionStore.getConfig(node.connectionId)?.driver_profile) : undefined;
|
||||
if (connectionWorkspace?.entryScopes.includes("connection")) {
|
||||
items.push({ label: t(connectionWorkspace.menuLabelKey), action: openProfileConnectionWorkspace, icon: GitBranch });
|
||||
|
||||
@@ -133,6 +133,11 @@ export const dropMongoIndexLoading = ref(false);
|
||||
export const showDropAllMongoIndexesConfirm = ref(false);
|
||||
export const dropAllMongoIndexesLoading = ref(false);
|
||||
export const showCreateMongoIndexDialog = ref(false);
|
||||
export const showCreateMeilisearchIndexDialog = ref(false);
|
||||
export const meilisearchCreateIndexUid = ref("");
|
||||
export const meilisearchCreateIndexPrimaryKey = ref("");
|
||||
export const meilisearchCreateIndexError = ref("");
|
||||
export const meilisearchCreateIndexLoading = ref(false);
|
||||
|
||||
function emptyMongoCreateIndexForm(): MongoCreateIndexForm {
|
||||
return {
|
||||
@@ -234,6 +239,7 @@ const openFlags = [
|
||||
showDropMongoIndexConfirm,
|
||||
showDropAllMongoIndexesConfirm,
|
||||
showCreateMongoIndexDialog,
|
||||
showCreateMeilisearchIndexDialog,
|
||||
showMongoIndexManagerDialog,
|
||||
showClearElasticsearchIndexConfirm,
|
||||
showFlushRedisDbConfirm,
|
||||
@@ -268,6 +274,10 @@ export function resetSidebarTreeDialogState() {
|
||||
cloneMongoCollectionError.value = "";
|
||||
cloneMongoCollectionLoading.value = false;
|
||||
resetMongoCreateIndexForm();
|
||||
meilisearchCreateIndexUid.value = "";
|
||||
meilisearchCreateIndexPrimaryKey.value = "";
|
||||
meilisearchCreateIndexError.value = "";
|
||||
meilisearchCreateIndexLoading.value = false;
|
||||
resetMongoIndexManager();
|
||||
vacuumTableExecuting.value = false;
|
||||
sidebarTreeDialogOwner.value = null;
|
||||
|
||||
Reference in new issue
Block a user