diff --git a/app.yml b/app.yml index 9f37147d67..d2e2f4c437 100644 --- a/app.yml +++ b/app.yml @@ -6,7 +6,7 @@ is_use_celery: True author: 蓝鲸智云 introduction: 标准运维是通过一套成熟稳定的任务调度引擎,把在多系统间的工作整合到一个流程,助力运维实现跨系统调度自动化的SaaS应用。 introduction_en: SOPS is a SaaS application that utilizes a set of mature and stable task scheduling engines to help realize cross-system scheduling automation, and integrates the work among multiple systems into a single process. -version: 3.31.0 +version: 3.31.10 category: 运维工具 language_support: 中文 desktop: diff --git a/app_desc.yaml b/app_desc.yaml index c45cea3485..ce8c26804c 100644 --- a/app_desc.yaml +++ b/app_desc.yaml @@ -1,5 +1,5 @@ spec_version: 2 -app_version: "3.31.0" +app_version: "3.31.10" app: region: default bk_app_code: bk_sops diff --git a/config/default.py b/config/default.py index 9b725d51d0..24bd55e7a7 100644 --- a/config/default.py +++ b/config/default.py @@ -211,7 +211,7 @@ # mako模板中: # 如果静态资源修改了以后,上线前改这个版本号即可 -STATIC_VERSION = "3.31.0" +STATIC_VERSION = "3.31.10" DEPLOY_DATETIME = datetime.datetime.now().strftime("%Y%m%d%H%M%S") STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] @@ -506,6 +506,8 @@ def _(s): CELERY_QUEUES.extend(taskflow3_celery_settings.CELERY_QUEUES) CELERY_QUEUES.extend(cleaner_settings.CELERY_QUEUES) +CELERY_ROUTES.update({"gcloud.clocked_task.tasks.clocked_task_start": PIPELINE_ADDITIONAL_PRIORITY_ROUTING}) + # CELERY与RabbitMQ增加60秒心跳设置项 BROKER_HEARTBEAT = 60 BROKER_POOL_LIMIT = env.CELERY_BROKER_POOL_LIMIT @@ -835,6 +837,10 @@ def check_engine_admin_permission(request, *args, **kwargs): # 任务列表过滤失败任务最大天数 TASK_LIST_STATUS_FILTER_DAYS = env.BKPAAS_TASK_LIST_STATUS_FILTER_DAYS +# 第三方插件特殊轮询时间配置 +REMOTE_PLUGIN_FIX_INTERVAL_CODES = env.REMOTE_PLUGIN_FIX_INTERVAL_CODES +REMOTE_PLUGIN_FIX_INTERVAL = env.REMOTE_PLUGIN_FIX_INTERVAL + # 支持限制接口的 app ALLOWED_LIMITED_API_APPS = env.ALLOWED_LIMITED_API_APPS diff --git a/env.py b/env.py index 65bdcd6f2a..832acf7ae7 100644 --- a/env.py +++ b/env.py @@ -124,6 +124,13 @@ # 默认六个月 BKPAAS_TASK_LIST_STATUS_FILTER_DAYS = int(os.getenv("BKPAAS_TASK_LIST_STATUS_FILTER_DAYS", 180)) +# 第三方插件特殊轮询时间配置 +REMOTE_PLUGIN_FIX_INTERVAL_CODES_STR = os.getenv("BKAPP_REMOTE_PLUGIN_FIX_INTERVAL_CODES", "") +REMOTE_PLUGIN_FIX_INTERVAL_CODES = ( + REMOTE_PLUGIN_FIX_INTERVAL_CODES_STR.split(",") if REMOTE_PLUGIN_FIX_INTERVAL_CODES_STR else [] +) +REMOTE_PLUGIN_FIX_INTERVAL = int(os.getenv("BKAPP_REMOTE_PLUGIN_FIX_INTERVAL", 60)) + # 支持限制接口的 app ALLOWED_LIMITED_API_APPS = [app for app in os.getenv("BKAPP_ALLOWED_LIMITED_API_APPS", "").split(",") if app] diff --git a/frontend/desktop/package.json b/frontend/desktop/package.json index b9cfec0a97..cf038db4f6 100644 --- a/frontend/desktop/package.json +++ b/frontend/desktop/package.json @@ -12,7 +12,7 @@ "license": "ISC", "dependencies": { "@blueking/bkcharts": "^2.0.11-alpha.5", - "@blueking/bkui-form": "0.0.35", + "@blueking/bkui-form": "0.0.41", "@blueking/crypto-js-sdk": "0.0.5", "@blueking/user-selector": "^1.0.5-beta.2", "@vue/babel-preset-jsx": "^1.3.0", diff --git a/frontend/desktop/src/components/common/RenderForm/tags/TagCodeEditor.vue b/frontend/desktop/src/components/common/RenderForm/tags/TagCodeEditor.vue index e438f40b05..b9804d79e5 100644 --- a/frontend/desktop/src/components/common/RenderForm/tags/TagCodeEditor.vue +++ b/frontend/desktop/src/components/common/RenderForm/tags/TagCodeEditor.vue @@ -223,6 +223,7 @@ }) }) this.decorationsMap = {} + this.globalVarLength = 0 } }, onLanguageChange () { diff --git a/frontend/desktop/src/components/common/RenderForm/tags/TagInput.vue b/frontend/desktop/src/components/common/RenderForm/tags/TagInput.vue index bc2ef4a6fc..5559ddd513 100644 --- a/frontend/desktop/src/components/common/RenderForm/tags/TagInput.vue +++ b/frontend/desktop/src/components/common/RenderForm/tags/TagInput.vue @@ -170,8 +170,8 @@ this.$nextTick(() => { const divInputDom = this.$el.querySelector('.div-input') if (divInputDom) { - divInputDom.innerHTML = this.value - this.handleInputBlur() + divInputDom.innerText = this.value + this.updateInputHtml() } }) } @@ -180,17 +180,17 @@ // 如果表单项开启了变量免渲染,不以tag展示 if (!val) { const divInputDom = this.$el.querySelector('.div-input') - divInputDom.innerHTML = this.value + divInputDom.innerText = this.value } else { - this.handleInputBlur() + this.updateInputHtml() } }, formMode (val) { if (val) { this.$nextTick(() => { const divInputDom = this.$el.querySelector('.div-input') - divInputDom.innerHTML = this.value - this.handleInputBlur() + divInputDom.innerText = this.value + this.updateInputHtml() }) } else { this.validate() @@ -203,14 +203,19 @@ mounted () { const divInputDom = this.$el.querySelector('.div-input') if (divInputDom) { - divInputDom.innerHTML = this.value + divInputDom.innerText = this.value if (this.render && this.value) { - this.handleInputBlur() + this.updateInputHtml() } + divInputDom.addEventListener('paste', this.handlePaste) } }, beforeDestroy () { window.removeEventListener('click', this.handleListShow, false) + const divInputDom = this.$el.querySelector('.div-input') + if (divInputDom) { + divInputDom.removeEventListener('paste', this.handlePaste) + } }, methods: { handleListShow (e) { @@ -310,6 +315,7 @@ // 文本框输入 handleInputChange (e, selection) { if (!selection) { + // 实时更新 this.updateInputValue() } let matchResult = [] @@ -318,10 +324,22 @@ this.isListOpen = false return } + // 获取文本 this.lastEditRange = window.getSelection().getRangeAt(0) const offsetText = focusNode.data.substring(0, anchorOffset) + let matchText = offsetText + + // 如果不包含$则不进行后续计算 + if (matchText.indexOf('$') === -1) { + this.isListOpen = false + return + } + + // 过滤掉完整的变量格式文本 const varRegexp = /\s?\${[a-zA-Z_][\w|.]*}\s?/g - let matchText = offsetText.split(varRegexp).pop() + if (varRegexp.test(matchText)) { + matchText = offsetText.split(varRegexp).pop() + } // 拿到字段最后以$开头的部分 matchText = matchText.replace(/(.*)(\$[^\}]*)/, ($0, $1, $2) => $2) // 判断是否为变量格式 @@ -389,16 +407,23 @@ ? item.value : item.textContent.trim() === '' ? ' ' - : item.textContent.replace(/ /g, ' ') + : item.textContent }).join('') } + inputValue = inputValue.replace(/\u00A0/g, ' ') this.input.value = inputValue - this.updateForm(inputValue) }, // 文本框失焦 handleInputBlur (e) { this.$emit('blur') this.input.focus = false + // 更新文本框结构,生成tag标签 + this.updateInputHtml() + // 向上更新表单 + this.updateForm(this.input.value) + }, + // 更新文本框结构,生成tag标签 + updateInputHtml () { // 如果表单项开启了变量免渲染,不以tag展示 if (!this.render) return // 支持所有变量(系统变量,内置变量,自定义变量) @@ -411,6 +436,13 @@ return item.type === 'button' ? item.value : item.textContent }).join('') } + // 将html标签拆成文本形式 + domValue = domValue.replace(/(<|>)/g, ($0, $1) => `${$1}`) + // 用户手动输入的实体字符渲染时需要切开展示 + domValue = domValue.replace(/&(nbsp|ensp|emsp|thinsp|zwnj|zwj|quot|apos|lt|gt|amp|cent|pound|yen|euro|sect|copy|reg|trade|times|divide);/g, ($0, $1) => { + return `&${$1};` + }) + const innerHtml = domValue.replace(varRegexp, (match, $0) => { let isExistVar = false if ($0) { @@ -424,12 +456,15 @@ } if (isExistVar) { const randomId = Math.random().toString().slice(-6) - return `` // 两边留空格保持间距 + // 将装转的尖括号恢复原样 + let value = match.replace(/(<|>)<\/span>/g, ($0, $1) => $1) + // 将双引号转为实体字符 + value = value.replace(/"/g, '"') + return `` } return match }) divInputDom.innerHTML = innerHtml - this.updateInputValue() }, // 文本框按键事件 handleInputKeyDown (e) { @@ -476,6 +511,28 @@ handleBlur () { this.emit_event(this.tagCode, 'blur', this.value) this.$emit('blur', this.value) + }, + handlePaste (e) { + event.preventDefault() + let text = '' + const clp = (e.originalEvent || e).clipboardData + if (clp === undefined || clp === null) { + text = window.clipboardData.getData('text') || '' + text = text.replace(/(\n|\r|\r\n)/g, ' ') + if (text !== '') { + if (window.getSelection) { + const newNode = document.createElement('span') + newNode.innerHTML = text + window.getSelection().getRangeAt(0).insertNode(newNode) + } else { + document.selection.createRange().pasteHTML(text) + } + } + } else { + text = clp.getData('text/plain') || '' + text = text.replace(/(\n|\r|\r\n)/g, ' ') + text && document.execCommand('insertText', false, text) + } } } } @@ -558,7 +615,7 @@ line-height: 18px; padding: 7px 0; color: #63656e; - white-space: nowrap; + white-space: pre; overflow: hidden; /deep/.var-tag { margin-right: 1px; diff --git a/frontend/desktop/src/components/common/RenderForm/tags/TagTextarea.vue b/frontend/desktop/src/components/common/RenderForm/tags/TagTextarea.vue index f3588658aa..e32e75f5e3 100644 --- a/frontend/desktop/src/components/common/RenderForm/tags/TagTextarea.vue +++ b/frontend/desktop/src/components/common/RenderForm/tags/TagTextarea.vue @@ -138,7 +138,7 @@ const divInputDom = this.$el.querySelector('.div-input') if (divInputDom) { divInputDom.innerText = this.value - this.handleInputBlur() + this.updateInputHtml() } }) } @@ -149,7 +149,7 @@ const divInputDom = this.$el.querySelector('.div-input') divInputDom.innerText = this.value } else { - this.handleInputBlur() + this.updateInputHtml() } } }, @@ -162,14 +162,17 @@ const value = typeof this.value === 'string' ? this.value : JSON.stringify(this.value) divInputDom.innerText = value if (this.render && value) { - this.handleInputBlur() - // 把用户手动换行变成div标签 - divInputDom.innerHTML = divInputDom.innerHTML.replace(/
/g, '

