140 Commits

Author SHA1 Message Date
4d5ca19b47 doc/changelog: changelog for v2.0.0 2025-12-20 19:19:46 +13:00
a0e70636a1 doc/LICENSE: merge qbolt/yvbolt license headers 2025-12-20 19:19:46 +13:00
13e139e023 app: show version in main window titlebar 2025-12-20 19:19:46 +13:00
1f7d03e67a doc: update screenshots 2025-12-20 19:19:46 +13:00
0b1f662e99 doc/README: add other features from qbolt's readme 2025-12-20 19:19:46 +13:00
e5f36b0f66 doc/README: remove yvbolt/qbolt cross reference link 2025-12-20 19:19:46 +13:00
0eccb12744 doc/README: add direct links to changelog/downloads 2025-12-20 19:19:46 +13:00
f2d3240153 git: merge yvbolt/qbolt changelogs as separate file 2025-12-20 19:19:46 +13:00
9fb4302000 git: merge all yvbolt/qbolt names 2025-12-20 19:19:40 +13:00
e3f94f1eba git: synthetic merge of qbolt and yvbolt branches 2025-12-20 18:32:58 +13:00
2259b3f455 doc: update changelog for v0.11.0 2025-12-20 18:24:24 +13:00
b4e8733798 doc: update TODO 2025-12-20 18:24:17 +13:00
ace5e3e65e bolt: support import/export as zip 2025-12-20 18:10:55 +13:00
e132500fd8 util: add slice_and, slice_apply 2025-12-20 18:10:33 +13:00
a6bb412a34 bolt: add comments on boltTargetBucket 2025-12-20 18:10:24 +13:00
51f2a69ed2 badger, sqlite: fix *.* spec for 'all files' in export file path 2025-12-20 17:46:07 +13:00
1db2d9781d ssh-agent: support lock/unlock 2025-12-20 17:21:35 +13:00
9f662a7fa2 badger: move encryption options to sub-struct [BREAKING] 2025-12-20 17:21:28 +13:00
034bd8114a ssh-agent: add initial support 2025-12-20 17:02:23 +13:00
7242e8644b ssh-tunnel: support ssh-agents 2025-12-20 16:43:07 +13:00
820285066b doc: update TODO 2025-12-19 19:17:46 +13:00
94c517a324 secretsvc: treat data column as binary 2025-12-19 19:11:11 +13:00
9fbc7b4ee6 config: fix saving new entries causing order to be lost 2025-12-19 19:06:51 +13:00
421cabb7a5 lmdb: support truncating existing childdb 2025-12-19 19:05:39 +13:00
910ef0dd9a makefile: add helper for running test mongodb server 2025-12-19 18:49:01 +13:00
a9c6b135c7 mongo: show index details in properties pane 2025-12-19 18:48:52 +13:00
02d4b918d6 ui: scroll to top when changing tables 2025-12-19 18:43:02 +13:00
0798547b39 ssh known_hosts support 2025-12-19 18:23:07 +13:00
3d114ec1a4 app: prevent editing if table did not load data 2025-12-19 17:55:41 +13:00
4913e36ad6 doc: changelog for v0.10.1 2025-12-17 18:17:51 +13:00
bafabdf690 makefile: build release binaries with go1.26rc1 2025-12-17 17:28:06 +13:00
4f2381ee33 go mod tidy 2025-12-17 17:27:58 +13:00
d876013ade main: fix mixing qt5 + qt6 libraries(!) 2025-12-17 17:27:54 +13:00
4e13d8dffd doc/README: changelog for v1.1.0 2025-05-04 14:45:50 +12:00
20e5efa711 makefile: set version to 1.1.0 2025-05-04 14:45:42 +12:00
6378740051 bump bbolt to v1.4.0 2025-05-04 14:43:03 +12:00
b8ce7a667b makefile: use miqt-docker for windows build 2025-05-04 14:25:22 +12:00
281ca18d90 uic: update to latest miqt-uic 2025-05-04 14:23:14 +12:00
1725de6ace import/export: use background thread 2025-04-18 12:13:56 +12:00
0ad7c03db0 makefile: add workaround for nested menu generation 2025-04-17 21:53:04 +12:00
1cdd0d113b support drag+drop database files in 2025-04-17 21:48:52 +12:00
a0fae43690 makefile/uic: work around miqt issue with SetObjectName 2025-04-17 21:27:06 +12:00
04ac766125 windows build: update build command for latest miqt 2025-04-17 21:26:51 +12:00
9ac26467c0 add zip roundtrip conversion feature 2025-04-17 21:26:27 +12:00
cbfc038839 add f1/f5 keyboard shortcuts for help/refresh 2025-04-17 21:25:38 +12:00
7ae6462da0 qbolt: do more work in []byte space instead of strings 2025-04-17 21:25:02 +12:00
09a3e5b90f main: force highdpi attributes (helps on windows) 2025-04-16 18:37:39 +12:00
1cddd17017 make(linux): smaller output binary 2025-04-16 18:33:42 +12:00
8fdc3a0428 makefile: allow overriding $GO 2025-04-16 18:17:52 +12:00
18f10fc1b4 update to miqt 0.10, update to qt 6 2025-04-16 18:17:44 +12:00
18331ae007 doc/README: updates for v1.0.3 2024-10-05 17:44:27 +13:00
f08242e93c Merge branch 'qbolt-go' 2024-10-05 17:42:14 +13:00
23965230e2 make: add windows resources, improve output compression 2024-10-05 17:41:38 +13:00
e43b261752 build: upgrade makefile/dockerfile for miqt and win64 support 2024-10-05 16:37:23 +13:00
f9b4cb71a5 qbolt: show makefile-based version number in help dialog 2024-10-05 16:37:15 +13:00
96c05641bd mod: upgrade bbolt v1.3.5 -> v1.3.11 2024-10-05 16:36:41 +13:00
2c5d1946ec qbolt: fixes post-port 2024-10-05 15:58:56 +13:00
a6cbc5a9ed qbolt: initial port to go/miqt 2024-10-03 19:34:28 +13:00
6008ae44a2 doc: rename image directory for teafolio 2021-04-12 18:03:05 +12:00
99096b2360 doc/README: add v1.0.2 changelog 2021-04-12 18:01:46 +12:00
0e92459779 no-op merge with legacy-codesite branch 2021-04-12 17:52:00 +12:00
a13eaaf523 make: upx doesn't support current mxe mingw/qt linked binaries 2021-04-12 17:44:38 +12:00
dca8d277d3 make: option to build windows binary in docker 2021-04-12 17:44:17 +12:00
15c739c0b9 squash 2021-04-12 17:43:59 +12:00
fc2da972ef dummy-data: fix build for renamed bbolt package 2021-04-12 17:07:16 +12:00
e45eea7111 makefile: remove 'hg export' function 2021-04-12 17:04:40 +12:00
1e62d79c07 doc: delete TODO.txt (issues now being tracked in Gitea)
The new issue tracker is at https://git.ivysaur.me/code.ivysaur.me/qbolt/issues
2021-04-12 17:04:27 +12:00
c74c5ae5c0 doc: move README/TODO to top-level directory 2021-04-12 17:00:49 +12:00
b0092c4a6e vendor: switch bolt implementation to etcd-io/bbolt v1.3.5 2021-04-12 17:00:40 +12:00
5ce6368c4a qt: fix missing -lpthread on Debian Bullseye 2021-04-12 16:54:06 +12:00
3e7e54da4b go: add go.mod file for bolt dependency 2021-04-12 16:53:57 +12:00
9f80d687b2 hg2git: replace hgignore/hgtags files 2021-04-12 16:53:43 +12:00
96411e877f bump all versions to 1.0.2 2017-06-19 21:04:40 +12:00
ac7e078c02 Added tag release-1.0.1 for changeset 0528a0ab20b6 2017-06-19 21:04:23 +12:00
e13314f5dc 1.0.1 meta 2017-06-19 21:04:17 +12:00
b50c3e738a doc: update features list in readme 2017-06-19 21:02:42 +12:00
54ad6015b7 more binary correctness 2017-06-19 20:53:50 +12:00
40e84ac230 dummy-data: change to generate binary names 2017-06-19 20:45:26 +12:00
767eaa0a47 add fallback display for non-printable characters 2017-06-19 20:42:51 +12:00
bb594e768c doc: update readme 2017-06-19 20:42:42 +12:00
516bd99c4d doc: update TODO 2017-06-19 20:28:33 +12:00
571bfcf4b6 one more preservation for previous 2017-06-19 20:27:52 +12:00
26f7a11d80 preserve the binary content of keys and bucket names during edit operations 2017-06-19 20:27:05 +12:00
6fb8d1ba0c commit all archived files 2017-06-19 00:00:00 +00:00
21588021d3 doc: update TODO 2017-05-25 20:00:10 +12:00
7441e0c15b option to open database as read-only 2017-05-25 19:59:53 +12:00
142f3f6bf4 track screenshot of qbolt on windows 2017-05-25 19:54:35 +12:00
19ddb1c956 select parent when deleting bucket (maybe fixes a crash?) 2017-05-25 19:53:35 +12:00
97467eae4d add icon for win32 binary 2017-05-25 19:50:11 +12:00
17c37b6568 bump dist version to 1.0.1 2017-05-21 18:23:41 +12:00
57237ef2e8 Added tag release-1.0.0 for changeset 74cacbbe8f6c 2017-05-21 18:23:29 +12:00
164f5a071d doc: update README 2017-05-21 18:22:23 +12:00
ac46b16d0e hgignore: simplify 2017-05-21 18:19:47 +12:00
b9d114a2d8 remove extra .db file from dist archives 2017-05-21 18:18:45 +12:00
b368457247 add some screenshots 2017-05-21 18:18:14 +12:00
fb940d5f60 doc: TODO.txt 2017-05-21 18:18:10 +12:00
6d1e671e44 doc: README 2017-05-21 18:18:04 +12:00
b5149c4efa bolt: strict 10 second timeout for opening databases 2017-05-21 18:05:20 +12:00
f0f642b6b0 add many more icons 2017-05-21 18:05:11 +12:00
7c62abb352 working add items 2017-05-21 17:59:27 +12:00
4f1c48b55d working edit/delete items 2017-05-21 17:53:40 +12:00
37f9307db1 wip set/delete items 2017-05-21 17:44:07 +12:00
14e7ebb59c go: simplify out a wrapper function 2017-05-21 17:26:09 +12:00
14510545d4 fix right-hand default pane when a bucket is selected 2017-05-21 17:14:32 +12:00
906cff2e57 working bucket deletions 2017-05-21 17:14:20 +12:00
bf28495b1f add our application icon to system popups 2017-05-21 17:12:02 +12:00
98b78ab1e6 option to create new buckets / sub-buckets 2017-05-21 17:08:15 +12:00
4394c8a50a option to create new databases 2017-05-21 16:50:17 +12:00
9fb5bdad78 always open databases read/write 2017-05-21 16:47:28 +12:00
d2ec12798e popup editor to view record content 2017-05-21 16:44:44 +12:00
5270dd00bc display data keys, data item length 2017-05-21 16:15:49 +12:00
35f28fa5ed makefile: fix qbolt.a target 2017-05-21 15:59:00 +12:00
5ea969e10c pretty-print json display 2017-05-21 15:49:47 +12:00
d0becd0c3c clean up qt project, always refer to makefile-produced qbolt.a files 2017-05-21 15:00:53 +12:00
1c81444645 makefile: distribution targets 2017-05-21 14:51:22 +12:00
d7c3bfd1f5 unify makefile, make win32 builds 2017-05-21 14:33:46 +12:00
0f1cc014d7 working 'refresh buckets' action 2017-05-21 13:49:41 +12:00
6ac8c3e67b remove dead code 2017-05-21 13:49:36 +12:00
546681a0ef working cgo slices, working nested-bucket operations 2017-05-21 13:47:46 +12:00
679d1140cc recursive bucket scan - we're crashing when passing a slice to go code 2017-05-21 13:31:28 +12:00
60d71104e8 retrieve bucket properties 2017-05-21 12:39:55 +12:00
8d8374be24 initial meta commit 2017-05-21 00:00:00 +00:00
95a1bfeea5 retrieve database properties 2017-05-21 11:59:38 +12:00
8597f270f6 context menus, disconnection, re-refresh, selection behaviour, ^O shortcut 2017-05-21 11:47:16 +12:00
d32ab82d73 don't pass go strings to c - pass char*+len, callee must call free() 2017-05-20 23:02:58 +12:00
b6d1cafe54 cgo: minor cleanups, remove unused export 2017-05-20 18:15:39 +12:00
3911d7d66c convert cgo function pointer call to repeated iteration 2017-05-20 18:01:55 +12:00
be594cb1a5 call bucket enumeration - function pointers are misbehaving 2017-05-20 17:28:27 +12:00
8a93f163c4 c++ wrappers for bucket enumeration function 2017-05-20 15:47:18 +12:00
e82d0a5dd2 add top-level db items to ui 2017-05-20 15:47:05 +12:00
4b3fb783c4 wire up other menu items, add app logo icon 2017-05-20 15:20:30 +12:00
e8030a9579 cgo: change interface to not return already-collected error interfaces 2017-05-20 15:13:16 +12:00
a94a330345 add GetMagic() check, move c/go interop into separate classes 2017-05-20 14:57:51 +12:00
8ce03cbed6 go: build .a instead of .so 2017-05-20 14:57:19 +12:00
b76fad9512 hgignore: pro.user file 2017-05-18 20:11:19 +12:00
a9158c3a0c gms: fix missing initialisation 2017-05-16 19:47:50 +12:00
dc31787526 track sample.db file 2017-05-16 19:47:41 +12:00
ff7484079e track dummy-data generator 2017-05-16 19:47:26 +12:00
6e1f9ca1e1 hgignore 2017-05-16 19:47:17 +12:00
ac56f5e6c8 initial commit 2017-05-16 19:34:54 +12:00
34 changed files with 971 additions and 297 deletions

