feat(connection): choose group when creating a connection
This commit is contained in:
13 files changed
+230
-39
No files matched your search
@@ -260,6 +260,20 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const store = useConnectionStore();
|
||||
const UNGROUPED_CONNECTION_GROUP = "__dbx_ungrouped_connection_group__";
|
||||
const selectedConnectionGroupId = ref<string | null>(null);
|
||||
const connectionGroupSelectValue = computed({
|
||||
get: () => selectedConnectionGroupId.value ?? UNGROUPED_CONNECTION_GROUP,
|
||||
set: (value: string) => {
|
||||
selectedConnectionGroupId.value = value === UNGROUPED_CONNECTION_GROUP ? null : value;
|
||||
},
|
||||
});
|
||||
const connectionGroupOptions = computed(() => store.connectionGroupOptions.map((group) => ({ id: group.id, label: group.path.join(" / ") })));
|
||||
|
||||
function initialConnectionGroupId(): string | null {
|
||||
const preferred = store.newConnectionGroupId ?? store.selectedConnectionGroupId;
|
||||
return preferred && connectionGroupOptions.value.some((group) => group.id === preferred) ? preferred : null;
|
||||
}
|
||||
const tunnelProfileStore = useTunnelProfileStore();
|
||||
const isTesting = ref(false);
|
||||
const isSaving = ref(false);
|
||||
@@ -2582,6 +2596,7 @@ watch(
|
||||
} else {
|
||||
clearSavedDatabaseInfo();
|
||||
editingId.value = null;
|
||||
selectedConnectionGroupId.value = initialConnectionGroupId();
|
||||
form.value = defaultForm();
|
||||
redisKeyTemplatesText.value = "";
|
||||
productionProtectionEnabled.value = false;
|
||||
@@ -4818,6 +4833,7 @@ function openJdbcDriverManagerFromError() {
|
||||
|
||||
function resetForm(options: { preservePickerState?: boolean } = {}) {
|
||||
editingId.value = null;
|
||||
selectedConnectionGroupId.value = initialConnectionGroupId();
|
||||
form.value = defaultForm();
|
||||
redisKeyTemplatesText.value = "";
|
||||
resetConnectionNoteVisibilityDraft(connectionNoteVisibilityDraft, settingsStore.editorSettings.sidebarShowConnectionNotes);
|
||||
@@ -4976,6 +4992,10 @@ watch(
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(connectionGroupOptions, (groups) => {
|
||||
if (selectedConnectionGroupId.value && !groups.some((group) => group.id === selectedConnectionGroupId.value)) selectedConnectionGroupId.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.prefillConfig,
|
||||
(draft) => {
|
||||
@@ -5231,7 +5251,7 @@ async function save() {
|
||||
await ensureRequiredAgentDriverInstalled(config);
|
||||
await ensureRequiredGaussdbMJdbcRuntime(config);
|
||||
await persistGlobalTimeoutDrafts();
|
||||
await store.addConnection(config);
|
||||
await store.addConnection(config, selectedConnectionGroupId.value);
|
||||
connectionSaved = true;
|
||||
await persistConnectionNoteVisibilityDraft();
|
||||
draftTestConnectionId.value = uuid();
|
||||
@@ -5846,35 +5866,49 @@ function openExternalUrl(url: string) {
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelClass">{{ t("connection.color") }}</Label>
|
||||
<div class="col-span-3 flex items-center gap-1.5">
|
||||
<button
|
||||
v-for="color in colorOptions"
|
||||
:key="color.value || 'none'"
|
||||
type="button"
|
||||
class="h-6 w-6 rounded-full border ring-offset-background transition hover:scale-105"
|
||||
:class="[color.class, form.color === color.value ? 'ring-2 ring-ring ring-offset-2' : 'border-border']"
|
||||
:title="t(color.labelKey)"
|
||||
@click="handlePresetClick(color.value)"
|
||||
/>
|
||||
<Popover v-model:open="customColorOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
class="h-6 w-6 rounded-full border flex items-center justify-center hover:scale-105 transition"
|
||||
:class="[!isPresetColor(form.color) && form.color ? 'border-border ring-2 ring-ring ring-offset-2' : 'border-dashed border-border']"
|
||||
:style="!isPresetColor(form.color) && form.color ? { backgroundColor: form.color } : {}"
|
||||
:title="t('connection.colorCustom')"
|
||||
>
|
||||
<Pipette class="h-3.5 w-3.5" :class="!isPresetColor(form.color) && form.color ? 'text-white' : 'text-muted-foreground'" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="color" :value="form.color" @input="handleCustomColorPicked(($event.target as HTMLInputElement).value)" class="h-6 w-6 cursor-pointer rounded border-0 p-0" />
|
||||
<Input type="text" :value="customColorInput || form.color" @input="handleCustomColorInput(($event.target as HTMLInputElement).value)" class="w-28 h-7 text-xs font-mono" :placeholder="t('connection.customColorPlaceholder')" />
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div class="col-span-3 flex min-w-0 items-center">
|
||||
<div class="flex shrink-0 items-center gap-1.5">
|
||||
<button
|
||||
v-for="color in colorOptions"
|
||||
:key="color.value || 'none'"
|
||||
type="button"
|
||||
class="h-6 w-6 rounded-full border ring-offset-background transition hover:scale-105"
|
||||
:class="[color.class, form.color === color.value ? 'ring-2 ring-ring ring-offset-2' : 'border-border']"
|
||||
:title="t(color.labelKey)"
|
||||
@click="handlePresetClick(color.value)"
|
||||
/>
|
||||
<Popover v-model:open="customColorOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
class="h-6 w-6 rounded-full border flex items-center justify-center hover:scale-105 transition"
|
||||
:class="[!isPresetColor(form.color) && form.color ? 'border-border ring-2 ring-ring ring-offset-2' : 'border-dashed border-border']"
|
||||
:style="!isPresetColor(form.color) && form.color ? { backgroundColor: form.color } : {}"
|
||||
:title="t('connection.colorCustom')"
|
||||
>
|
||||
<Pipette class="h-3.5 w-3.5" :class="!isPresetColor(form.color) && form.color ? 'text-white' : 'text-muted-foreground'" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="color" :value="form.color" @input="handleCustomColorPicked(($event.target as HTMLInputElement).value)" class="h-6 w-6 cursor-pointer rounded border-0 p-0" />
|
||||
<Input type="text" :value="customColorInput || form.color" @input="handleCustomColorInput(($event.target as HTMLInputElement).value)" class="w-28 h-7 text-xs font-mono" :placeholder="t('connection.customColorPlaceholder')" />
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div v-if="!editingId && connectionGroupOptions.length > 0" class="ml-3 flex min-w-0 flex-1 items-center gap-2 border-l pl-3">
|
||||
<Label class="shrink-0 text-xs text-muted-foreground">{{ t("connectionGroup.group") }}</Label>
|
||||
<Select v-model="connectionGroupSelectValue">
|
||||
<SelectTrigger class="h-8 min-w-0 flex-1">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="UNGROUPED_CONNECTION_GROUP">{{ t("connectionGroup.ungroupedLabel") }}</SelectItem>
|
||||
<SelectItem v-for="group in connectionGroupOptions" :key="group.id" :value="group.id">{{ group.label }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2348,6 +2348,7 @@ export default {
|
||||
},
|
||||
connectionGroup: {
|
||||
createGroup: "New Group",
|
||||
group: "Group",
|
||||
groupNamePlaceholder: "Group name",
|
||||
selectConnection: "Select connection",
|
||||
deselectConnection: "Deselect connection",
|
||||
@@ -2362,7 +2363,7 @@ export default {
|
||||
moveToGroup: "Move to Group",
|
||||
moveToNewGroup: "Move to New Group",
|
||||
ungrouped: "Ungrouped",
|
||||
ungroupedLabel: "Ungrouped",
|
||||
ungroupedLabel: "Default",
|
||||
newGroup: "New Group...",
|
||||
newGroupDefault: "New Group",
|
||||
deleteGroupConfirmTitle: "Delete Group",
|
||||
|
||||
@@ -2283,6 +2283,7 @@ export default withEnglishFallback({
|
||||
},
|
||||
connectionGroup: {
|
||||
createGroup: "Nuevo grupo",
|
||||
group: "Grupo",
|
||||
groupNamePlaceholder: "Nombre del grupo",
|
||||
renameGroup: "Renombrar grupo",
|
||||
closeConnections: "Cerrar conexiones del grupo ({count})",
|
||||
@@ -2291,7 +2292,7 @@ export default withEnglishFallback({
|
||||
moveToGroup: "Mover al grupo",
|
||||
moveToNewGroup: "Mover a nuevo grupo",
|
||||
ungrouped: "Sin grupo",
|
||||
ungroupedLabel: "Sin grupo",
|
||||
ungroupedLabel: "Predeterminado",
|
||||
newGroup: "Nuevo grupo...",
|
||||
newGroupDefault: "Nuevo grupo",
|
||||
deleteGroupConfirmTitle: "Eliminar grupo",
|
||||
|
||||
@@ -2281,6 +2281,7 @@ export default withEnglishFallback({
|
||||
},
|
||||
connectionGroup: {
|
||||
createGroup: "Nuovo Gruppo",
|
||||
group: "Gruppo",
|
||||
groupNamePlaceholder: "Nome gruppo",
|
||||
renameGroup: "Rinomina Gruppo",
|
||||
closeConnections: "Chiudi le connessioni del gruppo ({count})",
|
||||
@@ -2289,7 +2290,7 @@ export default withEnglishFallback({
|
||||
moveToGroup: "Sposta nel Gruppo",
|
||||
moveToNewGroup: "Sposta in un Nuovo Gruppo",
|
||||
ungrouped: "Non raggruppati",
|
||||
ungroupedLabel: "Non raggruppati",
|
||||
ungroupedLabel: "Predefinito",
|
||||
newGroup: "Nuovo Gruppo...",
|
||||
newGroupDefault: "Nuovo Gruppo",
|
||||
deleteGroupConfirmTitle: "Elimina Gruppo",
|
||||
|
||||
@@ -2308,6 +2308,7 @@ export default withEnglishFallback({
|
||||
},
|
||||
connectionGroup: {
|
||||
createGroup: "新しいグループ",
|
||||
group: "グループ",
|
||||
groupNamePlaceholder: "グループ名",
|
||||
renameGroup: "グループ名を変更",
|
||||
closeConnections: "グループ内の接続を閉じる({count})",
|
||||
@@ -2316,7 +2317,7 @@ export default withEnglishFallback({
|
||||
moveToGroup: "グループに移動",
|
||||
moveToNewGroup: "新しいグループに移動",
|
||||
ungrouped: "グループなし",
|
||||
ungroupedLabel: "グループなし",
|
||||
ungroupedLabel: "デフォルト",
|
||||
newGroup: "新しいグループ...",
|
||||
newGroupDefault: "新しいグループ",
|
||||
deleteGroupConfirmTitle: "グループを削除",
|
||||
|
||||
@@ -2182,6 +2182,7 @@ export default withEnglishFallback({
|
||||
},
|
||||
connectionGroup: {
|
||||
createGroup: "새 그룹",
|
||||
group: "그룹",
|
||||
groupNamePlaceholder: "그룹 이름",
|
||||
selectConnection: "연결 선택",
|
||||
deselectConnection: "연결 선택 해제",
|
||||
@@ -2196,7 +2197,7 @@ export default withEnglishFallback({
|
||||
moveToGroup: "그룹으로 이동",
|
||||
moveToNewGroup: "새 그룹으로 이동",
|
||||
ungrouped: "그룹 없음",
|
||||
ungroupedLabel: "그룹 없음",
|
||||
ungroupedLabel: "기본",
|
||||
newGroup: "새 그룹...",
|
||||
newGroupDefault: "새 그룹",
|
||||
deleteGroupConfirmTitle: "그룹 삭제",
|
||||
|
||||
@@ -2283,6 +2283,7 @@ export default withEnglishFallback({
|
||||
},
|
||||
connectionGroup: {
|
||||
createGroup: "Novo Grupo",
|
||||
group: "Grupo",
|
||||
groupNamePlaceholder: "Nome do grupo",
|
||||
renameGroup: "Renomear Grupo",
|
||||
closeConnections: "Fechar conexões do grupo ({count})",
|
||||
@@ -2291,7 +2292,7 @@ export default withEnglishFallback({
|
||||
moveToGroup: "Mover para Grupo",
|
||||
moveToNewGroup: "Mover para Novo Grupo",
|
||||
ungrouped: "Sem grupo",
|
||||
ungroupedLabel: "Sem grupo",
|
||||
ungroupedLabel: "Padrão",
|
||||
newGroup: "Novo Grupo...",
|
||||
newGroupDefault: "Novo Grupo",
|
||||
deleteGroupConfirmTitle: "Excluir Grupo",
|
||||
|
||||
@@ -2272,6 +2272,7 @@ export default withEnglishFallback({
|
||||
},
|
||||
connectionGroup: {
|
||||
createGroup: "新建分组",
|
||||
group: "分组",
|
||||
groupNamePlaceholder: "分组名称",
|
||||
selectConnection: "选择连接",
|
||||
deselectConnection: "取消选择连接",
|
||||
@@ -2286,7 +2287,7 @@ export default withEnglishFallback({
|
||||
moveToGroup: "移至分组",
|
||||
moveToNewGroup: "移至新分组",
|
||||
ungrouped: "取消分组",
|
||||
ungroupedLabel: "未分组",
|
||||
ungroupedLabel: "默认",
|
||||
newGroup: "新建分组...",
|
||||
newGroupDefault: "新分组",
|
||||
deleteGroupConfirmTitle: "删除分组",
|
||||
|
||||
@@ -2282,6 +2282,7 @@ export default withEnglishFallback({
|
||||
},
|
||||
connectionGroup: {
|
||||
createGroup: "建立群組",
|
||||
group: "群組",
|
||||
groupNamePlaceholder: "群組名稱",
|
||||
renameGroup: "重新命名群組",
|
||||
closeConnections: "關閉群組內連線({count})",
|
||||
@@ -2290,7 +2291,7 @@ export default withEnglishFallback({
|
||||
moveToGroup: "移至群組",
|
||||
moveToNewGroup: "移至新群組",
|
||||
ungrouped: "取消群組",
|
||||
ungroupedLabel: "未分組",
|
||||
ungroupedLabel: "預設",
|
||||
newGroup: "新增群組……",
|
||||
newGroupDefault: "新群組",
|
||||
deleteGroupConfirmTitle: "刪除群組",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { connectionIdsInGroups, deleteGroups, findConnectionGroupPath, reorderEntries } from "@/lib/sidebar/sidebarLayout";
|
||||
import { connectionGroupDestinationRows, connectionGroupIdForSelection, connectionIdsInGroups, deleteGroups, findConnectionGroupPath, reorderEntries } from "@/lib/sidebar/sidebarLayout";
|
||||
import type { SidebarLayout } from "@/types/database";
|
||||
|
||||
const layout: SidebarLayout = {
|
||||
@@ -203,3 +203,24 @@ describe("sorted sidebar move-only drops", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("connection group selection", () => {
|
||||
it("lists nested groups in sidebar order with complete paths", () => {
|
||||
expect(connectionGroupDestinationRows(layout)).toEqual([
|
||||
{ id: "project", name: "Project", depth: 0, path: ["Project"] },
|
||||
{ id: "staging", name: "Staging", depth: 1, path: ["Project", "Staging"] },
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses the focused group or the group containing a focused connection", () => {
|
||||
expect(connectionGroupIdForSelection(layout, "staging")).toBe("staging");
|
||||
expect(connectionGroupIdForSelection(layout, "nested", "nested")).toBe("staging");
|
||||
expect(connectionGroupIdForSelection(layout, "grouped")).toBe("project");
|
||||
});
|
||||
|
||||
it("falls back to ungrouped for root or unknown selections", () => {
|
||||
expect(connectionGroupIdForSelection(layout, "root", "root")).toBeNull();
|
||||
expect(connectionGroupIdForSelection(layout, "missing")).toBeNull();
|
||||
expect(connectionGroupIdForSelection(layout)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -367,6 +367,14 @@ export function connectionGroupDestinationRows(layout: SidebarLayout): Connectio
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** Resolve the group implied by a selected group, connection, or descendant node. */
|
||||
export function connectionGroupIdForSelection(layout: SidebarLayout, selectedNodeId?: string | null, selectedConnectionId?: string | null): string | null {
|
||||
if (selectedNodeId && connectionGroupDestinationRows(layout).some((group) => group.id === selectedNodeId)) return selectedNodeId;
|
||||
const connectionId = selectedConnectionId || selectedNodeId;
|
||||
if (!connectionId) return null;
|
||||
return findConnectionLocation(layout, connectionId)?.groupId ?? null;
|
||||
}
|
||||
|
||||
function findGroupEntry(entries: SidebarOrderEntry[], groupId: string): Extract<SidebarOrderEntry, { type: "group" }> | null {
|
||||
for (const entry of entries) {
|
||||
if (entry.type !== "group") continue;
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ConnectionConfig, SidebarLayout } from "@/types/database";
|
||||
|
||||
function connection(id: string): ConnectionConfig {
|
||||
return {
|
||||
id,
|
||||
name: id,
|
||||
db_type: "mysql",
|
||||
host: "127.0.0.1",
|
||||
port: 3306,
|
||||
username: "root",
|
||||
password: "",
|
||||
connect_timeout_secs: 10,
|
||||
query_timeout_secs: 30,
|
||||
connect_timeout_inherit: false,
|
||||
query_timeout_inherit: false,
|
||||
};
|
||||
}
|
||||
|
||||
const groupedLayout: SidebarLayout = {
|
||||
groups: [
|
||||
{ id: "parent", name: "Parent", collapsed: false },
|
||||
{ id: "child", name: "Child", collapsed: false },
|
||||
],
|
||||
order: [
|
||||
{
|
||||
type: "group",
|
||||
id: "parent",
|
||||
children: [{ type: "group", id: "child", children: [{ type: "connection", id: "existing" }] }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe("connectionStore creating connections in groups", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
vi.stubGlobal("localStorage", {
|
||||
getItem: vi.fn(() => null),
|
||||
setItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
});
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
loadEditorSettings: vi.fn().mockResolvedValue(null),
|
||||
saveConnections: vi.fn().mockResolvedValue(undefined),
|
||||
saveEditorSettings: vi.fn().mockResolvedValue(undefined),
|
||||
saveSidebarLayout: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("places a new connection in the explicitly selected nested group", async () => {
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
store.sidebarLayout = structuredClone(groupedLayout);
|
||||
|
||||
await store.addConnection(connection("created"), "child");
|
||||
|
||||
expect(store.groupIdForConnection("created")).toBe("child");
|
||||
expect(store.connectionGroupPaths.get("created")).toEqual(["Parent", "Child"]);
|
||||
});
|
||||
|
||||
it("places a new connection at the top level when ungrouped is selected", async () => {
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
store.sidebarLayout = structuredClone(groupedLayout);
|
||||
store.startCreatingConnectionInGroup("child");
|
||||
|
||||
await store.addConnection(connection("created"), null);
|
||||
|
||||
expect(store.groupIdForConnection("created")).toBeNull();
|
||||
expect(store.sidebarLayout.order.at(-1)).toEqual({ type: "connection", id: "created" });
|
||||
expect(store.newConnectionGroupId).toBeNull();
|
||||
});
|
||||
|
||||
it("derives the default group from a selected descendant node", async () => {
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
store.sidebarLayout = structuredClone(groupedLayout);
|
||||
store.addEphemeralConnection(connection("existing"));
|
||||
store.treeNodes = [
|
||||
{
|
||||
id: "parent",
|
||||
label: "Parent",
|
||||
type: "connection-group",
|
||||
children: [
|
||||
{
|
||||
id: "child",
|
||||
label: "Child",
|
||||
type: "connection-group",
|
||||
children: [
|
||||
{
|
||||
id: "existing",
|
||||
label: "existing",
|
||||
type: "connection",
|
||||
connectionId: "existing",
|
||||
children: [{ id: "existing:demo", label: "demo", type: "database", connectionId: "existing" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
store.selectedTreeNodeId = "existing:demo";
|
||||
|
||||
expect(store.selectedConnectionGroupId).toBe("child");
|
||||
});
|
||||
});
|
||||
@@ -58,6 +58,8 @@ import {
|
||||
reorderEntry as reorderEntryOp,
|
||||
reorderEntries as reorderEntriesOp,
|
||||
buildConnectionGroupPathMap,
|
||||
connectionGroupDestinationRows,
|
||||
connectionGroupIdForSelection,
|
||||
connectionSidebarSearchAliases,
|
||||
type DropPosition,
|
||||
type ReorderEntriesOptions,
|
||||
@@ -557,6 +559,12 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
} | null>(null);
|
||||
const sidebarLayout = ref<SidebarLayout>(emptyLayout());
|
||||
const connectionGroupPaths = computed(() => buildConnectionGroupPathMap(sidebarLayout.value));
|
||||
const connectionGroupOptions = computed(() => connectionGroupDestinationRows(sidebarLayout.value));
|
||||
const selectedConnectionGroupId = computed(() => {
|
||||
const selectedNodeId = selectedTreeNodeId.value;
|
||||
const selectedNode = selectedNodeId ? findNode(treeNodes.value, selectedNodeId) : null;
|
||||
return connectionGroupIdForSelection(sidebarLayout.value, selectedNodeId, selectedNode?.connectionId);
|
||||
});
|
||||
let layoutPersistTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
const staleTreeRefreshIds = new Set<string>();
|
||||
const activeTreeRefreshGenerations = new Map<string, number>();
|
||||
@@ -8749,6 +8757,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
recordConnectionLostError,
|
||||
sidebarLayout,
|
||||
connectionGroupPaths,
|
||||
connectionGroupOptions,
|
||||
selectedConnectionGroupId,
|
||||
getConfig,
|
||||
connectionIdentifierQuote,
|
||||
isTreeNodePinned,
|
||||
|
||||
Reference in new issue
Block a user