fix(elasticsearch): apply document filters in browser
This commit is contained in:
1 parent
3f25a69d23
commit
7923f4915e
14 files changed
+780
-173
No files matched your search
+73
-163
@@ -12,6 +12,7 @@ import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
|
||||
import DataGrid from "@/components/grid/DataGrid.vue";
|
||||
import * as api from "@/lib/api";
|
||||
import { clampSearchSplitWidth } from "@/lib/dataGridSearchSplit";
|
||||
import { buildDocumentFilterCondition, combineDocumentFilterConditions, currentDocumentFilterJson, defaultDocumentFilterRule, documentFilterModeNeedsValue, documentFilterModeOptions, documentStoreProviderFor, type DocumentFilterMode, type DocumentFilterRule } from "@/lib/documentStoreProvider";
|
||||
import { normalizeResultPageSize } from "@/lib/paginationPageSize";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import JsonEditNode from "./JsonEditNode.vue";
|
||||
@@ -60,22 +61,16 @@ const tableFindPaneWidth = ref<number | null>(null);
|
||||
const isResizingTableSearchSplit = ref(false);
|
||||
let tableSearchSplitStartX = 0;
|
||||
let tableSearchSplitStartWidth = 0;
|
||||
const documentStoreProvider = computed(() => documentStoreProviderFor(props.databaseType));
|
||||
|
||||
const tableFindPaneStyle = computed(() => {
|
||||
if (tableFindPaneWidth.value == null) return {};
|
||||
return { flex: `0 0 ${tableFindPaneWidth.value}px` };
|
||||
});
|
||||
const documentStoreLabels = computed(() =>
|
||||
props.databaseType === "elasticsearch"
|
||||
? {
|
||||
documentsLabel: "Documents",
|
||||
queryPreview: `${props.collection}/_search`,
|
||||
}
|
||||
: {
|
||||
documentsLabel: t("mongo.documents", { count: total.value }),
|
||||
queryPreview: mongoQueryPreview.value,
|
||||
},
|
||||
);
|
||||
const documentStoreLabels = computed(() => ({
|
||||
documentsLabel: documentStoreProvider.value.documentsLabel({ total: total.value, t }),
|
||||
queryPreview: documentQueryPreview.value,
|
||||
}));
|
||||
|
||||
type PendingDelete = { kind: "document"; index: number } | { kind: "field"; index: number; name: string };
|
||||
type LocalFilterSummary = {
|
||||
@@ -84,28 +79,9 @@ type LocalFilterSummary = {
|
||||
values: string[];
|
||||
hiddenValueCount: number;
|
||||
};
|
||||
type MongoFilterMode = "equals" | "not-equals" | "like" | "not-like" | "greater-than" | "less-than" | "is-null" | "is-not-null";
|
||||
type MongoFilterRule = {
|
||||
id: string;
|
||||
fieldName: string;
|
||||
mode: MongoFilterMode;
|
||||
rawValue: string;
|
||||
conjunction: "AND" | "OR";
|
||||
};
|
||||
|
||||
const mongoFilterModeOptions: Array<{ value: MongoFilterMode; labelKey: string }> = [
|
||||
{ value: "equals", labelKey: "grid.filterBuilderEquals" },
|
||||
{ value: "not-equals", labelKey: "grid.filterBuilderNotEquals" },
|
||||
{ value: "like", labelKey: "grid.filterBuilderContains" },
|
||||
{ value: "not-like", labelKey: "grid.filterBuilderNotContains" },
|
||||
{ value: "greater-than", labelKey: "grid.filterBuilderGreaterThan" },
|
||||
{ value: "less-than", labelKey: "grid.filterBuilderLessThan" },
|
||||
{ value: "is-null", labelKey: "grid.filterBuilderIsNull" },
|
||||
{ value: "is-not-null", labelKey: "grid.filterBuilderIsNotNull" },
|
||||
];
|
||||
const mongoFilterBuilderOpen = ref(false);
|
||||
const mongoFilterRules = ref<MongoFilterRule[]>([]);
|
||||
const appliedMongoFilter = ref<Record<string, unknown> | null>(null);
|
||||
const documentFilterBuilderOpen = ref(false);
|
||||
const documentFilterRules = ref<DocumentFilterRule[]>([]);
|
||||
const appliedDocumentFilter = ref<Record<string, unknown> | null>(null);
|
||||
|
||||
const pendingDelete = ref<PendingDelete | null>(null);
|
||||
|
||||
@@ -165,139 +141,77 @@ const gridResult = computed<QueryResult>(() => {
|
||||
|
||||
return { columns, rows, affected_rows: 0, execution_time_ms: 0, truncated: false };
|
||||
});
|
||||
const mongoFilterFieldOptions = computed(() => gridResult.value.columns);
|
||||
const mongoStructuredFilterCount = computed(() => (appliedMongoFilter.value ? 1 : 0));
|
||||
const documentFilterFieldOptions = computed(() => gridResult.value.columns);
|
||||
const documentStructuredFilterCount = computed(() => (appliedDocumentFilter.value ? 1 : 0));
|
||||
|
||||
function defaultMongoFilterRule(): MongoFilterRule {
|
||||
return {
|
||||
id: uuid(),
|
||||
fieldName: mongoFilterFieldOptions.value[0] ?? "",
|
||||
mode: "equals",
|
||||
rawValue: "",
|
||||
conjunction: "AND",
|
||||
};
|
||||
function createDocumentFilterRule(): DocumentFilterRule {
|
||||
return defaultDocumentFilterRule(uuid(), documentFilterFieldOptions.value[0] ?? "");
|
||||
}
|
||||
|
||||
function ensureMongoFilterRule() {
|
||||
if (mongoFilterRules.value.length === 0 && mongoFilterFieldOptions.value.length > 0) {
|
||||
mongoFilterRules.value = [defaultMongoFilterRule()];
|
||||
function ensureDocumentFilterRule() {
|
||||
if (documentFilterRules.value.length === 0 && documentFilterFieldOptions.value.length > 0) {
|
||||
documentFilterRules.value = [createDocumentFilterRule()];
|
||||
}
|
||||
}
|
||||
|
||||
function addMongoFilterRule() {
|
||||
ensureMongoFilterRule();
|
||||
mongoFilterRules.value = [...mongoFilterRules.value, defaultMongoFilterRule()];
|
||||
function addDocumentFilterRule() {
|
||||
ensureDocumentFilterRule();
|
||||
documentFilterRules.value = [...documentFilterRules.value, createDocumentFilterRule()];
|
||||
}
|
||||
|
||||
function removeMongoFilterRule(ruleId: string) {
|
||||
mongoFilterRules.value = mongoFilterRules.value.filter((rule) => rule.id !== ruleId);
|
||||
if (mongoFilterRules.value.length === 0) appliedMongoFilter.value = null;
|
||||
function removeDocumentFilterRule(ruleId: string) {
|
||||
documentFilterRules.value = documentFilterRules.value.filter((rule) => rule.id !== ruleId);
|
||||
if (documentFilterRules.value.length === 0) appliedDocumentFilter.value = null;
|
||||
}
|
||||
|
||||
function updateMongoFilterRule(ruleId: string, patch: Partial<MongoFilterRule>) {
|
||||
mongoFilterRules.value = mongoFilterRules.value.map((rule) => {
|
||||
function updateDocumentFilterRule(ruleId: string, patch: Partial<DocumentFilterRule>) {
|
||||
documentFilterRules.value = documentFilterRules.value.map((rule) => {
|
||||
if (rule.id !== ruleId) return rule;
|
||||
const next = { ...rule, ...patch };
|
||||
if (!mongoFilterModeNeedsValue(next.mode)) next.rawValue = "";
|
||||
if (!documentFilterModeNeedsValue(next.mode)) next.rawValue = "";
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
function resetMongoFilterBuilder() {
|
||||
appliedMongoFilter.value = null;
|
||||
mongoFilterRules.value = mongoFilterFieldOptions.value.length > 0 ? [defaultMongoFilterRule()] : [];
|
||||
function resetDocumentFilterBuilder() {
|
||||
appliedDocumentFilter.value = null;
|
||||
documentFilterRules.value = documentFilterFieldOptions.value.length > 0 ? [createDocumentFilterRule()] : [];
|
||||
}
|
||||
|
||||
function mongoFilterModeNeedsValue(mode: MongoFilterMode): boolean {
|
||||
return mode !== "is-null" && mode !== "is-not-null";
|
||||
function currentDocumentFilter(): string | undefined {
|
||||
return currentDocumentFilterJson(filterInput.value, appliedDocumentFilter.value);
|
||||
}
|
||||
|
||||
function parseMongoFilterValue(raw: string): unknown {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return "";
|
||||
try {
|
||||
return JSON.parse(trimmed);
|
||||
} catch {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
function mongoConditionForRule(rule: MongoFilterRule): Record<string, unknown> | null {
|
||||
if (!rule.fieldName) return null;
|
||||
if (mongoFilterModeNeedsValue(rule.mode) && !rule.rawValue.trim()) return null;
|
||||
const value = mongoFilterModeNeedsValue(rule.mode) ? parseMongoFilterValue(rule.rawValue) : null;
|
||||
switch (rule.mode) {
|
||||
case "equals":
|
||||
return { [rule.fieldName]: value };
|
||||
case "not-equals":
|
||||
return { [rule.fieldName]: { $ne: value } };
|
||||
case "like":
|
||||
return { [rule.fieldName]: { $regex: String(value), $options: "i" } };
|
||||
case "not-like":
|
||||
return { [rule.fieldName]: { $not: { $regex: String(value), $options: "i" } } };
|
||||
case "greater-than":
|
||||
return { [rule.fieldName]: { $gt: value } };
|
||||
case "less-than":
|
||||
return { [rule.fieldName]: { $lt: value } };
|
||||
case "is-null":
|
||||
return { [rule.fieldName]: null };
|
||||
case "is-not-null":
|
||||
return { [rule.fieldName]: { $ne: null } };
|
||||
}
|
||||
}
|
||||
|
||||
function combineMongoConditions(conditions: Record<string, unknown>[], rules: MongoFilterRule[]): Record<string, unknown> | null {
|
||||
if (conditions.length === 0) return null;
|
||||
let result = conditions[0];
|
||||
for (let i = 1; i < conditions.length; i++) {
|
||||
const operator = rules[i]?.conjunction === "OR" ? "$or" : "$and";
|
||||
result = { [operator]: [result, conditions[i]] };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseMongoFilterInput(): Record<string, unknown> {
|
||||
const trimmed = filterInput.value.trim();
|
||||
if (!trimmed) return {};
|
||||
const parsed = JSON.parse(trimmed);
|
||||
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};
|
||||
}
|
||||
|
||||
function currentMongoFilter(): string | undefined {
|
||||
const manual = parseMongoFilterInput();
|
||||
const structured = appliedMongoFilter.value;
|
||||
const filter = structured ? (Object.keys(manual).length ? { $and: [manual, structured] } : structured) : manual;
|
||||
return Object.keys(filter).length ? JSON.stringify(filter) : undefined;
|
||||
}
|
||||
|
||||
const mongoQueryPreview = computed(() => {
|
||||
const documentQueryPreview = computed(() => {
|
||||
let filter = "{}";
|
||||
try {
|
||||
filter = currentMongoFilter() ?? "{}";
|
||||
filter = currentDocumentFilter() ?? "{}";
|
||||
} catch {
|
||||
filter = filterInput.value.trim() || "{}";
|
||||
}
|
||||
const sort = sortInput.value.trim();
|
||||
const parts = [`db.${props.collection}.find(${filter})`];
|
||||
if (sort) parts.push(`.sort(${sort})`);
|
||||
parts.push(`.skip(${page.value * pageSize.value}).limit(${pageSize.value})`);
|
||||
return parts.join("");
|
||||
return documentStoreProvider.value.queryPreview({
|
||||
collection: props.collection,
|
||||
filterJson: filter,
|
||||
sortJson: sortInput.value.trim(),
|
||||
skip: page.value * pageSize.value,
|
||||
limit: pageSize.value,
|
||||
});
|
||||
});
|
||||
|
||||
async function applyMongoStructuredFilters() {
|
||||
const items = mongoFilterRules.value.map((rule) => ({ rule, condition: mongoConditionForRule(rule) })).filter((item): item is { rule: MongoFilterRule; condition: Record<string, unknown> } => !!item.condition);
|
||||
const structured = combineMongoConditions(
|
||||
async function applyDocumentStructuredFilters() {
|
||||
const items = documentFilterRules.value.map((rule) => ({ rule, condition: buildDocumentFilterCondition(rule) })).filter((item): item is { rule: DocumentFilterRule; condition: Record<string, unknown> } => !!item.condition);
|
||||
const structured = combineDocumentFilterConditions(
|
||||
items.map((item) => item.condition),
|
||||
items.map((item) => item.rule),
|
||||
);
|
||||
appliedMongoFilter.value = structured;
|
||||
mongoFilterBuilderOpen.value = false;
|
||||
appliedDocumentFilter.value = structured;
|
||||
documentFilterBuilderOpen.value = false;
|
||||
applyFilter();
|
||||
}
|
||||
|
||||
function clearMongoFilters(clearLocalFilter?: (columnIndex?: number) => void) {
|
||||
appliedMongoFilter.value = null;
|
||||
resetMongoFilterBuilder();
|
||||
function clearDocumentFilters(clearLocalFilter?: (columnIndex?: number) => void) {
|
||||
appliedDocumentFilter.value = null;
|
||||
resetDocumentFilterBuilder();
|
||||
clearLocalFilter?.();
|
||||
applyFilter();
|
||||
}
|
||||
@@ -346,9 +260,9 @@ async function load() {
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const filter = currentMongoFilter();
|
||||
const filter = currentDocumentFilter();
|
||||
const sort = sortInput.value.trim() || undefined;
|
||||
const result = await api.mongoFindDocuments(props.connectionId, props.database, props.collection, page.value * pageSize.value, pageSize.value, filter, sort);
|
||||
const result = await api.documentFindDocuments(props.connectionId, props.database, props.collection, page.value * pageSize.value, pageSize.value, filter, sort);
|
||||
const nextDocuments = result.documents.map(asRecord);
|
||||
documents.value = nextDocuments;
|
||||
if (nextDocuments.length > 0) {
|
||||
@@ -382,11 +296,7 @@ function paginate(offset: number, limit: number) {
|
||||
}
|
||||
|
||||
function onSort(column: string, _columnIndex: number, direction: "asc" | "desc" | null) {
|
||||
if (direction) {
|
||||
sortInput.value = JSON.stringify({ [column]: direction === "asc" ? 1 : -1 });
|
||||
} else {
|
||||
sortInput.value = "";
|
||||
}
|
||||
sortInput.value = documentStoreProvider.value.sortInputForColumn(column, direction);
|
||||
page.value = 0;
|
||||
load();
|
||||
}
|
||||
@@ -791,24 +701,24 @@ function resetTableSearchSplitWidth() {
|
||||
<template #search-bar="{ localFilterCount, hasLocalColumnFilters, localFilterSummaries, clearLocalFilter }: { localFilterCount: number; hasLocalColumnFilters: boolean; localFilterSummaries: LocalFilterSummary[]; clearLocalFilter: (columnIndex?: number) => void }">
|
||||
<div ref="tableSearchSplitContainerRef" class="flex flex-1 min-w-0">
|
||||
<div class="flex flex-1 items-center gap-1 px-2 py-0.5 min-w-0" :style="tableFindPaneStyle">
|
||||
<Popover v-model:open="mongoFilterBuilderOpen">
|
||||
<Popover v-model:open="documentFilterBuilderOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
class="relative flex h-5 w-5 shrink-0 items-center justify-center rounded border text-[11px] font-medium transition-colors"
|
||||
:class="hasLocalColumnFilters || appliedMongoFilter ? 'border-primary/40 bg-primary/10 text-primary hover:bg-primary/15' : 'border-border/70 text-muted-foreground hover:bg-accent hover:text-foreground'"
|
||||
@click="ensureMongoFilterRule"
|
||||
:class="hasLocalColumnFilters || appliedDocumentFilter ? 'border-primary/40 bg-primary/10 text-primary hover:bg-primary/15' : 'border-border/70 text-muted-foreground hover:bg-accent hover:text-foreground'"
|
||||
@click="ensureDocumentFilterRule"
|
||||
>
|
||||
<Filter class="h-3 w-3" />
|
||||
<span v-if="localFilterCount + mongoStructuredFilterCount" class="absolute -right-1 -top-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-primary px-1 text-[9px] leading-none text-primary-foreground">
|
||||
{{ localFilterCount + mongoStructuredFilterCount }}
|
||||
<span v-if="localFilterCount + documentStructuredFilterCount" class="absolute -right-1 -top-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-primary px-1 text-[9px] leading-none text-primary-foreground">
|
||||
{{ localFilterCount + documentStructuredFilterCount }}
|
||||
</span>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" class="w-[360px] max-w-[calc(100vw-24px)] gap-3 p-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="text-xs font-medium text-foreground">{{ t("grid.filter") }}</div>
|
||||
<Button variant="ghost" size="sm" class="h-7 px-2 text-xs" @click="addMongoFilterRule">
|
||||
<Button variant="ghost" size="sm" class="h-7 px-2 text-xs" @click="addDocumentFilterRule">
|
||||
<Plus class="mr-1 h-3.5 w-3.5" />
|
||||
{{ t("grid.filterBuilderAddRule") }}
|
||||
</Button>
|
||||
@@ -845,15 +755,15 @@ function resetTableSearchSplitWidth() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="mongoFilterRules.length" class="space-y-2">
|
||||
<template v-for="(rule, index) in mongoFilterRules" :key="rule.id">
|
||||
<div v-if="documentFilterRules.length" class="space-y-2">
|
||||
<template v-for="(rule, index) in documentFilterRules" :key="rule.id">
|
||||
<div v-if="index > 0" class="flex justify-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-6 px-2 text-[11px] font-medium text-muted-foreground hover:text-foreground"
|
||||
@click="
|
||||
updateMongoFilterRule(rule.id, {
|
||||
updateDocumentFilterRule(rule.id, {
|
||||
conjunction: rule.conjunction === 'AND' ? 'OR' : 'AND',
|
||||
})
|
||||
"
|
||||
@@ -862,41 +772,41 @@ function resetTableSearchSplitWidth() {
|
||||
</Button>
|
||||
</div>
|
||||
<div class="grid grid-cols-[minmax(0,1fr)_minmax(0,0.95fr)_minmax(0,1fr)_auto] items-center gap-1.5">
|
||||
<Select :model-value="rule.fieldName" @update:model-value="(value: any) => updateMongoFilterRule(rule.id, { fieldName: String(value) })">
|
||||
<Select :model-value="rule.fieldName" @update:model-value="(value: any) => updateDocumentFilterRule(rule.id, { fieldName: String(value) })">
|
||||
<SelectTrigger class="h-8 w-full min-w-0 overflow-hidden text-xs [&_[data-slot=select-value]]:min-w-0 [&_[data-slot=select-value]]:truncate">
|
||||
<SelectValue :placeholder="t('grid.filterBuilderColumn')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
<SelectItem v-for="fieldName in mongoFilterFieldOptions" :key="fieldName" :value="fieldName">
|
||||
<SelectItem v-for="fieldName in documentFilterFieldOptions" :key="fieldName" :value="fieldName">
|
||||
{{ fieldName }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select :model-value="rule.mode" @update:model-value="(value: any) => updateMongoFilterRule(rule.id, { mode: value as MongoFilterMode })">
|
||||
<Select :model-value="rule.mode" @update:model-value="(value: any) => updateDocumentFilterRule(rule.id, { mode: value as DocumentFilterMode })">
|
||||
<SelectTrigger class="h-8 w-full min-w-0 overflow-hidden text-xs [&_[data-slot=select-value]]:min-w-0 [&_[data-slot=select-value]]:truncate">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
<SelectItem v-for="option in mongoFilterModeOptions" :key="option.value" :value="option.value">
|
||||
<SelectItem v-for="option in documentFilterModeOptions" :key="option.value" :value="option.value">
|
||||
{{ t(option.labelKey) }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Input
|
||||
v-if="mongoFilterModeNeedsValue(rule.mode)"
|
||||
v-if="documentFilterModeNeedsValue(rule.mode)"
|
||||
:model-value="rule.rawValue"
|
||||
class="h-8 min-w-0 text-xs"
|
||||
:placeholder="t('grid.filterBuilderValue')"
|
||||
@update:model-value="(value) => updateMongoFilterRule(rule.id, { rawValue: String(value ?? '') })"
|
||||
@keydown.enter.prevent="applyMongoStructuredFilters"
|
||||
@update:model-value="(value) => updateDocumentFilterRule(rule.id, { rawValue: String(value ?? '') })"
|
||||
@keydown.enter.prevent="applyDocumentStructuredFilters"
|
||||
/>
|
||||
<div v-else class="flex h-8 min-w-0 items-center overflow-hidden rounded-md border border-dashed px-2 text-xs text-muted-foreground">
|
||||
<span class="truncate">{{ t("grid.filterBuilderNoValue") }}</span>
|
||||
</div>
|
||||
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8 shrink-0 text-muted-foreground hover:text-destructive" :disabled="mongoFilterRules.length === 1" @click="removeMongoFilterRule(rule.id)">
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8 shrink-0 text-muted-foreground hover:text-destructive" :disabled="documentFilterRules.length === 1" @click="removeDocumentFilterRule(rule.id)">
|
||||
<Trash2 class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -907,21 +817,21 @@ function resetTableSearchSplitWidth() {
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-2 pt-1">
|
||||
<Button variant="ghost" size="sm" class="h-8 px-2 text-xs" @click="clearMongoFilters(clearLocalFilter)">
|
||||
<Button variant="ghost" size="sm" class="h-8 px-2 text-xs" @click="clearDocumentFilters(clearLocalFilter)">
|
||||
{{ t("grid.clearFilter") }}
|
||||
</Button>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" size="sm" class="h-8 px-2 text-xs" @click="resetMongoFilterBuilder">
|
||||
<Button variant="ghost" size="sm" class="h-8 px-2 text-xs" @click="resetDocumentFilterBuilder">
|
||||
{{ t("grid.resetFilterBuilder") }}
|
||||
</Button>
|
||||
<Button size="sm" class="h-8 px-3 text-xs" @click="applyMongoStructuredFilters">
|
||||
<Button size="sm" class="h-8 px-3 text-xs" @click="applyDocumentStructuredFilters">
|
||||
{{ t("grid.applyFilter") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<span class="text-blue-600 dark:text-blue-400 text-xs font-medium select-none shrink-0">find</span>
|
||||
<span class="text-blue-600 dark:text-blue-400 text-xs font-medium select-none shrink-0">{{ documentStoreProvider.filterInputLabel }}</span>
|
||||
<input v-model="filterInput" autocapitalize="off" autocorrect="off" spellcheck="false" class="flex-1 h-5 min-w-0 text-xs bg-transparent outline-none placeholder:text-muted-foreground/60 font-mono" placeholder="{}" @keydown.enter="applyFilter" />
|
||||
<button
|
||||
v-if="filterInput.trim()"
|
||||
@@ -937,14 +847,14 @@ function resetTableSearchSplitWidth() {
|
||||
<button
|
||||
type="button"
|
||||
class="group relative flex w-2 shrink-0 cursor-col-resize items-center justify-center border-l border-r border-border/80 bg-muted/15 hover:bg-primary/10 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary"
|
||||
aria-label="Resize find and sort"
|
||||
aria-label="Resize document filter and sort"
|
||||
@mousedown="startTableSearchSplitResize"
|
||||
@dblclick.stop="resetTableSearchSplitWidth"
|
||||
>
|
||||
<span class="h-5 w-px bg-border group-hover:bg-primary/60" />
|
||||
</button>
|
||||
<div class="flex flex-1 items-center gap-1 px-2 py-0.5 min-w-0">
|
||||
<span class="text-orange-600 dark:text-orange-400 text-xs font-medium select-none shrink-0">sort</span>
|
||||
<span class="text-orange-600 dark:text-orange-400 text-xs font-medium select-none shrink-0">{{ documentStoreProvider.sortInputLabel }}</span>
|
||||
<input v-model="sortInput" autocapitalize="off" autocorrect="off" spellcheck="false" class="flex-1 h-5 min-w-0 text-xs bg-transparent outline-none placeholder:text-muted-foreground/60 font-mono" placeholder="{}" @keydown.enter="applyFilter" />
|
||||
<button
|
||||
v-if="sortInput.trim()"
|
||||
File renamed without changes.
@@ -33,7 +33,7 @@ function preloadDataGridComponent() {
|
||||
const DataGrid = defineAsyncComponent(loadDataGridComponent);
|
||||
const RedisKeyBrowser = defineAsyncComponent(() => import("@/components/redis/RedisKeyBrowser.vue"));
|
||||
const EtcdKeyBrowser = defineAsyncComponent(() => import("@/components/etcd/EtcdKeyBrowser.vue"));
|
||||
const MongoDocBrowser = defineAsyncComponent(() => import("@/components/mongo/MongoDocBrowser.vue"));
|
||||
const DocumentBrowser = defineAsyncComponent(() => import("@/components/document/DocumentBrowser.vue"));
|
||||
const ObjectBrowser = defineAsyncComponent(() => import("@/components/objects/ObjectBrowser.vue"));
|
||||
const TableStructureEditor = defineAsyncComponent(() => import("@/components/structure/TableStructureEditor.vue"));
|
||||
const DatabaseUserAdmin = defineAsyncComponent(() => import("@/components/admin/DatabaseUserAdmin.vue"));
|
||||
@@ -823,7 +823,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget });
|
||||
<!-- Document mode: MongoDB collections and Elasticsearch indices -->
|
||||
<template v-else-if="activeTab.mode === 'mongo'">
|
||||
<div class="flex-1 min-h-0">
|
||||
<MongoDocBrowser :key="activeTab.id" :connection-id="activeTab.connectionId" :database="activeTab.database" :collection="activeTab.sql" :database-type="activeEffectiveDatabaseType" />
|
||||
<DocumentBrowser :key="activeTab.id" :connection-id="activeTab.connectionId" :database="activeTab.database" :collection="activeTab.sql" :database-type="activeEffectiveDatabaseType" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in new issue
Block a user