This commit is contained in:
Jane
2023-12-22 12:18:52 +08:00
parent 340f82a67e
commit 812109656a
746 changed files with 84928 additions and 0 deletions

View File

@@ -0,0 +1,228 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
添加
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column label="排序" width="50" align="center">
<template slot-scope="scope">{{ scope.row.order }}</template>
</el-table-column>
<el-table-column label="名称" width="120" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row.title }}</template>
</el-table-column>
<el-table-column label="分组标识" width="200" align="center">
<template slot-scope="scope">{{ scope.row.appName }}</template>
</el-table-column>
<el-table-column label="注册方式" width="110" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope"> {{ addressTypes.find(t => t.value === scope.row.addressType).label }}</template>
</el-table-column>
<el-table-column label="在线机器" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row.addressList }}</template>
</el-table-column>
<el-table-column label="操作" align="center" width="230" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="handleUpdate(row)">
编辑
</el-button>
<el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="fetchData" />
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="80px" style="width: 400px; margin-left:50px;">
<el-form-item label="AppName" prop="appName">
<el-input v-model="temp.appName" placeholder="AppName" />
</el-form-item>
<el-form-item label="名称" prop="title">
<el-input v-model="temp.title" placeholder="请输入执行器名称" />
</el-form-item>
<el-form-item label="排序" prop="prop">
<el-input v-model="temp.order" placeholder="执行器序号" />
</el-form-item>
<el-form-item label="注册方式" prop="addressType">
<el-radio-group v-model="temp.addressType">
<el-radio :label="0">自动注册</el-radio>
<el-radio :label="1">手动录入</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="机器地址" prop="addressList">
<el-input v-model="temp.addressList" :disabled="dialogStatus!=='create'" placeholder="多个以逗号分隔" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as executor from '@/api/dts/datax-executor'
import waves from '@/directive/waves' // waves directive
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
export default {
name: 'Executor',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
name: undefined,
jobGroup: undefined
},
editJsonVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
appName: [{ required: true, message: 'appName is required', trigger: 'blur' }],
title: [{ required: true, message: 'title is required', trigger: 'blur' }],
order: [{ required: true, message: 'title is required', trigger: 'blur' }],
addressType: [{ required: true, message: 'title is required', trigger: 'change' }]
},
temp: {
id: undefined,
appName: undefined,
title: undefined,
order: undefined,
addressType: undefined,
addressList: undefined
},
addressTypes: [
{ value: 0, label: '自动注册' },
{ value: 1, label: '手动录入' }
]
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
executor.getList().then(response => {
const { content } = response
this.list = content
this.listLoading = false
})
},
resetTemp() {
this.temp = {
id: undefined,
appName: undefined,
title: undefined,
order: undefined,
addressType: undefined,
addressList: undefined
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
executor.created(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
tempData.configJson = this.configJson
executor.updated(tempData).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleDelete(row) {
executor.deleted(row.id).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
// const index = this.list.indexOf(row)
},
handleFetchPv(id) {
executor.fetch(id).then(response => {
this.pluginData = response
this.dialogPvVisible = true
})
}
}
}
</script>

View File

@@ -0,0 +1,426 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input
v-model="listQuery.datasourceName"
clearable
placeholder="数据源名称"
style="width: 200px;"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
添加
</el-button>
<!-- <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">
reviewer
</el-checkbox> -->
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<!-- <el-table-column align="center" label="序号" width="95">
<template slot-scope="scope">{{ scope.$index }}</template>
</el-table-column> -->
<el-table-column label="数据源" width="120" align="center">
<template slot-scope="scope">{{ scope.row.datasource }}</template>
</el-table-column>
<el-table-column label="数据源名称" width="150" align="center">
<template slot-scope="scope">{{ scope.row.datasourceName }}</template>
</el-table-column>
<el-table-column label="数据源分组" width="120" align="center">
<template slot-scope="scope">{{ scope.row.datasourceGroup }}
</template>
</el-table-column>
<!--<el-table-column label="用户名" width="150" align="center">
<template slot-scope="scope">{{ scope.row.jdbcUsername ? scope.row.jdbcUsername:'-' }}</template>
</el-table-column>-->
<el-table-column label="jdbc连接串" width="300" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row.jdbcUrl ? scope.row.jdbcUrl:'-' }}</template>
</el-table-column>
<!-- <el-table-column label="jdbc驱动类" width="200" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row.jdbcDriverClass ? scope.row.jdbcDriverClass:'-' }}</template>
</el-table-column>-->
<el-table-column label="ZK地址" width="200" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row.zkAdress ? scope.row.zkAdress:'-' }}</template>
</el-table-column>
<el-table-column label="数据库名" width="200" align="center" :show-overflow-tooltip="true">-->
<template slot-scope="scope">{{ scope.row.databaseName ? scope.row.databaseName:'-' }}</template>-->
</el-table-column>
<!-- <el-table-column label="备注" width="150" align="center">-->
<!-- <template slot-scope="scope">{{ scope.row.comments }}</template>-->
<!-- </el-table-column>-->
<el-table-column label="操作" align="center" width="230" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="handleUpdate(row)">
编辑
</el-button>
<el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="listQuery.current"
:limit.sync="listQuery.size"
@pagination="fetchData"
/>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="800px">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="100px">
<el-form-item label="数据源" prop="datasource">
<el-select
v-model="temp.datasource"
placeholder="数据源"
style="width: 200px"
@change="selectDataSource(temp.datasource)"
>
<el-option v-for="item in dataSources" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="数据源名称" prop="datasourceName">
<el-input v-model="temp.datasourceName" placeholder="数据源名称" style="width: 40%" />
</el-form-item>
<el-form-item label="数据源分组" prop="datasourceGroup">
<el-input v-model="temp.datasourceGroup" placeholder="数据源分组" style="width: 40%" />
</el-form-item>
<el-form-item v-if="jdbc" label="用户名">
<el-input v-model="temp.jdbcUsername" placeholder="用户名" style="width: 40%" />
</el-form-item>
<el-form-item v-if="visible" v-show="jdbc" label="密码">
<el-input v-model="temp.jdbcPassword" type="password" placeholder="密码" style="width: 40%">
<i slot="suffix" title="显示密码" style="cursor:pointer" class="el-icon-view" @click="changePass('show')" />
</el-input>
</el-form-item>
<el-form-item v-show="jdbc" v-else label="密码">
<el-input v-model="temp.jdbcPassword" type="text" placeholder="密码" style="width: 40%">
<i slot="suffix" title="隐藏密码" style="cursor:pointer" class="el-icon-check" @click="changePass('hide')" />
</el-input>
</el-form-item>
<el-form-item v-if="jdbc" label="jdbc url" prop="jdbcUrl">
<el-input
v-model="temp.jdbcUrl"
:autosize="{ minRows: 3, maxRows: 6}"
type="textarea"
placeholder="jdbc url"
style="width: 60%"
/>
</el-form-item>
<el-form-item v-if="mongodb" label="地址" prop="jdbcUrl">
<el-input
v-model="temp.jdbcUrl"
:autosize="{ minRows: 3, maxRows: 6}"
type="textarea"
placeholder="localhost:27017"
style="width: 60%"
/>
</el-form-item>
<el-form-item v-if="jdbc" label="jdbc驱动类" prop="jdbcDriverClass">
<el-input v-model="temp.jdbcDriverClass" placeholder="jdbc驱动类" style="width: 60%" />
</el-form-item>
<el-form-item v-if="hbase" label="ZK地址" prop="zkAdress">
<el-input v-model="temp.zkAdress" placeholder="localhost:2181" style="width: 60%" />
</el-form-item>
<el-form-item v-if="mongodb" label="数据库名称" prop="databaseName">
<el-input v-model="temp.databaseName" placeholder="数据库名称" style="width: 60%" />
</el-form-item>
<el-form-item label="注释">
<el-input
v-model="temp.comments"
:autosize="{ minRows: 2, maxRows: 4}"
type="textarea"
placeholder="Please input"
style="width: 60%"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
<el-button type="primary" @click="testDataSource()">
测试连接
</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="dialogPluginVisible" title="Reading statistics">
<el-table :data="pluginData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="key" label="Channel" />
<el-table-column prop="pv" label="Pv" />
</el-table>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogPvVisible = false">Confirm</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import * as datasourceApi from '@/api/dts/datax-jdbcDatasource'
import waves from '@/directive/waves' // waves directive
import { parseTime } from '@/utils'
import Pagination from '@/components/Pagination'
export default {
name: 'JdbcDatasource',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10
},
pluginTypeOptions: ['reader', 'writer'],
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
datasourceName: [{ required: true, message: 'this is required', trigger: 'blur' }],
jdbcUsername: [{ required: true, message: 'this is required', trigger: 'blur' }],
jdbcPassword: [{ required: true, message: 'this is required', trigger: 'blur' }],
jdbcUrl: [{ required: true, message: 'this is required', trigger: 'blur' }],
jdbcDriverClass: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasource: [{ required: true, message: 'this is required', trigger: 'change' }],
zkAdress: [{ required: true, message: 'this is required', trigger: 'blur' }],
databaseName: [{ required: true, message: 'this is required', trigger: 'blur' }]
},
temp: {
id: undefined,
datasourceName: '',
datasourceGroup: 'Default',
jdbcUsername: '',
jdbcPassword: '',
jdbcUrl: '',
jdbcDriverClass: '',
comments: '',
datasource: '',
zkAdress: '',
databaseName: ''
},
visible: true,
dataSources: [
{ value: 'mysql', label: 'mysql' },
{ value: 'oracle', label: 'oracle' },
{ value: 'hana', label: 'hana' },
{ value: 'postgresql', label: 'postgresql' },
{ value: 'sqlserver', label: 'sqlserver' },
{ value: 'hive', label: 'hive' },
{ value: 'hbase', label: 'hbase' },
{ value: 'mongodb', label: 'mongodb' },
{ value: 'clickhouse', label: 'clickhouse' }
],
jdbc: true,
hbase: false,
mongodb: false
}
},
created() {
this.fetchData()
},
methods: {
selectDataSource(datasource) {
if (datasource === 'mysql') {
this.temp.jdbcUrl = 'jdbc:mysql://{host}:{port}/{database}'
this.temp.jdbcDriverClass = 'com.mysql.jdbc.Driver'
} else if (datasource === 'hana') {
this.temp.jdbcUrl = 'jdbc:sap://{host}:{port}/{instanceNumber}'
this.temp.jdbcDriverClass = 'com.sap.db.jdbc.Driver'
} else if (datasource === 'oracle') {
this.temp.jdbcUrl = 'jdbc:oracle:thin:@//{host}:{port}/{serviceName}'
this.temp.jdbcDriverClass = 'oracle.jdbc.OracleDriver'
} else if (datasource === 'postgresql') {
this.temp.jdbcUrl = 'jdbc:postgresql://{host}:{port}/{database}'
this.temp.jdbcDriverClass = 'org.postgresql.Driver'
} else if (datasource === 'sqlserver') {
this.temp.jdbcUrl = 'jdbc:jtds:sqlserver://{host}:{port};DatabaseName={database}'
this.temp.jdbcDriverClass = 'net.sourceforge.jtds.jdbc.Driver'
} else if (datasource === 'clickhouse') {
this.temp.jdbcUrl = 'jdbc:clickhouse://{host}:{port}/{database}'
this.temp.jdbcDriverClass = 'ru.yandex.clickhouse.ClickHouseDriver'
} else if (datasource === 'hive') {
this.temp.jdbcUrl = 'jdbc:hive2://{host}:{port}/{database}'
this.temp.jdbcDriverClass = 'org.apache.hive.jdbc.HiveDriver'
this.hbase = this.mongodb = false
this.jdbc = true
}
this.getShowStrategy(datasource)
},
fetchData() {
this.listLoading = true
datasourceApi.list(this.listQuery).then(response => {
const { data } = response
const { total } = data.total
this.total = total
this.list = data.records
this.listLoading = false
})
},
resetTemp() {
this.temp = {
id: undefined,
datasourceName: '',
datasourceGroup: 'Default',
jdbcUsername: '',
jdbcPassword: '',
jdbcUrl: '',
jdbcDriverClass: '',
comments: ''
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
datasourceApi.created(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
testDataSource() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
datasourceApi.test(this.temp).then(response => {
if (response.data === false) {
this.$notify({
title: 'Fail',
message: response.data.msg,
type: 'fail',
duration: 2000
})
} else {
this.$notify({
title: 'Success',
message: 'Tested Successfully',
type: 'success',
duration: 2000
})
}
})
}
})
},
handleUpdate(row) {
this.getShowStrategy(row.datasource)
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
datasourceApi.updated(tempData).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
getShowStrategy(datasource) {
if (datasource === 'hbase') {
this.jdbc = this.mongodb = false
this.hbase = true
} else if (datasource === 'mongodb') {
this.jdbc = this.hbase = false
this.mongodb = true
this.temp.jdbcUrl = 'mongodb://[username:password@]host1[:port1][,...hostN[:portN]]][/[database][?options]]'
} else {
this.hbase = this.mongodb = false
this.jdbc = true
}
},
handleDelete(row) {
console.log('删除')
const idList = []
idList.push(row.id)
// 拼成 idList=xx
// 多个比较麻烦,这里不处理
datasourceApi.deleted({ idList: row.id }).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
// const index = this.list.indexOf(row)
},
handleFetchPv(id) {
datasourceApi.fetched(id).then(response => {
this.pluginData = response
this.dialogPvVisible = true
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
},
changePass(value) {
this.visible = !(value === 'show')
}
}
}
</script>

View File

@@ -0,0 +1,758 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.jobDesc" placeholder="任务名称" style="width: 200px;" class="filter-item" />
<el-select v-model="projectIds" multiple placeholder="所属项目" class="filter-item">
<el-option v-for="item in jobProjectList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.glueType" placeholder="任务类型" style="width: 200px" class="filter-item">
<el-option v-for="item in glueTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
添加
</el-button>
<!-- <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">
reviewer
</el-checkbox> -->
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
style="width: 100%"
size="medium"
>
<el-table-column align="center" label="ID" width="80">
<template slot-scope="scope">{{ scope.row.id }}</template>
</el-table-column>
<el-table-column label="任务名称" align="center">
<template slot-scope="scope">{{ scope.row.jobDesc }}</template>
</el-table-column>
<el-table-column label="所属项目" align="center" width="120">
<template slot-scope="scope">{{ scope.row.projectName }}</template>
</el-table-column>
<el-table-column label="Cron" align="center" width="120">
<template slot-scope="scope">
<span>{{ scope.row.jobCron }}</span>
</template>
</el-table-column>
<el-table-column label="路由策略" align="center" width="130">
<template slot-scope="scope"> {{ routeStrategies.find(t => t.value === scope.row.executorRouteStrategy).label }}</template>
</el-table-column>
<el-table-column label="状态" align="center" width="150">
<template slot-scope="scope">
<el-switch
v-model="scope.row.triggerStatus"
active-color="#00A854"
active-text="启动"
:active-value="1"
inactive-color="#F04134"
inactive-text="停止"
:inactive-value="0"
@change="changeSwitch(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="注册节点" align="center" width="100">
<template slot-scope="scope">
<el-popover
placement="bottom"
width="500"
@show="loadById(scope.row)"
>
<el-table :data="registerNode">
<el-table-column width="150" property="title" label="执行器名称" />
<el-table-column width="150" property="appName" label="分组标识" />
<el-table-column width="150" property="registryList" label="机器地址" />
</el-table>
<el-button slot="reference" size="small">查看</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column label="下次触发时间" align="center" width="120">
<template slot-scope="scope">
<el-popover
placement="bottom"
width="300"
@show="nextTriggerTime(scope.row)"
>
<h5 v-html="triggerNextTimes" />
<el-button slot="reference" size="small">查看</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column label="执行状态" align="center" width="80">
<template slot-scope="scope"> {{ statusList.find(t => t.value === scope.row.lastHandleCode).label }}</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right">
<template slot-scope="{row}">
<!-- <el-dropdown type="primary" size="small"> -->
<!-- 操作 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link">
操作<i class="el-icon-arrow-down el-icon--right" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="handlerExecute(row)">执行一次</el-dropdown-item>
<el-dropdown-item @click.native="handlerViewLog(row)">查询日志</el-dropdown-item>
<el-dropdown-item divided @click.native="handlerUpdate(row)">编辑</el-dropdown-item>
<el-dropdown-item @click.native="handlerDelete(row)">删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="fetchData" />
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="1000px" :before-close="handleClose">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="110px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="执行器" prop="jobGroup">
<el-select v-model="temp.jobGroup" placeholder="请选择执行器">
<el-option v-for="item in executorList" :key="item.id" :label="item.title" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务名称" prop="jobDesc">
<el-input v-model="temp.jobDesc" size="medium" placeholder="请输入任务描述" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="路由策略" prop="executorRouteStrategy">
<el-select v-model="temp.executorRouteStrategy" placeholder="请选择路由策略">
<el-option v-for="item in routeStrategies" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-dialog
title="提示"
:visible.sync="showCronBox"
width="60%"
append-to-body
>
<cron v-model="temp.jobCron" />
<span slot="footer" class="dialog-footer">
<el-button @click="showCronBox = false;">关闭</el-button>
<el-button type="primary" @click="showCronBox = false"> </el-button>
</span>
</el-dialog>
<el-form-item label="Cron" prop="jobCron">
<el-input v-model="temp.jobCron" auto-complete="off" placeholder="请输入Cron表达式">
<el-button v-if="!showCronBox" slot="append" icon="el-icon-turn-off" title="打开图形配置" @click="showCronBox = true" />
<el-button v-else slot="append" icon="el-icon-open" title="关闭图形配置" @click="showCronBox = false" />
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="阻塞处理" prop="executorBlockStrategy">
<el-select v-model="temp.executorBlockStrategy" placeholder="请选择阻塞处理策略">
<el-option v-for="item in blockStrategies" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="报警邮件">
<el-input v-model="temp.alarmEmail" placeholder="请输入报警邮件,多个用逗号分隔" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="任务类型" prop="glueType">
<el-select v-model="temp.glueType" placeholder="任务脚本类型">
<el-option v-for="item in glueTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="失败重试次数">
<el-input-number v-model="temp.executorFailRetryCount" :min="0" :max="20" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="所属项目" prop="projectId">
<el-select v-model="temp.projectId" placeholder="所属项目" class="filter-item">
<el-option v-for="item in jobProjectList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="超时时间(分钟)">
<el-input-number v-model="temp.executorTimeout" :min="0" :max="120" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="子任务">
<el-select v-model="temp.childJobId" multiple placeholder="子任务" value-key="id">
<el-option v-for="item in jobIdList" :key="item.id" :label="item.jobDesc" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" />
</el-row>
<el-row v-if="temp.glueType==='BEAN'" :gutter="20">
<el-col :span="12">
<el-form-item label="辅助参数" prop="incrementType">
<el-select v-model="temp.incrementType" placeholder="请选择参数类型" value="">
<el-option v-for="item in incrementTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="temp.glueType==='BEAN' && temp.incrementType === 1" :gutter="20">
<el-col :span="12">
<el-form-item label="增量主键开始ID" prop="incStartId">
<el-input v-model="temp.incStartId" placeholder="首次增量使用" style="width: 56%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="ID增量参数" prop="replaceParam">
<el-input v-model="temp.replaceParam" placeholder="-DstartId='%s' -DendId='%s'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="reader数据源" prop="datasourceId">
<el-select v-model="temp.datasourceId" placeholder="reader数据源" class="filter-item">
<el-option v-for="item in dataSourceList" :key="item.id" :label="item.datasourceName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item label="reader表" prop="readerTable">
<el-input v-model="temp.readerTable" placeholder="读表的表名" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="主键" label-width="40px" prop="primaryKey">
<el-input v-model="temp.primaryKey" placeholder="请填写主键字段名" />
</el-form-item>
</el-col>
</el-row>
<el-row v-if="temp.glueType==='BEAN' && temp.incrementType === 2" :gutter="20">
<el-col :span="12">
<el-form-item label="增量开始时间" prop="incStartTime">
<el-date-picker
v-model="temp.incStartTime"
type="datetime"
placeholder="首次增量使用"
format="yyyy-MM-dd HH:mm:ss"
style="width: 57%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="增量时间字段" prop="replaceParam">
<el-input v-model="temp.replaceParam" placeholder="-DlastTime='%s' -DcurrentTime='%s'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="增量时间格式" prop="replaceParamType">
<el-select v-model="temp.replaceParamType" placeholder="增量时间格式" @change="incStartTimeFormat">
<el-option v-for="item in replaceFormatTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="temp.glueType==='BEAN' && temp.incrementType === 3" :gutter="20">
<el-col :span="12">
<el-form-item label="分区字段" prop="partitionField">
<el-input v-model="partitionField" placeholder="请输入分区字段" style="width: 56%" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item label="分区时间">
<el-select v-model="timeFormatType" placeholder="分区时间格式">
<el-option v-for="item in timeFormatTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="5">
<el-input-number v-model="timeOffset" :min="-20" :max="0" style="width: 65%" />
</el-col>
</el-row>
<el-row v-if="temp.glueType==='BEAN'" :gutter="20">
<el-col :span="24">
<el-form-item label="JVM启动参数">
<el-input v-model="temp.jvmParam" placeholder="-Xms1024m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<json-editor v-if="temp.glueType==='BEAN'" ref="jsonEditor" v-model="jobJson" />
<shell-editor v-if="temp.glueType==='GLUE_SHELL'" ref="shellEditor" v-model="glueSource" />
<python-editor v-if="temp.glueType==='GLUE_PYTHON'" ref="pythonEditor" v-model="glueSource" />
<powershell-editor v-if="temp.glueType==='GLUE_POWERSHELL'" ref="powershellEditor" v-model="glueSource" />
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as executor from '@/api/dts/datax-executor'
import * as job from '@/api/dts/datax-job-info'
import waves from '@/directive/waves' // waves directive
import Cron from '@/components/Cron'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import JsonEditor from '@/components/JsonEditor'
import ShellEditor from '@/components/ShellEditor'
import PythonEditor from '@/components/PythonEditor'
import PowershellEditor from '@/components/PowershellEditor'
import * as datasourceApi from '@/api/dts/datax-jdbcDatasource'
import * as jobProjectApi from '@/api/dts/datax-job-project'
import { isJSON } from '@/utils/validate'
export default {
name: 'JobInfo',
components: { Pagination, JsonEditor, ShellEditor, PythonEditor, PowershellEditor, Cron },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
const validateIncParam = (rule, value, callback) => {
if (!value) {
callback(new Error('Increment parameters is required'))
}
callback()
}
const validatePartitionParam = (rule, value, callback) => {
if (!this.partitionField) {
callback(new Error('Partition parameters is required'))
}
callback()
}
return {
projectIds: '',
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
jobGroup: 0,
projectIds: '',
triggerStatus: -1,
jobDesc: '',
glueType: ''
},
showCronBox: false,
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
jobGroup: [{ required: true, message: 'jobGroup is required', trigger: 'change' }],
executorRouteStrategy: [{ required: true, message: 'executorRouteStrategy is required', trigger: 'change' }],
executorBlockStrategy: [{ required: true, message: 'executorBlockStrategy is required', trigger: 'change' }],
glueType: [{ required: true, message: 'jobType is required', trigger: 'change' }],
projectId: [{ required: true, message: 'projectId is required', trigger: 'change' }],
jobDesc: [{ required: true, message: 'jobDesc is required', trigger: 'blur' }],
jobProject: [{ required: true, message: 'jobProject is required', trigger: 'blur' }],
jobCron: [{ required: true, message: 'jobCron is required', trigger: 'blur' }],
incStartId: [{ trigger: 'blur', validator: validateIncParam }],
replaceParam: [{ trigger: 'blur', validator: validateIncParam }],
primaryKey: [{ trigger: 'blur', validator: validateIncParam }],
incStartTime: [{ trigger: 'change', validator: validateIncParam }],
replaceParamType: [{ trigger: 'change', validator: validateIncParam }],
partitionField: [{ trigger: 'blur', validator: validatePartitionParam }],
datasourceId: [{ trigger: 'change', validator: validateIncParam }],
readerTable: [{ trigger: 'blur', validator: validateIncParam }]
},
temp: {
id: undefined,
jobGroup: '',
jobCron: '',
jobDesc: '',
executorRouteStrategy: '',
executorBlockStrategy: '',
childJobId: '',
executorFailRetryCount: '',
alarmEmail: '',
executorTimeout: '',
userId: 0,
jobConfigId: '',
executorHandler: '',
glueType: '',
glueSource: '',
jobJson: '',
executorParam: '',
replaceParam: '',
replaceParamType: 'Timestamp',
jvmParam: '',
incStartTime: '',
partitionInfo: '',
incrementType: 0,
incStartId: '',
primaryKey: '',
projectId: '',
datasourceId: '',
readerTable: ''
},
resetTemp() {
// this.temp = this.$options.data().temp
this.jobJson = ''
this.glueSource = ''
this.timeOffset = 0
this.timeFormatType = 'yyyy-MM-dd'
this.partitionField = ''
},
executorList: '',
jobIdList: '',
jobProjectList: '',
dataSourceList: '',
blockStrategies: [
{ value: 'SERIAL_EXECUTION', label: '单机串行' },
{ value: 'DISCARD_LATER', label: '丢弃后续调度' },
{ value: 'COVER_EARLY', label: '覆盖之前调度' }
],
routeStrategies: [
{ value: 'FIRST', label: '第一个' },
{ value: 'LAST', label: '最后一个' },
{ value: 'ROUND', label: '轮询' },
{ value: 'RANDOM', label: '随机' },
{ value: 'CONSISTENT_HASH', label: '一致性HASH' },
{ value: 'LEAST_FREQUENTLY_USED', label: '最不经常使用' },
{ value: 'LEAST_RECENTLY_USED', label: '最近最久未使用' },
{ value: 'FAILOVER', label: '故障转移' },
{ value: 'BUSYOVER', label: '忙碌转移' }
// { value: 'SHARDING_BROADCAST', label: '分片广播' }
],
glueTypes: [
{ value: 'BEAN', label: 'FlinkX任务' },
{ value: 'GLUE_SHELL', label: 'Shell任务' },
{ value: 'GLUE_PYTHON', label: 'Python任务' },
{ value: 'GLUE_POWERSHELL', label: 'PowerShell任务' }
],
incrementTypes: [
{ value: 0, label: '无' },
{ value: 1, label: '主键自增' },
{ value: 2, label: '时间自增' },
{ value: 3, label: 'HIVE分区' }
],
triggerNextTimes: '',
registerNode: [],
jobJson: '',
glueSource: '',
timeOffset: 0,
timeFormatType: 'yyyy-MM-dd',
partitionField: '',
timeFormatTypes: [
{ value: 'yyyy-MM-dd', label: 'yyyy-MM-dd' },
{ value: 'yyyyMMdd', label: 'yyyyMMdd' },
{ value: 'yyyy/MM/dd', label: 'yyyy/MM/dd' }
],
replaceFormatTypes: [
{ value: 'yyyy/MM/dd', label: 'yyyy/MM/dd' },
{ value: 'yyyy-MM-dd', label: 'yyyy-MM-dd' },
{ value: 'HH:mm:ss', label: 'HH:mm:ss' },
{ value: 'yyyy/MM/dd HH:mm:ss', label: 'yyyy/MM/dd HH:mm:ss' },
{ value: 'yyyy-MM-dd HH:mm:ss', label: 'yyyy-MM-dd HH:mm:ss' },
{ value: 'Timestamp', label: '时间戳' }
],
statusList: [
{ value: 500, label: '失败' },
{ value: 502, label: '失败(超时)' },
{ value: 200, label: '成功' },
{ value: 0, label: '无' }
]
}
},
created() {
this.fetchData()
this.getExecutor()
this.getJobIdList()
this.getJobProject()
this.getDataSourceList()
},
methods: {
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done()
})
.catch(_ => {})
},
getExecutor() {
job.getExecutorList().then(response => {
const { content } = response
this.executorList = content
})
},
getJobIdList() {
job.getJobIdList().then(response => {
const { content } = response
this.jobIdList = content
})
},
getJobProject() {
jobProjectApi.getJobProjectList().then(response => {
this.jobProjectList = response.data
})
},
getDataSourceList() {
datasourceApi.getDataSourceList().then(response => {
this.dataSourceList = response
})
},
fetchData() {
this.listLoading = true
if (this.projectIds) {
this.listQuery.projectIds = this.projectIds.toString()
}
job.getList(this.listQuery).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
incStartTimeFormat(vData) {
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
if (this.temp.glueType === 'BEAN' && !isJSON(this.jobJson)) {
this.$notify({
title: 'Fail',
message: 'json格式错误',
type: 'error',
duration: 2000
})
return
}
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.temp.childJobId) {
const auth = []
for (const i in this.temp.childJobId) {
auth.push(this.temp.childJobId[i].id)
}
this.temp.childJobId = auth.toString()
}
this.temp.jobJson = this.jobJson
this.temp.glueSource = this.glueSource
this.temp.executorHandler = this.temp.glueType === 'BEAN' ? 'executorJobHandler' : ''
if (this.partitionField) this.temp.partitionInfo = this.partitionField + ',' + this.timeOffset + ',' + this.timeFormatType
job.createJob(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handlerUpdate(row) {
// this.resetTemp()
this.temp = Object.assign({}, row) // copy obj
if (this.temp.jobJson) this.jobJson = JSON.parse(this.temp.jobJson)
this.glueSource = this.temp.glueSource
const arrchildSet = []
const arrJobIdList = []
if (this.jobIdList) {
for (const n in this.jobIdList) {
if (this.jobIdList[n].id !== this.temp.id) {
arrJobIdList.push(this.jobIdList[n])
}
}
this.JobIdList = arrJobIdList
}
if (this.temp.childJobId) {
const arrString = this.temp.childJobId.split(',')
for (const i in arrString) {
for (const n in this.jobIdList) {
if (this.jobIdList[n].id === parseInt(arrString[i])) {
arrchildSet.push(this.jobIdList[n])
}
}
}
this.temp.childJobId = arrchildSet
}
if (this.temp.partitionInfo) {
const partition = this.temp.partitionInfo.split(',')
this.partitionField = partition[0]
this.timeOffset = partition[1]
this.timeFormatType = partition[2]
}
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.temp.jobJson = typeof (this.jobJson) !== 'string' ? JSON.stringify(this.jobJson) : this.jobJson
if (this.temp.glueType === 'BEAN' && !isJSON(this.temp.jobJson)) {
this.$notify({
title: 'Fail',
message: 'json格式错误',
type: 'error',
duration: 2000
})
return
}
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.temp.childJobId) {
const auth = []
for (const i in this.temp.childJobId) {
auth.push(this.temp.childJobId[i].id)
}
this.temp.childJobId = auth.toString()
}
this.temp.executorHandler = this.temp.glueType === 'BEAN' ? 'executorJobHandler' : ''
this.temp.glueSource = this.glueSource
if (this.partitionField) this.temp.partitionInfo = this.partitionField + ',' + this.timeOffset + ',' + this.timeFormatType
job.updateJob(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handlerDelete(row) {
this.$confirm('确定删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
job.removeJob(row.id).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
})
// const index = this.list.indexOf(row)
},
handlerExecute(row) {
this.$confirm('确定执行吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const param = {}
param.jobId = row.id
param.executorParam = row.executorParam
job.triggerJob(param).then(response => {
this.$notify({
title: 'Success',
message: 'Execute Successfully',
type: 'success',
duration: 2000
})
})
})
},
// 查看日志
handlerViewLog(row) {
this.$router.push({ path: '/data/log', query: { jobId: row.id }})
},
handlerStart(row) {
job.startJob(row.id).then(response => {
this.$notify({
title: 'Success',
message: 'Start Successfully',
type: 'success',
duration: 2000
})
})
},
handlerStop(row) {
job.stopJob(row.id).then(response => {
this.$notify({
title: 'Success',
message: 'Start Successfully',
type: 'success',
duration: 2000
})
})
},
changeSwitch(row) {
row.triggerStatus === 1 ? this.handlerStart(row) : this.handlerStop(row)
},
nextTriggerTime(row) {
job.nextTriggerTime(row.jobCron).then(response => {
const { content } = response
this.triggerNextTimes = content.join('<br>')
})
},
loadById(row) {
executor.loadById(row.jobGroup).then(response => {
this.registerNode = []
const { content } = response
this.registerNode.push(content)
})
}
}
}
</script>
<style>
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-dropdown + .el-dropdown {
margin-left: 15px;
}
</style>

