Compare commits

..

No commits in common. "cefc5a5078e1f6b62f3e7ce41cd1eef28ff0df7f" and "bc66c63b3d6ffa439f781239516ba100f6cb754b" have entirely different histories.

15 changed files with 77 additions and 112 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)).
* Provides visually and user-friendly UI, light and dark themes (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 ([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.
* Logs list for command operation history. * Operation command execution logs.
* Provides command-line mode. * Provides command-line operations.
* Provides slow logs list. * Provides slow logs.
* Segmented loading and querying for List/Hash/Set/Sorted Set. * Segmented loading and querying for List/Hash/Set/Sorted Set.
* Provide value decode/decompression for List/Hash/Set/Sorted Set. * Decode/decompression display for value of List/Hash/Set/Sorted Set.
* Integrate with Monaco Editor * Inbuilt advanced editor - Monaco Editor.
* Support real-time commands monitoring. * Real-time commands monitoring.
* Support import/export data. * Support import/export data.
## Roadmap ## Roadmap
@ -89,12 +89,7 @@ npm install --prefix ./frontend
```bash ```bash
wails dev wails dev
``` ```
## About
### Sponsor ## License
If this project helpful for you, feel free to buy me a cup of coffee ☕️. Tiny RDM is licensed under [GNU General Public](/LICENSE) license.
* Wechat Sponsor
<img src="docs/images/wechat_sponsor.jpg" alt="wechat" width="200" />

View File

@ -10,6 +10,8 @@
![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>
@ -23,7 +25,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/哨兵模式/集群模式
@ -87,24 +89,11 @@ wails dev
## 关于 ## 关于
如果你也同为独立开发者团队喜欢开源或者对Tiny Craft的相关产品感兴趣可以关注微信公众号或者加入QQ群探讨心得反馈意见交个朋友。 此APP由我个人开发也作为本人第一个开源项目的尝试由于精力有限可能会存在BUG或者使用体验上的问题欢迎提交issue和PR。
同时本人也在探索开源代码、独立开发和盈利性商业应用之间的平衡关系,欢迎有共同意向的小伙伴加入群聊探讨和交换想法。
### 微信公众号(用户交流微信群) * QQ群831077639
我会不定期更新一些关于独立开发的思考和感悟,以及独立产品的介绍,欢迎扫码关注~👏 ## 开源许可
<img src="docs/images/wechat_official.png" alt="wechat" width="360" /> Tiny RDM 基于 [GNU General Public](/LICENSE) 开源协议.
### 独立开发互助QQ群
```
831077639
```
### 赞助
该项目完全为爱发电,如果对你有所帮助,可以请作者喝杯咖啡 ☕️
* 微信赞赏
<img src="docs/images/wechat_sponsor.jpg" alt="wechat" width="200" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

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.36.0", "naive-ui": "^2.37.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.36.0", "version": "2.37.0",
"resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.36.0.tgz", "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.37.0.tgz",
"integrity": "sha512-r1ydtEm1Ryf/aWpbLCf32mQAGK99jd1eXgpkCtIomcBRZeAtusfy6zCtIpCppoCuIKM3BW5DMafhVxilubk/lQ==", "integrity": "sha512-TcuXM1zysnK6i/7o2ZqNjcLp3QMmcdSLWWiXcpEk+xdGpkJzs53/OXNpF4CoDM/npjha7qqtB8Pl17YPN5egFw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@css-render/plugin-bem": "^0.15.12", "@css-render/plugin-bem": "^0.15.12",
@ -1557,6 +1557,7 @@
"@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",
@ -1567,12 +1568,18 @@
"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.54" "vueuc": "^0.4.58"
}, },
"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",
@ -3394,9 +3401,9 @@
"dev": true "dev": true
}, },
"naive-ui": { "naive-ui": {
"version": "2.36.0", "version": "2.37.0",
"resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.36.0.tgz", "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.37.0.tgz",
"integrity": "sha512-r1ydtEm1Ryf/aWpbLCf32mQAGK99jd1eXgpkCtIomcBRZeAtusfy6zCtIpCppoCuIKM3BW5DMafhVxilubk/lQ==", "integrity": "sha512-TcuXM1zysnK6i/7o2ZqNjcLp3QMmcdSLWWiXcpEk+xdGpkJzs53/OXNpF4CoDM/npjha7qqtB8Pl17YPN5egFw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@css-render/plugin-bem": "^0.15.12", "@css-render/plugin-bem": "^0.15.12",
@ -3406,6 +3413,7 @@
"@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",
@ -3416,7 +3424,15 @@
"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.54" "vueuc": "^0.4.58"
},
"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.36.0", "naive-ui": "^2.37.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 @@
41f065f6e9d8aa8ad43c4d2d8065d48a 6dd5fc2cecb3eb5a0c7297245ac11808

View File

@ -60,10 +60,10 @@ const columns = computed(() => [
ellipsis: { ellipsis: {
tooltip: { tooltip: {
style: { style: {
maxWidth: '50vw', maxWidth: '80vw',
maxHeight: '50vh', maxHeight: '60vh',
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: '50vw', maxWidth: '80vw',
maxHeight: '50vh', maxHeight: '60vh',
overflowY: 'scroll',
}, },
scrollable: true,
}, },
lineClamp: 10, lineClamp: 10,
}, },
@ -122,10 +122,10 @@ const valueColumn = computed(() => ({
: { : {
tooltip: { tooltip: {
style: { style: {
maxWidth: '50vw', maxWidth: '80vw',
maxHeight: '50vh', maxHeight: '60vh',
overflowY: 'scroll',
}, },
scrollable: true,
}, },
}, },
// filterOptionValue: valueFilterOption.value, // filterOptionValue: valueFilterOption.value,

View File

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

View File

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

View File

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

View File

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

View File

@ -95,13 +95,7 @@ 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-virtual-list :item-size="25" :items="keyLines" class="list-wrapper"> <n-log :line-height="1.5" :lines="keyLines" :rows="10" style="user-select: text; cursor: text" />
<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>
@ -124,15 +118,4 @@ const onClose = () => {
</n-modal> </n-modal>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped></style>
.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, size } from 'lodash' import { isEmpty } from 'lodash'
import TtlInput from '@/components/common/TtlInput.vue' import TtlInput from '@/components/common/TtlInput.vue'
const ttlForm = reactive({ const ttlForm = reactive({
@ -42,14 +42,6 @@ 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') },
@ -102,7 +94,9 @@ 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">