diff --git a/README.md b/README.md
index cca899a..50cd82d 100644
--- a/README.md
+++ b/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 文件:
+
+
+
+
+
+ // js 文件
+
+```
+ 若未设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`,值为连接的名称
diff --git a/src/modules/app.provider.ts b/src/modules/app.provider.ts
index 791d2be..6ce5bce 100644
--- a/src/modules/app.provider.ts
+++ b/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],
diff --git a/src/modules/components/test_status/test_status.ts b/src/modules/components/test_status/test_status.ts
index 8929f7d..a055e6e 100644
--- a/src/modules/components/test_status/test_status.ts
+++ b/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,
+ });
+ }
}
diff --git a/src/modules/constants/constant.ts b/src/modules/constants/constant.ts
index dbf1b28..fa34f1f 100644
--- a/src/modules/constants/constant.ts
+++ b/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,
};
diff --git a/src/modules/crud/api.ts b/src/modules/crud/api.ts
index daa9e4d..01ed8dd 100644
--- a/src/modules/crud/api.ts
+++ b/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;
+ /**
+ * 获取驱动加载路径
+ */
+ getDriverLoadPath(data: Connection): Promise>;
+
+ /**
+ * 检测驱动冲突状态
+ * @param data 驱动路径
+ */
+ checkDriverStatus(data: checkDriverStatusParams): Promise>;
+
/**
* 获取连接池数据
* @param name
diff --git a/src/modules/crud/crud.typings.d.ts b/src/modules/crud/crud.typings.d.ts
index 0193583..8a5c8b9 100644
--- a/src/modules/crud/crud.typings.d.ts
+++ b/src/modules/crud/crud.typings.d.ts
@@ -306,8 +306,13 @@ export interface SocketResult {
errorMsg?: string;
}
-export interface ResultType {
- data?: any;
+export interface ResultType {
+ data?: T;
errorCode?: string;
errorMsg?: string;
}
+
+export type checkDriverStatusParams = {
+ path: string;
+ driver: ConnectionJDBC['driver'];
+}
\ No newline at end of file
diff --git a/src/modules/crud/decision.api.ts b/src/modules/crud/decision.api.ts
index a77f406..dbcf141 100644
--- a/src/modules/crud/decision.api.ts
+++ b/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> {
+ const form = {
+ ...data,
+ connectionData: JSON.stringify(data.connectionData),
+ };
+
+ return requestPost('driver/path', form);
+ }
+
+ /**
+ * 检测驱动冲突状态
+ * @param data 驱动路径
+ */
+ checkDriverStatus(data: checkDriverStatusParams): Promise> {
+ return requestGet(Dec.Utils.getEncodeURL('test/driver/conflict', '', data));
+ }
+
getConnectionPool(name: string): Promise<{ data?: ConnectionPoolType }> {
return requestGet(`pool/info?connectionName=${encodeURIComponent(name)}`, {});
}
diff --git a/src/modules/crud/design.api.ts b/src/modules/crud/design.api.ts
index 643ad8e..d629b16 100644
--- a/src/modules/crud/design.api.ts
+++ b/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> {
+ return new Promise(resolve => {
+ resolve({ data: '' });
+ });
+ }
+
+ /**
+ * 检测驱动冲突状态
+ * @param data 驱动路径
+ */
+ checkDriverStatus(data: checkDriverStatusParams): Promise> {
+ return new Promise(resolve => {
+ resolve({ data: false });
+ });
+ }
+
getConnectionPool(name: string): Promise<{ data: ConnectionPoolType }> {
return new Promise(resolve => {
resolve({
diff --git a/src/modules/pages/connection/list/list_item/list_item.ts b/src/modules/pages/connection/list/list_item/list_item.ts
index 58537d7..60736d3 100644
--- a/src/modules/pages/connection/list/list_item/list_item.ts
+++ b/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() {
diff --git a/src/modules/pages/maintain/forms/components/form.jdbc.ts b/src/modules/pages/maintain/forms/components/form.jdbc.ts
index 869f63d..7db6718 100644
--- a/src/modules/pages/maintain/forms/components/form.jdbc.ts
+++ b/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("");
+ }
+ }
+ ]
},
],
},
diff --git a/src/modules/pages/maintain/forms/form.server.ts b/src/modules/pages/maintain/forms/form.server.ts
index 67647fc..2098ac1 100644
--- a/src/modules/pages/maintain/forms/form.server.ts
+++ b/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 {
return new Promise(resolve => {
let testStatus = null;
@@ -15,16 +16,18 @@ export function testConnection(value: Connection): Promise {
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 {
} 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 {
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,
diff --git a/types/globals.d.ts b/types/globals.d.ts
index f868d4b..0eb147e 100644
--- a/types/globals.d.ts
+++ b/types/globals.d.ts
@@ -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