View File

@@ -0,0 +1,319 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.jobId" placeholder="全部" style="width: 200px" />
<el-select v-model="listQuery.jobGroup" placeholder="执行器">
<el-option v-for="item in executorList" :key="item.id" :label="item.title" :value="item.id" />
</el-select>
<el-select v-model="listQuery.logStatus" placeholder="类型" style="width: 200px">
<el-option v-for="item in logStatusList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button
class="filter-item"
style="margin-left: 10px;"
type="primary"
icon="el-icon-edit"
@click="handlerDelete"
>
清除
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column align="center" label="任务ID" width="80">
<template slot-scope="scope">{{ scope.row.jobId }}</template>
</el-table-column>
<el-table-column align="center" label="任务描述">
<template slot-scope="scope">{{ scope.row.jobDesc }}</template>
</el-table-column>
<el-table-column label="调度时间" align="center">
<template slot-scope="scope">{{ scope.row.triggerTime }}</template>
</el-table-column>
<el-table-column label="执行结果" align="center">
<template slot-scope="scope"> <span :style="`color:${scope.row.handleCode==500?'red':''}`">{{ statusList.find(t => t.value === scope.row.handleCode).label }}</span></template>
</el-table-column>
<el-table-column label="操作" align="center" width="300">
<template slot-scope="{row}">
<el-button type="primary" @click="handleViewJobLog(row)">日志查看</el-button>
<el-button type="primary" @click="killRunningJob(row)">
终止任务
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="listQuery.current"
:limit.sync="listQuery.size"
@pagination="fetchData"
/>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="600px">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="center" label-width="100px">
<el-row>
<el-col :span="14" :offset="5">
<el-form-item label="执行器">
<el-input size="medium" value="全部" :disabled="true" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="14" :offset="5">
<el-form-item label="任务">
<el-input size="medium" value="全部" :disabled="true" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="14" :offset="5">
<el-form-item label="执行器">
<el-select v-model="temp.deleteType" placeholder="请选择执行器" style="width: 230px">
<el-option v-for="item in deleteTypeList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="deleteLog">
确定
</el-button>
</div>
</el-dialog>
<el-dialog title="日志查看" :visible.sync="dialogVisible" width="95%">
<div class="log-container">
<pre :loading="logLoading" v-text="logContent" />
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">
关闭
</el-button>
<el-button type="primary" @click="loadLog">
刷新日志
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as log from '@/api/dts/datax-job-log'
import * as job from '@/api/dts/datax-job-info'
import waves from '@/directive/waves' // waves directive
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
export default {
name: 'JobLog',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
dialogVisible: false,
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
jobGroup: 0,
jobId: '',
logStatus: -1,
filterTime: ''
},
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
executorList: '',
textMap: {
create: 'Clear'
},
rules: {},
temp: {
deleteType: 1,
jobGroup: 0,
jobId: 0
},
statusList: [
{ value: 500, label: '失败' },
{ value: 502, label: '失败(超时)' },
{ value: 200, label: '成功' },
{ value: 0, label: '无' }
],
deleteTypeList: [
{ value: 1, label: '清理一个月之前日志数据' },
{ value: 2, label: '清理三个月之前日志数据' },
{ value: 3, label: '清理六个月之前日志数据' },
{ value: 4, label: '清理一年之前日志数据' },
{ value: 5, label: '清理一千条以前日志数据' },
{ value: 6, label: '清理一万条以前日志数据' },
{ value: 7, label: '清理三万条以前日志数据' },
{ value: 8, label: '清理十万条以前日志数据' },
{ value: 9, label: '清理所有日志数据' }
],
logStatusList: [
{ value: -1, label: '全部' },
{ value: 1, label: '成功' },
{ value: 2, label: '失败' },
{ value: 3, label: '进行中' }
],
// 日志查询参数
jobLogQuery: {
executorAddress: '',
triggerTime: '',
id: '',
fromLineNum: 1
},
// 日志内容
logContent: '',
// 显示日志
logShow: false,
// 日志显示加载中效果
logLoading: false
}
},
created() {
this.fetchData()
this.getExecutor()
},
methods: {
fetchData() {
this.listLoading = true
const param = Object.assign({}, this.listQuery)
const urlJobId = this.$route.query.jobId
if (urlJobId > 0 && !param.jobId) {
param.jobId = urlJobId
} else if (!urlJobId && !param.jobId) {
param.jobId = 0
}
log.getList(param).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
getExecutor() {
job.getExecutorList().then(response => {
const { content } = response
this.executorList = content
const defaultParam = { id: 0, title: '全部' }
this.executorList.unshift(defaultParam)
this.listQuery.jobGroup = this.executorList[0].id
})
},
handlerDelete() {
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
deleteLog() {
log.clearLog(this.temp.jobGroup, this.temp.jobId, this.temp.deleteType).then(response => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
// const index = this.list.indexOf(row)
},
// 查看日志
handleViewJobLog(row) {
// const str = location.href.split('#')[0]
// window.open(`${str}#/ router的name `)
this.dialogVisible = true
this.jobLogQuery.executorAddress = row.executorAddress
this.jobLogQuery.id = row.id
this.jobLogQuery.triggerTime = Date.parse(row.triggerTime)
if (this.logShow === false) {
this.logShow = true
}
// window.open(`#/data/log?executorAddress=${this.jobLogQuery.executorAddress}&triggerTime=${this.jobLogQuery.triggerTime}&id=${this.jobLogQuery.id}&fromLineNum=${this.jobLogQuery.fromLineNum}`)
this.loadLog()
},
// 获取日志
loadLog() {
this.logLoading = true
log.viewJobLog(this.jobLogQuery.executorAddress, this.jobLogQuery.triggerTime, this.jobLogQuery.id,
this.jobLogQuery.fromLineNum).then(response => {
// 判断是否是 '\n',如果是表示显示完成,不重新加载
if (response.content.logContent === '\n') {
// this.jobLogQuery.fromLineNum = response.toLineNum - 20;
// 重新加载
// setTimeout(() => {
// this.loadLog()
// }, 2000);
} else {
this.logContent = response.content.logContent
}
this.logLoading = false
})
},
killRunningJob(row) {
log.killJob(row).then(response => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Kill Successfully',
type: 'success',
duration: 2000
})
})
}
}
}
</script>
<style lang="scss" scoped>
.log-container {
margin-bottom: 20px;
background: #f5f5f5;
width: 100%;
height: 400px;
overflow: scroll;
pre {
display: block;
padding: 10px;
margin: 0 0 10.5px;
word-break: break-all;
word-wrap: break-word;
color: #334851;
background-color: #f5f5f5;
// border: 1px solid #ccd1d3;
border-radius: 1px;
}
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<div>
<div style="background-color: #304156; padding:10px 0; text-align:right;"><el-button type="primary" style="margin-right:20px;" @click="loadLog">刷新日志</el-button></div>
<div class="log-container">
<pre :loading="logLoading" v-text="logContent" />
</div>
</div>
</template>
<script>
import * as log from '@/api/dts/datax-job-log'
export default {
data() {
return {
logContent: '',
logLoading: false
}
},
created() {
this.loadLog()
},
methods: {
loadLog() {
this.logLoading = true
log.viewJobLog(this.$route.query.executorAddress, this.$route.query.triggerTime, this.$route.query.id, this.$route.query.fromLineNum).then(response => {
// 判断是否是 '\n',如果是表示显示完成,不重新加载
if (response.content.logContent === '\n') {
// this.jobLogQuery.fromLineNum = response.toLineNum - 20;
// 重新加载
// setTimeout(() => {
// this.loadLog()
// }, 2000);
} else {
this.logContent = response.content.logContent
}
this.logLoading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.log-container {
background: #f5f5f5;
height: 500px;
overflow: scroll;
margin:20px;
border:1px solid #ddd;
pre {
display: block;
padding: 10px;
margin: 0 0 10.5px;
word-break: break-all;
word-wrap: break-word;
color: #334851;
background-color: #f5f5f5;
// border: 1px solid #ccd1d3;
border-radius: 1px;
}
}
</style>

View File

@@ -0,0 +1,227 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input
v-model="listQuery.searchVal"
clearable
placeholder="项目名称"
style="width: 200px;"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
添加
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column align="center" label="序号" width="95">
<template slot-scope="scope">{{ scope.$index+1 }}</template>
</el-table-column>
<el-table-column label="项目名称" align="center">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column label="项目描述" align="center">
<template slot-scope="scope">{{ scope.row.description }}</template>
</el-table-column>
<el-table-column label="所属用户" width="200" align="center">
<template slot-scope="scope">{{ scope.row.userName }}
</template>
</el-table-column>
<el-table-column label="创建时间" width="200" align="center">
<template slot-scope="scope">{{ scope.row.createTime }}</template>
</el-table-column>
<el-table-column label="操作" align="center" width="230" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="handleUpdate(row)">
编辑
</el-button>
<el-button v-if="row.status!=='deleted'" size="mini" type="danger" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="listQuery.pageNo"
:limit.sync="listQuery.pageSize"
@pagination="fetchData"
/>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="800px">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="100px">
<el-form-item label="项目名称" prop="name">
<el-input v-model="temp.name" placeholder="项目名称" style="width: 40%" />
</el-form-item>
<el-form-item label="项目描述" prop="description">
<el-input v-model="temp.description" placeholder="项目描述" style="width: 40%" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="dialogPluginVisible" title="Reading statistics">
<el-table :data="pluginData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="key" label="Channel" />
<el-table-column prop="pv" label="Pv" />
</el-table>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogPvVisible = false">Confirm</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import * as jobProjectApi from '@/api/dts/datax-job-project'
import waves from '@/directive/waves'
import Pagination from '@/components/Pagination'
export default {
name: 'JobProject',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
pageNo: 1,
pageSize: 10,
searchVal: ''
},
pluginTypeOptions: ['reader', 'writer'],
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
name: [{ required: true, message: 'this is required', trigger: 'blur' }],
description: [{ required: true, message: 'this is required', trigger: 'blur' }]
},
temp: {
id: undefined,
name: '',
description: ''
},
visible: true
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
jobProjectApi.list(this.listQuery).then(response => {
const { records, total } = response.data
this.total = parseInt(total || 0)
this.list = records
this.listLoading = false
})
},
resetTemp() {
this.temp = {
id: undefined,
name: '',
description: ''
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
jobProjectApi.created(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
jobProjectApi.updated(tempData).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleDelete(row) {
console.log('删除')
const idList = []
idList.push(row.id)
jobProjectApi.deleted({ idList: row.id }).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
}
}
}
</script>

View File

@@ -0,0 +1,529 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.jobDesc" placeholder="任务描述" style="width: 200px;" class="filter-item" />
<el-select v-model="projectIds" multiple placeholder="所属项目" class="filter-item">
<el-option v-for="item in jobProjectList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
添加
</el-button>
<!-- <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">
reviewer
</el-checkbox> -->
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column align="center" label="任务ID" width="80">
<template slot-scope="scope">{{ scope.row.id }}</template>
</el-table-column>
<el-table-column label="任务描述" align="center">
<template slot-scope="scope">{{ scope.row.jobDesc }}</template>
</el-table-column>
<el-table-column label="所属项目" align="center" width="120">
<template slot-scope="scope">{{ scope.row.projectName }}</template>
</el-table-column>
<el-table-column label="Cron" align="center">
<template slot-scope="scope">
<span>{{ scope.row.jobCron }}</span>
</template>
</el-table-column>
<el-table-column label="路由策略" align="center">
<template slot-scope="scope"> {{ routeStrategies.find(t => t.value === scope.row.executorRouteStrategy).label }}</template>
</el-table-column>
<el-table-column label="修改用户" align="center" width="80">
<template slot-scope="scope">{{ scope.row.userName }}</template>
</el-table-column>
<el-table-column label="注册节点" align="center">
<template slot-scope="scope">
<el-popover
placement="bottom"
width="500"
@show="loadById(scope.row)"
>
<el-table :data="registerNode">
<el-table-column width="150" property="title" label="执行器名称" />
<el-table-column width="150" property="appName" label="appName" />
<el-table-column width="150" property="registryList" label="机器地址" />
</el-table>
<el-button slot="reference" size="small">查看</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column label="下次触发时间" align="center">
<template slot-scope="scope">
<el-popover
placement="bottom"
width="300"
@show="nextTriggerTime(scope.row)"
>
<h5 v-html="triggerNextTimes" />
<el-button slot="reference" size="small">查看</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="{row}">
<el-dropdown trigger="click">
<span class="el-dropdown-link">
操作<i class="el-icon-arrow-down el-icon--right" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item divided @click.native="handlerUpdate(row)">编辑</el-dropdown-item>
<el-dropdown-item @click.native="handlerDelete(row)">删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="fetchData" />
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" width="1000px" :before-close="handleClose">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="110px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="执行器" prop="jobGroup">
<el-select v-model="temp.jobGroup" placeholder="请选择执行器">
<el-option v-for="item in executorList" :key="item.id" :label="item.title" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务描述" prop="jobDesc">
<el-input v-model="temp.jobDesc" size="medium" placeholder="请输入任务描述" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="路由策略" prop="executorRouteStrategy">
<el-select v-model="temp.executorRouteStrategy" placeholder="请选择路由策略">
<el-option v-for="item in routeStrategies" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-dialog
title="提示"
:visible.sync="showCronBox"
width="60%"
append-to-body
>
<cron v-model="temp.jobCron" />
<span slot="footer" class="dialog-footer">
<el-button @click="showCronBox = false;">关闭</el-button>
<el-button type="primary" @click="showCronBox = false"> </el-button>
</span>
</el-dialog>
<el-form-item label="Cron" prop="jobCron">
<el-input v-model="temp.jobCron" auto-complete="off" placeholder="请输入Cron表达式">
<el-button v-if="!showCronBox" slot="append" icon="el-icon-turn-off" title="打开图形配置" @click="showCronBox = true" />
<el-button v-else slot="append" icon="el-icon-open" title="关闭图形配置" @click="showCronBox = false" />
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="阻塞处理" prop="executorBlockStrategy">
<el-select v-model="temp.executorBlockStrategy" placeholder="请选择阻塞处理策略">
<el-option v-for="item in blockStrategies" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="报警邮件">
<el-input v-model="temp.alarmEmail" placeholder="请输入报警邮件,多个用逗号分隔" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="任务类型" prop="glueType">
<el-select v-model="temp.glueType" placeholder="任务脚本类型">
<el-option v-for="item in glueTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="失败重试次数">
<el-input-number v-model="temp.executorFailRetryCount" :min="0" :max="20" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="所属项目" prop="projectId">
<el-select v-model="temp.projectId" placeholder="所属项目" class="filter-item">
<el-option v-for="item in jobProjectList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="超时时间(分钟)">
<el-input-number v-model="temp.executorTimeout" :min="0" :max="120" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="子任务">
<el-select v-model="temp.childJobId" multiple placeholder="子任务" value-key="id">
<el-option v-for="item in jobIdList" :key="item.id" :label="item.jobDesc" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" />
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="JVM启动参数">
<el-input
v-model="temp.jvmParam"
placeholder="-Xms1024m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确认
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as executor from '@/api/dts/datax-executor'
import Cron from '@/components/Cron'
import * as jobTemp from '@/api/dts/datax-job-template'
import waves from '@/directive/waves' // waves directive
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import * as datasourceApi from '@/api/dts/datax-jdbcDatasource'
import * as jobProjectApi from '@/api/dts/datax-job-project'
import * as job from '@/api/dts/datax-job-info'
export default {
name: 'JobTemplate',
components: { Pagination, Cron },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
const validateIncParam = (rule, value, callback) => {
if (!value) {
callback(new Error('Increment parameters is required'))
}
callback()
}
const validatePartitionParam = (rule, value, callback) => {
if (!this.partitionField) {
callback(new Error('Partition parameters is required'))
}
callback()
}
return {
projectIds: '',
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
jobGroup: 0,
triggerStatus: -1,
jobDesc: '',
executorHandler: '',
userId: 0,
projectIds: ''
},
showCronBox: false,
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
jobGroup: [{ required: true, message: 'jobGroup is required', trigger: 'change' }],
executorRouteStrategy: [{ required: true, message: 'executorRouteStrategy is required', trigger: 'change' }],
executorBlockStrategy: [{ required: true, message: 'executorBlockStrategy is required', trigger: 'change' }],
jobDesc: [{ required: true, message: 'jobDesc is required', trigger: 'blur' }],
jobProject: [{ required: true, message: 'jobProject is required', trigger: 'blur' }],
jobCron: [{ required: true, message: 'jobCron is required', trigger: 'blur' }],
incStartId: [{ trigger: 'blur', validator: validateIncParam }],
replaceParam: [{ trigger: 'blur', validator: validateIncParam }],
primaryKey: [{ trigger: 'blur', validator: validateIncParam }],
incStartTime: [{ trigger: 'change', validator: validateIncParam }],
replaceParamType: [{ trigger: 'change', validator: validateIncParam }],
partitionField: [{ trigger: 'blur', validator: validatePartitionParam }],
datasourceId: [{ trigger: 'change', validator: validateIncParam }],
readerTable: [{ trigger: 'blur', validator: validateIncParam }],
projectId: [{ required: true, message: 'projectId is required', trigger: 'change' }]
},
temp: {
id: undefined,
jobGroup: '',
jobCron: '',
jobDesc: '',
executorRouteStrategy: 'RANDOM',
executorBlockStrategy: 'DISCARD_LATER',
childJobId: '',
executorFailRetryCount: '',
alarmEmail: '',
executorTimeout: '',
userId: 0,
jobConfigId: '',
executorHandler: 'executorJobHandler',
glueType: 'BEAN',
executorParam: '',
jvmParam: '',
projectId: '',
datasourceId: 0,
readerTable: ''
},
resetTemp() {
this.temp = this.$options.data().temp
},
executorList: '',
jobIdList: '',
jobProjectList: '',
dataSourceList: '',
blockStrategies: [
{ value: 'SERIAL_EXECUTION', label: '单机串行' },
{ value: 'DISCARD_LATER', label: '丢弃后续调度' },
{ value: 'COVER_EARLY', label: '覆盖之前调度' }
],
routeStrategies: [
{ value: 'FIRST', label: '第一个' },
{ value: 'LAST', label: '最后一个' },
{ value: 'ROUND', label: '轮询' },
{ value: 'RANDOM', label: '随机' },
{ value: 'CONSISTENT_HASH', label: '一致性HASH' },
{ value: 'LEAST_FREQUENTLY_USED', label: '最不经常使用' },
{ value: 'LEAST_RECENTLY_USED', label: '最近最久未使用' },
{ value: 'FAILOVER', label: '故障转移' },
{ value: 'BUSYOVER', label: '忙碌转移' }
// { value: 'SHARDING_BROADCAST', label: '分片广播' }
],
glueTypes: [
{ value: 'BEAN', label: 'FlinkX任务' }
],
triggerNextTimes: '',
registerNode: []
}
},
created() {
this.fetchData()
this.getExecutor()
this.getJobIdList()
this.getJobProject()
this.getDataSourceList()
},
methods: {
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done()
})
.catch(_ => {})
},
getExecutor() {
jobTemp.getExecutorList().then(response => {
const { content } = response
this.executorList = content
})
},
getJobIdList() {
job.getJobIdList().then(response => {
const { content } = response
this.jobIdList = content
})
},
getJobProject() {
jobProjectApi.getJobProjectList().then(response => {
this.jobProjectList = response.data
})
},
getDataSourceList() {
datasourceApi.getDataSourceList().then(response => {
this.dataSourceList = response
})
},
fetchData() {
this.listLoading = true
if (this.projectIds) {
this.listQuery.projectIds = this.projectIds.toString()
}
jobTemp.getList(this.listQuery).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
handleCreate() {
//this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.temp.jobGroup = this.executorList[0]['id']
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.temp.childJobId) {
const childJobs = []
for (const i in this.temp.childJobId) {
childJobs.push(this.temp.childJobId[i].id)
}
this.temp.childJobId = childJobs.toString()
}
if (this.partitionField) this.temp.partitionInfo = this.partitionField + ',' + this.timeOffset + ',' + this.timeFormatType
jobTemp.createJob(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handlerUpdate(row) {
//this.resetTemp()
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
const arrchildSet = []
const arrJobIdList = []
if (this.JobIdList) {
for (const n in this.JobIdList) {
if (this.JobIdList[n].id !== this.temp.id) {
arrJobIdList.push(this.JobIdList[n])
}
}
this.JobIdList = arrJobIdList
}
if (this.temp.childJobId) {
const arrString = this.temp.childJobId.split(',')
for (const i in arrString) {
for (const n in this.jobIdList) {
if (this.jobIdList[n].id === parseInt(arrString[i])) {
arrchildSet.push(this.jobIdList[n])
}
}
}
this.temp.childJobId = arrchildSet
}
if (this.temp.partitionInfo) {
const partition = this.temp.partitionInfo.split(',')
this.partitionField = partition[0]
this.timeOffset = partition[1]
this.timeFormatType = partition[2]
}
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.temp.childJobId) {
const childJobs = []
for (const i in this.temp.childJobId) {
childJobs.push(this.temp.childJobId[i].id)
}
this.temp.childJobId = childJobs.toString()
}
if (this.partitionField) this.temp.partitionInfo = this.partitionField + ',' + this.timeOffset + ',' + this.timeFormatType
jobTemp.updateJob(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handlerDelete(row) {
this.$confirm('确定删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
jobTemp.removeJob(row.id).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
})
// const index = this.list.indexOf(row)
},
nextTriggerTime(row) {
jobTemp.nextTriggerTime(row.jobCron).then(response => {
const { content } = response
this.triggerNextTimes = content.join('<br>')
})
},
loadById(row) {
executor.loadById(row.jobGroup).then(response => {
this.registerNode = []
const { content } = response
this.registerNode.push(content)
})
}
}
}
</script>
<style>
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-dropdown + .el-dropdown {
margin-left: 15px;
}
</style>

View File

@@ -0,0 +1,9 @@
// 公共的组件,来做为中间传达的工具
import Vue from 'vue'
export default new Vue({
data() {
return {
dataSourceId: ''
}
}
})

View File

@@ -0,0 +1,9 @@
// 公共的组件,来做为中间传达的工具
import Vue from 'vue'
export default new Vue({
data() {
return {
dataSourceId: ''
}
}
})

View File

@@ -0,0 +1,76 @@
<template>
<div class="app-container">
<el-form label-position="left" label-width="80px" :model="readerForm">
<el-form-item label="源端表">
<el-checkbox
v-model="readerForm.lcheckAll"
:indeterminate="readerForm.isIndeterminate"
@change="lHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.ltables" @change="lHandleCheckedChange">
<el-checkbox v-for="c in fromTablesList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="目标表">
<el-checkbox
v-model="readerForm.rcheckAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 20px 0;" />
<el-checkbox-group v-model="readerForm.rtables" @change="rHandleCheckedChange">
<el-checkbox v-for="c in toTablesList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: 'TableMapper',
data() {
return {
mapperJson: {},
fromTablesList: [],
toTablesList: [],
readerForm: {
ltables: [],
rtables: [],
lcheckAll: false,
rcheckAll: false,
isIndeterminate: true
}
}
},
mounted() {
},
methods: {
lHandleCheckAllChange(val) {
this.readerForm.ltables = val ? this.fromTablesList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckAllChange(val) {
this.readerForm.rtables = val ? this.toTablesList : []
this.readerForm.isIndeterminate = false
},
lHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.fromTablesList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromTablesList.length
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.toTablesList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.toTablesList.length
},
getLTables() {
return this.readerForm.ltables
},
getRTables() {
return this.readerForm.rtables
}
}
}
</script>