11
.gitignore vendored
View File

@@ -1,8 +1,5 @@
testdata/ testdata/
liblcl-*.zip qbolt
liblcl.so qbolt.exe
liblcl.dll qbolt.linux64.tar.xz
yvbolt qbolt.win64.zip
yvbolt.exe
yvbolt.linux64.tar.xz
yvbolt.win64.zip

190
CHANGELOG.md Normal file
View File

@@ -0,0 +1,190 @@
# Changelog
2025-12-20 v2.0.0
- Merge yvbolt and QBolt together
- App: Show application version in main window titlebar
2025-12-20 v0.11.0 (yvbolt)
- Badger: Change encryption configuration options (**BREAKING**)
- SSH Agent: Initial support, including lock/unlock
- Badger, SQLite: Fix "all files" spec when choosing an export file path
- Bolt: Support import/export as zip archive
- Support SSH agents when using SSH tunnels (e.g. for Redis or Mongo)
2025-12-17 v0.10.1 (yvbolt)
- Fix mixed Qt 5 / Qt 6 syntax
- Build release binaries with Go1.26
- [⬇️ Download here](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/tag/v0.10.1)
2025-12-16 v0.10.0 (yvbolt)
- MongoDB: Initial support, including SSH tunnel, managing collections, traversing BSON documents, and querying
- LotusDB: Initial support, including editing
- Bitcask: Initial support, including editing and backup
- RoseDB: Initial support, including editing
- Badger: Fix k/v column display, support editing
- LMDB: Support editing, add warning for destructive actions
- LevelDB: Support editing
- Pebble: Support editing
- Starskey: Fix k/v column display, support editing
- Redis: Bump driver version v9.16.0 -> v9.17.2
- Bolt: Add warning for destructive actions
- Connection manager: When saving, use the database's preferred name
- App: Add hex viewer for binary data if it is not valid UTF-8
- App: Use virtualized table renderer
- App: Use multiple plug-in typed column stores in table backend
- App: Fix graphical flicker when editing connections on Windows
- App: Set up icon and exe properties on Windows
- App: Embed version number in release builds
- App: Show all driver versions as a virtual data table
- App: Show unsaved-changes warning also when changing database
- App: Fix rich text formatting appearing when pasting into the query window
2025-12-02 v0.9.0 (yvbolt)
- LMDB: Initial support, including multi-database mode, data editing, and managing child databases
- Starskey: Initial support
- Freedesktop.org Secret Service: Initial support, including unlocking collections and creating child collections
- App: Use global toolbar style
- App: Add connection manager, encrypting credentials with AEAD AES256-GCM using OS keychain
- App: Offer to save valid quick-connection to the connection manager
- App: Add 'About Qt' menu option
- App: Fixed file extension filter for database files with no extension
- App: Fixed background colour for Properties area on different OSes
- App: Fixed libpng warnings about greyscale image data for embedded logo images
- App: Fixed issue with non-UTF8 child database names
- Debconf: Improve navigation speed by splitting applications into virtual tables
- SSH: Redesign options to pick only one of the available auth methods
- Fixed running release binary on Debian 12
- [⬇️ Download here](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/tag/v0.9.0)
2025-11-23 v0.8.0 (yvbolt)
- Port from [liblcl](https://github.com/ying32/liblcl) to [MIQT Qt 6](https://github.com/mappu/miqt)
- Badger: Upgrade v4.2.0 -> v4.8.0
- Pebble: Upgrade v1.0.0 -> v1.1.5
- SQLite: Upgrade v1.14.22 -> v1.14.32
- Redis: Upgrade v9.5.3 -> v9.16.0
- Bolt: Upgrade v1.4.0-alpha.1 -> v1.4.3
- Bolt: Fix child buckets appearing in data area
- Badger, Pebble, Debconf: Remove redundant "Data" navigation layer
- Badger: Support encrypted databases
- Badger: Support readonly databases
- Badger: Add context-menu actions for backup, restore, and compact
- Pebble: Support readonly databases
- LevelDB: Add LevelDB database integration
- Redis: Support SSH tunnel
- SQLite: Allow editing the primary key column
- App: New style connection dialog
- App: Updated keyboard shortcuts (Ctrl+O to open new connection, F5 to refresh, F9 to execute query)
- App: Add confirmation when refreshing the data table if there are uncommitted changes
- [⬇️ Download here](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/tag/v0.8.0)
2025-05-04 1.1.0 (qbolt)
- New feature to import/export database as zip archive
- Upgrade to Qt 6
- Add keyboard shortcuts for refresh
- Improve High DPI support
- Rebuild artefacts with miqt v0.10.0, etcd-io/bbolt v1.4.0, go 1.23, Qt 6.8 (win64)
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.1.0)
2024-10-05 1.0.3 (qbolt)
- Port from hybrid Go/C++ to now using [MIQT](https://github.com/mappu/miqt)
- Switch Windows build to win64
- Rebuild artefacts with miqt v0.5.0, etcd-io/bbolt v1.3.11, go 1.19 (deb12), go 1.23 (win64)
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.3)
2024-07-18 v0.7.0 (yvbolt)
- SQLite, Bolt: Initial support for editing data (insert, per-cell update, delete)
- SQLite: Add context menu actions for compact (vacuum), export, and drop table
- App: New grid widget
- App: Add refresh button
- App: Bigger window size, use icons for toolbars, better UI colours for Windows
- App: Prevent submitting blank queries to database
- Refactor database interface and error handling
- [⬇️ Download here](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/tag/v0.7.0)
2024-06-30 v0.6.0 (yvbolt)
- Debconf: Add as supported database
- SQLite: Support table names containing special characters
- SQLite: Improvements for experimental command-line driver
- Redis: Improve connection dialog window position
- App: Cosmetic fixes for frame borders, help dialog, and Windows fonts+colours
- Build: Change compression parameters for release builds
- Build: Compile CGO with -O2 for release builds
- [⬇️ Download here](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/tag/v0.6.0)
2024-06-29 v0.5.0 (yvbolt)
- Pebble: Add as supported database
- Bolt: Support opening as readonly
- Bolt: Support creating new databases
- Bolt: Support adding/removing recursive child buckets
- SQLite: Support custom CLI driver that parses `/usr/bin/sqlite3 -json` output (experimental)
- Redis: Improve query parser to support quoted strings
- App: Support refreshing elements in nav tree
- App: Help menu option to show driver versions
- App: Add image icons for refresh and close context menu actions
- Build: Add makefile for cross-compiling release binaries
- [⬇️ Download here](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/tag/v0.5.0)
2024-06-23 v0.4.0 (yvbolt)
- Redis: Add as supported database
- Badger: Allow creating in-memory databases
- App: Allow selecting partial query text to execute
- App: Allow closing database connections from context menu
- App: Allow scrolling large content on Properties pane
- App: Preload recursive navigation
- App: Automatically switch to selected database when new connection is created
- App: Add help website link
- App: Add database logo images
2024-06-25 v0.3.0 (yvbolt)
- Badger: Add BadgerDB v4 as supported database
- SQLite: Add support for CGo-free SQLite driver under cross-compilation
- Bolt: Update Bolt to v1.4.0-alpha.1
- App: Add support for running custom queries
- App: Add status bar showing currently selected DB
- App: Fix missing icons in nav when selecting items
- App: Fix extra quotemarks when browsing string content of database
2024-06-08 v0.2.0 (yvbolt)
- SQLite: Add SQLite support (now requires CGo)
- App: Add images for menu and navigation items
2024-06-03 v0.1.0 (yvbolt)
- Initial public release
2020-04-12 1.0.2 (qbolt)
- Rebuild artefacts with etcd-io/bbolt v1.3.5, go 1.15, Qt 5.15, and new GCC versions
- Switch from hg to Git
- Use Go modules
- Add support for building Windows binary in Docker
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.2)
2017-06-19 1.0.1 (qbolt)
- Feature: Option to open database as read-only
- Fix an issue with support for bucket names and keys not surviving UTF-8 roundtrips (now binary-clean)
- Fix an issue with crashing when deleting a bucket other than the selected one
- Fix a cosmetic issue with application icon on Windows
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.1)
2017-05-21 1.0.0 (qbolt)
- Initial public release
- The project consists of two parts; a C binding (CGo) for the embeddable Bolt database engine, and a graphical interface built in C++/Qt that links to it.
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.0)

View File

@@ -1,7 +1,8 @@
ISC License ISC License
Copyright 2024 mappy Copyright 2025 mappy
Copyright 2024 The yvbolt Author(s) Copyright 2025 The QBolt Author(s)
Copyright 2025 The yvbolt Author(s)
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

View File

