Browse Source

Resource tree code merge (#2322)

* Change DOM label

* Change name to lowercase

* Limit customization file content to no more than 3000 lines

* dd branch flow node verification

* datax

* datax add custom

* Change normalize.scss import method and animation.scss license modification

* Resource tree code merge
pull/2/head
break60 4 years ago committed by GitHub
parent
commit
c52c92e015
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      dolphinscheduler-ui/package.json
  2. 90
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/flink.vue
  3. 93
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/mr.vue
  4. 50
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/python.vue
  5. 80
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/shell.vue
  6. 97
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/spark.vue
  7. 38
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/jsPlumbHandle.js
  8. 2
      dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/util.js
  9. 4
      dolphinscheduler-ui/src/js/conf/home/pages/dag/definitionDetails.vue
  10. 6
      dolphinscheduler-ui/src/js/conf/home/pages/dag/index.vue
  11. 1
      dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue
  12. 5
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/create/index.vue
  13. 144
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/createFolder/index.vue
  14. 131
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/createUdfFolder/index.vue
  15. 2
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/details/index.vue
  16. 8
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/edit/index.vue
  17. 14
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/list/_source/list.vue
  18. 2
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/list/index.vue
  19. 196
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subFile/index.vue
  20. 144
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subFileFolder/index.vue
  21. 251
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subdirectory/_source/list.vue
  22. 120
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subdirectory/_source/rename.vue
  23. 173
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subdirectory/index.vue
  24. 128
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/createUdfFolder/index.vue
  25. 90
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/function/_source/createUdf.vue
  26. 5
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/function/_source/list.vue
  27. 5
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/function/index.vue
  28. 17
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/resource/_source/list.vue
  29. 6
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/resource/index.vue
  30. 218
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/subUdfDirectory/_source/list.vue
  31. 120
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/subUdfDirectory/_source/rename.vue
  32. 174
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/subUdfDirectory/index.vue
  33. 128
      dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/subUdfFolder/index.vue
  34. 37
      dolphinscheduler-ui/src/js/conf/home/pages/security/pages/users/_source/list.vue
  35. 64
      dolphinscheduler-ui/src/js/conf/home/router/index.js
  36. 19
      dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js
  37. 1
      dolphinscheduler-ui/src/js/conf/home/store/dag/mutations.js
  38. 2
      dolphinscheduler-ui/src/js/conf/home/store/dag/state.js
  39. 21
      dolphinscheduler-ui/src/js/conf/home/store/resource/actions.js
  40. 39
      dolphinscheduler-ui/src/js/conf/home/store/security/actions.js
  41. 318
      dolphinscheduler-ui/src/js/module/components/fileUpdate/fileChildUpdate.vue
  42. 6
      dolphinscheduler-ui/src/js/module/components/fileUpdate/fileUpdate.vue
  43. 318
      dolphinscheduler-ui/src/js/module/components/fileUpdate/resourceChildUpdate.vue
  44. 18
      dolphinscheduler-ui/src/js/module/components/fileUpdate/udfUpdate.vue
  45. 82
      dolphinscheduler-ui/src/js/module/components/nav/nav.vue
  46. 4
      dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js
  47. 72
      dolphinscheduler-ui/src/js/module/components/transfer/resource.vue
  48. 13
      dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
  49. 13
      dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
  50. 6
      dolphinscheduler-ui/src/js/module/util/routerUtil.js
  51. 16
      dolphinscheduler-ui/src/sass/common/_mixin.scss
  52. 2
      dolphinscheduler-ui/src/sass/common/index.scss

1
dolphinscheduler-ui/package.json

@ -11,6 +11,7 @@
"build:release": "npm run clean && cross-env NODE_ENV=production PUBLIC_PATH=/dolphinscheduler/ui webpack --config ./build/webpack.config.release.js"
},
"dependencies": {
"@riophae/vue-treeselect": "^0.4.0",
"ans-ui": "1.1.7",
"axios": "^0.16.2",
"bootstrap": "3.3.7",

90
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/flink.vue

@ -48,19 +48,9 @@
<m-list-box>
<div slot="text">{{$t('Main jar package')}}</div>
<div slot="content">
<x-select
style="width: 100%;"
:placeholder="$t('Please enter main jar package')"
v-model="mainJar"
filterable
:disabled="isDetails">
<x-option
v-for="city in mainJarList"
:key="city.code"
:value="city.code"
:label="city.code">
</x-option>
</x-select>
<treeselect v-model="mainJar" :options="mainJarLists" :disable-branch-nodes="true" :normalizer="normalizer" :placeholder="$t('Please enter main jar package')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
</div>
</m-list-box>
<m-list-box>
@ -151,12 +141,9 @@
<m-list-box>
<div slot="text">{{$t('Resources')}}</div>
<div slot="content">
<m-resources
ref="refResources"
@on-resourcesData="_onResourcesData"
@on-cache-resourcesData="_onCacheResourcesData"
:resource-list="resourceList">
</m-resources>
<treeselect v-model="resourceList" :multiple="true" :options="mainJarList" :normalizer="normalizer" :placeholder="$t('Please select resources')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
</div>
</m-list-box>
<m-list-box>
@ -178,6 +165,8 @@
import mLocalParams from './_source/localParams'
import mListBox from './_source/listBox'
import mResources from './_source/resources'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import disabledState from '@/module/mixin/disabledState'
export default {
@ -189,6 +178,7 @@
// Master jar package
mainJar: null,
// Master jar package(List)
mainJarLists: [],
mainJarList: [],
// Deployment method
deployMode: 'cluster',
@ -215,7 +205,12 @@
// Program type
programType: 'SCALA',
// Program type(List)
programTypeList: [{ code: 'JAVA' }, { code: 'SCALA' }, { code: 'PYTHON' }]
programTypeList: [{ code: 'JAVA' }, { code: 'SCALA' }, { code: 'PYTHON' }],
normalizer(node) {
return {
label: node.name
}
}
}
},
props: {
@ -291,10 +286,6 @@
return false
}
if (!this.$refs.refResources._verifResources()) {
return false
}
// localParams Subcomponent verification
if (!this.$refs.refLocalParams._verifProp()) {
return false
@ -304,10 +295,12 @@
this.$emit('on-params', {
mainClass: this.mainClass,
mainJar: {
res: this.mainJar
id: this.mainJar
},
deployMode: this.deployMode,
resourceList: this.resourceList,
resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams,
slot: this.slot,
taskManager: this.taskManager,
@ -320,24 +313,12 @@
})
return true
},
/**
* get resources list
*/
_getResourcesList () {
return new Promise((resolve, reject) => {
let isJar = (alias) => {
return alias.substring(alias.lastIndexOf('.') + 1, alias.length) !== 'jar'
}
this.mainJarList = _.map(_.cloneDeep(this.store.state.dag.resourcesListS), v => {
return {
id: v.id,
code: v.alias,
disabled: isJar(v.alias)
}
})
resolve()
diGuiTree(item) { // Recursive convenience tree structure
item.forEach(item => {
item.children === '' || item.children === undefined || item.children === null || item.children.length === 0?        
delete item.children : this.diGuiTree(item.children);
})
}
},
},
watch: {
// Listening type
@ -356,10 +337,12 @@
return {
mainClass: this.mainClass,
mainJar: {
res: this.mainJar
id: this.mainJar
},
deployMode: this.deployMode,
resourceList: this.cacheResourceList,
resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams,
slot: this.slot,
taskManager: this.taskManager,
@ -373,13 +356,17 @@
}
},
created () {
this._getResourcesList().then(() => {
let item = this.store.state.dag.resourcesListS
let items = this.store.state.dag.resourcesListJar
this.diGuiTree(item)
this.diGuiTree(items)
this.mainJarList = item
this.mainJarLists = items
let o = this.backfillItem
// Non-null objects represent backfill
if (!_.isEmpty(o)) {
this.mainClass = o.params.mainClass || ''
this.mainJar = o.params.mainJar && o.params.mainJar.res ? o.params.mainJar.res : ''
this.mainJar = o.params.mainJar && o.params.mainJar.id ? o.params.mainJar.id : ''
this.deployMode = o.params.deployMode || ''
this.slot = o.params.slot || 1
this.taskManager = o.params.taskManager || '2'
@ -393,7 +380,9 @@
// backfill resourceList
let resourceList = o.params.resourceList || []
if (resourceList.length) {
this.resourceList = resourceList
this.resourceList = _.map(resourceList, v => {
return v.id
})
this.cacheResourceList = resourceList
}
@ -403,12 +392,11 @@
this.localParams = localParams
}
}
})
},
mounted () {
},
components: { mLocalParams, mListBox, mResources }
components: { mLocalParams, mListBox, mResources, Treeselect }
}
</script>

93
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/mr.vue

@ -44,19 +44,9 @@
<m-list-box>
<div slot="text">{{$t('Main jar package')}}</div>
<div slot="content">
<x-select
style="width: 100%;"
:placeholder="$t('Please enter main jar package')"
v-model="mainJar"
filterable
:disabled="isDetails">
<x-option
v-for="city in mainJarList"
:key="city.code"
:value="city.code"
:label="city.code">
</x-option>
</x-select>
<treeselect v-model="mainJar" :options="mainJarLists" :disable-branch-nodes="true" :normalizer="normalizer" :placeholder="$t('Please enter main jar package')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
</div>
</m-list-box>
<m-list-box>
@ -88,12 +78,9 @@
<m-list-box>
<div slot="text">{{$t('Resources')}}</div>
<div slot="content">
<m-resources
ref="refResources"
@on-resourcesData="_onResourcesData"
@on-cache-resourcesData="_onCacheResourcesData"
:resource-list="resourceList">
</m-resources>
<treeselect v-model="resourceList" :multiple="true" :options="mainJarList" :normalizer="normalizer" :placeholder="$t('Please select resources')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
</div>
</m-list-box>
<m-list-box>
@ -115,6 +102,8 @@
import mListBox from './_source/listBox'
import mResources from './_source/resources'
import mLocalParams from './_source/localParams'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import disabledState from '@/module/mixin/disabledState'
export default {
name: 'mr',
@ -125,6 +114,7 @@
// Master jar package
mainJar: null,
// Main jar package (List)
mainJarLists: [],
mainJarList: [],
// Resource(list)
resourceList: [],
@ -139,7 +129,12 @@
// Program type
programType: 'JAVA',
// Program type(List)
programTypeList: [{ code: 'JAVA' }, { code: 'PYTHON' }]
programTypeList: [{ code: 'JAVA' }, { code: 'PYTHON' }],
normalizer(node) {
return {
label: node.name
}
}
}
},
props: {
@ -165,6 +160,12 @@
_onCacheResourcesData (a) {
this.cacheResourceList = a
},
diGuiTree(item) { // Recursive convenience tree structure
item.forEach(item => {
item.children === '' || item.children === undefined || item.children === null || item.children.length === 0?        
delete item.children : this.diGuiTree(item.children);
})
},
/**
* verification
*/
@ -179,22 +180,19 @@
return false
}
if (!this.$refs.refResources._verifResources()) {
return false
}
// localParams Subcomponent verification
if (!this.$refs.refLocalParams._verifProp()) {
return false
}
// storage
this.$emit('on-params', {
mainClass: this.mainClass,
mainJar: {
res: this.mainJar
id: this.mainJar
},
resourceList: this.resourceList,
resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams,
mainArgs: this.mainArgs,
others: this.others,
@ -202,24 +200,7 @@
})
return true
},
/**
* Get resource data
*/
_getResourcesList () {
return new Promise((resolve, reject) => {
let isJar = (alias) => {
return alias.substring(alias.lastIndexOf('.') + 1, alias.length) !== 'jar'
}
this.mainJarList = _.map(_.cloneDeep(this.store.state.dag.resourcesListS), v => {
return {
id: v.id,
code: v.alias,
disabled: isJar(v.alias)
}
})
resolve()
})
}
},
watch: {
/**
@ -240,9 +221,11 @@
return {
mainClass: this.mainClass,
mainJar: {
res: this.mainJar
id: this.mainJar
},
resourceList: this.cacheResourceList,
resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams,
mainArgs: this.mainArgs,
others: this.others,
@ -251,13 +234,18 @@
}
},
created () {
this._getResourcesList().then(() => {
let item = this.store.state.dag.resourcesListS
let items = this.store.state.dag.resourcesListJar
this.diGuiTree(item)
this.diGuiTree(items)
this.mainJarList = item
this.mainJarLists = items
let o = this.backfillItem
// Non-null objects represent backfill
if (!_.isEmpty(o)) {
this.mainClass = o.params.mainClass || ''
this.mainJar = o.params.mainJar.res || ''
this.mainJar = o.params.mainJar.id || ''
this.mainArgs = o.params.mainArgs || ''
this.others = o.params.others
this.programType = o.params.programType || 'JAVA'
@ -265,7 +253,9 @@
// backfill resourceList
let resourceList = o.params.resourceList || []
if (resourceList.length) {
this.resourceList = resourceList
this.resourceList = _.map(resourceList, v => {
return v.id
})
this.cacheResourceList = resourceList
}
@ -275,12 +265,11 @@
this.localParams = localParams
}
}
})
},
mounted () {
},
components: { mLocalParams, mListBox, mResources }
components: { mLocalParams, mListBox, mResources, Treeselect }
}
</script>

50
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/python.vue

@ -108,10 +108,6 @@
return false
}
if (!this.$refs.refResources._verifResources()) {
return false
}
// localParams Subcomponent verification
if (!this.$refs.refLocalParams._verifProp()) {
return false
@ -119,7 +115,9 @@
// storage
this.$emit('on-params', {
resourceList: this.resourceList,
resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams,
rawScript: editor.getValue()
})
@ -129,8 +127,6 @@
* Processing code highlighting
*/
_handlerEditor () {
this._destroyEditor()
// editor
editor = codemirror('code-python-mirror', {
mode: 'python',
@ -145,45 +141,28 @@
}
}
this.changes = () => {
this._cacheParams()
}
// Monitor keyboard
editor.on('keypress', this.keypress)
editor.on('changes', this.changes)
editor.setValue(this.rawScript)
return editor
},
_cacheParams () {
this.$emit('on-cache-params', {
resourceList: this.cacheResourceList,
localParams: this.localParams,
rawScript: editor ? editor.getValue() : ''
});
},
_destroyEditor () {
if (editor) {
editor.toTextArea() // Uninstall
editor.off($('.code-python-mirror'), 'keypress', this.keypress)
editor.off($('.code-python-mirror'), 'changes', this.changes)
}
}
},
watch: {
//Watch the cacheParams
cacheParams (val) {
this._cacheParams()
this.$emit('on-cache-params', val);
}
},
computed: {
cacheParams () {
return {
resourceList: this.cacheResourceList,
localParams: this.localParams
resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams,
rawScript: editor ? editor.getValue() : ''
}
}
},
@ -197,7 +176,9 @@
// backfill resourceList
let resourceList = o.params.resourceList || []
if (resourceList.length) {
this.resourceList = resourceList
this.resourceList = _.map(resourceList, v => {
return v.id
})
this.cacheResourceList = resourceList
}
@ -214,11 +195,8 @@
}, 200)
},
destroyed () {
if (editor) {
editor.toTextArea() // Uninstall
editor.off($('.code-python-mirror'), 'keypress', this.keypress)
editor.off($('.code-python-mirror'), 'changes', this.changes)
}
editor.toTextArea() // Uninstall
editor.off($('.code-python-mirror'), 'keypress', this.keypress)
},
components: { mLocalParams, mListBox, mResources }
}

