Compare commits

..

4 Commits

15 changed files with 112 additions and 77 deletions

View File

@ -28,20 +28,20 @@ Linux.</strong>
* Super lightweight, built on Webview2, without embedded browsers (Thanks * Super lightweight, built on Webview2, without embedded browsers (Thanks
to [Wails](https://github.com/wailsapp/wails)). to [Wails](https://github.com/wailsapp/wails)).
* More elegant UI, frameless, offering light and dark themes (Thanks to [Naive UI](https://github.com/tusen-ai/naive-ui) * Provides visually and user-friendly UI, 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 ([Need more languages ? Click here to contribute](.github/CONTRIBUTING.md)). * Multi-language support ([Need more languages ? Click here to contribute](.github/CONTRIBUTING.md)).
* Better connection management: supports SSH Tunnel/SSL/Sentinel Mode/Cluster Mode. * Better connection management: supports SSH Tunnel/SSL/Sentinel Mode/Cluster Mode.
* Visualize key value operations, CRUD support for Lists, Hashes, Strings, Sets, Sorted Sets, and Streams. * Visualize key value operations, CRUD support for Lists, Hashes, Strings, Sets, Sorted Sets, and Streams.
* Support multiple data viewing format and decode/decompression methods. * Support multiple data viewing format and decode/decompression methods.
* Use SCAN for segmented loading, making it easy to list millions of keys. * Use SCAN for segmented loading, making it easy to list millions of keys.
* Operation command execution logs. * Logs list for command operation history.
* Provides command-line operations. * Provides command-line mode.
* Provides slow logs. * Provides slow logs list.
* Segmented loading and querying for List/Hash/Set/Sorted Set. * Segmented loading and querying for List/Hash/Set/Sorted Set.
* Decode/decompression display for value of List/Hash/Set/Sorted Set. * Provide value decode/decompression for List/Hash/Set/Sorted Set.
* Inbuilt advanced editor - Monaco Editor. * Integrate with Monaco Editor
* Real-time commands monitoring. * Support real-time commands monitoring.
* Support import/export data. * Support import/export data.
## Roadmap ## Roadmap
@ -89,7 +89,12 @@ npm install --prefix ./frontend
```bash ```bash
wails dev wails dev
``` ```
## About
## License ### Sponsor
Tiny RDM is licensed under [GNU General Public](/LICENSE) license. If this project helpful for you, feel free to buy me a cup of coffee ☕️.
* Wechat Sponsor
<img src="docs/images/wechat_sponsor.jpg" alt="wechat" width="200" />

View File

@ -10,8 +10,6 @@
![GitHub All Releases](https://img.shields.io/github/downloads/tiny-craft/tiny-rdm/total) ![GitHub All Releases](https://img.shields.io/github/downloads/tiny-craft/tiny-rdm/total)
[![GitHub stars](https://img.shields.io/github/stars/tiny-craft/tiny-rdm)](https://github.com/tiny-craft/tiny-rdm/stargazers) [![GitHub stars](https://img.shields.io/github/stars/tiny-craft/tiny-rdm)](https://github.com/tiny-craft/tiny-rdm/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/tiny-craft/tiny-rdm)](https://github.com/tiny-craft/tiny-rdm/fork) [![GitHub forks](https://img.shields.io/github/forks/tiny-craft/tiny-rdm)](https://github.com/tiny-craft/tiny-rdm/fork)
[![Discord](https://img.shields.io/discord/1170373259133456434?label=Discord&color=5865F2)](https://discord.gg/VTFbBMGjWh)
[![X](https://img.shields.io/badge/Twitter-black?logo=x&logoColor=white)](https://twitter.com/Lykin53448)
<strong>一个现代化轻量级的跨平台Redis桌面客户端支持Mac、Windows和Linux</strong> <strong>一个现代化轻量级的跨平台Redis桌面客户端支持Mac、Windows和Linux</strong>
</div> </div>
@ -25,7 +23,7 @@
## 功能特性 ## 功能特性
* 极度轻量基于Webview2无内嵌浏览器感谢[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) * 多国语言支持:英文/中文([需要更多语言支持?点我贡献语言](.github/CONTRIBUTING_zh.md)
* 更好用的连接管理支持SSH隧道/SSL/哨兵模式/集群模式 * 更好用的连接管理支持SSH隧道/SSL/哨兵模式/集群模式
@ -89,11 +87,24 @@ wails dev
## 关于 ## 关于
此APP由我个人开发也作为本人第一个开源项目的尝试由于精力有限可能会存在BUG或者使用体验上的问题欢迎提交issue和PR。 如果你也同为独立开发者团队喜欢开源或者对Tiny Craft的相关产品感兴趣可以关注微信公众号或者加入QQ群探讨心得反馈意见交个朋友。
同时本人也在探索开源代码、独立开发和盈利性商业应用之间的平衡关系,欢迎有共同意向的小伙伴加入群聊探讨和交换想法。
* QQ群831077639 ### 微信公众号(用户交流微信群)
## 开源许可 我会不定期更新一些关于独立开发的思考和感悟,以及独立产品的介绍,欢迎扫码关注~👏
Tiny RDM 基于 [GNU General Public](/LICENSE) 开源协议. <img src="docs/images/wechat_official.png" alt="wechat" width="360" />
### 独立开发互助QQ群
```
831077639
```
### 赞助
该项目完全为爱发电,如果对你有所帮助,可以请作者喝杯咖啡 ☕️
* 微信赞赏
<img src="docs/images/wechat_sponsor.jpg" alt="wechat" width="200" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -21,7 +21,7 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.2", "@vitejs/plugin-vue": "^5.0.2",
"naive-ui": "^2.37.0", "naive-ui": "^2.36.0",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"unplugin-auto-import": "^0.17.3", "unplugin-auto-import": "^0.17.3",
"unplugin-icons": "^0.18.1", "unplugin-icons": "^0.18.1",
@ -1545,9 +1545,9 @@
"dev": true "dev": true
}, },
"node_modules/naive-ui": { "node_modules/naive-ui": {
"version": "2.37.0", "version": "2.36.0",
"resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.37.0.tgz", "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.36.0.tgz",
"integrity": "sha512-TcuXM1zysnK6i/7o2ZqNjcLp3QMmcdSLWWiXcpEk+xdGpkJzs53/OXNpF4CoDM/npjha7qqtB8Pl17YPN5egFw==", "integrity": "sha512-r1ydtEm1Ryf/aWpbLCf32mQAGK99jd1eXgpkCtIomcBRZeAtusfy6zCtIpCppoCuIKM3BW5DMafhVxilubk/lQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@css-render/plugin-bem": "^0.15.12", "@css-render/plugin-bem": "^0.15.12",
@ -1557,7 +1557,6 @@
"@types/lodash-es": "^4.17.9", "@types/lodash-es": "^4.17.9",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"css-render": "^0.15.12", "css-render": "^0.15.12",
"csstype": "^3.1.3",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0", "date-fns-tz": "^2.0.0",
"evtd": "^0.2.4", "evtd": "^0.2.4",
@ -1568,18 +1567,12 @@
"treemate": "^0.3.11", "treemate": "^0.3.11",
"vdirs": "^0.1.8", "vdirs": "^0.1.8",
"vooks": "^0.2.12", "vooks": "^0.2.12",
"vueuc": "^0.4.58" "vueuc": "^0.4.54"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "^3.0.0" "vue": "^3.0.0"
} }
}, },
"node_modules/naive-ui/node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.7", "version": "3.3.7",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
@ -3401,9 +3394,9 @@
"dev": true "dev": true
}, },
"naive-ui": { "naive-ui": {
"version": "2.37.0", "version": "2.36.0",
"resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.37.0.tgz", "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.36.0.tgz",
"integrity": "sha512-TcuXM1zysnK6i/7o2ZqNjcLp3QMmcdSLWWiXcpEk+xdGpkJzs53/OXNpF4CoDM/npjha7qqtB8Pl17YPN5egFw==", "integrity": "sha512-r1ydtEm1Ryf/aWpbLCf32mQAGK99jd1eXgpkCtIomcBRZeAtusfy6zCtIpCppoCuIKM3BW5DMafhVxilubk/lQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@css-render/plugin-bem": "^0.15.12", "@css-render/plugin-bem": "^0.15.12",
@ -3413,7 +3406,6 @@
"@types/lodash-es": "^4.17.9", "@types/lodash-es": "^4.17.9",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"css-render": "^0.15.12", "css-render": "^0.15.12",
"csstype": "^3.1.3",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0", "date-fns-tz": "^2.0.0",
"evtd": "^0.2.4", "evtd": "^0.2.4",
@ -3424,15 +3416,7 @@
"treemate": "^0.3.11", "treemate": "^0.3.11",
"vdirs": "^0.1.8", "vdirs": "^0.1.8",
"vooks": "^0.2.12", "vooks": "^0.2.12",
"vueuc": "^0.4.58" "vueuc": "^0.4.54"
},
"dependencies": {
"csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
}
} }
}, },
"nanoid": { "nanoid": {

View File

@ -22,7 +22,7 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.2", "@vitejs/plugin-vue": "^5.0.2",
"naive-ui": "^2.37.0", "naive-ui": "^2.36.0",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"unplugin-auto-import": "^0.17.3", "unplugin-auto-import": "^0.17.3",
"unplugin-icons": "^0.18.1", "unplugin-icons": "^0.18.1",

View File

@ -1 +1 @@
6dd5fc2cecb3eb5a0c7297245ac11808 41f065f6e9d8aa8ad43c4d2d8065d48a

View File

@ -60,10 +60,10 @@ const columns = computed(() => [
ellipsis: { ellipsis: {
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '50vw',
maxHeight: '60vh', maxHeight: '50vh',
overflowY: 'scroll',
}, },
scrollable: true,
}, },
}, },
render: ({ client, addr }, index) => { render: ({ client, addr }, index) => {

View File

@ -90,10 +90,10 @@ const fieldColumn = computed(() => ({
ellipsis: { ellipsis: {
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '50vw',
maxHeight: '60vh', maxHeight: '50vh',
overflowY: 'scroll',
}, },
scrollable: true,
}, },
lineClamp: 10, lineClamp: 10,
}, },
@ -122,10 +122,10 @@ const valueColumn = computed(() => ({
: { : {
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '50vw',
maxHeight: '60vh', maxHeight: '50vh',
overflowY: 'scroll',
}, },
scrollable: true,
}, },
}, },
// filterOptionValue: valueFilterOption.value, // filterOptionValue: valueFilterOption.value,

View File

@ -91,10 +91,10 @@ const valueColumn = computed(() => ({
: { : {
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '50vw',
maxHeight: '60vh', maxHeight: '50vh',
overflowY: 'scroll',
}, },
scrollable: true,
}, },
}, },
filterOptionValue: valueFilterOption.value, filterOptionValue: valueFilterOption.value,

View File

@ -90,10 +90,10 @@ const valueColumn = computed(() => ({
: { : {
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '50vw',
maxHeight: '60vh', maxHeight: '50vh',
overflowY: 'scroll',
}, },
scrollable: true,
}, },
}, },
filterOptionValue: valueFilterOption.value, filterOptionValue: valueFilterOption.value,

View File

@ -139,10 +139,10 @@ const valueColumn = computed(() => ({
: { : {
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '50vw',
maxHeight: '60vh', maxHeight: '50vh',
overflowY: 'scroll',
}, },
scrollable: true,
}, },
}, },
filterOptionValue: valueFilterOption.value, filterOptionValue: valueFilterOption.value,

View File

@ -64,7 +64,7 @@ const resetAffected = () => {
deleteForm.affectedKeys = [] deleteForm.affectedKeys = []
} }
const logLines = computed(() => { const keyLines = computed(() => {
return map(deleteForm.affectedKeys, (k) => decodeRedisKey(k)) return map(deleteForm.affectedKeys, (k) => decodeRedisKey(k))
}) })
@ -126,12 +126,13 @@ const onClose = () => {
embedded embedded
size="small"> size="small">
<n-skeleton v-if="deleteForm.loadingAffected" :repeat="10" text /> <n-skeleton v-if="deleteForm.loadingAffected" :repeat="10" text />
<n-log <n-virtual-list v-else :item-size="25" :items="keyLines" class="list-wrapper">
v-else <template #default="{ item }">
:line-height="1.5" <div class="line-item content-value">
:lines="logLines" {{ item }}
:rows="10" </div>
style="user-select: text; cursor: text" /> </template>
</n-virtual-list>
</n-card> </n-card>
</n-form> </n-form>
</n-spin> </n-spin>
@ -152,7 +153,7 @@ const onClose = () => {
:disabled="isEmpty(deleteForm.affectedKeys)" :disabled="isEmpty(deleteForm.affectedKeys)"
:focusable="false" :focusable="false"
:loading="loading" :loading="loading"
type="error" type="primary"
@click="onConfirmDelete"> @click="onConfirmDelete">
{{ $t('dialogue.key.confirm_delete_key', { num: size(deleteForm.affectedKeys) }) }} {{ $t('dialogue.key.confirm_delete_key', { num: size(deleteForm.affectedKeys) }) }}
</n-button> </n-button>
@ -161,4 +162,15 @@ const onClose = () => {
</n-modal> </n-modal>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.line-item {
line-height: 1.6;
}
.list-wrapper {
box-sizing: border-box;
max-height: 180px;
user-select: text;
cursor: text;
}
</style>

View File

@ -95,7 +95,13 @@ const onClose = () => {
:title="$t('dialogue.key.affected_key') + `(${size(exportKeyForm.keys)})`" :title="$t('dialogue.key.affected_key') + `(${size(exportKeyForm.keys)})`"
embedded embedded
size="small"> size="small">
<n-log :line-height="1.5" :lines="keyLines" :rows="10" style="user-select: text; cursor: text" /> <n-virtual-list :item-size="25" :items="keyLines" class="list-wrapper">
<template #default="{ item }">
<div class="line-item content-value">
{{ item }}
</div>
</template>
</n-virtual-list>
</n-card> </n-card>
</n-form> </n-form>
</n-spin> </n-spin>
@ -118,4 +124,15 @@ const onClose = () => {
</n-modal> </n-modal>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.line-item {
line-height: 1.6;
}
.list-wrapper {
box-sizing: border-box;
max-height: 180px;
user-select: text;
cursor: text;
}
</style>

View File

@ -3,7 +3,7 @@ import { computed, reactive, ref, watchEffect } from 'vue'
import useDialog from 'stores/dialog' import useDialog from 'stores/dialog'
import useBrowserStore from 'stores/browser.js' import useBrowserStore from 'stores/browser.js'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { isEmpty } from 'lodash' import { isEmpty, size } from 'lodash'
import TtlInput from '@/components/common/TtlInput.vue' import TtlInput from '@/components/common/TtlInput.vue'
const ttlForm = reactive({ const ttlForm = reactive({
@ -42,6 +42,14 @@ const isBatchAction = computed(() => {
return !isEmpty(ttlForm.keys) return !isEmpty(ttlForm.keys)
}) })
const title = computed(() => {
if (isBatchAction.value) {
return i18n.t('dialogue.ttl.title_batch', { count: size(ttlForm.keys) })
} else {
return i18n.t('dialogue.ttl.title')
}
})
const i18n = useI18n() const i18n = useI18n()
const quickOption = computed(() => [ const quickOption = computed(() => [
{ value: -1, unit: 1, label: i18n.t('interface.forever') }, { value: -1, unit: 1, label: i18n.t('interface.forever') },
@ -94,9 +102,7 @@ const onConfirm = async () => {
:positive-button-props="{ focusable: false, size: 'medium', loading: procssing }" :positive-button-props="{ focusable: false, size: 'medium', loading: procssing }"
:positive-text="$t('common.save')" :positive-text="$t('common.save')"
:show-icon="false" :show-icon="false"
:title=" :title="title"
isBatchAction ? $t('dialogue.ttl.title_batch', { count: size(ttlForm.keys) }) : $t('dialogue.ttl.title')
"
preset="dialog" preset="dialog"
transform-origin="center"> transform-origin="center">
<n-form :model="ttlForm" :show-require-mark="false" label-placement="top"> <n-form :model="ttlForm" :show-require-mark="false" label-placement="top">