├── .DS_Store
├── .github
└── workflows
│ └── objective-c-xcode.yml
├── .gitignore
├── .version
├── CHANGELOG.md
├── LICENSE
├── Q&A.md
├── README.md
├── README.zh_CN.md
├── Tests
├── RedisBaseTests
│ ├── RedisBaseTest.swift
│ └── RedisClientBaseTest.swift
├── RedisCommandTests
│ └── RedisClentStringTest.swift
├── Store
│ ├── AppContextStoreTests.swift
│ ├── KeysDelStoreTests.swift
│ ├── RedisKeysStoreTests.swift
│ └── StoreBaseTests.swift
└── Tests.swift
├── redis-pro.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── swiftpm
│ │ │ └── Package.resolved
│ └── xcuserdata
│ │ ├── chengpan.xcuserdatad
│ │ ├── IDEFindNavigatorScopes.plist
│ │ ├── UserInterfaceState.xcuserstate
│ │ ├── WorkspaceSettings.xcsettings
│ │ ├── xcdebugger
│ │ │ └── Expressions.xcexplist
│ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ │ ├── chengpanwang.xcuserdatad
│ │ ├── UserInterfaceState.xcuserstate
│ │ └── WorkspaceSettings.xcsettings
│ │ └── chenpanwang.xcuserdatad
│ │ ├── UserInterfaceState.xcuserstate
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── xcshareddata
│ └── xcschemes
│ │ └── redis-pro.xcscheme
└── xcuserdata
│ ├── chengpan.xcuserdatad
│ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ │ └── xcschememanagement.plist
│ ├── chengpanwang.xcuserdatad
│ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ │ └── xcschememanagement.plist
│ └── chenpanwang.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ └── xcschememanagement.plist
└── redis-pro
├── .DS_Store
├── Assets.xcassets
├── AccentColor.colorset
│ └── Contents.json
├── AppIcon.appiconset
│ ├── Contents.json
│ ├── redis@128.png
│ ├── redis@256.png
│ ├── redis@32-1.png
│ ├── redis@32.png
│ └── redis@64.png
├── Contents.json
└── icon-redis.imageset
│ ├── Contents.json
│ ├── redis@1x.png
│ ├── redis@2x.png
│ └── redis@3x.png
├── Command
├── AbountCommand.swift
├── CheckUpdateCommand.swift
├── HomeCommand.swift
└── RedisProCommands.swift
├── Common
├── Assert.swift
├── BizError.swift
├── Const.swift
├── DoubleFormatter.swift
├── Enums
│ ├── ButtonTypeEnum.swift
│ ├── MainViewTypeEnum.swift
│ ├── RedisConnectionTypeEnum.swift
│ ├── RedisKeyTypeEnum.swift
│ └── TableContextMenuEnum.swift
├── Helpers
│ ├── DateHelper.swift
│ ├── NumberHelper.swift
│ ├── PasteboardHelper.swift
│ └── StringHelper.swift
├── LoggerFactory.swift
├── MTheme.swift
├── NIO
│ ├── ReadTimeoutHandler.swift
│ ├── ReconnectHandler.swift
│ └── TimeoutHandler.swift
├── NetworkMonitor.swift
├── Objects.swift
├── PuppyLogger.swift
├── RedisClient
│ ├── RediStackClient.swift
│ ├── RediStackClientStream.swift
│ ├── RedisClientConfig.swift
│ ├── RedisClientConn.swift
│ ├── RedisClientHash.swift
│ ├── RedisClientKeys.swift
│ ├── RedisClientList.swift
│ ├── RedisClientLua.swift
│ ├── RedisClientSSH.swift
│ ├── RedisClientScan.swift
│ ├── RedisClientSet.swift
│ ├── RedisClientSlowLog.swift
│ ├── RedisClientString.swift
│ ├── RedisClientSystem.swift
│ ├── RedisClientZSet.swift
│ └── RedisCommandExt.swift
├── SSH
│ ├── SSHAuthentication.swift
│ ├── SSHForward.swift
│ ├── SSHRediStackClient.swift
│ └── SSHTunnel.swift
├── Settings
│ ├── ColorSchemeEnum.swift
│ └── RedisFavoriteDefaultSelectTypeEnum.swift
├── Stopwatch.swift
├── StringFormatter.swift
├── UserDefaults
│ ├── RedisDefaults.swift
│ └── UserDefaultsKeysEnum.swift
└── VersionManager.swift
├── Info.plist
├── Language
├── MButton.strings
│ ├── en.xcloc
│ │ ├── Localized Contents
│ │ │ └── en.xliff
│ │ ├── Source Contents
│ │ │ └── redis-pro
│ │ │ │ ├── Views
│ │ │ │ └── Components
│ │ │ │ │ └── en.lproj
│ │ │ │ │ └── MButton.strings
│ │ │ │ └── en.lproj
│ │ │ │ ├── InfoPlist.strings
│ │ │ │ └── Localizable.strings
│ │ └── contents.json
│ └── zh-Hans.xcloc
│ │ ├── Localized Contents
│ │ └── zh-Hans.xliff
│ │ ├── Source Contents
│ │ └── redis-pro
│ │ │ ├── Views
│ │ │ └── Components
│ │ │ │ └── en.lproj
│ │ │ │ └── MButton.strings
│ │ │ └── en.lproj
│ │ │ ├── InfoPlist.strings
│ │ │ └── Localizable.strings
│ │ └── contents.json
└── redis-pro
│ ├── en.xcloc
│ ├── Localized Contents
│ │ └── en.xliff
│ ├── Source Contents
│ │ └── redis-pro
│ │ │ ├── Language
│ │ │ └── en.lproj
│ │ │ │ ├── Localizble.strings
│ │ │ │ └── RedisInfo.strings
│ │ │ └── en.lproj
│ │ │ ├── InfoPlist.strings
│ │ │ └── Localizable.strings
│ └── contents.json
│ └── zh-Hans.xcloc
│ ├── Localized Contents
│ └── zh-Hans.xliff
│ ├── Source Contents
│ └── redis-pro
│ │ ├── Language
│ │ └── en.lproj
│ │ │ ├── Localizble.strings
│ │ │ └── RedisInfo.strings
│ │ └── en.lproj
│ │ ├── InfoPlist.strings
│ │ └── Localizable.strings
│ └── contents.json
├── Model
├── ClientModel.swift
├── GlobalContext.swift
├── Page.swift
├── RedisConfigItemModel.swift
├── RedisFavoriteModel.swift
├── RedisHashEntryModel.swift
├── RedisInfoItemModel.swift
├── RedisInfoModel.swift
├── RedisInsanceModel.swift
├── RedisKeyModel.swift
├── RedisKeyValueModel.swift
├── RedisListItemModel.swift
├── RedisModel.swift
├── RedisZSetItemModel.swift
├── ScanModel.swift
└── SlowLogModel.swift
├── Preview Content
└── Preview Assets.xcassets
│ └── Contents.json
├── Store
├── AppContextStore.swift
├── AppStore.swift
├── ClientListStore.swift
├── DatabaseStore.swift
├── DependencyKeys.swift
├── FavoriteStore.swift
├── HashValueStore.swift
├── KeyObjectStore.swift
├── KeyStore.swift
├── ListValueStore.swift
├── LoadingStore.swift
├── LoginStore.swift
├── LuaStore.swift
├── PageStore.swift
├── RedisConfigStore.swift
├── RedisInfoStore.swift
├── RedisKeysStore.swift
├── RedisSystemStore.swift
├── RenameStore.swift
├── SetValueStore.swift
├── SettingsStore.swift
├── SlowLogStore.swift
├── StringValueStore.swift
├── SystemEnvironment.swift
├── TableStore.swift
├── ValueStore.swift
└── ZSetValueStore.swift
├── Views
├── .DS_Store
├── App
│ ├── AboutView.swift
│ └── Settings
│ │ └── SettingsView.swift
├── Components
│ ├── Extensions.swift
│ ├── Form
│ │ ├── FormItemDouble.swift
│ │ ├── FormItemInt.swift
│ │ ├── FormItemPassword.swift
│ │ ├── FormItemText.swift
│ │ ├── FormItemTextArea.swift
│ │ ├── FormLabel.swift
│ │ ├── FormText.swift
│ │ ├── FormWrapper.swift
│ │ ├── MDoubleField.swift
│ │ ├── MIntField.swift
│ │ ├── MPasswordField.swift
│ │ ├── MSecureField.swift
│ │ ├── MTextEditor.swift
│ │ ├── MTextField.swift
│ │ ├── NIntField.swift
│ │ ├── NPasswordField.swift
│ │ ├── NSearchField.swift
│ │ ├── NSecureField.swift
│ │ ├── NTextEditor.swift
│ │ └── NTextField.swift
│ ├── IconButton.swift
│ ├── KeyObjectBar.swift
│ ├── LoadingView.swift
│ ├── Loadings.swift
│ ├── MButton.swift
│ ├── MIcon.swift
│ ├── MLabel.swift
│ ├── MLoading.swift
│ ├── MSpin.swift
│ ├── MTabView.swift
│ ├── Messages.swift
│ ├── ModalView.swift
│ ├── PageBar.swift
│ ├── RedisKeyTypePicker.swift
│ ├── SearchBar.swift
│ ├── Table
│ │ ├── NTable.swift
│ │ ├── NTableColumn.swift
│ │ ├── TableCellView.swift
│ │ └── TableColumnType.swift
│ ├── Tag.swift
│ ├── TextViewController.swift
│ └── TextViewController.xib
├── HomeView.swift
├── IndexView.swift
├── Login
│ ├── LoginForm.swift
│ ├── LoginView.swift
│ └── RedisListView.swift
├── RedisEditorView
│ ├── HashEditorView.swift
│ ├── ListEditorView.swift
│ ├── RedisValueEditView.swift
│ ├── RedisValueHeaderView.swift
│ ├── RedisValueView.swift
│ ├── SetEditorView.swift
│ ├── StringEditorView.swift
│ └── ZSetEditorView.swift
├── Sidebar
│ ├── DatabasePicker.swift
│ └── RedisKeysListView.swift
└── System
│ ├── ClientsListView.swift
│ ├── LuaView.swift
│ ├── RedisConfigView.swift
│ ├── RedisInfoView.swift
│ ├── RedisSystemView.swift
│ └── SlowLogView.swift
├── en.lproj
└── Localizable.strings
├── redis-proRelease.entitlements
├── redis_pro.entitlements
├── redis_proApp.swift
└── zh-Hans.lproj
└── Localizable.strings
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/.DS_Store
--------------------------------------------------------------------------------
/.github/workflows/objective-c-xcode.yml:
--------------------------------------------------------------------------------
1 | name: Xcode - Build and Analyze
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | build:
11 | name: Build and analyse default scheme using xcodebuild command
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v3
17 | - name: Set Default Scheme
18 | run: |
19 | scheme_list=$(xcodebuild -list -json | tr -d "\n")
20 | default=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]")
21 | echo $default | cat >default
22 | echo Using default scheme: $default
23 | - name: Build
24 | env:
25 | scheme: ${{ 'default' }}
26 | run: |
27 | if [ $scheme = default ]; then scheme=$(cat default); fi
28 | if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi
29 | file_to_build=`echo $file_to_build | awk '{$1=$1;print}'`
30 | xcodebuild clean build analyze -scheme "$scheme" -"$filetype_parameter" "$file_to_build" | xcpretty && exit ${PIPESTATUS[0]}
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | redisProTests/RedisClientTest.swift
2 | .idea
3 |
--------------------------------------------------------------------------------
/.version:
--------------------------------------------------------------------------------
1 | {
2 | "latestVersionNum": 31,
3 | "latestVersion": "3.1.0",
4 | "updateType": "hint",
5 | "releaseNotes": "1. 增加批量删除功能。\n2. 编辑器样式调整。\n3. 修复查询空时, table select index 数值异常问题。 \n4. 刷新按钮时使用当前查询关键字。 \n5. 修复查询空时, table select index 数值异常问题。\n6. 优化查询时loading中断问题。\n7. 使用async优化确认框。"
6 | }
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.4.1
2 | 1. 优化scan, 没有通配符时不进行scan搜索。感谢@ydq
3 | 2. 使用swift concurrency 重写部分功能。
4 |
5 | # 1.4.2
6 | 1. 密码,搜索框使用swiftUI 原生组件。
7 | 2. 表格增加双击编辑。
8 | 3. 修复hash field 禁用状态错误bug。
9 | 4. 去除promisekit依赖,使用swift concurrency。
10 |
11 |
--------------------------------------------------------------------------------
/Q&A.md:
--------------------------------------------------------------------------------
1 | # 问题记录
2 |
3 | ## TCA
4 | 1. tca 监听不存在的属性值时, 会造成编译卡死, swift-frontend 内存泄漏
5 | 2. 同一个reducer被注入多次时, action方法会被多次调用,例如:
6 | ```
7 | Scope(state: \.redisKeysState, action: /Action.redisKeysAction) {
8 | RedisKeysStore(redisInstanceModel: redisInstanceModel)
9 | }
10 | Scope(state: \.redisKeysState, action: /Action.redisKeysAction) {
11 | RedisKeysStore(redisInstanceModel: redisInstanceModel)
12 | }
13 | ```
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Redis Pro
2 |
3 | English | [简体中文](./README.zh_CN.md)
4 |
5 | 
6 | [](https://github.com/cmushroom/redis-pro/releases)
7 | 
8 | [](https://gitter.im/redis-pro/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
9 |
10 | ## Intro
11 | * redis-pro is a simple and easy to use management of redis, written in SwiftUI.
12 | * [Sequel-Ace](https://github.com/Sequel-Ace/Sequel-Ace) and Aliyun DMS was borrowed in the development process,Sequel-Ace (Sequel-Pro) is a simple and easy to use mysql management
13 |
14 | ## Install
15 | * download package in release page
16 | [address](https://github.com/cmushroom/redis-pro/releases)
17 |
18 | * homebrew
19 | ```
20 | brew install redis-pro
21 | ```
22 |
23 |
24 |
25 | ## Platform
26 | support macos (Intel, Apple Silicon) platform, will support ipad os in the future
27 |
28 | ## Todo
29 | - [x] client list and kill
30 | - [x] homebrew install
31 | - [x] slow log
32 | - [x] redis config update
33 | - [x] ssh login
34 | - [x] favorite delete confirm
35 | - [x] TCA
36 | - [x] delete batch
37 | - [ ] terminal
38 | - [ ] ipad os support
39 | - [ ] ssh key
40 |
41 |
42 | ## Version
43 | * macos: >= 11.0
44 | * redis: 3.x¹ ... 6.x
45 |
46 | ## Dependency
47 | * RediStack a redis client wiritten in swiftNIO
48 | * swift-log swift log framework, need puppy implementation
49 | * Puppy log framework, support console, file
50 | * SwiftyJSON json convert
51 |
52 |
53 |
54 | ## Snapshot
55 | login
56 |
57 |
58 | home
59 |
60 |
61 | setting
62 |
63 |
64 | Info
65 |
66 |
67 | Clients
68 |
69 |
70 |
71 | dark mode
72 |
73 |
74 |
--------------------------------------------------------------------------------
/README.zh_CN.md:
--------------------------------------------------------------------------------
1 |
2 | # Redis Pro
3 |
4 | [English](./README.md) | 简体中文
5 |
6 | 
7 | [](https://github.com/cmushroom/redis-pro/releases)
8 | 
9 | [](https://gitter.im/redis-pro/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
10 |
11 | ## 简介
12 | * redis-pro 是一款 redis 轻量客户端管理工具, 采用SwiftUI 编写
13 | * 开发过程中借鉴了 [Sequel-Ace](https://github.com/Sequel-Ace/Sequel-Ace)! 和阿里云DMS,Sequel-Ace (前身 Sequel-Pro) 是一个简洁易用的小众 mysql桌面客户端
14 |
15 | ## 安装
16 | * 到release页面下载安装
17 | [下载地址](https://github.com/cmushroom/redis-pro/releases)
18 | * homebrew
19 | ```
20 | brew install redis-pro
21 | ```
22 |
23 |
24 |
25 |
26 | ## 平台
27 | 目前只支持 macos (Intel, Apple Silicon) 平台, 后续考虑支持 ipad os
28 |
29 | ## 功能计划(暂定)
30 | - [x] client list and kill
31 | - [x] homebrew install
32 | - [x] slow log
33 | - [x] redis config update
34 | - [x] ssh login
35 | - [x] favorite delete confirm
36 | - [x] TCA
37 | - [x] delete batch
38 | - [ ] terminal
39 | - [ ] ipad os support
40 | - [ ] ssh key support
41 |
42 |
43 |
44 | ## 版本要求
45 | * macos: >= 11.0
46 | * redis: 3.x¹ ... 6.x
47 |
48 | ## 依赖
49 | * RediStack 采用swiftNIO 编写的redis client
50 | * swift-log swift 日志框架, 是上层框架, 需要具体的实现
51 | * Puppy 日志实现, 滚动写入到日志文件
52 | * SwiftyJSON json 转换
53 |
54 |
55 |
56 | ## 应用截图
57 | 登录页
58 |
59 |
60 | 首页
61 |
62 |
63 | 设置
64 |
65 |
66 | Info
67 |
68 |
69 | Clients
70 |
71 |
72 |
73 | 暗黑模式
74 |
75 |
76 |
77 |
78 | ## FAQ
79 | * keys 分页数量不匹配
80 | redis scan 命令特性决定, COUNT 选项的作用就是让用户告知迭代命令, 在每次迭代中应该从数据集里返回多少元素。虽然 COUNT 选项只是对增量式迭代命令的一种提示(hint), 但是在大多数情况下, 这种提示都是有效的。少数情况会发生返回数量与COUNT不一致的情况, 多数发生在keys数量不多, 与页大小差距不大的情况
81 |
--------------------------------------------------------------------------------
/Tests/RedisBaseTests/RedisBaseTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisClientBaseTest.swift
3 | // Tests
4 | //
5 | // Created by chengpan on 2022/12/3.
6 | //
7 |
8 | import XCTest
9 |
10 | open class RedisBaseTest: XCTestCase {
11 | open var redisHostname: String {
12 | return ProcessInfo.processInfo.environment["REDIS_HOST"] ?? "localhost"
13 | }
14 |
15 | open var redisPort: Int {
16 | return Int(ProcessInfo.processInfo.environment["REDIS_PORT"] ?? "6379")!
17 | }
18 |
19 | open var redisUsername: String? {
20 | return ProcessInfo.processInfo.environment["REDIS_USERNAME"]
21 | }
22 |
23 | open var redisPassword: String? {
24 | return ProcessInfo.processInfo.environment["REDIS_PW"]
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Tests/RedisBaseTests/RedisClientBaseTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisClientBaseTests.swift
3 | // Tests
4 | //
5 | // Created by chengpan on 2022/12/3.
6 | //
7 |
8 | @testable import redis_pro
9 | import XCTest
10 | import Foundation
11 | import Logging
12 |
13 | open class RedisClientBaseTest: RedisBaseTest {
14 | let logger = Logger(label: "redis-client-test")
15 |
16 | var redisModel: RedisModel!
17 | var redisClient: RediStackClient!
18 |
19 | open override func setUp() {
20 | logger.info("redis client base test setUp...")
21 | self.redisModel = RedisModel(host: redisHostname, port: redisPort, username: redisUsername, password: redisPassword)
22 | redisClient = .init(redisModel)
23 | // let conn = try await redisClient.initConn(host: redisHostname, port: redisPort, username: redisUsername ?? "", pass: redisPassword ?? "", database: 0)
24 | }
25 |
26 | /// Sends a "FLUSHALL" command to Redis to clear it of any data from the previous test, then closes the connection.
27 | ///
28 | /// If any steps fail, a `fatalError` is thrown.
29 | ///
30 | /// See `XCTest.XCTestCase.tearDown()`
31 | open override func tearDown() {
32 | // Task {
33 | // await redisClient.flushDB()
34 | // }
35 |
36 | redisClient.close()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Tests/RedisCommandTests/RedisClentStringTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisClent.swift
3 | // Tests
4 | //
5 | // Created by chengpan on 2022/12/3.
6 | //
7 |
8 | @testable import redis_pro
9 | import XCTest
10 | import Foundation
11 |
12 | class RedisClentStringTest: RedisClientBaseTest {
13 | let key = "redis_client_test_key"
14 | let value = "redis_client_test_value"
15 |
16 | func testSetKey() async {
17 | await redisClient.set(key, value: value)
18 | }
19 |
20 | func testGetKey() async {
21 | await testSetKey()
22 | let r = await redisClient.get(key)
23 |
24 | logger.info("redis client test get key, r: \(r)")
25 | XCTAssertEqual(value, r)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Tests/Store/AppContextStoreTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppContextStoreTests.swift
3 | // Tests
4 | //
5 | // Created by chengpan on 2023/8/5.
6 | //
7 |
8 | @testable import redis_pro
9 | import Foundation
10 | import XCTest
11 | import ComposableArchitecture
12 |
13 | @MainActor
14 | class AppContextStoreTests: StoreBaseTests {
15 | func testShow() async {
16 | let store = TestStore(initialState: AppContextStore.State()) {
17 | AppContextStore()
18 | } withDependencies: {
19 | $0.redisInstance = redisInstance
20 | $0.redisClient = redisClient
21 | }
22 |
23 | await store.send(.show) {
24 | $0.loading = true
25 | $0.loadingCount = 1
26 | }
27 | await store.send(.hide) {
28 | $0.loading = false
29 | $0.loadingCount = 0
30 | }
31 |
32 | await store.send(.show) {
33 | $0.loading = true
34 | $0.loadingCount = 1
35 | }
36 | await store.send(.show) {
37 | $0.loading = true
38 | $0.loadingCount = 2
39 | }
40 | await store.send(.show) {
41 | $0.loading = true
42 | $0.loadingCount = 3
43 | }
44 | await store.send(.hide) {
45 | $0.loading = true
46 | $0.loadingCount = 2
47 | }
48 | await store.send(.show) {
49 | $0.loading = true
50 | $0.loadingCount = 3
51 | }
52 | await store.send(.hide) {
53 | $0.loading = true
54 | $0.loadingCount = 2
55 | }
56 | await store.send(.hide) {
57 | $0.loading = true
58 | $0.loadingCount = 1
59 | }
60 | await store.send(.hide) {
61 | $0.loading = false
62 | $0.loadingCount = 0
63 | }
64 | await store.send(.hide)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Tests/Store/KeysDelStoreTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppStoreTest.swift
3 | // Tests
4 | //
5 | // Created by chengpan on 2023/8/5.
6 | //
7 |
8 | @testable import redis_pro
9 | import Foundation
10 | import XCTest
11 | import ComposableArchitecture
12 |
13 | @MainActor
14 | class KeysDelStoreTests: StoreBaseTests {
15 | func testBasics() async {
16 | let store = TestStore(initialState: RedisKeysStore.State()) {
17 | RedisKeysStore()
18 | } withDependencies: {
19 | $0.redisInstance = redisInstance
20 | $0.redisClient = redisClient
21 | }
22 |
23 |
24 | await redisClient.set("__keys_del_str_1", value: UUID.init().uuidString)
25 | await redisClient.set("__keys_del_str_2", value: UUID.init().uuidString)
26 | await redisClient.set("__keys_del_str_3", value: UUID.init().uuidString)
27 | await redisClient.set("__keys_del_str_4", value: UUID.init().uuidString)
28 | await redisClient.set("__keys_del_str_5", value: UUID.init().uuidString)
29 | await store.send(.search("__keys_del_str_*")) {
30 | $0.pageState = PageStore.State()
31 | }
32 |
33 | XCTAssertEqual(store.state.tableState.datasource.count, 3)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Tests/Store/RedisKeysStoreTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisKeysStoreTest.swift
3 | // Tests
4 | //
5 | // Created by chengpan on 2024/1/1.
6 | //
7 |
8 | @testable import redis_pro
9 | import Foundation
10 | import XCTest
11 | import ComposableArchitecture
12 |
13 | @MainActor
14 | class RedisKeysStoreTests: StoreBaseTests {
15 | func testBasics() async {
16 | let store = TestStore(initialState: RedisKeysStore.State()) {
17 | RedisKeysStore()
18 | } withDependencies: {
19 | $0.redisClient = redisClient
20 | }
21 |
22 |
23 | await redisClient.set("__keys_del_str_1", value: UUID.init().uuidString)
24 | await redisClient.set("__keys_del_str_2", value: UUID.init().uuidString)
25 | await redisClient.set("__keys_del_str_3", value: UUID.init().uuidString)
26 | await redisClient.set("__keys_del_str_4", value: UUID.init().uuidString)
27 | await redisClient.set("__keys_del_str_5", value: UUID.init().uuidString)
28 | await store.send(.search("__keys_del_str_*"))
29 |
30 | // await store.receive(\.setKeys)
31 |
32 | XCTAssertEqual(store.state.tableState.datasource.count, 5)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Tests/Store/StoreBaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoreBaseTests.swift
3 | // Tests
4 | //
5 | // Created by chengpan on 2023/8/5.
6 | //
7 |
8 | @testable import redis_pro
9 | import Foundation
10 | import Logging
11 | import XCTest
12 | import ComposableArchitecture
13 |
14 |
15 | class StoreBaseTests: RedisClientBaseTest {
16 | var redisInstance: RedisInstanceModel!
17 |
18 | override func setUp() {
19 | super.setUp()
20 | self.redisInstance = RedisInstanceModel(redisModel: redisModel)
21 |
22 | logger.info("StoreBaseTests setup...")
23 | }
24 |
25 |
26 | func testExample() {
27 | logger.info("test example ...")
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Tests/Tests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tests.swift
3 | // Tests
4 | //
5 | // Created by chengpan on 2022/12/3.
6 | //
7 |
8 | import XCTest
9 |
10 | final class Tests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 | }
15 |
16 | override func tearDownWithError() throws {
17 | // Put teardown code here. This method is called after the invocation of each test method in the class.
18 | }
19 |
20 | func testExample() throws {
21 | // This is an example of a functional test case.
22 | // Use XCTAssert and related functions to verify your tests produce the correct results.
23 | // Any test you write for XCTest can be annotated as throws and async.
24 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
25 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
26 | }
27 |
28 | func testPerformanceExample() throws {
29 | // This is an example of a performance test case.
30 | measure {
31 | // Put the code you want to measure the time of here.
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 | PreviewsEnabled
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpan.xcuserdatad/IDEFindNavigatorScopes.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpan.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpan.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpan.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildLocationStyle
6 | UseAppPreferences
7 | CustomBuildLocationType
8 | RelativeToDerivedData
9 | DerivedDataLocationStyle
10 | Default
11 | IssueFilterStyle
12 | ShowActiveSchemeOnly
13 | LiveSourceIssuesEnabled
14 |
15 | ShowSharedSchemesAutomaticallyEnabled
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpan.xcuserdatad/xcdebugger/Expressions.xcexplist:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpan.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpanwang.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpanwang.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chengpanwang.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildLocationStyle
6 | UseAppPreferences
7 | CustomBuildLocationType
8 | RelativeToDerivedData
9 | DerivedDataLocationStyle
10 | Default
11 | IssueFilterStyle
12 | ShowActiveSchemeOnly
13 | LiveSourceIssuesEnabled
14 |
15 | ShowSharedSchemesAutomaticallyEnabled
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chenpanwang.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chenpanwang.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/project.xcworkspace/xcuserdata/chenpanwang.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/xcuserdata/chengpanwang.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | CustomDump (Playground) 1.xcscheme
8 |
9 | isShown
10 |
11 | orderHint
12 | 20
13 |
14 | CustomDump (Playground) 2.xcscheme
15 |
16 | isShown
17 |
18 | orderHint
19 | 21
20 |
21 | CustomDump (Playground).xcscheme
22 |
23 | isShown
24 |
25 | orderHint
26 | 19
27 |
28 | Playground (Playground) 1.xcscheme
29 |
30 | isShown
31 |
32 | orderHint
33 | 5
34 |
35 | Playground (Playground) 2.xcscheme
36 |
37 | isShown
38 |
39 | orderHint
40 | 6
41 |
42 | Playground (Playground).xcscheme
43 |
44 | isShown
45 |
46 | orderHint
47 | 1
48 |
49 | PromiseKit (Playground) 1.xcscheme
50 |
51 | isShown
52 |
53 | orderHint
54 | 3
55 |
56 | PromiseKit (Playground) 2.xcscheme
57 |
58 | isShown
59 |
60 | orderHint
61 | 4
62 |
63 | PromiseKit (Playground) 3.xcscheme
64 |
65 | isShown
66 |
67 | orderHint
68 | 7
69 |
70 | PromiseKit (Playground) 4.xcscheme
71 |
72 | isShown
73 |
74 | orderHint
75 | 8
76 |
77 | PromiseKit (Playground) 5.xcscheme
78 |
79 | isShown
80 |
81 | orderHint
82 | 9
83 |
84 | PromiseKit (Playground).xcscheme
85 |
86 | isShown
87 |
88 | orderHint
89 | 2
90 |
91 | redis-pro.xcscheme_^#shared#^_
92 |
93 | orderHint
94 | 0
95 |
96 |
97 | SuppressBuildableAutocreation
98 |
99 | 4320AAEF25B6740900A8E214
100 |
101 | primary
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/redis-pro.xcodeproj/xcuserdata/chenpanwang.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | CustomDump (Playground) 1.xcscheme
8 |
9 | isShown
10 |
11 | orderHint
12 | 20
13 |
14 | CustomDump (Playground) 2.xcscheme
15 |
16 | isShown
17 |
18 | orderHint
19 | 21
20 |
21 | CustomDump (Playground).xcscheme
22 |
23 | isShown
24 |
25 | orderHint
26 | 19
27 |
28 | Playground (Playground) 1.xcscheme
29 |
30 | isShown
31 |
32 | orderHint
33 | 5
34 |
35 | Playground (Playground) 2.xcscheme
36 |
37 | isShown
38 |
39 | orderHint
40 | 6
41 |
42 | Playground (Playground).xcscheme
43 |
44 | isShown
45 |
46 | orderHint
47 | 4
48 |
49 | PromiseKit (Playground) 1.xcscheme
50 |
51 | isShown
52 |
53 | orderHint
54 | 8
55 |
56 | PromiseKit (Playground) 2.xcscheme
57 |
58 | isShown
59 |
60 | orderHint
61 | 9
62 |
63 | PromiseKit (Playground).xcscheme
64 |
65 | isShown
66 |
67 | orderHint
68 | 7
69 |
70 | redis-pro.xcscheme_^#shared#^_
71 |
72 | orderHint
73 | 0
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/redis-pro/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/.DS_Store
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "filename" : "redis@32-1.png",
10 | "idiom" : "mac",
11 | "scale" : "2x",
12 | "size" : "16x16"
13 | },
14 | {
15 | "filename" : "redis@32.png",
16 | "idiom" : "mac",
17 | "scale" : "1x",
18 | "size" : "32x32"
19 | },
20 | {
21 | "filename" : "redis@64.png",
22 | "idiom" : "mac",
23 | "scale" : "2x",
24 | "size" : "32x32"
25 | },
26 | {
27 | "filename" : "redis@128.png",
28 | "idiom" : "mac",
29 | "scale" : "1x",
30 | "size" : "128x128"
31 | },
32 | {
33 | "filename" : "redis@256.png",
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/AppIcon.appiconset/redis@128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Assets.xcassets/AppIcon.appiconset/redis@128.png
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/AppIcon.appiconset/redis@256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Assets.xcassets/AppIcon.appiconset/redis@256.png
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/AppIcon.appiconset/redis@32-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Assets.xcassets/AppIcon.appiconset/redis@32-1.png
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/AppIcon.appiconset/redis@32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Assets.xcassets/AppIcon.appiconset/redis@32.png
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/AppIcon.appiconset/redis@64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Assets.xcassets/AppIcon.appiconset/redis@64.png
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/icon-redis.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "redis@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "redis@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "redis@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/icon-redis.imageset/redis@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Assets.xcassets/icon-redis.imageset/redis@1x.png
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/icon-redis.imageset/redis@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Assets.xcassets/icon-redis.imageset/redis@2x.png
--------------------------------------------------------------------------------
/redis-pro/Assets.xcassets/icon-redis.imageset/redis@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Assets.xcassets/icon-redis.imageset/redis@3x.png
--------------------------------------------------------------------------------
/redis-pro/Command/AbountCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AbountCommand.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/12/3.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct AboutCommands: View {
12 |
13 | @Environment(\.openURL) var openURL
14 | var body: some View {
15 | Button("About") {
16 | guard let url = URL(string: "redis-pro://AboutView") else {
17 | return
18 | }
19 | openURL(url)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/redis-pro/Command/CheckUpdateCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CheckUpdateCommand.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/12/3.
6 | //
7 |
8 | import SwiftUI
9 | import Foundation
10 |
11 | struct CheckUpdateCommands: View {
12 | var body: some View {
13 | Button("Check Update") {
14 | VersionManager().checkUpdate(isNoUpgradeHint: true)
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/redis-pro/Command/HomeCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeCommand.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/12/3.
6 | //
7 |
8 | import SwiftUI
9 | import Foundation
10 |
11 | struct HomeCommands: View {
12 | @Environment(\.openURL) var openURL
13 |
14 | var body: some View {
15 | Button("Home") {
16 | guard let url = URL(string: Const.REPO_URL) else {
17 | return
18 | }
19 | openURL(url)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/redis-pro/Command/RedisProCommands.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisProCommands.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/21.
6 | //
7 |
8 | import SwiftUI
9 | import Cocoa
10 | import Logging
11 | import ComposableArchitecture
12 |
13 | struct RedisProCommands: Commands {
14 |
15 | let logger = Logger(label: "commands")
16 |
17 | var body: some Commands {
18 | SidebarCommands()
19 |
20 | CommandGroup(replacing: CommandGroupPlacement.toolbar) {
21 | Button("New Tab", action: {
22 |
23 | if let currentWindow = NSApp.keyWindow,
24 | let windowController = currentWindow.windowController {
25 | windowController.newWindowForTab(nil)
26 | if let newWindow = NSApp.keyWindow,
27 | currentWindow != newWindow {
28 | currentWindow.addTabbedWindow(newWindow, ordered: .above)
29 | }
30 | }
31 |
32 | })
33 | .keyboardShortcut("t", modifiers: [.command])
34 | }
35 |
36 | CommandGroup(replacing: CommandGroupPlacement.help) {
37 | CheckUpdateCommands()
38 | HomeCommands()
39 | AboutCommands()
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/redis-pro/Common/Assert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Assert.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/3/27.
6 | //
7 |
8 | import Foundation
9 |
10 | class Assert {
11 | static func isTrue(_ bool:Bool, message:String) throws -> Void {
12 | if (!bool) {
13 | try fail(message)
14 | }
15 | }
16 |
17 | static func fail(_ message:String) throws{
18 | throw BizError(message: message)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/redis-pro/Common/BizError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BizError.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/1/28.
6 | //
7 |
8 | import Foundation
9 |
10 | struct BizError: Error{
11 | public let message: String
12 |
13 | public var errorDescription: String? { return message }
14 |
15 | init(message:String) {
16 | self.message = message
17 | }
18 | init(_ message:String) {
19 | self.message = message
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/redis-pro/Common/Const.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Const.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/6.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Const {
11 | static let EMPTY_STRING = ""
12 |
13 | static let LIST_VALUE_DELETE_MARK = "__REDIS_LIST_VALUE_DELETE_BY_REDIS_PRO__"
14 |
15 | static let REPO_URL = "https://github.com/cmushroom/redis-pro"
16 | static let RELEASE_URL = "https://github.com/cmushroom/redis-pro/releases"
17 |
18 | // 最大字符串展示长度默认值
19 | static let DEFAULT_STRING_MAX_LENGTH = 10240
20 | }
21 |
--------------------------------------------------------------------------------
/redis-pro/Common/DoubleFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DoubleFormatter.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/7.
6 | //
7 |
8 | import Foundation
9 |
10 | public class DoubleFormatter: Formatter {
11 |
12 | override public func string(for obj: Any?) -> String? {
13 | var retVal: String?
14 | let formatter = NumberFormatter()
15 | formatter.numberStyle = .decimal
16 | formatter.hasThousandSeparators = false
17 | formatter.maximumIntegerDigits = 50
18 | formatter.minimumFractionDigits = 0
19 | formatter.maximumFractionDigits = 8
20 |
21 | if let dbl = obj as? Double {
22 | retVal = formatter.string(from: NSNumber(value: dbl))
23 | } else {
24 | retVal = nil
25 | }
26 |
27 | return retVal
28 | }
29 |
30 | override public func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer?) -> Bool {
31 |
32 | var retVal = true
33 |
34 | if let dbl = Double(string), let objok = obj {
35 | objok.pointee = dbl as AnyObject?
36 | retVal = true
37 | } else {
38 | retVal = false
39 | }
40 |
41 | return retVal
42 |
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/redis-pro/Common/Enums/ButtonTypeEnum.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ButtonTypeEnum.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/6.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ButtonTypeEnum: String {
11 | case PRIMARY = "primary"
12 | case DEFAULT = "default"
13 | case PLAIN = "plain"
14 | }
15 |
--------------------------------------------------------------------------------
/redis-pro/Common/Enums/MainViewTypeEnum.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewTypeEnum.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/18.
6 | //
7 |
8 | import Foundation
9 |
10 |
11 | enum MainViewTypeEnum{
12 | case NONE
13 | // 编辑器
14 | case EDITOR
15 | case SYSTEM
16 | }
17 |
--------------------------------------------------------------------------------
/redis-pro/Common/Enums/RedisConnectionTypeEnum.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisConnectionType.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/8/6.
6 | //
7 |
8 | import Foundation
9 |
10 | enum RedisConnectionTypeEnum: String,CaseIterable {
11 | case TCP = "tcp"
12 | case SSH = "ssh"
13 | }
14 |
--------------------------------------------------------------------------------
/redis-pro/Common/Enums/RedisKeyTypeEnum.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisKeyTypeEnum.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2021/4/4.
6 | //
7 |
8 | import Foundation
9 |
10 | enum RedisKeyTypeEnum: String,CaseIterable {
11 | case NONE = "none"
12 | case STRING = "string"
13 | case HASH = "hash"
14 | case LIST = "list"
15 | case SET = "set"
16 | case ZSET = "zset"
17 | }
18 |
--------------------------------------------------------------------------------
/redis-pro/Common/Enums/TableContextMenuEnum.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableContextMenuEnum.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/8/20.
6 | //
7 |
8 | import Foundation
9 | import Cocoa
10 |
11 | enum TableContextMenu: String{
12 | case DELETE = "Delete"
13 | case EDIT = "Edit"
14 |
15 | // copy
16 | case COPY = "Copy"
17 | case COPY_SCORE = "Copy Score"
18 | case COPY_FIELD = "Copy Field"
19 | case COPY_VALUE = "Copy Value"
20 |
21 | // key list
22 | case RENAME = "Rename"
23 | // client list
24 | case KILL = "Kill"
25 |
26 | var ext: TableContextMenuExt {
27 | switch self {
28 | case .DELETE:
29 | return .init(keyEquivalent: String(Unicode.Scalar(NSBackspaceCharacter)!))
30 | case .EDIT:
31 | return .init(keyEquivalent: "e")
32 | case .COPY:
33 | return .init(keyEquivalent: "c")
34 | case .COPY_SCORE:
35 | return .init(keyEquivalent: "")
36 | case .COPY_FIELD:
37 | return .init(keyEquivalent: "")
38 | case .COPY_VALUE:
39 | return .init(keyEquivalent: "")
40 | case .RENAME:
41 | return .init(keyEquivalent: "")
42 | case .KILL:
43 | return .init(keyEquivalent: "k")
44 | }
45 | }
46 | }
47 |
48 | struct TableContextMenuExt {
49 | var keyEquivalent: String
50 | }
51 |
--------------------------------------------------------------------------------
/redis-pro/Common/Helpers/DateHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateHelper.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/15.
6 | //
7 |
8 | import Foundation
9 | import Logging
10 |
11 | class DateHelper {
12 |
13 | static let logger = Logger(label: "number-helper")
14 |
15 | private static var dateTimeFormatter:DateFormatter = initDateTimeFormater()
16 |
17 | private static func initDateTimeFormater() -> DateFormatter {
18 | logger.info("初始化 datetime formatter...")
19 | let dateFormatter = DateFormatter()
20 | dateFormatter.timeZone = TimeZone(abbreviation: "GMT+8") //Set timezone that you want
21 | dateFormatter.locale = NSLocale.current
22 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" //Specify your format that you want
23 | return dateFormatter
24 | }
25 |
26 | static func formatDateTime(timestamp:Int) -> String {
27 | let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
28 | return dateTimeFormatter.string(from: date)
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/redis-pro/Common/Helpers/NumberHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NumberFormatterHelper.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/8.
6 | //
7 |
8 | import Foundation
9 | import Logging
10 |
11 | class NumberHelper {
12 | static let logger = Logger(label: "number-helper")
13 |
14 | static var doubleFormatter:NumberFormatter = {
15 | logger.info("NumberFormatHelper init double formatter instance ...")
16 |
17 | let formatter = NumberFormatter()
18 | formatter.locale = Locale(identifier: "en_US")
19 | formatter.numberStyle = .decimal
20 | formatter.hasThousandSeparators = false
21 | formatter.maximumIntegerDigits = 20
22 | formatter.minimumFractionDigits = 0
23 | formatter.maximumFractionDigits = 10
24 | formatter.generatesDecimalNumbers = true
25 | formatter.maximumSignificantDigits = 20
26 | return formatter
27 | }()
28 |
29 |
30 | static var intFormatter:NumberFormatter = {
31 | logger.info("NumberFormatHelper init int formatter instance ...")
32 |
33 | let formatter = NumberFormatter()
34 | formatter.locale = Locale(identifier: "en_US")
35 | formatter.numberStyle = .decimal
36 | formatter.hasThousandSeparators = false
37 | formatter.maximumIntegerDigits = 20
38 | formatter.minimumFractionDigits = 0
39 | formatter.maximumFractionDigits = 0
40 | formatter.generatesDecimalNumbers = true
41 | formatter.maximumSignificantDigits = 20
42 | return formatter
43 | }()
44 |
45 | static func formatDouble(_ value:Double?, defaultValue:String? = "-") -> String {
46 | if value == nil {
47 | return defaultValue ?? "-"
48 | }
49 |
50 | let r = doubleFormatter.string(for: NSNumber(value: value!))
51 |
52 | if r == nil {
53 | return defaultValue ?? "-"
54 | }
55 | return r!
56 | }
57 |
58 | static func toInt(_ value:Any?, defaultValue:Int? = 0) -> Int {
59 | if value == nil {
60 | return defaultValue!
61 | }
62 |
63 | let r = intFormatter.number(from: "\(value!)")?.intValue
64 |
65 | if r == nil {
66 | return defaultValue!
67 | }
68 | return r!
69 | }
70 |
71 |
72 | static func isInt(_ value:String?) -> Bool {
73 | if value == nil {
74 | return false
75 | }
76 |
77 | return Int(value!) != nil
78 | }
79 |
80 | }
81 |
82 | extension Double {
83 | /// Max double value.
84 | static var max: Double {
85 | return Double(greatestFiniteMagnitude)
86 | }
87 |
88 | /// Min double value.
89 | static var min: Double {
90 | return Double(-greatestFiniteMagnitude)
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/redis-pro/Common/Helpers/PasteboardHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PasteboardHelper.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/8/20.
6 | //
7 |
8 | import Foundation
9 | import Cocoa
10 |
11 | class PasteboardHelper {
12 |
13 | static func copy(_ value: String) {
14 | let pasteboard = NSPasteboard.general
15 | pasteboard.clearContents()
16 | pasteboard.setString(value, forType: .string)
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/redis-pro/Common/LoggerFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoggerFactory.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/17.
6 | //
7 |
8 | import Foundation
9 | import Logging
10 | import Puppy
11 |
12 | class LoggerFactory {
13 | var puppy = Puppy.init()
14 |
15 | init() {
16 | let console = ConsoleLogger("com.cmushroom.redis-pro.console", logFormat: LogFormatter())
17 |
18 | let fileURL = URL(fileURLWithPath: "./redis-pro.log").absoluteURL
19 | let fileRotation = try! FileRotationLogger("com.cmushroom.redis-pro.file",
20 | fileURL: fileURL,
21 | rotationConfig: RotationConfig(
22 | maxFileSize: 10 * 1024 * 1024,
23 | maxArchivedFilesCount: 5
24 | ))
25 | self.puppy.add(console)
26 | self.puppy.add(fileRotation)
27 |
28 | self.puppy.info("init logger complete...")
29 | }
30 |
31 | func setUp() -> Void {
32 | LoggingSystem.bootstrap {
33 | var handler = PuppyLogHandler(label: $0, puppy: self.puppy)
34 | // Set the logging level.
35 | handler.logLevel = .info
36 | return handler
37 | }
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/redis-pro/Common/MTheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorEnum.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/6.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct MTheme {
12 | // static var PRIMARY:Color = Color(red: 0.1, green: 0.1, blue: 0.1)
13 | static var PRIMARY:Color = Color.gray
14 |
15 | static var CORNER_RADIUS:CGFloat = 2
16 |
17 | static var DIALOG_W:CGFloat = 640
18 | static var DIALOG_H:CGFloat = 400
19 |
20 | // spacing
21 | static var H_SPACING:CGFloat = 6
22 | static var H_SPACING_L:CGFloat = 10
23 | static var V_SPACING:CGFloat = 6
24 |
25 | static var HEADER_PADDING:EdgeInsets = EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0)
26 |
27 | // font size
28 | static var FONT_SIZE_BUTTON:CGFloat = 12
29 | static var FONT_SIZE_BUTTON_ICON:CGFloat = 11
30 | static var FONT_SIZE_BUTTON_S:CGFloat = 10
31 | static var FONT_SIZE_BUTTON_ICON_S:CGFloat = 9
32 | static var FONT_SIZE_BUTTON_L:CGFloat = 14
33 | static var FONT_SIZE_BUTTON_ICON_L:CGFloat = 12
34 |
35 | static var FONT_FOOTER:Font = .footnote
36 |
37 | // table cell null
38 | static var NULL_STRING = "-"
39 | }
40 |
--------------------------------------------------------------------------------
/redis-pro/Common/NIO/ReadTimeoutHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReadTimeoutHandler.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2024/1/13.
6 | //
7 |
8 | import Logging
9 | import Foundation
10 | import NIO
11 | import NIOConcurrencyHelpers
12 |
13 | class ReadTimeoutHandler: ChannelInboundHandler {
14 | typealias InboundIn = Any
15 |
16 | private let timeout: TimeAmount
17 | private var scheduledTimeout: Scheduled?
18 |
19 | private let logger = Logger(label: "nio-timeout-handler")
20 |
21 | init(timeout: TimeAmount) {
22 | self.timeout = timeout
23 | }
24 |
25 | func channelActive(context: ChannelHandlerContext) {
26 | // 当通道激活时,设置读取超时事件
27 | self.scheduledTimeout = context.eventLoop.scheduleTask(in: self.timeout) {
28 | // 处理读取超时逻辑,例如关闭通道或发出读取超时错误
29 | self.logger.info("NIO read timed out!")
30 | context.close(promise: nil)
31 | }
32 |
33 | // 继续传递通道激活事件
34 | context.fireChannelActive()
35 | }
36 |
37 | func channelRead(context: ChannelHandlerContext, data: NIOAny) {
38 | // 当有数据可读时,重置读取超时事件
39 | self.scheduledTimeout?.cancel()
40 | self.scheduledTimeout = context.eventLoop.scheduleTask(in: self.timeout) {
41 | // 处理读取超时逻辑,例如关闭通道或发出读取超时错误
42 | self.logger.info("NIO read timed out!")
43 | context.close(promise: nil)
44 | }
45 |
46 | // 继续传递读取事件
47 | context.fireChannelRead(data)
48 | }
49 |
50 | func channelInactive(context: ChannelHandlerContext) {
51 | // 当通道非活动时,取消已计划的读取超时事件
52 | self.scheduledTimeout?.cancel()
53 |
54 | // 继续传递通道非活动事件
55 | context.fireChannelInactive()
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/redis-pro/Common/NIO/ReconnectHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReconnectHandler.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2024/1/13.
6 | //
7 |
8 | import Foundation
9 | import NIO
10 |
11 | class ReconnectHandler: ChannelInboundHandler {
12 | typealias InboundIn = ByteBuffer
13 |
14 | private var reconnectScheduled: Scheduled?
15 | private let reconnectInterval: TimeAmount
16 |
17 | init(reconnectInterval: TimeAmount) {
18 | self.reconnectInterval = reconnectInterval
19 | }
20 |
21 | func channelActive(context: ChannelHandlerContext) {
22 | print("Connection established")
23 | cancelReconnect()
24 | }
25 |
26 | func channelInactive(context: ChannelHandlerContext) {
27 | print("Connection closed. Will attempt to reconnect in \(reconnectInterval)")
28 |
29 | // Schedule a reconnect after the specified interval
30 | reconnectScheduled = context.eventLoop.scheduleTask(in: reconnectInterval) {
31 | self.reconnect(context: context)
32 | }
33 | }
34 |
35 | private func reconnect(context: ChannelHandlerContext) {
36 | print("Attempting to reconnect...")
37 | // let newBootstrap = makeBootstrap(context: context)
38 | // let newChannel = try! newBootstrap.connect(host: "your_server_host", port: your_server_port).wait()
39 |
40 | // Add handlers to the new channel as needed
41 | // ...
42 |
43 | print("Reconnection successful")
44 | context.fireChannelActive()
45 | }
46 |
47 | private func cancelReconnect() {
48 | reconnectScheduled?.cancel()
49 | reconnectScheduled = nil
50 | }
51 |
52 | // private func makeBootstrap(context: ChannelHandlerContext) -> ClientBootstrap {
53 | // // Configure and return a new bootstrap instance
54 | // // ...
55 | // }
56 | }
57 |
--------------------------------------------------------------------------------
/redis-pro/Common/NIO/TimeoutHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimeoutHandler.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2024/1/13.
6 | //
7 |
8 | import Foundation
9 | import NIO
10 |
11 | class TimeoutHandler: ChannelInboundHandler {
12 | typealias InboundIn = ByteBuffer
13 |
14 | private var scheduledTimeout: Scheduled?
15 |
16 | func channelRead(context: ChannelHandlerContext, data: NIOAny) {
17 | // 处理接收到的数据
18 | // ...
19 | // 如果接收到预期的数据,取消超时任务
20 | cancelTimeout()
21 | }
22 |
23 | func errorCaught(context: ChannelHandlerContext, error: Error) {
24 | // 处理错误
25 | // ...
26 |
27 | // 如果发生错误,取消超时任务
28 | cancelTimeout()
29 | context.fireErrorCaught(error)
30 | }
31 |
32 | private func setupTimeout(context: ChannelHandlerContext, timeout: TimeAmount) {
33 | // 如果已经设置了超时任务,取消它
34 | cancelTimeout()
35 |
36 | // 安排一个新的超时任务
37 | scheduledTimeout = context.eventLoop.scheduleTask(in: timeout) {
38 | self.timeoutExpired(context: context)
39 | }
40 | }
41 |
42 | private func cancelTimeout() {
43 | // 取消超时任务
44 | scheduledTimeout?.cancel()
45 | scheduledTimeout = nil
46 | }
47 |
48 | private func timeoutExpired(context: ChannelHandlerContext) {
49 | // 处理超时
50 | // ...
51 |
52 | // 关闭连接或执行其他操作
53 | context.close(promise: nil)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/redis-pro/Common/NetworkMonitor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkMonitor.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2024/1/13.
6 | //
7 |
8 | import Logging
9 | import Foundation
10 | import Network
11 |
12 | class NetworkMonitor {
13 | private let logger = Logger(label: "network-monitor")
14 | private var monitor: NWPathMonitor?
15 |
16 | init() {
17 | monitor = NWPathMonitor()
18 | }
19 |
20 | func startMonitoring(_ callback: @escaping (Bool) async -> Void) {
21 | monitor?.pathUpdateHandler = { path in
22 | if path.usesInterfaceType(.wifi) {
23 | self.logger.info("WiFi status changed")
24 | if path.status == .satisfied {
25 | self.logger.info("WiFi is connected")
26 | Task {
27 | await callback(true)
28 | }
29 | } else {
30 | self.logger.info("WiFi is not connected")
31 | Task {
32 | await callback(false)
33 | }
34 | }
35 | }
36 | }
37 |
38 | let queue = DispatchQueue(label: "WiFiMonitorQueue")
39 | monitor?.start(queue: queue)
40 | }
41 |
42 | func stopMonitoring() {
43 | monitor?.cancel()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/redis-pro/Common/Objects.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Objects.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/18.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Objects {
11 |
12 | static func toDic(mirror: Mirror) -> [String: Any] {
13 | var dic: [String: Any] = [:]
14 | for child in mirror.children {
15 | // 如果没有labe就会被抛弃
16 | if let label = child.label {
17 | let propertyMirror = Mirror(reflecting: child.value)
18 | print(propertyMirror)
19 | dic[label] = child.value
20 | }
21 | }
22 | // 添加父类属性
23 | if let superMirror = mirror.superclassMirror {
24 | let superDic = toDic(mirror: superMirror)
25 | for p in superDic {
26 | dic[p.key] = p.value
27 | }
28 | }
29 | return dic
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/redis-pro/Common/PuppyLogger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogFormatter.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/17.
6 | //
7 |
8 | import Foundation
9 | import Puppy
10 |
11 | final class LogFormatter: LogFormattable {
12 | func formatMessage(_ level: LogLevel, message: String, tag: String, function: String,
13 | file: String, line: UInt, swiftLogInfo: [String : String],
14 | label: String, date: Date, threadID: UInt64) -> String {
15 | let date = dateFormatter(date, withFormatter: .init(), dateFormat: "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
16 | let file = fileName(file)
17 | return "\(date) [\(level.emoji) \(level)] \(file)#L.\(line) \(function) \(message)"
18 | }
19 | }
20 |
21 | final class PuppyFileRotationDelegate: FileRotationLoggerDelegate {
22 | func fileRotationLogger(_ fileRotationLogger: FileRotationLogger,
23 | didArchiveFileURL: URL, toFileURL: URL) {
24 | print("puppy didArchiveFileURL: \(didArchiveFileURL), to file URL: \(toFileURL)")
25 | }
26 | func fileRotationLogger(_ fileRotationLogger: FileRotationLogger,
27 | didRemoveArchivedFileURL: URL) {
28 | print("puppy didRemoveArchivedFileURL: \(didRemoveArchivedFileURL)")
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/redis-pro/Common/RedisClient/RediStackClientStream.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RediStackClientStream.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/8/21.
6 | //
7 |
8 | import Foundation
9 | import RediStack
10 |
11 | // MARK: - stream function
12 | // stream
13 | extension RediStackClient {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/redis-pro/Common/RedisClient/RedisClientConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisClientConfig.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/3/6.
6 | //
7 |
8 | import Foundation
9 | import RediStack
10 |
11 |
12 | // MARK: -config
13 | extension RediStackClient {
14 | func getConfigList(_ pattern:String = "*") async -> [RedisConfigItemModel] {
15 | logger.info("get redis config list, pattern: \(pattern)...")
16 |
17 | let command: RedisCommand<[RedisConfigItemModel]> = .configList(pattern)
18 | return await send(command, [])
19 | }
20 |
21 | func configRewrite() async -> Bool {
22 | logger.info("redis config rewrite ...")
23 | let command: RedisCommand = .configRewrite()
24 | return await send(command, false)
25 |
26 | }
27 |
28 | func getConfigOne(key:String) async -> String? {
29 | logger.info("get redis config ...")
30 | let command: RedisCommand = .getConfig(key)
31 | return await send(command)
32 | }
33 |
34 |
35 | func setConfig(key:String, value:String) async -> Bool {
36 | logger.info("set redis config, key: \(key), value: \(value)")
37 | let command: RedisCommand = .setConfig(key, value: value)
38 | return await send(command, false)
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/redis-pro/Common/RedisClient/RedisClientLua.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisClientLua.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/7/17.
6 | //
7 |
8 | import Foundation
9 | import RediStack
10 |
11 | // MARK: -lua script
12 | extension RediStackClient {
13 | func eval(_ lua:String) async -> String {
14 | logger.info("lua script eval: \(lua)")
15 | guard lua.count > 3 else {
16 | return "lua script invalid!"
17 | }
18 | begin()
19 | defer {
20 | complete()
21 | }
22 |
23 | do {
24 | let lua = StringHelper.trim(StringHelper.removeStartIgnoreCase(lua, start: "eval"))
25 | if !StringHelper.startWith(lua, start: "'") && !StringHelper.startWith(lua, start: "\"") {
26 | throw BizError("lua script syntax error, demo: \"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}\" 2 key1 key2 arg1 arg2")
27 | }
28 |
29 |
30 | let separator = lua[0]
31 | let scriptLastIndex = lua.lastIndexOf(separator)!
32 | let start = lua.index(lua.startIndex, offsetBy: 1)
33 | let script = String(lua[start.. = RedisCommand(keyword: "EVAL", arguments: respValues, mapValueToResult: {
46 | $0.description
47 | })
48 | return await send(command, "eval error")
49 | } catch {
50 | handleError(error)
51 | }
52 |
53 | return "eval error"
54 | }
55 |
56 |
57 |
58 | func scriptKill() async -> String {
59 | logger.info("lua script kill")
60 |
61 |
62 | let command:RedisCommand = RedisCommand(keyword: "SCRIPT", arguments: [.init(from: "KILL")], mapValueToResult: {
63 | $0.description
64 | })
65 | return await send(command, "script kill error")
66 | }
67 |
68 |
69 |
70 | func scriptFlush() async -> Void {
71 | logger.info("lua script flush")
72 | }
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/redis-pro/Common/RedisClient/RedisClientSSH.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisClientSSH.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/8/6.
6 | //
7 |
8 | import Foundation
9 | import RediStack
10 | import NIO
11 | import NIOSSH
12 | import Logging
13 |
14 | // MARK: -ssh
15 | extension RediStackClient {
16 |
17 | func initSSHConn() async throws -> RedisConnection {
18 | let bindHost = "127.0.0.1"
19 |
20 | let sshTunnel = SSHTunnel(sshHost: self.redisModel.sshHost, sshPort: self.redisModel.sshPort, user: self.redisModel.sshUser, pass: self.redisModel.sshPass, targetHost: self.redisModel.host, targetPort: self.redisModel.port)
21 | let localChannel = try await sshTunnel.openSSHTunnel()
22 |
23 | let localBindPort:Int = localChannel.localAddress?.port ?? 0
24 | self.logger.info("init forwarding server success, local port: \(localBindPort)")
25 | self.sshLocalChannel = localChannel
26 |
27 | return try await initConn(host: bindHost, port: localBindPort, username: self.redisModel.username, pass: self.redisModel.password, database: self.redisModel.database)
28 | }
29 |
30 |
31 | func initSSHPool() async throws -> RedisConnectionPool {
32 | let bindHost = "127.0.0.1"
33 |
34 | let sshTunnel = SSHTunnel(sshHost: self.redisModel.sshHost, sshPort: self.redisModel.sshPort, user: self.redisModel.sshUser, pass: self.redisModel.sshPass, targetHost: self.redisModel.host, targetPort: self.redisModel.port)
35 | let localChannel = try await sshTunnel.openSSHTunnel()
36 |
37 | let localBindPort:Int = localChannel.localAddress?.port ?? 0
38 | self.logger.info("init forwarding server success, local port: \(localBindPort)")
39 | self.sshLocalChannel = localChannel
40 |
41 | return try initPool(host: bindHost, port: localBindPort, username: self.redisModel.username, pass: self.redisModel.password, database: self.redisModel.database)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/redis-pro/Common/RedisClient/RedisClientScan.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisClientScan.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/3/6.
6 | //
7 |
8 | import Foundation
9 |
10 | extension RediStackClient {
11 |
12 | func isMatchAll(_ keywords:String?) -> Bool {
13 | guard let keywords = keywords else {
14 | return true
15 | }
16 | return keywords.isEmpty || keywords == "*" || keywords == "**"
17 | }
18 |
19 | func isScan(_ keywords:String) -> Bool {
20 | return keywords.isEmpty || keywords.contains("*") || keywords.contains("?")
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/redis-pro/Common/RedisClient/RedisClientSlowLog.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisClientSlowLog.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/3/6.
6 | //
7 |
8 | import Foundation
9 | import RediStack
10 |
11 | // MARK: -slow log
12 | extension RediStackClient {
13 | func slowLogReset() async -> Bool {
14 | logger.info("slow log reset ...")
15 | let command: RedisCommand = .slowlogReset()
16 | return await send(command, false)
17 |
18 | }
19 |
20 | func slowLogLen() async -> Int {
21 | logger.info("get slow log len ...")
22 | let command: RedisCommand = .slowlogLen()
23 | return await send(command, 0)
24 | }
25 |
26 | func getSlowLog(_ size:Int) async -> [SlowLogModel] {
27 | logger.info("get slow log list ...")
28 | let command: RedisCommand<[SlowLogModel]> = .slowlogList(size)
29 | return await send(command, [])
30 | }
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/redis-pro/Common/RedisClient/RedisClientSystem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisClientSystem.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/3/6.
6 | //
7 |
8 | import Foundation
9 | import RediStack
10 |
11 | // MARK: - system function
12 | // system
13 | extension RediStackClient {
14 |
15 | func selectDB(_ database: Int) async -> Bool {
16 | self.logger.info("select db: \(database)")
17 | self.redisModel.database = database
18 |
19 | self.connPool?.close()
20 | self.connPool = nil
21 |
22 | let command: RedisCommand = .select(database: database)
23 | let _ = await send(command)
24 | return true
25 | }
26 |
27 | func databases() async -> Int {
28 |
29 | let command: RedisCommand = .databases()
30 | return await send(command, 0)
31 | }
32 |
33 | func dbsize() async -> Int {
34 |
35 | let command: RedisCommand = .dbsize()
36 | return await send(command, 0)
37 | }
38 |
39 | func flushDB() async -> Bool {
40 | let command: RedisCommand = .flushDB()
41 | return await send(command, false)
42 | }
43 |
44 | func clientKill(_ clientModel:ClientModel) async -> Bool {
45 |
46 | let command: RedisCommand = .clientKill(clientModel.addr)
47 | return await send(command, false)
48 | }
49 |
50 | func clientList() async -> [ClientModel] {
51 |
52 | let command: RedisCommand<[ClientModel]> = .clientList()
53 | return await send(command, [])
54 | }
55 |
56 | func info() async -> [RedisInfoModel] {
57 | let command: RedisCommand<[RedisInfoModel]> = .info()
58 | return await send(command, [])
59 | }
60 |
61 | func resetState() async -> Bool {
62 | logger.info("reset state...")
63 | let command: RedisCommand = .resetState()
64 | return await send(command, false)
65 | }
66 |
67 | func ping() async -> Bool {
68 | let command: RedisCommand = .ping()
69 | return await send(command) == "PONG"
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/redis-pro/Common/Settings/ColorSchemeEnum.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorSchemeEnum.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/9.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | enum ColorSchemeEnum:String,CaseIterable{
12 | case SYSTEM = "system"
13 | case DARK = "dark"
14 | case LIGHT = "light"
15 | }
16 |
--------------------------------------------------------------------------------
/redis-pro/Common/Settings/RedisFavoriteDefaultSelectTypeEnum.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisFavoriteDefaultSelectTypeEnum.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/5/2.
6 | //
7 |
8 | import Foundation
9 |
10 | enum RedisFavoriteDefaultSelectTypeEnum:String{
11 | case LAST = "_last"
12 | case ID = "id"
13 | }
14 |
--------------------------------------------------------------------------------
/redis-pro/Common/Stopwatch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stopwatch.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/28.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Stopwatch {
11 | private var start:Int64 = 0
12 |
13 | init() {
14 | let timeInterval: TimeInterval = Date().timeIntervalSince1970
15 | start = CLongLong(round(timeInterval*1000))
16 | }
17 |
18 | static func createStarted() -> Stopwatch {
19 | return Stopwatch()
20 | }
21 |
22 | func elapsedMillis() -> Int64 {
23 | let timeInterval: TimeInterval = Date().timeIntervalSince1970
24 | return CLongLong(round(timeInterval * 1000)) - self.start
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/redis-pro/Common/StringFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringFormatter.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/7.
6 | //
7 |
8 | import Foundation
9 |
10 | public class StringFormatter: Formatter {
11 |
12 | override public func string(for obj: Any?) -> String? {
13 | var retVal: String?
14 |
15 | if let str = obj as? String {
16 | retVal = str.trimmingCharacters(in: .whitespacesAndNewlines)
17 | } else {
18 | retVal = nil
19 | }
20 |
21 | return retVal
22 | }
23 |
24 | override public func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer?) -> Bool {
25 | return true
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/redis-pro/Common/UserDefaults/UserDefaultsKeysEnum.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaultKeys.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/1/29.
6 | //
7 |
8 | import Foundation
9 |
10 |
11 | enum UserDefaulsKeysEnum: String {
12 | case RedisFavoriteListKey = "RedisFavoriteListKey"
13 | case RedisLastUseIdKey = "RedisLastUseIdKey"
14 | // 列表默认选中类型, last:最后一个, id: 上次成功连接的redis id
15 | case RedisFavoriteDefaultSelectType = "User.defaultFavorite"
16 | // color scheme
17 | case AppColorScheme = "App.ColorScheme"
18 | // string editor default max length
19 | case AppStringMaxLength = "App.StringMaxLength"
20 | // keepalive second
21 | case AppKeepalive = "App.Keepalive"
22 | // fast page
23 | case AppFastPage = "App.FastPage"
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/redis-pro/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleURLTypes
20 |
21 |
22 | CFBundleURLSchemes
23 |
24 | redis-pro
25 |
26 |
27 |
28 | CFBundleVersion
29 | $(CURRENT_PROJECT_VERSION)
30 | LSApplicationCategoryType
31 | public.app-category.developer-tools
32 | LSMinimumSystemVersion
33 | $(MACOSX_DEPLOYMENT_TARGET)
34 |
35 |
36 |
--------------------------------------------------------------------------------
/redis-pro/Language/MButton.strings/en.xcloc/Source Contents/redis-pro/Views/Components/en.lproj/MButton.strings:
--------------------------------------------------------------------------------
1 | /*
2 | MButton.strings
3 | redis-pro
4 |
5 | Created by chengpanwang on 2021/6/11.
6 |
7 | */
8 | "hello" = "HELLO";
9 |
--------------------------------------------------------------------------------
/redis-pro/Language/MButton.strings/en.xcloc/Source Contents/redis-pro/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Bundle name */
2 | "CFBundleName" = "redis-pro";
3 |
--------------------------------------------------------------------------------
/redis-pro/Language/MButton.strings/en.xcloc/Source Contents/redis-pro/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Language/MButton.strings/en.xcloc/Source Contents/redis-pro/en.lproj/Localizable.strings
--------------------------------------------------------------------------------
/redis-pro/Language/MButton.strings/en.xcloc/contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "developmentRegion" : "en",
3 | "project" : "redis-pro.xcodeproj",
4 | "targetLocale" : "en",
5 | "toolInfo" : {
6 | "toolBuildNumber" : "12E262",
7 | "toolID" : "com.apple.dt.xcode",
8 | "toolName" : "Xcode",
9 | "toolVersion" : "12.5"
10 | },
11 | "version" : "1.0"
12 | }
--------------------------------------------------------------------------------
/redis-pro/Language/MButton.strings/zh-Hans.xcloc/Source Contents/redis-pro/Views/Components/en.lproj/MButton.strings:
--------------------------------------------------------------------------------
1 | /*
2 | MButton.strings
3 | redis-pro
4 |
5 | Created by chengpanwang on 2021/6/11.
6 |
7 | */
8 | "hello" = "HELLO";
9 |
--------------------------------------------------------------------------------
/redis-pro/Language/MButton.strings/zh-Hans.xcloc/Source Contents/redis-pro/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Bundle name */
2 | "CFBundleName" = "redis-pro";
3 |
--------------------------------------------------------------------------------
/redis-pro/Language/MButton.strings/zh-Hans.xcloc/Source Contents/redis-pro/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Language/MButton.strings/zh-Hans.xcloc/Source Contents/redis-pro/en.lproj/Localizable.strings
--------------------------------------------------------------------------------
/redis-pro/Language/MButton.strings/zh-Hans.xcloc/contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "developmentRegion" : "en",
3 | "project" : "redis-pro.xcodeproj",
4 | "targetLocale" : "zh-Hans",
5 | "toolInfo" : {
6 | "toolBuildNumber" : "12E262",
7 | "toolID" : "com.apple.dt.xcode",
8 | "toolName" : "Xcode",
9 | "toolVersion" : "12.5"
10 | },
11 | "version" : "1.0"
12 | }
--------------------------------------------------------------------------------
/redis-pro/Language/redis-pro/en.xcloc/Source Contents/redis-pro/Language/en.lproj/Localizble.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizble.strings
3 | redis-pro
4 |
5 | Created by chengpanwang on 2021/6/11.
6 |
7 | */
8 |
9 | "hello" = "HELLO";
10 |
--------------------------------------------------------------------------------
/redis-pro/Language/redis-pro/en.xcloc/Source Contents/redis-pro/Language/en.lproj/RedisInfo.strings:
--------------------------------------------------------------------------------
1 | /*
2 | RedisInfo.strings
3 | redis-pro
4 |
5 | Created by chengpanwang on 2021/6/11.
6 |
7 | */
8 |
--------------------------------------------------------------------------------
/redis-pro/Language/redis-pro/en.xcloc/Source Contents/redis-pro/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Bundle name */
2 | "CFBundleName" = "redis-pro";
3 |
--------------------------------------------------------------------------------
/redis-pro/Language/redis-pro/en.xcloc/Source Contents/redis-pro/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Language/redis-pro/en.xcloc/Source Contents/redis-pro/en.lproj/Localizable.strings
--------------------------------------------------------------------------------
/redis-pro/Language/redis-pro/en.xcloc/contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "developmentRegion" : "en",
3 | "project" : "redis-pro.xcodeproj",
4 | "targetLocale" : "en",
5 | "toolInfo" : {
6 | "toolBuildNumber" : "12E262",
7 | "toolID" : "com.apple.dt.xcode",
8 | "toolName" : "Xcode",
9 | "toolVersion" : "12.5"
10 | },
11 | "version" : "1.0"
12 | }
--------------------------------------------------------------------------------
/redis-pro/Language/redis-pro/zh-Hans.xcloc/Source Contents/redis-pro/Language/en.lproj/Localizble.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizble.strings
3 | redis-pro
4 |
5 | Created by chengpanwang on 2021/6/11.
6 |
7 | */
8 |
9 | "hello" = "HELLO";
10 |
--------------------------------------------------------------------------------
/redis-pro/Language/redis-pro/zh-Hans.xcloc/Source Contents/redis-pro/Language/en.lproj/RedisInfo.strings:
--------------------------------------------------------------------------------
1 | /*
2 | RedisInfo.strings
3 | redis-pro
4 |
5 | Created by chengpanwang on 2021/6/11.
6 |
7 | */
8 |
--------------------------------------------------------------------------------
/redis-pro/Language/redis-pro/zh-Hans.xcloc/Source Contents/redis-pro/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Bundle name */
2 | "CFBundleName" = "redis-pro";
3 |
--------------------------------------------------------------------------------
/redis-pro/Language/redis-pro/zh-Hans.xcloc/Source Contents/redis-pro/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Language/redis-pro/zh-Hans.xcloc/Source Contents/redis-pro/en.lproj/Localizable.strings
--------------------------------------------------------------------------------
/redis-pro/Language/redis-pro/zh-Hans.xcloc/contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "developmentRegion" : "en",
3 | "project" : "redis-pro.xcodeproj",
4 | "targetLocale" : "zh-Hans",
5 | "toolInfo" : {
6 | "toolBuildNumber" : "12E262",
7 | "toolID" : "com.apple.dt.xcode",
8 | "toolName" : "Xcode",
9 | "toolVersion" : "12.5"
10 | },
11 | "version" : "1.0"
12 | }
--------------------------------------------------------------------------------
/redis-pro/Model/ClientModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClientModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/18.
6 | //
7 | //"id", "name", "addr", "laddr", "fd", "age", "idle", "flags", "db", "sub", "psub", "multi", "qbuf", "qbuf-free", "obl", "oll", "omem", "events", "cmd", "argv-mem", "tot-mem", "redir", "user"
8 |
9 | import Foundation
10 |
11 | public class ClientModel:NSObject, Identifiable {
12 | @objc public var id:String = ""
13 | @objc var name:String = ""
14 | @objc var addr:String = ""
15 | @objc var laddr:String = ""
16 | @objc var fd:String = ""
17 | @objc var age:String = ""
18 | @objc var idle:String = ""
19 | // O : 客户端是 MONITOR 模式下的附属节点(slave)
20 | // S : 客户端是一般模式下(normal)的附属节点
21 | // M : 客户端是主节点(master)
22 | // x : 客户端正在执行事务
23 | // b : 客户端正在等待阻塞事件
24 | // i : 客户端正在等待 VM I/O 操作(已废弃)
25 | // d : 一个受监视(watched)的键已被修改, EXEC 命令将失败
26 | // c : 在将回复完整地写出之后,关闭链接
27 | // u : 客户端未被阻塞(unblocked)
28 | // A : 尽可能快地关闭连接
29 | // N : 未设置任何 flag
30 | @objc var flags:String = ""
31 | @objc var db:String = ""
32 | @objc var sub:String = ""
33 | @objc var psub:String = ""
34 | @objc var multi:String = ""
35 | @objc var qbuf:String = ""
36 | @objc var qbuf_free:String = ""
37 | @objc var obl:String = ""
38 | @objc var oll:String = ""
39 | @objc var omem:String = ""
40 | // r : 客户端套接字(在事件 loop 中)是可读的(readable)
41 | // w : 客户端套接字(在事件 loop 中)是可写的(writeable)
42 | @objc var events:String = ""
43 | @objc var cmd:String = ""
44 | @objc var argv_mem:String = ""
45 | @objc var tot_mem:String = ""
46 | @objc var redir:String = ""
47 | @objc var user:String = ""
48 |
49 | override init() {
50 | }
51 |
52 | init(line:String) {
53 | let kvStrArray = line.components(separatedBy: .whitespaces)
54 |
55 | var item:[String:String] = [String:String]()
56 | kvStrArray.forEach({kvStr in
57 | if kvStr.contains("=") {
58 | let kv = kvStr.components(separatedBy: "=")
59 | if kv.count == 2 {
60 | item[kv[0]] = kv[1]
61 | }
62 | }
63 | })
64 |
65 | self.id = item["id"] ?? ""
66 | self.name = item["name"] ?? ""
67 | self.addr = item["addr"] ?? ""
68 | self.laddr = item["laddr"] ?? ""
69 | self.fd = item["fd"] ?? ""
70 | self.age = item["age"] ?? ""
71 | self.idle = item["idle"] ?? ""
72 | self.flags = item["flags"] ?? ""
73 | self.db = item["db"] ?? ""
74 | self.sub = item["sub"] ?? ""
75 | self.psub = item["psub"] ?? ""
76 | self.multi = item["multi"] ?? ""
77 | self.qbuf = item["qbuf"] ?? ""
78 | self.qbuf_free = item["qbuf-free"] ?? ""
79 | self.obl = item["obl"] ?? ""
80 | self.oll = item["oll"] ?? ""
81 | self.omem = item["omem"] ?? ""
82 | self.events = item["events"] ?? ""
83 | self.cmd = item["cmd"] ?? ""
84 | self.argv_mem = item["argv-mem"] ?? ""
85 | self.tot_mem = item["tot-mem"] ?? ""
86 | self.redir = item["redir"] ?? ""
87 | self.user = item["user"] ?? ""
88 |
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/redis-pro/Model/GlobalContext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GlobalContext.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/15.
6 | //
7 |
8 | import Foundation
9 | import RediStack
10 |
11 | class GlobalContext:ObservableObject, CustomStringConvertible {
12 | @Published var alertVisible:Bool = false
13 | var alertTitle:String = ""
14 | var alertMessage:String = ""
15 | var showSecondButton:Bool = false
16 | var primaryButtonText:String = "Ok"
17 | var secondButtonText:String = "Cancel"
18 | var primaryAction:() throws -> Void = {}
19 |
20 | @Published var loading:Bool = false
21 |
22 |
23 | func showError(_ error:Error) -> Void {
24 | self.alertVisible = true
25 | self.alertTitle = "Error!"
26 | self.primaryButtonText = "Ok"
27 | self.secondButtonText = "Cancel"
28 |
29 | if error is BizError {
30 | alertMessage = (error as! BizError).message
31 | } else if error is RedisError {
32 | alertMessage = (error as! RedisError).message
33 | } else {
34 | alertMessage = "\(error)"
35 | }
36 | }
37 |
38 | func showAlert(_ alertTitle:String, alertMessage:String = "", primaryAction: @escaping () throws -> Void = {}, primaryButtonText:String = "Ok") -> Void {
39 | self.alertVisible = true
40 | self.alertTitle = alertTitle
41 | self.alertMessage = alertMessage
42 | self.primaryAction = primaryAction
43 | self.primaryButtonText = primaryButtonText
44 | }
45 |
46 | func confirm(_ alertTitle:String, alertMessage:String = "", primaryAction: @escaping () throws -> Void = {}, primaryButton:String = "Ok") -> Void {
47 | self.alertVisible = true
48 | self.alertTitle = alertTitle
49 | self.alertMessage = alertMessage
50 | self.primaryAction = primaryAction
51 | self.primaryButtonText = primaryButton
52 | self.showSecondButton = true
53 | }
54 |
55 | var description: String {
56 | return "GlobalContext:[alertVisible:\(alertVisible), loading:\(loading), showSecondButton: \(showSecondButton), primaryButtonText:\(primaryButtonText)]"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/redis-pro/Model/Page.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Page.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/13.
6 | //
7 |
8 | import Foundation
9 |
10 | class Page: CustomStringConvertible, Equatable {
11 | static func == (lhs: Page, rhs: Page) -> Bool {
12 | return lhs.current == rhs.current && lhs.size == rhs.size && lhs.keywords == rhs.keywords
13 | }
14 |
15 | var current: Int = 1
16 | var size: Int = 50
17 | var total: Int = 0
18 | var keywords: String = ""
19 |
20 | var totalPage:Int {
21 | total < 1 ? 1 : (total % size == 0 ? total / size : total / size + 1)
22 | }
23 |
24 | var start: Int {
25 | (self.current - 1 ) * self.size
26 | }
27 | var end: Int {
28 | self.current * self.size
29 | }
30 |
31 | var hasPrev:Bool {
32 | totalPage > 1 && current > 1
33 | }
34 | var hasNext:Bool {
35 | totalPage > 1 && current < totalPage
36 | }
37 |
38 | var description: String {
39 | return "Page:[current:\(current), size:\(size), total:\(total)]"
40 | }
41 |
42 |
43 | func reset() {
44 | self.current = 1
45 | self.total = 0
46 | }
47 |
48 | func firstPage() {
49 | self.current = 1
50 | }
51 |
52 | func nextPage() -> Void {
53 | self.current += 1
54 | }
55 |
56 | func prevPage() -> Void {
57 | if self.current < 2 {
58 | return
59 | }
60 | self.current -= 1
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/redis-pro/Model/RedisConfigItemModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisConfigItemModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/21.
6 | //
7 |
8 | import Foundation
9 |
10 | public class RedisConfigItemModel:NSObject, Identifiable {
11 | @objc var key:String = ""
12 | @objc var value:String = ""
13 |
14 | override init() {
15 | }
16 |
17 | init(key:String?, value:String?) {
18 | self.key = key ?? ""
19 | self.value = value ?? ""
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/redis-pro/Model/RedisHashEntryModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisHashEntryModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/16.
6 | //
7 |
8 | import Foundation
9 |
10 | class RedisHashEntryModel:NSObject, Identifiable, ObservableObject {
11 | @objc @Published var field:String = ""
12 | @objc @Published var value:String = ""
13 | @Published var isNew = false
14 |
15 | var id:String {
16 | self.field
17 | }
18 |
19 | override init() {
20 | }
21 |
22 | init(field:String, value:String?) {
23 | self.field = field
24 | self.value = value ?? ""
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/redis-pro/Model/RedisInfoItemModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisInfoItemModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/19.
6 | //
7 |
8 | import Foundation
9 |
10 | public class RedisInfoItemModel: NSObject {
11 | @objc var section:String = ""
12 | @objc var key:String = ""
13 | @objc var value:String = ""
14 |
15 | @objc var desc:String {
16 | let tip:String = NSLocalizedString("REDIS_INFO_\(section)_\(key)".uppercased(), tableName: nil, bundle: Bundle.main, value: "", comment: "")
17 | return tip
18 | }
19 |
20 | override init() {
21 | }
22 |
23 | init(section:String, key:String, value:String) {
24 | self.section = section
25 | self.key = key
26 | self.value = value
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/redis-pro/Model/RedisInfoModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisInfoModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/10.
6 | //
7 | import Foundation
8 |
9 | public class RedisInfoModel:NSObject, Identifiable {
10 | public var id = UUID()
11 | var section:String = ""
12 | var infos:[RedisInfoItemModel] = [RedisInfoItemModel]()
13 |
14 | override init() {
15 | }
16 |
17 | init(section:String) {
18 | self.section = section
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/redis-pro/Model/RedisInsanceModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisInsanceModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/1/25.
6 | //
7 |
8 | import SwiftUI
9 | import Foundation
10 | import NIO
11 | import RediStack
12 | import Logging
13 | import ComposableArchitecture
14 |
15 | class RedisInstanceModel: Identifiable {
16 | var redisModel:RedisModel
17 | private var rediStackClient:RediStackClient?
18 | private var appContextviewStore:ViewStoreOf?
19 | private var settingViewStore:ViewStoreOf?
20 |
21 | let logger = Logger(label: "redis-instance")
22 |
23 |
24 | init(redisModel: RedisModel) {
25 | self.redisModel = redisModel
26 | logger.info("redis instance model init")
27 | }
28 |
29 | convenience init(_ redisModel:RedisModel, settingViewStore: ViewStoreOf?) {
30 | self.init(redisModel: redisModel)
31 | self.settingViewStore = settingViewStore
32 | }
33 |
34 | convenience init(_ redisClient: RediStackClient, settingViewStore: ViewStoreOf?) {
35 | self.init(redisModel: redisClient.redisModel)
36 | self.rediStackClient = redisClient
37 | self.settingViewStore = settingViewStore
38 | }
39 |
40 |
41 | func setAppStore(_ appStore: StoreOf) {
42 | let globalStore = appStore.scope(state: \.globalState, action: AppStore.Action.globalAction)
43 | self.appContextviewStore = ViewStore(globalStore, observe: { $0 })
44 | }
45 |
46 | // get client
47 | func getClient() -> RediStackClient {
48 | if let client = rediStackClient {
49 | return client
50 | }
51 |
52 | return initRedisClient(self.redisModel)
53 | }
54 |
55 | // init redis client
56 | func initRedisClient(_ redisModel: RedisModel) -> RediStackClient {
57 |
58 | logger.info("init new redis client, redisModel: \(redisModel)")
59 | self.redisModel = redisModel
60 | let client = RediStackClient(redisModel, settingViewStore: settingViewStore)
61 | client.setAppContextStore(self.appContextviewStore)
62 |
63 | self.rediStackClient = client
64 | return client
65 | }
66 |
67 | func connect(_ redisModel:RedisModel) async -> Bool {
68 | logger.info("connect to redis server: \(redisModel)")
69 | do {
70 | let r = await testConnect(redisModel)
71 | if r {
72 | let _ = try await initRedisClient(redisModel).getConn()
73 | }
74 |
75 | return r
76 | } catch {
77 | Messages.show(error)
78 | return false
79 | }
80 | }
81 |
82 | func testConnect(_ redisModel:RedisModel) async -> Bool {
83 | defer {
84 | self.close()
85 | }
86 | logger.info("test connect to redis server: \(redisModel)")
87 | return await initRedisClient(redisModel).testConn()
88 | }
89 |
90 | func close() -> Void {
91 | logger.info("redis stack client close...")
92 | rediStackClient?.close()
93 | rediStackClient = nil
94 | }
95 |
96 | func shutdown() -> Void {
97 | rediStackClient?.shutdown()
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/redis-pro/Model/RedisKeyModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisKeyModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2021/4/4.
6 | //
7 |
8 | import Foundation
9 | import Cocoa
10 |
11 | class RedisKeyModel: NSObject, Identifiable {
12 | @objc var key: String = ""
13 | @objc var type: String = RedisKeyTypeEnum.STRING.rawValue
14 | var ttl: Int = -1
15 | var isNew: Bool = false
16 |
17 | private var _id:String = ""
18 | var id:String {
19 | if isNew {
20 | return _id
21 | }
22 | return key
23 | }
24 |
25 | convenience init(_ key:String, type:String) {
26 | self.init()
27 | self.key = key
28 | self.type = type
29 | }
30 |
31 | func copyValue(_ redisKeyModel:RedisKeyModel) {
32 | self.isNew = redisKeyModel.isNew
33 | self.key = redisKeyModel.key
34 | self.type = redisKeyModel.type
35 | }
36 |
37 | func initNew() -> Void {
38 | self.isNew = true
39 | self.key = generateKey()
40 | self._id = self.key
41 | self.type = RedisKeyTypeEnum.STRING.rawValue
42 | }
43 |
44 | private func generateKey() -> String {
45 | return "NEW_KEY_\(Date().millis)"
46 | }
47 |
48 | override var description: String {
49 | return "RedisKeyModel:[key:\(key), type:\(type), isNew:\(isNew)]"
50 | }
51 |
52 | public static func == (lhs: RedisKeyModel, rhs: RedisKeyModel) -> Bool {
53 | // 需要比较的值
54 | return lhs.id == rhs.id
55 | }
56 | }
57 |
58 | extension RedisKeyModel {
59 | // type 颜色
60 | @objc var textColor: NSColor {
61 | switch type {
62 | case RedisKeyTypeEnum.STRING.rawValue:
63 | return NSColor.systemBlue
64 | case RedisKeyTypeEnum.HASH.rawValue:
65 | return NSColor.systemPink
66 | case RedisKeyTypeEnum.LIST.rawValue:
67 | return NSColor.systemOrange
68 | case RedisKeyTypeEnum.SET.rawValue:
69 | return NSColor.systemGreen
70 | case RedisKeyTypeEnum.ZSET.rawValue:
71 | return NSColor.systemTeal
72 | default:
73 | return NSColor.systemBrown
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/redis-pro/Model/RedisKeyValueModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisKeyValueModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/7.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct RedisKeyValueModel {
12 | var redisKey:RedisKeyModel
13 | var redisValue:Any
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/redis-pro/Model/RedisListItemModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisListItemModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/5/22.
6 | //
7 |
8 | import Foundation
9 |
10 | class RedisListItemModel: NSObject {
11 |
12 | @objc var index:Int = 0
13 | @objc var value:String = ""
14 |
15 | override init() {
16 | }
17 |
18 | init(_ index:Int, _ value:String) {
19 | self.index = index
20 | self.value = value
21 | }
22 |
23 | static func == (lhs: RedisListItemModel, rhs: RedisListItemModel) -> Bool {
24 | return lhs.index == rhs.index && lhs.value == rhs.value
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/redis-pro/Model/RedisZSetItemModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisSetItemModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/16.
6 | //
7 |
8 | import Foundation
9 |
10 | class RedisZSetItemModel:NSObject, ObservableObject, Identifiable {
11 | @objc @Published var value:String = ""
12 | @objc @Published var score:String = ""
13 |
14 | var id:String {
15 | self.value
16 | }
17 |
18 | override init() {
19 | }
20 |
21 | init(value:String, score:String) {
22 | self.score = score
23 | self.value = value
24 | }
25 |
26 |
27 | override var description: String {
28 | return "ZSetItem:[value:\(value), score:\(score)]"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/redis-pro/Model/ScanModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScanModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/1.
6 | //
7 |
8 | import Foundation
9 | import Logging
10 |
11 | class ScanModel:ObservableObject, CustomStringConvertible, Equatable {
12 | static func == (lhs: ScanModel, rhs: ScanModel) -> Bool {
13 | return lhs.cursor == rhs.cursor && lhs.size == rhs.size && lhs.keywords == rhs.keywords
14 | }
15 |
16 | var current:Int = 1
17 | @Published var total:Int = 0
18 | @Published var cursor:Int = 0
19 | @Published var size:Int = 50
20 | @Published var keywords:String = ""
21 | private var cursorHistory:[Int] = [Int]()
22 |
23 | let logger = Logger(label: "scan-model")
24 |
25 | var totalPage:Int {
26 | if total <= 0 {
27 | return 1
28 | }
29 |
30 | if total % size == 0 {
31 | return total / size
32 | } else {
33 | return total / size + 1
34 | }
35 | }
36 |
37 | var hasNext:Bool {
38 | self.cursor != 0
39 | // self.current < totalPage
40 | }
41 |
42 | var hasPrev:Bool {
43 | self.cursorHistory.count > 0
44 | // self.current > 1
45 | }
46 |
47 |
48 | var description: String {
49 | return "ScanModel:[cursor:\(cursor), size:\(size), keywords:\(keywords), history: \(cursorHistory), current: \(current)]"
50 | }
51 |
52 | func reset() -> Void {
53 | self.current = 1
54 | self.cursor = 0
55 | self.cursorHistory.removeAll()
56 | }
57 |
58 | func nextPage() -> Void {
59 | self.current += 1
60 | self.cursorHistory.append(self.cursor)
61 | }
62 |
63 | func prevPage() -> Void {
64 | self.current -= 1
65 | //
66 | if self.current <= 1 {
67 | self.current = 1
68 | self.cursor = 0
69 | cursorHistory.removeAll()
70 | }
71 |
72 | let index = self.current - 1
73 | self.cursor = (index == 0 || cursorHistory.count == 0) ? 0 : cursorHistory[index - 1]
74 | self.cursorHistory.removeSubrange(index...)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/redis-pro/Model/SlowLogModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SlowLogModel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/14.
6 | //
7 |
8 | import Foundation
9 |
10 | public class SlowLogModel:NSObject, Identifiable {
11 | @objc public var id:String = ""
12 | @objc var timestamp:Int = -1
13 | @objc var execTime:String = ""
14 | @objc var cmd:String = ""
15 | @objc var client:String = ""
16 | @objc var clientName:String = ""
17 |
18 | @objc var timestampFormat:String {
19 | timestamp == -1 ? MTheme.NULL_STRING : DateHelper.formatDateTime(timestamp: self.timestamp)
20 | }
21 |
22 | override init() {
23 | }
24 |
25 | init(id:String?, timestamp:Int?, execTime:String?, cmd:String?, client:String?, clientName:String?) {
26 | self.id = id ?? MTheme.NULL_STRING
27 | self.timestamp = timestamp ?? -1
28 | self.execTime = execTime ?? MTheme.NULL_STRING
29 | self.cmd = cmd ?? MTheme.NULL_STRING
30 | self.client = client ?? MTheme.NULL_STRING
31 | self.clientName = clientName ?? MTheme.NULL_STRING
32 | }
33 |
34 |
35 | static func == (lhs: SlowLogModel, rhs: SlowLogModel) -> Bool {
36 | return lhs.id == rhs.id
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/redis-pro/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/redis-pro/Store/AppContextStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GlobalStore.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/6/2.
6 | //
7 |
8 |
9 | import Logging
10 | import Foundation
11 | import ComposableArchitecture
12 |
13 | private let logger = Logger(label: "app-context-store")
14 |
15 | @Reducer
16 | struct AppContextStore {
17 | struct State: Equatable {
18 | var loading:Bool = false
19 | var loadingCount:Int = 0
20 |
21 | init() {
22 | logger.info("app context state init ...")
23 | }
24 | }
25 |
26 | enum Action: Equatable {
27 | case show
28 | case hide
29 | case _hide
30 | }
31 |
32 | var body: some Reducer {
33 |
34 | Reduce { state, action in
35 | switch action {
36 | case .show:
37 | if state.loadingCount <= 0 {
38 | state.loading = true
39 | }
40 |
41 | state.loadingCount += 1
42 | return .none
43 | case .hide:
44 | return .run { send in
45 | try await Task.sleep(nanoseconds: 100_000_000)
46 | await send(._hide)
47 | }
48 |
49 | case ._hide:
50 | state.loadingCount -= 1
51 | if state.loadingCount <= 0 {
52 | state.loading = false
53 | state.loadingCount = 0
54 | }
55 |
56 | return .none
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/redis-pro/Store/DatabaseStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatabaseStore.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2022/5/6.
6 | //
7 |
8 | import Logging
9 | import Foundation
10 | import ComposableArchitecture
11 |
12 | private let logger = Logger(label: "database-store")
13 |
14 |
15 | struct DatabaseStore: Reducer {
16 | struct State: Equatable {
17 | var database: Int = 0
18 | var databases:Int = 16
19 |
20 | init() {
21 | logger.info("database state init ...")
22 | }
23 | }
24 |
25 |
26 | enum Action:BindableAction, Equatable {
27 | case initial
28 | case getDatabases
29 | case setDB(Int)
30 | case selectDB(Int)
31 | case onDBChange(Int)
32 | case none
33 | case binding(BindingAction)
34 | }
35 |
36 | @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel
37 | var mainQueue: AnySchedulerOf = .main
38 |
39 | var body: some Reducer {
40 | BindingReducer()
41 | Reduce { state, action in
42 | switch action {
43 | // 初始化已设置的值
44 | case .initial:
45 | logger.info("database store initial...")
46 | state.database = redisInstanceModel.redisModel.database
47 | return .run { send in
48 | await send(.getDatabases)
49 | }
50 | case .getDatabases:
51 | return .run { send in
52 | let r = await redisInstanceModel.getClient().databases()
53 | await send(.setDB(r))
54 | }
55 | case let .setDB(databases):
56 | state.databases = databases
57 | return .none
58 | case let .selectDB(database):
59 | state.database = database
60 |
61 | return .run { send in
62 | let r = await redisInstanceModel.getClient().selectDB(database)
63 | if r {
64 | await send(.onDBChange(database))
65 | }
66 | }
67 |
68 | case .onDBChange:
69 | return .none
70 | case .none:
71 | return .none
72 | case .binding:
73 | return .none
74 | }
75 | }
76 | }
77 |
78 |
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/redis-pro/Store/DependencyKeys.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DependencyKeys.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2023/7/2.
6 | //
7 |
8 | import Foundation
9 | import Dependencies
10 | import ComposableArchitecture
11 |
12 | private enum RedisInstanceKey: DependencyKey {
13 | static let liveValue = RedisInstanceModel(redisModel: RedisModel())
14 | }
15 |
16 | private enum RedisClientKey: DependencyKey {
17 | static let liveValue = RediStackClient(RedisModel())
18 | }
19 |
20 | /// app 上下文
21 | struct AppContext {
22 | let store: StoreOf
23 | }
24 | private enum AppContextKey: DependencyKey {
25 | static let liveValue = Store(initialState: AppContextStore.State()) {
26 | AppContextStore()
27 | }
28 | }
29 |
30 |
31 | extension DependencyValues {
32 | var redisInstance: RedisInstanceModel {
33 | get { self[RedisInstanceKey.self] }
34 | set { self[RedisInstanceKey.self] = newValue }
35 | }
36 |
37 | var redisClient: RediStackClient {
38 | get { self[RedisClientKey.self] }
39 | set { self[RedisClientKey.self] = newValue }
40 | }
41 |
42 | var appContext: StoreOf {
43 | get { self[AppContextKey.self] }
44 | set { self[AppContextKey.self] = newValue }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/redis-pro/Store/KeyObjectStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjectEncodingStore.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2023/7/23.
6 | //
7 |
8 | import Logging
9 | import Foundation
10 | import ComposableArchitecture
11 |
12 | private let logger = Logger(label: "key-object-store")
13 |
14 | struct KeyObjectStore: Reducer {
15 |
16 | struct State: Equatable {
17 | var key: String = ""
18 | var encoding: String = ""
19 |
20 | var redisKeyModel:RedisKeyModel {
21 | get {
22 | let r = RedisKeyModel()
23 | r.key = key
24 | return r
25 | }
26 | set(n) {
27 | key = n.key
28 | }
29 | }
30 |
31 | init() {
32 | logger.info("key object init ...")
33 | }
34 | }
35 |
36 |
37 | enum Action: Equatable {
38 | case initial
39 | case refresh
40 | case setKey(String)
41 | case getEncoding
42 | case setEncoding(String)
43 | }
44 |
45 | @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel
46 |
47 | var body: some Reducer {
48 | Reduce { state, action in
49 | switch action {
50 | // 初始化已设置的值
51 | case .initial:
52 | logger.info("key object initial...")
53 | return .none
54 |
55 | case .refresh:
56 | return .run { send in
57 | await send(.getEncoding)
58 | }
59 |
60 | case let .setKey(key):
61 | state.key = key
62 | return .none
63 |
64 | case .getEncoding:
65 | let key = state.key
66 | return .run { send in
67 | let r = await redisInstanceModel.getClient().objectEncoding(key)
68 | return await send(.setEncoding(r))
69 | }
70 |
71 | case let .setEncoding(encoding):
72 | state.encoding = encoding
73 | return .none
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/redis-pro/Store/LoadingStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoadingStore.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/5/3.
6 | //
7 |
8 |
9 | import Logging
10 | import Foundation
11 | import ComposableArchitecture
12 |
13 | private let logger = Logger(label: "loading-store")
14 |
15 | struct LoadingStore: Reducer {
16 | struct State: Equatable {
17 | var loading: Bool = false
18 |
19 | init() {
20 | logger.info("loading state init ...")
21 | }
22 | }
23 |
24 | enum Action:Equatable {
25 | case show
26 | case hide
27 | }
28 |
29 | var body: some Reducer {
30 |
31 | Reduce { state, action in
32 | switch action {
33 | case .show:
34 | state.loading = true
35 | return .none
36 | case .hide:
37 | state.loading = false
38 | return .none
39 | }
40 | }
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/redis-pro/Store/LuaStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LuaStore.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/7/17.
6 | //
7 |
8 | import Logging
9 | import Foundation
10 | import ComposableArchitecture
11 |
12 |
13 | struct LuaStore: Reducer {
14 |
15 | struct State: Equatable {
16 | @BindingState var lua:String = "\"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}\" 2 key1 key2 arg1 arg2"
17 | @BindingState var evalResult:String = ""
18 | var luaSHA: String = "-"
19 | }
20 |
21 | enum Action:BindableAction,Equatable {
22 | case eval
23 | case scriptKill
24 | case scriptFlush
25 | case scriptLoad
26 | case setLuaResult(String)
27 | case setLuaSHA(String)
28 | case none
29 | case binding(BindingAction)
30 | }
31 |
32 | @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel
33 | let mainQueue: AnySchedulerOf = .main
34 |
35 | var body: some Reducer {
36 | Reduce { state, action in
37 | switch action {
38 |
39 | case .eval:
40 | let lua = state.lua
41 | return .run { send in
42 | let r = await redisInstanceModel.getClient().eval(lua)
43 | await send(.setLuaResult(r))
44 | }
45 |
46 | case .scriptKill:
47 |
48 | return .run { send in
49 | let _ = await redisInstanceModel.getClient().scriptKill()
50 | }
51 |
52 | case .scriptFlush:
53 |
54 | return .run { send in
55 | let _ = await redisInstanceModel.getClient().scriptFlush()
56 | }
57 |
58 | case .scriptLoad:
59 |
60 | let lua = state.lua
61 | return .run { send in
62 | await send(.setLuaSHA(""))
63 | }
64 |
65 | case let .setLuaResult(r):
66 | state.evalResult = r
67 | return .none
68 |
69 | case let .setLuaSHA(r):
70 | state.luaSHA = r
71 | return .none
72 |
73 | case .none:
74 | return .none
75 | case .binding:
76 | return .none
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/redis-pro/Store/PageStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageStore.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2022/5/6.
6 | //
7 |
8 | import Logging
9 | import Foundation
10 | import ComposableArchitecture
11 |
12 | private let logger = Logger(label: "page-store")
13 |
14 | struct PageStore: Reducer {
15 | struct State: Equatable {
16 | var showTotal: Bool = false
17 | var current:Int = 1
18 | @BindingState var size:Int = 50
19 | var total:Int = 0
20 | var keywords:String = ""
21 | var fastPage = true
22 | var fastPageMax = 99
23 |
24 | /// 总页数
25 | var totalPage:Int {
26 | get {
27 | return total < 1 ? 1 : (total % size == 0 ? total / size : total / size + 1)
28 | }
29 | }
30 |
31 | var totalPageText: String {
32 | get {
33 | if fastPage {
34 | return totalPage > fastPageMax ? "\(fastPageMax)+" : "\(totalPage)"
35 | }
36 | return "\(totalPage)"
37 | }
38 | }
39 |
40 |
41 | var hasPrev:Bool {
42 | totalPage > 1 && current > 1
43 | }
44 | var hasNext:Bool {
45 | totalPage > 1 && current < totalPage
46 | }
47 |
48 | var page:Page {
49 | get {
50 | let page = Page()
51 | page.current = current
52 | page.size = size
53 | page.total = total
54 | page.keywords = keywords
55 |
56 | return page
57 | }
58 | set(page) {
59 | current = page.current
60 | size = page.size
61 | total = page.total
62 | }
63 | }
64 | }
65 |
66 |
67 | enum Action: BindableAction, Equatable {
68 | case binding(BindingAction)
69 | case initial
70 | case updateSize(Int)
71 | case nextPage
72 | case prevPage
73 | case none
74 | }
75 |
76 |
77 | var body: some Reducer {
78 | BindingReducer()
79 | Reduce { state, action in
80 | switch action {
81 | // 初始化已设置的值
82 | case .initial:
83 | logger.info("page store initial...")
84 | return .none
85 |
86 | case let .updateSize(size):
87 | state.current = 1
88 | state.size = size
89 | return .none
90 | case .nextPage:
91 | state.current = state.current + 1
92 | return .none
93 | case .prevPage:
94 | state.current -= 1
95 | if state.current <= 1 {
96 | state.current = 1
97 | }
98 | return .none
99 | case .none:
100 | return .none
101 | case .binding:
102 | return .none
103 | }
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/redis-pro/Store/RedisSystemStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisSystemStore.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/6/4.
6 | //
7 |
8 |
9 | import Logging
10 | import Foundation
11 | import SwiftyJSON
12 | import ComposableArchitecture
13 |
14 | enum RedisSystemViewTypeEnum{
15 | case KEYS_DEL
16 | case REDIS_INFO
17 | case REDIS_CONFIG
18 | case CLIENT_LIST
19 | case SLOW_LOG
20 | case LUA
21 |
22 | }
23 |
24 | private let logger = Logger(label: "redis-system-store")
25 |
26 | struct RedisSystemStore: Reducer {
27 |
28 | struct State: Equatable {
29 | var systemView: RedisSystemViewTypeEnum = .REDIS_INFO
30 | var redisInfoState: RedisInfoStore.State = RedisInfoStore.State()
31 | var redisConfigState: RedisConfigStore.State = RedisConfigStore.State()
32 | var slowLogState: SlowLogStore.State = SlowLogStore.State()
33 | var clientListState: ClientListStore.State = ClientListStore.State()
34 | var luaState: LuaStore.State = LuaStore.State()
35 |
36 | init() {
37 | logger.info("redis system state init ...")
38 | }
39 | }
40 |
41 | enum Action: Equatable {
42 | case initial
43 | case setSystemView(RedisSystemViewTypeEnum)
44 | case redisInfoAction(RedisInfoStore.Action)
45 | case redisConfigAction(RedisConfigStore.Action)
46 | case slowLogAction(SlowLogStore.Action)
47 | case clientListAction(ClientListStore.Action)
48 | case luaAction(LuaStore.Action)
49 | }
50 |
51 | @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel
52 |
53 | var body: some Reducer {
54 | Scope(state: \.redisInfoState, action: /Action.redisInfoAction) {
55 | RedisInfoStore()
56 | }
57 | Scope(state: \.redisConfigState, action: /Action.redisConfigAction) {
58 | RedisConfigStore()
59 | }
60 | Scope(state: \.slowLogState, action: /Action.slowLogAction) {
61 | SlowLogStore()
62 | }
63 | Scope(state: \.clientListState, action: /Action.clientListAction) {
64 | ClientListStore()
65 | }
66 | Scope(state: \.luaState, action: /Action.luaAction) {
67 | LuaStore()
68 | }
69 | Reduce { state, action in
70 | switch action {
71 | // 初始化已设置的值
72 | case .initial:
73 | return .none
74 | case let .setSystemView(type):
75 | state.systemView = type
76 | return .none
77 | case .redisInfoAction:
78 | return .none
79 | case .redisConfigAction:
80 | return .none
81 | case .slowLogAction:
82 | return .none
83 | case .clientListAction:
84 | return .none
85 | case .luaAction:
86 | return .none
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/redis-pro/Store/RenameStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RenameStore.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/5/14.
6 | //
7 |
8 | import Logging
9 | import Foundation
10 | import SwiftyJSON
11 | import ComposableArchitecture
12 |
13 | private let logger = Logger(label: "rename-store")
14 |
15 | struct RenameStore: Reducer {
16 | struct State: Equatable {
17 | var key:String = ""
18 | var index:Int = -1
19 | var visible:Bool = false
20 | @BindingState var newKey:String = ""
21 |
22 | init() {
23 | logger.info("string value state init ...")
24 | }
25 | }
26 |
27 | enum Action:BindableAction, Equatable {
28 | case initial
29 | case submit
30 | case setKey(Int, String)
31 | case setNewKey(String)
32 | case hide
33 | case none
34 | case binding(BindingAction)
35 | }
36 |
37 | @Dependency(\.redisInstance) var redisInstanceModel:RedisInstanceModel
38 | var mainQueue: AnySchedulerOf = .main
39 |
40 | var body: some Reducer {
41 | BindingReducer()
42 | Reduce { state, action in
43 | switch action {
44 | // 初始化已设置的值
45 | case .initial:
46 |
47 | logger.info("rename store initial...")
48 | return .none
49 | case .hide:
50 | state.visible = false
51 | return .none
52 | case .submit:
53 | let key = state.key
54 | let index = state.index
55 | let newKey = state.newKey
56 | return .run { send in
57 |
58 | let r = await redisInstanceModel.getClient().rename(key, newKey: newKey)
59 | if r {
60 | await send(.setKey(index, newKey))
61 | }
62 | }
63 |
64 | case let .setKey(index, newKey):
65 | state.visible = false
66 | return .none
67 |
68 | case let .setNewKey(newKey):
69 | state.newKey = newKey
70 | return .none
71 | case .none:
72 | return .none
73 |
74 | case .binding:
75 | return .none
76 | }
77 | }
78 | }
79 |
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/redis-pro/Store/SystemEnvironment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SystemEnvironment.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/6/3.
6 | //
7 |
8 | import Foundation
9 | import ComposableArchitecture
10 |
11 | @dynamicMemberLookup
12 | struct SystemEnvironment {
13 | var date: () -> Date
14 | var environment: Environment
15 | var mainQueue: AnySchedulerOf
16 | var uuid: () -> UUID
17 |
18 | subscript(
19 | dynamicMember keyPath: WritableKeyPath
20 | ) -> Dependency {
21 | get { self.environment[keyPath: keyPath] }
22 | set { self.environment[keyPath: keyPath] = newValue }
23 | }
24 |
25 | /// Creates a live system environment with the wrapped environment provided.
26 | ///
27 | /// - Parameter environment: An environment to be wrapped in the system environment.
28 | /// - Returns: A new system environment.
29 | static func live(environment: Environment) -> Self {
30 | Self(
31 | date: Date.init,
32 | environment: environment,
33 | mainQueue: .main,
34 | uuid: UUID.init
35 | )
36 | }
37 |
38 | /// Transforms the underlying wrapped environment.
39 | func map(
40 | _ transform: @escaping (Environment) -> NewEnvironment
41 | ) -> SystemEnvironment {
42 | .init(
43 | date: self.date,
44 | environment: transform(self.environment),
45 | mainQueue: self.mainQueue,
46 | uuid: self.uuid
47 | )
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/redis-pro/Views/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmushroom/redis-pro/8c8645ca6c78c812eb469aec927f4a4f4183bc3a/redis-pro/Views/.DS_Store
--------------------------------------------------------------------------------
/redis-pro/Views/App/AboutView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AboutView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/2/12.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AboutView: View {
11 | var body: some View {
12 | VStack(spacing: 6) {
13 | Text("Github").font(.title3).bold()
14 | Link("redis-pro", destination: URL(string: "https://github.com/cmushroom/redis-pro")!)
15 |
16 | Text("感谢以下开源项目及项目维护者").font(.title3).bold()
17 | Link("RediStack", destination: URL(string: "https://github.com/Mordil/RediStack")!)
18 | Link("SwiftyJSON", destination: URL(string: "https://github.com/SwiftyJSON/SwiftyJSON")!)
19 | Link("Puppy", destination: URL(string: "https://github.com/sushichop/Puppy")!)
20 | }.padding(40)
21 | .frame(minWidth: 400, maxWidth: 1000, minHeight: 300, maxHeight: 800, alignment: .top)
22 | }
23 | }
24 |
25 | struct AboutView_Previews: PreviewProvider {
26 | static var previews: some View {
27 | AboutView()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/redis-pro/Views/App/Settings/SettingsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingsView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/9.
6 | //
7 |
8 | import Logging
9 | import SwiftUI
10 | import ComposableArchitecture
11 |
12 | struct SettingsView: View {
13 |
14 | private let labelWidth:CGFloat = 160
15 | var store:StoreOf
16 |
17 | private let logger = Logger(label: "settings-view")
18 |
19 | var body: some View {
20 | WithViewStore(self.store, observe: { $0 }) { viewStore in
21 | Form {
22 | VStack(alignment: .leading, spacing: 8) {
23 |
24 | Picker(selection: viewStore.binding(get: {$0.defaultFavorite}, send: SettingsStore.Action.setDefaultFavorite),
25 | label: Text("Default Favorite:").frame(width: labelWidth, alignment: .trailing)
26 | ) {
27 | Section {
28 | Text("Last Used").tag("last")
29 | }
30 |
31 | ForEach(viewStore.redisModels, id: \.id) { item in
32 | Text(item.name)
33 | }
34 | }
35 |
36 | Picker(selection: viewStore.binding(get: {$0.colorSchemeValue ?? ColorSchemeEnum.SYSTEM.rawValue}, send: SettingsStore.Action.setColorScheme),
37 | label: Text("Appearance:").frame(width: labelWidth, alignment: .trailing)) {
38 | ForEach(ColorSchemeEnum.allCases.map({$0.rawValue}), id: \.self) { item in
39 | Text(verbatim: item)
40 | }
41 | }
42 |
43 | FormItemInt(label: "String Max Length", labelWidth: labelWidth, tips:"HELP_STRING_GET_RANGE_LENGTH", value: viewStore.binding(get: {$0.stringMaxLength}, send: SettingsStore.Action.setStringMaxLength))
44 |
45 | Toggle(isOn: viewStore.binding(get: {$0.fastPage}, send: SettingsStore.Action.setFastPage)) {
46 | Text("Fast Page:")
47 | .frame(width: labelWidth, alignment: .trailing)
48 | }
49 | .toggleStyle(.switch)
50 | .help("HELP_FAST_PAGE")
51 | Spacer()
52 | }
53 | }
54 | .onAppear {
55 | viewStore.send(.initial)
56 | }
57 | .navigationTitle("Preferences")
58 | .padding(30)
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/13.
6 | //
7 |
8 | import SwiftUI
9 |
10 |
11 | struct EdgeBorder: Shape {
12 |
13 | var width: CGFloat
14 | var edges: [Edge]
15 |
16 | func path(in rect: CGRect) -> Path {
17 | var path = Path()
18 | for edge in edges {
19 | var x: CGFloat {
20 | switch edge {
21 | case .top, .bottom, .leading: return rect.minX
22 | case .trailing: return rect.maxX - width
23 | }
24 | }
25 |
26 | var y: CGFloat {
27 | switch edge {
28 | case .top, .leading, .trailing: return rect.minY
29 | case .bottom: return rect.maxY - width
30 | }
31 | }
32 |
33 | var w: CGFloat {
34 | switch edge {
35 | case .top, .bottom: return rect.width
36 | case .leading, .trailing: return self.width
37 | }
38 | }
39 |
40 | var h: CGFloat {
41 | switch edge {
42 | case .top, .bottom: return self.width
43 | case .leading, .trailing: return rect.height
44 | }
45 | }
46 | path.addPath(Path(CGRect(x: x, y: y, width: w, height: h)))
47 | }
48 | return path
49 | }
50 | }
51 |
52 | extension View {
53 | func border(width: CGFloat, edges: [Edge], color: Color) -> some View {
54 | overlay(EdgeBorder(width: width, edges: edges).foregroundColor(color))
55 | }
56 | }
57 |
58 | extension View {
59 | public func addBorder(_ content: S, width: CGFloat = 1, cornerRadius: CGFloat) -> some View where S : ShapeStyle {
60 | let roundedRect = RoundedRectangle(cornerRadius: cornerRadius)
61 | return clipShape(roundedRect)
62 | .overlay(roundedRect.strokeBorder(content, lineWidth: width))
63 | }
64 | }
65 |
66 |
67 | extension Date {
68 |
69 | /// 获取当前 秒级 时间戳 - 10位
70 | var timestamp : Int {
71 | let timeInterval: TimeInterval = self.timeIntervalSince1970
72 | return Int(timeInterval)
73 | }
74 |
75 | /// 获取当前 毫秒级 时间戳 - 13位
76 | var millis : Int64 {
77 | let timeInterval: TimeInterval = self.timeIntervalSince1970
78 | return CLongLong(round(timeInterval*1000))
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/FormItemDouble.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormItemNumber.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/7.
6 | //
7 |
8 | import SwiftUI
9 |
10 |
11 | struct FormItemDouble: View {
12 | var label:String
13 | var labelWidth:CGFloat = 80
14 | var placeholder:String?
15 | @Binding var value:Double
16 |
17 | let reg = #"^\d+(\.\d+)?$"#
18 |
19 | var body: some View {
20 | // let valueProxy = Binding(
21 | // get: { self.value },
22 | // set: {
23 | // let result = $0.range(
24 | // of: reg,
25 | // options: .regularExpression
26 | // )
27 | //
28 | // if result != nil {
29 | // self.value = $0
30 | // } else {
31 | // self.value = "0"
32 | // }
33 | // }
34 | // )
35 |
36 | HStack(alignment: .center) {
37 | if !label.isEmpty {
38 | FormLabel(label: label, width: labelWidth)
39 | }
40 | MDoubleField(value: $value, placeholder: placeholder)
41 | // MTextField(value: valueProxy)
42 | }
43 | }
44 | }
45 |
46 | //struct FormItemNumber_Previews: PreviewProvider {
47 | // @State static var v:String = "0"
48 | //
49 | // static var previews: some View {
50 | // FormItemDouble(label: "name", value: $v)
51 | // }
52 | //}
53 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/FormItemInt.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormItemInt.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/1/28.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FormItemInt: View {
11 | var label:String
12 | var labelWidth:CGFloat = 80
13 | var placeholder:String?
14 | var tips:LocalizedStringKey?
15 | @Binding var value:Int
16 | var suffix:String?
17 | var onCommit:(() -> Void)?
18 |
19 |
20 | var body: some View {
21 |
22 | HStack(alignment: .center) {
23 | if !label.isEmpty {
24 | FormLabel(label: label, width: labelWidth)
25 | }
26 | // NIntField(value: $value, placeholder: placeholder ?? label, onCommit: onCommit)
27 | // MTextField(value: valueProxy, placeholder: placeholder ?? label, suffix: suffix, onCommit: onCommit, autoCommit: autoCommit)
28 | MIntField(value: $value, placeholder: placeholder ?? label, onCommit: onCommit).help(tips ?? "")
29 | if(tips != nil) {
30 | MIcon(icon: "questionmark.circle", fontSize: 12).help(tips!)
31 | }
32 | }
33 | }
34 | }
35 |
36 | struct FormItemInt_Previews: PreviewProvider {
37 | @State static var v:Int = 0
38 |
39 | static var previews: some View {
40 | FormItemInt(label: "name", value: $v)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/FormItemPassword.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormItemSecure.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FormItemPassword: View {
11 | var label: String
12 | var labelWidth:CGFloat = 80
13 | var required:Bool = false
14 | @Binding var value: String
15 |
16 | var body: some View {
17 | HStack(alignment: .center) {
18 | if !label.isEmpty {
19 | FormLabel(label: label, width: labelWidth, required: required)
20 | }
21 | // MNSTextField(text: $value)
22 | MPasswordField(value: $value)
23 | // NPasswordField(value: $value)
24 | }
25 | }
26 | }
27 |
28 | struct FormItemPassword_Previews: PreviewProvider {
29 | @State static var v: String = "";
30 | static var previews: some View {
31 | FormItemPassword(label: "aaa", value: $v)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/FormItemText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormItemText.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/1/27.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FormItemText: View {
11 | var label: String?
12 | var labelWidth:CGFloat = 80
13 | var placeholder: String?
14 | var required:Bool = false
15 | var editable:Bool = true
16 | @Binding var value: String
17 |
18 | var body: some View {
19 | HStack(alignment: .center) {
20 | if label != nil && !label!.isEmpty {
21 | FormLabel(label: label!, width: labelWidth, required: required)
22 | }
23 | // NTextField(stringValue: $value, placeholder: placeholder ?? label ?? "")
24 | // MNSTextField(text: $value)
25 | MTextField(value: $value, placeholder: placeholder ?? label, editable: editable)
26 | .frame(maxWidth: .infinity)
27 | }
28 | }
29 | }
30 |
31 | struct FormItemText_Previews: PreviewProvider {
32 | @State static var v: String = "";
33 | static var previews: some View {
34 | FormItemText(label: "name", placeholder: "please input name", value: $v)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/FormItemTextArea.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormItemTextArea.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/29.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FormItemTextArea: View {
11 | var label: String = ""
12 | var labelWidth:CGFloat = 80
13 | var placeholder: String?
14 | var required:Bool = false
15 | @Binding var value: String
16 |
17 | var body: some View {
18 | HStack(alignment: .top) {
19 | if !label.isEmpty {
20 | FormLabel(label: label, width: labelWidth, required: required)
21 | .padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
22 | }
23 |
24 | MTextEditor(text: $value)
25 | }
26 | }
27 | }
28 |
29 | struct FormItemTextArea_Previews: PreviewProvider {
30 | @State static var v: String = "";
31 | static var previews: some View {
32 | FormItemTextArea(label: "name", placeholder: "please input name", value: $v)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/FormLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormLabel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/1/27.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FormLabel: View {
11 | var label:String
12 | var width:CGFloat?
13 | var required:Bool = false
14 |
15 | var body: some View {
16 | Text("\(required ? "* " : "")\(label):")
17 | .font(.body)
18 | .lineLimit(1)
19 | .frame(width: width, alignment: .trailing)
20 |
21 | }
22 | }
23 |
24 | struct FormLabel_Previews: PreviewProvider {
25 | static var previews: some View {
26 | FormLabel(label: "form label", width: 80, required: true)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/FormText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormText.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2023/9/10.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FormText: View {
11 | var label: String?
12 | var value: String?
13 |
14 | var body: some View {
15 | HStack {
16 | Text(label ?? "")
17 | .foregroundColor(.secondary)
18 | Text(value ?? "")
19 | .foregroundColor(.primary)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/FormWrapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormWrapper.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/29.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FormWrapper: View {
11 | var body: some View {
12 | Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
13 | }
14 | }
15 |
16 | struct FormWrapper_Previews: PreviewProvider {
17 | static var previews: some View {
18 | FormWrapper()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/MDoubleField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MDoubleField.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/7.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 |
11 | struct MDoubleField: View {
12 | @Binding var value:Double
13 | var placeholder:String?
14 | @State private var isEditing = false
15 | var onCommit:(() -> Void)?
16 |
17 | // 是否有编辑过,编辑过才会触commit
18 | @State private var isEdited:Bool = false
19 |
20 |
21 | let logger = Logger(label: "double-field")
22 |
23 | @ViewBuilder
24 | private var field: some View {
25 | if #available(macOS 12.0, *) {
26 | TextField("", value: $value, formatter: NumberHelper.doubleFormatter, prompt: Text(placeholder ?? ""))
27 | .onSubmit {
28 | doCommit()
29 | }
30 | } else {
31 | TextField(placeholder ?? "", value: $value, formatter: NumberHelper.doubleFormatter, onEditingChanged: { isEditing in
32 | self.isEditing = isEditing
33 | if isEditing {
34 | self.isEdited = true
35 | }
36 | }, onCommit: doCommit)
37 | }
38 | }
39 |
40 |
41 | var body: some View {
42 | HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 2) {
43 | field
44 | .labelsHidden()
45 | .lineLimit(1)
46 | .multilineTextAlignment(.leading)
47 | .font(.body)
48 | .disableAutocorrection(true)
49 | .textFieldStyle(PlainTextFieldStyle())
50 | .onHover { inside in
51 | self.isEditing = inside
52 | }
53 | }
54 | .padding(EdgeInsets(top: 3, leading: 4, bottom: 3, trailing: 4))
55 | .background(Color.init(NSColor.textBackgroundColor))
56 | .cornerRadius(MTheme.CORNER_RADIUS)
57 | .overlay(
58 | RoundedRectangle(cornerRadius: MTheme.CORNER_RADIUS).stroke(Color.gray.opacity(isEditing ? 0.4 : 0.2), lineWidth: 1)
59 | )
60 | }
61 |
62 | func doCommit() -> Void {
63 | if self.isEdited {
64 | self.isEdited = false
65 | logger.info("on double field commit, value: \(value)")
66 | onCommit?()
67 | }
68 | }
69 | }
70 |
71 | struct MDoubleField_Previews: PreviewProvider {
72 | @State static var v:Double = 0
73 | @State static var text:String = "0"
74 |
75 | static var previews: some View {
76 | VStack {
77 | TextField("sfsfdfdf", text: $text)
78 | MDoubleField(value: $v)
79 | Text(String(v))
80 | Text(text)
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/MIntField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MIntField.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2021/12/11.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 |
11 | struct MIntField: View {
12 | @Binding var value:Int
13 | var placeholder:String?
14 | @State private var isEditing = false
15 | var onCommit: (() -> Void)?
16 |
17 | // 是否有编辑过,编辑过才会触commit
18 | @State private var isEdited:Bool = false
19 |
20 | let logger = Logger(label: "int-field")
21 |
22 | @ViewBuilder
23 | private var field: some View {
24 | if #available(macOS 12.0, *) {
25 | TextField("", value: $value, formatter: NumberHelper.intFormatter, prompt: Text(placeholder ?? ""))
26 | .onSubmit {
27 | doCommit()
28 | }
29 | } else {
30 | TextField(placeholder ?? "", value: $value, formatter: NumberHelper.intFormatter, onEditingChanged: { isEditing in
31 | self.isEditing = isEditing
32 | if isEditing {
33 | self.isEdited = true
34 | }
35 | }, onCommit: doCommit)
36 | }
37 | }
38 |
39 | var body: some View {
40 | HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 2) {
41 | field
42 | .labelsHidden()
43 | .lineLimit(1)
44 | .multilineTextAlignment(.leading)
45 | .font(.body)
46 | .disableAutocorrection(true)
47 | .textFieldStyle(PlainTextFieldStyle())
48 | .onHover { inside in
49 | self.isEditing = inside
50 | }
51 | }
52 | .padding(EdgeInsets(top: 3, leading: 4, bottom: 3, trailing: 4))
53 | .background(Color.init(NSColor.textBackgroundColor))
54 | .cornerRadius(MTheme.CORNER_RADIUS)
55 | .overlay(
56 | RoundedRectangle(cornerRadius: MTheme.CORNER_RADIUS).stroke(Color.gray.opacity(isEditing ? 0.4 : 0.2), lineWidth: 1)
57 | )
58 | }
59 |
60 | func doCommit() -> Void {
61 | logger.info("on textField commit, value: \(value)")
62 | onCommit?()
63 | }
64 | }
65 |
66 | //struct MIntField_Previews: PreviewProvider {
67 | // static var previews: some View {
68 | // MIntField()
69 | // }
70 | //}
71 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/MSecureField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MSecureField.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 |
11 | import Logging
12 |
13 | struct MSecureField: View {
14 | @Binding var value:String
15 | var placeholder:String = "Password"
16 | @State private var isEditing = false
17 | var onCommit:() -> Void = {}
18 |
19 | @State private var showPass:Bool = false
20 |
21 | let logger = Logger(label: "secure-field")
22 |
23 | @ViewBuilder
24 | private var field: some View {
25 | if showPass {
26 | MTextField(value: $value, placeholder: placeholder, onCommit: onCommit)
27 | } else {
28 | SecureField(placeholder, text: $value, onCommit: doCommit)
29 | }
30 | }
31 |
32 | var body: some View {
33 | HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 2) {
34 | field
35 | .multilineTextAlignment(/*@START_MENU_TOKEN@*/.leading/*@END_MENU_TOKEN@*/)
36 | .font(.body)
37 | .lineLimit(1)
38 | .disableAutocorrection(true)
39 | .textFieldStyle(PlainTextFieldStyle())
40 | .onHover { inside in
41 | self.isEditing = inside
42 | }
43 |
44 | MIcon(icon: showPass ? "eye" : "eye.slash", fontSize: MTheme.FONT_SIZE_BUTTON, action: showPassAction)
45 | .padding(0)
46 | }
47 | .padding(EdgeInsets(top: 3, leading: 4, bottom: 3, trailing: 4))
48 | .background(Color.init(NSColor.textBackgroundColor))
49 | .cornerRadius(4)
50 | .overlay(
51 | RoundedRectangle(cornerRadius: 4).stroke(Color.gray.opacity(isEditing ? 0.4 : 0.2), lineWidth: 1)
52 | )
53 | }
54 |
55 | func doCommit() -> Void {
56 | doAction()
57 | }
58 |
59 | func showPassAction() -> Void {
60 | self.showPass.toggle()
61 | }
62 |
63 | func doAction() -> Void {
64 | logger.info("on textField commit, value: \(value)")
65 | onCommit()
66 | }
67 | }
68 | struct MSecureField_Previews: PreviewProvider {
69 |
70 | @State static var text:String = "pass"
71 | static var previews: some View {
72 | MSecureField(value: $text)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/MTextEditor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MTextEditor.swift
3 | // redis-pro
4 | // 存在问题,自动转换双引号, 暂时使用 NSTextEditor
5 | // Created by chengpanwang on 2021/4/29.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MTextEditor: View {
11 | @Binding var text:String
12 | @State private var editing:Bool = false
13 | @State private var disabled:Bool = false
14 |
15 | var body: some View {
16 | // text editor
17 | TextEditor(text: $text)
18 | .font(.body)
19 | .multilineTextAlignment(.leading)
20 | .padding(EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0))
21 | .lineSpacing(1.5)
22 | .disableAutocorrection(true)
23 | .onHover { inside in
24 | self.editing = inside
25 | }
26 | .addBorder(Color.gray.opacity(!disabled && editing ? 0.4 : 0.2), width: 1, cornerRadius: MTheme.CORNER_RADIUS)
27 | }
28 | }
29 |
30 |
31 | struct MTextEditor_Previews: PreviewProvider {
32 | @State static var text:String = ""
33 | static var previews: some View {
34 | MTextEditor(text: $text)
35 | }
36 | }
37 |
38 | extension NSTextView {
39 | open override var frame: CGRect {
40 | didSet {
41 | self.isAutomaticQuoteSubstitutionEnabled = false
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/NIntField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NIntField.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2021/12/5.
6 | //
7 |
8 | import SwiftUI
9 | import Cocoa
10 | import Logging
11 |
12 |
13 | struct NIntField: NSViewRepresentable {
14 | @Binding var value: Int
15 | var placeholder: String
16 | var disable = false
17 | var onChange: (() -> Void)?
18 | var onCommit: (() -> Void)?
19 |
20 | func makeNSView(context: Context) -> NSTextField {
21 | let textField = NSTextField()
22 | textField.integerValue = value
23 | textField.placeholderString = placeholder
24 | textField.delegate = context.coordinator
25 |
26 | textField.formatter = NumberHelper.intFormatter
27 | textField.isEnabled = !disable
28 | return textField
29 | }
30 |
31 |
32 | func updateNSView(_ nsView: NSTextField, context: Context) {
33 | nsView.integerValue = value
34 | }
35 |
36 |
37 | func makeCoordinator() -> Coordinator {
38 | Coordinator(with: self)
39 | }
40 |
41 |
42 | class Coordinator: NSObject, NSTextFieldDelegate {
43 | let parent: NIntField
44 | private var editing = false
45 |
46 | let logger = Logger(label: "int-field-coordinator")
47 |
48 | init(with parent: NIntField) {
49 | self.parent = parent
50 | super.init()
51 | }
52 |
53 |
54 | // MARK: - NSTextFieldDelegate Methods
55 |
56 | // change
57 | func controlTextDidChange(_ obj: Notification) {
58 | guard let textField = obj.object as? NSTextField else { return }
59 |
60 | if NumberHelper.isInt(textField.stringValue) {
61 | parent.value = textField.integerValue
62 | } else {
63 | textField.stringValue = String(parent.value)
64 | }
65 | editing = true
66 | parent.onChange?()
67 | }
68 |
69 | // commit
70 | func controlTextDidEndEditing(_ obj: Notification) {
71 | if editing {
72 | editing = false
73 | parent.onCommit?()
74 | }
75 | }
76 |
77 |
78 | func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool {
79 | logger.debug("on text field commit, value: \(parent.value)")
80 | parent.onCommit?()
81 | editing = false
82 | return true
83 | }
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/NPasswordField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NPasswordField.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2021/12/17.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct NPasswordField: View {
11 | @Binding var value:String
12 | @State private var visible:Bool = false
13 |
14 | var body: some View {
15 | ZStack(alignment: .trailing) {
16 | if !visible {
17 | // NSecureField(value:$value, placeholder: "Password")
18 | } else {
19 | NTextField(stringValue:$value, placeholder: "Password")
20 | }
21 | Button(action: {
22 | visible.toggle()
23 | }) {
24 | Image(systemName: !self.visible ? "eye.slash" : "eye")
25 | .accentColor(.gray)
26 | }
27 | .onHover { inside in
28 | if inside {
29 | NSCursor.pointingHand.push()
30 | } else {
31 | NSCursor.pop()
32 | }
33 | }
34 | .padding(EdgeInsets(top: 0, leading: 2, bottom: 0, trailing: 2))
35 | .buttonStyle(PlainButtonStyle())
36 | .contentShape(Circle())
37 | }
38 | }
39 | }
40 |
41 | //struct NPasswordField_Previews: PreviewProvider {
42 | // static var previews: some View {
43 | // NPasswordField()
44 | // }
45 | //}
46 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/NSecureField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSecureField.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2021/12/12.
6 | //
7 | import Cocoa
8 | import SwiftUI
9 | import Logging
10 |
11 | struct NSecureField: NSViewRepresentable {
12 |
13 | @Binding var value:String
14 | var placeholder:String?
15 | var onChange: (() -> Void)?
16 | var onCommit: (() -> Void)?
17 |
18 | func makeNSView(context: Context) -> NSSecureTextField {
19 | let textField = NSSecureTextField()
20 | textField.stringValue = value
21 | textField.placeholderString = placeholder
22 | textField.delegate = context.coordinator
23 | // textField.isBordered = true
24 | // textField.bezelStyle = .roundedBezel
25 | return textField
26 | }
27 |
28 |
29 | func updateNSView(_ nsView: NSSecureTextField, context: Context) {
30 | nsView.stringValue = value
31 | }
32 |
33 | func makeCoordinator() -> Coordinator {
34 | Coordinator(with: self)
35 | }
36 |
37 |
38 | class Coordinator: NSObject, NSTextFieldDelegate {
39 | let parent: NSecureField
40 |
41 |
42 | let logger = Logger(label: "text-field-coordinator")
43 |
44 | init(with parent: NSecureField) {
45 | self.parent = parent
46 | super.init()
47 | }
48 |
49 |
50 | // MARK: - NSTextFieldDelegate Methods
51 |
52 | func controlTextDidChange(_ obj: Notification) {
53 | guard let textField = obj.object as? NSSecureTextField else { return }
54 | parent.value = textField.stringValue
55 | parent.onChange?()
56 | }
57 |
58 | func controlTextDidEndEditing(_ obj: Notification) {
59 | guard let textField = obj.object as? NSSecureTextField else { return }
60 | parent.value = textField.stringValue
61 |
62 | parent.onChange?()
63 | }
64 | }
65 |
66 | }
67 |
68 | //struct NSecureField_Previews: PreviewProvider {
69 | // static var previews: some View {
70 | // NSecureField()
71 | // }
72 | //}
73 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Form/NTextEditor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NTextEditor.swift
3 | // redis-pro
4 | //
5 | // Created by chenpanwang on 2021/12/21.
6 | //
7 | import Cocoa
8 | import SwiftUI
9 | import Logging
10 |
11 | struct NTextEditor: NSViewRepresentable {
12 | @Binding var value: String
13 | // var placeholder: String
14 | var disable = false
15 | var onChange: (() -> Void)?
16 | var onCommit: (() -> Void)?
17 |
18 | func makeNSView(context: Context) -> NSTextView {
19 | let textField = NSTextView()
20 | textField.string = value
21 | // textField.placeho = placeholder
22 | textField.delegate = context.coordinator
23 |
24 | // textField.disable = !disable
25 | return textField
26 | }
27 |
28 |
29 | func updateNSView(_ nsView: NSTextView, context: Context) {
30 | nsView.string = value
31 | }
32 |
33 |
34 | func makeCoordinator() -> Coordinator {
35 | Coordinator(with: self)
36 | }
37 |
38 |
39 | class Coordinator: NSObject, NSTextViewDelegate {
40 | let parent: NTextEditor
41 | private var editing = false
42 |
43 | let logger = Logger(label: "text-editor-coordinator")
44 |
45 | init(with parent: NTextEditor) {
46 | self.parent = parent
47 | super.init()
48 | }
49 |
50 |
51 | // MARK: - NSTextFieldDelegate Methods
52 |
53 | // change
54 | func controlTextDidChange(_ obj: Notification) {
55 | guard let textField = obj.object as? NSTextView else { return }
56 |
57 | parent.value = textField.string
58 | editing = true
59 | parent.onChange?()
60 | }
61 |
62 | // commit
63 | func controlTextDidEndEditing(_ obj: Notification) {
64 | if editing {
65 | editing = false
66 | parent.onCommit?()
67 | }
68 | }
69 |
70 |
71 | func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool {
72 | logger.debug("on text field commit, value: \(parent.value)")
73 | parent.onCommit?()
74 | editing = false
75 | return true
76 | }
77 | }
78 | }
79 |
80 | //struct NTextEditor_Previews: PreviewProvider {
81 | // static var previews: some View {
82 | // NTextEditor()
83 | // }
84 | //}
85 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/IconButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IconButton.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/9.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import Cocoa
11 |
12 | struct IconButton: View {
13 | var icon:String
14 | var name:String
15 | var disabled:Bool = false
16 |
17 | var action: (() -> Void)
18 |
19 | let logger = Logger(label: "icon-button")
20 |
21 | var body: some View {
22 | NButton(title: name, action: action, icon: icon, disabled: disabled)
23 | // .buttonStyle(BorderedButtonStyle())
24 | .disabled(disabled)
25 | .onHover { inside in
26 | if !disabled && inside {
27 | NSCursor.pointingHand.push()
28 | } else {
29 | NSCursor.pop()
30 | }
31 | }
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/KeyObjectBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyObjectBar.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2023/7/30.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct KeyObjectBar: View {
13 | var store:StoreOf
14 |
15 | let logger = Logger(label: "key-object-bar")
16 |
17 | var body: some View {
18 | WithViewStore(self.store, observe: { $0 }) { viewStore in
19 | FormText(label: "Object Encoding:", value: viewStore.encoding)
20 | .padding(EdgeInsets(top: 0, leading: MTheme.H_SPACING, bottom: 0, trailing: MTheme.H_SPACING))
21 |
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/LoadingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NLoading.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/5/3.
6 | //
7 |
8 | import Logging
9 | import SwiftUI
10 | import ComposableArchitecture
11 |
12 | struct LoadingView: View {
13 | private var logger = Logger(label: "loading-view")
14 |
15 | @Dependency(\.appContext) var appContext
16 |
17 | init() {
18 | logger.info("loading view init...")
19 | }
20 |
21 | var body: some View {
22 | WithViewStore(appContext, observe: { $0 }) {viewStore in
23 | HStack{
24 | EmptyView()
25 | }
26 | .frame(height: 0)
27 | .overlay(MSpin(loading: viewStore.loading))
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Loadings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Loadings.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/6/1.
6 | //
7 |
8 | import Foundation
9 | import Cocoa
10 |
11 | struct Loadings {
12 | static func show() {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/MIcon.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MIcon.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/7.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MIcon: View {
11 | var icon:String
12 | var fontSize:CGFloat = 10.0
13 | var disabled:Bool = false
14 | var action: () -> Void = {}
15 |
16 | var body: some View {
17 |
18 | Button(action: action) {
19 | Label("", systemImage: icon)
20 | .font(.system(size: fontSize))
21 | .labelStyle(IconOnlyLabelStyle())
22 | .frame(height: fontSize)
23 | .contentShape(Circle())
24 | }
25 | .foregroundColor(.primary)
26 | .contentShape(Circle())
27 | .buttonStyle(PlainButtonStyle())
28 | .disabled(disabled)
29 | .onHover { inside in
30 | if !disabled && inside {
31 | NSCursor.pointingHand.push()
32 | } else {
33 | NSCursor.pop()
34 | }
35 |
36 | }
37 | }
38 | }
39 |
40 | struct MIcon_Previews: PreviewProvider {
41 | static var previews: some View {
42 | MIcon(icon: "chevron.right").disabled(true)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/MLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MLabel.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/20.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MLabel: View {
11 | var name:String
12 | var icon:String
13 | var size: MLabelSize = .M
14 |
15 | var textSize:CGFloat {
16 | if size == .M {
17 | return MTheme.FONT_SIZE_BUTTON
18 | } else if size == .L {
19 | return MTheme.FONT_SIZE_BUTTON_L
20 | }
21 |
22 | return MTheme.FONT_SIZE_BUTTON_S
23 | }
24 | var iconSize:CGFloat {
25 | if size == .M {
26 | return MTheme.FONT_SIZE_BUTTON_ICON
27 | } else if size == .L {
28 | return MTheme.FONT_SIZE_BUTTON_ICON_L
29 | }
30 |
31 | return MTheme.FONT_SIZE_BUTTON_ICON_S
32 | }
33 |
34 | var body: some View {
35 | Label {
36 | Text(name)
37 | .font(.system(size: textSize))
38 | } icon: {
39 | Image(systemName: icon)
40 | .font(.system(size: iconSize))
41 | .padding(EdgeInsets(top: 0, leading: 0, bottom: 2, trailing: -6))
42 | }
43 | .foregroundColor(.primary)
44 | }
45 | }
46 |
47 | enum MLabelSize {
48 | case S
49 | case M
50 | case L
51 | }
52 |
53 | struct MLabel_Previews: PreviewProvider {
54 | static var previews: some View {
55 | VStack {
56 | MLabel(name: "Add", icon: "plus")
57 | MLabel(name: "DB\(1)", icon: "cylinder.split.1x2", size: .S)
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/MLoading.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Loading.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/20.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MLoading: View {
11 | var text:String = ""
12 | var loadingText:String = "Connecting..."
13 | var loading:Bool = false
14 | var body: some View {
15 | HStack(alignment: .center, spacing: 4) {
16 | if loading {
17 | ProgressView()
18 | .scaleEffect(x: 0.5, y: 0.5, anchor: .center)
19 | .frame(width: 20, height: 20)
20 | }
21 | Text(loading ? loadingText : text)
22 | .font(.body)
23 | .multilineTextAlignment(.leading)
24 | .lineLimit(1)
25 | }
26 | }
27 | }
28 |
29 | struct MLoading_Previews: PreviewProvider {
30 | static var previews: some View {
31 | MLoading()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/MSpin.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MSpin.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/21.
6 | //
7 |
8 | import SwiftUI
9 | import AppKit
10 | import Foundation
11 |
12 | struct MSpin: View {
13 | var loading:Bool = false
14 |
15 | var spin: some View {
16 | loading ?
17 | VStack(alignment:.center, spacing: 8) {
18 | ProgressView()
19 | Text("Loading...")
20 | }
21 | .padding(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
22 | .frame(width: 140, height: 120)
23 | .background(Color.black.opacity(0.4))
24 | .cornerRadius(8)
25 | .shadow(color: .black.opacity(0.6), radius: 8, x: 4, y: 4)
26 | .colorScheme(.dark)
27 | : nil
28 | }
29 |
30 | var body: some View {
31 | spin
32 | }
33 | }
34 |
35 | struct MSpin_Previews: PreviewProvider {
36 | @State static var loading:Bool = true
37 | static var previews: some View {
38 |
39 | HStack {
40 | MSpin(loading: loading)
41 | }
42 | .colorScheme(.dark)
43 | .frame(width: 500, height: 400, alignment: .center)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/MTabView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MTabView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/10.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MTabView: View {
11 | var keys:[String] = ["a", "b", "c"]
12 | @State var selected:Int = 0
13 |
14 | var body: some View {
15 | Picker(selection: $selected, label: Text("Favorite Color")) {
16 | Text("Red").tag(1)
17 | Text("Green").tag(2)
18 | Text("Blue").tag(3)
19 | Text("Other").tag(4)
20 | }
21 | .pickerStyle(SegmentedPickerStyle())
22 | .horizontalRadioGroupLayout()
23 | }
24 | }
25 |
26 | struct MTabView_Previews: PreviewProvider {
27 | static var previews: some View {
28 | MTabView()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/ModalView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModalView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/28.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ModalView: View {
11 | @Environment(\.presentationMode) var presentation
12 | var title: String
13 | var action: () throws -> Void
14 | var content: Content
15 | var width = MTheme.DIALOG_W
16 | var height = MTheme.DIALOG_H
17 |
18 |
19 | init(_ title:String, action: @escaping () throws -> Void, @ViewBuilder content: () -> Content) {
20 | self.title = title
21 | self.action = action
22 | self.content = content()
23 | }
24 |
25 | init(_ title:String, width: CGFloat, height: CGFloat, action: @escaping () throws -> Void, @ViewBuilder content: () -> Content) {
26 | self.title = title
27 | self.width = width
28 | self.height = height
29 | self.action = action
30 | self.content = content()
31 | }
32 |
33 | var body: some View {
34 | VStack(alignment: .leading, spacing: 0) {
35 | // header
36 | HStack {
37 | Text(title)
38 | .font(.body)
39 | Spacer()
40 | }
41 | .padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8))
42 | Rectangle().frame(height: 1)
43 | .padding(.horizontal, 0).foregroundColor(Color.gray.opacity(0.2))
44 |
45 | VStack(alignment: .center, spacing: 0) {
46 | content
47 | }
48 | .padding(8)
49 |
50 | HStack(alignment: .center, spacing: 6) {
51 | Spacer()
52 | MButton(text: "Cancel", action: onCancel, keyEquivalent: .escape).keyboardShortcut(.cancelAction)
53 | MButton(text: "Submit", action: doAction, keyEquivalent: .return).keyboardShortcut(.defaultAction)
54 | }
55 | .padding(EdgeInsets(top: 0, leading: 8, bottom: 6, trailing: 8))
56 | }
57 | .frame(minWidth: width, minHeight: height)
58 | .padding(0)
59 | }
60 |
61 | func doAction() -> Void {
62 | presentation.wrappedValue.dismiss()
63 | try? action()
64 | }
65 |
66 | func onCancel() -> Void{
67 | presentation.wrappedValue.dismiss()
68 | }
69 | }
70 |
71 | struct ModalView_Previews: PreviewProvider {
72 | static var previews: some View {
73 | ModalView("title", action: {print("modal view action")}) {
74 | Text("modal view")
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/PageBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageBar.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/12.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct PageBar: View {
13 | var store:StoreOf
14 |
15 | let logger = Logger(label: "page-bar")
16 |
17 | var body: some View {
18 | WithViewStore(self.store, observe: { $0 }) { viewStore in
19 |
20 | HStack(alignment:.center, spacing: 4) {
21 | if viewStore.showTotal {
22 | Text("Total: \(viewStore.total)")
23 | .font(MTheme.FONT_FOOTER)
24 | .lineLimit(1)
25 | .multilineTextAlignment(.trailing)
26 | }
27 | Picker("", selection: viewStore.$size) {
28 | Text("10").tag(10)
29 | Text("50").tag(50)
30 | Text("100").tag(100)
31 | Text("200").tag(200)
32 | }
33 | .frame(width: 65)
34 |
35 | HStack(alignment:.center, spacing: 2) {
36 | MIcon(icon: "chevron.left", disabled: !viewStore.hasPrev, action: {viewStore.send(.prevPage)})
37 | Text("\(viewStore.current)/\(viewStore.totalPageText)")
38 | .font(MTheme.FONT_FOOTER)
39 | .multilineTextAlignment(.center)
40 | .lineLimit(1)
41 | .layoutPriority(1)
42 | MIcon(icon: "chevron.right", disabled: !viewStore.hasNext, action: {viewStore.send(.nextPage)})
43 | }
44 | .frame(minWidth: 60, idealWidth: 60)
45 | }
46 |
47 | }
48 | }
49 |
50 | }
51 |
52 | //struct PageBar_Previews: PreviewProvider {
53 | // static var previews: some View {
54 | // PageBar(page: Page())
55 | // }
56 | //}
57 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/RedisKeyTypePicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisKeyTypePicker.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/7.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct RedisKeyTypePicker: View {
11 | var label:String = ""
12 | @Binding var value:String
13 | var disabled:Bool = false
14 |
15 | var body: some View {
16 | Picker("\(label):", selection: $value) {
17 | ForEach(RedisKeyTypeEnum.allCases.filter{$0 != RedisKeyTypeEnum.NONE}, id: \.self) { item in
18 | Text(item.rawValue).tag(item.rawValue)
19 | }
20 | }
21 | .disabled(disabled)
22 | .frame(width: 120)
23 | }
24 | }
25 |
26 | struct RedisKeyTypePicker_Previews: PreviewProvider {
27 | @State static var value = RedisKeyTypeEnum.STRING.rawValue
28 | static var previews: some View {
29 | RedisKeyTypePicker(value: $value)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/SearchBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchBar.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/12.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct SearchBar: View {
13 |
14 | @State private var keywords: String = ""
15 | var placeholder:String = "Search..."
16 |
17 | var onCommit: ((String) -> Void)?
18 | let logger = Logger(label: "search-bar")
19 |
20 | var body: some View {
21 |
22 | HStack {
23 | // Search text field
24 | NSearchField(value: $keywords, placeholder: placeholder, onCommit: doAction)
25 | .help("HELP_SEARCH_BAR")
26 | }
27 | }
28 |
29 | func doAction(keywords: String) -> Void {
30 | logger.info("on search bar action, keywords: \(keywords)")
31 | onCommit?(keywords)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Table/NTableColumn.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NTableColumn.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/3/27.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 | import SwiftUI
11 | struct NTableColumn: Equatable {
12 | var type:TableColumnType = .DEFAULT
13 | var title:String
14 | var key:String
15 | var width:CGFloat?
16 | var icon: TableIconEnum?
17 | }
18 |
19 |
20 | extension NSImage.Name {
21 | static let icon = NSImage.Name("icon-redis")
22 | }
23 |
24 | class Icon {
25 | private static func appIcon() -> NSImage {
26 | let icon = NSImage(named: .icon)!
27 | icon.size = NSSize(width: 20, height: 20)
28 | return icon
29 | }
30 |
31 | static var ICON_APP = appIcon()
32 | }
33 |
34 | protocol TableIconImage {
35 | var image:NSImage { get }
36 | }
37 |
38 |
39 | enum TableIconEnum: TableIconImage {
40 | case APP
41 |
42 | var image: NSImage {
43 | switch self {
44 | case .APP:
45 | return Icon.ICON_APP
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Table/TableColumnType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableColumnType.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/3/27.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol TableColumnTypeData:Equatable {
11 | var width:CGFloat { get }
12 | }
13 |
14 |
15 | enum TableColumnType: TableColumnTypeData {
16 |
17 | case DEFAULT
18 | case INDEX
19 | case IMAGE
20 | case KEY_TYPE
21 |
22 | // case ID
23 | // case DATE
24 | // case DATETIME
25 |
26 | // 定义每个类型默认宽度
27 | var width: CGFloat {
28 | switch self {
29 | case .DEFAULT:
30 | return 100
31 | case .INDEX:
32 | return 20
33 | case .IMAGE:
34 | return 40
35 | case .KEY_TYPE:
36 | return 80
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/Tag.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tag.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/6.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct Tag: View {
11 | var name:String
12 | var color:Color = Color.orange
13 | var bgColor:Color = Color.clear
14 |
15 | var body: some View {
16 | Text(name).foregroundColor(color)
17 | .background(bgColor)
18 | }
19 | }
20 |
21 | struct Tag_Previews: PreviewProvider {
22 | static var previews: some View {
23 | Tag(name: "tag")
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/redis-pro/Views/Components/TextViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextViewController.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/12.
6 | //
7 |
8 | import Foundation
9 | import Cocoa
10 | import SwiftUI
11 | import Logging
12 |
13 | class TextViewController: NSViewController {
14 |
15 | @objc dynamic var text: String = ""
16 |
17 | @IBOutlet var textView: NSTextView!
18 | @IBOutlet var scrollView: NSScrollView!
19 | @IBOutlet weak var clipView: NSClipView!
20 |
21 | var selectedRanges: [NSValue] = [] {
22 | didSet {
23 | guard selectedRanges.count > 0 else {
24 | return
25 | }
26 | textView.selectedRanges = selectedRanges
27 | }
28 | }
29 |
30 | override func viewDidLoad() {
31 | super.viewDidLoad()
32 | self.textView.enabledTextCheckingTypes = 0
33 | }
34 |
35 | func setText(_ text:String) -> Void {
36 | self.text = text
37 | }
38 | }
39 |
40 |
41 |
42 | struct MTextView: NSViewControllerRepresentable {
43 | @Binding var text:String
44 |
45 | let logger = Logger(label: "text-view")
46 |
47 | func makeCoordinator() -> Coordinator {
48 | return Coordinator(self)
49 | }
50 |
51 |
52 | func makeNSViewController(context: Context) -> NSViewController {
53 | let controller = TextViewController()
54 | return controller
55 | }
56 |
57 |
58 | func updateNSViewController(_ nsViewController: NSViewController, context: Context) {
59 | guard let controller = nsViewController as? TextViewController else {return}
60 | controller.textView?.delegate = context.coordinator
61 | controller.setText(text)
62 | controller.selectedRanges = context.coordinator.selectedRanges
63 | }
64 |
65 | class Coordinator: NSObject, NSTextViewDelegate {
66 |
67 | var parent: MTextView
68 | var selectedRanges: [NSValue] = []
69 |
70 | let logger = Logger(label: "text-view-coordinator")
71 |
72 |
73 | init(_ parent: MTextView) {
74 | self.parent = parent
75 | }
76 |
77 | func textDidChange(_ notification: Notification) {
78 | guard let textView = notification.object as? NSTextView else { return }
79 | self.parent.text = textView.string
80 | self.selectedRanges = textView.selectedRanges
81 | }
82 | }
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/redis-pro/Views/HomeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2021/4/4.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 |
13 | struct HomeView: View {
14 | let logger = Logger(label: "home-view")
15 | var store:StoreOf
16 |
17 | var body: some View {
18 | WithViewStore(self.store, observe: { $0.title }) { viewStore in
19 |
20 | RedisKeysListView(store)
21 | .onAppear {
22 | logger.info("redis pro home view init complete")
23 | viewStore.send(.initial)
24 | }
25 | .onDisappear {
26 | logger.info("redis pro home view destroy...")
27 | viewStore.send(.onClose)
28 | }
29 | // 设置window标题
30 | .navigationTitle(viewStore.state)
31 |
32 | }
33 | }
34 | }
35 |
36 | //struct HomeView_Previews: PreviewProvider {
37 | // static var previews: some View {
38 | // HomeView()
39 | // }
40 | //}
41 |
--------------------------------------------------------------------------------
/redis-pro/Views/IndexView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IndexView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/8.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct IndexView: View {
13 | @State var appState:AppStore.State?
14 | var settingStore: StoreOf
15 | let logger = Logger(label: "index-view")
16 |
17 |
18 | init(settingStore: StoreOf) {
19 | logger.info("index view init ...")
20 | self.settingStore = settingStore
21 | }
22 |
23 | var body: some View {
24 | if let state = appState {
25 | let redisInstanceModel = RedisInstanceModel(RedisModel(), settingViewStore: ViewStore(settingStore, observe: { $0 }))
26 | let redisClient = RediStackClient(RedisModel(), settingViewStore: ViewStore(settingStore, observe: { $0 }))
27 |
28 | let store: StoreOf = Store(initialState: state) {
29 | AppStore()
30 | ._printChanges()
31 | } withDependencies: {
32 | $0.redisInstance = redisInstanceModel
33 | $0.redisClient = redisClient
34 | }
35 |
36 |
37 | WithViewStore(store, observe: { $0.isConnect }) {viewStore in
38 | ZStack {
39 | VStack {
40 | if (viewStore.state) {
41 | HomeView(store: store)
42 | } else {
43 | LoginView(store: store)
44 | }
45 |
46 |
47 |
48 | }
49 |
50 | LoadingView()
51 |
52 | }.onAppear {
53 | redisInstanceModel.setAppStore(store)
54 | }
55 | }
56 |
57 | } else {
58 | Spacer()
59 | .onAppear {
60 | appState = AppStore.State()
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/redis-pro/Views/Login/LoginView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Login.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/1/25.
6 | //
7 |
8 | import SwiftUI
9 | import NIO
10 | import RediStack
11 | import Logging
12 | import ComposableArchitecture
13 |
14 |
15 | struct LoginView: View {
16 | let logger = Logger(label: "login-view")
17 | let store: StoreOf
18 |
19 | init(store: StoreOf) {
20 | logger.info("login view init...")
21 | self.store = store
22 | }
23 |
24 | var body: some View {
25 | RedisListView(store: store.scope(state: \.favoriteState, action: AppStore.Action.favoriteAction))
26 | .onDisappear {
27 | logger.info("redis pro login view destroy...")
28 | }
29 | .onAppear {
30 | logger.info("redis pro login view init complete")
31 | }
32 | }
33 | }
34 |
35 | //struct Login_Previews: PreviewProvider {
36 | // static var previews: some View {
37 | // LoginView()
38 | // }
39 | //}
40 |
--------------------------------------------------------------------------------
/redis-pro/Views/Login/RedisListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisInstanceList.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/1/25.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct RedisListView: View {
13 | let logger = Logger(label: "redis-login")
14 |
15 | var store:StoreOf
16 |
17 | var body: some View {
18 | WithViewStore(self.store, observe: { $0 }) {viewStore in
19 | HSplitView {
20 | VStack(alignment: .leading,
21 | spacing: 0) {
22 |
23 | NTableView(
24 | store: store.scope(state: \.tableState, action: FavoriteStore.Action.tableAction)
25 | )
26 |
27 | // footer
28 | HStack(alignment: .center) {
29 | MIcon(icon: "plus", fontSize: 13, action: {
30 | viewStore.send(.addNew)
31 | })
32 | MIcon(icon: "minus", fontSize: 13, disabled: viewStore.tableState.selectIndex < 0, action: {
33 | viewStore.send(.deleteConfirm(viewStore.tableState.selectIndex))
34 | })
35 | }
36 | .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
37 | }
38 | .padding(0)
39 | .frame(minWidth:200)
40 | .layoutPriority(0)
41 | .onAppear{
42 | onLoad(viewStore)
43 | }
44 | LoginForm(store: store.scope(state: \.loginState, action: FavoriteStore.Action.loginAction))
45 | .frame(minWidth: 800, maxWidth: .infinity, minHeight: 520, maxHeight: .infinity)
46 | }
47 | }
48 | }
49 |
50 | func onLoad(_ viewStore:ViewStore) {
51 | viewStore.send(.getAll)
52 | viewStore.send(.initDefaultSelection)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/redis-pro/Views/RedisEditorView/HashEditorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyValueRowEditorView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/9.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct HashEditorView: View {
13 | var store: StoreOf
14 | var keyObjectStore: StoreOf
15 | private let logger = Logger(label: "redis-hash-editor")
16 |
17 | init(store: StoreOf) {
18 | self.store = store.scope(state: \.hashValueState, action: ValueStore.Action.hashValueAction)
19 | self.keyObjectStore = store.scope(state: \.keyObjectState, action: ValueStore.Action.keyObjectAction)
20 | }
21 |
22 |
23 | var body: some View {
24 | WithViewStore(self.store, observe: { $0 }) {viewStore in
25 | VStack(alignment: .leading, spacing: 0) {
26 | HStack(alignment: .center , spacing: MTheme.H_SPACING) {
27 | IconButton(icon: "plus", name: "Add", action: {viewStore.send(.addNew)})
28 | IconButton(icon: "trash", name: "Delete", disabled: viewStore.tableState.selectIndex < 0, action: {viewStore.send(.deleteConfirm(viewStore.tableState.selectIndex))})
29 |
30 | SearchBar(placeholder: "Search field...", onCommit: {viewStore.send(.search($0))})
31 | PageBar(store: store.scope(state: \.pageState, action: HashValueStore.Action.pageAction))
32 | }
33 | .padding(EdgeInsets(top: MTheme.V_SPACING, leading: 0, bottom: MTheme.V_SPACING, trailing: 0))
34 |
35 | NTableView(store: store.scope(state: \.tableState, action: HashValueStore.Action.tableAction))
36 |
37 | // footer
38 | HStack(alignment: .center, spacing: 0) {
39 | KeyObjectBar(store: keyObjectStore)
40 | Spacer()
41 | IconButton(icon: "arrow.clockwise", name: "Refresh", action: {viewStore.send(.refresh)})
42 |
43 | }
44 | .padding(EdgeInsets(top: MTheme.V_SPACING, leading: 0, bottom: 0, trailing: 0))
45 | }
46 | .onAppear {
47 | logger.info("redis hash editor view appear ...")
48 | viewStore.send(.initial)
49 | }
50 | .sheet(isPresented: viewStore.$editModalVisible, onDismiss: {
51 | }) {
52 | ModalView("Edit hash entry", action: {viewStore.send(.submit)}) {
53 | VStack(alignment:.leading, spacing: 8) {
54 | FormItemText(placeholder: "Field", editable: viewStore.isNew, value: viewStore.$field)
55 | FormItemTextArea(placeholder: "Value", value: viewStore.$value)
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/redis-pro/Views/RedisEditorView/ListEditorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListEditorView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/30.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct ListEditorView: View {
13 |
14 | var store:StoreOf
15 | var keyObjectStore: StoreOf
16 | let logger = Logger(label: "redis-list-editor")
17 |
18 | init(store: StoreOf) {
19 | self.store = store.scope(state: \.listValueState, action: ValueStore.Action.listValueAction)
20 | self.keyObjectStore = store.scope(state: \.keyObjectState, action: ValueStore.Action.keyObjectAction)
21 | }
22 |
23 | var body: some View {
24 | WithViewStore(self.store, observe: { $0 }) { viewStore in
25 | VStack(alignment: .leading, spacing: 0) {
26 | HStack(alignment: .center , spacing: 4) {
27 | IconButton(icon: "plus", name: "Add head", action: { viewStore.send(.addNew(-1))})
28 | IconButton(icon: "plus", name: "Add tail", action: { viewStore.send(.addNew(-2))})
29 | IconButton(icon: "trash", name: "Delete", disabled: viewStore.tableState.selectIndex < 0, action: {viewStore.send(.deleteConfirm(viewStore.tableState.selectIndex))})
30 |
31 | Spacer()
32 | PageBar(store: store.scope(state: \.pageState, action: ListValueStore.Action.pageAction))
33 | }
34 | .padding(EdgeInsets(top: MTheme.V_SPACING, leading: 0, bottom: MTheme.V_SPACING, trailing: 0))
35 |
36 |
37 | NTableView(store: store.scope(state: \.tableState, action: ListValueStore.Action.tableAction))
38 |
39 | // footer
40 | HStack(alignment: .center, spacing: MTheme.H_SPACING) {
41 | KeyObjectBar(store: keyObjectStore)
42 | Spacer()
43 | IconButton(icon: "arrow.clockwise", name: "Refresh", action: {viewStore.send(.refresh)})
44 | }
45 | .padding(EdgeInsets(top: MTheme.V_SPACING, leading: 0, bottom: 0, trailing: 0))
46 | }
47 | .sheet(isPresented: viewStore.$editModalVisible, onDismiss: {
48 | }) {
49 | ModalView("Edit list item", action: {viewStore.send(.submit)}) {
50 | VStack(alignment:.leading, spacing: MTheme.V_SPACING) {
51 | FormItemTextArea(label: "", placeholder: "value", value: viewStore.$editValue)
52 | }
53 |
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
60 | //struct ListEditorView_Previews: PreviewProvider {
61 | // static var redisKeyModel:RedisKeyModel = RedisKeyModel(key: "tes", type: "string")
62 | // static var previews: some View {
63 | // ListEditorView(redisKeyModel: redisKeyModel)
64 | // }
65 | //}
66 |
--------------------------------------------------------------------------------
/redis-pro/Views/RedisEditorView/RedisValueEditView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisValueEditView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/7.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct RedisValueEditView: View {
13 |
14 | var store: StoreOf
15 |
16 | let logger = Logger(label: "redis-value-edit-view")
17 |
18 | var body: some View {
19 | WithViewStore(self.store, observe: { $0.keyState }) { viewStore in
20 | VStack(alignment: .leading, spacing: 0) {
21 | if viewStore.type == RedisKeyTypeEnum.STRING.rawValue {
22 | StringEditorView(store: store)
23 | }
24 | // HASH
25 | else if viewStore.type == RedisKeyTypeEnum.HASH.rawValue {
26 | HashEditorView(store: store)
27 | }
28 | // LIST
29 | else if viewStore.type == RedisKeyTypeEnum.LIST.rawValue {
30 | ListEditorView(store: store)
31 | }
32 | // SET
33 | else if viewStore.type == RedisKeyTypeEnum.SET.rawValue {
34 | SetEditorView(store: store)
35 | }
36 | // ZSET
37 | else if viewStore.type == RedisKeyTypeEnum.ZSET.rawValue {
38 | ZSetEditorView(store: store)
39 | } else {
40 | EmptyView()
41 | }
42 | }
43 | }
44 |
45 | }
46 |
47 | }
48 |
49 | //struct RedisValueEditView_Previews: PreviewProvider {
50 | // static var previews: some View {
51 | // RedisValueEditView(redisKeyModel: RedisKeyModel(key: "user_session:1234", type: RedisKeyTypeEnum.STRING.rawValue))
52 | // }
53 | //}
54 |
--------------------------------------------------------------------------------
/redis-pro/Views/RedisEditorView/RedisValueHeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisValueHeaderView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/7.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct RedisValueHeaderView: View {
13 |
14 | var store: StoreOf
15 | let logger = Logger(label: "redis-value-header")
16 |
17 | private func ttlView(_ viewStore: ViewStore) -> some View {
18 | HStack(alignment:.center, spacing: 0) {
19 | FormItemInt(label: "TTL(s)", value: viewStore.binding(get: \.ttl, send: KeyStore.Action.setTtl), suffix: "square.and.pencil", onCommit: { viewStore.send(.saveTtl)})
20 | .disabled(viewStore.isNew)
21 | .help("HELP_TTL")
22 | .frame(width: 260)
23 | }
24 | }
25 |
26 | var body: some View {
27 | WithViewStore(self.store, observe: { $0 }) {viewStore in
28 |
29 | HStack(alignment: .center, spacing: 6) {
30 | FormItemText(label: "Key", labelWidth: 40, required: true, editable: viewStore.isNew, value: viewStore.binding(get: \.key, send: KeyStore.Action.setKey))
31 | .frame(maxWidth: .infinity)
32 |
33 | Spacer()
34 | RedisKeyTypePicker(label: "Type", value: viewStore.binding(get: \.type, send: KeyStore.Action.setType), disabled: !viewStore.isNew)
35 | ttlView(viewStore)
36 | }
37 | }
38 | }
39 |
40 | }
41 |
42 | //struct RedisValueHeaderView_Previews: PreviewProvider {
43 | // static var previews: some View {
44 | // RedisValueHeaderView(redisKeyModel: RedisKeyModel(key: "test", type: RedisKeyTypeEnum.STRING.rawValue))
45 | // }
46 | //}
47 |
--------------------------------------------------------------------------------
/redis-pro/Views/RedisEditorView/RedisValueView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisValueView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/7.
6 | //
7 |
8 | import SwiftUI
9 | import ComposableArchitecture
10 |
11 | struct RedisValueView: View {
12 | var store: StoreOf
13 |
14 | var body: some View {
15 | VStack(alignment: .leading, spacing: 0) {
16 | RedisValueHeaderView(store: store.scope(state: \.keyState, action: ValueStore.Action.keyAction))
17 | .padding(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0))
18 | Rectangle().frame(height: 1)
19 | .padding(.horizontal, 0).foregroundColor(Color.gray.opacity(0.1))
20 |
21 | RedisValueEditView(store: store)
22 | }
23 | }
24 | }
25 |
26 | //struct RedisValueView_Previews: PreviewProvider {
27 | // @State private static var redisKeyModel = RedisKeyModel("test", type: RedisKeyTypeEnum.STRING.rawValue)
28 | // static var previews: some View {
29 | // RedisValueView(redisKeyModel: $redisKeyModel)
30 | // }
31 | //}
32 |
--------------------------------------------------------------------------------
/redis-pro/Views/RedisEditorView/SetEditorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListEditorView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/30.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct SetEditorView: View {
13 |
14 | var store:StoreOf
15 | var keyObjectStore: StoreOf
16 | let logger = Logger(label: "redis-set-editor")
17 |
18 | init(store: StoreOf) {
19 | self.store = store.scope(state: \.setValueState, action: ValueStore.Action.setValueAction)
20 | self.keyObjectStore = store.scope(state: \.keyObjectState, action: ValueStore.Action.keyObjectAction)
21 | }
22 |
23 | var body: some View {
24 | WithViewStore(self.store, observe: { $0 }) { viewStore in
25 |
26 | VStack(alignment: .leading, spacing: 0) {
27 | HStack(alignment: .center , spacing: 4) {
28 | IconButton(icon: "plus", name: "Add", action: {viewStore.send(.addNew)})
29 | IconButton(icon: "trash", name: "Delete", disabled: viewStore.tableState.selectIndex < 0, action: {viewStore.send(.deleteConfirm(viewStore.tableState.selectIndex))})
30 |
31 | SearchBar(placeholder: "Search element...", onCommit: {viewStore.send(.search($0))})
32 | PageBar(store: store.scope(state: \.pageState, action: SetValueStore.Action.pageAction))
33 | }
34 | .padding(EdgeInsets(top: MTheme.V_SPACING, leading: 0, bottom: MTheme.V_SPACING, trailing: 0))
35 |
36 | NTableView(store: store.scope(state: \.tableState, action: SetValueStore.Action.tableAction))
37 |
38 |
39 | // footer
40 | HStack(alignment: .center, spacing: MTheme.H_SPACING) {
41 | KeyObjectBar(store: keyObjectStore)
42 | Spacer()
43 | IconButton(icon: "arrow.clockwise", name: "Refresh", action: {viewStore.send(.refresh)})
44 | }
45 | .padding(EdgeInsets(top: MTheme.V_SPACING, leading: 0, bottom: 0, trailing: 0))
46 | }
47 | .sheet(isPresented: viewStore.$editModalVisible, onDismiss: {
48 | }) {
49 | ModalView("Edit set element", action: {viewStore.send(.submit)}) {
50 | VStack(alignment:.leading, spacing: MTheme.V_SPACING) {
51 | FormItemTextArea(placeholder: "value", value: viewStore.$editValue)
52 | }
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
59 | //struct SetEditorView_Previews: PreviewProvider {
60 | // static var redisKeyModel:RedisKeyModel = RedisKeyModel(key: "tes", type: "string")
61 | // static var previews: some View {
62 | // SetEditorView(redisKeyModel: redisKeyModel)
63 | // }
64 | //}
65 |
--------------------------------------------------------------------------------
/redis-pro/Views/RedisEditorView/StringEditorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringEditView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/4/7.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct StringEditorView: View {
13 | var store: StoreOf
14 | var keyObjectStore: StoreOf
15 | private let logger = Logger(label: "string-editor")
16 |
17 | init(store: StoreOf) {
18 | self.store = store.scope(state: \.stringValueState, action: ValueStore.Action.stringValueAction)
19 | self.keyObjectStore = store.scope(state: \.keyObjectState, action: ValueStore.Action.keyObjectAction)
20 | }
21 |
22 | var body: some View {
23 | WithViewStore(self.store, observe: { $0 }) {viewStore in
24 | VStack(alignment: .leading, spacing: 0) {
25 | VStack(alignment: .leading, spacing: MTheme.V_SPACING){
26 | MTextEditor(text: viewStore.$text)
27 | }
28 | .background(Color.init(NSColor.textBackgroundColor))
29 |
30 | // footer
31 | HStack(alignment: .center, spacing: MTheme.V_SPACING) {
32 | KeyObjectBar(store: keyObjectStore)
33 |
34 | if (viewStore.isIntactString) {
35 | FormText(label: "Length:", value: "\(viewStore.length)")
36 | } else {
37 | Text("Range: 0~\(viewStore.stringMaxLength + 1) / \(viewStore.length)")
38 | MButton(text: "Show Intact", action: {viewStore.send(.getIntactString)})
39 | }
40 |
41 | Spacer()
42 | MButton(text: "Pretty Json", action: {viewStore.send(.jsonFormat)})
43 | IconButton(icon: "arrow.clockwise", name: "Refresh", action: {viewStore.send(.refresh)})
44 | IconButton(icon: "checkmark", name: "Submit", disabled: !viewStore.isIntactString, action: {viewStore.send(.submit)})
45 | }
46 | .padding(EdgeInsets(top: MTheme.V_SPACING, leading: 0, bottom: 0, trailing: 0))
47 |
48 | }
49 |
50 | .onAppear {
51 | logger.info("redis string value editor view appear ...")
52 | }
53 | }
54 | }
55 |
56 | }
57 |
58 | //struct StringEditView_Previews: PreviewProvider {
59 | // static var redisKeyModel:RedisKeyModel = RedisKeyModel(key: "tes", type: "string")
60 | // static var previews: some View {
61 | // StringEditorView(redisKeyModel: redisKeyModel)
62 | // }
63 | //}
64 |
--------------------------------------------------------------------------------
/redis-pro/Views/RedisEditorView/ZSetEditorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ZSetEditorView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/7.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct ZSetEditorView: View {
13 |
14 | var store:StoreOf
15 | var keyObjectStore: StoreOf
16 | let logger = Logger(label: "redis-set-editor")
17 |
18 |
19 | init(store: StoreOf) {
20 | self.store = store.scope(state: \.zsetValueState, action: ValueStore.Action.zsetValueAction)
21 | self.keyObjectStore = store.scope(state: \.keyObjectState, action: ValueStore.Action.keyObjectAction)
22 | }
23 |
24 | var body: some View {
25 |
26 | WithViewStore(self.store, observe: { $0 }) { viewStore in
27 | VStack(alignment: .leading, spacing: 0) {
28 | HStack(alignment: .center , spacing: 4) {
29 | IconButton(icon: "plus", name: "Add", action: {viewStore.send(.addNew)})
30 | IconButton(icon: "trash", name: "Delete", disabled: viewStore.tableState.selectIndex < 0, action: {viewStore.send(.deleteConfirm(viewStore.tableState.selectIndex))})
31 |
32 | SearchBar(placeholder: "Search element...", onCommit: {viewStore.send(.search($0))})
33 | PageBar(store: store.scope(state: \.pageState, action: ZSetValueStore.Action.pageAction))
34 | }
35 | .padding(EdgeInsets(top: MTheme.V_SPACING, leading: 0, bottom: MTheme.V_SPACING, trailing: 0))
36 |
37 | NTableView(store: store.scope(state: \.tableState, action: ZSetValueStore.Action.tableAction))
38 |
39 | // footer
40 | HStack(alignment: .center, spacing: 4) {
41 | KeyObjectBar(store: keyObjectStore)
42 | Spacer()
43 | IconButton(icon: "arrow.clockwise", name: "Refresh", action: {viewStore.send(.refresh)})
44 | }
45 | .padding(EdgeInsets(top: MTheme.V_SPACING, leading: 0, bottom: 0, trailing: 0))
46 | }
47 | .sheet(isPresented: viewStore.$editModalVisible, onDismiss: {
48 | }) {
49 | ModalView("Edit zset element", action: {viewStore.send(.submit)}) {
50 | VStack(alignment:.leading, spacing: MTheme.H_SPACING) {
51 | FormItemDouble(label: "Score", placeholder: "score", value: viewStore.$editScore)
52 | FormItemTextArea(label: "Value", placeholder: "value", value: viewStore.$editValue)
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
59 | }
60 |
61 | //struct ZSetEditorView_Previews: PreviewProvider {
62 | // static var redisKeyModel:RedisKeyModel = RedisKeyModel(key: "tes", type: "string")
63 | // static var previews: some View {
64 | // ZSetEditorView(redisKeyModel: redisKeyModel)
65 | // }
66 | //}
67 |
--------------------------------------------------------------------------------
/redis-pro/Views/Sidebar/DatabasePicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatabasePicker.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/5/10.
6 | //
7 |
8 | import SwiftUI
9 | import ComposableArchitecture
10 |
11 | struct DatabasePicker: View {
12 |
13 | var store:StoreOf
14 |
15 | var body: some View {
16 | WithViewStore(self.store, observe: { $0 }) {viewStore in
17 |
18 | Menu(content: {
19 | ForEach(0 ..< viewStore.databases, id: \.self) { item in
20 | Button("DB\(item)", action: { viewStore.send(.selectDB(item))})
21 | .font(.system(size: 10.0))
22 | .foregroundColor(.primary)
23 | }
24 | }, label: {
25 | MLabel(name: "DB\(viewStore.database)", icon: "cylinder.split.1x2").font(.system(size: 8))
26 | })
27 | .scaleEffect(0.9)
28 | .frame(width:56)
29 | .menuStyle(BorderlessButtonMenuStyle())
30 | .onAppear{
31 | viewStore.send(.initial)
32 | }
33 | }
34 | }
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/redis-pro/Views/System/ClientsListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClientsListView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/18.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct ClientsListView: View {
13 |
14 | var store:StoreOf
15 |
16 | var body: some View {
17 | WithViewStore(self.store, observe: { $0 }) {viewStore in
18 | VStack(alignment: .leading, spacing: MTheme.V_SPACING) {
19 |
20 | NTableView(store: store.scope(state: \.tableState, action: ClientListStore.Action.tableAction))
21 |
22 | HStack(alignment: .center , spacing: 8) {
23 | Spacer()
24 | MButton(text: "Kill Client", action: {viewStore.send(.killConfirm(viewStore.tableState.selectIndex))}, disabled: viewStore.tableState.selectIndex < 0)
25 | MButton(text: "Refresh", action: {viewStore.send(.refresh)})
26 | }
27 | }
28 | .onAppear {
29 | viewStore.send(.initial)
30 | }
31 | }
32 | }
33 |
34 | }
35 |
36 | //struct ClientsListView_Previews: PreviewProvider {
37 | // static var previews: some View {
38 | // ClientsListView()
39 | // }
40 | //}
41 |
--------------------------------------------------------------------------------
/redis-pro/Views/System/LuaView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LuaView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/7/17.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 |
13 | struct LuaView: View {
14 | var store:StoreOf
15 | let logger = Logger(label: "lua-view")
16 |
17 | var body: some View {
18 | WithViewStore(self.store, observe: { $0 }) {viewStore in
19 | VStack(alignment: .leading, spacing: MTheme.V_SPACING) {
20 |
21 | // header
22 | HStack(alignment: .center, spacing: MTheme.H_SPACING) {
23 | Text("Eval Lua Script")
24 | Spacer()
25 | MButton(text: "Script Flush", action: { viewStore.send(.scriptFlush) })
26 | }
27 |
28 | VSplitView {
29 | VStack(alignment: .leading, spacing: MTheme.V_SPACING){
30 | // text editor
31 | MTextEditor(text: viewStore.$lua)
32 |
33 | // btns
34 | HStack(alignment: .center, spacing: MTheme.H_SPACING) {
35 | // Text("Script SHA: \(viewStore.luaSHA)")
36 | Spacer()
37 | // MButton(text: "Script Kill", action: { viewStore.send(.scriptKill) })
38 | // MButton(text: "Eval SHA", action: { viewStore.send(.eval) })
39 | MButton(text: "Eval", action: { viewStore.send(.eval) }, keyEquivalent: .return)
40 | }
41 | .padding(EdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4))
42 |
43 | }
44 |
45 | MTextEditor(text: viewStore.$evalResult)
46 | }
47 |
48 | }
49 | }
50 | }
51 | }
52 |
53 | //struct LuaView_Previews: PreviewProvider {
54 | // static var previews: some View {
55 | // LuaView()
56 | // }
57 | //}
58 |
--------------------------------------------------------------------------------
/redis-pro/Views/System/RedisConfigView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisConfigView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/21.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct RedisConfigView: View {
13 |
14 | var store:StoreOf
15 | let logger = Logger(label: "redis-config-view")
16 |
17 | var body: some View {
18 |
19 | WithViewStore(self.store, observe: { $0 }) {viewStore in
20 | VStack(alignment: .leading, spacing: MTheme.V_SPACING) {
21 | HStack(alignment: .center , spacing: MTheme.H_SPACING) {
22 |
23 | SearchBar(placeholder: "Search config...", onCommit: {viewStore.send(.search($0))})
24 |
25 | Spacer()
26 | MButton(text: "Rewrite", action: {viewStore.send(.rewrite)})
27 | .help("REDIS_CONFIG_REWRITE")
28 | }.padding(MTheme.HEADER_PADDING)
29 |
30 | NTableView(store: store.scope(state: \.tableState, action: RedisConfigStore.Action.tableAction))
31 |
32 | HStack(alignment: .center , spacing: MTheme.H_SPACING) {
33 | Spacer()
34 | MButton(text: "Refresh", action: {viewStore.send(.refresh)})
35 | }
36 | }
37 | .sheet(isPresented: viewStore.$editModalVisible, onDismiss: {
38 | }) {
39 | ModalView("Edit Config Key: \(viewStore.editKey)", action: {viewStore.send(.submit)}) {
40 | VStack(alignment:.leading, spacing: MTheme.V_SPACING) {
41 | MTextView(text: viewStore.$editValue)
42 | }
43 | .frame(minWidth:500, minHeight:300)
44 | }
45 | }
46 | .onAppear {
47 | viewStore.send(.initial)
48 | }
49 | }
50 | }
51 |
52 | }
53 |
54 | //struct RedisConfigView_Previews: PreviewProvider {
55 | // static var previews: some View {
56 | // RedisConfigView()
57 | // }
58 | //}
59 |
--------------------------------------------------------------------------------
/redis-pro/Views/System/RedisInfoView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisInfoView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/6/10.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct RedisInfoView: View {
13 | var store:StoreOf
14 |
15 | var body: some View {
16 | WithViewStore(self.store, observe: { $0 }) {viewStore in
17 | VStack(alignment: .leading, spacing: MTheme.V_SPACING) {
18 | TabView(selection: viewStore.binding(get: \.section, send: RedisInfoStore.Action.setTab)) {
19 | ForEach(viewStore.redisInfoModels.indices, id:\.self) { index in
20 | NTableView(store: store.scope(state: \.tableState, action: RedisInfoStore.Action.tableAction))
21 | .tabItem {
22 | Text(viewStore.redisInfoModels[index].section)
23 | }
24 | .tag(viewStore.redisInfoModels[index].section)
25 | }
26 | }
27 | .frame(minWidth: 500, minHeight: 600)
28 |
29 | HStack(alignment: .center , spacing: MTheme.H_SPACING) {
30 | Spacer()
31 | MButton(text: "Reset State", action: {viewStore.send(.resetState)})
32 | MButton(text: "Refresh", action: {viewStore.send(.refresh)})
33 | }
34 | }
35 | .onAppear {
36 | viewStore.send(.initial)
37 | }
38 | }
39 | }
40 | }
41 |
42 | //struct RedisInfoView_Previews: PreviewProvider {
43 | //
44 | // static var redisInstanceModel:RedisInstanceModel = RedisInstanceModel(redisModel: RedisModel(password: ""))
45 | //
46 | // static var previews: some View {
47 | // RedisInfoView().environmentObject(redisInstanceModel)
48 | // }
49 | //
50 | //}
51 |
--------------------------------------------------------------------------------
/redis-pro/Views/System/RedisSystemView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedisSystemView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpan on 2022/6/4.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct RedisSystemView: View {
13 | var store:StoreOf
14 |
15 | var body: some View {
16 | WithViewStore(self.store, observe: { $0.systemView }) {viewStore in
17 |
18 | if viewStore.state == RedisSystemViewTypeEnum.REDIS_INFO {
19 | RedisInfoView(store: store.scope(state: \.redisInfoState, action: RedisSystemStore.Action.redisInfoAction))
20 | } else if viewStore.state == RedisSystemViewTypeEnum.REDIS_INFO {
21 | RedisInfoView(store: store.scope(state: \.redisInfoState, action: RedisSystemStore.Action.redisInfoAction))
22 | } else if viewStore.state == RedisSystemViewTypeEnum.CLIENT_LIST {
23 | ClientsListView(store: store.scope(state: \.clientListState, action: RedisSystemStore.Action.clientListAction))
24 | } else if viewStore.state == RedisSystemViewTypeEnum.SLOW_LOG {
25 | SlowLogView(store: store.scope(state: \.slowLogState, action: RedisSystemStore.Action.slowLogAction))
26 | } else if viewStore.state == RedisSystemViewTypeEnum.REDIS_CONFIG {
27 | RedisConfigView(store: store.scope(state: \.redisConfigState, action: RedisSystemStore.Action.redisConfigAction))
28 | } else if viewStore.state == RedisSystemViewTypeEnum.LUA {
29 | LuaView(store: store.scope(state: \.luaState, action: RedisSystemStore.Action.luaAction))
30 | } else {
31 | EmptyView()
32 | }
33 | }
34 | }
35 | }
36 |
37 | //struct RedisSystemView_Previews: PreviewProvider {
38 | // static var previews: some View {
39 | // RedisSystemView()
40 | // }
41 | //}
42 |
--------------------------------------------------------------------------------
/redis-pro/Views/System/SlowLogView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SlowLogView.swift
3 | // redis-pro
4 | //
5 | // Created by chengpanwang on 2021/7/14.
6 | //
7 |
8 | import SwiftUI
9 | import Logging
10 | import ComposableArchitecture
11 |
12 | struct SlowLogView: View {
13 | var store:StoreOf
14 | let logger = Logger(label: "slow-log-view")
15 |
16 | var body: some View {
17 | WithViewStore(self.store, observe: { $0 }) {viewStore in
18 | VStack(alignment: .leading, spacing: MTheme.V_SPACING) {
19 | // header
20 | HStack(alignment: .center, spacing: MTheme.H_SPACING) {
21 | FormItemInt(label: "Slower Than(us)", labelWidth: 120, value: viewStore.$slowerThan, suffix: "square.and.pencil", onCommit: {viewStore.send(.setSlowerThan)})
22 | .help("REDIS_SLOW_LOG_SLOWER_THAN")
23 | .frame(width: 320)
24 | FormItemInt(label: "Max Len", value: viewStore.$maxLen, suffix: "square.and.pencil", onCommit: {viewStore.send(.setMaxLen)})
25 | .help("REDIS_SLOW_LOG_MAX_LEN")
26 | .frame(width: 200)
27 |
28 | FormItemInt(label: "Size", value: viewStore.$size, suffix: "square.and.pencil", onCommit: {viewStore.send(.setSize)})
29 | .help("REDIS_SLOW_LOG_SIZE")
30 | .frame(width: 200)
31 |
32 | Spacer()
33 | MButton(text: "Reset", action: {viewStore.send(.reset)})
34 | .help("REDIS_SLOW_LOG_RESET")
35 | }
36 |
37 | NTableView(store: store.scope(state: \.tableState, action: SlowLogStore.Action.tableAction))
38 |
39 | // footer
40 | HStack(alignment: .center, spacing: MTheme.H_SPACING_L) {
41 | Spacer()
42 | Text("Total: \(viewStore.total)")
43 | .font(.system(size: 12))
44 | .help("REDIS_SLOW_LOG_TOTAL")
45 | Text("Current: \(viewStore.tableState.datasource.count)")
46 | .font(.system(size: 12))
47 | .help("REDIS_SLOW_LOG_SIZE")
48 | IconButton(icon: "arrow.clockwise", name: "Refresh", action: {viewStore.send(.refresh)})
49 | }
50 | .padding(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0))
51 | }.onAppear {
52 | viewStore.send(.initial)
53 | }
54 | }
55 | }
56 | }
57 |
58 | //struct SlowLogView_Previews: PreviewProvider {
59 | // static var redisInstanceModel:RedisInstanceModel = RedisInstanceModel(redisModel: RedisModel())
60 | //
61 | // static var previews: some View {
62 | // SlowLogView()
63 | // .environmentObject(redisInstanceModel)
64 | // }
65 | //}
66 |
--------------------------------------------------------------------------------
/redis-pro/redis-proRelease.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.downloads.read-only
8 |
9 | com.apple.security.files.user-selected.read-write
10 |
11 | com.apple.security.network.client
12 |
13 | com.apple.security.network.server
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/redis-pro/redis_pro.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.downloads.read-only
8 |
9 | com.apple.security.files.user-selected.read-write
10 |
11 | com.apple.security.network.client
12 |
13 | com.apple.security.network.server
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------