blob: 105f930248fd9a3c297e101d09ca5c720f0b4872 [file] [log] [blame]
<!--
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<template>
<div class="app-container">
<div class="createPost-container">
<el-form ref="postForm" :model="postForm" class="form-container">
<div class="createPost-main-container">
<el-row>
<el-col :span="12">
<div class="postInfo-container">
<el-row>
<el-col :span="8">
<el-form-item class="postInfo-container-item">
<el-select v-model="postForm.tenant" placeholder="select tenant" @change="getNamespacesList(postForm.tenant)">
<el-option v-for="(item,index) in tenantsListOptions" :key="item+index" :label="item" :value="item"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item class="postInfo-container-item">
<el-select v-model="postForm.namespace" placeholder="select namespace" @change="getSourcesList(postForm.tenant, postForm.namespace)">
<el-option v-for="(item,index) in namespacesListOptions" :key="item+index" :label="item" :value="item"/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</div>
</el-form>
</div>
<!-- <codeEditor :value="codeValue" :mode="codeMode"/> -->
<div>
<div class="filter-container">
<el-input :placeholder="$t('table.sources')" v-model="listQuery.topic" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
<el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.search') }}</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleUpdate">{{ $t('table.edit') }}</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleDelete">{{ $t('table.delete') }}</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="builtinSources">{{ $t('table.builtin') }}</el-button>
<!-- <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleTrigger">{{ $t('table.trigger') }}</el-button> -->
</div>
<el-row :gutter="8">
<el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 14}" :xl="{span: 14}" style="padding-right:8px;margin-bottom:30px;">
<el-table
v-loading="listLoading"
:key="tableKey"
:data="list"
border
fit
highlight-current-row
style="width: 100%;"
@row-click="getCurrentRow">
<el-table-column :label="$t('table.sources')" min-width="50px" align="center">
<template slot-scope="scope">
<span>{{ scope.row.source }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.stats')" min-width="40px" align="center">
<template slot-scope="scope">
<span class="link-type" @click="handleGet(scope.row)">{{ scope.row.stats }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.status')" min-width="40px" align="center">
<template slot-scope="scope">
<span class="link-type" @click="handleAction('status', scope.row)">{{ scope.row.status }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" align="center" width="250" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleAction('start', scope.row)">{{ $t('table.start') }}</el-button>
<el-button type="primary" size="mini" @click="handleAction('stop', scope.row)">{{ $t('table.stop') }}</el-button>
<el-button type="primary" size="mini" @click="handleAction('restart', scope.row)">{{ $t('table.restart') }}</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getSources" />
</el-col>
<el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 10}" :xl="{span: 10}" style="margin-bottom:30px;">
<jsonEditor :value="jsonValue"/>
</el-col>
</el-row>
<el-dialog :visible.sync="dialogFormVisible">
<el-form ref="temp" :rules="rules" :model="temp" label-position="left" label-width="100px" style="width: 400px; margin-left:50px;">
<div v-if="dialogStatus==='create'||dialogStatus==='update'">
<div v-if="dialogStatus==='create'">
<el-form-item label="name" prop="source">
<el-input v-model="temp.source"/>
</el-form-item>
</div>
<div v-if="dialogStatus==='update'">
<el-form-item label="name">
<span>{{ currentSource }}</span>
</el-form-item>
</div>
<el-form-item label="archive" prop="file">
<input type="file" @change="loadTextFromFile">
</el-form-item>
<el-form-item label="class" prop="className">
<el-input v-model="temp.className"/>
</el-form-item>
<el-form-item label="configFile" prop="sourceConfigFile">
<input type="file" @change="loadSourceConfigFile">
</el-form-item>
<el-form-item label="output" prop="outputTopic">
<el-select v-model="temp.outputTopic" placeholder="select topic" @focus="getTopicsList()">
<el-option v-for="(item,index) in outputTopicsListOptions" :key="item+index" :label="item" :value="item"/>
</el-select>
</el-form-item>
<el-form-item label="cpu" prop="cpu">
<el-input v-model="temp.cpu"/>
</el-form-item>
<el-form-item label="disk" prop="disk">
<el-input v-model="temp.disk"/>
</el-form-item>
<el-form-item label="ram" prop="ram">
<el-input v-model="temp.ram"/>
</el-form-item>
<el-form-item label="schema" prop="schemaInput">
<el-input v-model="temp.schemaInput"/>
</el-form-item>
<el-form-item label="deClassName" prop="deClassName">
<el-input v-model="temp.deClassName"/>
</el-form-item>
<el-form-item label="parallelism" prop="parallelism">
<el-input v-model="temp.parallelism"/>
</el-form-item>
<el-form-item label="schemaType" prop="schemaType">
<el-input v-model="temp.schemaType"/>
</el-form-item>
<el-form-item label="guarantees" prop="guarantees">
<el-select v-model="temp.guarantees" placeholder="select guarantees">
<el-option v-for="(item,index) in guanrateesListOptions" :key="item+index" :label="item" :value="index"/>
</el-select>
</el-form-item>
<el-form-item label="sourceConfig" prop="sourceConfig">
<el-input v-model="temp.sourceConfig"/>
</el-form-item>
</div>
<div v-if="dialogStatus==='start'||dialogStatus==='stop'||dialogStatus==='restart'||dialogStatus==='status'">
<el-form-item label="instanceId" prop="instanceId">
<el-select v-model="temp.instanceId" placeholder="select instanceId">
<el-option v-for="(item,index) in instancesListOptions" :key="item+index" :label="item" :value="item"/>
</el-select>
</el-form-item>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">{{ $t('table.cancel') }}</el-button>
<el-button type="primary" @click="handleOptions()">{{ $t('table.confirm') }}</el-button>
</div>
</el-dialog>
</div>
</div>
</template>
<script>
// import codeEditor from '@/components/CodeEditor'
import yaml from 'js-yaml'
import { fetchTenants } from '@/api/tenants'
import { fetchNamespaces } from '@/api/namespaces'
import jsonEditor from '@/components/JsonEditor'
import { validateEmpty } from '@/utils/validate'
import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
import {
fetchSources,
createSource,
updateSource,
getSourceStatus,
startSourceInstance,
stopSourceInstance,
restartSourceInstance,
deleteSource,
fetchSourcesStats,
getSourceStatusInstance,
getBuiltinSources
} from '@/api/sources'
import {
fetchTopics,
fetchPersistentPartitonsTopics,
fetchNonPersistentPartitonsTopics
} from '@/api/topics'
const defaultForm = {
tenant: '',
namespace: ''
}
export default {
name: 'Sources',
components: {
// codeEditor
jsonEditor,
Pagination
},
data() {
return {
codeValue: null,
postForm: Object.assign({}, defaultForm),
tenantsListOptions: [],
namespacesListOptions: [],
outputTopicsListOptions: [],
guanrateesListOptions: [],
instancesListOptions: [],
// codeMode: 'text/x-cython',
localList: [],
searchList: [],
tenant: '',
namespace: '',
jsonValue: {},
tableKey: 0,
total: 0,
listLoading: false,
dialogFormVisible: false,
list: null,
currentFile: '',
triggerFile: '',
currentTriggerFileName: '',
currentSource: '',
dialogStatus: '',
listQuery: {
topic: '',
page: 1,
limit: 10
},
temp: {
source: '',
instanceId: 0,
className: '',
cpu: '',
disk: '',
ram: '',
schemaInput: '',
parallelism: '',
schemaType: '',
currentFile: '',
currentFileName: '',
currentConfigFile: '',
currentConfigFileName: '',
sourceConfig: '',
triggerValue: '',
triggerInput: '',
deClassName: '',
guarantees: ''
},
rules: {
source: [{ required: true, message: 'sources name is required', trigger: 'blur' }],
className: [{ required: true, message: 'className is required', trigger: 'blur' }],
outputTopic: [{ required: true, message: 'outputTopic is required', trigger: 'blur' }]
}
}
},
mounted() {
this.guanrateesListOptions = ['ATLEAST_ONCE', 'ATMOST_ONCE', 'EFFECTIVELY_ONCE']
},
created() {
this.getSources()
this.getRemoteTenantsList()
},
methods: {
getSources() {
if (this.localList.length > 0) {
setTimeout(() => {
this.localPaging()
}, 0)
} else {
this.listLoading = true
if (this.postForm.tenant.length > 0) {
this.tenant = this.postForm.tenant
}
if (this.postForm.namespace.length > 0) {
this.namespace = this.postForm.namespace
}
if (this.tenant.length <= 0 || this.namespace.length <= 0) {
this.tenant = 'public'
this.namespace = 'default'
}
fetchSources(this.tenant, this.namespace).then(response => {
for (var i = 0; i < response.data.length; i++) {
this.localList.push({ 'source': response.data[i], 'stats': 'stats', 'status': 'status' })
}
this.total = this.localList.length
this.list = this.localList.slice((this.listQuery.page - 1) * this.listQuery.limit, this.listQuery.limit * this.listQuery.page)
setTimeout(() => {
this.listLoading = false
}, 1.5 * 1000)
})
}
},
localPaging() {
this.listLoading = true
if (!validateEmpty(this.listQuery.topic)) {
this.searchList = []
for (var i = 0; i < this.localList.length; i++) {
if (this.localList[i]['source'].indexOf(this.listQuery.topic) !== -1) {
this.searchList.push(this.localList[i])
}
}
this.total = this.searchList.length
this.list = this.searchList.slice((this.listQuery.page - 1) * this.listQuery.limit, this.listQuery.limit * this.listQuery.page)
} else {
this.total = this.localList.length
this.list = this.localList.slice((this.listQuery.page - 1) * this.listQuery.limit, this.listQuery.limit * this.listQuery.page)
}
this.listLoading = false
},
handleFilter(tenant, namespace) {
this.tenant = tenant
this.namespace = namespace
this.getSources()
},
handleCreate() {
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.resetTemp()
this.$nextTick(() => {
this.$refs['temp'].clearValidate()
})
},
getCurrentRow(item) {
this.currentSource = item.source
},
handleDelete() {
if (this.currentSource.length <= 0) {
this.$notify({
title: 'error',
message: 'Please select any one source in table',
type: 'error',
duration: 3000
})
return
}
deleteSource(this.tenant, this.namespace, this.currentSource).then(response => {
this.$notify({
title: 'success',
message: 'delete source success',
type: 'success',
duration: 2000
})
this.localList = []
this.getSources()
})
},
confirmCreateSource() {
this.$refs['temp'].validate((valid) => {
if (valid) {
const formData = this.prepareSourceParams(this.temp.source)
createSource(this.tenant, this.namespace, this.temp.source, formData).then(response => {
this.$notify({
title: 'success',
message: 'create source success',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
this.localList = []
this.getSources()
})
}
})
},
handleUpdate() {
this.resetTemp()
if (this.currentSource.length <= 0) {
this.$notify({
title: 'error',
message: 'Please select any one source in table',
type: 'error',
duration: 3000
})
return
}
this.dialogStatus = 'update'
this.dialogFormVisible = true
},
handleAction(action, row) {
this.dialogStatus = action
this.dialogFormVisible = true
this.instancesListOptions = []
this.currentSource = row.source
getSourceStatus(this.tenant, this.namespace, row.source).then(response => {
for (var i = 0; i < response.data.instances.length; i++) {
this.instancesListOptions.push(response.data.instances[i]['instanceId'])
}
})
},
handleOptions() {
switch (this.dialogStatus) {
case 'create':
this.confirmCreateSource()
break
case 'start':
this.confirmStart()
break
case 'stop':
this.confirmStop()
break
case 'restart':
this.confirmRestart()
break
case 'update':
this.confirmUpdate()
break
case 'status':
this.confirmStatus()
break
}
},
loadTextFromFile(ev) {
const file = ev.target.files[0]
console.log(ev.target)
console.log(ev.target.files)
this.temp.currentFileName = file.name
var reader = new FileReader()
reader.onload = e => {
this.temp.currentFile = e.target.result
}
reader.readAsArrayBuffer(file)
},
loadSourceConfigFile(ev) {
const file = ev.target.files[0]
this.temp.currentConfigFileName = file.name
var reader = new FileReader()
reader.onload = e => {
this.temp.currentConfigFile = e.target.result
}
reader.readAsText(file)
},
handleGet(row) {
fetchSourcesStats(this.tenant, this.namespace, row.source).then(response => {
this.jsonValue = response.data
})
},
// handleGetStatus(row) {
// getSourceStatus(this.tenant, this.namespace, row.source).then(response => {
// this.jsonValue = response.data
// })
// },
confirmStart() {
startSourceInstance(this.tenant, this.namespace, this.currentSource, this.temp.instanceId).then(response => {
this.$notify({
title: 'success',
message: 'start source success',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
})
},
confirmStop() {
stopSourceInstance(this.tenant, this.namespace, this.currentSource, this.temp.instanceId).then(response => {
this.$notify({
title: 'success',
message: 'stop source success',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
})
},
confirmRestart() {
restartSourceInstance(this.tenant, this.namespace, this.currentSource, this.temp.instanceId).then(response => {
this.$notify({
title: 'success',
message: 'restart source success',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
})
},
confirmStatus() {
getSourceStatusInstance(this.tenant, this.namespace, this.currentSource, this.temp.instanceId).then(response => {
this.jsonValue = response.data
this.$notify({
title: 'success',
message: 'get status source success',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
})
},
prepareSourceParams(sourceName) {
const formData = new FormData()
const sourceConfig = {
'topicName': 'my-topic',
'className': this.temp.className,
'tenant': this.tenant,
'namespace': this.namespace
}
if (sourceName.length > 0) {
sourceConfig['name'] = sourceName
}
const blob = new Blob([this.temp.currentFile], { type: 'application/octet-stream' })
sourceConfig['archive'] = this.temp.currentFileName
formData.append('data', blob, this.temp.currentFileName)
if (this.temp.schemaType.length > 0) {
sourceConfig['schemaType'] = this.temp.schemaType
}
if (this.temp.cpu.length > 0) {
sourceConfig['resources'] = { 'cpu': parseInt(this.temp.cpu) }
}
if (this.temp.disk.length > 0) {
sourceConfig['resources'] = { 'disk': this.temp.disk }
}
if (this.temp.ram.length > 0) {
sourceConfig['resources'] = { 'ram': this.temp.ram }
}
if (this.temp.parallelism.length > 0) {
sourceConfig['parallelism'] = parseInt(this.temp.parallelism)
}
if (this.temp.guarantees.length > 0) {
sourceConfig['processingGuarantees'] = this.temp.guarantees
}
if (this.temp.sourceConfig.length > 0) {
sourceConfig['configs'] = JSON.parse(this.temp.sourceConfig)
}
if (this.temp.currentConfigFile.length > 0) {
const yamlFile = yaml.load(this.temp.currentConfigFile)
const yamlJSON = JSON.parse(JSON.stringify(yamlFile))
sourceConfig['configs'] = yamlJSON['configs']
}
formData.append('sourceConfig', JSON.stringify(sourceConfig))
return formData
},
confirmUpdate() {
this.$refs['temp'].validate((valid) => {
if (valid) {
const formData = this.prepareSourceParams('')
updateSource(this.tenant, this.namespace, this.currentSource, formData).then(response => {
this.$notify({
title: 'success',
message: 'update source success',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
})
}
})
},
getSourcesList() {
this.getSources()
},
builtinSources() {
getBuiltinSources().then(response => {
this.jsonValue = response.data
})
},
getRemoteTenantsList() {
fetchTenants().then(response => {
if (!response.data) return
this.tenantsListOptions = response.data
})
},
getNamespacesList(tenant) {
this.tenant = tenant
fetchNamespaces(tenant, this.query).then(response => {
let namespace = []
for (var i = 0; i < response.data.length; i++) {
namespace = response.data[i].split('/')
// namespace.splice(1, namespace.length).join('/')
this.namespacesListOptions.push(namespace.splice(1, namespace.length).join('/'))
}
})
},
getTopicsList() {
this.outputTopicsListOptions = []
fetchPersistentPartitonsTopics(this.tenant, this.namespace).then(response => {
for (var i = 0; i < response.data.length; i++) {
this.outputTopicsListOptions.push(response.data[i])
}
})
fetchNonPersistentPartitonsTopics(this.tenant, this.namespace).then(response => {
for (var i = 0; i < response.data.length; i++) {
this.outputTopicsListOptions.push(response.data[i])
}
})
fetchTopics(this.tenant, this.namespace, this.listQuery).then(response => {
for (var i = 0; i < response.data.length; i++) {
this.outputTopicsListOptions.push(response.data[i])
}
})
},
resetTemp() {
this.temp = {
source: '',
instanceId: 0,
className: '',
cpu: '',
disk: '',
ram: '',
schemaInput: '',
parallelism: '',
schemaType: '',
currentFile: '',
currentFileName: '',
currentConfigFile: '',
currentConfigFileName: '',
sourceConfig: '',
triggerValue: '',
triggerInput: '',
deClassName: '',
guarantees: ''
}
}
}
}
</script>