Add common confirm dialog function

Add connection group handle logic
Refactor local connection profile struct
This commit is contained in:
tiny-craft 2023-07-02 16:52:07 +08:00
parent 8e3adbf0c3
commit f6a4ab8de0
16 changed files with 506 additions and 218 deletions

View File

@ -92,7 +92,7 @@ func (c *connectionService) GetConnection(name string) (resp types.JSResp) {
} }
// SaveConnection save connection config to local profile // SaveConnection save connection config to local profile
func (c *connectionService) SaveConnection(name string, param types.Connection) (resp types.JSResp) { func (c *connectionService) SaveConnection(name string, param types.ConnectionConfig) (resp types.JSResp) {
var err error var err error
if len(name) > 0 { if len(name) > 0 {
// update connection // update connection
@ -119,6 +119,39 @@ func (c *connectionService) RemoveConnection(name string) (resp types.JSResp) {
return return
} }
// CreateGroup create new group
func (c *connectionService) CreateGroup(name string) (resp types.JSResp) {
err := c.conns.CreateGroup(name)
if err != nil {
resp.Msg = err.Error()
return
}
resp.Success = true
return
}
// RenameGroup rename group
func (c *connectionService) RenameGroup(name, newName string) (resp types.JSResp) {
err := c.conns.RenameGroup(name, newName)
if err != nil {
resp.Msg = err.Error()
return
}
resp.Success = true
return
}
// RemoveGroup remove group by name
func (c *connectionService) RemoveGroup(name string, includeConn bool) (resp types.JSResp) {
err := c.conns.RemoveGroup(name, includeConn)
if err != nil {
resp.Msg = err.Error()
return
}
resp.Success = true
return
}
// OpenConnection open redis server connection // OpenConnection open redis server connection
func (c *connectionService) OpenConnection(name string) (resp types.JSResp) { func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(name, 0) rdb, ctx, err := c.getRedisClient(name, 0)
@ -195,18 +228,9 @@ func (c *connectionService) getRedisClient(connName string, db int) (*redis.Clie
if ok { if ok {
rdb, ctx = item.rdb, item.ctx rdb, ctx = item.rdb, item.ctx
} else { } else {
connGroups := c.conns.GetConnections() selConn := c.conns.GetConnection(connName)
var selConn *types.Connection
for _, connGroup := range connGroups {
for _, conn := range connGroup.Connections {
if conn.Name == connName {
selConn = &conn
break
}
}
}
if selConn == nil { if selConn == nil {
return nil, nil, errors.New("no match connection connName") return nil, nil, fmt.Errorf("no match connection \"%s\"", connName)
} }
rdb = redis.NewClient(&redis.Options{ rdb = redis.NewClient(&redis.Options{

View File

@ -5,7 +5,6 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sync" "sync"
"tinyrdm/backend/types" "tinyrdm/backend/types"
sliceutil "tinyrdm/backend/utils/slice"
) )
type ConnectionsStorage struct { type ConnectionsStorage struct {
@ -19,17 +18,12 @@ func NewConnections() *ConnectionsStorage {
} }
} }
func (c *ConnectionsStorage) defaultConnections() []types.ConnectionGroup { func (c *ConnectionsStorage) defaultConnections() types.Connections {
return []types.ConnectionGroup{ return types.Connections{}
{
GroupName: "",
Connections: []types.Connection{},
},
}
} }
func (c *ConnectionsStorage) defaultConnectionItem() types.Connection { func (c *ConnectionsStorage) defaultConnectionItem() types.ConnectionConfig {
return types.Connection{ return types.ConnectionConfig{
Name: "", Name: "",
Addr: "127.0.0.1", Addr: "127.0.0.1",
Port: 6379, Port: 6379,
@ -43,7 +37,7 @@ func (c *ConnectionsStorage) defaultConnectionItem() types.Connection {
} }
} }
func (c *ConnectionsStorage) getConnections() (ret []types.ConnectionGroup) { func (c *ConnectionsStorage) getConnections() (ret types.Connections) {
b, err := c.storage.Load() b, err := c.storage.Load()
if err != nil { if err != nil {
ret = c.defaultConnections() ret = c.defaultConnections()
@ -66,15 +60,19 @@ func (c *ConnectionsStorage) getConnections() (ret []types.ConnectionGroup) {
} }
// GetConnections get all store connections from local // GetConnections get all store connections from local
func (c *ConnectionsStorage) GetConnections() (ret []types.ConnectionGroup) { func (c *ConnectionsStorage) GetConnections() (ret types.Connections) {
return c.getConnections() return c.getConnections()
} }
// GetConnectionsFlat get all store connections from local flat(exclude group level) // GetConnectionsFlat get all store connections from local flat(exclude group level)
func (c *ConnectionsStorage) GetConnectionsFlat() (ret []types.Connection) { func (c *ConnectionsStorage) GetConnectionsFlat() (ret types.Connections) {
conns := c.getConnections() conns := c.getConnections()
for _, group := range conns { for _, conn := range conns {
ret = append(ret, group.Connections...) if conn.Type == "group" {
ret = append(ret, conn.Connections...)
} else {
ret = append(ret, conn)
}
} }
return return
} }
@ -82,18 +80,40 @@ func (c *ConnectionsStorage) GetConnectionsFlat() (ret []types.Connection) {
// GetConnection get connection by name // GetConnection get connection by name
func (c *ConnectionsStorage) GetConnection(name string) *types.Connection { func (c *ConnectionsStorage) GetConnection(name string) *types.Connection {
conns := c.getConnections() conns := c.getConnections()
for _, group := range conns {
for _, conn := range group.Connections { var findConn func(string, string, types.Connections) *types.Connection
findConn = func(name, groupName string, conns types.Connections) *types.Connection {
for i, conn := range conns {
if conn.Type != "group" {
if conn.Name == name { if conn.Name == name {
conn.Group = group.GroupName conns[i].Group = groupName
return &conn return &conns[i]
}
} else {
if ret := findConn(name, conn.Name, conn.Connections); ret != nil {
return ret
} }
} }
} }
return nil return nil
} }
func (c *ConnectionsStorage) saveConnections(conns []types.ConnectionGroup) error { return findConn(name, "", conns)
}
// GetGroup get connection group by name
func (c *ConnectionsStorage) GetGroup(name string) *types.Connection {
conns := c.getConnections()
for i, conn := range conns {
if conn.Type == "group" && conn.Name == name {
return &conns[i]
}
}
return nil
}
func (c *ConnectionsStorage) saveConnections(conns types.Connections) error {
b, err := yaml.Marshal(&conns) b, err := yaml.Marshal(&conns)
if err != nil { if err != nil {
return err return err
@ -105,7 +125,7 @@ func (c *ConnectionsStorage) saveConnections(conns []types.ConnectionGroup) erro
} }
// CreateConnection create new connection // CreateConnection create new connection
func (c *ConnectionsStorage) CreateConnection(param types.Connection) error { func (c *ConnectionsStorage) CreateConnection(param types.ConnectionConfig) error {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
@ -115,129 +135,159 @@ func (c *ConnectionsStorage) CreateConnection(param types.Connection) error {
} }
conns := c.getConnections() conns := c.getConnections()
groupIndex, existsGroup := sliceutil.Find(conns, func(i int) bool { var group *types.Connection
return conns[i].GroupName == param.Group if len(param.Group) > 0 {
}) for i, conn := range conns {
if !existsGroup { if conn.Type == "group" && conn.Name == param.Group {
// no group matched, create new group group = &conns[i]
group := types.ConnectionGroup{ break
GroupName: param.Group,
Connections: []types.Connection{param},
} }
conns = append(conns, group) }
}
if group != nil {
group.Connections = append(group.Connections, types.Connection{
ConnectionConfig: param,
})
} else { } else {
conns[groupIndex].Connections = append(conns[groupIndex].Connections, param) if len(param.Group) > 0 {
// no group matched, create new group
conns = append(conns, types.Connection{
Type: "group",
Connections: types.Connections{
types.Connection{
ConnectionConfig: param,
},
},
})
} else {
conns = append(conns, types.Connection{
ConnectionConfig: param,
})
}
} }
return c.saveConnections(conns) return c.saveConnections(conns)
} }
// UpdateConnection update existing connection by name // UpdateConnection update existing connection by name
func (c *ConnectionsStorage) UpdateConnection(name string, param types.Connection) error { func (c *ConnectionsStorage) UpdateConnection(name string, param types.ConnectionConfig) error {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
var updated bool
conns := c.getConnections() conns := c.getConnections()
groupIndex := -1 for i, conn := range conns {
connIndex := -1
// find out edit connection
for i, group := range conns {
for j, conn := range group.Connections {
// check conflict connection name
if conn.Name == name { if conn.Name == name {
// different group name, should move to new group conns[i] = types.Connection{
// remove from current group first ConnectionConfig: param,
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
} }
updated = true
} else if conn.Type == "group" {
for j, conn2 := range conn.Connections {
if conn2.Name == name {
conns[i].Connections[j] = types.Connection{
ConnectionConfig: param,
}
updated = true
break break
} }
} }
} }
if groupIndex >= 0 { if updated {
// group exists break
if connIndex >= 0 {
// connection exists
conns[groupIndex].Connections[connIndex] = param
} else {
// new connection
conns[groupIndex].Connections = append(conns[groupIndex].Connections, param)
} }
} else {
// new group
group := types.ConnectionGroup{
GroupName: param.Group,
Connections: []types.Connection{param},
}
conns = append(conns, group)
} }
if updated {
return c.saveConnections(conns) return c.saveConnections(conns)
} }
return errors.New("connection not found")
}
// RemoveConnection remove special connection // RemoveConnection remove special connection
func (c *ConnectionsStorage) RemoveConnection(name string) error { func (c *ConnectionsStorage) RemoveConnection(name string) error {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
conns := c.getConnections() conns := c.getConnections()
for i, connGroup := range conns { var updated bool
for j, conn := range connGroup.Connections { for i, conn := range conns {
if conn.Name == name { if conn.Type == "group" {
connList := conns[i].Connections for j, subConn := range conn.Connections {
connList = append(connList[:j], connList[j+1:]...) if subConn.Name == name {
conns[i].Connections = connList conns[i].Connections = append(conns[i].Connections[:j], conns[i].Connections[j+1:]...)
return c.saveConnections(conns) updated = true
break
} }
} }
} else if conn.Name == name {
conns = append(conns[:i], conns[i+1:]...)
updated = true
break
} }
if updated {
break
}
}
if !updated {
return errors.New("no match connection") return errors.New("no match connection")
} }
return c.saveConnections(conns)
}
// UpsertGroup update or insert a group // CreateGroup create new group
// When want to create group only, set group == param.name func (c *ConnectionsStorage) CreateGroup(name string) error {
func (c *ConnectionsStorage) UpsertGroup(group string, param types.ConnectionGroup) error {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
conns := c.getConnections() conns := c.getConnections()
for i, connGroup := range conns { for _, conn := range conns {
if connGroup.GroupName == group { if conn.Type == "group" && conn.Name == name {
conns[i].GroupName = param.GroupName return errors.New("duplicated group name")
}
}
conns = append(conns, types.Connection{
ConnectionConfig: types.ConnectionConfig{
Name: name,
},
Type: "group",
})
return c.saveConnections(conns)
}
// RenameGroup rename group
func (c *ConnectionsStorage) RenameGroup(name, newName string) error {
c.mutex.Lock()
defer c.mutex.Unlock()
conns := c.getConnections()
for i, conn := range conns {
if conn.Type == "group" && conn.Name == name {
conns[i].Name = newName
return c.saveConnections(conns) return c.saveConnections(conns)
} }
} }
// No match group, create one return errors.New("group not found")
connGroup := types.ConnectionGroup{
GroupName: param.GroupName,
Connections: []types.Connection{},
}
conns = append(conns, connGroup)
return c.saveConnections(conns)
} }
// RemoveGroup remove special group, include all connections under it // RemoveGroup remove special group, include all connections under it
func (c *ConnectionsStorage) RemoveGroup(group string) error { func (c *ConnectionsStorage) RemoveGroup(group string, includeConnection bool) error {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
conns := c.getConnections() conns := c.getConnections()
for i, connGroup := range conns { for i, conn := range conns {
if connGroup.GroupName == group { if conn.Type == "group" && conn.Name == group {
conns = append(conns[:i], conns[i+1:]...) conns = append(conns[:i], conns[i+1:]...)
if includeConnection {
conns = append(conns, conn.Connections...)
}
return c.saveConnections(conns) return c.saveConnections(conns)
} }
} }
return errors.New("group not found")
return errors.New("no match group")
} }

View File

@ -2,20 +2,28 @@ package types
type ConnectionCategory int type ConnectionCategory int
type Connection struct { type ConnectionConfig struct {
Group string `json:"group" yaml:"-"`
Name string `json:"name" yaml:"name"` Name string `json:"name" yaml:"name"`
Addr string `json:"addr" yaml:"addr"` Group string `json:"group" yaml:"-"`
Port int `json:"port" yaml:"port"` Addr string `json:"addr,omitempty" yaml:"addr,omitempty"`
Username string `json:"username" yaml:"username"` Port int `json:"port,omitempty" yaml:"port,omitempty"`
Password string `json:"password" yaml:"password"` Username string `json:"username,omitempty" yaml:"username,omitempty"`
DefaultFilter string `json:"defaultFilter" yaml:"default_filter"` Password string `json:"password,omitempty" yaml:"password,omitempty"`
KeySeparator string `json:"keySeparator" yaml:"key_separator"` DefaultFilter string `json:"defaultFilter,omitempty" yaml:"default_filter,omitempty"`
ConnTimeout int `json:"connTimeout" yaml:"conn_timeout"` KeySeparator string `json:"keySeparator,omitempty" yaml:"key_separator,omitempty"`
ExecTimeout int `json:"execTimeout" yaml:"exec_timeout"` ConnTimeout int `json:"connTimeout,omitempty" yaml:"conn_timeout,omitempty"`
MarkColor string `json:"markColor" yaml:"mark_color"` ExecTimeout int `json:"execTimeout,omitempty" yaml:"exec_timeout,omitempty"`
MarkColor string `json:"markColor,omitempty" yaml:"mark_color,omitempty"`
} }
type Connection struct {
ConnectionConfig `json:",inline" yaml:",inline"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Connections []Connection `json:"connections,omitempty" yaml:"connections,omitempty"`
}
type Connections []Connection
type ConnectionGroup struct { type ConnectionGroup struct {
GroupName string `json:"groupName" yaml:"group_name"` GroupName string `json:"groupName" yaml:"group_name"`
Connections []Connection `json:"connections" yaml:"connections"` Connections []Connection `json:"connections" yaml:"connections"`

View File

@ -9,6 +9,7 @@ import json from 'highlight.js/lib/languages/json'
import plaintext from 'highlight.js/lib/languages/plaintext' import plaintext from 'highlight.js/lib/languages/plaintext'
import AddFieldsDialog from './components/dialogs/AddFieldsDialog.vue' import AddFieldsDialog from './components/dialogs/AddFieldsDialog.vue'
import AppContent from './AppContent.vue' import AppContent from './AppContent.vue'
import GroupDialog from './components/dialogs/GroupDialog.vue'
hljs.registerLanguage('json', json) hljs.registerLanguage('json', json)
hljs.registerLanguage('plaintext', plaintext) hljs.registerLanguage('plaintext', plaintext)
@ -41,6 +42,7 @@ const themeOverrides = {
<!-- top modal dialogs --> <!-- top modal dialogs -->
<connection-dialog /> <connection-dialog />
<group-dialog />
<new-key-dialog /> <new-key-dialog />
<add-fields-dialog /> <add-fields-dialog />
<rename-key-dialog /> <rename-key-dialog />

View File

@ -11,6 +11,7 @@ import useTabStore from '../../stores/tab.js'
import { useDialog } from 'naive-ui' import { useDialog } from 'naive-ui'
import useConnectionStore from '../../stores/connections.js' import useConnectionStore from '../../stores/connections.js'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
const valueComponents = { const valueComponents = {
[types.STRING]: ContentValueString, [types.STRING]: ContentValueString,
@ -58,22 +59,13 @@ const onAddTab = () => {
} }
const i18n = useI18n() const i18n = useI18n()
const confirmDialog = useConfirmDialog()
const onCloseTab = (tabIndex) => { const onCloseTab = (tabIndex) => {
dialog.warning({ confirmDialog.warning(i18n.t('close_confirm'), () => {
title: i18n.t('close_confirm_title'),
content: i18n.t('close_confirm'),
positiveText: i18n.t('confirm'),
negativeText: i18n.t('cancel'),
closable: false,
closeOnEsc: false,
maskClosable: false,
transformOrigin: 'center',
onPositiveClick: () => {
const tab = get(tabStore.tabs, tabIndex) const tab = get(tabStore.tabs, tabIndex)
if (tab != null) { if (tab != null) {
connectionStore.closeConnection(tab.name) connectionStore.closeConnection(tab.name)
} }
},
}) })
} }
</script> </script>

View File

@ -85,8 +85,8 @@ const onSaveConnection = async () => {
return return
} }
message.success(i18n.t('new_conn_succ')) message.success(i18n.t('handle_succ'))
dialogStore.closeNewDialog() onClose()
} }
const resetForm = () => { const resetForm = () => {
@ -132,8 +132,12 @@ const onTestConnection = async () => {
} }
const onClose = () => { const onClose = () => {
if (isEditMode.value) {
dialogStore.closeEditDialog()
} else {
dialogStore.closeNewDialog() dialogStore.closeNewDialog()
} }
}
</script> </script>
<template> <template>
@ -162,7 +166,7 @@ const onClose = () => {
<n-form-item :label="$t('conn_name')" path="name" required> <n-form-item :label="$t('conn_name')" path="name" required>
<n-input v-model:value="generalForm.name" :placeholder="$t('conn_name_tip')" /> <n-input v-model:value="generalForm.name" :placeholder="$t('conn_name_tip')" />
</n-form-item> </n-form-item>
<n-form-item :label="$t('conn_group')" required> <n-form-item v-if="!isEditMode" :label="$t('conn_group')" required>
<n-select v-model:value="generalForm.group" :options="groupOptions"></n-select> <n-select v-model:value="generalForm.group" :options="groupOptions"></n-select>
</n-form-item> </n-form-item>
<n-form-item :label="$t('conn_addr')" path="addr" required> <n-form-item :label="$t('conn_addr')" path="addr" required>

View File

@ -0,0 +1,97 @@
<script setup>
import { computed, reactive, ref, watch } from 'vue'
import useDialog from '../../stores/dialog'
import { useMessage } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import useConnectionStore from '../../stores/connections.js'
import { isEmpty } from 'lodash'
/**
* Dialog for create or rename group
*/
const editGroup = ref('')
const groupForm = reactive({
name: '',
})
const isRenameMode = computed(() => !isEmpty(editGroup.value))
const dialogStore = useDialog()
const connectionStore = useConnectionStore()
watch(
() => dialogStore.groupDialogVisible,
(visible) => {
if (visible) {
groupForm.name = editGroup.value = dialogStore.editGroup
}
}
)
const i18n = useI18n()
const message = useMessage()
const onConfirm = async () => {
try {
const { name } = groupForm
if (isRenameMode.value) {
const { success, msg } = await connectionStore.renameGroup(editGroup.value, name)
if (success) {
message.success(i18n.t('handle_succ'))
} else {
message.error(msg)
}
} else {
const { success, msg } = await connectionStore.createGroup(name)
if (success) {
message.success(i18n.t('handle_succ'))
} else {
message.error(msg)
}
}
} catch (e) {
message.error(e.message)
}
onClose()
}
const onClose = () => {
if (isRenameMode.value) {
dialogStore.closeNewGroupDialog()
} else {
dialogStore.closeRenameGroupDialog()
}
}
</script>
<template>
<n-modal
v-model:show="dialogStore.groupDialogVisible"
:closable="false"
:close-on-esc="false"
:mask-closable="false"
:negative-button-props="{ size: 'medium' }"
:negative-text="$t('cancel')"
:positive-button-props="{ size: 'medium' }"
:positive-text="$t('confirm')"
:show-icon="false"
:title="isRenameMode ? $t('rename_group') : $t('new_group')"
preset="dialog"
transform-origin="center"
@positive-click="onConfirm"
@negative-click="onClose"
>
<n-form
:model="groupForm"
:show-require-mark="false"
label-align="left"
label-placement="left"
label-width="auto"
>
<n-form-item :label="$t('group_name')" required>
<n-input v-model:value="groupForm.name" placeholder="" />
</n-form-item>
</n-form>
</n-modal>
</template>
<style lang="scss" scoped></style>

View File

@ -16,6 +16,10 @@ const connectionStore = useConnectionStore()
const onSort = () => { const onSort = () => {
dialogStore.openPreferencesDialog() dialogStore.openPreferencesDialog()
} }
const onDisconnectAll = () => {
connectionStore.closeAllConnection()
}
</script> </script>
<template> <template>
@ -38,7 +42,7 @@ const onSort = () => {
size="20" size="20"
stroke-width="4" stroke-width="4"
t-tooltip="new_group" t-tooltip="new_group"
@click="dialogStore.openNewKeyDialog('aa:bb')" @click="dialogStore.openNewGroupDialog()"
/> />
<icon-button <icon-button
:disabled="!connectionStore.anyConnectionOpened" :disabled="!connectionStore.anyConnectionOpened"
@ -47,7 +51,7 @@ const onSort = () => {
size="20" size="20"
stroke-width="4" stroke-width="4"
t-tooltip="disconnect_all" t-tooltip="disconnect_all"
@click="dialogStore.openNewKeyDialog('aa:bb')" @click="onDisconnectAll"
/> />
<n-divider style="margin: 0 4px; --n-color: #aaa; width: 2px" vertical /> <n-divider style="margin: 0 4px; --n-color: #aaa; width: 2px" vertical />
<icon-button :icon="Sort" color="#555" size="20" stroke-width="4" t-tooltip="sort_conn" @click="onSort" /> <icon-button :icon="Sort" color="#555" size="20" stroke-width="4" t-tooltip="sort_conn" @click="onSort" />

View File

@ -15,6 +15,7 @@ import Connect from '../icons/Connect.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import useTabStore from '../../stores/tab.js' import useTabStore from '../../stores/tab.js'
import Edit from '../icons/Edit.vue' import Edit from '../icons/Edit.vue'
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
const i18n = useI18n() const i18n = useI18n()
const loadingConnection = ref(false) const loadingConnection = ref(false)
@ -55,9 +56,9 @@ const renderIcon = (icon) => {
const menuOptions = { const menuOptions = {
[ConnectionType.Group]: ({ opened }) => [ [ConnectionType.Group]: ({ opened }) => [
{ {
key: 'group_reload', key: 'group_rename',
label: i18n.t('edit_conn_group'), label: i18n.t('rename_conn_group'),
icon: renderIcon(Config), icon: renderIcon(Edit),
}, },
{ {
key: 'group_delete', key: 'group_delete',
@ -182,22 +183,24 @@ const openConnection = async (name) => {
} }
const dialog = useDialog() const dialog = useDialog()
const removeConnection = async (name) => { const removeConnection = (name) => {
dialog.warning({ confirmDialog.warning(i18n.t('remove_tip', { type: i18n.t('conn_name'), name }), async () => {
title: i18n.t('warning'),
content: i18n.t('remove_conn_tip', { conn: name }),
closable: false,
autoFocus: false,
transformOrigin: 'center',
positiveText: i18n.t('confirm'),
negativeText: i18n.t('cancel'),
onPositiveClick: async () => {
connectionStore.removeConnection(name).then(({ success, msg }) => { connectionStore.removeConnection(name).then(({ success, msg }) => {
if (!success) { if (!success) {
message.error(msg) message.error(msg)
} }
}) })
}, })
}
const confirmDialog = useConfirmDialog()
const removeGroup = async (name) => {
confirmDialog.warning(i18n.t('remove_tip', { type: i18n.t('conn_group'), name }), async () => {
connectionStore.deleteGroup(name).then(({ success, msg }) => {
if (!success) {
message.error(msg)
}
})
}) })
} }
@ -233,7 +236,7 @@ const renderContextLabel = (option) => {
const handleSelectContextMenu = (key) => { const handleSelectContextMenu = (key) => {
contextMenuParam.show = false contextMenuParam.show = false
const { name, db, key: nodeKey, redisKey } = contextMenuParam.currentNode const { name, label, db, key: nodeKey, redisKey } = contextMenuParam.currentNode
switch (key) { switch (key) {
case 'server_open': case 'server_open':
openConnection(name).then(() => {}) openConnection(name).then(() => {})
@ -247,6 +250,12 @@ const handleSelectContextMenu = (key) => {
case 'server_close': case 'server_close':
connectionStore.closeConnection(name) connectionStore.closeConnection(name)
break break
case 'group_rename':
dialogStore.openRenameGroupDialog(label)
break
case 'group_delete':
removeGroup(label)
break
} }
console.warn('TODO: handle context menu:' + key) console.warn('TODO: handle context menu:' + key)
} }

View File

@ -4,7 +4,7 @@ import { ConnectionType } from '../../consts/connection_type.js'
import { NIcon, useDialog, useMessage } from 'naive-ui' import { NIcon, useDialog, useMessage } from 'naive-ui'
import Key from '../icons/Key.vue' import Key from '../icons/Key.vue'
import ToggleDb from '../icons/ToggleDb.vue' import ToggleDb from '../icons/ToggleDb.vue'
import { indexOf } from 'lodash' import { get, indexOf } from 'lodash'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import Refresh from '../icons/Refresh.vue' import Refresh from '../icons/Refresh.vue'
import CopyLink from '../icons/CopyLink.vue' import CopyLink from '../icons/CopyLink.vue'
@ -15,6 +15,7 @@ import Connect from '../icons/Connect.vue'
import useDialogStore from '../../stores/dialog.js' import useDialogStore from '../../stores/dialog.js'
import { ClipboardSetText } from '../../../wailsjs/runtime/runtime.js' import { ClipboardSetText } from '../../../wailsjs/runtime/runtime.js'
import useConnectionStore from '../../stores/connections.js' import useConnectionStore from '../../stores/connections.js'
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
const i18n = useI18n() const i18n = useI18n()
const loading = ref(false) const loading = ref(false)
@ -269,6 +270,7 @@ const onLoadTree = async (node) => {
} }
} }
const confirmDialog = useConfirmDialog()
const handleSelectContextMenu = (key) => { const handleSelectContextMenu = (key) => {
contextMenuParam.show = false contextMenuParam.show = false
const { name, db, key: nodeKey, redisKey } = contextMenuParam.currentNode const { name, db, key: nodeKey, redisKey } = contextMenuParam.currentNode
@ -283,21 +285,12 @@ const handleSelectContextMenu = (key) => {
break break
case 'key_remove': case 'key_remove':
case 'value_remove': case 'value_remove':
dialog.warning({ confirmDialog.warning(i18n.t('delete_key_tip', { key: redisKey }), () => {
title: i18n.t('warning'),
content: i18n.t('delete_key_tip', { key: redisKey }),
closable: false,
autoFocus: false,
transformOrigin: 'center',
positiveText: i18n.t('confirm'),
negativeText: i18n.t('cancel'),
onPositiveClick: () => {
connectionStore.removeKey(name, db, redisKey).then((success) => { connectionStore.removeKey(name, db, redisKey).then((success) => {
if (success) { if (success) {
message.success(i18n.t('delete_key_succ', { key: redisKey })) message.success(i18n.t('delete_key_succ', { key: redisKey }))
} }
}) })
},
}) })
break break
case 'key_copy': case 'key_copy':

View File

@ -7,13 +7,13 @@
"save": "Save", "save": "Save",
"new_conn": "Add New Connection", "new_conn": "Add New Connection",
"new_group": "Add New Group", "new_group": "Add New Group",
"rename_group": "Rename Group",
"disconnect_all": "Disconnect all connections", "disconnect_all": "Disconnect all connections",
"sort_conn": "Resort Connections", "sort_conn": "Resort Connections",
"reload_key": "Reload Current Key", "reload_key": "Reload Current Key",
"close_confirm_title": "Confirm Close",
"close_confirm": "Confirm close this tab and connection", "close_confirm": "Confirm close this tab and connection",
"opening_connection": "Opening Connection...", "opening_connection": "Opening Connection...",
"remove_conn_tip": "Connection \"{conn}\" will be deleted", "remove_tip": "{type} \"{name}\" will be deleted",
"ttl": "TTL", "ttl": "TTL",
"forever": "Forever", "forever": "Forever",
"rename_key": "Rename Key", "rename_key": "Rename Key",
@ -38,6 +38,7 @@
"remove_conn": "Delete Connection", "remove_conn": "Delete Connection",
"edit_conn": "Edit Connection Config", "edit_conn": "Edit Connection Config",
"edit_conn_group": "Edit Connection Group", "edit_conn_group": "Edit Connection Group",
"rename_conn_group": "Rename Connection Group",
"remove_conn_group": "Delete Connection Group", "remove_conn_group": "Delete Connection Group",
"no_group": "No Group", "no_group": "No Group",
"copy_path": "Copy Path", "copy_path": "Copy Path",
@ -50,6 +51,7 @@
"advanced": "Advanced", "advanced": "Advanced",
"editor": "Editor", "editor": "Editor",
"conn_group": "Group", "conn_group": "Group",
"group_name": "Group Name",
"conn_name": "Name", "conn_name": "Name",
"conn_addr": "Address", "conn_addr": "Address",
"conn_usr": "Username", "conn_usr": "Username",
@ -68,7 +70,6 @@
"conn_advn_conn_timeout": "Connection Timeout", "conn_advn_conn_timeout": "Connection Timeout",
"conn_advn_exec_timeout": "Execution Timeout", "conn_advn_exec_timeout": "Execution Timeout",
"conn_advn_mark_color": "Mark Color", "conn_advn_mark_color": "Mark Color",
"new_conn_succ": "Create new connection success!",
"second": "Second(s)", "second": "Second(s)",
"new_key_name": "New Key Name", "new_key_name": "New Key Name",
"new_key": "Add New Key", "new_key": "Add New Key",

View File

@ -7,13 +7,13 @@
"save": "保存", "save": "保存",
"new_conn": "添加新连接", "new_conn": "添加新连接",
"new_group": "添加新分组", "new_group": "添加新分组",
"rename_group": "重命名分组",
"disconnect_all": "断开所有连接", "disconnect_all": "断开所有连接",
"sort_conn": "调整连接顺序", "sort_conn": "调整连接顺序",
"reload_key": "重新载入此键内容", "reload_key": "重新载入此键内容",
"close_confirm_title": "关闭确认",
"close_confirm": "是否关闭当前连接", "close_confirm": "是否关闭当前连接",
"opening_connection": "正在打开连接...", "opening_connection": "正在打开连接...",
"remove_conn_tip": "连接 \"{conn}\" 将会被删除", "remove_tip": "{type} \"{name}\" 将会被删除",
"ttl": "TTL", "ttl": "TTL",
"forever": "永久", "forever": "永久",
"rename_key": "重命名键", "rename_key": "重命名键",
@ -40,6 +40,7 @@
"remove_conn": "删除连接", "remove_conn": "删除连接",
"edit_conn": "编辑连接配置", "edit_conn": "编辑连接配置",
"edit_conn_group": "编辑连接分组", "edit_conn_group": "编辑连接分组",
"rename_conn_group": "重命名连接分组",
"remove_conn_group": "删除连接分组", "remove_conn_group": "删除连接分组",
"no_group": "无分组", "no_group": "无分组",
"copy_path": "复制路径", "copy_path": "复制路径",
@ -51,7 +52,8 @@
"general": "常规配置", "general": "常规配置",
"advanced": "高级配置", "advanced": "高级配置",
"editor": "编辑器", "editor": "编辑器",
"conn_group": "所属分组", "conn_group": "分组",
"group_name": "分组名",
"conn_name": "连接名", "conn_name": "连接名",
"conn_addr": "连接地址", "conn_addr": "连接地址",
"conn_usr": "用户名", "conn_usr": "用户名",
@ -70,7 +72,6 @@
"conn_advn_conn_timeout": "连接超时", "conn_advn_conn_timeout": "连接超时",
"conn_advn_exec_timeout": "执行超时", "conn_advn_exec_timeout": "执行超时",
"conn_advn_mark_color": "标记颜色", "conn_advn_mark_color": "标记颜色",
"new_conn_succ": "新建连接成功",
"second": "秒", "second": "秒",
"new_key_name": "新键名", "new_key_name": "新键名",
"new_key": "添加新键", "new_key": "添加新键",

View File

@ -5,13 +5,16 @@ import {
AddListItem, AddListItem,
AddZSetValue, AddZSetValue,
CloseConnection, CloseConnection,
CreateGroup,
GetConnection, GetConnection,
GetKeyValue, GetKeyValue,
ListConnection, ListConnection,
OpenConnection, OpenConnection,
OpenDatabase, OpenDatabase,
RemoveConnection, RemoveConnection,
RemoveGroup,
RemoveKey, RemoveKey,
RenameGroup,
RenameKey, RenameKey,
SaveConnection, SaveConnection,
SetHashValue, SetHashValue,
@ -76,29 +79,23 @@ const useConnectionStore = defineStore('connections', {
const conns = [] const conns = []
const groups = [] const groups = []
const { data = [{ groupName: '', connections: [] }] } = await ListConnection() const { data = [{ groupName: '', connections: [] }] } = await ListConnection()
for (let i = 0; i < data.length; i++) { for (const conn of data) {
const group = data[i] if (conn.type !== 'group') {
// Top level group // top level
if (isEmpty(group.groupName)) {
const len = size(group.connections)
for (let j = 0; j < len; j++) {
const item = group.connections[j]
conns.push({ conns.push({
key: item.name, key: conn.name,
label: item.name, label: conn.name,
name: item.name, name: conn.name,
type: ConnectionType.Server, type: ConnectionType.Server,
// isLeaf: false, // isLeaf: false,
}) })
}
} else { } else {
groups.push(group.groupName) // custom group
// Custom group groups.push(conn.name)
const subConns = get(conn, 'connections', [])
const children = [] const children = []
const len = size(group.connections) for (const item of subConns) {
for (let j = 0; j < len; j++) { const value = conn.name + '/' + item.name
const item = group.connections[j]
const value = group.groupName + '/' + item.name
children.push({ children.push({
key: value, key: value,
label: item.name, label: item.name,
@ -108,8 +105,8 @@ const useConnectionStore = defineStore('connections', {
}) })
} }
conns.push({ conns.push({
key: group.groupName, key: conn.name,
label: group.groupName, label: conn.name,
type: ConnectionType.Group, type: ConnectionType.Group,
children, children,
}) })
@ -127,7 +124,6 @@ const useConnectionStore = defineStore('connections', {
async getConnectionProfile(name) { async getConnectionProfile(name) {
try { try {
const { data, success } = await GetConnection(name) const { data, success } = await GetConnection(name)
console.log(JSON.stringify(data))
if (success) { if (success) {
return data return data
} }
@ -263,6 +259,20 @@ const useConnectionStore = defineStore('connections', {
return true return true
}, },
/**
* Close all connection
* @returns {Promise<void>}
*/
async closeAllConnection() {
for (const name in this.databases) {
await CloseConnection(name)
}
this.databases = {}
const tabStore = useTabStore()
tabStore.removeAllTab()
},
/** /**
* Remove connection * Remove connection
* @param name * @param name
@ -279,6 +289,53 @@ const useConnectionStore = defineStore('connections', {
return { success: true } return { success: true }
}, },
/**
* Create connection group
* @param name
* @returns {Promise<{success: boolean, [msg]: string}>}
*/
async createGroup(name) {
const { success, msg } = await CreateGroup(name)
if (!success) {
return { success: false, msg }
}
await this.initConnections(true)
return { success: true }
},
/**
* Rename connection group
* @param name
* @param newName
* @returns {Promise<{success: boolean, [msg]: string}>}
*/
async renameGroup(name, newName) {
if (name === newName) {
return { success: true }
}
const { success, msg } = await RenameGroup(name, newName)
if (!success) {
return { success: false, msg }
}
await this.initConnections(true)
return { success: true }
},
/**
* Remove group by name
* @param {string} name
* @param {boolean} [includeConn]
* @returns {Promise<{success: boolean, [msg]: string}>}
*/
async deleteGroup(name, includeConn) {
const { success, msg } = await RemoveGroup(name, includeConn === true)
if (!success) {
return { success: false, msg }
}
await this.initConnections(true)
return { success: true }
},
/** /**
* Open database and load all keys * Open database and load all keys
* @param connName * @param connName

View File

@ -6,6 +6,9 @@ const useDialogStore = defineStore('dialog', {
connDialogVisible: false, connDialogVisible: false,
connParam: null, connParam: null,
groupDialogVisible: false,
editGroup: '',
/** /**
* @property {string} prefix * @property {string} prefix
* @property {string} server * @property {string} server
@ -58,6 +61,21 @@ const useDialogStore = defineStore('dialog', {
this.connDialogVisible = false this.connDialogVisible = false
}, },
openNewGroupDialog() {
this.groupDialogVisible = true
},
closeNewGroupDialog() {
this.groupDialogVisible = false
},
openRenameGroupDialog(name) {
this.editGroup = name
this.groupDialogVisible = true
},
closeRenameGroupDialog() {
this.groupDialogVisible = false
},
/** /**
* *
* @param {string} server * @param {string} server

View File

@ -199,7 +199,7 @@ const useTabStore = defineStore('tab', {
}, },
removeAllTab() { removeAllTab() {
this.tabList = [] this.tabList = []
this.newBlankTab() this._setActivatedIndex(-1, false)
}, },
}, },
}) })

View File

@ -0,0 +1,28 @@
import { createDiscreteApi } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { onMounted } from 'vue'
const { dialog } = createDiscreteApi(['dialog'])
export function useConfirmDialog() {
let i18n
onMounted(() => {
i18n = useI18n()
})
return {
warning: (content, onConfirm) => {
dialog.warning({
title: i18n.t('warning'),
content: content,
closable: false,
autoFocus: false,
transformOrigin: 'center',
positiveText: i18n.t('confirm'),
negativeText: i18n.t('cancel'),
onPositiveClick: () => {
onConfirm && onConfirm()
},
})
},
}
}