From 7fd854160a0e2f097463add2fd022d316f8b5af4 Mon Sep 17 00:00:00 2001 From: GONGONGONG <506419689@qq.com> Date: Wed, 21 Feb 2024 14:48:26 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:Agent=E5=8C=85=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=BC=80=E5=8F=91--story=3D119532566=20Agent=E5=8C=85=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/modules/pkg_manage.js | 33 + frontend/src/common/demand-import.ts | 4 +- frontend/src/common/util.ts | 22 + .../src/components/common/flexible-tag.vue | 214 +++-- frontend/src/components/common/navigation.vue | 49 +- frontend/src/components/common/tag.vue | 458 +++++++++++ frontend/src/components/common/upload.vue | 20 +- .../setup-table/install-input-type.vue | 33 +- .../components/setup-table/install-table.vue | 67 +- .../components/setup-table/table-header.vue | 58 +- .../src/components/setup-table/upload.vue | 18 +- frontend/src/css/app.css | 20 +- frontend/src/css/install-table.css | 5 + frontend/src/css/variable.css | 2 + frontend/src/i18n/en.js | 78 +- frontend/src/i18n/zh.js | 76 +- frontend/src/router/modules/agent-manager.ts | 15 + frontend/src/router/navigation-config.ts | 8 +- frontend/src/setup/index.ts | 8 +- frontend/src/store/modules/agent.ts | 98 ++- frontend/src/store/modules/cloud.ts | 2 +- frontend/src/store/modules/main.ts | 20 +- frontend/src/types/agent/agent-type.ts | 2 + frontend/src/types/agent/pkg-manage.ts | 102 +++ frontend/src/types/index.ts | 15 +- frontend/src/views/agent/agent-list.vue | 106 ++- .../views/agent/agent-setup/agent-import.vue | 149 +++- .../views/agent/agent-setup/agent-setup.vue | 268 ++++++- .../agent/components/choose-pkg-dialog.vue | 516 +++++++++++++ .../views/agent/components/create-excel.ts | 1 + .../agent/components/setup-pkg-table.vue | 204 +++++ .../src/views/agent/config/editTableConfig.ts | 46 +- .../views/agent/config/importTableConfig.js | 13 + frontend/src/views/agent/package/PkgThead.vue | 345 +++++++++ frontend/src/views/agent/package/index.vue | 728 ++++++++++++++++++ .../src/views/agent/package/package-cols.vue | 401 ++++++++++ .../views/agent/package/package-upload.vue | 479 ++++++++++++ .../cloud-manager-add/cloud-manager-setup.vue | 99 ++- .../cloud-detail-slider.vue | 12 +- .../cloud-detail-table.vue | 93 ++- frontend/src/views/cloud/cloud-manager.vue | 2 +- .../components/sideslider-content-edit.vue | 249 +++++- .../cloud/components/sideslider-content.vue | 8 +- .../src/views/cloud/config/netTableConfig.ts | 19 + .../views/cloud/config/proxy-detail-config.js | 7 + .../plugin/plugin-list/plugin-list-table.vue | 2 +- .../plugin-package/plugin-package-table.vue | 2 +- .../plugin-rule-list/plugin-rule-table.vue | 4 +- frontend/src/views/task/task-detail-table.vue | 2 +- frontend/src/views/task/task-list-table.vue | 2 +- frontend/src/vue-proto.d.ts | 8 +- support-files/bkpkg/bk_nodeman.yaml | 2 +- support-files/bkpkg/bknodeman.yaml | 1 - 53 files changed, 4982 insertions(+), 213 deletions(-) create mode 100644 frontend/src/api/modules/pkg_manage.js create mode 100644 frontend/src/components/common/tag.vue create mode 100644 frontend/src/types/agent/pkg-manage.ts create mode 100644 frontend/src/views/agent/components/choose-pkg-dialog.vue create mode 100644 frontend/src/views/agent/components/setup-pkg-table.vue create mode 100644 frontend/src/views/agent/package/PkgThead.vue create mode 100644 frontend/src/views/agent/package/index.vue create mode 100644 frontend/src/views/agent/package/package-cols.vue create mode 100644 frontend/src/views/agent/package/package-upload.vue diff --git a/frontend/src/api/modules/pkg_manage.js b/frontend/src/api/modules/pkg_manage.js new file mode 100644 index 000000000..bd493304e --- /dev/null +++ b/frontend/src/api/modules/pkg_manage.js @@ -0,0 +1,33 @@ +import { request } from '../base'; + +export const listPackage = request('GET', 'api/agent/package/'); +export const listPackageNew = request('POST', 'api/agent/package/search/{{pk}}'); +export const updatePackage = request('PUT', 'api/agent/package/{{pk}}/'); +export const deletePackage = request('DELETE', 'api/agent/package/{{pk}}/'); +export const quickSearchCondition = request('GET', 'api/agent/package/quick_search_condition/'); +export const uploadPackage = request('POST', 'api/agent/package/upload/'); +export const parsePackage = request('POST', 'api/agent/package/parse/'); +export const createAgentRegisterTask = request('POST', 'api/agent/package/create_register_task/'); +export const queryAgentRegisterTask = request('GET', 'api/agent/package/query_register_task/'); +export const getTags = request('GET', 'api/agent/package/tags/'); +export const getVersion = request('POST', 'api/agent/package/version/'); +export const getDeployedHostsCount = request('POST', 'api/agent/package/deployed_hosts_count/'); +export const createAgentTags = request('POST', 'api/agent/package/create_agent_tags/'); +export const versionCompare = request('POST', 'api/agent/package/version_compare/'); + +export default { + listPackage, + updatePackage, + listPackageNew, + deletePackage, + quickSearchCondition, + uploadPackage, + parsePackage, + createAgentRegisterTask, + queryAgentRegisterTask, + getTags, + getVersion, + getDeployedHostsCount, + createAgentTags, + versionCompare, +}; diff --git a/frontend/src/common/demand-import.ts b/frontend/src/common/demand-import.ts index 6760bd3bc..e1852cc1b 100644 --- a/frontend/src/common/demand-import.ts +++ b/frontend/src/common/demand-import.ts @@ -4,7 +4,8 @@ import Vue from 'vue'; import { bkBadge, bkButton, bkCheckbox, bkCheckboxGroup, bkCol, bkCollapse, bkCollapseItem, bkContainer, bkDatePicker, - bkDialog, bkDropdownMenu, bkException, bkForm, bkFormItem, bkInfoBox, bkInput, bkLoading, bkMessage, + bkDialog, bkDropdownMenu, bkException, bkForm, bkFormItem, bkComposeFormItem, + bkInfoBox, bkInput, bkLoading, bkMessage, bkNavigation, bkNavigationMenu, bkNavigationMenuItem, bkNotify, bkOption, bkOptionGroup, bkPagination, bkPopover, bkProcess, bkProgress, bkRadio, bkRadioGroup, bkRoundProgress, bkRow, bkSearchSelect, bkSelect, bkSideslider, bkSlider, bkSteps, bkSwitcher, bkTab, bkTabPanel, bkTable, bkTableColumn, bkTagInput, bkTimePicker, @@ -31,6 +32,7 @@ Vue.use(bkDropdownMenu); Vue.use(bkException); Vue.use(bkForm); Vue.use(bkFormItem); +Vue.use(bkComposeFormItem); Vue.use(bkInput); Vue.use(bkNavigation); Vue.use(bkNavigationMenu); diff --git a/frontend/src/common/util.ts b/frontend/src/common/util.ts index 4ad20d4c0..acfa1f657 100644 --- a/frontend/src/common/util.ts +++ b/frontend/src/common/util.ts @@ -588,6 +588,28 @@ export const sort = (arr: any[], key: string) => { return (`${pre}`).toString().localeCompare((`${next}`)); }); }; +/** + * 单个函数用于解析和比较版本号 + * 排序规则:1. 数字 + */ +export const compareVersions = (a: string, b: string) => { + if (a === b) return 0; + if (!a || !b) return a ? 1 : -1; + // 解析版本号字符串,返回数字数组 + const parseVersion = (version: string) => version?.match(/\d+/g)?.map(Number); + + // 获取版本号的数字数组 + const versionA = parseVersion(a) || []; + const versionB = parseVersion(b) || []; + + // 比较主版本号、次版本号、补丁号和附加编号 + for (let i = 0; i < versionA.length; i++) { + const diff = versionA[i] - versionB[i]; + if (diff !== 0) return diff; + } + + return 0; // 全部相同 +} /** * 对象深拷贝 * @param {*} obj diff --git a/frontend/src/components/common/flexible-tag.vue b/frontend/src/components/common/flexible-tag.vue index ace9eb8cb..20c49672a 100644 --- a/frontend/src/components/common/flexible-tag.vue +++ b/frontend/src/components/common/flexible-tag.vue @@ -5,103 +5,155 @@ content: allTagContext }">
- {{ idKey ? item[idKey] : item }} + v-bind="item"> + {{ item.tagDisplay }}
diff --git a/frontend/src/components/common/navigation.vue b/frontend/src/components/common/navigation.vue index 055c8151f..18d8aba7b 100644 --- a/frontend/src/components/common/navigation.vue +++ b/frontend/src/components/common/navigation.vue @@ -1,5 +1,5 @@ diff --git a/frontend/src/components/setup-table/install-input-type.vue b/frontend/src/components/setup-table/install-input-type.vue index 9690964df..1ccdc4cb6 100644 --- a/frontend/src/components/setup-table/install-input-type.vue +++ b/frontend/src/components/setup-table/install-input-type.vue @@ -164,6 +164,14 @@ :value="inputValue" @change="handleChange"> +
+ {{ inputValue }} +
-- @@ -260,7 +268,11 @@ export default class InputType extends Mixins(emitter) { } @Watch('inputValue') - public handleValueChange() { + public handleValueChange(val: IValue) { + // 当类型为 choose 时是直接改变原始数据,从而出发change事件进行rules校验 + if (this.type === 'choose') { + this.handleChange(val); + } this.$nextTick(this.setRows); } @@ -277,6 +289,8 @@ export default class InputType extends Mixins(emitter) { } else if (['select', 'biz'].includes(this.type)) { // select框类型focus(展示下拉列表) this.inputRef && this.inputRef.show(); + } else if (this.type === 'choose') { + this.inputRef?.click?.(); } } } @@ -300,6 +314,10 @@ export default class InputType extends Mixins(emitter) { public handleEmitUpload(value: IFileInfo) { return value; } + @Emit('choose') + public handleEmitChoose(event?: Event) { + return { instance: this, event }; + } public handleInput(newValue: IValue, oldValue: IValue) { this.$emit('input', newValue, oldValue); } @@ -500,5 +518,18 @@ export default class InputType extends Mixins(emitter) { .group-text { padding: 0 4px; } + + .input-choose { + cursor: pointer; + &:before { + display: inline-flex; + align-items: center; + position: absolute; + top: 0; + content: attr(data-placeholder); + font-size: 12px; + color: #c4c6cc; + } + } } diff --git a/frontend/src/components/setup-table/install-table.vue b/frontend/src/components/setup-table/install-table.vue index 8e55403cc..3af74749b 100644 --- a/frontend/src/components/setup-table/install-table.vue +++ b/frontend/src/components/setup-table/install-table.vue @@ -44,7 +44,7 @@ batch: config.getBatch ? config.getBatch.call(_self) : config.batch, isBatchIconShow: !!table.data.length && (config.getBatch ? config.getBatch.call(_self) : config.batch), - type: config.type, + type: getHeadType({}, config), subTitle: config.subTitle, options: getCellInputOptions({}, config), multiple: !!config.multiple, @@ -53,8 +53,10 @@ appendSlot: config.appendSlot, parentProp: config.parentProp, parentTip: config.parentTip, - focusRow: focusRow + focusRow: focusRow, + extraInfo: config.extraInfo || {} }" + @chooseVersion="handleChooseVersion" @confirm="handleBatchConfirm(arguments, config)"> @@ -163,6 +165,7 @@ @focus="handleCellFocus(arguments, { row, config, rowIndex, colIndex })" @blur="handleCellBlur(arguments, { row, config, rowIndex, colIndex })" @input="handleCellValueInput(arguments, row, config)" + @choose="handleCellChoose(arguments, { row, config, rowIndex, colIndex })" @change="handleCellValueChange(row, config)" @upload-change="handleCellUploadChange($event, row)"> @@ -245,6 +248,7 @@ export default class SetupTable extends Vue { @Prop({ type: Array }) private readonly aps!: IAp[]; @Prop({ type: Array }) private readonly clouds!: ICloudSource[]; @Prop() private readonly arbitrary!: any; // 可以是任意值, 用来在config文件里做为必要的一些参数 + @Prop({ type: String, default: '' }) private readonly type!: string; @Ref('tableBody') private readonly tableBody!: any; @Ref('scrollPlace') private readonly scrollPlace!: any; @@ -266,7 +270,7 @@ export default class SetupTable extends Vue { // 滚动节流 private handleScroll!: Function; // 处于编辑态的数据 - private editData: { id: number, prop: string }[] = []; + public editData: { id: number, prop: string }[] = []; private hasScroll= false; private listenResize!: Function; private focusRow: any = {}; @@ -280,6 +284,28 @@ export default class SetupTable extends Vue { private get apList() { return this.aps || AgentStore.apList; } + private pkgVersionList: any = []; + // 获取agent包版本 + private async getPkgVersions() { + const { + pkg_info, + } = await AgentStore.apiGetPkgVersion({ + project: 'gse_agent', + os: '', + cpu_arch: '' + }); + const builtinTags = ['stable', 'latest', 'test']; + this.pkgVersionList.splice(0, this.pkgVersionList.length, ...pkg_info.map(item => ({ + ...item, + id: item.version, + name: item.version, + tags: item.tags.filter(tag => builtinTags.includes(tag.name)).map(tag => ({ + className: tag.name, + description: tag.description, + name: tag.description, + })), + }))); + } private get channelList() { return AgentStore.channelList; } @@ -312,9 +338,11 @@ export default class SetupTable extends Vue { private created() { this.handleInit(); + } private mounted() { this.handleScroll(); + this.getPkgVersions(); window.addEventListener('resize', this.initTableHead); } private beforeDestroy() { @@ -398,6 +426,17 @@ export default class SetupTable extends Vue { } row.validator = {}; } + /** + * 获取表头类型 + * @param {Object} row 当前行 + * @param {Object} config 当前配置项 + */ + private getHeadType(row: ISetupRow, config: ISetupHead): string { + if (config.prop === 'version' && config.batch) { + return 'version'; + } + return config.type; + } /** * 获取select框的options数据 * @param {Object} row 当前行 @@ -411,6 +450,8 @@ export default class SetupTable extends Vue { })); } if (config.type === 'select') { return config.getOptions ? config.getOptions.call(this, row) : config.options; + } if (config.prop === 'version') { + return this.pkgVersionList; } return []; } @@ -462,7 +503,7 @@ export default class SetupTable extends Vue { const [newValue] = arg; const prop = config.prop as IKeysMatch; const sync = config.sync as IKeysMatch; - const syncSource = typeof row[prop] === 'undefined' ? '' : row[prop].trim(); + const syncSource = typeof row[prop] === 'undefined' ? '' : row[prop]?.trim(); const syncTarget = typeof row[sync] === 'undefined' ? '' : row[sync].trim(); if (sync && syncSource === syncTarget) { row[sync] = newValue; @@ -478,7 +519,7 @@ export default class SetupTable extends Vue { MainStore.updateEdited(true); const prop = config.prop as IKeysMatch; if (config.type !== 'textarea' && row[prop] && typeof row[prop] === 'string') { - row[prop] = row[prop].trim(); + row[prop] = row[prop]?.trim(); } if (config.handleValueChange) { config.handleValueChange.call(this, row); @@ -497,6 +538,12 @@ export default class SetupTable extends Vue { } return ''; } + @Emit('choose') + private handleCellChoose(arg: any[], bb: { row: ISetupRow }) { + const [param] = arg; + const { row } = bb; + return { instance: param.instance, row }; + } private rootScroll() { if (!this.virtualScroll) return; @@ -575,7 +622,7 @@ export default class SetupTable extends Vue { } const ipRepeat = this.table.data.some((row: ISetupRow | any) => { if (row.id === rowId) return false; - let targetValue = !isEmpty(row[prop]) ? row[prop].trim() : ''; + let targetValue = !isEmpty(row[prop]) ? row[prop]?.trim() : ''; // 1. 处理多值的情况 // eslint-disable-next-line @typescript-eslint/prefer-optional-chain if (splitCode && splitCode.length) { @@ -858,6 +905,14 @@ export default class SetupTable extends Vue { } return []; } + /** + * 单独处理agent版本选择 + */ + private handleChooseVersion(version: string) { + this.table.data.forEach((row) => { + row.version = version; + }); + } /** * 批量编辑确定事件 */ diff --git a/frontend/src/components/setup-table/table-header.vue b/frontend/src/components/setup-table/table-header.vue index 75b02422d..ddc289c5e 100644 --- a/frontend/src/components/setup-table/table-header.vue +++ b/frontend/src/components/setup-table/table-header.vue @@ -16,9 +16,31 @@ {{ $t(label) }} + + ({}) }) private readonly focusRow!: ISetupRow; @Prop({ type: String, default: 'light setup-tips' }) private readonly tipTheme!: string; + @Prop({ type: Object, default: () => ({}) }) private readonly extraInfo!: any; @Ref('batch') private readonly batchRef!: any; @Ref('tipSpan') private readonly tipSpan!: any; @@ -139,7 +164,21 @@ export default class TableHeader extends Vue { private popoverInstance: any = null; // 切换Popover的触发方式, 规避无法切换导致的问题 (setProps函数的替代方案, 低版tippy本无此方法) private tipsTrigger: 'mouseenter' | 'manual' = 'mouseenter'; - + private chooseVersion = ''; + private versionsDialog = { + project: 'gse_agent', + show: false, + type: 'unified', + operate: 'reinstall_batch', + title: this.$t('选择 Agent 版本'), + versions: [] as string[], + }; + @Emit('chooseVersion') + public versionConfirm(info: { version: string; }) { + this.chooseVersion = info.version; + return info.version; + } + private get remark() { return getConfigRemark(`${this.prop}__description`, this.focusRow.os_type); } @@ -179,6 +218,21 @@ export default class TableHeader extends Vue { bus.$emit('batch-btn-click', this); } } + public handleChooseClick() { + if (this.isActive) { + this.versionsDialog.show = false; + } else { + if (this.extraInfo.pkgType === 'gse_proxy') { + this.versionsDialog.project = 'gse_proxy'; + this.versionsDialog.title = this.$t('选择Proxy版本'); + this.versionsDialog.operate = 'Install Proxy'; + } + this.versionsDialog.show = true; + this.versionsDialog.versions = this.chooseVersion ? [this.chooseVersion] : []; + this.isActive = true; + bus.$emit('batch-btn-click', this); + } + } @Emit('confirm') public handleBatchConfirm() { this.handleBatchCancel(); diff --git a/frontend/src/components/setup-table/upload.vue b/frontend/src/components/setup-table/upload.vue index d84c0a221..9752139f8 100644 --- a/frontend/src/components/setup-table/upload.vue +++ b/frontend/src/components/setup-table/upload.vue @@ -47,6 +47,7 @@ import { Vue, Component, Model, Prop, Emit, Ref, Watch } from 'vue-property-decorator'; import { IFileInfo } from '@/types'; import { OutgoingHttpHeaders } from 'http'; +import i18n from '@/setup'; interface IObject { [key: string]: any @@ -59,10 +60,11 @@ export default class Upload extends Vue { @Prop({ type: String, default: 'file_data' }) private readonly name!: string; // 上传至服务器的名称 @Prop({ type: String, default: '' }) private readonly accept!: string; // mime类型 - @Prop({ type: String, default: window.i18n.t('文件类型不符') }) private readonly acceptTips!: string; // 接受类型提示信息 + @Prop({ type: String, default: i18n.t('版本包文件类型') }) public readonly acceptDesc!: string; // mime类型提示 + @Prop({ type: String, default: i18n.t('文件类型不符') }) private readonly acceptTips!: string; // 类型错误提示信息 @Prop({ type: String, default: '' }) private readonly action!: string; // URL @Prop({ type: Number, default: 500 }) private readonly maxSize!: number; // 最大文件大小,单位M - @Prop({ type: String, default: 'MB', validator(v) { + @Prop({ type: String, default: 'MB', validator(v: string) { return ['KB', 'MB'].includes(v); } }) private readonly unit!: string; @Prop({ type: [Array, Object], default: () => ([]) }) private readonly headers!: OutgoingHttpHeaders; // 请求头 @@ -77,6 +79,7 @@ export default class Upload extends Vue { @Prop({ type: Boolean, default: false }) private readonly parseText!: boolean; // 是否前端解析 @Prop({ type: Boolean, default: false }) private readonly disableHoverCls!: boolean; // 禁用文件框悬浮样式 @Prop({ type: Object, default: () => ({}) }) private readonly fileInfo!: IFileInfo; // 回显文件信息 + @Prop({ type: Object, default: () => ({}) }) private readonly attached!: Dictionary; // 附带的参数 @Ref('uploadel') private readonly uploadel: any; @@ -243,13 +246,22 @@ export default class Upload extends Vue { const formData = new FormData(); formData.append(option.filename, option.file, option.file.name); + try { + if (typeof this.attached === 'object') { + Object.keys(this.attached).forEach((key) => { + formData.append(key, this.attached[key]); + }); + } + } catch (_) {} xhr.onerror = (e) => { option.onError(e); }; const { action } = option; xhr.onload = () => { - if (xhr.status < 200 || xhr.status >= 300 || !JSON.parse(xhr.response).result) { + const { result, code } = JSON.parse(xhr.response); + // 3800002 包管理 - 包名重复 + if (xhr.status < 200 || xhr.status >= 300 || (!result && code !== 3800002)) { return option.onError(this.onError(action, xhr)); } option.onSuccess(this.onSuccess(xhr)); diff --git a/frontend/src/css/app.css b/frontend/src/css/app.css index 2467d700a..570fd407b 100644 --- a/frontend/src/css/app.css +++ b/frontend/src/css/app.css @@ -318,11 +318,9 @@ body { .tag-yellow { background: #ffefd6; } - .flexible-tag-group { - .flexible-tag, - .num-tag { - background: #DCDEE5; - } + .flexible-tag, + .flexible-tag-group .num-tag { + background: #DCDEE5; } .pointer-row { cursor: pointer; @@ -630,9 +628,19 @@ body { flex-direction: column; width: 280px; &-title { + padding: 8px 15px; + line-height: 24px; font-size: 14px; + color: #313238; + &.pb0 { + padding-bottom: 0; + } + } + &-sub-title { + line-height: 20px; + padding: 6px 15px; + font-size: 12px; color: #63656E; - padding: 10px 15px; } &-content { flex: 1; diff --git a/frontend/src/css/install-table.css b/frontend/src/css/install-table.css index a97355876..4aa75be17 100644 --- a/frontend/src/css/install-table.css +++ b/frontend/src/css/install-table.css @@ -150,6 +150,7 @@ /* form-item-ghost */ .ghost-wrapper { width: 100%; + z-index: 1; &.is-disabled { background-color: #fafbfd; cursor: not-allowed; @@ -307,6 +308,10 @@ z-index: 2; } } + .input-choose { + line-height: 42px; + white-space: nowrap; + } .upload-btn { height: 42px; border: 0; diff --git a/frontend/src/css/variable.css b/frontend/src/css/variable.css index 252597e68..e328d1e9b 100644 --- a/frontend/src/css/variable.css +++ b/frontend/src/css/variable.css @@ -16,6 +16,7 @@ $defaultFontColor: #63656e; $primaryFontColor: #3a84ff; $fontColor: #7b7d8a; $fontWeightColor: #737987; +$fontTitleColor: #313238; /* 主体 icon 颜色 */ $iconPrimaryColor: #3c96ff; @@ -47,6 +48,7 @@ $bgWarning: #ff9c01; $bgSuccess: #2dcb56; $bgFailed: #ea3636; $titleColor: #313238; +$bgOptHover: #e1ecff; /* 白色 */ $whiteColor: #fff; diff --git a/frontend/src/i18n/en.js b/frontend/src/i18n/en.js index fb9326624..44562f7c0 100644 --- a/frontend/src/i18n/en.js +++ b/frontend/src/i18n/en.js @@ -13,15 +13,17 @@ export const dedicated = { export const nav = { // Agent nav_节点管理: 'Node Management', - nav_Agent状态: 'Agent Status', + nav_Agent: 'Agent', + nav_Agent状态: 'Agent Management', + nav_Agent包管理: 'Agent Package', nav_Excel导入安装: 'Excel Import', nav_重装Agent: 'Reinstall Agent', nav_重载Agent配置: 'Reload Agent Config', nav_卸载Agent: 'Uninstall Agent', - nav_插件管理: 'Plugin Management', - nav_插件状态: 'Plugin Status', - nav_插件部署: 'Plugin Deployment', + nav_插件管理: 'Plugin', + nav_插件状态: 'Plugin Management', + nav_插件部署: 'Deployment Strategy', nav_新建策略: 'New Strategy', nav_编辑策略: 'Edit Strategy', nav_已有策略: 'Existing Strategy', @@ -152,15 +154,25 @@ export default { 内网网卡IP: 'LAN IP', 外网网卡IP: 'WAN IP', 登录IP: 'Login IP', + Agent包版本: 'Agent package version', + Proxy包版本: 'Proxy package version', + 选择Proxy版本: 'Select Proxy version', + '选择 Agent 版本': 'Select Agent version', 数据IP: 'Data IP', 数据IP提示: 'IP address for Agent data collection, which is bound to the LAN IP by default, it can be developed separately if any special needs ', 确定: 'OK', 批量编辑: 'Multi-Edit {title}', + 批量编辑Agent: 'Based on the system range included in the bulk edit, the currently available versions support {0} simultaneously', 安装Agent: 'Install agent', 卸载: 'Uninstall', 正在重装: 'Reinstalling', 正在升级: 'Upgrading', 复制: 'Copy', + 已选IP: 'Selected {0} IP addresses', + 升级IP: '{0} upgrades', + 回退IP: '{0} rollbacks', + 已是目标版本Ip: '{0} already the target version', + 全部: 'All', 复制IPv4: 'Copy IPv4', 复制IPv6: 'Copy IPv6', 复制ProxyIP: 'Copy Proxy IP', @@ -286,6 +298,7 @@ export default { 自动发现: 'Automatic discovery', 管控区域详情: 'BK-Net details', 升级: 'Upgrade', + 升级回退: 'Upgrade/Rollback', 升级Agent: 'Upgrade Agent', 确定升级选择的主机: 'Are you sure to upgrade the selected hosts?', 请确认是否批量重启: 'Please confirm whether to restart in batch', @@ -296,6 +309,8 @@ export default { 卸载lower: 'uninstall', 升级lower: 'upgrade', 移除lower: 'remove', + 确认Agent目标版本: 'Confirm Agent Target Version', + Agent版本: 'Agent version', 请确认是否操作: 'Please confirm whether to {type}?', 请确认是否批量操作: 'Please confirm whether to {type} in batch?', 单条确认操作提示: '{type} {ip} Agent{suffix}', @@ -359,8 +374,14 @@ export default { 批量高级设置: 'Batch advanced settings', BT节点探测: 'BT accelerate', BT节点探测提示: 'After BT accelerate is enable, the transmission efficiency of the Agent\'s file distribution through the file pipeline can be greatly improved, and at the same time, the execution speed of the agent installation task can be correspondingly improved.\n\nNote: The cross-VPC network environment may occupy the dedicated line bandwidth, it is recommended to turn off BT transmission in this scenario', - 启用: 'Enable', - 停用: 'Disable', + 启用: 'enabled', + 停用: 'disabled', + 启用状态: 'Enabled', + 停用状态: 'Disabled', + 启用操作: 'Enable', + 停用操作: 'Disable', + 稳定版本不可删除: 'Stable version cannot be deleted', + 稳定版本不可停用: 'Stable version cannot be disabled', 无法查看完整Agent信息: 'There are unauthorized businesses in this "BK-Net", so the Agent information cannot be viewed completely.', 临时文件目录: 'Temp file path', 供proxy文件分发临时使用后台定期进行清理建议预留至少磁盘空间: 'It is used temporarily for the distribution of proxy files, and is cleaned up regularly in the background. It is recommended to reserve at least 1G disk space', @@ -416,6 +437,7 @@ export default { 动态: 'Dynamic', 静态: 'Static', 接入点互斥: 'The host is on a GSE{0} access point, and the {1} access point is disabled', + pkg的详细信息: 'Details of version {0}', // 安装通道 安装通道: 'Install channel', @@ -629,6 +651,7 @@ export default { 重装PROXY: 'Reinstall Proxy', 重装AGENT: 'Reinstall Agent', 升级PROXY: 'Upgrade Proxy', + 确认Proxy目标版本: 'Confirm Proxy Target Version', 升级Agent: 'Upgrade Agent', 已忽略IP无日志详情: 'Ignored IP no log details', 已忽略信息提示: '{ip} and other {num} IPs have been automatically ignored by the system, and can be viewed by filtering in the execution status', @@ -937,6 +960,8 @@ export default { 操作策略成功: '{0} strategy successfully', 不能低于当前版本: 'Cannot be lower than the current version', 版本未处于正式状态: 'Version is not in official state', + 已是目标版本: 'The current version is already up to date; no upgrade is necessary.', + 当前版本: 'The current version is up to date; no upgrade needed.', 部分目标机器已被部署策略管控无法进行操作如需操作请调整对应的部署策略: 'Some target machines have been controlled by deployment strategy and cannot be operated; if you need to operate, please adjust the corresponding deployment strategy', 停用策略dialogTitle: 'Disable strategy: {0}', 停用策略dialogContentOnly: 'Only disable the strategy, keep the plugin {0}', @@ -1114,6 +1139,47 @@ export default { 在目标主机通过: 'To {1} from the {0} on the target host:', 操作方式: '{0} method', + // agent包管理 + 统一填充: 'Fill in uniformly', + 包上传: 'Package upload', + '默认升级安装版本': 'Default Version', + '通过修改稳定版本标签来指定': 'Default upgrade/install version. Specify by modifying the "stable version" tag', + 快捷筛选: 'Quick filter', + 维度: 'Dimension', + '版本号、操作系统/架构、标签、上传用户、状态': 'Version, operating system/architecture, tags, upload user, status', + 版本号: 'Version number', + '操作系统/架构': 'System/Arch', + 上传用户: 'Upload user', + 上传时间: 'Upload time', + 已部署主机: 'Deployed hosts', + 启用成功: 'Enable success', + 停用成功: 'Disable success', + 确认停用该Agent包: 'Confirm to disable this agent package?', + 确认停用该Agent包Tip1: 'Disable target: {0}', + 确认停用该Agent包Tip2: 'After disabling, agent installation, reinstallation, and upgrade cannot be selected', + 确认删除该Agent包: 'Confirm to delete this agent package?', + 确认删除该Agent包Tip1: 'Delete target: {0}', + 确认删除该Agent包Tip2: 'After deletion, it cannot be recovered. Please operate with caution!', + '存在同名Agent包,是否覆盖上传?': 'An agent package with the same name exists. Do you want to overwrite it?', + '包覆盖包名:': 'Package name: {0}', + '包覆盖MD5:': 'MD5: {0}', + '继续上传,将会覆盖当前平台同名的 Agent 包': 'Continuing the upload will overwrite the agent package with the same name on the current platform.', + 覆盖上传: 'Overwrite upload', + 取消上传: 'Cancel upload', + '上传成功(存在同名 Agent 包,确认覆盖上传)': 'Upload successful (an agent package with the same name exists. Confirm to overwrite it)', + '存在同名 Agent 包': 'An agent package with the same name exists', + '支持 tgz、tar、gz 扩展名格式文件': 'Supports tgz, tar, gz format files', + 结果预览: 'Result preview', + 解析字段不可修改: 'The parsed field cannot be modified', + 包名: 'Package name', + 操作系统架构: 'Operating system architecture', + 包类型: 'Package type', + 标签信息: 'Tag information', + 描述: 'Description', + 解析错误: 'Parsing error', + '从包中解析的描述文本,不可修改': 'Description text parsed from the package, cannot be modified', + 请先上传包文件: 'Please upload the package file first', + // form-check 正常输入内容校验: 'Chinese and English, numbers, hyphens and underscores with a length not exceeding {0}', 字符串长度校验: 'The length cannot be greater than {0} Chinese or {1} English letters', diff --git a/frontend/src/i18n/zh.js b/frontend/src/i18n/zh.js index 6a1b0f2b0..8cb9d3675 100644 --- a/frontend/src/i18n/zh.js +++ b/frontend/src/i18n/zh.js @@ -13,15 +13,17 @@ export const dedicated = { export const nav = { // Agent nav_节点管理: '节点管理', - nav_Agent状态: 'Agent状态', + nav_Agent: 'Agent', + nav_Agent状态: 'Agent 管理', + nav_Agent包管理: 'Agent 包', nav_Excel导入安装: 'Excel 导入安装', nav_重装Agent: '重装 Agent', nav_重载Agent配置: '重载 Agent 配置', nav_卸载Agent: '卸载 Agent', - nav_插件管理: '插件管理', - nav_插件状态: '插件状态', - nav_插件部署: '插件部署', + nav_插件管理: '插件', + nav_插件状态: '插件管理', + nav_插件部署: '部署策略', nav_新建策略: '新建策略', nav_编辑策略: '编辑策略', nav_已有策略: '已有策略', @@ -152,15 +154,25 @@ export default { 内网网卡IP: '内网网卡IP', 外网网卡IP: '外网网卡IP', 登录IP: '登录IP', + Agent包版本: 'Agent 包版本', + Proxy包版本: 'Proxy 包版本', + '选择 Agent 版本': '选择 Agent 版本', + 选择Proxy版本: '选择Proxy版本', 数据IP: '数据IP', 数据IP提示: 'Agent采集数据的IP地址, 默认绑定到内网网卡所在的IP, 如有特殊需求可单独制定', 确定: '确定', 批量编辑: '批量编辑{title}', + 批量编辑Agent: '根据批量编辑所包含的系统范围,当前可选的版本为同时支持{0}', 安装Agent: '安装 Agent', 卸载: '卸载', 正在重装: '正在重装', 正在升级: '正在升级', 复制: '复制', + 已选IP: '已选{0}个IP', + 升级IP: '{0}个升级', + 回退IP: '{0}个回退', + 已是目标版本Ip: '{0}个已是目标版本', + 全部: '全部', 复制IPv4: '复制 IPv4', 复制IPv6: '复制 IPv6', 复制ProxyIP: '复制 Proxy IP', @@ -234,6 +246,7 @@ export default { 可选: '(可选)', 正在运行: '正在运行', 自动选择: '自动选择', + 未分配: '未分配', 服务状态: '服务状态', 监听端口: '监听端口', TCP连接数: 'TCP 连接数', @@ -286,6 +299,7 @@ export default { 自动发现: '自动发现', 管控区域详情: '管控区域详情', 升级: '升级', + 升级回退: '升级/回退', 升级Agent: '升级 Agent', 确定升级选择的主机: '确定升级选择的主机?', 请确认是否批量重启: '请确认是否批量重启', @@ -296,6 +310,8 @@ export default { 卸载lower: '卸载', 升级lower: '升级', 移除lower: '移除', + 确认Agent目标版本: '确认 Agent 目标版本', + Agent版本: 'Agent 版本', 请确认是否操作: '请确认是否{type}?', 请确认是否批量操作: '请确认是否批量{type}?', 单条确认操作提示: '{type} {ip} 的Agent{suffix}', @@ -361,6 +377,12 @@ export default { BT节点探测提示: '开启 BT 传输后可大大提升 Agent通过文件管道进行文件分发的传输效率, 同时相应提高agent安装任务的执行速度。\n\n注:跨 VPC 网络环境可能会占用专线带宽,建议此场景下关闭 BT 传输', 启用: '启用', 停用: '停用', + 启用状态: '启用', + 停用状态: '停用', + 启用操作:'启用', + 停用操作:'停用', + 稳定版本不可删除: '稳定版本不可删除', + 稳定版本不可停用: '稳定版本不可停用', 无法查看完整Agent信息: '该「管控区域」存在无权限业务,故无法完整查看 Agent 信息。', 临时文件目录: '临时文件目录', 供proxy文件分发临时使用后台定期进行清理建议预留至少磁盘空间: '供 proxy 文件分发临时使用,后台定期进行清理。建议预留至少 1G 磁盘空间', @@ -416,6 +438,7 @@ export default { 动态: '动态', 静态: '静态', 接入点互斥: '主机处于 GSE{0}接入点,{1} 的接入点禁用', + pkg的详细信息: '{0} 的详细信息', // 安装通道 安装通道: '安装通道', @@ -462,6 +485,7 @@ export default { 复制成功: '复制成功', 没有需要复制的内容: '没有需要复制的内容', 查看任务详情: '查看任务详情', + 新建标签: '新建 ”{0}“ 标签', // 全局配置 - GSE-环境管理 默认: '默认', @@ -629,6 +653,7 @@ export default { 重装PROXY: '重装 Proxy', 重装AGENT: '重装 Agent', 升级PROXY: '升级 Proxy', + 确认Proxy目标版本: '确认Proxy目标版本', 升级AGENT: '升级 Agent', 已忽略IP无日志详情: '已忽略IP无日志详情', 已忽略信息提示: '{ip} 等 {num} 个 IP 已被系统自动忽略,可在执行状态筛选查看', @@ -937,6 +962,8 @@ export default { 操作策略成功: '{0}策略成功!', 不能低于当前版本: '不能低于当前版本', 版本未处于正式状态: '版本未处于正式状态', + 已是目标版本: '已是目标版本,无需升级', + 当前版本: '当前版本,无需升级', 部分目标机器已被部署策略管控无法进行操作如需操作请调整对应的部署策略: '部分目标机器已被部署略管控,无法进行操作;如需操作请调整对应的部署策略', 停用策略dialogTitle: '停用策略:{0}', 停用策略dialogContentOnly: '仅停用策略,保留插件 {0}', @@ -1114,6 +1141,47 @@ export default { 在目标主机通过: '在目标主机通过 {0} {1}:', 操作方式: '{0}方式', + // agent包管理 + 统一填充: '统一填充', + 包上传: '包上传', + '默认升级安装版本': '默认版本', + '通过修改稳定版本标签来指定': '默认的升级/安装版本,通过修改“稳定版本”标签来指定', + 快捷筛选: '快捷筛选', + 维度: '维度', + '版本号、操作系统/架构、标签、上传用户、状态': '版本号、操作系统/架构、标签、上传用户、状态', + 版本号: '版本号', + '操作系统/架构': '操作系统/架构', + 上传用户: '上传用户', + 上传时间: '上传时间', + 已部署主机: '已部署主机', + 启用成功: '启用成功', + 停用成功: '停用成功', + 确认停用该Agent包: '确认停用该 Agent 包?', + 确认停用该Agent包Tip1: '停用目标:{0}', + 确认停用该Agent包Tip2: '停用后,Agent 安装、重装、升级时,不可选择', + 确认删除该Agent包: '确认删除该 Agent 包?', + 确认删除该Agent包Tip1: '删除目标:{0}', + 确认删除该Agent包Tip2: '删除后不可恢复,请谨慎操作!', + '存在同名Agent包,是否覆盖上传?': '存在同名Agent包,是否覆盖上传?', + '包覆盖包名:': '包名:{0}', + '包覆盖MD5:': 'MD5:{0}', + '继续上传,将会覆盖当前平台同名的 Agent 包': '继续上传,将会覆盖当前平台同名的 Agent 包', + 覆盖上传: '覆盖上传', + 取消上传: '取消上传', + '上传成功(存在同名 Agent 包,确认覆盖上传)': '上传成功(存在同名 Agent 包,确认覆盖上传)', + '存在同名 Agent 包': '存在同名 Agent 包', + '支持 tgz、tar、gz 扩展名格式文件': '支持 tgz、tar、gz 扩展名格式文件', + 结果预览: '结果预览', + 解析字段不可修改: '解析字段不可修改', + 包名: '包名', + 操作系统架构: '操作系统架构', + 包类型: '包类型', + 标签信息: '标签信息', + 描述: '描述', + 解析错误: '解析错误', + '从包中解析的描述文本,不可修改': '从包中解析的描述文本,不可修改', + 请先上传包文件: '请先上传包文件', + // form-check 正常输入内容校验: '长度不超过{0}的中英文、数字、连字符和下划线', 字符串长度校验: '长度不能大于{0}个中文或{1}个英文字母', diff --git a/frontend/src/router/modules/agent-manager.ts b/frontend/src/router/modules/agent-manager.ts index c054cebfb..cea7a0ca6 100644 --- a/frontend/src/router/modules/agent-manager.ts +++ b/frontend/src/router/modules/agent-manager.ts @@ -3,6 +3,7 @@ import { AGENT_VIEW, AGENT_OPERATE } from '../action-map'; const AgentStatus = () => import(/* webpackChunkName: 'AgentStatus' */'@/views/agent/agent-list.vue'); const AgentSetup = () => import(/* webpackChunkName: 'AgentSetup' */'@/views/agent/agent-setup/agent-setup.vue'); const AgentImport = () => import(/* webpackChunkName: 'AgentImport' */'@/views/agent/agent-setup/agent-import.vue'); +const AgentPackage = () => import(/* webpackChunkName: 'AgentPackage' */'@/views/agent/package/index.vue'); export default [ { @@ -70,4 +71,18 @@ export default [ }, }, }, + { + path: '/agent-manager/package', + name: 'agentPackage', + component: AgentPackage, + meta: { + navId: 'nodeManage', + title: 'nav_Agent包管理', + customContent: true, + authority: { + page: AGENT_VIEW, + operate: AGENT_OPERATE, + }, + }, + }, ] as RouteConfig[]; diff --git a/frontend/src/router/navigation-config.ts b/frontend/src/router/navigation-config.ts index cb75bbbe6..2731b6d63 100644 --- a/frontend/src/router/navigation-config.ts +++ b/frontend/src/router/navigation-config.ts @@ -7,7 +7,7 @@ export const navConfig: INavConfig[] = [ defaultActive: 'agentStatus', children: [ { - name: 'nav_Agent状态', + name: 'nav_Agent', children: [ { title: 'nav_Agent状态', @@ -15,6 +15,12 @@ export const navConfig: INavConfig[] = [ path: 'agent-manager/status', name: 'agentStatus', }, + { + title: 'nav_Agent包管理', + icon: 'nc-package-2', + path: '/agent-manager/package', + name: 'agentPackage', + }, ], }, { diff --git a/frontend/src/setup/index.ts b/frontend/src/setup/index.ts index ab3b16a88..78b5089c2 100644 --- a/frontend/src/setup/index.ts +++ b/frontend/src/setup/index.ts @@ -7,7 +7,7 @@ import './filters'; import NmSafety from './safety'; import { textTool } from './text-tool'; import { setIpProp, initIpProp } from './ipv6'; -import './mixins'; +// import './mixins'; Vue.prototype.$filters = function (filterName: string, value: any) { return this._f(filterName)(value); @@ -16,5 +16,11 @@ Vue.prototype.$safety = new NmSafety(); Vue.prototype.$textTool = textTool; Vue.prototype.$setIpProp = setIpProp; Vue.prototype.$initIpProp = initIpProp; +Vue.prototype.emptySearchClear = function () { + this.$emit('empty-clear'); +}; +Vue.prototype.emptyRefresh = function () { + this.$emit('empty-refresh'); +}; export default i18n; diff --git a/frontend/src/store/modules/agent.ts b/frontend/src/store/modules/agent.ts index 54b6fd388..2d7608acc 100644 --- a/frontend/src/store/modules/agent.ts +++ b/frontend/src/store/modules/agent.ts @@ -6,11 +6,17 @@ import { listAp } from '@/api/modules/ap'; import { listCloud } from '@/api/modules/cloud'; import { getFilterCondition } from '@/api/modules/meta'; import { fetchPwd } from '@/api/modules/tjj'; +import { + createAgentRegisterTask, createAgentTags, versionCompare, deletePackage, + getDeployedHostsCount, getTags, getVersion, listPackageNew, parsePackage, + queryAgentRegisterTask, quickSearchCondition, updatePackage, +} from '@/api/modules/pkg_manage'; import { sort } from '@/common/util'; -import { ISearchChild, ISearchItem } from '@/types'; +import { Mixin, ISearchChild, ISearchItem } from '@/types'; import { IAgentSearch, IAgentSearchIp, IAgentJob, IAgentHost } from '@/types/agent/agent-type'; import { IAp } from '@/types/config/config'; import { IChannel, ICloudSource } from '@/types/cloud/cloud'; +import { IPkgDelpyNumber, IPkgDimension, IPkgInfo, IPkgTagList, IPkgParseInfo, PkgType, IPkgVersion, IPkgParams } from '@/types/agent/pkg-manage'; export const SET_AP_LIST = 'setApList'; export const SET_CLOUD_LIST = 'setCloudList'; @@ -64,7 +70,7 @@ export default class AgentStore extends VuexModule { ...extraOther, ...item, status: item.status ? item.status.toLowerCase() : 'unknown', - version: item.version ? item.version : '--', + version: item.version ? item.version : '', job_result: item.job_result ? item.job_result : {} as any, topology: item.topology && item.topology.length ? item.topology : [], bt_speed_limit: btSpeedLimit || '', @@ -237,4 +243,92 @@ export default class AgentStore extends VuexModule { } this[UPDATE_AP_URL](apUrl); } + + // agent包管理 + @Action + public apiPkgList(param: IPkgParams): Promise<{ total: number; list: IPkgInfo[] }> { + return listPackageNew(`?page=${param.page}&pagesize=${param.pagesize}`,param).catch(() => ({})); + } + @Action + public apiPkgFilterCondtion(param: { category: 'agent_pkg_manage'; project?: PkgType }): Promise { + return getFilterCondition(param).catch(() => []); + } + @Action + public apiPkgQuickSearch(param): Promise { + return quickSearchCondition(param).catch(() => []); + } + @Action + public apiPkgUpdateStatus({ id, is_ready, tags }: { id: string|number; is_ready: boolean; tags?: any[] }): Promise { + return updatePackage(id, { is_ready, tags }).catch(() => {}); + } + @Action + public apiPkgDelete(id: number): Promise { + return deletePackage(`${id}`).catch(() => false); + } + @Action + public async apiPkgParse(param: { file_name: string }): Promise<{ + packages: IPkgParseInfo[]; + description: string; + } | string> { + try { + const result = await parsePackage(param); + return result; + } catch (error: any) { + // 假设 error 对象包含 message 属性 + const errorMessage = error.message || 'An error occurred'; + return errorMessage; + } + } + @Action + public apiPkgRegister(param: { + project: PkgType; + file_name: string; + tag_descriptions: string[]; + }): Promise<{ task_id: string }> { + return createAgentRegisterTask(param).catch(() => false); + } + /** + * 查询注册任务状态 + */ + @Action + public apiPkgRegisterQuery(param: { task_id: string, version?: string}): Promise<{ status: 'PENDING'|'SUCCESS'|'FAILURE'; task_id: string }> { + return queryAgentRegisterTask(param).catch(() => ({ status: 'FAILURE' })); + } + @Action + public apiPkgHostsCount(param: { + project: PkgType, + items: IPkgDelpyNumber[] + }): Promise[]> { + return getDeployedHostsCount(param).catch(() => []); + } + @Action + public apiPkgGetTags(param: { project: PkgType; tag_description?: string; }): Promise { + return getTags(param).catch(() => []); + } + @Action + public apiPkgCreateTags(param: { + project: PkgType; + tag_descriptions: string[]; + }): Promise { + return createAgentTags(param).catch(() => false); + } + + @Action + public apiGetPkgVersion(param: { project: PkgType; os: string; cpu_arch: string; versions?: string[] }): Promise<{ + default_version: string; + machine_latest_version: string; + package_latest_version: string; + is_visible: Boolean; + pkg_info: IPkgVersion[]; + }> { + return getVersion(param).catch(() => ({ default_version: '', machine_latest_version: '', pkg_info: [], is_visible: false, package_latest_version: '' })); + } + + @Action + public apiVersionCompare(param: { + current_version: string; + version_to_compares: string[]; + }): Promise<{upgrade_count: 0,downgrade_count: 0, no_change_count: 0}> { + return versionCompare(param).catch(() => false); + } } diff --git a/frontend/src/store/modules/cloud.ts b/frontend/src/store/modules/cloud.ts index fda128c89..148cf05c6 100644 --- a/frontend/src/store/modules/cloud.ts +++ b/frontend/src/store/modules/cloud.ts @@ -181,7 +181,7 @@ export default class CloudStore extends VuexModule { * @param {*} params */ @Action - public async operateJob(params: { 'job_type': string, 'bk_host_id': number[] }): Promise<{ 'job_id'?: number }> { + public async operateJob(params: { 'job_type': string, 'bk_host_id': number[], agent_setup_info?: {} }): Promise<{ 'job_id'?: number }> { const data = await operateJob(params).catch(() => false); return data; } diff --git a/frontend/src/store/modules/main.ts b/frontend/src/store/modules/main.ts index 3f7848fd7..700a91c60 100644 --- a/frontend/src/store/modules/main.ts +++ b/frontend/src/store/modules/main.ts @@ -3,7 +3,7 @@ import Vue from 'vue'; import http from '@/api'; import navList from '@/router/navigation-config'; import { retrieveBiz, fetchTopo } from '@/api/modules/cmdb'; -import { retrieveGlobalSettings, getFilterCondition } from '@/api/modules/meta'; +import { retrieveGlobalSettings, getFilterCondition, getAgentPackageUI } from '@/api/modules/meta'; import { fetchPublicKeys } from '@/api/modules/rsa'; import { fetchPermission, @@ -72,6 +72,16 @@ export default class Main extends VuexModule { public noticeShow = false; public AUTO_SELECT_INSTALL_CHANNEL = -1; + // agent_package 显示开关 + public ENABLE_AGENT_PACKAGE_UI = false; + /** + * 更新状态 + * + */ + @Mutation + public setAgentPackageUI(switchStatus: boolean) { + this.ENABLE_AGENT_PACKAGE_UI = switchStatus; + } /** * 设置全局可视区域的 loading 是否显示 * @@ -439,4 +449,12 @@ export default class Main extends VuexModule { const dataValue = data.AUTO_SELECT_INSTALL_CHANNEL_ONLY_DIRECT_AREA === undefined ? -1 : Number(data.AUTO_SELECT_INSTALL_CHANNEL_ONLY_DIRECT_AREA); this.setAutoJudge(dataValue); } + /** + * agent_package 相关的显示开关配置 + */ + @Action + public async getAgentPackageUI() { + const { ENABLE_AGENT_PACKAGE_UI = false } = await retrieveGlobalSettings({ key: 'ENABLE_AGENT_PACKAGE_UI' }).catch(() => ({})); + this.setAgentPackageUI(ENABLE_AGENT_PACKAGE_UI); + } } diff --git a/frontend/src/types/agent/agent-type.ts b/frontend/src/types/agent/agent-type.ts index 5fe6b56c5..aca92ab00 100644 --- a/frontend/src/types/agent/agent-type.ts +++ b/frontend/src/types/agent/agent-type.ts @@ -24,6 +24,7 @@ export interface IAgentHost { bk_biz_id: number bk_biz_name: string bk_cloud_id: number + is_unassigned?: boolean bk_cloud_name: string bk_host_name: string is_manual: boolean @@ -85,6 +86,7 @@ export interface IAgentJob { is_proxy?: boolean hosts?: IAgentHost[] exclude_hosts?: IAgentHost[] + bk_host_id?: number[] } export interface IOperateItem { diff --git a/frontend/src/types/agent/pkg-manage.ts b/frontend/src/types/agent/pkg-manage.ts new file mode 100644 index 000000000..cc07057c8 --- /dev/null +++ b/frontend/src/types/agent/pkg-manage.ts @@ -0,0 +1,102 @@ +import { TranslateResult } from 'vue-i18n'; + +// import { TranslateResult } from 'vue-i18n' +export type PkgType = 'gse_agent' | 'gse_proxy'; +export type PkgTagType = 'builtin' | 'custom'; + +// tag相关的参数 都是使用原本的name字段, 原本的description用于展示 +export interface IPkgTag { + // 标签id名 + tag_name?: string, + id?: number; + name: string; + description: string; + // 附加的 + className?: string +} +export interface IPkgTagOpt extends IPkgTag { + id: string; + description?: string; +}; + +export interface IPkgTagList { + name: PkgTagType; + description: string; + children: IPkgTag[]; +} + +export interface IPkgParams { + project: PkgType; + page: number; + pagesize: number; + tags?: string; // ,分隔 + os?: string; // 操作系统 + cpu_arch?: string; // 架构 + created_by?: string; // ,分隔 + is_ready?: string; // true,false + version?: string; // ,分隔 + created_time_before?: string; // 2022-10-01T00:00:00 + created_time_after?: string; + ordering?: string; + condition?: any[]; +} + +export interface IPkgQuickOpt { + id: string; + name: string | TranslateResult; + count: number; + icon?: string; + tips?: boolean; + isAll?: boolean; +} + +export interface IPkgDimension { + id: string; + name: string; + children: IPkgQuickOpt[] +} + + +export interface IPkgInfo { + id: number; + pkg_name: string; // 包名称 + version: string; // 版本号 + os: string; // 操作系统 + cpu_arch: string; // 架构 + created_by: string; // 上传用户 + created_time: string; // 上传时间 + is_ready: boolean; // 状态 + tags: IPkgTagList[]; +} + +export interface IPkgRow extends IPkgInfo { + hostNumber: string | number; + formatTags: IPkgTag[]; +} + +export interface IPkgDelpyNumber { + count?: number; + cpu_arch: string; + os_type: string; + version: string; +} + +export interface IPkgParseInfo { + project: PkgType; + os: string; + cpu_arch: string; + pkg_name: string; + version: string; + config_templates: any[]; +} + +export interface IPkgVersion { + project: PkgType; + version: string; + packages: { + pkg_name: string; + tags: IPkgTag[]; + }[]; + tags: IPkgTag[]; + description: string; +} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 1ab95f957..cf44c253a 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -2,6 +2,8 @@ import RequestQueue from '@/api/request-queue'; import CachedPromise from '@/api/cached-promise'; import { CancelToken, Canceler } from 'axios'; +export type Mixin = T & X; + export type IKeysMatch = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T]; // 根据value类型找key export type IAgentStatus = 'running' | 'terminated' | 'not_installed'; // 正常 异常 未安装 @@ -90,9 +92,14 @@ export interface INavConfig { currentActive?: string // 当前二级导航选中项 defaultActive?: string // 当前二级导航初始化选中项 disabled?: boolean // 是否禁用一级导航 - children?: ISubNavConfig[] // 二级导航配置 + children?: ISideMenuConfig[] // 二级导航配置 } +// 侧边栏菜单配置 +export interface ISideMenuConfig { + name: string // 侧边栏菜单分类名称 + children?: ISubNavConfig[] // 子菜单配置 +} export interface ISubNavConfig { title: string // 二级导航标题 icon: string // 二级导航icon @@ -151,7 +158,7 @@ export interface IIsp { export interface ISearchChild { id: string name: string - checked: boolean + checked?: boolean } // searchselect item @@ -227,6 +234,7 @@ export interface ISetupValidator { // 集合了agent 安装&导入、proxy安装 export interface ISetupRow { id: number + is_unassigned?: boolean is_manual?: boolean inner_ip?: string outer_ip?: string @@ -256,6 +264,8 @@ export interface ISetupRow { install_channel_id: string | number | null bk_addressing: 'static' | 'dynamic' gse_version?: 'V1'|'V2' // 前端添加 用于操作主机仅能选择对应版本的接入点 + bk_host_id?: number; + version?: string; } export interface ISetupParent { @@ -306,6 +316,7 @@ export interface ISetupHead { getDefaultValue?: Function handleValueChange?: Function handleBlur?: Function + extraInfo?: any } // 文件导入配置 diff --git a/frontend/src/views/agent/agent-list.vue b/frontend/src/views/agent/agent-list.vue index f8a159717..92c3b076f 100644 --- a/frontend/src/views/agent/agent-list.vue +++ b/frontend/src/views/agent/agent-list.vue @@ -351,6 +351,9 @@ :min-width="columnMinWidth['agent_version']" :render-header="renderFilterHeader" v-if="filter['agent_version'].mockChecked"> + + + @@ -618,12 +631,16 @@ import { debounce, getFilterChildBySelected, searchSelectPaste } from '@/common/ import { bus } from '@/common/bus'; import { STORAGE_KEY_COL } from '@/config/storage-key'; import { getDefaultConfig, DHCP_FILTER_KEYS } from '@/config/config'; +import ChoosePkgDialog from './components/choose-pkg-dialog.vue'; + +type VerionType = 'unified' | 'by_system_arch'; @Component({ name: 'agent-list', components: { BkFooter, CopyDropdown, + ChoosePkgDialog, }, }) export default class AgentList extends Mixins(pollMixin, TableHeaderMixins, authorityMixin()) { @@ -652,6 +669,15 @@ export default class AgentList extends Mixins(pollMixin, TableHeaderMixins, auth limitList: [50, 100, 200], }, }; + public versionsDialog = { + show: false, + type: 'unified', + title: '', + versions: [] as string[], + os_type: '', + cpu_arch: '', + operate: 'UPGRADE_AGENT', + }; private sortData: ISortData = { head: '', sort_type: '', @@ -837,15 +863,16 @@ export default class AgentList extends Mixins(pollMixin, TableHeaderMixins, auth }; // 批量操作 private operate: IOperateItem[] = [ - { - id: 'reinstall', - name: window.i18n.t('安装重装'), - disabled: false, - show: false, - }, + // 勾选数据后点击安装Agent也是进入安装重装,此处重复 + // { + // id: 'reinstall', + // name: window.i18n.t('安装重装'), + // disabled: false, + // show: false, + // }, { id: 'upgrade', - name: window.i18n.t('升级'), + name: window.i18n.t('升级回退'), disabled: false, show: true, }, @@ -934,6 +961,35 @@ export default class AgentList extends Mixins(pollMixin, TableHeaderMixins, auth .sort((a, b) => b - a); return ipv6SortRows.length ? Math.ceil(ipv6SortRows[0] * 6.9) : 90; } + + // 升级Agent版本 + public async updateAgentVersion(info: { version: string }) { + const data = this.isSelectedAllPages ? this.markDeleteArr : this.selection; + this.loading = true; + const params = this.getOperateHostCondition(data, 'UPGRADE_AGENT') as IAgentJob; + const versionList: { bk_host_id: number; version: string; }[] = []; + data.forEach(item => { + if (item.version !== info.version) { + versionList.push({ + bk_host_id: item.bk_host_id as number, + version: info.version as string, + }); + } + }); + params.bk_host_id = versionList.map(item => item.bk_host_id); + Object.assign(params, { + agent_setup_info: { + choice_version_type: 'by_host', + version_map_list: versionList, + } + }); + const result = await AgentStore.operateJob(params); + this.loading = false; + if (result.job_id) { + this.$router.push({ name: 'taskDetail', params: { taskId: result.job_id, routerBackName: 'taskList' } }); + } + } + // 可操作的数据 private get datasheets() { return this.table.data.filter(item => item.job_result.status !== 'RUNNING' && item.operate_permission); @@ -1112,8 +1168,9 @@ export default class AgentList extends Mixins(pollMixin, TableHeaderMixins, auth private initRouterQuery() { // this.search.biz = this.bk_biz_id.length ? [...this.bk_biz_id] : this.selectedBiz; const searchParams: ISearchItem[] = []; - const { cloud } = this.$route.params; + const { cloud, os_type, version } = this.$route.params; this.getFilterCondition().then((data) => { + this.filterData = data; if (cloud) { searchParams.push({ name: this.filter.bk_cloud_id.name, @@ -1147,6 +1204,26 @@ export default class AgentList extends Mixins(pollMixin, TableHeaderMixins, auth }); } } + if (os_type) { + const child = data.find(item => item.id === 'os_type'); + if (child) { + searchParams.push({ + id: child.id, + name: child.name, + values: [{ checked: true, id: os_type.toUpperCase(), name: os_type }], + }); + } + } + if (version) { + const child = data.find(item => item.id === 'version'); + if (child) { + searchParams.push({ + id: child.id, + name: child.name, + values: [{ checked: true, id: version, name: version }], + }); + } + } if (searchParams.length) { this.searchSelectValue.push(...searchParams); this.searchInputKey += 1; @@ -1761,11 +1838,12 @@ export default class AgentList extends Mixins(pollMixin, TableHeaderMixins, auth }, }); } + /** * 重启Host * @param {Array} data */ - private handleOperatetHost(data: IAgentHost[], batch: boolean, operateType: string) { + private async handleOperatetHost(data: IAgentHost[], batch: boolean, operateType: string) { const titleObj = { firstIp: batch ? this.selection[0].inner_ip : data[0].inner_ip, num: batch ? this.selectionCount : data.length, @@ -1793,6 +1871,16 @@ export default class AgentList extends Mixins(pollMixin, TableHeaderMixins, auth type = window.i18n.t('移除lower'); break; } + + if (operateType === 'UPGRADE_AGENT') { + const versions:string[] = data.filter(item => !!item.version).map(item => item.version) || []; + this.versionsDialog.show = true; + this.versionsDialog.versions = versions; + this.versionsDialog.title = `${this.$t('确认Agent目标版本')}`; + this.versionsDialog.type = 'unified'; + this.versionsDialog.operate = 'UPGRADE_AGENT'; + return; + } this.$bkInfo({ title: batch ? this.$t('请确认是否批量操作', { type }) diff --git a/frontend/src/views/agent/agent-setup/agent-import.vue b/frontend/src/views/agent/agent-setup/agent-import.vue index a9432ccb3..2a70314a7 100644 --- a/frontend/src/views/agent/agent-setup/agent-import.vue +++ b/frontend/src/views/agent/agent-setup/agent-import.vue @@ -34,6 +34,7 @@ + @delete="handleItemDelete" + @choose="handleChoose" + @change="handleValueChange"> @@ -89,6 +92,16 @@ + + diff --git a/frontend/src/views/agent/components/create-excel.ts b/frontend/src/views/agent/components/create-excel.ts index ca7c7a3c3..f26b5d2aa 100644 --- a/frontend/src/views/agent/components/create-excel.ts +++ b/frontend/src/views/agent/components/create-excel.ts @@ -20,6 +20,7 @@ const config: IHead[] = [ { name: window.i18n.t('密码/密钥'), prop: 'prove' }, { name: window.i18n.t('外网IP'), prop: 'outer_ip', colOptional: true, optional: true, width: 150 }, { name: window.i18n.t('登录IP'), prop: 'login_ip', optional: true, width: 150 }, + { name: window.i18n.t('Agent包版本'), prop: 'version' }, { name: window.i18n.t('业务'), prop: 'bk_biz_id', optional: true }, { name: window.i18n.t('管控区域'), prop: 'bk_cloud_id', optional: true }, { name: window.i18n.t('接入点'), prop: 'ap_id', optional: true }, diff --git a/frontend/src/views/agent/components/setup-pkg-table.vue b/frontend/src/views/agent/components/setup-pkg-table.vue new file mode 100644 index 000000000..997a69c0f --- /dev/null +++ b/frontend/src/views/agent/components/setup-pkg-table.vue @@ -0,0 +1,204 @@ + + + + + diff --git a/frontend/src/views/agent/config/editTableConfig.ts b/frontend/src/views/agent/config/editTableConfig.ts index 6eb72a0b3..6c7124fcf 100644 --- a/frontend/src/views/agent/config/editTableConfig.ts +++ b/frontend/src/views/agent/config/editTableConfig.ts @@ -22,21 +22,44 @@ export const config: ISetupHead[] = [ prop: 'bk_cloud_id', type: 'select', required: true, + // 管控区域可以批量编辑 + batch: true, popoverMinWidth: 160, noRequiredMark: false, parentProp: 'cloud_attr', placeholder: window.i18n.t('请选择'), manualProp: true, + // 管控区域校验,是未分配时提示必填 + rules: [ + { + trigger: 'blur', + message: window.i18n.t('请选择管控区域'), + validator(v: number, id: number) { + if (typeof v === 'undefined' || v === null || v === -1) return false; + const row = this.table.data.find(item => item.id === id); + if (!row) return; + return row.bk_cloud_id !== -1; + }, + }, + ], getOptions() { - return this.cloudList.map((item: ICloudSource) => ({ + const options = [{ + name: window.i18n.t('未分配'), + id: -1, + disabled: true, + }]; + return options.concat(this.cloudList.map((item: ICloudSource) => ({ name: item.bk_cloud_name, id: item.bk_cloud_id, - })); + }))); }, getProxyStatus(row: ISetupRow) { return row.proxyStatus; }, - readonly: true, + // 未分配允许修改 + getReadonly(row: ISetupRow) { + return !row.is_unassigned; + }, }, { label: '安装通道', @@ -270,6 +293,23 @@ export const config: ISetupHead[] = [ manualProp: true, rules: [reguIPMixins], }, + { + label: 'Agent包版本', + prop: 'version', + type: 'choose', + required: true, + readonly: false, + noRequiredMark: false, + placeholder: window.i18n.t('请选择'), + batch: true, + default: '', + width: 100, + parentProp: 'install_info', + getReadonly(row: ISetupRow) { + return this.type === 'UNINSTALL_AGENT' || row.version === 'stable'; + }, + // rules: [reguPort], + }, // { // label: 'BT节点探测', // prop: 'peer_exchange_switch_for_agent', diff --git a/frontend/src/views/agent/config/importTableConfig.js b/frontend/src/views/agent/config/importTableConfig.js index d13d29422..3fd1c40f6 100644 --- a/frontend/src/views/agent/config/importTableConfig.js +++ b/frontend/src/views/agent/config/importTableConfig.js @@ -7,6 +7,7 @@ export const parentHead = [ { label: '主机IPTip', prop: 'host_ip', type: 'text', colspan: 0, required: true, tips: 'agentSetupHostIp' }, { label: '主机属性', prop: 'host_attr', type: 'text', colspan: 0 }, { label: '登录信息', prop: 'login_info', type: 'text', tips: 'agentSetupLoginInfo', colspan: 0 }, + { label: '安装信息', prop: 'install_info', type: 'text', colspan: 0 }, { label: '传输信息', prop: 'trans_info', type: 'text', colspan: 0 }, { label: '', prop: '', type: 'operate' }, ]; @@ -297,6 +298,18 @@ const config = [ }, ], }, + { + label: 'Agent包版本', + prop: 'version', + type: 'choose', + required: true, + noRequiredMark: false, + placeholder: window.i18n.t('请选择'), + batch: false, + default: '', + width: 100, + parentProp: 'install_info', + }, // { // label: 'BT节点探测', // prop: 'peer_exchange_switch_for_agent', diff --git a/frontend/src/views/agent/package/PkgThead.vue b/frontend/src/views/agent/package/PkgThead.vue new file mode 100644 index 000000000..6bac1ec4d --- /dev/null +++ b/frontend/src/views/agent/package/PkgThead.vue @@ -0,0 +1,345 @@ + + + diff --git a/frontend/src/views/agent/package/index.vue b/frontend/src/views/agent/package/index.vue new file mode 100644 index 000000000..5e796f678 --- /dev/null +++ b/frontend/src/views/agent/package/index.vue @@ -0,0 +1,728 @@ + + + diff --git a/frontend/src/views/agent/package/package-cols.vue b/frontend/src/views/agent/package/package-cols.vue new file mode 100644 index 000000000..1e80218a0 --- /dev/null +++ b/frontend/src/views/agent/package/package-cols.vue @@ -0,0 +1,401 @@ + + + diff --git a/frontend/src/views/agent/package/package-upload.vue b/frontend/src/views/agent/package/package-upload.vue new file mode 100644 index 000000000..080731cf6 --- /dev/null +++ b/frontend/src/views/agent/package/package-upload.vue @@ -0,0 +1,479 @@ + + + diff --git a/frontend/src/views/cloud/cloud-manager-add/cloud-manager-setup.vue b/frontend/src/views/cloud/cloud-manager-add/cloud-manager-setup.vue index 57af406e0..2909b1eab 100644 --- a/frontend/src/views/cloud/cloud-manager-add/cloud-manager-setup.vue +++ b/frontend/src/views/cloud/cloud-manager-add/cloud-manager-setup.vue @@ -39,7 +39,8 @@ :setup-info="formData.bkCloudSetupInfo" :key="net.active" :before-delete="handleBeforeDeleteRow" - @change="handleSetupTableChange"> + @change="handleSetupTableChange" + @choose="handleChoose"> @@ -99,11 +100,22 @@ + + diff --git a/frontend/src/views/cloud/components/sideslider-content.vue b/frontend/src/views/cloud/components/sideslider-content.vue index ccf99ff05..e8c25799b 100644 --- a/frontend/src/views/cloud/components/sideslider-content.vue +++ b/frontend/src/views/cloud/components/sideslider-content.vue @@ -91,7 +91,9 @@ export default class SidesliderCcontent extends Vue { @Watch('basic', { immediate: true }) public async handlebasicChange(data: Dictionary) { const ipKeys: IProxyIpKeys[] = ['inner_ip', 'outer_ip', 'login_ip']; - const basicInfo = detailConfig.map((config: Dictionary) => { + // 设置判断当前接入点为v2版本的布尔值 + const isApV2 = this.apList?.find(item => item.id === data.ap_id)?.gse_version === 'V2' + let basicInfo = detailConfig.map((config: Dictionary) => { if (config.prop === 'auth_type') { config.authType = data.auth_type || 'PASSWORD'; config.value = config.authType === 'TJJ_PASSWORD' ? this.$t('自动拉取') : ''; @@ -102,6 +104,10 @@ export default class SidesliderCcontent extends Vue { } return JSON.parse(JSON.stringify(config)); }); + // 接入点不为v2时,不显示版本信息 + if (!isApV2) { + basicInfo = basicInfo.filter(item => item.prop !== 'version'); + }; this.basicInfo.splice(0, this.basicInfo.length, ...basicInfo); } diff --git a/frontend/src/views/cloud/config/netTableConfig.ts b/frontend/src/views/cloud/config/netTableConfig.ts index c2fcf1853..d296bbb25 100644 --- a/frontend/src/views/cloud/config/netTableConfig.ts +++ b/frontend/src/views/cloud/config/netTableConfig.ts @@ -2,12 +2,14 @@ import { authentication, DHCP_FILTER_KEYS, getDefaultConfig } from '@/config/con import { ISetupHead, ISetupRow } from '@/types'; import { osDirReplace, reguFnMinInteger, reguFnSysPath, reguIp, reguIPMixins, reguIPv6 } from '@/common/form-check'; import { splitCodeArr } from '@/common/regexp'; +import { MainStore } from '@/store'; export const parentHead = [ { label: '主机IPTip', prop: 'host_ip', type: 'text', colspan: 0, required: true, tips: 'proxySetupHostIp' }, { label: '主机属性', prop: 'host_attr', type: 'text', colspan: 0 }, { label: '登录信息', prop: 'login_info', type: 'text', tips: 'proxySetupLoginInfo', colspan: 0 }, { label: '传输信息', prop: 'trans_info', type: 'text', colspan: 0 }, + { label: '安装信息', prop: 'install_info', type: 'text', colspan: 0 }, { label: '', prop: '', type: 'operate' }, ]; @@ -213,6 +215,22 @@ const config: ISetupHead[] = [ manualProp: true, parentProp: 'trans_info', }, + { + label: 'Proxy包版本', + prop: 'version', + type: 'choose', + required: true, + noRequiredMark: false, + placeholder: window.i18n.t('请选择'), + batch: true, + default: '', + width: MainStore.language === 'en' ? 170 : 120, + manualProp: true, + parentProp: 'install_info', + extraInfo: { + pkgType: 'gse_proxy', + } + }, { label: '', prop: '', @@ -233,6 +251,7 @@ export const setupDiffConfigs = { bt_speed_limit: { width: 160, }, + operate: { local: true, }, diff --git a/frontend/src/views/cloud/config/proxy-detail-config.js b/frontend/src/views/cloud/config/proxy-detail-config.js index d2c2526e8..8781b56bf 100644 --- a/frontend/src/views/cloud/config/proxy-detail-config.js +++ b/frontend/src/views/cloud/config/proxy-detail-config.js @@ -75,6 +75,13 @@ const config = [ type: 'tag-switch', readonly: true, }, + // v2版本展示agent版本信息 + { + prop: 'version', + label: window.i18n.t('agent版本'), + type: 'text', + readonly: true, + }, ]; export const detailConfig = $DHCP ? config diff --git a/frontend/src/views/plugin/plugin-list/plugin-list-table.vue b/frontend/src/views/plugin/plugin-list/plugin-list-table.vue index 2544d29c8..eeae9b73b 100644 --- a/frontend/src/views/plugin/plugin-list/plugin-list-table.vue +++ b/frontend/src/views/plugin/plugin-list/plugin-list-table.vue @@ -183,7 +183,7 @@ slot="empty" :delay="tableLoading" :type="tableEmptyType" - @empty-clear="emptySearchClear" + @empty-clear="emptySearchClear()" @empty-refresh="emptyRefresh" /> diff --git a/frontend/src/views/plugin/plugin-rule/plugin-rule-list/plugin-rule-table.vue b/frontend/src/views/plugin/plugin-rule/plugin-rule-list/plugin-rule-table.vue index 26aadd654..e41a9bdb4 100644 --- a/frontend/src/views/plugin/plugin-rule/plugin-rule-list/plugin-rule-table.vue +++ b/frontend/src/views/plugin/plugin-rule/plugin-rule-list/plugin-rule-table.vue @@ -104,7 +104,7 @@ -- @@ -241,7 +241,7 @@ slot="empty" :type="tableEmptyType" :delay="tableLoading" - @empty-clear="emptySearchClear" + @empty-clear="emptySearchClear()" @empty-refresh="emptyRefresh" /> diff --git a/frontend/src/views/task/task-detail-table.vue b/frontend/src/views/task/task-detail-table.vue index 9d147c29f..d8c025abf 100644 --- a/frontend/src/views/task/task-detail-table.vue +++ b/frontend/src/views/task/task-detail-table.vue @@ -141,7 +141,7 @@ slot="empty" :type="tableEmptyType" :delay="loading" - @empty-clear="emptySearchClear" + @empty-clear="emptySearchClear()" @empty-refresh="emptyRefresh" /> diff --git a/frontend/src/vue-proto.d.ts b/frontend/src/vue-proto.d.ts index 7603508f2..782c2bfab 100644 --- a/frontend/src/vue-proto.d.ts +++ b/frontend/src/vue-proto.d.ts @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { VNode } from 'vue'; import { TranslateResult } from 'vue-i18n'; import 'vue-router'; import { INodemanHttp } from './types'; @@ -24,6 +24,7 @@ declare module 'vue/types/vue' { messageInfo: (message: string, delay?: number) => {} $bkInfo: (options: { title: string | TranslateResult + subHeader?: string | TranslateResult | VNode subTitle?: string | TranslateResult width?: number | string type?: string, @@ -41,8 +42,11 @@ declare module 'vue/types/vue' { } $initIpProp: (obj: Dictionary, keys: string[]) => void $setIpProp: (key: string, val: Dictionary) => any - $DHCP: boolean + $DHCP: boolean $http: INodemanHttp + emptySearchClear: () => void + emptyRefresh: () => void + filterEmpty: (any) => string } } diff --git a/support-files/bkpkg/bk_nodeman.yaml b/support-files/bkpkg/bk_nodeman.yaml index 88e548ffd..53a151638 100644 --- a/support-files/bkpkg/bk_nodeman.yaml +++ b/support-files/bkpkg/bk_nodeman.yaml @@ -7,4 +7,4 @@ relations: - rationale: 节点管理API requires: - bknodeman=__SAME_VERSION__ -bkimports: \ No newline at end of file +bkimports: diff --git a/support-files/bkpkg/bknodeman.yaml b/support-files/bkpkg/bknodeman.yaml index ef8ecc043..a16093487 100644 --- a/support-files/bkpkg/bknodeman.yaml +++ b/support-files/bkpkg/bknodeman.yaml @@ -1,4 +1,3 @@ - bkpkg: v1 name: bknodeman type: file+tar From 3f2b0dfb72e444c4596144e086296f8c7cd9fc29 Mon Sep 17 00:00:00 2001 From: hyunfa <1598047833@qq.com> Date: Mon, 11 Nov 2024 20:29:18 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:Agent=E5=8C=85=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=BC=80=E5=8F=91--story=3D119532566=20Agent=E5=8C=85=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20#=20Reviewed,=20transaction=20id:=2023517?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit