Compare commits
7 Commits
6996421cde
...
b95f293185
Author | SHA1 | Date |
---|---|---|
tiny-craft | b95f293185 | |
tiny-craft | 9b9d0e3c7c | |
tiny-craft | 67666f4edf | |
tiny-craft | 37e31b1636 | |
tiny-craft | 4c016b06de | |
tiny-craft | 4e38978711 | |
tiny-craft | 64895a98af |
|
@ -0,0 +1,43 @@
|
||||||
|
## Tiny RDM Contribute Guide
|
||||||
|
|
||||||
|
### Multi-language Contributions {#language}
|
||||||
|
|
||||||
|
#### Adding New Language
|
||||||
|
|
||||||
|
1. New file: Add a new JSON file in the [frontend/src/langs](../frontend/src/langs/), with the file naming format is "
|
||||||
|
{language}-{region}.json", e.g. English is "en-us.json", simplified Chinese is "zh-cn.json". Highly recommended to duplicate the [en-us.json](../frontend/src/langs/en-us.json) file and rename it.
|
||||||
|
2. Fill content: Refer to [en-us.json](../frontend/src/langs/en-us.json), or duplicate the file and modify the language content.
|
||||||
|
3. Update codes: Edit[frontend/src/langs/index.js](.../frontend/src/langs/index.js), import the new language data inside.
|
||||||
|
```javascript
|
||||||
|
import en from './en-us'
|
||||||
|
// import your new localize file 'zh-cn' here
|
||||||
|
import zh from './zh-cn'
|
||||||
|
|
||||||
|
export const lang = {
|
||||||
|
en,
|
||||||
|
// export new language data 'zh' here
|
||||||
|
zh,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
4. Submit review once there are no issues with the translation context in the application. (learn how to submit)
|
||||||
|
|
||||||
|
### Code Submission`(To be completed)` {#pull_request}
|
||||||
|
|
||||||
|
#### Pull Request Title
|
||||||
|
The format of PR's title like "<type>: <description>"
|
||||||
|
- type: PR type
|
||||||
|
- description: PR description
|
||||||
|
|
||||||
|
PR type list below:
|
||||||
|
|
||||||
|
| type | description |
|
||||||
|
|----------|----------------------------------------------------|
|
||||||
|
| revert | Revert a commit |
|
||||||
|
| feat | New features |
|
||||||
|
| perf | Performance improvements |
|
||||||
|
| fix | Fix any bugs |
|
||||||
|
| style | Style updates |
|
||||||
|
| docs | Document updates |
|
||||||
|
| refactor | Code refactors |
|
||||||
|
| chore | Some chores |
|
||||||
|
| ci | Automation process configuration or script updates |
|
|
@ -0,0 +1,42 @@
|
||||||
|
## Tiny RDM 代码贡献指南
|
||||||
|
|
||||||
|
### 多国语言贡献 {#language}
|
||||||
|
|
||||||
|
#### 增加新的语言
|
||||||
|
1. 创建文件:在[frontend/src/langs](../frontend/src/langs/)目录下新增语言配置JSON文件,文件名格式为“{语言}-{地区}.json”,如英文为“en-us.json”,简体中文为“zh-cn.json”,建议直接复制[en-us.json](../frontend/src/langs/en-us.json)文件进行改名。
|
||||||
|
2. 填充内容:参考[en-us.json](../frontend/src/langs/en-us.json),或者直接克隆一份文件,对语言部分内容进行修改。
|
||||||
|
3. 代码修改:在[frontend/src/langs/index.js](.../frontend/src/langs/index.js)文件内导入新增的语言数据
|
||||||
|
```javascript
|
||||||
|
import en from './en-us'
|
||||||
|
// import your new localize file 'zh-cn' here
|
||||||
|
import zh from './zh-cn'
|
||||||
|
|
||||||
|
export const lang = {
|
||||||
|
en,
|
||||||
|
// export new language data 'zh' here
|
||||||
|
zh,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
4. 检查应用中对应翻译语境无问题后,可提交审核([查看如何提交](#pull_request))
|
||||||
|
|
||||||
|
### 代码提交`(待完善)` {#pull_request}
|
||||||
|
|
||||||
|
#### PR提交规范
|
||||||
|
PR提交格式为“<type>: <description>”
|
||||||
|
- type: 提交类型
|
||||||
|
- description: 提交内容描述
|
||||||
|
|
||||||
|
其中提交类型如下:
|
||||||
|
|
||||||
|
| 提交类型 | 类型描述 |
|
||||||
|
|----------|--------------|
|
||||||
|
| revert | 回退某个commit提交 |
|
||||||
|
| feat | 新功能/新特性 |
|
||||||
|
| perf | 功能、体验等方面的优化 |
|
||||||
|
| fix | 修复问题 |
|
||||||
|
| style | 样式相关修改 |
|
||||||
|
| docs | 文档更新 |
|
||||||
|
| refactor | 代码重构 |
|
||||||
|
| chore | 杂项修改 |
|
||||||
|
| ci | 自动化流程配置或脚本修改 |
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: '[BUG]'
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
**Tiny RDM Version**
|
||||||
|
What version of Tiny RDM are you using?
|
||||||
|
|
||||||
|
**OS Version**
|
||||||
|
Which OS and version you launch? (Mac/Windows/Linux)
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
Steps to Reproduce:
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: '[FEATURE]'
|
||||||
|
---
|
27
README.md
|
@ -13,20 +13,29 @@
|
||||||
<strong>Tiny RDM is a modern lightweight cross-platform Redis desktop manager available for Mac, Windows, and Linux.</strong>
|
<strong>Tiny RDM is a modern lightweight cross-platform Redis desktop manager available for Mac, Windows, and Linux.</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
![](screenshots/light_en.png)
|
![](screenshots/dark_en.png)
|
||||||
|
|
||||||
## Feature
|
## Feature
|
||||||
|
|
||||||
* Built on Webview, no embedded browsers (Thanks to [Wails](https://github.com/wailsapp/wails)).
|
* Super lightweight, built on Webview2, without embedded browsers (Thanks to [Wails](https://github.com/wailsapp/wails)).
|
||||||
* More elegant UI and visualized layout (Thanks to [Naive UI](https://github.com/tusen-ai/naive-ui)
|
* More elegant UI, frameless, offering light and dark themes (Thanks to [Naive UI](https://github.com/tusen-ai/naive-ui)
|
||||||
and [IconPark](https://iconpark.oceanengine.com)).
|
and [IconPark](https://iconpark.oceanengine.com)).
|
||||||
* Multi-language support (Click here to contribute and support more languages).
|
* Multi-language support ([Need more languages ? Click here to contribute](.github/CONTRIBUTING.md)).
|
||||||
* Convenient data viewing and editing.
|
* Better connection management: supports SSH Tunnel/SSL/Sentinel Mode/Cluster Mode.
|
||||||
* More features under continuous development...
|
* Visualize key value operations, CRUD support for Lists, Hashes, Strings, Sets, Sorted Sets, and Streams.
|
||||||
|
* Support multiple data viewing format and decode/decompression methods.
|
||||||
|
* Operation command execution logs.
|
||||||
|
* Provides command-line operations.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
- [ ] Pagination and querying for List/Hash/Set/Sorted Set
|
||||||
|
- [ ] Decode/decompression display for value of List/Hash/Set/Sorted Set
|
||||||
|
- [ ] Slow logs
|
||||||
|
- [ ] Real-time commands monitoring
|
||||||
|
- [ ] Pub/Sub operations
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
We publish binaries for Mac, Windows, and Linux.
|
|
||||||
Available to download for free from [here](https://github.com/tiny-craft/tiny-rdm/releases).
|
Available to download for free from [here](https://github.com/tiny-craft/tiny-rdm/releases).
|
||||||
|
|
||||||
> If you can't open it after installation on macOS, exec the following command then reopen:
|
> If you can't open it after installation on macOS, exec the following command then reopen:
|
||||||
|
@ -34,9 +43,9 @@ Available to download for free from [here](https://github.com/tiny-craft/tiny-rd
|
||||||
> sudo xattr -d com.apple.quarantine /Applications/Tiny\ RDM.app
|
> sudo xattr -d com.apple.quarantine /Applications/Tiny\ RDM.app
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
## Build
|
## Build Guidelines
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
* Go >= 1.21
|
* Go (latest version)
|
||||||
* Node.js >= 16
|
* Node.js >= 16
|
||||||
* NPM >= 9
|
* NPM >= 9
|
||||||
|
|
||||||
|
|
26
README_zh.md
|
@ -13,20 +13,30 @@
|
||||||
<strong>一个现代化轻量级的跨平台Redis桌面客户端,支持Mac、Windows和Linux</strong>
|
<strong>一个现代化轻量级的跨平台Redis桌面客户端,支持Mac、Windows和Linux</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
![screenshot](screenshots/light_zh.png)
|
![screenshot](screenshots/dark_zh.png)
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
* 基于Webview,无内嵌浏览器(感谢[Wails](https://github.com/wailsapp/wails))
|
* 极度轻量,基于Webview2,无内嵌浏览器(感谢[Wails](https://github.com/wailsapp/wails))
|
||||||
* 更精美的界面和直观的结构布局(感谢[Naive UI](https://github.com/tusen-ai/naive-ui)
|
* 更精美的界面,无边框窗口,提供浅色/深色主题(感谢[Naive UI](https://github.com/tusen-ai/naive-ui)
|
||||||
和 [IconPark](https://iconpark.oceanengine.com))
|
和 [IconPark](https://iconpark.oceanengine.com))
|
||||||
* 多国语言支持(点我贡献和完善多国语言支持)
|
* 多国语言支持:英文/中文([需要更多语言支持?点我贡献语言](.github/CONTRIBUTING_zh.md))
|
||||||
* 便捷的数据查看和编辑修改
|
* 更好用的连接管理:支持SSH隧道/SSL/哨兵模式/集群模式
|
||||||
* 更多功能持续开发中…
|
* 可视化键值操作,增删查改一应俱全
|
||||||
|
* 支持多种数据查看格式以及转码/解压方式
|
||||||
|
* 操作命令执行日志展示
|
||||||
|
* 提供命令行操作
|
||||||
|
|
||||||
|
## 未来版本规划
|
||||||
|
- [ ] List/Hash/Set/Sorted Set的分页展示和查询
|
||||||
|
- [ ] List/Hash/Set/Sorted Set值的转码显示
|
||||||
|
- [ ] 慢日志展示
|
||||||
|
- [ ] 命令实时监控
|
||||||
|
- [ ] 发布/订阅支持
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
提供Mac、Windows和Linux下载安装,可[免费下载](https://github.com/tiny-craft/tiny-rdm/releases)。
|
提供Mac、Windows和Linux安装包,可[免费下载](https://github.com/tiny-craft/tiny-rdm/releases)。
|
||||||
|
|
||||||
> 如果在macOS上安装后无法打开,报错**不受信任**或者**移到垃圾箱**,执行下面命令后再启动即可:
|
> 如果在macOS上安装后无法打开,报错**不受信任**或者**移到垃圾箱**,执行下面命令后再启动即可:
|
||||||
> ``` shell
|
> ``` shell
|
||||||
|
@ -35,7 +45,7 @@
|
||||||
|
|
||||||
## 构建项目
|
## 构建项目
|
||||||
### 运行环境要求
|
### 运行环境要求
|
||||||
* Go >= 1.21
|
* Go(最新版本)
|
||||||
* Node.js >= 16
|
* Node.js >= 16
|
||||||
* NPM >= 9
|
* NPM >= 9
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -33,6 +34,14 @@ type cmdHistoryItem struct {
|
||||||
Cost int64 `json:"cost"`
|
Cost int64 `json:"cost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type slowLogItem struct {
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Client string `json:"client"`
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
Cmd string `json:"cmd"`
|
||||||
|
Cost int64 `json:"cost"`
|
||||||
|
}
|
||||||
|
|
||||||
type connectionService struct {
|
type connectionService struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
conns *ConnectionsStorage
|
conns *ConnectionsStorage
|
||||||
|
@ -1521,6 +1530,45 @@ func (c *connectionService) DeleteKey(connName string, db int, k any, async bool
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlushDB flush database
|
||||||
|
func (c *connectionService) FlushDB(connName string, db int, async bool) (resp types.JSResp) {
|
||||||
|
item, err := c.getRedisClient(connName, db)
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
flush := func(ctx context.Context, cli redis.UniversalClient) {
|
||||||
|
cli.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
|
||||||
|
pipe.Select(ctx, db)
|
||||||
|
if async {
|
||||||
|
pipe.FlushDBAsync(ctx)
|
||||||
|
} else {
|
||||||
|
pipe.FlushDB(ctx)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
client, ctx := item.client, item.ctx
|
||||||
|
if cluster, ok := client.(*redis.ClusterClient); ok {
|
||||||
|
// cluster mode
|
||||||
|
err = cluster.ForEachMaster(ctx, func(ctx context.Context, cli *redis.Client) error {
|
||||||
|
flush(ctx, cli)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
flush(ctx, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Success = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// RenameKey rename key
|
// RenameKey rename key
|
||||||
func (c *connectionService) RenameKey(connName string, db int, key, newKey string) (resp types.JSResp) {
|
func (c *connectionService) RenameKey(connName string, db int, key, newKey string) (resp types.JSResp) {
|
||||||
item, err := c.getRedisClient(connName, db)
|
item, err := c.getRedisClient(connName, db)
|
||||||
|
@ -1574,6 +1622,59 @@ func (c *connectionService) CleanCmdHistory() (resp types.JSResp) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSlowLogs get slow log list
|
||||||
|
func (c *connectionService) GetSlowLogs(connName string, db int, num int64) (resp types.JSResp) {
|
||||||
|
item, err := c.getRedisClient(connName, db)
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, ctx := item.client, item.ctx
|
||||||
|
var logs []redis.SlowLog
|
||||||
|
if cluster, ok := client.(*redis.ClusterClient); ok {
|
||||||
|
// cluster mode
|
||||||
|
var mu sync.Mutex
|
||||||
|
err = cluster.ForEachShard(ctx, func(ctx context.Context, cli *redis.Client) error {
|
||||||
|
if subLogs, _ := client.SlowLogGet(ctx, num).Result(); len(subLogs) > 0 {
|
||||||
|
mu.Lock()
|
||||||
|
logs = append(logs, subLogs...)
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
logs, err = client.SlowLogGet(ctx, num).Result()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(logs, func(i, j int) bool {
|
||||||
|
return logs[i].Time.UnixMilli() > logs[j].Time.UnixMilli()
|
||||||
|
})
|
||||||
|
if len(logs) > int(num) {
|
||||||
|
logs = logs[:num]
|
||||||
|
}
|
||||||
|
|
||||||
|
list := sliceutil.Map(logs, func(i int) slowLogItem {
|
||||||
|
return slowLogItem{
|
||||||
|
Timestamp: logs[i].Time.UnixMilli(),
|
||||||
|
Client: logs[i].ClientName,
|
||||||
|
Addr: logs[i].ClientAddr,
|
||||||
|
Cmd: sliceutil.JoinString(logs[i].Args, " "),
|
||||||
|
Cost: logs[i].Duration.Milliseconds(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resp.Success = true
|
||||||
|
resp.Data = map[string]any{
|
||||||
|
"list": list,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// update or insert key info to database
|
// update or insert key info to database
|
||||||
//func (c *connectionService) updateDBKey(connName string, db int, keys []string, separator string) {
|
//func (c *connectionService) updateDBKey(connName string, db int, keys []string, separator string) {
|
||||||
// dbStruct := map[string]any{}
|
// dbStruct := map[string]any{}
|
||||||
|
|
|
@ -99,7 +99,7 @@ func (l *LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.Proc
|
||||||
err := next(ctx, cmds)
|
err := next(ctx, cmds)
|
||||||
cost := time.Since(t).Milliseconds()
|
cost := time.Since(t).Milliseconds()
|
||||||
b := make([]byte, 0, 64)
|
b := make([]byte, 0, 64)
|
||||||
for _, cmd := range cmds {
|
for i, cmd := range cmds {
|
||||||
log.Println("pipeline: ", cmd)
|
log.Println("pipeline: ", cmd)
|
||||||
if l.cmdExec != nil {
|
if l.cmdExec != nil {
|
||||||
for i, arg := range cmd.Args() {
|
for i, arg := range cmd.Args() {
|
||||||
|
@ -108,9 +108,11 @@ func (l *LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.Proc
|
||||||
}
|
}
|
||||||
b = appendArg(b, arg)
|
b = appendArg(b, arg)
|
||||||
}
|
}
|
||||||
|
if i != len(cmds) {
|
||||||
b = append(b, '\n')
|
b = append(b, '\n')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if l.cmdExec != nil {
|
if l.cmdExec != nil {
|
||||||
l.cmdExec(string(b), cost)
|
l.cmdExec(string(b), cost)
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,10 +117,11 @@ func autoDecode(str string) (value, resultDecode string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if value, ok = decodeDeflate(str); ok {
|
// FIXME: skip decompress with deflate due to incorrect format checking
|
||||||
resultDecode = types.DECODE_DEFLATE
|
//if value, ok = decodeDeflate(str); ok {
|
||||||
return
|
// resultDecode = types.DECODE_DEFLATE
|
||||||
}
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
if value, ok = decodeZStd(str); ok {
|
if value, ok = decodeZStd(str); ok {
|
||||||
resultDecode = types.DECODE_ZSTD
|
resultDecode = types.DECODE_ZSTD
|
||||||
|
@ -202,6 +203,7 @@ func autoViewAs(str string) (value, resultFormat string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeJson(str string) (string, bool) {
|
func decodeJson(str string) (string, bool) {
|
||||||
|
str = strings.TrimSpace(str)
|
||||||
if (strings.HasPrefix(str, "{") && strings.HasSuffix(str, "}")) ||
|
if (strings.HasPrefix(str, "{") && strings.HasSuffix(str, "}")) ||
|
||||||
(strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]")) {
|
(strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]")) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
|
@ -350,15 +352,12 @@ func SaveAs(str, viewType, decodeType string) (value string, err error) {
|
||||||
|
|
||||||
func encodeJson(str string) (string, bool) {
|
func encodeJson(str string) (string, bool) {
|
||||||
var data any
|
var data any
|
||||||
if (strings.HasPrefix(str, "{") && strings.HasSuffix(str, "}")) ||
|
|
||||||
(strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]")) {
|
|
||||||
if err := json.Unmarshal([]byte(str), &data); err == nil {
|
if err := json.Unmarshal([]byte(str), &data); err == nil {
|
||||||
var jsonByte []byte
|
var jsonByte []byte
|
||||||
if jsonByte, err = json.Marshal(data); err == nil {
|
if jsonByte, err = json.Marshal(data); err == nil {
|
||||||
return string(jsonByte), true
|
return string(jsonByte), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return str, false
|
return str, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
# Vue 3 + Vite
|
# Frontend of Tiny RDM
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs,
|
Use Vue3 + Vite
|
||||||
check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
|
||||||
|
|
||||||
## Recommended IDE Setup
|
|
||||||
|
|
||||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import KeyFilterDialog from './components/dialogs/KeyFilterDialog.vue'
|
||||||
import { WindowSetDarkTheme, WindowSetLightTheme } from 'wailsjs/runtime/runtime.js'
|
import { WindowSetDarkTheme, WindowSetLightTheme } from 'wailsjs/runtime/runtime.js'
|
||||||
import { darkThemeOverrides, themeOverrides } from '@/utils/theme.js'
|
import { darkThemeOverrides, themeOverrides } from '@/utils/theme.js'
|
||||||
import AboutDialog from '@/components/dialogs/AboutDialog.vue'
|
import AboutDialog from '@/components/dialogs/AboutDialog.vue'
|
||||||
|
import FlushDbDialog from '@/components/dialogs/FlushDbDialog.vue'
|
||||||
|
|
||||||
hljs.registerLanguage('json', json)
|
hljs.registerLanguage('json', json)
|
||||||
hljs.registerLanguage('plaintext', plaintext)
|
hljs.registerLanguage('plaintext', plaintext)
|
||||||
|
@ -73,6 +74,7 @@ watch(
|
||||||
<add-fields-dialog />
|
<add-fields-dialog />
|
||||||
<rename-key-dialog />
|
<rename-key-dialog />
|
||||||
<delete-key-dialog />
|
<delete-key-dialog />
|
||||||
|
<flush-db-dialog />
|
||||||
<set-ttl-dialog />
|
<set-ttl-dialog />
|
||||||
<preferences-dialog />
|
<preferences-dialog />
|
||||||
<about-dialog />
|
<about-dialog />
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, reactive, ref } from 'vue'
|
import { computed, h, nextTick, reactive, ref } from 'vue'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
import Refresh from '@/components/icons/Refresh.vue'
|
import Refresh from '@/components/icons/Refresh.vue'
|
||||||
import useConnectionStore from 'stores/connections.js'
|
import useConnectionStore from 'stores/connections.js'
|
||||||
import { map, uniqBy } from 'lodash'
|
import { map, size, split, uniqBy } from 'lodash'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import Delete from '@/components/icons/Delete.vue'
|
import Delete from '@/components/icons/Delete.vue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
@ -74,7 +74,7 @@ defineExpose({
|
||||||
<n-card
|
<n-card
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:theme-overrides="{ borderRadius: '0px' }"
|
:theme-overrides="{ borderRadius: '0px' }"
|
||||||
:title="$t('log.launch_log')"
|
:title="$t('log.title')"
|
||||||
class="content-container flex-box-v"
|
class="content-container flex-box-v"
|
||||||
content-style="display: flex;flex-direction: column; overflow: hidden; backgroundColor: gray">
|
content-style="display: flex;flex-direction: column; overflow: hidden; backgroundColor: gray">
|
||||||
<n-form :disabled="data.loading" class="flex-item" inline>
|
<n-form :disabled="data.loading" class="flex-item" inline>
|
||||||
|
@ -132,6 +132,17 @@ defineExpose({
|
||||||
filter(value, row) {
|
filter(value, row) {
|
||||||
return value === '' || !!~row.cmd.indexOf(value.toString())
|
return value === '' || !!~row.cmd.indexOf(value.toString())
|
||||||
},
|
},
|
||||||
|
render({ cmd }, index) {
|
||||||
|
const cmdList = split(cmd, '\n')
|
||||||
|
if (size(cmdList) > 1) {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
null,
|
||||||
|
map(cmdList, (c) => h('div', null, c)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: $t('log.cost_time'),
|
title: $t('log.cost_time'),
|
||||||
|
|
|
@ -13,6 +13,7 @@ import ContentValueWrapper from '@/components/content_value/ContentValueWrapper.
|
||||||
import ContentCli from '@/components/content_value/ContentCli.vue'
|
import ContentCli from '@/components/content_value/ContentCli.vue'
|
||||||
import Monitor from '@/components/icons/Monitor.vue'
|
import Monitor from '@/components/icons/Monitor.vue'
|
||||||
import Pub from '@/components/icons/Pub.vue'
|
import Pub from '@/components/icons/Pub.vue'
|
||||||
|
import ContentSlog from '@/components/content_value/ContentSlog.vue'
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
|
@ -164,7 +165,7 @@ watch(
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- slow log pane -->
|
<!-- slow log pane -->
|
||||||
<n-tab-pane :disabled="true" :name="BrowserTabType.SlowLog.toString()">
|
<n-tab-pane :name="BrowserTabType.SlowLog.toString()" display-directive="if">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
||||||
<n-icon size="16">
|
<n-icon size="16">
|
||||||
|
@ -176,10 +177,11 @@ watch(
|
||||||
<span>{{ $t('interface.sub_tab.slow_log') }}</span>
|
<span>{{ $t('interface.sub_tab.slow_log') }}</span>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
|
<content-slog :db="tabContent.db" :server="props.server" />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- command monitor pane -->
|
<!-- command monitor pane -->
|
||||||
<n-tab-pane :disabled="true" :name="BrowserTabType.CmdMonitor.toString()">
|
<n-tab-pane :disabled="true" :name="BrowserTabType.CmdMonitor.toString()" display-directive="if">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
||||||
<n-icon size="16">
|
<n-icon size="16">
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
<script setup>
|
||||||
|
import { h, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||||
|
import Refresh from '@/components/icons/Refresh.vue'
|
||||||
|
import useConnectionStore from 'stores/connections.js'
|
||||||
|
import { debounce, isEmpty, map, size, split } from 'lodash'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { useThemeVars } from 'naive-ui'
|
||||||
|
|
||||||
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
|
const connectionStore = useConnectionStore()
|
||||||
|
const i18n = useI18n()
|
||||||
|
const props = defineProps({
|
||||||
|
server: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
db: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
list: [],
|
||||||
|
sortOrder: 'descend',
|
||||||
|
listLimit: 20,
|
||||||
|
loading: false,
|
||||||
|
autoLoading: false,
|
||||||
|
client: '',
|
||||||
|
keyword: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableRef = ref(null)
|
||||||
|
|
||||||
|
const _loadSlowLog = () => {
|
||||||
|
data.loading = true
|
||||||
|
connectionStore
|
||||||
|
.getSlowLog(props.server, props.db, data.listLimit)
|
||||||
|
.then((list) => {
|
||||||
|
data.list = list || []
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
data.loading = false
|
||||||
|
tableRef.value?.scrollTo({ top: data.sortOrder === 'ascend' ? 999999 : 0 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const loadSlowLog = debounce(_loadSlowLog, 1000, { leading: true, trailing: true })
|
||||||
|
|
||||||
|
let intervalID
|
||||||
|
onMounted(() => {
|
||||||
|
loadSlowLog()
|
||||||
|
intervalID = setInterval(() => {
|
||||||
|
if (data.autoLoading === true) {
|
||||||
|
loadSlowLog()
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(intervalID)
|
||||||
|
})
|
||||||
|
|
||||||
|
const onListLimitChanged = (limit) => {
|
||||||
|
loadSlowLog()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-card
|
||||||
|
:bordered="false"
|
||||||
|
:theme-overrides="{ borderRadius: '0px' }"
|
||||||
|
:title="$t('slog.title')"
|
||||||
|
class="content-container flex-box-v"
|
||||||
|
content-style="display: flex;flex-direction: column; overflow: hidden; backgroundColor: gray">
|
||||||
|
<n-form :disabled="data.loading" class="flex-item" inline>
|
||||||
|
<n-form-item :label="$t('slog.limit')">
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="data.listLimit"
|
||||||
|
:max="9999"
|
||||||
|
style="width: 120px"
|
||||||
|
@update:value="onListLimitChanged" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('slog.auto_refresh')">
|
||||||
|
<n-switch v-model:value="data.autoLoading" :loading="data.loading" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label=" ">
|
||||||
|
<n-tooltip>
|
||||||
|
{{ $t('slog.refresh') }}
|
||||||
|
<template #trigger>
|
||||||
|
<n-button :loading="data.loading" circle size="small" tertiary @click="_loadSlowLog">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="Refresh" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-tooltip>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('slog.filter')">
|
||||||
|
<n-input v-model:value="data.keyword" clearable placeholder="" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<div class="content-value fill-height flex-box-h">
|
||||||
|
<n-data-table
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="[
|
||||||
|
{
|
||||||
|
title: $t('slog.exec_time'),
|
||||||
|
key: 'timestamp',
|
||||||
|
sortOrder: data.sortOrder,
|
||||||
|
sorter: 'default',
|
||||||
|
width: 180,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
render({ timestamp }, index) {
|
||||||
|
return dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: $t('slog.client'),
|
||||||
|
key: 'client',
|
||||||
|
filterOptionValue: data.client,
|
||||||
|
resizable: true,
|
||||||
|
filter(value, row) {
|
||||||
|
return value === '' || row.client === value.toString() || row.addr === value.toString()
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
ellipsis: true,
|
||||||
|
render({ client, addr }, index) {
|
||||||
|
let content = ''
|
||||||
|
if (!isEmpty(client)) {
|
||||||
|
content += client
|
||||||
|
}
|
||||||
|
if (!isEmpty(addr)) {
|
||||||
|
if (!isEmpty(content)) {
|
||||||
|
content += ' - '
|
||||||
|
}
|
||||||
|
content += addr
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: $t('slog.cmd'),
|
||||||
|
key: 'cmd',
|
||||||
|
titleAlign: 'center',
|
||||||
|
filterOptionValue: data.keyword,
|
||||||
|
resizable: true,
|
||||||
|
width: 100,
|
||||||
|
filter(value, row) {
|
||||||
|
return value === '' || !!~row.cmd.indexOf(value.toString())
|
||||||
|
},
|
||||||
|
render({ cmd }, index) {
|
||||||
|
const cmdList = split(cmd, '\n')
|
||||||
|
if (size(cmdList) > 1) {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
null,
|
||||||
|
map(cmdList, (c) => h('div', null, c)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: $t('slog.cost_time'),
|
||||||
|
key: 'cost',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
render({ cost }, index) {
|
||||||
|
const ms = dayjs.duration(cost).asMilliseconds()
|
||||||
|
if (ms < 1000) {
|
||||||
|
return `${ms} ms`
|
||||||
|
} else {
|
||||||
|
return `${Math.floor(ms / 1000)} s`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@update:sorter="({ order }) => (data.sortOrder = order)"
|
||||||
|
:data="data.list"
|
||||||
|
class="flex-item-expand"
|
||||||
|
flex-height />
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/styles/content';
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
padding: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -91,4 +91,18 @@ const onDropdownShow = (show) => {
|
||||||
</n-dropdown>
|
</n-dropdown>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss">
|
||||||
|
.type-selector-header {
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-selector-item {
|
||||||
|
min-width: 100px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
<script setup>
|
||||||
|
import { reactive, watch } from 'vue'
|
||||||
|
import useDialog from 'stores/dialog'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import useConnectionStore from 'stores/connections.js'
|
||||||
|
|
||||||
|
const flushForm = reactive({
|
||||||
|
server: '',
|
||||||
|
db: 0,
|
||||||
|
key: '',
|
||||||
|
async: false,
|
||||||
|
confirm: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogStore = useDialog()
|
||||||
|
const connectionStore = useConnectionStore()
|
||||||
|
watch(
|
||||||
|
() => dialogStore.flushDBDialogVisible,
|
||||||
|
(visible) => {
|
||||||
|
if (visible) {
|
||||||
|
const { server, db } = dialogStore.flushDBParam
|
||||||
|
flushForm.server = server
|
||||||
|
flushForm.db = db
|
||||||
|
flushForm.async = true
|
||||||
|
flushForm.confirm = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const i18n = useI18n()
|
||||||
|
const onConfirmFlush = async () => {
|
||||||
|
try {
|
||||||
|
const { server, db, async } = flushForm
|
||||||
|
const success = await connectionStore.flushDatabase(server, db, async)
|
||||||
|
if (success) {
|
||||||
|
$message.success(i18n.t('dialogue.handle_succ'))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$message.error(e.message)
|
||||||
|
}
|
||||||
|
dialogStore.closeFlushDBDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
dialogStore.closeFlushDBDialog()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
v-model:show="dialogStore.flushDBDialogVisible"
|
||||||
|
:closable="false"
|
||||||
|
:close-on-esc="false"
|
||||||
|
:mask-closable="false"
|
||||||
|
:show-icon="false"
|
||||||
|
:title="$t('interface.flush_db')"
|
||||||
|
preset="dialog"
|
||||||
|
transform-origin="center">
|
||||||
|
<n-form :model="flushForm" :show-require-mark="false" label-placement="top">
|
||||||
|
<n-form-item :label="$t('dialogue.key.server')">
|
||||||
|
<n-input :value="flushForm.server" readonly />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('dialogue.key.db_index')">
|
||||||
|
<n-input :value="flushForm.db.toString()" readonly />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('dialogue.key.async_delete')" required>
|
||||||
|
<n-checkbox v-model:checked="flushForm.async">{{ $t('dialogue.key.async_delete_title') }}</n-checkbox>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('common.warning')" required>
|
||||||
|
<n-checkbox v-model:checked="flushForm.confirm">
|
||||||
|
<span style="color: red; font-weight: bold">{{ $t('dialogue.key.confirm_flush') }}</span>
|
||||||
|
</n-checkbox>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
|
||||||
|
<template #action>
|
||||||
|
<n-button :focusable="false" @click="onClose">{{ $t('common.cancel') }}</n-button>
|
||||||
|
<n-button :disabled="!!!flushForm.confirm" :focusable="false" type="primary" @click="onConfirmFlush">
|
||||||
|
{{ $t('dialogue.key.confirm_flush_db') }}
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -10,7 +10,7 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
strokeColor: {
|
strokeColor: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '#FFF',
|
default: 'currentColor',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -117,8 +117,8 @@ const menuOptions = {
|
||||||
key: 'd1',
|
key: 'd1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'key_remove',
|
key: 'db_flush',
|
||||||
label: i18n.t('interface.batch_delete'),
|
label: i18n.t('interface.flush_db'),
|
||||||
icon: renderIcon(Delete),
|
icon: renderIcon(Delete),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -163,7 +163,7 @@ const menuOptions = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'key_remove',
|
key: 'key_remove',
|
||||||
label: i18n.t('interface.batch_delete'),
|
label: i18n.t('interface.batch_delete_key'),
|
||||||
icon: renderIcon(Delete),
|
icon: renderIcon(Delete),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -260,7 +260,7 @@ const handleSelectContextMenu = (key) => {
|
||||||
connectionStore.closeDatabase(props.server, db)
|
connectionStore.closeDatabase(props.server, db)
|
||||||
break
|
break
|
||||||
case 'db_flush':
|
case 'db_flush':
|
||||||
dialogStore.openDeleteKeyDialog(props.server, db, '*')
|
dialogStore.openFlushDBDialog(props.server, db)
|
||||||
break
|
break
|
||||||
case 'db_newkey':
|
case 'db_newkey':
|
||||||
case 'key_newkey':
|
case 'key_newkey':
|
||||||
|
@ -548,7 +548,7 @@ const getDatabaseMenu = (opened, loading, end) => {
|
||||||
onClick: () => handleSelectContextMenu('db_loadall'),
|
onClick: () => handleSelectContextMenu('db_loadall'),
|
||||||
}),
|
}),
|
||||||
h(IconButton, {
|
h(IconButton, {
|
||||||
tTooltip: 'interface.batch_delete',
|
tTooltip: 'interface.flush_db',
|
||||||
icon: Delete,
|
icon: Delete,
|
||||||
disabled: loading === true,
|
disabled: loading === true,
|
||||||
onClick: () => handleSelectContextMenu('db_flush'),
|
onClick: () => handleSelectContextMenu('db_flush'),
|
||||||
|
@ -585,7 +585,7 @@ const getLayerMenu = () => {
|
||||||
onClick: () => handleSelectContextMenu('key_newkey'),
|
onClick: () => handleSelectContextMenu('key_newkey'),
|
||||||
}),
|
}),
|
||||||
h(IconButton, {
|
h(IconButton, {
|
||||||
tTooltip: 'interface.batch_delete',
|
tTooltip: 'interface.batch_delete_key',
|
||||||
icon: Delete,
|
icon: Delete,
|
||||||
onClick: () => handleSelectContextMenu('key_remove'),
|
onClick: () => handleSelectContextMenu('key_remove'),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
"rename_key": "Rename Key",
|
"rename_key": "Rename Key",
|
||||||
"delete_key": "Delete Key",
|
"delete_key": "Delete Key",
|
||||||
"batch_delete_key": "Batch Delete Keys",
|
"batch_delete_key": "Batch Delete Keys",
|
||||||
|
"flush_db": "Flush Database",
|
||||||
"copy_value": "Copy Value",
|
"copy_value": "Copy Value",
|
||||||
"edit_value": "Edit Value",
|
"edit_value": "Edit Value",
|
||||||
"save_update": "Save Update",
|
"save_update": "Save Update",
|
||||||
|
@ -82,7 +83,6 @@
|
||||||
"decode_with": "Decode / Decompression",
|
"decode_with": "Decode / Decompression",
|
||||||
"reload": "Reload",
|
"reload": "Reload",
|
||||||
"open_connection": "Open Connection",
|
"open_connection": "Open Connection",
|
||||||
"batch_delete": "Batch Delete",
|
|
||||||
"copy_path": "Copy Path",
|
"copy_path": "Copy Path",
|
||||||
"copy_key": "Copy Key",
|
"copy_key": "Copy Key",
|
||||||
"binary_key": "Binary Key Name",
|
"binary_key": "Binary Key Name",
|
||||||
|
@ -214,14 +214,16 @@
|
||||||
"new": "New Key",
|
"new": "New Key",
|
||||||
"new_name": "New Key Name",
|
"new_name": "New Key Name",
|
||||||
"persist_key": "Persist Key",
|
"persist_key": "Persist Key",
|
||||||
"server": "Belong",
|
"server": "Connection",
|
||||||
"db_index": "Database Index",
|
"db_index": "Database Index",
|
||||||
"key_expression": "Key Expression",
|
"key_expression": "Key Expression",
|
||||||
"affected_key": "Affected Keys",
|
"affected_key": "Affected Keys",
|
||||||
"show_affected_key": "Show Affected Keys",
|
"show_affected_key": "Show Affected Keys",
|
||||||
"confirm_delete_key": "Confirm Delete {num} Key(s)",
|
"confirm_delete_key": "Confirm Delete {num} Key(s)",
|
||||||
"async_delete": "Asynchronously Execute",
|
"async_delete": "Asynchronously Execute",
|
||||||
"async_delete_title": "Do not waiting for the operation's result"
|
"async_delete_title": "Do not waiting for the operation's result",
|
||||||
|
"confirm_flush": "I know what I'm doing!",
|
||||||
|
"confirm_flush_db": "Confirm Flush Database"
|
||||||
},
|
},
|
||||||
"field": {
|
"field": {
|
||||||
"new": "Add New Field",
|
"new": "Add New Field",
|
||||||
|
@ -272,7 +274,7 @@
|
||||||
"about": "About"
|
"about": "About"
|
||||||
},
|
},
|
||||||
"log": {
|
"log": {
|
||||||
"launch_log": "Launch Log",
|
"title": "Launch Log",
|
||||||
"filter_server": "Filter Server",
|
"filter_server": "Filter Server",
|
||||||
"filter_keyword": "Filter Keyword",
|
"filter_keyword": "Filter Keyword",
|
||||||
"clean_log": "Clean Launch Log",
|
"clean_log": "Clean Launch Log",
|
||||||
|
@ -291,5 +293,16 @@
|
||||||
"all_info": "Information",
|
"all_info": "Information",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"auto_refresh": "Auto Refresh"
|
"auto_refresh": "Auto Refresh"
|
||||||
|
},
|
||||||
|
"slog": {
|
||||||
|
"title": "Slow Log",
|
||||||
|
"limit": "Limit",
|
||||||
|
"filter": "Filter",
|
||||||
|
"exec_time": "Time",
|
||||||
|
"client": "Client",
|
||||||
|
"cmd": "Command",
|
||||||
|
"cost_time": "Cost",
|
||||||
|
"refresh": "Refresh Now",
|
||||||
|
"auto_refresh": "Auto Refresh"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import en from './en'
|
import en from './en-us'
|
||||||
import zh from './zh-cn'
|
import zh from './zh-cn'
|
||||||
|
|
||||||
export const lang = {
|
export const lang = {
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
"rename_key": "重命名键",
|
"rename_key": "重命名键",
|
||||||
"delete_key": "删除键",
|
"delete_key": "删除键",
|
||||||
"batch_delete_key": "批量删除键",
|
"batch_delete_key": "批量删除键",
|
||||||
|
"flush_db": "清空数据库",
|
||||||
"copy_value": "复制值",
|
"copy_value": "复制值",
|
||||||
"edit_value": "修改值",
|
"edit_value": "修改值",
|
||||||
"save_update": "保存修改",
|
"save_update": "保存修改",
|
||||||
|
@ -82,7 +83,6 @@
|
||||||
"decode_with": "解码/解压方式",
|
"decode_with": "解码/解压方式",
|
||||||
"reload": "重新载入",
|
"reload": "重新载入",
|
||||||
"open_connection": "打开连接",
|
"open_connection": "打开连接",
|
||||||
"batch_delete": "批量删除键",
|
|
||||||
"copy_path": "复制路径",
|
"copy_path": "复制路径",
|
||||||
"copy_key": "复制键名",
|
"copy_key": "复制键名",
|
||||||
"binary_key": "二进制键名",
|
"binary_key": "二进制键名",
|
||||||
|
@ -220,7 +220,9 @@
|
||||||
"show_affected_key": "查看受影响的键名",
|
"show_affected_key": "查看受影响的键名",
|
||||||
"confirm_delete_key": "确认删除{num}个键",
|
"confirm_delete_key": "确认删除{num}个键",
|
||||||
"async_delete": "异步执行",
|
"async_delete": "异步执行",
|
||||||
"async_delete_title": "不等待操作结果"
|
"async_delete_title": "不等待操作结果",
|
||||||
|
"confirm_flush": "我知道我正在执行的操作!",
|
||||||
|
"confirm_flush_db": "确认清空数据库"
|
||||||
},
|
},
|
||||||
"field": {
|
"field": {
|
||||||
"new": "添加新字段",
|
"new": "添加新字段",
|
||||||
|
@ -271,7 +273,7 @@
|
||||||
"about": "关于"
|
"about": "关于"
|
||||||
},
|
},
|
||||||
"log": {
|
"log": {
|
||||||
"launch_log": "运行日志",
|
"title": "运行日志",
|
||||||
"filter_server": "筛选服务器",
|
"filter_server": "筛选服务器",
|
||||||
"filter_keyword": "筛选关键字",
|
"filter_keyword": "筛选关键字",
|
||||||
"clean_log": "清空运行日志",
|
"clean_log": "清空运行日志",
|
||||||
|
@ -290,5 +292,16 @@
|
||||||
"all_info": "全部信息",
|
"all_info": "全部信息",
|
||||||
"refresh": "立即刷新",
|
"refresh": "立即刷新",
|
||||||
"auto_refresh": "自动刷新"
|
"auto_refresh": "自动刷新"
|
||||||
|
},
|
||||||
|
"slog": {
|
||||||
|
"title": "慢日志",
|
||||||
|
"limit": "条数",
|
||||||
|
"filter": "筛选",
|
||||||
|
"exec_time": "执行时间",
|
||||||
|
"client": "客户端",
|
||||||
|
"cmd": "命令",
|
||||||
|
"cost_time": "耗时",
|
||||||
|
"refresh": "立即刷新",
|
||||||
|
"auto_refresh": "自动刷新"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,11 @@ import {
|
||||||
DeleteConnection,
|
DeleteConnection,
|
||||||
DeleteGroup,
|
DeleteGroup,
|
||||||
DeleteKey,
|
DeleteKey,
|
||||||
|
FlushDB,
|
||||||
GetCmdHistory,
|
GetCmdHistory,
|
||||||
GetConnection,
|
GetConnection,
|
||||||
GetKeyValue,
|
GetKeyValue,
|
||||||
|
GetSlowLogs,
|
||||||
ListConnection,
|
ListConnection,
|
||||||
LoadAllKeys,
|
LoadAllKeys,
|
||||||
LoadNextKeys,
|
LoadNextKeys,
|
||||||
|
@ -1666,6 +1668,30 @@ const useConnectionStore = defineStore('connections', {
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flush database
|
||||||
|
* @param connName
|
||||||
|
* @param db
|
||||||
|
* @param async
|
||||||
|
* @return {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async flushDatabase(connName, db, async) {
|
||||||
|
try {
|
||||||
|
const { success = false } = await FlushDB(connName, db, async)
|
||||||
|
|
||||||
|
if (success === true) {
|
||||||
|
// update tree view data
|
||||||
|
this._deleteKeyNode(connName, db)
|
||||||
|
// set tab content empty
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.emptyTab(connName)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* rename key
|
* rename key
|
||||||
* @param {string} connName
|
* @param {string} connName
|
||||||
|
@ -1719,6 +1745,23 @@ const useConnectionStore = defineStore('connections', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get slow log list
|
||||||
|
* @param {string} server
|
||||||
|
* @param {number} db
|
||||||
|
* @param {number} num
|
||||||
|
* @return {Promise<[]>}
|
||||||
|
*/
|
||||||
|
async getSlowLog(server, db, num) {
|
||||||
|
try {
|
||||||
|
const { success, data = { list: [] } } = await GetSlowLogs(server, db, num)
|
||||||
|
const { list } = data
|
||||||
|
return list
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get key filter pattern and filter type
|
* get key filter pattern and filter type
|
||||||
* @param {string} server
|
* @param {string} server
|
||||||
|
|
|
@ -63,6 +63,12 @@ const useDialogStore = defineStore('dialog', {
|
||||||
},
|
},
|
||||||
deleteKeyDialogVisible: false,
|
deleteKeyDialogVisible: false,
|
||||||
|
|
||||||
|
flushDBParam: {
|
||||||
|
server: '',
|
||||||
|
db: 0,
|
||||||
|
},
|
||||||
|
flushDBDialogVisible: false,
|
||||||
|
|
||||||
selectTTL: -1,
|
selectTTL: -1,
|
||||||
ttlDialogVisible: false,
|
ttlDialogVisible: false,
|
||||||
|
|
||||||
|
@ -164,6 +170,15 @@ const useDialogStore = defineStore('dialog', {
|
||||||
this.deleteKeyDialogVisible = false
|
this.deleteKeyDialogVisible = false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openFlushDBDialog(server, db) {
|
||||||
|
this.flushDBParam.server = server
|
||||||
|
this.flushDBParam.db = db
|
||||||
|
this.flushDBDialogVisible = true
|
||||||
|
},
|
||||||
|
closeFlushDBDialog() {
|
||||||
|
this.flushDBDialogVisible = false
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} prefix
|
* @param {string} prefix
|
||||||
|
|
|
@ -121,19 +121,6 @@ body {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-selector-header {
|
|
||||||
height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-selector-item {
|
|
||||||
min-width: 100px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-pane-container {
|
.nav-pane-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { createI18n } from 'vue-i18n'
|
||||||
import { lang } from '@/langs/index.js'
|
import { lang } from '@/langs/index.js'
|
||||||
|
|
||||||
export const i18n = createI18n({
|
export const i18n = createI18n({
|
||||||
locale: 'en',
|
locale: 'en-us',
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en-us',
|
||||||
globalInjection: true,
|
globalInjection: true,
|
||||||
legacy: false,
|
legacy: false,
|
||||||
messages: {
|
messages: {
|
||||||
|
|
Before Width: | Height: | Size: 311 KiB After Width: | Height: | Size: 320 KiB |
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 325 KiB |
Before Width: | Height: | Size: 311 KiB After Width: | Height: | Size: 329 KiB |
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 334 KiB |