80
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/shell.vue

@ -32,6 +32,14 @@
</div>
</m-list-box>
<m-list-box>
<div slot="text">{{$t('Resources')}}</div>
<div slot="content">
<treeselect v-model="resourceList" :multiple="true" :options="options" :normalizer="normalizer" :placeholder="$t('Please select resources')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
</div>
</m-list-box>
<!-- <m-list-box>
<div slot="text">{{$t('Resources')}}</div>
<div slot="content">
<m-resources
@ -41,7 +49,7 @@
:resource-list="resourceList">
</m-resources>
</div>
</m-list-box>
</m-list-box> -->
<m-list-box>
<div slot="text">{{$t('Custom Parameters')}}</div>
<div slot="content">
@ -63,6 +71,8 @@
import mResources from './_source/resources'
import mLocalParams from './_source/localParams'
import disabledState from '@/module/mixin/disabledState'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import codemirror from '@/conf/home/pages/resource/pages/file/pages/_source/codemirror'
let editor
@ -78,7 +88,14 @@
// resource(list)
resourceList: [],
// Cache ResourceList
cacheResourceList: []
cacheResourceList: [],
// define options
options: [],
normalizer(node) {
return {
label: node.name
}
},
}
},
mixins: [disabledState],
@ -143,17 +160,19 @@
return false
}
if (!this.$refs.refResources._verifResources()) {
return false
}
// localParams Subcomponent verification
if (!this.$refs.refLocalParams._verifProp()) {
return false
}
// Process resourcelist
let dataProcessing= _.map(this.resourceList, v => {
return {
id: v
}
})
// storage
this.$emit('on-params', {
resourceList: this.resourceList,
resourceList: dataProcessing,
localParams: this.localParams,
rawScript: editor.getValue()
})
@ -163,8 +182,6 @@
* Processing code highlighting
*/
_handlerEditor () {
this._destroyEditor()
// editor
editor = codemirror('code-shell-mirror', {
mode: 'shell',
@ -179,51 +196,41 @@
}
}
this.changes = () => {
this._cacheParams()
}
// Monitor keyboard
editor.on('keypress', this.keypress)
editor.on('changes', this.changes)
editor.setValue(this.rawScript)
return editor
},
_cacheParams () {
this.$emit('on-cache-params', {
resourceList: this.cacheResourceList,
localParams: this.localParams,
rawScript: editor ? editor.getValue() : ''
});
},
_destroyEditor () {
if (editor) {
editor.toTextArea() // Uninstall
editor.off($('.code-sql-mirror'), 'keypress', this.keypress)
editor.off($('.code-sql-mirror'), 'changes', this.changes)
}
diGuiTree(item) { // Recursive convenience tree structure
item.forEach(item => {
item.children === '' || item.children === undefined || item.children === null || item.children.length === 0?        
delete item.children : this.diGuiTree(item.children);
})
}
},
watch: {
//Watch the cacheParams
cacheParams (val) {
this._cacheParams()
this.$emit('on-cache-params', val);
}
},
computed: {
cacheParams () {
return {
resourceList: this.cacheResourceList,
localParams: this.localParams
resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams,
rawScript: editor ? editor.getValue() : ''
}
}
},
created () {
let item = this.store.state.dag.resourcesListS
this.diGuiTree(item)
this.options = item
let o = this.backfillItem
// Non-null objects represent backfill
if (!_.isEmpty(o)) {
this.rawScript = o.params.rawScript || ''
@ -231,7 +238,9 @@
// backfill resourceList
let resourceList = o.params.resourceList || []
if (resourceList.length) {
this.resourceList = resourceList
this.resourceList = _.map(resourceList, v => {
return v.id
})
this.cacheResourceList = resourceList
}
@ -251,10 +260,9 @@
if (editor) {
editor.toTextArea() // Uninstall
editor.off($('.code-shell-mirror'), 'keypress', this.keypress)
editor.off($('.code-shell-mirror'), 'changes', this.changes)
}
},
components: { mLocalParams, mListBox, mResources, mScriptBox }
components: { mLocalParams, mListBox, mResources, mScriptBox, Treeselect }
}
</script>
<style lang="scss" rel="stylesheet/scss" scope>

97
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/spark.vue

@ -63,19 +63,9 @@
<m-list-box>
<div slot="text">{{$t('Main jar package')}}</div>
<div slot="content">
<x-select
style="width: 100%;"
:placeholder="$t('Please enter main jar package')"
v-model="mainJar"
filterable
:disabled="isDetails">
<x-option
v-for="city in mainJarList"
:key="city.code"
:value="city.code"
:label="city.code">
</x-option>
</x-select>
<treeselect v-model="mainJar" :options="mainJarLists" :disable-branch-nodes="true" :normalizer="normalizer" :placeholder="$t('Please enter main jar package')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
</div>
</m-list-box>
<m-list-box>
@ -177,6 +167,14 @@
</div>
</m-list-box>
<m-list-box>
<div slot="text">{{$t('Resources')}}</div>
<div slot="content">
<treeselect v-model="resourceList" :multiple="true" :options="mainJarList" :normalizer="normalizer" :placeholder="$t('Please select resources')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
</div>
</m-list-box>
<!-- <m-list-box>
<div slot="text">{{$t('Resources')}}</div>
<div slot="content">
<m-resources
@ -186,7 +184,7 @@
:resource-list="resourceList">
</m-resources>
</div>
</m-list-box>
</m-list-box> -->
<m-list-box>
<div slot="text">{{$t('Custom Parameters')}}</div>
<div slot="content">
@ -206,6 +204,8 @@
import mLocalParams from './_source/localParams'
import mListBox from './_source/listBox'
import mResources from './_source/resources'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import disabledState from '@/module/mixin/disabledState'
export default {
@ -217,6 +217,7 @@
// Master jar package
mainJar: null,
// Master jar package(List)
mainJarLists: [],
mainJarList: [],
// Deployment method
deployMode: 'cluster',
@ -247,7 +248,12 @@
// Spark version
sparkVersion: 'SPARK2',
// Spark version(LIst)
sparkVersionList: [{ code: 'SPARK2' }, { code: 'SPARK1' }]
sparkVersionList: [{ code: 'SPARK2' }, { code: 'SPARK1' }],
normalizer(node) {
return {
label: node.name
}
}
}
},
props: {
@ -273,6 +279,12 @@
_onCacheResourcesData (a) {
this.cacheResourceList = a
},
diGuiTree(item) { // Recursive convenience tree structure
item.forEach(item => {
item.children === '' || item.children === undefined || item.children === null || item.children.length === 0?        
delete item.children : this.diGuiTree(item.children);
})
},
/**
* verification
*/
@ -321,24 +333,25 @@
this.$message.warning(`${i18n.$t('Core number should be positive integer')}`)
return false
}
if (!this.$refs.refResources._verifResources()) {
return false
}
// localParams Subcomponent verification
if (!this.$refs.refLocalParams._verifProp()) {
return false
}
// Process resourcelist
let dataProcessing= _.map(this.resourceList, v => {
return {
id: v
}
})
// storage
this.$emit('on-params', {
mainClass: this.mainClass,
mainJar: {
res: this.mainJar
id: this.mainJar
},
deployMode: this.deployMode,
resourceList: this.resourceList,
resourceList: dataProcessing,
localParams: this.localParams,
driverCores: this.driverCores,
driverMemory: this.driverMemory,
@ -351,24 +364,6 @@
sparkVersion: this.sparkVersion
})
return true
},
/**
* get resources list
*/
_getResourcesList () {
return new Promise((resolve, reject) => {
let isJar = (alias) => {
return alias.substring(alias.lastIndexOf('.') + 1, alias.length) !== 'jar'
}
this.mainJarList = _.map(_.cloneDeep(this.store.state.dag.resourcesListS), v => {
return {
id: v.id,
code: v.alias,
disabled: isJar(v.alias)
}
})
resolve()
})
}
},
watch: {
@ -388,10 +383,12 @@
return {
mainClass: this.mainClass,
mainJar: {
res: this.mainJar
id: this.mainJar
},
deployMode: this.deployMode,
resourceList: this.cacheResourceList,
resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams,
driverCores: this.driverCores,
driverMemory: this.driverMemory,
@ -406,13 +403,18 @@
}
},
created () {
this._getResourcesList().then(() => {
let item = this.store.state.dag.resourcesListS
let items = this.store.state.dag.resourcesListJar
this.diGuiTree(item)
this.diGuiTree(items)
this.mainJarList = item
this.mainJarLists = items
let o = this.backfillItem
// Non-null objects represent backfill
if (!_.isEmpty(o)) {
this.mainClass = o.params.mainClass || ''
this.mainJar = o.params.mainJar && o.params.mainJar.res ? o.params.mainJar.res : ''
this.mainJar = o.params.mainJar && o.params.mainJar.id ? o.params.mainJar.id : ''
this.deployMode = o.params.deployMode || ''
this.driverCores = o.params.driverCores || 1
this.driverMemory = o.params.driverMemory || '512M'
@ -427,7 +429,9 @@
// backfill resourceList
let resourceList = o.params.resourceList || []
if (resourceList.length) {
this.resourceList = resourceList
this.resourceList = _.map(resourceList, v => {
return v.id
})
this.cacheResourceList = resourceList
}
@ -437,12 +441,11 @@
this.localParams = localParams
}
}
})
},
mounted () {
},
components: { mLocalParams, mListBox, mResources }
components: { mLocalParams, mListBox, mResources, Treeselect }
}
</script>

38
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/jsPlumbHandle.js