View File

@@ -0,0 +1,250 @@
<template>
<div class="app-container">
<div class="build-container">
<el-steps :active="active" finish-status="success">
<el-step title="步骤 1" description="构建reader">1</el-step>
<el-step title="步骤 2" description="构建writer">2</el-step>
<el-step title="步骤 3" description="表映射">3</el-step>
<el-step title="步骤 4" description="批量创建">4</el-step>
</el-steps>
<div v-show="active===1" class="step1">
<Reader ref="reader" />
</div>
<div v-show="active===2" class="step2">
<Writer ref="writer" />
</div>
<div v-show="active===3" class="step3">
<Mapper ref="mapper" />
</div>
<div v-show="active===4" class="step4">
<el-button type="primary" @click="handleJobTemplateSelectDrawer">{{ jobTemplate ? jobTemplate : "1.选择模板" }}</el-button>
<el-button type="primary" @click="createJob">2.批量创建任务</el-button>
(步骤选择模板->批量创建任务)
<el-drawer
ref="jobTemplateSelectDrawer"
title="选择模板"
:visible.sync="jobTemplateSelectDrawer"
direction="rtl"
size="50%"
>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
destroy-on-close="true"
@current-change="handleCurrentChange"
>
<el-table-column align="center" label="任务ID" width="80">
<template slot-scope="scope">{{ scope.row.id }}</template>
</el-table-column>
<el-table-column label="任务描述" align="center">
<template slot-scope="scope">{{ scope.row.jobDesc }}</template>
</el-table-column>
<el-table-column label="所属项目" align="center">
<template slot-scope="scope">{{ scope.row.jobProject }}</template>
</el-table-column>
<el-table-column label="Cron" align="center">
<template slot-scope="scope"><span>{{ scope.row.jobCron }}</span></template>
</el-table-column>
<el-table-column label="路由策略" align="center">
<template slot-scope="scope"> {{ routeStrategies.find(t => t.value === scope.row.executorRouteStrategy).label }}</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="fetchData" />
</el-drawer>
<div style="margin-bottom: 20px;" />
</div>
<el-button :disabled="active===1" style="margin-top: 12px;" @click="last">上一步</el-button>
<el-button v-show="active!==4" type="primary" style="margin-top: 12px;margin-bottom: 12px;" @click="next">下一步</el-button>
</div>
</div>
</template>
<script>
import * as jobTemplate from '@/api/dts/datax-job-template'
import * as job from '@/api/dts/datax-job-info'
import Pagination from '@/components/Pagination'
import Reader from './reader'
import Writer from './writer'
import clip from '@/utils/clipboard'
import Mapper from './mapper'
export default {
name: 'JsonBuild',
components: { Reader, Writer, Pagination, Mapper },
data() {
return {
configJson: '',
active: 1,
jobTemplate: '',
jobTemplateSelectDrawer: false,
list: null,
currentRow: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
jobGroup: 0,
triggerStatus: -1,
jobDesc: '',
executorHandler: '',
userId: 0
},
blockStrategies: [
{ value: 'SERIAL_EXECUTION', label: '单机串行' },
{ value: 'DISCARD_LATER', label: '丢弃后续调度' },
{ value: 'COVER_EARLY', label: '覆盖之前调度' }
],
routeStrategies: [
{ value: 'FIRST', label: '第一个' },
{ value: 'LAST', label: '最后一个' },
{ value: 'ROUND', label: '轮询' },
{ value: 'RANDOM', label: '随机' },
{ value: 'CONSISTENT_HASH', label: '一致性HASH' },
{ value: 'LEAST_FREQUENTLY_USED', label: '最不经常使用' },
{ value: 'LEAST_RECENTLY_USED', label: '最近最久未使用' },
{ value: 'FAILOVER', label: '故障转移' },
{ value: 'BUSYOVER', label: '忙碌转移' }
// { value: 'SHARDING_BROADCAST', label: '分片广播' }
],
triggerNextTimes: '',
registerNode: [],
jobJson: '',
temp: {
id: undefined,
jobGroup: '',
jobCron: '',
jobDesc: '',
executorRouteStrategy: '',
executorBlockStrategy: '',
childJobId: '',
executorFailRetryCount: '',
alarmEmail: '',
executorTimeout: '',
userId: 0,
jobConfigId: '',
executorHandler: 'executorJobHandler',
glueType: 'BEAN',
jobJson: '',
executorParam: '',
replaceParam: '',
jvmParam: '',
incStartTime: '',
templateId: 0
}
}
},
created() {
// this.getJdbcDs()
},
methods: {
next() {
const fromTableList = this.$refs.reader.getData().tables
const toTableList = this.$refs.writer.getData().tables
// const fromTableName = this.$refs.reader.getData().tableName
// 第一步 reader 判断是否已选字段
if (this.active === 1) {
// 实现第一步骤读取的表和字段直接带到第二步骤
// this.$refs.writer.sendTableNameAndColumns(fromTableName, fromColumnList)
// 取子组件的数据
// console.info(this.$refs.reader.getData())
this.active++
} else {
// 将第一步和第二步得到的字段名字发送到第三步
if (this.active === 2) {
this.$refs.mapper.sendTables(fromTableList, toTableList)
}
if (this.active !== 4) {
this.active++
}
}
},
last() {
if (this.active > 1) {
this.active--
}
},
// Create job
createJob() {
const readerData = this.$refs.reader.getData()
const writeData = this.$refs.writer.getData()
const readerTables = this.$refs.mapper.getLTables()
const writerTables = this.$refs.mapper.getRTables()
const rdbmsReader = {
readerSplitPk: readerData.splitPk
}
const rdbmsWriter = {}
const obj = {
readerDatasourceId: readerData.datasourceId,
readerTables: readerTables,
writerDatasourceId: writeData.datasourceId,
writerTables: writerTables,
rdbmsReader: rdbmsReader,
rdbmsWriter: rdbmsWriter,
templateId: this.temp.templateId
}
// 调api
job.batchAddJob(obj).then(response => {
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
// 切回第一步
this.active = 1
})
},
handleCopy(text, event) {
clip(this.configJson, event)
this.$message({
message: 'copy success',
type: 'success'
})
},
handleJobTemplateSelectDrawer() {
this.jobTemplateSelectDrawer = !this.jobTemplateSelectDrawer
if (this.jobTemplateSelectDrawer) {
this.fetchData()
this.getExecutor()
}
},
getReaderData() {
return this.$refs.reader.getData()
},
getExecutor() {
jobTemplate.getExecutorList().then(response => {
const { content } = response
this.executorList = content
})
},
fetchData() {
this.listLoading = true
jobTemplate.getList(this.listQuery).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
handleCurrentChange(val) {
this.temp = Object.assign({}, val)
this.temp.id = undefined
this.temp.jobDesc = this.getReaderData().tableName
this.$refs.jobTemplateSelectDrawer.closeDrawer()
this.jobTemplate = val.id + '(' + val.jobDesc + ')'
this.temp.templateId = val.id
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,24 @@
<template>
<div class="app-container">
<TableMapper ref="mapper" />
</div>
</template>
<script>
import TableMapper from './components/tableMapper'
export default {
name: 'Mapper',
components: { TableMapper },
methods: {
sendTables(fromTablesList, toTablesList) {
this.$refs.mapper.fromTablesList = fromTablesList
this.$refs.mapper.toTablesList = toTablesList
},
getLTables() {
return this.$refs.mapper.getLTables()
},
getRTables() {
return this.$refs.mapper.getRTables()
}
}
}
</script>

View File

@@ -0,0 +1,27 @@
<template>
<div class="app-container">
<TableReader ref="tablereader" @selectDataSource="showDataSource" />
</div>
</template>
<script>
import TableReader from './reader/tableReader'
export default {
name: 'Reader',
components: { TableReader },
data() {
return {
dataSource: ''
}
},
methods: {
getData() {
return this.$refs.tablereader.getData()
},
showDataSource(data) {
this.dataSource = data
this.getData()
}
}
}
</script>

View File

@@ -0,0 +1,170 @@
<template>
<div class="app-container">
<el-form label-position="right" label-width="120px" :model="readerForm" :rules="rules">
<el-form-item label="数据库源:" prop="datasourceId">
<el-select v-model="readerForm.datasourceId" filterable style="width: 300px" @change="rDsChange">
<el-option
v-for="item in rDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item v-show="dataSource==='postgresql' || dataSource==='oracle' ||dataSource==='sqlserver' ||dataSource==='hana'" label="Schema">
<el-select v-model="readerForm.tableSchema" filterable style="width: 300px" @change="schemaChange">
<el-option
v-for="item in schemaList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="切分字段:">
<el-input v-model="readerForm.splitPk" placeholder="切分主键" style="width: 13%" />
</el-form-item>
<el-form-item label="数据库表名:">
<el-checkbox
v-model="readerForm.checkAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选
</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.tables" @change="rHandleCheckedChange">
<el-checkbox v-for="c in rTbList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busReader'
export default {
name: 'TableReader',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200,
ascs: 'datasource_name'
},
rDsList: [],
rTbList: [],
schemaList: [],
loading: false,
active: 1,
customFields: '',
customType: '',
customValue: '',
dataSource: '',
readerForm: {
datasourceId: undefined,
tables: [],
checkAll: false,
isIndeterminate: true,
splitPk: '',
tableSchema: ''
},
rules: {
datasourceId: [{ required: true, message: 'this is required', trigger: 'change' }],
tableName: [{ required: true, message: 'this is required', trigger: 'change' }]
}
}
},
watch: {
'readerForm.datasourceId': function(oldVal, newVal) {
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
this.getSchema()
} else {
this.getTables('reader')
}
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs() {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response.data
this.rDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'reader') {
let obj = {}
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
obj = {
datasourceId: this.readerForm.datasourceId,
tableSchema: this.readerForm.tableSchema
}
} else {
obj = {
datasourceId: this.readerForm.datasourceId
}
}
// 组装
dsQueryApi.getTables(obj).then(response => {
if (response) {
this.rTbList = response.data
}
})
}
},
getSchema() {
const obj = {
datasourceId: this.readerForm.datasourceId
}
dsQueryApi.getTableSchema(obj).then(response => {
this.schemaList = response
})
},
// schema 切换
schemaChange(e) {
this.readerForm.tableSchema = e
// 获取可用表
this.getTables('reader')
},
// reader 数据源切换
rDsChange(e) {
// 清空
this.readerForm.tableName = ''
this.readerForm.datasourceId = e
this.rDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables('reader')
},
rHandleCheckAllChange(val) {
this.readerForm.tables = val ? this.rTbList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.rTbList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.rTbList.length
},
getData() {
if (Bus.dataSourceId) {
this.readerForm.datasourceId = Bus.dataSourceId
}
return this.readerForm
}
}
}
</script>

