Compare commits

..

No commits in common. "4a0807e4636ec78e6beab185bf0a63aa535662cc" and "44f8581a41b595b64b6cb3c5aec408079f55f43e" have entirely different histories.

27 changed files with 374 additions and 1027 deletions

1
.gitignore vendored
View File

@ -5,4 +5,3 @@ frontend/wailsjs
design/ design/
.vscode .vscode
.idea .idea
test

27
app.go Normal file
View File

@ -0,0 +1,27 @@
package main
import (
"context"
"fmt"
)
// App struct
type App struct {
ctx context.Context
}
// NewApp creates a new App application struct
func NewApp() *App {
return &App{}
}
// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s, It's show time!", name)
}

File diff suppressed because it is too large Load Diff

View File

@ -113,12 +113,10 @@ func (p *preferencesService) GetAppVersion() (resp types.JSResp) {
} }
func (p *preferencesService) SaveWindowSize(width, height int) { func (p *preferencesService) SaveWindowSize(width, height int) {
if width >= consts.DEFAULT_WINDOW_WIDTH && height >= consts.DEFAULT_WINDOW_HEIGHT { p.UpdatePreferences(map[string]any{
p.UpdatePreferences(map[string]any{ "behavior.windowWidth": width,
"behavior.windowWidth": width, "behavior.windowHeight": height,
"behavior.windowHeight": height, })
})
}
} }
func (p *preferencesService) GetWindowSize() (width, height int) { func (p *preferencesService) GetWindowSize() (width, height int) {

View File

@ -1,105 +0,0 @@
package services
import (
"context"
"github.com/wailsapp/wails/v2/pkg/runtime"
"log"
"sync"
"time"
"tinyrdm/backend/types"
)
type systemService struct {
ctx context.Context
}
var system *systemService
var onceSystem sync.Once
func System() *systemService {
if system == nil {
onceSystem.Do(func() {
system = &systemService{}
go system.loopWindowEvent()
})
}
return system
}
func (s *systemService) Start(ctx context.Context) {
s.ctx = ctx
}
// SelectFile open file dialog to select a file
func (s *systemService) SelectFile(title string) (resp types.JSResp) {
filepath, err := runtime.OpenFileDialog(s.ctx, runtime.OpenDialogOptions{
Title: title,
ShowHiddenFiles: true,
})
if err != nil {
log.Println(err)
resp.Msg = err.Error()
return
}
resp.Success = true
resp.Data = map[string]any{
"path": filepath,
}
return
}
func (s *systemService) loopWindowEvent() {
var fullscreen, maximised, minimised, normal bool
var width, height int
var dirty bool
for {
time.Sleep(300 * time.Millisecond)
if s.ctx == nil {
continue
}
dirty = false
if f := runtime.WindowIsFullscreen(s.ctx); f != fullscreen {
// full-screen switched
fullscreen = f
dirty = true
}
if w, h := runtime.WindowGetSize(s.ctx); w != width || h != height {
// window size changed
width, height = w, h
dirty = true
}
if m := runtime.WindowIsMaximised(s.ctx); m != maximised {
maximised = m
dirty = true
}
if m := runtime.WindowIsMinimised(s.ctx); m != minimised {
minimised = m
dirty = true
}
if n := runtime.WindowIsNormal(s.ctx); n != normal {
normal = n
dirty = true
}
if dirty {
runtime.EventsEmit(s.ctx, "window_changed", map[string]any{
"fullscreen": fullscreen,
"width": width,
"height": height,
"maximised": maximised,
"minimised": minimised,
"normal": normal,
})
if !fullscreen && !minimised {
// save window size
Preferences().SaveWindowSize(width, height)
}
}
}
}

View File

@ -16,10 +16,8 @@ type ConnectionConfig struct {
DBFilterType string `json:"dbFilterType" yaml:"db_filter_type,omitempty"` DBFilterType string `json:"dbFilterType" yaml:"db_filter_type,omitempty"`
DBFilterList []int `json:"dbFilterList" yaml:"db_filter_list,omitempty"` DBFilterList []int `json:"dbFilterList" yaml:"db_filter_list,omitempty"`
MarkColor string `json:"markColor,omitempty" yaml:"mark_color,omitempty"` MarkColor string `json:"markColor,omitempty" yaml:"mark_color,omitempty"`
SSL ConnectionSSL `json:"ssl,omitempty" yaml:"ssl,omitempty"`
SSH ConnectionSSH `json:"ssh,omitempty" yaml:"ssh,omitempty"` SSH ConnectionSSH `json:"ssh,omitempty" yaml:"ssh,omitempty"`
Sentinel ConnectionSentinel `json:"sentinel,omitempty" yaml:"sentinel,omitempty"` Sentinel ConnectionSentinel `json:"sentinel,omitempty" yaml:"sentinel,omitempty"`
Cluster ConnectionCluster `json:"cluster,omitempty" yaml:"cluster,omitempty"`
} }
type Connection struct { type Connection struct {
@ -30,6 +28,11 @@ type Connection struct {
type Connections []Connection type Connections []Connection
type ConnectionGroup struct {
GroupName string `json:"groupName" yaml:"group_name"`
Connections []Connection `json:"connections" yaml:"connections"`
}
type ConnectionDB struct { type ConnectionDB struct {
Name string `json:"name"` Name string `json:"name"`
Index int `json:"index"` Index int `json:"index"`
@ -38,31 +41,20 @@ type ConnectionDB struct {
AvgTTL int `json:"avgTtl,omitempty"` AvgTTL int `json:"avgTtl,omitempty"`
} }
type ConnectionSSL struct {
Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"`
KeyFile string `json:"keyFile,omitempty" yaml:"keyFile,omitempty"`
CertFile string `json:"certFile,omitempty" yaml:"certFile,omitempty"`
CAFile string `json:"caFile,omitempty" yaml:"caFile,omitempty"`
}
type ConnectionSSH struct { type ConnectionSSH struct {
Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"` Enable bool `json:"enable" yaml:"enable"`
Addr string `json:"addr,omitempty" yaml:"addr,omitempty"` Addr string `json:"addr,omitempty" yaml:"addr,omitempty"`
Port int `json:"port,omitempty" yaml:"port,omitempty"` Port int `json:"port,omitempty" yaml:"port,omitempty"`
LoginType string `json:"loginType,omitempty" yaml:"login_type"` LoginType string `json:"loginType" yaml:"login_type"`
Username string `json:"username,omitempty" yaml:"username,omitempty"` Username string `json:"username" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"` Password string `json:"password,omitempty" yaml:"password,omitempty"`
PKFile string `json:"pkFile,omitempty" yaml:"pk_file,omitempty"` PKFile string `json:"pkFile,omitempty" yaml:"pk_file,omitempty"`
Passphrase string `json:"passphrase,omitempty" yaml:"passphrase,omitempty"` Passphrase string `json:"passphrase,omitempty" yaml:"passphrase,omitempty"`
} }
type ConnectionSentinel struct { type ConnectionSentinel struct {
Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"` Enable bool `json:"enable" yaml:"enable"`
Master string `json:"master,omitempty" yaml:"master,omitempty"` Master string `json:"master" yaml:"master"`
Username string `json:"username,omitempty" yaml:"username,omitempty"` Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"` Password string `json:"password,omitempty" yaml:"password,omitempty"`
} }
type ConnectionCluster struct {
Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"`
}

View File

@ -76,17 +76,17 @@ func (l *LogHook) DialHook(next redis.DialHook) redis.DialHook {
func (l *LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook { func (l *LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
return func(ctx context.Context, cmd redis.Cmder) error { return func(ctx context.Context, cmd redis.Cmder) error {
log.Println(cmd)
t := time.Now() t := time.Now()
err := next(ctx, cmd) err := next(ctx, cmd)
b := make([]byte, 0, 64)
for i, arg := range cmd.Args() {
if i > 0 {
b = append(b, ' ')
}
b = appendArg(b, arg)
}
log.Println(string(b))
if l.cmdExec != nil { if l.cmdExec != nil {
b := make([]byte, 0, 64)
for i, arg := range cmd.Args() {
if i > 0 {
b = append(b, ' ')
}
b = appendArg(b, arg)
}
l.cmdExec(string(b), time.Since(t).Milliseconds()) l.cmdExec(string(b), time.Since(t).Milliseconds())
} }
return err return err
@ -98,22 +98,19 @@ func (l *LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.Proc
t := time.Now() t := time.Now()
err := next(ctx, cmds) err := next(ctx, cmds)
cost := time.Since(t).Milliseconds() cost := time.Since(t).Milliseconds()
b := make([]byte, 0, 64)
for _, cmd := range cmds { for _, cmd := range cmds {
log.Println("pipeline: ", cmd) log.Println("pipeline: ", cmd)
if l.cmdExec != nil { if l.cmdExec != nil {
b := make([]byte, 0, 64)
for i, arg := range cmd.Args() { for i, arg := range cmd.Args() {
if i > 0 { if i > 0 {
b = append(b, ' ') b = append(b, ' ')
} }
b = appendArg(b, arg) b = appendArg(b, arg)
} }
b = append(b, '\n') l.cmdExec(string(b), cost)
} }
} }
if l.cmdExec != nil {
l.cmdExec(string(b), cost)
}
return err return err
} }
} }

View File

@ -14,7 +14,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"tinyrdm/backend/types" "tinyrdm/backend/types"
"unicode/utf8"
) )
// ConvertTo convert string to specified type // ConvertTo convert string to specified type
@ -188,9 +187,7 @@ func decodeJson(str string) (string, bool) {
func decodeBase64(str string) (string, bool) { func decodeBase64(str string) (string, bool) {
if decodedStr, err := base64.StdEncoding.DecodeString(str); err == nil { if decodedStr, err := base64.StdEncoding.DecodeString(str); err == nil {
if s := string(decodedStr); utf8.ValidString(s) { return string(decodedStr), true
return s, true
}
} }
return str, false return str, false
} }

View File

@ -9,10 +9,10 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"highlight.js": "^11.9.0", "highlight.js": "^11.8.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"pinia": "^2.1.7", "pinia": "^2.1.6",
"sass": "^1.69.3", "sass": "^1.69.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.5.0" "vue-i18n": "^9.5.0"
}, },
@ -1048,9 +1048,9 @@
} }
}, },
"node_modules/highlight.js": { "node_modules/highlight.js": {
"version": "11.9.0", "version": "11.8.0",
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.9.0.tgz", "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.8.0.tgz",
"integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==",
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=12.0.0"
} }
@ -1393,9 +1393,9 @@
} }
}, },
"node_modules/pinia": { "node_modules/pinia": {
"version": "2.1.7", "version": "2.1.6",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz", "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.6.tgz",
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==", "integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
"dependencies": { "dependencies": {
"@vue/devtools-api": "^6.5.0", "@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5" "vue-demi": ">=0.14.5"
@ -1545,9 +1545,9 @@
} }
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.69.3", "version": "1.69.0",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.3.tgz", "resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.0.tgz",
"integrity": "sha512-X99+a2iGdXkdWn1akFPs0ZmelUzyAQfvqYc2P/MPTrJRuIRoTffGzT9W9nFqG00S+c8hXzVmgxhUuHFdrwxkhQ==", "integrity": "sha512-l3bbFpfTOGgQZCLU/gvm1lbsQ5mC/WnLz3djL2v4WCJBDrWm58PO+jgngcGRNnKUh6wSsdm50YaovTqskZ0xDQ==",
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0", "immutable": "^4.0.0",
@ -2656,9 +2656,9 @@
} }
}, },
"highlight.js": { "highlight.js": {
"version": "11.9.0", "version": "11.8.0",
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.9.0.tgz", "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.8.0.tgz",
"integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==" "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg=="
}, },
"human-signals": { "human-signals": {
"version": "2.1.0", "version": "2.1.0",
@ -2926,9 +2926,9 @@
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
}, },
"pinia": { "pinia": {
"version": "2.1.7", "version": "2.1.6",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz", "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.6.tgz",
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==", "integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
"requires": { "requires": {
"@vue/devtools-api": "^6.5.0", "@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5" "vue-demi": ">=0.14.5"
@ -3025,9 +3025,9 @@
} }
}, },
"sass": { "sass": {
"version": "1.69.3", "version": "1.69.0",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.3.tgz", "resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.0.tgz",
"integrity": "sha512-X99+a2iGdXkdWn1akFPs0ZmelUzyAQfvqYc2P/MPTrJRuIRoTffGzT9W9nFqG00S+c8hXzVmgxhUuHFdrwxkhQ==", "integrity": "sha512-l3bbFpfTOGgQZCLU/gvm1lbsQ5mC/WnLz3djL2v4WCJBDrWm58PO+jgngcGRNnKUh6wSsdm50YaovTqskZ0xDQ==",
"requires": { "requires": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0", "immutable": "^4.0.0",

View File

@ -10,10 +10,10 @@
}, },
"dependencies": { "dependencies": {
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"highlight.js": "^11.9.0", "highlight.js": "^11.8.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"pinia": "^2.1.7", "pinia": "^2.1.6",
"sass": "^1.69.3", "sass": "^1.69.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.5.0" "vue-i18n": "^9.5.0"
}, },

View File

@ -1 +1 @@
3b7cabd69c1c3dad11dea0682b3c6bef 82f42b67ae979cb1af64c05c79c5251f

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import ContentPane from './components/content/ContentPane.vue' import ContentPane from './components/content/ContentPane.vue'
import BrowserPane from './components/sidebar/BrowserPane.vue' import BrowserPane from './components/sidebar/BrowserPane.vue'
import { computed, onMounted, reactive, ref, watch } from 'vue' import { computed, reactive, ref, watch } from 'vue'
import { debounce, get } from 'lodash' import { debounce, get } from 'lodash'
import { useThemeVars } from 'naive-ui' import { useThemeVars } from 'naive-ui'
import NavMenu from './components/sidebar/NavMenu.vue' import NavMenu from './components/sidebar/NavMenu.vue'
@ -13,7 +13,7 @@ import useConnectionStore from './stores/connections.js'
import ContentLogPane from './components/content/ContentLogPane.vue' import ContentLogPane from './components/content/ContentLogPane.vue'
import ContentValueTab from '@/components/content/ContentValueTab.vue' import ContentValueTab from '@/components/content/ContentValueTab.vue'
import ToolbarControlWidget from '@/components/common/ToolbarControlWidget.vue' import ToolbarControlWidget from '@/components/common/ToolbarControlWidget.vue'
import { EventsOn, WindowIsFullscreen, WindowIsMaximised, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js' import { WindowIsFullscreen, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js'
import { isMacOS } from '@/utils/platform.js' import { isMacOS } from '@/utils/platform.js'
import iconUrl from '@/assets/images/icon.png' import iconUrl from '@/assets/images/icon.png'
@ -37,11 +37,11 @@ const logPaneRef = ref(null)
// const preferences = ref({}) // const preferences = ref({})
// provide('preferences', preferences) // provide('preferences', preferences)
const saveSidebarWidth = debounce(prefStore.savePreferences, 1000, { trailing: true }) const saveWidth = debounce(prefStore.savePreferences, 1000, { trailing: true })
const handleResize = (evt) => { const handleResize = (evt) => {
if (data.resizing) { if (data.resizing) {
prefStore.setAsideWidth(Math.max(evt.clientX - data.navMenuWidth, 300)) prefStore.setAsideWidth(Math.max(evt.clientX - data.navMenuWidth, 300))
saveSidebarWidth() saveWidth()
} }
} }
@ -49,6 +49,7 @@ const stopResize = () => {
data.resizing = false data.resizing = false
document.removeEventListener('mousemove', handleResize) document.removeEventListener('mousemove', handleResize)
document.removeEventListener('mouseup', stopResize) document.removeEventListener('mouseup', stopResize)
// TODO: Save sidebar x-position
} }
const startResize = () => { const startResize = () => {
@ -74,54 +75,20 @@ watch(
}, },
) )
const borderRadius = computed(() => {
// FIXME: cannot get full screen status sync?
// if (isMacOS()) {
// return WindowIsFullscreen().then((full) => {
// return full ? '0' : '10px'
// })
// }
return '10px'
})
const border = computed(() => { const border = computed(() => {
const color = isMacOS() && false ? '#0000' : themeVars.value.borderColor const color = isMacOS() && false ? '#0000' : themeVars.value.borderColor
return `1px solid ${color}` return `1px solid ${color}`
}) })
const borderRadius = ref(10)
const logoPaddingLeft = ref(10)
const maximised = ref(false)
const toggleWindowRadius = (on) => {
borderRadius.value = on ? 10 : 0
}
const onToggleFullscreen = (fullscreen) => {
if (fullscreen) {
logoPaddingLeft.value = 10
toggleWindowRadius(false)
} else {
logoPaddingLeft.value = isMacOS() ? 70 : 10
toggleWindowRadius(true)
}
}
const onToggleMaximize = (isMaximised) => {
if (isMaximised) {
maximised.value = true
if (!isMacOS()) {
toggleWindowRadius(false)
}
} else {
maximised.value = false
if (!isMacOS()) {
toggleWindowRadius(true)
}
}
}
EventsOn('window_changed', (info) => {
const { fullscreen, maximised } = info
onToggleFullscreen(fullscreen === true)
onToggleMaximize(maximised)
})
onMounted(async () => {
const fullscreen = await WindowIsFullscreen()
onToggleFullscreen(fullscreen === true)
const maximised = await WindowIsMaximised()
onToggleMaximize(maximised)
})
</script> </script>
<template> <template>
@ -129,7 +96,7 @@ onMounted(async () => {
<n-spin <n-spin
:show="props.loading" :show="props.loading"
:theme-overrides="{ opacitySpinning: 0 }" :theme-overrides="{ opacitySpinning: 0 }"
:style="{ backgroundColor: themeVars.bodyColor, borderRadius: `${borderRadius}px`, border }"> :style="{ backgroundColor: themeVars.bodyColor, borderRadius, border }">
<div <div
id="app-content-wrapper" id="app-content-wrapper"
class="flex-box-v" class="flex-box-v"
@ -149,7 +116,7 @@ onMounted(async () => {
id="app-toolbar-title" id="app-toolbar-title"
:style="{ :style="{
width: `${data.navMenuWidth + prefStore.behavior.asideWidth - 4}px`, width: `${data.navMenuWidth + prefStore.behavior.asideWidth - 4}px`,
paddingLeft: `${logoPaddingLeft}px`, paddingLeft: isMacOS() ? '70px' : '10px',
}"> }">
<n-space align="center" :wrap-item="false" :wrap="false" :size="3"> <n-space align="center" :wrap-item="false" :wrap="false" :size="3">
<n-avatar :src="iconUrl" color="#0000" :size="35" style="min-width: 35px" /> <n-avatar :src="iconUrl" color="#0000" :size="35" style="min-width: 35px" />
@ -176,11 +143,7 @@ onMounted(async () => {
</div> </div>
<div class="flex-item-expand"></div> <div class="flex-item-expand"></div>
<!-- simulate window control buttons --> <!-- simulate window control buttons -->
<toolbar-control-widget <toolbar-control-widget v-if="!isMacOS()" :size="data.toolbarHeight" style="align-self: flex-start" />
v-if="!isMacOS()"
:size="data.toolbarHeight"
:maximised="maximised"
style="align-self: flex-start" />
</div> </div>
<!-- content --> <!-- content -->

View File

@ -4,8 +4,7 @@ import WindowMax from '@/components/icons/WindowMax.vue'
import WindowClose from '@/components/icons/WindowClose.vue' import WindowClose from '@/components/icons/WindowClose.vue'
import { computed } from 'vue' import { computed } from 'vue'
import { useThemeVars } from 'naive-ui' import { useThemeVars } from 'naive-ui'
import { Quit, WindowMinimise, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js' import { Quit, WindowIsMaximised, WindowMinimise, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js'
import WindowRestore from '@/components/icons/WindowRestore.vue'
const themeVars = useThemeVars() const themeVars = useThemeVars()
const props = defineProps({ const props = defineProps({
@ -13,9 +12,6 @@ const props = defineProps({
type: Number, type: Number,
default: 35, default: 35,
}, },
maximised: {
type: Boolean,
},
}) })
const buttonSize = computed(() => { const buttonSize = computed(() => {
@ -37,7 +33,7 @@ const handleClose = () => {
<template> <template>
<n-space :wrap-item="false" align="center" justify="center" :size="0"> <n-space :wrap-item="false" align="center" justify="center" :size="0">
<n-tooltip :show-arrow="false" :delay="1000"> <n-tooltip :show-arrow="false">
{{ $t('menu.minimise') }} {{ $t('menu.minimise') }}
<template #trigger> <template #trigger>
<div class="btn-wrapper" @click="handleMinimise"> <div class="btn-wrapper" @click="handleMinimise">
@ -45,23 +41,15 @@ const handleClose = () => {
</div> </div>
</template> </template>
</n-tooltip> </n-tooltip>
<n-tooltip :show-arrow="false" :delay="1000" v-if="maximised"> <n-tooltip :show-arrow="false">
{{ $t('menu.restore') }} {{ WindowIsMaximised() ? $t('menu.restore') : $t('menu.maximise') }}
<template #trigger>
<div class="btn-wrapper" @click="handleMaximise">
<window-restore />
</div>
</template>
</n-tooltip>
<n-tooltip :show-arrow="false" :delay="1000" v-else>
{{ $t('menu.maximise') }}
<template #trigger> <template #trigger>
<div class="btn-wrapper" @click="handleMaximise"> <div class="btn-wrapper" @click="handleMaximise">
<window-max /> <window-max />
</div> </div>
</template> </template>
</n-tooltip> </n-tooltip>
<n-tooltip :show-arrow="false" :delay="1000"> <n-tooltip :show-arrow="false">
{{ $t('menu.close') }} {{ $t('menu.close') }}
<template #trigger> <template #trigger>
<div class="btn-wrapper" @click="handleClose"> <div class="btn-wrapper" @click="handleClose">

View File

@ -12,7 +12,7 @@ import useConnectionStore from 'stores/connections.js'
import Copy from '@/components/icons/Copy.vue' import Copy from '@/components/icons/Copy.vue'
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js' import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
import { computed } from 'vue' import { computed } from 'vue'
import { isEmpty, padStart } from 'lodash' import { isEmpty } from 'lodash'
const props = defineProps({ const props = defineProps({
server: String, server: String,
@ -51,23 +51,6 @@ const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
}) })
const ttlString = computed(() => {
let s = ''
if (props.ttl > 0) {
const hours = Math.floor(props.ttl / 3600)
s += padStart(hours + ':', 3, '0')
const minutes = Math.floor((props.ttl % 3600) / 60)
s += padStart(minutes + ':', 3, '0')
const seconds = Math.floor(props.ttl % 60)
s += padStart(seconds + '', 2, '0')
} else if (props.ttl < 0) {
s = i18n.t('interface.forever')
} else {
s = '00:00:00'
}
return s
})
const onReloadKey = () => { const onReloadKey = () => {
connectionStore.loadKeyValue(props.server, props.db, keyName.value) connectionStore.loadKeyValue(props.server, props.db, keyName.value)
} }
@ -121,10 +104,13 @@ const onDeleteKey = () => {
<template #icon> <template #icon>
<n-icon :component="Timer" size="18" /> <n-icon :component="Timer" size="18" />
</template> </template>
{{ ttlString }} <template v-if="ttl < 0">
{{ $t('interface.forever') }}
</template>
<template v-else>{{ ttl }} {{ $t('common.second') }}</template>
</n-button> </n-button>
</template> </template>
TTL{{ `${ttl > 0 ? ': ' + ttl + $t('common.second') : ''}` }} TTL
</n-tooltip> </n-tooltip>
<icon-button :icon="Edit" border size="18" t-tooltip="interface.rename_key" @click="onRenameKey" /> <icon-button :icon="Edit" border size="18" t-tooltip="interface.rename_key" @click="onRenameKey" />
</n-button-group> </n-button-group>

View File

@ -2,11 +2,10 @@
import { every, get, includes, isEmpty, map, sortBy, toNumber } from 'lodash' import { every, get, includes, isEmpty, map, sortBy, toNumber } from 'lodash'
import { computed, nextTick, ref, watch } from 'vue' import { computed, nextTick, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { ListSentinelMasters, TestConnection } from 'wailsjs/go/services/connectionService.js' import { ListSentinelMasters, SelectKeyFile, TestConnection } from 'wailsjs/go/services/connectionService.js'
import useDialog, { ConnDialogType } from 'stores/dialog' import useDialog, { ConnDialogType } from 'stores/dialog'
import Close from '@/components/icons/Close.vue' import Close from '@/components/icons/Close.vue'
import useConnectionStore from 'stores/connections.js' import useConnectionStore from 'stores/connections.js'
import { SelectFile } from 'wailsjs/go/services/systemService.js'
/** /**
* Dialog for new or edit connection * Dialog for new or edit connection
@ -83,36 +82,12 @@ const sshLoginType = computed(() => {
return get(generalForm.value, 'ssh.loginType', 'pwd') return get(generalForm.value, 'ssh.loginType', 'pwd')
}) })
const onSSHChooseKey = async () => { const onChoosePKFile = async () => {
const { success, data } = await SelectFile() const { success, data } = await SelectKeyFile(i18n.t('dialogue.connection.ssh.pkfile_selection_title'))
const path = get(data, 'path', '')
if (!isEmpty(path)) {
generalForm.value.ssh.pkFile = path
}
}
const onSSLChooseCert = async () => {
const { success, data } = await SelectFile()
const path = get(data, 'path', '')
if (!isEmpty(path)) {
generalForm.value.ssl.certFile = path
}
}
const onSSLChooseKey = async () => {
const { success, data } = await SelectFile()
const path = get(data, 'path', '')
if (!isEmpty(path)) {
generalForm.value.ssl.keyFile = path
}
}
const onSSLChooseCA = async () => {
const { success, data } = await SelectFile()
if (!success) { if (!success) {
generalForm.value.ssl.caFile = '' generalForm.value.ssh.pkFile = ''
} else { } else {
generalForm.value.ssl.caFile = get(data, 'path', '') generalForm.value.ssh.pkFile = get(data, 'path', '')
} }
} }
@ -174,13 +149,8 @@ const onSaveConnection = async () => {
generalForm.value.dbFilterList = [] generalForm.value.dbFilterList = []
} }
// trim ssl data
if (!!!generalForm.value.ssl.enable) {
generalForm.value.ssl = {}
}
// trim ssh login data // trim ssh login data
if (!!generalForm.value.ssh.enable) { if (generalForm.value.ssh.enable) {
switch (generalForm.value.ssh.loginType) { switch (generalForm.value.ssh.loginType) {
case 'pkfile': case 'pkfile':
generalForm.value.ssh.password = '' generalForm.value.ssh.password = ''
@ -192,16 +162,15 @@ const onSaveConnection = async () => {
} }
} else { } else {
// ssh disabled, reset to default value // ssh disabled, reset to default value
generalForm.value.ssh = {} const { ssh } = connectionStore.newDefaultConnection()
generalForm.value.ssh = ssh
} }
// trim sentinel data // trim sentinel data
if (!!!generalForm.value.sentinel.enable) { if (!generalForm.value.sentinel.enable) {
generalForm.value.sentinel = {} generalForm.value.sentinel.master = ''
} generalForm.value.sentinel.username = ''
generalForm.value.sentinel.password = ''
if (!!!generalForm.value.cluster.enable) {
generalForm.value.cluster = {}
} }
// store new connection // store new connection
@ -279,7 +248,6 @@ const onClose = () => {
:show-icon="false" :show-icon="false"
:title="isEditMode ? $t('dialogue.connection.edit_title') : $t('dialogue.connection.new_title')" :title="isEditMode ? $t('dialogue.connection.edit_title') : $t('dialogue.connection.new_title')"
preset="dialog" preset="dialog"
style="width: 600px"
transform-origin="center"> transform-origin="center">
<n-spin :show="closingConnection"> <n-spin :show="closingConnection">
<n-tabs v-model:value="tab" animated type="line"> <n-tabs v-model:value="tab" animated type="line">
@ -418,60 +386,6 @@ const onClose = () => {
</n-form> </n-form>
</n-tab-pane> </n-tab-pane>
<!-- SSL pane -->
<n-tab-pane :tab="$t('dialogue.connection.ssl.title')" display-directive="show" name="ssl">
<n-form-item label-placement="left">
<n-checkbox v-model:checked="generalForm.ssl.enable" size="medium">
{{ $t('dialogue.connection.ssl.enable') }}
</n-checkbox>
</n-form-item>
<n-form
:model="generalForm.ssl"
:show-require-mark="false"
:disabled="!generalForm.ssl.enable"
label-placement="top">
<n-form-item :label="$t('dialogue.connection.ssl.cert_file')">
<n-input-group>
<n-input
v-model:value="generalForm.ssl.certFile"
:placeholder="$t('dialogue.connection.ssl.cert_file_tip')"
clearable />
<n-button
:focusable="false"
:disabled="!generalForm.ssl.enable"
@click="onSSLChooseCert">
...
</n-button>
</n-input-group>
</n-form-item>
<n-form-item :label="$t('dialogue.connection.ssl.key_file')">
<n-input-group>
<n-input
v-model:value="generalForm.ssl.keyFile"
:placeholder="$t('dialogue.connection.ssl.key_file_tip')"
clearable />
<n-button
:focusable="false"
:disabled="!generalForm.ssl.enable"
@click="onSSLChooseKey">
...
</n-button>
</n-input-group>
</n-form-item>
<n-form-item :label="$t('dialogue.connection.ssl.ca_file')">
<n-input-group>
<n-input
v-model:value="generalForm.ssl.caFile"
:placeholder="$t('dialogue.connection.ssl.ca_file_tip')"
clearable />
<n-button :focusable="false" :disabled="!generalForm.ssl.enable" @click="onSSLChooseCA">
...
</n-button>
</n-input-group>
</n-form-item>
</n-form>
</n-tab-pane>
<!-- SSH pane --> <!-- SSH pane -->
<n-tab-pane :tab="$t('dialogue.connection.ssh.title')" display-directive="show" name="ssh"> <n-tab-pane :tab="$t('dialogue.connection.ssh.title')" display-directive="show" name="ssh">
<n-form-item label-placement="left"> <n-form-item label-placement="left">
@ -520,13 +434,7 @@ const onClose = () => {
<n-input <n-input
v-model:value="generalForm.ssh.pkFile" v-model:value="generalForm.ssh.pkFile"
:placeholder="$t('dialogue.connection.ssh.pkfile_tip')" /> :placeholder="$t('dialogue.connection.ssh.pkfile_tip')" />
<n-button <n-button :focusable="false" @click="onChoosePKFile">...</n-button>
:focusable="false"
:disabled="!generalForm.ssh.enable"
@click="onSSHChooseKey"
clearable>
...
</n-button>
</n-input-group> </n-input-group>
</n-form-item> </n-form-item>
<n-form-item v-if="sshLoginType === 'pkfile'" :label="$t('dialogue.connection.ssh.passphrase')"> <n-form-item v-if="sshLoginType === 'pkfile'" :label="$t('dialogue.connection.ssh.passphrase')">
@ -578,20 +486,8 @@ const onClose = () => {
</n-form> </n-form>
</n-tab-pane> </n-tab-pane>
<!-- Cluster pane --> <!-- TODO: SSL tab pane -->
<n-tab-pane :tab="$t('dialogue.connection.cluster.title')" display-directive="show" name="cluster"> <!-- TODO: Cluster tab pane -->
<n-form-item label-placement="left">
<n-checkbox v-model:checked="generalForm.cluster.enable" size="medium">
{{ $t('dialogue.connection.cluster.enable') }}
</n-checkbox>
</n-form-item>
<!-- <n-form-->
<!-- :model="generalForm.cluster"-->
<!-- :show-require-mark="false"-->
<!-- :disabled="!generalForm.cluster.enable"-->
<!-- label-placement="top">-->
<!-- </n-form>-->
</n-tab-pane>
</n-tabs> </n-tabs>
<!-- test result alert--> <!-- test result alert-->

View File

@ -31,7 +31,6 @@ watch(
ttlForm.keyCode = tab.keyCode ttlForm.keyCode = tab.keyCode
if (tab.ttl < 0) { if (tab.ttl < 0) {
// forever // forever
ttlForm.ttl = -1
} else { } else {
ttlForm.ttl = tab.ttl ttlForm.ttl = tab.ttl
} }

View File

@ -1,187 +0,0 @@
<script setup>
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
},
strokeWidth: {
type: [Number, String],
default: 3,
},
fillColor: {
type: String,
default: '#dc423c',
},
})
</script>
<template>
<svg v-if="props.modelValue" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect
x="4"
y="34"
width="8"
height="8"
:fill="props.fillColor"
:stroke="props.fillColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<rect
x="8"
y="6"
width="32"
height="12"
:fill="props.fillColor"
:stroke="props.fillColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M24 34V18"
:stroke="props.fillColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M8 34V26H40V34"
:stroke="props.fillColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<rect
x="36"
y="34"
width="8"
height="8"
:fill="props.fillColor"
:stroke="props.fillColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<rect
x="20"
y="34"
width="8"
height="8"
:fill="props.fillColor"
:stroke="props.fillColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M14 12H16"
stroke="#FFF"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<svg v-else viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5 24L43 24"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M28 4H20C18.8954 4 18 4.89543 18 6V14C18 15.1046 18.8954 16 20 16H28C29.1046 16 30 15.1046 30 14V6C30 4.89543 29.1046 4 28 4Z"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
<path
d="M16 32H8C6.89543 32 6 32.8954 6 34V42C6 43.1046 6.89543 44 8 44H16C17.1046 44 18 43.1046 18 42V34C18 32.8954 17.1046 32 16 32Z"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
<path
d="M40 32H32C30.8954 32 30 32.8954 30 34V42C30 43.1046 30.8954 44 32 44H40C41.1046 44 42 43.1046 42 42V34C42 32.8954 41.1046 32 40 32Z"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
<path
d="M24 24V16"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M36 32V24"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M12 32V24"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<svg v-else viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect
x="4"
y="34"
width="8"
height="8"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<rect
x="8"
y="6"
width="32"
height="12"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M24 34V18"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M8 34V26H40V34"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<rect
x="36"
y="34"
width="8"
height="8"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<rect
x="20"
y="34"
width="8"
height="8"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M14 12H16"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</template>
<style lang="scss" scoped></style>

View File

@ -2,15 +2,17 @@
const props = defineProps({ const props = defineProps({
size: { size: {
type: [Number, String], type: [Number, String],
default: 14, default: 12,
}, },
}) })
</script> </script>
<template> <template>
<svg :width="props.size" :height="props.size" viewBox="0 0 48 48" fill="none"> <svg aria-hidden="false" :width="props.size" :height="props.size" viewBox="0 0 12 12">
<path d="M8 8L40 40" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" /> <polygon
<path d="M8 40L40 8" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" /> fill="currentColor"
fill-rule="evenodd"
points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"></polygon>
</svg> </svg>
</template> </template>

View File

@ -2,18 +2,14 @@
const props = defineProps({ const props = defineProps({
size: { size: {
type: [Number, String], type: [Number, String],
default: 14, default: 12,
}, },
}) })
</script> </script>
<template> <template>
<svg :width="props.size" :height="props.size" viewBox="0 0 48 48" fill="none"> <svg aria-hidden="false" :width="props.size" :height="props.size" viewBox="0 0 12 12">
<path <rect width="9" height="9" x="1.5" y="1.5" fill="none" stroke="currentColor"></rect>
d="M39 6H9C7.34315 6 6 7.34315 6 9V39C6 40.6569 7.34315 42 9 42H39C40.6569 42 42 40.6569 42 39V9C42 7.34315 40.6569 6 39 6Z"
fill="none"
stroke="currentColor"
stroke-width="4" />
</svg> </svg>
</template> </template>

View File

@ -2,14 +2,14 @@
const props = defineProps({ const props = defineProps({
size: { size: {
type: [Number, String], type: [Number, String],
default: 14, default: 12,
}, },
}) })
</script> </script>
<template> <template>
<svg :width="props.size" :height="props.size" viewBox="0 0 48 48" fill="none"> <svg aria-hidden="false" :width="props.size" :height="props.size" viewBox="0 0 12 12">
<path d="M8 24L40 24" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" /> <rect fill="currentColor" width="10" height="1" x="1" y="6"></rect>
</svg> </svg>
</template> </template>

View File

@ -1,27 +0,0 @@
<script setup>
const props = defineProps({
size: {
type: [Number, String],
default: 14,
},
})
</script>
<template>
<svg :width="props.size" :height="props.size" viewBox="0 0 48 48" fill="none">
<path
d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z"
fill="none"
stroke="currentColor"
stroke-width="4"
stroke-linejoin="round" />
</svg>
</template>
<style lang="scss" scoped></style>

View File

@ -5,7 +5,7 @@ import { NIcon, NSpace, NTag } from 'naive-ui'
import Key from '@/components/icons/Key.vue' import Key from '@/components/icons/Key.vue'
import Binary from '@/components/icons/Binary.vue' import Binary from '@/components/icons/Binary.vue'
import ToggleDb from '@/components/icons/ToggleDb.vue' import ToggleDb from '@/components/icons/ToggleDb.vue'
import { find, get, includes, indexOf, isEmpty, remove, size } from 'lodash' import { find, get, includes, indexOf, isEmpty, pull, remove, size } from 'lodash'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import Refresh from '@/components/icons/Refresh.vue' import Refresh from '@/components/icons/Refresh.vue'
import CopyLink from '@/components/icons/CopyLink.vue' import CopyLink from '@/components/icons/CopyLink.vue'
@ -16,6 +16,7 @@ import Connect from '@/components/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 ToggleServer from '@/components/icons/ToggleServer.vue'
import Unlink from '@/components/icons/Unlink.vue' import Unlink from '@/components/icons/Unlink.vue'
import Filter from '@/components/icons/Filter.vue' import Filter from '@/components/icons/Filter.vue'
import Close from '@/components/icons/Close.vue' import Close from '@/components/icons/Close.vue'
@ -347,15 +348,14 @@ const onUpdateSelectedKeys = (keys, options) => {
const renderPrefix = ({ option }) => { const renderPrefix = ({ option }) => {
switch (option.type) { switch (option.type) {
// case ConnectionType.Server: case ConnectionType.Server:
// const icon = option.cluster === true ? ToggleCluster : ToggleServer return h(
// return h( NIcon,
// NIcon, { size: 20 },
// { size: 20 }, {
// { default: () => h(ToggleServer, { modelValue: false }),
// default: () => h(icon, { modelValue: false }), },
// }, )
// )
case ConnectionType.RedisDB: case ConnectionType.RedisDB:
return h( return h(
NIcon, NIcon,

View File

@ -6,7 +6,6 @@ import { NIcon, NSpace, NText, useThemeVars } from 'naive-ui'
import { ConnectionType } from '@/consts/connection_type.js' import { ConnectionType } from '@/consts/connection_type.js'
import ToggleFolder from '@/components/icons/ToggleFolder.vue' import ToggleFolder from '@/components/icons/ToggleFolder.vue'
import ToggleServer from '@/components/icons/ToggleServer.vue' import ToggleServer from '@/components/icons/ToggleServer.vue'
import ToggleCluster from '@/components/icons/ToggleCluster.vue'
import { debounce, get, includes, indexOf, isEmpty, split } from 'lodash' import { debounce, get, includes, indexOf, isEmpty, split } from 'lodash'
import Config from '@/components/icons/Config.vue' import Config from '@/components/icons/Config.vue'
import Delete from '@/components/icons/Delete.vue' import Delete from '@/components/icons/Delete.vue'
@ -194,13 +193,12 @@ const renderPrefix = ({ option }) => {
case ConnectionType.Server: case ConnectionType.Server:
const connected = connectionStore.isConnected(option.name) const connected = connectionStore.isConnected(option.name)
const color = getServerMarkColor(option.name) const color = getServerMarkColor(option.name)
const icon = option.cluster === true ? ToggleCluster : ToggleServer
return h( return h(
NIcon, NIcon,
{ size: 20, color: !!!connected ? color : undefined }, { size: 20, color: !!!connected ? color : undefined },
{ {
default: () => default: () =>
h(icon, { h(ToggleServer, {
modelValue: !!connected, modelValue: !!connected,
fillColor: `rgba(220,66,60,${iconTransparency})`, fillColor: `rgba(220,66,60,${iconTransparency})`,
}), }),

View File

@ -93,7 +93,7 @@
}, },
"ribbon": { "ribbon": {
"server": "Server", "server": "Server",
"browser": "Data Browser", "browser": "Browser",
"log": "Log" "log": "Log"
}, },
"dialogue": { "dialogue": {
@ -147,16 +147,6 @@
"dbfilter_input_tip": "Press Enter to confirm", "dbfilter_input_tip": "Press Enter to confirm",
"mark_color": "Mark Color" "mark_color": "Mark Color"
}, },
"ssl": {
"title": "SSL/TLS",
"enable": "Enable SSL/TLS",
"cert_file": "Public Key",
"key_file": "Private Key",
"ca_file": "Authority",
"cert_file_tip":"Public Key File in PEM format(Cert)",
"key_file_tip": "Private Key File in PEM format(Key)",
"ca_file_tip": "Certificate Authority File in PEM format(CA)"
},
"ssh": { "ssh": {
"title": "SSH Tunnel", "title": "SSH Tunnel",
"enable": "Enable SSH Tuntel", "enable": "Enable SSH Tuntel",
@ -167,7 +157,8 @@
"usr_tip": "SSH Username", "usr_tip": "SSH Username",
"pwd_tip": "SSH Password", "pwd_tip": "SSH Password",
"pkfile_tip": "SSH Private Key File Path", "pkfile_tip": "SSH Private Key File Path",
"passphrase_tip": "(Optional) Passphrase for Private Key" "passphrase_tip": "(Optional) Passphrase for Private Key",
"pkfile_selection_title": "Please Select Private Key File"
}, },
"sentinel": { "sentinel": {
"title": "Sentinel", "title": "Sentinel",
@ -178,11 +169,6 @@
"username": "Username for Master Node", "username": "Username for Master Node",
"pwd_tip": "(Optional) Authentication username for master node", "pwd_tip": "(Optional) Authentication username for master node",
"usr_tip": "(Optional) Authentication password for master node (Redis > 6.0)" "usr_tip": "(Optional) Authentication password for master node (Redis > 6.0)"
},
"cluster": {
"title": "Cluster",
"enable": "Serve as Cluster Node",
"readonly": "Enables read-only commands on slave nodes"
} }
}, },
"group": { "group": {

View File

@ -93,7 +93,7 @@
}, },
"ribbon": { "ribbon": {
"server": "服务器", "server": "服务器",
"browser": "数据浏览", "browser": "浏览",
"log": "日志" "log": "日志"
}, },
"dialogue": { "dialogue": {
@ -147,16 +147,6 @@
"dbfilter_input_tip": "按回车确认", "dbfilter_input_tip": "按回车确认",
"mark_color": "标记颜色" "mark_color": "标记颜色"
}, },
"ssl": {
"title": "SSL/TLS",
"enable": "启用SSL",
"cert_file": "公钥文件",
"key_file": "私钥文件",
"ca_file": "授权文件",
"cert_file_tip":"PEM格式公钥文件(Cert)",
"key_file_tip": "PEM格式私钥文件(Key)",
"ca_file_tip": "PEM格式授权文件(CA)"
},
"ssh": { "ssh": {
"enable": "启用SSH隧道", "enable": "启用SSH隧道",
"title": "SSH隧道", "title": "SSH隧道",
@ -167,7 +157,8 @@
"usr_tip": "SSH登录用户名", "usr_tip": "SSH登录用户名",
"pwd_tip": "SSH登录密码", "pwd_tip": "SSH登录密码",
"pkfile_tip": "SSH私钥文件路径", "pkfile_tip": "SSH私钥文件路径",
"passphrase_tip": "(可选)SSH私钥密码" "passphrase_tip": "(可选)SSH私钥密码",
"pkfile_selection_title": "请选择私钥文件"
}, },
"sentinel": { "sentinel": {
"title": "哨兵模式", "title": "哨兵模式",
@ -178,10 +169,6 @@
"username": "主节点用户名", "username": "主节点用户名",
"pwd_tip": "(可选)主节点服务授权用户名", "pwd_tip": "(可选)主节点服务授权用户名",
"usr_tip": "(可选)主节点服务授权密码 (Redis > 6.0)" "usr_tip": "(可选)主节点服务授权密码 (Redis > 6.0)"
},
"cluster": {
"title": "集群模式",
"enable": "当前为集群节点"
} }
}, },
"group": { "group": {

View File

@ -59,7 +59,6 @@ const useConnectionStore = defineStore('connections', {
* @property {string} label display label * @property {string} label display label
* @property {string} name database name * @property {string} name database name
* @property {number} type * @property {number} type
* @property {boolean} cluster is cluster node
* @property {ConnectionItem[]} children * @property {ConnectionItem[]} children
*/ */
@ -147,7 +146,6 @@ const useConnectionStore = defineStore('connections', {
label: conn.name, label: conn.name,
name: conn.name, name: conn.name,
type: ConnectionType.Server, type: ConnectionType.Server,
cluster: get(conn, 'cluster.enable', false),
// isLeaf: false, // isLeaf: false,
}) })
profiles[conn.name] = { profiles[conn.name] = {
@ -167,7 +165,6 @@ const useConnectionStore = defineStore('connections', {
label: item.name, label: item.name,
name: item.name, name: item.name,
type: ConnectionType.Server, type: ConnectionType.Server,
cluster: get(item, 'cluster.enable', false),
// isLeaf: false, // isLeaf: false,
}) })
profiles[item.name] = { profiles[item.name] = {
@ -231,12 +228,6 @@ const useConnectionStore = defineStore('connections', {
dbFilterType: 'none', dbFilterType: 'none',
dbFilterList: [], dbFilterList: [],
markColor: '', markColor: '',
ssl: {
enable: false,
certFile: '',
keyFile: '',
caFile: '',
},
ssh: { ssh: {
enable: false, enable: false,
addr: '', addr: '',
@ -253,9 +244,6 @@ const useConnectionStore = defineStore('connections', {
username: '', username: '',
password: '', password: '',
}, },
cluster: {
enable: false,
},
} }
}, },
@ -636,7 +624,7 @@ const useConnectionStore = defineStore('connections', {
} }
// its danger to delete "non-exists" key, just remove from tree view // its danger to delete "non-exists" key, just remove from tree view
await this.deleteKey(server, db, key, true) await this.deleteKey(server, db, key, true)
// TODO: show key not found page or check exists on server first? // TODO: show key not found page?
} }
} }

15
main.go
View File

@ -10,6 +10,7 @@ import (
"github.com/wailsapp/wails/v2/pkg/options/linux" "github.com/wailsapp/wails/v2/pkg/options/linux"
"github.com/wailsapp/wails/v2/pkg/options/mac" "github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2/pkg/options/windows" "github.com/wailsapp/wails/v2/pkg/options/windows"
runtime2 "github.com/wailsapp/wails/v2/pkg/runtime"
"runtime" "runtime"
"tinyrdm/backend/consts" "tinyrdm/backend/consts"
"tinyrdm/backend/services" "tinyrdm/backend/services"
@ -25,7 +26,7 @@ var version = "0.0.0"
func main() { func main() {
// Create an instance of the app structure // Create an instance of the app structure
sysSvc := services.System() app := NewApp()
connSvc := services.Connection() connSvc := services.Connection()
prefSvc := services.Preferences() prefSvc := services.Preferences()
prefSvc.SetAppVersion(version) prefSvc.SetAppVersion(version)
@ -53,14 +54,22 @@ func main() {
}, },
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 0}, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 0},
OnStartup: func(ctx context.Context) { OnStartup: func(ctx context.Context) {
sysSvc.Start(ctx) app.startup(ctx)
connSvc.Start(ctx) connSvc.Start(ctx)
}, },
OnBeforeClose: func(ctx context.Context) (prevent bool) {
// save current window size
width, height := runtime2.WindowGetSize(ctx)
if width > 0 && height > 0 {
prefSvc.SaveWindowSize(width, height)
}
return false
},
OnShutdown: func(ctx context.Context) { OnShutdown: func(ctx context.Context) {
connSvc.Stop(ctx) connSvc.Stop(ctx)
}, },
Bind: []interface{}{ Bind: []interface{}{
sysSvc, app,
connSvc, connSvc,
prefSvc, prefSvc,
}, },