@@ -25,36 +25,43 @@ optimize-images:
optipng -quiet -o5 assets/*.png optipng -quiet -o5 assets/*.png
make generate make generate
yvbolt: $(SOURCES) qbolt: $(SOURCES)
# Target a debian-12 baseline build # Target a debian-12 baseline build
sed -i -re 's/".+"/"$(GIT_REV)"/g' version.go sed -i -re 's/".+"/"$(GIT_REV)"/g' version.go
$(MIQT_DOCKER) linux64-go1.25-qt6.4-dynamic -minify-build $(MIQT_DOCKER) linux64-go1.26-qt6.4-dynamic -minify-build
git checkout -- version.go git checkout -- version.go
chmod 755 yvbolt chmod 755 qbolt
upx --lzma yvbolt upx --lzma qbolt
yvbolt.exe: $(SOURCES) qbolt.exe: $(SOURCES)
sed -i -re 's/".+"/"$(GIT_REV)"/g' version.go sed -i -re 's/".+"/"$(GIT_REV)"/g' version.go
$(MIQT_DOCKER) win64-cross-go1.24-qt6.5-static -windows-build --tags=windowsqtstatic $(MIQT_DOCKER) win64-cross-go1.26-qt6.5-static -windows-build --tags=windowsqtstatic
git checkout -- version.go git checkout -- version.go
$(GO_WINRES) patch --in winres.json --no-backup --product-version git-tag --file-version git-tag yvbolt.exe $(GO_WINRES) patch --in winres.json --no-backup --product-version git-tag --file-version git-tag qbolt.exe
upx --lzma yvbolt.exe upx --lzma qbolt.exe
yvbolt.apk: $(SOURCES) qbolt.apk: $(SOURCES)
$(MIQT_DOCKER) android-armv8a-go1.23-qt6.6-dynamic -android-build $(MIQT_DOCKER) android-armv8a-go1.23-qt6.6-dynamic -android-build
yvbolt.linux64.tar.xz: yvbolt qbolt.linux64.tar.xz: qbolt
rm -f yvbolt.linux64.tar.xz rm -f qbolt.linux64.tar.xz
XZ_OPT='-T0 -9' tar caf yvbolt.linux64.tar.xz --owner=0 --group=0 yvbolt XZ_OPT='-T0 -9' tar caf qbolt.linux64.tar.xz --owner=0 --group=0 qbolt
yvbolt.win64.zip: yvbolt.exe qbolt.win64.zip: qbolt.exe
rm -f yvbolt.win64.zip rm -f qbolt.win64.zip
zip -9 yvbolt.win64.zip yvbolt.exe zip -9 qbolt.win64.zip qbolt.exe
.PHONY: dist .PHONY: dist
dist: yvbolt.linux64.tar.xz yvbolt.win64.zip dist: qbolt.linux64.tar.xz qbolt.win64.zip
.PHONY: clean .PHONY: clean
clean: clean:
git checkout -- version.go git checkout -- version.go
rm -f yvbolt.exe yvbolt yvbolt.linux64.tar.xz yvbolt.win64.zip rm -f qbolt.exe qbolt qbolt.linux64.tar.xz qbolt.win64.zip
#####
# Test databases in Docker
.PHONY: test-mongo
test-mongo:
sudo docker run --rm -p 127.0.0.1:27017:27017 -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=toor mongo:latest

161
README.md
View File

@@ -1,4 +1,4 @@
# yvbolt # QBolt
A graphical interface for multiple databases. A graphical interface for multiple databases.
@@ -8,23 +8,23 @@ A graphical interface for multiple databases.
- Connect to multiple databases at once - Connect to multiple databases at once
- Browse table/bucket content - Browse table/bucket content
- Use context menu to perform special table/bucket actions - Use context menu to perform special table/bucket actions
- Edit content, and add/delete rows for supported databases
- View database/bucket statistics and metadata
- Run custom SQL queries - Run custom SQL queries
- Select text to run partial query - Select text to run partial query
- Safe handling for non-UTF8 key and data fields - Safe handling for non-UTF8 key and data fields
- Hex viewer for binary data - Hex viewer for binary data
- Connection Manager saves connections with AEAD AES256-GCM using OS keychain - Connection Manager saves connections with AEAD AES256-GCM using OS keychain
See also [qbolt](https://code.ivysaur.me/qbolt) for more/different functionality.
## Supported databases ## Supported databases
There are currently 14 supported databases: There are currently 15 supported databases:
Database |Read |Editing |Query |Connection options |Context menu actions Database |Read |Editing |Query |Connection options |Context menu actions
-------------|------|---------|------|--------------------|-------- -------------|------|---------|------|--------------------|--------
Badger v4 |Yes |Yes |No |Encrypted, readonly, in-memory |Backup, restore, compact Badger v4 |Yes |Yes |No |Encrypted, readonly, in-memory |Backup, restore, compact
Bitcask |Yes |Yes |No |Readonly, autorecovery |Backup Bitcask |Yes |Yes |No |Readonly, autorecovery |Backup
Bolt |Yes |Yes |No |Readonly |Create/delete child buckets Bolt |Yes |Yes |No |Readonly |Create/delete child buckets, import/export as zip
Debconf |Yes |No |No | | Debconf |Yes |No |No | |
Freedesktop.org Secret Service |Yes |No | No | |Unlock, create new collection Freedesktop.org Secret Service |Yes |No | No | |Unlock, create new collection
LevelDB |Yes |Yes |No |Readonly | LevelDB |Yes |Yes |No |Readonly |
@@ -35,6 +35,7 @@ Pebble |Yes |Yes |No |Readonly, in-memory |
Redis |Yes |No |Yes |SSH tunnel, RESP v3 | Redis |Yes |No |Yes |SSH tunnel, RESP v3 |
RoseDB |Yes |Yes |No | | RoseDB |Yes |Yes |No | |
SQLite |Yes |Yes |Yes |CLI driver, in-memory |Vacuum, export SQLite |Yes |Yes |Yes |CLI driver, in-memory |Vacuum, export
SSH Agent |Yes |No |No |Unix/TCP |Lock, unlock
Starskey |Yes |Yes |No |Compression | Starskey |Yes |Yes |No |Compression |
## License ## License
@@ -46,150 +47,10 @@ The code in this project is licensed under the ISC license (see `LICENSE` file f
- This project includes trademarked logo images for each supported database type. - This project includes trademarked logo images for each supported database type.
- The Windows binary is released under LGPL-3+ owing to the static copy of Qt. - The Windows binary is released under LGPL-3+ owing to the static copy of Qt.
## Download
Get the latest version from [the releases page »](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases)
## Changelog ## Changelog
2025-12-16 v0.10.0 See [the full change history »](https://git.ivysaur.me/code.ivysaur.me/qbolt/src/branch/master/CHANGELOG.md)
- MongoDB: Initial support, including SSH tunnel, managing collections, traversing BSON documents, and querying
- LotusDB: Initial support, including editing
- Bitcask: Initial support, including editing and backup
- RoseDB: Initial support, including editing
- Badger: Fix k/v column display, support editing
- LMDB: Support editing, add warning for destructive actions
- LevelDB: Support editing
- Pebble: Support editing
- Starskey: Fix k/v column display, support editing
- Redis: Bump driver version v9.16.0 -> v9.17.2
- Bolt: Add warning for destructive actions
- Connection manager: When saving, use the database's preferred name
- App: Add hex viewer for binary data if it is not valid UTF-8
- App: Use virtualized table renderer
- App: Use multiple plug-in typed column stores in table backend
- App: Fix graphical flicker when editing connections on Windows
- App: Set up icon and exe properties on Windows
- App: Embed version number in release builds
- App: Show all driver versions as a virtual data table
- App: Show unsaved-changes warning also when changing database
- App: Fix rich text formatting appearing when pasting into the query window
2025-12-02 v0.9.0
- LMDB: Initial support, including multi-database mode, data editing, and managing child databases
- Starskey: Initial support
- Freedesktop.org Secret Service: Initial support, including unlocking collections and creating child collections
- App: Use global toolbar style
- App: Add connection manager, encrypting credentials with AEAD AES256-GCM using OS keychain
- App: Offer to save valid quick-connection to the connection manager
- App: Add 'About Qt' menu option
- App: Fixed file extension filter for database files with no extension
- App: Fixed background colour for Properties area on different OSes
- App: Fixed libpng warnings about greyscale image data for embedded logo images
- App: Fixed issue with non-UTF8 child database names
- Debconf: Improve navigation speed by splitting applications into virtual tables
- SSH: Redesign options to pick only one of the available auth methods
- Fixed running release binary on Debian 12
[⬇️ Download for Windows x86_64](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/download/v0.9.0/yvbolt.win64.zip)
[⬇️ Download for Linux x86_64](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/download/v0.9.0/yvbolt.linux64.tar.xz)
2025-11-23 v0.8.0
- Port from [liblcl](https://github.com/ying32/liblcl) to [MIQT Qt 6](https://github.com/mappu/miqt)
- Badger: Upgrade v4.2.0 -> v4.8.0
- Pebble: Upgrade v1.0.0 -> v1.1.5
- SQLite: Upgrade v1.14.22 -> v1.14.32
- Redis: Upgrade v9.5.3 -> v9.16.0
- Bolt: Upgrade v1.4.0-alpha.1 -> v1.4.3
- Bolt: Fix child buckets appearing in data area
- Badger, Pebble, Debconf: Remove redundant "Data" navigation layer
- Badger: Support encrypted databases
- Badger: Support readonly databases
- Badger: Add context-menu actions for backup, restore, and compact
- Pebble: Support readonly databases
- LevelDB: Add LevelDB database integration
- Redis: Support SSH tunnel
- SQLite: Allow editing the primary key column
- App: New style connection dialog
- App: Updated keyboard shortcuts (Ctrl+O to open new connection, F5 to refresh, F9 to execute query)
- App: Add confirmation when refreshing the data table if there are uncommitted changes
[⬇️ Download for Windows x86_64](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/download/v0.8.0/yvbolt.win64.zip)
[⬇️ Download for Linux x86_64](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/download/v0.8.0/yvbolt.linux64.tar.xz)
2024-07-18 v0.7.0
- SQLite, Bolt: Initial support for editing data (insert, per-cell update, delete)
- SQLite: Add context menu actions for compact (vacuum), export, and drop table
- App: New grid widget
- App: Add refresh button
- App: Bigger window size, use icons for toolbars, better UI colours for Windows
- App: Prevent submitting blank queries to database
- Refactor database interface and error handling
[⬇️ Download for Windows x86_64](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/download/v0.7.0/yvbolt.win64.zip)
[⬇️ Download for Linux x86_64](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/download/v0.7.0/yvbolt.linux64.tar.xz)
2024-06-30 v0.6.0
- Debconf: Add as supported database
- SQLite: Support table names containing special characters
- SQLite: Improvements for experimental command-line driver
- Redis: Improve connection dialog window position
- App: Cosmetic fixes for frame borders, help dialog, and Windows fonts+colours
- Build: Change compression parameters for release builds
- Build: Compile CGO with -O2 for release builds
[⬇️ Download for Windows x86_64](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/download/v0.6.0/yvbolt.win64.zip)
[⬇️ Download for Linux x86_64](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/download/v0.6.0/yvbolt.linux64.tar.xz)
2024-06-29 v0.5.0
- Pebble: Add as supported database
- Bolt: Support opening as readonly
- Bolt: Support creating new databases
- Bolt: Support adding/removing recursive child buckets
- SQLite: Support custom CLI driver that parses `/usr/bin/sqlite3 -json` output (experimental)
- Redis: Improve query parser to support quoted strings
- App: Support refreshing elements in nav tree
- App: Help menu option to show driver versions
- App: Add image icons for refresh and close context menu actions
- Build: Add makefile for cross-compiling release binaries
[⬇️ Download for Windows x86_64](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/download/v0.5.0/yvbolt.win64.zip)
[⬇️ Download for Linux x86_64](https://git.ivysaur.me/code.ivysaur.me/yvbolt/releases/download/v0.5.0/yvbolt.linux64.tar.xz)
2024-06-23 v0.4.0
- Redis: Add as supported database
- Badger: Allow creating in-memory databases
- App: Allow selecting partial query text to execute
- App: Allow closing database connections from context menu
- App: Allow scrolling large content on Properties pane
- App: Preload recursive navigation
- App: Automatically switch to selected database when new connection is created
- App: Add help website link
- App: Add database logo images
2024-06-25 v0.3.0
- Badger: Add BadgerDB v4 as supported database
- SQLite: Add support for CGo-free SQLite driver under cross-compilation
- Bolt: Update Bolt to v1.4.0-alpha.1
- App: Add support for running custom queries
- App: Add status bar showing currently selected DB
- App: Fix missing icons in nav when selecting items
- App: Fix extra quotemarks when browsing string content of database
2024-06-08 v0.2.0
- SQLite: Add SQLite support (now requires CGo)
- App: Add images for menu and navigation items
2024-06-03 v0.1.0
- Initial public release

48
TODO
View File

@@ -1,8 +1,14 @@
- BUG: Adding new entries to connection manager, then selecting them, opens wrong entry - BUG: Connecting to multiple DBs from connection manager: if one has an error, the error popup from the others is lost
- BUG: ExecQuery being called multiple times on error? - BUG: ExecQuery being called multiple times on error?
- Parity with QBolt, merge projects together - Drag and drop database into UI (QBolt parity)
- Merge with QBolt
- Portable mode (portable.txt or portable/ dir) - Portable mode (portable.txt or portable/ dir)
- Syntax highlighting in editor - Syntax highlighting in editor
- Autorefresh
- Sshagent
- want to trigger an async refresh from inside the LDB after lock/unlock
- support adding/removing keys (will need per-row actions)
- Bolt: import/export should support passworded zips
- Table: BSON view can't see data - Table: BSON view can't see data
- Table: quick filter - Table: quick filter
- QSortFilterProxyModel - QSortFilterProxyModel
@@ -14,7 +20,6 @@
- SecretService: Support insert/update/delete - SecretService: Support insert/update/delete
- Binary data viewer - Binary data viewer
- Detect jpg/png and show as image - Detect jpg/png and show as image
- Prevent editing invalid-utf8 fields as text (binary data editor?)
- More DB types - More DB types
- MySQL (& MariaDB/TiDB) - MySQL (& MariaDB/TiDB)
- Postgres - Postgres
@@ -23,6 +28,7 @@
- MSSQL (recursive navigation for instances) - MSSQL (recursive navigation for instances)
- Other K/V stores from https://github.com/smallnest/kvbench - Other K/V stores from https://github.com/smallnest/kvbench
- Windows registry - Windows registry
- Allow entering path for quick navigation
- LDAP - LDAP
- Dolt - Dolt
- Memcache - Memcache
@@ -35,27 +41,37 @@
- Etcd - Etcd
- v2: hierarchal - v2: hierarchal
- v3: flat key namespace - v3: flat key namespace
- Tokyo Cabinet, Kyoto Cabinet, Tkrzw
- https://github.com/TerraTech/go-tokyocabinet needs pkg-config tokyocabinet
- https://github.com/estraier/tkrzw-go needs pkg-config tkrzw
- CSV file - CSV file
- Allow querying with sqlite or duckDB? - Allow querying with sqlite or duckDB?
- Parquet file - Parquet file
- Allow querying with duckDB? - Allow querying with duckDB?
- IRC client
- SSDB (Redis-compatible) - SSDB (Redis-compatible)
- Time-series DBs - Time-series DBs
- Prometheus - Prometheus
- VictoriaMetrics - VictoriaMetrics
- FrostDB https://github.com/polarsignals/frostdb - FrostDB https://github.com/polarsignals/frostdb
- https://dbdb.io/browse?programming=go-lang&q= - https://dbdb.io/browse?programming=go-lang&q=
- Stoolap https://github.com/stoolap/stoolap
- Rust, needs C binding layer https://github.com/mozilla/cbindgen
- Maxmind GeoIP MMDB format - Maxmind GeoIP MMDB format
- https://github.com/maxmind/mmdbwriter - https://github.com/maxmind/mmdbwriter
- ssh-agent - KeePass kdbx
- Connection dialog - Not-quite-DBs
- SSH over Cockpit - IRC client
- Docker daemon (images, containers, ...)
- ssh-agent
- ssh known-hosts
- golang.org/x/crypto/ssh/knownhosts - already using this package
- Generic ODBC, database/sql, ...
- Other language DBs
- C, C++
- Tokyo Cabinet, Kyoto Cabinet, Tkrzw
- https://github.com/TerraTech/go-tokyocabinet needs pkg-config tokyocabinet
- https://github.com/estraier/tkrzw-go needs pkg-config tkrzw
- cdb (DJB's Constant Database)
- Rust
- Stoolap https://github.com/stoolap/stoolap
- Rust, needs C binding layer https://github.com/mozilla/cbindgen
- JDBC Java databases
- H2, HSQLDB, Apache Derby
- SQLite CLI driver: - SQLite CLI driver:
- Context support - Context support
- Write support - Write support
@@ -69,17 +85,20 @@
- SQLite: - SQLite:
- drop table doesn't autorefresh nav since callback is late - drop table doesn't autorefresh nav since callback is late
- more accurate type handling - more accurate type handling
- binary data currently shows as "<<binary>>", edits wrongly
- views - views
- other special objects (triggers? udf functions?) - other special objects (triggers? udf functions?)
- remove current hardcoded LIMIT 1000 - remove current hardcoded LIMIT 1000
- attach additional db to same connection
- LMDB: dupsort mode (duplicate keys / entries-per-key) - LMDB: dupsort mode (duplicate keys / entries-per-key)
- MongoDB - MongoDB
- UI for replica sets, ssl certs, cluster, custom auth database - UI for replica sets, ssl certs, cluster, custom auth database
- SSH tunnel: error `ssh: tcpChan: deadline not supported` - needs workaround - SSH tunnel: error `ssh: tcpChan: deadline not supported` - needs workaround
- BSON type column with autoconfig based column editor
- Backup/restore - Backup/restore
- drop db/collection doesn't autorefresh nav since server is asynchronous - drop db/collection doesn't autorefresh nav since server is asynchronous
- SSH tunnel - option to use external/system SSH - SSH tunnel
- option to use external/system SSH
- SSH over Cockpit
- Performance - Performance
- Warning if data table is filtered to 1000 rows, or add pagination - Warning if data table is filtered to 1000 rows, or add pagination
- Context/interrupt slow queries - Context/interrupt slow queries
@@ -91,4 +110,3 @@
- Export all data from grid - Export all data from grid
- Export all data from all buckets within a DB - Export all data from all buckets within a DB
- Reconnect - Reconnect
- Connecting to multiple DBs from connection manager: if one has an error, the error popup from the others is lost

BIN
assets/vendor_ssh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

View File

@@ -32,6 +32,7 @@ type ConnectionConfig struct {
Redis *redisConnectionOptions `yicon:":/assets/vendor_redis.png" json:",omitempty"` Redis *redisConnectionOptions `yicon:":/assets/vendor_redis.png" json:",omitempty"`
RoseDB *roseDBConn `ylabel:"RoseDB" yicon:":/assets/vendor_rosedb.png" json:",omitempty"` RoseDB *roseDBConn `ylabel:"RoseDB" yicon:":/assets/vendor_rosedb.png" json:",omitempty"`
SQLite *sqliteConnection `ylabel:"SQLite" yicon:":/assets/vendor_sqlite.png" json:",omitempty"` SQLite *sqliteConnection `ylabel:"SQLite" yicon:":/assets/vendor_sqlite.png" json:",omitempty"`
SSHAgent *sshAgentConn `yicon:":/assets/vendor_ssh.png" json:",omitempty"`
Starskey *starskeyConnection `yicon:":/assets/vendor_starskey.png" json:",omitempty"` Starskey *starskeyConnection `yicon:":/assets/vendor_starskey.png" json:",omitempty"`
} }
@@ -99,6 +100,11 @@ func (cc *ConnectionConfig) Connect(ctx context.Context) (loadedDatabase, string
var _ DBConnector = &ConnectionConfig{} var _ DBConnector = &ConnectionConfig{}
func (f *App) OnMnuConnectClick() { func (f *App) OnMnuConnectClick() {
config := NewConnectionConfig()
f.showConnectDialog(config)
}
func (f *App) showConnectDialog(config *ConnectionConfig) {
// autoconfig.OpenDialog() does not give us a "yes/no" result back from the // autoconfig.OpenDialog() does not give us a "yes/no" result back from the
// dialog, so we still have to custom construct the dialog and use the // dialog, so we still have to custom construct the dialog and use the
@@ -109,8 +115,6 @@ func (f *App) OnMnuConnectClick() {
dlg.ConnectDialog.SetModal(true) dlg.ConnectDialog.SetModal(true)
dlg.ConnectDialog.SetAttribute(qt.WA_DeleteOnClose) dlg.ConnectDialog.SetAttribute(qt.WA_DeleteOnClose)
config := NewConnectionConfig()
saver := autoconfig.MakeConfigArea(config, dlg.formLayout) saver := autoconfig.MakeConfigArea(config, dlg.formLayout)
dlg.ConnectDialog.OnAccept(func(super func()) { dlg.ConnectDialog.OnAccept(func(super func()) {

View File

@@ -88,6 +88,12 @@ func (f *App) getConnectionManagerContents() (*SavedConfig, error) {
return nil, err return nil, err
} }
// Before returning the configuration, sort the entries alphabetically
sort.Slice(ret.Entries, func(i, j int) bool {
// FIXME there is probably a slightly more efficient way of doing this
return strings.ToLower(ret.Entries[i].Description) < strings.ToLower(ret.Entries[j].Description)
})
// Success // Success
return &ret, nil return &ret, nil
} }
@@ -153,15 +159,7 @@ func (f *App) saveConnectionManagerContents(sc *SavedConfig) error {
} }
// Force update the saved version // Force update the saved version
sc.UserAgent = APPNAME // TODO get app's own version sc.UserAgent = APPNAME + `/` + appVersion // e.g. QBolt/v0.0.0-devel
// When saving, the version that gets saved always has descriptions sorted
// alphabetically.
// But, you won't know this until you reload the contents.
sort.Slice(sc.Entries, func(i, j int) bool {
// FIXME there is probably a slightly more efficient way of doing this
return strings.ToLower(sc.Entries[i].Description) < strings.ToLower(sc.Entries[j].Description)
})
// Marshal // Marshal

View File

@@ -82,7 +82,7 @@ func (ld *badgerLoadedDatabase) NavContext(bucketPath []string) ([]contextAction
} }
func (ld *badgerLoadedDatabase) ExportBackup(sender *qt.QTreeWidgetItem, bucketPath []string) error { func (ld *badgerLoadedDatabase) ExportBackup(sender *qt.QTreeWidgetItem, bucketPath []string) error {
saveAs := qt.QFileDialog_GetSaveFileName4(sender.TreeWidget().QWidget, "Export backup...", "", "Badger database backups (*.bak);;All files (*.*)") saveAs := qt.QFileDialog_GetSaveFileName4(sender.TreeWidget().QWidget, "Export backup...", "", "Badger database backups (*.bak);;All files (*)")
if saveAs == "" { if saveAs == "" {
return nil return nil
} }
@@ -98,7 +98,7 @@ func (ld *badgerLoadedDatabase) ExportBackup(sender *qt.QTreeWidgetItem, bucketP
} }
func (ld *badgerLoadedDatabase) ImportBackup(sender *qt.QTreeWidgetItem, bucketPath []string) error { func (ld *badgerLoadedDatabase) ImportBackup(sender *qt.QTreeWidgetItem, bucketPath []string) error {
openPath := qt.QFileDialog_GetOpenFileName4(sender.TreeWidget().QWidget, "Import backup...", "", "Badger database backups (*.bak);;All files (*.*)") openPath := qt.QFileDialog_GetOpenFileName4(sender.TreeWidget().QWidget, "Import backup...", "", "Badger database backups (*.bak);;All files (*)")
if openPath == "" { if openPath == "" {
return nil return nil
} }
@@ -140,37 +140,54 @@ var _ editableLoadedDatabase = &badgerLoadedDatabase{} // interface assertion
type badgerConnection struct { type badgerConnection struct {
Type autoconfig.OneOf Type autoconfig.OneOf
Disk *struct { Disk *struct {
Directory autoconfig.ExistingDirectory Directory autoconfig.ExistingDirectory
Readonly bool Readonly bool
Encryption_Method autoconfig.EnumList `yenum:"None;;Hex (AES-128/192/256);;Passphrase (SHA256 KDF to AES-256)"` Encryption *encryptionKey
Encryption_Key autoconfig.Password
} }
Memory *struct{} Memory *struct{}
} }
type encryptionKey struct {
Method autoconfig.EnumList `yenum:"Text;;Hex;;Passphrase (SHA256 KDF to AES-256)"`
Key autoconfig.Password
}
func (e encryptionKey) Get() ([]byte, error) {
switch e.Method {
case 0: // Text
return []byte(e.Key), nil
case 1: // Hex
// For Badger, the input must be 16/24/32 bytes for AES-128/192/256
// The library checks this, we don't need to
return hex.DecodeString(string(e.Key))
case 2: // Passphrase (SHA256 KDF to AES-256)
hasher := sha256.New()
hasher.Write([]byte(e.Key))
return hasher.Sum(nil), nil
default:
return nil, fmt.Errorf("Unsupported encoding method for encryption key")
}
}
func (bdc *badgerConnection) Connect(ctx context.Context) (loadedDatabase, string, error) { func (bdc *badgerConnection) Connect(ctx context.Context) (loadedDatabase, string, error) {
if bdc.Disk != nil { if bdc.Disk != nil {
opts := badger.DefaultOptions(string(bdc.Disk.Directory)) opts := badger.DefaultOptions(string(bdc.Disk.Directory))
opts.ReadOnly = bdc.Disk.Readonly opts.ReadOnly = bdc.Disk.Readonly
opts.MetricsEnabled = false opts.MetricsEnabled = false
switch bdc.Disk.Encryption_Method { if bdc.Disk.Encryption != nil {
case 0: // None ehx, err := bdc.Disk.Encryption.Get()
case 1: // Hex (AES-128/192/256) - must be 16/24/32 bytes
ehx, err := hex.DecodeString(string(bdc.Disk.Encryption_Key))
if err != nil { if err != nil {
return nil, "", err return nil, "", fmt.Errorf("Loading encryption key: %w", err)
} }
if !(len(ehx) == 16 || len(ehx) == 24 || len(ehx) == 32) { if !(len(ehx) == 16 || len(ehx) == 24 || len(ehx) == 32) {
return nil, "", fmt.Errorf("Hex encryption key must be 16/24/32 bytes long, got %d", len(ehx)) return nil, "", fmt.Errorf("Encryption key must be 16/24/32 bytes long, got %d", len(ehx))
} }
opts.EncryptionKey = ehx opts.EncryptionKey = ehx
case 2: // Passphrase (SHA256 KDF to AES-256)
hasher := sha256.New()
hasher.Write([]byte(bdc.Disk.Encryption_Key))
opts.EncryptionKey = hasher.Sum(nil)
} }
db, err := badger.Open(opts) db, err := badger.Open(opts)

View File

@@ -183,14 +183,30 @@ func (ld *boltLoadedDatabase) NavChildren(bucketPath []string) ([]string, error)
return boltChildBucketNames(ld.db, bucketPath) return boltChildBucketNames(ld.db, bucketPath)
} }
func (ld *boltLoadedDatabase) NavContext(bucketPath []string) (ret []contextAction, err error) { func (ld *boltLoadedDatabase) NavContext(bucketPath []string) ([]contextAction, error) {
ret = append(ret, contextAction{"Add bucket...", ld.AddChildBucket}) ret := []contextAction{
{"Add bucket...", ld.AddChildBucket},
}
if len(bucketPath) > 0 { if len(bucketPath) > 0 {
ret = append(ret, contextAction{"Delete bucket", ld.DeleteBucket}) ret = append(ret, contextAction{"Delete bucket", ld.DeleteBucket})
} }
return if len(bucketPath) == 0 {
ret = append(ret, contextAction{"Export to .zip...", ld.exportToZip})
}
return ret, nil
}
func (ld *boltLoadedDatabase) exportToZip(sender *qt.QTreeWidgetItem, bucketPath []string) error {
// Popup for output file
savePath := qt.QFileDialog_GetSaveFileName4(sender.TreeWidget().QWidget, "Save backup as...", "", "Zip archive (*.zip);;All files (*)")
if savePath == "" {
return nil // cancelled
}
return ld.exportDatabaseToZip(savePath)
} }
func (ld *boltLoadedDatabase) AddChildBucket(sender *qt.QTreeWidgetItem, bucketPath []string) error { func (ld *boltLoadedDatabase) AddChildBucket(sender *qt.QTreeWidgetItem, bucketPath []string) error {
@@ -248,6 +264,11 @@ var _ editableLoadedDatabase = &boltLoadedDatabase{} // interface assertion
// //
// boltTargetBucket resolves the bucketPath to a specific bolt.Bucket.
// If the path is empty, this function returns <nil> and you must manually access
// the root bucket.
// If the bucket does not exist, this function returns <nil>. It does not create
// the bucket.
func boltTargetBucket(tx *bbolt.Tx, path []string) *bbolt.Bucket { func boltTargetBucket(tx *bbolt.Tx, path []string) *bbolt.Bucket {
// If we are already deep in buckets, go directly there to find children // If we are already deep in buckets, go directly there to find children

230
db_bolt_zip.go Normal file
View File

@@ -0,0 +1,230 @@
package main
import (
"archive/zip"
"bytes"
"fmt"
"io"
"io/fs"
"os"
"path"
"strings"
"github.com/mappu/autoconfig"
qt "github.com/mappu/miqt/qt6"
"go.etcd.io/bbolt"
)
func (ld *boltLoadedDatabase) exportDatabaseToZip(zippath string) error {
fh, err := os.OpenFile(zippath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("Error opening output file: %w", err)
}
defer fh.Close()
zw := zip.NewWriter(fh)
// Filenames in zip files cannot contain `/` characters. Mangle it
// TODO undo this transfomation on import(!)
safename := func(n string) string {
return strings.ReplaceAll(string(n), `/`, `__`)
}
err = ld.db.View(func(tx *bbolt.Tx) error {
var process func(currentPath []string) error
process = func(currentPath []string) error {
// Create folder-entry for our own bucket
ourFolderName := path.Join(slice_apply(currentPath, safename)...)
ourBucket := zip.FileHeader{
Name: ourFolderName + `/`, // Trailing slash = directory
}
ourBucket.SetMode(fs.ModeDir | 0755)
_, err := zw.CreateHeader(&ourBucket)
if err != nil {
return err
}
// Create file entries for all non-bucket children
b := boltTargetBucket(tx, currentPath)
var childBuckets []string
var c *bbolt.Cursor
if b != nil {
c = b.Cursor() // in bucket
} else {
c = tx.Cursor()
}
for k, v := c.First(); k != nil; k, v = c.Next() {
if v == nil {
// That's a bucket
childBuckets = append(childBuckets, string(k))
continue
}
fileItem := zip.FileHeader{
Name: path.Join(ourFolderName, safename(string(k))),
}
fileItem.SetMode(0644)
fileW, err := zw.CreateHeader(&fileItem)
if err != nil {
return err
}
_, err = io.CopyN(fileW, bytes.NewReader(v), int64(len(v)))
if err != nil {
return err
}
}
// Recurse for all bucket-type children
for _, childBucketName := range childBuckets {
process(slice_and(currentPath, childBucketName))
}
// Done
return nil
}
return process([]string{})
})
if err != nil {
return err
}
err = zw.Flush()
if err != nil {
return err
}
err = zw.Close()
if err != nil {
return err
}
return fh.Close()
}
func (f *App) Bolt_ImportZipToDatabase_OnTriggered() {
zippath := qt.QFileDialog_GetOpenFileName4(f.ui.MainWindow.QWidget, "Select a zip archive to import...", "", "Zip archives (*.zip);;All files (*)")
if zippath == "" {
return // cancelled
}
dbpath := qt.QFileDialog_GetSaveFileName4(f.ui.MainWindow.QWidget, "Select an output file to save as...", "", "Bolt database (*.db);;All files (*)")
if dbpath == "" {
return // cancelled
}
err := Bolt_ImportZipToDatabase(dbpath, zippath)
if err != nil {
qt.QMessageBox_Warning(f.ui.MainWindow.QWidget, APPNAME, err.Error())
return
}
res := qt.QMessageBox_Question2(f.ui.MainWindow.QWidget, APPNAME, "The import was successful. Would you like to open the Bolt database now?", qt.QMessageBox__Yes, qt.QMessageBox__No)
if res != int(qt.QMessageBox__Yes) {
return
}
config := NewConnectionConfig()
config.Type = "Bolt"
config.Bolt = &boltConfig{
Path: autoconfig.ExistingFile(dbpath),
Readonly: false,
}
f.showConnectDialog(config)
}
func Bolt_ImportZipToDatabase(dbpath, zippath string) error {
db, err := bbolt.Open(dbpath, 0644, nil)
if err != nil {
return fmt.Errorf("Opening target database: %w", err)
}
defer db.Close()
fh, err := os.OpenFile(zippath, os.O_RDONLY, 0400)
if err != nil {
return fmt.Errorf("Opening input archive: %w", err)
}
defer fh.Close()
fstat, err := fh.Stat()
if err != nil {
return err
}
zr, err := zip.NewReader(fh, fstat.Size())
if err != nil {
return fmt.Errorf("Reading zip file format: %w", err)
}
return db.Update(func(tx *bbolt.Tx) error {
for _, zf := range zr.File {
if strings.HasSuffix(zf.Name, `/`) || (zf.Mode()&fs.ModeDir) != 0 {
// Bucket
if zf.Name == `/` {
continue // virtual entry for top-level directory, skip
}
bucketPath := strings.Split(strings.TrimSuffix(zf.Name, `/`), `/`)
parentBucket := boltTargetBucket(tx, bucketPath[0:len(bucketPath)-1])
newBucketName := []byte(bucketPath[len(bucketPath)-1])
var err error
if parentBucket == nil {
_, err = tx.CreateBucket(newBucketName) // at top level
} else {
_, err = parentBucket.CreateBucket(newBucketName) // child bucket
}
if err != nil {
return fmt.Errorf("Creating bucket %q: %w", zf.Name, err)
}
} else {
// Object
objectPath := strings.Split(zf.Name, `/`)
rc, err := zf.Open()
if err != nil {
return err
}
content, err := io.ReadAll(rc)
if err != nil {
return err
}
parentBucket := boltTargetBucket(tx, objectPath[0:len(objectPath)-1]) // Can't be nil, items always exist within a bucket
objectKey := []byte(objectPath[len(objectPath)-1])
err = parentBucket.Put(objectKey, content)
if err != nil {
return err
}
err = rc.Close()
if err != nil {
return err
}
}
}
// Done
return nil
})
}

View File

@@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"yvbolt/debconf" "qbolt/debconf"
"github.com/mappu/autoconfig" "github.com/mappu/autoconfig"
) )

View File

@@ -178,9 +178,14 @@ func (ld *lmdbDatabase) createChildDatabase(sender *qt.QTreeWidgetItem, bucketPa
}) })
} }
//func (ld *lmdbDatabase) truncateAllContent(sender *qt.QTreeWidgetItem, bucketPath []string) error { func (ld *lmdbDatabase) truncateAllContent(sender *qt.QTreeWidgetItem, bucketPath []string) error {
// TODO add warning
//} if !vcl_confirm_dialog(sender.TreeWidget().QWidget, fmt.Sprintf("Are you sure you want to empty the child database %q?", bucketPath[0])) {
return nil // cancelled
}
return ld.drop(bucketPath[0], true)
}
func (ld *lmdbDatabase) deleteChildDatabase(sender *qt.QTreeWidgetItem, bucketPath []string) error { func (ld *lmdbDatabase) deleteChildDatabase(sender *qt.QTreeWidgetItem, bucketPath []string) error {
@@ -188,8 +193,12 @@ func (ld *lmdbDatabase) deleteChildDatabase(sender *qt.QTreeWidgetItem, bucketPa
return nil // cancelled return nil // cancelled
} }
return ld.drop(bucketPath[0], true)
}
func (ld *lmdbDatabase) drop(multiDbName string, delet bool) error {
return ld.db.Update(func(txn *lmdb.Txn) error { return ld.db.Update(func(txn *lmdb.Txn) error {
dbi, err := txn.OpenDBI(bucketPath[0], 0) dbi, err := txn.OpenDBI(multiDbName, 0)
if err != nil { if err != nil {
return err return err
} }
@@ -208,6 +217,7 @@ func (ld *lmdbDatabase) NavContext(bucketPath []string) ([]contextAction, error)
}, nil }, nil
} else { } else {
return []contextAction{ return []contextAction{
contextAction{"Truncate and remove all contents...", ld.truncateAllContent},
contextAction{"Remove child database...", ld.deleteChildDatabase}, contextAction{"Remove child database...", ld.deleteChildDatabase},
}, nil }, nil
} }

View File

@@ -18,8 +18,7 @@ import (
) )
// MongoDB support // MongoDB support
// To test: // To test: `make test-mongo`
// sudo docker run --rm -p 127.0.0.1:27017:27017 -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=toor mongo:latest
type mongoLdb struct { type mongoLdb struct {
client *mongo.Client client *mongo.Client
@@ -49,13 +48,23 @@ func (ld *mongoLdb) Properties(bucketPath []string) (string, error) {
info := "Database " + db.Name() + "\n" info := "Database " + db.Name() + "\n"
info += "Collection " + coll.Name() + "\n" info += "Collection " + coll.Name() + "\n"
// Document count
count, err := coll.EstimatedDocumentCount(ctx) count, err := coll.EstimatedDocumentCount(ctx)
if err != nil { if err != nil {
return "", err return "", fmt.Errorf("Estimating document count: %w", err)
} }
info += fmt.Sprintf("Estimated document count: %d", count) + "\n" info += fmt.Sprintf("Estimated document count: %d", count) + "\n"
// TODO return more details // Index info
allIndexes, err := coll.Indexes().ListSpecifications(ctx)
if err != nil {
return "", fmt.Errorf("Checking indexes: %w", err)
}
info += fmt.Sprintf("\nIndexes (%d):\n", len(allIndexes))
for _, idxInfo := range allIndexes {
info += fmt.Sprintf("- %q (namespace=%q, version=%d)\n", idxInfo.Name, idxInfo.Namespace, idxInfo.Version)
}
return info, nil return info, nil

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"yvbolt/lexer" "qbolt/lexer"
"github.com/mappu/autoconfig" "github.com/mappu/autoconfig"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"

View File

@@ -37,7 +37,7 @@ func (ld *secretServiceDb) RenderForNav(f *tableState, bucketPath []string) erro
} else if len(bucketPath) == 1 { } else if len(bucketPath) == 1 {
f.SetupColumns( f.SetupColumns(
slice_repeat(columnType_inlineText, 6), []columnType{columnType_inlineText, columnType_inlineText, columnType_inlineText, columnType_inlineText, columnType_inlineText, columnType_popupData},
[]string{"ID", "Label", "Attributes", "ContentType", "Parameters", "Value"}, []string{"ID", "Label", "Attributes", "ContentType", "Parameters", "Value"},
) )
@@ -74,13 +74,12 @@ func (ld *secretServiceDb) RenderForNav(f *tableState, bucketPath []string) erro
} }
f.AddRowData( f.AddRowData(
// All strings
string(item), // ID string(item), // ID
label.Value().(string), // Label label.Value().(string), // Label
attrs.String(), // Attributes (JSON) attrs.String(), // Attributes (JSON)
secr.ContentType, // ContentType secr.ContentType, // ContentType
string(secr.Parameters), // Parameters string(secr.Parameters), // Parameters
string(secr.Value), // Value secr.Value, // Value - []byte
) )
} }

View File

@@ -8,7 +8,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"yvbolt/sqliteclidriver" "qbolt/sqliteclidriver"
"github.com/mappu/autoconfig" "github.com/mappu/autoconfig"
qt "github.com/mappu/miqt/qt6" qt "github.com/mappu/miqt/qt6"
@@ -365,7 +365,7 @@ func (ld *sqliteLoadedDatabase) CompactDatabase(sender *qt.QTreeWidgetItem, buck
func (ld *sqliteLoadedDatabase) ExportBackup(sender *qt.QTreeWidgetItem, bucketPath []string) error { func (ld *sqliteLoadedDatabase) ExportBackup(sender *qt.QTreeWidgetItem, bucketPath []string) error {
// Popup for output file // Popup for output file
savePath := qt.QFileDialog_GetSaveFileName4(sender.TreeWidget().QWidget, "Save backup as...", "", "SQLite database (*.db *.db3 *.sqlite *.sqlite3);;All files (*.*)") savePath := qt.QFileDialog_GetSaveFileName4(sender.TreeWidget().QWidget, "Save backup as...", "", "SQLite database (*.db *.db3 *.sqlite *.sqlite3);;All files (*)")
if savePath == "" { if savePath == "" {
return nil // cancelled return nil // cancelled
} }

179
db_sshagent.go Normal file
View File

@@ -0,0 +1,179 @@
package main
import (
"context"
"fmt"
"net"
"net/netip"
"os"
"github.com/mappu/autoconfig"
qt "github.com/mappu/miqt/qt6"
"golang.org/x/crypto/ssh/agent"
)
type sshAgentLdb struct {
conn agent.ExtendedAgent
}
func (ld *sshAgentLdb) DriverName() string {
return "ssh-agent"
}
func (ld *sshAgentLdb) Properties(bucketPath []string) (string, error) {
return "", nil
}
func (ld *sshAgentLdb) RenderForNav(f *tableState, bucketPath []string) error {
keys, err := ld.conn.List()
if err != nil {
return err
}
f.SetupColumns([]columnType{columnType_inlineText, columnType_inlineText, columnType_popupData}, []string{"Comment", "Type", "Public Key"})
for _, key := range keys {
// The publicKey blob is the effective primary-key for DB manipulation
f.AddRow_PK_Data(key.Blob, key.Comment, key.Format, key.Blob)
}
f.Ready()
return nil
}
func (ld *sshAgentLdb) NavChildren(bucketPath []string) ([]string, error) {
return []string{}, nil // No children
}
func (ld *sshAgentLdb) lockPrompt(sender *qt.QTreeWidgetItem, bucketPath []string) error {
parent := sender.TreeWidget().QWidget
props := encryptionKey{}
autoconfig.OpenDialog(&props, parent, "Enter lock password...", func() {
key, err := props.Get()
if err != nil {
qt.QMessageBox_Warning(parent, APPNAME, err.Error())
return
}
if len(key) == 0 {
// Cancelled
return
}
err = ld.conn.Lock(key)
if err != nil {
qt.QMessageBox_Warning(parent, APPNAME, "Locking SSH agent: "+err.Error())
return
}
})
return nil // n.b. refreshes now
}
func (ld *sshAgentLdb) unlockPrompt(sender *qt.QTreeWidgetItem, bucketPath []string) error {
parent := sender.TreeWidget().QWidget
props := encryptionKey{}
autoconfig.OpenDialog(&props, parent, "Enter unlock password...", func() {
key, err := props.Get()
if err != nil {
qt.QMessageBox_Warning(parent, APPNAME, err.Error())
return
}
if len(key) == 0 {
// Cancelled
return
}
err = ld.conn.Unlock(key)
if err != nil {
qt.QMessageBox_Warning(parent, APPNAME, "Unlocking SSH agent: "+err.Error())
return
}
// Trigger a refresh
})
return nil // n.b. refreshes now, which may cause double-error if we are still locked
}
func (ld *sshAgentLdb) NavContext(bucketPath []string) ([]contextAction, error) {
return []contextAction{
{"Lock agent...", ld.lockPrompt},
{"Unlock agent...", ld.unlockPrompt},
}, nil
}
func (ld *sshAgentLdb) Close() {
// noop
}
var _ loadedDatabase = &sshAgentLdb{} // interface assertion
//
type sshAgentConn struct {
Type autoconfig.OneOf
Unix *autoconfig.ExistingFile
TCP *autoconfig.AddressPort
}
func (c *sshAgentConn) Reset() {
if sshAuthSock := os.Getenv("SSH_AUTH_SOCK"); sshAuthSock != "" {
if _, err := os.Stat(sshAuthSock); err == nil {
// File
c.Type = "Unix"
val := autoconfig.ExistingFile(sshAuthSock)
c.Unix = &val
} else if props, err := netip.ParseAddrPort(sshAuthSock); err == nil {
// IP:Port
c.Type = "TCP"
val := autoconfig.AddressPort{
Address: props.Addr().String(),
Port: int(props.Port()),
}
c.TCP = &val
} else {
// Can't parse env var
}
}
}
func (c *sshAgentConn) getAgent() (agent.ExtendedAgent, error) {
if c.Unix != nil {
conn, err := net.Dial("unix", string(*c.Unix))
if err != nil {
return nil, fmt.Errorf("Connecting to SSH agent %q: %w", c.Unix, err)
}
return agent.NewClient(conn), nil
} else if c.TCP != nil {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", c.TCP.Address, c.TCP.Port))
if err != nil {
return nil, fmt.Errorf("Connecting to SSH agent %q: %w", c.TCP.String(), err)
}
return agent.NewClient(conn), nil
} else {
return nil, fmt.Errorf("No connection details specified")
}
}
func (c *sshAgentConn) Connect(ctx context.Context) (loadedDatabase, string, error) {
agent, err := c.getAgent()
if err != nil {
return nil, "", err
}
return &sshAgentLdb{conn: agent}, "SSH Agent", nil
}

BIN
doc/screenshot-000.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -39,6 +39,7 @@
<file>assets/vendor_riak.png</file> <file>assets/vendor_riak.png</file>
<file>assets/vendor_rosedb.png</file> <file>assets/vendor_rosedb.png</file>
<file>assets/vendor_sqlite.png</file> <file>assets/vendor_sqlite.png</file>
<file>assets/vendor_ssh.png</file>
<file>assets/vendor_starskey.png</file> <file>assets/vendor_starskey.png</file>
</qresource> </qresource>
</RCC> </RCC>

BIN
embed.rcc

Binary file not shown.

8
go.mod
View File

@@ -1,4 +1,4 @@
module yvbolt module qbolt
go 1.24.0 go 1.24.0
@@ -10,15 +10,18 @@ require (
github.com/godbus/dbus/v5 v5.2.0 github.com/godbus/dbus/v5 v5.2.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/ledgerwatch/lmdb-go v1.18.2 github.com/ledgerwatch/lmdb-go v1.18.2
github.com/lotusdblabs/lotusdb/v2 v2.1.0
github.com/mappu/autoconfig v0.4.1 github.com/mappu/autoconfig v0.4.1
github.com/mappu/miqt v0.12.1-0.20251106063543-466b60e47edf github.com/mappu/miqt v0.12.1-0.20251106063543-466b60e47edf
github.com/mattn/go-sqlite3 v1.14.32 github.com/mattn/go-sqlite3 v1.14.32
github.com/redis/go-redis/v9 v9.17.2 github.com/redis/go-redis/v9 v9.17.2
github.com/rosedblabs/rosedb/v2 v2.3.6
github.com/starskey-io/starskey v0.1.9 github.com/starskey-io/starskey v0.1.9
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
github.com/zalando/go-keyring v0.2.6 github.com/zalando/go-keyring v0.2.6
go.etcd.io/bbolt v1.4.3 go.etcd.io/bbolt v1.4.3
go.mills.io/bitcask/v2 v2.1.5
go.mongodb.org/mongo-driver/v2 v2.4.1 go.mongodb.org/mongo-driver/v2 v2.4.1
golang.org/x/crypto v0.46.0 golang.org/x/crypto v0.46.0
) )
@@ -54,7 +57,6 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/lotusdblabs/lotusdb/v2 v2.1.0 // indirect
github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
@@ -66,7 +68,6 @@ require (
github.com/robfig/cron/v3 v3.0.0 // indirect github.com/robfig/cron/v3 v3.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rosedblabs/diskhash v0.0.0-20230910084041-289755737e2a // indirect github.com/rosedblabs/diskhash v0.0.0-20230910084041-289755737e2a // indirect
github.com/rosedblabs/rosedb/v2 v2.3.6 // indirect
github.com/rosedblabs/wal v1.3.6 // indirect github.com/rosedblabs/wal v1.3.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
@@ -76,7 +77,6 @@ require (
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect
go.mills.io/bitcask/v2 v2.1.5 // indirect
go.mongodb.org/mongo-driver v1.17.6 // indirect go.mongodb.org/mongo-driver v1.17.6 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect

14
go.sum
View File

@@ -32,8 +32,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs=
github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w=
@@ -76,13 +76,17 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hashicorp/go-immutable-radix/v2 v2.0.0 h1:nq9lQ5I71Heg2lRb2/+szuIWKY3Y73d8YKyXyN91WzU= github.com/hashicorp/go-immutable-radix/v2 v2.0.0 h1:nq9lQ5I71Heg2lRb2/+szuIWKY3Y73d8YKyXyN91WzU=
github.com/hashicorp/go-immutable-radix/v2 v2.0.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= github.com/hashicorp/go-immutable-radix/v2 v2.0.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -118,8 +122,8 @@ github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTw
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
@@ -140,16 +144,14 @@ github.com/rosedblabs/diskhash v0.0.0-20230910084041-289755737e2a h1:BNp46nsknQi
github.com/rosedblabs/diskhash v0.0.0-20230910084041-289755737e2a/go.mod h1:3xvIg+7iOFUL/vMCE/6DwE6Yecb0okVYJBEfpdC/E+8= github.com/rosedblabs/diskhash v0.0.0-20230910084041-289755737e2a/go.mod h1:3xvIg+7iOFUL/vMCE/6DwE6Yecb0okVYJBEfpdC/E+8=
github.com/rosedblabs/rosedb/v2 v2.3.6 h1:o8vVOp61hFdORrz/PTosqU21/Z2Bug5I7cy1D3MZh2M= github.com/rosedblabs/rosedb/v2 v2.3.6 h1:o8vVOp61hFdORrz/PTosqU21/Z2Bug5I7cy1D3MZh2M=
github.com/rosedblabs/rosedb/v2 v2.3.6/go.mod h1:/de9n2CoYaAGBDxZTJC5Jb0LCQOtoA3GOom+9QD9Z98= github.com/rosedblabs/rosedb/v2 v2.3.6/go.mod h1:/de9n2CoYaAGBDxZTJC5Jb0LCQOtoA3GOom+9QD9Z98=
github.com/rosedblabs/rosedb/v2 v2.4.0 h1:gwI3zHm5tJlvCb6DMtHEhHsq/BiWS2vNS7Lg409ii6k=
github.com/rosedblabs/rosedb/v2 v2.4.0/go.mod h1:yehP2SiNojwdleiYhvjQU9jWPuXhO7hbWBzohueVPVY=
github.com/rosedblabs/wal v1.3.6 h1:oxZYTPX/u4JuGDW98wQ1YamWqerlrlSUFKhgP6Gd/Ao= github.com/rosedblabs/wal v1.3.6 h1:oxZYTPX/u4JuGDW98wQ1YamWqerlrlSUFKhgP6Gd/Ao=
github.com/rosedblabs/wal v1.3.6/go.mod h1:wdq54KJUyVTOv1uddMc6Cdh2d/YCIo8yjcwJAb1RCEM= github.com/rosedblabs/wal v1.3.6/go.mod h1:wdq54KJUyVTOv1uddMc6Cdh2d/YCIo8yjcwJAb1RCEM=
github.com/rosedblabs/wal v1.3.8 h1:tErpD9JT/ICiyV3mv5l7qUH6lybn5XF1TbI0e8kvH8M=
github.com/rosedblabs/wal v1.3.8/go.mod h1:DFvhrmTTeiXvn2btXXT2MW9Nvu99PU0g/pKGgh0+T+o=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=

20
main.go
View File

@@ -7,14 +7,14 @@ import (
"os" "os"
"strings" "strings"
"github.com/mappu/miqt/qt/mainthread" "github.com/mappu/miqt/qt6/mainthread"
qt "github.com/mappu/miqt/qt6" qt "github.com/mappu/miqt/qt6"
) )
const ( const (
APPNAME = "yvbolt" APPNAME = "QBolt"
HOMEPAGE_URL = "https://code.ivysaur.me/yvbolt" HOMEPAGE_URL = "https://code.ivysaur.me/qbolt"
) )
type App struct { type App struct {
@@ -37,6 +37,7 @@ func newApp() *App {
a.dbs = make(map[int]loadedDatabase, 0) a.dbs = make(map[int]loadedDatabase, 0)
a.ui = NewMainWindowUi() a.ui = NewMainWindowUi()
a.ui.MainWindow.SetWindowTitle(APPNAME + " " + appVersion)
// Using stylesheet works better than picking a palette colour across all // Using stylesheet works better than picking a palette colour across all
// different QStyles // different QStyles
@@ -54,6 +55,8 @@ func newApp() *App {
a.ui.mnuDriverVersions.OnTriggered(a.OnMenuHelpVersion) a.ui.mnuDriverVersions.OnTriggered(a.OnMenuHelpVersion)
a.ui.mnuHelpAbout.OnTriggered(a.OnMnuHelpHomepage) a.ui.mnuHelpAbout.OnTriggered(a.OnMnuHelpHomepage)
a.ui.actionCreate_Bolt_database_from_zip.OnTriggered(a.Bolt_ImportZipToDatabase_OnTriggered)
// //
a.ui.Buckets.OnCurrentItemChanged(a.OnNavChange) a.ui.Buckets.OnCurrentItemChanged(a.OnNavChange)
@@ -277,7 +280,7 @@ func (f *App) OnDataCommitClick() {
f.refreshContent(node) // Refresh RHS pane/data content f.refreshContent(node) // Refresh RHS pane/data content
// Preserve scroll position // Preserve scroll position
// TODO // (Happens automatically with QTableView)
} }
func (f *App) OnNavContextClose() { func (f *App) OnNavContextClose() {
@@ -422,6 +425,12 @@ func (f *App) OnNavChange(node *qt.QTreeWidgetItem, prev *qt.QTreeWidgetItem) {
} }
} }
if node != prev {
// Changing tables (not just refreshing the current table)
// Reset scroll position to top
f.ui.contentBox.ScrollToTop()
}
// OK, continue with change // OK, continue with change
f.refreshContent(node) f.refreshContent(node)
} }
@@ -461,7 +470,8 @@ func (f *App) refreshContent(node *qt.QTreeWidgetItem) {
// Toggle the Edit functionality // Toggle the Edit functionality
// Do this *after* RenderForNav as it disables editing by default. // Do this *after* RenderForNav as it disables editing by default.
_, editable := ld.(editableLoadedDatabase) _, editable := ld.(editableLoadedDatabase)
f.ui.actionApply_changes.SetEnabled(false) // will be enabled after add/delete/edit editable = editable && f.contentTbl.IsReady() // if there was a failure loading, don't allow edit
f.ui.actionApply_changes.SetEnabled(false) // will be enabled after add/delete/edit
f.ui.actionDelete_row.SetEnabled(editable) f.ui.actionDelete_row.SetEnabled(editable)
f.ui.actionAddRow.SetEnabled(editable) f.ui.actionAddRow.SetEnabled(editable)
f.contentTbl.SetAllowEditing(editable) f.contentTbl.SetAllowEditing(editable)

View File

@@ -10,41 +10,43 @@ import (
) )
type MainWindowUi struct { type MainWindowUi struct {
MainWindow *qt.QMainWindow MainWindow *qt.QMainWindow
centralwidget *qt.QWidget centralwidget *qt.QWidget
gridLayout *qt.QGridLayout gridLayout *qt.QGridLayout
splitter *qt.QSplitter splitter *qt.QSplitter
Buckets *qt.QTreeWidget Buckets *qt.QTreeWidget
tabWidget *qt.QTabWidget tabWidget *qt.QTabWidget
tabProperties *qt.QWidget tabProperties *qt.QWidget
gridLayout_2 *qt.QGridLayout gridLayout_2 *qt.QGridLayout
propertiesBox *qt.QTextEdit propertiesBox *qt.QTextEdit
tabData *qt.QWidget tabData *qt.QWidget
verticalLayout *qt.QVBoxLayout verticalLayout *qt.QVBoxLayout
contentBox *qt.QTableView contentBox *qt.QTableView
tabQuery *qt.QWidget tabQuery *qt.QWidget
verticalLayout_2 *qt.QVBoxLayout verticalLayout_2 *qt.QVBoxLayout
splitter_2 *qt.QSplitter splitter_2 *qt.QSplitter
queryInput *qt.QPlainTextEdit queryInput *qt.QPlainTextEdit
queryResult *qt.QTableView queryResult *qt.QTableView
menubar *qt.QMenuBar menubar *qt.QMenuBar
menu_File *qt.QMenu menu_File *qt.QMenu
menu_Query *qt.QMenu menu_Query *qt.QMenu
menu_Help *qt.QMenu menu_Help *qt.QMenu
menu_Data *qt.QMenu menu_Data *qt.QMenu
statusbar *qt.QStatusBar menu_Tools *qt.QMenu
toolBar *qt.QToolBar statusbar *qt.QStatusBar
actionE_xit *qt.QAction toolBar *qt.QToolBar
mnuExecute *qt.QAction actionE_xit *qt.QAction
mnuDriverVersions *qt.QAction mnuExecute *qt.QAction
mnuHelpAbout *qt.QAction mnuDriverVersions *qt.QAction
actionConnect *qt.QAction mnuHelpAbout *qt.QAction
actionRefresh *qt.QAction actionConnect *qt.QAction
actionAbout_Qt *qt.QAction actionRefresh *qt.QAction
actionAddRow *qt.QAction actionAbout_Qt *qt.QAction
actionDelete_row *qt.QAction actionAddRow *qt.QAction
actionApply_changes *qt.QAction actionDelete_row *qt.QAction
actionConnectionManager *qt.QAction actionApply_changes *qt.QAction
actionConnectionManager *qt.QAction
actionCreate_Bolt_database_from_zip *qt.QAction
} }
// NewMainWindowUi creates all Qt widget classes for MainWindow. // NewMainWindowUi creates all Qt widget classes for MainWindow.
@@ -137,6 +139,10 @@ func NewMainWindowUi() *MainWindowUi {
icon9.AddFile4(":/assets/database_key.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) icon9.AddFile4(":/assets/database_key.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off)
ui.actionConnectionManager.SetIcon(icon9) ui.actionConnectionManager.SetIcon(icon9)
/* miqt-uic: no handler for QAction property 'menuRole' */ /* miqt-uic: no handler for QAction property 'menuRole' */
ui.actionCreate_Bolt_database_from_zip = qt.NewQAction()
actionCreate_Bolt_database_from_zip__objectName := qt.NewQAnyStringView3("actionCreate_Bolt_database_from_zip")
ui.actionCreate_Bolt_database_from_zip.SetObjectName(*actionCreate_Bolt_database_from_zip__objectName)
actionCreate_Bolt_database_from_zip__objectName.Delete() // setter copied value
ui.centralwidget = qt.NewQWidget(ui.MainWindow.QWidget) ui.centralwidget = qt.NewQWidget(ui.MainWindow.QWidget)
centralwidget__objectName := qt.NewQAnyStringView3("centralwidget") centralwidget__objectName := qt.NewQAnyStringView3("centralwidget")
ui.centralwidget.SetObjectName(*centralwidget__objectName) ui.centralwidget.SetObjectName(*centralwidget__objectName)
@@ -289,9 +295,15 @@ func NewMainWindowUi() *MainWindowUi {
ui.menu_Data.QWidget.AddAction(ui.actionAddRow) ui.menu_Data.QWidget.AddAction(ui.actionAddRow)
ui.menu_Data.QWidget.AddAction(ui.actionDelete_row) ui.menu_Data.QWidget.AddAction(ui.actionDelete_row)
ui.menu_Data.QWidget.AddAction(ui.actionApply_changes) ui.menu_Data.QWidget.AddAction(ui.actionApply_changes)
ui.menu_Tools = qt.NewQMenu(ui.menubar.QWidget)
menu_Tools__objectName := qt.NewQAnyStringView3("menu_Tools")
ui.menu_Tools.SetObjectName(*menu_Tools__objectName)
menu_Tools__objectName.Delete() // setter copied value
ui.menu_Tools.QWidget.AddAction(ui.actionCreate_Bolt_database_from_zip)
ui.menubar.AddMenu(ui.menu_File) ui.menubar.AddMenu(ui.menu_File)
ui.menubar.AddMenu(ui.menu_Data) ui.menubar.AddMenu(ui.menu_Data)
ui.menubar.AddMenu(ui.menu_Query) ui.menubar.AddMenu(ui.menu_Query)
ui.menubar.AddMenu(ui.menu_Tools)
ui.menubar.AddMenu(ui.menu_Help) ui.menubar.AddMenu(ui.menu_Help)
ui.MainWindow.SetMenuBar(ui.menubar) ui.MainWindow.SetMenuBar(ui.menubar)
ui.statusbar = qt.NewQStatusBar(ui.MainWindow.QWidget) ui.statusbar = qt.NewQStatusBar(ui.MainWindow.QWidget)
@@ -326,12 +338,13 @@ func NewMainWindowUi() *MainWindowUi {
// Retranslate reapplies all text translations. // Retranslate reapplies all text translations.
func (ui *MainWindowUi) Retranslate() { func (ui *MainWindowUi) Retranslate() {
ui.MainWindow.SetWindowTitle(qt.QCoreApplication_Tr("yvbolt")) ui.MainWindow.SetWindowTitle(qt.QCoreApplication_Tr("QBolt"))
ui.actionE_xit.SetText(qt.QMainWindow_Tr("E&xit")) ui.actionE_xit.SetText(qt.QMainWindow_Tr("E&xit"))
ui.mnuExecute.SetText(qt.QMainWindow_Tr("E&xecute")) ui.mnuExecute.SetText(qt.QMainWindow_Tr("E&xecute"))
ui.mnuExecute.SetShortcut(qt.NewQKeySequence2(qt.QMainWindow_Tr("F9"))) ui.mnuExecute.SetShortcut(qt.NewQKeySequence2(qt.QMainWindow_Tr("F9")))
ui.mnuDriverVersions.SetText(qt.QMainWindow_Tr("Driver versions...")) ui.mnuDriverVersions.SetText(qt.QMainWindow_Tr("Driver versions..."))
ui.mnuHelpAbout.SetText(qt.QMainWindow_Tr("&About yvbolt")) ui.mnuHelpAbout.SetText(qt.QMainWindow_Tr("&About QBolt"))
ui.mnuHelpAbout.SetToolTip(qt.QMainWindow_Tr("About QBolt"))
ui.mnuHelpAbout.SetShortcut(qt.NewQKeySequence2(qt.QMainWindow_Tr("F1"))) ui.mnuHelpAbout.SetShortcut(qt.NewQKeySequence2(qt.QMainWindow_Tr("F1")))
ui.actionConnect.SetText(qt.QMainWindow_Tr("Connect...")) ui.actionConnect.SetText(qt.QMainWindow_Tr("Connect..."))
ui.actionConnect.SetShortcut(qt.NewQKeySequence2(qt.QMainWindow_Tr("Ctrl+O"))) ui.actionConnect.SetShortcut(qt.NewQKeySequence2(qt.QMainWindow_Tr("Ctrl+O")))
@@ -344,6 +357,7 @@ func (ui *MainWindowUi) Retranslate() {
ui.actionApply_changes.SetText(qt.QMainWindow_Tr("Apply changes")) ui.actionApply_changes.SetText(qt.QMainWindow_Tr("Apply changes"))
ui.actionConnectionManager.SetText(qt.QMainWindow_Tr("Connection Manager")) ui.actionConnectionManager.SetText(qt.QMainWindow_Tr("Connection Manager"))
ui.actionConnectionManager.SetToolTip(qt.QMainWindow_Tr("Connection Manager")) ui.actionConnectionManager.SetToolTip(qt.QMainWindow_Tr("Connection Manager"))
ui.actionCreate_Bolt_database_from_zip.SetText(qt.QMainWindow_Tr("Create Bolt database from zip"))
ui.tabWidget.SetTabText(ui.tabWidget.IndexOf(ui.tabProperties), qt.QTabWidget_Tr("Properties")) ui.tabWidget.SetTabText(ui.tabWidget.IndexOf(ui.tabProperties), qt.QTabWidget_Tr("Properties"))
ui.tabWidget.SetTabText(ui.tabWidget.IndexOf(ui.tabData), qt.QTabWidget_Tr("Data")) ui.tabWidget.SetTabText(ui.tabWidget.IndexOf(ui.tabData), qt.QTabWidget_Tr("Data"))
ui.tabWidget.SetTabText(ui.tabWidget.IndexOf(ui.tabQuery), qt.QTabWidget_Tr("Query")) ui.tabWidget.SetTabText(ui.tabWidget.IndexOf(ui.tabQuery), qt.QTabWidget_Tr("Query"))
@@ -351,5 +365,6 @@ func (ui *MainWindowUi) Retranslate() {
ui.menu_Query.SetTitle(qt.QMenuBar_Tr("&Query")) ui.menu_Query.SetTitle(qt.QMenuBar_Tr("&Query"))
ui.menu_Help.SetTitle(qt.QMenuBar_Tr("&Help")) ui.menu_Help.SetTitle(qt.QMenuBar_Tr("&Help"))
ui.menu_Data.SetTitle(qt.QMenuBar_Tr("&Data")) ui.menu_Data.SetTitle(qt.QMenuBar_Tr("&Data"))
ui.menu_Tools.SetTitle(qt.QMenuBar_Tr("&Tools"))
ui.toolBar.SetWindowTitle(qt.QMainWindow_Tr("toolBar")) ui.toolBar.SetWindowTitle(qt.QMainWindow_Tr("toolBar"))
} }

View File

@@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>yvbolt</string> <string>QBolt</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="embed.qrc"> <iconset resource="embed.qrc">
@@ -179,9 +179,16 @@
<addaction name="actionDelete_row"/> <addaction name="actionDelete_row"/>
<addaction name="actionApply_changes"/> <addaction name="actionApply_changes"/>
</widget> </widget>
<widget class="QMenu" name="menu_Tools">
<property name="title">
<string>&amp;Tools</string>
</property>
<addaction name="actionCreate_Bolt_database_from_zip"/>
</widget>
<addaction name="menu_File"/> <addaction name="menu_File"/>
<addaction name="menu_Data"/> <addaction name="menu_Data"/>
<addaction name="menu_Query"/> <addaction name="menu_Query"/>
<addaction name="menu_Tools"/>
<addaction name="menu_Help"/> <addaction name="menu_Help"/>
</widget> </widget>
<widget class="QStatusBar" name="statusbar"/> <widget class="QStatusBar" name="statusbar"/>
@@ -245,7 +252,10 @@
<normaloff>:/assets/help.png</normaloff>:/assets/help.png</iconset> <normaloff>:/assets/help.png</normaloff>:/assets/help.png</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;About yvbolt</string> <string>&amp;About QBolt</string>
</property>
<property name="toolTip">
<string>About QBolt</string>
</property> </property>
<property name="shortcut"> <property name="shortcut">
<string>F1</string> <string>F1</string>
@@ -347,6 +357,11 @@
<enum>QAction::MenuRole::NoRole</enum> <enum>QAction::MenuRole::NoRole</enum>
</property> </property>
</action> </action>
<action name="actionCreate_Bolt_database_from_zip">
<property name="text">
<string>Create Bolt database from zip</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="embed.qrc"/> <include location="embed.qrc"/>

View File

@@ -23,7 +23,7 @@ import (
"io" "io"
"os/exec" "os/exec"
"yvbolt/lexer" "qbolt/lexer"
"github.com/google/uuid" "github.com/google/uuid"
) )
@@ -136,7 +136,7 @@ func (s *SCStmt) buildQuery(args []driver.Value) ([]byte, error) {
// Embed query params // Embed query params
// WARNING: Not secure against injection? That's not a security boundary // WARNING: Not secure against injection? That's not a security boundary
// for the purposes of yvbolt, but maybe for other package users // for the purposes of qbolt, but maybe for other package users
var querybuilder bytes.Buffer var querybuilder bytes.Buffer

View File

@@ -5,10 +5,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path/filepath"
"github.com/mappu/autoconfig" "github.com/mappu/autoconfig"
qt "github.com/mappu/miqt/qt6" qt "github.com/mappu/miqt/qt6"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
) )
type SSHTunnel struct { type SSHTunnel struct {
@@ -28,11 +30,40 @@ type SSHTunnel struct {
PrivateKeyContent autoconfig.MultiLineString `ylabel:"Private Key"` PrivateKeyContent autoconfig.MultiLineString `ylabel:"Private Key"`
PrivateKeyContentPassword autoconfig.Password `ylabel:"Password (optional)"` PrivateKeyContentPassword autoconfig.Password `ylabel:"Password (optional)"`
} `ylabel:"Private key (Internal)"` } `ylabel:"Private key (Internal)"`
SSHAgent *sshAgentConn `ylabel:"SSH Agent"`
}
H2 autoconfig.Header `ylabel:"Server host key:" json:",omitempty"`
HostVerification struct {
Type autoconfig.OneOf
InsecureSkipVerify *struct{}
ExternalKnownHostsFile *struct {
Path autoconfig.ExistingFile
}
} }
} }
func (s *SSHTunnel) Reset() { // Used by autoconfig package when creating a new value func (s *SSHTunnel) Reset() { // Used by autoconfig package when creating a new value
s.Server.Port = 22 s.Server.Port = 22
if sshAuthSock := os.Getenv("SSH_AUTH_SOCK"); sshAuthSock != "" {
s.Auth.Type = "SSHAgent"
s.Auth.SSHAgent = &sshAgentConn{}
s.Auth.SSHAgent.Reset()
}
if homeDir, err := os.UserHomeDir(); err == nil {
defaultKnownHostsFile := filepath.Join(homeDir, `.ssh`, `known_hosts`)
if _, err := os.Stat(defaultKnownHostsFile); err == nil {
s.HostVerification.Type = "ExternalKnownHostsFile"
alloc_inplace(&s.HostVerification.ExternalKnownHostsFile)
s.HostVerification.ExternalKnownHostsFile.Path = autoconfig.ExistingFile(defaultKnownHostsFile)
}
}
if s.HostVerification.Type == "" {
s.HostVerification.Type = "InsecureSkipVerify"
alloc_inplace(&s.HostVerification.InsecureSkipVerify)
}
} }
func (s *SSHTunnel) String() string { func (s *SSHTunnel) String() string {
@@ -56,8 +87,7 @@ func (s SSHTunnel) Open(ctx context.Context) (*ssh.Client, error) {
return nil return nil
}, },
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // FIXME User: s.Username,
User: s.Username,
} }
if s.Auth.Password != nil { if s.Auth.Password != nil {
@@ -86,8 +116,40 @@ func (s SSHTunnel) Open(ctx context.Context) (*ssh.Client, error) {
return nil, fmt.Errorf("Parsing private key: %w", err) return nil, fmt.Errorf("Parsing private key: %w", err)
} }
cfg.Auth = append(cfg.Auth, ssh.PublicKeys(signer)) cfg.Auth = append(cfg.Auth, ssh.PublicKeys(signer))
} else if s.Auth.SSHAgent != nil {
sa, err := s.Auth.SSHAgent.getAgent()
if err != nil {
return nil, fmt.Errorf("Connecting to SSH agent: %w", err)
}
cfg.Auth = append(cfg.Auth, ssh.PublicKeysCallback(sa.Signers))
} else {
return nil, fmt.Errorf("Unsupported value %q for Auth", s.Auth.Type)
} }
//
if s.HostVerification.Type == "" || s.HostVerification.InsecureSkipVerify != nil {
cfg.HostKeyCallback = ssh.InsecureIgnoreHostKey()
} else if s.HostVerification.ExternalKnownHostsFile != nil {
cb, err := knownhosts.New(string(s.HostVerification.ExternalKnownHostsFile.Path))
if err != nil {
return nil, fmt.Errorf("Parsing known_hosts file %q: %w")
}
cfg.HostKeyCallback = cb
} else {
return nil, fmt.Errorf("Unsupported value %q for HostVerification", s.HostVerification.Type)
}
//
// TODO DialContext // TODO DialContext
// Use wrapper from https://github.com/golang/go/issues/20288 // Use wrapper from https://github.com/golang/go/issues/20288
return ssh.Dial("tcp", fmt.Sprintf("%s:%d", s.Server.Address, s.Server.Port), &cfg) return ssh.Dial("tcp", fmt.Sprintf("%s:%d", s.Server.Address, s.Server.Port), &cfg)