@ -68,7 +68,7 @@ JSP.prototype.init = function ({ dag, instance, options }) {
// Register jsplumb connection type and configuration
this.JspInstance.registerConnectionType('basic', {
anchor: 'Continuous',
connector: 'Straight' // Line type
connector: 'Bezier' // Line type
})
// Initial configuration
@ -236,7 +236,7 @@ JSP.prototype.initNode = function (el) {
filter: '.ep',
anchor: 'Continuous',
connectorStyle: {
stroke: '#555',
stroke: '#2d8cf0',
strokeWidth: 2,
outlineStroke: 'transparent',
outlineWidth: 4
@ -297,6 +297,7 @@ JSP.prototype.tasksContextmenu = function (event) {
if (isOne) {
// start run
$('#startRunning').on('click', () => {
let name = store.state.dag.name
let id = router.history.current.params.id
store.dispatch('dag/getStartCheck', { processDefinitionId: id }).then(res => {
let modal = Vue.$modal.dialog({
@ -317,7 +318,8 @@ JSP.prototype.tasksContextmenu = function (event) {
},
props: {
item: {
id: id
id: id,
name: name
},
startNodeList: $name,
sourceType: 'contextmenu'
@ -378,7 +380,7 @@ JSP.prototype.tasksClick = function (e) {
$('.w').removeClass('jtk-tasks-active')
$(e.currentTarget).addClass('jtk-tasks-active')
if ($connect) {
setSvgColor($connect, '#555')
setSvgColor($connect, '#2d8cf0')
this.selectedElement.connect = null
}
this.selectedElement.id = $(e.currentTarget).attr('id')
@ -437,19 +439,19 @@ JSP.prototype.handleEventPointer = function (is) {
isClick: is,
isAttachment: false
})
wDom.removeClass('jtk-ep')
if (!is) {
wDom.removeClass('jtk-tasks-active')
this.selectedElement = {}
_.map($('#canvas svg'), v => {
if ($(v).attr('class')) {
_.map($(v).find('path'), v1 => {
$(v1).attr('fill', '#555')
$(v1).attr('stroke', '#555')
})
}
})
}
// wDom.removeClass('jtk-ep')
// if (!is) {
// wDom.removeClass('jtk-tasks-active')
// this.selectedElement = {}
// _.map($('#canvas svg'), v => {
// if ($(v).attr('class')) {
// _.map($(v).find('path'), v1 => {
// $(v1).attr('fill', '#555')
// $(v1).attr('stroke', '#555')
// })
// }
// })
// }
}
/**
@ -764,7 +766,7 @@ JSP.prototype.jspBackfill = function ({ connects, locations, largeJson }) {
source: sourceId,
target: targetId,
type: 'basic',
paintStyle: { strokeWidth: 2, stroke: '#555' }
paintStyle: { strokeWidth: 2, stroke: '#2d8cf0' }
})
})
})

2
dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/plugIn/util.js

@ -100,7 +100,7 @@ const setSvgColor = (e, color) => {
// Traverse clear all colors
$('.jtk-connector').each((i, o) => {
_.map($(o)[0].childNodes, v => {
$(v).attr('fill', '#555').attr('stroke', '#555').attr('stroke-width', 2)
$(v).attr('fill', '#2d8cf0').attr('stroke', '#2d8cf0').attr('stroke-width', 2)
})
})

4
dolphinscheduler-ui/src/js/conf/home/pages/dag/definitionDetails.vue

@ -41,7 +41,7 @@
props: {},
methods: {
...mapMutations('dag', ['resetParams', 'setIsDetails']),
...mapActions('dag', ['getProcessList','getProjectList', 'getResourcesList', 'getProcessDetails']),
...mapActions('dag', ['getProcessList','getProjectList', 'getResourcesList', 'getProcessDetails','getResourcesListJar']),
...mapActions('security', ['getTenantList','getWorkerGroupsAll']),
/**
* init
@ -60,6 +60,8 @@
this.getProjectList(),
// get resource
this.getResourcesList(),
// get jar
this.getResourcesListJar(),
// get worker group list
this.getWorkerGroupsAll(),
this.getTenantList()

6
dolphinscheduler-ui/src/js/conf/home/pages/dag/index.vue

@ -40,7 +40,7 @@
props: {},
methods: {
...mapMutations('dag', ['resetParams']),
...mapActions('dag', ['getProcessList','getProjectList', 'getResourcesList']),
...mapActions('dag', ['getProcessList','getProjectList', 'getResourcesList','getResourcesListJar','getResourcesListJar']),
...mapActions('security', ['getTenantList','getWorkerGroupsAll']),
/**
* init
@ -55,8 +55,12 @@
this.getProcessList(),
// get project
this.getProjectList(),
// get jar
this.getResourcesListJar(),
// get resource
this.getResourcesList(),
// get jar
this.getResourcesListJar(),
// get worker group list
this.getWorkerGroupsAll(),
this.getTenantList()

1
dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue

@ -274,6 +274,7 @@
},
created () {
this.warningType = this.warningTypeList[0].id
this.workflowName = this.item.name
this._getReceiver()
},

5
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/create/index.vue

@ -96,6 +96,8 @@
description: '',
fileTypeList: filtTypeArr,
content: '',
pid: -1,
currentDir: '/',
spinnerLoading: false
}
},
@ -107,6 +109,8 @@
this.spinnerLoading = true
this.createResourceFile({
type: 'FILE',
pid: this.pid,
currentDir: this.currentDir,
fileName: this.fileName,
suffix: this.suffix,
description: this.description,
@ -136,6 +140,7 @@
this.$message.warning(`${i18n.$t('Resource content cannot exceed 3000 lines')}`)
return false
}
return true
},
/**

144
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/createFolder/index.vue

@ -0,0 +1,144 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
<m-list-construction :title="$t('Create folder')">
<template slot="content">
<div class="resource-create-model">
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Folder Name')}}</template>
<template slot="content">
<x-input
type="input"
v-model="name"
maxlength="60"
style="width: 300px;"
:placeholder="$t('Please enter name')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<!-- <m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Folder Format')}}</template>
<template slot="content">
<x-select v-model="type" style="width: 100px;">
<x-option
v-for="item in folderList"
:key="item.value"
:value="item.value"
:label="item.label">
</x-option>
</x-select>
</template>
</m-list-box-f> -->
<m-list-box-f>
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<x-input
type="textarea"
v-model="description"
style="width: 430px;"
:placeholder="$t('Please enter description')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">&nbsp;</template>
<template slot="content">
<div class="submit">
<x-button type="primary" shape="circle" :loading="spinnerLoading" @click="ok()">{{spinnerLoading ? 'Loading...' : $t('Create')}} </x-button>
<x-button type="text" @click="() => $router.push({name: 'file'})"> {{$t('Cancel')}} </x-button>
</div>
</template>
</m-list-box-f>
</div>
</template>
</m-list-construction>
</template>
<script>
import i18n from '@/module/i18n'
import { mapActions } from 'vuex'
import { folderList } from '../_source/common'
import { handlerSuffix } from '../details/_source/utils'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
import mSpin from '@/module/components/spin/spin'
import mConditions from '@/module/components/conditions/conditions'
import localStore from '@/module/util/localStorage'
import mListConstruction from '@/module/components/listConstruction/listConstruction'
export default {
name: 'resource-list-create-FILE',
data () {
return {
type: '',
name: '',
description: '',
folderList: folderList,
spinnerLoading: false
}
},
props: {},
methods: {
...mapActions('resource', ['createResourceFolder']),
ok () {
if (this._validation()) {
this.spinnerLoading = true
this.createResourceFolder({
type: 'FILE',
name: this.name,
currentDir: '/',
pid: -1,
description: this.description
}).then(res => {
this.$message.success(res.msg)
setTimeout(() => {
this.spinnerLoading = false
this.$router.push({ path: `/resource/file`})
}, 800)
}).catch(e => {
this.$message.error(e.msg || '')
this.spinnerLoading = false
})
}
},
_validation () {
if (!this.name) {
this.$message.warning(`${i18n.$t('Please enter resource folder name')}`)
return false
}
return true
},
},
watch: {},
created () {
},
mounted () {
this.$modal.destroy()
},
destroyed () {
},
computed: {},
components: { mListConstruction, mConditions, mSpin, mListBoxF }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.resource-create-model {
padding: 30px;
}
</style>

131
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/createUdfFolder/index.vue

@ -0,0 +1,131 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
<m-list-construction :title="$t('Create folder')">
<template slot="content">
<div class="resource-create-model">
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Folder Name')}}</template>
<template slot="content">
<x-input
type="input"
v-model="name"
maxlength="60"
style="width: 300px;"
:placeholder="$t('Please enter name')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<x-input
type="textarea"
v-model="description"
style="width: 430px;"
:placeholder="$t('Please enter description')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">&nbsp;</template>
<template slot="content">
<div class="submit">
<x-button type="primary" shape="circle" :loading="spinnerLoading" @click="ok()">{{spinnerLoading ? 'Loading...' : $t('Create')}} </x-button>
<x-button type="text" @click="() => $router.push({name: 'resource-udf'})"> {{$t('Cancel')}} </x-button>
</div>
</template>
</m-list-box-f>
</div>
</template>
</m-list-construction>
</template>
<script>
import i18n from '@/module/i18n'
import { mapActions } from 'vuex'
import { folderList } from '../_source/common'
import { handlerSuffix } from '../details/_source/utils'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
import mSpin from '@/module/components/spin/spin'
import mConditions from '@/module/components/conditions/conditions'
import localStore from '@/module/util/localStorage'
import mListConstruction from '@/module/components/listConstruction/listConstruction'
export default {
name: 'resource-list-create-udf',
data () {
return {
type: '',
name: '',
description: '',
folderList: folderList,
spinnerLoading: false
}
},
props: {},
methods: {
...mapActions('resource', ['createResourceFolder']),
ok () {
if (this._validation()) {
this.spinnerLoading = true
this.createResourceFolder({
type: 'UDF',
name: this.name,
currentDir: '/',
pid: -1,
description: this.description
}).then(res => {
this.$message.success(res.msg)
setTimeout(() => {
this.spinnerLoading = false
this.$router.push({ path: `/resource/udf/resource`})
}, 800)
}).catch(e => {
this.$message.error(e.msg || '')
this.spinnerLoading = false
})
}
},
_validation () {
if (!this.name) {
this.$message.warning(`${i18n.$t('Please enter resource folder name')}`)
return false
}
return true
},
},
watch: {},
created () {
},
mounted () {
this.$modal.destroy()
},
destroyed () {
},
computed: {},
components: { mListConstruction, mConditions, mSpin, mListBoxF }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.resource-create-model {
padding: 30px;
}
</style>

2
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/details/index.vue

@ -21,7 +21,7 @@
<h2>
<span>{{name}}</span>
<div class="down">
<em class="ans-icon-download" data-container="body" data-toggle="tooltip" :title="$t('Download Details')" @click="_downloadFile"></em>
<em class="ans-icon-download" style="font-size:20px" data-container="body" data-toggle="tooltip" :title="$t('Download Details')" @click="_downloadFile"></em>
<em>{{size}}</em>
</div>
</h2>

8
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/edit/index.vue

@ -44,8 +44,8 @@
</m-list-construction>
</template>
<script>
import _ from 'lodash'
import i18n from '@/module/i18n'
import _ from 'lodash'
import { mapActions } from 'vuex'
import { filtTypeArr } from '../_source/common'
import mNoType from '../details/_source/noType'
@ -80,8 +80,8 @@
...mapActions('resource', ['getViewResources', 'updateContent']),
ok () {
if (this._validation()) {
this.spinnerLoading = true
this.updateContent({
this.spinnerLoading = true
this.updateContent({
id: this.$route.params.id,
content: editor.getValue()
}).then(res => {
@ -104,7 +104,7 @@
return true
},
close () {
this.$router.push({ name: 'file' })
this.$router.go(-1)
},
_getViewResources () {
this.isLoading = true

14
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/list/_source/list.vue

@ -25,6 +25,9 @@
<th scope="col">
<span>{{$t('Name')}}</span>
</th>
<th scope="col">
<span>{{$t('Whether directory')}}</span>
</th>
<th scope="col">
<span>{{$t('File Name')}}</span>
</th>
@ -50,6 +53,9 @@
<a href="javascript:" class="links" @click="_go(item)">{{item.alias}}</a>
</span>
</td>
<td>
<span>{{item.directory? $t('Yes') : $t('No')}}</span>
</td>
<td><span class="ellipsis" v-tooltip.large.top.start.light="{text: item.fileName, maxWidth: '500px'}">{{item.fileName}}</span></td>
<td>
<span v-if="item.description" class="ellipsis" v-tooltip.large.top.start.light="{text: item.description, maxWidth: '500px'}">{{item.description}}</span>
@ -89,6 +95,7 @@
size="xsmall"
data-toggle="tooltip"
:title="$t('Download')"
:disabled="item.directory? true: false"
@click="_downloadFile(item)"
icon="ans-icon-download">
</x-button>
@ -148,7 +155,12 @@
},
_go (item) {
localStore.setItem('file', `${item.alias}|${item.size}`)
this.$router.push({ path: `/resource/file/list/${item.id}` })
if(item.directory) {
localStore.setItem('currentDir', `${item.fullName}`)
this.$router.push({ path: `/resource/file/subdirectory/${item.id}` })
} else {
this.$router.push({ path: `/resource/file/list/${item.id}` })
}
},
_downloadFile (item) {
downloadFile('/dolphinscheduler/resources/download', {

2
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/list/index.vue

@ -20,6 +20,7 @@
<m-conditions @on-conditions="_onConditions">
<template slot="button-group">
<x-button-group size="small" >
<x-button type="ghost" @click="() => $router.push({name: 'resource-file-createFolder'})">{{$t('Create folder')}}</x-button>
<x-button type="ghost" @click="() => $router.push({name: 'resource-file-create'})">{{$t('Create File')}}</x-button>
<x-button type="ghost" @click="_uploading">{{$t('Upload Files')}}</x-button>
</x-button-group>
@ -61,6 +62,7 @@
isLoading: false,
fileResourcesList: [],
searchParams: {
id: -1,
pageSize: 10,
pageNo: 1,
searchVal: '',

196
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subFile/index.vue

@ -0,0 +1,196 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
<m-list-construction :title="$t('Create File')">
<template slot="content">
<div class="resource-create-model">
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('File Name')}}</template>
<template slot="content">
<x-input
type="input"
v-model="fileName"
maxlength="60"
style="width: 300px;"
:placeholder="$t('Please enter name')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('File Format')}}</template>
<template slot="content">
<x-select v-model="suffix" style="width: 100px;" @on-change="_onChange">
<x-option
v-for="city in fileTypeList"
:key="city"
:value="city"
:label="city">
</x-option>
</x-select>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<x-input
type="textarea"
v-model="description"
style="width: 430px;"
:placeholder="$t('Please enter description')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('File Content')}}</template>
<template slot="content">
<textarea id="code-create-mirror" name="code-create-mirror"></textarea>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">&nbsp;</template>
<template slot="content">
<div class="submit">
<x-button type="primary" shape="circle" :loading="spinnerLoading" @click="ok()">{{spinnerLoading ? 'Loading...' : $t('Create')}} </x-button>
<x-button type="text" @click="() => $router.push({name: 'file'})"> {{$t('Cancel')}} </x-button>
</div>
</template>
</m-list-box-f>
</div>
</template>
</m-list-construction>
</template>
<script>
import i18n from '@/module/i18n'
import { mapActions } from 'vuex'
import { filtTypeArr } from '../_source/common'
import { handlerSuffix } from '../details/_source/utils'
import codemirror from '../_source/codemirror'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
import mSpin from '@/module/components/spin/spin'
import mConditions from '@/module/components/conditions/conditions'
import localStore from '@/module/util/localStorage'
import mListConstruction from '@/module/components/listConstruction/listConstruction'
let editor
export default {
name: 'resource-list-create-FILE',
data () {
return {
suffix: 'sh',
fileName: '',
description: '',
fileTypeList: filtTypeArr,
content: '',
pid: -1,
currentDir: '/',
spinnerLoading: false
}
},
props: {},
methods: {
...mapActions('resource', ['createResourceFile']),
ok () {
if (this._validation()) {
this.spinnerLoading = true
this.createResourceFile({
type: 'FILE',
pid: this.$route.params.id,
currentDir: localStore.getItem('currentDir'),
fileName: this.fileName,
suffix: this.suffix,
description: this.description,
content: editor.getValue()
}).then(res => {
this.$message.success(res.msg)
setTimeout(() => {
this.spinnerLoading = false
this.$router.push({ path: `/resource/file/subdirectory/${this.$route.params.id}`})
}, 800)
}).catch(e => {
this.$message.error(e.msg || '')
this.spinnerLoading = false
})
}
},
_validation () {
if (!this.fileName) {
this.$message.warning(`${i18n.$t('Please enter resource name')}`)
return false
}
if (!editor.getValue()) {
this.$message.warning(`${i18n.$t('Please enter the resource content')}`)
return false
}
if (editor.doc.size>3000) {
this.$message.warning(`${i18n.$t('Resource content cannot exceed 3000 lines')}`)
return false
}
return true
},
/**
* Processing code highlighting
*/
_handlerEditor () {
// editor
editor = codemirror('code-create-mirror', {
mode: 'shell',
readOnly: false
})
this.keypress = () => {
if (!editor.getOption('readOnly')) {
editor.showHint({
completeSingle: false
})
}
}
// Monitor keyboard
editor.on('keypress', this.keypress)
},
_onChange (val) {
editor.setOption('mode', handlerSuffix['.' + val.label])
}
},
watch: {},
created () {
},
mounted () {
this.$modal.destroy()
this._handlerEditor()
},
destroyed () {
editor.toTextArea() // uninstall
editor.off($('.code-create-mirror'), 'keypress', this.keypress)
},
computed: {},
components: { mListConstruction, mConditions, mSpin, mListBoxF }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.resource-create-model {
padding: 30px;
}
.CodeMirror {
border:1px solid #DDDEDD;
border-radius: 3px;
}
</style>

144
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subFileFolder/index.vue

@ -0,0 +1,144 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
<m-list-construction :title="$t('Create folder')">
<template slot="content">
<div class="resource-create-model">
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Folder Name')}}</template>
<template slot="content">
<x-input
type="input"
v-model="name"
maxlength="60"
style="width: 300px;"
:placeholder="$t('Please enter name')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<!-- <m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Folder Format')}}</template>
<template slot="content">
<x-select v-model="type" style="width: 100px;">
<x-option
v-for="item in folderList"
:key="item.value"
:value="item.value"
:label="item.label">
</x-option>
</x-select>
</template>
</m-list-box-f> -->
<m-list-box-f>
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<x-input
type="textarea"
v-model="description"
style="width: 430px;"
:placeholder="$t('Please enter description')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">&nbsp;</template>
<template slot="content">
<div class="submit">
<x-button type="primary" shape="circle" :loading="spinnerLoading" @click="ok()">{{spinnerLoading ? 'Loading...' : $t('Create')}} </x-button>
<x-button type="text" @click="() => $router.push({name: 'file'})"> {{$t('Cancel')}} </x-button>
</div>
</template>
</m-list-box-f>
</div>
</template>
</m-list-construction>
</template>
<script>
import i18n from '@/module/i18n'
import { mapActions } from 'vuex'
import { folderList } from '../_source/common'
import { handlerSuffix } from '../details/_source/utils'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
import mSpin from '@/module/components/spin/spin'
import mConditions from '@/module/components/conditions/conditions'
import localStore from '@/module/util/localStorage'
import mListConstruction from '@/module/components/listConstruction/listConstruction'
export default {
name: 'resource-list-create-FILE',
data () {
return {
type: '',
name: '',
description: '',
folderList: folderList,
spinnerLoading: false
}
},
props: {},
methods: {
...mapActions('resource', ['createResourceFolder']),
ok () {
if (this._validation()) {
this.spinnerLoading = true
this.createResourceFolder({
type: 'FILE',
name: this.name,
currentDir: localStore.getItem('currentDir'),
pid: this.$route.params.id,
description: this.description
}).then(res => {
this.$message.success(res.msg)
setTimeout(() => {
this.spinnerLoading = false
this.$router.push({ path: `/resource/file/subdirectory/${this.$route.params.id}`})
}, 800)
}).catch(e => {
this.$message.error(e.msg || '')
this.spinnerLoading = false
})
}
},
_validation () {
if (!this.name) {
this.$message.warning(`${i18n.$t('Please enter resource folder name')}`)
return false
}
return true
},
},
watch: {},
created () {
},
mounted () {
this.$modal.destroy()
},
destroyed () {
},
computed: {},
components: { mListConstruction, mConditions, mSpin, mListBoxF }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.resource-create-model {
padding: 30px;
}
</style>

251
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subdirectory/_source/list.vue

@ -0,0 +1,251 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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="list-model">
<div class="table-box">
<table class="fixed">
<tr>
<th scope="col">
<span>{{$t('#')}}</span>
</th>
<th scope="col">
<span>{{$t('Name')}}</span>
</th>
<th scope="col">
<span>{{$t('Whether directory')}}</span>
</th>
<th scope="col">
<span>{{$t('File Name')}}</span>
</th>
<th scope="col">
<span>{{$t('Description')}}</span>
</th>
<th scope="col" width="100">
<span>{{$t('Size')}}</span>
</th>
<th scope="col" width="140">
<span>{{$t('Update Time')}}</span>
</th>
<th scope="col" width="140">
<span>{{$t('Operation')}}</span>
</th>
</tr>
<tr v-for="(item, $index) in list" :key="item.id">
<td>
<span>{{parseInt(pageNo === 1 ? ($index + 1) : (($index + 1) + (pageSize * (pageNo - 1))))}}</span>
</td>
<td>
<span class="ellipsis" v-tooltip.large.top.start.light="{text: item.alias, maxWidth: '500px'}">
<a href="javascript:" class="links" @click="_go(item)">{{item.alias}}</a>
</span>
</td>
<td>
<span>{{item.directory? $t('Yes') : $t('No')}}</span>
</td>
<td><span class="ellipsis" v-tooltip.large.top.start.light="{text: item.fileName, maxWidth: '500px'}">{{item.fileName}}</span></td>
<td>
<span v-if="item.description" class="ellipsis" v-tooltip.large.top.start.light="{text: item.description, maxWidth: '500px'}">{{item.description}}</span>
<span v-else>-</span>
</td>
<td>
<span>{{_rtSize(item.size)}}</span>
</td>
<td>
<span v-if="item.updateTime">{{item.updateTime | formatDate}}</span>
<span v-else>-</span>
</td>
<td>
<x-button
type="info"
shape="circle"
size="xsmall"
data-toggle="tooltip"
:title="$t('Edit')"
:disabled="_rtDisb(item)"
@click="_edit(item,$index)"
icon="ans-icon-edit">
</x-button>
<x-button
type="info"
shape="circle"
size="xsmall"
icon="ans-icon-play"
data-toggle="tooltip"
:title="$t('Rename')"
@click="_rename(item,$index)">
</x-button>
<x-button
type="info"
shape="circle"
size="xsmall"
data-toggle="tooltip"
:title="$t('Download')"
:disabled="item.directory? true: false"
@click="_downloadFile(item)"
icon="ans-icon-download">
</x-button>
<x-poptip
:ref="'poptip-' + $index"
placement="bottom-end"
width="90">
<p>{{$t('Delete?')}}</p>
<div style="text-align: right; margin: 0;padding-top: 4px;">
<x-button type="text" size="xsmall" shape="circle" @click="_closeDelete($index)">{{$t('Cancel')}}</x-button>
<x-button type="primary" size="xsmall" shape="circle" @click="_delete(item,$index)">{{$t('Confirm')}}</x-button>
</div>
<template slot="reference">
<x-button
icon="ans-icon-trash"
type="error"
shape="circle"
size="xsmall"
data-toggle="tooltip"
:title="$t('delete')">
</x-button>
</template>
</x-poptip>
</td>
</tr>
</table>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import mRename from './rename'
import { mapActions } from 'vuex'
import { filtTypeArr } from '../../_source/common'
import { bytesToSize } from '@/module/util/util'
import { downloadFile } from '@/module/download'
import localStore from '@/module/util/localStorage'
export default {
name: 'file-manage-list',
data () {
return {
list: []
}
},
props: {
fileResourcesList: Array,
pageNo: Number,
pageSize: Number
},
methods: {
...mapActions('resource', ['deleteResource']),
_edit (item) {
localStore.setItem('file', `${item.alias}|${item.size}`)
this.$router.push({ path: `/resource/file/edit/${item.id}` })
},
_go (item) {
localStore.setItem('file', `${item.alias}|${item.size}`)
if(item.directory) {
localStore.setItem('currentDir', `${item.fullName}`)
this.$router.push({ path: `/resource/file/subdirectory/${item.id}` })
} else {
this.$router.push({ path: `/resource/file/list/${item.id}` })
}
},
_downloadFile (item) {
downloadFile('/dolphinscheduler/resources/download', {
id: item.id
})
},
_rtSize (val) {
return bytesToSize(parseInt(val))
},
_closeDelete (i) {
this.$refs[`poptip-${i}`][0].doClose()
},
_delete (item, i) {
this.deleteResource({
id: item.id
}).then(res => {
this.$refs[`poptip-${i}`][0].doClose()
this.$emit('on-update')
this.$message.success(res.msg)
}).catch(e => {
this.$refs[`poptip-${i}`][0].doClose()
this.$message.error(e.msg || '')
})
},
_rename (item, i) {
let self = this
let modal = this.$modal.dialog({
closable: false,
showMask: true,
escClose: true,
className: 'v-modal-custom',
transitionName: 'opacityp',
render (h) {
return h(mRename, {
on: {
onUpDate (item) {
self.$set(self.list, i, item)
modal.remove()
},
close () {
modal.remove()
}
},
props: {
item: item
}
})
}
})
},
_rtDisb ({ alias, size }) {
let i = alias.lastIndexOf('.')
let a = alias.substring(i, alias.length)
let flag = _.includes(filtTypeArr, _.trimStart(a, '.'))
if (flag && (size < 1000000)) {
flag = true
} else {
flag = false
}
return !flag
}
},
watch: {
fileResourcesList (a) {
this.list = []
setTimeout(() => {
this.list = a
})
},
// Listening for routing changes
// '$route': {
// deep: false,
// handler () {
// this.$emit('on-update',this.$route.params.id)
// }
// }
},
beforeRouteUpdate (to, from, next) {
next() // next
},
created () {
},
mounted () {
this.list = this.fileResourcesList
},
components: { }
}
</script>

120
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subdirectory/_source/rename.vue

@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
<m-popup :ok-text="$t('Rename')" :nameText="$t('Rename')" @ok="_ok" :asyn-loading="true">
<template slot="content">
<div class="resource-rename-model">
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Name')}}</template>
<template slot="content">
<x-input
type="input"
v-model="name"
:placeholder="$t('Please enter name')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<x-input
type="textarea"
v-model="description"
:placeholder="$t('Please enter description')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
</div>
</template>
</m-popup>
</template>
<script>
import i18n from '@/module/i18n'
import store from '@/conf/home/store'
import localStore from '@/module/util/localStorage'
import mPopup from '@/module/components/popup/popup'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
export default {
name: 'resource-file-rename',
data () {
return {
store,
description: '',
name: ''
}
},
props: {
item: Object
},
methods: {
_ok (fn) {
this._verification().then(res => {
if (this.name === this.item.alias) {
return new Promise((resolve,reject) => {
this.description === this.item.description ? reject({msg:'内容未修改'}) : resolve()
})
}else{
return this.store.dispatch('resource/resourceVerifyName', {
fullName: localStore.getItem('currentDir')+'/'+this.name,
type: 'FILE'
})
}
}).then(res => {
return this.store.dispatch('resource/resourceRename', {
name: this.name,
description: this.description,
id: this.item.id,
type: 'FILE'
})
}).then(res => {
this.$message.success(res.msg)
this.$emit('onUpDate', res.data)
fn()
}).catch(e => {
fn()
this.$message.error(e.msg || '')
})
},
_verification () {
return new Promise((resolve, reject) => {
if (!this.name) {
reject({ // eslint-disable-line
msg: `${i18n.$t('Please enter resource name')}`
})
} else {
resolve()
}
})
}
},
watch: {},
created () {
let item = this.item || {}
if (item) {
this.name = item.alias
this.description = item.description
}
},
mounted () {
},
components: { mPopup, mListBoxF }
}
</script>

173
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subdirectory/index.vue

@ -0,0 +1,173 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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="home-main list-construction-model">
<div class="content-title">
<a class="bread" style="padding-left: 15px;" @click="() => $router.push({path: `/resource/file`})">{{$t('File Manage')}}</a>
<a class="bread" v-for="(item,$index) in breadList" :key="$index" @click="_ckOperation($index)">{{'>'+item}}</a>
</div>
<div class="conditions-box">
<m-conditions @on-conditions="_onConditions">
<template slot="button-group">
<x-button-group size="small" >
<x-button type="ghost" @click="() => $router.push({path: `/resource/file/subFileFolder/${searchParams.id}`})">{{$t('Create folder')}}</x-button>
<x-button type="ghost" @click="() => $router.push({path: `/resource/file/subFile/${searchParams.id}`})">{{$t('Create File')}}</x-button>
<x-button type="ghost" @click="_uploading">{{$t('Upload Files')}}</x-button>
</x-button-group>
</template>
</m-conditions>
</div>
<div class="list-box">
<template v-if="fileResourcesList.length || total>0">
<m-list @on-update="_onUpdate" @on-updateList="_updateList" :file-resources-list="fileResourcesList" :page-no="searchParams.pageNo" :page-size="searchParams.pageSize">
</m-list>
<div class="page-box">
<x-page :current="parseInt(searchParams.pageNo)" :total="total" :page-size="searchParams.pageSize" show-elevator @on-change="_page" show-sizer :page-size-options="[10,30,50]" @on-size-change="_pageSize"></x-page>
</div>
</template>
<template v-if="!fileResourcesList.length && total<=0">
<m-no-data></m-no-data>
</template>
<m-spin :is-spin="isLoading">
</m-spin>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import { mapActions } from 'vuex'
import mList from './_source/list'
import localStore from '@/module/util/localStorage'
import mSpin from '@/module/components/spin/spin'
import { findComponentDownward } from '@/module/util/'
import mNoData from '@/module/components/noData/noData'
import listUrlParamHandle from '@/module/mixin/listUrlParamHandle'
import mConditions from '@/module/components/conditions/conditions'
import mListConstruction from '@/module/components/listConstruction/listConstruction'
export default {
name: 'resource-list-index-FILE',
data () {
return {
total: null,
isLoading: false,
fileResourcesList: [],
searchParams: {
id: this.$route.params.id,
pageSize: 10,
pageNo: 1,
searchVal: '',
type: 'FILE'
},
breadList: []
}
},
mixins: [listUrlParamHandle],
props: {},
methods: {
...mapActions('resource', ['getResourcesListP','getResourceId']),
/**
* File Upload
*/
_uploading () {
findComponentDownward(this.$root, 'roof-nav')._fileChildUpdate('FILE',this.searchParams.id)
},
_onConditions (o) {
this.searchParams = _.assign(this.searchParams, o)
this.searchParams.pageNo = 1
},
_page (val) {
this.searchParams.pageNo = val
},
_pageSize (val) {
this.searchParams.pageSize = val
},
_getList (flag) {
this.isLoading = !flag
this.getResourcesListP(this.searchParams).then(res => {
if(this.searchParams.pageNo>1 && res.totalList.length == 0) {
this.searchParams.pageNo = this.searchParams.pageNo -1
} else {
this.fileResourcesList = res.totalList
this.total = res.total
this.isLoading = false
}
}).catch(e => {
this.isLoading = false
})
},
_updateList (data) {
this.searchParams.id = data
this.searchParams.pageNo = 1
this.searchParams.searchVal = ''
this._debounceGET()
},
_onUpdate () {
this.searchParams.id = this.$route.params.id
this._debounceGET()
},
_ckOperation(index) {
let breadName =''
this.breadList.forEach((item, i) => {
if(i<=index) {
breadName = breadName+'/'+item
}
})
this.transferApi(breadName)
},
transferApi(api) {
this.getResourceId({
type: 'FILE',
fullName: api
}).then(res => {
localStore.setItem('currentDir', `${res.fullName}`)
this.$router.push({ path: `/resource/file/subdirectory/${res.id}` })
}).catch(e => {
this.$message.error(e.msg || '')
})
}
},
watch: {
// router
'$route' (a) {
// url no params get instance list
this.searchParams.pageNo = _.isEmpty(a.query) ? 1 : a.query.pageNo
this.searchParams.id = a.params.id
let dir = localStore.getItem('currentDir').split('/')
dir.shift()
this.breadList = dir
}
},
created () {},
mounted () {
let dir = localStore.getItem('currentDir').split('/')
dir.shift()
this.breadList = dir
this.$modal.destroy()
},
components: { mListConstruction, mConditions, mList, mSpin, mNoData }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.bread {
font-size: 22px;
padding-top: 10px;
color: #2a455b;
display: inline-block;
cursor: pointer;
}
</style>

128
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/createUdfFolder/index.vue

@ -0,0 +1,128 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
<m-list-construction :title="$t('Create folder')">
<template slot="content">
<div class="resource-create-model">
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Folder Name')}}</template>
<template slot="content">
<x-input
type="input"
v-model="name"
maxlength="60"
style="width: 300px;"
:placeholder="$t('Please enter name')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<x-input
type="textarea"
v-model="description"
style="width: 430px;"
:placeholder="$t('Please enter description')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">&nbsp;</template>
<template slot="content">
<div class="submit">
<x-button type="primary" shape="circle" :loading="spinnerLoading" @click="ok()">{{spinnerLoading ? 'Loading...' : $t('Create')}} </x-button>
<x-button type="text" @click="() => $router.push({name: 'resource-udf'})"> {{$t('Cancel')}} </x-button>
</div>
</template>
</m-list-box-f>
</div>
</template>
</m-list-construction>
</template>
<script>
import i18n from '@/module/i18n'
import { mapActions } from 'vuex'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
import mSpin from '@/module/components/spin/spin'
import mConditions from '@/module/components/conditions/conditions'
import localStore from '@/module/util/localStorage'
import mListConstruction from '@/module/components/listConstruction/listConstruction'
export default {
name: 'resource-list-create-udf',
data () {
return {
type: '',
name: '',
description: '',
spinnerLoading: false
}
},
props: {},
methods: {
...mapActions('resource', ['createResourceFolder']),
ok () {
if (this._validation()) {
this.spinnerLoading = true
this.createResourceFolder({
type: 'UDF',
name: this.name,
currentDir: '/',
pid: -1,
description: this.description
}).then(res => {
this.$message.success(res.msg)
setTimeout(() => {
this.spinnerLoading = false
this.$router.push({ path: `/resource/udf`})
}, 800)
}).catch(e => {
this.$message.error(e.msg || '')
this.spinnerLoading = false
})
}
},
_validation () {
if (!this.name) {
this.$message.warning(`${i18n.$t('Please enter resource folder name')}`)
return false
}
return true
},
},
watch: {},
created () {
},
mounted () {
this.$modal.destroy()
},
destroyed () {
},
computed: {},
components: { mListConstruction, mConditions, mSpin, mListBoxF }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.resource-create-model {
padding: 30px;
}
</style>

90
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/function/_source/createUdf.vue

@ -15,7 +15,7 @@
* limitations under the License.
*/
<template>
<m-popup :ok-text="item ? $t('Edit') : $t('Submit')" :nameText="item ? $t('Edit UDF Function') : $t('Create UDF Function')" @ok="_ok" ref="popup">
<m-popup style="width:800px" :ok-text="item ? $t('Edit') : $t('Submit')" :nameText="item ? $t('Edit UDF Function') : $t('Create UDF Function')" @ok="_ok" ref="popup">
<template slot="content">
<div class="udf-create-model">
<m-list-box-f>
@ -72,26 +72,25 @@
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('UDF Resources')}}</template>
<template slot="content">
<x-select
filterable
v-model="resourceId"
:disabled="isUpdate"
:add-title="true"
style="width: 261px">
<x-option
v-for="city in udfResourceList"
:key="city.id"
:value="city.id"
:label="city.alias">
</x-option>
</x-select>
<treeselect style="width:535px;float:left;" v-model="resourceId" :disable-branch-nodes="true" :options="udfResourceList" :disabled="isUpdate" :normalizer="normalizer" :placeholder="$t('Please select UDF resources directory')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
<x-button type="primary" @click="_toggleUpdate" :disabled="upDisabled">{{$t('Upload Resources')}}</x-button>
</template>
</m-list-box-f>
<m-list-box-f v-if="isUpdate">
<template slot="name"><strong>*</strong>{{$t('UDF resources directory')}}</template>
<template slot="content">
<treeselect style="width:535px;float:left;" v-model="pid" @select="selTree" :options="udfResourceDirList" :normalizer="normalizer" :placeholder="$t('Please select UDF resources directory')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
</template>
</m-list-box-f>
<m-list-box-f v-if="isUpdate">
<template slot="name">&nbsp;</template>
<template slot="content">
<m-udf-update
ref="assignment"
@on-update-present="_onUpdatePresent"
@on-update="_onUpdate">
</m-udf-update>
@ -115,6 +114,8 @@
import _ from 'lodash'
import i18n from '@/module/i18n'
import store from '@/conf/home/store'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import mPopup from '@/module/components/popup/popup'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
import mUdfUpdate from '@/module/components/fileUpdate/udfUpdate'
@ -130,10 +131,16 @@
argTypes: '',
database: '',
description: '',
resourceId: '',
resourceId: null,
pid: null,
udfResourceList: [],
isUpdate: false,
upDisabled: false
upDisabled: false,
normalizer(node) {
return {
label: node.name
}
},
}
},
props: {
@ -192,17 +199,54 @@
// disabled update
this.upDisabled = true
},
// selTree
selTree(node) {
this.$refs.assignment.receivedValue(node.id,node.fullName)
},
/**
* get udf resources
*/
_getUdfList () {
return new Promise((resolve, reject) => {
this.store.dispatch('resource/getResourcesList', { type: 'UDF' }).then(res => {
this.udfResourceList = res.data
let item = res.data
this.filterEmptyDirectory(item)
item = this.filterEmptyDirectory(item)
let item1 = _.cloneDeep(res.data)
this.diGuiTree(item)
this.diGuiTree(this.filterJarFile(item1))
this.udfResourceList = item
this.udfResourceDirList = item1
resolve()
})
})
},
// filterEmptyDirectory
filterEmptyDirectory(array) {
for (const item of array) {
if (item.children) {
this.filterEmptyDirectory(item.children)
}
}
return array.filter(n => ((/\.jar$/.test(n.name) && n.children.length==0) || (!/\.jar$/.test(n.name) && n.children.length>0)))
},
// filterJarFile
filterJarFile (array) {
for (const item of array) {
if (item.children) {
item.children = this.filterJarFile(item.children)
}
}
return array.filter(n => !/\.jar$/.test(n.name))
},
// diGuiTree
diGuiTree(item) { // Recursive convenience tree structure
item.forEach(item => {
item.children === '' || item.children === undefined || item.children === null || item.children.length === 0?        
delete item.children : this.diGuiTree(item.children);
})
},
/**
* Upload udf resources
*/
@ -257,8 +301,7 @@
})
}
},
watch: {
},
watch: {},
created () {
this._getUdfList().then(res => {
// edit
@ -271,13 +314,18 @@
this.description = this.item.description || ''
this.resourceId = this.item.resourceId
} else {
this.resourceId = this.udfResourceList.length && this.udfResourceList[0].id || ''
this.resourceId = null
}
})
},
mounted () {
},
components: { mPopup, mListBoxF, mUdfUpdate }
components: { mPopup, mListBoxF, mUdfUpdate, Treeselect }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.vue-treeselect__control {
height: 32px;
}
</style>

5
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/function/_source/list.vue

@ -43,7 +43,7 @@ v-ps<template>
<!-- <th scope="col">
<span>{{$t('Library Name')}}</span>
</th> -->
<th scope="col" width="140">
<th scope="col" width="150">
<span>{{$t('Update Time')}}</span>
</th>
<th scope="col" width="80">
@ -71,7 +71,8 @@ v-ps<template>
<span v-else>-</span>
</td>
<td>
<span>{{item.resourceName}}</span>
<span v-if="item.resourceName" class="ellipsis" v-tooltip.large.top.start.light="{text: item.resourceName, maxWidth: '500px'}">{{item.resourceName}}</span>
<span v-else>-</span>
</td>
<!-- <td>
<span>{{item.database || '-'}}</span>

5
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/function/index.vue

@ -19,7 +19,9 @@
<template slot="conditions">
<m-conditions @on-conditions="_onConditions">
<template slot="button-group">
<x-button type="ghost" @click="_create" size="small" >{{$t('Create UDF Function')}}</x-button>
<x-button-group size="small">
<x-button type="ghost" @click="_create">{{$t('Create UDF Function')}}</x-button>
</x-button-group>
</template>
</m-conditions>
</template>
@ -58,6 +60,7 @@
isLoading: false,
udfFuncList: [],
searchParams: {
id: -1,
pageSize: 10,
pageNo: 1,
searchVal: ''

17
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/resource/_source/list.vue

@ -25,6 +25,9 @@
<th scope="col">
<span>{{$t('UDF Resource Name')}}</span>
</th>
<th scope="col">
<span>{{$t('Whether directory')}}</span>
</th>
<th scope="col">
<span>{{$t('File Name')}}</span>
</th>
@ -50,9 +53,12 @@
</td>
<td>
<span class="ellipsis" v-tooltip.large.top.start.light="{text: item.alias, maxWidth: '500px'}">
<a href="javascript:" class="links" >{{item.alias}}</a>
<a href="javascript:" class="links" @click="_go(item)">{{item.alias}}</a>
</span>
</td>
<td>
<span>{{item.directory? $t('Yes') : $t('No')}}</span>
</td>
<td><span class="ellipsis" v-tooltip.large.top.start.light="{text: item.fileName, maxWidth: '500px'}">{{item.fileName}}</span></td>
<td>
<span>{{_rtSize(item.size)}}</span>
@ -85,6 +91,7 @@
size="xsmall"
data-toggle="tooltip"
:title="$t('Download')"
:disabled="item.directory? true: false"
icon="ans-icon-download"
@click="_downloadFile(item)">
</x-button>
@ -120,6 +127,7 @@
import mRename from './rename'
import { downloadFile } from '@/module/download'
import { bytesToSize } from '@/module/util/util'
import localStore from '@/module/util/localStorage'
export default {
name: 'udf-manage-list',
@ -140,6 +148,13 @@
id: item.id
})
},
_go (item) {
localStore.setItem('file', `${item.alias}|${item.size}`)
if(item.directory) {
localStore.setItem('currentDir', `${item.fullName}`)
this.$router.push({ path: `/resource/udf/subUdfDirectory/${item.id}` })
}
},
_rtSize (val) {
return bytesToSize(parseInt(val))
},

6
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/resource/index.vue

@ -19,7 +19,10 @@
<template slot="conditions">
<m-conditions @on-conditions="_onConditions">
<template slot="button-group">
<x-button type="ghost" size="small" @click="_uploading">{{$t('Upload UDF Resources')}}</x-button>
<x-button-group size="small">
<x-button type="ghost" @click="() => this.$router.push({name: 'resource-udf-createUdfFolder'})">{{$t('Create folder')}}</x-button>
<x-button type="ghost" size="small" @click="_uploading">{{$t('Upload UDF Resources')}}</x-button>
</x-button-group>
</template>
</m-conditions>
</template>
@ -58,6 +61,7 @@
isLoading: false,
udfResourcesList: [],
searchParams: {
id: -1,
pageSize: 10,
pageNo: 1,
searchVal: '',

218
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/subUdfDirectory/_source/list.vue

@ -0,0 +1,218 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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="list-model">
<div class="table-box">
<table class="fixed">
<tr>
<th scope="col">
<span>{{$t('#')}}</span>
</th>
<th scope="col">
<span>{{$t('UDF Resource Name')}}</span>
</th>
<th scope="col">
<span>{{$t('Whether directory')}}</span>
</th>
<th scope="col">
<span>{{$t('File Name')}}</span>
</th>
<th scope="col" width="80">
<span>{{$t('File Size')}}</span>
</th>
<th scope="col">
<span>{{$t('Description')}}</span>
</th>
<th scope="col" width="140">
<span>{{$t('Create Time')}}</span>
</th>
<th scope="col" width="140">
<span>{{$t('Update Time')}}</span>
</th>
<th scope="col" width="110">
<span>{{$t('Operation')}}</span>
</th>
</tr>
<tr v-for="(item, $index) in list" :key="$index">
<td>
<span>{{parseInt(pageNo === 1 ? ($index + 1) : (($index + 1) + (pageSize * (pageNo - 1))))}}</span>
</td>
<td>
<span class="ellipsis" v-tooltip.large.top.start.light="{text: item.alias, maxWidth: '500px'}">
<a href="javascript:" class="links" @click="_go(item)">{{item.alias}}</a>
</span>
</td>
<td>
<span>{{item.directory? $t('Yes') : $t('No')}}</span>
</td>
<td><span class="ellipsis" v-tooltip.large.top.start.light="{text: item.fileName, maxWidth: '500px'}">{{item.fileName}}</span></td>
<td>
<span>{{_rtSize(item.size)}}</span>
</td>
<td>
<span v-if="item.description" class="ellipsis" v-tooltip.large.top.start.light="{text: item.description, maxWidth: '500px'}">{{item.description}}</span>
<span v-else>-</span>
</td>
<td>
<span v-if="item.createTime">{{item.createTime | formatDate}}</span>
<span v-else>-</span>
</td>
<td>
<span v-if="item.updateTime">{{item.updateTime | formatDate}}</span>
<span v-else>-</span>
</td>
<td>
<x-button
type="info"
shape="circle"
size="xsmall"
icon="ans-icon-play"
data-toggle="tooltip"
:title="$t('Rename')"
@click="_rename(item,$index)">
</x-button>
<x-button
type="info"
shape="circle"
size="xsmall"
data-toggle="tooltip"
:title="$t('Download')"
:disabled="item.directory? true: false"
icon="ans-icon-download"
@click="_downloadFile(item)">
</x-button>
<x-poptip
:ref="'poptip-' + $index"
placement="bottom-end"
width="90">
<p>{{$t('Delete?')}}</p>
<div style="text-align: right; margin: 0;padding-top: 4px;">
<x-button type="text" size="xsmall" shape="circle" @click="_closeDelete($index)">{{$t('Cancel')}}</x-button>
<x-button type="primary" size="xsmall" shape="circle" @click="_delete(item,$index)">{{$t('Confirm')}}</x-button>
</div>
<template slot="reference">
<x-button
type="error"
shape="circle"
size="xsmall"
data-toggle="tooltip"
:title="$t('delete')"
icon="ans-icon-trash">
</x-button>
</template>
</x-poptip>
</td>
</tr>
</table>
</div>
</div>
</template>
<script>
import { mapActions } from 'vuex'
import mRename from './rename'
import { downloadFile } from '@/module/download'
import { bytesToSize } from '@/module/util/util'
import localStore from '@/module/util/localStorage'
export default {
name: 'udf-manage-list',
data () {
return {
list: []
}
},
props: {
udfResourcesList: Array,
pageNo: Number,
pageSize: Number
},
methods: {
...mapActions('resource', ['deleteResource']),
_downloadFile (item) {
downloadFile('/dolphinscheduler/resources/download', {
id: item.id
})
},
_go (item) {
localStore.setItem('file', `${item.alias}|${item.size}`)
if(item.directory) {
localStore.setItem('currentDir', `${item.fullName}`)
this.$router.push({ path: `/resource/udf/subUdfDirectory/${item.id}` })
}
},
_rtSize (val) {
return bytesToSize(parseInt(val))
},
_closeDelete (i) {
this.$refs[`poptip-${i}`][0].doClose()
},
_delete (item, i) {
this.deleteResource({
id: item.id
}).then(res => {
this.$refs[`poptip-${i}`][0].doClose()
this.$emit('on-update')
this.$message.success(res.msg)
}).catch(e => {
this.$refs[`poptip-${i}`][0].doClose()
this.$message.error(e.msg || '')
})
},
_rename (item, i) {
let self = this
let modal = this.$modal.dialog({
closable: false,
showMask: true,
escClose: true,
className: 'v-modal-custom',
transitionName: 'opacityp',
render (h) {
return h(mRename, {
on: {
onUpDate (item) {
self.$set(self.list, i, item)
modal.remove()
},
close () {
modal.remove()
}
},
props: {
item: item
}
})
}
})
}
},
watch: {
udfResourcesList (a) {
this.list = []
setTimeout(() => {
this.list = a
})
}
},
created () {
},
mounted () {
this.list = this.udfResourcesList
},
components: { }
}
</script>

120
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/subUdfDirectory/_source/rename.vue

@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
<m-popup :ok-text="$t('Rename')" :nameText="$t('Rename')" @ok="_ok" :asyn-loading="true">
<template slot="content">
<div class="resource-rename-model">
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Name')}}</template>
<template slot="content">
<x-input
type="input"
v-model="name"
maxlength="60"
:placeholder="$t('Please enter name')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<x-input
type="textarea"
v-model="description"
:placeholder="$t('Please enter description')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
</div>
</template>
</m-popup>
</template>
<script>
import i18n from '@/module/i18n'
import store from '@/conf/home/store'
import localStore from '@/module/util/localStorage'
import mPopup from '@/module/components/popup/popup'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
export default {
name: 'resource-udf-rename',
data () {
return {
store,
description: '',
name: ''
}
},
props: {
item: Object
},
methods: {
_ok (fn) {
this._verification().then(res => {
if (this.name === this.item.alias) {
return new Promise((resolve,reject) => {
this.description === this.item.description ? reject({msg:'内容未修改'}) : resolve()
})
}else{
return this.store.dispatch('resource/resourceVerifyName', {
fullName: localStore.getItem('currentDir')+'/'+this.name,
type: 'UDF'
})
}
}).then(res => {
return this.store.dispatch('resource/resourceRename', {
name: this.name,
description: this.description,
id: this.item.id,
type: 'UDF'
})
}).then(res => {
this.$message.success(res.msg)
this.$emit('onUpDate', res.data)
fn()
}).catch(e => {
fn()
this.$message.error(e.msg || '')
})
},
_verification () {
return new Promise((resolve, reject) => {
if (!this.name) {
reject({ // eslint-disable-line
msg: `${i18n.$t('Please enter resource name')}`
})
} else {
resolve()
}
})
}
},
watch: {},
created () {
let item = this.item || {}
if (item) {
this.name = item.alias
this.description = item.description
}
},
mounted () {
},
components: { mPopup, mListBoxF }
}
</script>

174
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/subUdfDirectory/index.vue

@ -0,0 +1,174 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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="home-main list-construction-model">
<div class="content-title">
<a class="bread" style="padding-left: 15px;" @click="() => $router.push({path: `/resource/udf`})">{{$t('UDF Resources')}}</a>
<a class="bread" v-for="(item,$index) in breadList" :key="$index" @click="_ckOperation($index)">{{'>'+item}}</a>
</div>
<div class="conditions-box">
<m-conditions @on-conditions="_onConditions">
<template slot="button-group">
<x-button-group size="small" >
<x-button type="ghost" @click="() => $router.push({name: 'resource-udf-subCreateUdfFolder'})">{{$t('Create folder')}}</x-button>
<x-button type="ghost" size="small" @click="_uploading">{{$t('Upload UDF Resources')}}</x-button>
</x-button-group>
</template>
</m-conditions>
</div>
<div class="list-box">
<template v-if="udfResourcesList.length || total>0">
<m-list @on-update="_onUpdate" :udf-resources-list="udfResourcesList" :page-no="searchParams.pageNo" :page-size="searchParams.pageSize">
</m-list>
<div class="page-box">
<x-page :current="parseInt(searchParams.pageNo)" :total="total" :page-size="searchParams.pageSize" show-elevator @on-change="_page" show-sizer :page-size-options="[10,30,50]" @on-size-change="_pageSize"></x-page>
</div>
</template>
<template v-if="!udfResourcesList.length && total<=0">
<m-no-data></m-no-data>
</template>
<m-spin :is-spin="isLoading">
</m-spin>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import { mapActions } from 'vuex'
import mList from './_source/list'
import localStore from '@/module/util/localStorage'
import mSpin from '@/module/components/spin/spin'
import { findComponentDownward } from '@/module/util/'
import mNoData from '@/module/components/noData/noData'
import listUrlParamHandle from '@/module/mixin/listUrlParamHandle'
import mConditions from '@/module/components/conditions/conditions'
import mListConstruction from '@/module/components/listConstruction/listConstruction'
export default {
name: 'resource-list-index-UDF',
data () {
return {
total: null,
isLoading: false,
udfResourcesList: [],
searchParams: {
id: this.$route.params.id,
pageSize: 10,
pageNo: 1,
searchVal: '',
type: 'UDF'
},
breadList: []
}
},
mixins: [listUrlParamHandle],
props: {},
methods: {
...mapActions('resource', ['getResourcesListP','getResourceId']),
/**
* File Upload
*/
_uploading () {
findComponentDownward(this.$root, 'roof-nav')._resourceChildUpdate('UDF',this.searchParams.id)
},
_onConditions (o) {
this.searchParams = _.assign(this.searchParams, o)
this.searchParams.pageNo = 1
},
_page (val) {
this.searchParams.pageNo = val
},
_pageSize (val) {
this.searchParams.pageSize = val
},
_onUpdate () {
this.searchParams.id = this.$route.params.id
this._debounceGET()
},
_updateList (data) {
this.searchParams.id = data
this.searchParams.pageNo = 1
this.searchParams.searchVal = ''
this._debounceGET()
},
_getList (flag) {
this.isLoading = !flag
this.getResourcesListP(this.searchParams).then(res => {
if(this.searchParams.pageNo>1 && res.totalList.length == 0) {
this.searchParams.pageNo = this.searchParams.pageNo -1
} else {
this.udfResourcesList = []
this.udfResourcesList = res.totalList
this.total = res.total
this.isLoading = false
}
}).catch(e => {
this.isLoading = false
})
},
_ckOperation(index) {
let breadName =''
this.breadList.forEach((item, i) => {
if(i<=index) {
breadName = breadName+'/'+item
}
})
this.transferApi(breadName)
},
transferApi(api) {
this.getResourceId({
type: 'UDF',
fullName: api
}).then(res => {
localStore.setItem('currentDir', `${res.fullName}`)
this.$router.push({ path: `/resource/udf/subUdfDirectory/${res.id}` })
}).catch(e => {
this.$message.error(e.msg || '')
})
}
},
watch: {
// router
'$route' (a) {
// url no params get instance list
this.searchParams.pageNo = _.isEmpty(a.query) ? 1 : a.query.pageNo
this.searchParams.id = a.params.id
let dir = localStore.getItem('currentDir').split('/')
dir.shift()
this.breadList = dir
}
},
created () {
},
mounted () {
let dir = localStore.getItem('currentDir').split('/')
dir.shift()
this.breadList = dir
this.$modal.destroy()
},
components: { mListConstruction, mConditions, mList, mSpin, mNoData }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.bread {
font-size: 22px;
padding-top: 10px;
color: #2a455b;
display: inline-block;
cursor: pointer;
}
</style>

128
dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/udf/pages/subUdfFolder/index.vue

@ -0,0 +1,128 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
<m-list-construction :title="$t('Create folder')">
<template slot="content">
<div class="resource-create-model">
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Folder Name')}}</template>
<template slot="content">
<x-input
type="input"
v-model="name"
maxlength="60"
style="width: 300px;"
:placeholder="$t('Please enter name')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<x-input
type="textarea"
v-model="description"
style="width: 430px;"
:placeholder="$t('Please enter description')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">&nbsp;</template>
<template slot="content">
<div class="submit">
<x-button type="primary" shape="circle" :loading="spinnerLoading" @click="ok()">{{spinnerLoading ? 'Loading...' : $t('Create')}} </x-button>
<x-button type="text" @click="() => $router.push({name: 'resource-udf-subUdfDirectory'})"> {{$t('Cancel')}} </x-button>
</div>
</template>
</m-list-box-f>
</div>
</template>
</m-list-construction>
</template>
<script>
import i18n from '@/module/i18n'
import { mapActions } from 'vuex'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
import mSpin from '@/module/components/spin/spin'
import mConditions from '@/module/components/conditions/conditions'
import localStore from '@/module/util/localStorage'
import mListConstruction from '@/module/components/listConstruction/listConstruction'
export default {
name: 'resource-list-create-FILE',
data () {
return {
type: '',
name: '',
description: '',
spinnerLoading: false
}
},
props: {},
methods: {
...mapActions('resource', ['createResourceFolder']),
ok () {
if (this._validation()) {
this.spinnerLoading = true
this.createResourceFolder({
type: 'UDF',
name: this.name,
currentDir: localStore.getItem('currentDir'),
pid: this.$route.params.id,
description: this.description
}).then(res => {
this.$message.success(res.msg)
setTimeout(() => {
this.spinnerLoading = false
this.$router.push({ path: `/resource/udf/subUdfDirectory/${this.$route.params.id}`})
}, 800)
}).catch(e => {
this.$message.error(e.msg || '')
this.spinnerLoading = false
})
}
},
_validation () {
if (!this.name) {
this.$message.warning(`${i18n.$t('Please enter resource folder name')}`)
return false
}
return true
},
},
watch: {},
created () {
},
mounted () {
this.$modal.destroy()
},
destroyed () {
},
computed: {},
components: { mListConstruction, mConditions, mSpin, mListBoxF }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.resource-create-model {
padding: 30px;
}
</style>

37
dolphinscheduler-ui/src/js/conf/home/pages/security/pages/users/_source/list.vue

@ -144,7 +144,7 @@
pageSize: Number
},
methods: {
...mapActions('security', ['deleteUser', 'getAuthList', 'grantAuthorization']),
...mapActions('security', ['deleteUser', 'getAuthList', 'grantAuthorization','getResourceList']),
_closeDelete (i) {
this.$refs[`poptip-delete-${i}`][0].doClose()
},
@ -215,45 +215,44 @@
})
})
},
_authFile (item, i) {
_authFile (item, i) {
this.$refs[`poptip-auth-${i}`][0].doClose()
this.getAuthList({
this.getResourceList({
id: item.id,
type: 'file',
category: 'resources'
}).then(data => {
let sourceListPrs = _.map(data[0], v => {
return {
id: v.id,
name: v.alias,
type: v.type
}
})
// let sourceListPrs = _.map(data[0], v => {
// return {
// id: v.id,
// name: v.alias,
// type: v.type
// }
// })
let fileSourceList = []
let udfSourceList = []
sourceListPrs.forEach((value,index,array)=>{
data[0].forEach((value,index,array)=>{
if(value.type =='FILE'){
fileSourceList.push(value)
} else{
udfSourceList.push(value)
}
})
let targetListPrs = _.map(data[1], v => {
return {
id: v.id,
name: v.alias,
type: v.type
}
})
let fileTargetList = []
let udfTargetList = []
targetListPrs.forEach((value,index,array)=>{
data[1].forEach((value,index,array)=>{
if(value.type =='FILE'){
fileTargetList.push(value)
} else{
udfTargetList.push(value)
}
})
fileTargetList = _.map(fileTargetList, v => {
return v.id
})
udfTargetList = _.map(udfTargetList, v => {
return v.id
})
let self = this
let modal = this.$modal.dialog({
closable: false,

64
dolphinscheduler-ui/src/js/conf/home/router/index.js

@ -210,6 +210,30 @@ const router = new Router({
title: `${i18n.$t('Create Resource')}`
}
},
{
path: '/resource/file/createFolder',
name: 'resource-file-createFolder',
component: resolve => require(['../pages/resource/pages/file/pages/createFolder/index'], resolve),
meta: {
title: `${i18n.$t('Create Resource')}`
}
},
{
path: '/resource/file/subFileFolder/:id',
name: 'resource-file-subFileFolder',
component: resolve => require(['../pages/resource/pages/file/pages/subFileFolder/index'], resolve),
meta: {
title: `${i18n.$t('Create Resource')}`
}
},
{
path: '/resource/file/subFile/:id',
name: 'resource-file-subFile',
component: resolve => require(['../pages/resource/pages/file/pages/subFile/index'], resolve),
meta: {
title: `${i18n.$t('Create Resource')}`
}
},
{
path: '/resource/file/list/:id',
name: 'resource-file-details',
@ -218,6 +242,14 @@ const router = new Router({
title: `${i18n.$t('File Details')}`
}
},
{
path: '/resource/file/subdirectory/:id',
name: 'resource-file-subdirectory',
component: resolve => require(['../pages/resource/pages/file/pages/subdirectory/index'], resolve),
meta: {
title: `${i18n.$t('File Manage')}`
}
},
{
path: '/resource/file/edit/:id',
name: 'resource-file-edit',
@ -235,16 +267,40 @@ const router = new Router({
},
children: [
{
path: '/resource/udf/resource',
name: 'resource-udf-resource',
path: '/resource/udf',
name: 'resource-udf',
component: resolve => require(['../pages/resource/pages/udf/pages/resource/index'], resolve),
meta: {
title: `${i18n.$t('UDF Resources')}`
}
},
{
path: '/resource/udf/function',
name: 'resource-udf-function',
path: '/resource/udf/subUdfDirectory/:id',
name: 'resource-udf-subUdfDirectory',
component: resolve => require(['../pages/resource/pages/udf/pages/subUdfDirectory/index'], resolve),
meta: {
title: `${i18n.$t('UDF Resources')}`
}
},
{
path: '/resource/udf/createUdfFolder',
name: 'resource-udf-createUdfFolder',
component: resolve => require(['../pages/resource/pages/udf/pages/createUdfFolder/index'], resolve),
meta: {
title: `${i18n.$t('Create Resource')}`
}
},
{
path: '/resource/udf/subCreateUdfFolder/:id',
name: 'resource-udf-subCreateUdfFolder',
component: resolve => require(['../pages/resource/pages/udf/pages/subUdfFolder/index'], resolve),
meta: {
title: `${i18n.$t('Create Resource')}`
}
},
{
path: '/resource/func',
name: 'resource-func',
component: resolve => require(['../pages/resource/pages/udf/pages/function/index'], resolve),
meta: {
title: `${i18n.$t('UDF Function')}`

19
dolphinscheduler-ui/src/js/conf/home/store/dag/actions.js

@ -333,6 +333,25 @@ export default {
})
})
},
/**
* get jar
*/
getResourcesListJar ({ state }) {
return new Promise((resolve, reject) => {
if (state.resourcesListJar.length) {
resolve()
return
}
io.get(`resources/list/jar`, {
type: 'FILE'
}, res => {
state.resourcesListJar = res.data
resolve(res.data)
}).catch(res => {
reject(res)
})
})
},
/**
* Get process instance
*/

1
dolphinscheduler-ui/src/js/conf/home/store/dag/mutations.js

@ -108,6 +108,7 @@ export default {
state.tenantId = payload && payload.tenantId || -1
state.processListS = payload && payload.processListS || []
state.resourcesListS = payload && payload.resourcesListS || []
state.resourcesListJar = payload && payload.resourcesListJar || []
state.projectListS = payload && payload.projectListS || []
state.isDetails = payload && payload.isDetails || false
state.runFlag = payload && payload.runFlag || ''

2
dolphinscheduler-ui/src/js/conf/home/store/dag/state.js

@ -53,6 +53,8 @@ export default {
projectListS: [],
// tasks resourcesList
resourcesListS: [],
// tasks resourcesListJar
resourcesListJar: [],
// tasks datasource Type
dsTypeListS: [
{

21
dolphinscheduler-ui/src/js/conf/home/store/resource/actions.js

@ -30,6 +30,15 @@ export default {
})
})
},
getResourceId ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`resources/queryResource`, payload, res => {
resolve(res.data)
}).catch(e => {
reject(e)
})
})
},
getResourcesList ({ state }, payload) {
return new Promise((resolve, reject) => {
io.get(`resources/list`, payload, res => {
@ -160,6 +169,18 @@ export default {
})
})
},
/**
* Resource online create folder
*/
createResourceFolder ({ state }, payload) {
return new Promise((resolve, reject) => {
io.post(`resources/directory/create`, payload, res => {
resolve(res)
}).catch(e => {
reject(e)
})
})
},
/**
* Resource rename
*/

39
dolphinscheduler-ui/src/js/conf/home/store/security/actions.js

@ -195,6 +195,45 @@ export default {
})
})
},
getResourceList ({ state }, payload) {
let o = {
type: payload.type,
category: payload.category
}
let param = {}
// Manage user
if (o.type === 'user') {
param.alertgroupId = payload.id
} else {
param.userId = payload.id
}
// Authorized project
const p1 = new Promise((resolve, reject) => {
io.get(`${o.category}/authorize-resource-tree`, param, res => {
resolve(res.data)
}).catch(e => {
reject(e)
})
})
// Unauthorized project
const p2 = new Promise((resolve, reject) => {
io.get(`${o.category}/authed-${o.type}`, param, res => {
resolve(res.data)
}).catch(e => {
reject(e)
})
})
return new Promise((resolve, reject) => {
Promise.all([p1, p2]).then(a => {
resolve(a)
}).catch(e => {
reject(e)
})
})
},
/**
* Authorization [project, resource, data source]
* @param Project,Resources,Datasource

318
dolphinscheduler-ui/src/js/module/components/fileUpdate/fileChildUpdate.vue

@ -0,0 +1,318 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
<m-popup
ref="popup"
:ok-text="$t('Upload')"
:nameText="$t('File Upload')"
@ok="_ok"
:disabled="progress === 0 ? false : true">
<template slot="content">
<form name="files" enctype="multipart/form-data" method="post">
<div class="file-update-model"
@drop.prevent="_onDrop"
@dragover.prevent="dragOver = true"
@dragleave.prevent="dragOver = false"
id="file-update-model">
<div class="tooltip-info">
<em class="ans ans-icon-warn-solid"></em>
<span>{{$t('Drag the file into the current upload window')}}</span>
</div>
<!--<div class="hide-archive" v-if="progress !== 0" @click="_ckArchive">
<em class="fa fa-minus" data-toggle="tooltip" title="关闭窗口 继续上传" data-container="body" ></em>
</div>-->
<div class="update-popup" v-if="dragOver">
<div class="icon-box">
<em class="ans ans-icon-upload"></em>
</div>
<p class="p1">
<span>{{$t('Drag area upload')}}</span>
</p>
</div>
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('File Name')}}</template>
<template slot="content">
<x-input
type="input"
v-model="name"
:disabled="progress !== 0"
:placeholder="$t('Please enter name')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<x-input
type="textarea"
v-model="description"
:disabled="progress !== 0"
:placeholder="$t('Please enter description')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Upload Files')}}</template>
<template slot="content">
<div class="file-update-box">
<template v-if="progress === 0">
<input name="file" id="file" type="file" class="file-update">
<x-button type="dashed" size="xsmall"> {{$t('Upload')}} </x-button>
</template>
<div class="progress-box" v-if="progress !== 0">
<m-progress-bar :value="progress" text-placement="left-right"></m-progress-bar>
</div>
</div>
</template>
</m-list-box-f>
</div>
</form>
</template>
</m-popup>
</template>
<script>
import io from '@/module/io'
import i18n from '@/module/i18n'
import store from '@/conf/home/store'
import localStore from '@/module/util/localStorage'
import mPopup from '@/module/components/popup/popup'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
import mProgressBar from '@/module/components/progressBar/progressBar'
export default {
name: 'file-update',
data () {
return {
store,
// name
name: '',
// description
description: '',
// progress
progress: 0,
// file
file: '',
currentDir: localStore.getItem('currentDir'),
pid: this.id,
// Whether to drag upload
dragOver: false
}
},
watch: {
},
props: {
type: String,
id: Number
},
methods: {
/**
* submit
*/
_ok () {
this.$refs['popup'].spinnerLoading = true
if (this._validation()) {
this.store.dispatch('resource/resourceVerifyName', {
fullName: this.currentDir+'/'+this.name,
type: this.type
}).then(res => {
const isLt1024M = this.file.size / 1024 / 1024 < 1024
if(isLt1024M) {
this._formDataUpdate().then(res => {
setTimeout(() => {
this.$refs['popup'].spinnerLoading = false
}, 800)
}).catch(e => {
this.$refs['popup'].spinnerLoading = false
})
} else {
this.$message.warning(`${i18n.$t('Upload File Size')}`)
this.$refs['popup'].spinnerLoading = false
}
}).catch(e => {
this.$message.error(e.msg || '')
this.$refs['popup'].spinnerLoading = false
})
} else {
this.$refs['popup'].spinnerLoading = false
}
},
/**
* validation
*/
_validation () {
if (!this.name) {
this.$message.warning(`${i18n.$t('Please enter file name')}`)
return false
}
if (!this.file) {
this.$message.warning(`${i18n.$t('Please select the file to upload')}`)
return false
}
return true
},
/**
* update file
*/
_formDataUpdate () {
return new Promise((resolve, reject) => {
let self = this
let formData = new FormData()
formData.append('file', this.file)
formData.append('type', this.type)
formData.append('name', this.name)
formData.append('pid', this.pid)
formData.append('currentDir', this.currentDir)
formData.append('description', this.description)
io.post(`resources/create`, res => {
this.$message.success(res.msg)
resolve()
self.$emit('onUpdate')
}, e => {
reject(e)
self.$emit('close')
this.$message.error(e.msg || '')
}, {
data: formData,
emulateJSON: false,
onUploadProgress (progressEvent) {
// Size has been uploaded
let loaded = progressEvent.loaded
// Total attachment size
let total = progressEvent.total
self.progress = Math.floor(100 * loaded / total)
self.$emit('onProgress', self.progress)
}
})
})
},
/**
* Archive to the top right corner Continue uploading
*/
_ckArchive () {
$('.update-file-modal').hide()
this.$emit('onArchive')
},
/**
* Drag and drop upload
*/
_onDrop (e) {
let file = e.dataTransfer.files[0]
this.file = file
this.name = file.name
this.dragOver = false
}
},
mounted () {
$('#file').change(() => {
let file = $('#file')[0].files[0]
this.file = file
this.name = file.name
})
},
components: { mPopup, mListBoxF, mProgressBar }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.file-update-model {
.tooltip-info {
position: absolute;
left: 20px;
bottom: 26px;
span {
font-size: 12px;
color: #666;
vertical-align: middle;
}
.fa,.ans {
color: #0097e0;
font-size: 14px;
vertical-align: middle;
}
}
.hide-archive {
position: absolute;
right: 22px;
top: 17px;
.fa,.ans{
font-size: 16px;
color: #333;
font-weight: normal;
cursor: pointer;
&:hover {
color: #0097e0;
}
}
}
.file-update-box {
padding-top: 4px;
position: relative;
.file-update {
width: 70px;
height: 40px;
position: absolute;
left: 0;
top: 0;
cursor: pointer;
filter: alpha(opacity=0);
-moz-opacity: 0;
opacity: 0;
}
&:hover {
.v-btn-dashed {
background-color: transparent;
border-color: #47c3ff;
color: #47c3ff;
cursor: pointer;
}
}
.progress-box {
width: 200px;
position: absolute;
left: 70px;
top: 14px;
}
}
.update-popup {
width: calc(100% - 20px);
height: calc(100% - 20px);
background: rgba(255,253,239,.7);
position: absolute;
top: 10px;
left: 10px;
border-radius: 3px;
z-index: 1;
border: .18rem dashed #cccccc;
.icon-box {
text-align: center;
margin-top: 96px;
.fa,.ans {
font-size: 50px;
color: #2d8cf0;
}
}
.p1 {
text-align: center;
font-size: 16px;
color: #333;
padding-top: 8px;
}
}
}
</style>

6
dolphinscheduler-ui/src/js/module/components/fileUpdate/fileUpdate.vue

@ -107,6 +107,8 @@
progress: 0,
// file
file: '',
currentDir: '/',
pid: -1,
// Whether to drag upload
dragOver: false
}
@ -124,7 +126,7 @@
this.$refs['popup'].spinnerLoading = true
if (this._validation()) {
this.store.dispatch('resource/resourceVerifyName', {
name: this.name,
fullName: '/'+this.name,
type: this.type
}).then(res => {
const isLt1024M = this.file.size / 1024 / 1024 < 1024
@ -172,6 +174,8 @@
formData.append('file', this.file)
formData.append('type', this.type)
formData.append('name', this.name)
formData.append('pid', this.pid)
formData.append('currentDir', this.currentDir)
formData.append('description', this.description)
io.post(`resources/create`, res => {
this.$message.success(res.msg)

318
dolphinscheduler-ui/src/js/module/components/fileUpdate/resourceChildUpdate.vue

@ -0,0 +1,318 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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>
<m-popup
ref="popup"
:ok-text="$t('Upload')"
:nameText="$t('File Upload')"
@ok="_ok"
:disabled="progress === 0 ? false : true">
<template slot="content">
<form name="files" enctype="multipart/form-data" method="post">
<div class="file-update-model"
@drop.prevent="_onDrop"
@dragover.prevent="dragOver = true"
@dragleave.prevent="dragOver = false"
id="file-update-model">
<div class="tooltip-info">
<em class="ans ans-icon-warn-solid"></em>
<span>{{$t('Drag the file into the current upload window')}}</span>
</div>
<!--<div class="hide-archive" v-if="progress !== 0" @click="_ckArchive">
<em class="fa fa-minus" data-toggle="tooltip" title="关闭窗口 继续上传" data-container="body" ></em>
</div>-->
<div class="update-popup" v-if="dragOver">
<div class="icon-box">
<em class="ans ans-icon-upload"></em>
</div>
<p class="p1">
<span>{{$t('Drag area upload')}}</span>
</p>
</div>
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('File Name')}}</template>
<template slot="content">
<x-input
type="input"
v-model="name"
:disabled="progress !== 0"
:placeholder="$t('Please enter name')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<x-input
type="textarea"
v-model="description"
:disabled="progress !== 0"
:placeholder="$t('Please enter description')"
autocomplete="off">
</x-input>
</template>
</m-list-box-f>
<m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('Upload Files')}}</template>
<template slot="content">
<div class="file-update-box">
<template v-if="progress === 0">
<input name="file" id="file" type="file" class="file-update">
<x-button type="dashed" size="xsmall"> {{$t('Upload')}} </x-button>
</template>
<div class="progress-box" v-if="progress !== 0">
<m-progress-bar :value="progress" text-placement="left-right"></m-progress-bar>
</div>
</div>
</template>
</m-list-box-f>
</div>
</form>
</template>
</m-popup>
</template>
<script>
import io from '@/module/io'
import i18n from '@/module/i18n'
import store from '@/conf/home/store'
import localStore from '@/module/util/localStorage'
import mPopup from '@/module/components/popup/popup'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
import mProgressBar from '@/module/components/progressBar/progressBar'
export default {
name: 'file-update',
data () {
return {
store,
// name
name: '',
// description
description: '',
// progress
progress: 0,
// file
file: '',
currentDir: localStore.getItem('currentDir'),
pid: this.id,
// Whether to drag upload
dragOver: false
}
},
watch: {
},
props: {
type: String,
id: Number
},
methods: {
/**
* submit
*/
_ok () {
this.$refs['popup'].spinnerLoading = true
if (this._validation()) {
this.store.dispatch('resource/resourceVerifyName', {
fullName: this.currentDir+'/'+this.name,
type: this.type
}).then(res => {
const isLt1024M = this.file.size / 1024 / 1024 < 1024
if(isLt1024M) {
this._formDataUpdate().then(res => {
setTimeout(() => {
this.$refs['popup'].spinnerLoading = false
}, 800)
}).catch(e => {
this.$refs['popup'].spinnerLoading = false
})
} else {
this.$message.warning(`${i18n.$t('Upload File Size')}`)
this.$refs['popup'].spinnerLoading = false
}
}).catch(e => {
this.$message.error(e.msg || '')
this.$refs['popup'].spinnerLoading = false
})
} else {
this.$refs['popup'].spinnerLoading = false
}
},
/**
* validation
*/
_validation () {
if (!this.name) {
this.$message.warning(`${i18n.$t('Please enter file name')}`)
return false
}
if (!this.file) {
this.$message.warning(`${i18n.$t('Please select the file to upload')}`)
return false
}
return true
},
/**
* update file
*/
_formDataUpdate () {
return new Promise((resolve, reject) => {
let self = this
let formData = new FormData()
formData.append('file', this.file)
formData.append('type', this.type)
formData.append('name', this.name)
formData.append('pid', this.pid)
formData.append('currentDir', this.currentDir)
formData.append('description', this.description)
io.post(`resources/create`, res => {
this.$message.success(res.msg)
resolve()
self.$emit('onUpdate')
}, e => {
reject(e)
self.$emit('close')
this.$message.error(e.msg || '')
}, {
data: formData,
emulateJSON: false,
onUploadProgress (progressEvent) {
// Size has been uploaded
let loaded = progressEvent.loaded
// Total attachment size
let total = progressEvent.total
self.progress = Math.floor(100 * loaded / total)
self.$emit('onProgress', self.progress)
}
})
})
},
/**
* Archive to the top right corner Continue uploading
*/
_ckArchive () {
$('.update-file-modal').hide()
this.$emit('onArchive')
},
/**
* Drag and drop upload
*/
_onDrop (e) {
let file = e.dataTransfer.files[0]
this.file = file
this.name = file.name
this.dragOver = false
}
},
mounted () {
$('#file').change(() => {
let file = $('#file')[0].files[0]
this.file = file
this.name = file.name
})
},
components: { mPopup, mListBoxF, mProgressBar }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.file-update-model {
.tooltip-info {
position: absolute;
left: 20px;
bottom: 26px;
span {
font-size: 12px;
color: #666;
vertical-align: middle;
}
.fa,.ans {
color: #0097e0;
font-size: 14px;
vertical-align: middle;
}
}
.hide-archive {
position: absolute;
right: 22px;
top: 17px;
.fa,.ans{
font-size: 16px;
color: #333;
font-weight: normal;
cursor: pointer;
&:hover {
color: #0097e0;
}
}
}
.file-update-box {
padding-top: 4px;
position: relative;
.file-update {
width: 70px;
height: 40px;
position: absolute;
left: 0;
top: 0;
cursor: pointer;
filter: alpha(opacity=0);
-moz-opacity: 0;
opacity: 0;
}
&:hover {
.v-btn-dashed {
background-color: transparent;
border-color: #47c3ff;
color: #47c3ff;
cursor: pointer;
}
}
.progress-box {
width: 200px;
position: absolute;
left: 70px;
top: 14px;
}
}
.update-popup {
width: calc(100% - 20px);
height: calc(100% - 20px);
background: rgba(255,253,239,.7);
position: absolute;
top: 10px;
left: 10px;
border-radius: 3px;
z-index: 1;
border: .18rem dashed #cccccc;
.icon-box {
text-align: center;
margin-top: 96px;
.fa,.ans {
font-size: 50px;
color: #2d8cf0;
}
}
.p1 {
text-align: center;
font-size: 16px;
color: #333;
padding-top: 8px;
}
}
}
</style>

18
dolphinscheduler-ui/src/js/module/components/fileUpdate/udfUpdate.vue

@ -25,7 +25,7 @@
size="small"
v-model="udfName"
:disabled="progress !== 0"
style="width: 268px"
style="width: 535px"
:placeholder="$t('Please enter resource name')"
autocomplete="off">
</x-input>
@ -66,7 +66,9 @@
udfDesc: '',
file: '',
progress: 0,
spinnerLoading: false
spinnerLoading: false,
pid: null,
currentDir: ''
}
},
props: {
@ -77,6 +79,10 @@
* validation
*/
_validation () {
if (!this.currentDir) {
this.$message.warning(`${i18n.$t('Please select UDF resources directory')}`)
return false
}
if (!this.udfName) {
this.$message.warning(`${i18n.$t('Please enter file name')}`)
return false
@ -90,7 +96,7 @@
_verifyName () {
return new Promise((resolve, reject) => {
this.store.dispatch('resource/resourceVerifyName', {
name: this.udfName,
fullName: '/'+this.udfName,
type: 'UDF'
}).then(res => {
resolve()
@ -100,11 +106,17 @@
})
})
},
receivedValue(pid,name) {
this.pid = pid
this.currentDir = name
},
_formDataUpdate () {
let self = this
let formData = new FormData()
formData.append('file', this.file)
formData.append('type', 'UDF')
formData.append('pid', this.pid)
formData.append('currentDir', this.currentDir)
formData.append('name', this.udfName)
formData.append('description', this.udfDesc)
this.spinnerLoading = true

82
dolphinscheduler-ui/src/js/module/components/nav/nav.vue

@ -155,6 +155,8 @@
import { mapState, mapActions } from 'vuex'
import { findComponentDownward } from '@/module/util/'
import mFileUpdate from '@/module/components/fileUpdate/fileUpdate'
import mFileChildUpdate from '@/module/components/fileUpdate/fileChildUpdate'
import mResourceChildUpdate from '@/module/components/fileUpdate/resourceChildUpdate'
import mDefinitionUpdate from '@/module/components/fileUpdate/definitionUpdate'
import mProgressBar from '@/module/components/progressBar/progressBar'
@ -260,6 +262,86 @@
}
})
},
_fileChildUpdate (type,data) {
if (this.progress) {
this._toggleArchive()
return
}
let self = this
let modal = this.$modal.dialog({
closable: false,
showMask: true,
escClose: true,
className: 'update-file-modal',
transitionName: 'opacityp',
render (h) {
return h(mFileChildUpdate, {
on: {
onProgress (val) {
self.progress = val
},
onUpdate () {
findComponentDownward(self.$root, `resource-list-index-${type}`)._updateList(data)
self.isUpdate = false
self.progress = 0
modal.remove()
},
onArchive () {
self.isUpdate = true
},
close () {
self.progress = 0
modal.remove()
}
},
props: {
type: type,
id: data
}
})
}
})
},
_resourceChildUpdate (type,data) {
if (this.progress) {
this._toggleArchive()
return
}
let self = this
let modal = this.$modal.dialog({
closable: false,
showMask: true,
escClose: true,
className: 'update-file-modal',
transitionName: 'opacityp',
render (h) {
return h(mResourceChildUpdate, {
on: {
onProgress (val) {
self.progress = val
},
onUpdate () {
findComponentDownward(self.$root, `resource-list-index-${type}`)._updateList(data)
self.isUpdate = false
self.progress = 0
modal.remove()
},
onArchive () {
self.isUpdate = true
},
close () {
self.progress = 0
modal.remove()
}
},
props: {
type: type,
id: data
}
})
}
})
},
/**
* Upload popup layer display
*/

4
dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js

@ -148,13 +148,13 @@ let menu = {
children: [
{
name: `${i18n.$t('Resource manage')}`,
path: 'resource-udf-resource',
path: 'resource-udf',
id: 0,
disabled: true
},
{
name: `${i18n.$t('Function manage')}`,
path: 'resource-udf-function',
path: 'resource-func',
id: 1,
disabled: true
}

72
dolphinscheduler-ui/src/js/module/components/transfer/resource.vue

@ -20,26 +20,21 @@
<div class="clearfix transfer-model" style="width: 660px">
<div>
<x-button-group v-model="checkedValue" size="small">
<x-button type="ghost" value="fileResource" @click="_ckFile">{{$t('File resources')}}</x-button>
<x-button type="ghost" value="udfResource" @click="_ckUDf">{{$t('UDF resources')}}</x-button>
<x-button type="ghost" value="fileResource" @click="_ckFile">{{$t('File resources')}}</x-button>
<x-button type="ghost" value="udfResource" @click="_ckUDf">{{$t('UDF resources')}}</x-button>
</x-button-group>
</div>
<div class="select-list-box">
<treeselect v-show="checkedValue=='fileResource'" v-model="selectFileSource" :multiple="true" :options="fileList" :normalizer="normalizer" :placeholder="$t('Please select resources')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
<treeselect v-show="checkedValue=='udfResource'" v-model="selectUdfSource" :multiple="true" :options="udfList" :normalizer="normalizer" :placeholder="$t('Please select resources')">
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
</treeselect>
<!-- <div class="select-list-box">
<div class="tf-header">
<div class="title">{{type.name}}{{$t('List')}}</div>
<div class="count">{{cacheSourceList.length}}</div>
</div>
<!--<div class="tf-search">
<x-input v-model="searchSourceVal"
@on-enterkey="_sourceQuery"
@on-click-icon="_sourceQuery"
size="small"
placeholder="Please enter keyword"
type="text"
style="width:202px;">
<em slot="suffix" class="ans-icon-search"></em>
</x-input>
</div>-->
<div class="scrollbar tf-content">
<ul>
<li v-for="(item,$index) in sourceList" :key="$index" @click="_ckSource(item)">
@ -55,23 +50,12 @@
<div class="title">{{$t('Selected')}}{{type.name}}</div>
<div class="count">{{cacheTargetList.length}}</div>
</div>
<!--<div class="tf-search">
<x-input v-model="searchTargetVal"
@on-enterkey="_targetQuery"
@on-click-icon="_targetQuery"
size="small"
placeholder="Please enter keyword"
type="text"
style="width:202px;">
<em slot="suffix" class="ans-icon-search"></em>
</x-input>
</div>-->
<div class="scrollbar tf-content">
<ul>
<li v-for="(item,$index) in targetList" :key="$index" @click="_ckTarget(item)"><span :title="item.name">{{item.name}}</span></li>
</ul>
</div>
</div>
</div> -->
</div>
</template>
</m-popup>
@ -80,6 +64,9 @@
import _ from 'lodash'
import mPopup from '@/module/components/popup/popup'
import mListBoxF from '@/module/components/listBoxF/listBoxF'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
export default {
name: 'transfer',
@ -92,11 +79,22 @@
cacheTargetList: this.fileTargetList,
fileSource: this.fileSourceList,
fileList: [],
udfList: [],
selectFileSource: [],
selectUdfSource: [],
fileTarget: this.fileTargetList,
udfSource: this.udfSourceList,
udfTarget: this.udfTargetList,
searchSourceVal: '',
searchTargetVal: ''
searchTargetVal: '',
// define default value
value: null,
normalizer(node) {
return {
label: node.name
}
},
}
},
props: {
@ -106,12 +104,22 @@
fileTargetList: Array,
udfTargetList: Array,
},
created() {
let file = this.fileSourceList
let udf = this.udfSourceList
this.diGuiTree(file)
this.diGuiTree(udf)
this.fileList = file
this.udfList = udf
this.selectFileSource = this.fileTargetList
this.selectUdfSource = this.udfTargetList
},
methods: {
_ok () {
this.$refs['popup'].spinnerLoading = true
setTimeout(() => {
this.$refs['popup'].spinnerLoading = false
this.$emit('onUpdate', _.map(this.fileTarget.concat(this.udfTarget), v => v.id).join(','))
this.$emit('onUpdate', _.map(this.selectFileSource.concat(this.selectUdfSource), v => v).join(','))
}, 800)
},
_ckFile() {
@ -169,6 +177,12 @@
this.udfSource = this.sourceList
this.udfTarget = this.targetList
}
},
diGuiTree(item) { // Recursive convenience tree structure
item.forEach(item => {
item.children === '' || item.children === undefined || item.children === null || item.children.length === 0?        
delete item.children : this.diGuiTree(item.children);
})
}
},
watch: {
@ -187,7 +201,7 @@
this._targetQuery()
}
},
components: { mPopup, mListBoxF }
components: { mPopup, mListBoxF, Treeselect }
}
</script>

13
dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js

@ -138,6 +138,7 @@ export default {
'jdbc connect parameters': 'jdbc connect parameters',
'Test Connect': 'Test Connect',
'Please enter resource name': 'Please enter resource name',
'Please enter resource folder name': 'Please enter resource folder name',
'Please enter a non-query SQL statement': 'Please enter a non-query SQL statement',
'Please enter IP/hostname': 'Please enter IP/hostname',
'jdbc connection parameters is not a correct JSON format': 'jdbc connection parameters is not a correct JSON format',
@ -183,6 +184,8 @@ export default {
'Authorize': 'Authorize',
'File resources': 'File resources',
'UDF resources': 'UDF resources',
'Please select UDF resources directory': 'Please select UDF resources directory',
'UDF resources directory' : 'UDF resources directory',
'Upload File Size': 'Upload File size cannot exceed 1g',
'Edit alarm group': 'Edit alarm group',
'Create alarm group': 'Create alarm group',
@ -226,8 +229,11 @@ export default {
'execution': 'execution',
'finish': 'finish',
'Create File': 'Create File',
'Create folder': 'Create folder',
'File Name': 'File Name',
'Folder Name': 'Folder Name',
'File Format': 'File Format',
'Folder Format': 'Folder Format',
'File Content': 'File Content',
'Create': 'Create',
'Please enter the resource content': 'Please enter the resource content',
@ -274,6 +280,9 @@ export default {
'Edit UDF Function': 'Edit UDF Function',
'type': 'type',
'UDF Function Name': 'UDF Function Name',
'FILE': 'FILE',
'UDF': 'UDF',
'File Subdirectory': 'File Subdirectory',
'Please enter a function name': 'Please enter a function name',
'Package Name': 'Package Name',
'Please enter a Package name': 'Please enter a Package name',
@ -523,6 +532,10 @@ export default {
'0 means unlimited by byte': '0 means unlimited',
'0 means unlimited by count': '0 means unlimited',
'Modify User': 'Modify User',
'Whether directory': 'Whether directory',
'Yes': 'Yes',
'No': 'No',
'Modify User': 'Modify User',
'Please enter Mysql Database(required)': 'Please enter Mysql Database(required)',
'Please enter Mysql Table(required)': 'Please enter Mysql Table(required)',
'Please enter Columns (Comma separated)': 'Please enter Columns (Comma separated)',

13
dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js

@ -120,6 +120,9 @@ export default {
'SQL Parameter': 'sql参数',
'SQL Statement': 'sql语句',
'UDF Function': 'UDF函数',
'FILE': '文件',
'UDF': 'UDF',
'File Subdirectory': '文件子目录',
'Please enter a SQL Statement(required)': '请输入sql语句(必填)',
'Please enter a JSON Statement(required)': '请输入json语句(必填)',
'One form or attachment must be selected': '表格附件必须勾选一个',
@ -139,6 +142,7 @@ export default {
'jdbc connect parameters': 'jdbc连接参数',
'Test Connect': '测试连接',
'Please enter resource name': '请输入数据源名称',
'Please enter resource folder name': '请输入资源文件夹名称',
'Please enter a non-query SQL statement': '请输入非查询sql语句',
'Please enter IP/hostname': '请输入IP/主机名',
'jdbc connection parameters is not a correct JSON format': 'jdbc连接参数不是一个正确的JSON格式',
@ -182,6 +186,8 @@ export default {
'Authorize': '授权',
'File resources': '文件资源',
'UDF resources': 'UDF资源',
'UDF resources directory': 'UDF资源目录',
'Please select UDF resources directory': '请选择UDF资源目录',
'Edit alarm group': '编辑告警组',
'Create alarm group': '创建告警组',
'Group Name': '组名称',
@ -224,8 +230,11 @@ export default {
'execution': '执行中',
'finish': '完成',
'Create File': '创建文件',
'Create folder': '创建文件夹',
'File Name': '文件名称',
'Folder Name': '文件夹名称',
'File Format': '文件格式',
'Folder Format': '文件夹格式',
'File Content': '文件内容',
'Upload File Size': '文件大小不能超过1G',
'Create': '创建',
@ -523,6 +532,10 @@ export default {
'0 means unlimited by byte': 'KB0代表不限制',
'0 means unlimited by count': '0代表不限制',
'Modify User': '修改用户',
'Whether directory' : '是否文件夹',
'Yes': '是',
'No': '否',
'Modify User': '修改用户',
'Please enter Mysql Database(required)': '请输入Mysql数据库(必填)',
'Please enter Mysql Table(required)': '请输入Mysql表名(必填)',
'Please enter Columns (Comma separated)': '请输入列名 , 隔开',

6
dolphinscheduler-ui/src/js/module/util/routerUtil.js

@ -19,7 +19,7 @@ import merge from 'webpack-merge'
import router from '@/conf/home/router'
export function setUrlParams (o) {
router.push({
query: merge(router.history.current.query, o)
})
// router.push({
// query: merge(router.history.current.query, o)
// })
}

16
dolphinscheduler-ui/src/sass/common/_mixin.scss

@ -1,16 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/

2
dolphinscheduler-ui/src/sass/common/index.scss

@ -14,8 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "mixin";
@import "animation";
@import "scrollbar";
@import "table";

Loading…
Cancel
Save