mirror of
				https://github.com/1Panel-dev/1Panel.git
				synced 2025-10-31 19:26:02 +08:00 
			
		
		
		
	fix: 修改镜像构建和编排创建路径限制,增加 config 校验 (#342)
fix: 修改镜像构建和编排创建路径限制,增加 config 校验
This commit is contained in:
		
							parent
							
								
									68a457ae89
								
							
						
					
					
						commit
						6ee9789a2f
					
				
					 12 changed files with 256 additions and 55 deletions
				
			
		|  | @ -70,6 +70,34 @@ func (b *BaseApi) SearchCompose(c *gin.Context) { | |||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // @Tags Container Compose | ||||
| // @Summary Test compose | ||||
| // @Description 测试 compose 是否可用 | ||||
| // @Accept json | ||||
| // @Param request body dto.ComposeCreate true "request" | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /containers/compose/test [post] | ||||
| // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"检测 compose [name] 格式","formatEN":"check compose [name]"} | ||||
| func (b *BaseApi) TestCompose(c *gin.Context) { | ||||
| 	var req dto.ComposeCreate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| 	} | ||||
| 	if err := global.VALID.Struct(req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	isOK, err := containerService.TestCompose(req) | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| 	} | ||||
| 	helper.SuccessWithData(c, isOK) | ||||
| } | ||||
| 
 | ||||
| // @Tags Container Compose | ||||
| // @Summary Create compose | ||||
| // @Description 创建容器编排 | ||||
|  |  | |||
|  | @ -125,40 +125,28 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface | |||
| 	return int64(total), BackDatas, nil | ||||
| } | ||||
| 
 | ||||