View File

@@ -202,6 +202,10 @@ func (ts *tableState) Ready() {
ts.tbl.SetEnabled(true) ts.tbl.SetEnabled(true)
} }
func (ts *tableState) IsReady() bool {
return ts.tbl.IsEnabled()
}
// DeleteSelectedRows marks the selected rows for deletion. // DeleteSelectedRows marks the selected rows for deletion.
func (ts *tableState) DeleteSelectedRows() { func (ts *tableState) DeleteSelectedRows() {
rows := ts.tbl.SelectionModel().SelectedRows() rows := ts.tbl.SelectionModel().SelectedRows()

View File

@@ -32,6 +32,7 @@ func (bc *binColumn) Display(aRow int) *qt.QVariant {
func (bc *binColumn) Inspect(parent *qt.QWidget, aRow int) { func (bc *binColumn) Inspect(parent *qt.QWidget, aRow int) {
// do nothing // do nothing
// TODO support hexview for noneditable columns
} }
func (bc *binColumn) SimpleEditData(aRow int) *qt.QVariant { func (bc *binColumn) SimpleEditData(aRow int) *qt.QVariant {

View File

@@ -54,6 +54,24 @@ func slice_remove_index[T comparable](slice []T, s int) []T {
return append(slice[:s], slice[s+1:]...) return append(slice[:s], slice[s+1:]...)
} }
// slice_and copies a slice and adds one more thing on the end.
func slice_and[T comparable](base []T, and T) []T {
ret := make([]T, len(base)+1)
copy(ret, base)
ret[len(ret)-1] = and
return ret
}
// slice_apply creates a new slice where every element of the input arr is transformed.
func slice_apply[T comparable](arr []T, transform func(T) T) []T {
ret := make([]T, len(arr))
for i := 0; i < len(arr); i++ {
ret[i] = transform(arr[i])
}
return ret
}
// slice_set_len sets the length of the slice. Use it like append - // slice_set_len sets the length of the slice. Use it like append -
// x = slice_set_len(x, 10) // x = slice_set_len(x, 10)
func slice_set_len[T any](slice []T, newlen int) []T { func slice_set_len[T any](slice []T, newlen int) []T {
@@ -94,3 +112,8 @@ func bson_find_id(doc bson.D) (string, bool) {
return "", false return "", false
} }
func alloc_inplace[T any](target **T) {
var newObj T
*target = &newObj
}

View File

@@ -15,11 +15,11 @@
"0409": { "0409": {
"Comments": "", "Comments": "",
"CompanyName": "code.ivysaur.me", "CompanyName": "code.ivysaur.me",
"FileDescription": "yvbolt Database Editor", "FileDescription": "QBolt Database Editor",
"FileVersion": "0.0.0.0", "FileVersion": "0.0.0.0",
"LegalCopyright": "code.ivysaur.me", "LegalCopyright": "code.ivysaur.me",
"LegalTrademarks": "", "LegalTrademarks": "",
"ProductName": "yvbolt", "ProductName": "QBolt",
"ProductVersion": "0.0.0" "ProductVersion": "0.0.0"
} }
} }