Browse Source

[Feature-11195][UI] Add re-upload feature for resource files and udf files (#11203)

* add re-upload feature for resource files
* add re-upload feature for udf files

Co-authored-by: sheldonliu <sheldonliu>
3.2.0-release
Sheldon 2 years ago committed by GitHub
parent
commit
75c5915b78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/FileManagePage.java
  2. 26
      dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/UdfManagePage.java
  3. 26
      dolphinscheduler-ui/src/locales/en_US/resource.ts
  4. 28
      dolphinscheduler-ui/src/locales/zh_CN/resource.ts
  5. 1
      dolphinscheduler-ui/src/service/modules/resources/types.ts
  6. 138
      dolphinscheduler-ui/src/views/resource/components/resource/create/index.tsx
  7. 0
      dolphinscheduler-ui/src/views/resource/components/resource/create/use-create.ts
  8. 3
      dolphinscheduler-ui/src/views/resource/components/resource/create/use-form.ts
  9. 132
      dolphinscheduler-ui/src/views/resource/components/resource/edit/index.tsx
  10. 7
      dolphinscheduler-ui/src/views/resource/components/resource/edit/use-edit.ts
  11. 0
      dolphinscheduler-ui/src/views/resource/components/resource/edit/use-form.ts
  12. 6
      dolphinscheduler-ui/src/views/resource/components/resource/folder/index.tsx
  13. 0
      dolphinscheduler-ui/src/views/resource/components/resource/folder/use-folder.ts
  14. 5
      dolphinscheduler-ui/src/views/resource/components/resource/folder/use-form.ts
  15. 0
      dolphinscheduler-ui/src/views/resource/components/resource/index.module.scss
  16. 274
      dolphinscheduler-ui/src/views/resource/components/resource/index.tsx
  17. 9
      dolphinscheduler-ui/src/views/resource/components/resource/rename/index.tsx
  18. 12
      dolphinscheduler-ui/src/views/resource/components/resource/rename/use-form.ts
  19. 0
      dolphinscheduler-ui/src/views/resource/components/resource/rename/use-rename.ts
  20. 45
      dolphinscheduler-ui/src/views/resource/components/resource/table/table-action.tsx
  21. 229
      dolphinscheduler-ui/src/views/resource/components/resource/table/use-table.ts
  22. 60
      dolphinscheduler-ui/src/views/resource/components/resource/types.ts
  23. 42
      dolphinscheduler-ui/src/views/resource/components/resource/upload/index.tsx
  24. 7
      dolphinscheduler-ui/src/views/resource/components/resource/upload/use-form.ts
  25. 20
      dolphinscheduler-ui/src/views/resource/components/resource/upload/use-upload.ts
  26. 6
      dolphinscheduler-ui/src/views/resource/components/resource/use-file.ts
  27. 116
      dolphinscheduler-ui/src/views/resource/file/create/index.tsx
  28. 110
      dolphinscheduler-ui/src/views/resource/file/edit/index.tsx
  29. 349
      dolphinscheduler-ui/src/views/resource/file/index.tsx
  30. 120
      dolphinscheduler-ui/src/views/resource/file/table/use-table.ts
  31. 6
      dolphinscheduler-ui/src/views/resource/udf/function/components/function-modal.tsx
  32. 2
      dolphinscheduler-ui/src/views/resource/udf/function/components/use-modal.ts
  33. 130
      dolphinscheduler-ui/src/views/resource/udf/resource/components/folder-modal.tsx
  34. 124
      dolphinscheduler-ui/src/views/resource/udf/resource/components/upload-modal.tsx
  35. 84
      dolphinscheduler-ui/src/views/resource/udf/resource/components/use-form.ts
  36. 117
      dolphinscheduler-ui/src/views/resource/udf/resource/components/use-modal.ts
  37. 43
      dolphinscheduler-ui/src/views/resource/udf/resource/index.module.scss
  38. 206
      dolphinscheduler-ui/src/views/resource/udf/resource/index.tsx
  39. 40
      dolphinscheduler-ui/src/views/resource/udf/resource/types.ts
  40. 320
      dolphinscheduler-ui/src/views/resource/udf/resource/use-table.ts

2
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/FileManagePage.java

@ -51,7 +51,7 @@ public class FileManagePage extends NavBarPage implements ResourcePage.Tab {
@FindBy(className = "btn-create-file")
private WebElement buttonCreateFile;
@FindBy(className = "btn-upload-file")
@FindBy(className = "btn-upload-resource")
private WebElement buttonUploadFile;
private final CreateDirectoryBox createDirectoryBox;

26
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/resource/UdfManagePage.java

@ -40,7 +40,7 @@ public class UdfManagePage extends NavBarPage implements ResourcePage.Tab {
@FindBy(className = "btn-create-directory")
private WebElement buttonCreateDirectory;
@FindBy(className = "btn-upload-udf")
@FindBy(className = "btn-upload-resource")
private WebElement buttonUploadUdf;
@FindBy(className = "items")
@ -54,7 +54,7 @@ public class UdfManagePage extends NavBarPage implements ResourcePage.Tab {
private final UploadFileBox uploadFileBox;
private final RenameDirectoryBox renameDirectoryBox;
private final RenameBox renameBox;
private final CreateDirectoryBox createDirectoryBox;
@ -63,7 +63,7 @@ public class UdfManagePage extends NavBarPage implements ResourcePage.Tab {
uploadFileBox = new UploadFileBox();
renameDirectoryBox = new RenameDirectoryBox();
renameBox = new RenameBox();
createDirectoryBox = new CreateDirectoryBox();
}
@ -106,15 +106,15 @@ public class UdfManagePage extends NavBarPage implements ResourcePage.Tab {
udfList()
.stream()
.filter(it -> it.getText().contains(currentName))
.flatMap(it -> it.findElements(By.className("btn-edit")).stream())
.flatMap(it -> it.findElements(By.className("btn-rename")).stream())
.filter(WebElement::isDisplayed)
.findFirst()
.orElseThrow(() -> new RuntimeException("No rename button in udf manage list"))
.click();
renameDirectoryBox().inputName().clear();
renameDirectoryBox().inputName().sendKeys(AfterName);
renameDirectoryBox().buttonSubmit().click();
renameBox().inputName().clear();
renameBox().inputName().sendKeys(AfterName);
renameBox().buttonSubmit().click();
return this;
}
@ -135,20 +135,20 @@ public class UdfManagePage extends NavBarPage implements ResourcePage.Tab {
}
@Getter
public class RenameDirectoryBox {
RenameDirectoryBox() {
public class RenameBox {
RenameBox() {
PageFactory.initElements(driver, this);
}
@FindBys({
@FindBy(className = "input-directory-name"),
@FindBy(tagName = "input"),
@FindBy(className = "input-name"),
@FindBy(tagName = "input"),
})
private WebElement inputName;
@FindBys({
@FindBy(className = "input-description"),
@FindBy(tagName = "textarea"),
@FindBy(className = "input-description"),
@FindBy(tagName = "textarea"),
})
private WebElement inputDescription;

26
dolphinscheduler-ui/src/locales/en_US/resource.ts

@ -29,9 +29,11 @@ export default {
file_name: 'File Name',
description: 'Description',
size: 'Size',
create_time: 'Create Time',
update_time: 'Update Time',
operation: 'Operation',
edit: 'Edit',
reupload: 'ReUpload File',
rename: 'Rename',
download: 'Download',
delete: 'Delete',
@ -55,33 +57,9 @@ export default {
},
udf: {
udf_resources: 'UDF resources',
create_folder: 'Create Folder',
upload_udf_resources: 'Upload UDF Resources',
udf_source_name: 'UDF Resource Name',
user_name: 'Resource userName',
tenant_name: 'Resource tenantName',
whether_directory: 'Whether directory',
file_name: 'File Name',
file_size: 'File Size',
description: 'Description',
create_time: 'Create Time',
update_time: 'Update Time',
operation: 'Operation',
yes: 'Yes',
no: 'No',
edit: 'Edit',
rename: 'Rename',
download: 'Download',
delete: 'Delete',
delete_confirm: 'Delete?',
success: 'Success',
folder_name: 'Folder Name',
upload: 'Upload',
upload_files: 'Upload Files',
file_upload: 'File Upload',
enter_keyword_tips: 'Please enter keyword',
enter_name_tips: 'Please enter name',
enter_description_tips: 'Please enter description'
},
function: {
udf_function: 'UDF Function',

28
dolphinscheduler-ui/src/locales/zh_CN/resource.ts

@ -29,9 +29,11 @@ export default {
file_name: '文件名称',
description: '描述',
size: '大小',
create_time: '创建时间',
update_time: '更新时间',
operation: '操作',
edit: '编辑',
reupload: '重新上传文件',
rename: '重命名',
download: '下载',
delete: '删除',
@ -55,32 +57,8 @@ export default {
},
udf: {
udf_resources: 'UDF资源',
create_folder: '创建文件夹',
upload_udf_resources: '上传UDF资源',
udf_source_name: 'UDF资源名称',
user_name: '所属用户',
tenant_name: '所属租户',
whether_directory: '是否文件夹',
file_name: '文件名称',
file_size: '文件大小',
description: '描述',
create_time: '创建时间',
update_time: '更新时间',
operation: '操作',
yes: '是',
no: '否',
edit: '编辑',
download: '下载',
delete: '删除',
success: '成功',
folder_name: '文件夹名称',
upload: '上传',
upload_files: '上传文件',
file_upload: '文件上传',
delete_confirm: '确定删除吗?',
enter_keyword_tips: '请输入关键词',
enter_name_tips: '请输入名称',
enter_description_tips: '请输入描述'
udf_source_name: 'UDF资源名称'
},
function: {
udf_function: 'UDF函数',

1
dolphinscheduler-ui/src/service/modules/resources/types.ts

@ -103,6 +103,7 @@ interface ResourceFile {
fullName: string
description: string
size: number
createTime: string
updateTime: string
}

138
dolphinscheduler-ui/src/views/resource/components/resource/create/index.tsx

@ -0,0 +1,138 @@
/*
* 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.
*/
import { defineComponent, getCurrentInstance, toRefs } from 'vue'
import { useRouter } from 'vue-router'
import { NForm, NFormItem, NInput, NSelect, NButton } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Card from '@/components/card'
import MonacoEditor from '@/components/monaco-editor'
import { useCreate } from './use-create'
import { useForm } from './use-form'
import { fileTypeArr } from '@/common/common'
import styles from '../index.module.scss'
import type { Router } from 'vue-router'
export default defineComponent({
name: 'ResourceCreate',
setup() {
const router: Router = useRouter()
const { state } = useForm()
const { handleCreateFile } = useCreate(state)
const fileSuffixOptions = fileTypeArr.map((suffix) => ({
key: suffix,
label: suffix,
value: suffix
}))
const handleFile = () => {
handleCreateFile()
}
const handleReturn = () => {
router.go(-1)
}
const trim = getCurrentInstance()?.appContext.config.globalProperties.trim
return {
fileSuffixOptions,
handleFile,
handleReturn,
...toRefs(state),
trim
}
},
render() {
const { t } = useI18n()
return (
<Card title={t('resource.file.file_details')}>
<NForm
rules={this.rules}
ref='fileFormRef'
class={styles['form-content']}
>
<NFormItem label={t('resource.file.file_name')} path='fileName'>
<NInput
allowInput={this.trim}
v-model={[this.fileForm.fileName, 'value']}
placeholder={t('resource.file.enter_name_tips')}
style={{ width: '300px' }}
class='input-file-name'
/>
</NFormItem>
<NFormItem label={t('resource.file.file_format')} path='suffix'>
<NSelect
defaultValue={[this.fileForm.suffix]}
v-model={[this.fileForm.suffix, 'value']}
options={this.fileSuffixOptions}
style={{ width: '100px' }}
class='select-file-format'
/>
</NFormItem>
<NFormItem label={t('resource.file.description')} path='description'>
<NInput
allowInput={this.trim}
type='textarea'
v-model={[this.fileForm.description, 'value']}
placeholder={t('resource.file.enter_description_tips')}
style={{ width: '430px' }}
class='input-description'
/>
</NFormItem>
<NFormItem label={t('resource.file.file_content')} path='content'>
<div
style={{
width: '90%'
}}
>
<MonacoEditor v-model={[this.fileForm.content, 'value']} />
</div>
</NFormItem>
<div class={styles['file-edit-content']}>
<div class={styles.submit}>
<NButton
type='info'
size='small'
round
onClick={this.handleFile}
class='btn-submit'
>
{t('resource.file.save')}
</NButton>
<NButton
type='info'
size='small'
text
style={{ marginLeft: '15px' }}
onClick={this.handleReturn}
class='btn-cancel'
>
{t('resource.file.return')}
</NButton>
</div>
</div>
</NForm>
</Card>
)
}
})

0
dolphinscheduler-ui/src/views/resource/file/create/use-create.ts → dolphinscheduler-ui/src/views/resource/components/resource/create/use-create.ts

3
dolphinscheduler-ui/src/views/resource/file/create/use-form.ts → dolphinscheduler-ui/src/views/resource/components/resource/create/use-form.ts

@ -18,8 +18,9 @@
import { useI18n } from 'vue-i18n'
import { reactive, ref, unref } from 'vue'
import type { FormRules } from 'naive-ui'
import { ICreateFileDefaultValue } from "@/views/resource/components/resource/types";
const defaultValue = () => ({
const defaultValue: ICreateFileDefaultValue = () => ({
pid: -1,
type: 'FILE',
suffix: 'sh',

132
dolphinscheduler-ui/src/views/resource/components/resource/edit/index.tsx

@ -0,0 +1,132 @@
/*
* 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.
*/
import { useRoute, useRouter } from 'vue-router'
import { defineComponent, toRefs, watch } from 'vue'
import { NButton, NForm, NFormItem, NSpace, NSpin } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { useForm } from './use-form'
import { useEdit } from './use-edit'
import Card from '@/components/card'
import MonacoEditor from '@/components/monaco-editor'
import styles from '../index.module.scss'
export default defineComponent({
name: 'ResourceEdit',
setup() {
const route = useRoute()
const router = useRouter()
const componentName = route.name
// fullname is now the id of resources
const fullName = String(router.currentRoute.value.query.prefix || "")
const tenantCode = String(router.currentRoute.value.query.tenantCode || "")
const { state } = useForm()
const { getResourceView, handleUpdateContent } = useEdit(state)
const handleFileContent = () => {
state.fileForm.content = resourceViewRef.state.value.content
handleUpdateContent(fullName, tenantCode)
}
const handleReturn = () => {
router.go(-1)
}
const resourceViewRef = getResourceView(fullName, tenantCode)
watch(
() => resourceViewRef.state.value.content,
() => (state.fileForm.content = resourceViewRef.state.value.content)
)
return {
componentName,
resourceViewRef,
handleReturn,
handleFileContent,
...toRefs(state)
}
},
render() {
const { t } = useI18n()
return (
<Card title={t('resource.file.file_details')}>
{this.resourceViewRef.isReady.value ? (
<div class={styles['file-edit-content']}>
<h2>
<span>{this.resourceViewRef.state.value.alias}</span>
</h2>
<NForm
rules={this.rules}
ref='fileFormRef'
class={styles['form-content']}
disabled={this.componentName !== 'resource-file-edit'}
>
<NFormItem path='content'>
<MonacoEditor
v-model={[this.resourceViewRef.state.value.content, 'value']}
/>
</NFormItem>
<NSpace>
<NButton
type='info'
size='small'
text
style={{ marginRight: '15px' }}
onClick={this.handleReturn}
class='btn-cancel'
>
{t('resource.file.return')}
</NButton>
{this.componentName === 'resource-file-edit' && (
<NButton
type='info'
size='small'
round
onClick={() => this.handleFileContent()}
class='btn-submit'
>
{t('resource.file.save')}
</NButton>
)}
</NSpace>
</NForm>
</div>
) : (
<NSpace justify='center'>
<NSpace vertical>
<NSpin show={true} />
<NSpace>
<NButton
type='info'
size='small'
text
style={{ marginRight: '15px' }}
onClick={this.handleReturn}
class='btn-cancel'
>
{t('resource.file.return')}
</NButton>
</NSpace>
</NSpace>
</NSpace>
)}
</Card>
)
}
})

7
dolphinscheduler-ui/src/views/resource/file/edit/use-edit.ts → dolphinscheduler-ui/src/views/resource/components/resource/edit/use-edit.ts

@ -16,13 +16,10 @@
*/
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import type { Router } from 'vue-router'
import { useRouter } from 'vue-router'
import { useAsyncState } from '@vueuse/core'
import {
viewResource,
updateResourceContent
} from '@/service/modules/resources'
import { updateResourceContent, viewResource } from '@/service/modules/resources'
export function useEdit(state: any) {
const { t } = useI18n()

0
dolphinscheduler-ui/src/views/resource/file/edit/use-form.ts → dolphinscheduler-ui/src/views/resource/components/resource/edit/use-form.ts

6
dolphinscheduler-ui/src/views/resource/file/folder/index.tsx → dolphinscheduler-ui/src/views/resource/components/resource/folder/index.tsx

@ -22,11 +22,16 @@ import Modal from '@/components/modal'
import { noSpace } from '@/utils/trim'
import { useForm } from './use-form'
import { useFolder } from './use-folder'
import { ResourceType } from "@/views/resource/components/resource/types";
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false
},
resourceType: {
type: String as PropType<ResourceType>,
default: undefined
}
}
@ -43,6 +48,7 @@ export default defineComponent({
}
const handleFolder = () => {
state.folderForm.type = props.resourceType!
handleCreateFolder(ctx.emit, hideModal, resetForm)
}

0
dolphinscheduler-ui/src/views/resource/file/folder/use-folder.ts → dolphinscheduler-ui/src/views/resource/components/resource/folder/use-folder.ts

5
dolphinscheduler-ui/src/views/resource/file/folder/use-form.ts → dolphinscheduler-ui/src/views/resource/components/resource/folder/use-form.ts

@ -18,10 +18,11 @@
import { reactive, ref, unref } from 'vue'
import { useI18n } from 'vue-i18n'
import type { FormRules } from 'naive-ui'
import { IFolderDefaultValue } from "@/views/resource/components/resource/types";
const defaultValue = () => ({
const defaultValue: IFolderDefaultValue = () => ({
pid: -1,
type: 'FILE',
type: undefined!,
name: '',
description: '',
currentDir: '/'

0
dolphinscheduler-ui/src/views/resource/file/index.module.scss → dolphinscheduler-ui/src/views/resource/components/resource/index.module.scss

274
dolphinscheduler-ui/src/views/resource/components/resource/index.tsx

@ -0,0 +1,274 @@
/*
* 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.
*/
import { useRouter } from 'vue-router'
import {
defineComponent,
onMounted,
ref,
getCurrentInstance,
PropType,
toRefs
} from 'vue'
import {
NIcon,
NSpace,
NDataTable,
NButtonGroup,
NButton,
NPagination,
NBreadcrumb,
NBreadcrumbItem
} from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { SearchOutlined } from '@vicons/antd'
import { useTable } from './table/use-table'
import { useFileStore } from '@/store/file/file'
import Card from '@/components/card'
import ResourceFolderModal from './folder'
import ResourceUploadModal from './upload'
import ResourceRenameModal from './rename'
import styles from './index.module.scss'
import type { Router } from 'vue-router'
import Search from "@/components/input-search"
import { ResourceType } from "@/views/resource/components/resource/types";
const props = {
resourceType: {
type: String as PropType<ResourceType>,
default: undefined
}
}
export default defineComponent({
name: 'ResourceList',
props,
setup(props) {
const router: Router = useRouter()
const fileStore = useFileStore()
const breadListRef = ref<Array<string>>()
const {
variables,
columnsRef,
tableWidth,
requestData,
updateList,
handleCreateFile,
} = useTable()
variables.resourceType = props.resourceType
const handleUpdatePage = (page: number) => {
variables.pagination.page = page
requestData()
}
const handleUpdatePageSize = (pageSize: number) => {
variables.pagination.page = 1
variables.pagination.pageSize = pageSize
requestData()
}
const handleConditions = () => {
requestData()
}
const handleCreateFolder = () => {
variables.folderShowRef = true
}
const handleUploadFile = () => {
variables.isReupload = false
variables.uploadShowRef = true
}
const handleRenameFile = () => {
variables.renameShowRef = true
}
onMounted(() => {
fileStore.setCurrentDir(variables.fullName)
breadListRef.value = fileStore.getCurrentDir.replace(/\/+$/g, '')
.split('/').slice(2) as Array<string>
requestData()
})
const trim = getCurrentInstance()?.appContext.config.globalProperties.trim
const handleBread = (index: number) => {
const breadName = variables.fullName.split('/').slice(0, index+3).join('/')+'/'
goBread(breadName)
}
const goBread = (fullName: string) => {
const { resourceType, tenantCode } = variables
if (fullName === '') {
router.push({ name: resourceType === 'UDF' ? 'resource-manage' : 'file-manage' })
} else {
router.push({
name: resourceType === 'UDF' ? 'resource-sub-manage' : 'resource-file-subdirectory',
query: { prefix: fullName, tenantCode: tenantCode}
})
}
}
return {
breadListRef,
columnsRef,
tableWidth,
updateList,
handleConditions,
handleCreateFolder,
handleCreateFile,
handleUploadFile,
handleRenameFile,
handleUpdatePage,
handleUpdatePageSize,
handleBread,
trim,
...toRefs(variables)
}
},
render() {
const { t } = useI18n()
const {
handleConditions,
handleCreateFolder,
handleCreateFile,
handleUploadFile,
columnsRef,
tableWidth,
} = this
const manageTitle = this.resourceType === 'UDF'
? t('resource.udf.udf_resources')
: t('resource.file.file_manage')
return (
<NSpace vertical>
<Card>
<NSpace justify='space-between'>
<NButtonGroup size='small'>
<NButton
type='primary'
onClick={handleCreateFolder}
class='btn-create-directory'
>
{t('resource.file.create_folder')}
</NButton>
{this.resourceType !== 'UDF' &&
<NButton onClick={handleCreateFile} class='btn-create-file'>
{t('resource.file.create_file')}
</NButton>
}
<NButton onClick={handleUploadFile} class='btn-upload-resource'>
{this.resourceType === 'UDF'
? t('resource.udf.upload_udf_resources')
: t('resource.file.upload_files')
}
</NButton>
</NButtonGroup>
<NSpace>
<Search
placeholder = {t('resource.file.enter_keyword_tips')}
v-model:value={this.searchRef}
onSearch={handleConditions}
/>
<NButton size='small' type='primary' onClick={handleConditions}>
<NIcon>
<SearchOutlined/>
</NIcon>
</NButton>
</NSpace>
</NSpace>
</Card>
<Card title={manageTitle}>
{{
header: () => (
<NBreadcrumb separator='>'>
{this.breadListRef?.map((item, index) => (
<NBreadcrumbItem>
<NButton
text
disabled={index > 0 && index === this.breadListRef!.length - 1}
onClick={() => this.handleBread(index)}
>{index === 0 ? manageTitle : item}
</NButton>
</NBreadcrumbItem>
))}
</NBreadcrumb>
),
default: () => (
<NSpace vertical>
<NDataTable
remote
columns={columnsRef}
data={this.resourceList?.table}
striped
size={'small'}
class={styles['table-box']}
row-class-name='items'
scrollX={tableWidth}
/>
<NSpace justify='center'>
<NPagination
v-model:page={this.pagination.page}
v-model:pageSize={this.pagination.pageSize}
pageSizes={this.pagination.pageSizes}
item-count={this.pagination.itemCount}
onUpdatePage={this.handleUpdatePage}
onUpdatePageSize={this.handleUpdatePageSize}
show-quick-jumper
show-size-picker
/>
</NSpace>
</NSpace>
)
}}
</Card>
<ResourceFolderModal
v-model:show={this.folderShowRef}
resourceType={this.resourceType}
onUpdateList={this.updateList}
/>
<ResourceUploadModal
v-model:show={this.uploadShowRef}
isReupload={this.isReupload}
resourceType={this.resourceType}
name={this.reuploadInfo.name}
fullName={this.reuploadInfo.fullName}
description={this.reuploadInfo.description}
userName={this.reuploadInfo.user_name}
onUpdateList={this.updateList}
/>
<ResourceRenameModal
v-model:show={this.renameShowRef}
resourceType={this.resourceType}
name={this.renameInfo.name}
fullName={this.renameInfo.fullName}
description={this.renameInfo.description}
userName={this.renameInfo.user_name}
onUpdateList={this.updateList}
/>
</NSpace>
)
}
})

9
dolphinscheduler-ui/src/views/resource/file/rename/index.tsx → dolphinscheduler-ui/src/views/resource/components/resource/rename/index.tsx

@ -26,12 +26,17 @@ import { useI18n } from 'vue-i18n'
import Modal from '@/components/modal'
import { useForm } from './use-form'
import { useRename } from './use-rename'
import type { ResourceType } from "@/views/resource/components/resource/types";
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false
},
resourceType: {
type: String as PropType<ResourceType>,
default: undefined
},
name: {
type: String as PropType<string>,
default: ''
@ -47,7 +52,7 @@ const props = {
userName: {
type: String as PropType<string>,
default: ''
},
}
}
export default defineComponent({
@ -55,7 +60,7 @@ export default defineComponent({
props,
emits: ['updateList', 'update:show'],
setup(props, ctx) {
const { state, resetForm } = useForm(props.fullName, props.name, props.description, props.userName)
const { state, resetForm } = useForm(props.resourceType!,props.fullName, props.name, props.description, props.userName)
const { handleRenameFile } = useRename(state)
const hideModal = () => {
ctx.emit('update:show', false)

12
dolphinscheduler-ui/src/views/resource/file/rename/use-form.ts → dolphinscheduler-ui/src/views/resource/components/resource/rename/use-form.ts

@ -18,25 +18,25 @@
import { reactive, ref, unref } from 'vue'
import { useI18n } from 'vue-i18n'
import type { FormRules } from 'naive-ui'
import { IRenameDefaultValue, ResourceType } from "@/views/resource/components/resource/types";
const defaultValue = (fullName = '',name = '', description = '', user_name = '') => ({
id: -1,
const defaultValue:IRenameDefaultValue = (type: ResourceType, fullName = '', name = '', description = '', user_name = '') => ({
fullName,
name,
type: 'FILE',
type: type,
description,
user_name
})
export function useForm(fullName: string, name: string, description: string, user_name: string) {
export function useForm(resourceType: ResourceType, fullName: string, name: string, description: string, user_name: string) {
const { t } = useI18n()
const resetForm = () => {
state.renameForm = Object.assign(unref(state.renameForm), defaultValue())
state.renameForm = Object.assign(unref(state.renameForm), defaultValue(resourceType))
}
const state = reactive({
renameFormRef: ref(),
renameForm: defaultValue(fullName, name, description, user_name),
renameForm: defaultValue(resourceType,fullName, name, description, user_name),
saving: false,
rules: {
name: {

0
dolphinscheduler-ui/src/views/resource/file/rename/use-rename.ts → dolphinscheduler-ui/src/views/resource/components/resource/rename/use-rename.ts

45
dolphinscheduler-ui/src/views/resource/file/table/table-action.tsx → dolphinscheduler-ui/src/views/resource/components/resource/table/table-action.tsx

@ -23,11 +23,12 @@ import {
DownloadOutlined,
FormOutlined,
EditOutlined,
InfoCircleFilled
InfoCircleFilled,
UploadOutlined
} from '@vicons/antd'
import _ from 'lodash'
import { useI18n } from 'vue-i18n'
import { ResourceFileTableData, IRenameFile, IRtDisb } from '../types'
import { ResourceFileTableData, IRenameResource, IRtDisb, IReuploadResource } from '../types'
import { fileTypeArr } from '@/common/common'
import { downloadResource, deleteResource } from '@/service/modules/resources'
import type { Router } from 'vue-router'
@ -50,7 +51,7 @@ const props = {
export default defineComponent({
name: 'TableAction',
props,
emits: ['updateList', 'renameResource'],
emits: ['updateList', 'reuploadResource', 'renameResource'],
setup(props, { emit }) {
const { t } = useI18n()
const router: Router = useRouter()
@ -70,7 +71,11 @@ export default defineComponent({
deleteResource(fullNameObj).then(() => emit('updateList'))
}
const handleRenameFile: IRenameFile = (name: string, description: string, fullName: string, user_name: string) => {
const handleReuploadFile: IReuploadResource = (name: string, description: string, fullName: string, user_name: string) => {
emit('reuploadResource', name, description, fullName, user_name)
}
const handleRenameFile: IRenameResource = (name: string, description: string, fullName: string, user_name: string) => {
emit('renameResource', name, description, fullName, user_name)
}
@ -79,6 +84,7 @@ export default defineComponent({
rtDisb,
handleEditFile,
handleDeleteFile,
handleReuploadFile,
handleRenameFile,
...props
}
@ -87,6 +93,7 @@ export default defineComponent({
const { t } = useI18n()
return (
<NSpace>
{ this.row.type !== 'UDF' &&
<NTooltip trigger={'hover'}>
{{
default: () => t('resource.file.edit'),
@ -110,6 +117,34 @@ export default defineComponent({
)
}}
</NTooltip>
}
<NTooltip trigger={'hover'}>
{{
default: () => t('resource.file.reupload'),
trigger: () => (
<NButton
size='tiny'
type='info'
onClick={() =>
this.handleReuploadFile(
this.row.name,
this.row.description,
this.row.fullName,
this.row.user_name
)
}
disabled={!!this.row?.directory}
style={{ marginRight: '-5px' }}
circle
class='btn-reupload'
>
<NIcon>
<UploadOutlined/>
</NIcon>
</NButton>
)
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () => t('resource.file.rename'),
@ -145,7 +180,7 @@ export default defineComponent({
<NButton
size='tiny'
type='info'
disabled={this.row?.directory ? true : false}
disabled={!!this.row?.directory}
tag='div'
circle
style={{ marginRight: '-5px' }}

229
dolphinscheduler-ui/src/views/resource/components/resource/table/use-table.ts

@ -0,0 +1,229 @@
/*
* 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.
*/
import { h, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { bytesToSize } from '@/common/common'
import TableAction from './table-action'
import { IRenameResource, IReuploadResource, ResourceFileTableData, ResourceType } from '../types'
import ButtonLink from '@/components/button-link'
import { NEllipsis } from 'naive-ui'
import {
COLUMN_WIDTH_CONFIG,
calculateTableWidth,
DefaultTableWidth
} from '@/common/column-width-config'
import type { Router } from 'vue-router'
import type { TableColumns } from 'naive-ui/es/data-table/src/interface'
import { useFileState } from "@/views/resource/components/resource/use-file";
const goSubFolder = (router: Router, item: ResourceFileTableData) => {
if (item.directory) {
router.push({
name: item.type === 'UDF' ? 'resource-sub-manage' : 'resource-file-subdirectory',
query: { prefix: item.fullName, tenantCode: item.user_name}
})
} else if (item.type === 'FILE') {
router.push({ name: 'resource-file-list', query: {prefix: item.fullName, tenantCode: item.user_name}} )
}
}
export function useTable() {
const { t } = useI18n()
const router: Router = useRouter()
const variables = reactive({
fullName: ref(String(router.currentRoute.value.query.prefix || "")),
tenantCode: ref(String(router.currentRoute.value.query.tenantCode || "")),
resourceType: ref<ResourceType>(),
resourceList: ref(),
folderShowRef: ref(false),
uploadShowRef: ref(false),
isReupload: ref(false),
renameShowRef: ref(false),
searchRef: ref(),
renameInfo: ref({
name: '',
description: '',
fullName: '',
user_name: ''
}),
reuploadInfo: ref({
name: '',
description: '',
fullName: '',
user_name: ''
}),
pagination: ref({
page: 1,
pageSize: 10,
itemCount: 0,
pageSizes: [10, 30, 50]
})
})
const columnsRef: TableColumns<any> = [
{
title: '#',
key: 'id',
...COLUMN_WIDTH_CONFIG['index'],
render: (_row, index) => index + 1
},
{
title: t('resource.file.name'),
key: 'name',
...COLUMN_WIDTH_CONFIG['linkName'],
render: (row) => {
return !row.directory
? row.alias
: h(
ButtonLink,
{
onClick: () => goSubFolder(router, row)
},
{
default: () =>
h(
NEllipsis,
COLUMN_WIDTH_CONFIG['linkEllipsis'],
() => row.alias
)
}
)
}
},
{
title: t('resource.file.tenant_name'),
...COLUMN_WIDTH_CONFIG['userName'],
key: 'user_name'
},
{
title: t('resource.file.whether_directory'),
key: 'whether_directory',
...COLUMN_WIDTH_CONFIG['yesOrNo'],
render: (row) =>
row.directory ? t('resource.file.yes') : t('resource.file.no')
},
{
title: t('resource.file.file_name'),
...COLUMN_WIDTH_CONFIG['name'],
key: 'file_name'
},
{
title: t('resource.file.description'),
...COLUMN_WIDTH_CONFIG['note'],
key: 'description'
},
{
title: t('resource.file.size'),
key: 'size',
...COLUMN_WIDTH_CONFIG['size'],
render: (row) => bytesToSize(row.size)
},
{
title: t('resource.file.create_time'),
...COLUMN_WIDTH_CONFIG['time'],
key: 'create_time'
},
{
title: t('resource.file.update_time'),
...COLUMN_WIDTH_CONFIG['time'],
key: 'update_time'
},
{
title: t('resource.file.operation'),
key: 'operation',
render: (row) =>
h(TableAction, {
row,
onReuploadResource: ( name, description, fullName, user_name ) =>
reuploadResource( name, description, fullName, user_name ),
onRenameResource: ( name, description, fullName, user_name ) =>
renameResource( name, description, fullName, user_name ),
onUpdateList: () => updateList()
}),
...COLUMN_WIDTH_CONFIG['operation'](variables.resourceType === 'UDF' ? 4 : 5)
}
]
const createFile = () => {
const { fullName } = variables
const name = fullName
? 'resource-subfile-create'
: 'resource-file-create'
router.push({
name,
params: { id: fullName}
})
}
const reuploadResource: IReuploadResource = ( name, description, fullName, user_name ) => {
variables.reuploadInfo = {
name: name,
description: description,
fullName: fullName,
user_name:user_name
}
variables.isReupload = true
variables.uploadShowRef = true
}
const renameResource: IRenameResource = ( name, description, fullName, user_name ) => {
variables.renameInfo = {
name: name,
description: description,
fullName: fullName,
user_name:user_name
}
variables.renameShowRef = true
}
const setPagination = (count: number) => {
variables.pagination.itemCount = count
}
const { getResourceListState } = useFileState(setPagination)
const requestData = () => {
variables.resourceList = getResourceListState(
variables.resourceType!,
variables.fullName,
variables.tenantCode,
variables.searchRef,
variables.pagination.page,
variables.pagination.pageSize
)
}
const updateList = () => {
variables.pagination.page = 1
requestData()
}
return {
variables,
columnsRef,
tableWidth: calculateTableWidth(columnsRef) || DefaultTableWidth,
requestData,
updateList,
handleCreateFile: createFile,
}
}

60
dolphinscheduler-ui/src/views/resource/file/types.ts → dolphinscheduler-ui/src/views/resource/components/resource/types.ts

@ -15,6 +15,8 @@
* limitations under the License.
*/
export type ResourceType = 'FILE' | 'UDF'
export interface ResourceFileTableData {
name: string
fullName: string
@ -23,6 +25,7 @@ export interface ResourceFileTableData {
file_name: string
description: string
size: number
type: ResourceType
update_time: string
}
@ -30,15 +33,20 @@ export interface IEmit {
(event: any, ...args: any[]): void
}
export interface IRenameFile {
export interface IReuploadResource {
(name: string, description: string, fullName: string, user_name: string): void
}
export interface IRenameResource {
(name: string, description: string, fullName: string, user_name: string): void
}
export interface IRtDisb {
(name: string, size: number): boolean
}
export interface IResourceListState {
(searchVal?: string, fullName?: string, tenantCode?: string, pageNo?: number, pageSize?: number): any
(type: ResourceType, searchVal?: string, fullName?: string, tenantCode?: string, pageNo?: number, pageSize?: number): any
}
export interface BasicTableProps {
@ -70,3 +78,51 @@ export interface BreadcrumbItem {
fullName: string
userName: string
}
export interface ICreateFileDefaultValue {
(): {
pid: number,
type: ResourceType,
suffix: string,
fileName: string,
description: string,
content: string,
currentDir: string
}
}
export interface IFolderDefaultValue {
():
{
pid: number,
type: ResourceType,
name: string,
description: string,
currentDir: string
}
}
export interface IRenameDefaultValue {
(
type: ResourceType,
fullName?: string,
name?: string,
description?: string,
user_name?: string
): {fullName: string, name: string, type: ResourceType, description: string, user_name: string}
}
export interface IUploadDefaultValue {
():
{
isReupload: boolean,
fullName: string,
user_name: string,
name: string,
file: string,
description: string,
type: ResourceType,
pid: number,
currentDir: string
}
}

42
dolphinscheduler-ui/src/views/resource/file/upload/index.tsx → dolphinscheduler-ui/src/views/resource/components/resource/upload/index.tsx

@ -14,18 +14,43 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, toRefs, PropType, getCurrentInstance } from 'vue'
import { defineComponent, toRefs, PropType, getCurrentInstance, watch } from 'vue'
import { NButton, NForm, NFormItem, NInput, NUpload } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Modal from '@/components/modal'
import { useForm } from './use-form'
import { useUpload } from './use-upload'
import { ResourceType } from "@/views/resource/components/resource/types";
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false
},
resourceType: {
type: String as PropType<ResourceType>,
default: undefined
},
isReupload: {
type: Boolean as PropType<boolean>,
default: false
},
name: {
type: String as PropType<string>,
default: ''
},
description: {
type: String as PropType<string>,
default: ''
},
fullName: {
type: String as PropType<string>,
default: ''
},
userName: {
type: String as PropType<string>,
default: ''
}
}
@ -59,6 +84,21 @@ export default defineComponent({
const trim = getCurrentInstance()?.appContext.config.globalProperties.trim
watch(
() => props.show,
() => {
state.uploadForm.type = props.resourceType!
state.uploadForm.isReupload = props.isReupload
if (props.isReupload && props.show) {
state.uploadForm.fullName = props.fullName
state.uploadForm.name = props.name
state.uploadForm.description = props.description
state.uploadForm.user_name = props.userName
}
}
)
return {
hideModal,
customRequest,

7
dolphinscheduler-ui/src/views/resource/file/upload/use-form.ts → dolphinscheduler-ui/src/views/resource/components/resource/upload/use-form.ts

@ -18,11 +18,16 @@
import { reactive, ref, unref } from 'vue'
import { useI18n } from 'vue-i18n'
import type { FormRules } from 'naive-ui'
import { IUploadDefaultValue } from "@/views/resource/components/resource/types";
const defaultValue = () => ({
const defaultValue: IUploadDefaultValue = () => ({
isReupload: false,
fullName: '',
user_name: '',
name: '',
file: '',
description: '',
type: undefined!,
pid: -1,
currentDir: '/'
})

20
dolphinscheduler-ui/src/views/resource/file/upload/use-upload.ts → dolphinscheduler-ui/src/views/resource/components/resource/upload/use-upload.ts

@ -18,7 +18,7 @@
import { useI18n } from 'vue-i18n'
import { IEmit } from '../types'
import { useFileStore } from '@/store/file/file'
import { createResource } from '@/service/modules/resources'
import { createResource, updateResource } from '@/service/modules/resources'
export function useUpload(state: any) {
const { t } = useI18n()
@ -34,16 +34,24 @@ export function useUpload(state: any) {
if (state.saving) return
state.saving = true
try {
// no more pid, as currentDir acts as the pid or parent path right now.
const currentDir = fileStore.getCurrentDir || '/'
const formData = new FormData()
formData.append('file', state.uploadForm.file)
formData.append('type', 'FILE')
formData.append('type', state.uploadForm.type)
formData.append('name', state.uploadForm.name)
formData.append('currentDir', currentDir)
formData.append('description', state.uploadForm.description)
await createResource(formData as any)
if (state.uploadForm.isReupload) {
formData.append('user_name', state.uploadForm.user_name)
formData.append('fullName', state.uploadForm.fullName)
formData.append('tenantCode', state.uploadForm.user_name)
await updateResource(formData as any)
} else {
// no more pid, as currentDir acts as the pid or parent path right now.
const currentDir = fileStore.getCurrentDir || '/'
formData.append('currentDir', currentDir)
await createResource(formData as any)
}
window.$message.success(t('resource.file.success'))
state.saving = false
emit('updateList')

6
dolphinscheduler-ui/src/views/resource/file/use-file.ts → dolphinscheduler-ui/src/views/resource/components/resource/use-file.ts

@ -21,12 +21,13 @@ import {
viewResource
} from '@/service/modules/resources'
import type { ResourceListRes } from '@/service/modules/resources/types'
import { IResourceListState, ISetPagination } from './types'
import { IResourceListState, ISetPagination, ResourceType } from './types'
export function useFileState(
setPagination: ISetPagination = {} as ISetPagination
) {
const getResourceListState: IResourceListState = (
type: ResourceType,
fullName = '',
tenantCode = '',
searchVal = '',
@ -37,7 +38,7 @@ export function useFileState(
queryResourceListPaging({
fullName,
tenantCode,
type: 'FILE',
type: type,
searchVal,
pageNo,
pageSize
@ -56,6 +57,7 @@ export function useFileState(
file_name: item.fileName,
description: item.description,
size: item.size,
create_time: item.createTime,
update_time: item.updateTime
}
})

116
dolphinscheduler-ui/src/views/resource/file/create/index.tsx

@ -15,124 +15,14 @@
* limitations under the License.
*/
import { defineComponent, getCurrentInstance, toRefs } from 'vue'
import { useRouter } from 'vue-router'
import { NForm, NFormItem, NInput, NSelect, NButton } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Card from '@/components/card'
import MonacoEditor from '@/components/monaco-editor'
import { useCreate } from './use-create'
import { useForm } from './use-form'
import { fileTypeArr } from '@/common/common'
import styles from '../index.module.scss'
import type { Router } from 'vue-router'
import { defineComponent } from 'vue'
import ResourceCreateModal from '../../components/resource/create'
export default defineComponent({
name: 'ResourceFileCreate',
setup() {
const router: Router = useRouter()
const { state } = useForm()
const { handleCreateFile } = useCreate(state)
const fileSuffixOptions = fileTypeArr.map((suffix) => ({
key: suffix,
label: suffix,
value: suffix
}))
const handleFile = () => {
handleCreateFile()
}
const handleReturn = () => {
router.go(-1)
}
const trim = getCurrentInstance()?.appContext.config.globalProperties.trim
return {
fileSuffixOptions,
handleFile,
handleReturn,
...toRefs(state),
trim
}
},
render() {
const { t } = useI18n()
return (
<Card title={t('resource.file.file_details')}>
<NForm
rules={this.rules}
ref='fileFormRef'
class={styles['form-content']}
>
<NFormItem label={t('resource.file.file_name')} path='fileName'>
<NInput
allowInput={this.trim}
v-model={[this.fileForm.fileName, 'value']}
placeholder={t('resource.file.enter_name_tips')}
style={{ width: '300px' }}
class='input-file-name'
/>
</NFormItem>
<NFormItem label={t('resource.file.file_format')} path='suffix'>
<NSelect
defaultValue={[this.fileForm.suffix]}
v-model={[this.fileForm.suffix, 'value']}
options={this.fileSuffixOptions}
style={{ width: '100px' }}
class='select-file-format'
/>
</NFormItem>
<NFormItem label={t('resource.file.description')} path='description'>
<NInput
allowInput={this.trim}
type='textarea'
v-model={[this.fileForm.description, 'value']}
placeholder={t('resource.file.enter_description_tips')}
style={{ width: '430px' }}
class='input-description'
/>
</NFormItem>
<NFormItem label={t('resource.file.file_content')} path='content'>
<div
style={{
width: '90%'
}}
>
<MonacoEditor v-model={[this.fileForm.content, 'value']} />
</div>
</NFormItem>
<div class={styles['file-edit-content']}>
<div class={styles.submit}>
<NButton
type='info'
size='small'
round
onClick={this.handleFile}
class='btn-submit'
>
{t('resource.file.save')}
</NButton>
<NButton
type='info'
size='small'
text
style={{ marginLeft: '15px' }}
onClick={this.handleReturn}
class='btn-cancel'
>
{t('resource.file.return')}
</NButton>
</div>
</div>
</NForm>
</Card>
)
return <ResourceCreateModal/>
}
})

110
dolphinscheduler-ui/src/views/resource/file/edit/index.tsx

@ -15,118 +15,14 @@
* limitations under the License.
*/
import { useRoute, useRouter } from 'vue-router'
import { defineComponent, toRefs, watch } from 'vue'
import { NButton, NForm, NFormItem, NSpace, NSpin } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { useForm } from './use-form'
import { useEdit } from './use-edit'
import Card from '@/components/card'
import MonacoEditor from '@/components/monaco-editor'
import styles from '../index.module.scss'
import { defineComponent } from 'vue'
import ResourceEditModal from '../../components/resource/edit'
export default defineComponent({
name: 'ResourceFileEdit',
setup() {
const route = useRoute()
const router = useRouter()
const componentName = route.name
// fullname is now the id of resources
const fullName = String(router.currentRoute.value.query.prefix || "")
const tenantCode = String(router.currentRoute.value.query.tenantCode || "")
const { state } = useForm()
const { getResourceView, handleUpdateContent } = useEdit(state)
const handleFileContent = () => {
state.fileForm.content = resourceViewRef.state.value.content
handleUpdateContent(fullName, tenantCode)
}
const handleReturn = () => {
router.go(-1)
}
const resourceViewRef = getResourceView(fullName, tenantCode)
watch(
() => resourceViewRef.state.value.content,
() => (state.fileForm.content = resourceViewRef.state.value.content)
)
return {
componentName,
resourceViewRef,
handleReturn,
handleFileContent,
...toRefs(state)
}
},
render() {
const { t } = useI18n()
return (
<Card title={t('resource.file.file_details')}>
{this.resourceViewRef.isReady.value ? (
<div class={styles['file-edit-content']}>
<h2>
<span>{this.resourceViewRef.state.value.alias}</span>
</h2>
<NForm
rules={this.rules}
ref='fileFormRef'
class={styles['form-content']}
disabled={this.componentName !== 'resource-file-edit'}
>
<NFormItem path='content'>
<MonacoEditor
v-model={[this.resourceViewRef.state.value.content, 'value']}
/>
</NFormItem>
<NSpace>
<NButton
type='info'
size='small'
text
style={{ marginRight: '15px' }}
onClick={this.handleReturn}
class='btn-cancel'
>
{t('resource.file.return')}
</NButton>
{this.componentName === 'resource-file-edit' && (
<NButton
type='info'
size='small'
round
onClick={() => this.handleFileContent()}
class='btn-submit'
>
{t('resource.file.save')}
</NButton>
)}
</NSpace>
</NForm>
</div>
) : (
<NSpace justify='center'>
<NSpace vertical>
<NSpin show={true} />
<NSpace>
<NButton
type='info'
size='small'
text
style={{ marginRight: '15px' }}
onClick={this.handleReturn}
class='btn-cancel'
>
{t('resource.file.return')}
</NButton>
</NSpace>
</NSpace>
</NSpace>
)}
</Card>
)
return <ResourceEditModal/>
}
})

349
dolphinscheduler-ui/src/views/resource/file/index.tsx

@ -15,355 +15,16 @@
* limitations under the License.
*/
import { useRouter } from 'vue-router'
import {
defineComponent,
onMounted,
ref,
reactive,
Ref,
getCurrentInstance
} from 'vue'
import {
NIcon,
NSpace,
NDataTable,
NButtonGroup,
NButton,
NPagination,
NBreadcrumb,
NBreadcrumbItem
} from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { SearchOutlined } from '@vicons/antd'
import { useTable } from './table/use-table'
import { useFileState } from './use-file'
import { BreadcrumbItem, IRenameFile } from './types'
import { useFileStore } from '@/store/file/file'
import {
queryCurrentResourceByFullName,
queryCurrentResourceByFileName
} from '@/service/modules/resources'
import Card from '@/components/card'
import ResourceFolderModal from './folder'
import ResourceUploadModal from './upload'
import ResourceRenameModal from './rename'
import styles from './index.module.scss'
import type { ResourceFile } from '@/service/modules/resources/types'
import type { Router } from 'vue-router'
import Search from "@/components/input-search";
import { defineComponent } from 'vue'
import ResourceListModal from '../components/resource'
export default defineComponent({
name: 'File',
setup() {
const router: Router = useRouter()
const fullName = ref(String(router.currentRoute.value.query.prefix || ""))
const tenantCode = ref(String(router.currentRoute.value.query.tenantCode || ""))
const resourceListRef = ref()
const folderShowRef = ref(false)
const uploadShowRef = ref(false)
const renameShowRef = ref(false)
const searchRef = ref()
const renameInfo = reactive({
name: '',
description: '',
fullName: '',
user_name: ''
})
const paginationReactive = reactive({
page: 1,
pageSize: 10,
itemCount: 0,
pageSizes: [10, 30, 50]
})
const handleUpdatePage = (page: number) => {
paginationReactive.page = page
resourceListRef.value = getResourceListState(
fullName.value,
tenantCode.value,
searchRef.value,
paginationReactive.page,
paginationReactive.pageSize
)
}
const handleUpdatePageSize = (pageSize: number) => {
paginationReactive.page = 1
paginationReactive.pageSize = pageSize
resourceListRef.value = getResourceListState(
fullName.value,
tenantCode.value,
searchRef.value,
paginationReactive.page,
paginationReactive.pageSize
)
}
const handleShowModal = (showRef: Ref<Boolean>) => {
showRef.value = true
}
const setPagination = (count: number) => {
paginationReactive.itemCount = count
}
const { getResourceListState } = useFileState(setPagination)
const handleConditions = () => {
resourceListRef.value = getResourceListState(
fullName.value,
tenantCode.value,
searchRef.value
)
}
const handleCreateFolder = () => {
handleShowModal(folderShowRef)
}
const handleCreateFile = () => {
const name = fullName.value
? 'resource-subfile-create'
: 'resource-file-create'
router.push({
name,
params: { id: fullName.value }
})
}
const handleUploadFile = () => {
handleShowModal(uploadShowRef)
}
const handleRenameFile: IRenameFile = (name: string, description: string, fullName: string, user_name: string) => {
renameInfo.fullName = fullName
renameInfo.name = name
renameInfo.description = description
renameInfo.user_name = user_name
handleShowModal(renameShowRef)
}
const handleGoRoot = () => {
router.push({
name: 'file-manage'
})
}
const updateList = () => {
resourceListRef.value = getResourceListState(
fullName.value,
tenantCode.value,
searchRef.value
)
}
const fileStore = useFileStore()
onMounted(() => {
resourceListRef.value = getResourceListState(fullName.value, tenantCode.value,searchRef.value)
})
const breadcrumbItemsRef: Ref<Array<BreadcrumbItem> | undefined> = ref([
{
id: 1,
fullName: 'l1',
userName: 'u1'
},
{
id: 2,
fullName: 'l2',
userName: 'u2'
},
{
id: 4,
fullName: 'l3',
userName: 'u3'
}
])
const trim = getCurrentInstance()?.appContext.config.globalProperties.trim
onMounted(() => {
const currfullName = String(router.currentRoute.value.query.prefix || "")
if (currfullName === "") {
fileStore.setCurrentDir('/')
} else {
fileStore.setCurrentDir(currfullName)
}
})
const initBreadcrumb = async (dirs: string[]) => {
for (let index = 0; index < dirs.length; index ++) {
const newDir = dirs.slice(0, index + 1).join('/')
const resource = await queryCurrentResourceByFileName(
{
type: 'FILE',
fileName: newDir+"/",
tenantCode: tenantCode.value
}
)
breadcrumbItemsRef.value?.push({ id: resource.fullName, fullName: resource.alias, userName: resource.userName })
}
}
onMounted(() => {
breadcrumbItemsRef.value = []
if (fullName.value != "") {
breadcrumbItemsRef.value?.push({ id: 0, fullName: 'Root', userName: '' })
queryCurrentResourceByFullName(
{
type: 'FILE',
fullName: fullName.value,
tenantCode: tenantCode.value,
}
).then((res: ResourceFile) => {
if (res.fileName) {
const dirs = res.fileName.split('/')
if (dirs && dirs.length > 1) {
dirs.pop()
initBreadcrumb(dirs)
}
}
})
}
})
return {
fullName,
searchRef,
folderShowRef,
uploadShowRef,
renameShowRef,
handleShowModal,
resourceListRef,
updateList,
handleConditions,
handleCreateFolder,
handleCreateFile,
handleUploadFile,
handleRenameFile,
handleUpdatePage,
handleUpdatePageSize,
handleGoRoot,
pagination: paginationReactive,
renameInfo,
breadcrumbItemsRef,
trim
}
},
render() {
const { t } = useI18n()
const { columnsRef, tableWidth } = useTable(
this.handleRenameFile,
this.updateList
)
const {
handleConditions,
handleCreateFolder,
handleCreateFile,
handleUploadFile
} = this
return (
<NSpace vertical>
<Card>
<NSpace justify='space-between'>
<NButtonGroup size='small'>
<NButton
onClick={handleCreateFolder}
class='btn-create-directory'
>
{t('resource.file.create_folder')}
</NButton>
<NButton onClick={handleCreateFile} class='btn-create-file'>
{t('resource.file.create_file')}
</NButton>
<NButton onClick={handleUploadFile} class='btn-upload-file'>
{t('resource.file.upload_files')}
</NButton>
</NButtonGroup>
<NSpace>
<Search
placeholder = {t('resource.file.enter_keyword_tips')}
v-model:value={this.searchRef}
onSearch={handleConditions}
/>
<NButton size='small' type='primary' onClick={handleConditions}>
<NIcon>
<SearchOutlined />
</NIcon>
</NButton>
</NSpace>
</NSpace>
</Card>
<Card title={t('resource.file.file_manage')}>
{{
'header-extra': () => (
<NBreadcrumb separator='>' class={styles['breadcrumb']}>
{this.breadcrumbItemsRef?.map((item: BreadcrumbItem) => {
if (item.id === 0) {
return (
<NBreadcrumbItem>
<span onClick={this.handleGoRoot}>{item.fullName}</span>
</NBreadcrumbItem>
)
} else {
return (
<NBreadcrumbItem href={"0?prefix=" + item.id.toString() + "&tenantCode=" + item.userName}>
{item.fullName}
</NBreadcrumbItem>
)
}
})}
</NBreadcrumb>
),
default: () => (
<NSpace vertical>
<NDataTable
remote
columns={columnsRef}
data={this.resourceListRef?.value.table}
striped
size={'small'}
class={styles['table-box']}
row-class-name='items'
scrollX={tableWidth}
/>
<NSpace justify='center'>
<NPagination
v-model:page={this.pagination.page}
v-model:pageSize={this.pagination.pageSize}
pageSizes={this.pagination.pageSizes}
item-count={this.pagination.itemCount}
onUpdatePage={this.handleUpdatePage}
onUpdatePageSize={this.handleUpdatePageSize}
show-quick-jumper
show-size-picker
/>
</NSpace>
</NSpace>
)
}}
</Card>
<ResourceFolderModal
v-model:show={this.folderShowRef}
onUpdateList={this.updateList}
/>
<ResourceUploadModal
v-model:show={this.uploadShowRef}
onUpdateList={this.updateList}
/>
<ResourceRenameModal
v-model:show={this.renameShowRef}
name={this.renameInfo.name}
fullName={this.renameInfo.fullName}
description={this.renameInfo.description}
userName={this.renameInfo.user_name}
onUpdateList={this.updateList}
/>
</NSpace>
)
return <ResourceListModal
resourceType={'FILE'}
/>
}
})

120
dolphinscheduler-ui/src/views/resource/file/table/use-table.ts

@ -1,120 +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.
*/
import { h } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { bytesToSize } from '@/common/common'
import { useFileStore } from '@/store/file/file'
import TableAction from './table-action'
import { IRenameFile } from '../types'
import ButtonLink from '@/components/button-link'
import { NEllipsis } from 'naive-ui'
import {
COLUMN_WIDTH_CONFIG,
calculateTableWidth,
DefaultTableWidth
} from '@/common/column-width-config'
import type { Router } from 'vue-router'
import type { TableColumns } from 'naive-ui/es/data-table/src/interface'
const goSubFolder = (router: Router, item: any) => {
const fileStore = useFileStore()
fileStore.setFileInfo(`${item.alias}|${item.size}`)
if (item.directory) {
fileStore.setCurrentDir(`${item.fullName}`)
router.push({ name: 'resource-file-subdirectory', query: { prefix: item.fullName, tenantCode: item.user_name}})
} else {
router.push({ name: 'resource-file-list', query: {prefix: item.fullName, tenantCode: item.user_name} })
}
}
export function useTable(renameResource: IRenameFile, updateList: () => void) {
const { t } = useI18n()
const router: Router = useRouter()
const columnsRef: TableColumns<any> = [
{
title: '#',
key: 'id',
...COLUMN_WIDTH_CONFIG['index'],
render: (_row, index) => index + 1
},
{
title: t('resource.file.name'),
key: 'name',
...COLUMN_WIDTH_CONFIG['linkName'],
render: (row) =>
h(
ButtonLink,
{
onClick: () => void goSubFolder(router, row)
},
{
default: () =>
h(NEllipsis, COLUMN_WIDTH_CONFIG['linkEllipsis'], () => row.name)
}
)
},
{
title: t('resource.file.tenant_name'),
...COLUMN_WIDTH_CONFIG['userName'],
key: 'user_name'
},
{
title: t('resource.file.whether_directory'),
key: 'whether_directory',
...COLUMN_WIDTH_CONFIG['yesOrNo'],
render: (row) =>
row.directory ? t('resource.file.yes') : t('resource.file.no')
},
{
title: t('resource.file.file_name'),
...COLUMN_WIDTH_CONFIG['name'],
key: 'fullName'
},
{
title: t('resource.file.size'),
key: 'size',
...COLUMN_WIDTH_CONFIG['size'],
render: (row) => bytesToSize(row.size)
},
{
title: t('resource.file.update_time'),
...COLUMN_WIDTH_CONFIG['time'],
key: 'update_time'
},
{
title: t('resource.file.operation'),
key: 'operation',
render: (row) =>
h(TableAction, {
row,
onRenameResource: ( name, description, fullName, user_name ) => {
renameResource(name, description, fullName, user_name)
},
onUpdateList: () => updateList()
}),
...COLUMN_WIDTH_CONFIG['operation'](4)
}
]
return {
columnsRef,
tableWidth: calculateTableWidth(columnsRef) || DefaultTableWidth
}
}

6
dolphinscheduler-ui/src/views/resource/udf/function/components/function-modal.tsx

@ -189,7 +189,7 @@ export default defineComponent({
disabled={this.uploadShow}
showPath={false}
class='btn-udf-resource-dropdown'
></NTreeSelect>
/>
<NButton
type='primary'
ghost
@ -217,7 +217,7 @@ export default defineComponent({
'resource.function.enter_select_udf_resources_directory_tips'
)}
defaultValue={this.uploadForm.pid}
></NTreeSelect>
/>
</NFormItem>
<NFormItem
label=' '
@ -239,7 +239,7 @@ export default defineComponent({
<NButton>
{t('resource.function.upload')}
<NIcon>
<CloudUploadOutlined />
<CloudUploadOutlined/>
</NIcon>
</NButton>
</NUpload>

2
dolphinscheduler-ui/src/views/resource/udf/function/components/use-modal.ts

@ -64,7 +64,7 @@ export function useModal(
try {
await serviceHandle()
window.$message.success(t('resource.udf.success'))
window.$message.success(t('resource.function.success'))
state.saving = false
ctx.emit('updateList')
ctx.emit('update:show')

130
dolphinscheduler-ui/src/views/resource/udf/resource/components/folder-modal.tsx

@ -1,130 +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.
*/
import {
defineComponent,
toRefs,
PropType,
watch,
computed,
getCurrentInstance
} from 'vue'
import { NForm, NFormItem, NInput } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Modal from '@/components/modal'
import { noSpace } from '@/utils/trim'
import { useForm } from './use-form'
import { useModal } from './use-modal'
import type { IUdf } from '../types'
const props = {
row: {
type: Object as PropType<IUdf>,
default: {}
},
show: {
type: Boolean as PropType<boolean>,
default: false
}
}
export default defineComponent({
name: 'ResourceFileFolder',
props,
emits: ['update:show', 'updateList'],
setup(props, ctx) {
const { folderState: state } = useForm()
const { handleCreateResource, handleRenameResource } = useModal(state, ctx)
const hideModal = () => {
ctx.emit('update:show')
}
const handleCreate = () => {
handleCreateResource()
}
const handleRename = () => {
handleRenameResource(props.row.fullName)
}
const trim = getCurrentInstance()?.appContext.config.globalProperties.trim
watch(
() => props.row,
() => {
state.folderForm.name = props.row.alias
state.folderForm.description = props.row.description
}
)
const fileEdit = computed(() => props.row.fullName && !props.row.directory)
return {
fileEdit,
hideModal,
handleCreate,
handleRename,
...toRefs(state),
trim
}
},
render() {
const { t } = useI18n()
return (
<Modal
show={this.$props.show}
title={
this.row.fullName ? t('resource.udf.edit') : t('resource.udf.create_folder')
}
onCancel={this.hideModal}
onConfirm={this.row.fullName ? this.handleRename : this.handleCreate}
confirmClassName='btn-submit'
cancelClassName='btn-cancel'
confirmLoading={this.saving}
>
<NForm rules={this.rules} ref='folderFormRef'>
<NFormItem
label={
this.fileEdit
? t('resource.udf.file_name')
: t('resource.udf.folder_name')
}
path='name'
>
<NInput
allowInput={this.fileEdit ? this.trim : noSpace}
v-model={[this.folderForm.name, 'value']}
placeholder={t('resource.udf.enter_name_tips')}
class='input-directory-name'
/>
</NFormItem>
<NFormItem label={t('resource.udf.description')} path='description'>
<NInput
allowInput={this.trim}
type='textarea'
v-model={[this.folderForm.description, 'value']}
placeholder={t('resource.udf.enter_description_tips')}
class='input-description'
/>
</NFormItem>
</NForm>
</Modal>
)
}
})

124
dolphinscheduler-ui/src/views/resource/udf/resource/components/upload-modal.tsx

@ -1,124 +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.
*/
import { defineComponent, toRefs, PropType, getCurrentInstance } from 'vue'
import { NForm, NFormItem, NInput, NUpload, NButton, NIcon } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Modal from '@/components/modal'
import { useForm } from './use-form'
import { useModal } from './use-modal'
import { CloudUploadOutlined } from '@vicons/antd'
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false
}
}
export default defineComponent({
name: 'ResourceFileFolder',
props,
emits: ['update:show', 'updateList'],
setup(props, ctx) {
const { uploadState: state } = useForm()
const { handleUploadFile } = useModal(state, ctx)
const hideModal = () => {
state.uploadForm.name = ''
state.uploadForm.description = ''
state.uploadForm.file = ''
ctx.emit('update:show')
}
const handleFolder = () => {
handleUploadFile()
}
const customRequest = ({ file }: any) => {
state.uploadForm.name = file.name
state.uploadForm.file = file.file
state.uploadFormRef.validate()
}
const removeFile = () => {
state.uploadForm.name = ''
state.uploadForm.file = ''
}
const trim = getCurrentInstance()?.appContext.config.globalProperties.trim
return {
hideModal,
handleFolder,
customRequest,
removeFile,
...toRefs(state),
trim
}
},
render() {
const { t } = useI18n()
return (
<Modal
show={this.$props.show}
title={t('resource.udf.file_upload')}
onCancel={this.hideModal}
onConfirm={this.handleFolder}
confirmClassName='btn-submit'
cancelClassName='btn-cancel'
confirmLoading={this.saving}
>
<NForm rules={this.rules} ref='uploadFormRef'>
<NFormItem label={t('resource.udf.file_name')} path='name'>
<NInput
allowInput={this.trim}
v-model={[this.uploadForm.name, 'value']}
placeholder={t('resource.udf.enter_name_tips')}
class='input-file-name'
/>
</NFormItem>
<NFormItem label={t('resource.udf.description')} path='description'>
<NInput
allowInput={this.trim}
type='textarea'
v-model={[this.uploadForm.description, 'value']}
placeholder={t('resource.udf.enter_description_tips')}
class='input-description'
/>
</NFormItem>
<NFormItem label={t('resource.udf.upload_files')} path='file'>
<NUpload
v-model={[this.uploadForm.file, 'value']}
customRequest={this.customRequest}
max={1}
class='btn-upload'
onRemove={this.removeFile}
>
<NButton>
{t('resource.udf.upload')}
<NIcon>
<CloudUploadOutlined />
</NIcon>
</NButton>
</NUpload>
</NFormItem>
</NForm>
</Modal>
)
}
})

84
dolphinscheduler-ui/src/views/resource/udf/resource/components/use-form.ts

@ -1,84 +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.
*/
import { reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import type { FormRules } from 'naive-ui'
export const useForm = () => {
const { t } = useI18n()
const folderState = reactive({
folderFormRef: ref(),
folderForm: {
pid: -1,
type: 'UDF',
name: '',
description: '',
currentDir: '/'
},
saving: false,
rules: {
name: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (folderState.folderForm.name === '') {
return new Error(t('resource.udf.enter_name_tips'))
}
}
}
} as FormRules
})
const uploadState = reactive({
uploadFormRef: ref(),
uploadForm: {
name: '',
file: '',
description: '',
pid: -1,
currentDir: '/'
},
saving: false,
rules: {
name: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (uploadState.uploadForm.name === '') {
return new Error(t('resource.udf.enter_name_tips'))
}
}
},
file: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (uploadState.uploadForm.file === '') {
return new Error(t('resource.file.enter_content_tips'))
}
}
}
} as FormRules
})
return {
folderState,
uploadState
}
}

117
dolphinscheduler-ui/src/views/resource/udf/resource/components/use-modal.ts

@ -1,117 +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.
*/
import { SetupContext } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import type { Router } from 'vue-router'
import { useFileStore } from '@/store/file/file'
import {
createDirectory,
createResource,
updateResource
} from '@/service/modules/resources'
export function useModal(
state: any,
ctx: SetupContext<('update:show' | 'updateList')[]>
) {
const { t } = useI18n()
const router: Router = useRouter()
const fileStore = useFileStore()
const handleCreateResource = async () => {
const pid = router.currentRoute.value.params.id || -1
const currentFullName = String(router.currentRoute.value.query.prefix || "")
const currentDir = currentFullName == "" ? '/' : fileStore.getCurrentDir || '/'
submitRequest(
async () =>
await createDirectory({
...state.folderForm,
...{ pid, currentDir }
})
)
}
const handleRenameResource = async (fullName: string) => {
submitRequest(async () => {
await updateResource(
{
fullName: fullName,
...state.folderForm,
}
)
})
}
const submitRequest = async (serviceHandle: any) => {
await state.folderFormRef.validate()
if (state.saving) return
state.saving = true
try {
await serviceHandle()
window.$message.success(t('resource.udf.success'))
state.saving = false
ctx.emit('updateList')
ctx.emit('update:show')
} catch (err) {
state.saving = false
}
}
const resetUploadForm = () => {
state.uploadForm.name = ''
state.uploadForm.file = ''
state.uploadForm.description = ''
}
const handleUploadFile = async () => {
await state.uploadFormRef.validate()
if (state.saving) return
state.saving = true
try {
const pid = Number(router.currentRoute.value.params.id) || "-1"
const currentFullName = String(router.currentRoute.value.query.prefix || "")
const currentDir = currentFullName == "" ? '/' : fileStore.getCurrentDir || '/'
const formData = new FormData()
formData.append('file', state.uploadForm.file)
formData.append('type', 'UDF')
formData.append('name', state.uploadForm.name)
formData.append('pid', String(pid))
formData.append('currentDir', currentDir)
formData.append('description', state.uploadForm.description)
await createResource(formData as any)
window.$message.success(t('resource.udf.success'))
ctx.emit('updateList')
ctx.emit('update:show')
resetUploadForm()
} finally {
state.saving = false
}
}
return {
handleCreateResource,
handleRenameResource,
handleUploadFile
}
}

43
dolphinscheduler-ui/src/views/resource/udf/resource/index.module.scss

@ -1,43 +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.
*/
.table {
table {
width: 100%;
tr {
height: 40px;
font-size: 12px;
th,
td {
&:nth-child(1) {
width: 50px;
text-align: center;
}
}
th {
&:nth-child(1) {
width: 60px;
text-align: center;
}
> span {
font-size: 12px;
color: #555;
}
}
}
}
}

206
dolphinscheduler-ui/src/views/resource/udf/resource/index.tsx

@ -15,212 +15,16 @@
* limitations under the License.
*/
import {
defineComponent,
Ref,
toRefs,
onMounted,
toRef,
watch,
getCurrentInstance
} from 'vue'
import {
NIcon,
NSpace,
NDataTable,
NButton,
NPagination,
NBreadcrumb,
NBreadcrumbItem
} from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { useTable } from './use-table'
import { SearchOutlined } from '@vicons/antd'
import Card from '@/components/card'
import FolderModal from './components/folder-modal'
import UploadModal from './components/upload-modal'
import styles from './index.module.scss'
import Search from "@/components/input-search";
import { defineComponent } from 'vue'
import ResourceListModal from '../../components/resource'
export default defineComponent({
name: 'resource-manage',
setup() {
const { variables, createColumns, getTableData, goUdfManage, goBread } =
useTable()
const requestData = () => {
getTableData({
id: -1,
fullName: variables.fullName,
tenantCode: variables.tenantCode,
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
}
const handleUpdateList = () => {
requestData()
}
const handleChangePageSize = () => {
variables.page = 1
requestData()
}
const handleSearch = () => {
variables.page = 1
requestData()
}
const handleShowModal = (showRef: Ref<Boolean>) => {
showRef.value = true
}
const handleCreateFolder = () => {
variables.row = {}
handleShowModal(toRef(variables, 'folderShowRef'))
}
const handleUploadFile = () => {
handleShowModal(toRef(variables, 'uploadShowRef'))
}
const handleBread = (index: number) => {
let breadName = ''
variables.breadList.forEach((item, i) => {
if (i <= index) {
breadName = breadName === "" ? item.toString() : breadName + '/' + item.toString();
}
})
goBread(breadName)
}
const trim = getCurrentInstance()?.appContext.config.globalProperties.trim
watch(useI18n().locale, () => {
createColumns(variables)
})
onMounted(() => {
createColumns(variables)
requestData()
})
return {
goUdfManage,
handleBread,
requestData,
handleSearch,
handleUpdateList,
handleCreateFolder,
handleUploadFile,
handleChangePageSize,
...toRefs(variables),
trim
}
},
render() {
const { t } = useI18n()
const { loadingRef } = this
return (
<NSpace vertical>
<Card>
<NSpace justify='space-between'>
<NSpace>
<NButton
type='primary'
size='small'
onClick={this.handleCreateFolder}
class='btn-create-directory'
>
{t('resource.udf.create_folder')}
</NButton>
<NButton
strong
size='small'
secondary
onClick={this.handleUploadFile}
class='btn-upload-udf'
>
{t('resource.udf.upload_udf_resources')}
</NButton>
</NSpace>
<NSpace>
<Search
placeholder={t('resource.udf.enter_keyword_tips')}
v-model:value={this.searchVal}
onSearch={this.handleSearch}
/>
<NButton type='primary' size='small' onClick={this.handleSearch}>
<NIcon>
<SearchOutlined />
</NIcon>
</NButton>
</NSpace>
</NSpace>
</Card>
<Card title={t('resource.udf.udf_resources')}>
{{
header: () => (
<NBreadcrumb separator='>'>
<NBreadcrumbItem>
<NButton text onClick={() => this.goUdfManage()}>
{t('resource.udf.udf_resources')}
</NButton>
</NBreadcrumbItem>
{this.breadList.map((item, index) => (
<NBreadcrumbItem>
<NButton
text
disabled={index === this.breadList.length - 1}
onClick={() => this.handleBread(index)}
>
{item}
</NButton>
</NBreadcrumbItem>
))}
</NBreadcrumb>
),
default: () => (
<NSpace vertical>
<NDataTable
loading={loadingRef}
columns={this.columns}
data={this.tableData}
striped
size={'small'}
class={styles.table}
row-class-name='items'
scrollX={this.tableWidth}
/>
<NSpace justify='center'>
<NPagination
v-model:page={this.page}
v-model:page-size={this.pageSize}
page-count={this.totalPage}
show-size-picker
page-sizes={[10, 30, 50]}
show-quick-jumper
onUpdatePage={this.requestData}
onUpdatePageSize={this.handleChangePageSize}
/>
</NSpace>
</NSpace>
)
}}
</Card>
<FolderModal
v-model:row={this.row}
v-model:show={this.folderShowRef}
onUpdateList={this.handleUpdateList}
/>
<UploadModal
v-model:show={this.uploadShowRef}
onUpdateList={this.handleUpdateList}
/>
</NSpace>
)
return <ResourceListModal
resourceType={'UDF'}
/>
}
})

40
dolphinscheduler-ui/src/views/resource/udf/resource/types.ts

@ -1,40 +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.
*/
export interface IUdfResourceParam {
fullName: string
tenantCode: string
id: number
pageSize: number
pageNo: number
searchVal: string | undefined
}
export interface IUdf {
id: number
pid: number
userId: number
fileName: string
fullName: string
alias: string
directory: boolean
size: number
type: 'UDF'
description: string
createTime: string
updateTime: string
}

320
dolphinscheduler-ui/src/views/resource/udf/resource/use-table.ts

@ -1,320 +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.
*/
import { h, ref, reactive } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { bytesToSize } from '@/common/common'
import { useFileStore } from '@/store/file/file'
import type { Router } from 'vue-router'
import { NEllipsis } from 'naive-ui'
import type { TableColumns } from 'naive-ui/es/data-table/src/interface'
import { NSpace, NTooltip, NButton, NPopconfirm } from 'naive-ui'
import { EditOutlined, DeleteOutlined, DownloadOutlined } from '@vicons/antd'
import { useAsyncState } from '@vueuse/core'
import {
queryResourceListPaging,
downloadResource,
deleteResource,
queryCurrentResourceByFileName,
queryCurrentResourceByFullName
} from '@/service/modules/resources'
import ButtonLink from '@/components/button-link'
import {
COLUMN_WIDTH_CONFIG,
calculateTableWidth,
DefaultTableWidth
} from '@/common/column-width-config'
import type { IUdfResourceParam } from './types'
import { ResourceFile } from '@/service/modules/resources/types'
const goSubFolder = (router: Router, item: any) => {
const fileStore = useFileStore()
fileStore.setFileInfo(`${item.alias}|${item.size}`)
if (item.directory) {
fileStore.setCurrentDir(`${item.fullName}`)
router.push({ name: 'resource-sub-manage',
query: {prefix: item.fullName, tenantCode: item.userName} })
}
}
export function useTable() {
const { t } = useI18n()
const router: Router = useRouter()
const fileStore = useFileStore()
const variables = reactive({
columns: [],
tableWidth: DefaultTableWidth,
row: {},
tableData: [],
breadList: [] as String[],
fullName: ref(String(router.currentRoute.value.query.prefix || "")),
tenantCode: ref(String(router.currentRoute.value.query.tenantCode || "")),
page: ref(1),
pageSize: ref(10),
searchVal: ref(),
totalPage: ref(1),
folderShowRef: ref(false),
uploadShowRef: ref(false),
loadingRef: ref(false)
})
const createColumns = (variables: any) => {
variables.columns = [
{
title: '#',
key: 'id',
...COLUMN_WIDTH_CONFIG['index'],
render: (_row, index) => index + 1
},
{
title: t('resource.udf.udf_source_name'),
key: 'alias',
...COLUMN_WIDTH_CONFIG['linkName'],
render: (row) => {
return !row.directory
? row.alias
: h(
ButtonLink,
{
onClick: () => void goSubFolder(router, row)
},
{
default: () =>
h(
NEllipsis,
COLUMN_WIDTH_CONFIG['linkEllipsis'],
() => row.alias
)
}
)
}
},
{
title: t('resource.udf.tenant_name'),
...COLUMN_WIDTH_CONFIG['userName'],
key: 'userName'
},
{
title: t('resource.udf.whether_directory'),
key: 'whether_directory',
...COLUMN_WIDTH_CONFIG['yesOrNo'],
render: (row) =>
row.directory ? t('resource.file.yes') : t('resource.file.no')
},
{
title: t('resource.udf.file_name'),
...COLUMN_WIDTH_CONFIG['name'],
key: 'fullName'
},
{
title: t('resource.udf.file_size'),
key: 'size',
...COLUMN_WIDTH_CONFIG['size'],
render: (row) => bytesToSize(row.size)
},
{
title: t('resource.udf.create_time'),
key: 'createTime',
...COLUMN_WIDTH_CONFIG['time']
},
{
title: t('resource.udf.update_time'),
key: 'updateTime',
...COLUMN_WIDTH_CONFIG['time']
},
{
title: t('resource.udf.operation'),
key: 'operation',
...COLUMN_WIDTH_CONFIG['operation'](3),
render: (row) => {
return h(NSpace, null, {
default: () => [
h(
NTooltip,
{},
{
trigger: () =>
h(
NButton,
{
tag: 'div',
circle: true,
type: 'info',
size: 'tiny',
class: 'btn-edit',
onClick: () => {
handleEdit(row)
}
},
{
icon: () => h(EditOutlined)
}
),
default: () => t('resource.udf.edit')
}
),
h(
NTooltip,
{},
{
trigger: () =>
h(
NButton,
{
tag: 'div',
circle: true,
type: 'info',
size: 'tiny',
class: 'btn-download',
disabled: row?.directory ? true : false,
onClick: () => downloadResource({fullName: row.fullName})
},
{
icon: () => h(DownloadOutlined)
}
),
default: () => t('resource.udf.download')
}
),
h(
NPopconfirm,
{
onPositiveClick: () => {
handleDelete({fullName: row.fullName, tenantCode: row.userName})
}
},
{
trigger: () =>
h(
NTooltip,
{},
{
trigger: () =>
h(
NButton,
{
tag: 'div',
circle: true,
type: 'error',
size: 'tiny',
class: 'btn-delete'
},
{
icon: () => h(DeleteOutlined)
}
),
default: () => t('resource.udf.delete')
}
),
default: () => t('resource.udf.delete_confirm')
}
)
]
})
}
}
] as TableColumns<any>
if (variables.tableWidth) {
variables.tableWidth = calculateTableWidth(variables.columns)
}
}
const getTableData = (params: IUdfResourceParam) => {
if (variables.loadingRef) return
variables.loadingRef = true
const { state } = useAsyncState(
queryResourceListPaging({ ...params, type: 'UDF' }).then((res: any) => {
// use strict checking here
if (variables.fullName !== ""){
queryCurrentResourceByFullName(
{
type: 'UDF',
fullName: variables.fullName,
tenantCode: variables.tenantCode,
}
).then((res: ResourceFile) => {
if (res.fileName) {
const breadList = res.fileName.split('/')
// pop the alias from the fullname path
breadList.pop()
variables.breadList = breadList
}
})
} else {
variables.breadList = []
}
variables.totalPage = res.totalPage
variables.tableData = res.totalList.map((item: any) => {
return { ...item }
})
variables.loadingRef = false
}),
{ total: 0, table: [] }
)
return state
}
const handleEdit = (row: any) => {
variables.folderShowRef = true
variables.row = row
}
const handleDelete = (fullNameObj: {fullName: string, tenantCode: string}) => {
/* after deleting data from the current page, you need to jump forward when the page is empty. */
if (variables.tableData.length === 1 && variables.page > 1) {
variables.page -= 1
}
deleteResource(fullNameObj).then(() =>
getTableData({
id: -1,
fullName: variables.fullName,
tenantCode: variables.tenantCode,
pageSize: variables.pageSize,
pageNo: variables.page,
searchVal: variables.searchVal
})
)
}
const goUdfManage = () => {
router.push({ name: 'resource-manage' })
}
const goBread = (fileName: string) => {
queryCurrentResourceByFileName(
{
type: 'UDF',
fileName: fileName + "/",
tenantCode: variables.tenantCode
}
).then((res: any) => {
fileStore.setCurrentDir(res.fullName)
router.push({ name: 'resource-sub-manage', query: {prefix: res.fullName, tenantCode: res.userName} })
})
}
return {
variables,
createColumns,
getTableData,
goUdfManage,
goBread
}
}
Loading…
Cancel
Save