View File

@@ -0,0 +1,38 @@
<template>
<div class="app-container">
<TableWriter ref="tablewriter" @selectDataSource="showDataSource" />
</div>
</template>
<script>
import TableWriter from './writer/tableWriter'
export default {
name: 'Writer',
components: { TableWriter },
data() {
return {
dataSource: ''
}
},
methods: {
getData() {
return this.$refs.tablewriter.getData()
},
getTableName() {
return this.$refs.tablewriter.getTableName()
},
getReaderData() {
return this.$parent.getReaderData()
},
showDataSource(data) {
this.dataSource = data
this.getData()
},
sendTableNameAndColumns(fromTableName, fromColumnList) {
this.$refs.hivewriter.fromTableName = fromTableName
this.$refs.hivewriter.fromColumnList = fromColumnList
}
}
}
</script>

View File

@@ -0,0 +1,181 @@
<template>
<div>
<el-form label-position="right" label-width="150px" :model="writerForm" :rules="rules">
<el-form-item label="数据库源:" prop="datasourceId">
<el-select
v-model="writerForm.datasourceId"
filterable
style="width: 300px;"
@change="wDsChange"
>
<el-option
v-for="item in wDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item v-show="dataSource==='postgresql' || dataSource==='oracle' ||dataSource==='sqlserver' ||dataSource==='hana'" label="Schema">
<el-select v-model="writerForm.tableSchema" filterable style="width: 300px" @change="schemaChange">
<el-option
v-for="item in schemaList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<div style="margin: 5px 0;" />
<el-form-item label="数据库表名:">
<el-checkbox v-model="writerForm.checkAll" :indeterminate="writerForm.isIndeterminate" @change="wHandleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="writerForm.tables" @change="wHandleCheckedChange">
<el-checkbox v-for="c in wTbList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busWriter'
export default {
name: 'TableWriter',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200,
ascs: 'datasource_name'
},
wDsList: [],
schemaList: [],
fromTableName: '',
wTbList: [],
dataSource: '',
createTableName: '',
writerForm: {
datasourceId: undefined,
tables: [],
checkAll: false,
isIndeterminate: true,
tableSchema: ''
},
readerForm: this.getReaderData(),
rules: {
datasourceId: [{ required: true, message: 'this is required', trigger: 'change' }],
tableName: [{ required: true, message: 'this is required', trigger: 'change' }]
}
}
},
watch: {
'writerForm.datasourceId': function(oldVal, newVal) {
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
this.getSchema()
} else {
this.getTables('writer')
}
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs() {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response.data
this.wDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'writer') {
let obj = {}
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
obj = {
datasourceId: this.writerForm.datasourceId,
tableSchema: this.writerForm.tableSchema
}
} else {
obj = {
datasourceId: this.writerForm.datasourceId
}
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.wTbList = response.data
})
}
},
getSchema() {
const obj = {
datasourceId: this.writerForm.datasourceId
}
dsQueryApi.getTableSchema(obj).then(response => {
this.schemaList = response
})
},
// schema 切换
schemaChange(e) {
this.writerForm.tableSchema = e
// 获取可用表
this.getTables('writer')
},
wDsChange(e) {
// 清空
this.writerForm.tableName = ''
this.writerForm.datasourceId = e
this.wDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables()
},
wHandleCheckAllChange(val) {
this.writerForm.tables = val ? this.wTbList : []
this.writerForm.isIndeterminate = false
},
wHandleCheckedChange(value) {
const checkedCount = value.length
this.writerForm.checkAll = checkedCount === this.wTbList.length
this.writerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.wTbList.length
},
getData() {
if (Bus.dataSourceId) {
this.writerForm.datasourceId = Bus.dataSourceId
}
return this.writerForm
},
getReaderData() {
return this.$parent.getReaderData()
},
getTableName() {
return this.fromTableName
},
createTable() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.createTableName
}
dsQueryApi.createTable(obj).then(response => {
this.$notify({
title: 'Success',
message: 'Create Table Successfully',
type: 'success',
duration: 2000
})
}).catch(() => console.log('promise catch err'))
}
}
}
</script>

