Browse Source

Merge pull request #7367 in DEC/decision-webui-dcm from final/11.0 to persist/11.0

* commit '4d341bd6565eaf832f0de4710e83d8a2d4b08d7d':
  REPORT-91742 fix: 补充下默认值
  优化下逻辑
  REPORT-91742 fix: 修复测试连接失败问题
  REPORT-92280 fix:timeBetweenEvictionRunsMillis默认值调整
  KERNEL-10281 docs:完善数据连接readme
  无JIRA,处理下格式问题
  移除无用代码
  REPORT-90545: 驱动检测平台前端适配
  REPORT-90357 fix: 修改主机、端口后清空密码
  REPORT-91394 fix:jdbcResolve添加适配syabse
persist/11.0
superman 2 years ago
parent
commit
0e1a113d06
  1. 100
      README.md
  2. 2
      src/modules/app.provider.ts
  3. 36
      src/modules/components/test_status/test_status.ts
  4. 2
      src/modules/constants/constant.ts
  5. 12
      src/modules/crud/api.ts
  6. 9
      src/modules/crud/crud.typings.d.ts
  7. 23
      src/modules/crud/decision.api.ts
  8. 23
      src/modules/crud/design.api.ts
  9. 16
      src/modules/pages/connection/list/list_item/list_item.ts
  10. 16
      src/modules/pages/maintain/forms/components/form.jdbc.ts
  11. 58
      src/modules/pages/maintain/forms/form.server.ts
  12. 1
      types/globals.d.ts

100
README.md

@ -7,7 +7,7 @@
## 开始
安装依赖
```
yarn
yarn install
```
开始开发
@ -15,6 +15,104 @@ yarn
yarn dev
```
## 决策平台开发:
### A.项目运行
#### 1. 工程`decision-webui-dev`添加代理(可跳过)
```js
webpack/webpack.config
"/plugin/dcm": {
pathRewrite: { "^/plugin/dcm": "" },
target: "http://localhost:10002",
},
```
#### 2. 工程`decision-webui-dev`引入
fr环境:`templates/bundle.report.html` bi环境:`templates/bundle.bi.html`
```html
// css 文件:
<head>
<link rel="stylesheet" type="text/css" href="/plugin/dcm/show.dev.css" />
<link rel="stylesheet" type="text/css" href="http://localhost:10002/show.dev.css" />
</head>
// js 文件
<script type="text/javascript" src="/plugin/dcm/show.dev.js"></script>
```
若未设1,将`/plugin/dcm`替换成`http://localhost:10002`亦可
#### 3. 启动工程[decision-webui-dev]以及数据连接[desicion-webui-dcm]工程
#### 4. 此时工程`decision-webui-dev`的`http://localhost:9002/#management/connnection`数据连接模块已替换成该工程
### B.插件形式添加数据连接-数据库
#### 1. 以多版本的tdsql为例 单一版本数据库不需drivers,versions,hasSchemas
```js
BI.config("dec.connection.provider.datebase", function (provider) {
BI.isFunction(provider.registerJdbcDatabase) && provider.registerJdbcDatabase({
text: 'TDSQL', // 数据库名称
databaseType: 'tdsql', // 数据库key
driver: 'org.postgresql.Driver', // 默认驱动
drivers: {
"pgsql": ["org.postgresql.Driver"],
"mysql": ["com.mysql.jdbc.Driver"]
}, // 驱动可选项,version: array[driver],[0]为该版本的默认驱动
versions: ["pgsql", "mysql"], // array[version]
urls: {
"org.postgresql.Driver": "jdbc:postgresql://hostname:port/database?finedbType=tdsql-pgsql",
"com.mysql.jdbc.Driver": "jdbc:mysql://hostname:port/database?finedbType=tdsql-mysql"
}, // urlkey : url 一个驱动对应一个url
url: 'jdbc:postgresql://hostname:port/database?finedbType=tdsql-pgsql',
commonly: false,
internal: true,
type: 'jdbc', 数据库类型
hasSchema: true, // 默认是否支持模式
hasSchemas: {
"pgsql": true,
"mysql": false,
},是否支持模式 version: boolean
kerberos: false, // 是否添加kerberos认证方式
}, function (url) {
var result = url.match(/^jdbc:(mysql|postgresql):\/\/([0-9a-zA-Z_\\.-]+)(:([0-9|port]+))?\/([0-9a-zA-Z_\\.]+)(.*)finedbType=([^&]+)(|(&.*))/i); // 匹配正则
if (result) {
return {
host: result[2], //主机
port: result[4] === "port" ? "" : result[4], // 端口
databaseName: result[5], // 数据库名称
version: result[7].split('-')[1] ?? "pgsql", // 版本 单版本不要返回这个
};
}
//适配原先tbase的url
result = url.match(/^jdbc:postgresql:\/\/([0-9a-zA-Z_\\.-]+)(:([0-9|port]+))?\/([0-9a-zA-Z_\\.]+)(.*)/i);
if (result) {
return {
host: result[1],
port: result[3] === "port" ? "" : result[3],
databaseName: result[4],
version: "pgsql",
};
}
});
});
```
### C 工程开发
#### 1. 图片资源添加
工程`decision-webui-dev`
decision-webui/dist/images/1x/icon/database
decision-webui/dist/images/2x/icon/database
#### 2. 国际化添加
工程`decision-webui-dev`
decision-i18n/decision-main-i18n/src/main/resources/com/fr/decision/web/i18n
#### 3. 版本控制
版本和平台保持一致
## 接口文档:
### 增加数据连接类型
使用`BI.config`,ConstantName名称为`dec.constant.database.conf.connect.types`,值为连接的名称

