Add create/edit connection and remove connection logic

This commit is contained in:
tiny-craft 2023-07-02 03:25:57 +08:00
parent e1f6aed33b
commit 19c8e153c3
9 changed files with 280 additions and 82 deletions

View File

@ -83,9 +83,24 @@ func (c *connectionService) ListConnection() (resp types.JSResp) {
return
}
// GetConnection get connection profile by name
func (c *connectionService) GetConnection(name string) (resp types.JSResp) {
conn := c.conns.GetConnection(name)
resp.Success = conn != nil
resp.Data = conn
return
}
// SaveConnection save connection config to local profile
func (c *connectionService) SaveConnection(param types.Connection, replace bool) (resp types.JSResp) {
if err := c.conns.UpsertConnection(param, replace); err != nil {
func (c *connectionService) SaveConnection(name string, param types.Connection) (resp types.JSResp) {
var err error
if len(name) > 0 {
// update connection
err = c.conns.UpdateConnection(name, param)
} else {
err = c.conns.CreateConnection(param)
}
if err != nil {
resp.Msg = err.Error()
} else {
resp.Success = true
@ -93,6 +108,17 @@ func (c *connectionService) SaveConnection(param types.Connection, replace bool)
return
}
// RemoveConnection remove connection by name
func (c *connectionService) RemoveConnection(name string) (resp types.JSResp) {
err := c.conns.RemoveConnection(name)
if err != nil {
resp.Msg = err.Error()
return
}
resp.Success = true
return
}
// OpenConnection open redis server connection
func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(name, 0)

View File

@ -67,24 +67,29 @@ func (c *ConnectionsStorage) getConnections() (ret []types.ConnectionGroup) {
// GetConnections get all store connections from local
func (c *ConnectionsStorage) GetConnections() (ret []types.ConnectionGroup) {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.getConnections()
}
// GetConnectionsFlat get all store connections from local flat(exclude group level)
func (c *ConnectionsStorage) GetConnectionsFlat() (ret []types.Connection) {
c.mutex.Lock()
defer c.mutex.Unlock()
conns := c.getConnections()
for _, group := range conns {
ret = append(ret, group.Connections...)
}
return
}
// GetConnection get connection by name
func (c *ConnectionsStorage) GetConnection(name string) *types.Connection {
conns := c.getConnections()
for _, group := range conns {
for _, conn := range group.Connections {
ret = append(ret, conn)
if conn.Name == name {
return &conn
}
}
}
return
return nil
}
func (c *ConnectionsStorage) saveConnections(conns []types.ConnectionGroup) error {
@ -98,36 +103,61 @@ func (c *ConnectionsStorage) saveConnections(conns []types.ConnectionGroup) erro
return nil
}
// UpsertConnection update or insert a connection
func (c *ConnectionsStorage) UpsertConnection(param types.Connection, replace bool) error {
// CreateConnection create new connection
func (c *ConnectionsStorage) CreateConnection(param types.Connection) error {
c.mutex.Lock()
defer c.mutex.Unlock()
conn := c.GetConnection(param.Name)
if conn != nil {
return errors.New("duplicated connection name")
}
conns := c.getConnections()
groupIndex, existsGroup := sliceutil.Find(conns, func(i int) bool {
return conns[i].GroupName == param.Group
})
if !existsGroup {
// no group matched, create new group
group := types.ConnectionGroup{
GroupName: param.Group,
Connections: []types.Connection{param},
}
conns = append(conns, group)
} else {
conns[groupIndex].Connections = append(conns[groupIndex].Connections, param)
}
return c.saveConnections(conns)
}
// UpdateConnection update existing connection by name
func (c *ConnectionsStorage) UpdateConnection(name string, param types.Connection) error {
c.mutex.Lock()
defer c.mutex.Unlock()
conns := c.getConnections()
groupIndex := -1
connIndex := -1
// find out edit connection
for i, group := range conns {
for j, conn := range group.Connections {
// check conflict connection name
if conn.Name == param.Name {
if !replace {
return errors.New("duplicated connection name")
} else {
// different group name, should move group
// remove from current group first
if group.GroupName != param.Group {
group.Connections = append(group.Connections[:j], group.Connections[j+1:]...)
if conn.Name == name {
// different group name, should move to new group
// remove from current group first
if group.GroupName != param.Group {
conns[i].Connections = append(conns[i].Connections[:j], conns[i].Connections[j+1:]...)
// find new group index
groupIndex, _ = sliceutil.Find(conns, func(i int) bool {
return conns[i].GroupName == param.Group
})
} else {
groupIndex = i
connIndex = j
}
break
// find new group index
groupIndex, _ = sliceutil.Find(conns, func(i int) bool {
return conns[i].GroupName == param.Group
})
} else {
groupIndex = i
connIndex = j
}
break
}
}
}
@ -149,25 +179,22 @@ func (c *ConnectionsStorage) UpsertConnection(param types.Connection, replace bo
}
conns = append(conns, group)
}
return c.saveConnections(conns)
}
// RemoveConnection remove special connection
func (c *ConnectionsStorage) RemoveConnection(group, name string) error {
func (c *ConnectionsStorage) RemoveConnection(name string) error {
c.mutex.Lock()
defer c.mutex.Unlock()
conns := c.getConnections()
for i, connGroup := range conns {
if connGroup.GroupName == group {
for j, conn := range connGroup.Connections {
if conn.Name == name {
connList := conns[i].Connections
connList = append(connList[:j], connList[j+1:]...)
conns[i].Connections = connList
return c.saveConnections(conns)
}
for j, conn := range connGroup.Connections {
if conn.Name == name {
connList := conns[i].Connections
connList = append(connList[:j], connList[j+1:]...)
conns[i].Connections = connList
return c.saveConnections(conns)
}
}
}

View File

@ -1,5 +1,5 @@
<script setup>
import NewConnDialog from './components/dialogs/NewConnDialog.vue'
import ConnectionDialog from './components/dialogs/ConnectionDialog.vue'
import NewKeyDialog from './components/dialogs/NewKeyDialog.vue'
import PreferencesDialog from './components/dialogs/PreferencesDialog.vue'
import RenameKeyDialog from './components/dialogs/RenameKeyDialog.vue'
@ -40,7 +40,7 @@ const themeOverrides = {
<app-content />
<!-- top modal dialogs -->
<new-conn-dialog />
<connection-dialog />
<new-key-dialog />
<add-fields-dialog />
<rename-key-dialog />

View File

@ -1,30 +1,24 @@
<script setup>
import { isEmpty } from 'lodash'
import { get, isEmpty, map } from 'lodash'
import { computed, nextTick, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { SaveConnection, TestConnection } from '../../../wailsjs/go/services/connectionService.js'
import { TestConnection } from '../../../wailsjs/go/services/connectionService.js'
import useDialog from '../../stores/dialog'
import { useMessage } from 'naive-ui'
import Close from '../icons/Close.vue'
import useConnectionStore from '../../stores/connections.js'
const generalFormValue = {
group: '',
name: '',
addr: '127.0.0.1',
port: 6379,
username: '',
password: '',
defaultFilter: '*',
keySeparator: ':',
connTimeout: 60,
execTimeout: 60,
markColor: '',
}
/**
* Dialog for create or edit connection
*/
const dialogStore = useDialog()
const connectionStore = useConnectionStore()
const message = useMessage()
const i18n = useI18n()
const generalForm = ref(Object.assign({}, generalFormValue))
const editName = ref('')
const generalForm = ref(null)
const generalFormRules = () => {
const requiredMsg = i18n.t('field_required')
return {
@ -34,6 +28,19 @@ const generalFormRules = () => {
keySeparator: { required: true, message: requiredMsg, trigger: 'input' },
}
}
const isEditMode = computed(() => !isEmpty(editName.value))
const groupOptions = computed(() => {
const options = map(connectionStore.groups, (group) => ({
label: group,
value: group,
}))
options.splice(0, 0, {
label: i18n.t('no_group'),
value: '',
})
return options
})
const tab = ref('general')
const testing = ref(false)
@ -53,23 +60,26 @@ const formLabelWidth = computed(() => {
return '80px'
})
const predefineColors = ref(['', '#FE5959', '#FEC230', '#FEF27F', '#6CEFAF', '#46C3FC', '#B388FC', '#B0BEC5'])
const dialogStore = useDialog()
const generalFormRef = ref(null)
const advanceFormRef = ref(null)
const onCreateConnection = async () => {
const onSaveConnection = async () => {
// Validate general form
await generalFormRef.value?.validate((err) => {
nextTick(() => (tab.value = 'general'))
if (err) {
nextTick(() => (tab.value = 'general'))
}
})
// Validate advance form
await advanceFormRef.value?.validate((err) => {
nextTick(() => (tab.value = 'advanced'))
if (err) {
nextTick(() => (tab.value = 'advanced'))
}
})
// Store new connection
const { success, msg } = await SaveConnection(generalForm.value, false)
const { success, msg } = await connectionStore.saveConnection(editName.value, generalForm.value)
if (!success) {
message.error(msg)
return
@ -80,15 +90,21 @@ const onCreateConnection = async () => {
}
const resetForm = () => {
generalForm.value = generalFormValue
generalForm.value = connectionStore.newDefaultConnection()
generalFormRef.value?.restoreValidation()
showTestResult.value = false
testResult.value = ''
tab.value = 'general'
}
watch(
() => dialogStore.newDialogVisible,
(visible) => {}
() => dialogStore.connDialogVisible,
(visible) => {
if (visible) {
editName.value = get(dialogStore.connParam, 'name', '')
generalForm.value = dialogStore.connParam || connectionStore.newDefaultConnection()
}
}
)
const onTestConnection = async () => {
@ -122,13 +138,13 @@ const onClose = () => {
<template>
<n-modal
v-model:show="dialogStore.newDialogVisible"
v-model:show="dialogStore.connDialogVisible"
:closable="false"
:close-on-esc="false"
:mask-closable="false"
:on-after-leave="resetForm"
:show-icon="false"
:title="$t('new_conn_title')"
:title="isEditMode ? $t('edit_conn_title') : $t('new_conn_title')"
preset="dialog"
transform-origin="center"
>
@ -146,6 +162,9 @@ const onClose = () => {
<n-form-item :label="$t('conn_name')" path="name" required>
<n-input v-model:value="generalForm.name" :placeholder="$t('conn_name_tip')" />
</n-form-item>
<n-form-item :label="$t('conn_group')" required>
<n-select v-model:value="generalForm.group" :options="groupOptions"></n-select>
</n-form-item>
<n-form-item :label="$t('conn_addr')" path="addr" required>
<n-input v-model:value="generalForm.addr" :placeholder="$t('conn_addr_tip')" />
<n-text style="width: 40px; text-align: center">:</n-text>
@ -230,7 +249,9 @@ const onClose = () => {
</div>
<div class="flex-item n-dialog__action">
<n-button @click="onClose">{{ $t('cancel') }}</n-button>
<n-button type="primary" @click="onCreateConnection">{{ $t('confirm') }}</n-button>
<n-button type="primary" @click="onSaveConnection">
{{ isEditMode ? $t('update') : $t('confirm') }}
</n-button>
</div>
</template>
</n-modal>

View File

@ -2,7 +2,7 @@
import useDialogStore from '../../stores/dialog.js'
import { h, nextTick, onMounted, reactive, ref } from 'vue'
import useConnectionStore from '../../stores/connections.js'
import { NIcon, useMessage } from 'naive-ui'
import { NIcon, useDialog, useMessage } from 'naive-ui'
import { ConnectionType } from '../../consts/connection_type.js'
import ToggleFolder from '../icons/ToggleFolder.vue'
import ToggleServer from '../icons/ToggleServer.vue'
@ -111,7 +111,7 @@ const menuOptions = {
key: 'd1',
},
{
key: 'server_delete',
key: 'server_remove',
label: i18n.t('remove_conn'),
icon: renderIcon(Delete),
},
@ -171,7 +171,7 @@ const openConnection = async (name) => {
await connectionStore.openConnection(name)
}
tabStore.upsertTab({
server: nam,
server: nae,
})
} catch (e) {
message.error(e.message)
@ -181,6 +181,28 @@ const openConnection = async (name) => {
}
}
const dialog = useDialog()
const removeConnection = async (name) => {
dialog.warning({
title: i18n.t('warning'),
content: i18n.t('delete_key_tip', { key: name }),
closable: false,
autoFocus: false,
transformOrigin: 'center',
positiveText: i18n.t('confirm'),
negativeText: i18n.t('cancel'),
onPositiveClick: async () => {
connectionStore.removeConnection(name).then(({ success, msg }) => {
if (!success) {
message.error(msg)
} else {
message.success(i18n.t('delete_key_succ', { key: name }))
}
})
},
})
}
const nodeProps = ({ option }) => {
return {
onDblclick: async () => {
@ -218,6 +240,12 @@ const handleSelectContextMenu = (key) => {
case 'server_open':
openConnection(name).then(() => {})
break
case 'server_edit':
dialogStore.openEditDialog(name)
break
case 'server_remove':
removeConnection(name)
break
case 'server_close':
connectionStore.closeConnection(name)
break

View File

@ -37,14 +37,17 @@
"edit_conn": "Edit Connection Config",
"edit_conn_group": "Edit Connection Group",
"remove_conn_group": "Delete Connection Group",
"no_group": "No Group",
"copy_path": "Copy Path",
"remove_path": "Remove Path",
"copy_key": "Copy Key Name",
"remove_key": "Remove Key",
"new_conn_title": "New Connection",
"edit_conn_title": "Edit Connection",
"general": "General",
"advanced": "Advanced",
"editor": "Editor",
"conn_group": "Group",
"conn_name": "Name",
"conn_addr": "Address",
"conn_usr": "Username",

View File

@ -39,14 +39,17 @@
"edit_conn": "编辑连接配置",
"edit_conn_group": "编辑连接分组",
"remove_conn_group": "删除连接分组",
"no_group": "无分组",
"copy_path": "复制路径",
"remove_path": "删除路径",
"copy_key": "复制键名",
"remove_key": "删除键",
"new_conn_title": "新建连接",
"edit_conn_title": "编辑连接",
"general": "常规配置",
"advanced": "高级配置",
"editor": "编辑器",
"conn_group": "所属分组",
"conn_name": "连接名",
"conn_addr": "连接地址",
"conn_usr": "用户名",

View File

@ -1,23 +1,26 @@
import { defineStore } from 'pinia'
import { get, isEmpty, last, remove, size, sortedIndexBy, split } from 'lodash'
import { get, isEmpty, last, remove, size, sortedIndexBy, split, uniq } from 'lodash'
import {
AddHashField,
AddListItem,
AddZSetValue,
CloseConnection,
GetConnection,
GetKeyValue,
ListConnection,
OpenConnection,
OpenDatabase,
RemoveConnection,
RemoveKey,
RenameKey,
SaveConnection,
SetHashValue,
SetKeyTTL,
SetKeyValue,
SetListItem,
SetSetItem,
UpdateSetItem,
UpdateZSetValue,
UpdateZSetValue
} from '../../wailsjs/go/services/connectionService.js'
import { ConnectionType } from '../consts/connection_type.js'
import useTabStore from './tab.js'
@ -51,8 +54,9 @@ const useConnectionStore = defineStore('connections', {
* @returns {{databases: Object<string, DatabaseItem[]>, connections: ConnectionItem[]}}
*/
state: () => ({
groups: [], // all group name
connections: [], // all connections
databases: {}, // all databases in opened connections group by name
databases: {, // all databases in opened connections group by name
}),
getters: {
anyConnectionOpened() {
@ -61,14 +65,16 @@ const useConnectionStore = defineStore('connections', {
},
actions: {
/**
* Load all store connections struct from local profile
* load all store connections struct from local profile
* @param {boolean} [force]
* @returns {Promise<void>}
*/
async initConnections() {
if (!isEmpty(this.connections)) {
async initConnections(force) {
if (!force && !isEmpty(this.connections)) {
return
}
const conns = []
const groups = []
const { data = [{ groupName: '', connections: [] }] } = await ListConnection()
for (let i = 0; i < data.length; i++) {
const group = data[i]
@ -86,6 +92,7 @@ const useConnectionStore = defineStore('connections', {
})
}
} else {
groups.push(group.groupName)
// Custom group
const children = []
const len = size(group.connections)
@ -97,7 +104,6 @@ const useConnectionStore = defineStore('connections', {
label: item.name,
name: item.name,
type: ConnectionType.Server,
children: j === len - 1 ? undefined : [],
// isLeaf: false,
})
}
@ -105,12 +111,49 @@ const useConnectionStore = defineStore('connections', {
key: group.groupName,
label: group.groupName,
type: ConnectionType.Group,
children,
children
})
}
}
this.connections = conns
console.debug(JSON.stringify(this.connections))
this.groups = uniq(groups)
},
/**
* get connection by name from local profile
* @param name
* @returns {Promise<{}|null>}
*/
async getConnectionProfile(name) {
try {
const { data, success } = await GetConnection(name)
if (success) {
return data
}
} finally {
}
return null
},
/**
* create a new default connection
* @param {string} [name]
* @returns {{}}
*/
newDefaultConnection(name) {
return {
group: '',
name: name || '',
addr: '127.0.0.1',
port: 6379,
username: '',
password: '',
defaultFilter: '*',
keySeparator: ':',
connTimeout: 60,
execTimeout: 60,
markColor: ''
}
},
/**
@ -135,6 +178,23 @@ const useConnectionStore = defineStore('connections', {
return null
},
/**
* Create a new connection or update current connection profile
* @param {string} name set null if create a new connection
* @param {{}} param
* @returns {Promise<{success: boolean, [msg]: string}>}
*/
async saveConnection(name, param) {
const { success, msg } = await SaveConnection(name, param)
if (!success) {
return { success: false, msg }
}
// reload connection list
await this.initConnections(true)
return { success: true }
},
/**
* Check if connection is connected
* @param name
@ -202,6 +262,22 @@ const useConnectionStore = defineStore('connections', {
return true
},
/**
* Remove connection
* @param name
* @returns {Promise<{success: boolean, [msg]: string}>}
*/
async removeConnection(name) {
// close connection first
await this.closeConnection(name)
const { success, msg } = await RemoveConnection(name)
if (!success) {
return { success: false, msg }
}
await this.initConnections(true)
return { success: true }
},
/**
* Open database and load all keys
* @param connName

View File

@ -1,8 +1,10 @@
import { defineStore } from 'pinia'
import useConnectionStore from './connections.js'
const useDialogStore = defineStore('dialog', {
state: () => ({
newDialogVisible: false,
connDialogVisible: false,
connParam: null,
/**
* @property {string} prefix
@ -38,10 +40,22 @@ const useDialogStore = defineStore('dialog', {
}),
actions: {
openNewDialog() {
this.newDialogVisible = true
this.connParam = null
this.connDialogVisible = true
},
closeNewDialog() {
this.newDialogVisible = false
this.connDialogVisible = false
},
async openEditDialog(name) {
console.log('open edit dialog:' + name)
const connStore = useConnectionStore()
const profile = await connStore.getConnectionProfile(name)
this.connParam = profile || connStore.newDefaultConnection(name)
this.connDialogVisible = true
},
closeEditDialog() {
this.connDialogVisible = false
},
/**