View File

@@ -0,0 +1,9 @@
// 公共的组件,来做为中间传达的工具
import Vue from 'vue'
export default new Vue({
data() {
return {
dataSourceId: ''
}
}
})

View File

@@ -0,0 +1,9 @@
// 公共的组件,来做为中间传达的工具
import Vue from 'vue'
export default new Vue({
data() {
return {
dataSourceId: ''
}
}
})

View File

@@ -0,0 +1,76 @@
<template>
<div class="app-container">
<el-form label-position="left" label-width="80px" :model="readerForm">
<el-form-item label="源端字段">
<el-checkbox
v-model="readerForm.lcheckAll"
:indeterminate="readerForm.isIndeterminate"
@change="lHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.lcolumns" @change="lHandleCheckedChange">
<el-checkbox v-for="c in fromColumnsList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="目标字段">
<el-checkbox
v-model="readerForm.rcheckAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 20px 0;" />
<el-checkbox-group v-model="readerForm.rcolumns" @change="rHandleCheckedChange">
<el-checkbox v-for="c in toColumnsList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: 'FieldMapper',
data() {
return {
mapperJson: {},
fromColumnsList: [],
toColumnsList: [],
readerForm: {
lcolumns: [],
rcolumns: [],
lcheckAll: false,
rcheckAll: false,
isIndeterminate: true
}
}
},
mounted() {
},
methods: {
lHandleCheckAllChange(val) {
this.readerForm.lcolumns = val ? this.fromColumnsList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckAllChange(val) {
this.readerForm.rcolumns = val ? this.toColumnsList : []
this.readerForm.isIndeterminate = false
},
lHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.fromColumnsList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromColumnsList.length
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.toColumnsList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.toColumnsList.length
},
getLColumns() {
return this.readerForm.lcolumns
},
getRColumns() {
return this.readerForm.rcolumns
}
}
}
</script>

View File

