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" "build:release": "npm run clean && cross-env NODE_ENV=production PUBLIC_PATH=/dolphinscheduler/ui webpack --config ./build/webpack.config.release.js"
}, },
"dependencies": { "dependencies": {
"@riophae/vue-treeselect": "^0.4.0",
"ans-ui": "1.1.7", "ans-ui": "1.1.7",
"axios": "^0.16.2", "axios": "^0.16.2",
"bootstrap": "3.3.7", "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> <m-list-box>
<div slot="text">{{$t('Main jar package')}}</div> <div slot="text">{{$t('Main jar package')}}</div>
<div slot="content"> <div slot="content">
<x-select <treeselect v-model="mainJar" :options="mainJarLists" :disable-branch-nodes="true" :normalizer="normalizer" :placeholder="$t('Please enter main jar package')">
style="width: 100%;" <div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
:placeholder="$t('Please enter main jar package')" </treeselect>
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>
</div> </div>
</m-list-box> </m-list-box>
<m-list-box> <m-list-box>
@ -151,12 +141,9 @@
<m-list-box> <m-list-box>
<div slot="text">{{$t('Resources')}}</div> <div slot="text">{{$t('Resources')}}</div>
<div slot="content"> <div slot="content">
<m-resources <treeselect v-model="resourceList" :multiple="true" :options="mainJarList" :normalizer="normalizer" :placeholder="$t('Please select resources')">
ref="refResources" <div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
@on-resourcesData="_onResourcesData" </treeselect>
@on-cache-resourcesData="_onCacheResourcesData"
:resource-list="resourceList">
</m-resources>
</div> </div>
</m-list-box> </m-list-box>
<m-list-box> <m-list-box>
@ -178,6 +165,8 @@
import mLocalParams from './_source/localParams' import mLocalParams from './_source/localParams'
import mListBox from './_source/listBox' import mListBox from './_source/listBox'
import mResources from './_source/resources' 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' import disabledState from '@/module/mixin/disabledState'
export default { export default {
@ -189,6 +178,7 @@
// Master jar package // Master jar package
mainJar: null, mainJar: null,
// Master jar package(List) // Master jar package(List)
mainJarLists: [],
mainJarList: [], mainJarList: [],
// Deployment method // Deployment method
deployMode: 'cluster', deployMode: 'cluster',
@ -215,7 +205,12 @@
// Program type // Program type
programType: 'SCALA', programType: 'SCALA',
// Program type(List) // Program type(List)
programTypeList: [{ code: 'JAVA' }, { code: 'SCALA' }, { code: 'PYTHON' }] programTypeList: [{ code: 'JAVA' }, { code: 'SCALA' }, { code: 'PYTHON' }],
normalizer(node) {
return {
label: node.name
}
}
} }
}, },
props: { props: {
@ -291,10 +286,6 @@
return false return false
} }
if (!this.$refs.refResources._verifResources()) {
return false
}
// localParams Subcomponent verification // localParams Subcomponent verification
if (!this.$refs.refLocalParams._verifProp()) { if (!this.$refs.refLocalParams._verifProp()) {
return false return false
@ -304,10 +295,12 @@
this.$emit('on-params', { this.$emit('on-params', {
mainClass: this.mainClass, mainClass: this.mainClass,
mainJar: { mainJar: {
res: this.mainJar id: this.mainJar
}, },
deployMode: this.deployMode, deployMode: this.deployMode,
resourceList: this.resourceList, resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams, localParams: this.localParams,
slot: this.slot, slot: this.slot,
taskManager: this.taskManager, taskManager: this.taskManager,
@ -320,24 +313,12 @@
}) })
return true return true
}, },
/** diGuiTree(item) { // Recursive convenience tree structure
* get resources list item.forEach(item => {
*/ item.children === '' || item.children === undefined || item.children === null || item.children.length === 0?        
_getResourcesList () { delete item.children : this.diGuiTree(item.children);
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: { watch: {
// Listening type // Listening type
@ -356,10 +337,12 @@
return { return {
mainClass: this.mainClass, mainClass: this.mainClass,
mainJar: { mainJar: {
res: this.mainJar id: this.mainJar
}, },
deployMode: this.deployMode, deployMode: this.deployMode,
resourceList: this.cacheResourceList, resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams, localParams: this.localParams,
slot: this.slot, slot: this.slot,
taskManager: this.taskManager, taskManager: this.taskManager,
@ -373,13 +356,17 @@
} }
}, },
created () { 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 let o = this.backfillItem
// Non-null objects represent backfill // Non-null objects represent backfill
if (!_.isEmpty(o)) { if (!_.isEmpty(o)) {
this.mainClass = o.params.mainClass || '' 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.deployMode = o.params.deployMode || ''
this.slot = o.params.slot || 1 this.slot = o.params.slot || 1
this.taskManager = o.params.taskManager || '2' this.taskManager = o.params.taskManager || '2'
@ -393,7 +380,9 @@
// backfill resourceList // backfill resourceList
let resourceList = o.params.resourceList || [] let resourceList = o.params.resourceList || []
if (resourceList.length) { if (resourceList.length) {
this.resourceList = resourceList this.resourceList = _.map(resourceList, v => {
return v.id
})
this.cacheResourceList = resourceList this.cacheResourceList = resourceList
} }
@ -403,12 +392,11 @@
this.localParams = localParams this.localParams = localParams
} }
} }
})
}, },
mounted () { mounted () {
}, },
components: { mLocalParams, mListBox, mResources } components: { mLocalParams, mListBox, mResources, Treeselect }
} }
</script> </script>

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