') + this.updateInputHtml() } + divInputDom.addEventListener('paste', this.handlePaste) } }, beforeDestroy () { window.removeEventListener('click', this.handleListShow, false) + const divInputDom = this.$el.querySelector('.div-input') + if (divInputDom) { + divInputDom.removeEventListener('paste', this.handlePaste) + } }, methods: { handleListShow (e) { @@ -256,6 +259,7 @@ // 文本框输入 handleInputChange (e, updateForm = true) { if (updateForm) { + // 实时更新 this.updateInputValue() } const range = window.getSelection().getRangeAt(0) @@ -271,12 +275,12 @@ const lastNode = textNode.childNodes[startOffset - 1] previousText = lastNode.textContent } - const matchText = previousText.replace(/(.*)(\$[^\}]*)/, ($0, $1, $2) => $2) - // 如果是完整全局变量则不进行后续操作 - if (/^\$\{\w+\}$/.test(matchText)) { + // 如果不包含$则不进行后续计算、 如果是完整全局变量则不进行后续操作 + if (previousText.indexOf('$') === -1 || /\${[a-zA-Z_][\w|.]*}/.test(previousText)) { this.isListOpen = false return } + const matchText = previousText.replace(/(.*)(\$[^\}]*)/, ($0, $1, $2) => $2) // 判断是否为变量格式 if (matchText === '$' || /^\${[a-zA-Z_]*[\w|.]*/.test(matchText)) { this.varList = this.constantArr.filter(item => item.key.indexOf(matchText) > -1) @@ -344,32 +348,44 @@ const childNodes = Array.from(divInputDom.childNodes).filter(item => item.nodeName !== 'TEXT') const inputValue = childNodes.map(dom => { // 获取行内纯文本 - let domValue = dom.textContent || '\n' + let domValue = dom.textContent if (dom.childNodes.length) { domValue = Array.from(dom.childNodes).map(item => { return item.type === 'button' ? item.value - : item.textContent.trim() === '' - ? ' ' - : item.textContent.replace(/ /g, ' ') + : item.nodeName === 'BR' + ? '' + : item.textContent }).join('') } - return domValue + return domValue.replace(/\u00A0/g, ' ') }).join('\n') this.input.value = inputValue - this.updateForm(inputValue) }, // 文本框失焦 handleInputBlur (e) { this.$emit('blur') this.input.focus = false + // 更新文本框结构,生成tag标签 + this.updateInputHtml() + // 向上更新表单 + this.updateForm(this.input.value) + }, + // 更新文本框结构,生成tag标签 + updateInputHtml () { // 如果表单项开启了变量免渲染,不以tag展示 if (!this.render) return // 支持所有变量(系统变量,内置变量,自定义变量) const varRegexp = /\${([^${}]+)}/g const divInputDom = this.$el.querySelector('.div-input') const childNodes = Array.from(divInputDom.childNodes).filter(item => item.nodeName !== 'TEXT') - childNodes.forEach(dom => { + const deleteMap = {} // 需要删除的br下标 + childNodes.forEach((dom, index) => { + // 删除多余的br标签 + if (deleteMap[index]) { + divInputDom.removeChild(dom) + return + } // 获取行内纯文本 let domValue = dom.textContent if (dom.childNodes.length) { @@ -377,6 +393,15 @@ return item.type === 'button' ? item.value : item.textContent }).join('') } + // 将html标签拆成文本形式 + domValue = domValue.replace(/(<|>)/g, ($0, $1) => `${$1}`) + // 用户手动输入的实体字符渲染时需要切开展示 + domValue = domValue.replace(/&(nbsp|ensp|emsp|thinsp|zwnj|zwj|quot|apos|lt|gt|amp|cent|pound|yen|euro|sect|copy|reg|trade|times|divide);/g, ($0, $1) => { + return `&${$1};` + }) + + // 初始化时是通过innerText进行复制的,如果有多个连续空格则只会显示一个,所以需手动将转为  + domValue = domValue.replace(/( )/g, ' ') // 支持匹配变量内运算 const innerHtml = domValue.replace(varRegexp, (match, $0) => { let isExistVar = false @@ -390,25 +415,38 @@ }) } if (isExistVar) { - // 两边留空格保持间距 const randomId = Math.random().toString().slice(-6) - return `` + // 将装转的尖括号恢复原样 + let value = match.replace(/(<|>)<\/span>/g, ($0, $1) => $1) + // 将双引号转为实体字符 + value = value.replace(/"/g, '"') + return `` } return match }) - // div会将\n解析成
标签,需要手动把内容标签下的
标签清理掉 - if (dom.nodeName !== 'BR' && dom.nextElementSibling?.nodeName === 'BR') { - divInputDom.removeChild(dom.nextElementSibling) - } + // 初始化时\n会转化为【独占一行】的
标签,导致渲染异常。当我们手动把text标签转为div标签时需要删除【紧挨】着的
标签 if (dom.nodeName === '#text') { + // 记录需要被删除的br标签下标 + if (dom.nextSibling?.nodeName === 'BR') { + deleteMap[index + 1] = true + } const newDom = document.createElement('div') newDom.innerHTML = innerHtml divInputDom.replaceChild(newDom, dom) - } else if (dom.nodeName === 'DIV') { + } else if (dom.nodeName === 'DIV' && innerHtml) { dom.innerHTML = innerHtml + } else if (dom.nodeName === 'BR') { + // br标签实际上是初始化时\n转化的,\n表示当前行换行了,那么br标签必定会有下一行!!! + if (!dom.nextSibling) { + const appendDom = document.createElement('div') + appendDom.innerHTML = '
' + divInputDom.appendChild(appendDom) + } + const newDom = document.createElement('div') + newDom.innerHTML = '
' + divInputDom.replaceChild(newDom, dom) } }) - this.updateInputValue() }, // 文本框按键事件 handleInputKeyDown (e) { @@ -465,6 +503,28 @@ handleBlur () { this.emit_event(this.tagCode, 'blur', this.value) this.$emit('blur', this.value) + }, + handlePaste (e) { + event.preventDefault() + let text = '' + const clp = (e.originalEvent || e).clipboardData + if (clp === undefined || clp === null) { + text = window.clipboardData.getData('text') || '' + text = text.replace(/(\r|\r\n)/g, '') + if (text !== '') { + if (window.getSelection) { + const newNode = document.createElement('span') + newNode.innerHTML = text + window.getSelection().getRangeAt(0).insertNode(newNode) + } else { + document.selection.createRange().pasteHTML(text) + } + } + } else { + text = clp.getData('text/plain') || '' + text = text.replace(/(\r|\r\n)/g, '') + text && document.execCommand('insertText', false, text) + } } } } @@ -553,6 +613,9 @@ background: #eaebf0; } } + /deep/div { + word-break: break-all; + } &.input-before::before { content: attr(data-placeholder); color: #c4c6cc; diff --git a/frontend/desktop/src/components/common/TemplateCanvas/PalettePanel/index.vue b/frontend/desktop/src/components/common/TemplateCanvas/PalettePanel/index.vue index 75cd4e75ed..1d65dc2f7b 100755 --- a/frontend/desktop/src/components/common/TemplateCanvas/PalettePanel/index.vue +++ b/frontend/desktop/src/components/common/TemplateCanvas/PalettePanel/index.vue @@ -34,14 +34,12 @@
+ data-type="tasknode">
+ data-type="subflow">
diff --git a/frontend/desktop/src/components/common/TemplateCanvas/ToolPanel/index.vue b/frontend/desktop/src/components/common/TemplateCanvas/ToolPanel/index.vue index b4299a7edc..1efc7007c1 100644 --- a/frontend/desktop/src/components/common/TemplateCanvas/ToolPanel/index.vue +++ b/frontend/desktop/src/components/common/TemplateCanvas/ToolPanel/index.vue @@ -27,6 +27,7 @@
{{ zoomRatio + '%' }}

- {{ '--' }} + {{ $t('执行历史') }}
@@ -747,6 +747,8 @@ } .empty-text { padding: 5px; + color: #ccc; + cursor: not-allowed; } } diff --git a/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue b/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue index 3181e16b05..26c2f3387f 100644 --- a/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue +++ b/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue @@ -30,7 +30,8 @@
@@ -165,7 +169,7 @@
-
+