@@ -0,0 +1,299 @@
<template>
<div class="app-container">
<div class="build-container">
<el-steps :active="active" finish-status="success">
<el-step title="步骤 1" description="构建reader">1</el-step>
<el-step title="步骤 2" description="构建writer">2</el-step>
<el-step title="步骤 3" description="字段映射">3</el-step>
<el-step title="步骤 4" description="构建">4</el-step>
</el-steps>
<div v-show="active===1" class="step1">
<Reader ref="reader" />
</div>
<div v-show="active===2" class="step2">
<Writer ref="writer" />
</div>
<div v-show="active===3" class="step3">
<Mapper ref="mapper" />
</div>
<div v-show="active===4" class="step4">
<el-button type="primary" @click="buildJson">1.构建</el-button>
<el-button type="primary" @click="handleJobTemplateSelectDrawer">{{ jobTemplate ? jobTemplate : "2.选择模板" }}</el-button>
<el-button type="info" @click="handleCopy(inputData,$event)">复制json</el-button>
(步骤构建->选择模板->下一步)
<el-drawer
ref="jobTemplateSelectDrawer"
title="选择模板"
:visible.sync="jobTemplateSelectDrawer"
direction="rtl"
size="50%"
>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
destroy-on-close="true"
@current-change="handleCurrentChange"
>
<el-table-column align="center" label="任务ID" width="80">
<template slot-scope="scope">{{ scope.row.id }}</template>
</el-table-column>
<el-table-column label="任务描述" align="center">
<template slot-scope="scope">{{ scope.row.jobDesc }}</template>
</el-table-column>
<el-table-column label="所属项目" align="center" width="120">
<template slot-scope="scope">{{ scope.row.projectName }}</template>
</el-table-column>
<el-table-column label="Cron" align="center">
<template slot-scope="scope"><span>{{ scope.row.jobCron }}</span></template>
</el-table-column>
<el-table-column label="路由策略" align="center">
<template slot-scope="scope"> {{ routeStrategies.find(t => t.value === scope.row.executorRouteStrategy).label }}</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="fetchData" />
</el-drawer>
<div style="margin-bottom: 20px;" />
<json-editor v-show="active===4" ref="jsonEditor" v-model="configJson" />
</div>
<el-button :disabled="active===1" style="margin-top: 12px;" @click="last">上一步</el-button>
<el-button type="primary" style="margin-top: 12px;margin-bottom: 12px;" @click="next">下一步</el-button>
</div>
</div>
</template>
<script>
import * as dataxJsonApi from '@/api/dts/datax-json'
import * as jobTemplate from '@/api/dts/datax-job-template'
import * as job from '@/api/dts/datax-job-info'
import Pagination from '@/components/Pagination'
import JsonEditor from '@/components/JsonEditor'
import Reader from './reader'
import Writer from './writer'
import clip from '@/utils/clipboard'
import Mapper from './mapper'
export default {
name: 'JsonBuild',
components: { Reader, Writer, Pagination, JsonEditor, Mapper },
data() {
return {
configJson: '',
active: 1,
jobTemplate: '',
jobTemplateSelectDrawer: false,
list: null,
currentRow: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
jobGroup: 0,
triggerStatus: -1,
jobDesc: '',
executorHandler: '',
userId: 0
},
blockStrategies: [
{ value: 'SERIAL_EXECUTION', label: '单机串行' },
{ value: 'DISCARD_LATER', label: '丢弃后续调度' },
{ value: 'COVER_EARLY', label: '覆盖之前调度' }
],
routeStrategies: [
{ value: 'FIRST', label: '第一个' },
{ value: 'LAST', label: '最后一个' },
{ value: 'ROUND', label: '轮询' },
{ value: 'RANDOM', label: '随机' },
{ value: 'CONSISTENT_HASH', label: '一致性HASH' },
{ value: 'LEAST_FREQUENTLY_USED', label: '最不经常使用' },
{ value: 'LEAST_RECENTLY_USED', label: '最近最久未使用' },
{ value: 'FAILOVER', label: '故障转移' },
{ value: 'BUSYOVER', label: '忙碌转移' }
// { value: 'SHARDING_BROADCAST', label: '分片广播' }
],
triggerNextTimes: '',
registerNode: [],
jobJson: '',
temp: {
id: undefined,
jobGroup: '',
jobCron: '',
jobDesc: '',
executorRouteStrategy: '',
executorBlockStrategy: '',
childJobId: '',
executorFailRetryCount: '',
alarmEmail: '',
executorTimeout: '',
userId: 0,
jobConfigId: '',
executorHandler: 'executorJobHandler',
glueType: 'BEAN',
jobJson: '',
executorParam: '',
replaceParam: '',
jvmParam: '',
incStartTime: ''
}
}
},
created() {
// this.getJdbcDs()
},
methods: {
next() {
const fromColumnList = this.$refs.reader.getData().columns
const toColumnsList = this.$refs.writer.getData().columns
// const fromTableName = this.$refs.reader.getData().tableName
// 第一步 reader 判断是否已选字段
if (this.active === 1) {
// 实现第一步骤读取的表和字段直接带到第二步骤
// this.$refs.writer.sendTableNameAndColumns(fromTableName, fromColumnList)
// 取子组件的数据
// console.info(this.$refs.reader.getData())
this.active++
} else {
// 将第一步和第二步得到的字段名字发送到第三步
if (this.active === 2) {
this.$refs.mapper.sendColumns(fromColumnList, toColumnsList)
}
if (this.active === 4) {
this.temp.jobJson = this.configJson
job.createJob(this.temp).then(() => {
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
// 切回第一步
this.active = 1
})
} else {
this.active++
}
}
},
last() {
if (this.active > 1) {
this.active--
}
},
// 构建json
buildJson() {
const readerData = this.$refs.reader.getData()
const writeData = this.$refs.writer.getData()
const readerColumns = this.$refs.mapper.getLColumns()
const writerColumns = this.$refs.mapper.getRColumns()
const hiveReader = {
readerPath: readerData.path,
readerDefaultFS: readerData.defaultFS,
readerFileType: readerData.fileType,
readerFieldDelimiter: readerData.fieldDelimiter,
readerSkipHeader: readerData.skipHeader
}
const hiveWriter = {
writerDefaultFS: writeData.defaultFS,
writerFileType: writeData.fileType,
writerPath: writeData.path,
writerFileName: writeData.fileName,
writeMode: writeData.writeMode,
writeFieldDelimiter: writeData.fieldDelimiter
}
const hbaseReader = {
readerMode: readerData.mode,
readerMaxVersion: readerData.maxVersion,
readerRange: readerData.range
}
const hbaseWriter = {
writerMode: writeData.mode,
writerRowkeyColumn: writeData.rowkeyColumn,
writerVersionColumn: writeData.versionColumn,
writeNullMode: writeData.nullMode
}
const mongoDBReader = {}
const mongoDBWriter = {
upsertInfo: writeData.upsertInfo
}
const rdbmsReader = {
readerSplitPk: readerData.splitPk,
whereParams: readerData.where,
querySql: readerData.querySql
}
const rdbmsWriter = {
preSql: writeData.preSql,
postSql: writeData.postSql
}
const obj = {
readerDatasourceId: readerData.datasourceId,
readerTables: [readerData.tableName],
readerColumns: readerColumns,
writerDatasourceId: writeData.datasourceId,
writerTables: [writeData.tableName],
writerColumns: writerColumns,
hiveReader: hiveReader,
hiveWriter: hiveWriter,
rdbmsReader: rdbmsReader,
rdbmsWriter: rdbmsWriter,
hbaseReader: hbaseReader,
hbaseWriter: hbaseWriter,
mongoDBReader: mongoDBReader,
mongoDBWriter: mongoDBWriter
}
// 调api
dataxJsonApi.buildJobJson(obj).then(response => {
this.configJson = JSON.parse(response.data)
})
},
handleCopy(text, event) {
clip(this.configJson, event)
this.$message({
message: 'copy success',
type: 'success'
})
},
handleJobTemplateSelectDrawer() {
this.jobTemplateSelectDrawer = !this.jobTemplateSelectDrawer
if (this.jobTemplateSelectDrawer) {
this.fetchData()
this.getExecutor()
}
},
getReaderData() {
return this.$refs.reader.getData()
},
getExecutor() {
jobTemplate.getExecutorList().then(response => {
const { content } = response
this.executorList = content
})
},
fetchData() {
this.listLoading = true
jobTemplate.getList(this.listQuery).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
handleCurrentChange(val) {
this.temp = Object.assign({}, val)
this.temp.id = undefined
this.temp.jobDesc = this.getReaderData().tableName
this.$refs.jobTemplateSelectDrawer.closeDrawer()
this.jobTemplate = val.id + '(' + val.jobDesc + ')'
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,24 @@
<template>
<div class="app-container">
<FieldMapper ref="mapper" />
</div>
</template>
<script>
import FieldMapper from './components/fieldMapper'
export default {
name: 'Mapper',
components: { FieldMapper },
methods: {
sendColumns(fromColumnsList, toColumnsList) {
this.$refs.mapper.fromColumnsList = fromColumnsList
this.$refs.mapper.toColumnsList = toColumnsList
},
getLColumns() {
return this.$refs.mapper.getLColumns()
},
getRColumns() {
return this.$refs.mapper.getRColumns()
}
}
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<div class="app-container">
<RDBMSReader v-show="dataSource!=='hive' && dataSource!=='hbase' && dataSource!=='mongodb'" ref="rdbmsreader" @selectDataSource="showDataSource" />
<HiveReader v-show="dataSource==='hive'" ref="hivereader" @selectDataSource="showDataSource" />
<HBaseReader v-show="dataSource==='hbase'" ref="hbasereader" @selectDataSource="showDataSource" />
<MongoDBReader v-show="dataSource==='mongodb'" ref="mongodbreader" @selectDataSource="showDataSource" />
</div>
</template>
<script>
import RDBMSReader from './reader/RDBMSReader'
import HiveReader from './reader/HiveReader'
import HBaseReader from './reader/HBaseReader'
import MongoDBReader from './reader/MongoDBReader'
export default {
name: 'Reader',
components: { RDBMSReader, HiveReader, HBaseReader, MongoDBReader },
data() {
return {
dataSource: ''
}
},
methods: {
getData() {
if (this.dataSource === 'hive') {
return this.$refs.hivereader.getData()
} else if (this.dataSource === 'hbase') {
return this.$refs.hbasereader.getData()
} else if (this.dataSource === 'mongodb') {
return this.$refs.mongodbreader.getData()
} else {
return this.$refs.rdbmsreader.getData()
}
},
showDataSource(data) {
this.dataSource = data
this.getData()
}
}
}
</script>

View File

@@ -0,0 +1,203 @@
<template>
<div class="app-container">
<el-form label-position="left" label-width="105px" :model="readerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select v-model="readerForm.datasourceId" filterable @change="rDsChange">
<el-option
v-for="item in rDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="表" prop="tableName">
<el-select v-model="readerForm.tableName" filterable @change="rTbChange">
<el-option v-for="item in rTbList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="mode" prop="mode">
<el-select v-model="readerForm.mode" placeholder="读取hbase的模式">
<el-option v-for="item in modeTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="maxVersion">
<el-input v-model="readerForm.maxVersion" placeholder="多版本模式下读取的版本数,取值只能为1或者大于1的数字" style="width: 50%" />
</el-form-item>
<el-form-item label="range">
<el-input v-model="readerForm.range.startRowkey" placeholder="startRowkey指定开始rowkey" style="width: 50%" />
</el-form-item>
<el-form-item>
<el-input v-model="readerForm.range.endRowkey" placeholder="endRowkey指定结束rowkey" style="width: 50%" />
</el-form-item>
<el-form-item>
<el-select v-model="readerForm.range.isBinaryRowkey" placeholder="转换方式">
<el-option v-for="item in binaryRowkeyTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="字段">
<el-checkbox
v-model="readerForm.checkAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.columns" @change="rHandleCheckedChange">
<el-checkbox v-for="c in rColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busReader'
export default {
name: 'HBaseReader',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200
},
rDsList: [],
rTbList: [],
rColumnList: [],
loading: false,
active: 1,
customFields: '',
customType: '',
customValue: '',
dataSource: '',
readerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true,
mode: '',
maxVersion: '',
range: {
startRowkey: '',
endRowkey: '',
isBinaryRowkey: ''
}
},
modeTypes: [
{ value: 'normal', label: 'normal' },
{ value: 'multiVersionFixedColumn', label: 'multiVersionFixedColumn' }
],
binaryRowkeyTypes: [
{ value: 'true', label: '调用Bytes.toBytesBinary(rowkey)' },
{ value: 'false', label: '调用Bytes.toBytes(rowkey)' }
],
rules: {
mode: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
tableName: [{ required: true, message: 'this is required', trigger: 'blur' }]
}
}
},
watch: {
'readerForm.datasourceId': function(oldVal, newVal) {
this.getTables('hbaseReader')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs() {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.rDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'hbaseReader') {
const obj = {
datasourceId: this.readerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.rTbList = response
})
}
},
// reader 数据源切换
rDsChange(e) {
// 清空
this.readerForm.tableName = ''
this.readerForm.datasourceId = e
this.rDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables('reader')
},
getTableColumns() {
const obj = {
datasourceId: this.readerForm.datasourceId,
tableName: this.readerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
getColumnsByQuerySql() {
const obj = {
datasourceId: this.readerForm.datasourceId,
querySql: this.readerForm.querySql
}
dsQueryApi.getColumnsByQuerySql(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
// 获取表字段
getColumns(type) {
if (type === 'reader') {
this.getTableColumns()
}
},
// 表切换
rTbChange(t) {
this.readerForm.tableName = t
this.rColumnList = []
this.readerForm.columns = []
this.getColumns('reader')
},
rHandleCheckAllChange(val) {
this.readerForm.columns = val ? this.rColumnList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.rColumnList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.rColumnList.length
},
getData() {
if (Bus.dataSourceId) {
this.readerForm.datasourceId = Bus.dataSourceId
}
return this.readerForm
}
}
}
</script>

View File

@@ -0,0 +1,206 @@
<template>
<div class="app-container">
<el-form label-position="left" label-width="105px" :model="readerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select v-model="readerForm.datasourceId" filterable @change="rDsChange">
<el-option
v-for="item in rDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="表" prop="tableName">
<el-select v-model="readerForm.tableName" filterable @change="rTbChange">
<el-option v-for="item in rTbList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="path" prop="path">
<el-input v-model="readerForm.path" :autosize="{ minRows: 2, maxRows: 20}" type="textarea" placeholder="要读取的文件路径,如果要读取多个文件,可以使用正则表达式'*'" style="width: 42%" />
</el-form-item>
<el-form-item label="defaultFS" prop="defaultFS">
<el-input v-model="readerForm.defaultFS" placeholder="Hadoop hdfs文件系统namenode节点地址" style="width: 42%" />
</el-form-item>
<el-form-item label="fileType" prop="fileType">
<el-select v-model="readerForm.fileType" placeholder="文件的类型">
<el-option v-for="item in fileTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="fieldDelimiter" prop="fieldDelimiter">
<el-input v-model="readerForm.fieldDelimiter" placeholder="读取的字段分隔符" style="width: 42%" />
</el-form-item>
<el-form-item label="skipHeader">
<el-select v-model="readerForm.skipHeader" placeholder="是否跳过表头">
<el-option v-for="item in skipHeaderTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="字段">
<el-checkbox
v-model="readerForm.checkAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.columns" @change="rHandleCheckedChange">
<el-checkbox v-for="c in rColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busReader'
export default {
name: 'HiveReader',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200
},
rDsList: [],
rTbList: [],
rColumnList: [],
loading: false,
active: 1,
customFields: '',
customType: '',
customValue: '',
dataSource: '',
readerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true,
path: '',
defaultFS: '',
fileType: '',
fieldDelimiter: '',
skipHeader: ''
},
rules: {
path: [{ required: true, message: 'this is required', trigger: 'blur' }],
defaultFS: [{ required: true, message: 'this is required', trigger: 'blur' }],
fileType: [{ required: true, message: 'this is required', trigger: 'change' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
tableName: [{ required: true, message: 'this is required', trigger: 'blur' }]
},
fileTypes: [
{ value: 'text', label: 'text' },
{ value: 'orc', label: 'orc' },
{ value: 'rc', label: 'rc' },
{ value: 'seq', label: 'seq' },
{ value: 'csv', label: 'csv' }
],
skipHeaderTypes: [
{ value: 'true', label: '读取跳过表头' },
{ value: 'false', label: '读取包含表头' }
]
}
},
watch: {
'readerForm.datasourceId': function(oldVal, newVal) {
this.getTables('hiveReader')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs(type) {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.rDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'hiveReader') {
const obj = {
datasourceId: this.readerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.rTbList = response
})
}
},
// reader 数据源切换
rDsChange(e) {
// 清空
this.readerForm.tableName = ''
this.readerForm.datasourceId = e
this.rDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables('reader')
},
getTableColumns() {
const obj = {
datasourceId: this.readerForm.datasourceId,
tableName: this.readerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
getColumnsByQuerySql() {
const obj = {
datasourceId: this.readerForm.datasourceId,
querySql: this.readerForm.querySql
}
dsQueryApi.getColumnsByQuerySql(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
// 获取表字段
getColumns(type) {
if (type === 'reader') {
this.getTableColumns()
}
},
// 表切换
rTbChange(t) {
this.readerForm.tableName = t
this.rColumnList = []
this.readerForm.columns = []
this.getColumns('reader')
},
rHandleCheckAllChange(val) {
this.readerForm.columns = val ? this.rColumnList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.rColumnList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.rColumnList.length
},
getData() {
if (Bus.dataSourceId) {
this.readerForm.datasourceId = Bus.dataSourceId
}
return this.readerForm
}
}
}
</script>

View File

@@ -0,0 +1,169 @@
<template>
<div class="app-container">
<el-form label-position="left" label-width="105px" :model="readerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select v-model="readerForm.datasourceId" filterable @change="rDsChange">
<el-option
v-for="item in rDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="文档" prop="tableName">
<el-select v-model="readerForm.tableName" filterable @change="rTbChange">
<el-option v-for="item in rTbList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="字段">
<el-checkbox
v-model="readerForm.checkAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.columns" @change="rHandleCheckedChange">
<el-checkbox v-for="c in rColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busReader'
export default {
name: 'MongoDBReader',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200
},
rDsList: [],
rTbList: [],
rColumnList: [],
loading: false,
active: 1,
customFields: '',
customType: '',
customValue: '',
dataSource: '',
readerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true
},
rules: {
mode: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
tableName: [{ required: true, message: 'this is required', trigger: 'blur' }]
}
}
},
watch: {
'readerForm.datasourceId': function(oldVal, newVal) {
this.getTables('mongodbReader')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs() {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.rDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'mongodbReader') {
const obj = {
datasourceId: this.readerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.rTbList = response
})
}
},
// reader 数据源切换
rDsChange(e) {
// 清空
this.readerForm.tableName = ''
this.readerForm.datasourceId = e
this.rDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables('reader')
},
getTableColumns() {
const obj = {
datasourceId: this.readerForm.datasourceId,
tableName: this.readerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
getColumnsByQuerySql() {
const obj = {
datasourceId: this.readerForm.datasourceId,
querySql: this.readerForm.querySql
}
dsQueryApi.getColumnsByQuerySql(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
// 获取表字段
getColumns(type) {
if (type === 'reader') {
this.getTableColumns()
}
},
// 表切换
rTbChange(t) {
this.readerForm.tableName = t
this.rColumnList = []
this.readerForm.columns = []
this.getColumns('reader')
},
rHandleCheckAllChange(val) {
this.readerForm.columns = val ? this.rColumnList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.rColumnList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.rColumnList.length
},
getData() {
if (Bus.dataSourceId) {
this.readerForm.datasourceId = Bus.dataSourceId
}
return this.readerForm
}
}
}
</script>

View File

@@ -0,0 +1,226 @@
<template>
<div class="app-container">
<el-form label-position="right" label-width="120px" :model="readerForm" :rules="rules">
<el-form-item label="数据库源:" prop="datasourceId">
<el-select v-model="readerForm.datasourceId" filterable style="width: 300px" @change="rDsChange">
<el-option
v-for="item in rDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item v-show="dataSource==='postgresql' || dataSource==='oracle' || dataSource==='sqlserver' || dataSource==='hana'" label="Schema" prop="tableSchema">
<el-select v-model="readerForm.tableSchema" allow-create default-first-option filterable style="width: 300px" @change="schemaChange">
<el-option
v-for="item in schemaList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="数据库表名:" prop="tableName">
<el-select v-model="readerForm.tableName" allow-create default-first-option filterable style="width: 300px" @change="rTbChange">
<el-option v-for="item in rTbList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="SQL语句">
<el-input v-model="readerForm.querySql" :autosize="{ minRows: 3, maxRows: 20}" type="textarea" placeholder="sql查询一般用于多表关联查询时才用" style="width: 42%" />
<el-button type="primary" @click.prevent="getColumns('reader')">解析字段</el-button>
</el-form-item>
<el-form-item label="切分字段:">
<el-input v-model="readerForm.splitPk" placeholder="切分主键" style="width: 13%" />
</el-form-item>
<el-form-item label="表所有字段:">
<el-checkbox
v-model="readerForm.checkAll"
:indeterminate="readerForm.isIndeterminate"
@change="rHandleCheckAllChange"
>全选
</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="readerForm.columns" @change="rHandleCheckedChange">
<el-checkbox v-for="c in rColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="where条件" prop="where">
<el-input v-model="readerForm.where" placeholder="where条件不需要再加where" type="textarea" style="width: 42%" />
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busReader'
export default {
name: 'RDBMSReader',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200,
ascs: 'datasource_name'
},
rDsList: [],
rTbList: [],
schemaList: [],
rColumnList: [],
loading: false,
active: 1,
customFields: '',
customType: '',
customValue: '',
dataSource: '',
readerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
where: '',
querySql: '',
checkAll: false,
isIndeterminate: true,
splitPk: '',
tableSchema: ''
},
rules: {
datasourceId: [{ required: true, message: 'this is required', trigger: 'change' }],
tableName: [{ required: true, message: 'this is required', trigger: 'change' }],
tableSchema: [{ required: true, message: 'this is required', trigger: 'change' }]
}
}
},
watch: {
'readerForm.datasourceId': function(oldVal, newVal) {
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
this.getSchema()
} else {
this.getTables('rdbmsReader')
}
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs(type) {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response.data
this.rDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'rdbmsReader') {
let obj = {}
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
obj = {
datasourceId: this.readerForm.datasourceId,
tableSchema: this.readerForm.tableSchema
}
} else {
obj = {
datasourceId: this.readerForm.datasourceId
}
}
// 组装
dsQueryApi.getTables(obj).then(response => {
if (response) {
this.rTbList = response.data
}
})
}
},
getSchema() {
const obj = {
datasourceId: this.readerForm.datasourceId
}
dsQueryApi.getTableSchema(obj).then(response => {
this.schemaList = response
})
},
// schema 切换
schemaChange(e) {
this.readerForm.tableSchema = e
// 获取可用表
this.getTables('rdbmsReader')
},
// reader 数据源切换
rDsChange(e) {
// 清空
this.readerForm.tableName = ''
this.readerForm.datasourceId = e
this.rDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
},
getTableColumns() {
const obj = {
datasourceId: this.readerForm.datasourceId,
tableName: this.readerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.rColumnList = response.data
this.readerForm.columns = response.data
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
getColumnsByQuerySql() {
const obj = {
datasourceId: this.readerForm.datasourceId,
querySql: this.readerForm.querySql
}
dsQueryApi.getColumnsByQuerySql(obj).then(response => {
this.rColumnList = response
this.readerForm.columns = response
this.readerForm.checkAll = true
this.readerForm.isIndeterminate = false
})
},
// 获取表字段
getColumns(type) {
if (type === 'reader') {
if (this.readerForm.querySql !== '') {
this.getColumnsByQuerySql()
} else {
this.getTableColumns()
}
}
},
// 表切换
rTbChange(t) {
this.readerForm.tableName = t
this.rColumnList = []
this.readerForm.columns = []
this.getColumns('reader')
},
rHandleCheckAllChange(val) {
this.readerForm.columns = val ? this.rColumnList : []
this.readerForm.isIndeterminate = false
},
rHandleCheckedChange(value) {
const checkedCount = value.length
this.readerForm.checkAll = checkedCount === this.rColumnList.length
this.readerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.rColumnList.length
},
getData() {
if (Bus.dataSourceId) {
this.readerForm.datasourceId = Bus.dataSourceId
}
return this.readerForm
}
}
}
</script>

View File

@@ -0,0 +1,60 @@
<template>
<div class="app-container">
<RDBMSWriter v-show="dataSource!=='hive' && dataSource!=='hbase' && dataSource!=='mongodb'" ref="rdbmswriter" @selectDataSource="showDataSource" />
<HiveWriter v-show="dataSource==='hive'" ref="hivewriter" @selectDataSource="showDataSource" />
<HBaseWriter v-show="dataSource==='hbase'" ref="hbasewriter" @selectDataSource="showDataSource" />
<MongoDBWriter v-show="dataSource==='mongodb'" ref="mongodbwriter" @selectDataSource="showDataSource" />
</div>
</template>
<script>
import RDBMSWriter from './writer/RDBMSWriter'
import HiveWriter from './writer/HiveWriter'
import HBaseWriter from './writer/HBaseWriter'
import MongoDBWriter from './writer/MongoDBWriter'
export default {
name: 'Writer',
components: { RDBMSWriter, HiveWriter, HBaseWriter, MongoDBWriter },
data() {
return {
dataSource: ''
}
},
methods: {
getData() {
if (this.dataSource === 'hive') {
return this.$refs.hivewriter.getData()
} else if (this.dataSource === 'hbase') {
return this.$refs.hbasewriter.getData()
} else if (this.dataSource === 'mongodb') {
return this.$refs.mongodbwriter.getData()
} else {
return this.$refs.rdbmswriter.getData()
}
},
getTableName() {
if (this.dataSource === 'hive') {
return this.$refs.hivewriter.getTableName()
} else if (this.dataSource === 'hbase') {
return this.$refs.hbasewriter.getData()
} else if (this.dataSource === 'mongodb') {
return this.$refs.mongodbwriter.getData()
} else {
return this.$refs.rdbmswriter.getTableName()
}
},
getReaderData() {
return this.$parent.getReaderData()
},
showDataSource(data) {
this.dataSource = data
this.getData()
},
sendTableNameAndColumns(fromTableName, fromColumnList) {
this.$refs.hivewriter.fromTableName = fromTableName
this.$refs.hivewriter.fromColumnList = fromColumnList
}
}
}
</script>

View File

@@ -0,0 +1,223 @@
<template>
<div>
<el-form label-position="left" label-width="115px" :model="writerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select
v-model="writerForm.datasourceId"
filterable
@change="wDsChange"
>
<el-option
v-for="item in wDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="表" prop="fromTableName">
<el-select
v-model="fromTableName"
:disabled="writerForm.ifCreateTable"
filterable
@change="wTbChange"
>
<el-option
v-for="item in wTbList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="rowkeyColumn" prop="rowkeyColumn">
<el-input v-model="writerForm.rowkeyColumn" :autosize="{ minRows: 5, maxRows: 20}" type="textarea" style="width: 42%" />
</el-form-item>
<el-form-item label="versionColumn">
<el-input v-model="writerForm.versionColumn.index" placeholder="index指定对应reader端column的索引" style="width: 42%" />
</el-form-item>
<el-form-item>
<el-input v-model="writerForm.versionColumn.value" placeholder="value指定时间的值,long值" style="width: 42%" />
</el-form-item>
<el-form-item label="nullMode">
<el-select v-model="writerForm.nullMode" placeholder="null值转换方式">
<el-option v-for="item in nullModeTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="字段">
<el-checkbox v-model="writerForm.checkAll" :indeterminate="writerForm.isIndeterminate" @change="wHandleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="writerForm.columns" @change="wHandleCheckedChange">
<el-checkbox v-for="c in fromColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busWriter'
export default {
name: 'HBaseWriter',
data() {
const checkJson = (rule, value, callback) => {
if (!value) {
callback(new Error('不能为空'))
}
if (typeof value === 'string') {
try {
var obj = JSON.parse(value)
if (typeof obj !== 'object' || !obj) {
callback(new Error('JSON格式错误'))
}
if (!(obj instanceof Array)) {
callback(new Error('JSON必须为数组'))
}
} catch (e) {
callback(new Error('JSON格式错误'))
}
}
}
return {
jdbcDsQuery: {
current: 1,
size: 200
},
wDsList: [],
fromTableName: '',
fromColumnList: [],
wTbList: [],
dataSource: '',
writerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true,
ifCreateTable: false,
mode: 'normal',
rowkeyColumn: '[{\n' +
'\t"index": 0,\n' +
'\t"type": "string"\n' +
'}]',
versionColumn: {
index: '',
value: ''
},
nullMode: ''
},
nullModeTypes: [
{ value: 'skip', label: '不向hbase写这列' },
{ value: 'empty', label: '写入new byte [0]' }
],
rules: {
mode: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
fromTableName: [{ required: true, message: 'this is required', trigger: 'blur' }],
rowkeyColumn: [{ required: true, trigger: 'blur', validator: checkJson }]
},
readerForm: this.getReaderData()
}
},
watch: {
'writerForm.datasourceId': function(oldVal, newVal) {
this.getTables('hbaseWriter')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs(type) {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.wDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'hbaseWriter') {
const obj = {
datasourceId: this.writerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.wTbList = response
})
}
},
wDsChange(e) {
// 清空
this.writerForm.tableName = ''
this.writerForm.datasourceId = e
this.wDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables()
},
// 获取表字段
getColumns() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.writerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.fromColumnList = response
this.writerForm.columns = response
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
})
},
// 表切换
wTbChange(t) {
this.writerForm.tableName = t
this.fromColumnList = []
this.writerForm.columns = []
this.getColumns('writer')
},
wHandleCheckAllChange(val) {
this.writerForm.columns = val ? this.fromColumnList : []
this.writerForm.isIndeterminate = false
},
wHandleCheckedChange(value) {
const checkedCount = value.length
this.writerForm.checkAll = checkedCount === this.fromColumnList.length
this.writerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromColumnList.length
},
createTableCheckedChange(val) {
this.writerForm.tableName = val ? this.readerForm.tableName : ''
this.fromColumnList = this.readerForm.columns
this.writerForm.columns = this.readerForm.columns
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
},
getData() {
if (Bus.dataSourceId) {
this.writerForm.datasourceId = Bus.dataSourceId
}
return this.writerForm
},
getReaderData() {
return this.$parent.getReaderData()
},
getTableName() {
return this.fromTableName
}
}
}
</script>

View File

@@ -0,0 +1,240 @@
<template>
<div>
<el-form label-position="left" label-width="105px" :model="writerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select
v-model="writerForm.datasourceId"
filterable
@change="wDsChange"
>
<el-option
v-for="item in wDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="表" prop="fromTableName">
<el-select
v-model="fromTableName"
:disabled="writerForm.ifCreateTable"
filterable
@change="wTbChange"
>
<el-option
v-for="item in wTbList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<!--<el-col :span="6">
<el-form-item>
<el-button type="primary" :disabled="!this.fromTableName" @click="createTable()">一键生成目标表</el-button>
</el-form-item>
</el-col>-->
</el-row>
<el-form-item label="path" prop="path">
<el-input v-model="writerForm.path" :autosize="{ minRows: 2, maxRows: 20}" type="textarea" placeholder="为与hive表关联请填写hive表在hdfs上的存储路径" style="width: 42%" />
</el-form-item>
<el-form-item label="defaultFS" prop="defaultFS">
<el-input v-model="writerForm.defaultFS" placeholder="Hadoop hdfs文件系统namenode节点地址" style="width: 42%" />
</el-form-item>
<el-form-item label="fileName" prop="fileName">
<el-input v-model="writerForm.fileName" placeholder="HdfsWriter写入时的文件名" style="width: 42%" />
</el-form-item>
<el-form-item label="fileType" prop="fileType">
<el-select v-model="writerForm.fileType" placeholder="文件的类型">
<el-option v-for="item in fileTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="writeMode" prop="writeMode">
<el-select v-model="writerForm.writeMode" placeholder="文件的类型">
<el-option v-for="item in writeModes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="fieldDelimiter" prop="fieldDelimiter">
<el-input v-model="writerForm.fieldDelimiter" placeholder="与创建表的分隔符一致" style="width: 13%" />
</el-form-item>
<el-form-item label="字段">
<el-checkbox v-model="writerForm.checkAll" :indeterminate="writerForm.isIndeterminate" @change="wHandleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="writerForm.columns" @change="wHandleCheckedChange">
<el-checkbox v-for="c in fromColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busWriter'
export default {
name: 'HiveWriter',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200
},
wDsList: [],
fromTableName: '',
fromColumnList: [],
wTbList: [],
dataSource: '',
writerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true,
ifCreateTable: false,
defaultFS: '',
fileType: '',
path: '',
fileName: '',
writeMode: '',
fieldDelimiter: ''
},
rules: {
path: [{ required: true, message: 'this is required', trigger: 'blur' }],
defaultFS: [{ required: true, message: 'this is required', trigger: 'blur' }],
fileName: [{ required: true, message: 'this is required', trigger: 'blur' }],
fileType: [{ required: true, message: 'this is required', trigger: 'change' }],
writeMode: [{ required: true, message: 'this is required', trigger: 'change' }],
fieldDelimiter: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
fromTableName: [{ required: true, message: 'this is required', trigger: 'blur' }]
},
readerForm: this.getReaderData(),
fileTypes: [
{ value: 'text', label: 'text' },
{ value: 'orc', label: 'orc' }
],
writeModes: [
{ value: 'append', label: 'append 写入前不做任何处理' },
{ value: 'nonConflict', label: 'nonConflict 目录下有fileName前缀的文件直接报错' }
]
}
},
watch: {
'writerForm.datasourceId': function(oldVal, newVal) {
this.getTables('hiveWriter')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs(type) {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.wDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'hiveWriter') {
const obj = {
datasourceId: this.writerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.wTbList = response
})
}
},
wDsChange(e) {
// 清空
this.writerForm.tableName = ''
this.writerForm.datasourceId = e
this.wDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables()
},
// 获取表字段
getColumns() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.writerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.fromColumnList = response
this.writerForm.columns = response
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
})
},
// 表切换
wTbChange(t) {
this.writerForm.tableName = t
this.fromColumnList = []
this.writerForm.columns = []
this.getColumns('writer')
},
wHandleCheckAllChange(val) {
this.writerForm.columns = val ? this.fromColumnList : []
this.writerForm.isIndeterminate = false
},
wHandleCheckedChange(value) {
const checkedCount = value.length
this.writerForm.checkAll = checkedCount === this.fromColumnList.length
this.writerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromColumnList.length
},
createTableCheckedChange(val) {
this.writerForm.tableName = val ? this.readerForm.tableName : ''
this.fromColumnList = this.readerForm.columns
this.writerForm.columns = this.readerForm.columns
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
},
getData() {
if (Bus.dataSourceId) {
this.writerForm.datasourceId = Bus.dataSourceId
}
return this.writerForm
},
getReaderData() {
return this.$parent.getReaderData()
},
getTableName() {
return this.fromTableName
},
createTable() {
const tableName = this.fromTableName
const datasourceId = this.writerForm.datasourceId
const columns = this.fromColumnList
const jsonString = {}
jsonString['datasourceId'] = datasourceId
jsonString['tableName'] = tableName
jsonString['columns'] = columns
console.info(jsonString)
dsQueryApi.createTable(jsonString).then(response => {
this.$notify({
title: 'Success',
message: 'Create Table Successfully',
type: 'success',
duration: 2000
})
}).catch(() => console.log('promise catch err'))
}
}
}
</script>