@ -44,19 +44,9 @@
<m-list-box> <m-list-box>
<div slot="text">{{$t('Main jar package')}}</div> <div slot="text">{{$t('Main jar package')}}</div>
<div slot="content"> <div slot="content">
<x-select <treeselect v-model="mainJar" :options="mainJarLists" :disable-branch-nodes="true" :normalizer="normalizer" :placeholder="$t('Please enter main jar package')">
style="width: 100%;" <div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
:placeholder="$t('Please enter main jar package')" </treeselect>
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>
</div> </div>
</m-list-box> </m-list-box>
<m-list-box> <m-list-box>
@ -88,12 +78,9 @@
<m-list-box> <m-list-box>
<div slot="text">{{$t('Resources')}}</div> <div slot="text">{{$t('Resources')}}</div>
<div slot="content"> <div slot="content">
<m-resources <treeselect v-model="resourceList" :multiple="true" :options="mainJarList" :normalizer="normalizer" :placeholder="$t('Please select resources')">
ref="refResources" <div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
@on-resourcesData="_onResourcesData" </treeselect>
@on-cache-resourcesData="_onCacheResourcesData"
:resource-list="resourceList">
</m-resources>
</div> </div>
</m-list-box> </m-list-box>
<m-list-box> <m-list-box>
@ -115,6 +102,8 @@
import mListBox from './_source/listBox' import mListBox from './_source/listBox'
import mResources from './_source/resources' import mResources from './_source/resources'
import mLocalParams from './_source/localParams' 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' import disabledState from '@/module/mixin/disabledState'
export default { export default {
name: 'mr', name: 'mr',
@ -125,6 +114,7 @@
// Master jar package // Master jar package
mainJar: null, mainJar: null,
// Main jar package (List) // Main jar package (List)
mainJarLists: [],
mainJarList: [], mainJarList: [],
// Resource(list) // Resource(list)
resourceList: [], resourceList: [],
@ -139,7 +129,12 @@
// Program type // Program type
programType: 'JAVA', programType: 'JAVA',
// Program type(List) // Program type(List)
programTypeList: [{ code: 'JAVA' }, { code: 'PYTHON' }] programTypeList: [{ code: 'JAVA' }, { code: 'PYTHON' }],
normalizer(node) {
return {
label: node.name
}
}
} }
}, },
props: { props: {
@ -165,6 +160,12 @@
_onCacheResourcesData (a) { _onCacheResourcesData (a) {
this.cacheResourceList = 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 * verification
*/ */
@ -179,22 +180,19 @@
return false return false
} }
if (!this.$refs.refResources._verifResources()) {
return false
}
// localParams Subcomponent verification // localParams Subcomponent verification
if (!this.$refs.refLocalParams._verifProp()) { if (!this.$refs.refLocalParams._verifProp()) {
return false return false
} }
// storage // storage
this.$emit('on-params', { this.$emit('on-params', {
mainClass: this.mainClass, mainClass: this.mainClass,
mainJar: { mainJar: {
res: this.mainJar id: this.mainJar
}, },
resourceList: this.resourceList, resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams, localParams: this.localParams,
mainArgs: this.mainArgs, mainArgs: this.mainArgs,
others: this.others, others: this.others,
@ -202,24 +200,7 @@
}) })
return true 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: { watch: {
/** /**
@ -240,9 +221,11 @@
return { return {
mainClass: this.mainClass, mainClass: this.mainClass,
mainJar: { mainJar: {
res: this.mainJar id: this.mainJar
}, },
resourceList: this.cacheResourceList, resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams, localParams: this.localParams,
mainArgs: this.mainArgs, mainArgs: this.mainArgs,
others: this.others, others: this.others,
@ -251,13 +234,18 @@
} }
}, },
created () { 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 let o = this.backfillItem
// Non-null objects represent backfill // Non-null objects represent backfill
if (!_.isEmpty(o)) { if (!_.isEmpty(o)) {
this.mainClass = o.params.mainClass || '' this.mainClass = o.params.mainClass || ''
this.mainJar = o.params.mainJar.res || '' this.mainJar = o.params.mainJar.id || ''
this.mainArgs = o.params.mainArgs || '' this.mainArgs = o.params.mainArgs || ''
this.others = o.params.others this.others = o.params.others
this.programType = o.params.programType || 'JAVA' this.programType = o.params.programType || 'JAVA'
@ -265,7 +253,9 @@
// backfill resourceList // backfill resourceList
let resourceList = o.params.resourceList || [] let resourceList = o.params.resourceList || []
if (resourceList.length) { if (resourceList.length) {
this.resourceList = resourceList this.resourceList = _.map(resourceList, v => {
return v.id
})
this.cacheResourceList = resourceList this.cacheResourceList = resourceList
} }
@ -275,12 +265,11 @@
this.localParams = localParams this.localParams = localParams
} }
} }
})
}, },
mounted () { mounted () {
}, },
components: { mLocalParams, mListBox, mResources } components: { mLocalParams, mListBox, mResources, Treeselect }
} }
</script> </script>

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

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

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

@ -32,6 +32,14 @@
</div> </div>
</m-list-box> </m-list-box>
<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="text">{{$t('Resources')}}</div>
<div slot="content"> <div slot="content">
<m-resources <m-resources
@ -41,7 +49,7 @@
:resource-list="resourceList"> :resource-list="resourceList">
</m-resources> </m-resources>
</div> </div>
</m-list-box> </m-list-box> -->
<m-list-box> <m-list-box>
<div slot="text">{{$t('Custom Parameters')}}</div> <div slot="text">{{$t('Custom Parameters')}}</div>
<div slot="content"> <div slot="content">
@ -63,6 +71,8 @@
import mResources from './_source/resources' import mResources from './_source/resources'
import mLocalParams from './_source/localParams' import mLocalParams from './_source/localParams'
import disabledState from '@/module/mixin/disabledState' 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' import codemirror from '@/conf/home/pages/resource/pages/file/pages/_source/codemirror'
let editor let editor
@ -78,7 +88,14 @@
// resource(list) // resource(list)
resourceList: [], resourceList: [],
// Cache ResourceList // Cache ResourceList
cacheResourceList: [] cacheResourceList: [],
// define options
options: [],
normalizer(node) {
return {
label: node.name
}
},
} }
}, },
mixins: [disabledState], mixins: [disabledState],
@ -143,17 +160,19 @@
return false return false
} }
if (!this.$refs.refResources._verifResources()) {
return false
}
// localParams Subcomponent verification // localParams Subcomponent verification
if (!this.$refs.refLocalParams._verifProp()) { if (!this.$refs.refLocalParams._verifProp()) {
return false return false
} }
// Process resourcelist
let dataProcessing= _.map(this.resourceList, v => {
return {
id: v
}
})
// storage // storage
this.$emit('on-params', { this.$emit('on-params', {
resourceList: this.resourceList, resourceList: dataProcessing,
localParams: this.localParams, localParams: this.localParams,
rawScript: editor.getValue() rawScript: editor.getValue()
}) })
@ -163,8 +182,6 @@
* Processing code highlighting * Processing code highlighting
*/ */
_handlerEditor () { _handlerEditor () {
this._destroyEditor()
// editor // editor
editor = codemirror('code-shell-mirror', { editor = codemirror('code-shell-mirror', {
mode: 'shell', mode: 'shell',
@ -179,51 +196,41 @@
} }
} }
this.changes = () => {
this._cacheParams()
}
// Monitor keyboard // Monitor keyboard
editor.on('keypress', this.keypress) editor.on('keypress', this.keypress)
editor.on('changes', this.changes)
editor.setValue(this.rawScript) editor.setValue(this.rawScript)
return editor return editor
}, },
_cacheParams () { diGuiTree(item) { // Recursive convenience tree structure
this.$emit('on-cache-params', { item.forEach(item => {
resourceList: this.cacheResourceList, item.children === '' || item.children === undefined || item.children === null || item.children.length === 0?        
localParams: this.localParams, delete item.children : this.diGuiTree(item.children);
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)
}
} }
}, },
watch: { watch: {
//Watch the cacheParams //Watch the cacheParams
cacheParams (val) { cacheParams (val) {
this._cacheParams() this.$emit('on-cache-params', val);
} }
}, },
computed: { computed: {
cacheParams () { cacheParams () {
return { return {
resourceList: this.cacheResourceList, resourceList: _.map(this.resourceList, v => {
localParams: this.localParams return {id: v}
}),
localParams: this.localParams,
rawScript: editor ? editor.getValue() : ''
} }
} }
}, },
created () { created () {
let item = this.store.state.dag.resourcesListS
this.diGuiTree(item)
this.options = item
let o = this.backfillItem let o = this.backfillItem
// Non-null objects represent backfill // Non-null objects represent backfill
if (!_.isEmpty(o)) { if (!_.isEmpty(o)) {
this.rawScript = o.params.rawScript || '' this.rawScript = o.params.rawScript || ''
@ -231,7 +238,9 @@
// backfill resourceList // backfill resourceList
let resourceList = o.params.resourceList || [] let resourceList = o.params.resourceList || []
if (resourceList.length) { if (resourceList.length) {
this.resourceList = resourceList this.resourceList = _.map(resourceList, v => {
return v.id
})
this.cacheResourceList = resourceList this.cacheResourceList = resourceList
} }
@ -251,10 +260,9 @@
if (editor) { if (editor) {
editor.toTextArea() // Uninstall editor.toTextArea() // Uninstall
editor.off($('.code-shell-mirror'), 'keypress', this.keypress) 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> </script>
<style lang="scss" rel="stylesheet/scss" scope> <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> <m-list-box>
<div slot="text">{{$t('Main jar package')}}</div> <div slot="text">{{$t('Main jar package')}}</div>
<div slot="content"> <div slot="content">
<x-select <treeselect v-model="mainJar" :options="mainJarLists" :disable-branch-nodes="true" :normalizer="normalizer" :placeholder="$t('Please enter main jar package')">
style="width: 100%;" <div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
:placeholder="$t('Please enter main jar package')" </treeselect>
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>
</div> </div>
</m-list-box> </m-list-box>
<m-list-box> <m-list-box>
@ -177,6 +167,14 @@
</div> </div>
</m-list-box> </m-list-box>
<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="text">{{$t('Resources')}}</div>
<div slot="content"> <div slot="content">
<m-resources <m-resources
@ -186,7 +184,7 @@
:resource-list="resourceList"> :resource-list="resourceList">
</m-resources> </m-resources>
</div> </div>
</m-list-box> </m-list-box> -->
<m-list-box> <m-list-box>
<div slot="text">{{$t('Custom Parameters')}}</div> <div slot="text">{{$t('Custom Parameters')}}</div>
<div slot="content"> <div slot="content">
@ -206,6 +204,8 @@
import mLocalParams from './_source/localParams' import mLocalParams from './_source/localParams'
import mListBox from './_source/listBox' import mListBox from './_source/listBox'
import mResources from './_source/resources' 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' import disabledState from '@/module/mixin/disabledState'
export default { export default {
@ -217,6 +217,7 @@
// Master jar package // Master jar package
mainJar: null, mainJar: null,
// Master jar package(List) // Master jar package(List)
mainJarLists: [],
mainJarList: [], mainJarList: [],
// Deployment method // Deployment method
deployMode: 'cluster', deployMode: 'cluster',
@ -247,7 +248,12 @@
// Spark version // Spark version
sparkVersion: 'SPARK2', sparkVersion: 'SPARK2',
// Spark version(LIst) // Spark version(LIst)
sparkVersionList: [{ code: 'SPARK2' }, { code: 'SPARK1' }] sparkVersionList: [{ code: 'SPARK2' }, { code: 'SPARK1' }],
normalizer(node) {
return {
label: node.name
}
}
} }
}, },
props: { props: {
@ -273,6 +279,12 @@
_onCacheResourcesData (a) { _onCacheResourcesData (a) {
this.cacheResourceList = 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 * verification
*/ */
@ -321,24 +333,25 @@
this.$message.warning(`${i18n.$t('Core number should be positive integer')}`) this.$message.warning(`${i18n.$t('Core number should be positive integer')}`)
return false return false
} }
if (!this.$refs.refResources._verifResources()) {
return false
}
// localParams Subcomponent verification // localParams Subcomponent verification
if (!this.$refs.refLocalParams._verifProp()) { if (!this.$refs.refLocalParams._verifProp()) {
return false return false
} }
// Process resourcelist
let dataProcessing= _.map(this.resourceList, v => {
return {
id: v
}
})
// storage // storage
this.$emit('on-params', { this.$emit('on-params', {
mainClass: this.mainClass, mainClass: this.mainClass,
mainJar: { mainJar: {
res: this.mainJar id: this.mainJar
}, },
deployMode: this.deployMode, deployMode: this.deployMode,
resourceList: this.resourceList, resourceList: dataProcessing,
localParams: this.localParams, localParams: this.localParams,
driverCores: this.driverCores, driverCores: this.driverCores,
driverMemory: this.driverMemory, driverMemory: this.driverMemory,
@ -351,24 +364,6 @@
sparkVersion: this.sparkVersion sparkVersion: this.sparkVersion
}) })
return true 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: { watch: {
@ -388,10 +383,12 @@
return { return {
mainClass: this.mainClass, mainClass: this.mainClass,
mainJar: { mainJar: {
res: this.mainJar id: this.mainJar
}, },
deployMode: this.deployMode, deployMode: this.deployMode,
resourceList: this.cacheResourceList, resourceList: _.map(this.resourceList, v => {
return {id: v}
}),
localParams: this.localParams, localParams: this.localParams,
driverCores: this.driverCores, driverCores: this.driverCores,
driverMemory: this.driverMemory, driverMemory: this.driverMemory,
@ -406,13 +403,18 @@
} }
}, },
created () { 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 let o = this.backfillItem
// Non-null objects represent backfill // Non-null objects represent backfill
if (!_.isEmpty(o)) { if (!_.isEmpty(o)) {
this.mainClass = o.params.mainClass || '' 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.deployMode = o.params.deployMode || ''
this.driverCores = o.params.driverCores || 1 this.driverCores = o.params.driverCores || 1
this.driverMemory = o.params.driverMemory || '512M' this.driverMemory = o.params.driverMemory || '512M'
@ -427,7 +429,9 @@
// backfill resourceList // backfill resourceList
let resourceList = o.params.resourceList || [] let resourceList = o.params.resourceList || []
if (resourceList.length) { if (resourceList.length) {
this.resourceList = resourceList this.resourceList = _.map(resourceList, v => {
return v.id
})
this.cacheResourceList = resourceList this.cacheResourceList = resourceList
} }
@ -437,12 +441,11 @@
this.localParams = localParams this.localParams = localParams
} }
} }
})
}, },
mounted () { mounted () {
}, },
components: { mLocalParams, mListBox, mResources } components: { mLocalParams, mListBox, mResources, Treeselect }
} }
</script> </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 // Register jsplumb connection type and configuration
this.JspInstance.registerConnectionType('basic', { this.JspInstance.registerConnectionType('basic', {
anchor: 'Continuous', anchor: 'Continuous',
connector: 'Straight' // Line type connector: 'Bezier' // Line type
}) })
// Initial configuration // Initial configuration
@ -236,7 +236,7 @@ JSP.prototype.initNode = function (el) {
filter: '.ep', filter: '.ep',
anchor: 'Continuous', anchor: 'Continuous',
connectorStyle: { connectorStyle: {
stroke: '#555', stroke: '#2d8cf0',
strokeWidth: 2, strokeWidth: 2,
outlineStroke: 'transparent', outlineStroke: 'transparent',
outlineWidth: 4 outlineWidth: 4
@ -297,6 +297,7 @@ JSP.prototype.tasksContextmenu = function (event) {
if (isOne) { if (isOne) {
// start run // start run
$('#startRunning').on('click', () => { $('#startRunning').on('click', () => {
let name = store.state.dag.name
let id = router.history.current.params.id let id = router.history.current.params.id
store.dispatch('dag/getStartCheck', { processDefinitionId: id }).then(res => { store.dispatch('dag/getStartCheck', { processDefinitionId: id }).then(res => {
let modal = Vue.$modal.dialog({ let modal = Vue.$modal.dialog({
@ -317,7 +318,8 @@ JSP.prototype.tasksContextmenu = function (event) {
}, },
props: { props: {
item: { item: {
id: id id: id,
name: name
}, },
startNodeList: $name, startNodeList: $name,
sourceType: 'contextmenu' sourceType: 'contextmenu'
@ -378,7 +380,7 @@ JSP.prototype.tasksClick = function (e) {
$('.w').removeClass('jtk-tasks-active') $('.w').removeClass('jtk-tasks-active')
$(e.currentTarget).addClass('jtk-tasks-active') $(e.currentTarget).addClass('jtk-tasks-active')
if ($connect) { if ($connect) {
setSvgColor($connect, '#555') setSvgColor($connect, '#2d8cf0')
this.selectedElement.connect = null this.selectedElement.connect = null
} }
this.selectedElement.id = $(e.currentTarget).attr('id') this.selectedElement.id = $(e.currentTarget).attr('id')
@ -437,19 +439,19 @@ JSP.prototype.handleEventPointer = function (is) {
isClick: is, isClick: is,
isAttachment: false isAttachment: false
}) })
wDom.removeClass('jtk-ep') // wDom.removeClass('jtk-ep')
if (!is) { // if (!is) {
wDom.removeClass('jtk-tasks-active') // wDom.removeClass('jtk-tasks-active')
this.selectedElement = {} // this.selectedElement = {}
_.map($('#canvas svg'), v => { // _.map($('#canvas svg'), v => {
if ($(v).attr('class')) { // if ($(v).attr('class')) {
_.map($(v).find('path'), v1 => { // _.map($(v).find('path'), v1 => {
$(v1).attr('fill', '#555') // $(v1).attr('fill', '#555')
$(v1).attr('stroke', '#555') // $(v1).attr('stroke', '#555')
}) // })
} // }
}) // })
} // }
} }
/** /**
@ -764,7 +766,7 @@ JSP.prototype.jspBackfill = function ({ connects, locations, largeJson }) {
source: sourceId, source: sourceId,
target: targetId, target: targetId,
type: 'basic', 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 // Traverse clear all colors
$('.jtk-connector').each((i, o) => { $('.jtk-connector').each((i, o) => {
_.map($(o)[0].childNodes, v => { _.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: {}, props: {},
methods: { methods: {
...mapMutations('dag', ['resetParams', 'setIsDetails']), ...mapMutations('dag', ['resetParams', 'setIsDetails']),
...mapActions('dag', ['getProcessList','getProjectList', 'getResourcesList', 'getProcessDetails']), ...mapActions('dag', ['getProcessList','getProjectList', 'getResourcesList', 'getProcessDetails','getResourcesListJar']),
...mapActions('security', ['getTenantList','getWorkerGroupsAll']), ...mapActions('security', ['getTenantList','getWorkerGroupsAll']),
/** /**
* init * init
@ -60,6 +60,8 @@
this.getProjectList(), this.getProjectList(),
// get resource // get resource
this.getResourcesList(), this.getResourcesList(),
// get jar
this.getResourcesListJar(),
// get worker group list // get worker group list
this.getWorkerGroupsAll(), this.getWorkerGroupsAll(),
this.getTenantList() this.getTenantList()

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

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

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

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

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

@ -96,6 +96,8 @@
description: '', description: '',
fileTypeList: filtTypeArr, fileTypeList: filtTypeArr,
content: '', content: '',
pid: -1,
currentDir: '/',
spinnerLoading: false spinnerLoading: false
} }
}, },
@ -107,6 +109,8 @@
this.spinnerLoading = true this.spinnerLoading = true
this.createResourceFile({ this.createResourceFile({
type: 'FILE', type: 'FILE',
pid: this.pid,
currentDir: this.currentDir,
fileName: this.fileName, fileName: this.fileName,
suffix: this.suffix, suffix: this.suffix,
description: this.description, description: this.description,
@ -136,6 +140,7 @@
this.$message.warning(`${i18n.$t('Resource content cannot exceed 3000 lines')}`) this.$message.warning(`${i18n.$t('Resource content cannot exceed 3000 lines')}`)
return false return false
} }
return true 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> <h2>
<span>{{name}}</span> <span>{{name}}</span>
<div class="down"> <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> <em>{{size}}</em>
</div> </div>
</h2> </h2>

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

@ -44,8 +44,8 @@
</m-list-construction> </m-list-construction>
</template> </template>
<script> <script>
import _ from 'lodash'
import i18n from '@/module/i18n' import i18n from '@/module/i18n'
import _ from 'lodash'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import { filtTypeArr } from '../_source/common' import { filtTypeArr } from '../_source/common'
import mNoType from '../details/_source/noType' import mNoType from '../details/_source/noType'
@ -80,8 +80,8 @@
...mapActions('resource', ['getViewResources', 'updateContent']), ...mapActions('resource', ['getViewResources', 'updateContent']),
ok () { ok () {
if (this._validation()) { if (this._validation()) {
this.spinnerLoading = true this.spinnerLoading = true
this.updateContent({ this.updateContent({
id: this.$route.params.id, id: this.$route.params.id,
content: editor.getValue() content: editor.getValue()
}).then(res => { }).then(res => {
@ -104,7 +104,7 @@
return true return true
}, },
close () { close () {
this.$router.push({ name: 'file' }) this.$router.go(-1)
}, },
_getViewResources () { _getViewResources () {
this.isLoading = true 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"> <th scope="col">
<span>{{$t('Name')}}</span> <span>{{$t('Name')}}</span>
</th> </th>
<th scope="col">
<span>{{$t('Whether directory')}}</span>
</th>
<th scope="col"> <th scope="col">
<span>{{$t('File Name')}}</span> <span>{{$t('File Name')}}</span>
</th> </th>
@ -50,6 +53,9 @@
<a href="javascript:" class="links" @click="_go(item)">{{item.alias}}</a> <a href="javascript:" class="links" @click="_go(item)">{{item.alias}}</a>
</span> </span>
</td> </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 class="ellipsis" v-tooltip.large.top.start.light="{text: item.fileName, maxWidth: '500px'}">{{item.fileName}}</span></td>
<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-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" size="xsmall"
data-toggle="tooltip" data-toggle="tooltip"
:title="$t('Download')" :title="$t('Download')"
:disabled="item.directory? true: false"
@click="_downloadFile(item)" @click="_downloadFile(item)"
icon="ans-icon-download"> icon="ans-icon-download">
</x-button> </x-button>
@ -148,7 +155,12 @@
}, },
_go (item) { _go (item) {
localStore.setItem('file', `${item.alias}|${item.size}`) 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 (item) {
downloadFile('/dolphinscheduler/resources/download', { 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"> <m-conditions @on-conditions="_onConditions">
<template slot="button-group"> <template slot="button-group">
<x-button-group size="small" > <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="() => $router.push({name: 'resource-file-create'})">{{$t('Create File')}}</x-button>
<x-button type="ghost" @click="_uploading">{{$t('Upload Files')}}</x-button> <x-button type="ghost" @click="_uploading">{{$t('Upload Files')}}</x-button>
</x-button-group> </x-button-group>
@ -61,6 +62,7 @@
isLoading: false, isLoading: false,
fileResourcesList: [], fileResourcesList: [],
searchParams: { searchParams: {
id: -1,
pageSize: 10, pageSize: 10,
pageNo: 1, pageNo: 1,
searchVal: '', 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. * limitations under the License.
*/ */
<template> <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"> <template slot="content">
<div class="udf-create-model"> <div class="udf-create-model">
<m-list-box-f> <m-list-box-f>
@ -72,26 +72,25 @@
<m-list-box-f> <m-list-box-f>
<template slot="name"><strong>*</strong>{{$t('UDF Resources')}}</template> <template slot="name"><strong>*</strong>{{$t('UDF Resources')}}</template>
<template slot="content"> <template slot="content">
<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')">
filterable <div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div>
v-model="resourceId" </treeselect>
: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>
<x-button type="primary" @click="_toggleUpdate" :disabled="upDisabled">{{$t('Upload Resources')}}</x-button> <x-button type="primary" @click="_toggleUpdate" :disabled="upDisabled">{{$t('Upload Resources')}}</x-button>
</template> </template>
</m-list-box-f> </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"> <m-list-box-f v-if="isUpdate">
<template slot="name">&nbsp;</template> <template slot="name">&nbsp;</template>
<template slot="content"> <template slot="content">
<m-udf-update <m-udf-update
ref="assignment"
@on-update-present="_onUpdatePresent" @on-update-present="_onUpdatePresent"
@on-update="_onUpdate"> @on-update="_onUpdate">
</m-udf-update> </m-udf-update>
@ -115,6 +114,8 @@
import _ from 'lodash' import _ from 'lodash'
import i18n from '@/module/i18n' import i18n from '@/module/i18n'
import store from '@/conf/home/store' 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 mPopup from '@/module/components/popup/popup'
import mListBoxF from '@/module/components/listBoxF/listBoxF' import mListBoxF from '@/module/components/listBoxF/listBoxF'
import mUdfUpdate from '@/module/components/fileUpdate/udfUpdate' import mUdfUpdate from '@/module/components/fileUpdate/udfUpdate'
@ -130,10 +131,16 @@
argTypes: '', argTypes: '',
database: '', database: '',
description: '', description: '',
resourceId: '', resourceId: null,
pid: null,
udfResourceList: [], udfResourceList: [],
isUpdate: false, isUpdate: false,
upDisabled: false upDisabled: false,
normalizer(node) {
return {
label: node.name
}
},
} }
}, },
props: { props: {
@ -192,17 +199,54 @@
// disabled update // disabled update
this.upDisabled = true this.upDisabled = true
}, },
// selTree
selTree(node) {
this.$refs.assignment.receivedValue(node.id,node.fullName)
},
/** /**
* get udf resources * get udf resources
*/ */
_getUdfList () { _getUdfList () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.store.dispatch('resource/getResourcesList', { type: 'UDF' }).then(res => { 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() 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 * Upload udf resources
*/ */
@ -257,8 +301,7 @@
}) })
} }
}, },
watch: { watch: {},
},
created () { created () {
this._getUdfList().then(res => { this._getUdfList().then(res => {
// edit // edit
@ -271,13 +314,18 @@
this.description = this.item.description || '' this.description = this.item.description || ''
this.resourceId = this.item.resourceId this.resourceId = this.item.resourceId
} else { } else {
this.resourceId = this.udfResourceList.length && this.udfResourceList[0].id || '' this.resourceId = null
} }
}) })
}, },
mounted () { mounted () {
}, },
components: { mPopup, mListBoxF, mUdfUpdate } components: { mPopup, mListBoxF, mUdfUpdate, Treeselect }
} }
</script> </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"> <!-- <th scope="col">
<span>{{$t('Library Name')}}</span> <span>{{$t('Library Name')}}</span>
</th> --> </th> -->
<th scope="col" width="140"> <th scope="col" width="150">
<span>{{$t('Update Time')}}</span> <span>{{$t('Update Time')}}</span>
</th> </th>
<th scope="col" width="80"> <th scope="col" width="80">
@ -71,7 +71,8 @@ v-ps<template>
<span v-else>-</span> <span v-else>-</span>
</td> </td>
<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>
<!-- <td> <!-- <td>
<span>{{item.database || '-'}}</span> <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"> <template slot="conditions">
<m-conditions @on-conditions="_onConditions"> <m-conditions @on-conditions="_onConditions">
<template slot="button-group"> <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> </template>
</m-conditions> </m-conditions>
</template> </template>
@ -58,6 +60,7 @@
isLoading: false, isLoading: false,
udfFuncList: [], udfFuncList: [],
searchParams: { searchParams: {
id: -1,
pageSize: 10, pageSize: 10,
pageNo: 1, pageNo: 1,
searchVal: '' searchVal: ''

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

@ -25,6 +25,9 @@
<th scope="col"> <th scope="col">
<span>{{$t('UDF Resource Name')}}</span> <span>{{$t('UDF Resource Name')}}</span>
</th> </th>
<th scope="col">
<span>{{$t('Whether directory')}}</span>
</th>
<th scope="col"> <th scope="col">
<span>{{$t('File Name')}}</span> <span>{{$t('File Name')}}</span>
</th> </th>
@ -50,9 +53,12 @@
</td> </td>
<td> <td>
<span class="ellipsis" v-tooltip.large.top.start.light="{text: item.alias, maxWidth: '500px'}"> <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> </span>
</td> </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 class="ellipsis" v-tooltip.large.top.start.light="{text: item.fileName, maxWidth: '500px'}">{{item.fileName}}</span></td>
<td> <td>
<span>{{_rtSize(item.size)}}</span> <span>{{_rtSize(item.size)}}</span>
@ -85,6 +91,7 @@
size="xsmall" size="xsmall"
data-toggle="tooltip" data-toggle="tooltip"
:title="$t('Download')" :title="$t('Download')"
:disabled="item.directory? true: false"
icon="ans-icon-download" icon="ans-icon-download"
@click="_downloadFile(item)"> @click="_downloadFile(item)">
</x-button> </x-button>
@ -120,6 +127,7 @@
import mRename from './rename' import mRename from './rename'
import { downloadFile } from '@/module/download' import { downloadFile } from '@/module/download'
import { bytesToSize } from '@/module/util/util' import { bytesToSize } from '@/module/util/util'
import localStore from '@/module/util/localStorage'
export default { export default {
name: 'udf-manage-list', name: 'udf-manage-list',
@ -140,6 +148,13 @@
id: item.id 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) { _rtSize (val) {
return bytesToSize(parseInt(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"> <template slot="conditions">
<m-conditions @on-conditions="_onConditions"> <m-conditions @on-conditions="_onConditions">
<template slot="button-group"> <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> </template>
</m-conditions> </m-conditions>
</template> </template>
@ -58,6 +61,7 @@
isLoading: false, isLoading: false,
udfResourcesList: [], udfResourcesList: [],
searchParams: { searchParams: {
id: -1,
pageSize: 10, pageSize: 10,
pageNo: 1, pageNo: 1,
searchVal: '', 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 pageSize: Number
}, },
methods: { methods: {
...mapActions('security', ['deleteUser', 'getAuthList', 'grantAuthorization']), ...mapActions('security', ['deleteUser', 'getAuthList', 'grantAuthorization','getResourceList']),
_closeDelete (i) { _closeDelete (i) {
this.$refs[`poptip-delete-${i}`][0].doClose() 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.$refs[`poptip-auth-${i}`][0].doClose()
this.getAuthList({ this.getResourceList({
id: item.id, id: item.id,
type: 'file', type: 'file',
category: 'resources' category: 'resources'
}).then(data => { }).then(data => {
let sourceListPrs = _.map(data[0], v => { // let sourceListPrs = _.map(data[0], v => {
return { // return {
id: v.id, // id: v.id,
name: v.alias, // name: v.alias,
type: v.type // type: v.type
} // }
}) // })
let fileSourceList = [] let fileSourceList = []
let udfSourceList = [] let udfSourceList = []
sourceListPrs.forEach((value,index,array)=>{ data[0].forEach((value,index,array)=>{
if(value.type =='FILE'){ if(value.type =='FILE'){
fileSourceList.push(value) fileSourceList.push(value)
} else{ } else{
udfSourceList.push(value) udfSourceList.push(value)
} }
}) })
let targetListPrs = _.map(data[1], v => {
return {
id: v.id,
name: v.alias,
type: v.type
}
})
let fileTargetList = [] let fileTargetList = []
let udfTargetList = [] let udfTargetList = []
targetListPrs.forEach((value,index,array)=>{ data[1].forEach((value,index,array)=>{
if(value.type =='FILE'){ if(value.type =='FILE'){
fileTargetList.push(value) fileTargetList.push(value)
} else{ } else{
udfTargetList.push(value) udfTargetList.push(value)
} }
}) })
fileTargetList = _.map(fileTargetList, v => {
return v.id
})
udfTargetList = _.map(udfTargetList, v => {
return v.id
})
let self = this let self = this
let modal = this.$modal.dialog({ let modal = this.$modal.dialog({
closable: false, 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')}` 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', path: '/resource/file/list/:id',
name: 'resource-file-details', name: 'resource-file-details',
@ -218,6 +242,14 @@ const router = new Router({
title: `${i18n.$t('File Details')}` 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', path: '/resource/file/edit/:id',
name: 'resource-file-edit', name: 'resource-file-edit',
@ -235,16 +267,40 @@ const router = new Router({
}, },
children: [ children: [
{ {
path: '/resource/udf/resource', path: '/resource/udf',
name: 'resource-udf-resource', name: 'resource-udf',
component: resolve => require(['../pages/resource/pages/udf/pages/resource/index'], resolve), component: resolve => require(['../pages/resource/pages/udf/pages/resource/index'], resolve),
meta: { meta: {
title: `${i18n.$t('UDF Resources')}` title: `${i18n.$t('UDF Resources')}`
} }
}, },
{ {
path: '/resource/udf/function', path: '/resource/udf/subUdfDirectory/:id',
name: 'resource-udf-function', 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), component: resolve => require(['../pages/resource/pages/udf/pages/function/index'], resolve),
meta: { meta: {
title: `${i18n.$t('UDF Function')}` 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 * 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.tenantId = payload && payload.tenantId || -1
state.processListS = payload && payload.processListS || [] state.processListS = payload && payload.processListS || []
state.resourcesListS = payload && payload.resourcesListS || [] state.resourcesListS = payload && payload.resourcesListS || []
state.resourcesListJar = payload && payload.resourcesListJar || []
state.projectListS = payload && payload.projectListS || [] state.projectListS = payload && payload.projectListS || []
state.isDetails = payload && payload.isDetails || false state.isDetails = payload && payload.isDetails || false
state.runFlag = payload && payload.runFlag || '' state.runFlag = payload && payload.runFlag || ''

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

@ -53,6 +53,8 @@ export default {
projectListS: [], projectListS: [],
// tasks resourcesList // tasks resourcesList
resourcesListS: [], resourcesListS: [],
// tasks resourcesListJar
resourcesListJar: [],
// tasks datasource Type // tasks datasource Type
dsTypeListS: [ 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) { getResourcesList ({ state }, payload) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
io.get(`resources/list`, payload, res => { 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 * 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] * Authorization [project, resource, data source]
* @param Project,Resources,Datasource * @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, progress: 0,
// file // file
file: '', file: '',
currentDir: '/',
pid: -1,
// Whether to drag upload // Whether to drag upload
dragOver: false dragOver: false
} }
@ -124,7 +126,7 @@
this.$refs['popup'].spinnerLoading = true this.$refs['popup'].spinnerLoading = true
if (this._validation()) { if (this._validation()) {
this.store.dispatch('resource/resourceVerifyName', { this.store.dispatch('resource/resourceVerifyName', {
name: this.name, fullName: '/'+this.name,
type: this.type type: this.type
}).then(res => { }).then(res => {
const isLt1024M = this.file.size / 1024 / 1024 < 1024 const isLt1024M = this.file.size / 1024 / 1024 < 1024
@ -172,6 +174,8 @@
formData.append('file', this.file) formData.append('file', this.file)
formData.append('type', this.type) formData.append('type', this.type)
formData.append('name', this.name) formData.append('name', this.name)
formData.append('pid', this.pid)
formData.append('currentDir', this.currentDir)
formData.append('description', this.description) formData.append('description', this.description)
io.post(`resources/create`, res => { io.post(`resources/create`, res => {
this.$message.success(res.msg) 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" size="small"
v-model="udfName" v-model="udfName"
:disabled="progress !== 0" :disabled="progress !== 0"
style="width: 268px" style="width: 535px"
:placeholder="$t('Please enter resource name')" :placeholder="$t('Please enter resource name')"
autocomplete="off"> autocomplete="off">
</x-input> </x-input>
@ -66,7 +66,9 @@
udfDesc: '', udfDesc: '',
file: '', file: '',
progress: 0, progress: 0,
spinnerLoading: false spinnerLoading: false,
pid: null,
currentDir: ''
} }
}, },
props: { props: {
@ -77,6 +79,10 @@
* validation * validation
*/ */
_validation () { _validation () {
if (!this.currentDir) {
this.$message.warning(`${i18n.$t('Please select UDF resources directory')}`)
return false
}
if (!this.udfName) { if (!this.udfName) {
this.$message.warning(`${i18n.$t('Please enter file name')}`) this.$message.warning(`${i18n.$t('Please enter file name')}`)
return false return false
@ -90,7 +96,7 @@
_verifyName () { _verifyName () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.store.dispatch('resource/resourceVerifyName', { this.store.dispatch('resource/resourceVerifyName', {
name: this.udfName, fullName: '/'+this.udfName,
type: 'UDF' type: 'UDF'
}).then(res => { }).then(res => {
resolve() resolve()
@ -100,11 +106,17 @@
}) })
}) })
}, },
receivedValue(pid,name) {
this.pid = pid
this.currentDir = name
},
_formDataUpdate () { _formDataUpdate () {
let self = this let self = this
let formData = new FormData() let formData = new FormData()
formData.append('file', this.file) formData.append('file', this.file)
formData.append('type', 'UDF') formData.append('type', 'UDF')
formData.append('pid', this.pid)
formData.append('currentDir', this.currentDir)
formData.append('name', this.udfName) formData.append('name', this.udfName)
formData.append('description', this.udfDesc) formData.append('description', this.udfDesc)
this.spinnerLoading = true this.spinnerLoading = true

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

@ -155,6 +155,8 @@
import { mapState, mapActions } from 'vuex' import { mapState, mapActions } from 'vuex'
import { findComponentDownward } from '@/module/util/' import { findComponentDownward } from '@/module/util/'
import mFileUpdate from '@/module/components/fileUpdate/fileUpdate' 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 mDefinitionUpdate from '@/module/components/fileUpdate/definitionUpdate'
import mProgressBar from '@/module/components/progressBar/progressBar' 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 * Upload popup layer display
*/ */

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

@ -148,13 +148,13 @@ let menu = {
children: [ children: [
{ {
name: `${i18n.$t('Resource manage')}`, name: `${i18n.$t('Resource manage')}`,
path: 'resource-udf-resource', path: 'resource-udf',
id: 0, id: 0,
disabled: true disabled: true
}, },
{ {
name: `${i18n.$t('Function manage')}`, name: `${i18n.$t('Function manage')}`,
path: 'resource-udf-function', path: 'resource-func',
id: 1, id: 1,
disabled: true 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 class="clearfix transfer-model" style="width: 660px">
<div> <div>
<x-button-group v-model="checkedValue" size="small"> <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="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="udfResource" @click="_ckUDf">{{$t('UDF resources')}}</x-button>
</x-button-group> </x-button-group>
</div> </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="tf-header">
<div class="title">{{type.name}}{{$t('List')}}</div> <div class="title">{{type.name}}{{$t('List')}}</div>
<div class="count">{{cacheSourceList.length}}</div> <div class="count">{{cacheSourceList.length}}</div>
</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"> <div class="scrollbar tf-content">
<ul> <ul>
<li v-for="(item,$index) in sourceList" :key="$index" @click="_ckSource(item)"> <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="title">{{$t('Selected')}}{{type.name}}</div>
<div class="count">{{cacheTargetList.length}}</div> <div class="count">{{cacheTargetList.length}}</div>
</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"> <div class="scrollbar tf-content">
<ul> <ul>
<li v-for="(item,$index) in targetList" :key="$index" @click="_ckTarget(item)"><span :title="item.name">{{item.name}}</span></li> <li v-for="(item,$index) in targetList" :key="$index" @click="_ckTarget(item)"><span :title="item.name">{{item.name}}</span></li>
</ul> </ul>
</div> </div>
</div> </div> -->
</div> </div>
</template> </template>
</m-popup> </m-popup>
@ -80,6 +64,9 @@
import _ from 'lodash' import _ from 'lodash'
import mPopup from '@/module/components/popup/popup' import mPopup from '@/module/components/popup/popup'
import mListBoxF from '@/module/components/listBoxF/listBoxF' import mListBoxF from '@/module/components/listBoxF/listBoxF'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
export default { export default {
name: 'transfer', name: 'transfer',
@ -92,11 +79,22 @@
cacheTargetList: this.fileTargetList, cacheTargetList: this.fileTargetList,
fileSource: this.fileSourceList, fileSource: this.fileSourceList,
fileList: [],
udfList: [],
selectFileSource: [],
selectUdfSource: [],
fileTarget: this.fileTargetList, fileTarget: this.fileTargetList,
udfSource: this.udfSourceList, udfSource: this.udfSourceList,
udfTarget: this.udfTargetList, udfTarget: this.udfTargetList,
searchSourceVal: '', searchSourceVal: '',
searchTargetVal: '' searchTargetVal: '',
// define default value
value: null,
normalizer(node) {
return {
label: node.name
}
},
} }
}, },
props: { props: {
@ -106,12 +104,22 @@
fileTargetList: Array, fileTargetList: Array,
udfTargetList: 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: { methods: {
_ok () { _ok () {
this.$refs['popup'].spinnerLoading = true this.$refs['popup'].spinnerLoading = true
setTimeout(() => { setTimeout(() => {
this.$refs['popup'].spinnerLoading = false 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) }, 800)
}, },
_ckFile() { _ckFile() {
@ -169,6 +177,12 @@
this.udfSource = this.sourceList this.udfSource = this.sourceList
this.udfTarget = this.targetList 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: { watch: {
@ -187,7 +201,7 @@
this._targetQuery() this._targetQuery()
} }
}, },
components: { mPopup, mListBoxF } components: { mPopup, mListBoxF, Treeselect }
} }
</script> </script>

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

@ -138,6 +138,7 @@ export default {
'jdbc connect parameters': 'jdbc connect parameters', 'jdbc connect parameters': 'jdbc connect parameters',
'Test Connect': 'Test Connect', 'Test Connect': 'Test Connect',
'Please enter resource name': 'Please enter resource name', '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 a non-query SQL statement': 'Please enter a non-query SQL statement',
'Please enter IP/hostname': 'Please enter IP/hostname', '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', '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', 'Authorize': 'Authorize',
'File resources': 'File resources', 'File resources': 'File resources',
'UDF resources': 'UDF 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', 'Upload File Size': 'Upload File size cannot exceed 1g',
'Edit alarm group': 'Edit alarm group', 'Edit alarm group': 'Edit alarm group',
'Create alarm group': 'Create alarm group', 'Create alarm group': 'Create alarm group',
@ -226,8 +229,11 @@ export default {
'execution': 'execution', 'execution': 'execution',
'finish': 'finish', 'finish': 'finish',
'Create File': 'Create File', 'Create File': 'Create File',
'Create folder': 'Create folder',
'File Name': 'File Name', 'File Name': 'File Name',
'Folder Name': 'Folder Name',
'File Format': 'File Format', 'File Format': 'File Format',
'Folder Format': 'Folder Format',
'File Content': 'File Content', 'File Content': 'File Content',
'Create': 'Create', 'Create': 'Create',
'Please enter the resource content': 'Please enter the resource content', 'Please enter the resource content': 'Please enter the resource content',
@ -274,6 +280,9 @@ export default {
'Edit UDF Function': 'Edit UDF Function', 'Edit UDF Function': 'Edit UDF Function',
'type': 'type', 'type': 'type',
'UDF Function Name': 'UDF Function Name', 'UDF Function Name': 'UDF Function Name',
'FILE': 'FILE',
'UDF': 'UDF',
'File Subdirectory': 'File Subdirectory',
'Please enter a function name': 'Please enter a function name', 'Please enter a function name': 'Please enter a function name',
'Package Name': 'Package Name', 'Package Name': 'Package Name',
'Please enter a Package name': 'Please enter a 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 byte': '0 means unlimited',
'0 means unlimited by count': '0 means unlimited', '0 means unlimited by count': '0 means unlimited',
'Modify User': 'Modify User', '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 Database(required)': 'Please enter Mysql Database(required)',
'Please enter Mysql Table(required)': 'Please enter Mysql Table(required)', 'Please enter Mysql Table(required)': 'Please enter Mysql Table(required)',
'Please enter Columns (Comma separated)': 'Please enter Columns (Comma separated)', '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 Parameter': 'sql参数',
'SQL Statement': 'sql语句', 'SQL Statement': 'sql语句',
'UDF Function': 'UDF函数', 'UDF Function': 'UDF函数',
'FILE': '文件',
'UDF': 'UDF',
'File Subdirectory': '文件子目录',
'Please enter a SQL Statement(required)': '请输入sql语句(必填)', 'Please enter a SQL Statement(required)': '请输入sql语句(必填)',
'Please enter a JSON Statement(required)': '请输入json语句(必填)', 'Please enter a JSON Statement(required)': '请输入json语句(必填)',
'One form or attachment must be selected': '表格附件必须勾选一个', 'One form or attachment must be selected': '表格附件必须勾选一个',
@ -139,6 +142,7 @@ export default {
'jdbc connect parameters': 'jdbc连接参数', 'jdbc connect parameters': 'jdbc连接参数',
'Test Connect': '测试连接', 'Test Connect': '测试连接',
'Please enter resource name': '请输入数据源名称', 'Please enter resource name': '请输入数据源名称',
'Please enter resource folder name': '请输入资源文件夹名称',
'Please enter a non-query SQL statement': '请输入非查询sql语句', 'Please enter a non-query SQL statement': '请输入非查询sql语句',
'Please enter IP/hostname': '请输入IP/主机名', 'Please enter IP/hostname': '请输入IP/主机名',
'jdbc connection parameters is not a correct JSON format': 'jdbc连接参数不是一个正确的JSON格式', 'jdbc connection parameters is not a correct JSON format': 'jdbc连接参数不是一个正确的JSON格式',
@ -182,6 +186,8 @@ export default {
'Authorize': '授权', 'Authorize': '授权',
'File resources': '文件资源', 'File resources': '文件资源',
'UDF resources': 'UDF资源', 'UDF resources': 'UDF资源',
'UDF resources directory': 'UDF资源目录',
'Please select UDF resources directory': '请选择UDF资源目录',
'Edit alarm group': '编辑告警组', 'Edit alarm group': '编辑告警组',
'Create alarm group': '创建告警组', 'Create alarm group': '创建告警组',
'Group Name': '组名称', 'Group Name': '组名称',
@ -224,8 +230,11 @@ export default {
'execution': '执行中', 'execution': '执行中',
'finish': '完成', 'finish': '完成',
'Create File': '创建文件', 'Create File': '创建文件',
'Create folder': '创建文件夹',
'File Name': '文件名称', 'File Name': '文件名称',
'Folder Name': '文件夹名称',
'File Format': '文件格式', 'File Format': '文件格式',
'Folder Format': '文件夹格式',
'File Content': '文件内容', 'File Content': '文件内容',
'Upload File Size': '文件大小不能超过1G', 'Upload File Size': '文件大小不能超过1G',
'Create': '创建', 'Create': '创建',
@ -523,6 +532,10 @@ export default {
'0 means unlimited by byte': 'KB0代表不限制', '0 means unlimited by byte': 'KB0代表不限制',
'0 means unlimited by count': '0代表不限制', '0 means unlimited by count': '0代表不限制',
'Modify User': '修改用户', 'Modify User': '修改用户',
'Whether directory' : '是否文件夹',
'Yes': '是',
'No': '否',
'Modify User': '修改用户',
'Please enter Mysql Database(required)': '请输入Mysql数据库(必填)', 'Please enter Mysql Database(required)': '请输入Mysql数据库(必填)',
'Please enter Mysql Table(required)': '请输入Mysql表名(必填)', 'Please enter Mysql Table(required)': '请输入Mysql表名(必填)',
'Please enter Columns (Comma separated)': '请输入列名 , 隔开', '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' import router from '@/conf/home/router'
export function setUrlParams (o) { export function setUrlParams (o) {
router.push({ // router.push({
query: merge(router.history.current.query, o) // 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
@import "mixin";
@import "animation"; @import "animation";
@import "scrollbar"; @import "scrollbar";
@import "table"; @import "table";

Loading…
Cancel
Save