mirror of
https://github.com/garraflavatra/rolens.git
synced 2025-07-07 09:34:05 +00:00
Compare commits
68 Commits
Author | SHA1 | Date | |
---|---|---|---|
0027a4333b
|
|||
2a29f5226c | |||
a12417d4e9
|
|||
58dd9de4ba
|
|||
77ce1ab842
|
|||
b5bb51c869
|
|||
dc0094b27c
|
|||
964e66e8a3
|
|||
7eb41630db
|
|||
2ecc664e48
|
|||
756fa10555
|
|||
bd265d0548
|
|||
c41572dbb7
|
|||
9a07954e4c
|
|||
535b474d65
|
|||
43b059a579
|
|||
0365e8e0ec
|
|||
fcdefedffd
|
|||
005f23c322
|
|||
030edaa561
|
|||
a4262a6ab7 | |||
982d112665 | |||
e388b2513b | |||
c751169b7d
|
|||
0a3f99fa32
|
|||
a1456b3987 | |||
3fe5f09163
|
|||
29154d04d7 | |||
cde13d9ec0 | |||
19d9b8addd | |||
67ebaecb31 | |||
3fd2be55b0
|
|||
2ee5c7dadd
|
|||
6cc329982a
|
|||
15d30e67c3
|
|||
abc9df0897
|
|||
be4e3e778e
|
|||
be7643bd31
|
|||
ea376f5ba7
|
|||
b29b534b2f | |||
775e4d98d8 | |||
f44165e41a
|
|||
9c1e6fe37a | |||
c0f072b72e | |||
8e5beb9a2d | |||
4e12591fc8 | |||
0ddf172327 | |||
cc1f67af45
|
|||
6e7ca49623
|
|||
1524cdb244
|
|||
2fc7607ea7
|
|||
1327714557
|
|||
f2401a07c2
|
|||
7ae3fdab53
|
|||
1308d74967
|
|||
cc1b4fb04c
|
|||
12d398320d
|
|||
c5c3c6f873
|
|||
9090f9425f
|
|||
5240bdefab
|
|||
d0b5c0616e
|
|||
cd946307db
|
|||
ec99c15ff2
|
|||
536740259d
|
|||
a44f4c8b7e
|
|||
4b618f9b25
|
|||
5463b1a2c4
|
|||
7b8efa3022
|
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -1,2 +1,5 @@
|
|||||||
|
# Let GitHub Linguist ignore documentation and Wails generated files
|
||||||
|
# https://github.com/github-linguist/linguist/blob/master/docs/overrides.md
|
||||||
/frontend/src/components/icon.svelte linguist-vendored
|
/frontend/src/components/icon.svelte linguist-vendored
|
||||||
/frontend/wailsjs/**/* linguist-generated
|
/frontend/wailsjs/**/* linguist-generated
|
||||||
|
/website/**/* linguist-documentation
|
||||||
|
4
.github/ISSUE_TEMPLATE/docs.yml
vendored
4
.github/ISSUE_TEMPLATE/docs.yml
vendored
@ -22,10 +22,8 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Problem
|
label: Problem
|
||||||
description: Why is it wrong?
|
description: Why is it wrong?
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
- type: textarea
|
||||||
id: solution
|
id: solution
|
||||||
attributes:
|
attributes:
|
||||||
label: Solution
|
label: Solution
|
||||||
|
59
.github/workflows/ci.yml
vendored
59
.github/workflows/ci.yml
vendored
@ -13,7 +13,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-2022, macos-12, ubuntu-22.04]
|
platform:
|
||||||
|
- windows-2019
|
||||||
|
- windows-2022
|
||||||
|
- macos-11
|
||||||
|
- macos-12
|
||||||
|
- macos-13
|
||||||
|
- ubuntu-20.04
|
||||||
|
- ubuntu-22.04
|
||||||
go-version: [1.18]
|
go-version: [1.18]
|
||||||
node-version: [16]
|
node-version: [16]
|
||||||
|
|
||||||
@ -32,8 +39,12 @@ jobs:
|
|||||||
- name: Install Wails
|
- name: Install Wails
|
||||||
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||||||
|
|
||||||
- name: Install Wails dependencies for Linux
|
- name: Install build dependencies for macOS
|
||||||
if: matrix.platform == 'ubuntu-22.04'
|
if: contains(matrix.platform, 'macos')
|
||||||
|
run: npm install --global appdmg
|
||||||
|
|
||||||
|
- name: Install build dependencies for Linux
|
||||||
|
if: contains(matrix.platform, 'ubuntu')
|
||||||
run: sudo apt-get install gtk+-3.0 webkit2gtk-4.0
|
run: sudo apt-get install gtk+-3.0 webkit2gtk-4.0
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
@ -43,14 +54,44 @@ jobs:
|
|||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: frontend/package-lock.json
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Cross-compile Rolens for Windows
|
||||||
run: cd frontend && npm ci && cd ..
|
if: contains(matrix.platform, 'windows')
|
||||||
|
run: ./build/windows/ci_generate.ps1 -platform "${{ matrix.platform }}"
|
||||||
|
|
||||||
- name: Build Rolens
|
- name: Cross-compile Rolens for Darwin
|
||||||
run: wails build
|
if: contains(matrix.platform, 'macos')
|
||||||
|
run: ./build/darwin/ci_generate.sh "${{ matrix.platform }}"
|
||||||
|
|
||||||
- name: Upload binary
|
- name: Cross-compile Rolens for Linux
|
||||||
|
if: contains(matrix.platform, 'ubuntu')
|
||||||
|
run: ./build/linux/ci_generate.sh "${{ matrix.platform }}"
|
||||||
|
|
||||||
|
- name: Upload generated binaries
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: rolens-${{ matrix.platform }}
|
name: rolens-${{ matrix.platform }}
|
||||||
path: build/bin/*
|
path: releases/*
|
||||||
|
|
||||||
|
bundle:
|
||||||
|
name: Bundle artifacts
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: build
|
||||||
|
if: ${{ always() }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
|
||||||
|
- name: Place all tarballs in the same directory
|
||||||
|
run: build/ci_bundle.sh
|
||||||
|
|
||||||
|
- name: Upload the bundle as an artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: rolens-bundle
|
||||||
|
path: bundle
|
||||||
|
24
.github/workflows/lint.yml
vendored
Normal file
24
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: Linter
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
eslint:
|
||||||
|
name: Run ESlint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
working-directory: ./frontend
|
||||||
|
|
||||||
|
- name: Run ESLint
|
||||||
|
run: npx eslint .
|
||||||
|
working-directory: ./frontend
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,7 +1,11 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
/build/version.txt
|
||||||
/build/bin/
|
/build/bin/
|
||||||
/build/windows/installer/tmp/
|
/build/windows/installer/tmp/
|
||||||
|
/build/darwin/dmg_settings.json
|
||||||
|
|
||||||
|
/releases
|
||||||
|
|
||||||
/frontend/node_modules/
|
/frontend/node_modules/
|
||||||
/frontend/dist/
|
/frontend/dist/
|
||||||
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@ -14,5 +14,12 @@
|
|||||||
"[css]": {
|
"[css]": {
|
||||||
"editor.suggest.insertMode": "replace",
|
"editor.suggest.insertMode": "replace",
|
||||||
"editor.tabSize": 2
|
"editor.tabSize": 2
|
||||||
}
|
},
|
||||||
|
|
||||||
|
"eslint.format.enable": true,
|
||||||
|
"eslint.lintTask.enable": true,
|
||||||
|
"eslint.lintTask.options": "frontend",
|
||||||
|
"eslint.workingDirectories": [
|
||||||
|
"frontend"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
17
CHANGELOG.md
17
CHANGELOG.md
@ -1,9 +1,22 @@
|
|||||||
## [v0.2.0](https://github.com/garraflavatra/rolens/releases/tag/v0.2.0)
|
## [v0.2.1]
|
||||||
|
|
||||||
|
* Display host and database statistics generated by the corresponding diagnostic MongoDB commands in addition to collection stats (#15).
|
||||||
|
* Added version number of the running Rolens instance in the about dialog (#25, #28).
|
||||||
|
* Added meaningful window titles, and actually show these in the title bar (macOS).
|
||||||
|
* Corrected link to documentation in the about box (#30).
|
||||||
|
* Fixed host/database selection bug in grid (#31, #32), involving a frontend refactoring.
|
||||||
|
* Replaced (some) harsh loading dialogs with smooth spinners, and replaced (some) capricious error dialogs with friendly error messages.
|
||||||
|
|
||||||
|
## [v0.2.0]
|
||||||
|
|
||||||
* Added some external links related to Rolens to the application menu.
|
* Added some external links related to Rolens to the application menu.
|
||||||
* Fix an infinite loop bug when a document in the find view has been double clicked.
|
* Fix an infinite loop bug when a document in the find view has been double clicked.
|
||||||
* Find view: added the ability to make changes to single documents from within the object editor.
|
* Find view: added the ability to make changes to single documents from within the object editor.
|
||||||
|
|
||||||
## [v0.1.0](https://github.com/garraflavatra/rolens/releases/tag/v0.1.0)
|
## [v0.1.0]
|
||||||
|
|
||||||
Initial release.
|
Initial release.
|
||||||
|
|
||||||
|
[v0.1.0]: https://github.com/garraflavatra/rolens/releases/tag/v0.1.0
|
||||||
|
[v0.2.0]: https://github.com/garraflavatra/rolens/releases/tag/v0.2.0
|
||||||
|
[v0.2.1]: https://github.com/garraflavatra/rolens/releases/tag/v0.2.1
|
||||||
|
@ -37,6 +37,6 @@ To obtain a copy of the source code, do either of the following:
|
|||||||
`cd` into the root directory of the source code and run either:
|
`cd` into the root directory of the source code and run either:
|
||||||
|
|
||||||
* `wails build` to generate an executable for your platform.
|
* `wails build` to generate an executable for your platform.
|
||||||
* `wails build -nsis` to generate an [NSIS installer](https://nsis.sourceforge.io/Main_Page). This requires that you have NSIS installed on your machine.
|
* `wails build -nsis` to generate an [NSIS installer](https://nsis.sourceforge.io/Main_Page) for Windows. This requires that you have NSIS installed on your machine.
|
||||||
|
|
||||||
The generated binary will live in `build/bin`. You may want to run the installer (Windows) or move the app to the Applications folder (Mac).
|
The generated binary will live in `build/bin`. You may want to run the installer (Windows) or move the app to the Applications folder (Mac).
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
Robust, blazing-fast, comprehensive, yet simple [MongoDB](https://www.mongodb.com/) administration tool for Windows, macOS and Linux.
|
Robust, blazing-fast, comprehensive, yet simple [MongoDB](https://www.mongodb.com/) administration tool for Windows, macOS and Linux.
|
||||||
|
|
||||||
<a href="https://github.com/garraflavatra/rolens/actions/workflows/ci.yml" target="_blank"><img src="https://github.com/garraflavatra/rolens/actions/workflows/ci.yml/badge.svg" alt="CI" /></a> <a target="_blank" href="https://garraflavatra.github.io/rolens"><img src="./.github/docs-badge.svg" alt="Documentation" /></a> <a href="https://fosstodon.org/@rolens" target="_blank" rel="me"><img src="./.github/fosstodon-badge.svg" alt="Fosstodon" /></a>
|
<a href="https://github.com/garraflavatra/rolens/actions/workflows/ci.yml" target="_blank"><img src="https://github.com/garraflavatra/rolens/actions/workflows/ci.yml/badge.svg" alt="CI" /></a> <a target="_blank" href="https://garraflavatra.github.io/rolens"><img src="./.github/docs-badge.svg" alt="Documentation" /></a>
|
||||||
|
|
||||||
|
<!-- <a href="https://fosstodon.org/@rolens" target="_blank" rel="me"><img src="./.github/fosstodon-badge.svg" alt="Fosstodon" /></a> -->
|
||||||
|
|
||||||
## Why another MongoDB client?
|
## Why another MongoDB client?
|
||||||
|
|
||||||
@ -54,5 +56,6 @@ Feel free to contact me if you have questions! [Send an e-mail.](mailto:romein@v
|
|||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
* [Wails](https://wails.io/) facilitates the build process for multiple OSes.
|
||||||
* Icons are from [Feather Icons](https://feathericons.com/) by [Cole Bemis](https://github.com/colebemis).
|
* Icons are from [Feather Icons](https://feathericons.com/) by [Cole Bemis](https://github.com/colebemis).
|
||||||
* Vector drawings come from [unDraw](https://undraw.co/).
|
* Vector drawings come from [unDraw](https://undraw.co/).
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Build Directory
|
# Build Directory
|
||||||
|
|
||||||
The build directory is used to house all the build files and assets for your application.
|
The build directory is used to house all the build files and assets for your application.
|
||||||
|
|
||||||
The structure is:
|
The structure is:
|
||||||
|
|
||||||
@ -10,10 +10,7 @@ The structure is:
|
|||||||
|
|
||||||
## Mac
|
## Mac
|
||||||
|
|
||||||
The `darwin` directory holds files specific to Mac builds.
|
The `darwin` directory holds files specific to Mac builds. These may be customised and used as part of the build. To return these files to the default state, simply delete them and build with `wails build`.
|
||||||
These may be customised and used as part of the build. To return these files to the default state, simply delete them
|
|
||||||
and
|
|
||||||
build with `wails build`.
|
|
||||||
|
|
||||||
The directory contains the following files:
|
The directory contains the following files:
|
||||||
|
|
||||||
@ -22,14 +19,9 @@ The directory contains the following files:
|
|||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
The `windows` directory contains the manifest and rc files used when building with `wails build`.
|
The `windows` directory contains the manifest and rc files used when building with `wails build`. These may be customised for your application. To return these files to the default state, simply delete them and build with `wails build`.
|
||||||
These may be customised for your application. To return these files to the default state, simply delete them and
|
|
||||||
build with `wails build`.
|
|
||||||
|
|
||||||
- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to
|
- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file will be created using the `appicon.png` file in the build directory.
|
||||||
use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file
|
|
||||||
will be created using the `appicon.png` file in the build directory.
|
|
||||||
- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
|
- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
|
||||||
- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer,
|
- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, as well as the application itself (right click the exe -> properties -> details)
|
||||||
as well as the application itself (right click the exe -> properties -> details)
|
- `wails.exe.manifest` - The main application manifest file.
|
||||||
- `wails.exe.manifest` - The main application manifest file.
|
|
||||||
|
41
build/ci_bundle.sh
Executable file
41
build/ci_bundle.sh
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# This script bundles the binaries generated by GitHub Actions.
|
||||||
|
#
|
||||||
|
# Platforms to choose from:
|
||||||
|
# - windows-2019
|
||||||
|
# - windows-2022
|
||||||
|
# - macos-11
|
||||||
|
# - macos-12
|
||||||
|
# - macos-13
|
||||||
|
# - ubuntu-20.04
|
||||||
|
# - ubuntu-22.04
|
||||||
|
#
|
||||||
|
# Bundles to choose from:
|
||||||
|
# - rolens-macos-11-amd64.tar.gz
|
||||||
|
# - rolens-macos-11-arm64.tar.gz
|
||||||
|
# - rolens-macos-12-amd64.tar.gz
|
||||||
|
# - rolens-macos-12-arm64.tar.gz
|
||||||
|
# - rolens-macos-13-amd64.tar.gz
|
||||||
|
# - rolens-macos-13-arm64.tar.gz
|
||||||
|
# - rolens-ubuntu-20.04-amd64.tar.gz
|
||||||
|
# - rolens-ubuntu-22.04-amd64.tar.gz
|
||||||
|
# - rolens-windows-2019-amd64.zip
|
||||||
|
# - rolens-windows-2019-arm64.zip
|
||||||
|
# - rolens-windows-2022-amd64.zip
|
||||||
|
# - rolens-windows-2022-arm64.zip
|
||||||
|
#
|
||||||
|
|
||||||
|
node ./build/version_to_file.js
|
||||||
|
version=$(<./build/version.txt)
|
||||||
|
|
||||||
|
mkdir bundle
|
||||||
|
|
||||||
|
# macOS binaries
|
||||||
|
mv artifacts/*/rolens-macos-11-amd64.tar.gz bundle/rolens-$version-macos-amd64.tar.gz
|
||||||
|
mv artifacts/*/rolens-macos-11-arm64.tar.gz bundle/rolens-$version-macos-arm64.tar.gz
|
||||||
|
|
||||||
|
# Windows binaries
|
||||||
|
mv artifacts/*/rolens-windows-2019-amd64.zip bundle/rolens-$version-windows-amd64.zip
|
||||||
|
mv artifacts/*/rolens-windows-2019-arm64.zip bundle/rolens-$version-windows-arm64.zip
|
60
build/darwin/ci_generate.sh
Executable file
60
build/darwin/ci_generate.sh
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -rf releases
|
||||||
|
rm -rf build/bin
|
||||||
|
mkdir releases
|
||||||
|
mkdir -p build/bin
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
cat > build/darwin/dmg_settings.json << EOF
|
||||||
|
{
|
||||||
|
"title": "Rolens",
|
||||||
|
"background": "$(pwd)/build/darwin/dmg_background.png",
|
||||||
|
"icon-size": 100,
|
||||||
|
"window": {
|
||||||
|
"size": { "width": 155, "height": 250 },
|
||||||
|
"position": { "x": 360, "y": 360 }
|
||||||
|
},
|
||||||
|
"contents": [
|
||||||
|
{ "x": 750, "y": 500, "type": "link", "path": "/Applications" },
|
||||||
|
{ "x": 595, "y": 250, "type": "file", "path": "$(pwd)/build/bin/Rolens.app" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# AMD/Intel
|
||||||
|
wails build -platform darwin/amd64
|
||||||
|
# create-dmg \
|
||||||
|
# --volname Rolens \
|
||||||
|
# --window-size 155 250 \
|
||||||
|
# --volicon build/appicon.png \
|
||||||
|
# --eula LICENSE \
|
||||||
|
# --app-drop-link 750 500 \
|
||||||
|
# --icon-size 100 \
|
||||||
|
# --background build/darwin/dmg_background.png \
|
||||||
|
# --add-file Rolens.app build/bin/Rolens.app 595 250 \
|
||||||
|
# build/bin/Rolens.dmg emptydir
|
||||||
|
# appdmg build/darwin/dmg_settings.json build/bin/Rolens.dmg
|
||||||
|
tar -czvf releases/rolens-$1-amd64.tar.gz --directory build/bin Rolens.app
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -rf build/bin/Rolens.app
|
||||||
|
|
||||||
|
# ARM/AppleM1
|
||||||
|
wails build -platform darwin/arm64
|
||||||
|
# create-dmg \
|
||||||
|
# --volname Rolens \
|
||||||
|
# --window-size 155 250 \
|
||||||
|
# --volicon build/appicon.png \
|
||||||
|
# --eula LICENSE \
|
||||||
|
# --app-drop-link 750 500 \
|
||||||
|
# --icon-size 100 \
|
||||||
|
# --background build/darwin/dmg_background.png \
|
||||||
|
# --add-file Rolens.app build/bin/Rolens.app 595 250 \
|
||||||
|
# build/bin/Rolens.dmg emptydir
|
||||||
|
# appdmg build/darwin/dmg_settings.json build/bin/Rolens.dmg
|
||||||
|
tar -czvf releases/rolens-$1-arm64.tar.gz --directory build/bin Rolens.app
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -rf build/bin/Rolens.app
|
BIN
build/darwin/dmg_background.png
Normal file
BIN
build/darwin/dmg_background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
9
build/linux/ci_generate.sh
Executable file
9
build/linux/ci_generate.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
mkdir releases
|
||||||
|
wails build -platform linux/amd64
|
||||||
|
tar -czvf releases/rolens-$1-amd64.tar.gz --directory build/bin Rolens
|
||||||
|
|
||||||
|
# rm -rf build/bin
|
||||||
|
# wails build -platform linux/arm64
|
||||||
|
# tar -czvf releases/rolens-$1-arm64.tar.gz --directory build/bin Rolens
|
32
build/release_readme.txt
Normal file
32
build/release_readme.txt
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
Thank you for downloading Rolens!
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Star the project on GitHub:
|
||||||
|
https://github.com/garraflavatra/rolens
|
||||||
|
|
||||||
|
User guide
|
||||||
|
----------
|
||||||
|
|
||||||
|
Rolens is designed to be as intuitive as possible. But if something is unclear
|
||||||
|
nevertheless, you can consult the user manual:
|
||||||
|
https://garraflavatra.github.io/rolens/
|
||||||
|
|
||||||
|
Questions and bugs
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Did you capture a bug? Please report it — thank you!
|
||||||
|
https://github.com/garraflavatra/rolens/issues/new?assignees=garraflavatra&labels=bug&projects=&template=bug.yml
|
||||||
|
|
||||||
|
Would you like to see a new feature? You can request it:
|
||||||
|
https://github.com/garraflavatra/rolens/issues/new?assignees=garraflavatra&labels=enhancement&projects=&template=feature.yml
|
||||||
|
|
||||||
|
Do you have a question? Ask questions on the discussion board:
|
||||||
|
https://github.com/garraflavatra/rolens/discussions/new?category=questions
|
||||||
|
|
||||||
|
Feel free to contact me if you have questions! Send an e-mail to romein@vburen.nl
|
||||||
|
|
||||||
|
Author and license
|
||||||
|
------------------
|
||||||
|
|
||||||
|
© Romein van Buren 2023. The source code and compiled binaries are released
|
||||||
|
under the GNU GPLv3 license — see LICENSE for the full license text.
|
13
build/version_to_file.js
Normal file
13
build/version_to_file.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/node
|
||||||
|
|
||||||
|
// This script extracts the version number from wails.json in the project root
|
||||||
|
// and writes it to version.txt
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
__dirname + '/version.txt',
|
||||||
|
JSON.parse(
|
||||||
|
fs.readFileSync(__dirname + '/../wails.json')
|
||||||
|
).info.productVersion
|
||||||
|
);
|
9
build/windows/ci_generate.ps1
Executable file
9
build/windows/ci_generate.ps1
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
param([string]$platform)
|
||||||
|
|
||||||
|
mkdir releases
|
||||||
|
wails build -platform windows/amd64
|
||||||
|
Compress-Archive -Path build\bin\* -DestinationPath releases\rolens-$platform-amd64.zip
|
||||||
|
|
||||||
|
Remove-Item -Recurse -Confirm:$false .\build\bin
|
||||||
|
wails build -platform windows/arm64
|
||||||
|
Compress-Archive -Path build\bin\* -DestinationPath releases\rolens-$platform-arm64.zip
|
@ -7,5 +7,6 @@ Rolens is © [Romein van Buren](mailto:romein@vburen.nl) 2023. The source code a
|
|||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
* [Wails](https://wails.io/) facilitates the build process for multiple OSes.
|
||||||
* Icons are from [Feather Icons](https://feathericons.com/) by [Cole Bemis](https://github.com/colebemis).
|
* Icons are from [Feather Icons](https://feathericons.com/) by [Cole Bemis](https://github.com/colebemis).
|
||||||
* Vector drawings come from [unDraw](https://undraw.co/).
|
* Vector drawings come from [unDraw](https://undraw.co/).
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 623 KiB After Width: | Height: | Size: 451 KiB |
@ -1,195 +0,0 @@
|
|||||||
{
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2020,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true,
|
|
||||||
"es6": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:svelte/recommended"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"svelte",
|
|
||||||
"import"
|
|
||||||
],
|
|
||||||
"overrides": [{
|
|
||||||
"files": "*.svelte",
|
|
||||||
"rules": {
|
|
||||||
"no-inner-declarations": 0,
|
|
||||||
"svelte/no-inner-declarations": [
|
|
||||||
"error",
|
|
||||||
"functions"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
"rules": {
|
|
||||||
"no-undef": [
|
|
||||||
"error",
|
|
||||||
{ "typeof": true }
|
|
||||||
],
|
|
||||||
"require-atomic-updates": 0,
|
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
2,
|
|
||||||
{ "SwitchCase": 1 }
|
|
||||||
],
|
|
||||||
"strict": 0,
|
|
||||||
"quotes": [ "error", "single" ],
|
|
||||||
"semi": [ "warn", "always" ],
|
|
||||||
"accessor-pairs": "error",
|
|
||||||
"array-bracket-spacing": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"arrow-body-style": [
|
|
||||||
"error",
|
|
||||||
"as-needed",
|
|
||||||
{ "requireReturnForObjectLiteral": true }
|
|
||||||
],
|
|
||||||
"arrow-parens": [ "error", "as-needed" ],
|
|
||||||
"arrow-spacing": "error",
|
|
||||||
"block-spacing": [ "error", "always" ],
|
|
||||||
"comma-dangle": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"arrays": "always-multiline",
|
|
||||||
"objects": "always-multiline",
|
|
||||||
"imports": "never",
|
|
||||||
"exports": "never",
|
|
||||||
"functions": "never"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"comma-spacing": "error",
|
|
||||||
"computed-property-spacing": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"generator-star-spacing": "error",
|
|
||||||
"id-blacklist": "error",
|
|
||||||
"id-match": "error",
|
|
||||||
"jsx-quotes": "error",
|
|
||||||
"keyword-spacing": "error",
|
|
||||||
"key-spacing": [
|
|
||||||
"warn",
|
|
||||||
{ "beforeColon": false }
|
|
||||||
],
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"unix"
|
|
||||||
],
|
|
||||||
"no-unused-vars": "warn",
|
|
||||||
"no-alert": "error",
|
|
||||||
"no-caller": "error",
|
|
||||||
"no-confusing-arrow": [
|
|
||||||
"error",
|
|
||||||
{ "allowParens": true }
|
|
||||||
],
|
|
||||||
"no-console": "off",
|
|
||||||
"no-div-regex": "error",
|
|
||||||
"no-duplicate-imports": "error",
|
|
||||||
"no-extend-native": "error",
|
|
||||||
"no-extra-label": "error",
|
|
||||||
"no-fallthrough": "off",
|
|
||||||
"no-floating-decimal": "error",
|
|
||||||
"no-implicit-coercion": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"boolean": false,
|
|
||||||
"number": false,
|
|
||||||
"string": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-inner-declarations": [
|
|
||||||
"error",
|
|
||||||
"functions"
|
|
||||||
],
|
|
||||||
"no-iterator": "error",
|
|
||||||
"no-label-var": "error",
|
|
||||||
"no-lone-blocks": "error",
|
|
||||||
"no-new-object": "error",
|
|
||||||
"no-new-require": "error",
|
|
||||||
"no-new-wrappers": "error",
|
|
||||||
"no-restricted-globals": [
|
|
||||||
"error",
|
|
||||||
"event",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"no-restricted-imports": "error",
|
|
||||||
"no-restricted-modules": "error",
|
|
||||||
"no-restricted-syntax": "error",
|
|
||||||
"no-script-url": "error",
|
|
||||||
"no-self-compare": "error",
|
|
||||||
"no-sequences": "error",
|
|
||||||
"no-shadow-restricted-names": "error",
|
|
||||||
"no-spaced-func": "error",
|
|
||||||
"no-trailing-spaces": "error",
|
|
||||||
"no-unmodified-loop-condition": "error",
|
|
||||||
"no-useless-constructor": "error",
|
|
||||||
"no-whitespace-before-property": "error",
|
|
||||||
"no-with": "error",
|
|
||||||
"object-curly-spacing": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"prefer-const": "error",
|
|
||||||
"require-yield": "error",
|
|
||||||
"semi-spacing": [
|
|
||||||
"error"
|
|
||||||
],
|
|
||||||
"space-before-blocks": "error",
|
|
||||||
"space-before-function-paren": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"space-in-parens": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"space-infix-ops": "error",
|
|
||||||
"space-unary-ops": "error",
|
|
||||||
"template-curly-spacing": "error",
|
|
||||||
"curly": 2,
|
|
||||||
"brace-style": [
|
|
||||||
"error",
|
|
||||||
"stroustrup"
|
|
||||||
],
|
|
||||||
"wrap-iife": [
|
|
||||||
"error",
|
|
||||||
"any"
|
|
||||||
],
|
|
||||||
"yield-star-spacing": "error",
|
|
||||||
"multiline-ternary": [
|
|
||||||
"warn",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"no-nested-ternary": "error",
|
|
||||||
"svelte/html-quotes": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"prefer": "double",
|
|
||||||
"dynamic": { "quoted": false }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"svelte/no-useless-mustaches": "warn",
|
|
||||||
"svelte/require-store-reactive-access": "warn",
|
|
||||||
"svelte/no-reactive-literals": "error",
|
|
||||||
"svelte/html-closing-bracket-spacing": "warn",
|
|
||||||
"svelte/indent": "warn",
|
|
||||||
"svelte/max-attributes-per-line": [
|
|
||||||
"warn",
|
|
||||||
{ "multiline": 1, "singleline": 5 }
|
|
||||||
],
|
|
||||||
"svelte/mustache-spacing": "warn",
|
|
||||||
"svelte/no-extra-reactive-curlies": "error",
|
|
||||||
"svelte/no-spaces-around-equal-signs-in-attribute": "warn",
|
|
||||||
"svelte/prefer-class-directive": "warn",
|
|
||||||
"svelte/shorthand-attribute": "warn",
|
|
||||||
"svelte/shorthand-directive": "warn",
|
|
||||||
"svelte/spaced-html-comment": "warn",
|
|
||||||
"svelte/no-at-html-tags": 0
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,6 +15,7 @@
|
|||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="dialogoutlets"></div>
|
||||||
<script src="./src/main.js" type="module"></script>
|
<script src="./src/main.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
1895
frontend/package-lock.json
generated
1895
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,17 +9,33 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-javascript": "^6.1.8",
|
"@codemirror/lang-javascript": "^6.1.9",
|
||||||
"@codemirror/language": "^6.7.0",
|
"@codemirror/language": "^6.8.0",
|
||||||
"@codemirror/view": "^6.12.0",
|
"@codemirror/view": "^6.13.2",
|
||||||
"bson": "^4.7.2",
|
"bson": "^4.7.2",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"date-fns": "^2.30.0"
|
"date-fns": "^2.30.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||||
"eslint": "^8.41.0",
|
"eslint": "^8.43.0",
|
||||||
|
"eslint-config-svelte3": "github:johbog/eslint-config-svelte3",
|
||||||
"svelte": "^3.59.1",
|
"svelte": "^3.59.1",
|
||||||
"vite": "^3.0.0"
|
"vite": "^3.2.7"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "svelte3",
|
||||||
|
"ignorePatterns": ["dist", "wailsjs"],
|
||||||
|
"rules": {
|
||||||
|
"svelte/html-quotes": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"prefer": "double",
|
||||||
|
"dynamic": { "quoted": false }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"svelte/no-useless-mustaches": "off",
|
||||||
|
"svelte/no-extra-reactive-curlies": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
d933a4e16d5bb0132361eef3b5d3ba22
|
7fc6d7b66151030191ded7136d96970e
|
@ -1 +1 @@
|
|||||||
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="647.63626" height="632.17383" viewBox="0 0 647.63626 632.17383" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M687.3279,276.08691H512.81813a15.01828,15.01828,0,0,0-15,15v387.85l-2,.61005-42.81006,13.11a8.00676,8.00676,0,0,1-9.98974-5.31L315.678,271.39691a8.00313,8.00313,0,0,1,5.31006-9.99l65.97022-20.2,191.25-58.54,65.96972-20.2a7.98927,7.98927,0,0,1,9.99024,5.3l32.5498,106.32Z" transform="translate(-276.18187 -133.91309)" fill="#f2f2f2"/><path d="M725.408,274.08691l-39.23-128.14a16.99368,16.99368,0,0,0-21.23-11.28l-92.75,28.39L380.95827,221.60693l-92.75,28.4a17.0152,17.0152,0,0,0-11.28028,21.23l134.08008,437.93a17.02661,17.02661,0,0,0,16.26026,12.03,16.78926,16.78926,0,0,0,4.96972-.75l63.58008-19.46,2-.62v-2.09l-2,.61-64.16992,19.65a15.01489,15.01489,0,0,1-18.73-9.95l-134.06983-437.94a14.97935,14.97935,0,0,1,9.94971-18.73l92.75-28.4,191.24024-58.54,92.75-28.4a15.15551,15.15551,0,0,1,4.40966-.66,15.01461,15.01461,0,0,1,14.32032,10.61l39.0498,127.56.62012,2h2.08008Z" transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/><path d="M398.86279,261.73389a9.0157,9.0157,0,0,1-8.61133-6.3667l-12.88037-42.07178a8.99884,8.99884,0,0,1,5.9712-11.24023l175.939-53.86377a9.00867,9.00867,0,0,1,11.24072,5.9707l12.88037,42.07227a9.01029,9.01029,0,0,1-5.9707,11.24072L401.49219,261.33887A8.976,8.976,0,0,1,398.86279,261.73389Z" transform="translate(-276.18187 -133.91309)" fill="#00008b"/><circle cx="190.15351" cy="24.95465" r="20" fill="#00008b"/><circle cx="190.15351" cy="24.95465" r="12.66462" fill="#fff"/><path d="M878.81836,716.08691h-338a8.50981,8.50981,0,0,1-8.5-8.5v-405a8.50951,8.50951,0,0,1,8.5-8.5h338a8.50982,8.50982,0,0,1,8.5,8.5v405A8.51013,8.51013,0,0,1,878.81836,716.08691Z" transform="translate(-276.18187 -133.91309)" fill="#e6e6e6"/><path d="M723.31813,274.08691h-210.5a17.02411,17.02411,0,0,0-17,17v407.8l2-.61v-407.19a15.01828,15.01828,0,0,1,15-15H723.93825Zm183.5,0h-394a17.02411,17.02411,0,0,0-17,17v458a17.0241,17.0241,0,0,0,17,17h394a17.0241,17.0241,0,0,0,17-17v-458A17.02411,17.02411,0,0,0,906.81813,274.08691Zm15,475a15.01828,15.01828,0,0,1-15,15h-394a15.01828,15.01828,0,0,1-15-15v-458a15.01828,15.01828,0,0,1,15-15h394a15.01828,15.01828,0,0,1,15,15Z" transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/><path d="M801.81836,318.08691h-184a9.01015,9.01015,0,0,1-9-9v-44a9.01016,9.01016,0,0,1,9-9h184a9.01016,9.01016,0,0,1,9,9v44A9.01015,9.01015,0,0,1,801.81836,318.08691Z" transform="translate(-276.18187 -133.91309)" fill="#00008b"/><circle cx="433.63626" cy="105.17383" r="20" fill="#00008b"/><circle cx="433.63626" cy="105.17383" r="12.18187" fill="#fff"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="647.636" height="632.174" data-name="Layer 1" viewBox="0 0 647.636 632.174"><path fill="#f2f2f2" d="M687.3279,276.08691H512.81813a15.01828,15.01828,0,0,0-15,15v387.85l-2,.61005-42.81006,13.11a8.00676,8.00676,0,0,1-9.98974-5.31L315.678,271.39691a8.00313,8.00313,0,0,1,5.31006-9.99l65.97022-20.2,191.25-58.54,65.96972-20.2a7.98927,7.98927,0,0,1,9.99024,5.3l32.5498,106.32Z" transform="translate(-276.18187 -133.91309)"/><path fill="#3f3d56" d="M725.408,274.08691l-39.23-128.14a16.99368,16.99368,0,0,0-21.23-11.28l-92.75,28.39L380.95827,221.60693l-92.75,28.4a17.0152,17.0152,0,0,0-11.28028,21.23l134.08008,437.93a17.02661,17.02661,0,0,0,16.26026,12.03,16.78926,16.78926,0,0,0,4.96972-.75l63.58008-19.46,2-.62v-2.09l-2,.61-64.16992,19.65a15.01489,15.01489,0,0,1-18.73-9.95l-134.06983-437.94a14.97935,14.97935,0,0,1,9.94971-18.73l92.75-28.4,191.24024-58.54,92.75-28.4a15.15551,15.15551,0,0,1,4.40966-.66,15.01461,15.01461,0,0,1,14.32032,10.61l39.0498,127.56.62012,2h2.08008Z" transform="translate(-276.18187 -133.91309)"/><path fill="#00008b" d="M398.86279,261.73389a9.0157,9.0157,0,0,1-8.61133-6.3667l-12.88037-42.07178a8.99884,8.99884,0,0,1,5.9712-11.24023l175.939-53.86377a9.00867,9.00867,0,0,1,11.24072,5.9707l12.88037,42.07227a9.01029,9.01029,0,0,1-5.9707,11.24072L401.49219,261.33887A8.976,8.976,0,0,1,398.86279,261.73389Z" transform="translate(-276.18187 -133.91309)"/><circle cx="190.154" cy="24.955" r="20" fill="#00008b"/><circle cx="190.154" cy="24.955" r="12.665" fill="#fff"/><path fill="#e6e6e6" d="M878.81836,716.08691h-338a8.50981,8.50981,0,0,1-8.5-8.5v-405a8.50951,8.50951,0,0,1,8.5-8.5h338a8.50982,8.50982,0,0,1,8.5,8.5v405A8.51013,8.51013,0,0,1,878.81836,716.08691Z" transform="translate(-276.18187 -133.91309)"/><path fill="#3f3d56" d="M723.31813,274.08691h-210.5a17.02411,17.02411,0,0,0-17,17v407.8l2-.61v-407.19a15.01828,15.01828,0,0,1,15-15H723.93825Zm183.5,0h-394a17.02411,17.02411,0,0,0-17,17v458a17.0241,17.0241,0,0,0,17,17h394a17.0241,17.0241,0,0,0,17-17v-458A17.02411,17.02411,0,0,0,906.81813,274.08691Zm15,475a15.01828,15.01828,0,0,1-15,15h-394a15.01828,15.01828,0,0,1-15-15v-458a15.01828,15.01828,0,0,1,15-15h394a15.01828,15.01828,0,0,1,15,15Z" transform="translate(-276.18187 -133.91309)"/><path fill="#00008b" d="M801.81836,318.08691h-184a9.01015,9.01015,0,0,1-9-9v-44a9.01016,9.01016,0,0,1,9-9h184a9.01016,9.01016,0,0,1,9,9v44A9.01015,9.01015,0,0,1,801.81836,318.08691Z" transform="translate(-276.18187 -133.91309)"/><circle cx="433.636" cy="105.174" r="20" fill="#00008b"/><circle cx="433.636" cy="105.174" r="12.182" fill="#fff"/></svg>
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@ -1,66 +1,65 @@
|
|||||||
<script>
|
<script>
|
||||||
import BlankState from '$components/blankstate.svelte';
|
import BlankState from '$components/blankstate.svelte';
|
||||||
import ContextMenu from '$components/contextmenu.svelte';
|
import ContextMenu from '$components/contextmenu.svelte';
|
||||||
import connections from '$lib/stores/connections';
|
import dialogs from '$lib/dialogs';
|
||||||
import contextMenu from '$lib/stores/contextmenu';
|
import contextMenu from '$lib/stores/contextmenu';
|
||||||
import environment from '$lib/stores/environment';
|
import environment from '$lib/stores/environment';
|
||||||
import hosts from '$lib/stores/hosts';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
import applicationInited from '$lib/stores/inited';
|
import applicationInited from '$lib/stores/inited';
|
||||||
import About from '$organisms/about.svelte';
|
import windowTitle from '$lib/stores/windowtitle';
|
||||||
import Connection from '$organisms/connection/index.svelte';
|
import Connection from '$organisms/connection/index.svelte';
|
||||||
import Settings from '$organisms/settings/index.svelte';
|
import { EventsOn } from '$wails/runtime';
|
||||||
import { EventsEmit, EventsOn } from '$wails/runtime';
|
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
|
import AboutDialog from './dialogs/about.svelte';
|
||||||
|
import SettingsDialog from './dialogs/settings/index.svelte';
|
||||||
|
|
||||||
const activeHostKey = '';
|
|
||||||
let activeDbKey = '';
|
|
||||||
let activeCollKey = '';
|
|
||||||
let settingsModalOpen = false;
|
|
||||||
let aboutModalOpen = false;
|
|
||||||
let connectionManager;
|
|
||||||
let showWelcomeScreen = undefined;
|
let showWelcomeScreen = undefined;
|
||||||
|
|
||||||
$: host = hosts[activeHostKey];
|
applicationInited.defer(() => {
|
||||||
$: connection = $connections[activeHostKey];
|
hostTree.subscribe(hosts => {
|
||||||
|
if (hostTree.hasBeenInited() && (showWelcomeScreen === undefined)) {
|
||||||
hosts.subscribe(h => {
|
showWelcomeScreen = !Object.keys(hosts || {}).length;
|
||||||
if (h && (showWelcomeScreen === undefined)) {
|
}
|
||||||
showWelcomeScreen = !Object.keys($hosts || {}).length;
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createFirstHost() {
|
async function createFirstHost() {
|
||||||
showWelcomeScreen = false;
|
showWelcomeScreen = false;
|
||||||
await tick();
|
await tick();
|
||||||
connectionManager.createHost();
|
hostTree.newHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
EventsOn('OpenPreferences', () => settingsModalOpen = true);
|
function showAboutDialog() {
|
||||||
EventsOn('OpenAboutModal', () => aboutModalOpen = true);
|
dialogs.new(AboutDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSettings() {
|
||||||
|
dialogs.new(SettingsDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventsOn('OpenPreferences', showSettings);
|
||||||
|
EventsOn('OpenAboutModal', showAboutDialog);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:contextmenu|preventDefault />
|
<svelte:window on:contextmenu|preventDefault />
|
||||||
|
|
||||||
<div id="root" class="platform-{$environment?.platform}">
|
<div id="root" class="platform-{$environment?.platform}">
|
||||||
<div class="titlebar"></div>
|
<div class="titlebar">{$windowTitle}</div>
|
||||||
|
|
||||||
{#if $applicationInited && $hosts && (showWelcomeScreen !== undefined)}
|
{#if $applicationInited && (showWelcomeScreen !== undefined)}
|
||||||
<main class:empty={showWelcomeScreen}>
|
<main class:empty={showWelcomeScreen}>
|
||||||
{#if showWelcomeScreen}
|
{#if showWelcomeScreen}
|
||||||
<BlankState label="Welcome to Rolens!" image="/logo.png" pale={false} big={true}>
|
<BlankState label="Welcome to Rolens!" image="/logo.png" pale={false} big={true}>
|
||||||
<button class="btn" on:click={createFirstHost}>Add your first host</button>
|
<button class="btn" on:click={createFirstHost}>Add your first host</button>
|
||||||
</BlankState>
|
</BlankState>
|
||||||
{:else}
|
{:else}
|
||||||
<Connection {activeHostKey} bind:activeCollKey bind:activeDbKey bind:this={connectionManager} />
|
<Connection />
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{#key $contextMenu}
|
{#key $contextMenu}
|
||||||
<ContextMenu {...$contextMenu} on:close={contextMenu.hide} />
|
<ContextMenu {...$contextMenu} on:close={contextMenu.hide} />
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<Settings bind:show={settingsModalOpen} />
|
|
||||||
<About bind:show={aboutModalOpen} />
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -69,6 +68,10 @@
|
|||||||
height: 0;
|
height: 0;
|
||||||
background-color: #00002a;
|
background-color: #00002a;
|
||||||
--wails-draggable: drag;
|
--wails-draggable: drag;
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
#root.platform-darwin .titlebar {
|
#root.platform-darwin .titlebar {
|
||||||
height: var(--darwin-titlebar-height);
|
height: var(--darwin-titlebar-height);
|
||||||
|
@ -1,14 +1,25 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import Icon from './icon.svelte';
|
||||||
|
|
||||||
|
export let title = '';
|
||||||
export let label = 'No items';
|
export let label = 'No items';
|
||||||
export let image = '/empty.svg';
|
export let image = '/empty.svg';
|
||||||
|
export let icon = '';
|
||||||
export let pale = true;
|
export let pale = true;
|
||||||
export let big = false;
|
export let big = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="blankstate" class:pale class:big>
|
<div class="blankstate" class:pale class:big>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<img src={image} alt="" />
|
{#if icon}
|
||||||
<p>{label}</p>
|
<Icon name={icon} />
|
||||||
|
{:else if image}
|
||||||
|
<img src={image} alt="" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<p class="title">{title}</p>
|
||||||
|
<p class="label">{label}</p>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -27,12 +38,20 @@
|
|||||||
height: 150px;
|
height: 150px;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
.content > :global(svg) {
|
||||||
|
height: 40px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 2.85rem 0;
|
margin: 2.85rem 0;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
line-height: 1.25rem;
|
line-height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
p.title {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: -1.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
.blankstate :global(.btn) {
|
.blankstate :global(.btn) {
|
||||||
font-size: 1.35rem;
|
font-size: 1.35rem;
|
||||||
@ -48,7 +67,7 @@
|
|||||||
filter: grayscale(1);
|
filter: grayscale(1);
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
.pale p {
|
.pale {
|
||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { resolveKeypath, setValue } from '$lib/objects';
|
import { pathsAreEqual, resolveKeypath, setValue } from '$lib/objects';
|
||||||
import contextMenu from '$lib/stores/contextmenu';
|
import contextMenu from '$lib/stores/contextmenu';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import FormInput from './forminput.svelte';
|
import FormInput from './forminput.svelte';
|
||||||
@ -50,7 +50,9 @@
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
else if ((typeof obj === 'object') && (obj !== null)) {
|
else if ((typeof obj === 'object') && (obj !== null)) {
|
||||||
return Object.entries(obj).map(([ k, item ]) => ({ ...item, [key]: k }));
|
return Object.entries(obj).map(([ k, item ]) => {
|
||||||
|
return { ...item, [key]: k };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return obj;
|
return obj;
|
||||||
@ -62,18 +64,9 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleChildren(itemKey, false);
|
activeKey = itemKey;
|
||||||
|
activePath = [ ...path.slice(0, level), itemKey ];
|
||||||
if (activeKey !== itemKey) {
|
dispatch('select', { level, itemKey, index, path: activePath });
|
||||||
activeKey = itemKey;
|
|
||||||
if (level === 0) {
|
|
||||||
activePath = [ itemKey ];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
activePath = [ ...path, itemKey ];
|
|
||||||
}
|
|
||||||
dispatch('select', { level, itemKey, index });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAll() {
|
function closeAll() {
|
||||||
@ -89,7 +82,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function doubleClick(itemKey, index) {
|
function doubleClick(itemKey, index) {
|
||||||
// toggleChildren(itemKey, false);
|
toggleChildren(itemKey, false);
|
||||||
dispatch('trigger', { level, itemKey, index });
|
dispatch('trigger', { level, itemKey, index });
|
||||||
childrenOpen[itemKey] = true;
|
childrenOpen[itemKey] = true;
|
||||||
}
|
}
|
||||||
@ -136,7 +129,7 @@
|
|||||||
on:dblclick={() => doubleClick(item[key], index)}
|
on:dblclick={() => doubleClick(item[key], index)}
|
||||||
on:contextmenu|preventDefault={evt => showContextMenu(evt, item)}
|
on:contextmenu|preventDefault={evt => showContextMenu(evt, item)}
|
||||||
class:selectable={canSelect}
|
class:selectable={canSelect}
|
||||||
class:selected={canSelect && !activePath[level + 1] && activePath.every(k => path.includes(k) || k === item[key]) && (activePath[level] === item[key])}
|
class:selected={canSelect && pathsAreEqual(activePath, [ ...path, item[key] ])}
|
||||||
class:striped
|
class:striped
|
||||||
>
|
>
|
||||||
{#if !hideChildrenToggles}
|
{#if !hideChildrenToggles}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
import BlankState from './blankstate.svelte';
|
||||||
import GridItems from './grid-items.svelte';
|
import GridItems from './grid-items.svelte';
|
||||||
|
import Icon from './icon.svelte';
|
||||||
|
|
||||||
export let columns = [];
|
export let columns = [];
|
||||||
export let items = [];
|
export let items = [];
|
||||||
@ -12,7 +15,20 @@
|
|||||||
export let canSelect = true;
|
export let canSelect = true;
|
||||||
export let canRemoveItems = false;
|
export let canRemoveItems = false;
|
||||||
export let inputsValid = false;
|
export let inputsValid = false;
|
||||||
// export let actions = [];
|
export let errorTitle = '';
|
||||||
|
export let errorDescription = '';
|
||||||
|
export let busy = false;
|
||||||
|
|
||||||
|
let copySucceeded = false;
|
||||||
|
let timeout;
|
||||||
|
|
||||||
|
async function copyErrorDescription() {
|
||||||
|
await navigator.clipboard.writeText(errorDescription);
|
||||||
|
copySucceeded = true;
|
||||||
|
timeout = setTimeout(() => copySucceeded = false, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(() => clearTimeout(timeout));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
@ -27,45 +43,55 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if} -->
|
{/if} -->
|
||||||
|
|
||||||
<table>
|
{#if busy}
|
||||||
{#if showHeaders && columns.some(col => col.title)}
|
<BlankState label={(busy === true) ? 'Loading…' : busy} icon="loading" />
|
||||||
<thead>
|
{:else if errorTitle || errorDescription}
|
||||||
<tr>
|
<BlankState title={errorTitle} label={errorDescription} icon="!">
|
||||||
{#if !hideChildrenToggles}
|
<button class="button-small" on:click={copyErrorDescription}>
|
||||||
<th class="has-toggle"></th>
|
<Icon name={copySucceeded ? 'check' : 'clipboard'} /> Copy error message
|
||||||
{/if}
|
</button>
|
||||||
|
</BlankState>
|
||||||
|
{:else}
|
||||||
|
<table>
|
||||||
|
{#if showHeaders && columns.some(col => col.title)}
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{#if !hideChildrenToggles}
|
||||||
|
<th class="has-toggle"></th>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<th class="has-icon"></th>
|
<th class="has-icon"></th>
|
||||||
|
|
||||||
{#each columns as column}
|
{#each columns as column}
|
||||||
<th scope="col">{column.title || ''}</th>
|
<th scope="col">{column.title || ''}</th>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#if canRemoveItems}
|
{#if canRemoveItems}
|
||||||
<th class="has-button"></th>
|
<th class="has-button"></th>
|
||||||
{/if}
|
{/if}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<GridItems
|
<GridItems
|
||||||
{items}
|
{items}
|
||||||
{columns}
|
{columns}
|
||||||
{key}
|
{key}
|
||||||
{striped}
|
{striped}
|
||||||
{canSelect}
|
{canSelect}
|
||||||
{canRemoveItems}
|
{canRemoveItems}
|
||||||
{hideObjectIndicators}
|
{hideObjectIndicators}
|
||||||
{hideChildrenToggles}
|
{hideChildrenToggles}
|
||||||
bind:activePath
|
bind:activePath
|
||||||
bind:inputsValid
|
bind:inputsValid
|
||||||
on:select
|
on:select
|
||||||
on:trigger
|
on:trigger
|
||||||
on:removeItem
|
on:removeItem
|
||||||
/>
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -75,15 +101,6 @@
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .actions {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
.actions button {
|
|
||||||
margin-right: 0.2rem;
|
|
||||||
} */
|
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -99,7 +116,20 @@
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid :global(.blankstate) {
|
||||||
|
height: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* tfoot button {
|
/* tfoot button {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
.actions button {
|
||||||
|
margin-right: 0.2rem;
|
||||||
} */
|
} */
|
||||||
</style>
|
</style>
|
||||||
|
103
frontend/src/components/icon.svelte
vendored
103
frontend/src/components/icon.svelte
vendored
@ -14,9 +14,29 @@
|
|||||||
width: auto;
|
width: auto;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes spinning {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.spinning {
|
||||||
|
animation: spinning 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class:spinning={name === 'loading'}
|
||||||
|
>
|
||||||
{#if name === 'radio'}
|
{#if name === 'radio'}
|
||||||
<circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path>
|
<circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path>
|
||||||
{:else if name === 'chev-l'}
|
{:else if name === 'chev-l'}
|
||||||
@ -26,13 +46,13 @@
|
|||||||
{:else if name === 'chev-d'}
|
{:else if name === 'chev-d'}
|
||||||
<polyline points="6 9 12 15 18 9"></polyline>
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
{:else if name === 'chev-u'}
|
{:else if name === 'chev-u'}
|
||||||
<path d="m18 15-6-6-6 6"/>
|
<path d="m18 15-6-6-6 6" />
|
||||||
{:else if name === 'chevs-l'}
|
{:else if name === 'chevs-l'}
|
||||||
<path d="m11 17-5-5 5-5M18 17l-5-5 5-5"/>
|
<path d="m11 17-5-5 5-5M18 17l-5-5 5-5" />
|
||||||
{:else if name === 'chevs-r'}
|
{:else if name === 'chevs-r'}
|
||||||
<path d="m13 17 5-5-5-5M6 17l5-5-5-5"/>
|
<path d="m13 17 5-5-5-5M6 17l5-5-5-5" />
|
||||||
{:else if name === 'arr-d'}
|
{:else if name === 'arr-d'}
|
||||||
<path d="M12 5v14M19 12l-7 7-7-7"/>
|
<path d="M12 5v14M19 12l-7 7-7-7" />
|
||||||
{:else if name === 'db'}
|
{:else if name === 'db'}
|
||||||
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
||||||
{:else if name === 'x'}
|
{:else if name === 'x'}
|
||||||
@ -44,54 +64,83 @@
|
|||||||
{:else if name === 'reload'}
|
{:else if name === 'reload'}
|
||||||
<polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
|
<polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
|
||||||
{:else if name === 'clipboard'}
|
{:else if name === 'clipboard'}
|
||||||
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
|
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8"
|
||||||
|
y="2"
|
||||||
|
width="8"
|
||||||
|
height="4"
|
||||||
|
rx="1"
|
||||||
|
ry="1"></rect>
|
||||||
{:else if name === 'edit'}
|
{:else if name === 'edit'}
|
||||||
<path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
|
<path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
|
||||||
{:else if name === 'check'}
|
{:else if name === 'check'}
|
||||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline>
|
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline>
|
||||||
{:else if name === 'list'}
|
{:else if name === 'list'}
|
||||||
<path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/>
|
<path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" />
|
||||||
{:else if name === 'table'}
|
{:else if name === 'table'}
|
||||||
<path d="M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18"/>
|
<path d="M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18" />
|
||||||
{:else if name === 'form'}
|
{:else if name === 'form'}
|
||||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8"/>
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" />
|
||||||
{:else if name === 'cog'}
|
{:else if name === 'cog'}
|
||||||
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
<circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
||||||
{:else if name === 'zap'}
|
{:else if name === 'zap'}
|
||||||
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>
|
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z" />
|
||||||
{:else if name === 'server'}
|
{:else if name === 'server'}
|
||||||
<rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><path d="M6 6h.01M6 18h.01"/>
|
<rect x="2"
|
||||||
|
y="2"
|
||||||
|
width="20"
|
||||||
|
height="8"
|
||||||
|
rx="2"
|
||||||
|
ry="2" /><rect x="2"
|
||||||
|
y="14"
|
||||||
|
width="20"
|
||||||
|
height="8"
|
||||||
|
rx="2"
|
||||||
|
ry="2" /><path d="M6 6h.01M6 18h.01" />
|
||||||
{:else if name === 'text'}
|
{:else if name === 'text'}
|
||||||
<path d="M4 7V4h16v3M9 20h6M12 4v16"/>
|
<path d="M4 7V4h16v3M9 20h6M12 4v16" />
|
||||||
{:else if name === 'hash'}
|
{:else if name === 'hash'}
|
||||||
<path d="M4 9h16M4 15h16M10 3 8 21M16 3l-2 18"/>
|
<path d="M4 9h16M4 15h16M10 3 8 21M16 3l-2 18" />
|
||||||
{:else if name === 'toggle-l'}
|
{:else if name === 'toggle-l'}
|
||||||
<rect x="1" y="5" width="22" height="14" rx="7" ry="7"/><circle cx="8" cy="12" r="3"/>
|
<rect x="1"
|
||||||
|
y="5"
|
||||||
|
width="22"
|
||||||
|
height="14"
|
||||||
|
rx="7"
|
||||||
|
ry="7" /><circle cx="8" cy="12" r="3" />
|
||||||
{:else if name === 'cal'}
|
{:else if name === 'cal'}
|
||||||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><path d="M16 2v4M8 2v4M3 10h18"/>
|
<rect x="3"
|
||||||
|
y="4"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
rx="2"
|
||||||
|
ry="2" /><path d="M16 2v4M8 2v4M3 10h18" />
|
||||||
{:else if name === 'code'}
|
{:else if name === 'code'}
|
||||||
<path d="m16 18 6-6-6-6M8 6l-6 6 6 6"/>
|
<path d="m16 18 6-6-6-6M8 6l-6 6 6 6" />
|
||||||
{:else if name === 'target'}
|
{:else if name === 'target'}
|
||||||
<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>
|
<circle cx="12" cy="12" r="10" /><circle cx="12" cy="12" r="6" /><circle cx="12" cy="12" r="2" />
|
||||||
{:else if name === 'trash'}
|
{:else if name === 'trash'}
|
||||||
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M10 11v6M14 11v6"/>
|
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M10 11v6M14 11v6" />
|
||||||
{:else if name === 'anchor'}
|
{:else if name === 'anchor'}
|
||||||
<circle cx="12" cy="5" r="3"/><path d="M12 22V8M5 12H2a10 10 0 0 0 20 0h-3"/>
|
<circle cx="12" cy="5" r="3" /><path d="M12 22V8M5 12H2a10 10 0 0 0 20 0h-3" />
|
||||||
{:else if name === 'o'}
|
{:else if name === 'o'}
|
||||||
<circle cx="12" cy="12" r="10"/>
|
<circle cx="12" cy="12" r="10" />
|
||||||
{:else if name === 'info'}
|
{:else if name === 'info'}
|
||||||
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>
|
<circle cx="12" cy="12" r="10" /><path d="M12 16v-4M12 8h.01" />
|
||||||
{:else if name === 'play'}
|
{:else if name === 'play'}
|
||||||
<path d="m5 3 14 9-14 9V3z"/>
|
<path d="m5 3 14 9-14 9V3z" />
|
||||||
{:else if name === 'upload'}
|
{:else if name === 'upload'}
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12"/>
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12" />
|
||||||
{:else if name === 'save'}
|
{:else if name === 'save'}
|
||||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8M7 3v5h8"/>
|
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" /><path d="M17 21v-8H7v8M7 3v5h8" />
|
||||||
{:else if name === 're'}
|
{:else if name === 're'}
|
||||||
<path d="m17 1 4 4-4 4"/><path d="M3 11V9a4 4 0 0 1 4-4h14M7 23l-4-4 4-4"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/>
|
<path d="m17 1 4 4-4 4" /><path d="M3 11V9a4 4 0 0 1 4-4h14M7 23l-4-4 4-4" /><path d="M21 13v2a4 4 0 0 1-4 4H3" />
|
||||||
{:else if name === 'chart'}
|
{:else if name === 'chart'}
|
||||||
<path d="M18 20V10M12 20V4M6 20v-6"/>
|
<path d="M18 20V10M12 20V4M6 20v-6" />
|
||||||
{:else if name === '?'}
|
{:else if name === '?'}
|
||||||
<circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line>
|
<circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||||
|
{:else if name === '!'}
|
||||||
|
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4M12 17h.01" />
|
||||||
|
{:else if name === 'loading'}
|
||||||
|
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" />
|
||||||
{/if}
|
{/if}
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -4,15 +4,17 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Beep } from '$wails/go/ui/UI';
|
import { Beep } from '$wails/go/ui/UI';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade, fly } from 'svelte/transition';
|
import { fade, fly } from 'svelte/transition';
|
||||||
import Icon from './icon.svelte';
|
import Icon from './icon.svelte';
|
||||||
|
|
||||||
export let show = false;
|
export let show = true;
|
||||||
export let title = undefined;
|
export let title = undefined;
|
||||||
export let contentPadding = true;
|
export let contentPadding = true;
|
||||||
export let width = '80vw';
|
export let width = '80vw';
|
||||||
export let overflow = true;
|
export let overflow = true;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
const level = numberOfModalsOpen + 1;
|
const level = numberOfModalsOpen + 1;
|
||||||
let isNew = true;
|
let isNew = true;
|
||||||
|
|
||||||
@ -29,9 +31,13 @@
|
|||||||
function keydown(event) {
|
function keydown(event) {
|
||||||
if ((event.key === 'Escape') && (level === numberOfModalsOpen)) {
|
if ((event.key === 'Escape') && (level === numberOfModalsOpen)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
show = false;
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
dispatch('close');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={keydown} />
|
<svelte:window on:keydown={keydown} />
|
||||||
@ -42,7 +48,7 @@
|
|||||||
{#if title}
|
{#if title}
|
||||||
<header>
|
<header>
|
||||||
<div class="title">{title}</div>
|
<div class="title">{title}</div>
|
||||||
<button class="btn close" on:click={() => show = false} title="close" type="button">
|
<button class="btn close" on:click={close} title="close" type="button">
|
||||||
<Icon name="x" />
|
<Icon name="x" />
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
@ -69,6 +75,7 @@
|
|||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-top: 50px;
|
padding-top: 50px;
|
||||||
|
--wails-draggable: drag;
|
||||||
}
|
}
|
||||||
:global(#root.platform-darwin) .outer {
|
:global(#root.platform-darwin) .outer {
|
||||||
margin-top: var(--darwin-titlebar-height);
|
margin-top: var(--darwin-titlebar-height);
|
||||||
@ -86,6 +93,7 @@
|
|||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
--wails-draggable: nodrag;
|
||||||
}
|
}
|
||||||
.inner > :global(*:first-child) {
|
.inner > :global(*:first-child) {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
export let activePath = [];
|
export let activePath = [];
|
||||||
export let hideObjectIndicators = false;
|
export let hideObjectIndicators = false;
|
||||||
export let getRootMenu = () => undefined;
|
export let getRootMenu = () => undefined;
|
||||||
|
export let errorTitle = '';
|
||||||
|
export let errorDescription = '';
|
||||||
|
export let busy = false;
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ key: 'key', label: 'Key' },
|
{ key: 'key', label: 'Key' },
|
||||||
@ -116,4 +119,7 @@
|
|||||||
{columns}
|
{columns}
|
||||||
{items}
|
{items}
|
||||||
{hideObjectIndicators}
|
{hideObjectIndicators}
|
||||||
|
{errorTitle}
|
||||||
|
{errorDescription}
|
||||||
|
{busy}
|
||||||
/>
|
/>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings';
|
import { looseJsonIsValid } from '$lib/strings';
|
||||||
|
import { EJSON } from 'bson';
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte';
|
import { createEventDispatcher, onDestroy } from 'svelte';
|
||||||
import Icon from './icon.svelte';
|
import Icon from './icon.svelte';
|
||||||
import Modal from './modal.svelte';
|
import Modal from './modal.svelte';
|
||||||
import ObjectEditor from './objecteditor.svelte';
|
import ObjectEditor from './objecteditor.svelte';
|
||||||
import { EJSON } from 'bson';
|
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
export let saveable = false;
|
export let saveable = false;
|
||||||
|
@ -1,26 +1,39 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import Icon from './icon.svelte';
|
import Icon from './icon.svelte';
|
||||||
|
|
||||||
export let tabs = [];
|
export let tabs = [];
|
||||||
export let selectedKey = {};
|
export let selectedKey = {};
|
||||||
export let canAddTab = false;
|
export let canAddTab = false;
|
||||||
|
export let multiline = false;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
const maxPixelsPerMultilineTab = 120;
|
||||||
|
let navEl;
|
||||||
|
let pixelsPerTab = 0;
|
||||||
|
$: tabs && navEl && updateMeasurements();
|
||||||
|
|
||||||
|
function updateMeasurements() {
|
||||||
|
pixelsPerTab = (navEl.offsetWidth ?? 0) / tabs.length;
|
||||||
|
}
|
||||||
|
|
||||||
function select(tabKey) {
|
function select(tabKey) {
|
||||||
selectedKey = tabKey;
|
selectedKey = tabKey;
|
||||||
dispatch('select', tabKey);
|
dispatch('select', tabKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
window.addEventListener('resize', updateMeasurements);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="tabs">
|
<nav class="tabs" class:multiline={multiline || (pixelsPerTab < maxPixelsPerMultilineTab)} bind:this={navEl}>
|
||||||
<ul>
|
<ul>
|
||||||
{#each tabs as tab (tab.key)}
|
{#each tabs as tab (tab.key)}
|
||||||
<li class:active={tab.key === selectedKey}>
|
<li class:active={tab.key === selectedKey}>
|
||||||
<button class="tab" on:click={() => select(tab.key)}>
|
<button class="tab" on:click={() => select(tab.key)}>
|
||||||
{#if tab.icon} <Icon name={tab.icon} /> {/if}
|
{#if tab.icon} <Icon name={tab.icon} /> {/if}
|
||||||
{tab.title}
|
<span class="label">{tab.title}</span>
|
||||||
</button>
|
</button>
|
||||||
{#if tab.closable}
|
{#if tab.closable}
|
||||||
<button class="button-small" on:click={() => dispatch('closeTab', tab.key)}>
|
<button class="button-small" on:click={() => dispatch('closeTab', tab.key)}>
|
||||||
@ -48,7 +61,7 @@
|
|||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
flex-grow: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,6 +102,11 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav.tabs.multiline button.tab .label {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.button-small {
|
.button-small {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 7px;
|
right: 7px;
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
<script>
|
<script>
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import alink from '$lib/actions/alink';
|
import alink from '$lib/actions/alink';
|
||||||
|
import environment from '$lib/stores/environment';
|
||||||
export let show = true;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:show width="400px" title=" ">
|
<Modal width="400px" title=" " on:close>
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<img src="/logo.png" alt="Rolens logo" />
|
<img src="/logo.png" alt="Rolens logo" />
|
||||||
<div>
|
<div>
|
||||||
<div class="title">Rolens</div>
|
<div class="title">
|
||||||
<div class="description">Intuitive MongoDB <br/> administration tool</div>
|
Rolens
|
||||||
|
<span class="version">{$environment.version}</span>
|
||||||
|
</div>
|
||||||
|
<div class="description">Intuitive MongoDB <br /> administration tool</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -19,7 +21,7 @@
|
|||||||
<div class="info">
|
<div class="info">
|
||||||
<p class="copy">© Romein van Buren, 2023.</p>
|
<p class="copy">© Romein van Buren, 2023.</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="" use:alink>Documentation</a> |
|
<a href="https://garraflavatra.github.io/rolens/" use:alink>Documentation</a> |
|
||||||
<a href="https://github.com/garraflavatra/rolens" use:alink>GitHub</a> |
|
<a href="https://github.com/garraflavatra/rolens" use:alink>GitHub</a> |
|
||||||
<a href="https://github.com/garraflavatra/rolens/issues/new" use:alink>Report a bug</a> |
|
<a href="https://github.com/garraflavatra/rolens/issues/new" use:alink>Report a bug</a> |
|
||||||
<a href="https://github.com/garraflavatra/rolens/blob/main/LICENSE" use:alink>License</a>
|
<a href="https://github.com/garraflavatra/rolens/blob/main/LICENSE" use:alink>License</a>
|
||||||
@ -42,6 +44,11 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 2.5rem;
|
line-height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
.brand .title .version {
|
||||||
|
font-size: 80%;
|
||||||
|
font-weight: 300;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
.brand .description {
|
.brand .description {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
line-height: 1.6rem;
|
line-height: 1.6rem;
|
44
frontend/src/dialogs/input.svelte
Normal file
44
frontend/src/dialogs/input.svelte
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<script>
|
||||||
|
import Modal from '$components/modal.svelte';
|
||||||
|
import { createEventDispatcher, onMount, tick } from 'svelte';
|
||||||
|
|
||||||
|
export let title = '';
|
||||||
|
export let description = '';
|
||||||
|
export let value = '';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
let input;
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
dispatch('submit', { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
dispatch('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => tick().then(() => input.select()));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal {title} on:close width="350px">
|
||||||
|
{#if description}
|
||||||
|
<p>{description}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={submit}>
|
||||||
|
<label class="field">
|
||||||
|
<input type="text" bind:value bind:this={input} spellcheck="false" />
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<svelte:fragment slot="footer">
|
||||||
|
<button on:click={submit} class="btn">OK</button>
|
||||||
|
<button on:click={close} class="btn secondary">Cancel</button>
|
||||||
|
</svelte:fragment>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
</style>
|
@ -3,11 +3,9 @@
|
|||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import settings from '$lib/stores/settings';
|
import settings from '$lib/stores/settings';
|
||||||
|
|
||||||
export let show = false;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal title="Preferences" bind:show>
|
<Modal title="Preferences" on:close>
|
||||||
<div class="prefs">
|
<div class="prefs">
|
||||||
<label for="defaultLimit">Initial number of items to retrieve using one query (limit):</label>
|
<label for="defaultLimit">Initial number of items to retrieve using one query (limit):</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
@ -27,7 +25,7 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<label for="defaultExportDirectory">Default export directory</label>
|
<label for="defaultExportDirectory">Default export directory</label>
|
||||||
<!-- svelte-ignore a11y-label-has-associated-control - input is in DirectoryChooser -->
|
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<DirectoryChooser id="defaultExportDirectory" bind:value={$settings.defaultExportDirectory} />
|
<DirectoryChooser id="defaultExportDirectory" bind:value={$settings.defaultExportDirectory} />
|
||||||
</label>
|
</label>
|
34
frontend/src/lib/dialogs.js
Normal file
34
frontend/src/lib/dialogs.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import InputDialog from '../dialogs/input.svelte';
|
||||||
|
|
||||||
|
function newDialog(dialogComponent, data = {}) {
|
||||||
|
const outlet = document.createElement('div');
|
||||||
|
outlet.className = 'dialogoutlet';
|
||||||
|
document.getElementById('dialogoutlets').appendChild(outlet);
|
||||||
|
|
||||||
|
const instance = new dialogComponent({ target: outlet, props: data });
|
||||||
|
|
||||||
|
instance.$close = function() {
|
||||||
|
instance.$destroy();
|
||||||
|
outlet.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.$on('close', instance.$close);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterText(title = '', description = '', value = '') {
|
||||||
|
const instance = newDialog(InputDialog, { title, description, value });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
instance.$on('submit', event => {
|
||||||
|
instance.$close();
|
||||||
|
resolve(event.detail.value);
|
||||||
|
});
|
||||||
|
instance.$on('close', () => resolve(undefined));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogs = { new: newDialog, enterText };
|
||||||
|
|
||||||
|
export default dialogs;
|
@ -20,7 +20,7 @@
|
|||||||
<label for="collationLocale">Locale</label>
|
<label for="collationLocale">Locale</label>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<select id="collationLocale" bind:value={collation.locale}>
|
<select id="collationLocale" bind:value={collation.locale}>
|
||||||
{#each Object.entries(locales) as [value, name]}
|
{#each Object.entries(locales) as [ value, name ]}
|
||||||
<option {value}>({value}) {name}</option>
|
<option {value}>({value}) {name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
@ -58,6 +58,18 @@ export function setValue(object, path, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function deepClone(obj) {
|
export function deepClone(obj) {
|
||||||
// Room for improvement below
|
// @todo: Room for improvement below
|
||||||
return JSON.parse(JSON.stringify(obj));
|
return JSON.parse(JSON.stringify(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pathsAreEqual(x, y) {
|
||||||
|
const lengthOfLongest = (x.length >= y.length) ? x.length : y.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < lengthOfLongest; i++) {
|
||||||
|
if (x[i] !== y[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
const connections = writable({});
|
|
||||||
|
|
||||||
export default connections;
|
|
@ -1,11 +0,0 @@
|
|||||||
import { Hosts } from "$wails/go/app/App";
|
|
||||||
import { writable } from "svelte/store";
|
|
||||||
import applicationInited from "./inited";
|
|
||||||
|
|
||||||
const { set, subscribe } = writable();
|
|
||||||
|
|
||||||
const update = async () => set(await Hosts());
|
|
||||||
applicationInited.defer(update);
|
|
||||||
|
|
||||||
const hosts = { update, subscribe };
|
|
||||||
export default hosts;
|
|
322
frontend/src/lib/stores/hosttree.js
Normal file
322
frontend/src/lib/stores/hosttree.js
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
import dialogs from '$lib/dialogs';
|
||||||
|
import { startProgress } from '$lib/progress';
|
||||||
|
import { get, writable } from 'svelte/store';
|
||||||
|
import applicationInited from './inited';
|
||||||
|
import queries from './queries';
|
||||||
|
import windowTitle from './windowtitle';
|
||||||
|
|
||||||
|
import ExportDialog from '$organisms/connection/collection/dialogs/export.svelte';
|
||||||
|
import IndexDetailDialog from '$organisms/connection/collection/dialogs/indexdetail.svelte';
|
||||||
|
import QueryChooserDialog from '$organisms/connection/collection/dialogs/querychooser.svelte';
|
||||||
|
import DumpDialog from '$organisms/connection/database/dialogs/dump.svelte';
|
||||||
|
import HostDetailDialog from '$organisms/connection/host/dialogs/hostdetail.svelte';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateIndex,
|
||||||
|
DropCollection,
|
||||||
|
DropDatabase,
|
||||||
|
DropIndex,
|
||||||
|
GetIndexes,
|
||||||
|
Hosts,
|
||||||
|
OpenCollection,
|
||||||
|
OpenConnection,
|
||||||
|
OpenDatabase,
|
||||||
|
PerformDump,
|
||||||
|
PerformFindExport,
|
||||||
|
RemoveHost,
|
||||||
|
RenameCollection,
|
||||||
|
TruncateCollection
|
||||||
|
} from '$wails/go/app/App';
|
||||||
|
|
||||||
|
const { set, subscribe } = writable({});
|
||||||
|
const getValue = () => get({ subscribe });
|
||||||
|
let hostTreeInited = false;
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
const hosts = await Hosts();
|
||||||
|
const hostTree = getValue();
|
||||||
|
|
||||||
|
for (const [ hostKey, hostDetails ] of Object.entries(hosts)) {
|
||||||
|
hostTree[hostKey] = hostTree[hostKey] || {};
|
||||||
|
const host = hostTree[hostKey];
|
||||||
|
host.key = hostKey;
|
||||||
|
host.name = hostDetails.name;
|
||||||
|
host.uri = hostDetails.uri;
|
||||||
|
|
||||||
|
host.open = async function() {
|
||||||
|
const { databases: dbNames, status, statusError, systemInfo, systemInfoError } = await OpenConnection(hostKey);
|
||||||
|
host.status = status;
|
||||||
|
host.statusError = statusError;
|
||||||
|
host.systemInfo = systemInfo;
|
||||||
|
host.systemInfoError = systemInfoError;
|
||||||
|
host.databases = host.databases || {};
|
||||||
|
|
||||||
|
if (!dbNames) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dbKey of dbNames.sort((a, b) => a.localeCompare(b))) {
|
||||||
|
host.databases[dbKey] = host.databases[dbKey] || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [ dbKey, database ] of Object.entries(host.databases)) {
|
||||||
|
if (!database.new && !dbNames.includes(dbKey)) {
|
||||||
|
delete host.databases[dbKey];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
database.key = dbKey;
|
||||||
|
database.hostKey = hostKey;
|
||||||
|
database.collections = database.collections || {};
|
||||||
|
|
||||||
|
delete database.new;
|
||||||
|
|
||||||
|
database.open = async function() {
|
||||||
|
const { collections: collNames, stats, statsError } = await OpenDatabase(hostKey, dbKey);
|
||||||
|
database.stats = stats;
|
||||||
|
database.statsError = statsError;
|
||||||
|
|
||||||
|
if (!collNames) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const collKey of collNames.sort((a, b) => a.localeCompare(b))) {
|
||||||
|
database.collections[collKey] = database.collections[collKey] || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [ collKey, collection ] of Object.entries(database.collections)) {
|
||||||
|
if (!collection.new && !collNames.includes(collKey)) {
|
||||||
|
delete database.collections[collKey];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.key = collKey;
|
||||||
|
collection.dbKey = dbKey;
|
||||||
|
collection.hostKey = hostKey;
|
||||||
|
collection.indexes = collection.indexes || [];
|
||||||
|
|
||||||
|
delete collection.new;
|
||||||
|
|
||||||
|
collection.open = async function() {
|
||||||
|
const { stats, statsError } = await OpenCollection(hostKey, dbKey, collKey);
|
||||||
|
|
||||||
|
collection.stats = stats;
|
||||||
|
collection.statsError = statsError;
|
||||||
|
|
||||||
|
await refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.rename = async function() {
|
||||||
|
const newCollKey = await dialogs.enterText('Rename collection', `Enter a new name for collection ${collKey}.`, collKey);
|
||||||
|
if (newCollKey && (newCollKey !== collKey)) {
|
||||||
|
const progress = startProgress(`Renaming collection "${collKey}" to "${newCollKey}"…`);
|
||||||
|
const ok = await RenameCollection(hostKey, dbKey, collKey, newCollKey);
|
||||||
|
await database.open();
|
||||||
|
progress.end();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.export = function(query) {
|
||||||
|
const dialog = dialogs.new(ExportDialog, { collection, query });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('export', async event => {
|
||||||
|
const success = await PerformFindExport(hostKey, dbKey, collKey, JSON.stringify(event.detail.exportInfo));
|
||||||
|
if (success) {
|
||||||
|
dialog.$close();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.dump = function() {
|
||||||
|
const dialog = dialogs.new(DumpDialog, { info: {
|
||||||
|
hostKey,
|
||||||
|
dbKey,
|
||||||
|
collKeys: [ collKey ],
|
||||||
|
} });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('dump', async event => {
|
||||||
|
const success = await PerformDump(JSON.stringify(event.detail.info));
|
||||||
|
if (success) {
|
||||||
|
dialog.$close();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.truncate = async function() {
|
||||||
|
const progress = startProgress(`Truncating collection "${collKey}"…`);
|
||||||
|
await TruncateCollection(hostKey, dbKey, collKey);
|
||||||
|
await refresh();
|
||||||
|
progress.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.drop = async function() {
|
||||||
|
const progress = startProgress(`Dropping collection "${collKey}"…`);
|
||||||
|
const success = await DropCollection(hostKey, dbKey, collKey);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.getIndexes = async function() {
|
||||||
|
const progress = startProgress(`Retrieving indexes of "${collKey}"…`);
|
||||||
|
collection.indexes = [];
|
||||||
|
const { indexes, error } = await GetIndexes(hostKey, dbKey, collKey);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
progress.end();
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const indexDetails of indexes) {
|
||||||
|
const index = {
|
||||||
|
name: indexDetails.name,
|
||||||
|
background: indexDetails.background || false,
|
||||||
|
unique: indexDetails.unique || false,
|
||||||
|
sparse: indexDetails.sparse || false,
|
||||||
|
model: indexDetails.model,
|
||||||
|
};
|
||||||
|
|
||||||
|
index.drop = async function() {
|
||||||
|
const progress = startProgress(`Dropping index ${index.name}…`);
|
||||||
|
const hasBeenDropped = await DropIndex(hostKey, dbKey, collKey, index.name);
|
||||||
|
progress.end();
|
||||||
|
return hasBeenDropped;
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.indexes.push(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.getIndexByName = function(indesName) {
|
||||||
|
return collection.indexes.find(idx => idx.name = indesName);
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.newIndex = function() {
|
||||||
|
const dialog = dialogs.new(IndexDetailDialog, { collection });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('create', async event => {
|
||||||
|
const progress = startProgress('Creating index…');
|
||||||
|
const newIndexName = await CreateIndex(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(event.detail.index));
|
||||||
|
|
||||||
|
if (newIndexName) {
|
||||||
|
dialog.$close();
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.end();
|
||||||
|
resolve(newIndexName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.openQueryChooser = function(queryToSave = undefined) {
|
||||||
|
const dialog = dialogs.new(QueryChooserDialog, { collection, queryToSave });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('select', async event => {
|
||||||
|
dialog.$close();
|
||||||
|
resolve(event.detail.query);
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.$on('create', async event => {
|
||||||
|
const ok = await queries.create(event.detail.query);
|
||||||
|
if (ok) {
|
||||||
|
dialog.$close();
|
||||||
|
resolve(event.detail.query);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await refresh();
|
||||||
|
windowTitle.setSegments(dbKey, host.name, 'Rolens');
|
||||||
|
};
|
||||||
|
|
||||||
|
database.dump = function() {
|
||||||
|
const dialog = dialogs.new(DumpDialog, { info: { hostKey, dbKey } });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('dump', async event => {
|
||||||
|
const success = await PerformDump(JSON.stringify(event.detail.info));
|
||||||
|
if (success) {
|
||||||
|
dialog.$close();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
database.drop = async function() {
|
||||||
|
const progress = startProgress(`Dropping database "${dbKey}"…`);
|
||||||
|
const success = await DropDatabase(hostKey, dbKey);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
database.newCollection = async function() {
|
||||||
|
const name = await dialogs.enterText('Create a collection', 'Note: collections in MongoDB do not exist until they have at least one item. Your new collection will not persist on the server; fill it to have it created.', '');
|
||||||
|
if (name) {
|
||||||
|
database.collections[name] = { key: name, new: true };
|
||||||
|
await database.open();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
host.newDatabase = async function() {
|
||||||
|
const name = await dialogs.enterText('Create a database', 'Enter the database name. Note: databases in MongoDB do not exist until they have a collection and an item. Your new database will not persist on the server; fill it to have it created.', '');
|
||||||
|
if (name) {
|
||||||
|
host.databases[name] = { key: name, new: true };
|
||||||
|
await host.open();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
host.remove = async function() {
|
||||||
|
await RemoveHost(hostKey);
|
||||||
|
await refresh();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
hostTreeInited = true;
|
||||||
|
set(hostTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
function newHost() {
|
||||||
|
const dialog = dialogs.new(HostDetailDialog, { hostKey: '' });
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('close', () => {
|
||||||
|
refresh().then(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationInited.defer(refresh);
|
||||||
|
|
||||||
|
const hostTree = {
|
||||||
|
refresh,
|
||||||
|
subscribe,
|
||||||
|
get: getValue,
|
||||||
|
newHost,
|
||||||
|
hasBeenInited: () => hostTreeInited,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default hostTree;
|
@ -10,7 +10,7 @@ const defer = listener => {
|
|||||||
listener();
|
listener();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
listeners.push(listener)
|
listeners.push(listener);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -18,16 +18,12 @@ const { subscribe } = derived([ environment, applicationSettings ], ([ env, sett
|
|||||||
if (alreadyInited) {
|
if (alreadyInited) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (env && settings) {
|
||||||
if (env && settings) {
|
Promise.all(listeners.map(l => l())).then(() => {
|
||||||
set(true);
|
set(true);
|
||||||
alreadyInited = true;
|
alreadyInited = true;
|
||||||
|
document.getElementById('app-loading')?.remove();
|
||||||
// Remove loading spinner.
|
});
|
||||||
document.getElementById('app-loading')?.remove();
|
|
||||||
|
|
||||||
// Call hooks
|
|
||||||
listeners.forEach(l => l());
|
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import dialogs from '$lib/dialogs';
|
||||||
|
import ViewConfigDialog from '$organisms/connection/collection/dialogs/viewconfig.svelte';
|
||||||
import { UpdateViewStore, Views } from '$wails/go/app/App';
|
import { UpdateViewStore, Views } from '$wails/go/app/App';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
@ -23,6 +25,11 @@ function forCollection(hostKey, dbKey, collKey) {
|
|||||||
return Object.fromEntries(entries);
|
return Object.fromEntries(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openConfig(collection, firstItem = {}) {
|
||||||
|
const dialog = dialogs.new(ViewConfigDialog, { collection, firstItem });
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
reload();
|
reload();
|
||||||
subscribe(newViewStore => {
|
subscribe(newViewStore => {
|
||||||
if (skipUpdate) {
|
if (skipUpdate) {
|
||||||
@ -32,5 +39,5 @@ subscribe(newViewStore => {
|
|||||||
UpdateViewStore(JSON.stringify(newViewStore));
|
UpdateViewStore(JSON.stringify(newViewStore));
|
||||||
});
|
});
|
||||||
|
|
||||||
const views = { reload, set, subscribe, forCollection };
|
const views = { reload, set, subscribe, forCollection, openConfig };
|
||||||
export default views;
|
export default views;
|
||||||
|
14
frontend/src/lib/stores/windowtitle.js
Normal file
14
frontend/src/lib/stores/windowtitle.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { WindowSetTitle } from '$wails/runtime/runtime';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
const { set, subscribe } = writable('Rolens');
|
||||||
|
|
||||||
|
subscribe(newTitle => WindowSetTitle(newTitle));
|
||||||
|
|
||||||
|
const windowTitle = {
|
||||||
|
set,
|
||||||
|
setSegments: (...segments) => set(segments.map(s => s.trim()).join(' — ')),
|
||||||
|
subscribe,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default windowTitle;
|
@ -2,9 +2,9 @@
|
|||||||
import Details from '$components/details.svelte';
|
import Details from '$components/details.svelte';
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import Collation from '$lib/mongo/collation.svelte';
|
|
||||||
import ObjectEditor from '$components/objecteditor.svelte';
|
import ObjectEditor from '$components/objecteditor.svelte';
|
||||||
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo';
|
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo';
|
||||||
|
import Collation from '$lib/mongo/collation.svelte';
|
||||||
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings';
|
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings';
|
||||||
import { Aggregate } from '$wails/go/app/App';
|
import { Aggregate } from '$wails/go/app/App';
|
||||||
import { BrowserOpenURL } from '$wails/runtime/runtime';
|
import { BrowserOpenURL } from '$wails/runtime/runtime';
|
||||||
@ -31,7 +31,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
const pipeline = stages.map(stage => ({ [stage.type]: jsonLooseParse(stage.data) }));
|
const pipeline = stages.map(stage => {
|
||||||
|
return { [stage.type]: jsonLooseParse(stage.data) };
|
||||||
|
});
|
||||||
await Aggregate(
|
await Aggregate(
|
||||||
collection.hostKey,
|
collection.hostKey,
|
||||||
collection.dbKey,
|
collection.dbKey,
|
||||||
@ -66,19 +68,20 @@
|
|||||||
|
|
||||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<ObjectEditor bind:text={stage.data} on:inited={e => {
|
<ObjectEditor bind:text={stage.data}
|
||||||
e.detail.editor.dispatch({
|
on:inited={e => {
|
||||||
changes: {
|
e.detail.editor.dispatch({
|
||||||
from: 0,
|
changes: {
|
||||||
to: e.detail.editor.state.doc.length,
|
from: 0,
|
||||||
insert: '{\n\t\n}',
|
to: e.detail.editor.state.doc.length,
|
||||||
},
|
insert: '{\n\t\n}',
|
||||||
selection: {
|
},
|
||||||
anchor: 3,
|
selection: {
|
||||||
},
|
anchor: 3,
|
||||||
});
|
},
|
||||||
e.detail.editor.focus();
|
});
|
||||||
}} />
|
e.detail.editor.focus();
|
||||||
|
}} />
|
||||||
</label>
|
</label>
|
||||||
</Details>
|
</Details>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import FormInput from '$components/forminput.svelte';
|
import FormInput from '$components/forminput.svelte';
|
||||||
|
import Hint from '$components/hint.svelte';
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import { inputTypes } from '$lib/mongo';
|
import { inputTypes } from '$lib/mongo';
|
||||||
import { resolveKeypath, setValue } from '$lib/objects';
|
import { resolveKeypath, setValue } from '$lib/objects';
|
||||||
import Hint from '$components/hint.svelte';
|
|
||||||
|
|
||||||
export let item = {};
|
export let item = {};
|
||||||
export let view = {};
|
export let view = {};
|
||||||
@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
{#if item && view}
|
{#if item && view}
|
||||||
{#each view?.columns?.filter(c => inputTypes.includes(c.inputType)) || [] as column, index}
|
{#each view?.columns?.filter(c => inputTypes.includes(c.inputType)) || [] as column, index}
|
||||||
<!-- svelte-ignore a11y-label-has-associated-control because FormInput contains one -->
|
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||||
<label class="column">
|
<label class="column">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<Icon name={iconMap[column.inputType]} />
|
<Icon name={iconMap[column.inputType]} />
|
||||||
|
@ -2,43 +2,34 @@
|
|||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import views from '$lib/stores/views';
|
import views from '$lib/stores/views';
|
||||||
import { PerformFindExport } from '$wails/go/app/App';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let info;
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
export let query = undefined;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let viewKey = collection.viewKey;
|
const exportInfo = { ...query, viewKey: collection.viewKey };
|
||||||
$: viewKey = collection.viewKey;
|
|
||||||
$: if (info) {
|
|
||||||
info.viewKey = viewKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function performExport() {
|
function submit() {
|
||||||
info.view = $views[viewKey];
|
exportInfo.view = $views[exportInfo.viewKey];
|
||||||
const success = await PerformFindExport(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(info));
|
dispatch('export', { exportInfo });
|
||||||
|
|
||||||
if (success) {
|
|
||||||
info = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:show={info} title="Export results" width="450px">
|
<Modal title="Export results" width="450px" on:close>
|
||||||
<form on:submit|preventDefault={performExport}>
|
<form on:submit|preventDefault={submit}>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Export</span>
|
<span class="label">Export</span>
|
||||||
<select bind:value={info.contents}>
|
<select bind:value={exportInfo.contents}>
|
||||||
<option value="all">all records</option>
|
<option value="all">all records</option>
|
||||||
<option value="query">all records matching query</option>
|
<option value="query" disabled={!query}>all records matching query</option>
|
||||||
<option value="querylimitskip">all records matching query, considering limit and skip</option>
|
<option value="querylimitskip" disabled={!query}>all records matching query, considering limit and skip</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Format</span>
|
<span class="label">Format</span>
|
||||||
<select bind:value={info.format}>
|
<select bind:value={exportInfo.format}>
|
||||||
<option value="jsonarray">JSON array</option>
|
<option value="jsonarray">JSON array</option>
|
||||||
<option value="ndjson">Newline delimited JSON</option>
|
<option value="ndjson">Newline delimited JSON</option>
|
||||||
<option value="csv">CSV</option>
|
<option value="csv">CSV</option>
|
||||||
@ -47,8 +38,8 @@
|
|||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">View to use</span>
|
<span class="label">View to use</span>
|
||||||
<select bind:value={viewKey}>
|
<select bind:value={exportInfo.viewKey}>
|
||||||
{#each Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key)) as [key, { name }]}
|
{#each Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key)) as [ key, { name } ]}
|
||||||
<option value={key}>{name}</option>
|
<option value={key}>{name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
@ -59,7 +50,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<svelte:fragment slot="footer">
|
<svelte:fragment slot="footer">
|
||||||
<button class="btn" on:click={performExport}>
|
<button class="btn" on:click={submit}>
|
||||||
<Icon name="play" /> Start export
|
<Icon name="play" /> Start export
|
||||||
</button>
|
</button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
@ -2,14 +2,12 @@
|
|||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import { CreateIndex } from '$wails/go/app/App';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let collection = {};
|
export let collection;
|
||||||
export let creatingNewIndex = false;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let index = { model: [] };
|
const index = { model: [] };
|
||||||
|
|
||||||
function addRule() {
|
function addRule() {
|
||||||
index.model = [ ...index.model, {} ];
|
index.model = [ ...index.model, {} ];
|
||||||
@ -21,16 +19,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function create() {
|
async function create() {
|
||||||
const newIndexName = await CreateIndex(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(index));
|
dispatch('create', { index });
|
||||||
if (newIndexName) {
|
|
||||||
creatingNewIndex = false;
|
|
||||||
index = { model: [] };
|
|
||||||
dispatch('reload');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal title="Create new index {collection ? `on collection ${collection.key}` : ''}" bind:show={creatingNewIndex}>
|
<Modal title="Create new index on {collection.key}" on:close>
|
||||||
<form on:submit|preventDefault={create}>
|
<form on:submit|preventDefault={create}>
|
||||||
<label class="field name">
|
<label class="field name">
|
||||||
<span class="label">Name</span>
|
<span class="label">Name</span>
|
@ -4,13 +4,12 @@
|
|||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import hosts from '$lib/stores/hosts';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
import queries from '$lib/stores/queries';
|
import queries from '$lib/stores/queries';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let queryToSave = undefined;
|
export let queryToSave = undefined;
|
||||||
export let collection = {};
|
export let collection = {};
|
||||||
export let show = false;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let gridSelectedPath = [];
|
let gridSelectedPath = [];
|
||||||
@ -22,23 +21,16 @@
|
|||||||
queryToSave.dbKey = collection.dbKey;
|
queryToSave.dbKey = collection.dbKey;
|
||||||
queryToSave.collKey = collection.key;
|
queryToSave.collKey = collection.key;
|
||||||
|
|
||||||
const newId = queries.create(queryToSave);
|
dispatch('create', { query: queryToSave });
|
||||||
|
selectedKey = queryToSave.name;
|
||||||
if (newId) {
|
|
||||||
dispatch('created', newId);
|
|
||||||
queryToSave = undefined;
|
|
||||||
selectedKey = newId;
|
|
||||||
select();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
select();
|
selectActive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function select() {
|
function selectActive() {
|
||||||
dispatch('select', selectedKey);
|
dispatch('select', { query: $queries[selectedKey] });
|
||||||
show = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function gridSelect(event) {
|
function gridSelect(event) {
|
||||||
@ -53,7 +45,7 @@
|
|||||||
|
|
||||||
function gridTrigger(event) {
|
function gridTrigger(event) {
|
||||||
gridSelect(event);
|
gridSelect(event);
|
||||||
select();
|
selectActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function gridRemove(event) {
|
async function gridRemove(event) {
|
||||||
@ -71,7 +63,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:show title={queryToSave ? 'Save query' : 'Load query'} width="500px">
|
<Modal title={queryToSave ? 'Save query' : 'Load query'} width="500px" on:close>
|
||||||
<form on:submit|preventDefault={submit}>
|
<form on:submit|preventDefault={submit}>
|
||||||
{#if queryToSave}
|
{#if queryToSave}
|
||||||
<label class="field queryname">
|
<label class="field queryname">
|
||||||
@ -88,7 +80,11 @@
|
|||||||
columns={[ { key: 'n', title: 'Query name' }, { key: 'h', title: 'Host' }, { key: 'ns', title: 'Namespace' } ]}
|
columns={[ { key: 'n', title: 'Query name' }, { key: 'h', title: 'Host' }, { key: 'ns', title: 'Namespace' } ]}
|
||||||
key="n"
|
key="n"
|
||||||
items={Object.entries($queries).reduce((object, [ name, query ]) => {
|
items={Object.entries($queries).reduce((object, [ name, query ]) => {
|
||||||
object[query.name] = { n: name, h: $hosts[query.hostKey]?.name || '?', ns: `${query.dbKey}.${query.collKey}` };
|
object[query.name] = {
|
||||||
|
n: name,
|
||||||
|
h: $hostTree[query.hostKey]?.name || '?',
|
||||||
|
ns: `${query.dbKey}.${query.collKey}`,
|
||||||
|
};
|
||||||
return object;
|
return object;
|
||||||
}, {})}
|
}, {})}
|
||||||
showHeaders={true}
|
showHeaders={true}
|
@ -7,13 +7,18 @@
|
|||||||
import views from '$lib/stores/views';
|
import views from '$lib/stores/views';
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
export let show = false;
|
|
||||||
export let activeViewKey = 'list';
|
|
||||||
export let firstItem = {};
|
export let firstItem = {};
|
||||||
|
|
||||||
$: tabs = Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key))
|
let tabs = [];
|
||||||
.sort((a, b) => sortTabKeys(a[0], b[0]))
|
$: $views && refresh();
|
||||||
.map(([ key, v ]) => ({ key, title: v.name, closable: key !== 'list' }));
|
|
||||||
|
function refresh() {
|
||||||
|
tabs = Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key))
|
||||||
|
.sort((a, b) => sortTabKeys(a[0], b[0]))
|
||||||
|
.map(([ key, v ]) => {
|
||||||
|
return { key, title: v.name, closable: key !== 'list' };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function sortTabKeys(a, b) {
|
function sortTabKeys(a, b) {
|
||||||
if (a === 'list') {
|
if (a === 'list') {
|
||||||
@ -37,29 +42,29 @@
|
|||||||
type: 'table',
|
type: 'table',
|
||||||
columns: [ { key: '_id', showInTable: true, inputType: 'objectid', mandatory: true } ],
|
columns: [ { key: '_id', showInTable: true, inputType: 'objectid', mandatory: true } ],
|
||||||
};
|
};
|
||||||
activeViewKey = newViewKey;
|
collection.viewKey = newViewKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeView(viewKey) {
|
function removeView(viewKey) {
|
||||||
const keys = Object.keys($views).sort(sortTabKeys);
|
const keys = Object.keys($views).sort(sortTabKeys);
|
||||||
const oldIndex = keys.indexOf(viewKey);
|
const oldIndex = keys.indexOf(viewKey);
|
||||||
const newKey = keys[oldIndex - 1];
|
const newKey = keys[oldIndex - 1];
|
||||||
activeViewKey = newKey;
|
collection.viewKey = newKey;
|
||||||
delete $views[viewKey];
|
delete $views[viewKey];
|
||||||
$views = $views;
|
$views = $views;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addColumn(before) {
|
function addColumn(before) {
|
||||||
if (typeof before === 'number') {
|
if (typeof before === 'number') {
|
||||||
$views[activeViewKey].columns = [
|
$views[collection.viewKey].columns = [
|
||||||
...$views[activeViewKey].columns.slice(0, before),
|
...$views[collection.viewKey].columns.slice(0, before),
|
||||||
{ showInTable: true, inputType: 'none' },
|
{ showInTable: true, inputType: 'none' },
|
||||||
...$views[activeViewKey].columns.slice(before),
|
...$views[collection.viewKey].columns.slice(before),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$views[activeViewKey].columns = [
|
$views[collection.viewKey].columns = [
|
||||||
...$views[activeViewKey].columns,
|
...$views[collection.viewKey].columns,
|
||||||
{ showInTable: true, inputType: 'none' },
|
{ showInTable: true, inputType: 'none' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -70,65 +75,67 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$views[activeViewKey].columns = Object.keys(firstItem).sort().map(key => ({
|
$views[collection.viewKey].columns = Object.keys(firstItem).sort().map(key => {
|
||||||
key,
|
return {
|
||||||
showInTable: true,
|
key,
|
||||||
inputType: 'none',
|
showInTable: true,
|
||||||
}));
|
inputType: 'none',
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveColumn(oldIndex, delta) {
|
function moveColumn(oldIndex, delta) {
|
||||||
const column = $views[activeViewKey].columns[oldIndex];
|
const column = $views[collection.viewKey].columns[oldIndex];
|
||||||
const newIndex = oldIndex + delta;
|
const newIndex = oldIndex + delta;
|
||||||
|
|
||||||
$views[activeViewKey].columns.splice(oldIndex, 1);
|
$views[collection.viewKey].columns.splice(oldIndex, 1);
|
||||||
$views[activeViewKey].columns.splice(newIndex, 0, column);
|
$views[collection.viewKey].columns.splice(newIndex, 0, column);
|
||||||
$views[activeViewKey].columns = $views[activeViewKey].columns;
|
$views[collection.viewKey].columns = $views[collection.viewKey].columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeColumn(index) {
|
function removeColumn(index) {
|
||||||
$views[activeViewKey].columns.splice(index, 1);
|
$views[collection.viewKey].columns.splice(index, 1);
|
||||||
$views[activeViewKey].columns = $views[activeViewKey].columns;
|
$views[collection.viewKey].columns = $views[collection.viewKey].columns;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal title="View configuration" bind:show contentPadding={false}>
|
<Modal title="View configuration" contentPadding={false} on:close>
|
||||||
<TabBar
|
<TabBar
|
||||||
{tabs}
|
{tabs}
|
||||||
canAddTab={true}
|
canAddTab={true}
|
||||||
on:addTab={createView}
|
on:addTab={createView}
|
||||||
on:closeTab={e => removeView(e.detail)}
|
on:closeTab={e => removeView(e.detail)}
|
||||||
bind:selectedKey={activeViewKey}
|
bind:selectedKey={collection.viewKey}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="options">
|
<div class="options">
|
||||||
{#if $views[activeViewKey]}
|
{#if $views[collection.viewKey]}
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
{#key activeViewKey}
|
{#key collection.viewKey}
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">View name</span>
|
<span class="label">View name</span>
|
||||||
<input type="text" use:input={{ autofocus: true }} bind:value={$views[activeViewKey].name} disabled={activeViewKey === 'list'} />
|
<input type="text" use:input={{ autofocus: true }} bind:value={$views[collection.viewKey].name} disabled={collection.viewKey === 'list'} />
|
||||||
</label>
|
</label>
|
||||||
{/key}
|
{/key}
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">View type</span>
|
<span class="label">View type</span>
|
||||||
<select bind:value={$views[activeViewKey].type} disabled>
|
<select bind:value={$views[collection.viewKey].type} disabled>
|
||||||
<option value="list">List view</option>
|
<option value="list">List view</option>
|
||||||
<option value="table">Table view</option>
|
<option value="table">Table view</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $views[activeViewKey].type === 'list'}
|
{#if $views[collection.viewKey].type === 'list'}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<input type="checkbox" id="hideObjectIndicators" bind:checked={$views[activeViewKey].hideObjectIndicators} />
|
<input type="checkbox" id="hideObjectIndicators" bind:checked={$views[collection.viewKey].hideObjectIndicators} />
|
||||||
<label for="hideObjectIndicators">
|
<label for="hideObjectIndicators">
|
||||||
Hide object indicators ({'{...}'} and [...]) in list view and show nothing instead
|
Hide object indicators ({'{...}'} and [...]) in list view and show nothing instead
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{:else if $views[activeViewKey].type === 'table'}
|
{:else if $views[collection.viewKey].type === 'table'}
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
{#each $views[activeViewKey].columns as column, columnIndex}
|
{#each $views[collection.viewKey].columns as column, columnIndex}
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<input type="text" use:input bind:value={column.key} placeholder="Column keypath" />
|
<input type="text" use:input bind:value={column.key} placeholder="Column keypath" />
|
||||||
@ -184,7 +191,7 @@
|
|||||||
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, -1)} disabled={columnIndex === 0} title="Move column one position up">
|
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, -1)} disabled={columnIndex === 0} title="Move column one position up">
|
||||||
<Icon name="chev-u" />
|
<Icon name="chev-u" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, 1)} disabled={columnIndex === $views[activeViewKey].columns.length - 1} title="Move column one position down">
|
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, 1)} disabled={columnIndex === $views[collection.viewKey].columns.length - 1} title="Move column one position down">
|
||||||
<Icon name="chev-d" />
|
<Icon name="chev-d" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn danger" type="button" on:click={() => removeColumn(columnIndex)} title="Remove this column">
|
<button class="btn danger" type="button" on:click={() => removeColumn(columnIndex)} title="Remove this column">
|
@ -6,18 +6,15 @@
|
|||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import { deepClone } from '$lib/objects';
|
import { deepClone } from '$lib/objects';
|
||||||
import { startProgress } from '$lib/progress';
|
import { startProgress } from '$lib/progress';
|
||||||
import queries from '$lib/stores/queries';
|
|
||||||
import applicationSettings from '$lib/stores/settings';
|
import applicationSettings from '$lib/stores/settings';
|
||||||
import views from '$lib/stores/views';
|
import views from '$lib/stores/views';
|
||||||
|
import { convertLooseJson } from '$lib/strings';
|
||||||
import { FindItems, RemoveItemById, UpdateFoundDocument } from '$wails/go/app/App';
|
import { FindItems, RemoveItemById, UpdateFoundDocument } from '$wails/go/app/App';
|
||||||
import { EJSON } from 'bson';
|
import { EJSON } from 'bson';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import ExportInfo from './components/export.svelte';
|
|
||||||
import QueryChooser from './components/querychooser.svelte';
|
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
query: '{}',
|
query: '{}',
|
||||||
sort: $applicationSettings.defaultSort || '{ "_id": 1 }',
|
sort: $applicationSettings.defaultSort || '{ "_id": 1 }',
|
||||||
@ -32,24 +29,24 @@
|
|||||||
let queryField;
|
let queryField;
|
||||||
let activePath = [];
|
let activePath = [];
|
||||||
let objectViewerData;
|
let objectViewerData;
|
||||||
let queryToSave;
|
|
||||||
let showQueryChooser = false;
|
|
||||||
let exportInfo;
|
|
||||||
let querying = false;
|
let querying = false;
|
||||||
let objectViewerSuccessMessage = '';
|
let objectViewerSuccessMessage = '';
|
||||||
|
let viewsForCollection = {};
|
||||||
|
|
||||||
$: viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key);
|
// $: code = `db.${collection.key}.find(${form.query || '{}'}${form.fields && form.fields !== '{}' ? `, ${form.fields}` : ''}).sort(${form.sort})${form.skip ? `.skip(${form.skip})` : ''}${form.limit ? `.limit(${form.limit})` : ''};`;
|
||||||
$: code = `db.${collection.key}.find(${form.query || '{}'}${form.fields && form.fields !== '{}' ? `, ${form.fields}` : ''}).sort(${form.sort})${form.skip ? `.skip(${form.skip})` : ''}${form.limit ? `.limit(${form.limit})` : ''};`;
|
|
||||||
$: lastPage = (submittedForm.limit && result?.results?.length) ? Math.max(0, Math.ceil((result.total - submittedForm.limit) / submittedForm.limit)) : 0;
|
$: lastPage = (submittedForm.limit && result?.results?.length) ? Math.max(0, Math.ceil((result.total - submittedForm.limit) / submittedForm.limit)) : 0;
|
||||||
$: activePage = (submittedForm.limit && submittedForm.skip && result?.results?.length) ? submittedForm.skip / submittedForm.limit : 0;
|
$: activePage = (submittedForm.limit && submittedForm.skip && result?.results?.length) ? submittedForm.skip / submittedForm.limit : 0;
|
||||||
|
|
||||||
|
$: if ($views) {
|
||||||
|
viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key);
|
||||||
|
}
|
||||||
|
|
||||||
async function submitQuery() {
|
async function submitQuery() {
|
||||||
if (querying) {
|
if (querying) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
querying = true;
|
querying = `Querying ${collection.key}…`;
|
||||||
const progress = startProgress('Performing query…');
|
|
||||||
activePath = [];
|
activePath = [];
|
||||||
const newResult = await FindItems(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(form));
|
const newResult = await FindItems(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(form));
|
||||||
|
|
||||||
@ -59,7 +56,6 @@
|
|||||||
submittedForm = deepClone(form);
|
submittedForm = deepClone(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.end();
|
|
||||||
resetFocus();
|
resetFocus();
|
||||||
querying = false;
|
querying = false;
|
||||||
}
|
}
|
||||||
@ -70,19 +66,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadQuery() {
|
async function loadQuery() {
|
||||||
queryToSave = undefined;
|
const query = await collection.openQueryChooser();
|
||||||
showQueryChooser = true;
|
if (query) {
|
||||||
|
form = { ...query };
|
||||||
|
submitQuery();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveQuery() {
|
async function saveQuery() {
|
||||||
queryToSave = form;
|
const query = await collection.openQueryChooser(form);
|
||||||
showQueryChooser = true;
|
if (query) {
|
||||||
}
|
form = { ...query };
|
||||||
|
|
||||||
function queryChosen(event) {
|
|
||||||
if ($queries[event?.detail]) {
|
|
||||||
form = { ...$queries[event?.detail] };
|
|
||||||
submitQuery();
|
submitQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,6 +125,10 @@
|
|||||||
objectViewerData = item;
|
objectViewerData = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openViewConfig() {
|
||||||
|
views.openConfig(collection, result.results?.[0] || {});
|
||||||
|
}
|
||||||
|
|
||||||
export function performQuery(q) {
|
export function performQuery(q) {
|
||||||
form = { ...defaults, ...q };
|
form = { ...defaults, ...q };
|
||||||
submitQuery();
|
submitQuery();
|
||||||
@ -142,7 +141,7 @@
|
|||||||
collection.dbKey,
|
collection.dbKey,
|
||||||
collection.key,
|
collection.key,
|
||||||
EJSON.stringify({ _id: event.detail.originalData._id }),
|
EJSON.stringify({ _id: event.detail.originalData._id }),
|
||||||
event.detail.text
|
convertLooseJson(event.detail.text)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -162,7 +161,12 @@
|
|||||||
<div class="form-row one">
|
<div class="form-row one">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Query or id</span>
|
<span class="label">Query or id</span>
|
||||||
<input type="text" class="code" bind:this={queryField} bind:value={form.query} use:input={{ type: 'json', autofocus: true }} placeholder={defaults.query} />
|
<input type="text"
|
||||||
|
class="code"
|
||||||
|
bind:this={queryField}
|
||||||
|
bind:value={form.query}
|
||||||
|
use:input={{ type: 'json', autofocus: true }}
|
||||||
|
placeholder={defaults.query} />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
@ -179,12 +183,22 @@
|
|||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Skip</span>
|
<span class="label">Skip</span>
|
||||||
<input type="number" min="0" bind:value={form.skip} use:input placeholder={defaults.skip} list="skipstops" />
|
<input type="number"
|
||||||
|
min="0"
|
||||||
|
bind:value={form.skip}
|
||||||
|
use:input
|
||||||
|
placeholder={defaults.skip}
|
||||||
|
list="skipstops" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Limit</span>
|
<span class="label">Limit</span>
|
||||||
<input type="number" min="0" bind:value={form.limit} use:input placeholder={defaults.limit} list="limits" />
|
<input type="number"
|
||||||
|
min="0"
|
||||||
|
bind:value={form.limit}
|
||||||
|
use:input
|
||||||
|
placeholder={defaults.limit}
|
||||||
|
list="limits" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -192,7 +206,7 @@
|
|||||||
<button type="submit" class="btn" title="Run query">
|
<button type="submit" class="btn" title="Run query">
|
||||||
<Icon name="play" /> Run
|
<Icon name="play" /> Run
|
||||||
</button>
|
</button>
|
||||||
<button class="btn secondary" type="button" on:click={() => exportInfo = {}}>
|
<button class="btn secondary" type="button" on:click={() => collection.export(form)}>
|
||||||
<Icon name="save" /> Export results…
|
<Icon name="save" /> Export results…
|
||||||
</button>
|
</button>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@ -215,15 +229,23 @@
|
|||||||
hideObjectIndicators={$views[collection.viewKey]?.hideObjectIndicators}
|
hideObjectIndicators={$views[collection.viewKey]?.hideObjectIndicators}
|
||||||
bind:activePath
|
bind:activePath
|
||||||
on:trigger={e => openJson(e.detail?.index)}
|
on:trigger={e => openJson(e.detail?.index)}
|
||||||
|
errorTitle={result.errorTitle}
|
||||||
|
errorDescription={result.errorDescription}
|
||||||
|
busy={querying}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<Grid
|
<Grid
|
||||||
key="_id"
|
key="_id"
|
||||||
columns={$views[collection.viewKey]?.columns?.map(c => ({ key: c.key, title: c.key })) || []}
|
columns={$views[collection.viewKey]?.columns?.map(c => {
|
||||||
|
return { key: c.key, title: c.key };
|
||||||
|
}) || []}
|
||||||
showHeaders={true}
|
showHeaders={true}
|
||||||
items={result.results ? result.results.map(r => EJSON.deserialize(r)) : []}
|
items={result.results ? result.results.map(r => EJSON.deserialize(r)) : []}
|
||||||
bind:activePath
|
bind:activePath
|
||||||
on:trigger={e => openJson(e.detail?.index)}
|
on:trigger={e => openJson(e.detail?.index)}
|
||||||
|
errorTitle={result.errorTitle}
|
||||||
|
errorDescription={result.errorDescription}
|
||||||
|
busy={querying}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
@ -238,11 +260,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="field inline">
|
<label class="field inline">
|
||||||
<select bind:value={collection.viewKey}>
|
<select bind:value={collection.viewKey}>
|
||||||
{#each Object.entries(viewsForCollection) as [key, view]}
|
{#each Object.entries(viewsForCollection) as [ key, view ]}
|
||||||
<option value={key}>{view.name}</option>
|
<option value={key}>{view.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<button class="btn" on:click={() => dispatch('openViewConfig', { firstItem: result.results?.[0] })} title="Configure view">
|
<button class="btn" on:click={openViewConfig} title="Configure view">
|
||||||
<Icon name="cog" />
|
<Icon name="cog" />
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
@ -266,15 +288,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<QueryChooser
|
|
||||||
bind:queryToSave
|
|
||||||
bind:show={showQueryChooser}
|
|
||||||
on:select={queryChosen}
|
|
||||||
{collection}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ExportInfo on:openViewConfig bind:collection bind:info={exportInfo} />
|
|
||||||
|
|
||||||
{#if objectViewerData}
|
{#if objectViewerData}
|
||||||
<!-- @todo Implement save -->
|
<!-- @todo Implement save -->
|
||||||
<ObjectViewer bind:data={objectViewerData} saveable on:save={saveDocument} bind:successMessage={objectViewerSuccessMessage} />
|
<ObjectViewer bind:data={objectViewerData} saveable on:save={saveDocument} bind:successMessage={objectViewerSuccessMessage} />
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
import TabBar from '$components/tabbar.svelte';
|
import TabBar from '$components/tabbar.svelte';
|
||||||
import { EventsOn } from '$wails/runtime/runtime';
|
import { EventsOn } from '$wails/runtime/runtime';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
|
|
||||||
import Aggregate from './aggregate.svelte';
|
import Aggregate from './aggregate.svelte';
|
||||||
import ViewConfig from './components/viewconfig.svelte';
|
|
||||||
import Find from './find.svelte';
|
import Find from './find.svelte';
|
||||||
import Indexes from './indexes.svelte';
|
import Indexes from './indexes.svelte';
|
||||||
import Insert from './insert.svelte';
|
import Insert from './insert.svelte';
|
||||||
@ -15,20 +15,18 @@
|
|||||||
export let collection;
|
export let collection;
|
||||||
export let hostKey;
|
export let hostKey;
|
||||||
export let dbKey;
|
export let dbKey;
|
||||||
export let collectionKey;
|
export let collKey;
|
||||||
|
|
||||||
let tab = 'find';
|
let tab = 'find';
|
||||||
let find;
|
let find;
|
||||||
let viewConfigModalOpen = false;
|
|
||||||
let firstItem;
|
|
||||||
|
|
||||||
$: if (collection) {
|
$: if (collection) {
|
||||||
collection.hostKey = hostKey;
|
collection.hostKey = hostKey;
|
||||||
collection.dbKey = dbKey;
|
collection.dbKey = dbKey;
|
||||||
collection.key = collectionKey;
|
collection.key = collKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (hostKey || dbKey || collectionKey) {
|
$: if (hostKey || dbKey || collKey) {
|
||||||
tab = 'find';
|
tab = 'find';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,14 +37,9 @@
|
|||||||
await tick();
|
await tick();
|
||||||
find.performQuery(event.detail);
|
find.performQuery(event.detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openViewConfig(event) {
|
|
||||||
firstItem = event.detail?.firstItem;
|
|
||||||
viewConfigModalOpen = true;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="collection" class:empty={!collection}>
|
<div class="view" class:empty={!collection}>
|
||||||
{#if collection}
|
{#if collection}
|
||||||
{#key collection}
|
{#key collection}
|
||||||
<TabBar tabs={[
|
<TabBar tabs={[
|
||||||
@ -57,12 +50,13 @@
|
|||||||
{ key: 'remove', icon: 'trash', title: 'Remove' },
|
{ key: 'remove', icon: 'trash', title: 'Remove' },
|
||||||
{ key: 'indexes', icon: 'list', title: 'Indexes' },
|
{ key: 'indexes', icon: 'list', title: 'Indexes' },
|
||||||
{ key: 'aggregate', icon: 're', title: 'Aggregate' },
|
{ key: 'aggregate', icon: 're', title: 'Aggregate' },
|
||||||
]} bind:selectedKey={tab} />
|
]}
|
||||||
|
bind:selectedKey={tab} />
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{#if tab === 'stats'} <Stats {collection} />
|
{#if tab === 'stats'} <Stats {collection} />
|
||||||
{:else if tab === 'find'} <Find {collection} bind:this={find} on:openViewConfig={openViewConfig} />
|
{:else if tab === 'find'} <Find {collection} bind:this={find} />
|
||||||
{:else if tab === 'insert'} <Insert {collection} on:performFind={catchQuery} on:openViewConfig={openViewConfig} />
|
{:else if tab === 'insert'} <Insert {collection} on:performFind={catchQuery} />
|
||||||
{:else if tab === 'update'} <Update {collection} on:performFind={catchQuery} />
|
{:else if tab === 'update'} <Update {collection} on:performFind={catchQuery} />
|
||||||
{:else if tab === 'remove'} <Remove {collection} />
|
{:else if tab === 'remove'} <Remove {collection} />
|
||||||
{:else if tab === 'indexes'} <Indexes {collection} />
|
{:else if tab === 'indexes'} <Indexes {collection} />
|
||||||
@ -75,22 +69,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if collection}
|
|
||||||
<ViewConfig
|
|
||||||
bind:show={viewConfigModalOpen}
|
|
||||||
bind:activeViewKey={collection.viewKey}
|
|
||||||
{firstItem}
|
|
||||||
{collection}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.collection {
|
.view {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template: auto 1fr / 1fr;
|
grid-template: auto 1fr / 1fr;
|
||||||
}
|
}
|
||||||
.collection.empty {
|
.view.empty {
|
||||||
grid-template: 1fr / 1fr;
|
grid-template: 1fr / 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,66 +1,77 @@
|
|||||||
<script>
|
<script>
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import ObjectGrid from '$components/objectgrid.svelte';
|
import ObjectGrid from '$components/objectgrid.svelte';
|
||||||
import ObjectViewer from '$components/objectviewer.svelte';
|
import { onMount } from 'svelte';
|
||||||
import { DropIndex, GetIndexes } from '$wails/go/app/App';
|
|
||||||
import IndexDetail from './components/indexdetail.svelte';
|
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
let indexes = [];
|
|
||||||
let activePath = [];
|
let activePath = [];
|
||||||
let creatingNewIndex = false;
|
let _indexes = [];
|
||||||
|
let error = '';
|
||||||
|
|
||||||
$: collection && getIndexes();
|
async function refresh() {
|
||||||
|
error = await collection.getIndexes();
|
||||||
async function getIndexes() {
|
if (!error) {
|
||||||
const result = await GetIndexes(collection.hostKey, collection.dbKey, collection.key);
|
_indexes = collection.indexes.map(idx => {
|
||||||
if (result) {
|
return {
|
||||||
indexes = result;
|
name: idx.name,
|
||||||
|
background: idx.background || false,
|
||||||
|
unique: idx.unique || false,
|
||||||
|
sparse: idx.sparse || false,
|
||||||
|
model: idx.model,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createIndex() {
|
async function createIndex() {
|
||||||
creatingNewIndex = true;
|
const newIndexName = await collection.newIndex();
|
||||||
|
if (newIndexName) {
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function drop(key) {
|
async function dropIndex(indexName) {
|
||||||
if (typeof key !== 'string') {
|
if (typeof indexName !== 'string') {
|
||||||
key = activePath[0];
|
indexName = activePath[0];
|
||||||
}
|
}
|
||||||
const success = await DropIndex(collection.hostKey, collection.dbKey, collection.key, key);
|
|
||||||
|
const success = await collection.getIndexByName(indexName).drop();
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
await getIndexes();
|
|
||||||
activePath[0] = '';
|
activePath[0] = '';
|
||||||
|
await refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(refresh);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="indexes">
|
<div class="indexes">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<ObjectGrid
|
<ObjectGrid
|
||||||
key="name"
|
key="name"
|
||||||
data={indexes}
|
data={_indexes}
|
||||||
getRootMenu={(_, idx) => [ { label: 'Drop this index', fn: () => drop(idx.name) } ]}
|
getRootMenu={(_, idx) => [ { label: 'Drop this index', fn: () => dropIndex(idx.name) } ]}
|
||||||
|
errorTitle={error ? 'Error while getting indexes' : ''}
|
||||||
|
errorDescription={error}
|
||||||
bind:activePath
|
bind:activePath
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn" on:click={getIndexes}>
|
<button class="btn" on:click={refresh}>
|
||||||
<Icon name="reload" /> Reload
|
<Icon name="reload" /> Reload
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" on:click={createIndex}>
|
<button class="btn" on:click={createIndex}>
|
||||||
<Icon name="+" /> Create index…
|
<Icon name="+" /> Create index…
|
||||||
</button>
|
</button>
|
||||||
<button class="btn danger" on:click={drop} disabled={!indexes?.length || !activePath[0]}>
|
<button class="btn danger" on:click={dropIndex} disabled={!_indexes.length || !activePath[0]}>
|
||||||
<Icon name="x" /> Drop selected
|
<Icon name="x" /> Drop selected
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<IndexDetail bind:creatingNewIndex {collection} on:reload={getIndexes} />
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.indexes {
|
.indexes {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import Details from '$components/details.svelte';
|
import Details from '$components/details.svelte';
|
||||||
import Grid from '$components/grid.svelte';
|
import Grid from '$components/grid.svelte';
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
|
import ObjectEditor from '$components/objecteditor.svelte';
|
||||||
import ObjectViewer from '$components/objectviewer.svelte';
|
import ObjectViewer from '$components/objectviewer.svelte';
|
||||||
import { randomString } from '$lib/math';
|
import { randomString } from '$lib/math';
|
||||||
import { inputTypes } from '$lib/mongo';
|
import { inputTypes } from '$lib/mongo';
|
||||||
@ -11,7 +12,6 @@
|
|||||||
import { EJSON } from 'bson';
|
import { EJSON } from 'bson';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import Form from './components/form.svelte';
|
import Form from './components/form.svelte';
|
||||||
import ObjectEditor from '$components/objecteditor.svelte';
|
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
@ -25,8 +25,8 @@
|
|||||||
let objectViewerData = '';
|
let objectViewerData = '';
|
||||||
let viewType = 'form';
|
let viewType = 'form';
|
||||||
let allValid = false;
|
let allValid = false;
|
||||||
|
let viewsForCollection = {};
|
||||||
|
|
||||||
$: viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key);
|
|
||||||
$: oppositeViewType = viewType === 'table' ? 'form' : 'table';
|
$: oppositeViewType = viewType === 'table' ? 'form' : 'table';
|
||||||
$: allValid = Object.values(formValidity).every(v => v !== false);
|
$: allValid = Object.values(formValidity).every(v => v !== false);
|
||||||
|
|
||||||
@ -46,6 +46,10 @@
|
|||||||
newItems = [ {} ];
|
newItems = [ {} ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if ($views) {
|
||||||
|
viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key);
|
||||||
|
}
|
||||||
|
|
||||||
async function insert() {
|
async function insert() {
|
||||||
insertedIds = await InsertItems(
|
insertedIds = await InsertItems(
|
||||||
collection.hostKey,
|
collection.hostKey,
|
||||||
@ -60,9 +64,7 @@
|
|||||||
|
|
||||||
function showDocs() {
|
function showDocs() {
|
||||||
dispatch('performFind', {
|
dispatch('performFind', {
|
||||||
query: insertedIds.length === 1
|
query: insertedIds.length === 1 ? `{ "_id": ${JSON.stringify(insertedIds[0])} }` : `{ "_id": { "$in": [ ${insertedIds.map(id => JSON.stringify(id)).join(', ')} ] } }`,
|
||||||
? `{ "_id": ${JSON.stringify(insertedIds[0])} }`
|
|
||||||
: `{ "_id": { "$in": [ ${insertedIds.map(id => JSON.stringify(id)).join(', ')} ] } }`,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +99,10 @@
|
|||||||
newItems = newItems;
|
newItems = newItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openViewConfig() {
|
||||||
|
views.openConfig(collection);
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (collection.viewKey === 'list') {
|
if (collection.viewKey === 'list') {
|
||||||
editor.dispatch({
|
editor.dispatch({
|
||||||
@ -146,11 +152,11 @@
|
|||||||
<Grid
|
<Grid
|
||||||
key="id"
|
key="id"
|
||||||
items={newItems}
|
items={newItems}
|
||||||
columns={
|
columns={$views[collection.viewKey]?.columns
|
||||||
$views[collection.viewKey]?.columns
|
?.filter(c => inputTypes.includes(c.inputType))
|
||||||
?.filter(c => inputTypes.includes(c.inputType))
|
.map(c => {
|
||||||
.map(c => ({ ...c, id: randomString(8), title: c.key })) || []
|
return { ...c, id: randomString(8), title: c.key };
|
||||||
}
|
}) || []}
|
||||||
showHeaders={true}
|
showHeaders={true}
|
||||||
canSelect={false}
|
canSelect={false}
|
||||||
canRemoveItems={true}
|
canRemoveItems={true}
|
||||||
@ -188,11 +194,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<label class="field inline">
|
<label class="field inline">
|
||||||
<select bind:value={collection.viewKey}>
|
<select bind:value={collection.viewKey}>
|
||||||
{#each Object.entries(viewsForCollection) as [key, view]}
|
{#each Object.entries(viewsForCollection) as [ key, view ]}
|
||||||
<option value={key}>{key === 'list' ? 'Raw JSON' : view.name}</option>
|
<option value={key}>{key === 'list' ? 'Raw JSON' : view.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<button class="btn" type="button" on:click={() => dispatch('openViewConfig')} title="Configure view">
|
<button class="btn" type="button" on:click={openViewConfig} title="Configure view">
|
||||||
<Icon name="cog" />
|
<Icon name="cog" />
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
let many = true;
|
let many = true;
|
||||||
let result = undefined;
|
let result = undefined;
|
||||||
let editor;
|
let editor;
|
||||||
$: code = `db.${collection.key}.remove(${json});`;
|
// $: code = `db.${collection.key}.remove(${json});`;
|
||||||
|
|
||||||
async function removeItems() {
|
async function removeItems() {
|
||||||
result = await RemoveItems(
|
result = await RemoveItems(
|
||||||
|
@ -18,11 +18,16 @@
|
|||||||
<!-- <CodeExample code="db.stats()" /> -->
|
<!-- <CodeExample code="db.stats()" /> -->
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<ObjectGrid data={collection.stats} />
|
<ObjectGrid
|
||||||
|
data={collection.stats}
|
||||||
|
errorTitle={collection.statsError ? 'Error fetching collection stats' : ''}
|
||||||
|
errorDescription={collection.statsError}
|
||||||
|
busy={!collection.stats && !collection.statsError && `Fetching stats for ${collection.key}…`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button class="btn secondary" on:click={copy}>
|
<button class="btn secondary" on:click={copy} disabled={!collection.stats}>
|
||||||
<Icon name={copySucceeded ? 'check' : 'clipboard'} />
|
<Icon name={copySucceeded ? 'check' : 'clipboard'} />
|
||||||
Copy JSON
|
Copy JSON
|
||||||
</button>
|
</button>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
const allOperators = Object.values(atomicUpdateOperators).map(Object.keys).flat();
|
const allOperators = Object.values(atomicUpdateOperators).map(Object.keys).flat();
|
||||||
const form = { query: '{}', parameters: [ { type: '$set' } ] };
|
const form = { query: '{}', parameters: [ { type: '$set' } ] };
|
||||||
let updatedCount;
|
let updatedCount;
|
||||||
$: code = buildCode(form);
|
// $: code = buildCode(form);
|
||||||
$: invalid = !form.query || form.parameters?.some(param => {
|
$: invalid = !form.query || form.parameters?.some(param => {
|
||||||
if (!param.value) {
|
if (!param.value) {
|
||||||
return true;
|
return true;
|
||||||
@ -26,26 +26,28 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function buildCode(form) {
|
// function buildCode(form) {
|
||||||
let operation = '{ ' + form.parameters.filter(p => p.type).map(p => `${p.type}: ${p.value || '{}'}`).join(', ') + ' }';
|
// let operation = '{ ' + form.parameters.filter(p => p.type).map(p => `${p.type}: ${p.value || '{}'}`).join(', ') + ' }';
|
||||||
if (operation === '{ }') {
|
// if (operation === '{ }') {
|
||||||
operation = '{}';
|
// operation = '{}';
|
||||||
}
|
// }
|
||||||
|
|
||||||
let options = (form.upsert || form.many) ? ', { ' : '';
|
// let options = (form.upsert || form.many) ? ', { ' : '';
|
||||||
form.upsert && (options += 'upsert: true');
|
// form.upsert && (options += 'upsert: true');
|
||||||
form.upsert && form.many && (options += ', ');
|
// form.upsert && form.many && (options += ', ');
|
||||||
form.many && (options += 'multi: true');
|
// form.many && (options += 'multi: true');
|
||||||
(form.upsert || form.many) && (options += ' }');
|
// (form.upsert || form.many) && (options += ' }');
|
||||||
|
|
||||||
const code = `db.${collection.key}.update(${form.query || '{}'}, ${operation}${options});`;
|
// const code = `db.${collection.key}.update(${form.query || '{}'}, ${operation}${options});`;
|
||||||
return code;
|
// return code;
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function submitQuery() {
|
async function submitQuery() {
|
||||||
const f = deepClone(form);
|
const f = deepClone(form);
|
||||||
f.query = convertLooseJson(f.query);
|
f.query = convertLooseJson(f.query);
|
||||||
f.parameters = f.parameters.map(param => ({ ...param, value: convertLooseJson(param.value) }));
|
f.parameters = f.parameters.map(param => {
|
||||||
|
return { ...param, value: convertLooseJson(param.value) };
|
||||||
|
});
|
||||||
updatedCount = await UpdateItems(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(f));
|
updatedCount = await UpdateItems(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,9 +122,9 @@
|
|||||||
<fieldset class="parameter">
|
<fieldset class="parameter">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<select bind:value={param.type} class="type">
|
<select bind:value={param.type} class="type">
|
||||||
{#each Object.entries(atomicUpdateOperators) as [groupName, options]}
|
{#each Object.entries(atomicUpdateOperators) as [ groupName, options ]}
|
||||||
<optgroup label={groupName}>
|
<optgroup label={groupName}>
|
||||||
{#each Object.entries(options) as [key, label]}
|
{#each Object.entries(options) as [ key, label ]}
|
||||||
<option value={key} disabled={form.parameters.some(p => p.type === key) && (key !== param.type)}>
|
<option value={key} disabled={form.parameters.some(p => p.type === key) && (key !== param.type)}>
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</option>
|
||||||
|
@ -3,12 +3,14 @@
|
|||||||
import Grid from '$components/grid.svelte';
|
import Grid from '$components/grid.svelte';
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import { startProgress } from '$lib/progress';
|
import { startProgress } from '$lib/progress';
|
||||||
import connections from '$lib/stores/connections';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
import hosts from '$lib/stores/hosts';
|
|
||||||
import applicationSettings from '$lib/stores/settings';
|
import applicationSettings from '$lib/stores/settings';
|
||||||
import { OpenConnection, OpenDatabase, PerformDump } from '$wails/go/app/App';
|
import { OpenConnection, OpenDatabase } from '$wails/go/app/App';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let info;
|
export let info = {};
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
$: if (info) {
|
$: if (info) {
|
||||||
info.outdir = info.outdir || $applicationSettings.defaultExportDirectory;
|
info.outdir = info.outdir || $applicationSettings.defaultExportDirectory;
|
||||||
@ -24,10 +26,10 @@
|
|||||||
const progress = startProgress(`Opening connection to host "${hostKey}"`);
|
const progress = startProgress(`Opening connection to host "${hostKey}"`);
|
||||||
const databases = await OpenConnection(hostKey);
|
const databases = await OpenConnection(hostKey);
|
||||||
|
|
||||||
if (databases && !$connections[hostKey]) {
|
if (databases && !$hostTree[hostKey]) {
|
||||||
$connections[hostKey] = { databases: {} };
|
$hostTree[hostKey] = { databases: {} };
|
||||||
databases.sort().forEach(dbKey => {
|
databases.sort().forEach(dbKey => {
|
||||||
$connections[hostKey].databases[dbKey] = $connections[hostKey].databases[dbKey] || { collections: {} };
|
$hostTree[hostKey].databases[dbKey] = $hostTree[hostKey].databases[dbKey] || { collections: {} };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,28 +46,24 @@
|
|||||||
const collections = await OpenDatabase(info.hostKey, dbKey);
|
const collections = await OpenDatabase(info.hostKey, dbKey);
|
||||||
|
|
||||||
for (const collKey of collections?.sort() || []) {
|
for (const collKey of collections?.sort() || []) {
|
||||||
$connections[info.hostKey].databases[dbKey].collections[collKey] = {};
|
$hostTree[info.hostKey].databases[dbKey].collections[collKey] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.end();
|
progress.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function performDump() {
|
|
||||||
const ok = await PerformDump(JSON.stringify(info));
|
|
||||||
if (ok) {
|
|
||||||
info = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectCollection(collKey) {
|
function selectCollection(collKey) {
|
||||||
info.collKeys = [ collKey ];
|
info.collKeys = [ collKey ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function performDump() {
|
||||||
|
dispatch('dump', { info });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:show={info} title="Perform dump">
|
<Modal title="Perform dump" on:close>
|
||||||
<form on:submit|preventDefault={performDump}>
|
<form on:submit|preventDefault={performDump}>
|
||||||
<!-- svelte-ignore a11y-label-has-associated-control - input is in DirectoryChooser -->
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Output destination:</span>
|
<span class="label">Output destination:</span>
|
||||||
<DirectoryChooser bind:value={info.outdir} />
|
<DirectoryChooser bind:value={info.outdir} />
|
||||||
@ -83,7 +81,9 @@
|
|||||||
hideChildrenToggles
|
hideChildrenToggles
|
||||||
items={[
|
items={[
|
||||||
{ id: undefined, name: '(localhost)' },
|
{ id: undefined, name: '(localhost)' },
|
||||||
...Object.keys($hosts).map(id => ({ id, name: $hosts[id]?.name })),
|
...Object.keys($hostTree).map(id => {
|
||||||
|
return { id, name: $hostTree[id]?.name };
|
||||||
|
}),
|
||||||
]}
|
]}
|
||||||
on:select={e => selectHost(e.detail?.itemKey)}
|
on:select={e => selectHost(e.detail?.itemKey)}
|
||||||
/>
|
/>
|
||||||
@ -97,9 +97,9 @@
|
|||||||
hideChildrenToggles
|
hideChildrenToggles
|
||||||
items={[
|
items={[
|
||||||
{ id: undefined, name: '(all databases)' },
|
{ id: undefined, name: '(all databases)' },
|
||||||
...($connections[info.hostKey]?.databases
|
...($hostTree[info.hostKey]?.databases ? Object.keys($hostTree[info.hostKey].databases).map(id => {
|
||||||
? Object.keys($connections[info.hostKey].databases).map(id => ({ id, name: id }))
|
return { id, name: id };
|
||||||
: []
|
}) : []
|
||||||
),
|
),
|
||||||
]}
|
]}
|
||||||
on:select={e => selectDatabase(e.detail?.itemKey)}
|
on:select={e => selectDatabase(e.detail?.itemKey)}
|
||||||
@ -114,9 +114,9 @@
|
|||||||
hideChildrenToggles
|
hideChildrenToggles
|
||||||
items={[
|
items={[
|
||||||
{ id: undefined, name: '(all collections)' },
|
{ id: undefined, name: '(all collections)' },
|
||||||
...($connections[info.hostKey]?.databases[info.dbKey]?.collections
|
...($hostTree[info.hostKey]?.databases[info.dbKey]?.collections ? Object.keys($hostTree[info.hostKey].databases[info.dbKey].collections).map(id => {
|
||||||
? Object.keys($connections[info.hostKey].databases[info.dbKey].collections).map(id => ({ id, name: id }))
|
return { id, name: id };
|
||||||
: []
|
}) : []
|
||||||
),
|
),
|
||||||
]}
|
]}
|
||||||
on:select={e => selectCollection(e.detail?.itemKey)}
|
on:select={e => selectCollection(e.detail?.itemKey)}
|
60
frontend/src/organisms/connection/database/index.svelte
Normal file
60
frontend/src/organisms/connection/database/index.svelte
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<script>
|
||||||
|
import BlankState from '$components/blankstate.svelte';
|
||||||
|
import TabBar from '$components/tabbar.svelte';
|
||||||
|
import { EventsOn } from '$wails/runtime/runtime';
|
||||||
|
import Stats from './stats.svelte';
|
||||||
|
|
||||||
|
export let database;
|
||||||
|
export let hostKey;
|
||||||
|
export let dbKey;
|
||||||
|
|
||||||
|
let tab = 'stats';
|
||||||
|
|
||||||
|
$: if (database) {
|
||||||
|
database.hostKey = hostKey;
|
||||||
|
database.dbKey = dbKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (hostKey || dbKey) {
|
||||||
|
tab = 'stats';
|
||||||
|
}
|
||||||
|
|
||||||
|
EventsOn('OpenStatsTab', name => (tab = name || tab));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="view" class:empty={!database}>
|
||||||
|
{#if database}
|
||||||
|
{#key database}
|
||||||
|
<TabBar tabs={[ { key: 'stats', icon: 'chart', title: 'Database stats' } ]} bind:selectedKey={tab} />
|
||||||
|
<div class="container">
|
||||||
|
{#if tab === 'stats'} <Stats {database} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
|
{:else}
|
||||||
|
<BlankState label="Select a database to continue" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.view {
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template: auto 1fr / 1fr;
|
||||||
|
}
|
||||||
|
.view.empty {
|
||||||
|
grid-template: 1fr / 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.container > :global(*) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
50
frontend/src/organisms/connection/database/stats.svelte
Normal file
50
frontend/src/organisms/connection/database/stats.svelte
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script>
|
||||||
|
import Icon from '$components/icon.svelte';
|
||||||
|
import ObjectGrid from '$components/objectgrid.svelte';
|
||||||
|
|
||||||
|
export let database;
|
||||||
|
|
||||||
|
let copySucceeded = false;
|
||||||
|
|
||||||
|
async function copy() {
|
||||||
|
const json = JSON.stringify(database.stats, undefined, '\t');
|
||||||
|
await navigator.clipboard.writeText(json);
|
||||||
|
copySucceeded = true;
|
||||||
|
setTimeout(() => copySucceeded = false, 1500);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<!-- <CodeExample code="db.stats()" /> -->
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<ObjectGrid
|
||||||
|
data={database.stats}
|
||||||
|
errorTitle={database.statsError ? 'Error fetching database stats' : ''}
|
||||||
|
errorDescription={database.statsError}
|
||||||
|
busy={!database.stats && !database.statsError && `Fetching stats for ${database.key}…`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="btn secondary" on:click={copy} disabled={!database.stats}>
|
||||||
|
<Icon name={copySucceeded ? 'check' : 'clipboard'} />
|
||||||
|
Copy JSON
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.stats {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5rem;
|
||||||
|
grid-template: 1fr auto / 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats .grid {
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,26 +1,17 @@
|
|||||||
<script>
|
<script>
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import hosts from '$lib/stores/hosts';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
import { AddHost, UpdateHost } from '$wails/go/app/App';
|
import { AddHost, UpdateHost } from '$wails/go/app/App';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
|
|
||||||
export let show = false;
|
|
||||||
export let hostKey = '';
|
export let hostKey = '';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let form = {};
|
let form = {};
|
||||||
let error = '';
|
let error = '';
|
||||||
$: valid = validate(form);
|
$: valid = validate(form);
|
||||||
$: host = $hosts[hostKey];
|
$: host = $hostTree[hostKey];
|
||||||
|
|
||||||
$: if (show || !show) {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
form = { ...(host || {}) };
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate(form) {
|
function validate(form) {
|
||||||
return form.name && form.uri && true;
|
return form.name && form.uri && true;
|
||||||
@ -41,16 +32,20 @@
|
|||||||
hostKey = newHostKey;
|
hostKey = newHostKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
show = false;
|
dispatch('updated', form);
|
||||||
dispatch('reload');
|
dispatch('close');
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
form = { ...(host || {}) };
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:show title={host ? `Edit ${host.name}` : 'Create a new host'}>
|
<Modal title={host ? `Edit ${host.name}` : 'Create a new host'} on:close>
|
||||||
<form on:submit|preventDefault={submit}>
|
<form on:submit|preventDefault={submit}>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Label</span>
|
<span class="label">Label</span>
|
65
frontend/src/organisms/connection/host/index.svelte
Normal file
65
frontend/src/organisms/connection/host/index.svelte
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<script>
|
||||||
|
import BlankState from '$components/blankstate.svelte';
|
||||||
|
import TabBar from '$components/tabbar.svelte';
|
||||||
|
import { EventsOn } from '$wails/runtime/runtime';
|
||||||
|
import Status from './status.svelte';
|
||||||
|
import SystemInfo from './systeminfo.svelte';
|
||||||
|
|
||||||
|
export let host;
|
||||||
|
export let hostKey;
|
||||||
|
|
||||||
|
let tab = 'status';
|
||||||
|
|
||||||
|
$: if (host) {
|
||||||
|
host.hostKey = hostKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (hostKey) {
|
||||||
|
tab = 'status';
|
||||||
|
}
|
||||||
|
|
||||||
|
EventsOn('OpenStatusTab', name => (tab = name || tab));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="view" class:empty={!host}>
|
||||||
|
{#if host}
|
||||||
|
{#key host}
|
||||||
|
<TabBar tabs={[
|
||||||
|
{ key: 'status', icon: 'chart', title: 'Host status' },
|
||||||
|
{ key: 'systemInfo', icon: 'server', title: 'System info' },
|
||||||
|
]}
|
||||||
|
bind:selectedKey={tab} />
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{#if tab === 'status'} <Status {host} />
|
||||||
|
{:else if tab === 'systemInfo'} <SystemInfo {host} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
|
{:else}
|
||||||
|
<BlankState label="Select a host to continue" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.view {
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template: auto 1fr / 1fr;
|
||||||
|
}
|
||||||
|
.view.empty {
|
||||||
|
grid-template: 1fr / 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.container > :global(*) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
50
frontend/src/organisms/connection/host/status.svelte
Normal file
50
frontend/src/organisms/connection/host/status.svelte
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script>
|
||||||
|
import Icon from '$components/icon.svelte';
|
||||||
|
import ObjectGrid from '$components/objectgrid.svelte';
|
||||||
|
|
||||||
|
export let host;
|
||||||
|
|
||||||
|
let copySucceeded = false;
|
||||||
|
|
||||||
|
async function copy() {
|
||||||
|
const json = JSON.stringify(host.status, undefined, '\t');
|
||||||
|
await navigator.clipboard.writeText(json);
|
||||||
|
copySucceeded = true;
|
||||||
|
setTimeout(() => copySucceeded = false, 1500);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<!-- <CodeExample code="db.stats()" /> -->
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<ObjectGrid
|
||||||
|
data={host.status || {}}
|
||||||
|
errorTitle={host.statusError ? 'Error fetching server status' : ''}
|
||||||
|
errorDescription={host.statusError}
|
||||||
|
busy={!host.status && !host.statusError && 'Fetching server status…'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="btn secondary" on:click={copy} disabled={!host.status}>
|
||||||
|
<Icon name={copySucceeded ? 'check' : 'clipboard'} />
|
||||||
|
Copy JSON
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.stats {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5rem;
|
||||||
|
grid-template: 1fr auto / 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats .grid {
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
</style>
|
50
frontend/src/organisms/connection/host/systeminfo.svelte
Normal file
50
frontend/src/organisms/connection/host/systeminfo.svelte
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script>
|
||||||
|
import Icon from '$components/icon.svelte';
|
||||||
|
import ObjectGrid from '$components/objectgrid.svelte';
|
||||||
|
|
||||||
|
export let host;
|
||||||
|
|
||||||
|
let copySucceeded = false;
|
||||||
|
|
||||||
|
async function copy() {
|
||||||
|
const json = JSON.stringify(host.systemInfo, undefined, '\t');
|
||||||
|
await navigator.clipboard.writeText(json);
|
||||||
|
copySucceeded = true;
|
||||||
|
setTimeout(() => copySucceeded = false, 1500);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<!-- <CodeExample code="db.stats()" /> -->
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<ObjectGrid
|
||||||
|
data={host.systemInfo}
|
||||||
|
errorTitle={host.systemInfoError ? 'Error fetching system info' : ''}
|
||||||
|
errorDescription={host.systemInfoError}
|
||||||
|
busy={!host.systemInfo && !host.systemInfoError && 'Fetching system info…'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="btn secondary" on:click={copy} disabled={!host.systemInfo}>
|
||||||
|
<Icon name={copySucceeded ? 'check' : 'clipboard'} />
|
||||||
|
Copy JSON
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.stats {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5rem;
|
||||||
|
grid-template: 1fr auto / 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats .grid {
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,148 +1,70 @@
|
|||||||
<script>
|
<script>
|
||||||
import Grid from '$components/grid.svelte';
|
import Grid from '$components/grid.svelte';
|
||||||
import { startProgress } from '$lib/progress';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
import connections from '$lib/stores/connections';
|
|
||||||
import { WindowSetTitle } from '$wails/runtime/runtime';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { DropCollection, DropDatabase, OpenCollection, OpenConnection, OpenDatabase, RemoveHost, TruncateCollection } from '../../../wailsjs/go/app/App';
|
|
||||||
import hosts from '$lib/stores/hosts';
|
|
||||||
import { tick } from 'svelte';
|
|
||||||
|
|
||||||
export let activeHostKey = '';
|
export let path = [];
|
||||||
export let activeDbKey = '';
|
|
||||||
export let activeCollKey = '';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
let activeGridPath = [];
|
|
||||||
$: activeHostKey = activeGridPath[0] || activeHostKey;
|
|
||||||
$: activeDbKey = activeGridPath[1];
|
|
||||||
$: activeCollKey = activeGridPath[2];
|
|
||||||
$: host = $hosts[activeHostKey];
|
|
||||||
$: connection = $connections[activeHostKey];
|
|
||||||
$: database = connection?.databases[activeDbKey];
|
|
||||||
$: collection = database?.collections?.[activeCollKey];
|
|
||||||
|
|
||||||
export async function reload() {
|
|
||||||
activeHostKey && await openConnection(activeHostKey);
|
|
||||||
activeDbKey && await openDatabase(activeDbKey);
|
|
||||||
activeCollKey && await openCollection(activeCollKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openConnection(hostKey) {
|
|
||||||
const progress = startProgress(`Connecting to "${hostKey}"…`);
|
|
||||||
const databases = await OpenConnection(hostKey);
|
|
||||||
|
|
||||||
if (databases) {
|
|
||||||
$connections[hostKey] = { databases: {} };
|
|
||||||
databases.forEach(dbKey => {
|
|
||||||
$connections[hostKey].databases[dbKey] = { collections: {} };
|
|
||||||
});
|
|
||||||
activeHostKey = hostKey;
|
|
||||||
dispatch('connected', hostKey);
|
|
||||||
WindowSetTitle(`${$hosts[activeHostKey].name} - Rolens`);
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeHost(hostKey) {
|
|
||||||
activeCollKey = '';
|
|
||||||
activeDbKey = '';
|
|
||||||
activeHostKey = '';
|
|
||||||
await tick();
|
|
||||||
await RemoveHost(hostKey);
|
|
||||||
hosts.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openDatabase(dbKey) {
|
|
||||||
const progress = startProgress(`Opening database "${dbKey}"…`);
|
|
||||||
const collections = await OpenDatabase(activeHostKey, dbKey);
|
|
||||||
|
|
||||||
for (const collKey of collections || []) {
|
|
||||||
$connections[activeHostKey].databases[dbKey].collections[collKey] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function dropDatabase(dbKey) {
|
|
||||||
const progress = startProgress(`Dropping database "${dbKey}"…`);
|
|
||||||
await DropDatabase(activeHostKey, dbKey);
|
|
||||||
await reload();
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openCollection(collKey) {
|
|
||||||
const progress = startProgress(`Opening collection "${collKey}"…`);
|
|
||||||
const stats = await OpenCollection(activeHostKey, activeDbKey, collKey);
|
|
||||||
$connections[activeHostKey].databases[activeDbKey].collections[collKey] = $connections[activeHostKey].databases[activeDbKey].collections[collKey] || {};
|
|
||||||
$connections[activeHostKey].databases[activeDbKey].collections[collKey].stats = stats;
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function truncateCollection(dbKey, collKey) {
|
|
||||||
const progress = startProgress(`Truncating collection "${collKey}"…`);
|
|
||||||
await TruncateCollection(activeHostKey, dbKey, collKey);
|
|
||||||
await reload();
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function dropCollection(dbKey, collKey) {
|
|
||||||
const progress = startProgress(`Dropping collection "${collKey}"…`);
|
|
||||||
await DropCollection(activeHostKey, dbKey, collKey);
|
|
||||||
await reload();
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
striped={false}
|
striped={false}
|
||||||
columns={[ { key: 'name' }, { key: 'count', right: true } ]}
|
columns={[ { key: 'name' }, { key: 'count', right: true } ]}
|
||||||
items={Object.keys($hosts).map(hostKey => ({
|
items={Object.values($hostTree || {}).map(host => {
|
||||||
id: hostKey,
|
return {
|
||||||
name: $hosts[hostKey].name,
|
id: host.key,
|
||||||
icon: 'server',
|
name: host.name,
|
||||||
children: Object.keys(connection?.databases || {}).sort().map(dbKey => ({
|
icon: 'server',
|
||||||
id: dbKey,
|
children: Object.values(host.databases || {})
|
||||||
name: dbKey,
|
.sort((a, b) => a.key.localeCompare(b))
|
||||||
icon: 'db',
|
.map(database => {
|
||||||
count: Object.keys(connection.databases[dbKey].collections || {}).length || '',
|
return {
|
||||||
children: Object.keys(connection.databases[dbKey].collections).sort().map(collKey => ({
|
id: database.key,
|
||||||
id: collKey,
|
name: database.key,
|
||||||
name: collKey,
|
icon: 'db',
|
||||||
icon: 'list',
|
count: Object.keys(database.collections || {}).length || '',
|
||||||
menu: [
|
children: Object.values(database.collections)
|
||||||
{ label: 'Export collection (JSON, CSV)…', fn: () => dispatch('exportCollection', collKey) },
|
.sort((a, b) => a.key.localeCompare(b))
|
||||||
{ label: 'Dump collection (BSON via mongodump)…', fn: () => dispatch('dumpCollection', collKey) },
|
.map(collection => {
|
||||||
{ separator: true },
|
return {
|
||||||
{ label: 'Rename collection…', fn: () => dispatch('renameCollection', collKey) },
|
id: collection.key,
|
||||||
{ label: 'Truncate collection…', fn: () => truncateCollection(dbKey, collKey) },
|
name: collection.key,
|
||||||
{ label: 'Drop collection…', fn: () => dropCollection(dbKey, collKey) },
|
icon: 'list',
|
||||||
{ separator: true },
|
menu: [
|
||||||
{ label: 'New collection…', fn: () => dispatch('newCollection') },
|
{ label: 'Export collection…', fn: collection.export },
|
||||||
],
|
{ label: 'Dump collection (BSON via mongodump)…', fn: collection.dump },
|
||||||
})) || [],
|
{ separator: true },
|
||||||
|
{ label: 'Rename collection…', fn: collection.rename },
|
||||||
|
{ label: 'Truncate collection…', fn: collection.truncate },
|
||||||
|
{ label: 'Drop collection…', fn: collection.drop },
|
||||||
|
{ separator: true },
|
||||||
|
{ label: 'New collection…', fn: database.newCollection },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}) || [],
|
||||||
|
menu: [
|
||||||
|
{ label: 'Dump database (BSON via mongodump)…', fn: database.dump },
|
||||||
|
{ label: 'Drop database…', fn: database.drop },
|
||||||
|
{ separator: true },
|
||||||
|
{ label: 'New database…', fn: host.newDatabase },
|
||||||
|
{ label: 'New collection…', fn: database.newCollection },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}),
|
||||||
menu: [
|
menu: [
|
||||||
{ label: 'Drop database…', fn: () => dropDatabase(dbKey) },
|
{ label: 'New database…', fn: host.newDatabase },
|
||||||
{ separator: true },
|
{ separator: true },
|
||||||
{ label: 'New database…', fn: () => dispatch('newDatabase') },
|
{ label: `Edit host ${host.name}…`, fn: host.edit },
|
||||||
{ label: 'New collection…', fn: () => dispatch('newCollection') },
|
{ label: 'Remove host…', fn: host.remove },
|
||||||
],
|
],
|
||||||
})),
|
};
|
||||||
menu: [
|
})}
|
||||||
{ label: 'New database…', fn: () => dispatch('newDatabase') },
|
|
||||||
{ separator: true },
|
|
||||||
{ label: `Edit host ${$hosts[hostKey].name}…`, fn: () => dispatch('editHost', hostKey) },
|
|
||||||
{ label: `Remove host…`, fn: () => removeHost(hostKey) },
|
|
||||||
],
|
|
||||||
}))}
|
|
||||||
bind:activePath={activeGridPath}
|
|
||||||
on:select={e => {
|
on:select={e => {
|
||||||
const key = e.detail.itemKey;
|
let level;
|
||||||
switch (e.detail?.level) {
|
({ path, level } = e.detail);
|
||||||
case 0: return openConnection(key);
|
|
||||||
case 1: return openDatabase(key);
|
switch (level) {
|
||||||
case 2: return openCollection(key);
|
case 0: return $hostTree[path[0]].open();
|
||||||
|
case 1: return $hostTree[path[0]].databases[path[1]].open();
|
||||||
|
case 2: return $hostTree[path[0]].databases[path[1]].collections[path[2]].open();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,129 +1,52 @@
|
|||||||
<script>
|
<script>
|
||||||
import { startProgress } from '$lib/progress';
|
|
||||||
import connections from '$lib/stores/connections';
|
|
||||||
import { Hosts, RenameCollection } from '$wails/go/app/App';
|
|
||||||
import { EnterText } from '$wails/go/ui/UI';
|
|
||||||
import { EventsOn } from '$wails/runtime/runtime';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import CollectionDetail from './collection/index.svelte';
|
|
||||||
import DumpInfo from './dump.svelte';
|
|
||||||
import HostDetail from './hostdetail.svelte';
|
|
||||||
import HostTree from './hosttree.svelte';
|
|
||||||
import sharedState from '$lib/stores/sharedstate';
|
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import hosts from '$lib/stores/hosts';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
|
import sharedState from '$lib/stores/sharedstate';
|
||||||
|
import CollectionView from './collection/index.svelte';
|
||||||
|
import DatabaseView from './database/index.svelte';
|
||||||
|
import HostView from './host/index.svelte';
|
||||||
|
import HostTree from './hosttree.svelte';
|
||||||
|
|
||||||
export let activeHostKey = '';
|
let path = [];
|
||||||
export let activeDbKey = '';
|
|
||||||
export let activeCollKey = '';
|
|
||||||
|
|
||||||
let hostTree;
|
$: activeHostKey = path[0];
|
||||||
let showHostDetail = false;
|
$: activeDbKey = path[1];
|
||||||
let hostDetailKey = '';
|
$: activeCollKey = path[2];
|
||||||
let exportInfo;
|
|
||||||
|
|
||||||
$: sharedState.currentHost.set(activeHostKey);
|
$: sharedState.currentHost.set(activeHostKey);
|
||||||
$: sharedState.currentDb.set(activeDbKey);
|
$: sharedState.currentDb.set(activeDbKey);
|
||||||
$: sharedState.currentColl.set(activeCollKey);
|
$: sharedState.currentColl.set(activeCollKey);
|
||||||
|
|
||||||
export function createHost() {
|
|
||||||
hostDetailKey = '';
|
|
||||||
showHostDetail = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function editHost(hostKey) {
|
|
||||||
hostDetailKey = hostKey;
|
|
||||||
showHostDetail = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createDatabase() {
|
|
||||||
const name = await EnterText('Create a database', 'Enter the database name. Note: databases in MongoDB do not exist until they have a collection and an item. Your new database will not persist on the server; fill it to have it created.');
|
|
||||||
if (name) {
|
|
||||||
$connections[activeHostKey].databases[name] = { collections: {} };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function renameCollection(oldCollKey) {
|
|
||||||
const newCollKey = await EnterText('Rename collection', `Enter a new name for collection ${oldCollKey}.`, oldCollKey);
|
|
||||||
if (newCollKey && (newCollKey !== oldCollKey)) {
|
|
||||||
const progress = startProgress(`Renaming collection "${oldCollKey}" to "${newCollKey}"…`);
|
|
||||||
const ok = await RenameCollection(activeHostKey, activeDbKey, oldCollKey, newCollKey);
|
|
||||||
if (ok) {
|
|
||||||
activeCollKey = newCollKey;
|
|
||||||
await hostTree.reload();
|
|
||||||
}
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createCollection() {
|
|
||||||
const name = await EnterText('Create a collection', 'Note: collections in MongoDB do not exist until they have at least one item. Your new collection will not persist on the server; fill it to have it created.');
|
|
||||||
if (name) {
|
|
||||||
$connections[activeHostKey].databases[activeDbKey].collections[name] = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function exportCollection(collKey) {
|
|
||||||
exportInfo = {
|
|
||||||
type: 'export',
|
|
||||||
filetype: 'json',
|
|
||||||
hostKey: activeHostKey,
|
|
||||||
dbKey: activeDbKey,
|
|
||||||
collKeys: [ collKey ],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function dumpCollection(collKey) {
|
|
||||||
exportInfo = {
|
|
||||||
type: 'dump',
|
|
||||||
filetype: 'bson',
|
|
||||||
hostKey: activeHostKey,
|
|
||||||
dbKey: activeDbKey,
|
|
||||||
collKeys: [ collKey ],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
EventsOn('CreateHost', createHost);
|
|
||||||
EventsOn('CreateDatabase', createDatabase);
|
|
||||||
EventsOn('CreateCollection', createCollection);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="tree">
|
<div class="tree">
|
||||||
<div class="tree-buttons">
|
<div class="tree-buttons">
|
||||||
<button class="button-small" on:click={createHost}>
|
<button class="button-small" on:click={hostTree.newHost}>
|
||||||
<Icon name="+" /> New host
|
<Icon name="+" /> New host
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<HostTree
|
<HostTree bind:path />
|
||||||
bind:activeHostKey
|
|
||||||
bind:activeCollKey
|
|
||||||
bind:activeDbKey
|
|
||||||
bind:this={hostTree}
|
|
||||||
on:newHost={createHost}
|
|
||||||
on:newDatabase={createDatabase}
|
|
||||||
on:newCollection={createCollection}
|
|
||||||
on:editHost={e => editHost(e.detail)}
|
|
||||||
on:renameCollection={e => renameCollection(e.detail)}
|
|
||||||
on:exportCollection={e => exportCollection(e.detail)}
|
|
||||||
on:dumpCollection={e => dumpCollection(e.detail)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CollectionDetail
|
{#if activeCollKey}
|
||||||
collection={$connections[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
|
<CollectionView
|
||||||
hostKey={activeHostKey}
|
collection={$hostTree[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
|
||||||
dbKey={activeDbKey}
|
hostKey={activeHostKey}
|
||||||
collectionKey={activeCollKey}
|
dbKey={activeDbKey}
|
||||||
/>
|
collKey={activeCollKey}
|
||||||
|
/>
|
||||||
<HostDetail
|
{:else if activeDbKey}
|
||||||
bind:show={showHostDetail}
|
<DatabaseView
|
||||||
on:reload={hosts.update}
|
database={$hostTree[activeHostKey]?.databases[activeDbKey]}
|
||||||
hostKey={activeHostKey}
|
hostKey={activeHostKey}
|
||||||
/>
|
dbKey={activeDbKey}
|
||||||
|
/>
|
||||||
<DumpInfo bind:info={exportInfo} />
|
{:else if activeHostKey}
|
||||||
|
<HostView
|
||||||
|
host={$hostTree[activeHostKey]}
|
||||||
|
hostKey={activeHostKey}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.tree {
|
.tree {
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
function createHost() {
|
|
||||||
dispatch('createHost');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="welcome">
|
|
||||||
<div class="brand">
|
|
||||||
<img src="/logo.png" alt="" class="logo" />
|
|
||||||
<div class="text">
|
|
||||||
<div class="name">Welcome to Rolens!</div>
|
|
||||||
<div class="subtitle">A modest MongoDB client</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="btn" on:click={createHost}>Create your first host</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.welcome {
|
|
||||||
/* transform: translateY(-80px); */
|
|
||||||
margin-top: -90px;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.brand .logo {
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
.brand .text {
|
|
||||||
align-self: flex-end;
|
|
||||||
margin: 0 0 4rem 1rem;
|
|
||||||
}
|
|
||||||
.brand .text .name {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.brand .text .subtitle {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
11
frontend/wailsjs/go/app/App.d.ts
generated
vendored
11
frontend/wailsjs/go/app/App.d.ts
generated
vendored
@ -1,7 +1,6 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
import {app} from '../models';
|
import {app} from '../models';
|
||||||
import {primitive} from '../models';
|
|
||||||
import {map[string]app} from '../models';
|
import {map[string]app} from '../models';
|
||||||
import {menu} from '../models';
|
import {menu} from '../models';
|
||||||
import {context} from '../models';
|
import {context} from '../models';
|
||||||
@ -21,9 +20,9 @@ export function DropIndex(arg1:string,arg2:string,arg3:string,arg4:string):Promi
|
|||||||
|
|
||||||
export function Environment():Promise<app.EnvironmentInfo>;
|
export function Environment():Promise<app.EnvironmentInfo>;
|
||||||
|
|
||||||
export function FindItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise<app.QueryResult>;
|
export function FindItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise<app.FindItemsResult>;
|
||||||
|
|
||||||
export function GetIndexes(arg1:string,arg2:string,arg3:string):Promise<Array<primitive.M>>;
|
export function GetIndexes(arg1:string,arg2:string,arg3:string):Promise<app.GetIndexesResult>;
|
||||||
|
|
||||||
export function Hosts():Promise<map[string]app.Host>;
|
export function Hosts():Promise<map[string]app.Host>;
|
||||||
|
|
||||||
@ -31,11 +30,11 @@ export function InsertItems(arg1:string,arg2:string,arg3:string,arg4:string):Pro
|
|||||||
|
|
||||||
export function Menu():Promise<menu.Menu>;
|
export function Menu():Promise<menu.Menu>;
|
||||||
|
|
||||||
export function OpenCollection(arg1:string,arg2:string,arg3:string):Promise<primitive.M>;
|
export function OpenCollection(arg1:string,arg2:string,arg3:string):Promise<app.OpenCollectionResult>;
|
||||||
|
|
||||||
export function OpenConnection(arg1:string):Promise<Array<string>>;
|
export function OpenConnection(arg1:string):Promise<app.OpenConnectionResult>;
|
||||||
|
|
||||||
export function OpenDatabase(arg1:string,arg2:string):Promise<Array<string>>;
|
export function OpenDatabase(arg1:string,arg2:string):Promise<app.OpenDatabaseResult>;
|
||||||
|
|
||||||
export function PerformDump(arg1:string):Promise<boolean>;
|
export function PerformDump(arg1:string):Promise<boolean>;
|
||||||
|
|
||||||
|
65
frontend/wailsjs/go/models.ts
generated
65
frontend/wailsjs/go/models.ts
generated
@ -1,65 +0,0 @@
|
|||||||
export namespace app {
|
|
||||||
|
|
||||||
export class EnvironmentInfo {
|
|
||||||
arch: string;
|
|
||||||
buildType: string;
|
|
||||||
platform: string;
|
|
||||||
hasMongoExport: boolean;
|
|
||||||
hasMongoDump: boolean;
|
|
||||||
homeDirectory: string;
|
|
||||||
dataDirectory: string;
|
|
||||||
logDirectory: string;
|
|
||||||
downloadDirectory: string;
|
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
|
||||||
return new EnvironmentInfo(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
|
||||||
this.arch = source["arch"];
|
|
||||||
this.buildType = source["buildType"];
|
|
||||||
this.platform = source["platform"];
|
|
||||||
this.hasMongoExport = source["hasMongoExport"];
|
|
||||||
this.hasMongoDump = source["hasMongoDump"];
|
|
||||||
this.homeDirectory = source["homeDirectory"];
|
|
||||||
this.dataDirectory = source["dataDirectory"];
|
|
||||||
this.logDirectory = source["logDirectory"];
|
|
||||||
this.downloadDirectory = source["downloadDirectory"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class QueryResult {
|
|
||||||
total: number;
|
|
||||||
results: string[];
|
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
|
||||||
return new QueryResult(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
|
||||||
this.total = source["total"];
|
|
||||||
this.results = source["results"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class Settings {
|
|
||||||
defaultLimit: number;
|
|
||||||
defaultSort: string;
|
|
||||||
autosubmitQuery: boolean;
|
|
||||||
defaultExportDirectory: string;
|
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
|
||||||
return new Settings(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
|
||||||
this.defaultLimit = source["defaultLimit"];
|
|
||||||
this.defaultSort = source["defaultSort"];
|
|
||||||
this.autosubmitQuery = source["autosubmitQuery"];
|
|
||||||
this.defaultExportDirectory = source["defaultExportDirectory"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
2
frontend/wailsjs/go/ui/UI.d.ts
generated
vendored
2
frontend/wailsjs/go/ui/UI.d.ts
generated
vendored
@ -4,8 +4,6 @@ import {context} from '../models';
|
|||||||
|
|
||||||
export function Beep():Promise<void>;
|
export function Beep():Promise<void>;
|
||||||
|
|
||||||
export function EnterText(arg1:string,arg2:string,arg3:string):Promise<string>;
|
|
||||||
|
|
||||||
export function OpenDirectory(arg1:string):Promise<string>;
|
export function OpenDirectory(arg1:string):Promise<string>;
|
||||||
|
|
||||||
export function Reveal(arg1:string):Promise<void>;
|
export function Reveal(arg1:string):Promise<void>;
|
||||||
|
4
frontend/wailsjs/go/ui/UI.js
generated
4
frontend/wailsjs/go/ui/UI.js
generated
@ -6,10 +6,6 @@ export function Beep() {
|
|||||||
return window['go']['ui']['UI']['Beep']();
|
return window['go']['ui']['UI']['Beep']();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EnterText(arg1, arg2, arg3) {
|
|
||||||
return window['go']['ui']['UI']['EnterText'](arg1, arg2, arg3);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function OpenDirectory(arg1) {
|
export function OpenDirectory(arg1) {
|
||||||
return window['go']['ui']['UI']['OpenDirectory'](arg1);
|
return window['go']['ui']['UI']['OpenDirectory'](arg1);
|
||||||
}
|
}
|
||||||
|
8
go.mod
8
go.mod
@ -7,7 +7,7 @@ require github.com/wailsapp/wails/v2 v2.3.1
|
|||||||
require (
|
require (
|
||||||
github.com/gen2brain/beeep v0.0.0-20220909211152-5a9ec94374f6
|
github.com/gen2brain/beeep v0.0.0-20220909211152-5a9ec94374f6
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
go.mongodb.org/mongo-driver v1.11.6
|
go.mongodb.org/mongo-driver v1.11.7
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -35,7 +35,7 @@ require (
|
|||||||
github.com/mattn/go-colorable v0.1.11 // indirect
|
github.com/mattn/go-colorable v0.1.11 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||||
github.com/ncruces/zenity v0.10.8
|
github.com/ncruces/zenity v0.10.9
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect
|
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
@ -53,8 +53,8 @@ require (
|
|||||||
golang.org/x/crypto v0.1.0 // indirect
|
golang.org/x/crypto v0.1.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
golang.org/x/net v0.7.0 // indirect
|
||||||
golang.org/x/sync v0.1.0
|
golang.org/x/sync v0.3.0
|
||||||
golang.org/x/sys v0.7.0 // indirect
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
15
go.sum
15
go.sum
@ -54,8 +54,8 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
|
|||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
github.com/ncruces/zenity v0.10.8 h1:vNwTJazgxj47VOBXqik14fN8ML5eLMhl/Nw646Ln/7o=
|
github.com/ncruces/zenity v0.10.9 h1:TYdNwEj9HiDDcpdsIUecBMsQw7L80Aiu/IJMM0Tao1E=
|
||||||
github.com/ncruces/zenity v0.10.8/go.mod h1:gPHbGF/lkwfJS3wdVG9sXYgK8c0vLAXkA2xHacOiFZY=
|
github.com/ncruces/zenity v0.10.9/go.mod h1:FzjqP1loicusCFJTdIt5Oqbmoj2zySHpM0RsgJeeCbk=
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||||
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc=
|
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc=
|
||||||
@ -98,8 +98,8 @@ github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgk
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.mongodb.org/mongo-driver v1.11.6 h1:XM7G6PjiGAO5betLF13BIa5TlLUUE3uJ/2Ox3Lz1K+o=
|
go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs=
|
||||||
go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
|
go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
@ -123,8 +123,9 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -139,8 +140,8 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/garraflavatra/rolens/internal/ui"
|
"github.com/garraflavatra/rolens/internal/ui"
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
@ -21,6 +22,7 @@ type EnvironmentInfo struct {
|
|||||||
Arch string `json:"arch"`
|
Arch string `json:"arch"`
|
||||||
BuildType string `json:"buildType"`
|
BuildType string `json:"buildType"`
|
||||||
Platform string `json:"platform"`
|
Platform string `json:"platform"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
|
||||||
HasMongoExport bool `json:"hasMongoExport"`
|
HasMongoExport bool `json:"hasMongoExport"`
|
||||||
HasMongoDump bool `json:"hasMongoDump"`
|
HasMongoDump bool `json:"hasMongoDump"`
|
||||||
@ -38,8 +40,9 @@ type App struct {
|
|||||||
ui *ui.UI
|
ui *ui.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp() *App {
|
func NewApp(version string) *App {
|
||||||
a := &App{}
|
a := &App{}
|
||||||
|
a.Env.Version = strings.TrimSpace(version)
|
||||||
|
|
||||||
_, err := exec.LookPath("mongodump")
|
_, err := exec.LookPath("mongodump")
|
||||||
a.Env.HasMongoDump = err == nil
|
a.Env.HasMongoDump = err == nil
|
||||||
|
@ -36,8 +36,6 @@ func (a *App) Menu() *menu.Menu {
|
|||||||
|
|
||||||
fileMenu := appMenu.AddSubmenu("File")
|
fileMenu := appMenu.AddSubmenu("File")
|
||||||
fileMenu.AddText("New host…", keys.CmdOrCtrl("y"), menuCallbackEmit(a, "CreateHost"))
|
fileMenu.AddText("New host…", keys.CmdOrCtrl("y"), menuCallbackEmit(a, "CreateHost"))
|
||||||
fileMenu.AddText("New database", keys.CmdOrCtrl("y"), menuCallbackEmit(a, "CreateDatabase"))
|
|
||||||
fileMenu.AddText("New collection…", keys.CmdOrCtrl("i"), menuCallbackEmit(a, "CreateCollection"))
|
|
||||||
fileMenu.AddSeparator()
|
fileMenu.AddSeparator()
|
||||||
fileMenu.AddText("Stats", keys.Combo("h", keys.CmdOrCtrlKey, keys.OptionOrAltKey), menuCallbackEmit(a, "OpenCollectionTab", "stats"))
|
fileMenu.AddText("Stats", keys.Combo("h", keys.CmdOrCtrlKey, keys.OptionOrAltKey), menuCallbackEmit(a, "OpenCollectionTab", "stats"))
|
||||||
fileMenu.AddText("Find", keys.Combo("f", keys.CmdOrCtrlKey, keys.OptionOrAltKey), menuCallbackEmit(a, "OpenCollectionTab", "find"))
|
fileMenu.AddText("Find", keys.Combo("f", keys.CmdOrCtrlKey, keys.OptionOrAltKey), menuCallbackEmit(a, "OpenCollectionTab", "find"))
|
||||||
|
@ -8,23 +8,27 @@ import (
|
|||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) OpenCollection(hostKey, dbKey, collKey string) (result bson.M) {
|
type OpenCollectionResult struct {
|
||||||
|
Stats bson.M `json:"stats"`
|
||||||
|
StatsError string `json:"statsError"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) OpenCollection(hostKey, dbKey, collKey string) (result OpenCollectionResult) {
|
||||||
client, ctx, close, err := a.connectToHost(hostKey)
|
client, ctx, close, err := a.connectToHost(hostKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
defer close()
|
||||||
|
|
||||||
command := bson.M{"collStats": collKey}
|
command := bson.M{"collStats": collKey}
|
||||||
err = client.Database(dbKey).RunCommand(ctx, command).Decode(&result)
|
err = client.Database(dbKey).RunCommand(ctx, command).Decode(&result.Stats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarning(a.ctx, "Could not retrieve collection stats for "+collKey)
|
runtime.LogWarning(a.ctx, "Could not retrieve collection stats for "+collKey)
|
||||||
runtime.LogWarning(a.ctx, err.Error())
|
runtime.LogWarning(a.ctx, err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Could not get stats"), zenity.ErrorIcon)
|
result.StatsError = err.Error()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer close()
|
return
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) RenameCollection(hostKey, dbKey, collKey, newCollKey string) bool {
|
func (a *App) RenameCollection(hostKey, dbKey, collKey, newCollKey string) bool {
|
||||||
@ -32,6 +36,7 @@ func (a *App) RenameCollection(hostKey, dbKey, collKey, newCollKey string) bool
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
defer close()
|
||||||
|
|
||||||
var result bson.M
|
var result bson.M
|
||||||
command := bson.D{
|
command := bson.D{
|
||||||
@ -46,7 +51,6 @@ func (a *App) RenameCollection(hostKey, dbKey, collKey, newCollKey string) bool
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
defer close()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +64,7 @@ func (a *App) TruncateCollection(hostKey, dbKey, collKey string) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
defer close()
|
||||||
|
|
||||||
_, err = client.Database(dbKey).Collection(collKey).DeleteMany(ctx, bson.D{})
|
_, err = client.Database(dbKey).Collection(collKey).DeleteMany(ctx, bson.D{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -69,7 +74,6 @@ func (a *App) TruncateCollection(hostKey, dbKey, collKey string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
defer close()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +87,7 @@ func (a *App) DropCollection(hostKey, dbKey, collKey string) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
defer close()
|
||||||
|
|
||||||
err = client.Database(dbKey).Collection(collKey).Drop(ctx)
|
err = client.Database(dbKey).Collection(collKey).Drop(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -92,6 +97,5 @@ func (a *App) DropCollection(hostKey, dbKey, collKey string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
defer close()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,14 @@ type Query struct {
|
|||||||
Sort string `json:"sort"`
|
Sort string `json:"sort"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryResult struct {
|
type FindItemsResult struct {
|
||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
Results []string `json:"results"`
|
Results []string `json:"results"`
|
||||||
|
ErrorTitle string `json:"errorTitle"`
|
||||||
|
ErrorDescription string `json:"errorDescription"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) QueryResult {
|
func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) (result FindItemsResult) {
|
||||||
var out QueryResult
|
|
||||||
var form Query
|
var form Query
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(formJson), &form)
|
err := json.Unmarshal([]byte(formJson), &form)
|
||||||
@ -31,15 +32,15 @@ func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) QueryResult {
|
|||||||
runtime.LogError(a.ctx, "Could not parse find form:")
|
runtime.LogError(a.ctx, "Could not parse find form:")
|
||||||
runtime.LogError(a.ctx, err.Error())
|
runtime.LogError(a.ctx, err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Could not parse form"), zenity.ErrorIcon)
|
zenity.Error(err.Error(), zenity.Title("Could not parse form"), zenity.ErrorIcon)
|
||||||
return out
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client, ctx, close, err := a.connectToHost(hostKey)
|
client, ctx, close, err := a.connectToHost(hostKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
var query bson.M
|
var query bson.M
|
||||||
var projection bson.M
|
var projection bson.M
|
||||||
var sort bson.M
|
var sort bson.M
|
||||||
@ -47,22 +48,25 @@ func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) QueryResult {
|
|||||||
err = bson.UnmarshalExtJSON([]byte(form.Query), true, &query)
|
err = bson.UnmarshalExtJSON([]byte(form.Query), true, &query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogInfof(a.ctx, "Invalid find query: %s", err.Error())
|
runtime.LogInfof(a.ctx, "Invalid find query: %s", err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Invalid query"), zenity.ErrorIcon)
|
result.ErrorTitle = "Invalid query"
|
||||||
return out
|
result.ErrorDescription = err.Error()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(form.Fields), &projection)
|
err = json.Unmarshal([]byte(form.Fields), &projection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogInfof(a.ctx, "Invalid find projection: %s", err.Error())
|
runtime.LogInfof(a.ctx, "Invalid find projection: %s", err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Invalid projection"), zenity.ErrorIcon)
|
result.ErrorTitle = "Invalid projection"
|
||||||
return out
|
result.ErrorDescription = err.Error()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(form.Sort), &sort)
|
err = json.Unmarshal([]byte(form.Sort), &sort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogInfof(a.ctx, "Invalid find sort: %s", err.Error())
|
runtime.LogInfof(a.ctx, "Invalid find sort: %s", err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Invalid sort"), zenity.ErrorIcon)
|
result.ErrorTitle = "Invalid sort"
|
||||||
return out
|
result.ErrorDescription = err.Error()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
opt := mongoOptions.FindOptions{
|
opt := mongoOptions.FindOptions{
|
||||||
@ -75,15 +79,17 @@ func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) QueryResult {
|
|||||||
total, err := client.Database(dbKey).Collection(collKey).CountDocuments(ctx, query, nil)
|
total, err := client.Database(dbKey).Collection(collKey).CountDocuments(ctx, query, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarningf(a.ctx, "Encountered an error while counting documents: %s", err.Error())
|
runtime.LogWarningf(a.ctx, "Encountered an error while counting documents: %s", err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while counting docs"), zenity.ErrorIcon)
|
result.ErrorTitle = "Error while counting documents"
|
||||||
return out
|
result.ErrorDescription = err.Error()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cur, err := client.Database(dbKey).Collection(collKey).Find(ctx, query, &opt)
|
cur, err := client.Database(dbKey).Collection(collKey).Find(ctx, query, &opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarningf(a.ctx, "Encountered an error while performing query: %s", err.Error())
|
runtime.LogWarningf(a.ctx, "Encountered an error while performing query: %s", err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while querying"), zenity.ErrorIcon)
|
result.ErrorTitle = "Error while querying"
|
||||||
return out
|
result.ErrorDescription = err.Error()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer cur.Close(ctx)
|
defer cur.Close(ctx)
|
||||||
@ -92,25 +98,27 @@ func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) QueryResult {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarningf(a.ctx, "Encountered an error while performing query: %s", err.Error())
|
runtime.LogWarningf(a.ctx, "Encountered an error while performing query: %s", err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while querying"), zenity.ErrorIcon)
|
result.ErrorTitle = "Error while querying"
|
||||||
return out
|
result.ErrorDescription = err.Error()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Total = total
|
result.Total = total
|
||||||
out.Results = make([]string, 0)
|
result.Results = make([]string, 0)
|
||||||
|
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
marshalled, err := bson.MarshalExtJSON(r, true, true)
|
marshalled, err := bson.MarshalExtJSON(r, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogError(a.ctx, "Failed to marshal find BSON:")
|
runtime.LogError(a.ctx, "Failed to marshal find BSON:")
|
||||||
runtime.LogError(a.ctx, err.Error())
|
runtime.LogError(a.ctx, err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Failed to marshal JSON"), zenity.ErrorIcon)
|
result.ErrorTitle = "Failed to marshal JSON"
|
||||||
return out
|
result.ErrorDescription = err.Error()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
out.Results = append(out.Results, string(marshalled))
|
result.Results = append(result.Results, string(marshalled))
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UpdateFoundDocument(hostKey, dbKey, collKey, idJson, newDocJson string) bool {
|
func (a *App) UpdateFoundDocument(hostKey, dbKey, collKey, idJson, newDocJson string) bool {
|
||||||
@ -134,9 +142,9 @@ func (a *App) UpdateFoundDocument(hostKey, dbKey, collKey, idJson, newDocJson st
|
|||||||
}
|
}
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
if _, err := client.Database(dbKey).Collection(collKey).UpdateOne(ctx, id, bson.M{"$set": newDoc}); err != nil {
|
if _, err := client.Database(dbKey).Collection(collKey).ReplaceOne(ctx, id, newDoc); err != nil {
|
||||||
runtime.LogInfof(a.ctx, "Error while performing find/update: %s", err.Error())
|
runtime.LogInfof(a.ctx, "Error while replacing document: %s", err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Unable to perform update"), zenity.ErrorIcon)
|
zenity.Error(err.Error(), zenity.Title("Unable to replace document"), zenity.ErrorIcon)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
csvItem = append(csvItem, string(v.(string)))
|
csvItem = append(csvItem, fmt.Sprintf("%v", v))
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -11,10 +11,15 @@ import (
|
|||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) GetIndexes(hostKey, dbKey, collKey string) []bson.M {
|
type GetIndexesResult struct {
|
||||||
|
Indexes []bson.M `json:"indexes"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetIndexes(hostKey, dbKey, collKey string) (result GetIndexesResult) {
|
||||||
client, ctx, close, err := a.connectToHost(hostKey)
|
client, ctx, close, err := a.connectToHost(hostKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
@ -22,20 +27,18 @@ func (a *App) GetIndexes(hostKey, dbKey, collKey string) []bson.M {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarning(a.ctx, "Encountered an error while creating index cursor:")
|
runtime.LogWarning(a.ctx, "Encountered an error while creating index cursor:")
|
||||||
runtime.LogWarning(a.ctx, err.Error())
|
runtime.LogWarning(a.ctx, err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while creating cursor"), zenity.ErrorIcon)
|
result.Error = err.Error()
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []bson.M
|
err = cur.All(ctx, &result.Indexes)
|
||||||
err = cur.All(ctx, &results)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarning(a.ctx, "Encountered an error while executing index cursor:")
|
runtime.LogWarning(a.ctx, "Encountered an error while executing index cursor:")
|
||||||
runtime.LogWarning(a.ctx, err.Error())
|
runtime.LogWarning(a.ctx, err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while running cursor"), zenity.ErrorIcon)
|
result.Error = err.Error()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) CreateIndex(hostKey, dbKey, collKey, jsonData string) string {
|
func (a *App) CreateIndex(hostKey, dbKey, collKey, jsonData string) string {
|
||||||
|
@ -28,8 +28,8 @@ func (a *App) InsertItems(hostKey, dbKey, collKey, jsonData string) interface{}
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
res, err := client.Database(dbKey).Collection(collKey).InsertMany(ctx, data)
|
res, err := client.Database(dbKey).Collection(collKey).InsertMany(ctx, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarning(a.ctx, "Encountered an error while performing insert:")
|
runtime.LogWarning(a.ctx, "Encountered an error while performing insert:")
|
||||||
|
@ -12,16 +12,25 @@ import (
|
|||||||
mongoOptions "go.mongodb.org/mongo-driver/mongo/options"
|
mongoOptions "go.mongodb.org/mongo-driver/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OpenConnectionResult struct {
|
||||||
|
Databases []string `json:"databases"`
|
||||||
|
Status bson.M `json:"status"`
|
||||||
|
StatusError string `json:"statusError"`
|
||||||
|
SystemInfo bson.M `json:"systemInfo"`
|
||||||
|
SystemInfoError string `json:"systemInfoError"`
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) connectToHost(hostKey string) (*mongo.Client, context.Context, func(), error) {
|
func (a *App) connectToHost(hostKey string) (*mongo.Client, context.Context, func(), error) {
|
||||||
hosts, err := a.Hosts()
|
hosts, err := a.Hosts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
runtime.LogInfof(a.ctx, "Error while getting hosts: %s", err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while getting hosts"), zenity.ErrorIcon)
|
zenity.Error(err.Error(), zenity.Title("Error while getting hosts"), zenity.ErrorIcon)
|
||||||
return nil, nil, nil, errors.New("could not retrieve hosts")
|
return nil, nil, nil, errors.New("could not retrieve hosts")
|
||||||
}
|
}
|
||||||
|
|
||||||
h := hosts[hostKey]
|
h := hosts[hostKey]
|
||||||
if len(h.URI) == 0 {
|
if len(h.URI) == 0 {
|
||||||
runtime.LogInfo(a.ctx, "Invalid URI (len == 0) for host "+hostKey)
|
runtime.LogInfof(a.ctx, "Invalid URI (len == 0) for host %s", hostKey)
|
||||||
zenity.Warning("You haven't specified a valid uri for the selected host.", zenity.Title("Invalid query"), zenity.WarningIcon)
|
zenity.Warning("You haven't specified a valid uri for the selected host.", zenity.Title("Invalid query"), zenity.WarningIcon)
|
||||||
return nil, nil, nil, errors.New("invalid uri")
|
return nil, nil, nil, errors.New("invalid uri")
|
||||||
}
|
}
|
||||||
@ -29,8 +38,7 @@ func (a *App) connectToHost(hostKey string) (*mongo.Client, context.Context, fun
|
|||||||
client, err := mongo.NewClient(mongoOptions.Client().ApplyURI(h.URI))
|
client, err := mongo.NewClient(mongoOptions.Client().ApplyURI(h.URI))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarning(a.ctx, "Could not connect to host "+hostKey)
|
runtime.LogWarningf(a.ctx, "Could not connect to host %s: %s", hostKey, err.Error())
|
||||||
runtime.LogWarning(a.ctx, err.Error())
|
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while connecting to "+h.Name), zenity.ErrorIcon)
|
zenity.Error(err.Error(), zenity.Title("Error while connecting to "+h.Name), zenity.ErrorIcon)
|
||||||
return nil, nil, nil, errors.New("could not establish a connection with " + h.Name)
|
return nil, nil, nil, errors.New("could not establish a connection with " + h.Name)
|
||||||
}
|
}
|
||||||
@ -43,18 +51,35 @@ func (a *App) connectToHost(hostKey string) (*mongo.Client, context.Context, fun
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) OpenConnection(hostKey string) (databases []string) {
|
func (a *App) OpenConnection(hostKey string) (result OpenConnectionResult) {
|
||||||
client, ctx, close, err := a.connectToHost(hostKey)
|
client, ctx, close, err := a.connectToHost(hostKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
databases, err = client.ListDatabaseNames(ctx, bson.M{})
|
defer close()
|
||||||
|
|
||||||
|
result.Databases, err = client.ListDatabaseNames(ctx, bson.M{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarning(a.ctx, "Could not retrieve database names for host "+hostKey)
|
runtime.LogWarning(a.ctx, "Could not retrieve database names for host "+hostKey)
|
||||||
runtime.LogWarning(a.ctx, err.Error())
|
runtime.LogWarning(a.ctx, err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while getting databases"), zenity.ErrorIcon)
|
zenity.Error(err.Error(), zenity.Title("Error while getting databases"), zenity.ErrorIcon)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
defer close()
|
|
||||||
return databases
|
command := bson.M{"serverStatus": 1}
|
||||||
|
err = client.Database("admin").RunCommand(ctx, command).Decode(&result.Status)
|
||||||
|
if err != nil {
|
||||||
|
runtime.LogWarning(a.ctx, "Could not retrieve server status")
|
||||||
|
runtime.LogWarning(a.ctx, err.Error())
|
||||||
|
result.StatusError = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
command = bson.M{"hostInfo": 1}
|
||||||
|
err = client.Database("admin").RunCommand(ctx, command).Decode(&result.SystemInfo)
|
||||||
|
if err != nil {
|
||||||
|
runtime.LogWarning(a.ctx, "Could not retrieve system info")
|
||||||
|
runtime.LogWarning(a.ctx, err.Error())
|
||||||
|
result.SystemInfoError = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
@ -6,22 +6,35 @@ import (
|
|||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) OpenDatabase(hostKey, dbKey string) (collections []string) {
|
type OpenDatabaseResult struct {
|
||||||
|
Collections []string `json:"collections"`
|
||||||
|
Stats bson.M `json:"stats"`
|
||||||
|
StatsError string `json:"statsError"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) OpenDatabase(hostKey, dbKey string) (result OpenDatabaseResult) {
|
||||||
client, ctx, close, err := a.connectToHost(hostKey)
|
client, ctx, close, err := a.connectToHost(hostKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return
|
||||||
|
}
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
command := bson.M{"dbStats": 1}
|
||||||
|
err = client.Database(dbKey).RunCommand(ctx, command).Decode(&result.Stats)
|
||||||
|
if err != nil {
|
||||||
|
runtime.LogWarning(a.ctx, "Could not retrieve database stats for "+dbKey)
|
||||||
|
runtime.LogWarning(a.ctx, err.Error())
|
||||||
|
result.StatsError = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
collections, err = client.Database(dbKey).ListCollectionNames(ctx, bson.D{})
|
result.Collections, err = client.Database(dbKey).ListCollectionNames(ctx, bson.D{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarning(a.ctx, "Could not retrieve collection list for db "+dbKey)
|
runtime.LogWarning(a.ctx, "Could not retrieve collection list for db "+dbKey)
|
||||||
runtime.LogWarning(a.ctx, err.Error())
|
runtime.LogWarning(a.ctx, err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while getting collections"), zenity.ErrorIcon)
|
zenity.Error(err.Error(), zenity.Title("Error while getting collections"), zenity.ErrorIcon)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer close()
|
return
|
||||||
return collections
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) DropDatabase(hostKey, dbKey string) bool {
|
func (a *App) DropDatabase(hostKey, dbKey string) bool {
|
||||||
@ -34,6 +47,7 @@ func (a *App) DropDatabase(hostKey, dbKey string) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
defer close()
|
||||||
|
|
||||||
err = client.Database(dbKey).Drop(ctx)
|
err = client.Database(dbKey).Drop(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -43,6 +57,5 @@ func (a *App) DropDatabase(hostKey, dbKey string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
defer close()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -15,16 +15,3 @@ func (u *UI) OpenDirectory(title string) string {
|
|||||||
|
|
||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) EnterText(title, info, defaultEntry string) string {
|
|
||||||
input, err := zenity.Entry(info, zenity.Title(title), zenity.EntryText(defaultEntry), zenity.Modal())
|
|
||||||
|
|
||||||
if err == zenity.ErrCanceled {
|
|
||||||
return ""
|
|
||||||
} else if err != nil {
|
|
||||||
zenity.Error(err.Error(), zenity.Title("Encountered an error!"), zenity.ErrorIcon)
|
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
12
main.go
12
main.go
@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/garraflavatra/rolens/internal"
|
"github.com/garraflavatra/rolens/internal"
|
||||||
"github.com/garraflavatra/rolens/internal/app"
|
"github.com/garraflavatra/rolens/internal/app"
|
||||||
uictrl "github.com/garraflavatra/rolens/internal/ui"
|
uictrl "github.com/garraflavatra/rolens/internal/ui"
|
||||||
"github.com/ncruces/zenity"
|
|
||||||
"github.com/wailsapp/wails/v2"
|
"github.com/wailsapp/wails/v2"
|
||||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
@ -22,10 +21,13 @@ var (
|
|||||||
|
|
||||||
//go:embed build/appicon.png
|
//go:embed build/appicon.png
|
||||||
appIcon []byte
|
appIcon []byte
|
||||||
|
|
||||||
|
//go:embed build/version.txt
|
||||||
|
version string
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := app.NewApp()
|
app := app.NewApp(version)
|
||||||
ui := uictrl.New()
|
ui := uictrl.New()
|
||||||
|
|
||||||
err := wails.Run(&options.App{
|
err := wails.Run(&options.App{
|
||||||
@ -45,7 +47,11 @@ func main() {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if p := recover(); p != nil {
|
if p := recover(); p != nil {
|
||||||
runtime.LogFatalf(ctx, "Application panicked: %v", p)
|
runtime.LogFatalf(ctx, "Application panicked: %v", p)
|
||||||
zenity.Error("A fatal error occured.")
|
runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
||||||
|
Type: runtime.ErrorDialog,
|
||||||
|
Title: "A fatal error occured!",
|
||||||
|
Message: "Please try to restart the application, or consult the logs for more details.",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -13,8 +13,11 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"productName": "Rolens",
|
"productName": "Rolens",
|
||||||
"companyName": "Romein van Buren",
|
"companyName": "Romein van Buren",
|
||||||
"productVersion": "0.2.0",
|
"productVersion": "0.2.1",
|
||||||
"comments": "The intuitive MongoDB administration tool",
|
"comments": "The intuitive MongoDB administration tool",
|
||||||
"copyright": "© Romein van Buren 2023 (GNU GPL 3.0)."
|
"copyright": "© Romein van Buren 2023 (GNU GPL 3.0)."
|
||||||
|
},
|
||||||
|
"preBuildHooks": {
|
||||||
|
"*/*": "node ../version_to_file.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ const { default: fetch } = require("node-fetch")
|
|||||||
|
|
||||||
module.exports = async function() {
|
module.exports = async function() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('https://api.github.com/repos/garraflavatra/alphabets');
|
const res = await fetch('https://api.github.com/repos/garraflavatra/rolens');
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
return { stars: json.stargazers_count || 0 };
|
return { stars: json.stargazers_count || 0 };
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
<svg class="icon" viewBox="0 0 16 16"><path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/></svg>
|
<svg class="icon" viewBox="0 0 16 16"><path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/></svg>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{% if github.stars %}
|
<span class="count">{{ stars }}</span>
|
||||||
<span class="count">{{ github.stars }}</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
Star on GitHub!
|
Star on GitHub!
|
||||||
</a>
|
</a>
|
||||||
|
@ -56,8 +56,8 @@ navigationOptions:
|
|||||||
on GitHub.
|
on GitHub.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{% render "githubstars" %}
|
{% render "githubstars", stars: github.stars %}
|
||||||
{% render "mastodon" %}
|
{% # render "mastodon" %}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
© <a href="mailto:romein@vburen.nl">Romein van Buren</a> 2023.
|
© <a href="mailto:romein@vburen.nl">Romein van Buren</a> 2023.
|
||||||
|
Reference in New Issue
Block a user