View File

@@ -0,0 +1,192 @@
<template>
<div>
<el-form label-position="left" label-width="105px" :model="writerForm" :rules="rules">
<el-form-item label="数据源" prop="datasourceId">
<el-select
v-model="writerForm.datasourceId"
filterable
@change="wDsChange"
>
<el-option
v-for="item in wDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="表" prop="fromTableName">
<el-select
v-model="fromTableName"
:disabled="writerForm.ifCreateTable"
filterable
@change="wTbChange"
>
<el-option
v-for="item in wTbList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="更新信息">
<el-select v-model="writerForm.upsertInfo.isUpsert" placeholder="是否更新">
<el-option v-for="item in upsertType" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="writerForm.upsertInfo.upsertKey" placeholder="更新的业务主键" style="width: 42%" />
</el-form-item>
<el-form-item label="字段">
<el-checkbox v-model="writerForm.checkAll" :indeterminate="writerForm.isIndeterminate" @change="wHandleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="writerForm.columns" @change="wHandleCheckedChange">
<el-checkbox v-for="c in fromColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busWriter'
export default {
name: 'MongoDBWriter',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200
},
wDsList: [],
fromTableName: '',
fromColumnList: [],
wTbList: [],
dataSource: '',
writerForm: {
datasourceId: undefined,
columns: [],
tableName: '',
checkAll: false,
isIndeterminate: true,
ifCreateTable: false,
upsertInfo: {
isUpsert: '',
upsertKey: ''
}
},
upsertType: [
{ value: true, label: '针对相同的upsertKey做更新' },
{ value: false, label: '不做更新' }
],
rules: {
mode: [{ required: true, message: 'this is required', trigger: 'blur' }],
datasourceId: [{ required: true, message: 'this is required', trigger: 'blur' }],
fromTableName: [{ required: true, message: 'this is required', trigger: 'blur' }]
},
readerForm: this.getReaderData()
}
},
watch: {
'writerForm.datasourceId': function(oldVal, newVal) {
this.getTables('mongodbWriter')
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs(type) {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response
this.wDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'mongodbWriter') {
const obj = {
datasourceId: this.writerForm.datasourceId
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.wTbList = response
})
}
},
wDsChange(e) {
// 清空
this.writerForm.tableName = ''
this.writerForm.datasourceId = e
this.wDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
// 获取可用表
this.getTables()
},
// 获取表字段
getColumns() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.writerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.fromColumnList = response
this.writerForm.columns = response
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
})
},
// 表切换
wTbChange(t) {
this.writerForm.tableName = t
this.fromColumnList = []
this.writerForm.columns = []
this.getColumns('writer')
},
wHandleCheckAllChange(val) {
this.writerForm.columns = val ? this.fromColumnList : []
this.writerForm.isIndeterminate = false
},
wHandleCheckedChange(value) {
const checkedCount = value.length
this.writerForm.checkAll = checkedCount === this.fromColumnList.length
this.writerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromColumnList.length
},
createTableCheckedChange(val) {
this.writerForm.tableName = val ? this.readerForm.tableName : ''
this.fromColumnList = this.readerForm.columns
this.writerForm.columns = this.readerForm.columns
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
},
getData() {
if (Bus.dataSourceId) {
this.writerForm.datasourceId = Bus.dataSourceId
}
return this.writerForm
},
getReaderData() {
return this.$parent.getReaderData()
},
getTableName() {
return this.fromTableName
}
}
}
</script>

View File

@@ -0,0 +1,232 @@
<template>
<div>
<el-form label-position="right" label-width="150px" :model="writerForm" :rules="rules">
<el-form-item label="数据库源:" prop="datasourceId">
<el-select
v-model="writerForm.datasourceId"
filterable
style="width: 300px;"
@change="wDsChange"
>
<el-option
v-for="item in wDsList"
:key="item.id"
:label="item.datasourceName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item v-show="dataSource==='postgresql' || dataSource==='oracle' ||dataSource==='sqlserver' || dataSource==='hana'" label="Schema" prop="tableSchema">
<el-select v-model="writerForm.tableSchema" filterable style="width: 300px" @change="schemaChange">
<el-option
v-for="item in schemaList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="数据库表名:" prop="tableName">
<el-select
v-model="fromTableName"
allow-create
default-first-option
filterable
:disabled="writerForm.ifCreateTable"
style="width: 300px"
@change="wTbChange"
>
<el-option
v-for="item in wTbList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<el-input v-show="writerForm.ifCreateTable" v-model="writerForm.tableName" style="width: 200px;" :placeholder="readerForm.tableName" />
<!--<el-input v-model="createTableName" style="width: 195px" />
<el-button type="primary" @click="createTable">新增</el-button>-->
</el-form-item>
<div style="margin: 5px 0;" />
<el-form-item label="字段:">
<el-checkbox v-model="writerForm.checkAll" :indeterminate="writerForm.isIndeterminate" @change="wHandleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="writerForm.columns" @change="wHandleCheckedChange">
<el-checkbox v-for="c in fromColumnList" :key="c" :label="c">{{ c }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="前置sql语句">
<el-input v-model="writerForm.preSql" placeholder="前置sql在insert之前执行" type="textarea" style="width: 42%" />
</el-form-item>
<el-form-item label="postSql">
<el-input v-model="writerForm.postSql" placeholder="多个用;分隔" type="textarea" style="width: 42%" />
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as dsQueryApi from '@/api/dts/metadata-query'
import { list as jdbcDsList } from '@/api/dts/datax-jdbcDatasource'
import Bus from '../busWriter'
export default {
name: 'RDBMSWriter',
data() {
return {
jdbcDsQuery: {
current: 1,
size: 200,
ascs: 'datasource_name'
},
wDsList: [],
schemaList: [],
fromTableName: '',
fromColumnList: [],
wTbList: [],
dataSource: '',
createTableName: '',
writerForm: {
datasourceId: undefined,
tableName: '',
columns: [],
checkAll: false,
isIndeterminate: true,
preSql: '',
postSql: '',
ifCreateTable: false,
tableSchema: ''
},
readerForm: this.getReaderData(),
rules: {
datasourceId: [{ required: true, message: 'this is required', trigger: 'change' }],
tableName: [{ required: true, message: 'this is required', trigger: 'change' }],
tableSchema: [{ required: true, message: 'this is required', trigger: 'change' }]
}
}
},
watch: {
'writerForm.datasourceId': function(oldVal, newVal) {
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
this.getSchema()
} else {
this.getTables('rdbmsWriter')
}
}
},
created() {
this.getJdbcDs()
},
methods: {
// 获取可用数据源
getJdbcDs() {
this.loading = true
jdbcDsList(this.jdbcDsQuery).then(response => {
const { records } = response.data
this.wDsList = records
this.loading = false
})
},
// 获取表名
getTables(type) {
if (type === 'rdbmsWriter') {
let obj = {}
if (this.dataSource === 'postgresql' || this.dataSource === 'oracle' || this.dataSource === 'sqlserver' || this.dataSource === 'hana') {
obj = {
datasourceId: this.writerForm.datasourceId,
tableSchema: this.writerForm.tableSchema
}
} else {
obj = {
datasourceId: this.writerForm.datasourceId
}
}
// 组装
dsQueryApi.getTables(obj).then(response => {
this.wTbList = response.data
})
}
},
getSchema() {
const obj = {
datasourceId: this.writerForm.datasourceId
}
dsQueryApi.getTableSchema(obj).then(response => {
this.schemaList = response
})
},
// schema 切换
schemaChange(e) {
this.writerForm.tableSchema = e
// 获取可用表
this.getTables('rdbmsWriter')
},
wDsChange(e) {
// 清空
this.writerForm.tableName = ''
this.writerForm.datasourceId = e
this.wDsList.find((item) => {
if (item.id === e) {
this.dataSource = item.datasource
}
})
Bus.dataSourceId = e
this.$emit('selectDataSource', this.dataSource)
},
// 获取表字段
getColumns() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.writerForm.tableName
}
dsQueryApi.getColumns(obj).then(response => {
this.fromColumnList = response.data
this.writerForm.columns = response.data
this.writerForm.checkAll = true
this.writerForm.isIndeterminate = false
})
},
// 表切换
wTbChange(t) {
this.writerForm.tableName = t
this.fromColumnList = []
this.writerForm.columns = []
this.getColumns('writer')
},
wHandleCheckAllChange(val) {
this.writerForm.columns = val ? this.fromColumnList : []
this.writerForm.isIndeterminate = false
},
wHandleCheckedChange(value) {
const checkedCount = value.length
this.writerForm.checkAll = checkedCount === this.fromColumnList.length
this.writerForm.isIndeterminate = checkedCount > 0 && checkedCount < this.fromColumnList.length
},
getData() {
if (Bus.dataSourceId) {
this.writerForm.datasourceId = Bus.dataSourceId
}
return this.writerForm
},
getReaderData() {
return this.$parent.getReaderData()
},
getTableName() {
return this.fromTableName
},
createTable() {
const obj = {
datasourceId: this.writerForm.datasourceId,
tableName: this.createTableName
}
dsQueryApi.createTable(obj).then(response => {
this.$notify({
title: 'Success',
message: 'Create Table Successfully',
type: 'success',
duration: 2000
})
}).catch(() => console.log('promise catch err'))
}
}
}
</script>

View File

@@ -0,0 +1,174 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.registryKey" placeholder="执行器" style="width: 200px;" class="filter-item" />
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
Search
</el-button>
</div>
<div v-for="item in list" :key="item.id" class="container">
<p><span class="fl">执行器{{ item.registryKey }}</span><span class="fl">&nbsp;&nbsp;注册地址{{ item.registryValue }}</span><span class="fr">更新时间{{ item.updateTime }}</span></p>
<div :class="item.id + ' fl'" style="width: 30%;height: 300px" />
<div :class="item.id + ' fl'" style="width: 30%;height: 300px" />
<div :class="item.id + ' fl' + ' loadAverage'" style="width: 30%;height: 300px ">
<p class="title">平均负载</p>
<p class="number">{{ item.loadAverage >= 0 ? item.loadAverage : 0 }}</p>
</div>
</div>
</div>
</template>
<script>
import { getList } from '@/api/dts/datax-registry'
import waves from '@/directive/waves' // waves directive
export default {
name: 'Registry',
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
registryKey: undefined
},
dialogPluginVisible: false
}
},
created() {
this.fetchData()
},
mounted() {
},
methods: {
fetchData() {
this.listLoading = true
this.list = []
getList(this.listQuery).then(response => {
const { data } = response
const { total } = data.total
this.total = total
this.list = data.records
this.listLoading = false
this.$nextTick(function() {
if (this.list) {
for (let i = 0; i < this.list.length; i++) {
this.initEcharts(this.list[i])
}
}
})
})
},
initEcharts(data) {
const myChart1 = this.$echarts.init(document.getElementsByClassName(data.id)[0])
// 绘制图表
var option1 = {
title: {
text: 'cpu使用率',
subtext: '',
x: 'center'
},
tooltip: {
formatter: '{a} <br/>{b} : {c}%'
},
toolbox: {
feature: {
restore: {},
saveAsImage: {}
},
show: false
},
series: [{
name: 'cpu使用率',
type: 'gauge',
max: 100,
radius: '70%', // 半径
startAngle: 215, // 起始位置
endAngle: -35, // 终点位置
detail: {
formatter: '{value}%'
},
data: [{
value: data.cpuUsage,
name: ''
}]
}]
}
myChart1.setOption(option1)
const myChart2 = this.$echarts.init(document.getElementsByClassName(data.id)[1])
// 绘制图表
var option2 = {
title: {
text: '内存使用率',
subtext: '',
x: 'center'
},
tooltip: {
formatter: '{a} <br/>{b} : {c}%'
},
toolbox: {
feature: {
restore: {},
saveAsImage: {}
},
show: false
},
series: [{
name: '内存使用率',
type: 'gauge',
max: 100,
radius: '70%', // 半径
startAngle: 215, // 起始位置
endAngle: -35, // 终点位置
detail: {
formatter: '{value}%'
},
data: [{
value: data.memoryUsage,
name: ''
}]
}]
}
myChart2.setOption(option2)
}
}
}
</script>
<style lang="scss" scope>
.container{
overflow: hidden;
p{
font-size: 14px;color: #666;padding: 10px 0;
.fl{
float: left;
}
.fr{
float: right;
}
}
.loadAverage{
p{
text-align: center;
}
.title{
font-size: 18px;font-weight: bold;color: #333;padding: 5px 0;margin: 0;
}
.number{
font-size: 50px;color: #3d90d0
}
}
}
</style>

View File

@@ -0,0 +1,215 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.username" placeholder="资源名称" style="width: 200px;" class="filter-item" />
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
搜索
</el-button>
<el-button
class="filter-item"
style="margin-left: 10px;"
type="primary"
icon="el-icon-edit"
@click="handleCreate"
>
添加
</el-button>
<!-- <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">
reviewer
</el-checkbox> -->
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column align="center" label="序号" width="95">
<template slot-scope="scope">{{ scope.$index+1 }}</template>
</el-table-column>
<el-table-column label="资源名称" align="center">
<template slot-scope="scope">{{ scope.row.username }}</template>
</el-table-column>
<el-table-column label="资源地址" align="center">
<template slot-scope="scope">{{ scope.row.username }}</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="handleUpdate(row)">
编辑
</el-button>
<el-button v-if="row.status!=='deleted'" size="mini" type="danger" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="listQuery.current"
:limit.sync="listQuery.size"
@pagination="fetchData"
/>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form
ref="dataForm"
:rules="rules"
:model="temp"
label-position="right"
label-width="100px"
style="width: 400px; margin-left:50px;"
>
<el-form-item label="资源名称" prop="resourceName">
<el-input v-model="temp.resourceName" placeholder="资源名称" />
</el-form-item>
<el-form-item label="资源地址" prop="resourcePath">
<el-input v-model="temp.resourcePath" placeholder="资源地址" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as user from '@/api/dts/datax-user'
import waves from '@/directive/waves' // waves directive
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
export default {
name: 'User',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
current: 1,
size: 10,
username: undefined
},
roles: ['ROLE_USER', 'ROLE_ADMIN'],
dialogPluginVisible: false,
pluginData: [],
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
rules: {
role: [{ required: true, message: 'role is required', trigger: 'change' }],
username: [{ required: true, message: 'username is required', trigger: 'blur' }],
password: [{ required: false, message: 'password is required', trigger: 'blur' }]
},
temp: {
id: undefined,
role: '',
username: '',
password: '',
permission: ''
},
resetTemp() {
this.temp = this.$options.data().temp
}
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
user.getList(this.listQuery).then(response => {
const { content } = response
this.total = content.recordsTotal
this.list = content.data
this.listLoading = false
})
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
user.createUser(this.temp).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
user.updateUser(tempData).then(() => {
this.fetchData()
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleDelete(row) {
user.deleteUser(row.id).then(response => {
this.fetchData()
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
})
}
}
}
</script>