feat: support mysql as data source (#1018)
Reviewed-on: daoyoucloud/tachybase#1018
This commit is contained in:
parent
f0e51e1e89
commit
5410c362a0
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@hera/plugin-external-data-source",
|
||||
"name": "@tachybase/plugin-external-data-source",
|
||||
"version": "0.0.13",
|
||||
"main": "dist/server/index.js",
|
||||
"dependencies": {
|
@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { SchemaComponent } from '@tachybase/client';
|
||||
import { tval, usePluginTranslation } from '../locale';
|
||||
|
||||
export const MysqlDataSourceSettingsForm = () => {
|
||||
const { t } = usePluginTranslation();
|
||||
return (
|
||||
<SchemaComponent
|
||||
scope={{ t }}
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
title: tval('Data source name'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-validator': 'uid',
|
||||
'x-disabled': '{{ createOnly }}',
|
||||
description: tval(
|
||||
'Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.',
|
||||
),
|
||||
},
|
||||
displayName: {
|
||||
type: 'string',
|
||||
title: tval('Data source display name'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
options: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
host: {
|
||||
type: 'string',
|
||||
title: tval('Host'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
default: 'localhost',
|
||||
},
|
||||
port: {
|
||||
type: 'string',
|
||||
title: tval('Port'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
default: 3306,
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
title: tval('Database'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
title: tval('Username'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
title: tval('Password'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Password',
|
||||
},
|
||||
tablePrefix: {
|
||||
type: 'string',
|
||||
title: tval('Table prefix'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
enabled: {
|
||||
type: 'string',
|
||||
'x-content': tval('Enabled the data source'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,11 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Plugin, SchemaComponent } from '@tachybase/client';
|
||||
import {} from '@tachybase/client';
|
||||
import { PluginDataSourceManagerClient } from '@nocobase/plugin-data-source-manager/client';
|
||||
import { Space } from 'antd';
|
||||
import { generateNTemplate as tval, usePluginTranslation } from './locale';
|
||||
import { SchemaComponent } from '@tachybase/client';
|
||||
import { tval, usePluginTranslation } from '../locale';
|
||||
|
||||
const DataSourceSettingsForm = () => {
|
||||
export const PgDataSourceSettingsForm = () => {
|
||||
const { t } = usePluginTranslation();
|
||||
return (
|
||||
<SchemaComponent
|
||||
@ -29,7 +27,7 @@ const DataSourceSettingsForm = () => {
|
||||
displayName: {
|
||||
type: 'string',
|
||||
title: tval('Data source display name'),
|
||||
required: !0,
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
@ -39,7 +37,7 @@ const DataSourceSettingsForm = () => {
|
||||
host: {
|
||||
type: 'string',
|
||||
title: tval('Host'),
|
||||
required: !0,
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
default: 'localhost',
|
||||
@ -47,7 +45,7 @@ const DataSourceSettingsForm = () => {
|
||||
port: {
|
||||
type: 'string',
|
||||
title: tval('Port'),
|
||||
required: !0,
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
default: 5432,
|
||||
@ -55,14 +53,14 @@ const DataSourceSettingsForm = () => {
|
||||
database: {
|
||||
type: 'string',
|
||||
title: tval('Database'),
|
||||
required: !0,
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
title: tval('Username'),
|
||||
required: !0,
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
@ -98,13 +96,3 @@ const DataSourceSettingsForm = () => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export class PluginExternalDataSourceClient extends Plugin {
|
||||
async load() {
|
||||
this.app.pm
|
||||
.get(PluginDataSourceManagerClient)
|
||||
.registerType('postgres', { DataSourceSettingsForm, label: tval('PostgreSQL') });
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginExternalDataSourceClient;
|
@ -0,0 +1,19 @@
|
||||
import { Plugin } from '@tachybase/client';
|
||||
import { PluginDataSourceManagerClient } from '@nocobase/plugin-data-source-manager/client';
|
||||
import { tval } from './locale';
|
||||
import { PgDataSourceSettingsForm } from './forms/pg';
|
||||
import { MysqlDataSourceSettingsForm } from './forms/msql';
|
||||
|
||||
export class PluginExternalDataSourceClient extends Plugin {
|
||||
async load() {
|
||||
this.app.pm
|
||||
.get(PluginDataSourceManagerClient)
|
||||
.registerType('postgres', { DataSourceSettingsForm: PgDataSourceSettingsForm, label: tval('PostgreSQL') });
|
||||
|
||||
this.app.pm
|
||||
.get(PluginDataSourceManagerClient)
|
||||
.registerType('mysql', { DataSourceSettingsForm: MysqlDataSourceSettingsForm, label: tval('MySQL') });
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginExternalDataSourceClient;
|
@ -6,7 +6,7 @@ export const NAMESPACE = '@hera/plugin-external-data-source';
|
||||
export function lang(key: string, options = {}) {
|
||||
return i18n.t(key, { ...options, ns: NAMESPACE });
|
||||
}
|
||||
export function generateNTemplate(key: string) {
|
||||
export function tval(key: string) {
|
||||
return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
|
||||
}
|
||||
export function usePluginTranslation() {
|
@ -0,0 +1,219 @@
|
||||
import { ViewFieldInference } from '@tachybase/database';
|
||||
import { mergeOptions } from '@nocobase/plugin-data-source-manager';
|
||||
import { EventEmitter } from 'events';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class DatabaseIntrospector extends EventEmitter {
|
||||
db;
|
||||
typeInterfaceMap;
|
||||
constructor(options) {
|
||||
super();
|
||||
this.db = options.db;
|
||||
this.typeInterfaceMap = options.typeInterfaceMap;
|
||||
}
|
||||
async getCollections(options: any = {}) {
|
||||
let tableList = await this.db.sequelize.getQueryInterface().showAllTables();
|
||||
const views = (await this.db.queryInterface.listViews()).map((view) => view.name);
|
||||
tableList = tableList.concat(views);
|
||||
if (this.db.options.tablePrefix) {
|
||||
tableList = tableList.filter((tableName) => {
|
||||
return tableName.startsWith(this.db.options.tablePrefix);
|
||||
});
|
||||
}
|
||||
const batchSize = 5;
|
||||
const results = [];
|
||||
for (let i = 0; i < tableList.length; i += batchSize) {
|
||||
const batch = tableList.slice(i, i + batchSize);
|
||||
const batchResults = await Promise.all(
|
||||
batch.map(async (tableName) => {
|
||||
const tableInfo = {
|
||||
tableName,
|
||||
};
|
||||
this.emit('loadMessage', {
|
||||
message: `load table ${tableName}`,
|
||||
});
|
||||
const collectionOptions = this.tableInfoToCollectionOptions(tableInfo);
|
||||
const localOptions = options.localData?.[collectionOptions.name];
|
||||
try {
|
||||
return await this.getCollection({
|
||||
tableInfo,
|
||||
localOptions,
|
||||
mergedOptions: views.includes(tableName) ? { view: true, template: 'view' } : {},
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.message.includes('No description found for')) {
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}),
|
||||
);
|
||||
results.push(...batchResults);
|
||||
}
|
||||
return results.filter(Boolean);
|
||||
}
|
||||
async getCollection(options) {
|
||||
const { tableInfo } = options;
|
||||
const columnsInfo = await this.db.sequelize.getQueryInterface().describeTable(tableInfo);
|
||||
const collectionOptions = this.tableInfoToCollectionOptions(tableInfo);
|
||||
const constraints = await this.db.sequelize
|
||||
.getQueryInterface()
|
||||
.showIndex(this.db.inDialect('postgres') ? tableInfo : tableInfo.tableName);
|
||||
try {
|
||||
const fields = Object.keys(columnsInfo).map((columnName) => {
|
||||
return this.columnInfoToFieldOptions(columnsInfo, columnName, constraints);
|
||||
});
|
||||
const unsupportedFields = fields.filter((field: any) => {
|
||||
return field.supported === false;
|
||||
});
|
||||
const supportFields = fields.filter((field: any) => {
|
||||
return field.supported !== false;
|
||||
});
|
||||
const remoteCollectionInfo = {
|
||||
...collectionOptions,
|
||||
...((options == null ? void 0 : options.mergedOptions) || {}),
|
||||
...this.collectionOptionsByFields(supportFields),
|
||||
fields: supportFields,
|
||||
};
|
||||
if (unsupportedFields.length) {
|
||||
remoteCollectionInfo.unsupportedFields = unsupportedFields;
|
||||
}
|
||||
const finalOptions = this.mergeLocalDataIntoCollectionOptions(remoteCollectionInfo, options.localOptions);
|
||||
if (finalOptions.view && !finalOptions.filterTargetKey && supportFields.find((field) => field.name === 'id')) {
|
||||
finalOptions.filterTargetKey = 'id';
|
||||
}
|
||||
return finalOptions;
|
||||
} catch (e) {
|
||||
throw new Error(`table ${tableInfo.tableName} introspection error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
loadCollection(options) {
|
||||
this.db.collection({
|
||||
...options,
|
||||
introspected: true,
|
||||
});
|
||||
}
|
||||
loadCollections(options) {
|
||||
options.collections.forEach((collection) => {
|
||||
this.loadCollection(collection);
|
||||
});
|
||||
}
|
||||
tableInfoToCollectionOptions(tableInfo) {
|
||||
const tableName = tableInfo.tableName;
|
||||
let name = tableName;
|
||||
if (this.db.options.tablePrefix) {
|
||||
name = tableName.replace(this.db.options.tablePrefix, '');
|
||||
}
|
||||
return {
|
||||
name,
|
||||
title: name,
|
||||
schema: tableInfo.schema,
|
||||
tableName,
|
||||
};
|
||||
}
|
||||
collectionOptionsByFields(fields) {
|
||||
const options: any = {
|
||||
timestamps: false,
|
||||
autoGenId: false,
|
||||
};
|
||||
const autoIncrementField = fields.find((field) => field.autoIncrement);
|
||||
if (autoIncrementField) {
|
||||
options.filterTargetKey = autoIncrementField.name;
|
||||
}
|
||||
const primaryKeys = fields.filter((field) => field.primaryKey);
|
||||
if (!options.filterTargetKey && primaryKeys.length == 1) {
|
||||
options.filterTargetKey = primaryKeys[0].name;
|
||||
}
|
||||
const uniques = fields.filter((field) => field.unique);
|
||||
if (!options.filterTargetKey && uniques.length == 1) {
|
||||
options.filterTargetKey = uniques[0].name;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
mergeLocalDataIntoCollectionOptions(collectionOptions, localData) {
|
||||
if (!localData) {
|
||||
return collectionOptions;
|
||||
}
|
||||
const collectionFields = collectionOptions.fields || [];
|
||||
const localFieldsAsObject = _.keyBy(localData.fields, 'name');
|
||||
const newFields = collectionFields.map((field) => {
|
||||
const localField = localFieldsAsObject[field.name];
|
||||
if (!localField) {
|
||||
return field;
|
||||
}
|
||||
return mergeOptions(field, localField);
|
||||
});
|
||||
const localAssociationFields = localData.fields?.filter((field) => {
|
||||
return ['belongsTo', 'belongsToMany', 'hasMany', 'hasOne'].includes(field.type);
|
||||
});
|
||||
if (localAssociationFields) {
|
||||
newFields.push(...localAssociationFields);
|
||||
}
|
||||
return {
|
||||
...collectionOptions,
|
||||
..._.omit(localData, ['fields']),
|
||||
fields: newFields,
|
||||
};
|
||||
}
|
||||
columnInfoToFieldOptions(columnsInfo, columnName, indexes) {
|
||||
const columnInfo = columnsInfo[columnName];
|
||||
let fieldOptions: any = {
|
||||
...this.columnAttribute(columnsInfo, columnName, indexes),
|
||||
...ViewFieldInference.inferToFieldType({
|
||||
dialect: this.db.options.dialect,
|
||||
type: columnInfo.type,
|
||||
name: columnName,
|
||||
}),
|
||||
rawType: columnInfo.type,
|
||||
name: columnName,
|
||||
};
|
||||
if (!fieldOptions.type) {
|
||||
return {
|
||||
rawType: columnInfo.type,
|
||||
name: columnName,
|
||||
supported: false,
|
||||
};
|
||||
}
|
||||
const interfaceConfig = this.getDefaultInterfaceByType(columnsInfo, columnName, fieldOptions.type);
|
||||
if (typeof interfaceConfig === 'string') {
|
||||
fieldOptions.interface = interfaceConfig;
|
||||
} else {
|
||||
fieldOptions = {
|
||||
...fieldOptions,
|
||||
...interfaceConfig,
|
||||
};
|
||||
}
|
||||
_.set(fieldOptions, 'uiSchema.title', columnName);
|
||||
return fieldOptions;
|
||||
}
|
||||
getDefaultInterfaceByType(columnsInfo, columnName, type) {
|
||||
const interfaceConfig = this.typeInterfaceMap[type];
|
||||
if (typeof interfaceConfig === 'function') {
|
||||
return interfaceConfig(columnsInfo[columnName]);
|
||||
}
|
||||
return interfaceConfig;
|
||||
}
|
||||
columnAttribute(columnsInfo, columnName, indexes) {
|
||||
const columnInfo = columnsInfo[columnName];
|
||||
const attr = {
|
||||
type: columnInfo.type,
|
||||
allowNull: columnInfo.allowNull,
|
||||
primaryKey: columnInfo.primaryKey,
|
||||
unique: false,
|
||||
autoIncrement: columnInfo.autoIncrement,
|
||||
};
|
||||
if (columnInfo.defaultValue && typeof columnInfo.defaultValue === 'string') {
|
||||
const isSerial = columnInfo.defaultValue.match(/^nextval\(/);
|
||||
const isUUID = columnInfo.defaultValue.match(/^uuid_generate_v4\(/);
|
||||
if (isSerial || isUUID) {
|
||||
attr.autoIncrement = true;
|
||||
}
|
||||
}
|
||||
for (const index of indexes) {
|
||||
if (index.fields.length == 1 && index.fields[0].attribute == columnName && index.unique) {
|
||||
attr.unique = true;
|
||||
}
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
import { SequelizeCollectionManager } from '@tachybase/data-source-manager';
|
||||
export class MySQLCollectionManager extends SequelizeCollectionManager {}
|
@ -0,0 +1,76 @@
|
||||
import { DataSource } from '@tachybase/data-source-manager';
|
||||
import { Database } from '@tachybase/database';
|
||||
import { MySQLCollectionManager } from './mysql-collection-manager';
|
||||
import { DatabaseIntrospector } from './database-introspector';
|
||||
import typeInterfaceMap from './type-interface-map';
|
||||
|
||||
export class MySQLDataSource extends DataSource {
|
||||
static async testConnection(options) {
|
||||
const database = new Database({
|
||||
dialect: 'mysql',
|
||||
...options,
|
||||
});
|
||||
try {
|
||||
await database.sequelize.authenticate();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
} finally {
|
||||
await database.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async load(options) {
|
||||
const db = this.collectionManager.db;
|
||||
const introspector = new DatabaseIntrospector({
|
||||
db,
|
||||
typeInterfaceMap,
|
||||
});
|
||||
introspector.on('loadMessage', ({ message }) => {
|
||||
this.emit('loadMessage', { message });
|
||||
});
|
||||
const { localData } = options;
|
||||
const collections = await introspector.getCollections({
|
||||
localData,
|
||||
});
|
||||
const delayFields = new Map();
|
||||
for (const collection of collections) {
|
||||
try {
|
||||
const fields = collection.fields;
|
||||
for (const field of fields) {
|
||||
if (field.type === 'belongsToMany') {
|
||||
if (!delayFields.has(collection.name)) {
|
||||
delayFields.set(collection.name, []);
|
||||
}
|
||||
delayFields.get(collection.name).push(field);
|
||||
}
|
||||
}
|
||||
this.collectionManager.defineCollection({
|
||||
...collection,
|
||||
fields: fields.filter((field) => field.type !== 'belongsToMany'),
|
||||
introspected: true,
|
||||
});
|
||||
} catch (e) {
|
||||
db.logger.error(`load collection failed, ${e}`);
|
||||
delayFields.delete(collection.name);
|
||||
}
|
||||
}
|
||||
for (const [name, fields] of delayFields.entries()) {
|
||||
try {
|
||||
for (const field of fields) {
|
||||
this.collectionManager.getCollection(name).setField(field.name, field);
|
||||
}
|
||||
} catch (e) {
|
||||
db.logger.error(`load belongs to many field failed, ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
createCollectionManager(options) {
|
||||
const database = new Database({
|
||||
dialect: 'mysql',
|
||||
...options,
|
||||
});
|
||||
return new MySQLCollectionManager({
|
||||
database,
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
const typeInterfaceMap = {
|
||||
array: '',
|
||||
belongsTo: '',
|
||||
belongsToMany: '',
|
||||
boolean: () => {
|
||||
return {
|
||||
interface: 'checkbox',
|
||||
uiSchema: {
|
||||
type: 'boolean',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
};
|
||||
},
|
||||
context: '',
|
||||
date: () => {
|
||||
return {
|
||||
interface: 'datetime',
|
||||
uiSchema: {
|
||||
'x-component': 'DatePicker',
|
||||
'x-component-props': {
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
showTime: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
hasMany: '',
|
||||
hasOne: '',
|
||||
json: () => {
|
||||
return {
|
||||
interface: 'json',
|
||||
uiSchema: {
|
||||
'x-component': 'Input.JSON',
|
||||
'x-component-props': {
|
||||
autoSize: {
|
||||
minRows: 5,
|
||||
// maxRows: 20,
|
||||
},
|
||||
},
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
jsonb: () => {
|
||||
return {
|
||||
interface: 'json',
|
||||
uiSchema: {
|
||||
'x-component': 'Input.JSON',
|
||||
'x-component-props': {
|
||||
autoSize: {
|
||||
minRows: 5,
|
||||
// maxRows: 20,
|
||||
},
|
||||
},
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
integer: () => ({
|
||||
interface: 'integer',
|
||||
// name,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
// title,
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
stringMode: true,
|
||||
step: '1',
|
||||
},
|
||||
'x-validator': 'integer',
|
||||
},
|
||||
}),
|
||||
bigInt: (columnInfo) => {
|
||||
return {
|
||||
interface: 'integer',
|
||||
uiSchema: {
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
float: () => {
|
||||
return {
|
||||
interface: 'number',
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
// title,
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
stringMode: true,
|
||||
step: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
double: () => {
|
||||
return {
|
||||
interface: 'number',
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
// title,
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
stringMode: true,
|
||||
step: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
real: () => {
|
||||
return {
|
||||
interface: 'number',
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
// title,
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
stringMode: true,
|
||||
step: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
decimal: () => {
|
||||
return {
|
||||
interface: 'number',
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
// title,
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
stringMode: true,
|
||||
step: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
password: () => ({
|
||||
interface: 'password',
|
||||
hidden: true,
|
||||
// name,
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
// title,
|
||||
'x-component': 'Password',
|
||||
},
|
||||
}),
|
||||
radio: '',
|
||||
set: '',
|
||||
sort: '',
|
||||
string: (columnInfo) => {
|
||||
const type = columnInfo.type;
|
||||
if (type.startsWith('ENUM')) {
|
||||
return {
|
||||
interface: 'select',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Select',
|
||||
enum: type
|
||||
.replace('ENUM(', '')
|
||||
.replace(')', '')
|
||||
.replace(/'/g, '')
|
||||
.split(',')
|
||||
.map((item) => ({
|
||||
label: item,
|
||||
value: item,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
text: () => {
|
||||
return {
|
||||
interface: 'textarea',
|
||||
// name,
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input.TextArea',
|
||||
},
|
||||
};
|
||||
},
|
||||
time: () => ({
|
||||
interface: 'time',
|
||||
// name,
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'TimePicker',
|
||||
'x-component-props': {
|
||||
format: 'HH:mm:ss',
|
||||
},
|
||||
},
|
||||
}),
|
||||
uid: () => {
|
||||
return {
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
uuid: () => {
|
||||
return {
|
||||
interface: 'uuid',
|
||||
uiSchema: {
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
virtual: '',
|
||||
point: '',
|
||||
polygon: '',
|
||||
lineString: '',
|
||||
circle: '',
|
||||
};
|
||||
|
||||
export default typeInterfaceMap;
|
@ -1,8 +1,8 @@
|
||||
import { DataSource } from '@tachybase/data-source-manager';
|
||||
import { Database } from '@tachybase/database';
|
||||
import { PostgresCollectionManager } from './postgres-collection-manager';
|
||||
import { DatabaseIntrospector } from './services/database-introspector';
|
||||
import typeInterfaceMap from './services/type-interface-map';
|
||||
import { DatabaseIntrospector } from './database-introspector';
|
||||
import typeInterfaceMap from './type-interface-map';
|
||||
|
||||
export class PostgresDataSource extends DataSource {
|
||||
static async testConnection(options) {
|
@ -1,11 +1,13 @@
|
||||
import { Plugin } from '@tachybase/server';
|
||||
import { PostgresDataSource } from './postgres-data-source';
|
||||
import { PostgresDataSource } from './pg/postgres-data-source';
|
||||
import { MySQLDataSource } from './mysql/mysql-data-source';
|
||||
|
||||
export class PluginExternalDataSourceServer extends Plugin {
|
||||
async afterAdd() {}
|
||||
|
||||
async beforeLoad() {
|
||||
this.app.dataSourceManager.factory.register('postgres', PostgresDataSource);
|
||||
this.app.dataSourceManager.factory.register('mysql', MySQLDataSource);
|
||||
}
|
||||
async beforeEnable() {
|
||||
const plugin = this.pm.get('data-source-manager');
|
@ -51,6 +51,7 @@
|
||||
"@nocobase/plugin-users": "workspace:*",
|
||||
"@nocobase/plugin-verification": "workspace:*",
|
||||
"@tachybase/client": "workspace:*",
|
||||
"@tachybase/plugin-external-data-source": "workspace:*",
|
||||
"@tachybase/plugin-mobile-client": "workspace:*",
|
||||
"@tachybase/plugin-system-settings": "workspace:*",
|
||||
"@tachybase/plugin-workflow": "workspace:*",
|
||||
|
@ -2,7 +2,7 @@ import PresetTachyBase from '@tachybase/preset-tachybase';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class PluginRental extends PresetTachyBase {
|
||||
#builtInPlugins = ['approval-mobile', 'core', 'homepage'];
|
||||
#builtInPlugins = ['approval-mobile', 'core', 'homepage', 'external-data-source'];
|
||||
|
||||
get builtInPlugins() {
|
||||
return super.builtInPlugins.concat(this.#builtInPlugins);
|
||||
|
117
pnpm-lock.yaml
117
pnpm-lock.yaml
@ -1638,40 +1638,6 @@ importers:
|
||||
specifier: ^8.13.0
|
||||
version: 8.15.1
|
||||
|
||||
packages/plugins/@hera/plugin-external-data-source:
|
||||
dependencies:
|
||||
'@tachybase/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/client
|
||||
'@tachybase/data-source-manager':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/data-source-manager
|
||||
'@tachybase/database':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/database
|
||||
'@tachybase/server':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/server
|
||||
'@tachybase/test':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/test
|
||||
antd:
|
||||
specifier: 5.16.1
|
||||
version: 5.16.1(react-dom@18.2.0)(react@18.2.0)
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
pg:
|
||||
specifier: ^8.11.3
|
||||
version: 8.11.3
|
||||
react-i18next:
|
||||
specifier: ^11.15.1
|
||||
version: 11.18.6(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
devDependencies:
|
||||
'@types/lodash':
|
||||
specifier: ^4.17.0
|
||||
version: 4.17.0
|
||||
|
||||
packages/plugins/@hera/plugin-homepage:
|
||||
dependencies:
|
||||
'@tachybase/client':
|
||||
@ -3819,6 +3785,40 @@ importers:
|
||||
specifier: ^4.0.525
|
||||
version: 4.0.759
|
||||
|
||||
packages/plugins/@tachybase/plugin-external-data-source:
|
||||
dependencies:
|
||||
'@tachybase/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/client
|
||||
'@tachybase/data-source-manager':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/data-source-manager
|
||||
'@tachybase/database':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/database
|
||||
'@tachybase/server':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/server
|
||||
'@tachybase/test':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/test
|
||||
antd:
|
||||
specifier: 5.16.1
|
||||
version: 5.16.1(react-dom@18.2.0)(react@18.2.0)
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
pg:
|
||||
specifier: ^8.11.3
|
||||
version: 8.11.3
|
||||
react-i18next:
|
||||
specifier: ^11.15.1
|
||||
version: 11.18.6(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
devDependencies:
|
||||
'@types/lodash':
|
||||
specifier: ^4.17.0
|
||||
version: 4.17.0
|
||||
|
||||
packages/plugins/@tachybase/plugin-field-markdown-vditor:
|
||||
dependencies:
|
||||
'@tachybase/client':
|
||||
@ -4357,6 +4357,9 @@ importers:
|
||||
'@tachybase/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/client
|
||||
'@tachybase/plugin-external-data-source':
|
||||
specifier: workspace:*
|
||||
version: link:../../plugins/@tachybase/plugin-external-data-source
|
||||
'@tachybase/plugin-mobile-client':
|
||||
specifier: workspace:*
|
||||
version: link:../../plugins/@tachybase/plugin-mobile-client
|
||||
@ -4847,7 +4850,7 @@ packages:
|
||||
react-dom: '>=17.0.0'
|
||||
dependencies:
|
||||
'@ant-design/cssinjs': 1.20.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@ctrl/tinycolor': 3.6.1
|
||||
antd: 5.16.1(react-dom@18.2.0)(react@18.2.0)
|
||||
rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -4864,7 +4867,7 @@ packages:
|
||||
dependencies:
|
||||
'@ant-design/icons': 5.3.6(react-dom@18.2.0)(react@18.2.0)
|
||||
'@ant-design/pro-provider': 2.13.5(antd@5.16.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
antd: 5.16.1(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
dayjs: 1.11.10
|
||||
@ -4880,7 +4883,7 @@ packages:
|
||||
peerDependencies:
|
||||
react: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
classnames: 2.5.1
|
||||
json2mq: 0.2.0
|
||||
react: 18.2.0
|
||||
@ -8056,7 +8059,7 @@ packages:
|
||||
resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==}
|
||||
dependencies:
|
||||
'@babel/helper-module-imports': 7.22.15
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@emotion/hash': 0.9.1
|
||||
'@emotion/memoize': 0.8.1
|
||||
'@emotion/serialize': 1.1.2
|
||||
@ -8109,7 +8112,7 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@emotion/babel-plugin': 11.11.0
|
||||
'@emotion/cache': 11.11.0
|
||||
'@emotion/serialize': 1.1.2
|
||||
@ -10530,7 +10533,7 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
@ -10562,7 +10565,7 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -25641,7 +25644,7 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
array-tree-filter: 2.1.0
|
||||
classnames: 2.5.1
|
||||
rc-select: 14.13.0(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -25681,7 +25684,7 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -25695,7 +25698,7 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -25736,7 +25739,7 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
async-validator: 4.2.5
|
||||
rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
@ -25748,7 +25751,7 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
rc-dialog: 9.4.0(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -25763,7 +25766,7 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/mini-decimal': 1.1.0
|
||||
classnames: 2.5.1
|
||||
rc-input: 1.4.5(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -25805,7 +25808,7 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -25885,7 +25888,7 @@ packages:
|
||||
moment:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
dayjs: 1.11.10
|
||||
@ -25971,7 +25974,7 @@ packages:
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/trigger': 1.18.3(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -25989,7 +25992,7 @@ packages:
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -26044,7 +26047,7 @@ packages:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@rc-component/context': 1.4.0(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -26117,7 +26120,7 @@ packages:
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
classnames: 2.5.1
|
||||
rc-select: 14.13.0(react-dom@18.2.0)(react@18.2.0)
|
||||
rc-tree: 5.8.5(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -26163,7 +26166,7 @@ packages:
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
classnames: 2.5.1
|
||||
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
|
||||
rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -26179,7 +26182,7 @@ packages:
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
classnames: 2.5.1
|
||||
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
|
||||
rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -26585,7 +26588,7 @@ packages:
|
||||
react-native:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
'@types/react-redux': 7.1.33
|
||||
hoist-non-react-statics: 3.3.2
|
||||
loose-envify: 1.4.0
|
||||
@ -26943,7 +26946,7 @@ packages:
|
||||
/redux@4.2.1:
|
||||
resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.5
|
||||
|
||||
/reflect-metadata@0.1.14:
|
||||
resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==}
|
||||
|
Loading…
Reference in New Issue
Block a user