feat(connection): add reusable tunnel profiles
This commit is contained in:
1 parent
4dcee1aca0
commit
977e7f78ca
34 files changed
+1335
-22
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
import { ref, computed, watch } from "vue";
|
||||
import type { ColumnInfo, CompletionAssistantCandidate, CompletionAssistantObjectKind, CompletionAssistantRequest, ConnectionConfig, CatalogInfo, ForeignKeyInfo, ObjectInfo, SchemaInfo, SidebarLayout, TableInfo, TreeNode, VectorCollectionMeta } from "@/types/database";
|
||||
import type { ColumnInfo, CompletionAssistantCandidate, CompletionAssistantObjectKind, CompletionAssistantRequest, ConnectionConfig, CatalogInfo, ForeignKeyInfo, ObjectInfo, SchemaInfo, SidebarLayout, TableInfo, TreeNode, TunnelProfile, VectorCollectionMeta } from "@/types/database";
|
||||
import { applyPinnedTreeNodeState, updatePinnedTreeNodeInPlace } from "@/lib/app/pinnedItems";
|
||||
import {
|
||||
reconcileLayout,
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import type { SqlCompletionColumn, SqlCompletionForeignKey, SqlCompletionObject, SqlCompletionTable } from "@/lib/sql/sqlCompletion";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { useTunnelProfileStore } from "@/stores/tunnelProfileStore";
|
||||
import { connectionIsDorisFamilyCatalogCapable, isInternalDorisCatalog, isSchemaAware, normalizeSidebarObjectKind, sidebarObjectKindsForDatabase, usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
|
||||
import { connectionObjectTreeNodeSchema, connectionObjectTreeQuerySchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { buildDatabaseTreeNodes, buildDuckDbConnectionTreeNodes, sortSidebarDatabases, sortSidebarNames, shouldIncludeDefaultDatabaseNode } from "@/lib/database/databaseTree";
|
||||
@@ -4672,7 +4673,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
|
||||
async function exportConnectionsToFile(passphrase: string) {
|
||||
const { encryptConfig } = await import("@/lib/backend/configCrypto");
|
||||
const exportData = { connections: connections.value, layout: sidebarLayout.value };
|
||||
const tunnelProfileStore = useTunnelProfileStore();
|
||||
await tunnelProfileStore.init();
|
||||
const exportData = { connections: connections.value, layout: sidebarLayout.value, tunnelProfiles: tunnelProfileStore.profiles };
|
||||
const json = JSON.stringify(exportData);
|
||||
const payload = await encryptConfig(json, passphrase);
|
||||
const content = JSON.stringify(payload, null, 2);
|
||||
@@ -4860,6 +4863,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
async function importConnectionsFromFile(content: string, passphrase: string | null): Promise<{ count: number; layout?: SidebarLayout }> {
|
||||
let imported: ConnectionConfig[] = [];
|
||||
let importedLayout: SidebarLayout | undefined;
|
||||
let importedTunnelProfiles: TunnelProfile[] = [];
|
||||
|
||||
if (!passphrase && content.trimStart().startsWith("<")) {
|
||||
const { parseNavicatConnections } = await import("@/lib/imports/navicatImport");
|
||||
@@ -4889,6 +4893,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (parsed.layout?.groups && parsed.layout?.order) {
|
||||
importedLayout = parsed.layout;
|
||||
}
|
||||
if (Array.isArray(parsed.tunnelProfiles)) {
|
||||
importedTunnelProfiles = parsed.tunnelProfiles;
|
||||
}
|
||||
} else {
|
||||
imported = [];
|
||||
}
|
||||
@@ -4907,12 +4914,31 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (decrypted.layout?.groups && decrypted.layout?.order) {
|
||||
importedLayout = decrypted.layout;
|
||||
}
|
||||
if (Array.isArray(decrypted.tunnelProfiles)) {
|
||||
importedTunnelProfiles = decrypted.tunnelProfiles;
|
||||
}
|
||||
} else {
|
||||
imported = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Profiles keep their original ids: imported connections reference them
|
||||
// via transport_layers[].profile_id, so regenerating ids would break the
|
||||
// links. Same-id profiles are overwritten with the imported copy.
|
||||
if (importedTunnelProfiles.length) {
|
||||
const tunnelProfileStore = useTunnelProfileStore();
|
||||
await tunnelProfileStore.init();
|
||||
const merged = [...tunnelProfileStore.profiles];
|
||||
for (const profile of importedTunnelProfiles) {
|
||||
if (!profile || typeof profile.id !== "string" || !profile.id) continue;
|
||||
const index = merged.findIndex((existing) => existing.id === profile.id);
|
||||
if (index >= 0) merged[index] = profile;
|
||||
else merged.push(profile);
|
||||
}
|
||||
await tunnelProfileStore.saveProfiles(merged);
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
const importedConnectionIdMap = new Map<string, string>();
|
||||
for (const config of imported) {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import type { TunnelProfile } from "@/types/database";
|
||||
|
||||
/**
|
||||
* Shared tunnel profiles (Settings > Tunnels). Connections reference a
|
||||
* profile by id via `transport_layers[].profile_id`; the backend resolves
|
||||
* the reference at connect time, so profile edits reach every referencing
|
||||
* connection without touching the stored connections.
|
||||
*/
|
||||
export const useTunnelProfileStore = defineStore("tunnelProfiles", () => {
|
||||
const profiles = ref<TunnelProfile[]>([]);
|
||||
const isLoaded = ref(false);
|
||||
|
||||
async function init() {
|
||||
if (isLoaded.value) return;
|
||||
await refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
profiles.value = (await api.loadTunnelProfiles()) || [];
|
||||
isLoaded.value = true;
|
||||
} catch {
|
||||
// Backend unavailable (e.g. stale web session): keep previous state and
|
||||
// retry on the next init/refresh call.
|
||||
}
|
||||
}
|
||||
|
||||
function profileById(id: string | undefined): TunnelProfile | undefined {
|
||||
if (!id) return undefined;
|
||||
return profiles.value.find((profile) => profile.id === id);
|
||||
}
|
||||
|
||||
async function saveProfiles(next: TunnelProfile[]) {
|
||||
const previous = profiles.value;
|
||||
profiles.value = next;
|
||||
try {
|
||||
await api.saveTunnelProfiles(next);
|
||||
} catch (error) {
|
||||
profiles.value = previous;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return { profiles, isLoaded, init, refresh, profileById, saveProfiles };
|
||||
});
|
||||
Reference in new issue
Block a user