2
src/modules/app.provider.ts

@ -26,7 +26,7 @@ BI.provider('dec.connection.provider.datebase', function () {
urlInfo: greenplumUrl[9],
};
}
const result = url.match(/^jdbc:(mysql|sqlserver|db2|dm|impala|kylin|phoenix|derby|gbase|gbasedbt-sqli|informix-sqli|h2|postgresql|hive2|vertica|kingbase|presto|redshift|postgresql|clickhouse|trino):(thin:([0-9a-zA-Z/]*)?@|thin:([0-9a-zA-Z/]*)?@\/\/|\/\/|)([0-9a-zA-Z_\\.-]+)(:([0-9|port]+))?(\/|;DatabaseName=)?([^]+)?(.*)/i);
const result = url.match(/^jdbc:(mysql|sqlserver|db2|dm|impala|kylin|phoenix|derby|gbase|gbasedbt-sqli|informix-sqli|h2|postgresql|hive2|vertica|kingbase|presto|redshift|postgresql|clickhouse|trino|sybase:Tds):(thin:([0-9a-zA-Z/]*)?@|thin:([0-9a-zA-Z/]*)?@\/\/|\/\/|)([0-9a-zA-Z_\\.-]+)(:([0-9|port]+))?(\/|;DatabaseName=)?([^]+)?(.*)/i);
if (result) {
return {
host: result[5],

36
src/modules/components/test_status/test_status.ts

@ -29,7 +29,9 @@ export class TestStatus extends BI.Widget {
failDriverMessage: Label;
driverLink: FloatLeftLayout;
detail: VerticalLayout;
failMaskers:any;
failMaskers: any;
extraContainer: VerticalLayout;
watch = {
status: (status: string) => {
@ -38,8 +40,9 @@ export class TestStatus extends BI.Widget {
}
render() {
const LAYOUT_WIDTH = 400;
const { loadingCls, loadingText, successCls, successText, failCls, failText, retryText } = this.options;
var self=this;
return {
type: BI.CenterAdaptLayout.xtype,
cls: 'bi-z-index-mask',
@ -71,10 +74,10 @@ export class TestStatus extends BI.Widget {
tipCls: failCls,
tipText: failText,
retryText,
ref:(_ref:any)=>{
self.failMaskers=_ref;
if(this.failMessage.getText()===''){
this.failMaskers.populateFail(BI.i18nText("Dec-Conn-ect-Failed"),false);
ref: (_ref: TipFail) => {
this.failMaskers = _ref;
if (BI.isEmptyString(this.failMessage.getText())) {
this.failMaskers.populateFail(BI.i18nText('Dec-Conn-ect-Failed'), false);
}
},
listeners: [
@ -123,10 +126,17 @@ export class TestStatus extends BI.Widget {
scrolly: true,
height: 75,
items: [
{
type: BI.VerticalLayout.xtype,
width: LAYOUT_WIDTH,
ref: (_ref: VerticalLayout) => {
this.extraContainer = _ref;
}
},
{
type: BI.Label.xtype,
whiteSpace: 'normal',
width: 400,
width: LAYOUT_WIDTH,
textAlign: 'left',
text: '',
ref: (_ref: Label) => {
@ -175,7 +185,7 @@ export class TestStatus extends BI.Widget {
this.store.setStatus(TEST_STATUS.SUCCESS);
}
setFail(message: string='', driver = '', link = '') {
setFail(message: string = '', driver = '', link = '') {
this.store.setStatus(TEST_STATUS.FAIL);
this.failMessage.setText(message);
this.failDriverMessage.setVisible(!!driver);
@ -189,4 +199,14 @@ export class TestStatus extends BI.Widget {
setLoading() {
this.store.setStatus(TEST_STATUS.LOADING);
}
/**
*
*/
setExtraContainer(container: Obj) {
BI.createWidget({
...container,
element: this.extraContainer,
});
}
}

2
src/modules/constants/constant.ts

@ -831,7 +831,7 @@ export const DEFAULT_JDBC_POOL = {
testOnBorrow: true,
testOnReturn: false,
testWhileIdle: false,
timeBetweenEvictionRunsMillis: -1,
timeBetweenEvictionRunsMillis: 60000,
numTestsPerEvictionRun: 3,
minEvictableIdleTimeMillis: 1800,
};

12
src/modules/crud/api.ts

@ -5,6 +5,7 @@ import {
ConnectionPoolType,
SocketResult,
ResultType,
checkDriverStatusParams,
} from './crud.typings';
export interface Api {
@ -46,6 +47,17 @@ export interface Api {
*/
testConnection(data: Connection): Promise<TestRequest>;
/**
*
*/
getDriverLoadPath(data: Connection): Promise<ResultType<string>>;
/**
*
* @param data
*/
checkDriverStatus(data: checkDriverStatusParams): Promise<ResultType<boolean>>;
/**
*
* @param name

9
src/modules/crud/crud.typings.d.ts vendored

@ -306,8 +306,13 @@ export interface SocketResult {
errorMsg?: string;
}
export interface ResultType {
data?: any;
export interface ResultType<T = any> {
data?: T;
errorCode?: string;
errorMsg?: string;
}
export type checkDriverStatusParams = {
path: string;
driver: ConnectionJDBC['driver'];
}

23
src/modules/crud/decision.api.ts

@ -1,5 +1,5 @@
import { Api } from './api';
import { Connection, TestRequest, ConnectionPoolType, SocketResult, ConnectionLicInfo } from './crud.typings';
import { Connection, TestRequest, ConnectionPoolType, SocketResult, ConnectionLicInfo, ResultType, checkDriverStatusParams } from './crud.typings';
import { requestGet, requestDelete, requestPost, requestPut } from './crud.service';
import { editStatusEvent, errorCode } from '@constants/env';
@ -48,6 +48,27 @@ export class DecisionApi implements Api {
return requestPost('test', form);
}
/**
*
* @returns
*/
getDriverLoadPath(data: Connection): Promise<ResultType<string>> {
const form = {
...data,
connectionData: JSON.stringify(data.connectionData),
};
return requestPost('driver/path', form);
}
/**
*
* @param data
*/
checkDriverStatus(data: checkDriverStatusParams): Promise<ResultType<boolean>> {
return requestGet(Dec.Utils.getEncodeURL('test/driver/conflict', '', data));
}
getConnectionPool(name: string): Promise<{ data?: ConnectionPoolType }> {
return requestGet(`pool/info?connectionName=${encodeURIComponent(name)}`, {});
}

23
src/modules/crud/design.api.ts

@ -1,5 +1,5 @@
import { Api } from './api';
import { Connection, TestRequest, ConnectionPoolType, SocketResult, ConnectionLicInfo } from './crud.typings';
import { Connection, TestRequest, ConnectionPoolType, SocketResult, ConnectionLicInfo, ResultType, ConnectionJDBC, checkDriverStatusParams } from './crud.typings';
import { requestGet } from './crud.service';
// TODO: 此页面的接口等待设计器提供相应的方法
@ -39,6 +39,27 @@ export class DesignApi implements Api {
});
}
/**
*
* @param name
* @returns
*/
getDriverLoadPath(data: Connection): Promise<ResultType<string>> {
return new Promise(resolve => {
resolve({ data: '' });
});
}
/**
*
* @param data
*/
checkDriverStatus(data: checkDriverStatusParams): Promise<ResultType<boolean>> {
return new Promise(resolve => {
resolve({ data: false });
});
}
getConnectionPool(name: string): Promise<{ data: ConnectionPoolType }> {
return new Promise(resolve => {
resolve({

16
src/modules/pages/connection/list/list_item/list_item.ts

@ -6,6 +6,9 @@ import { hasRegistered } from '../list.service';
import { connectionCanEdit, getTextByDatabaseType, getChartLength } from '../../../../app.service';
import { testConnection } from '../../../maintain/forms/form.server';
import { DownListCombo, Label, SignEditor } from '@fui/core';
import { ApiFactory } from '../../../../crud/apiFactory';
const api = new ApiFactory().create();
@shortcut()
@store(ListItemModel)
@ -206,8 +209,17 @@ export class ListItem extends BI.BasicButton {
}
private testConnectionAction() {
const { name } = this.options;
testConnection(this.model.connections.find(item => item.connectionName === name));
// 接口返回的内容是对称加密的,前端要先解密再用新加密传回去
const connection = this.model.connections
.find(item => item.connectionName === this.options.name);
if (BI.isNull(connection)) return;
const validationQuery = connection?.connectionData?.connectionPoolAttr?.validationQuery || '';
BI.set(connection, 'connectionData.connectionPoolAttr.validationQuery', api.getCipher(api.getPlain(validationQuery)));
testConnection(connection);
}
private itemActionCalculate() {

16
src/modules/pages/maintain/forms/components/form.jdbc.ts

@ -1015,6 +1015,14 @@ export class FormJdbc extends BI.Widget {
watermark: BI.i18nText('Dec-Dcm_Connection_Form_Host'),
allowBlank: false,
value: sshIp || 'hostname',
listeners: [
{
eventName: BI.Editor.EVENT_CHANGE,
action: () => {
this.form.sshSecret.setValue("");
}
}
]
},
],
},
@ -1038,6 +1046,14 @@ export class FormJdbc extends BI.Widget {
valueRangeConfig,
],
value: String(sshPort || 22),
listeners: [
{
eventName: BI.Editor.EVENT_CHANGE,
action: () => {
this.form.sshSecret.setValue("");
}
}
]
},
],
},

58
src/modules/pages/maintain/forms/form.server.ts

@ -5,6 +5,7 @@ import { TestStatus } from '../../../components/test_status/test_status';
import { getJdbcDatabaseType } from '../../../app.service';
import { ApiFactory } from '../../../crud/apiFactory';
const api = new ApiFactory().create();
export function testConnection(value: Connection): Promise<string[]> {
return new Promise(resolve => {
let testStatus = null;
@ -15,16 +16,18 @@ export function testConnection(value: Connection): Promise<string[]> {
return false;
}
const id = BI.UUID();
const testConnection = () => {
const formValue = value;
api.testConnection(formValue).then(re => {
if (re && re.errorCode) {
if(re.errorCode === DecCst.ErrorCode.NO_IP_AUTHORIZED){
if (re.errorCode === DecCst.ErrorCode.NO_IP_AUTHORIZED) {
testStatus.setFail();
return;
}
// 判断是否是缺少驱动,如果缺少驱动则显示下载驱动的连接
// 判断是否是缺少驱动,如果缺少驱动则显示下载驱动的连接
if (api.isDriverError(re.errorCode)) {
if (formValue.connectionType === connectionType.JDBC) {
const driver = (formValue.connectionData as ConnectionJDBC).driver;
@ -44,7 +47,11 @@ export function testConnection(value: Connection): Promise<string[]> {
} else if (re.errorCode === errorCode.DUPLICATE_NAMES) {
testStatus.setFail(BI.i18nText(re.errorMsg));
} else {
// 不缺少驱动,但连接失败,打印出当前驱动加载路径,并显示检测驱动按钮
testStatus.setFail(re.errorMsg);
api.getDriverLoadPath(formValue).then(res => {
testStatus.setExtraContainer(createDriverTestContainer(res.data));
})
}
} else if (re.data) {
testStatus.setSuccess();
@ -59,7 +66,54 @@ export function testConnection(value: Connection): Promise<string[]> {
BI.Maskers.remove(id);
}
});
/**
*
*/
function createDriverTestContainer(path: string) {
return {
type: BI.VerticalLayout.xtype,
vgap: 5,
items: [
{
type: BI.Label.xtype,
text: BI.i18nText('Dec-Connection_Driver_Current_Load_Path', path),
textAlign: 'left',
whiteSpace: 'normal',
},
{
type: BI.TextButton.xtype,
cls: 'bi-high-light',
text: BI.i18nText('Dec-Connection_Driver_Check'),
textAlign: 'left',
handler: () => {
api.checkDriverStatus({
driver: (formValue.connectionData as ConnectionJDBC).driver,
path,
}).then(res => {
const isDriverConflict = res.data;
testStatus.setExtraContainer({
type: BI.VerticalLayout.xtype,
items: [
{
type: BI.Label.xtype,
textAlign: 'left',
text: isDriverConflict
? BI.i18nText('Dec-Connection_Driver_Has_Confilt_Tip')
: BI.i18nText('Dec-Connection_Driver_No_Confilt_Tip'),
cls: isDriverConflict ? 'bi-error' : '',
}
]
})
});
}
}
]
}
}
};
BI.Maskers.create(id, null, {
render: {
type: TestStatus.xtype,

1
types/globals.d.ts vendored

@ -16,6 +16,7 @@ declare const Dec: {
personal: {
username: string;
};
Utils: Obj;
reqByEncrypt: (method: AxiosType.X_Method, url: string, data?: any, config?: AxiosType.X_AxiosRequestConfig) => {},
socketEmit: (type: string, name: string, callback: (re: any) => void) => void;
// req

Loading…
Cancel
Save