diff --git a/apps/desktop/src/components/diff/DataCompareDialog.vue b/apps/desktop/src/components/diff/DataCompareDialog.vue
index ff9b7f0e47..56d14e7214 100644
--- a/apps/desktop/src/components/diff/DataCompareDialog.vue
+++ b/apps/desktop/src/components/diff/DataCompareDialog.vue
@@ -114,7 +114,11 @@ const showModified = ref(true);
let syncPlanRequestId = 0;
+<<<<<<< HEAD
+const sqlConnections = computed(() => store.connections.filter((connection) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd", "zookeeper", "mq", "nacos"].includes(connection.db_type)));
+=======
const sqlConnections = computed(() => store.connections.filter((connection) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "weaviate", "etcd", "mq", "nacos"].includes(connection.db_type)));
+>>>>>>> origin/main
const selectedSourceTableNames = computed(() => sourceTables.value.filter((table) => selectedSourceTables.value.has(table)));
const isBatchCompare = computed(() => selectedSourceTableNames.value.length > 1);
const filteredSourceTables = computed(() => {
diff --git a/apps/desktop/src/components/etcd/EtcdKeyBrowser.vue b/apps/desktop/src/components/etcd/EtcdKeyBrowser.vue
index e1a262d866..5106de5120 100644
--- a/apps/desktop/src/components/etcd/EtcdKeyBrowser.vue
+++ b/apps/desktop/src/components/etcd/EtcdKeyBrowser.vue
@@ -1,297 +1,52 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t("etcd.loadingKeys") }}
-
-
- {{ t("etcd.empty") }}
-
-
-
-
-
-
-
-
-
-
-
- {{ t("etcd.selectKey") }}
-
-
-
-
-
{{ selectedKey }}
-
- rev {{ metadataLabel(selectedMetadata?.modRevision) }}
- ver {{ metadataLabel(selectedMetadata?.version) }}
- lease {{ metadataLabel(selectedMetadata?.lease) }}
- {{ metadataLabel(selectedMetadata?.valueSize) }} B
-
-
-
-
-
-
-
-
-
- {{ t("etcd.loadingValue") }}
-
-
{{ detailError }}
-
- {{ t("etcd.notFound") }}
-
-
{{ selectedTextValue }}
-
-
-
-
-
-
-
-
+
diff --git a/apps/desktop/src/components/export/DatabaseExportDialog.vue b/apps/desktop/src/components/export/DatabaseExportDialog.vue
index b32aaa40ce..bd5df672be 100644
--- a/apps/desktop/src/components/export/DatabaseExportDialog.vue
+++ b/apps/desktop/src/components/export/DatabaseExportDialog.vue
@@ -65,7 +65,11 @@ const exportCancelled = ref(false);
const pendingPrefillTable = ref("");
const pendingPrefillTables = ref
([]);
+<<<<<<< HEAD
+const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd", "zookeeper", "mq", "nacos"].includes(c.db_type)));
+=======
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "weaviate", "etcd", "mq", "nacos"].includes(c.db_type)));
+>>>>>>> origin/main
const canExport = computed(() => connectionId.value && database.value && schema.value && !loadingTables.value && !tableError.value && (tables.value.length === 0 || selectedTables.value.length > 0) && (includeStructure.value || includeData.value || includeObjects.value) && !isExporting.value);
diff --git a/apps/desktop/src/components/icons/DatabaseIcon.vue b/apps/desktop/src/components/icons/DatabaseIcon.vue
index 7545ebbfe2..6d88998118 100644
--- a/apps/desktop/src/components/icons/DatabaseIcon.vue
+++ b/apps/desktop/src/components/icons/DatabaseIcon.vue
@@ -85,6 +85,7 @@ const assetIcons: Record = {
nacos: "nacos.png",
iris: "iris.png",
influxdb: "influxdb",
+ zookeeper: "zookeeper",
};
const letterIcons: Record = {};
diff --git a/apps/desktop/src/components/kv/KvKeyBrowser.vue b/apps/desktop/src/components/kv/KvKeyBrowser.vue
new file mode 100644
index 0000000000..581bbecedd
--- /dev/null
+++ b/apps/desktop/src/components/kv/KvKeyBrowser.vue
@@ -0,0 +1,536 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ labels.loadingKeys }}
+
+
+ {{ labels.empty }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ labels.selectKey }}
+
+
+
+
+
{{ selectedKey }}
+
+
+
+ {{ `${badge.label} ${badge.value}` }}
+
+
+
+ rev {{ metadataLabel(selectedMetadata?.modRevision) }}
+ ver {{ metadataLabel(selectedMetadata?.version) }}
+ lease {{ metadataLabel(selectedMetadata?.lease) }}
+ {{ metadataLabel(selectedMetadata?.valueSize) }} B
+
+
+
+
+
+
+
+
+
+
+ {{ labels.loadingValue }}
+
+
{{ detailError }}
+
+ {{ labels.notFound }}
+
+
+
+
{{ labels.value || "Value" }}
+
{{ displayedSelectedTextValue }}
+
+
+
+
+
+
{{ labels.metadata || "Metadata" }}
+
+
+
{{ row.label }}
+
{{ row.value }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/desktop/src/components/layout/AppTabBar.vue b/apps/desktop/src/components/layout/AppTabBar.vue
index 51c9e8ea2e..4b4f8c4703 100644
--- a/apps/desktop/src/components/layout/AppTabBar.vue
+++ b/apps/desktop/src/components/layout/AppTabBar.vue
@@ -235,7 +235,7 @@ const openTabMenuItems = computed(() =>
function tabMenuIcon(tab: QueryTab) {
if (tab.mode === "data" || tab.mode === "mongo" || tab.mode === "redis") return Table2;
if (tab.mode === "vector") return TableProperties;
- if (tab.mode === "etcd") return KeyRound;
+ if (tab.mode === "etcd" || tab.mode === "zookeeper") return KeyRound;
if (tab.mode === "nacos") return Network;
if (tab.mode === "objects") return TableProperties;
if (tab.mode === "structure") return PencilRuler;
@@ -329,7 +329,7 @@ function activateTab(tabId: string) {
-
+
diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue
index d940a335b9..cf0750d461 100644
--- a/apps/desktop/src/components/layout/ContentArea.vue
+++ b/apps/desktop/src/components/layout/ContentArea.vue
@@ -37,6 +37,7 @@ const DataGrid = defineAsyncComponent(loadDataGridComponent);
const RedisKeyBrowser = defineAsyncComponent(() => import("@/components/redis/RedisKeyBrowser.vue"));
const RedisDashboard = defineAsyncComponent(() => import("@/components/redis/RedisDashboard.vue"));
const EtcdKeyBrowser = defineAsyncComponent(() => import("@/components/etcd/EtcdKeyBrowser.vue"));
+const ZooKeeperKeyBrowser = defineAsyncComponent(() => import("@/components/zookeeper/ZooKeeperKeyBrowser.vue"));
const DocumentBrowser = defineAsyncComponent(() => import("@/components/document/DocumentBrowser.vue"));
const VectorBrowser = defineAsyncComponent(() => import("@/components/vector/VectorBrowser.vue"));
const MqAdminConsole = defineAsyncComponent(() => import("@/components/mq/MqAdminConsole.vue"));
@@ -94,6 +95,7 @@ type DataGridHandle = {
type SearchableBrowserHandle = {
focusSearch: () => boolean;
+ refresh?: () => boolean;
};
const props = defineProps<{
@@ -145,6 +147,7 @@ onMounted(() => {
} else {
setTimeout(preload, 300);
}
+ window.addEventListener("dbx-refresh-active-kv-browser", onRefreshActiveKvBrowser);
});
watch(
@@ -167,6 +170,7 @@ const columnVisibilitySearch = ref("");
const columnVisibilityOptions = computed(() => dataGridRef.value?.filteredColumnVisibilityOptions(columnVisibilitySearch.value) ?? []);
const redisKeyBrowserRef = ref();
const etcdKeyBrowserRef = ref();
+const zookeeperKeyBrowserRef = ref();
const objectBrowserRef = ref();
const activeTableMeta = computed(() => props.activeTab.tableMeta);
const activeDataTabTableMeta = computed(() => tableMetaForDataTab(props.activeTab));
@@ -298,7 +302,10 @@ const queryRunningElapsedSeconds = computed(() => (queryRunningElapsed.value / 1
watch(() => [props.activeTab.id, props.activeTab.isExecuting, props.activeTab.queryExecutionStartedAt] as const, startQueryRunningElapsedTimer, { immediate: true });
-onUnmounted(stopQueryRunningElapsedTimer);
+onUnmounted(() => {
+ stopQueryRunningElapsedTimer();
+ window.removeEventListener("dbx-refresh-active-kv-browser", onRefreshActiveKvBrowser);
+});
watch(
hasQueryOutput,
@@ -474,17 +481,26 @@ function onHandleCloseColumnPanel() {
function focusSearch(): boolean {
if (props.activeTab.mode === "redis") return redisKeyBrowserRef.value?.focusSearch() ?? false;
if (props.activeTab.mode === "etcd") return etcdKeyBrowserRef.value?.focusSearch() ?? false;
+ if (props.activeTab.mode === "zookeeper") return zookeeperKeyBrowserRef.value?.focusSearch() ?? false;
if (props.activeTab.mode === "objects") return objectBrowserRef.value?.focusSearch() ?? false;
if (props.activeTab.mode === "query") return queryEditorRef.value?.openSearch() ?? false;
return dataGridRef.value?.focusSearch() ?? false;
}
function refreshData(): boolean {
+ if (props.activeTab.mode === "etcd") return etcdKeyBrowserRef.value?.refresh?.() ?? false;
+ if (props.activeTab.mode === "zookeeper") return zookeeperKeyBrowserRef.value?.refresh?.() ?? false;
if (!dataGridRef.value) return false;
void dataGridRef.value.onToolbarRefresh();
return true;
}
+function onRefreshActiveKvBrowser(event: Event) {
+ const detail = (event as CustomEvent<{ mode?: string; connectionId?: string }>).detail;
+ if (!detail || props.activeTab.mode !== detail.mode || props.activeTab.connectionId !== detail.connectionId) return;
+ void nextTick(() => refreshData());
+}
+
async function exportResultArchive() {
if (resultArchiveExporting.value) return;
resultArchiveExporting.value = true;
@@ -1034,6 +1050,13 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe