mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
45 Commits
v0.7.25-be
...
v0.7.28-be
Author | SHA1 | Date | |
---|---|---|---|
97279742e9 | |||
6cb21ff792 | |||
72bacdeabf | |||
c434855879 | |||
ba0ffcc6f5 | |||
8e7965a0f9 | |||
4aab84fbd5 | |||
|
74e97e660d | ||
|
f5d236b521 | ||
|
e794d207ad | ||
f99a3cc054 | |||
59f7d3c670 | |||
8f2daa0f1c | |||
141d5bb69c | |||
171b6f924a | |||
f7419d8e9c | |||
5b1cd70e25 | |||
3e223b475e | |||
4a38656b7e | |||
25b7ae57c6 | |||
4b5718e9b7 | |||
780d83deaa | |||
e952f9f5f8 | |||
49f1a8ef2e | |||
121aa21a6d | |||
cb25963a67 | |||
3a47607a5f | |||
7494ff6fcf | |||
838491bfd4 | |||
0b9898f3e7 | |||
a973ec3c60 | |||
d0c50f17ca | |||
b4cdd58973 | |||
9947479fdc | |||
4a1697d633 | |||
b7dfd5cb8c | |||
0ec9d3cfc1 | |||
|
4f615b26cf | ||
|
86a1e05197 | ||
3fa9873d20 | |||
37a160a03f | |||
|
243984e697 | ||
bd4502ee47 | |||
|
93de974b09 | ||
|
949bf4cbcb |
@@ -275,6 +275,24 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "penguinlab",
|
||||||
|
"name": "Naoki Ishikawa",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/10959317?v=4",
|
||||||
|
"profile": "https://github.com/penguinlab",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "mangas",
|
||||||
|
"name": "Filipe Azevedo",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/1640325?v=4",
|
||||||
|
"profile": "https://fazevedo.dev",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
8
.github/workflows/build-beta.yml
vendored
8
.github/workflows/build-beta.yml
vendored
@@ -19,17 +19,19 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: beta
|
ref: beta
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm i
|
run: |
|
||||||
|
npm i
|
||||||
|
npm install "dmg-license" --save-optional
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -25,17 +25,19 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
|
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm i
|
run: |
|
||||||
|
npm i
|
||||||
|
npm install "dmg-license" --save-optional
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# a pull request then we can checkout the head.
|
||||||
|
@@ -8,12 +8,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
run: npm run build -- --arm64 --linux deb AppImage
|
run: npm run build -- --arm64 --linux deb AppImage
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: linux-build
|
name: linux-build
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
6
.github/workflows/create-artifact-linux.yml
vendored
6
.github/workflows/create-artifact-linux.yml
vendored
@@ -8,10 +8,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: linux-build
|
name: linux-build
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
12
.github/workflows/create-artifact-macos.yml
vendored
12
.github/workflows/create-artifact-macos.yml
vendored
@@ -8,20 +8,21 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: npm install & build
|
- name: npm install & build
|
||||||
run: |
|
run: |
|
||||||
npm install
|
npm install
|
||||||
|
npm install "dmg-license" --save-optional
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos-build
|
name: macos-build
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
@@ -38,17 +39,18 @@ jobs:
|
|||||||
ref: beta
|
ref: beta
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: npm install & build
|
- name: npm install & build
|
||||||
run: |
|
run: |
|
||||||
npm install
|
npm install
|
||||||
|
npm install "dmg-license" --save-optional
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos-build-beta
|
name: macos-build-beta
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
@@ -11,7 +11,7 @@ jobs:
|
|||||||
- name: Install Python
|
- name: Install Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.8'
|
python-version: '3.11'
|
||||||
|
|
||||||
- name: Install pipx
|
- name: Install pipx
|
||||||
uses: CfirTsabari/actions-pipx@v1
|
uses: CfirTsabari/actions-pipx@v1
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
# - name: Delete old package-lock.json
|
# - name: Delete old package-lock.json
|
||||||
# run: rm package-lock.json
|
# run: rm package-lock.json
|
||||||
|
4
.github/workflows/test-builds.yml
vendored
4
.github/workflows/test-builds.yml
vendored
@@ -17,12 +17,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: develop
|
ref: develop
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
|
11
.github/workflows/test-e2e-win.yml
vendored
11
.github/workflows/test-e2e-win.yml
vendored
@@ -1,9 +1,10 @@
|
|||||||
name: Test end-to-end
|
name: Test end-to-end
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch: {}
|
||||||
branches:
|
# push:
|
||||||
- develop
|
# branches:
|
||||||
|
# - develop
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -15,10 +16,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
|
77
CHANGELOG.md
77
CHANGELOG.md
@@ -2,6 +2,83 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
### [0.7.28-beta.0](https://github.com/antares-sql/antares/compare/v0.7.27...v0.7.28-beta.0) (2024-08-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add missing Dutch strings ([e794d20](https://github.com/antares-sql/antares/commit/e794d207ad75e0f5380f57df579912893e5edb6a))
|
||||||
|
* **translation:** Add more faker translations ([74e97e6](https://github.com/antares-sql/antares/commit/74e97e660df089ed8273565942118e112f6b3220))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* disabled column sort during loadings ([72bacde](https://github.com/antares-sql/antares/commit/72bacdeabf833880482a839c4735505573d33bdc))
|
||||||
|
* html tags searching in history or saved queries, fixes [#847](https://github.com/antares-sql/antares/issues/847) ([6cb21ff](https://github.com/antares-sql/antares/commit/6cb21ff7926c74469b421c47b434612b3894b4c2))
|
||||||
|
* **PostgreSQL:** issue exporting tables with primary keys ([c434855](https://github.com/antares-sql/antares/commit/c434855879de16f83e17784e38e931decdd94873))
|
||||||
|
* **PostgreSQL:** wrong export formato of JSON fields ([8e7965a](https://github.com/antares-sql/antares/commit/8e7965a0f94a17ed73d5c8913f66e4e9cf0b11c7))
|
||||||
|
* **translation:** Spelling error ([f5d236b](https://github.com/antares-sql/antares/commit/f5d236b521a3534754de0b1031513f8eb83b3cc0))
|
||||||
|
* wrong password message importing app data ([ba0ffcc](https://github.com/antares-sql/antares/commit/ba0ffcc6f56c5506c1768c05d43bb07f7b090a68))
|
||||||
|
|
||||||
|
### [0.7.27](https://github.com/antares-sql/antares/compare/v0.7.26...v0.7.27) (2024-07-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* issue with new console and languages different than english, fixes [#837](https://github.com/antares-sql/antares/issues/837) ([59f7d3c](https://github.com/antares-sql/antares/commit/59f7d3c67083ac7e32bd29c9b7e6e044f2060c2f))
|
||||||
|
|
||||||
|
### [0.7.26](https://github.com/antares-sql/antares/compare/v0.7.26-beta.1...v0.7.26) (2024-07-15)
|
||||||
|
|
||||||
|
### [0.7.26-beta.1](https://github.com/antares-sql/antares/compare/v0.7.26-beta.0...v0.7.26-beta.1) (2024-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* custom SVG icons for connections, closes [#663](https://github.com/antares-sql/antares/issues/663) ([171b6f9](https://github.com/antares-sql/antares/commit/171b6f924acc7d7696f4f850a704af0baf616b87))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* table name in column list on export as SQL features, fixes [#822](https://github.com/antares-sql/antares/issues/822) ([f7419d8](https://github.com/antares-sql/antares/commit/f7419d8e9c4fe8ea80dbf9b2612ff44a66f50365))
|
||||||
|
|
||||||
|
### [0.7.26-beta.0](https://github.com/antares-sql/antares/compare/v0.7.25...v0.7.26-beta.0) (2024-07-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* in-app debug console, closes [#824](https://github.com/antares-sql/antares/issues/824) ([3e223b4](https://github.com/antares-sql/antares/commit/3e223b475ea57b24a6782feeabecad9c5596e271))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **PostgreSQL:** issue with ssl enabled and connection strings, fixes [#786](https://github.com/antares-sql/antares/issues/786) ([4a38656](https://github.com/antares-sql/antares/commit/4a38656b7e1094d3a9df28ce263c272f2014adb7))
|
||||||
|
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
* **UI:** improved all connections modal, closes [#761](https://github.com/antares-sql/antares/issues/761) ([25b7ae5](https://github.com/antares-sql/antares/commit/25b7ae57c6a4be9e825dddc7a52a49b67e03771b))
|
||||||
|
|
||||||
|
### [0.7.25](https://github.com/antares-sql/antares/compare/v0.7.25-beta.2...v0.7.25) (2024-06-19)
|
||||||
|
|
||||||
|
### [0.7.25-beta.2](https://github.com/antares-sql/antares/compare/v0.7.25-beta.1...v0.7.25-beta.2) (2024-06-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **PostgreSQL:** support to materialized views, closes [#804](https://github.com/antares-sql/antares/issues/804) ([0b9898f](https://github.com/antares-sql/antares/commit/0b9898f3e714d2cb24d100f55dd3858a644de162))
|
||||||
|
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
* **UI:** views grouped in folders ([a973ec3](https://github.com/antares-sql/antares/commit/a973ec3c60398cb16685a4f991c43ec4ee74c986))
|
||||||
|
|
||||||
|
### [0.7.25-beta.1](https://github.com/antares-sql/antares/compare/v0.7.25-beta.0...v0.7.25-beta.1) (2024-06-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* issue switching table after using a filter, fixes[#691](https://github.com/antares-sql/antares/issues/691) ([4a1697d](https://github.com/antares-sql/antares/commit/4a1697d63351b9990efff5804b95d92ac2fc9783))
|
||||||
|
|
||||||
### [0.7.25-beta.0](https://github.com/antares-sql/antares/compare/v0.7.24...v0.7.25-beta.0) (2024-05-26)
|
### [0.7.25-beta.0](https://github.com/antares-sql/antares/compare/v0.7.24...v0.7.25-beta.0) (2024-05-26)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -150,6 +150,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bagusindrayana"><img src="https://avatars.githubusercontent.com/u/36830534?v=4?s=100" width="100px;" alt="Bagus Indrayana"/><br /><sub><b>Bagus Indrayana</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=bagusindrayana" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bagusindrayana"><img src="https://avatars.githubusercontent.com/u/36830534?v=4?s=100" width="100px;" alt="Bagus Indrayana"/><br /><sub><b>Bagus Indrayana</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=bagusindrayana" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/penguinlab"><img src="https://avatars.githubusercontent.com/u/10959317?v=4?s=100" width="100px;" alt="Naoki Ishikawa"/><br /><sub><b>Naoki Ishikawa</b></sub></a><br /><a href="#translation-penguinlab" title="Translation">🌍</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://fazevedo.dev"><img src="https://avatars.githubusercontent.com/u/1640325?v=4?s=100" width="100px;" alt="Filipe Azevedo"/><br /><sub><b>Filipe Azevedo</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=mangas" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
19447
package-lock.json
generated
19447
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "antares",
|
"name": "antares",
|
||||||
"productName": "Antares",
|
"productName": "Antares",
|
||||||
"version": "0.7.25-beta.0",
|
"version": "0.7.28-beta.0",
|
||||||
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/antares-sql/antares.git",
|
"repository": "https://github.com/antares-sql/antares.git",
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "~2.0.1",
|
"@electron/remote": "~2.1.2",
|
||||||
"@fabio286/ssh2-promise": "~1.0.4-b",
|
"@fabio286/ssh2-promise": "~1.0.4-b",
|
||||||
"@faker-js/faker": "~6.1.2",
|
"@faker-js/faker": "~6.1.2",
|
||||||
"@jamescoyle/vue-icon": "~0.1.2",
|
"@jamescoyle/vue-icon": "~0.1.2",
|
||||||
@@ -127,10 +127,11 @@
|
|||||||
"@turf/helpers": "~6.5.0",
|
"@turf/helpers": "~6.5.0",
|
||||||
"@vue/compiler-sfc": "~3.2.33",
|
"@vue/compiler-sfc": "~3.2.33",
|
||||||
"@vueuse/core": "~10.4.1",
|
"@vueuse/core": "~10.4.1",
|
||||||
"ace-builds": "~1.24.1",
|
"ace-builds": "~1.34.1",
|
||||||
"babel-loader": "~8.2.3",
|
"babel-loader": "~8.2.3",
|
||||||
"better-sqlite3": "~9.4.1",
|
"better-sqlite3": "~10.0.0",
|
||||||
"chalk": "~4.1.2",
|
"chalk": "~4.1.2",
|
||||||
|
"cpu-features": "^0.0.10",
|
||||||
"cross-env": "~7.0.2",
|
"cross-env": "~7.0.2",
|
||||||
"css-loader": "~6.5.0",
|
"css-loader": "~6.5.0",
|
||||||
"electron-log": "~5.0.1",
|
"electron-log": "~5.0.1",
|
||||||
@@ -146,10 +147,10 @@
|
|||||||
"marked": "~12.0.0",
|
"marked": "~12.0.0",
|
||||||
"mini-css-extract-plugin": "~2.4.5",
|
"mini-css-extract-plugin": "~2.4.5",
|
||||||
"moment": "~2.30.1",
|
"moment": "~2.30.1",
|
||||||
"mysql2": "~3.9.1",
|
"mysql2": "~3.9.7",
|
||||||
"node-firebird": "~1.1.4",
|
"node-firebird": "~1.1.8",
|
||||||
"node-loader": "~2.0.0",
|
"node-loader": "~2.0.0",
|
||||||
"pg": "~8.11.3",
|
"pg": "~8.11.5",
|
||||||
"pg-connection-string": "~2.5.0",
|
"pg-connection-string": "~2.5.0",
|
||||||
"pg-query-stream": "~4.2.3",
|
"pg-query-stream": "~4.2.3",
|
||||||
"pgsql-ast-parser": "~7.2.1",
|
"pgsql-ast-parser": "~7.2.1",
|
||||||
@@ -169,11 +170,11 @@
|
|||||||
"typescript": "~4.6.3",
|
"typescript": "~4.6.3",
|
||||||
"unzip-crx-3": "~0.2.0",
|
"unzip-crx-3": "~0.2.0",
|
||||||
"v-mask": "~2.3.0",
|
"v-mask": "~2.3.0",
|
||||||
"vue": "~3.4.19",
|
"vue": "~3.4.27",
|
||||||
"vue-i18n": "~9.2.2",
|
"vue-i18n": "~9.13.1",
|
||||||
"vue-loader": "~16.8.3",
|
"vue-loader": "~16.8.3",
|
||||||
"vuedraggable": "~4.1.0",
|
"vuedraggable": "~4.1.0",
|
||||||
"webpack": "~5.72.0",
|
"webpack": "^5.91.0",
|
||||||
"webpack-cli": "~4.9.1"
|
"webpack-cli": "~4.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -192,8 +193,8 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "~5.18.0",
|
"@typescript-eslint/eslint-plugin": "~5.18.0",
|
||||||
"@typescript-eslint/parser": "~5.18.0",
|
"@typescript-eslint/parser": "~5.18.0",
|
||||||
"all-contributors-cli": "~6.20.0",
|
"all-contributors-cli": "~6.20.0",
|
||||||
"electron": "~26.6.9",
|
"electron": "~30.0.8",
|
||||||
"electron-builder": "~24.6.4",
|
"electron-builder": "~24.13.3",
|
||||||
"eslint": "~7.32.0",
|
"eslint": "~7.32.0",
|
||||||
"eslint-config-standard": "~16.0.3",
|
"eslint-config-standard": "~16.0.3",
|
||||||
"eslint-plugin-import": "~2.24.2",
|
"eslint-plugin-import": "~2.24.2",
|
||||||
|
@@ -31,6 +31,7 @@ export const customizations: Customizations = {
|
|||||||
schemas: true,
|
schemas: true,
|
||||||
tables: true,
|
tables: true,
|
||||||
views: true,
|
views: true,
|
||||||
|
materializedViews: true,
|
||||||
triggers: true,
|
triggers: true,
|
||||||
triggerFunctions: true,
|
triggerFunctions: true,
|
||||||
routines: true,
|
routines: true,
|
||||||
@@ -42,6 +43,7 @@ export const customizations: Customizations = {
|
|||||||
tableDuplicate: true,
|
tableDuplicate: true,
|
||||||
tableDdl: true,
|
tableDdl: true,
|
||||||
viewAdd: true,
|
viewAdd: true,
|
||||||
|
materializedViewAdd: true,
|
||||||
triggerAdd: true,
|
triggerAdd: true,
|
||||||
triggerFunctionAdd: true,
|
triggerFunctionAdd: true,
|
||||||
routineAdd: true,
|
routineAdd: true,
|
||||||
@@ -52,6 +54,7 @@ export const customizations: Customizations = {
|
|||||||
databaseEdit: false,
|
databaseEdit: false,
|
||||||
tableSettings: true,
|
tableSettings: true,
|
||||||
viewSettings: true,
|
viewSettings: true,
|
||||||
|
materializedViewSettings: true,
|
||||||
triggerSettings: true,
|
triggerSettings: true,
|
||||||
triggerFunctionSettings: true,
|
triggerFunctionSettings: true,
|
||||||
routineSettings: true,
|
routineSettings: true,
|
||||||
|
@@ -28,6 +28,7 @@ export interface Customizations {
|
|||||||
schemas?: boolean;
|
schemas?: boolean;
|
||||||
tables?: boolean;
|
tables?: boolean;
|
||||||
views?: boolean;
|
views?: boolean;
|
||||||
|
materializedViews?: boolean;
|
||||||
triggers?: boolean;
|
triggers?: boolean;
|
||||||
triggerFunctions?: boolean;
|
triggerFunctions?: boolean;
|
||||||
routines?: boolean;
|
routines?: boolean;
|
||||||
@@ -45,6 +46,8 @@ export interface Customizations {
|
|||||||
tableDdl?: boolean;
|
tableDdl?: boolean;
|
||||||
viewAdd?: boolean;
|
viewAdd?: boolean;
|
||||||
viewSettings?: boolean;
|
viewSettings?: boolean;
|
||||||
|
materializedViewAdd?: boolean;
|
||||||
|
materializedViewSettings?: boolean;
|
||||||
triggerAdd?: boolean;
|
triggerAdd?: boolean;
|
||||||
triggerFunctionAdd?: boolean;
|
triggerFunctionAdd?: boolean;
|
||||||
routineAdd?: boolean;
|
routineAdd?: boolean;
|
||||||
|
@@ -40,7 +40,7 @@ export const objectToGeoJSON = (val: any) => {
|
|||||||
export const escapeAndQuote = (val: string, client: ClientCode) => {
|
export const escapeAndQuote = (val: string, client: ClientCode) => {
|
||||||
const { stringsWrapper: sw } = customizations[client];
|
const { stringsWrapper: sw } = customizations[client];
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
|
const CHARS_TO_ESCAPE = sw === '"' ? /[\0\b\t\n\r\x1a"'\\]/g : /[\0\b\t\n\r\x1a'\\]/g;
|
||||||
const CHARS_ESCAPE_MAP: Record<string, string> = {
|
const CHARS_ESCAPE_MAP: Record<string, string> = {
|
||||||
'\0': '\\0',
|
'\0': '\\0',
|
||||||
'\b': '\\b',
|
'\b': '\\b',
|
||||||
@@ -48,10 +48,13 @@ export const escapeAndQuote = (val: string, client: ClientCode) => {
|
|||||||
'\n': '\\n',
|
'\n': '\\n',
|
||||||
'\r': '\\r',
|
'\r': '\\r',
|
||||||
'\x1a': '\\Z',
|
'\x1a': '\\Z',
|
||||||
'"': '\\"',
|
|
||||||
'\'': '\\\'',
|
'\'': '\\\'',
|
||||||
'\\': '\\\\'
|
'\\': '\\\\'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (sw === '"')
|
||||||
|
CHARS_ESCAPE_MAP['"'] = '\\"';
|
||||||
|
|
||||||
let chunkIndex = CHARS_TO_ESCAPE.lastIndex = 0;
|
let chunkIndex = CHARS_TO_ESCAPE.lastIndex = 0;
|
||||||
let escapedVal = '';
|
let escapedVal = '';
|
||||||
let match;
|
let match;
|
||||||
@@ -97,10 +100,19 @@ export const valueToSqlString = (args: {
|
|||||||
}
|
}
|
||||||
else if ('isArray' in field && field.isArray) {
|
else if ('isArray' in field && field.isArray) {
|
||||||
let localVal;
|
let localVal;
|
||||||
if (Array.isArray(val))
|
if (Array.isArray(val)) {
|
||||||
localVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
|
localVal = JSON
|
||||||
else
|
.stringify(val)
|
||||||
localVal = typeof val === 'string' ? val.replaceAll('[', '{').replaceAll(']', '}') : '';
|
.replaceAll('[', '{')
|
||||||
|
.replaceAll(']', '}');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
localVal = typeof val === 'string'
|
||||||
|
? val
|
||||||
|
.replaceAll('[', '{')
|
||||||
|
.replaceAll(']', '}')
|
||||||
|
: '';
|
||||||
|
}
|
||||||
parsedValue = `'${localVal}'`;
|
parsedValue = `'${localVal}'`;
|
||||||
}
|
}
|
||||||
else if (TEXT_SEARCH.includes(field.type))
|
else if (TEXT_SEARCH.includes(field.type))
|
||||||
@@ -163,7 +175,7 @@ export const jsonToSqlInsert = (args: {
|
|||||||
const sqlInsertAfter = options && options.sqlInsertAfter ? options.sqlInsertAfter : 1;
|
const sqlInsertAfter = options && options.sqlInsertAfter ? options.sqlInsertAfter : 1;
|
||||||
const sqlInsertDivider = options && options.sqlInsertDivider ? options.sqlInsertDivider : 'rows';
|
const sqlInsertDivider = options && options.sqlInsertDivider ? options.sqlInsertDivider : 'rows';
|
||||||
const { elementsWrapper: ew } = customizations[client];
|
const { elementsWrapper: ew } = customizations[client];
|
||||||
const fieldNames = Object.keys(json[0]).map(key => `${ew}${key}${ew}`);
|
const fieldNames = Object.keys(json[0]).map(key => `${ew}${key.split('.').pop()}${ew}`);
|
||||||
let insertStmt = `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
|
let insertStmt = `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
|
||||||
let insertsString = '';
|
let insertsString = '';
|
||||||
let queryLength = 0;
|
let queryLength = 0;
|
||||||
|
@@ -87,10 +87,10 @@ export default () => {
|
|||||||
shortCutRegister.unregister();
|
shortCutRegister.unregister();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('read-file', (event, filePath) => {
|
ipcMain.handle('read-file', (event, { filePath, encoding }) => {
|
||||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(filePath, 'utf-8');
|
const content = fs.readFileSync(filePath, encoding);
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
@@ -251,7 +251,7 @@ export default (connections: Record<string, antares.Client>) => {
|
|||||||
setTimeout(() => { // Ensures that writing thread has finished
|
setTimeout(() => { // Ensures that writing thread has finished
|
||||||
exporter?.terminate();
|
exporter?.terminate();
|
||||||
exporter = null;
|
exporter = null;
|
||||||
}, 2000);
|
}, 500);
|
||||||
resolve({ status: 'success', response: payload });
|
resolve({ status: 'success', response: payload });
|
||||||
break;
|
break;
|
||||||
case 'cancel':
|
case 'cancel':
|
||||||
|
@@ -51,4 +51,52 @@ export default (connections: Record<string, antares.Client>) => {
|
|||||||
return { status: 'error', response: err.toString() };
|
return { status: 'error', response: err.toString() };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-materialized-view-informations', async (event, params) => {
|
||||||
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await connections[params.uid].getMaterializedViewInformations(params);
|
||||||
|
return { status: 'success', response: result };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('drop-materialized-view', async (event, params) => {
|
||||||
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connections[params.uid].dropMaterializedView(params);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('alter-materialized-view', async (event, params) => {
|
||||||
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connections[params.uid].alterView(params);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('create-materialized-view', async (event, params) => {
|
||||||
|
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connections[params.uid].createMaterializedView(params);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -234,6 +234,18 @@ export abstract class BaseClient {
|
|||||||
throw new Error('Method "getVariables" not implemented');
|
throw new Error('Method "getVariables" not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMaterializedViewInformations (...args: any) {
|
||||||
|
throw new Error('Method "getMaterializedViewInformations" not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
dropMaterializedView (...args: any) {
|
||||||
|
throw new Error('Method "dropMaterializedView" not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
createMaterializedView (...args: any) {
|
||||||
|
throw new Error('Method "createMaterializedView" not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
getEventInformations (...args: any) {
|
getEventInformations (...args: any) {
|
||||||
throw new Error('Method "getEventInformations" not implemented');
|
throw new Error('Method "getEventInformations" not implemented');
|
||||||
}
|
}
|
||||||
|
@@ -335,6 +335,19 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
ORDER BY table_name
|
ORDER BY table_name
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
let { rows: matViews } = await this.raw<antares.QueryResult<ShowTableResult>>(`
|
||||||
|
SELECT schemaname AS schema_name,
|
||||||
|
matviewname AS table_name,
|
||||||
|
matviewowner AS owner,
|
||||||
|
ispopulated AS is_populated,
|
||||||
|
definition,
|
||||||
|
'materializedview' AS table_type
|
||||||
|
FROM pg_matviews
|
||||||
|
WHERE schemaname = '${db.database}'
|
||||||
|
ORDER BY schema_name,
|
||||||
|
table_name;
|
||||||
|
`);
|
||||||
|
|
||||||
if (tables.length) {
|
if (tables.length) {
|
||||||
tables = tables.map(table => {
|
tables = tables.map(table => {
|
||||||
table.Db = db.database;
|
table.Db = db.database;
|
||||||
@@ -343,6 +356,14 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
tablesArr.push(...tables);
|
tablesArr.push(...tables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (matViews.length) {
|
||||||
|
matViews = matViews.map(view => {
|
||||||
|
view.Db = db.database;
|
||||||
|
return view;
|
||||||
|
});
|
||||||
|
tablesArr.push(...matViews);
|
||||||
|
}
|
||||||
|
|
||||||
let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`
|
let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`
|
||||||
SELECT
|
SELECT
|
||||||
pg_class.relname AS table_name,
|
pg_class.relname AS table_name,
|
||||||
@@ -378,7 +399,11 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name: table.table_name,
|
name: table.table_name,
|
||||||
type: table.table_type === 'VIEW' ? 'view' : 'table',
|
type: table.table_type === 'VIEW'
|
||||||
|
? 'view'
|
||||||
|
: table.table_type === 'materializedview'
|
||||||
|
? 'materializedview'
|
||||||
|
: 'table',
|
||||||
rows: table.reltuples,
|
rows: table.reltuples,
|
||||||
size: tableSize,
|
size: tableSize,
|
||||||
collation: table.Collation,
|
collation: table.Collation,
|
||||||
@@ -566,8 +591,8 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
if (schema !== 'public')
|
// if (schema !== 'public')
|
||||||
await this.use(schema);
|
await this.use(schema);
|
||||||
|
|
||||||
const { rows } = await this.raw<antares.QueryResult<ShowIntexesResult>>(`WITH ndx_list AS (
|
const { rows } = await this.raw<antares.QueryResult<ShowIntexesResult>>(`WITH ndx_list AS (
|
||||||
SELECT pg_index.indexrelid, pg_class.oid
|
SELECT pg_index.indexrelid, pg_class.oid
|
||||||
@@ -611,35 +636,7 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
}, {} as {table: string; schema: string}[]);
|
}, {} as {table: string; schema: string}[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
async getTableDll ({ schema, table }: { schema: string; table: string }) {
|
async getTableDll ({ schema, table }: { schema: string; table: string }) {
|
||||||
// const { rows } = await this.raw<antares.QueryResult<{'ddl'?: string}>>(`
|
|
||||||
// SELECT
|
|
||||||
// 'CREATE TABLE ' || relname || E'\n(\n' ||
|
|
||||||
// array_to_string(
|
|
||||||
// array_agg(' ' || column_name || ' ' || type || ' '|| not_null)
|
|
||||||
// , E',\n'
|
|
||||||
// ) || E'\n);\n' AS ddl
|
|
||||||
// FROM (
|
|
||||||
// SELECT
|
|
||||||
// a.attname AS column_name
|
|
||||||
// , pg_catalog.format_type(a.atttypid, a.atttypmod) AS type
|
|
||||||
// , CASE WHEN a.attnotnull THEN 'NOT NULL' ELSE 'NULL' END AS not_null
|
|
||||||
// , c.relname
|
|
||||||
// FROM pg_attribute a, pg_class c, pg_type t
|
|
||||||
// WHERE a.attnum > 0
|
|
||||||
// AND a.attrelid = c.oid
|
|
||||||
// AND a.atttypid = t.oid
|
|
||||||
// AND c.relname = '${table}'
|
|
||||||
// ORDER BY a.attnum
|
|
||||||
// ) AS tabledefinition
|
|
||||||
// GROUP BY relname
|
|
||||||
// `);
|
|
||||||
|
|
||||||
// if (rows.length)
|
|
||||||
// return rows[0].ddl;
|
|
||||||
// else return '';
|
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
interface SequenceRecord {
|
interface SequenceRecord {
|
||||||
sequence_catalog: string;
|
sequence_catalog: string;
|
||||||
@@ -681,6 +678,34 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
|
|
||||||
if (!rows.length) return '';
|
if (!rows.length) return '';
|
||||||
|
|
||||||
|
const indexes = await this.getTableIndexes({ schema, table });
|
||||||
|
const primaryKey = indexes
|
||||||
|
.filter(i => i.type === 'PRIMARY')
|
||||||
|
.reduce((acc, cur) => {
|
||||||
|
if (!Object.keys(acc).length) {
|
||||||
|
cur.column = `"${cur.column}"`;
|
||||||
|
acc = cur;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
acc.column += `, "${cur.column}"`;
|
||||||
|
return acc;
|
||||||
|
}, {} as { name: string; column: string; type: string});
|
||||||
|
|
||||||
|
const remappedIndexes = indexes
|
||||||
|
.filter(i => i.type !== 'PRIMARY')
|
||||||
|
.reduce((acc, cur) => {
|
||||||
|
const existingIndex = acc.findIndex(i => i.name === cur.name);
|
||||||
|
|
||||||
|
if (existingIndex >= 0)
|
||||||
|
acc[existingIndex].column += `, "${cur.column}"`;
|
||||||
|
else {
|
||||||
|
cur.column = `"${cur.column}"`;
|
||||||
|
acc.push(cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [] as { name: string; column: string; type: string}[]);
|
||||||
|
|
||||||
for (const column of rows) {
|
for (const column of rows) {
|
||||||
let fieldType = column.data_type;
|
let fieldType = column.data_type;
|
||||||
if (fieldType === 'USER-DEFINED') fieldType = `"${schema}".${column.udt_name}`;
|
if (fieldType === 'USER-DEFINED') fieldType = `"${schema}".${column.udt_name}`;
|
||||||
@@ -708,6 +733,9 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
columnsSql.push(columnArr.join(' '));
|
columnsSql.push(columnArr.join(' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (primaryKey)
|
||||||
|
columnsSql.push(`CONSTRAINT "${primaryKey.name}" PRIMARY KEY (${primaryKey.column})`);
|
||||||
|
|
||||||
// Table sequences
|
// Table sequences
|
||||||
for (let sequence of sequences) {
|
for (let sequence of sequences) {
|
||||||
if (sequence.includes('.')) sequence = sequence.split('.')[1];
|
if (sequence.includes('.')) sequence = sequence.split('.')[1];
|
||||||
@@ -724,25 +752,22 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
INCREMENT BY ${rows[0].increment}
|
INCREMENT BY ${rows[0].increment}
|
||||||
MINVALUE ${rows[0].minimum_value}
|
MINVALUE ${rows[0].minimum_value}
|
||||||
MAXVALUE ${rows[0].maximum_value}
|
MAXVALUE ${rows[0].maximum_value}
|
||||||
CACHE 1;\n`;
|
CACHE 1;\n\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Table create
|
// Table create
|
||||||
createSql += `\nCREATE TABLE "${schema}"."${table}"(
|
createSql += `CREATE TABLE "${schema}"."${table}"(
|
||||||
${columnsSql.join(',\n ')}
|
${columnsSql.join(',\n ')}
|
||||||
);\n`;
|
);\n`;
|
||||||
|
|
||||||
// Table indexes
|
// Table indexes
|
||||||
createSql += '\n';
|
createSql += '\n';
|
||||||
const { rows: indexes } = await this.select('*')
|
|
||||||
.schema('pg_catalog')
|
|
||||||
.from('pg_indexes')
|
|
||||||
.where({ schemaname: `= '${schema}'`, tablename: `= '${table}'` })
|
|
||||||
.run<{indexdef: string}>();
|
|
||||||
|
|
||||||
for (const index of indexes)
|
for (const index of remappedIndexes) {
|
||||||
createSql += `${index.indexdef};\n`;
|
if (index.type !== 'PRIMARY')
|
||||||
|
createSql += `CREATE ${index.type}${index.type === 'UNIQUE' ? ' INDEX' : ''} "${index.name}" ON "${schema}"."${table}" (${index.column});\n`;
|
||||||
|
}
|
||||||
|
|
||||||
return createSql;
|
return createSql;
|
||||||
}
|
}
|
||||||
@@ -1056,11 +1081,32 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
})[0];
|
})[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMaterializedViewInformations ({ schema, view }: { schema: string; view: string }) {
|
||||||
|
const sql = `SELECT "definition" FROM "pg_matviews" WHERE "matviewname"='${view}' AND "schemaname"='${schema}'`;
|
||||||
|
const results = await this.raw(sql);
|
||||||
|
|
||||||
|
return results.rows.map(row => {
|
||||||
|
return {
|
||||||
|
algorithm: '',
|
||||||
|
definer: '',
|
||||||
|
security: '',
|
||||||
|
updateOption: '',
|
||||||
|
sql: row.definition,
|
||||||
|
name: view
|
||||||
|
};
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
|
||||||
async dropView (params: { schema: string; view: string }) {
|
async dropView (params: { schema: string; view: string }) {
|
||||||
const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
|
const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
|
||||||
return await this.raw(sql);
|
return await this.raw(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async dropMaterializedView (params: { schema: string; view: string }) {
|
||||||
|
const sql = `DROP MATERIALIZED VIEW "${params.schema}"."${params.view}"`;
|
||||||
|
return await this.raw(sql);
|
||||||
|
}
|
||||||
|
|
||||||
async alterView ({ view }: { view: antares.AlterViewParams }) {
|
async alterView ({ view }: { view: antares.AlterViewParams }) {
|
||||||
let sql = `CREATE OR REPLACE VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`;
|
let sql = `CREATE OR REPLACE VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`;
|
||||||
|
|
||||||
@@ -1070,11 +1116,25 @@ export class PostgreSQLClient extends BaseClient {
|
|||||||
return await this.raw(sql);
|
return await this.raw(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async alterMaterializedView ({ view }: { view: antares.AlterViewParams }) {
|
||||||
|
let sql = `CREATE OR REPLACE MATERIALIZED VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`;
|
||||||
|
|
||||||
|
if (view.name !== view.oldName)
|
||||||
|
sql += `; ALTER VIEW "${view.schema}"."${view.oldName}" RENAME TO "${view.name}"`;
|
||||||
|
|
||||||
|
return await this.raw(sql);
|
||||||
|
}
|
||||||
|
|
||||||
async createView (params: antares.CreateViewParams) {
|
async createView (params: antares.CreateViewParams) {
|
||||||
const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
|
const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
|
||||||
return await this.raw(sql);
|
return await this.raw(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createMaterializedView (params: antares.CreateViewParams) {
|
||||||
|
const sql = `CREATE MATERIALIZED VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
|
||||||
|
return await this.raw(sql);
|
||||||
|
}
|
||||||
|
|
||||||
async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) {
|
async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) {
|
||||||
const [table, triggerName] = trigger.split('.');
|
const [table, triggerName] = trigger.split('.');
|
||||||
|
|
||||||
|
@@ -336,7 +336,8 @@ CREATE TABLE \`${view.Name}\`(
|
|||||||
const connection = await this._client.getConnection();
|
const connection = await this._client.getConnection();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const stream = (connection as any).connection.query(sql).stream();
|
const stream = (connection as any).connection.query(sql).stream();
|
||||||
const dispose = () => connection.end();
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const dispose = () => (connection as any).release();
|
||||||
|
|
||||||
stream.on('end', dispose);
|
stream.on('end', dispose);
|
||||||
stream.on('error', dispose);
|
stream.on('error', dispose);
|
||||||
|
@@ -39,115 +39,7 @@ SET row_security = off;\n\n\n`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCreateTable (tableName: string) {
|
async getCreateTable (tableName: string) {
|
||||||
/* eslint-disable camelcase */
|
const createSql = await this._client.getTableDll({ schema: this.schemaName, table: tableName });
|
||||||
interface SequenceRecord {
|
|
||||||
sequence_catalog: string;
|
|
||||||
sequence_schema: string;
|
|
||||||
sequence_name: string;
|
|
||||||
data_type: string;
|
|
||||||
numeric_precision: number;
|
|
||||||
numeric_precision_radix: number;
|
|
||||||
numeric_scale: number;
|
|
||||||
start_value: string;
|
|
||||||
minimum_value: string;
|
|
||||||
maximum_value: string;
|
|
||||||
increment: string;
|
|
||||||
cycle_option: string;
|
|
||||||
}
|
|
||||||
/* eslint-enable camelcase */
|
|
||||||
|
|
||||||
let createSql = '';
|
|
||||||
const sequences = [];
|
|
||||||
const columnsSql = [];
|
|
||||||
const arrayTypes: Record<string, string> = {
|
|
||||||
_int2: 'smallint',
|
|
||||||
_int4: 'integer',
|
|
||||||
_int8: 'bigint',
|
|
||||||
_float4: 'real',
|
|
||||||
_float8: 'double precision',
|
|
||||||
_char: '"char"',
|
|
||||||
_varchar: 'character varying'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Table columns
|
|
||||||
const { rows } = await this._client.raw(`
|
|
||||||
SELECT *
|
|
||||||
FROM "information_schema"."columns"
|
|
||||||
WHERE "table_schema" = '${this.schemaName}'
|
|
||||||
AND "table_name" = '${tableName}'
|
|
||||||
ORDER BY "ordinal_position" ASC
|
|
||||||
`, { schema: 'information_schema' });
|
|
||||||
|
|
||||||
if (!rows.length) return '';
|
|
||||||
|
|
||||||
for (const column of rows) {
|
|
||||||
let fieldType = column.data_type;
|
|
||||||
if (fieldType === 'USER-DEFINED') fieldType = `"${this.schemaName}".${column.udt_name}`;
|
|
||||||
else if (fieldType === 'ARRAY') {
|
|
||||||
if (Object.keys(arrayTypes).includes(fieldType))
|
|
||||||
fieldType = arrayTypes[column.udt_name] + '[]';
|
|
||||||
else
|
|
||||||
fieldType = column.udt_name.replaceAll('_', '') + '[]';
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnArr = [
|
|
||||||
`"${column.column_name}"`,
|
|
||||||
`${fieldType}${column.character_maximum_length ? `(${column.character_maximum_length})` : ''}`
|
|
||||||
];
|
|
||||||
|
|
||||||
if (column.column_default) {
|
|
||||||
columnArr.push(`DEFAULT ${column.column_default}`);
|
|
||||||
if (column.column_default.includes('nextval')) {
|
|
||||||
const sequenceName = column.column_default.split('\'')[1];
|
|
||||||
sequences.push(sequenceName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (column.is_nullable === 'NO') columnArr.push('NOT NULL');
|
|
||||||
|
|
||||||
columnsSql.push(columnArr.join(' '));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table sequences
|
|
||||||
for (let sequence of sequences) {
|
|
||||||
if (sequence.includes('.')) sequence = sequence.split('.')[1];
|
|
||||||
|
|
||||||
const { rows } = await this._client
|
|
||||||
.select('*')
|
|
||||||
.schema('information_schema')
|
|
||||||
.from('sequences')
|
|
||||||
.where({ sequence_schema: `= '${this.schemaName}'`, sequence_name: `= '${sequence}'` })
|
|
||||||
.run<SequenceRecord>();
|
|
||||||
|
|
||||||
if (rows.length) {
|
|
||||||
createSql += `CREATE SEQUENCE "${this.schemaName}"."${sequence}"
|
|
||||||
START WITH ${rows[0].start_value}
|
|
||||||
INCREMENT BY ${rows[0].increment}
|
|
||||||
MINVALUE ${rows[0].minimum_value}
|
|
||||||
MAXVALUE ${rows[0].maximum_value}
|
|
||||||
CACHE 1;\n`;
|
|
||||||
|
|
||||||
// createSql += `\nALTER TABLE "${sequence}" OWNER TO ${this._client._params.user};\n\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table create
|
|
||||||
createSql += `\nCREATE TABLE "${this.schemaName}"."${tableName}"(
|
|
||||||
${columnsSql.join(',\n ')}
|
|
||||||
);\n`;
|
|
||||||
|
|
||||||
// createSql += `\nALTER TABLE "${tableName}" OWNER TO ${this._client._params.user};\n\n`;
|
|
||||||
|
|
||||||
// Table indexes
|
|
||||||
createSql += '\n';
|
|
||||||
const { rows: indexes } = await this._client
|
|
||||||
.select('*')
|
|
||||||
.schema('pg_catalog')
|
|
||||||
.from('pg_indexes')
|
|
||||||
.where({ schemaname: `= '${this.schemaName}'`, tablename: `= '${tableName}'` })
|
|
||||||
.run<{indexdef: string}>();
|
|
||||||
|
|
||||||
for (const index of indexes)
|
|
||||||
createSql += `${index.indexdef};\n`;
|
|
||||||
|
|
||||||
// Table foreigns
|
// Table foreigns
|
||||||
const { rows: foreigns } = await this._client.raw(`
|
const { rows: foreigns } = await this._client.raw(`
|
||||||
|
@@ -10,9 +10,7 @@
|
|||||||
:key="connection.uid"
|
:key="connection.uid"
|
||||||
:connection="connection"
|
:connection="connection"
|
||||||
/>
|
/>
|
||||||
<div class="connection-panel-wrapper p-relative">
|
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
||||||
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<TheFooter />
|
<TheFooter />
|
||||||
<TheNotificationsBoard />
|
<TheNotificationsBoard />
|
||||||
@@ -48,6 +46,8 @@ import { useSchemaExportStore } from '@/stores/schemaExport';
|
|||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
|
import { useConsoleStore } from './stores/console';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const TheTitleBar = defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue'));
|
const TheTitleBar = defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue'));
|
||||||
@@ -80,6 +80,8 @@ const schemaExportStore = useSchemaExportStore();
|
|||||||
const { hideExportModal } = schemaExportStore;
|
const { hideExportModal } = schemaExportStore;
|
||||||
const { isExportModal: isExportSchemaModal } = storeToRefs(schemaExportStore);
|
const { isExportModal: isExportSchemaModal } = storeToRefs(schemaExportStore);
|
||||||
|
|
||||||
|
const consoleStore = useConsoleStore();
|
||||||
|
|
||||||
const isAllConnectionsModal: Ref<boolean> = ref(false);
|
const isAllConnectionsModal: Ref<boolean> = ref(false);
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
@@ -152,6 +154,60 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Console messages
|
||||||
|
const oldLog = console.log;
|
||||||
|
const oldWarn = console.warn;
|
||||||
|
const oldInfo = console.info;
|
||||||
|
const oldError = console.error;
|
||||||
|
|
||||||
|
console.log = function (...args) {
|
||||||
|
consoleStore.putLog('debug', {
|
||||||
|
level: 'log',
|
||||||
|
process: 'renderer',
|
||||||
|
message: args.join(' '),
|
||||||
|
date: new Date()
|
||||||
|
});
|
||||||
|
oldLog.apply(this, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.info = function (...args) {
|
||||||
|
consoleStore.putLog('debug', {
|
||||||
|
level: 'info',
|
||||||
|
process: 'renderer',
|
||||||
|
message: args.join(' '),
|
||||||
|
date: new Date()
|
||||||
|
});
|
||||||
|
oldInfo.apply(this, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.warn = function (...args) {
|
||||||
|
consoleStore.putLog('debug', {
|
||||||
|
level: 'warn',
|
||||||
|
process: 'renderer',
|
||||||
|
message: args.join(' '),
|
||||||
|
date: new Date()
|
||||||
|
});
|
||||||
|
oldWarn.apply(this, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.error = function (...args) {
|
||||||
|
consoleStore.putLog('debug', {
|
||||||
|
level: 'error',
|
||||||
|
process: 'renderer',
|
||||||
|
message: args.join(' '),
|
||||||
|
date: new Date()
|
||||||
|
});
|
||||||
|
oldError.apply(this, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
|
console.error(event.reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('error', (event) => {
|
||||||
|
console.error(event.error, '| File name:', event.filename.split('/').pop().split('?')[0]);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@@ -1,11 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
|
v-if="type === 'mdi'"
|
||||||
:type="type"
|
:type="type"
|
||||||
:path="iconPath"
|
:path="iconPath"
|
||||||
:size="size"
|
:size="size"
|
||||||
:rotate="rotate"
|
:rotate="rotate"
|
||||||
:class="iconFlip"
|
:class="iconFlip"
|
||||||
/>
|
/>
|
||||||
|
<svg
|
||||||
|
v-else
|
||||||
|
:width="size"
|
||||||
|
:height="size"
|
||||||
|
:viewBox="`0 0 ${size} ${size}`"
|
||||||
|
v-html="iconPath"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -13,6 +21,10 @@ import SvgIcon from '@jamescoyle/vue-icon';
|
|||||||
import * as Icons from '@mdi/js';
|
import * as Icons from '@mdi/js';
|
||||||
import { computed, PropType } from 'vue';
|
import { computed, PropType } from 'vue';
|
||||||
|
|
||||||
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
|
|
||||||
|
const { getIconByUid } = useConnectionsStore();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
iconName: {
|
iconName: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -23,7 +35,7 @@ const props = defineProps({
|
|||||||
default: 48
|
default: 48
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String as PropType<'mdi' | 'custom'>,
|
||||||
default: () => 'mdi'
|
default: () => 'mdi'
|
||||||
},
|
},
|
||||||
flip: {
|
flip: {
|
||||||
@@ -37,7 +49,18 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const iconPath = computed(() => {
|
const iconPath = computed(() => {
|
||||||
return (Icons as {[k:string]: string})[props.iconName];
|
if (props.type === 'mdi')
|
||||||
|
return (Icons as {[k:string]: string})[props.iconName];
|
||||||
|
else if (props.type === 'custom') {
|
||||||
|
const base64 = getIconByUid(props.iconName)?.base64;
|
||||||
|
const svgString = Buffer
|
||||||
|
.from(base64, 'base64')
|
||||||
|
.toString('utf-8')
|
||||||
|
.replaceAll(/width="[^"]*"|height="[^"]*"/g, '');
|
||||||
|
|
||||||
|
return svgString;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const iconFlip = computed(() => {
|
const iconFlip = computed(() => {
|
||||||
|
@@ -365,7 +365,11 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleWheelEvent = (e) => {
|
const handleWheelEvent = (e) => {
|
||||||
if (!e.target.className.includes('select__')) deactivate();
|
try {
|
||||||
|
if (!e.target.className.includes('select__')) deactivate();
|
||||||
|
}
|
||||||
|
catch (_) {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
285
src/renderer/components/DebugConsole.vue
Normal file
285
src/renderer/components/DebugConsole.vue
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="wrapper"
|
||||||
|
class="console-wrapper"
|
||||||
|
@mouseenter="isHover = true"
|
||||||
|
@mouseleave="isHover = false"
|
||||||
|
>
|
||||||
|
<div ref="resizer" class="console-resizer" />
|
||||||
|
<div
|
||||||
|
id="console"
|
||||||
|
ref="queryConsole"
|
||||||
|
class="console column col-12"
|
||||||
|
:style="{height: localHeight ? localHeight+'px' : ''}"
|
||||||
|
>
|
||||||
|
<div class="console-header">
|
||||||
|
<ul class="tab tab-block">
|
||||||
|
<li class="tab-item" :class="{'active': selectedTab === 'query'}">
|
||||||
|
<a class="tab-link" @click="selectedTab = 'query'">{{ t('application.executedQueries') }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="tab-item" :class="{'active': selectedTab === 'debug'}">
|
||||||
|
<a class="tab-link" @click="selectedTab = 'debug'">{{ t('application.debugConsole') }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button class="btn btn-clear mr-1" @click="resizeConsole(0)" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-show="selectedTab === 'query'"
|
||||||
|
ref="queryConsoleBody"
|
||||||
|
class="console-body"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(wLog, i) in workspaceQueryLogs"
|
||||||
|
:key="i"
|
||||||
|
class="console-log"
|
||||||
|
tabindex="0"
|
||||||
|
@contextmenu.prevent="contextMenu($event, wLog)"
|
||||||
|
>
|
||||||
|
<span class="console-log-datetime">{{ moment(wLog.date).format('HH:mm:ss') }}</span>: <code class="console-log-sql" v-html="highlight(wLog.sql, {html: true})" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-show="selectedTab === 'debug'"
|
||||||
|
ref="logConsoleBody"
|
||||||
|
class="console-body"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(log, i) in debugLogs"
|
||||||
|
:key="i"
|
||||||
|
class="console-log"
|
||||||
|
tabindex="0"
|
||||||
|
@contextmenu.prevent="contextMenu($event, log)"
|
||||||
|
>
|
||||||
|
<span class="console-log-datetime">{{ moment(log.date).format('HH:mm:ss') }}</span> <small>[{{ log.process.substring(0, 1).toUpperCase() }}]</small>: <span class="console-log-message" :class="`console-log-level-${log.level}`">{{ log.message }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<BaseContextMenu
|
||||||
|
v-if="isContext"
|
||||||
|
:context-event="contextEvent"
|
||||||
|
@close-context="isContext = false"
|
||||||
|
>
|
||||||
|
<div class="context-element" @click="copyLog">
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiContentCopy"
|
||||||
|
:size="18"
|
||||||
|
/> {{ t('general.copy') }}</span>
|
||||||
|
</div>
|
||||||
|
</BaseContextMenu>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { highlight } from 'sql-highlight';
|
||||||
|
import { computed, nextTick, onMounted, Ref, ref, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||||
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
|
import { copyText } from '@/libs/copyText';
|
||||||
|
import { useConsoleStore } from '@/stores/console';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const consoleStore = useConsoleStore();
|
||||||
|
|
||||||
|
const { resizeConsole, getLogsByWorkspace } = consoleStore;
|
||||||
|
const {
|
||||||
|
isConsoleOpen,
|
||||||
|
consoleHeight,
|
||||||
|
selectedTab,
|
||||||
|
debugLogs
|
||||||
|
} = storeToRefs(consoleStore);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
uid: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper: Ref<HTMLInputElement> = ref(null);
|
||||||
|
const queryConsole: Ref<HTMLInputElement> = ref(null);
|
||||||
|
const queryConsoleBody: Ref<HTMLInputElement> = ref(null);
|
||||||
|
const logConsoleBody: Ref<HTMLInputElement> = ref(null);
|
||||||
|
const resizer: Ref<HTMLInputElement> = ref(null);
|
||||||
|
const localHeight = ref(consoleHeight.value);
|
||||||
|
const isHover = ref(false);
|
||||||
|
const isContext = ref(false);
|
||||||
|
const contextContent: Ref<string> = ref(null);
|
||||||
|
const contextEvent: Ref<MouseEvent> = ref(null);
|
||||||
|
|
||||||
|
const resize = (e: MouseEvent) => {
|
||||||
|
const el = queryConsole.value;
|
||||||
|
let elementHeight = el.getBoundingClientRect().bottom - e.pageY;
|
||||||
|
if (elementHeight > 400) elementHeight = 400;
|
||||||
|
localHeight.value = elementHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
const workspaceQueryLogs = computed(() => {
|
||||||
|
return getLogsByWorkspace(props.uid);
|
||||||
|
});
|
||||||
|
|
||||||
|
const stopResize = () => {
|
||||||
|
if (localHeight.value < 0) localHeight.value = 0;
|
||||||
|
resizeConsole(localHeight.value);
|
||||||
|
window.removeEventListener('mousemove', resize);
|
||||||
|
window.removeEventListener('mouseup', stopResize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const contextMenu = (event: MouseEvent, wLog: {date: Date; sql?: string; message?: string}) => {
|
||||||
|
contextEvent.value = event;
|
||||||
|
contextContent.value = wLog.sql || wLog.message;
|
||||||
|
isContext.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyLog = () => {
|
||||||
|
copyText(contextContent.value);
|
||||||
|
isContext.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(workspaceQueryLogs, async () => {
|
||||||
|
if (!isHover.value) {
|
||||||
|
await nextTick();
|
||||||
|
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => debugLogs.value.length, async () => {
|
||||||
|
if (!isHover.value) {
|
||||||
|
await nextTick();
|
||||||
|
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(isConsoleOpen, async () => {
|
||||||
|
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
||||||
|
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(selectedTab, async () => {
|
||||||
|
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
||||||
|
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(consoleHeight, async (val) => {
|
||||||
|
await nextTick();
|
||||||
|
localHeight.value = val;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
||||||
|
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
|
||||||
|
|
||||||
|
resizer.value.addEventListener('mousedown', (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', resize);
|
||||||
|
window.addEventListener('mouseup', stopResize);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.console-wrapper {
|
||||||
|
width: -webkit-fill-available;
|
||||||
|
z-index: 9;
|
||||||
|
margin-top: auto;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
.console-resizer {
|
||||||
|
height: 4px;
|
||||||
|
top: -1px;
|
||||||
|
width: 100%;
|
||||||
|
cursor: ns-resize;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 99;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--primary-color-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.console {
|
||||||
|
padding: 0;
|
||||||
|
padding-bottom: $footer-height;
|
||||||
|
|
||||||
|
.console-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 4px;
|
||||||
|
|
||||||
|
.tab-block {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-block,
|
||||||
|
.tab-item {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-link {
|
||||||
|
padding: 0.2rem 0.6rem;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-body {
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 100%;
|
||||||
|
padding: 0 6px 3px;
|
||||||
|
|
||||||
|
.console-log {
|
||||||
|
padding: 1px 3px;
|
||||||
|
margin: 1px 0;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
user-select: text;
|
||||||
|
|
||||||
|
&-datetime {
|
||||||
|
opacity: .6;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-sql {
|
||||||
|
font-size: 95%;
|
||||||
|
opacity: 0.8;
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-message {
|
||||||
|
font-size: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-level {
|
||||||
|
// &-log,
|
||||||
|
// &-info {}
|
||||||
|
&-warn {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
&-error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
opacity: .6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@@ -57,8 +57,22 @@
|
|||||||
>
|
>
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-header p-2 text-center p-relative">
|
<div class="panel-header p-2 text-center p-relative">
|
||||||
<figure class="avatar avatar-lg pt-1 mb-1">
|
<figure class="avatar avatar-lg pt-1 mb-1 bg-dark">
|
||||||
<i class="settingbar-element-icon dbi" :class="[`dbi-${connection.client}`]" />
|
<div
|
||||||
|
v-if="connection.icon"
|
||||||
|
class="settingbar-connection-icon"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
:icon-name="camelize(connection.icon)"
|
||||||
|
:type="connection.hasCustomIcon ? 'custom' : 'mdi'"
|
||||||
|
:size="42"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="settingbar-element-icon dbi ml-1"
|
||||||
|
:class="[`dbi-${connection.client}`]"
|
||||||
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
<div class="panel-title h6 text-ellipsis">
|
<div class="panel-title h6 text-ellipsis">
|
||||||
{{ getConnectionName(connection.uid) }}
|
{{ getConnectionName(connection.uid) }}
|
||||||
@@ -136,7 +150,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-footer text-center py-0">
|
<div class="panel-footer text-center py-0">
|
||||||
<div v-if="connection.ssl" class="chip bg-success mt-2">
|
<div
|
||||||
|
v-if="connection.folderName"
|
||||||
|
class="chip mt-2 bg-dark"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiFolder"
|
||||||
|
class="mr-1"
|
||||||
|
:style="[connection.color ? `color: ${connection.color};`: '']"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
{{ connection.folderName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="connection.ssl" class="chip bg-dark mt-2">
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
icon-name="mdiShieldKey"
|
icon-name="mdiShieldKey"
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
@@ -144,7 +170,7 @@
|
|||||||
/>
|
/>
|
||||||
SSL
|
SSL
|
||||||
</div>
|
</div>
|
||||||
<div v-if="connection.ssh" class="chip bg-success mt-2">
|
<div v-if="connection.ssh" class="chip bg-dark mt-2">
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
icon-name="mdiConsoleNetwork"
|
icon-name="mdiConsoleNetwork"
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
@@ -152,7 +178,7 @@
|
|||||||
/>
|
/>
|
||||||
SSH
|
SSH
|
||||||
</div>
|
</div>
|
||||||
<div v-if="connection.readonly" class="chip bg-success mt-2">
|
<div v-if="connection.readonly" class="chip bg-dark mt-2">
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
icon-name="mdiLock"
|
icon-name="mdiLock"
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
@@ -209,6 +235,7 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||||
import BaseIcon from '@/components/BaseIcon.vue';
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||||
|
import { camelize } from '@/libs/camelize';
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
@@ -218,7 +245,9 @@ const connectionsStore = useConnectionsStore();
|
|||||||
const workspacesStore = useWorkspacesStore();
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
const { connections,
|
const { connections,
|
||||||
lastConnections
|
connectionsOrder,
|
||||||
|
lastConnections,
|
||||||
|
getFolders: folders
|
||||||
} = storeToRefs(connectionsStore);
|
} = storeToRefs(connectionsStore);
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
|
|
||||||
@@ -236,7 +265,8 @@ const clients = new Map([
|
|||||||
['mysql', 'MySQL'],
|
['mysql', 'MySQL'],
|
||||||
['maria', 'MariaDB'],
|
['maria', 'MariaDB'],
|
||||||
['pg', 'PostgreSQL'],
|
['pg', 'PostgreSQL'],
|
||||||
['sqlite', 'SQLite']
|
['sqlite', 'SQLite'],
|
||||||
|
['firebird', 'Firebird SQL']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const searchTerm = ref('');
|
const searchTerm = ref('');
|
||||||
@@ -244,12 +274,20 @@ const isConfirmModal = ref(false);
|
|||||||
const connectionHover: Ref<string> = ref(null);
|
const connectionHover: Ref<string> = ref(null);
|
||||||
const selectedConnection: Ref<ConnectionParams> = ref(null);
|
const selectedConnection: Ref<ConnectionParams> = ref(null);
|
||||||
|
|
||||||
const sortedConnections = computed(() => {
|
const remappedConnections = computed(() => {
|
||||||
return connections.value
|
return connections.value
|
||||||
.map(c => {
|
.map(c => {
|
||||||
const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0;
|
const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0;
|
||||||
|
const connIcon = connectionsOrder.value.find((co) => co.uid === c.uid).icon;
|
||||||
|
const connHasCustomIcon = connectionsOrder.value.find((co) => co.uid === c.uid).hasCustomIcon;
|
||||||
|
const folder = folders.value.find(f => f.connections.includes(c.uid));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...c,
|
...c,
|
||||||
|
icon: connIcon,
|
||||||
|
color: folder?.color,
|
||||||
|
folderName: folder?.name,
|
||||||
|
hasCustomIcon: connHasCustomIcon,
|
||||||
time: connTime
|
time: connTime
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -261,7 +299,7 @@ const sortedConnections = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const filteredConnections = computed(() => {
|
const filteredConnections = computed(() => {
|
||||||
return sortedConnections.value.filter(connection => {
|
return remappedConnections.value.filter(connection => {
|
||||||
return connection.name?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
return connection.name?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||||
connection.host?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
connection.host?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||||
connection.database?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
connection.database?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||||
|
@@ -49,18 +49,46 @@
|
|||||||
class="icon-box"
|
class="icon-box"
|
||||||
:title="icon.name"
|
:title="icon.name"
|
||||||
:class="[{'selected': localConnection.icon === icon.code}]"
|
:class="[{'selected': localConnection.icon === icon.code}]"
|
||||||
@click="localConnection.icon = icon.code"
|
@click="setIcon(icon.code)"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="icon-box"
|
class="icon-box"
|
||||||
:title="icon.name"
|
:title="icon.name"
|
||||||
:class="[`dbi dbi-${connection.client}`, {'selected': localConnection.icon === icon.code}]"
|
:class="[`dbi dbi-${connection.client}`, {'selected': localConnection.icon === null}]"
|
||||||
@click="localConnection.icon = icon.code"
|
@click="setIcon(null)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-label">{{ t('application.customIcon') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-9 icons-wrapper">
|
||||||
|
<div
|
||||||
|
v-for="icon in customIcons"
|
||||||
|
:key="icon.uid"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
v-if="icon.uid"
|
||||||
|
:icon-name="icon.uid"
|
||||||
|
type="custom"
|
||||||
|
:size="36"
|
||||||
|
class="icon-box"
|
||||||
|
:class="[{'selected': localConnection.icon === icon.uid}]"
|
||||||
|
@click="setIcon(icon.uid, 'custom')"
|
||||||
|
@contextmenu.prevent="contextMenu($event, icon.uid)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<BaseIcon
|
||||||
|
:icon-name="'mdiPlus'"
|
||||||
|
:size="36"
|
||||||
|
class="icon-box"
|
||||||
|
@click="openFile"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,20 +102,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<BaseContextMenu
|
||||||
|
v-if="isContext"
|
||||||
|
:context-event="contextEvent"
|
||||||
|
@close-context="isContext = false"
|
||||||
|
>
|
||||||
|
<div class="context-element" @click="removeIconHandler">
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiDelete"
|
||||||
|
:size="18"
|
||||||
|
/> {{ t('general.delete') }}</span>
|
||||||
|
</div>
|
||||||
|
</BaseContextMenu>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { onBeforeUnmount, PropType, Ref, ref } from 'vue';
|
import { onBeforeUnmount, PropType, Ref, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||||
import BaseIcon from '@/components/BaseIcon.vue';
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||||
|
import Application from '@/ipc-api/Application';
|
||||||
|
import { camelize } from '@/libs/camelize';
|
||||||
import { unproxify } from '@/libs/unproxify';
|
import { unproxify } from '@/libs/unproxify';
|
||||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||||
|
|
||||||
const connectionsStore = useConnectionsStore();
|
const connectionsStore = useConnectionsStore();
|
||||||
|
|
||||||
|
const { addIcon, removeIcon, updateConnectionOrder, getConnectionName } = connectionsStore;
|
||||||
|
const { customIcons } = storeToRefs(connectionsStore);
|
||||||
|
|
||||||
|
const isContext = ref(false);
|
||||||
|
const contextContent: Ref<string> = ref(null);
|
||||||
|
const contextEvent: Ref<MouseEvent> = ref(null);
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -99,8 +152,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['close']);
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
const { updateConnectionOrder, getConnectionName } = connectionsStore;
|
|
||||||
|
|
||||||
const icons = [
|
const icons = [
|
||||||
{ name: 'default', code: null },
|
{ name: 'default', code: null },
|
||||||
|
|
||||||
@@ -160,14 +211,33 @@ const editFolderAppearance = () => {
|
|||||||
closeModal();
|
closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const camelize = (text: string) => {
|
const setIcon = (code: string, type?: 'mdi' | 'custom') => {
|
||||||
const textArr = text.split('-');
|
localConnection.value.icon = code;
|
||||||
for (let i = 0; i < textArr.length; i++) {
|
localConnection.value.hasCustomIcon = type === 'custom';
|
||||||
if (i === 0) continue;
|
};
|
||||||
textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return textArr.join('');
|
const removeIconHandler = () => {
|
||||||
|
if (localConnection.value.icon === contextContent.value) {
|
||||||
|
setIcon(null);
|
||||||
|
updateConnectionOrder(localConnection.value);
|
||||||
|
}
|
||||||
|
removeIcon(contextContent.value);
|
||||||
|
isContext.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openFile = async () => {
|
||||||
|
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: '"SVG"', extensions: ['svg'] }] });
|
||||||
|
if (result && !result.canceled) {
|
||||||
|
const file = result.filePaths[0];
|
||||||
|
const content = await Application.readFile({ filePath: file, encoding: 'base64url' });
|
||||||
|
addIcon(content);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const contextMenu = (event: MouseEvent, iconUid: string) => {
|
||||||
|
contextEvent.value = event;
|
||||||
|
contextContent.value = iconUid;
|
||||||
|
isContext.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => emit('close');
|
const closeModal = () => emit('close');
|
||||||
|
@@ -282,7 +282,7 @@
|
|||||||
import { ClientCode, SchemaInfos } from 'common/interfaces/antares';
|
import { ClientCode, SchemaInfos } from 'common/interfaces/antares';
|
||||||
import { Customizations } from 'common/interfaces/customizations';
|
import { Customizations } from 'common/interfaces/customizations';
|
||||||
import { ExportOptions, ExportState } from 'common/interfaces/exporter';
|
import { ExportOptions, ExportState } from 'common/interfaces/exporter';
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||||
@@ -293,6 +293,7 @@ import BaseSelect from '@/components/BaseSelect.vue';
|
|||||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||||
import Application from '@/ipc-api/Application';
|
import Application from '@/ipc-api/Application';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
|
import { useConsoleStore } from '@/stores/console';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useSchemaExportStore } from '@/stores/schemaExport';
|
import { useSchemaExportStore } from '@/stores/schemaExport';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
@@ -379,21 +380,34 @@ const startExport = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { status, response } = await Schema.export(params);
|
const { status, response } = await Schema.export(params);
|
||||||
|
|
||||||
if (status === 'success')
|
if (status === 'success')
|
||||||
progressStatus.value = response.cancelled ? t('general.aborted') : t('general.completed');
|
progressStatus.value = response.cancelled ? t('general.aborted') : t('general.completed');
|
||||||
else {
|
else {
|
||||||
progressStatus.value = response;
|
progressStatus.value = response;
|
||||||
addNotification({ status: 'error', message: response });
|
addNotification({ status: 'error', message: response });
|
||||||
|
useConsoleStore().putLog('debug', {
|
||||||
|
level: 'error',
|
||||||
|
process: 'worker',
|
||||||
|
message: response,
|
||||||
|
date: new Date()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
addNotification({ status: 'error', message: err.stack });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
|
useConsoleStore().putLog('debug', {
|
||||||
|
level: 'error',
|
||||||
|
process: 'worker',
|
||||||
|
message: err.stack,
|
||||||
|
date: new Date()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isExporting.value = false;
|
isExporting.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateProgress = (event: Event, state: ExportState) => {
|
const updateProgress = (event: IpcRendererEvent, state: ExportState) => {
|
||||||
progressPercentage.value = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
|
progressPercentage.value = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
|
||||||
switch (state.op) {
|
switch (state.op) {
|
||||||
case 'PROCESSING':
|
case 'PROCESSING':
|
||||||
|
@@ -75,7 +75,7 @@
|
|||||||
<code
|
<code
|
||||||
class="cut-text"
|
class="cut-text"
|
||||||
:title="query.sql"
|
:title="query.sql"
|
||||||
v-html="highlight(highlightWord(query.sql), {html: true})"
|
v-html="highlight(query.sql, {html: true})"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="tile-bottom-content">
|
<div class="tile-bottom-content">
|
||||||
@@ -210,17 +210,6 @@ const resizeResults = () => {
|
|||||||
const refreshScroller = () => resizeResults();
|
const refreshScroller = () => resizeResults();
|
||||||
const closeModal = () => emit('close');
|
const closeModal = () => emit('close');
|
||||||
|
|
||||||
const highlightWord = (string: string) => {
|
|
||||||
string = string.replaceAll('<', '<').replaceAll('>', '>');
|
|
||||||
|
|
||||||
if (searchTerm.value) {
|
|
||||||
const regexp = new RegExp(`(${searchTerm.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
|
||||||
return string.replace(regexp, '<span class="text-primary text-bold">$1</span>');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onKey = (e: KeyboardEvent) => {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (e.key === 'Escape')
|
if (e.key === 'Escape')
|
||||||
|
@@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ImportState } from 'common/interfaces/importer';
|
import { ImportState } from 'common/interfaces/importer';
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||||
@@ -63,6 +63,7 @@ import { useI18n } from 'vue-i18n';
|
|||||||
|
|
||||||
import BaseIcon from '@/components/BaseIcon.vue';
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
|
import { useConsoleStore } from '@/stores/console';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
@@ -118,23 +119,35 @@ const startImport = async (file: string) => {
|
|||||||
else {
|
else {
|
||||||
progressStatus.value = response;
|
progressStatus.value = response;
|
||||||
addNotification({ status: 'error', message: response });
|
addNotification({ status: 'error', message: response });
|
||||||
|
useConsoleStore().putLog('debug', {
|
||||||
|
level: 'error',
|
||||||
|
process: 'worker',
|
||||||
|
message: response,
|
||||||
|
date: new Date()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
refreshSchema({ uid, schema: props.selectedSchema });
|
refreshSchema({ uid, schema: props.selectedSchema });
|
||||||
completed.value = true;
|
completed.value = true;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
addNotification({ status: 'error', message: err.stack });
|
addNotification({ status: 'error', message: err.stack });
|
||||||
|
useConsoleStore().putLog('debug', {
|
||||||
|
level: 'error',
|
||||||
|
process: 'worker',
|
||||||
|
message: err.stack,
|
||||||
|
date: new Date()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isImporting.value = false;
|
isImporting.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateProgress = (event: Event, state: ImportState) => {
|
const updateProgress = (event: IpcRendererEvent, state: ImportState) => {
|
||||||
progressPercentage.value = parseFloat(Number(state.percentage).toFixed(1));
|
progressPercentage.value = parseFloat(Number(state.percentage).toFixed(1));
|
||||||
queryCount.value = Number(state.queryCount);
|
queryCount.value = Number(state.queryCount);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQueryError = (event: Event, err: { time: string; message: string }) => {
|
const handleQueryError = (event: IpcRendererEvent, err: { time: string; message: string }) => {
|
||||||
queryErrors.value.push(err);
|
queryErrors.value.push(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -169,7 +169,7 @@ const emit = defineEmits(['close']);
|
|||||||
const { trapRef } = useFocusTrap();
|
const { trapRef } = useFocusTrap();
|
||||||
|
|
||||||
const { getConnectionName } = useConnectionsStore();
|
const { getConnectionName } = useConnectionsStore();
|
||||||
const { connectionsOrder, connections } = storeToRefs(useConnectionsStore());
|
const { connectionsOrder, connections, customIcons } = storeToRefs(useConnectionsStore());
|
||||||
const localConnections = unproxify<ConnectionParams[]>(connections.value);
|
const localConnections = unproxify<ConnectionParams[]>(connections.value);
|
||||||
const localConnectionsOrder = unproxify<SidebarElement[]>(connectionsOrder.value);
|
const localConnectionsOrder = unproxify<SidebarElement[]>(connectionsOrder.value);
|
||||||
|
|
||||||
@@ -246,7 +246,8 @@ const exportData = () => {
|
|||||||
|
|
||||||
const exportObj = encrypt(JSON.stringify({
|
const exportObj = encrypt(JSON.stringify({
|
||||||
connections: filteredConnections,
|
connections: filteredConnections,
|
||||||
connectionsOrder: filteredOrders
|
connectionsOrder: filteredOrders,
|
||||||
|
customIcons: customIcons.value
|
||||||
}), options.value.passkey);
|
}), options.value.passkey);
|
||||||
|
|
||||||
// console.log(exportObj, JSON.parse(decrypt(exportObj, options.value.passkey)));
|
// console.log(exportObj, JSON.parse(decrypt(exportObj, options.value.passkey)));
|
||||||
|
@@ -103,7 +103,7 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import BaseIcon from '@/components/BaseIcon.vue';
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||||
import { unproxify } from '@/libs/unproxify';
|
import { unproxify } from '@/libs/unproxify';
|
||||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
import { CustomIcon, SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -156,6 +156,7 @@ const importData = () => {
|
|||||||
const importObj: {
|
const importObj: {
|
||||||
connections: ConnectionParams[];
|
connections: ConnectionParams[];
|
||||||
connectionsOrder: SidebarElement[];
|
connectionsOrder: SidebarElement[];
|
||||||
|
customIcons: CustomIcon[];
|
||||||
} = JSON.parse(decrypt(hash, options.value.passkey));
|
} = JSON.parse(decrypt(hash, options.value.passkey));
|
||||||
|
|
||||||
if (options.value.ignoreDuplicates) {
|
if (options.value.ignoreDuplicates) {
|
||||||
@@ -205,7 +206,6 @@ const importData = () => {
|
|||||||
.includes(c.uid) ||
|
.includes(c.uid) ||
|
||||||
(c.isFolder && c.connections.every(c => newConnectionsUid.includes(c))));
|
(c.isFolder && c.connections.every(c => newConnectionsUid.includes(c))));
|
||||||
}
|
}
|
||||||
|
|
||||||
importConnections(importObj);
|
importConnections(importObj);
|
||||||
|
|
||||||
addNotification({
|
addNotification({
|
||||||
@@ -215,6 +215,7 @@ const importData = () => {
|
|||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
addNotification({
|
addNotification({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: t('application.wrongImportPassword')
|
message: t('application.wrongImportPassword')
|
||||||
@@ -222,6 +223,7 @@ const importData = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
addNotification({
|
addNotification({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: t('application.wrongFileFormat')
|
message: t('application.wrongFileFormat')
|
||||||
|
@@ -341,7 +341,7 @@ onMounted(() => {
|
|||||||
lastTableFields.value = res.response.map((field: { name: string }) => field.name);
|
lastTableFields.value = res.response.map((field: { name: string }) => field.name);
|
||||||
editor.value.completers = [tableFieldsCompleter.value];
|
editor.value.completers = [tableFieldsCompleter.value];
|
||||||
editor.value.execCommand('startAutocomplete');
|
editor.value.execCommand('startAutocomplete');
|
||||||
}).catch(console.log);
|
}).catch(console.error);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
editor.value.completers = customCompleter.value;
|
editor.value.completers = customCompleter.value;
|
||||||
|
@@ -32,7 +32,7 @@
|
|||||||
v-if="note.type === 'query'"
|
v-if="note.type === 'query'"
|
||||||
ref="noteParagraph"
|
ref="noteParagraph"
|
||||||
class="tile-paragraph sql"
|
class="tile-paragraph sql"
|
||||||
v-html="highlight(highlightWord(note.note), {html: true})"
|
v-html="highlight(note.note, {html: true})"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
|
@@ -56,6 +56,7 @@
|
|||||||
>
|
>
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
:icon-name="camelize(element.icon)"
|
:icon-name="camelize(element.icon)"
|
||||||
|
:type="element.hasCustomIcon ? 'custom' : 'mdi'"
|
||||||
:size="36"
|
:size="36"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,6 +94,7 @@ import * as Draggable from 'vuedraggable';
|
|||||||
|
|
||||||
import BaseIcon from '@/components/BaseIcon.vue';
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
import SettingBarConnectionsFolder from '@/components/SettingBarConnectionsFolder.vue';
|
import SettingBarConnectionsFolder from '@/components/SettingBarConnectionsFolder.vue';
|
||||||
|
import { camelize } from '@/libs/camelize';
|
||||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
@@ -165,16 +167,6 @@ const getStatusBadge = (uid: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const camelize = (text: string) => {
|
|
||||||
const textArr = text.split('-');
|
|
||||||
for (let i = 0; i < textArr.length; i++) {
|
|
||||||
if (i === 0) continue;
|
|
||||||
textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return textArr.join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(() => dummyNested.value.length, () => {
|
watch(() => dummyNested.value.length, () => {
|
||||||
dummyNested.value = [];
|
dummyNested.value = [];
|
||||||
});
|
});
|
||||||
|
@@ -70,6 +70,7 @@
|
|||||||
>
|
>
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
:icon-name="camelize(getConnectionOrderByUid(element).icon)"
|
:icon-name="camelize(getConnectionOrderByUid(element).icon)"
|
||||||
|
:type="getConnectionOrderByUid(element).hasCustomIcon ? 'custom' : 'mdi'"
|
||||||
:size="36"
|
:size="36"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -43,11 +43,7 @@
|
|||||||
|
|
||||||
<div class="footer-right-elements">
|
<div class="footer-right-elements">
|
||||||
<ul class="footer-elements">
|
<ul class="footer-elements">
|
||||||
<li
|
<li class="footer-element footer-link" @click="toggleConsole()">
|
||||||
v-if="workspace?.connectionStatus === 'connected'"
|
|
||||||
class="footer-element footer-link"
|
|
||||||
@click="toggleConsole()"
|
|
||||||
>
|
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
icon-name="mdiConsoleLine"
|
icon-name="mdiConsoleLine"
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,440 +1,446 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="connection-panel">
|
<div class="connection-panel-wrapper p-relative">
|
||||||
<div class="panel">
|
<div class="connection-panel">
|
||||||
<div class="panel-nav">
|
<div class="panel">
|
||||||
<ul class="tab tab-block">
|
<div class="panel-nav">
|
||||||
<li
|
<ul class="tab tab-block">
|
||||||
class="tab-item c-hand"
|
<li
|
||||||
:class="{'active': selectedTab === 'general'}"
|
class="tab-item c-hand"
|
||||||
@click="selectTab('general')"
|
:class="{'active': selectedTab === 'general'}"
|
||||||
>
|
@click="selectTab('general')"
|
||||||
<a class="tab-link">{{ t('application.general') }}</a>
|
>
|
||||||
</li>
|
<a class="tab-link">{{ t('application.general') }}</a>
|
||||||
<li
|
</li>
|
||||||
v-if="clientCustomizations.sslConnection"
|
<li
|
||||||
class="tab-item c-hand"
|
v-if="clientCustomizations.sslConnection"
|
||||||
:class="{'active': selectedTab === 'ssl'}"
|
class="tab-item c-hand"
|
||||||
@click="selectTab('ssl')"
|
:class="{'active': selectedTab === 'ssl'}"
|
||||||
>
|
@click="selectTab('ssl')"
|
||||||
<a class="tab-link">{{ t('connection.ssl') }}</a>
|
>
|
||||||
</li>
|
<a class="tab-link">{{ t('connection.ssl') }}</a>
|
||||||
<li
|
</li>
|
||||||
v-if="clientCustomizations.sshConnection"
|
<li
|
||||||
class="tab-item c-hand"
|
v-if="clientCustomizations.sshConnection"
|
||||||
:class="{'active': selectedTab === 'ssh'}"
|
class="tab-item c-hand"
|
||||||
@click="selectTab('ssh')"
|
:class="{'active': selectedTab === 'ssh'}"
|
||||||
>
|
@click="selectTab('ssh')"
|
||||||
<a class="tab-link">{{ t('connection.sshTunnel') }}</a>
|
>
|
||||||
</li>
|
<a class="tab-link">{{ t('connection.sshTunnel') }}</a>
|
||||||
</ul>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
<div v-if="selectedTab === 'general'" class="panel-body py-0">
|
|
||||||
<div>
|
|
||||||
<form class="form-horizontal">
|
|
||||||
<fieldset class="m-0" :disabled="isBusy">
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.connectionName') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
ref="firstInput"
|
|
||||||
v-model="connection.name"
|
|
||||||
class="form-input"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.client') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<BaseSelect
|
|
||||||
v-model="connection.client"
|
|
||||||
:options="clients"
|
|
||||||
option-track-by="slug"
|
|
||||||
option-label="name"
|
|
||||||
class="form-select"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="connection.client === 'pg'" class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.connectionString') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
ref="pgString"
|
|
||||||
v-model="connection.pgConnString"
|
|
||||||
class="form-input"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.host"
|
|
||||||
class="form-input"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="clientCustomizations.fileConnection" class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('database.database') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<BaseUploadInput
|
|
||||||
:model-value="connection.databasePath"
|
|
||||||
:message="t('general.browse')"
|
|
||||||
@clear="pathClear('databasePath')"
|
|
||||||
@change="pathSelection($event, 'databasePath')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.port') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.port"
|
|
||||||
class="form-input"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="65535"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="clientCustomizations.database" class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('database.database') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.database"
|
|
||||||
class="form-input"
|
|
||||||
type="text"
|
|
||||||
:placeholder="clientCustomizations.defaultDatabase"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.user') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.user"
|
|
||||||
class="form-input"
|
|
||||||
type="text"
|
|
||||||
:disabled="connection.ask"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.password') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.password"
|
|
||||||
class="form-input"
|
|
||||||
type="password"
|
|
||||||
:disabled="connection.ask"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="clientCustomizations.connectionSchema" class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('database.schema') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.schema"
|
|
||||||
class="form-input"
|
|
||||||
type="text"
|
|
||||||
:placeholder="t('general.all')"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="clientCustomizations.readOnlyMode" class="form-group columns mb-0">
|
|
||||||
<div class="column col-5 col-sm-12" />
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<label class="form-checkbox form-inline my-0">
|
|
||||||
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns mb-0">
|
|
||||||
<div class="column col-5 col-sm-12" />
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<label class="form-checkbox form-inline my-0">
|
|
||||||
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="clientCustomizations.singleConnectionMode" class="form-group columns mb-0">
|
|
||||||
<div class="column col-5 col-sm-12" />
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<label class="form-checkbox form-inline my-0">
|
|
||||||
<input v-model="connection.singleConnectionMode" type="checkbox"><i class="form-icon" /> {{ t('connection.singleConnection') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-if="selectedTab === 'general'" class="panel-body py-0">
|
||||||
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
|
<div>
|
||||||
<div>
|
<form class="form-horizontal">
|
||||||
<form class="form-horizontal">
|
<fieldset class="m-0" :disabled="isBusy">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-5 col-sm-12">
|
<div class="column col-5 col-sm-12">
|
||||||
<label class="form-label cut-text">
|
<label class="form-label cut-text">{{ t('connection.connectionName') }}</label>
|
||||||
{{ t('connection.enableSsl') }}
|
</div>
|
||||||
</label>
|
<div class="column col-7 col-sm-12">
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
|
|
||||||
<input type="checkbox" :checked="connection.ssl">
|
|
||||||
<i class="form-icon" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<BaseUploadInput
|
|
||||||
:model-value="connection.key"
|
|
||||||
:message="t('general.browse')"
|
|
||||||
@clear="pathClear('key')"
|
|
||||||
@change="pathSelection($event, 'key')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.certificate') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<BaseUploadInput
|
|
||||||
:model-value="connection.cert"
|
|
||||||
:message="t('general.browse')"
|
|
||||||
@clear="pathClear('cert')"
|
|
||||||
@change="pathSelection($event, 'cert')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.caCertificate') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<BaseUploadInput
|
|
||||||
:model-value="connection.ca"
|
|
||||||
:message="t('general.browse')"
|
|
||||||
@clear="pathClear('ca')"
|
|
||||||
@change="pathSelection($event, 'ca')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.ciphers') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
ref="firstInput"
|
|
||||||
v-model="connection.ciphers"
|
|
||||||
class="form-input"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12" />
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<label class="form-checkbox form-inline">
|
|
||||||
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('connection.untrustedConnection') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
|
|
||||||
<div>
|
|
||||||
<form class="form-horizontal">
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">
|
|
||||||
{{ t('connection.enableSsh') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
|
|
||||||
<input type="checkbox" :checked="connection.ssh">
|
|
||||||
<i class="form-icon" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.sshHost"
|
|
||||||
class="form-input"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.user') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.sshUser"
|
|
||||||
class="form-input"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.password') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.sshPass"
|
|
||||||
class="form-input"
|
|
||||||
type="password"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.port') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.sshPort"
|
|
||||||
class="form-input"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="65535"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<BaseUploadInput
|
|
||||||
:model-value="connection.sshKey"
|
|
||||||
:message="t('general.browse')"
|
|
||||||
@clear="pathClear('sshKey')"
|
|
||||||
@change="pathSelection($event, 'sshKey')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.passphrase') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<input
|
|
||||||
v-model="connection.sshPassphrase"
|
|
||||||
class="form-input"
|
|
||||||
type="password"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group columns">
|
|
||||||
<div class="column col-5 col-sm-12">
|
|
||||||
<label class="form-label cut-text">{{ t('connection.keepAliveInterval') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="column col-7 col-sm-12">
|
|
||||||
<div class="input-group">
|
|
||||||
<input
|
<input
|
||||||
v-model="connection.sshKeepAliveInterval"
|
ref="firstInput"
|
||||||
|
v-model="connection.name"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.client') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<BaseSelect
|
||||||
|
v-model="connection.client"
|
||||||
|
:options="clients"
|
||||||
|
option-track-by="slug"
|
||||||
|
option-label="name"
|
||||||
|
class="form-select"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="connection.client === 'pg'" class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.connectionString') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
ref="pgString"
|
||||||
|
v-model="connection.pgConnString"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.host"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="clientCustomizations.fileConnection" class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('database.database') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<BaseUploadInput
|
||||||
|
:model-value="connection.databasePath"
|
||||||
|
:message="t('general.browse')"
|
||||||
|
@clear="pathClear('databasePath')"
|
||||||
|
@change="pathSelection($event, 'databasePath')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.port') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.port"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
|
max="65535"
|
||||||
>
|
>
|
||||||
<span class="input-group-addon">{{ t('general.seconds') }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="clientCustomizations.database" class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('database.database') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.database"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
:placeholder="clientCustomizations.defaultDatabase"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.user') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.user"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
:disabled="connection.ask"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.password') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.password"
|
||||||
|
class="form-input"
|
||||||
|
type="password"
|
||||||
|
:disabled="connection.ask"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="clientCustomizations.connectionSchema" class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('database.schema') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.schema"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
:placeholder="t('general.all')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="clientCustomizations.readOnlyMode" class="form-group columns mb-0">
|
||||||
|
<div class="column col-5 col-sm-12" />
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<label class="form-checkbox form-inline my-0">
|
||||||
|
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!clientCustomizations.fileConnection" class="form-group columns mb-0">
|
||||||
|
<div class="column col-5 col-sm-12" />
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<label class="form-checkbox form-inline my-0">
|
||||||
|
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="clientCustomizations.singleConnectionMode" class="form-group columns mb-0">
|
||||||
|
<div class="column col-5 col-sm-12" />
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<label class="form-checkbox form-inline my-0">
|
||||||
|
<input v-model="connection.singleConnectionMode" type="checkbox"><i class="form-icon" /> {{ t('connection.singleConnection') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
|
||||||
|
<div>
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">
|
||||||
|
{{ t('connection.enableSsl') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
|
||||||
|
<input type="checkbox" :checked="connection.ssl">
|
||||||
|
<i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
|
||||||
</form>
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<BaseUploadInput
|
||||||
|
:model-value="connection.key"
|
||||||
|
:message="t('general.browse')"
|
||||||
|
@clear="pathClear('key')"
|
||||||
|
@change="pathSelection($event, 'key')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.certificate') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<BaseUploadInput
|
||||||
|
:model-value="connection.cert"
|
||||||
|
:message="t('general.browse')"
|
||||||
|
@clear="pathClear('cert')"
|
||||||
|
@change="pathSelection($event, 'cert')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.caCertificate') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<BaseUploadInput
|
||||||
|
:model-value="connection.ca"
|
||||||
|
:message="t('general.browse')"
|
||||||
|
@clear="pathClear('ca')"
|
||||||
|
@change="pathSelection($event, 'ca')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.ciphers') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
ref="firstInput"
|
||||||
|
v-model="connection.ciphers"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12" />
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<label class="form-checkbox form-inline">
|
||||||
|
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('connection.untrustedConnection') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
|
||||||
|
<div>
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">
|
||||||
|
{{ t('connection.enableSsh') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
|
||||||
|
<input type="checkbox" :checked="connection.ssh">
|
||||||
|
<i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.sshHost"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.user') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.sshUser"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.password') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.sshPass"
|
||||||
|
class="form-input"
|
||||||
|
type="password"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.port') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.sshPort"
|
||||||
|
class="form-input"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<BaseUploadInput
|
||||||
|
:model-value="connection.sshKey"
|
||||||
|
:message="t('general.browse')"
|
||||||
|
@clear="pathClear('sshKey')"
|
||||||
|
@change="pathSelection($event, 'sshKey')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.passphrase') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<input
|
||||||
|
v-model="connection.sshPassphrase"
|
||||||
|
class="form-input"
|
||||||
|
type="password"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group columns">
|
||||||
|
<div class="column col-5 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ t('connection.keepAliveInterval') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-7 col-sm-12">
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
v-model="connection.sshKeepAliveInterval"
|
||||||
|
class="form-input"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
>
|
||||||
|
<span class="input-group-addon">{{ t('general.seconds') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer">
|
||||||
|
<button
|
||||||
|
id="connection-test"
|
||||||
|
class="btn btn-gray mr-2 d-flex"
|
||||||
|
:class="{'loading': isTesting}"
|
||||||
|
:disabled="isBusy"
|
||||||
|
@click="startTest"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiLightningBolt"
|
||||||
|
:size="24"
|
||||||
|
class="mr-1"
|
||||||
|
/>
|
||||||
|
{{ t('connection.testConnection') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="connection-save"
|
||||||
|
class="btn btn-primary mr-2 d-flex"
|
||||||
|
:disabled="isBusy"
|
||||||
|
@click="saveConnection"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
icon-name="mdiContentSave"
|
||||||
|
:size="24"
|
||||||
|
class="mr-1"
|
||||||
|
/>
|
||||||
|
{{ t('general.save') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-footer">
|
<ModalAskCredentials
|
||||||
<button
|
v-if="isAsking"
|
||||||
id="connection-test"
|
@close-asking="closeAsking"
|
||||||
class="btn btn-gray mr-2 d-flex"
|
@credentials="continueTest"
|
||||||
:class="{'loading': isTesting}"
|
/>
|
||||||
:disabled="isBusy"
|
|
||||||
@click="startTest"
|
|
||||||
>
|
|
||||||
<BaseIcon
|
|
||||||
icon-name="mdiLightningBolt"
|
|
||||||
:size="24"
|
|
||||||
class="mr-1"
|
|
||||||
/>
|
|
||||||
{{ t('connection.testConnection') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
id="connection-save"
|
|
||||||
class="btn btn-primary mr-2 d-flex"
|
|
||||||
:disabled="isBusy"
|
|
||||||
@click="saveConnection"
|
|
||||||
>
|
|
||||||
<BaseIcon
|
|
||||||
icon-name="mdiContentSave"
|
|
||||||
:size="24"
|
|
||||||
class="mr-1"
|
|
||||||
/>
|
|
||||||
{{ t('general.save') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ModalAskCredentials
|
|
||||||
v-if="isAsking"
|
|
||||||
@close-asking="closeAsking"
|
|
||||||
@credentials="continueTest"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<DebugConsole v-if="isConsoleOpen" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import customizations from 'common/customizations';
|
import customizations from 'common/customizations';
|
||||||
import { ConnectionParams } from 'common/interfaces/antares';
|
import { ConnectionParams } from 'common/interfaces/antares';
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { computed, Ref, ref, watch } from 'vue';
|
import { computed, Ref, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import BaseIcon from '@/components/BaseIcon.vue';
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
import BaseSelect from '@/components/BaseSelect.vue';
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||||
|
import DebugConsole from '@/components/DebugConsole.vue';
|
||||||
import ModalAskCredentials from '@/components/ModalAskCredentials.vue';
|
import ModalAskCredentials from '@/components/ModalAskCredentials.vue';
|
||||||
import Connection from '@/ipc-api/Connection';
|
import Connection from '@/ipc-api/Connection';
|
||||||
import { useConnectionsStore } from '@/stores/connections';
|
import { useConnectionsStore } from '@/stores/connections';
|
||||||
|
import { useConsoleStore } from '@/stores/console';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
@@ -443,6 +449,7 @@ const { t } = useI18n();
|
|||||||
const { addConnection } = useConnectionsStore();
|
const { addConnection } = useConnectionsStore();
|
||||||
const { addNotification } = useNotificationsStore();
|
const { addNotification } = useNotificationsStore();
|
||||||
const workspacesStore = useWorkspacesStore();
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
const { isConsoleOpen } = storeToRefs(useConsoleStore());
|
||||||
|
|
||||||
const { connectWorkspace, selectWorkspace } = workspacesStore;
|
const { connectWorkspace, selectWorkspace } = workspacesStore;
|
||||||
|
|
||||||
|
@@ -598,6 +598,22 @@ localConnection.value = JSON.parse(JSON.stringify(props.connection));
|
|||||||
min-width: 450px;
|
min-width: 450px;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
.panel-nav {
|
||||||
|
.tab-block {
|
||||||
|
background: transparent;
|
||||||
|
margin: 0.2rem 0 0.15rem 0;
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
background: transparent;
|
||||||
|
flex: 1 0 0;
|
||||||
|
|
||||||
|
> a {
|
||||||
|
padding: 8px 4px 6px 4px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.panel-body {
|
.panel-body {
|
||||||
flex: initial;
|
flex: initial;
|
||||||
}
|
}
|
||||||
|
@@ -111,6 +111,7 @@
|
|||||||
@close-context="closeDatabaseContext"
|
@close-context="closeDatabaseContext"
|
||||||
@open-create-table-tab="openCreateElementTab('table')"
|
@open-create-table-tab="openCreateElementTab('table')"
|
||||||
@open-create-view-tab="openCreateElementTab('view')"
|
@open-create-view-tab="openCreateElementTab('view')"
|
||||||
|
@open-create-materialized-view-tab="openCreateElementTab('materialized-view')"
|
||||||
@open-create-trigger-tab="openCreateElementTab('trigger')"
|
@open-create-trigger-tab="openCreateElementTab('trigger')"
|
||||||
@open-create-routine-tab="openCreateElementTab('routine')"
|
@open-create-routine-tab="openCreateElementTab('routine')"
|
||||||
@open-create-function-tab="openCreateElementTab('function')"
|
@open-create-function-tab="openCreateElementTab('function')"
|
||||||
@@ -141,10 +142,12 @@
|
|||||||
:selected-misc="selectedMisc"
|
:selected-misc="selectedMisc"
|
||||||
:selected-schema="selectedSchema"
|
:selected-schema="selectedSchema"
|
||||||
:context-event="miscContextEvent"
|
:context-event="miscContextEvent"
|
||||||
|
@open-create-view-tab="openCreateElementTab('view')"
|
||||||
|
@open-create-materializedview-tab="openCreateElementTab('materialized-view')"
|
||||||
@open-create-trigger-tab="openCreateElementTab('trigger')"
|
@open-create-trigger-tab="openCreateElementTab('trigger')"
|
||||||
|
@open-create-trigger-function-tab="openCreateElementTab('trigger-function')"
|
||||||
@open-create-routine-tab="openCreateElementTab('routine')"
|
@open-create-routine-tab="openCreateElementTab('routine')"
|
||||||
@open-create-function-tab="openCreateElementTab('function')"
|
@open-create-function-tab="openCreateElementTab('function')"
|
||||||
@open-create-trigger-function-tab="openCreateElementTab('trigger-function')"
|
|
||||||
@open-create-scheduler-tab="openCreateElementTab('scheduler')"
|
@open-create-scheduler-tab="openCreateElementTab('scheduler')"
|
||||||
@close-context="closeMiscFolderContext"
|
@close-context="closeMiscFolderContext"
|
||||||
@reload="refresh"
|
@reload="refresh"
|
||||||
|
@@ -3,6 +3,30 @@
|
|||||||
:context-event="props.contextEvent"
|
:context-event="props.contextEvent"
|
||||||
@close-context="closeContext"
|
@close-context="closeContext"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-if="props.selectedMisc === 'view'"
|
||||||
|
class="context-element"
|
||||||
|
@click="emit('open-create-view-tab')"
|
||||||
|
>
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiTableCog"
|
||||||
|
:size="18"
|
||||||
|
/> {{ t('database.createNewView') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="props.selectedMisc === 'materializedview'"
|
||||||
|
class="context-element"
|
||||||
|
@click="emit('open-create-materializedview-tab')"
|
||||||
|
>
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiTableCog"
|
||||||
|
:size="18"
|
||||||
|
/> {{ t('database.createNewMaterializedView') }}</span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="props.selectedMisc === 'trigger'"
|
v-if="props.selectedMisc === 'trigger'"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@@ -81,6 +105,8 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
|
'open-create-view-tab',
|
||||||
|
'open-create-materializedview-tab',
|
||||||
'open-create-trigger-tab',
|
'open-create-trigger-tab',
|
||||||
'open-create-routine-tab',
|
'open-create-routine-tab',
|
||||||
'open-create-function-tab',
|
'open-create-function-tab',
|
||||||
|
@@ -67,6 +67,104 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="filteredViews.length" class="database-misc">
|
||||||
|
<details class="accordion">
|
||||||
|
<summary
|
||||||
|
class="accordion-header misc-name"
|
||||||
|
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}"
|
||||||
|
@contextmenu.prevent="showMiscFolderContext($event, 'view')"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
class="misc-icon mr-1"
|
||||||
|
icon-name="mdiFolderEye"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
<BaseIcon
|
||||||
|
class="misc-icon open-folder mr-1"
|
||||||
|
icon-name="mdiFolderOpen"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
{{ t('database.view', 2) }}
|
||||||
|
</summary>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div>
|
||||||
|
<ul class="menu menu-nav pt-0">
|
||||||
|
<li
|
||||||
|
v-for="view of filteredViews"
|
||||||
|
:key="view.name"
|
||||||
|
class="menu-item"
|
||||||
|
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.view === view.name}"
|
||||||
|
@mousedown.left="selectTable({schema: database.name, table: view})"
|
||||||
|
@dblclick="openDataTab({schema: database.name, table: view})"
|
||||||
|
@contextmenu.prevent="showTableContext($event, view)"
|
||||||
|
>
|
||||||
|
<a class="table-name">
|
||||||
|
<div v-if="checkLoadingStatus(view.name, 'table')" class="icon loading mr-1" />
|
||||||
|
<BaseIcon
|
||||||
|
v-else
|
||||||
|
class="table-icon mr-1"
|
||||||
|
icon-name="mdiTableEye"
|
||||||
|
:size="18"
|
||||||
|
:style="`min-width: 18px`"
|
||||||
|
/>
|
||||||
|
<span v-html="highlightWord(view.name)" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="filteredMatViews.length && customizations.materializedViews" class="database-misc">
|
||||||
|
<details class="accordion">
|
||||||
|
<summary
|
||||||
|
class="accordion-header misc-name"
|
||||||
|
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}"
|
||||||
|
@contextmenu.prevent="showMiscFolderContext($event, 'materializedview')"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
class="misc-icon mr-1"
|
||||||
|
icon-name="mdiFolderEye"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
<BaseIcon
|
||||||
|
class="misc-icon open-folder mr-1"
|
||||||
|
icon-name="mdiFolderOpen"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
{{ t('database.materializedview', 2) }}
|
||||||
|
</summary>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div>
|
||||||
|
<ul class="menu menu-nav pt-0">
|
||||||
|
<li
|
||||||
|
v-for="view of filteredMatViews"
|
||||||
|
:key="view.name"
|
||||||
|
class="menu-item"
|
||||||
|
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.view === view.name}"
|
||||||
|
@mousedown.left="selectTable({schema: database.name, table: view})"
|
||||||
|
@dblclick="openDataTab({schema: database.name, table: view})"
|
||||||
|
@contextmenu.prevent="showTableContext($event, view)"
|
||||||
|
>
|
||||||
|
<a class="table-name">
|
||||||
|
<div v-if="checkLoadingStatus(view.name, 'table')" class="icon loading mr-1" />
|
||||||
|
<BaseIcon
|
||||||
|
v-else
|
||||||
|
class="table-icon mr-1"
|
||||||
|
icon-name="mdiTableEye"
|
||||||
|
:size="18"
|
||||||
|
:style="`min-width: 18px`"
|
||||||
|
/>
|
||||||
|
<span v-html="highlightWord(view.name)" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="filteredTriggers.length && customizations.triggers" class="database-misc">
|
<div v-if="filteredTriggers.length && customizations.triggers" class="database-misc">
|
||||||
<details class="accordion">
|
<details class="accordion">
|
||||||
<summary
|
<summary
|
||||||
@@ -380,11 +478,25 @@ const searchTerm = computed(() => {
|
|||||||
|
|
||||||
const filteredTables = computed(() => {
|
const filteredTables = computed(() => {
|
||||||
if (props.searchMethod === 'elements')
|
if (props.searchMethod === 'elements')
|
||||||
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0);
|
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0 && table.type === 'table');
|
||||||
else
|
else
|
||||||
return props.database.tables;
|
return props.database.tables;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filteredViews = computed(() => {
|
||||||
|
if (props.searchMethod === 'elements')
|
||||||
|
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0 && table.type === 'view');
|
||||||
|
else
|
||||||
|
return props.database.tables.filter(table => table.type === 'view');
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredMatViews = computed(() => {
|
||||||
|
if (props.searchMethod === 'elements')
|
||||||
|
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0 && table.type === 'materializedview');
|
||||||
|
else
|
||||||
|
return props.database.tables.filter(table => table.type === 'materializedview');
|
||||||
|
});
|
||||||
|
|
||||||
const filteredTriggers = computed(() => {
|
const filteredTriggers = computed(() => {
|
||||||
if (props.searchMethod === 'elements')
|
if (props.searchMethod === 'elements')
|
||||||
return props.database.triggers.filter(trigger => trigger.name.search(searchTerm.value) >= 0);
|
return props.database.triggers.filter(trigger => trigger.name.search(searchTerm.value) >= 0);
|
||||||
|
@@ -40,6 +40,18 @@
|
|||||||
:size="18"
|
:size="18"
|
||||||
/> {{ t('database.view') }}</span>
|
/> {{ t('database.view') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="workspace.customizations.materializedViewAdd"
|
||||||
|
class="context-element"
|
||||||
|
@click="openCreateMaterializedViewTab"
|
||||||
|
>
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiTableEye"
|
||||||
|
:size="18"
|
||||||
|
/> {{ t('database.materializedview') }}</span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="workspace.customizations.triggerAdd"
|
v-if="workspace.customizations.triggerAdd"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@@ -221,6 +233,7 @@ const props = defineProps({
|
|||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'open-create-table-tab',
|
'open-create-table-tab',
|
||||||
'open-create-view-tab',
|
'open-create-view-tab',
|
||||||
|
'open-create-materialized-view-tab',
|
||||||
'open-create-trigger-tab',
|
'open-create-trigger-tab',
|
||||||
'open-create-routine-tab',
|
'open-create-routine-tab',
|
||||||
'open-create-function-tab',
|
'open-create-function-tab',
|
||||||
@@ -257,6 +270,10 @@ const openCreateViewTab = () => {
|
|||||||
emit('open-create-view-tab');
|
emit('open-create-view-tab');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openCreateMaterializedViewTab = () => {
|
||||||
|
emit('open-create-materialized-view-tab');
|
||||||
|
};
|
||||||
|
|
||||||
const openCreateTriggerTab = () => {
|
const openCreateTriggerTab = () => {
|
||||||
emit('open-create-trigger-tab');
|
emit('open-create-trigger-tab');
|
||||||
};
|
};
|
||||||
|
@@ -47,6 +47,18 @@
|
|||||||
:size="18"
|
:size="18"
|
||||||
/> {{ t('application.settings') }}</span>
|
/> {{ t('application.settings') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="selectedTable && selectedTable.type === 'materializedview' && customizations.materializedViewSettings"
|
||||||
|
class="context-element"
|
||||||
|
@click="openMaterializedViewSettingTab"
|
||||||
|
>
|
||||||
|
<span class="d-flex">
|
||||||
|
<BaseIcon
|
||||||
|
class="text-light mt-1 mr-1"
|
||||||
|
icon-name="mdiWrenchCog"
|
||||||
|
:size="18"
|
||||||
|
/> {{ t('application.settings') }}</span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="selectedTable && selectedTable.type === 'table' && customizations.tableDuplicate"
|
v-if="selectedTable && selectedTable.type === 'table' && customizations.tableDuplicate"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@@ -238,6 +250,23 @@ const openViewSettingTab = () => {
|
|||||||
closeContext();
|
closeContext();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openMaterializedViewSettingTab = () => {
|
||||||
|
newTab({
|
||||||
|
uid: selectedWorkspace.value,
|
||||||
|
elementType: 'table',
|
||||||
|
elementName: props.selectedTable.name,
|
||||||
|
schema: props.selectedSchema,
|
||||||
|
type: 'materialized-view-props'
|
||||||
|
});
|
||||||
|
|
||||||
|
changeBreadcrumbs({
|
||||||
|
schema: props.selectedSchema,
|
||||||
|
view: props.selectedTable.name
|
||||||
|
});
|
||||||
|
|
||||||
|
closeContext();
|
||||||
|
};
|
||||||
|
|
||||||
const duplicateTable = () => {
|
const duplicateTable = () => {
|
||||||
emit('duplicate-table', { schema: props.selectedSchema, table: props.selectedTable });
|
emit('duplicate-table', { schema: props.selectedSchema, table: props.selectedTable });
|
||||||
};
|
};
|
||||||
|
@@ -1,189 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
ref="wrapper"
|
|
||||||
class="query-console-wrapper"
|
|
||||||
@mouseenter="isHover = true"
|
|
||||||
@mouseleave="isHover = false"
|
|
||||||
>
|
|
||||||
<div ref="resizer" class="query-console-resizer" />
|
|
||||||
<div
|
|
||||||
id="query-console"
|
|
||||||
ref="queryConsole"
|
|
||||||
class="query-console column col-12"
|
|
||||||
:style="{height: localHeight ? localHeight+'px' : ''}"
|
|
||||||
>
|
|
||||||
<div class="query-console-header">
|
|
||||||
<div>{{ t('application.console') }}</div>
|
|
||||||
<button class="btn btn-clear mr-1" @click="resizeConsole(0)" />
|
|
||||||
</div>
|
|
||||||
<div ref="queryConsoleBody" class="query-console-body">
|
|
||||||
<div
|
|
||||||
v-for="(wLog, i) in workspaceLogs"
|
|
||||||
:key="i"
|
|
||||||
class="query-console-log"
|
|
||||||
tabindex="0"
|
|
||||||
@contextmenu.prevent="contextMenu($event, wLog)"
|
|
||||||
>
|
|
||||||
<span class="type-datetime">{{ moment(wLog.date).format('HH:mm:ss') }}</span>: <code class="query-console-log-sql" v-html="highlight(wLog.sql, {html: true})" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<BaseContextMenu
|
|
||||||
v-if="isContext"
|
|
||||||
:context-event="contextEvent"
|
|
||||||
@close-context="isContext = false"
|
|
||||||
>
|
|
||||||
<div class="context-element" @click="copyQuery">
|
|
||||||
<span class="d-flex">
|
|
||||||
<BaseIcon
|
|
||||||
class="text-light mt-1 mr-1"
|
|
||||||
icon-name="mdiContentCopy"
|
|
||||||
:size="18"
|
|
||||||
/> {{ t('general.copy') }}</span>
|
|
||||||
</div>
|
|
||||||
</BaseContextMenu>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import * as moment from 'moment';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import { highlight } from 'sql-highlight';
|
|
||||||
import { computed, nextTick, onMounted, Ref, ref, watch } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
|
||||||
import BaseIcon from '@/components/BaseIcon.vue';
|
|
||||||
import { copyText } from '@/libs/copyText';
|
|
||||||
import { useConsoleStore } from '@/stores/console';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const consoleStore = useConsoleStore();
|
|
||||||
|
|
||||||
const { resizeConsole, getLogsByWorkspace } = consoleStore;
|
|
||||||
const { consoleHeight } = storeToRefs(consoleStore);
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
uid: String
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper: Ref<HTMLInputElement> = ref(null);
|
|
||||||
const queryConsole: Ref<HTMLInputElement> = ref(null);
|
|
||||||
const queryConsoleBody: Ref<HTMLInputElement> = ref(null);
|
|
||||||
const resizer: Ref<HTMLInputElement> = ref(null);
|
|
||||||
const localHeight = ref(250);
|
|
||||||
const isHover = ref(false);
|
|
||||||
const isContext = ref(false);
|
|
||||||
const contextQuery: Ref<string> = ref(null);
|
|
||||||
const contextEvent: Ref<MouseEvent> = ref(null);
|
|
||||||
|
|
||||||
const resize = (e: MouseEvent) => {
|
|
||||||
const el = queryConsole.value;
|
|
||||||
let consoleHeight = el.getBoundingClientRect().bottom - e.pageY;
|
|
||||||
if (consoleHeight > 400) consoleHeight = 400;
|
|
||||||
localHeight.value = consoleHeight;
|
|
||||||
};
|
|
||||||
|
|
||||||
const workspaceLogs = computed(() => {
|
|
||||||
return getLogsByWorkspace(props.uid);
|
|
||||||
});
|
|
||||||
|
|
||||||
const stopResize = () => {
|
|
||||||
if (localHeight.value < 0) localHeight.value = 0;
|
|
||||||
resizeConsole(localHeight.value);
|
|
||||||
window.removeEventListener('mousemove', resize);
|
|
||||||
window.removeEventListener('mouseup', stopResize);
|
|
||||||
};
|
|
||||||
|
|
||||||
const contextMenu = (event: MouseEvent, wLog: {date: Date; sql: string}) => {
|
|
||||||
contextEvent.value = event;
|
|
||||||
contextQuery.value = wLog.sql;
|
|
||||||
isContext.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const copyQuery = () => {
|
|
||||||
copyText(contextQuery.value);
|
|
||||||
isContext.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(workspaceLogs, async () => {
|
|
||||||
if (!isHover.value) {
|
|
||||||
await nextTick();
|
|
||||||
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
localHeight.value = consoleHeight.value;
|
|
||||||
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
resizer.value.addEventListener('mousedown', (e: MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
window.addEventListener('mousemove', resize);
|
|
||||||
window.addEventListener('mouseup', stopResize);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.query-console-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
z-index: 9;
|
|
||||||
margin-top: auto;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
.query-console-resizer {
|
|
||||||
height: 4px;
|
|
||||||
top: -1px;
|
|
||||||
width: 100%;
|
|
||||||
cursor: ns-resize;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 99;
|
|
||||||
transition: background 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--primary-color-dark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-console {
|
|
||||||
padding: 0;
|
|
||||||
padding-bottom: $footer-height;
|
|
||||||
|
|
||||||
.query-console-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 4px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-console-body {
|
|
||||||
overflow: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-height: 100%;
|
|
||||||
padding: 0 6px 3px;
|
|
||||||
|
|
||||||
.query-console-log {
|
|
||||||
padding: 1px 3px;
|
|
||||||
margin: 1px 0;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
|
|
||||||
.query-console-log-sql {
|
|
||||||
font-size: 95%;
|
|
||||||
opacity: 0.8;
|
|
||||||
font-weight: 700;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
user-select: text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
293
src/renderer/components/WorkspaceTabNewMaterializedView.vue
Normal file
293
src/renderer/components/WorkspaceTabNewMaterializedView.vue
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
|
||||||
|
<div class="workspace-query-runner column col-12">
|
||||||
|
<div class="workspace-query-runner-footer">
|
||||||
|
<div class="workspace-query-buttons">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
:disabled="!isChanged"
|
||||||
|
:class="{'loading':isSaving}"
|
||||||
|
@click="saveChanges"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
class="mr-1"
|
||||||
|
icon-name="mdiContentSave"
|
||||||
|
:size="24"
|
||||||
|
/>
|
||||||
|
<span>{{ t('general.save') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:disabled="!isChanged"
|
||||||
|
class="btn btn-link btn-sm mr-0"
|
||||||
|
:title="t('database.clearChanges')"
|
||||||
|
@click="clearChanges"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
class="mr-1"
|
||||||
|
icon-name="mdiDeleteSweep"
|
||||||
|
:size="24"
|
||||||
|
/>
|
||||||
|
<span>{{ t('general.clear') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="workspace-query-info">
|
||||||
|
<div class="d-flex" :title="t('database.schema')">
|
||||||
|
<BaseIcon
|
||||||
|
class="mt-1 mr-1"
|
||||||
|
icon-name="mdiDatabase"
|
||||||
|
:size="18"
|
||||||
|
/><b>{{ schema }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column col-auto">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{{ t('general.name') }}</label>
|
||||||
|
<input
|
||||||
|
ref="firstInput"
|
||||||
|
v-model="localView.name"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-auto">
|
||||||
|
<div v-if="workspace.customizations.definer" class="form-group">
|
||||||
|
<label class="form-label">{{ t('database.definer') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="localView.definer"
|
||||||
|
:options="users"
|
||||||
|
:option-label="(user: any) => user.value === '' ? t('database.currentUser') : `${user.name}@${user.host}`"
|
||||||
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
|
class="form-select"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-auto mr-2">
|
||||||
|
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
|
||||||
|
<label class="form-label">{{ t('database.sqlSecurity') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="localView.security"
|
||||||
|
:options="['DEFINER', 'INVOKER']"
|
||||||
|
class="form-select"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-auto mr-2">
|
||||||
|
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
|
||||||
|
<label class="form-label">{{ t('database.algorithm') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="localView.algorithm"
|
||||||
|
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
|
||||||
|
class="form-select"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{{ t('database.updateOption') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="localView.updateOption"
|
||||||
|
:option-track-by="(user: any) => user.value"
|
||||||
|
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
|
||||||
|
class="form-select"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
|
<BaseLoader v-if="isLoading" />
|
||||||
|
<label class="form-label ml-2">{{ t('database.selectStatement') }}</label>
|
||||||
|
<QueryEditor
|
||||||
|
v-show="isSelected"
|
||||||
|
ref="queryEditor"
|
||||||
|
v-model="localView.sql"
|
||||||
|
:workspace="workspace"
|
||||||
|
:schema="schema"
|
||||||
|
:height="editorHeight"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Ace } from 'ace-builds';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
|
import Views from '@/ipc-api/Views';
|
||||||
|
import { useConsoleStore } from '@/stores/console';
|
||||||
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
tab: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const { addNotification } = useNotificationsStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
const { consoleHeight } = storeToRefs(useConsoleStore());
|
||||||
|
|
||||||
|
const {
|
||||||
|
getWorkspace,
|
||||||
|
refreshStructure,
|
||||||
|
setUnsavedChanges,
|
||||||
|
changeBreadcrumbs,
|
||||||
|
newTab,
|
||||||
|
removeTab
|
||||||
|
} = workspacesStore;
|
||||||
|
|
||||||
|
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||||
|
const firstInput: Ref<HTMLInputElement> = ref(null);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const isSaving = ref(false);
|
||||||
|
const originalView = ref(null);
|
||||||
|
const localView = ref(null);
|
||||||
|
const editorHeight = ref(300);
|
||||||
|
|
||||||
|
const workspace = computed(() => getWorkspace(props.connection.uid));
|
||||||
|
const isChanged = computed(() => JSON.stringify(originalView.value) !== JSON.stringify(localView.value));
|
||||||
|
const isDefinerInUsers = computed(() => originalView.value ? workspace.value.users.some(user => originalView.value.definer === `\`${user.name}\`@\`${user.host}\``) : true);
|
||||||
|
|
||||||
|
const users = computed(() => {
|
||||||
|
const users = [{ value: '' }, ...workspace.value.users];
|
||||||
|
if (!isDefinerInUsers.value) {
|
||||||
|
const [name, host] = originalView.value.definer.replaceAll('`', '').split('@');
|
||||||
|
users.unshift({ name, host });
|
||||||
|
}
|
||||||
|
|
||||||
|
return users;
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveChanges = async () => {
|
||||||
|
if (isSaving.value) return;
|
||||||
|
isSaving.value = true;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
...localView.value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Views.createMaterializedView(params);
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
await refreshStructure(props.connection.uid);
|
||||||
|
|
||||||
|
newTab({
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
elementName: localView.value.name,
|
||||||
|
elementType: 'materializedview',
|
||||||
|
type: 'materialized-view-props'
|
||||||
|
});
|
||||||
|
|
||||||
|
removeTab({ uid: props.connection.uid, tab: props.tab.uid });
|
||||||
|
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
isSaving.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearChanges = () => {
|
||||||
|
localView.value = JSON.parse(JSON.stringify(originalView.value));
|
||||||
|
queryEditor.value.editor.session.setValue(localView.value.sql);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeQueryEditor = () => {
|
||||||
|
if (queryEditor.value) {
|
||||||
|
let sizeToSubtract = 0;
|
||||||
|
const footer = document.getElementById('footer');
|
||||||
|
if (footer) sizeToSubtract += footer.offsetHeight;
|
||||||
|
sizeToSubtract += consoleHeight.value;
|
||||||
|
|
||||||
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - sizeToSubtract;
|
||||||
|
editorHeight.value = size;
|
||||||
|
queryEditor.value.editor.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveContentListener = () => {
|
||||||
|
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
|
||||||
|
if (props.isSelected && !hasModalOpen && isChanged.value)
|
||||||
|
saveChanges();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => props.isSelected, (val) => {
|
||||||
|
if (val) {
|
||||||
|
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resizeQueryEditor();
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(isChanged, (val) => {
|
||||||
|
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(consoleHeight, () => {
|
||||||
|
resizeQueryEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
originalView.value = {
|
||||||
|
algorithm: 'UNDEFINED',
|
||||||
|
definer: '',
|
||||||
|
security: 'DEFINER',
|
||||||
|
updateOption: '',
|
||||||
|
sql: '',
|
||||||
|
name: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
localView.value = JSON.parse(JSON.stringify(originalView.value));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resizeQueryEditor();
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.isSelected)
|
||||||
|
changeBreadcrumbs({ schema: props.schema });
|
||||||
|
|
||||||
|
ipcRenderer.on('save-content', saveContentListener);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
firstInput.value.focus();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
window.addEventListener('resize', resizeQueryEditor);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resizeQueryEditor);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ipcRenderer.removeListener('save-content', saveContentListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
316
src/renderer/components/WorkspaceTabPropsMaterializedView.vue
Normal file
316
src/renderer/components/WorkspaceTabPropsMaterializedView.vue
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
|
||||||
|
<div class="workspace-query-runner column col-12">
|
||||||
|
<div class="workspace-query-runner-footer">
|
||||||
|
<div class="workspace-query-buttons">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
:disabled="!isChanged"
|
||||||
|
:class="{'loading':isSaving}"
|
||||||
|
@click="saveChanges"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
class="mr-1"
|
||||||
|
icon-name="mdiContentSave"
|
||||||
|
:size="24"
|
||||||
|
/>
|
||||||
|
<span>{{ t('general.save') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:disabled="!isChanged"
|
||||||
|
class="btn btn-link btn-sm mr-0"
|
||||||
|
:title="t('database.clearChanges')"
|
||||||
|
@click="clearChanges"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
class="mr-1"
|
||||||
|
icon-name="mdiDeleteSweep"
|
||||||
|
:size="24"
|
||||||
|
/>
|
||||||
|
<span>{{ t('general.clear') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="workspace-query-info">
|
||||||
|
<div class="d-flex" :title="t('database.schema')">
|
||||||
|
<BaseIcon
|
||||||
|
class="mt-1 mr-1"
|
||||||
|
icon-name="mdiDatabase"
|
||||||
|
:size="18"
|
||||||
|
/><b>{{ schema }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column col-auto">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{{ t('general.name') }}</label>
|
||||||
|
<input
|
||||||
|
v-model="localView.name"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-auto">
|
||||||
|
<div v-if="workspace.customizations.definer" class="form-group">
|
||||||
|
<label class="form-label">{{ t('database.definer') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="localView.definer"
|
||||||
|
:options="users"
|
||||||
|
:option-label="(user: any) => user.value === '' ? t('database.currentUser') : `${user.name}@${user.host}`"
|
||||||
|
:option-track-by="(user: any) => user.value === '' ? '' : `\`${user.name}\`@\`${user.host}\``"
|
||||||
|
class="form-select"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-auto mr-2">
|
||||||
|
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
|
||||||
|
<label class="form-label">{{ t('database.sqlSecurity') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="localView.security"
|
||||||
|
:options="['DEFINER', 'INVOKER']"
|
||||||
|
class="form-select"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-auto mr-2">
|
||||||
|
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
|
||||||
|
<label class="form-label">{{ t('database.algorithm') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="localView.algorithm"
|
||||||
|
:options="['UNDEFINED', 'MERGE', 'TEMPTABLE']"
|
||||||
|
class="form-select"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{{ t('database.updateOption') }}</label>
|
||||||
|
<BaseSelect
|
||||||
|
v-model="localView.updateOption"
|
||||||
|
:option-track-by="(user: any) => user.value"
|
||||||
|
:options="[{label: 'None', value: ''}, {label: 'CASCADED', value: 'CASCADED'}, {label: 'LOCAL', value: 'LOCAL'}]"
|
||||||
|
class="form-select"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||||
|
<BaseLoader v-if="isLoading" />
|
||||||
|
<label class="form-label ml-2">{{ t('database.selectStatement') }}</label>
|
||||||
|
<QueryEditor
|
||||||
|
v-show="isSelected"
|
||||||
|
ref="queryEditor"
|
||||||
|
v-model="localView.sql"
|
||||||
|
:workspace="workspace"
|
||||||
|
:schema="schema"
|
||||||
|
:height="editorHeight"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Ace } from 'ace-builds';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import { Component, computed, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import BaseIcon from '@/components/BaseIcon.vue';
|
||||||
|
import BaseLoader from '@/components/BaseLoader.vue';
|
||||||
|
import BaseSelect from '@/components/BaseSelect.vue';
|
||||||
|
import QueryEditor from '@/components/QueryEditor.vue';
|
||||||
|
import Views from '@/ipc-api/Views';
|
||||||
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tabUid: String,
|
||||||
|
connection: Object,
|
||||||
|
isSelected: Boolean,
|
||||||
|
schema: String,
|
||||||
|
view: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const { addNotification } = useNotificationsStore();
|
||||||
|
const workspacesStore = useWorkspacesStore();
|
||||||
|
|
||||||
|
const {
|
||||||
|
getWorkspace,
|
||||||
|
refreshStructure,
|
||||||
|
renameTabs,
|
||||||
|
changeBreadcrumbs,
|
||||||
|
setUnsavedChanges
|
||||||
|
} = workspacesStore;
|
||||||
|
|
||||||
|
const queryEditor: Ref<Component & {editor: Ace.Editor; $el: HTMLElement}> = ref(null);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const isSaving = ref(false);
|
||||||
|
const originalView = ref(null);
|
||||||
|
const localView = ref(null);
|
||||||
|
const editorHeight = ref(300);
|
||||||
|
const lastView = ref(null);
|
||||||
|
const sqlProxy = ref('');
|
||||||
|
|
||||||
|
const workspace = computed(() => getWorkspace(props.connection.uid));
|
||||||
|
const isChanged = computed(() => JSON.stringify(originalView.value) !== JSON.stringify(localView.value));
|
||||||
|
const isDefinerInUsers = computed(() => originalView.value ? workspace.value.users.some(user => originalView.value.definer === `\`${user.name}\`@\`${user.host}\``) : true);
|
||||||
|
|
||||||
|
const users = computed(() => {
|
||||||
|
const users = [{ value: '' }, ...workspace.value.users];
|
||||||
|
if (!isDefinerInUsers.value) {
|
||||||
|
const [name, host] = originalView.value.definer.replaceAll('`', '').split('@');
|
||||||
|
users.unshift({ name, host });
|
||||||
|
}
|
||||||
|
|
||||||
|
return users;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getViewData = async () => {
|
||||||
|
if (!props.view) return;
|
||||||
|
isLoading.value = true;
|
||||||
|
localView.value = { sql: '' };
|
||||||
|
lastView.value = props.view;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
view: props.view
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Views.getMaterializedViewInformations(params);
|
||||||
|
if (status === 'success') {
|
||||||
|
originalView.value = response;
|
||||||
|
localView.value = JSON.parse(JSON.stringify(originalView.value));
|
||||||
|
sqlProxy.value = localView.value.sql;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeQueryEditor();
|
||||||
|
isLoading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveChanges = async () => {
|
||||||
|
if (isSaving.value) return;
|
||||||
|
isSaving.value = true;
|
||||||
|
const params = {
|
||||||
|
uid: props.connection.uid,
|
||||||
|
view: {
|
||||||
|
...localView.value,
|
||||||
|
schema: props.schema,
|
||||||
|
oldName: originalView.value.name
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Views.alterMaterializedView(params);
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
const oldName = originalView.value.name;
|
||||||
|
|
||||||
|
await refreshStructure(props.connection.uid);
|
||||||
|
|
||||||
|
if (oldName !== localView.value.name) {
|
||||||
|
renameTabs({
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
elementName: oldName,
|
||||||
|
elementNewName: localView.value.name,
|
||||||
|
elementType: 'materializedview'
|
||||||
|
});
|
||||||
|
|
||||||
|
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
getViewData();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
isSaving.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearChanges = () => {
|
||||||
|
localView.value = JSON.parse(JSON.stringify(originalView.value));
|
||||||
|
queryEditor.value.editor.session.setValue(localView.value.sql);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeQueryEditor = () => {
|
||||||
|
if (queryEditor.value) {
|
||||||
|
const footer = document.getElementById('footer');
|
||||||
|
const size = window.innerHeight - queryEditor.value.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
|
editorHeight.value = size;
|
||||||
|
queryEditor.value.editor.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveContentListener = () => {
|
||||||
|
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
|
||||||
|
if (props.isSelected && !hasModalOpen && isChanged.value)
|
||||||
|
saveChanges();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => props.schema, async () => {
|
||||||
|
if (props.isSelected) {
|
||||||
|
await getViewData();
|
||||||
|
queryEditor.value.editor.session.setValue(localView.value.sql);
|
||||||
|
lastView.value = props.view;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.view, async () => {
|
||||||
|
if (props.isSelected) {
|
||||||
|
await getViewData();
|
||||||
|
queryEditor.value.editor.session.setValue(localView.value.sql);
|
||||||
|
lastView.value = props.view;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.isSelected, (val) => {
|
||||||
|
if (val) {
|
||||||
|
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resizeQueryEditor();
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(isChanged, (val) => {
|
||||||
|
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||||
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await getViewData();
|
||||||
|
queryEditor.value.editor.session.setValue(localView.value.sql);
|
||||||
|
})();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', resizeQueryEditor);
|
||||||
|
|
||||||
|
ipcRenderer.on('save-content', saveContentListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resizeQueryEditor);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ipcRenderer.removeListener('save-content', saveContentListener);
|
||||||
|
});
|
||||||
|
</script>
|
@@ -253,6 +253,7 @@
|
|||||||
v-if="results"
|
v-if="results"
|
||||||
v-show="!isQuering"
|
v-show="!isQuering"
|
||||||
ref="queryTable"
|
ref="queryTable"
|
||||||
|
:is-quering="isQuering"
|
||||||
:results="results"
|
:results="results"
|
||||||
:tab-uid="tab.uid"
|
:tab-uid="tab.uid"
|
||||||
:conn-uid="connection.uid"
|
:conn-uid="connection.uid"
|
||||||
@@ -715,7 +716,7 @@ const openFile = async () => {
|
|||||||
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql', 'txt'] }] });
|
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql', 'txt'] }] });
|
||||||
if (result && !result.canceled) {
|
if (result && !result.canceled) {
|
||||||
const file = result.filePaths[0];
|
const file = result.filePaths[0];
|
||||||
const content = await Application.readFile(file);
|
const content = await Application.readFile({ filePath: file, encoding: 'utf-8' });
|
||||||
const fileName = file.split('/').pop().split('\\').pop();
|
const fileName = file.split('/').pop().split('\\').pop();
|
||||||
if (props.tab.filePath && props.tab.filePath !== file) {
|
if (props.tab.filePath && props.tab.filePath !== file) {
|
||||||
newTab({
|
newTab({
|
||||||
@@ -755,7 +756,7 @@ const saveFile = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadFileContent = async (file: string) => {
|
const loadFileContent = async (file: string) => {
|
||||||
const content = await Application.readFile(file);
|
const content = await Application.readFile({ filePath: file, encoding: 'utf-8' });
|
||||||
query.value = content;
|
query.value = content;
|
||||||
lastSavedQuery.value = content;
|
lastSavedQuery.value = content;
|
||||||
};
|
};
|
||||||
|
@@ -291,6 +291,7 @@ const { consoleHeight } = storeToRefs(consoleStore);
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
results: Array as Prop<QueryResult[]>,
|
results: Array as Prop<QueryResult[]>,
|
||||||
connUid: String,
|
connUid: String,
|
||||||
|
isQuering: Boolean,
|
||||||
mode: String as Prop<'table' | 'query'>,
|
mode: String as Prop<'table' | 'query'>,
|
||||||
page: {
|
page: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -790,7 +791,7 @@ const contextMenu = (event: MouseEvent, cell: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sort = (field: TableField) => {
|
const sort = (field: TableField) => {
|
||||||
if (!isSortable.value) return;
|
if (!isSortable.value || props.isQuering) return;
|
||||||
|
|
||||||
selectedRows.value = [];
|
selectedRows.value = [];
|
||||||
let fieldName = field.name;
|
let fieldName = field.name;
|
||||||
|
@@ -202,6 +202,7 @@
|
|||||||
v-if="results"
|
v-if="results"
|
||||||
ref="queryTable"
|
ref="queryTable"
|
||||||
:results="results"
|
:results="results"
|
||||||
|
:is-quering="isQuering"
|
||||||
:page="page"
|
:page="page"
|
||||||
:tab-uid="tabUid"
|
:tab-uid="tabUid"
|
||||||
:conn-uid="connection.uid"
|
:conn-uid="connection.uid"
|
||||||
@@ -305,7 +306,7 @@ const customizations = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isTable = computed(() => {
|
const isTable = computed(() => {
|
||||||
return !workspace.value.breadcrumbs.view;
|
return props.elementType === 'table';
|
||||||
});
|
});
|
||||||
|
|
||||||
const fields = computed(() => {
|
const fields = computed(() => {
|
||||||
@@ -441,6 +442,25 @@ const resizeScroller = () => {
|
|||||||
const updateFilters = (clausoles: TableFilterClausole[]) => {
|
const updateFilters = (clausoles: TableFilterClausole[]) => {
|
||||||
filters.value = clausoles;
|
filters.value = clausoles;
|
||||||
results.value = [];
|
results.value = [];
|
||||||
|
|
||||||
|
const permanentTabs = {
|
||||||
|
table: 'data',
|
||||||
|
view: 'data',
|
||||||
|
trigger: 'trigger-props',
|
||||||
|
triggerFunction: 'trigger-function-props',
|
||||||
|
function: 'function-props',
|
||||||
|
routine: 'routine-props',
|
||||||
|
procedure: 'routine-props',
|
||||||
|
scheduler: 'scheduler-props'
|
||||||
|
} as Record<string, string>;
|
||||||
|
|
||||||
|
newTab({
|
||||||
|
uid: props.connection.uid,
|
||||||
|
schema: props.schema,
|
||||||
|
elementName: props.table,
|
||||||
|
type: permanentTabs[props.elementType],
|
||||||
|
elementType: props.elementType
|
||||||
|
});
|
||||||
getTableData();
|
getTableData();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -480,8 +500,8 @@ const openTableSettingTab = () => {
|
|||||||
uid: workspace.value.uid,
|
uid: workspace.value.uid,
|
||||||
elementName: props.table,
|
elementName: props.table,
|
||||||
schema: props.schema,
|
schema: props.schema,
|
||||||
type: isTable.value ? 'table-props' : 'view-props',
|
type: isTable.value ? 'table-props' : props.elementType === 'view' ? 'view-props' : 'materialized-view-props',
|
||||||
elementType: isTable.value ? 'table' : 'view'
|
elementType: props.elementType
|
||||||
});
|
});
|
||||||
|
|
||||||
changeBreadcrumbs({
|
changeBreadcrumbs({
|
||||||
|
@@ -140,6 +140,7 @@ export const enUS = {
|
|||||||
total: 'Total',
|
total: 'Total',
|
||||||
table: 'Table | Tables',
|
table: 'Table | Tables',
|
||||||
view: 'View | Views',
|
view: 'View | Views',
|
||||||
|
materializedview: 'Materialized view | Materialized views',
|
||||||
definer: 'Definer',
|
definer: 'Definer',
|
||||||
algorithm: 'Algorithm',
|
algorithm: 'Algorithm',
|
||||||
trigger: 'Trigger | Triggers',
|
trigger: 'Trigger | Triggers',
|
||||||
@@ -216,6 +217,7 @@ export const enUS = {
|
|||||||
updateOption: 'Update option',
|
updateOption: 'Update option',
|
||||||
deleteView: 'Delete view',
|
deleteView: 'Delete view',
|
||||||
createNewView: 'Create new view',
|
createNewView: 'Create new view',
|
||||||
|
createNewMaterializedView: 'Create new materialized view',
|
||||||
deleteTrigger: 'Delete trigger',
|
deleteTrigger: 'Delete trigger',
|
||||||
createNewTrigger: 'Create new trigger',
|
createNewTrigger: 'Create new trigger',
|
||||||
currentUser: 'Current user',
|
currentUser: 'Current user',
|
||||||
@@ -248,6 +250,7 @@ export const enUS = {
|
|||||||
thereAreNoTableFields: 'There are no table fields',
|
thereAreNoTableFields: 'There are no table fields',
|
||||||
newTable: 'New table',
|
newTable: 'New table',
|
||||||
newView: 'New view',
|
newView: 'New view',
|
||||||
|
newMaterializedView: 'New materialized view',
|
||||||
newTrigger: 'New trigger',
|
newTrigger: 'New trigger',
|
||||||
newRoutine: 'New routine',
|
newRoutine: 'New routine',
|
||||||
newFunction: 'New function',
|
newFunction: 'New function',
|
||||||
@@ -309,6 +312,7 @@ export const enUS = {
|
|||||||
color: 'Color',
|
color: 'Color',
|
||||||
label: 'Label',
|
label: 'Label',
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
|
customIcon: 'Custom icon',
|
||||||
fileName: 'File name',
|
fileName: 'File name',
|
||||||
choseFile: 'Choose file',
|
choseFile: 'Choose file',
|
||||||
data: 'Data',
|
data: 'Data',
|
||||||
@@ -404,8 +408,10 @@ export const enUS = {
|
|||||||
saveFile: 'Save file',
|
saveFile: 'Save file',
|
||||||
saveFileAs: 'Save file as',
|
saveFileAs: 'Save file as',
|
||||||
openFile: 'Open file',
|
openFile: 'Open file',
|
||||||
openNotes: 'Open notes'
|
openNotes: 'Open notes',
|
||||||
|
debugConsole: 'Debug console', // <- console tab name
|
||||||
|
executedQueries: 'Executed queries', // <- console tab name
|
||||||
|
sizeLimitError: 'Maximum size of {size} exceeded'
|
||||||
},
|
},
|
||||||
faker: { // Faker.js methods, used in random generated content
|
faker: { // Faker.js methods, used in random generated content
|
||||||
address: 'Address',
|
address: 'Address',
|
||||||
|
@@ -46,6 +46,8 @@ export type AvailableLocale = keyof typeof messages
|
|||||||
|
|
||||||
const i18n = createI18n<[NestedPartial<MessageSchema>], AvailableLocale>({
|
const i18n = createI18n<[NestedPartial<MessageSchema>], AvailableLocale>({
|
||||||
fallbackLocale: 'en-US',
|
fallbackLocale: 'en-US',
|
||||||
|
silentTranslationWarn: true,
|
||||||
|
silentFallbackWarn: true,
|
||||||
allowComposition: true,
|
allowComposition: true,
|
||||||
messages
|
messages
|
||||||
});
|
});
|
||||||
|
@@ -15,7 +15,7 @@ export const nlNL = {
|
|||||||
results: 'Resultaten',
|
results: 'Resultaten',
|
||||||
size: 'Grootte',
|
size: 'Grootte',
|
||||||
mimeType: 'Mime-Type',
|
mimeType: 'Mime-Type',
|
||||||
download: 'Download',
|
download: 'Download', // Same as English
|
||||||
add: 'Toevoegen',
|
add: 'Toevoegen',
|
||||||
data: 'Data',
|
data: 'Data',
|
||||||
properties: 'Eigenschappen',
|
properties: 'Eigenschappen',
|
||||||
@@ -65,7 +65,13 @@ export const nlNL = {
|
|||||||
outputFormat: 'Uitvoerformaat',
|
outputFormat: 'Uitvoerformaat',
|
||||||
singleFile: 'Enkel {ext}-bestand',
|
singleFile: 'Enkel {ext}-bestand',
|
||||||
zipCompressedFile: 'ZIP gecomprimeerd {ext}-bestand',
|
zipCompressedFile: 'ZIP gecomprimeerd {ext}-bestand',
|
||||||
include: 'Inclusief'
|
include: 'Inclusief',
|
||||||
|
search: 'Zoek',
|
||||||
|
copyName: 'Kopieer naam',
|
||||||
|
title: 'Titel',
|
||||||
|
archive: 'Archief',
|
||||||
|
undo: 'Ongedaan maken',
|
||||||
|
moveTo: 'Verplaats naar'
|
||||||
},
|
},
|
||||||
connection: {
|
connection: {
|
||||||
connectionName: 'Naam verbinding',
|
connectionName: 'Naam verbinding',
|
||||||
@@ -100,7 +106,10 @@ export const nlNL = {
|
|||||||
readOnlyMode: 'Alleen lezen modus',
|
readOnlyMode: 'Alleen lezen modus',
|
||||||
untrustedConnection: 'Niet vertrouwde verbinding',
|
untrustedConnection: 'Niet vertrouwde verbinding',
|
||||||
allConnections: 'Alle verbindingen',
|
allConnections: 'Alle verbindingen',
|
||||||
searchForConnections: 'Zoek naar verbindingen'
|
searchForConnections: 'Zoek naar verbindingen',
|
||||||
|
singleConnection: 'Enkele verbinding',
|
||||||
|
connection: 'Verbinding',
|
||||||
|
keepAliveInterval: 'Keep alive interval'
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
schema: 'Schema',
|
schema: 'Schema',
|
||||||
@@ -260,7 +269,15 @@ export const nlNL = {
|
|||||||
targetTable: 'Doeltabel',
|
targetTable: 'Doeltabel',
|
||||||
switchDatabase: 'Wissel van database',
|
switchDatabase: 'Wissel van database',
|
||||||
importQueryErrors: 'Waarschuwing: {n} fout is opgetreden | Waarschuwing: {n} fouten opgetreden',
|
importQueryErrors: 'Waarschuwing: {n} fout is opgetreden | Waarschuwing: {n} fouten opgetreden',
|
||||||
executedQueries: '{n} query uitgevoerd | {n} queries uitgevoerd'
|
executedQueries: '{n} query uitgevoerd | {n} queries uitgevoerd',
|
||||||
|
insert: 'Invoegen',
|
||||||
|
exportTable: 'Exporteer tabel',
|
||||||
|
savedQueries: 'Opgeslagen queries',
|
||||||
|
searchForElements: 'Zoek naar elementen',
|
||||||
|
searchForSchemas: 'Zoek naar schema\'s',
|
||||||
|
materializedview: 'Materialized view | Materialized views',
|
||||||
|
createNewMaterializedView: 'Materialized view maken',
|
||||||
|
newMaterializedView: 'Nieuwe materialized view'
|
||||||
},
|
},
|
||||||
application: {
|
application: {
|
||||||
settings: 'Instellingen',
|
settings: 'Instellingen',
|
||||||
@@ -367,7 +384,30 @@ export const nlNL = {
|
|||||||
wrongFileFormat: 'Bestand is geen geldig .antares bestand',
|
wrongFileFormat: 'Bestand is geen geldig .antares bestand',
|
||||||
required: 'Verplicht',
|
required: 'Verplicht',
|
||||||
choseFile: 'Selecteer bestand',
|
choseFile: 'Selecteer bestand',
|
||||||
password: 'Wachtwoord'
|
password: 'Wachtwoord',
|
||||||
|
note: 'Notitie',
|
||||||
|
data: 'Data',
|
||||||
|
event: 'Event',
|
||||||
|
key: 'Key',
|
||||||
|
customIcon: 'Aangepast pictogram',
|
||||||
|
fileName: 'bestandsnaam',
|
||||||
|
newFolder: 'Nieuwe map',
|
||||||
|
outOfFolder: 'Out of folder',
|
||||||
|
dataImportSuccess: 'Data succesvol geïmporteerd',
|
||||||
|
thereAreNoNotesYet: 'Er zijn nog geen notities',
|
||||||
|
addNote: 'Voeg notitie toe',
|
||||||
|
editNote: 'Bewerk notitie',
|
||||||
|
saveAsNote: 'Sla op als notitie',
|
||||||
|
showArchivedNotes: 'Toon gearchiveerde notities',
|
||||||
|
hideArchivedNotes: 'Verberg gearchiveerde notities',
|
||||||
|
tag: 'Tag',
|
||||||
|
saveFile: 'Bestand opslaan',
|
||||||
|
saveFileAs: 'Bestand opslaan als',
|
||||||
|
openFile: 'Open bestand',
|
||||||
|
openNotes: 'Open notities',
|
||||||
|
debugConsole: 'Debug Console',
|
||||||
|
executedQueries: 'Voer queries uit',
|
||||||
|
sizeLimitError: 'Maximum grootte {size} overschreden'
|
||||||
},
|
},
|
||||||
faker: {
|
faker: {
|
||||||
address: 'Adres',
|
address: 'Adres',
|
||||||
@@ -434,7 +474,7 @@ export const nlNL = {
|
|||||||
engine: 'Engine',
|
engine: 'Engine',
|
||||||
past: 'Verleden',
|
past: 'Verleden',
|
||||||
now: 'Nu',
|
now: 'Nu',
|
||||||
future: 'Future',
|
future: 'Toekomstig',
|
||||||
between: 'Between',
|
between: 'Between',
|
||||||
recent: 'Recent',
|
recent: 'Recent',
|
||||||
soon: 'Soon',
|
soon: 'Soon',
|
||||||
@@ -447,11 +487,11 @@ export const nlNL = {
|
|||||||
amount: 'Amount',
|
amount: 'Amount',
|
||||||
transactionType: 'Transaction type',
|
transactionType: 'Transaction type',
|
||||||
currencyCode: 'Currency code',
|
currencyCode: 'Currency code',
|
||||||
currencyName: 'Currency name',
|
currencyName: 'Valutanaam',
|
||||||
currencySymbol: 'Currency symbol',
|
currencySymbol: 'Valutateken',
|
||||||
bitcoinAddress: 'Bitcoin address',
|
bitcoinAddress: 'Bitcoin adres',
|
||||||
litecoinAddress: 'Litecoin address',
|
litecoinAddress: 'Litecoin adres',
|
||||||
creditCardNumber: 'Credit card number',
|
creditCardNumber: 'Credit card nummer',
|
||||||
creditCardCVV: 'Credit card CVV',
|
creditCardCVV: 'Credit card CVV',
|
||||||
ethereumAddress: 'Ethereum adres',
|
ethereumAddress: 'Ethereum adres',
|
||||||
iban: 'IBAN',
|
iban: 'IBAN',
|
||||||
@@ -487,10 +527,10 @@ export const nlNL = {
|
|||||||
sentence: 'Zin',
|
sentence: 'Zin',
|
||||||
slug: 'Slug',
|
slug: 'Slug',
|
||||||
sentences: 'Zinnen',
|
sentences: 'Zinnen',
|
||||||
paragraph: 'Paragraph',
|
paragraph: 'Paragraaf',
|
||||||
paragraphs: 'Paragraphs',
|
paragraphs: 'Paragrafen',
|
||||||
text: 'Text',
|
text: 'Tekst',
|
||||||
lines: 'Lines',
|
lines: 'Regels',
|
||||||
genre: 'Genre',
|
genre: 'Genre',
|
||||||
firstName: 'Voornaam',
|
firstName: 'Voornaam',
|
||||||
lastName: 'Achternaam',
|
lastName: 'Achternaam',
|
||||||
@@ -500,7 +540,7 @@ export const nlNL = {
|
|||||||
gender: 'Gender',
|
gender: 'Gender',
|
||||||
prefix: 'Prefix',
|
prefix: 'Prefix',
|
||||||
suffix: 'Suffix',
|
suffix: 'Suffix',
|
||||||
title: 'Title',
|
title: 'Titel',
|
||||||
jobDescriptor: 'Job descriptor',
|
jobDescriptor: 'Job descriptor',
|
||||||
jobArea: 'Job area',
|
jobArea: 'Job area',
|
||||||
jobType: 'Job type',
|
jobType: 'Job type',
|
||||||
@@ -514,24 +554,24 @@ export const nlNL = {
|
|||||||
objectElement: 'Object element',
|
objectElement: 'Object element',
|
||||||
uuid: 'Uuid',
|
uuid: 'Uuid',
|
||||||
boolean: 'Boolean',
|
boolean: 'Boolean',
|
||||||
image: 'Image',
|
image: 'Afbeelding',
|
||||||
locale: 'Locale',
|
locale: 'Locale',
|
||||||
alpha: 'Alpha',
|
alpha: 'Alpha',
|
||||||
alphaNumeric: 'Alphanumeric',
|
alphaNumeric: 'Alfanumeriek',
|
||||||
hexaDecimal: 'Hexadecimal',
|
hexaDecimal: 'Hexadecimaal',
|
||||||
fileName: 'File name',
|
fileName: 'Bestandsnaam',
|
||||||
commonFileName: 'Common file name',
|
commonFileName: 'Common file name',
|
||||||
mimeType: 'Mime type',
|
mimeType: 'Mime type',
|
||||||
commonFileType: 'Common file type',
|
commonFileType: 'Common file type',
|
||||||
commonFileExt: 'Common file extension',
|
commonFileExt: 'Common file extension',
|
||||||
fileType: 'File type',
|
fileType: 'Filetype',
|
||||||
fileExt: 'File extension',
|
fileExt: 'File extension',
|
||||||
directoryPath: 'Directory path',
|
directoryPath: 'Directory path',
|
||||||
filePath: 'File path',
|
filePath: 'File path',
|
||||||
semver: 'Semver',
|
semver: 'Semver',
|
||||||
manufacturer: 'Manufacturer',
|
manufacturer: 'Fabrikant',
|
||||||
model: 'Model',
|
model: 'Model',
|
||||||
fuel: 'Fuel',
|
fuel: 'Brandstof',
|
||||||
vin: 'Vin'
|
vin: 'Vin'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -12,7 +12,7 @@ import { createApp } from 'vue';
|
|||||||
import App from '@/App.vue';
|
import App from '@/App.vue';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
import { useApplicationStore } from '@/stores/application';
|
import { useApplicationStore } from '@/stores/application';
|
||||||
import { useConsoleStore } from '@/stores/console';
|
import { QueryLog, useConsoleStore } from '@/stores/console';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
|
|
||||||
@@ -37,11 +37,17 @@ i18n.global.locale = locale;
|
|||||||
// IPC exceptions
|
// IPC exceptions
|
||||||
ipcRenderer.on('unhandled-exception', (event, error) => {
|
ipcRenderer.on('unhandled-exception', (event, error) => {
|
||||||
useNotificationsStore().addNotification({ status: 'error', message: error.message });
|
useNotificationsStore().addNotification({ status: 'error', message: error.message });
|
||||||
|
useConsoleStore().putLog('debug', {
|
||||||
|
level: 'error',
|
||||||
|
process: 'main',
|
||||||
|
message: error.message,
|
||||||
|
date: new Date()
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// IPC query logs
|
// IPC query logs
|
||||||
ipcRenderer.on('query-log', (event, logRecord) => {
|
ipcRenderer.on('query-log', (event, logRecord: QueryLog) => {
|
||||||
useConsoleStore().putLog(logRecord);
|
useConsoleStore().putLog('query', logRecord);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('toggle-console', () => {
|
ipcRenderer.on('toggle-console', () => {
|
||||||
|
@@ -32,8 +32,8 @@ export default class {
|
|||||||
return ipcRenderer.invoke('unregister-shortcuts');
|
return ipcRenderer.invoke('unregister-shortcuts');
|
||||||
}
|
}
|
||||||
|
|
||||||
static readFile (path: string): Promise<string> {
|
static readFile (params: {filePath: string; encoding: string}): Promise<string> {
|
||||||
return ipcRenderer.invoke('read-file', path);
|
return ipcRenderer.invoke('read-file', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static writeFile (path: string, content: unknown) {
|
static writeFile (path: string, content: unknown) {
|
||||||
|
@@ -19,4 +19,20 @@ export default class {
|
|||||||
static createView (params: CreateViewParams & { uid: string }): Promise<IpcResponse> {
|
static createView (params: CreateViewParams & { uid: string }): Promise<IpcResponse> {
|
||||||
return ipcRenderer.invoke('create-view', unproxify(params));
|
return ipcRenderer.invoke('create-view', unproxify(params));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createMaterializedView (params: CreateViewParams & { uid: string }): Promise<IpcResponse> {
|
||||||
|
return ipcRenderer.invoke('create-materialized-view', unproxify(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
static getMaterializedViewInformations (params: { uid: string; schema: string; view: string }): Promise<IpcResponse> {
|
||||||
|
return ipcRenderer.invoke('get-materialized-view-informations', unproxify(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
static dropMaterializedView (params: { uid: string; schema: string; view: string }): Promise<IpcResponse> {
|
||||||
|
return ipcRenderer.invoke('drop-materialized-view', unproxify(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
static alterMaterializedView (params: { view: AlterViewParams & { uid: string }}): Promise<IpcResponse> {
|
||||||
|
return ipcRenderer.invoke('alter-materialized-view', unproxify(params));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
9
src/renderer/libs/camelize.ts
Normal file
9
src/renderer/libs/camelize.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export const camelize = (text: string) => {
|
||||||
|
const textArr = text.split('-');
|
||||||
|
for (let i = 0; i < textArr.length; i++) {
|
||||||
|
if (i === 0) continue;
|
||||||
|
textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textArr.join('');
|
||||||
|
};
|
@@ -38,7 +38,7 @@ const connStringConstruct = (args: ConnectionParams & { pgConnString?: string })
|
|||||||
args.sshPort = stringArgs.sshPort;
|
args.sshPort = stringArgs.sshPort;
|
||||||
|
|
||||||
// ssl mode
|
// ssl mode
|
||||||
args.ssl = checkForSSl(args.pgConnString);
|
args.ssl = checkForSSl(args.pgConnString) || args.ssl;
|
||||||
args.cert = stringArgs.sslcert;
|
args.cert = stringArgs.sslcert;
|
||||||
args.key = stringArgs.sslkey;
|
args.key = stringArgs.sslkey;
|
||||||
args.ca = stringArgs.sslrootcert;
|
args.ca = stringArgs.sslrootcert;
|
||||||
|
@@ -349,11 +349,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-console {
|
.console {
|
||||||
border-top: 1px solid #444;
|
border-top: 1px solid #444;
|
||||||
background-color: $bg-color-dark;
|
background-color: $bg-color-dark;
|
||||||
|
|
||||||
.query-console-log {
|
.console-log {
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: $bg-color-gray;
|
background: $bg-color-gray;
|
||||||
|
@@ -173,11 +173,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.query-console {
|
.console {
|
||||||
border-top: 1px solid darken($bg-color-light-gray, 15%);
|
border-top: 1px solid darken($bg-color-light-gray, 15%);
|
||||||
background-color: $bg-color-light;
|
background-color: $bg-color-light;
|
||||||
|
|
||||||
.query-console-log {
|
.console-log {
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: $bg-color-light-gray;
|
background: $bg-color-light-gray;
|
||||||
|
@@ -5,7 +5,11 @@ import { ipcRenderer } from 'electron';
|
|||||||
import * as Store from 'electron-store';
|
import * as Store from 'electron-store';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
import { i18n } from '@/i18n';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
|
|
||||||
|
import { useNotificationsStore } from './notifications';
|
||||||
|
|
||||||
let key = localStorage.getItem('key');
|
let key = localStorage.getItem('key');
|
||||||
|
|
||||||
export interface SidebarElement {
|
export interface SidebarElement {
|
||||||
@@ -16,8 +20,11 @@ export interface SidebarElement {
|
|||||||
color?: string;
|
color?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
icon?: null | string;
|
icon?: null | string;
|
||||||
|
hasCustomIcon?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CustomIcon {base64: string; uid: string}
|
||||||
|
|
||||||
if (!key) { // If no key in local storace
|
if (!key) { // If no key in local storace
|
||||||
const storedKey = ipcRenderer.sendSync('get-key');// Ask for key stored on disk
|
const storedKey = ipcRenderer.sendSync('get-key');// Ask for key stored on disk
|
||||||
|
|
||||||
@@ -44,7 +51,8 @@ export const useConnectionsStore = defineStore('connections', {
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
connections: persistentStore.get('connections', []) as ConnectionParams[],
|
connections: persistentStore.get('connections', []) as ConnectionParams[],
|
||||||
lastConnections: persistentStore.get('lastConnections', []) as {uid: string; time: number}[],
|
lastConnections: persistentStore.get('lastConnections', []) as {uid: string; time: number}[],
|
||||||
connectionsOrder: persistentStore.get('connectionsOrder', []) as SidebarElement[]
|
connectionsOrder: persistentStore.get('connectionsOrder', []) as SidebarElement[],
|
||||||
|
customIcons: persistentStore.get('custom_icons', []) as CustomIcon[]
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getConnectionByUid: state => (uid:string) => state.connections.find(connection => connection.uid === uid),
|
getConnectionByUid: state => (uid:string) => state.connections.find(connection => connection.uid === uid),
|
||||||
@@ -74,7 +82,8 @@ export const useConnectionsStore = defineStore('connections', {
|
|||||||
.find(connection => connection.uid === uid),
|
.find(connection => connection.uid === uid),
|
||||||
getFolders: state => state.connectionsOrder.filter(conn => conn.isFolder),
|
getFolders: state => state.connectionsOrder.filter(conn => conn.isFolder),
|
||||||
getConnectionFolder: state => (uid:string) => state.connectionsOrder
|
getConnectionFolder: state => (uid:string) => state.connectionsOrder
|
||||||
.find(folder => folder.isFolder && folder.connections.includes(uid))
|
.find(folder => folder.isFolder && folder.connections.includes(uid)),
|
||||||
|
getIconByUid: state => (uid:string) => state.customIcons.find(i => i.uid === uid)
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
addConnection (connection: ConnectionParams) {
|
addConnection (connection: ConnectionParams) {
|
||||||
@@ -198,7 +207,8 @@ export const useConnectionsStore = defineStore('connections', {
|
|||||||
client: conn.client,
|
client: conn.client,
|
||||||
uid: conn.uid,
|
uid: conn.uid,
|
||||||
icon: conn.icon,
|
icon: conn.icon,
|
||||||
name: conn.name
|
name: conn.name,
|
||||||
|
hasCustomIcon: conn.hasCustomIcon
|
||||||
});
|
});
|
||||||
|
|
||||||
connIndex = connections.findIndex((conn, i) => conn.uid === el.uid && i !== el.index);
|
connIndex = connections.findIndex((conn, i) => conn.uid === el.uid && i !== el.index);
|
||||||
@@ -247,15 +257,41 @@ export const useConnectionsStore = defineStore('connections', {
|
|||||||
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => !emptyFolders.includes(el.uid));
|
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => !emptyFolders.includes(el.uid));
|
||||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||||
},
|
},
|
||||||
|
// Custom Icons
|
||||||
|
addIcon (svg: string) {
|
||||||
|
if (svg.length > 16384) {
|
||||||
|
const { t } = i18n.global;
|
||||||
|
useNotificationsStore().addNotification({
|
||||||
|
status: 'error',
|
||||||
|
message: t('application.sizeLimitError', { size: '16KB' })
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon: CustomIcon = {
|
||||||
|
uid: uidGen('I'),
|
||||||
|
base64: svg
|
||||||
|
};
|
||||||
|
|
||||||
|
this.customIcons.push(icon);
|
||||||
|
persistentStore.set('custom_icons', this.customIcons);
|
||||||
|
},
|
||||||
|
removeIcon (uid: string) {
|
||||||
|
this.customIcons = this.customIcons.filter((i: CustomIcon) => i.uid !== uid);
|
||||||
|
persistentStore.set('custom_icons', this.customIcons);
|
||||||
|
},
|
||||||
importConnections (importObj: {
|
importConnections (importObj: {
|
||||||
connections: ConnectionParams[];
|
connections: ConnectionParams[];
|
||||||
connectionsOrder: SidebarElement[];
|
connectionsOrder: SidebarElement[];
|
||||||
|
customIcons: CustomIcon[];
|
||||||
}) {
|
}) {
|
||||||
this.connections = [...this.connections, ...importObj.connections];
|
this.connections = [...this.connections, ...importObj.connections];
|
||||||
this.connectionsOrder = [...this.connectionsOrder, ...importObj.connectionsOrder];
|
this.connectionsOrder = [...this.connectionsOrder, ...importObj.connectionsOrder];
|
||||||
|
this.customIcons = [...this.customIcons, ...importObj.customIcons];
|
||||||
|
|
||||||
persistentStore.set('connections', this.connections);
|
persistentStore.set('connections', this.connections);
|
||||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||||
|
persistentStore.set('customIcons', this.customIcons);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -1,56 +1,63 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
import { useWorkspacesStore } from './workspaces';
|
|
||||||
const logsSize = 1000;
|
const logsSize = 1000;
|
||||||
|
|
||||||
export interface ConsoleRecord {
|
export type LogType = 'query' | 'debug'
|
||||||
|
export interface QueryLog {
|
||||||
cUid: string;
|
cUid: string;
|
||||||
sql: string;
|
sql: string;
|
||||||
date: Date;
|
date: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DebugLog {
|
||||||
|
level: 'log' | 'info' | 'warn' | 'error';
|
||||||
|
process: 'renderer' | 'main' | 'worker';
|
||||||
|
message: string;
|
||||||
|
date: Date;
|
||||||
|
}
|
||||||
|
|
||||||
export const useConsoleStore = defineStore('console', {
|
export const useConsoleStore = defineStore('console', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
records: [] as ConsoleRecord[],
|
isConsoleOpen: false,
|
||||||
consolesHeight: new Map<string, number>(),
|
queryLogs: [] as QueryLog[],
|
||||||
consolesOpened: new Set([])
|
debugLogs: [] as DebugLog[],
|
||||||
|
selectedTab: 'query' as LogType,
|
||||||
|
consoleHeight: 0
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getLogsByWorkspace: state => (uid: string) => state.records.filter(r => r.cUid === uid),
|
getLogsByWorkspace: state => (uid: string) => state.queryLogs.filter(r => r.cUid === uid)
|
||||||
isConsoleOpen: state => (uid: string) => state.consolesOpened.has(uid),
|
|
||||||
consoleHeight: state => {
|
|
||||||
const uid = useWorkspacesStore().getSelected;
|
|
||||||
return state.consolesHeight.get(uid) || 0;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
putLog (record: ConsoleRecord) {
|
putLog (type: LogType, record: QueryLog | DebugLog) {
|
||||||
this.records.push(record);
|
if (type === 'query') {
|
||||||
|
this.queryLogs.push(record);
|
||||||
|
|
||||||
if (this.records.length > logsSize)
|
if (this.queryLogs.length > logsSize)
|
||||||
this.records = this.records.slice(0, logsSize);
|
this.queryLogs = this.queryLogs.slice(0, logsSize);
|
||||||
|
}
|
||||||
|
else if (type === 'debug') {
|
||||||
|
this.debugLogs.push(record);
|
||||||
|
|
||||||
|
if (this.debugLogs.length > logsSize)
|
||||||
|
this.debugLogs = this.debugLogs.slice(0, logsSize);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
openConsole () {
|
openConsole () {
|
||||||
const uid = useWorkspacesStore().getSelected;
|
this.isConsoleOpen = true;
|
||||||
this.consolesOpened.add(uid);
|
this.consoleHeight = 250;
|
||||||
this.consolesHeight.set(uid, 250);
|
|
||||||
},
|
},
|
||||||
closeConsole () {
|
closeConsole () {
|
||||||
const uid = useWorkspacesStore().getSelected;
|
this.isConsoleOpen = false;
|
||||||
this.consolesOpened.delete(uid);
|
this.consoleHeight = 0;
|
||||||
this.consolesHeight.set(uid, 0);
|
|
||||||
},
|
},
|
||||||
resizeConsole (height: number) {
|
resizeConsole (height: number) {
|
||||||
const uid = useWorkspacesStore().getSelected;
|
|
||||||
if (height < 30)
|
if (height < 30)
|
||||||
this.closeConsole();
|
this.closeConsole();
|
||||||
else
|
else
|
||||||
this.consolesHeight.set(uid, height);
|
this.consoleHeight = height;
|
||||||
},
|
},
|
||||||
toggleConsole () {
|
toggleConsole () {
|
||||||
const uid = useWorkspacesStore().getSelected;
|
if (this.isConsoleOpen)
|
||||||
|
|
||||||
if (this.consolesOpened.has(uid))
|
|
||||||
this.closeConsole();
|
this.closeConsole();
|
||||||
else
|
else
|
||||||
this.openConsole();
|
this.openConsole();
|
||||||
|
@@ -558,6 +558,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'new-table':
|
case 'new-table':
|
||||||
|
case 'new-view':
|
||||||
|
case 'new-materialized-view':
|
||||||
case 'new-trigger':
|
case 'new-trigger':
|
||||||
case 'new-trigger-function':
|
case 'new-trigger-function':
|
||||||
case 'new-function':
|
case 'new-function':
|
||||||
@@ -659,6 +661,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||||||
break;
|
break;
|
||||||
case 'data':
|
case 'data':
|
||||||
case 'table-props':
|
case 'table-props':
|
||||||
|
case 'view-props':
|
||||||
|
case 'materialized-view-props':
|
||||||
case 'trigger-props':
|
case 'trigger-props':
|
||||||
case 'trigger-function-props':
|
case 'trigger-function-props':
|
||||||
case 'function-props':
|
case 'function-props':
|
||||||
|
Reference in New Issue
Block a user