| func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) { | ||||
| 	if req.From == "template" { | ||||
| 		template, err := composeRepo.Get(commonRepo.WithByID(req.Template)) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		req.From = "edit" | ||||
| 		req.File = template.Content | ||||
| func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) { | ||||
| 	if err := u.loadPath(&req); err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	if req.From == "edit" { | ||||
| 		dir := fmt.Sprintf("%s/docker/compose/%s", constant.DataDir, req.Name) | ||||
| 		if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { | ||||
| 			if err = os.MkdirAll(dir, os.ModePerm); err != nil { | ||||
| 				return "", err | ||||
| 			} | ||||
| 		} | ||||
| 	cmd := exec.Command("docker-compose", "-f", req.Path, "config") | ||||
| 	stdout, err := cmd.CombinedOutput() | ||||
| 	if err != nil { | ||||
| 		return false, errors.New(string(stdout)) | ||||
| 	} | ||||
| 	return true, nil | ||||
| } | ||||
| 
 | ||||
| 		path := fmt.Sprintf("%s/docker-compose.yml", dir) | ||||
| 		file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		defer file.Close() | ||||
| 		write := bufio.NewWriter(file) | ||||
| 		_, _ = write.WriteString(string(req.File)) | ||||
| 		write.Flush() | ||||
| 		req.Path = path | ||||
| func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) { | ||||
| 	if err := u.loadPath(&req); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	global.LOG.Infof("docker-compose.yml %s create successful, start to docker-compose up", req.Name) | ||||
| 
 | ||||
| 	if req.From == "path" { | ||||
| 		req.Name = path.Base(strings.ReplaceAll(req.Path, "/docker-compose.yml", "")) | ||||
| 		req.Name = path.Base(strings.ReplaceAll(req.Path, "/"+path.Base(req.Path), "")) | ||||
| 	} | ||||
| 	logName := strings.ReplaceAll(req.Path, "docker-compose.yml", "compose.log") | ||||
| 	logName := path.Dir(req.Path) + "/compose.log" | ||||
| 	file, err := os.OpenFile(logName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
|  | @ -221,3 +209,34 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error { | |||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (u *ContainerService) loadPath(req *dto.ComposeCreate) error { | ||||
| 	if req.From == "template" { | ||||
| 		template, err := composeRepo.Get(commonRepo.WithByID(req.Template)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		req.From = "edit" | ||||
| 		req.File = template.Content | ||||
| 	} | ||||
| 	if req.From == "edit" { | ||||
| 		dir := fmt.Sprintf("%s/docker/compose/%s", constant.DataDir, req.Name) | ||||
| 		if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { | ||||
| 			if err = os.MkdirAll(dir, os.ModePerm); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		path := fmt.Sprintf("%s/docker-compose.yml", dir) | ||||
| 		file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		defer file.Close() | ||||
| 		write := bufio.NewWriter(file) | ||||
| 		_, _ = write.WriteString(string(req.File)) | ||||
| 		write.Flush() | ||||
| 		req.Path = path | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
|  | @ -122,6 +122,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) { | |||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	fileName := "Dockerfile" | ||||
| 	if req.From == "edit" { | ||||
| 		dir := fmt.Sprintf("%s/docker/build/%s", constant.DataDir, strings.ReplaceAll(req.Name, ":", "_")) | ||||
| 		if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { | ||||
|  | @ -141,7 +142,8 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) { | |||
| 		write.Flush() | ||||
| 		req.Dockerfile = dir | ||||
| 	} else { | ||||
| 		req.Dockerfile = strings.ReplaceAll(req.Dockerfile, "/Dockerfile", "") | ||||
| 		fileName = path.Base(req.Dockerfile) | ||||
| 		req.Dockerfile = path.Dir(req.Dockerfile) | ||||
| 	} | ||||
| 	tar, err := archive.TarWithOptions(req.Dockerfile+"/", &archive.TarOptions{}) | ||||
| 	if err != nil { | ||||
|  | @ -149,7 +151,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) { | |||
| 	} | ||||
| 
 | ||||
| 	opts := types.ImageBuildOptions{ | ||||
| 		Dockerfile: "Dockerfile", | ||||
| 		Dockerfile: fileName, | ||||
| 		Tags:       []string{req.Name}, | ||||
| 		Remove:     true, | ||||
| 		Labels:     stringsToMap(req.Tags), | ||||
|  |  | |||
|  | @ -33,6 +33,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) { | |||
| 
 | ||||
| 		baRouter.POST("/compose/search", baseApi.SearchCompose) | ||||
| 		baRouter.POST("/compose", baseApi.CreateCompose) | ||||
| 		baRouter.POST("/compose/test", baseApi.TestCompose) | ||||
| 		baRouter.POST("/compose/operate", baseApi.OperatorCompose) | ||||
| 		baRouter.POST("/compose/update", baseApi.ComposeUpdate) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1038,6 +1038,48 @@ const docTemplate = `{ | |||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "/containers/compose/test": { | ||||
|             "post": { | ||||
|                 "security": [ | ||||
|                     { | ||||
|                         "ApiKeyAuth": [] | ||||
|                     } | ||||
|                 ], | ||||
|                 "description": "测试 compose 是否可用", | ||||
|                 "consumes": [ | ||||
|                     "application/json" | ||||
|                 ], | ||||
|                 "tags": [ | ||||
|                     "Container Compose" | ||||
|                 ], | ||||
|                 "summary": "Test compose", | ||||
|                 "parameters": [ | ||||
|                     { | ||||
|                         "description": "request", | ||||
|                         "name": "request", | ||||
|                         "in": "body", | ||||
|                         "required": true, | ||||
|                         "schema": { | ||||
|                             "$ref": "#/definitions/dto.ComposeCreate" | ||||
|                         } | ||||
|                     } | ||||
|                 ], | ||||
|                 "responses": { | ||||
|                     "200": { | ||||
|                         "description": "" | ||||
|                     } | ||||
|                 }, | ||||
|                 "x-panel-log": { | ||||
|                     "BeforeFuntions": [], | ||||
|                     "bodyKeys": [ | ||||
|                         "name" | ||||
|                     ], | ||||
|                     "formatEN": "create compose [name]", | ||||
|                     "formatZH": "创建 compose [name]", | ||||
|                     "paramKeys": [] | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "/containers/compose/update": { | ||||
|             "post": { | ||||
|                 "security": [ | ||||
|  |  | |||
|  | @ -1031,6 +1031,48 @@ | |||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "/containers/compose/test": { | ||||
|             "post": { | ||||
|                 "security": [ | ||||
|                     { | ||||
|                         "ApiKeyAuth": [] | ||||
|                     } | ||||
|                 ], | ||||
|                 "description": "测试 compose 是否可用", | ||||
|                 "consumes": [ | ||||
|                     "application/json" | ||||
|                 ], | ||||
|                 "tags": [ | ||||
|                     "Container Compose" | ||||
|                 ], | ||||
|                 "summary": "Test compose", | ||||
|                 "parameters": [ | ||||
|                     { | ||||
|                         "description": "request", | ||||
|                         "name": "request", | ||||
|                         "in": "body", | ||||
|                         "required": true, | ||||
|                         "schema": { | ||||
|                             "$ref": "#/definitions/dto.ComposeCreate" | ||||
|                         } | ||||
|                     } | ||||
|                 ], | ||||
|                 "responses": { | ||||
|                     "200": { | ||||
|                         "description": "" | ||||
|                     } | ||||
|                 }, | ||||
|                 "x-panel-log": { | ||||
|                     "BeforeFuntions": [], | ||||
|                     "bodyKeys": [ | ||||
|                         "name" | ||||
|                     ], | ||||
|                     "formatEN": "create compose [name]", | ||||
|                     "formatZH": "创建 compose [name]", | ||||
|                     "paramKeys": [] | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "/containers/compose/update": { | ||||
|             "post": { | ||||
|                 "security": [ | ||||
|  |  | |||
|  | @ -3286,6 +3286,33 @@ paths: | |||
|       summary: Page composes | ||||
|       tags: | ||||
|       - Container Compose | ||||
|   /containers/compose/test: | ||||
|     post: | ||||
|       consumes: | ||||
|       - application/json | ||||
|       description: 测试 compose 是否可用 | ||||
|       parameters: | ||||
|       - description: request | ||||
|         in: body | ||||
|         name: request | ||||
|         required: true | ||||
|         schema: | ||||
|           $ref: '#/definitions/dto.ComposeCreate' | ||||
|       responses: | ||||
|         "200": | ||||
|           description: "" | ||||
|       security: | ||||
|       - ApiKeyAuth: [] | ||||
|       summary: Test compose | ||||
|       tags: | ||||
|       - Container Compose | ||||
|       x-panel-log: | ||||
|         BeforeFuntions: [] | ||||
|         bodyKeys: | ||||
|         - name | ||||
|         formatEN: create compose [name] | ||||
|         formatZH: 创建 compose [name] | ||||
|         paramKeys: [] | ||||
|   /containers/compose/update: | ||||
|     post: | ||||
|       consumes: | ||||
|  |  | |||
|  | @ -117,7 +117,10 @@ export const searchCompose = (params: SearchWithPage) => { | |||
|     return http.post<ResPage<Container.ComposeInfo>>(`/containers/compose/search`, params); | ||||
| }; | ||||
| export const upCompose = (params: Container.ComposeCreate) => { | ||||
|     return http.post<string>(`/containers/compose`, params, 600000); | ||||
|     return http.post<string>(`/containers/compose`, params); | ||||
| }; | ||||
| export const testCompose = (params: Container.ComposeCreate) => { | ||||
|     return http.post<boolean>(`/containers/compose/test`, params); | ||||
| }; | ||||
| export const composeOperator = (params: Container.ComposeOpration) => { | ||||
|     return http.post(`/containers/compose/operate`, params); | ||||
|  |  | |||
|  | @ -521,6 +521,8 @@ const message = { | |||
|         registrieHelper: 'One in a row, for example:\n172.16.10.111:8081 \n172.16.10.112:8081', | ||||
| 
 | ||||
|         compose: 'Compose', | ||||
|         composeHelper: | ||||
|             'The current content has passed the format verification. Please click Submit to complete the creation', | ||||
|         apps: 'Apps', | ||||
|         local: 'Local', | ||||
|         createCompose: 'Create compose', | ||||
|  |  | |||
|  | @ -532,6 +532,7 @@ const message = { | |||
|         registrieHelper: '一行一个,例:\n172.16.10.111:8081 \n172.16.10.112:8081', | ||||
| 
 | ||||
|         compose: '编排', | ||||
|         composeHelper: '当前内容已通过格式验证,请点击确认完成创建', | ||||
|         composePathHelper: '容器编排将保存在: {0}', | ||||
|         apps: '应用商店', | ||||
|         local: '本地', | ||||
|  | @ -842,7 +843,7 @@ const message = { | |||
|         versionHelper: '1Panel 版本号命名规则为: [大版本].[功能版本].[Bug 修复版本],示例如下:', | ||||
|         versionHelper1: 'v1.0.1 是 v1.0.0 之后的 Bug 修复版本', | ||||
|         versionHelper2: 'v1.1.0 是 v1.0.0 之后的功能版本', | ||||
|         newVersion: '(Bug fix version)', | ||||
|         newVersion: '(Bug 修复版本)', | ||||
|         latestVersion: '(功能版本)', | ||||
|         upgradeCheck: '检查更新', | ||||
|         upgradeNotes: '更新内容', | ||||
|  |  | |||
|  | @ -9,12 +9,12 @@ | |||
|         <template #header> | ||||
|             <DrawerHeader :header="$t('container.compose')" :back="handleClose" /> | ||||
|         </template> | ||||
|         <div> | ||||
|         <div v-loading="loading"> | ||||
|             <el-row type="flex" justify="center"> | ||||
|                 <el-col :span="22"> | ||||
|                     <el-form ref="formRef" label-position="top" :model="form" :rules="rules" label-width="80px"> | ||||
|                         <el-form-item :label="$t('container.from')"> | ||||
|                             <el-radio-group v-model="form.from"> | ||||
|                             <el-radio-group v-model="form.from" @change="hasChecked = false"> | ||||
|                                 <el-radio label="edit">{{ $t('commons.button.edit') }}</el-radio> | ||||
|                                 <el-radio label="path">{{ $t('container.pathSelect') }}</el-radio> | ||||
|                                 <el-radio label="template">{{ $t('container.composeTemplate') }}</el-radio> | ||||
|  | @ -37,7 +37,7 @@ | |||
|                             <span class="input-help">{{ $t('container.composePathHelper', [composeFile]) }}</span> | ||||
|                         </el-form-item> | ||||
|                         <el-form-item v-if="form.from === 'template'" prop="template"> | ||||
|                             <el-select v-model="form.template"> | ||||
|                             <el-select v-model="form.template" @change="hasChecked = false"> | ||||
|                                 <el-option | ||||
|                                     v-for="item in templateOptions" | ||||
|                                     :key="item.id" | ||||
|  | @ -52,10 +52,11 @@ | |||
|                                 placeholder="#Define or paste the content of your docker-compose file here" | ||||
|                                 :indent-with-tab="true" | ||||
|                                 :tabSize="4" | ||||
|                                 style="width: 100%; height: 200px" | ||||
|                                 style="width: 100%; height: 250px" | ||||
|                                 :lineWrapping="true" | ||||
|                                 :matchBrackets="true" | ||||
|                                 theme="cobalt" | ||||
|                                 @change="hasChecked = false" | ||||
|                                 :styleActiveLine="true" | ||||
|                                 :extensions="extensions" | ||||
|                                 v-model="form.file" | ||||
|  | @ -63,12 +64,28 @@ | |||
|                         </el-form-item> | ||||
|                     </el-form> | ||||
|                     <codemirror | ||||
|                         v-if="logVisiable" | ||||
|                         v-if="logVisiable && form.from !== 'edit'" | ||||
|                         :autofocus="true" | ||||
|                         placeholder="Waiting for build output..." | ||||
|                         placeholder="Waiting for docker-compose up output..." | ||||
|                         :indent-with-tab="true" | ||||
|                         :tabSize="4" | ||||
|                         style="max-height: calc(100vh - 537px)" | ||||
|                         style="height: calc(100vh - 370px)" | ||||
|                         :lineWrapping="true" | ||||
|                         :matchBrackets="true" | ||||
|                         theme="cobalt" | ||||
|                         :styleActiveLine="true" | ||||
|                         :extensions="extensions" | ||||
|                         @ready="handleReady" | ||||
|                         v-model="logInfo" | ||||
|                         :readOnly="true" | ||||
|                     /> | ||||
|                     <codemirror | ||||
|                         v-if="logVisiable && form.from === 'edit'" | ||||
|                         :autofocus="true" | ||||
|                         placeholder="Waiting for docker-compose up output..." | ||||
|                         :indent-with-tab="true" | ||||
|                         :tabSize="4" | ||||
|                         style="height: calc(100vh - 590px)" | ||||
|                         :lineWrapping="true" | ||||
|                         :matchBrackets="true" | ||||
|                         theme="cobalt" | ||||
|  | @ -86,7 +103,10 @@ | |||
|                 <el-button @click="drawerVisiable = false"> | ||||
|                     {{ $t('commons.button.cancel') }} | ||||
|                 </el-button> | ||||
|                 <el-button type="primary" :disabled="buttonDisabled" @click="onSubmit(formRef)"> | ||||
|                 <el-button :disabled="buttonDisabled" @click="onTest(formRef)"> | ||||
|                     {{ $t('commons.button.verify') }} | ||||
|                 </el-button> | ||||
|                 <el-button type="primary" :disabled="buttonDisabled || !hasChecked" @click="onSubmit(formRef)"> | ||||
|                     {{ $t('commons.button.confirm') }} | ||||
|                 </el-button> | ||||
|             </span> | ||||
|  | @ -104,10 +124,13 @@ import { Rules } from '@/global/form-rules'; | |||
| import i18n from '@/lang'; | ||||
| import { ElForm } from 'element-plus'; | ||||
| import DrawerHeader from '@/components/drawer-header/index.vue'; | ||||
| import { listComposeTemplate, upCompose } from '@/api/modules/container'; | ||||
| import { listComposeTemplate, testCompose, upCompose } from '@/api/modules/container'; | ||||
| import { loadBaseDir } from '@/api/modules/setting'; | ||||
| import { LoadFile } from '@/api/modules/files'; | ||||
| import { formatImageStdout } from '@/utils/docker'; | ||||
| import { MsgSuccess } from '@/utils/message'; | ||||
| 
 | ||||
| const loading = ref(); | ||||
| 
 | ||||
| const extensions = [javascript(), oneDark]; | ||||
| const view = shallowRef(); | ||||
|  | @ -124,14 +147,9 @@ const buttonDisabled = ref(false); | |||
| const baseDir = ref(); | ||||
| const composeFile = ref(); | ||||
| 
 | ||||
| let timer: NodeJS.Timer | null = null; | ||||
| const hasChecked = ref(); | ||||
| 
 | ||||
| const varifyPath = (rule: any, value: any, callback: any) => { | ||||
|     if (value.indexOf('docker-compose.yml') === -1) { | ||||
|         callback(new Error(i18n.global.t('commons.rule.selectHelper', ['docker-compose.yml']))); | ||||
|     } | ||||
|     callback(); | ||||
| }; | ||||
| let timer: NodeJS.Timer | null = null; | ||||
| 
 | ||||
| const form = reactive({ | ||||
|     name: '', | ||||
|  | @ -142,7 +160,7 @@ const form = reactive({ | |||
| }); | ||||
| const rules = reactive({ | ||||
|     name: [Rules.requiredInput, Rules.imageName], | ||||
|     path: [Rules.requiredSelect, { validator: varifyPath, trigger: 'change', required: true }], | ||||
|     path: [Rules.requiredSelect], | ||||
| }); | ||||
| 
 | ||||
| const loadTemplates = async () => { | ||||
|  | @ -160,6 +178,7 @@ const acceptParams = (): void => { | |||
|     form.path = ''; | ||||
|     form.file = ''; | ||||
|     logVisiable.value = false; | ||||
|     hasChecked.value = false; | ||||
|     logInfo.value = ''; | ||||
|     loadTemplates(); | ||||
|     loadPath(); | ||||
|  | @ -186,6 +205,25 @@ const changePath = async () => { | |||
| type FormInstance = InstanceType<typeof ElForm>; | ||||
| const formRef = ref<FormInstance>(); | ||||
| 
 | ||||
| const onTest = async (formEl: FormInstance | undefined) => { | ||||
|     if (!formEl) return; | ||||
|     formEl.validate(async (valid) => { | ||||
|         if (!valid) return; | ||||
|         loading.value = true; | ||||
|         await testCompose(form) | ||||
|             .then((res) => { | ||||
|                 loading.value = false; | ||||
|                 if (res.data) { | ||||
|                     MsgSuccess(i18n.global.t('container.composeHelper')); | ||||
|                     hasChecked.value = true; | ||||
|                 } | ||||
|             }) | ||||
|             .catch(() => { | ||||
|                 loading.value = false; | ||||
|             }); | ||||
|     }); | ||||
| }; | ||||
| 
 | ||||
| const onSubmit = async (formEl: FormInstance | undefined) => { | ||||
|     if (!formEl) return; | ||||
|     formEl.validate(async (valid) => { | ||||
|  | @ -224,6 +262,7 @@ const loadLogs = async (path: string) => { | |||
| 
 | ||||
| const loadDir = async (path: string) => { | ||||
|     form.path = path; | ||||
|     hasChecked.value = false; | ||||
| }; | ||||
| 
 | ||||
| defineExpose({ | ||||
|  |  | |||
|  | @ -116,16 +116,11 @@ const form = reactive({ | |||
|     tagStr: '', | ||||
|     tags: [] as Array<string>, | ||||
| }); | ||||
| const varifyPath = (rule: any, value: any, callback: any) => { | ||||
|     if (value.indexOf('Dockerfile') === -1) { | ||||
|         callback(new Error(i18n.global.t('commons.rule.selectHelper', ['Dockerfile']))); | ||||
|     } | ||||
|     callback(); | ||||
| }; | ||||
| 
 | ||||
| const rules = reactive({ | ||||
|     name: [Rules.requiredInput, Rules.imageName], | ||||
|     from: [Rules.requiredSelect], | ||||
|     dockerfile: [Rules.requiredInput, { validator: varifyPath, trigger: 'change', required: true }], | ||||
|     dockerfile: [Rules.requiredInput], | ||||
| }); | ||||
| const acceptParams = async () => { | ||||
|     logVisiable.value = false; | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue