diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..14943fca --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..bf8c4c73 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,36 @@ +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: '39 13 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript', 'TypeScript' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 00000000..5799839a --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,14 @@ +name: 'Dependency Review' +on: [ pull_request ] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v3 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v1 diff --git a/README.md b/README.md index 6d782221..6d8dfe99 100644 --- a/README.md +++ b/README.md @@ -74,9 +74,10 @@ DBM can query data from any SQL-speaking datastore or data engine (ClickHouse an Here are some of the major database solutions that are supported:

- clickhouse - trino - presto + ClickHouse + Trino + Presto + MySQL

## Features diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..e89ab94d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +|----------| ------------------ | +| > 1.18.0 | :white_check_mark: | +| < 1.18.0 | :x: | + +## Reporting a Vulnerability + +If you find a software vulnerability, please submit Issues. We will fix it after review. It will generally be fixed in the next version! diff --git a/docs/docs/assets/images/others/management/datasource/datasource_type.png b/docs/docs/assets/images/others/management/datasource/datasource_type.png index 0be5cfff..8c24a5d4 100644 Binary files a/docs/docs/assets/images/others/management/datasource/datasource_type.png and b/docs/docs/assets/images/others/management/datasource/datasource_type.png differ diff --git a/docs/docs/assets/images/others/management/datasource/mysql/img.png b/docs/docs/assets/images/others/management/datasource/mysql/img.png new file mode 100644 index 00000000..bd04c85d Binary files /dev/null and b/docs/docs/assets/images/others/management/datasource/mysql/img.png differ diff --git a/docs/docs/assets/integrate/MySQL.svg b/docs/docs/assets/integrate/MySQL.svg new file mode 100644 index 00000000..8611de93 --- /dev/null +++ b/docs/docs/assets/integrate/MySQL.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/docs/assets/powered_by/ClickVisual.png b/docs/docs/assets/powered_by/ClickVisual.png new file mode 100644 index 00000000..80afe078 Binary files /dev/null and b/docs/docs/assets/powered_by/ClickVisual.png differ diff --git a/docs/docs/assets/powered_by/dbm.png b/docs/docs/assets/powered_by/dbm.png new file mode 100644 index 00000000..0c3af744 Binary files /dev/null and b/docs/docs/assets/powered_by/dbm.png differ diff --git a/docs/docs/assets/powered_by/qianmoq.png b/docs/docs/assets/powered_by/qianmoq.png new file mode 100644 index 00000000..430f9e7d Binary files /dev/null and b/docs/docs/assets/powered_by/qianmoq.png differ diff --git a/docs/docs/development/version/1.17.0-development.md b/docs/docs/development/version/1.17.0-development.md deleted file mode 100644 index 0982565f..00000000 --- a/docs/docs/development/version/1.17.0-development.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -template: overrides/main.html -icon: material/gesture-tap-button ---- - -DBM Version for `1.17.0` is development! - -!!! danger "Warning" - - The development version will not be released. If you need to experience, please pull the source code for local compilation. We will not release this version until the end of the final development work!!! - -#### Enhancement - ---- - -#### UI - ---- - -#### Optimize - ----- - -#### Docs - ---- - -#### Bug - ---- - - ---- - -- @qianmoQ diff --git a/docs/docs/download.md b/docs/docs/download.md new file mode 100644 index 00000000..595a54e7 --- /dev/null +++ b/docs/docs/download.md @@ -0,0 +1,93 @@ +--- +template: overrides/main.html + +hide: +- navigation +- toc +--- + + + +
+The current Trino release is version . Learn more details from the release notes. +
+ +
+ +- :fontawesome-brands-app-store-ios: __Mac App package__ + + --- + + See [Installation dbm](./reference/get_started/install.md) for complete install instructions. + +
+ + [Download latest](https://github.com/EdurtIO/dbm/releases/latest){ .md-button .md-button-primary } + +- :fontawesome-brands-windows: __Windows App package__ + + --- + + See [Installation dbm](./reference/get_started/install.md) for complete install instructions. + +
+ + [Download latest](https://github.com/EdurtIO/dbm/releases/latest){ .md-button .md-button-primary } + +- :material-more: __More App package__ + + --- + + See [The source code to install](./reference/get_started/install.md#the-source-code-to-install) for complete install instructions. + +
+ + [Source Code](https://github.com/EdurtIO/dbm){ .md-button .md-button-primary } + +
+ +
+ +- __Community resources__ + + --- + + * **Chat On Slack**: [edurtio.slack.com](https://edurtio.slack.com/archives/C02EU2YM2N8) + * **Issues**: [GitHub issues](https://github.com/EdurtIO/dbm/issues) + * **DingTalk**: [44559111](https://gitee.com/EdurtIO/dbm/raw/master/src/shared/common/dingtalk.jpg) + +- __Getting help__ + + --- + + If you need help using or running dbm, please ask a question on [Slack](https://edurtio.slack.com/archives/C02EU2YM2N8). Please [report](https://github.com/EdurtIO/dbm/issues/new/choose) any issue you find with dbm. + +
+ +# External resources + +
+ +- __ClickHouse__ + + --- + + * **Documentation**: [clickhouse.com](https://clickhouse.com/docs) + * **Issues**: [GitHub issues](https://github.com/ClickHouse/ClickHouse/issues) + * **Source Code**: [Code](https://github.com/ClickHouse/ClickHouse) + * **YouTube channel** [YouTube channel](https://www.youtube.com/c/ClickHouseDB) + +- __Trino__ + + --- + + * **Documentation**: [trino.io](https://trino.io/docs/current/) + * **Issues**: [GitHub issues](https://github.com/trinodb/trino/issues) + * **Source Code**: [Code](https://github.com/trinodb/trino) + * **YouTube channel** [YouTube channel](https://www.youtube.com/c/trinodb) +
diff --git a/docs/docs/powered_by.md b/docs/docs/powered_by.md new file mode 100644 index 00000000..e9ac7d69 --- /dev/null +++ b/docs/docs/powered_by.md @@ -0,0 +1,50 @@ +--- +template: overrides/main.html + +hide: +- navigation +- toc +--- + + + +
+ Add Your or Company +
+ +!!! note + + There are many companies, individuals and open source organizations that use this program. Some of them are listed below. + +
+ +- __DBM developer__ + + --- + + All DBM-related developers, who use the software and develop and debug it, have released a new version. + + [:octicons-arrow-right-24: Getting started](https://github.com/EdurtIO/dbm) + +- __qianmoQ__ + + --- + + DBM-related developers, who use the software and develop and debug it, have released new versions. + + [:octicons-arrow-right-24: Visit me](https://github.com/qianmoQ) + +- __ClickVisual__ + + --- + + ClickVisual is a lightweight browser-based data analytics and data search platform for ClickHouse. + + [:octicons-arrow-right-24: Getting started](https://github.com/clickvisual/clickvisual) + +
diff --git a/docs/docs/reference/management/datasource/mysql.md b/docs/docs/reference/management/datasource/mysql.md new file mode 100644 index 00000000..c8fbaa2c --- /dev/null +++ b/docs/docs/reference/management/datasource/mysql.md @@ -0,0 +1,61 @@ +--- +template: overrides/main.html +--- + +!!! note "MySQL" + + It is mainly used to describe how the software builds the MySQL data source for subsequent operations. + +!!! warning "System requirements" + + \>= `1.18.0` + +### Supported Versions + +--- + +| Version | Tested? | +|---------|---------------------------------------------| +| `5.6.x` | :material-checkbox-marked-circle:{.success} | +| `5.7.x` | :material-checkbox-marked-circle:{.success} | + +!!! note "Supported versions" + + Most versions have been tested, please submit issues for non-adapted versions. + +### Created a Source + +--- + +After entering the data source management page, click the Add data source button. + +![img.png](../../../assets/images/others/management/datasource/datasource_type.png) + +Select the MySQL icon in the `Experimental` type (the third). + +After selecting the type, click the `Next` button at the bottom to configure the relevant information. + +![img.png](../../../assets/images/others/management/datasource/mysql/img.png) + +!!! note "Supported protocols" + + - [x] `HTTP` + +#### HTTP Protocol + +--- + +!!! note "HTTP Protocol" + + Use the HTTP interface provided by MySQL to connect to the service. + +| Parameter | Description | Required | Unique | Default | +|-------------|-----------------------------------------------------------------------------------------------------|----------|--------|---------| +| `Alias` | The alias of the data source, which will be displayed later in the selected data source on the page | Yes | Yes | | +| `Host` | The host of the MySQL server | Yes | Yes | | +| `Port` | The port of the MySQL server | Yes | Yes | `3306` | +| `User Name` | The user name of the MySQL server | Yes | Yes | | +| `Password` | The password of the MySQL server | Yes | Yes | | +| `Database` | The database of the MySQL server | Yes | Yes | | + +When we have configured the above parameters, click the `Test` button at the bottom. If the service can be accessed normally, the `OK` button can be used. Click it and it will be saved. diff --git a/docs/docs/release/1.18.0-20220610.md b/docs/docs/release/1.18.0-20220610.md new file mode 100644 index 00000000..9ee0e2b6 --- /dev/null +++ b/docs/docs/release/1.18.0-20220610.md @@ -0,0 +1,57 @@ +--- +template: overrides/main.html +icon: material/gesture-tap-button +--- + +DBM Version for `1.18.0` is released! + +Release Time: `2022-06-10` + +#### General + +--- + +- Split multiple data sources and configure them as separate components +- Add code analysis ci +- Add dependency review ci +- Add dependabot ci +- Add comments system to docs +- Add powered by page +- Add download page +- Support tray function +- Fix the exception of Presto using quick query +- Support metadata host filtering +- Support query history reference to the editor +- Fix the error message that the query history cannot be viewed + +#### Security + +--- + +- Upgrade electron to `13.6.6` +- Add `SECURITY.md` file + +#### ClickHouse + +--- + +- Support specifying a database when building a data source + +#### Trino & Presto + +--- + +- Support specifying a database and catalog when building a data source [issues-158](https://github.com/EdurtIO/dbm/issues/158) +- Support metadata source management Trino & Presto service information [issues-158](https://github.com/EdurtIO/dbm/issues/158) +- Support metadata source management Trino & Presto create database [issues-158](https://github.com/EdurtIO/dbm/issues/158) + +#### MySQL + +--- + +- Support MySQL (custom query and data source management) [issues-180](https://github.com/EdurtIO/dbm/issues/180) +- Add MySQL docs [issues-180](https://github.com/EdurtIO/dbm/issues/180) + +--- + +- @qianmoQ diff --git a/docs/material/overrides/home.html b/docs/material/overrides/home.html index 33f3c746..f651c214 100644 --- a/docs/material/overrides/home.html +++ b/docs/material/overrides/home.html @@ -14,6 +14,9 @@

What is DBM?

DBM is a set of open source database management desktop software, it will support more data sources, masterpiece-friendly database desktop management tools.

+ + Download + Quick start @@ -100,6 +103,17 @@

Supported Databases

Trino, a query engine that runs at ludicrous speed Fast distributed SQL query engine for big data analytics that helps you explore your data universe.
+
  • +
    + + + + MySQL +
    +
    + MySQL is the world's most popular open source database. Whether you are a fast growing web property, technology ISV or large enterprise, MySQL can cost-effectively help you deliver high performance, scalable database applications. +
    +
  • diff --git a/docs/material/overrides/home.zh.html b/docs/material/overrides/home.zh.html index 6b929dfc..6948bb91 100644 --- a/docs/material/overrides/home.zh.html +++ b/docs/material/overrides/home.zh.html @@ -14,6 +14,9 @@

    What is DBM?

    DBM is a set of open source database management desktop software, it will support more data sources, masterpiece-friendly database desktop management tools.

    + + Download + Quick start diff --git a/docs/material/overrides/main.html b/docs/material/overrides/main.html index 046f6e4f..10aae88b 100644 --- a/docs/material/overrides/main.html +++ b/docs/material/overrides/main.html @@ -7,14 +7,59 @@ {% endblock %} {% block announce %} - - For updates follow @qianmoQ on - - Twitter + + The new version has been released, please go to check. {% endblock %} + +{% block content %} +{{ super() }} + +

    {{ lang.t("meta.comments") }}

    + + + + +{% endblock %} + {% block scripts %} {{ super() }} diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c72d1797..4517d6fc 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,12 +1,12 @@ -site_name: DBM - Database GUI +site_name: Database GUI site_url: https://dbm.incubator.edurt.io/ site_author: qinamoQ site_description: >- - Database Gui! + Full platform database management tool, supports ClickHouse, Presto, Trino repo_name: EdurtIO/dbm repo_url: https://github.com/EdurtIO/dbm -edit_uri: "" +edit_uri: "https://github.com/EdurtIO/dbm/blob/master/docs/docs" copyright: Copyright © 2021 EdurtIO @@ -127,6 +127,10 @@ plugins: nav_translations: en: Home: Home + Download: Get started with dbm + Datasource_MySQL: MySQL (Experimental 1.18.0) + Datasource_Presto_Trino: Presto & Trino (Experimental 1.17.0) + Datasource_ClickHouse: ClickHouse (1.0.0-SNAPSHOT) zh: Home: 主页 Documentation: 文档 @@ -144,6 +148,13 @@ plugins: Development: 开发 Document: 文档 Contribution: 贡献 + Download: Get started with dbm + Datasource_MySQL: MySQL (Experimental 1.18.0) + Datasource_Presto_Trino: Presto & Trino (Experimental 1.17.0) + Datasource_ClickHouse: ClickHouse (1.0.0-SNAPSHOT) + - redirects: + redirect_maps: + release-latest.md: release/1.18.0-20220610.md nav: - Home: index.md @@ -155,16 +166,17 @@ nav: - Snippet: reference/query/snippet/snippet.md - Management: - Datasource: - - ClickHouse: reference/management/datasource/clickhouse.md - - Presto: reference/management/datasource/presto_trino.md - - Trino: reference/management/datasource/presto_trino.md + - Datasource_ClickHouse: reference/management/datasource/clickhouse.md + - Datasource_Presto_Trino: reference/management/datasource/presto_trino.md + - Datasource_MySQL: reference/management/datasource/mysql.md - Monitor: - Processor: reference/monitor/monitor-processor.md - Connection: reference/monitor/monitor_connection.md - Mutations: reference/monitor/monitor_mutations.md - Query: reference/monitor/monitor_query.md - Release Note: - - 1.17.0 (latest): release/1.17.0-20220529.md + - 1.18.0 (latest): release/1.18.0-20220610.md + - 1.17.0: release/1.17.0-20220529.md - 1.16.0: release/1.16.0-20220513.md - 1.15.0: release/1.15.0-20220425.md - 1.14.0: release/1.14.0-20220410.md @@ -182,7 +194,7 @@ nav: - 1.2.0-SNAPSHOT: release/1.2.0-SNAPSHOT.md - 1.1.0-SNAPSHOT: release/1.1.0-SNAPSHOT.md - 1.0.0-SNAPSHOT: release/1.0.0-SNAPSHOT.md - - Development Version: development/version/1.17.0-development.md - Development: - Contribution: - Document: development/document.md + - Powered By: powered_by.md diff --git a/electron-builder.yml b/electron-builder.yml index f32eb3a9..9f44e4e4 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -10,40 +10,22 @@ releaseInfo: --- - - Support metadata management to filter the table - - Support trino and presto for query - - Support trino and presto for monitor --> processors - - Support trino and presto for monitor --> connection - - Support trino and presto for monitor --> slow query - #### Docs --- - - Refactoring software homepage - - Add clickhouse datasource docs - - Add presto & trino datasource docs - #### Optimize ---- - - Optimize project description and introduction - - Optimize the homepage is not available & does not support data source charts - #### Bug --- - - Fix the exception that the data source is not selected in the track, the Track list can be selected - #### UI --- - - Add multiple editor themes - - Query the list of data sources on the page, support the display of logo - directories: output: ./release files: diff --git a/package.json b/package.json index 3006ea58..a9943d39 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "dbm", - "version": "1.17.0", + "version": "1.18.0", "author": "qianmoQ ", - "description": "ClickHouse DataBase GUI", + "description": "DataBase GUI", "github": "https://github.com/EdurtIO/dbm.git", "homepage": "https://dbm.edurt.io", "keywords": [ @@ -16,7 +16,8 @@ "dbm", "clickhouse", "trino", - "presto" + "presto", + "mysql" ], "publish": [ { @@ -65,6 +66,7 @@ "jsstore": "^4.3.8", "lodash": "^4.17.21", "moment": "^2.29.2", + "mysql": "^2.18.1", "ng-zorro-antd": "^12.1.0", "ngx-clipboard": "^14.0.2", "ngx-easy-table": "^15.2.0", @@ -85,8 +87,8 @@ "@angular-builders/custom-webpack": "12.1.0", "@angular-devkit/build-angular": "12.1.2", "@angular-eslint/builder": "12.3.1", - "@angular-eslint/eslint-plugin": "12.3.1", - "@angular-eslint/eslint-plugin-template": "12.3.1", + "@angular-eslint/eslint-plugin": "13.2.1", + "@angular-eslint/eslint-plugin-template": "13.2.1", "@angular-eslint/schematics": "12.3.1", "@angular-eslint/template-parser": "12.3.1", "@angular/cli": "12.1.2", @@ -96,11 +98,11 @@ "@types/mocha": "8.2.2", "@types/node": "15.6.1", "@typescript-eslint/eslint-plugin": "^4.27.0", - "@typescript-eslint/parser": "^4.27.0", + "@typescript-eslint/parser": "^5.27.1", "cfonts": "^2.9.3", "chalk": "^4.1.1", "cross-env": "^7.0.3", - "electron": "13.1.2", + "electron": "13.6.6", "electron-builder": "22.11.7", "electron-reload": "1.5.0", "eslint": "7.29.0", @@ -111,12 +113,12 @@ "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.0.3", - "karma-jasmine": "~4.0.0", + "karma-jasmine": "~5.0.1", "karma-jasmine-html-reporter": "^1.5.0", "ts-loader": "^9.2.3", "typescript": "4.2.4", "wait-on": "^5.3.0", - "webpack": "5.39.1", + "webpack": "5.73.0", "webpack-cli": "4.7.2" }, "engines": { diff --git a/src/example/package.json b/src/example/package.json index beb26e51..bbe7cb9b 100644 --- a/src/example/package.json +++ b/src/example/package.json @@ -90,7 +90,7 @@ "cfonts": "^2.9.3", "chalk": "^4.1.1", "cross-env": "^7.0.3", - "electron": "13.1.2", + "electron": "13.6.6", "electron-builder": "22.11.7", "electron-reload": "1.5.0", "eslint": "7.29.0", diff --git a/src/main/main.ts b/src/main/main.ts index 9659eca7..c26aa74b 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,13 +1,22 @@ -import { app, BrowserWindow, globalShortcut, ipcMain } from 'electron'; +import { app, BrowserWindow, globalShortcut, ipcMain, nativeImage, Tray } from 'electron'; import * as path from 'path'; import { createAbout } from './about'; import { createMenu } from './menu'; import { handlerUpdater } from './update'; +import { handlerTray } from "./tray"; let win: BrowserWindow = null; const isDevelopment = process.env.NODE_ENV === 'development'; +let tray: Tray = null; +const image = nativeImage.createFromPath( + path.join(__dirname, '../shared/assets/icons/favicon.png') +); + function createWindow(): BrowserWindow { + if (win !== null) { + win = null; + } win = new BrowserWindow({ center: true, width: 800, @@ -41,6 +50,10 @@ function createWindow(): BrowserWindow { app.dock.setIcon(path.join(__dirname, '../shared/assets/icons/favicon.png')); } handlerUpdater(win); + if (tray === null) { + tray = new Tray(image.resize({width: 16, height: 16})); + handlerTray(tray, app, win); + } return win; } @@ -72,3 +85,5 @@ try { console.warn('Err: ', e); process.exit(0); } + +export { createWindow }; diff --git a/src/main/tray.ts b/src/main/tray.ts new file mode 100644 index 00000000..60678853 --- /dev/null +++ b/src/main/tray.ts @@ -0,0 +1,33 @@ +import { app, BrowserWindow, ipcMain, ipcRenderer, Menu, Tray } from "electron"; + +function handlerTray(tray: Tray, app, win: BrowserWindow) { + tray.on("click", () => { + // Trigger the reactivation window + // app.emit('activate'); + tray.popUpContextMenu(createClickMenu(app, win)); + }); +} + +function createClickMenu(app, win: BrowserWindow): Menu { + + return Menu.buildFromTemplate([ + { + label: 'Open', + click: function () { + app.emit('activate'); + } + }, + { + label: 'Quit', + click: function () { + if (process.platform !== 'darwin') { + app.quit(); + } + } + } + ]); +} + +export { + handlerTray +} diff --git a/src/renderer/app/layout/layout.module.ts b/src/renderer/app/layout/layout.module.ts index 466c0194..ff74370a 100644 --- a/src/renderer/app/layout/layout.module.ts +++ b/src/renderer/app/layout/layout.module.ts @@ -1,20 +1,21 @@ -import {FormsModule} from '@angular/forms'; -import {NgModule} from '@angular/core'; -import {LayoutRouting} from './layout.routing'; -import {LayoutComponent} from './layout.component'; -import {HeaderComponent} from './header/header.component'; -import {CommonModule} from '@angular/common'; -import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; -import {HttpClient} from '@angular/common/http'; -import {TranslateHttpLoader} from '@ngx-translate/http-loader'; -import {NgZorroAntdModule} from '@renderer/app/ng-zorro-antd.module'; -import {BasicService} from '@renderer/services/system/basic.service'; -import {MarkdownModule} from 'ngx-markdown'; -import {DatasourceService} from '@renderer/services/management/datasource.service'; -import {HttpService} from '@renderer/services/http.service'; -import {SshService} from '@renderer/services/ssh.service'; -import {PrestoService} from "@renderer/services/presto.service"; -import {FactoryService} from "@renderer/services/factory.service"; +import { FormsModule } from '@angular/forms'; +import { NgModule } from '@angular/core'; +import { LayoutRouting } from './layout.routing'; +import { LayoutComponent } from './layout.component'; +import { HeaderComponent } from './header/header.component'; +import { CommonModule } from '@angular/common'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { NgZorroAntdModule } from '@renderer/app/ng-zorro-antd.module'; +import { BasicService } from '@renderer/services/system/basic.service'; +import { MarkdownModule } from 'ngx-markdown'; +import { DatasourceService } from '@renderer/services/management/datasource.service'; +import { HttpService } from '@renderer/services/http.service'; +import { SshService } from '@renderer/services/ssh.service'; +import { PrestoService } from "@renderer/services/presto.service"; +import { FactoryService } from "@renderer/services/factory.service"; +import { MySQLService } from "@renderer/services/plugin/mysql.service"; const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './renderer/assets/i18n/', '.json'); @@ -44,7 +45,8 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => HttpService, SshService, PrestoService, - FactoryService + FactoryService, + MySQLService ] }) export class LayoutModule { diff --git a/src/renderer/app/pages/management/datasource/datasource.component.html b/src/renderer/app/pages/management/datasource/datasource.component.html index 7ea6ca1a..be4d9a7a 100644 --- a/src/renderer/app/pages/management/datasource/datasource.component.html +++ b/src/renderer/app/pages/management/datasource/datasource.component.html @@ -16,7 +16,12 @@ {{'common.alias' | translate}} {{'common.name' | translate}} - {{'common.host' | translate}} + {{'common.host' | translate}} + + + + {{'common.protocol' | translate}} {{'common.type' | translate}} {{'common.username' | translate}} @@ -58,6 +63,17 @@ + +
    + +
    +
    @@ -127,116 +143,10 @@
    -
    - - -
    -
    -
    -
    - - {{'common.alias'|translate}} - - - - - - - - {{'common.protocol'|translate}} - - - - - - - - - - {{'common.host'| translate}} - - - - - - - - {{'common.port'| translate}} - - - - - - - - {{'common.username'| translate}} - - - - - - - - {{'common.password'| translate}} - - - - - - -
    -
    -
    -
    - - - -
    -
    -
    -
    - - - {{'common.sshHost'| translate}} - - - - - - - - {{'common.sshPort'| translate}} - - - - - - - - {{'common.sshUsername'| translate}} - - - - - - - - {{'common.sshPassword'| translate}} - - - - - -
    -
    -
    -
    -
    + + +
    @@ -267,12 +177,11 @@ - -
    diff --git a/src/renderer/app/pages/management/datasource/datasource.component.ts b/src/renderer/app/pages/management/datasource/datasource.component.ts index 53979af0..79432e13 100644 --- a/src/renderer/app/pages/management/datasource/datasource.component.ts +++ b/src/renderer/app/pages/management/datasource/datasource.component.ts @@ -4,7 +4,6 @@ import { DatasourceService } from '@renderer/services/management/datasource.serv import { BaseComponent } from '@renderer/app/base.component'; import { ActionEnum } from '@renderer/enum/action.enum'; import { DatasourceJob } from '@renderer/job/datasource.job'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { NzMessageService } from 'ng-zorro-antd/message'; import { DatabaseModel } from '@renderer/model/database.model'; import { SourceTypeConfig } from '@renderer/config/source.type.config'; @@ -13,6 +12,7 @@ import { TranslateService } from '@ngx-translate/core'; import { StringUtils } from '@renderer/utils/string.utils'; import { RequestModel } from '@renderer/model/request.model'; import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { FormBuilder, FormGroup } from "@angular/forms"; @Component({ selector: 'app-management-datasource', @@ -27,6 +27,24 @@ import { DatabaseEnum } from "@renderer/enum/database.enum"; .gutter-row { margin-top: 10px; } + + .search-box { + padding: 8px; + } + + .search-box input { + width: 188px; + margin-bottom: 8px; + display: block; + } + + .search-box button { + width: 90px; + } + + .search-button { + margin-right: 8px; + } ` ] }) @@ -35,7 +53,6 @@ export class DatasourceComponent extends BaseComponent implements OnInit { tableDetails: DatasourceModel[] = new Array(); actionType: ActionEnum; actionSource: string; - validateForm!: FormGroup; currentStep: number = 1; showButton = { previous: false, @@ -43,6 +60,13 @@ export class DatasourceComponent extends BaseComponent implements OnInit { }; sourceTypes: DatabaseModel[]; dataSourceType = DatabaseEnum; + validateForm!: FormGroup; + search = { + host: { + visible: false, + value: null + } + } constructor(private service: DatasourceService, private messageService: NzMessageService, @@ -51,22 +75,12 @@ export class DatasourceComponent extends BaseComponent implements OnInit { private datasourceJob: DatasourceJob, private formBuilder: FormBuilder) { super(); - this.validateForm = this.formBuilder.group({ - alias: [null, [Validators.required]], - protocol: [null, [Validators.required]], - host: [null, [Validators.required]], - port: [null, [Validators.required]], - username: [null, []], - password: [null, []], - maxTotal: [null, []], - sshHost: [null, []], - sshPort: [null, []], - sshUsername: [null, []], - sshPassword: [null, []] - }); this.sourceTypes = new SourceTypeConfig().getConfig(); this.handlerGetAll(); this.handlerResetButton(); + this.validateForm = this.formBuilder.group({ + maxTotal: [null, []] + }); } ngOnInit() { @@ -119,7 +133,6 @@ export class DatasourceComponent extends BaseComponent implements OnInit { this.showButton.next = false; this.showButton.previous = false; this.handlerResetButton(); - this.validateForm.clearValidators(); } handlerTest() { @@ -142,26 +155,26 @@ export class DatasourceComponent extends BaseComponent implements OnInit { handlerSave() { this.service.save(this.formInfo) - .then(() => { - this.messageService.success(this.translateService.instant('common.success')); - this.handlerCloseModal(); - this.handlerGetAll(); - }) - .catch(() => { - this.messageService.error(this.translateService.instant('common.error')); - this.disabled.button = false; - }); + .then(() => { + this.messageService.success(this.translateService.instant('common.success')); + this.handlerCloseModal(); + this.handlerGetAll(); + }) + .catch(() => { + this.messageService.error(this.translateService.instant('common.error')); + this.disabled.button = false; + }); this.loading.button = false; } handlerGetAll() { this.service.getAll() - .then(response => { - this.tableDetails = response; - }) - .catch(() => { - this.messageService.error(this.translateService.instant('common.error')); - }); + .then(response => { + this.tableDetails = response; + }) + .catch(() => { + this.messageService.error(this.translateService.instant('common.error')); + }); } handlerDelete(id: number) { @@ -200,19 +213,6 @@ export class DatasourceComponent extends BaseComponent implements OnInit { this.handlerGetAll(); } - handlerSubmitForm(): void { - if (this.validateForm.valid) { - this.handlerTest(); - } else { - Object.values(this.validateForm.controls).forEach(control => { - if (control.invalid) { - control.markAsDirty(); - control.updateValueAndValidity({onlySelf: true}); - } - }); - } - } - handlerNext() { this.currentStep++; this.handlerResetButton(); @@ -230,4 +230,18 @@ export class DatasourceComponent extends BaseComponent implements OnInit { nzOkText: this.translateService.instant('common.ok') }); } + + handlerEmitterValue(value: DatasourceModel) { + this.formInfo = value; + } + + handlerSearch() { + this.search.host.visible = false; + this.tableDetails = this.tableDetails.filter(item => item.host.indexOf(this.search.host.value) !== -1); + } + + handlerSearchReset() { + this.search.host.value = ''; + this.handlerGetAll(); + } } diff --git a/src/renderer/app/pages/management/datasource/datasource.module.ts b/src/renderer/app/pages/management/datasource/datasource.module.ts index 7ebde5c6..939c1bdd 100644 --- a/src/renderer/app/pages/management/datasource/datasource.module.ts +++ b/src/renderer/app/pages/management/datasource/datasource.module.ts @@ -11,7 +11,17 @@ import { ServiceModule } from '@renderer/app/service.module'; import { CommonShareModule } from '@renderer/app/common-share.module'; import { NzModalService } from 'ng-zorro-antd/modal'; import { SshService } from '@renderer/services/ssh.service'; -import {PrestoService} from "@renderer/services/presto.service"; +import { PrestoService } from "@renderer/services/presto.service"; +import { DatasourceCommonComponent } from "@renderer/components/datasource/common/datasource.common.component"; +import { + DatasourceClickHouseComponent +} from "@renderer/components/datasource/clickhouse/datasource.clickhouse.component"; +import { + DatasourceProtocolSshComponent +} from "@renderer/components/datasource/protocol/ssh/datasource.protocol.ssh.component"; +import { DatasourceTrinoComponent } from "@renderer/components/datasource/trino/datasource.trino.component"; +import { DatasourceMysqlComponent } from "@renderer/components/datasource/mysql/datasource.mysql.component"; +import { MySQLService } from "@renderer/services/plugin/mysql.service"; const DATASOURCE_ROUTES: Routes = [ {path: '', component: DatasourceComponent} @@ -30,14 +40,20 @@ const DATASOURCE_ROUTES: Routes = [ ], exports: [], declarations: [ - DatasourceComponent + DatasourceComponent, + DatasourceCommonComponent, + DatasourceClickHouseComponent, + DatasourceProtocolSshComponent, + DatasourceTrinoComponent, + DatasourceMysqlComponent ], providers: [ DatasourceService, DatasourceJob, NzModalService, SshService, - PrestoService + PrestoService, + MySQLService ] }) export class DatasourceModule { diff --git a/src/renderer/app/pages/management/metadata/metadata.component.ts b/src/renderer/app/pages/management/metadata/metadata.component.ts index f74fb1dd..45d548cf 100644 --- a/src/renderer/app/pages/management/metadata/metadata.component.ts +++ b/src/renderer/app/pages/management/metadata/metadata.component.ts @@ -55,9 +55,12 @@ export class MetadataComponent extends BaseComponent implements OnInit { configModel.title = k.alias; configModel.type = TypeEnum.disk; configModel.disabled = k.status ? false : true; - if (k.type === DatabaseEnum.presto || k.type === DatabaseEnum.trino) { + if (k.type === DatabaseEnum.mysql) { configModel.disabled = true; } + if (k.type === DatabaseEnum.presto || k.type === DatabaseEnum.trino) { + configModel.isLeaf = true; + } if (configModel.disabled) { configModel.isLeaf = true; } @@ -79,8 +82,10 @@ export class MetadataComponent extends BaseComponent implements OnInit { this.rootNode = origin; } if (!origin.disabled) { - this.contextMenus = this.contextMenuService.getContextMenu(origin.type); - this.nzContextMenuService.create($event, menu); + this.dataSourceService.findByAlias(this.rootNode.key).then(response => { + this.contextMenus = this.contextMenuService.getContextMenu(origin.type, response.type); + this.nzContextMenuService.create($event, menu); + }); } } @@ -175,15 +180,21 @@ export class MetadataComponent extends BaseComponent implements OnInit { } this.handlerLevel(this.selectNode); const request = new RequestModel(); - request.config = await this.dataSourceService.getByAliasAsync(this.rootNode.value); - this.metadataService.getDiskUsedAndRatio(request, this.selectNode.origin).then(response => { - if (response.status) { - this.items = response.data.columns; - } else { - this.messageService.error(response.message); - } + const dataSource = await this.dataSourceService.getByAliasAsync(this.rootNode.value); + request.config = dataSource; + if (dataSource.type === DatabaseEnum.trino || dataSource.type === DatabaseEnum.presto) { + this.items = []; this.loading.button = false; - }); + } else { + this.metadataService.getDiskUsedAndRatio(request, this.selectNode.origin).then(response => { + if (response.status) { + this.items = response.data.columns; + } else { + this.messageService.error(response.message); + } + this.loading.button = false; + }); + } // } } diff --git a/src/renderer/app/pages/monitor/connection/monitor.connection.component.ts b/src/renderer/app/pages/monitor/connection/monitor.connection.component.ts index 51cf156e..4c444bef 100644 --- a/src/renderer/app/pages/monitor/connection/monitor.connection.component.ts +++ b/src/renderer/app/pages/monitor/connection/monitor.connection.component.ts @@ -29,7 +29,12 @@ export class MonitorConnectionComponent extends BaseComponent implements OnDestr private messageService: NzMessageService) { super(); this.datasourceService.getAll().then(response => { - this.dataSources = response; + this.dataSources = response.map(item => { + if (item.type === DatabaseEnum.mysql) { + item.status = false; + } + return item; + }); }); } diff --git a/src/renderer/app/pages/monitor/mutations/monitor.mutations.component.ts b/src/renderer/app/pages/monitor/mutations/monitor.mutations.component.ts index 24e8f04b..5b3be584 100644 --- a/src/renderer/app/pages/monitor/mutations/monitor.mutations.component.ts +++ b/src/renderer/app/pages/monitor/mutations/monitor.mutations.component.ts @@ -32,7 +32,7 @@ export class MonitorMutationsComponent extends BaseComponent implements OnDestro super(); this.datasourceService.getAll().then(response => { this.dataSources = response.map(item => { - if (item.type === DatabaseEnum.trino || item.type === DatabaseEnum.presto) { + if (item.type === DatabaseEnum.trino || item.type === DatabaseEnum.presto || item.type === DatabaseEnum.mysql) { item.status = false; } return item; diff --git a/src/renderer/app/pages/monitor/processor/monitor.processor.component.ts b/src/renderer/app/pages/monitor/processor/monitor.processor.component.ts index 6563bc50..1eb4a61e 100644 --- a/src/renderer/app/pages/monitor/processor/monitor.processor.component.ts +++ b/src/renderer/app/pages/monitor/processor/monitor.processor.component.ts @@ -31,7 +31,12 @@ export class MonitorProcessorComponent extends BaseComponent implements OnDestro private messageService: NzMessageService) { super(); this.datasourceService.getAll().then(response => { - this.dataSources = response; + this.dataSources = response.map(item => { + if (item.type === DatabaseEnum.mysql) { + item.status = false; + } + return item; + }); }); } diff --git a/src/renderer/app/pages/monitor/query/monitor.query.component.ts b/src/renderer/app/pages/monitor/query/monitor.query.component.ts index 2d7a74ff..9d69a643 100644 --- a/src/renderer/app/pages/monitor/query/monitor.query.component.ts +++ b/src/renderer/app/pages/monitor/query/monitor.query.component.ts @@ -7,6 +7,7 @@ import { ResponseDataModel } from '@renderer/model/response.model'; import { DatasourceService } from '@renderer/services/management/datasource.service'; import { MonitorService } from '@renderer/services/monitor/monitor.service'; import { NzMessageService } from 'ng-zorro-antd/message'; +import { DatabaseEnum } from "@renderer/enum/database.enum"; @Component({ selector: 'app-monitor-query', @@ -27,7 +28,12 @@ export class MonitorQueryComponent extends BaseComponent { private messageService: NzMessageService) { super(); this.datasourceService.getAll().then(response => { - this.dataSources = response; + this.dataSources = response.map(item => { + if (item.type === DatabaseEnum.mysql) { + item.status = false; + } + return item; + }); }); } diff --git a/src/renderer/app/pages/query/history/history.component.html b/src/renderer/app/pages/query/history/history.component.html index 55730a9f..d353e29c 100644 --- a/src/renderer/app/pages/query/history/history.component.html +++ b/src/renderer/app/pages/query/history/history.component.html @@ -45,6 +45,11 @@ (click)="handlerShowDDL(data)"> + - + + +
    • diff --git a/src/renderer/components/query/quick/quick.query.component.ts b/src/renderer/components/query/quick/quick.query.component.ts index 2301fab2..377bca10 100644 --- a/src/renderer/components/query/quick/quick.query.component.ts +++ b/src/renderer/components/query/quick/quick.query.component.ts @@ -8,6 +8,8 @@ import { RequestModel } from '@renderer/model/request.model'; import { NzMessageService } from 'ng-zorro-antd/message'; import { QueryQuickService } from '@renderer/services/query/query.quick.service'; import { StringUtils } from '@renderer/utils/string.utils'; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { FactoryService } from "@renderer/services/factory.service"; @Component({ selector: 'app-component-quick-query', @@ -23,24 +25,34 @@ export class QuickQueryComponent extends BaseComponent { dataSourceSet: DatasourceModel[]; databaseSet: any[]; tableSet: any[]; + tableSchemaSet: any[]; dataSource: string = null; database: string = null; table: string = null; + tableSchema: string = null; loading = { button: false, database: false, table: false }; quickCommands: any[]; + quickType = QuickEnum; + spanSize = 8; constructor(private dataSourceService: DatasourceService, private queryService: QueryService, private messageService: NzMessageService, - private queryQuickService: QueryQuickService) { + private queryQuickService: QueryQuickService, + private factoryService: FactoryService) { super(); this.quickCommands = this.queryQuickService.getQuickAll(); this.dataSourceService.getAll().then(response => { - this.dataSourceSet = response; + this.dataSourceSet = response.map(item => { + if (item.type === DatabaseEnum.mysql) { + item.status = false; + } + return item; + }); }); } @@ -49,43 +61,75 @@ export class QuickQueryComponent extends BaseComponent { this.emitter.emit(this.visible); } - handlerChangeValue(quick: QuickEnum) { + handlerChangeValue(quick: QuickEnum, value?: any) { this.table = null; const request = new RequestModel(); - this.dataSourceService.findByAlias(this.dataSource).then(response => { - request.config = response; - switch (quick) { - case QuickEnum.database: - this.disabled.dialog = true; - this.loading.database = true; - this.databaseSet = []; - this.queryService.getResponse(request, 'SHOW DATABASES').then(response => { + let sql; + const response = this.dataSourceSet.find(item => item.alias === this.dataSource); + request.config = response; + if (response.type === DatabaseEnum.presto || response.type === DatabaseEnum.trino) { + this.spanSize = 6; + } else { + this.spanSize = 8; + } + switch (quick) { + case QuickEnum.database: + this.disabled.dialog = true; + this.loading.database = true; + this.databaseSet = []; + sql = this.factoryService.forward(request.config.type).databaseFetchAll; + this.queryService.getResponse(request, sql).then(response => { + if (response.status) { + this.databaseSet = response.data.columns; + } else { + this.messageService.error(response.message); + } + this.loading.database = false; + this.disabled.dialog = false; + }); + break; + case QuickEnum.table: + this.tableSet = []; + if (this.spanSize === 8) { + sql = StringUtils.format(this.factoryService.forward(request.config.type).tableFetchAll, [this.database]); + } else { + sql = StringUtils.format(this.factoryService.forward(request.config.type).schemaFetchAll, [this.database]); + } + this.queryService.getResponse(request, sql).then(response => { + if (response.status) { + this.tableSet = response.data.columns; + } else { + this.messageService.error(response.message); + } + this.loading.table = false; + }); + break; + case QuickEnum.table_schema: + this.table = value; + if (this.spanSize === 6) { + this.tableSchemaSet = []; + sql = StringUtils.format(this.factoryService.forward(request.config.type).tableSchemaFetchAll, + [this.database, this.table]); + this.queryService.getResponse(request, sql).then(response => { if (response.status) { - this.databaseSet = response.data.columns; + this.tableSchemaSet = response.data.columns; } else { this.messageService.error(response.message); } - this.loading.database = false; - this.disabled.dialog = false; }); break; - case QuickEnum.table: - this.tableSet = []; - this.queryService.getResponse(request, 'SHOW TABLES FROM ' + this.database).then(response => { - if (response.status) { - this.tableSet = response.data.columns; - } else { - this.messageService.error(response.message); - } - this.loading.table = false; - }); - break; - } - }); + } + } } handlerQuickCommand(command: { name: string, value: string }) { - const sql = StringUtils.format(command.value, [this.database, this.table]); + let sql; + if (this.spanSize === 8) { + sql = StringUtils.format(command.value, [this.database, this.table]); + } else { + sql = StringUtils.format(command.value, + [StringUtils.format('{0}.{1}', [this.database, this.table]), this.tableSchema]); + } this.emitterValue.emit(sql); this.handlerCancel(); } diff --git a/src/renderer/config/base.config.ts b/src/renderer/config/base.config.ts index 2cb99609..9692b79b 100644 --- a/src/renderer/config/base.config.ts +++ b/src/renderer/config/base.config.ts @@ -3,6 +3,11 @@ export interface BaseConfig { processesFetchAll: string; connectionFetchAll: string; slowQueryFetchAll: string; + databaseFetchAll: string; + databaseCreate: string; + schemaFetchAll: string; + tableFetchAll: string; + tableSchemaFetchAll: string; diskUsedRatio: string; databaseDiskUsedRatio: string; tableDiskUsedRatio: string; @@ -14,4 +19,5 @@ export interface BaseConfig { tableItemsFilterPrecise: string; tableItemsFilterFuzzy: string; columnItems: string; + serverInfo: string; } diff --git a/src/renderer/config/clickhouse.config.ts b/src/renderer/config/clickhouse.config.ts index dee2912f..5b9289bd 100644 --- a/src/renderer/config/clickhouse.config.ts +++ b/src/renderer/config/clickhouse.config.ts @@ -46,6 +46,11 @@ WHERE metric LIKE '%Connection' GROUP BY metric ORDER BY metric DESC `; + databaseFetchAll = `SHOW DATABASES`; + databaseCreate = `CREATE DATABASE {0}`; + schemaFetchAll: string; + tableFetchAll = `SHOW TABLES FROM {0}`; + tableSchemaFetchAll = ``; diskUsedRatio = ` SELECT name, path, formatReadableSize(free_space) AS freeSize, formatReadableSize(total_space) AS totalSize, diff --git a/src/renderer/config/database.config.ts b/src/renderer/config/database.config.ts index 91d4af42..b7553a06 100644 --- a/src/renderer/config/database.config.ts +++ b/src/renderer/config/database.config.ts @@ -17,10 +17,12 @@ export class DatabaseConfig { [TranslateUtils.getValue('common.database'), TranslateUtils.getValue('common.engine')]); basicDatabase.description = basicDatabase.name; const defaultEngines = new Array(); - defaultEngines.push(DatabaseModel.builder(TranslateUtils.getValue('common.default'), + const defaultEngine = DatabaseModel.builder(TranslateUtils.getValue('common.default'), TranslateUtils.getValue('tooltip.database.default'), DatabaseEnum.none, - null)); + null); + defaultEngine.supportedSource.push(DatabaseEnum.presto, DatabaseEnum.trino); + defaultEngines.push(defaultEngine); defaultEngines.push(DatabaseModel.builder(TranslateUtils.getValue('common.atomic'), TranslateUtils.getValue('tooltip.database.atomic'), DatabaseEnum.atomic, diff --git a/src/renderer/config/operation.config.ts b/src/renderer/config/operation.config.ts index 9f3ab452..64f01758 100644 --- a/src/renderer/config/operation.config.ts +++ b/src/renderer/config/operation.config.ts @@ -1,6 +1,7 @@ import { OperationModel } from '@renderer/model/operation.model'; import { TypeEnum } from '@renderer/enum/type.enum'; import { OperationEnum } from '@renderer/enum/operation.enum'; +import { DatabaseEnum } from "@renderer/enum/database.enum"; export class OperationConfig { getConfig(): OperationModel[] { @@ -9,39 +10,48 @@ export class OperationConfig { server.name = TypeEnum.disk.toString(); server.type = TypeEnum.disk; server.operations = [ - {type: TypeEnum.server, actions: [OperationEnum.info]}, - {type: TypeEnum.database, actions: [OperationEnum.filter]}, - {type: TypeEnum.database, actions: [OperationEnum.create]} + { + type: TypeEnum.server, + actions: [OperationEnum.info], + supportedSource: [DatabaseEnum.clickhosue, DatabaseEnum.trino, DatabaseEnum.presto] + }, + {type: TypeEnum.database, actions: [OperationEnum.filter], supportedSource: [DatabaseEnum.clickhosue]}, + { + type: TypeEnum.database, + actions: [OperationEnum.create], + supportedSource: [DatabaseEnum.clickhosue, DatabaseEnum.trino, DatabaseEnum.presto] + } ]; opertions.push(server); const database = new OperationModel(); database.name = TypeEnum.database.toString(); database.type = TypeEnum.database; database.operations = [ - {type: TypeEnum.table, actions: [OperationEnum.create]}, - {type: TypeEnum.table, actions: [OperationEnum.filter]}, - {type: TypeEnum.database, actions: [OperationEnum.delete]}, - {type: TypeEnum.database, actions: [OperationEnum.structure]}, - {type: TypeEnum.database, actions: [OperationEnum.rename]} + {type: TypeEnum.table, actions: [OperationEnum.create], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.table, actions: [OperationEnum.filter], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.database, actions: [OperationEnum.delete], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.database, actions: [OperationEnum.structure], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.database, actions: [OperationEnum.rename], supportedSource: [DatabaseEnum.clickhosue]} ]; opertions.push(database); const table = new OperationModel(); table.name = TypeEnum.table.toString(); table.type = TypeEnum.table; table.operations = [ - {type: TypeEnum.table, actions: [OperationEnum.preview]}, - {type: TypeEnum.table, actions: [OperationEnum.delete]}, - {type: TypeEnum.table, actions: [OperationEnum.structure]}, - {type: TypeEnum.table, actions: [OperationEnum.rename]}, - {type: TypeEnum.table, actions: [OperationEnum.truncate]}, - {type: TypeEnum.table, actions: [OperationEnum.clean]}, - {type: TypeEnum.table, actions: [OperationEnum.optimize]}, + {type: TypeEnum.table, actions: [OperationEnum.preview], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.table, actions: [OperationEnum.delete], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.table, actions: [OperationEnum.structure], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.table, actions: [OperationEnum.rename], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.table, actions: [OperationEnum.truncate], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.table, actions: [OperationEnum.clean], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.table, actions: [OperationEnum.optimize], supportedSource: [DatabaseEnum.clickhosue]}, { type: TypeEnum.table, actions: [OperationEnum.ttl], + supportedSource: [DatabaseEnum.clickhosue], children: [ - {type: TypeEnum.table, actions: [OperationEnum.ttl_modify]}, - {type: TypeEnum.table, actions: [OperationEnum.ttl_remove]} + {type: TypeEnum.table, actions: [OperationEnum.ttl_modify], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.table, actions: [OperationEnum.ttl_remove], supportedSource: [DatabaseEnum.clickhosue]} ] } ]; @@ -50,11 +60,11 @@ export class OperationConfig { column.name = TypeEnum.column.toString(); column.type = TypeEnum.column; column.operations = [ - {type: TypeEnum.column, actions: [OperationEnum.preview]}, - {type: TypeEnum.column, actions: [OperationEnum.create]}, - {type: TypeEnum.column, actions: [OperationEnum.delete]}, - {type: TypeEnum.column, actions: [OperationEnum.rename]}, - {type: TypeEnum.column, actions: [OperationEnum.comment]} + {type: TypeEnum.column, actions: [OperationEnum.preview], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.column, actions: [OperationEnum.create], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.column, actions: [OperationEnum.delete], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.column, actions: [OperationEnum.rename], supportedSource: [DatabaseEnum.clickhosue]}, + {type: TypeEnum.column, actions: [OperationEnum.comment], supportedSource: [DatabaseEnum.clickhosue]} ]; opertions.push(column); return opertions; diff --git a/src/renderer/config/plugin/mysql.config.ts b/src/renderer/config/plugin/mysql.config.ts new file mode 100644 index 00000000..69c2b9ca --- /dev/null +++ b/src/renderer/config/plugin/mysql.config.ts @@ -0,0 +1,25 @@ +import { BaseConfig } from "@renderer/config/base.config"; + +export class MySQLConfig implements BaseConfig { + columnDiskUsedRatio: string; + columnItems: string; + connectionFetchAll: string; + databaseCreate: string; + databaseDiskUsedRatio: string; + databaseFetchAll: string; + databaseItems: string; + databaseItemsFilterFuzzy: string; + databaseItemsFilterPrecise: string; + diskUsedRatio: string; + processesFetchAll: string; + schemaFetchAll: string; + serverInfo: string; + slowQueryFetchAll: string; + tableDiskUsedRatio: string; + tableFetchAll: string; + tableItems: string; + tableItemsFilterFuzzy: string; + tableItemsFilterPrecise: string; + tableSchemaFetchAll: string; + version = `SELECT version() AS version`; +} diff --git a/src/renderer/config/presto.config.ts b/src/renderer/config/presto.config.ts index 9d108a95..70a90cde 100644 --- a/src/renderer/config/presto.config.ts +++ b/src/renderer/config/presto.config.ts @@ -45,6 +45,11 @@ WHERE (analysis_time_ms + planning_time_ms + queued_time_ms) >= {0} ORDER BY (analysis_time_ms + planning_time_ms + queued_time_ms) DESC LIMIT 100 `; + databaseFetchAll = `SHOW CATALOGS`; + databaseCreate = `CREATE SCHEMA {0}`; + schemaFetchAll = 'SHOW SCHEMAS FROM {0}'; + tableFetchAll = 'SHOW TABLES FROM {0}.{1}'; + tableSchemaFetchAll = `SELECT table_name AS name FROM {0}.information_schema.tables WHERE table_schema='{1}'`; columnDiskUsedRatio: string; columnItems: string; databaseDiskUsedRatio: string; @@ -56,4 +61,5 @@ LIMIT 100 tableItems: string; tableItemsFilterFuzzy: string; tableItemsFilterPrecise: string; + serverInfo = this.version; } diff --git a/src/renderer/config/source.type.config.ts b/src/renderer/config/source.type.config.ts index fdbd2fcc..11fa5e90 100644 --- a/src/renderer/config/source.type.config.ts +++ b/src/renderer/config/source.type.config.ts @@ -31,7 +31,7 @@ export class SourceTypeConfig { experimentalType.description = TranslateUtils.getValue('tooltip.experimental'); // Presto experimentalEngines.push(DatabaseModel.builder(TranslateUtils.getValue('common.presto'), - TranslateUtils.getValue('tooltip.source.resto'), + TranslateUtils.getValue('tooltip.source.presto'), DatabaseEnum.presto, null, true, @@ -45,6 +45,14 @@ export class SourceTypeConfig { true, null, './renderer/assets/icon/source/Trino.svg')); + // MySQL + experimentalEngines.push(DatabaseModel.builder(TranslateUtils.getValue('common.mysql'), + TranslateUtils.getValue('tooltip.source.mysql'), + DatabaseEnum.mysql, + null, + true, + null, + './renderer/assets/icon/source/MySQL.svg')); experimentalType.engines = experimentalEngines; typeEngines.push(basicType, experimentalType); diff --git a/src/renderer/db/dexiedb.ts b/src/renderer/db/dexiedb.ts index 29485dc2..538cb010 100644 --- a/src/renderer/db/dexiedb.ts +++ b/src/renderer/db/dexiedb.ts @@ -10,7 +10,7 @@ export class DexieDb extends Dexie { constructor() { super('dbm_db'); - this.version(5) + this.version(10) .stores({ QueryHistoryTable: '++id,createdTime,startTime,endTime', SnippetTable: '++id,name,created,updated', diff --git a/src/renderer/enum/quick.enum.ts b/src/renderer/enum/quick.enum.ts index 8268c027..effadf21 100644 --- a/src/renderer/enum/quick.enum.ts +++ b/src/renderer/enum/quick.enum.ts @@ -1,5 +1,6 @@ export enum QuickEnum { server, database, - table + table, + table_schema } diff --git a/src/renderer/model/database.model.ts b/src/renderer/model/database.model.ts index a3629b54..5caf8872 100644 --- a/src/renderer/model/database.model.ts +++ b/src/renderer/model/database.model.ts @@ -28,6 +28,7 @@ export class DatabaseModel { * Optional parameter. The user can customize the processing based on whether the user enters a value */ optionalProperties: PropertyModel[]; + supportedSource: DatabaseEnum[] = [DatabaseEnum.clickhosue]; public static builder(name: string, description: string, diff --git a/src/renderer/model/datasource.model.ts b/src/renderer/model/datasource.model.ts index 0c2638c0..1a8e6cd4 100644 --- a/src/renderer/model/datasource.model.ts +++ b/src/renderer/model/datasource.model.ts @@ -1,3 +1,5 @@ +import { DatabaseEnum } from "@renderer/enum/database.enum"; + export class DatasourceModel { id: number; name: string; @@ -14,9 +16,13 @@ export class DatasourceModel { sshPort: number = 22; sshUsername: string = 'root'; sshPassword: string = '123456'; - type: string; + type: DatabaseEnum; version: string; maxTotal = 0; created: Date; updated: Date; + validate: boolean = false; + authorization: boolean = false; + database: string; + catalog: string; } diff --git a/src/renderer/model/operation.model.ts b/src/renderer/model/operation.model.ts index 859571c0..6a04f620 100644 --- a/src/renderer/model/operation.model.ts +++ b/src/renderer/model/operation.model.ts @@ -1,5 +1,6 @@ import { OperationEnum } from '@renderer/enum/operation.enum'; import { TypeEnum } from '@renderer/enum/type.enum'; +import { DatabaseEnum } from "@renderer/enum/database.enum"; export class OperationModel { name: string; @@ -11,4 +12,5 @@ export class OperationNodeModel { type: TypeEnum; actions: OperationEnum[]; children?: OperationNodeModel[]; + supportedSource?: DatabaseEnum[] = [DatabaseEnum.clickhosue]; } diff --git a/src/renderer/model/ssh.model.ts b/src/renderer/model/ssh.model.ts index 9df050bd..5e773a3a 100644 --- a/src/renderer/model/ssh.model.ts +++ b/src/renderer/model/ssh.model.ts @@ -8,4 +8,5 @@ export class SshModel { localPort: number = 8123; localUsername: string; localPassword: string; + database: string; } diff --git a/src/renderer/services/context.menu.service.ts b/src/renderer/services/context.menu.service.ts index 6837c4a1..5ee13b89 100644 --- a/src/renderer/services/context.menu.service.ts +++ b/src/renderer/services/context.menu.service.ts @@ -6,6 +6,7 @@ import { TypeEnum } from '@renderer/enum/type.enum'; import { OperationConfig } from '@renderer/config/operation.config'; import { OperationEnum } from '@renderer/enum/operation.enum'; import { StringUtils } from '@renderer/utils/string.utils'; +import { DatabaseEnum } from "@renderer/enum/database.enum"; @Injectable() export class ContextMenuService { @@ -15,20 +16,23 @@ export class ContextMenuService { this.commonConfig = new OperationConfig().getConfig(); } - getContextMenu(type: TypeEnum, configs?: OperationModel[]): MenuModel[] { + getContextMenu(type: TypeEnum, dataSourceType: DatabaseEnum, configs?: OperationModel[]): MenuModel[] { const menus = new Array(); this.commonConfig.filter(v => v.type === type).forEach(operation => { operation.operations.forEach(operationNode => { - operationNode.actions.forEach(action => { - const menu = this.getMenu(operation, operationNode, action); - if (operationNode?.children) { - menu['children'] = new Array(); - operationNode.children.forEach(operationNodeChild => { - menu['children'].push(this.getMenu(operation, operationNodeChild, operationNodeChild.actions[0])); - }); - } - menus.push(menu); - }); + const supported = operationNode.supportedSource.find(value => value === dataSourceType); + if (supported !== undefined) { + operationNode.actions.forEach(action => { + const menu = this.getMenu(operation, operationNode, action); + if (operationNode?.children) { + menu['children'] = new Array(); + operationNode.children.forEach(operationNodeChild => { + menu['children'].push(this.getMenu(operation, operationNodeChild, operationNodeChild.actions[0])); + }); + } + menus.push(menu); + }); + } }); }); return menus; diff --git a/src/renderer/services/factory.service.ts b/src/renderer/services/factory.service.ts index e00762fb..61b20326 100644 --- a/src/renderer/services/factory.service.ts +++ b/src/renderer/services/factory.service.ts @@ -1,7 +1,8 @@ -import {DatabaseEnum} from "@renderer/enum/database.enum"; -import {Factory} from "@renderer/factory"; -import {ClickhouseConfig} from "@renderer/config/clickhouse.config"; -import {PrestoConfig} from "@renderer/config/presto.config"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { Factory } from "@renderer/factory"; +import { ClickhouseConfig } from "@renderer/config/clickhouse.config"; +import { PrestoConfig } from "@renderer/config/presto.config"; +import { MySQLConfig } from "@renderer/config/plugin/mysql.config"; export class FactoryService { public forward(type: string) { @@ -11,6 +12,8 @@ export class FactoryService { case DatabaseEnum.trino: case DatabaseEnum.presto: return Factory.create(PrestoConfig); + case DatabaseEnum.mysql: + return Factory.create(MySQLConfig); default: new Error("Unsupported database type"); return null; diff --git a/src/renderer/services/forward.service.ts b/src/renderer/services/forward.service.ts index bd94783d..561ac44b 100644 --- a/src/renderer/services/forward.service.ts +++ b/src/renderer/services/forward.service.ts @@ -1,14 +1,15 @@ -import {HttpService} from '@renderer/services/http.service'; -import {SshService} from '@renderer/services/ssh.service'; -import {ResponseModel} from '@renderer/model/response.model'; -import {UrlUtils} from '@renderer/utils/url.utils'; -import {SshModel} from '@renderer/model/ssh.model'; -import {RequestModel} from '@renderer/model/request.model'; -import {SystemBasicModel} from '@renderer/model/system.model'; -import {BasicService} from '@renderer/services/system/basic.service'; -import {PrestoService} from "@renderer/services/presto.service"; -import {DatabaseEnum} from "@renderer/enum/database.enum"; -import {FactoryService} from "@renderer/services/factory.service"; +import { HttpService } from '@renderer/services/http.service'; +import { SshService } from '@renderer/services/ssh.service'; +import { ResponseModel } from '@renderer/model/response.model'; +import { UrlUtils } from '@renderer/utils/url.utils'; +import { SshModel } from '@renderer/model/ssh.model'; +import { RequestModel } from '@renderer/model/request.model'; +import { SystemBasicModel } from '@renderer/model/system.model'; +import { BasicService } from '@renderer/services/system/basic.service'; +import { PrestoService } from "@renderer/services/presto.service"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { FactoryService } from "@renderer/services/factory.service"; +import { MySQLService } from "@renderer/services/plugin/mysql.service"; export class ForwardService { constructor( @@ -17,6 +18,7 @@ export class ForwardService { protected httpService: HttpService, protected sshService: SshService, protected prestoService?: PrestoService, + protected mysqlService?: MySQLService ) { } @@ -37,6 +39,9 @@ export class ForwardService { case DatabaseEnum.presto: response = this.prestoService.execute(configure, sql); break + case DatabaseEnum.mysql: + response = this.mysqlService.execute(configure, sql); + break } return response; case 'SSH': @@ -52,6 +57,7 @@ export class ForwardService { sshConfigure.localPort = configure.port; sshConfigure.localUsername = configure.username; sshConfigure.localPassword = configure.password; + sshConfigure.database = configure.database; return this.sshService.post(sql + '\n FORMAT ' + basicConfig.format, sshConfigure); default: return Promise.reject(new Error('Unsupported protocol')); diff --git a/src/renderer/services/management/datasource.service.ts b/src/renderer/services/management/datasource.service.ts index 56ff978c..639e7d42 100644 --- a/src/renderer/services/management/datasource.service.ts +++ b/src/renderer/services/management/datasource.service.ts @@ -11,6 +11,7 @@ import {SshService} from '@renderer/services/ssh.service'; import {BasicService} from '@renderer/services/system/basic.service'; import {PrestoService} from "@renderer/services/presto.service"; import {FactoryService} from "@renderer/services/factory.service"; +import { MySQLService } from "@renderer/services/plugin/mysql.service"; @Injectable() export class DatasourceService extends PersistenceService implements BaseService { @@ -21,8 +22,9 @@ export class DatasourceService extends PersistenceService implements BaseService factoryService: FactoryService, httpService: HttpService, sshService: SshService, - prestoService: PrestoService) { - super(basicService, factoryService, httpService, sshService, prestoService); + prestoService: PrestoService, + mysqlService: MySQLService) { + super(basicService, factoryService, httpService, sshService, prestoService, mysqlService); this.db = new DexieDb(); } diff --git a/src/renderer/services/management/metadata.service.ts b/src/renderer/services/management/metadata.service.ts index 7418b5a5..0e3c9571 100644 --- a/src/renderer/services/management/metadata.service.ts +++ b/src/renderer/services/management/metadata.service.ts @@ -1,32 +1,34 @@ -import {BaseService} from '@renderer/services/base.service'; -import {HttpService} from '@renderer/services/http.service'; -import {Injectable} from '@angular/core'; -import {ResponseModel} from '@renderer/model/response.model'; -import {RequestModel} from '@renderer/model/request.model'; -import {ConfigModel} from '@renderer/model/config.model'; -import {TypeEnum} from '@renderer/enum/type.enum'; -import {ClickhouseConfig} from '@renderer/config/clickhouse.config'; -import {Factory} from '@renderer/factory'; -import {StringUtils} from '@renderer/utils/string.utils'; -import {DatabaseModel} from '@renderer/model/database.model'; -import {DatabaseEnum} from '@renderer/enum/database.enum'; -import {PropertyModel} from '@renderer/model/property.model'; -import {SshService} from '@renderer/services/ssh.service'; -import {BasicService} from '@renderer/services/system/basic.service'; -import {ForwardService} from '@renderer/services/forward.service'; -import {FilterModel} from '@renderer/model/filter.model'; -import {FactoryService} from "@renderer/services/factory.service"; +import { BaseService } from '@renderer/services/base.service'; +import { HttpService } from '@renderer/services/http.service'; +import { Injectable } from '@angular/core'; +import { ResponseModel } from '@renderer/model/response.model'; +import { RequestModel } from '@renderer/model/request.model'; +import { ConfigModel } from '@renderer/model/config.model'; +import { TypeEnum } from '@renderer/enum/type.enum'; +import { ClickhouseConfig } from '@renderer/config/clickhouse.config'; +import { Factory } from '@renderer/factory'; +import { StringUtils } from '@renderer/utils/string.utils'; +import { DatabaseModel } from '@renderer/model/database.model'; +import { DatabaseEnum } from '@renderer/enum/database.enum'; +import { PropertyModel } from '@renderer/model/property.model'; +import { SshService } from '@renderer/services/ssh.service'; +import { BasicService } from '@renderer/services/system/basic.service'; +import { ForwardService } from '@renderer/services/forward.service'; +import { FilterModel } from '@renderer/model/filter.model'; +import { FactoryService } from "@renderer/services/factory.service"; +import { PrestoService } from "@renderer/services/presto.service"; @Injectable() export class MetadataService extends ForwardService implements BaseService { baseConfig: any; WORD = 'ENGINE'; - constructor(httpService: HttpService, + constructor(basicService: BasicService, factoryService: FactoryService, + httpService: HttpService, sshService: SshService, - basicService: BasicService) { - super(basicService, factoryService, httpService, sshService); + prestoService: PrestoService) { + super(basicService, factoryService, httpService, sshService, prestoService); this.baseConfig = Factory.create(ClickhouseConfig); } @@ -88,12 +90,13 @@ export class MetadataService extends ForwardService implements BaseService { } getInfo(request: RequestModel) { - const sql = this.baseConfig.serverInfo; + const sql = this.factoryService.forward(request.config.type).serverInfo; return this.getResponse(request, sql); } createDatabase(request: RequestModel, database: DatabaseModel): Promise { - const prefix = StringUtils.format('CREATE DATABASE {0}', [database.name]); + const sql = this.factoryService.forward(request.config.type).databaseCreate; + const prefix = StringUtils.format(sql, [database.name]); let suffix; switch (database.type) { case DatabaseEnum.none: diff --git a/src/renderer/services/plugin/mysql.service.ts b/src/renderer/services/plugin/mysql.service.ts new file mode 100644 index 00000000..4f366ce6 --- /dev/null +++ b/src/renderer/services/plugin/mysql.service.ts @@ -0,0 +1,62 @@ +import { Injectable } from "@angular/core"; +import { SystemBasicModel } from "@renderer/model/system.model"; +import { BasicService } from "@renderer/services/system/basic.service"; +import { DatasourceModel } from "@renderer/model/datasource.model"; +import { ResponseDataModel, ResponseModel } from "@renderer/model/response.model"; +import { timeout, TimeoutError } from 'promise-timeout'; + +@Injectable() +export class MySQLService { + + constructor(private basicService: BasicService) { + } + + private getConfig(): SystemBasicModel { + return this.basicService.get() === null ? new SystemBasicModel() : this.basicService.get(); + } + + execute(configure: DatasourceModel, sql: string): Promise { + const network = this.getConfig().network * 1000; + const mysql = require('mysql'); + const connection = mysql.createConnection({ + host: configure.host, + port: configure.port, + user: configure.username, + password: configure.password, + database: configure.database + }); + const response = new ResponseModel(); + const responseData = new ResponseDataModel(); + const somePromise = new Promise((resolve) => { + connection.connect(); + connection.query(sql, function (error, results, fields) { + if (error) { + response.status = false; + response.message = error?.message; + resolve(response); + } else { + responseData.headers = fields; + responseData.columns = results; + response.status = true; + response.data = responseData; + resolve(response); + } + connection.end(); + console.log('Close connection'); + }); + }); + return timeout(somePromise, network) + .then((thing) => { + return thing; + }) + .catch((err) => { + response.status = false; + if (err instanceof TimeoutError) { + response.message = `Promise timed out after ${this.getConfig().network} ms`; + } else { + response.message = err; + } + return response; + }); + } +} diff --git a/src/renderer/services/presto.service.ts b/src/renderer/services/presto.service.ts index 4459ed19..44512535 100644 --- a/src/renderer/services/presto.service.ts +++ b/src/renderer/services/presto.service.ts @@ -1,10 +1,10 @@ -import {Injectable} from "@angular/core"; -import {SystemBasicModel} from "@renderer/model/system.model"; -import {BasicService} from "@renderer/services/system/basic.service"; -import {DatasourceModel} from "@renderer/model/datasource.model"; -import {ResponseDataModel, ResponseModel} from "@renderer/model/response.model"; -import {StringUtils} from "@renderer/utils/string.utils"; -import {timeout, TimeoutError} from 'promise-timeout'; +import { Injectable } from "@angular/core"; +import { SystemBasicModel } from "@renderer/model/system.model"; +import { BasicService } from "@renderer/services/system/basic.service"; +import { DatasourceModel } from "@renderer/model/datasource.model"; +import { ResponseDataModel, ResponseModel } from "@renderer/model/response.model"; +import { StringUtils } from "@renderer/utils/string.utils"; +import { timeout, TimeoutError } from 'promise-timeout'; @Injectable() export class PrestoService { @@ -38,8 +38,8 @@ export class PrestoService { const somePromise = new Promise((resolve) => { client.execute({ query: sql, - catalog: 'hive', - schema: 'default', + catalog: StringUtils.isNotEmpty(configure.catalog) ? configure.catalog : 'hive', + schema: StringUtils.isNotEmpty(configure.database) ? configure.database : 'default', objectMode: true }).then((statement) => { statement.on('columns', (columns) => { diff --git a/src/renderer/services/query/query.history.service.ts b/src/renderer/services/query/query.history.service.ts index 9cdfcb85..0a069094 100644 --- a/src/renderer/services/query/query.history.service.ts +++ b/src/renderer/services/query/query.history.service.ts @@ -4,6 +4,7 @@ import { ResponseModel } from '@renderer/model/response.model'; import { PersistenceService } from '@renderer/services/persistence.service'; import { DexieDb } from '@renderer/db/dexiedb'; import { QueryHistoryModel } from '@renderer/model/query.history.model'; +import { PromiseExtended } from "dexie"; export class QueryHistoryService extends PersistenceService implements BaseService { getResponse(request: RequestModel, sql?: string): Promise { @@ -19,13 +20,21 @@ export class QueryHistoryService extends PersistenceService implements BaseServi const db = new DexieDb(); const queryHistories = new Array(); db.QueryHistoryTable - .orderBy('createdTime') - .reverse() - .toArray() - .then(result => result.forEach(item => queryHistories.push(item))); + .orderBy('createdTime') + .reverse() + .toArray() + .then(result => result.forEach(item => queryHistories.push(item))); return queryHistories; } + getById(id: number): PromiseExtended { + const db = new DexieDb(); + return db.QueryHistoryTable + .orderBy('createdTime') + .filter(value => id.toString() === value.id.toString()) + .first(); + } + clear(): boolean { const db = new DexieDb(); try { diff --git a/src/renderer/services/query/query.quick.service.ts b/src/renderer/services/query/query.quick.service.ts index f39b6df0..0496e55e 100644 --- a/src/renderer/services/query/query.quick.service.ts +++ b/src/renderer/services/query/query.quick.service.ts @@ -2,16 +2,16 @@ export class QueryQuickService { getQuickAll() { return [{ name: 'DESCRIBE ...', - value: 'DESCRIBE `{0}`.`{1}`' + value: 'DESCRIBE {0}.{1}' }, { name: 'SHOW CREATE TABLE ...', - value: 'SHOW CREATE TABLE `{0}`.`{1}`' + value: 'SHOW CREATE TABLE {0}.{1}' }, { name: 'SELECT ... LIMIT 100', - value: 'SELECT * FROM `{0}`.`{1}` LIMIT 100' + value: 'SELECT * FROM {0}.{1} LIMIT 100' }, { name: 'SELECT COUNT FROM ...', - value: 'SELECT COUNT(1) FROM `{0}`.`{1}`' + value: 'SELECT COUNT(1) FROM {0}.{1}' }, { name: 'SHOW PARTITIONS FROM ...', value: 'SELECT `partition` FROM `system`.parts WHERE `database` = \'{0}\' AND `table` = \'{1}\'' diff --git a/src/renderer/services/query/query.service.ts b/src/renderer/services/query/query.service.ts index d1d61319..6432b081 100644 --- a/src/renderer/services/query/query.service.ts +++ b/src/renderer/services/query/query.service.ts @@ -9,6 +9,7 @@ import { SshService } from '@renderer/services/ssh.service'; import { BasicService } from '@renderer/services/system/basic.service'; import { FactoryService } from "@renderer/services/factory.service"; import { PrestoService } from "@renderer/services/presto.service"; +import { MySQLService } from "@renderer/services/plugin/mysql.service"; const {Parser} = require('node-sql-parser'); @@ -18,8 +19,9 @@ export class QueryService extends ForwardService implements BaseService { sshService: SshService, basicService: BasicService, factoryService: FactoryService, - prestoService: PrestoService) { - super(basicService, factoryService, httpService, sshService, prestoService); + prestoService: PrestoService, + mysqlService: MySQLService) { + super(basicService, factoryService, httpService, sshService, prestoService, mysqlService); } getResponse(request: RequestModel, sql?: string): Promise { diff --git a/src/renderer/services/ssh.service.ts b/src/renderer/services/ssh.service.ts index fb1f608f..6d26ca71 100644 --- a/src/renderer/services/ssh.service.ts +++ b/src/renderer/services/ssh.service.ts @@ -21,10 +21,10 @@ export class SshService { const http = require('http'); const Client = require('electron-ssh2').Client; const conn = new Client(); - conn.on('ready', function() { + conn.on('ready', function () { console.log('Ssh Client :: ready'); const response = new ResponseModel(); - conn.forwardOut('', 0, sshConfigure.localHost, sshConfigure.localPort, function(err, stream) { + conn.forwardOut('', 0, sshConfigure.localHost, sshConfigure.localPort, function (err, stream) { if (err) { response.status = false; response.message = err; @@ -37,6 +37,15 @@ export class SshService { path = StringUtils.format('/?user={0}&password={1}', [sshConfigure.localUsername, sshConfigure.localPassword]); } + + if (StringUtils.isNotEmpty(sshConfigure.database)) { + if (path.indexOf('?') === -1) { + path = StringUtils.format('{0}?database={1}', [path, sshConfigure.database]); + } else { + path = StringUtils.format('{0}&database={1}', [path, sshConfigure.database]); + } + } + console.log('Ssh Client :: path', path); // Setup HTTP request parameters const requestParams = { @@ -44,92 +53,92 @@ export class SshService { path: encodeURI(path), headers: {}, timeout: 3, - createConnection: function() { + createConnection: function () { return stream; } }; - const request = http.request(requestParams, function(response) { + const request = http.request(requestParams, function (response) { console.log('STATUS: ' + response.statusCode); const bodyArray = []; - response.on('data', function(chunk) { + response.on('data', function (chunk) { console.log('BODY Length: ' + chunk.length); bodyArray.push(chunk); }) - .on('error', function(err) { - console.log('HTTP :: ERROR: ' + err); - stream.on('close', function() { - console.log('HTTP :: Stream closed'); - conn.end(); - }).end(); - response.status = false; - response.message = err; - resolve(response); - }) - .on('end', function() { - console.log('HTTP :: Stream ended'); - stream.on('close', function() { - console.log('HTTP :: Stream closed'); - conn.end(); - }).end(); - if (response.statusCode === 200) { - try { - const body = JSON.parse(Buffer.concat(bodyArray).toString()); - const responseData = new ResponseDataModel(); - responseData.headers = body['meta']; - responseData.columns = body['data']; - responseData.rows = body['rows']; - responseData.statistics = body['statistics']; - response.data = responseData; - } catch (e) { - response.message = 'Success'; - } - response.status = true; - } else { + .on('error', function (err) { + console.log('HTTP :: ERROR: ' + err); + stream.on('close', function () { + console.log('HTTP :: Stream closed'); + conn.end(); + }).end(); response.status = false; - response.message = Buffer.concat(bodyArray).toString(); - } - resolve(response); - }); + response.message = err; + resolve(response); + }) + .on('end', function () { + console.log('HTTP :: Stream ended'); + stream.on('close', function () { + console.log('HTTP :: Stream closed'); + conn.end(); + }).end(); + if (response.statusCode === 200) { + try { + const body = JSON.parse(Buffer.concat(bodyArray).toString()); + const responseData = new ResponseDataModel(); + responseData.headers = body['meta']; + responseData.columns = body['data']; + responseData.rows = body['rows']; + responseData.statistics = body['statistics']; + response.data = responseData; + } catch (e) { + response.message = 'Success'; + } + response.status = true; + } else { + response.status = false; + response.message = Buffer.concat(bodyArray).toString(); + } + resolve(response); + }); }); - request.on('timeout', function() { + request.on('timeout', function () { console.log('HTTP :: Timeout'); response.status = false; response.message = 'Connection timeout'; resolve(response); request.destroy(); }) - .on('error', function(err) { - console.log('HTTP :: ERROR: ' + err); - stream.on('close', function() { - console.log('HTTP :: Stream closed'); - conn.end(); - }).end(); - response.status = false; - response.message = err; - resolve(response); - }); + .on('error', function (err) { + console.log('HTTP :: ERROR: ' + err); + stream.on('close', function () { + console.log('HTTP :: Stream closed'); + conn.end(); + }).end(); + response.status = false; + response.message = err; + resolve(response); + }); console.log('HTTP :: Sending request'); request.end(data); }); }) - .on('error', function(err) { - console.log('Client :: error :: ' + err); - const response = new ResponseModel(); - response.status = false; - response.message = err; - resolve(response); - }) - .on('end', function() { - console.log('Client :: stream :: ended'); - conn.end(); - }) - .connect({ - host: sshConfigure.sshHost, - port: sshConfigure.sshPort, - username: sshConfigure.sshUsername, - password: sshConfigure.sshPassword, - timeout: sshConfigure.timeout - }); + .on('error', function (err) { + console.log('Client :: error :: ' + err); + const response = new ResponseModel(); + response.status = false; + response.message = err; + resolve(response); + }) + .on('end', function () { + console.log('Client :: stream :: ended'); + conn.end(); + }) + .connect({ + host: sshConfigure.sshHost, + port: sshConfigure.sshPort, + username: sshConfigure.sshUsername, + password: sshConfigure.sshPassword, + timeout: sshConfigure.timeout + }); }); } } diff --git a/src/renderer/utils/url.utils.ts b/src/renderer/utils/url.utils.ts index 7a2973a8..9592bc00 100644 --- a/src/renderer/utils/url.utils.ts +++ b/src/renderer/utils/url.utils.ts @@ -15,14 +15,21 @@ export class UrlUtils { } if (request.params) { const params = request.params - .map(param => StringUtils.format('{0}={1}', [param.key, param.value])) - .join('&'); + .map(param => StringUtils.format('{0}={1}', [param.key, param.value])) + .join('&'); if (hasAuthentication) { remoteUrl = StringUtils.format('{0}&{1}', [remoteUrl, params]); } else { remoteUrl = StringUtils.format('{0}?{1}', [remoteUrl, params]); } } + if (StringUtils.isNotEmpty(request.config.database)) { + if (remoteUrl.indexOf('?') === -1) { + remoteUrl = StringUtils.format('{0}?database={1}', [remoteUrl, request.config.database]); + } else { + remoteUrl = StringUtils.format('{0}&database={1}', [remoteUrl, request.config.database]); + } + } return remoteUrl; } } diff --git a/src/shared/assets/integrate/mysql.png b/src/shared/assets/integrate/mysql.png new file mode 100644 index 00000000..7542565a Binary files /dev/null and b/src/shared/assets/integrate/mysql.png differ diff --git a/webpack.renderer.js b/webpack.renderer.js index 60468c48..b9d60da1 100644 --- a/webpack.renderer.js +++ b/webpack.renderer.js @@ -1,5 +1,6 @@ module.exports = (config, options) => { config.target = 'electron-renderer'; - + // Fix mysql Received packet in the wrong sequence. + config.optimization.minimize = false; return config; };